patch 8.1.0579: cannot attach properties to text

Problem:    Cannot attach properties to text.
Solution:   First part of adding text properties.
This commit is contained in:
Bram Moolenaar
2018-12-13 22:20:09 +01:00
parent 5c5697f298
commit 98aefe7c32
26 changed files with 1747 additions and 46 deletions

View File

@@ -91,6 +91,7 @@ SRC_ALL = \
src/terminal.c \ src/terminal.c \
src/term.h \ src/term.h \
src/termlib.c \ src/termlib.c \
src/textprop.c \
src/ui.c \ src/ui.c \
src/undo.c \ src/undo.c \
src/userfunc.c \ src/userfunc.c \
@@ -198,6 +199,7 @@ SRC_ALL = \
src/proto/term.pro \ src/proto/term.pro \
src/proto/terminal.pro \ src/proto/terminal.pro \
src/proto/termlib.pro \ src/proto/termlib.pro \
src/proto/textprop.pro \
src/proto/ui.pro \ src/proto/ui.pro \
src/proto/undo.pro \ src/proto/undo.pro \
src/proto/userfunc.pro \ src/proto/userfunc.pro \

View File

@@ -102,6 +102,7 @@ DOCS = \
tagsrch.txt \ tagsrch.txt \
term.txt \ term.txt \
terminal.txt \ terminal.txt \
textprop.txt \
tips.txt \ tips.txt \
todo.txt \ todo.txt \
uganda.txt \ uganda.txt \
@@ -238,6 +239,7 @@ HTMLS = \
tagsrch.html \ tagsrch.html \
term.html \ term.html \
terminal.html \ terminal.html \
textprop.html \
tips.html \ tips.html \
todo.html \ todo.html \
uganda.html \ uganda.html \

View File

@@ -1,4 +1,4 @@
*eval.txt* For Vim version 8.1. Last change: 2018 Dec 09 *eval.txt* For Vim version 8.1. Last change: 2018 Dec 13
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@@ -1104,7 +1104,7 @@ Or, if you don't want to write them in as floating-point literals, you can
also use functions, like the following: > also use functions, like the following: >
:let pi = acos(-1.0) :let pi = acos(-1.0)
:let e = exp(1.0) :let e = exp(1.0)
<
*floating-point-precision* *floating-point-precision*
The precision and range of floating points numbers depends on what "double" The precision and range of floating points numbers depends on what "double"
means in the library Vim was compiled with. There is no way to change this at means in the library Vim was compiled with. There is no way to change this at
@@ -2317,6 +2317,22 @@ printf({fmt}, {expr1}...) String format text
prompt_setcallback({buf}, {expr}) none set prompt callback function prompt_setcallback({buf}, {expr}) none set prompt callback function
prompt_setinterrupt({buf}, {text}) none set prompt interrupt function prompt_setinterrupt({buf}, {text}) none set prompt interrupt function
prompt_setprompt({buf}, {text}) none set prompt text prompt_setprompt({buf}, {text}) none set prompt text
prop_add({lnum}, {col}, {props}) none add a text property
prop_clear({lnum} [, {lnum_end} [, {bufnr}]])
none remove all text properties
prop_find({props} [, {direction}])
Dict search for a text property
prop_list({lnum} [, {props}) List text properties in {lnum}
prop_remove({props} [, {lnum} [, {lnum_end}]])
Number remove a text property
prop_type_add({name}, {props}) none define a new property type
prop_type_change({name}, {props})
none change an existing property type
prop_type_delete({name} [, {props}])
none delete a property type
prop_type_get([{name} [, {props}])
Dict get property type values
prop_type_list([{props}]) List get list of property types
pumvisible() Number whether popup menu is visible pumvisible() Number whether popup menu is visible
pyeval({expr}) any evaluate |Python| expression pyeval({expr}) any evaluate |Python| expression
py3eval({expr}) any evaluate |python3| expression py3eval({expr}) any evaluate |python3| expression
@@ -3142,8 +3158,9 @@ ch_logfile({fname} [, {mode}]) *ch_logfile()*
When {mode} is omitted or "a" append to the file. When {mode} is omitted or "a" append to the file.
When {mode} is "w" start with an empty file. When {mode} is "w" start with an empty file.
The file is flushed after every message, on Unix you can use Use |ch_log()| to write log messages. The file is flushed
"tail -f" to see what is going on in real time. after every message, on Unix you can use "tail -f" to see what
is going on in real time.
This function is not available in the |sandbox|. This function is not available in the |sandbox|.
NOTE: the channel communication is stored in the file, be NOTE: the channel communication is stored in the file, be
@@ -6656,6 +6673,191 @@ prompt_setprompt({buf}, {text}) *prompt_setprompt()*
The result is only visible if {buf} has 'buftype' set to The result is only visible if {buf} has 'buftype' set to
"prompt". Example: > "prompt". Example: >
call prompt_setprompt(bufnr(''), 'command: ') call prompt_setprompt(bufnr(''), 'command: ')
<
*prop_add()* *E965*
prop_add({lnum}, {col}, {props})
Attach a text property at position {lnum}, {col}. Use one for
the first column.
If {lnum} is invalid an error is given. *E966*
If {col} is invalid an error is given. *E964*
{props} is a dictionary with these fields:
"length" - length of text in characters, can only be
used for a property that does not
continue in another line
"end_lnum" - line number for end of text
"end_col" - column for end of text; not used when
"length" is present
"bufnr - buffer to add the property to; when
omitted the current buffer is used
"id" - user defined ID for the property; when
omitted zero is used
"type" - name of the text property type
All fields except "type" are optional.
It is an error when both "length" and "end_lnum" or "end_col"
are passed. Either use "length" or "end_col" for a property
within one line, or use "end_lnum" and "end_col" for a
property that spans more than one line.
When neither "length" or "end_col" are passed the property
will apply to one character.
"type" will first be looked up in the buffer the property is
added to. When not found, the global property types are used.
If not found an error is given.
See |text-properties| for information about text properties.
prop_clear({lnum} [, {lnum_end} [, {props}]]) *prop_clear()*
Remove all text properties from line {lnum}.
When {lnum_end} is given, remove all text properties from line
{lnum} to {lnum_end} (inclusive).
When {props} contains a "bufnr" item use this buffer,
otherwise use the current buffer.
See |text-properties| for information about text properties.
*prop_find()*
prop_find({props} [, {direction}])
NOT IMPLEMENTED YET
Search for a text property as specified with {props}:
"id" property with this ID
"type" property with this type name
"bufnr buffer to search in; when present a
start position with "lnum" and "col"
must be given; when omitted the
current buffer is used
"lnum" start in this line (when omitted start
at the cursor)
"col" start at this column (when omitted
and "lnum" is given: use column 1,
otherwise start at the cursor)
"skipstart" do not look for a match at the start
position
{direction} can be "f" for forward and "b" for backward. When
omitted forward search is performed.
If a match is found then a Dict is returned with the entries
as with prop_list(), and additionally an "lnum" entry.
If no match is found then an empty Dict is returned.
See |text-properties| for information about text properties.
prop_list({lnum} [, {props}]) *prop_list()*
Return a List with all text properties in line {lnum}.
When {props} contains a "bufnr" item, use this buffer instead
of the current buffer.
The properties are ordered by starting column and priority.
Each property is a Dict with these entries:
"col" starting column
"length" length in bytes
"id" property ID
"type" name of the property type, omitted if
the type was deleted
"start" when TRUE property starts in this line
"end" when TRUE property ends in this line
When "start" is zero the property started in a previous line,
the current one is a continuation.
When "end" is zero the property continues in the next line.
The line break after this line is included.
See |text-properties| for information about text properties.
*prop_remove()* *E968*
prop_remove({props} [, {lnum} [, {lnum_end}]])
Remove a matching text property from line {lnum}. When
{lnum_end} is given, remove matching text properties from line
{lnum} to {lnum_end} (inclusive).
When {lnum} is omitted remove matching text properties from
all lines.
{props} is a dictionary with these fields:
"id" - remove text properties with this ID
"type" - remove text properties with this type name
"bufnr" - use this buffer instead of the current one
"all" - when TRUE remove all matching text
properties, not just the first one
A property matches when either "id" or "type" matches.
Returns the number of properties that were removed.
See |text-properties| for information about text properties.
prop_type_add({name}, {props}) *prop_type_add()* *E969* *E970*
Add a text property type {name}. If a property type with this
name already exists an error is given.
{props} is a dictionary with these optional fields:
"bufnr" - define the property only for this
buffer; this avoids name collisions and
automatically clears the property types
when the buffer is deleted.
"highlight" - name of highlight group to use
"priority" - when a character has multiple text
properties the one with the highest
priority will be used; negative values
can be used, the default priority is
zero
"start_incl" - when TRUE inserts at the start
position will be included in the text
property
"end_incl" - when TRUE inserts at the end
position will be included in the text
property
See |text-properties| for information about text properties.
prop_type_change({name}, {props}) *prop_type_change()*
Change properties of an existing text property type. If a
property with this name does not exist an error is given.
The {props} argument is just like |prop_type_add()|.
See |text-properties| for information about text properties.
prop_type_delete({name} [, {props}]) *prop_type_delete()*
Remove the text property type {name}. When text properties
using the type {name} are still in place, they will not have
an effect and can no longer be removed by name.
{props} can contain a "bufnr" item. When it is given, delete
a property type from this buffer instead of from the global
property types.
When text property type {name} is not found there is no error.
See |text-properties| for information about text properties.
prop_type_get([{name} [, {props}]) *prop_type_get()*
Returns the properties of property type {name}. This is a
dictionary with the same fields as was given to
prop_type_add().
When the property type {name} does not exist, an empty
dictionary is returned.
{props} can contain a "bufnr" item. When it is given, use
this buffer instead of the global property types.
See |text-properties| for information about text properties.
prop_type_list([{props}]) *prop_type_list()*
Returns a list with all property type names.
{props} can contain a "bufnr" item. When it is given, use
this buffer instead of the global property types.
See |text-properties| for information about text properties.
pumvisible() *pumvisible()* pumvisible() *pumvisible()*
@@ -9609,6 +9811,7 @@ terminal Compiled with |terminal| support.
terminfo Compiled with terminfo instead of termcap. terminfo Compiled with terminfo instead of termcap.
termresponse Compiled with support for |t_RV| and |v:termresponse|. termresponse Compiled with support for |t_RV| and |v:termresponse|.
textobjects Compiled with support for |text-objects|. textobjects Compiled with support for |text-objects|.
textprop Compiled with support for |text-properties|.
tgetent Compiled with tgetent support, able to use a termcap tgetent Compiled with tgetent support, able to use a termcap
or terminfo file. or terminfo file.
timers Compiled with |timer_start()| support. timers Compiled with |timer_start()| support.

114
runtime/doc/textprop.txt Normal file
View File

@@ -0,0 +1,114 @@
*textprop.txt* For Vim version 8.1. Last change: 2018 Dec 13
VIM REFERENCE MANUAL by Bram Moolenaar
Displaying text with properties attached. *text-properties*
THIS IS UNDER DEVELOPMENT - ANYTHING MAY STILL CHANGE *E967*
What is not working yet:
- Adjusting column/length when inserting text
- Text properties spanning more than one line
- prop_find()
- callbacks when text properties are outdated
1. Introduction |text-prop-intro|
2. Functions |text-prop-functions|
{Vi does not have text properties}
{not able to use text properties when the |+textprop| feature was
disabled at compile time}
==============================================================================
1. Introduction *text-prop-intro*
Text properties can be attached to text in a buffer. They will move with the
text: If lines are deleted or inserted the properties move with the text they
are attached to. Also when inserting/deleting text in the line before the
text property. And when inserting/deleting text inside the text property, it
will increase/decrease in size.
The main use for text properties is to highlight text. This can be seen as a
replacement for syntax highlighting. Instead of defining patterns to match
the text, the highlighting is set by a script, possibly using the output of an
external parser. This only needs to be done once, not every time when
redrawing the screen, thus can be much faster, after the initial cost of
attaching the text properties.
Text properties can also be used for other purposes to identify text. For
example, add a text property on a function name, so that a search can be
defined to jump to the next/previous function.
A text property is attached at a specific line and column, and has a specified
length. The property can span multiple lines.
A text property has these fields:
"id" a number to be used as desired
"type" the name of a property type
Property Types ~
*E971*
A text property normally has the name of a property type, which defines
how to highlight the text. The property type can have these entries:
"highlight" name of the highlight group to use
"priority" when properties overlap, the one with the highest
priority will be used.
"start_incl" when TRUE inserts at the start position will be
included in the text property
"end_incl" when TRUE inserts at the end position will be
included in the text property
Example ~
Suppose line 11 in a buffer has this text (excluding the indent):
The number 123 is smaller than 4567.
To highlight the numbers: >
call prop_type_add('number', {'highlight': 'Constant'})
call prop_add(11, 12, {'length': 3, 'type': 'number})
call prop_add(11, 32, {'length': 4, 'type': 'number})
Setting "start_incl" and "end_incl" is useful when white space surrounds the
text, e.g. for a function name. Using false is useful when the text starts
and/or ends with a specific character, such as the quote surrounding a string.
func FuncName(arg) ~
^^^^^^^^ property with start_incl and end_incl set
var = "text"; ~
^^^^^^ property with start_incl and end_incl not set
Nevertheless, when text is inserted or deleted the text may need to be parsed
and the text properties updated. But this can be done asynchrnously.
==============================================================================
2. Functions *text-prop-functions*
Manipulating text property types:
prop_type_add({name}, {props}) define a new property type
prop_type_change({name}, {props}) change an existing property type
prop_type_delete({name} [, {props}]) delete a property type
prop_type_get([{name} [, {props}]) get property type values
prop_type_list([{props}]) get list of property types
Manipulating text properties:
prop_add({lnum}, {col}, {props}) add a text property
prop_clear({lnum} [, {lnum_end} [, {bufnr}]])
remove all text properties
prop_find({props} [, {direction}]) search for a text property
prop_list({lnum} [, {props}) text properties in {lnum}
prop_remove({props} [, {lnum} [, {lnum_end}]])
remove a text property
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@@ -186,6 +186,7 @@ NEW_TESTS = \
test_terminal_fail \ test_terminal_fail \
test_textformat \ test_textformat \
test_textobjects \ test_textobjects \
test_textprop \
test_timers \ test_timers \
test_true_false \ test_true_false \
test_undo \ test_undo \

View File

@@ -751,6 +751,7 @@ OBJ = \
$(OUTDIR)/syntax.o \ $(OUTDIR)/syntax.o \
$(OUTDIR)/tag.o \ $(OUTDIR)/tag.o \
$(OUTDIR)/term.o \ $(OUTDIR)/term.o \
$(OUTDIR)/textprop.o \
$(OUTDIR)/ui.o \ $(OUTDIR)/ui.o \
$(OUTDIR)/undo.o \ $(OUTDIR)/undo.o \
$(OUTDIR)/userfunc.o \ $(OUTDIR)/userfunc.o \
@@ -1090,6 +1091,9 @@ $(OUTDIR)/regexp.o: regexp.c regexp_nfa.c $(INCL)
$(OUTDIR)/terminal.o: terminal.c $(INCL) $(TERM_DEPS) $(OUTDIR)/terminal.o: terminal.c $(INCL) $(TERM_DEPS)
$(CC) -c $(CFLAGS) terminal.c -o $(OUTDIR)/terminal.o $(CC) -c $(CFLAGS) terminal.c -o $(OUTDIR)/terminal.o
$(OUTDIR)/textprop.o: textprop.c $(INCL)
$(CC) -c $(CFLAGS) textprop.c -o $(OUTDIR)/textprop.o
CCCTERM = $(CC) -c $(CFLAGS) -Ilibvterm/include -DINLINE="" \ CCCTERM = $(CC) -c $(CFLAGS) -Ilibvterm/include -DINLINE="" \
-DVSNPRINTF=vim_vsnprintf \ -DVSNPRINTF=vim_vsnprintf \

View File

@@ -754,6 +754,7 @@ OBJ = \
$(OUTDIR)\syntax.obj \ $(OUTDIR)\syntax.obj \
$(OUTDIR)\tag.obj \ $(OUTDIR)\tag.obj \
$(OUTDIR)\term.obj \ $(OUTDIR)\term.obj \
$(OUTDIR)\textprop.obj \
$(OUTDIR)\ui.obj \ $(OUTDIR)\ui.obj \
$(OUTDIR)\undo.obj \ $(OUTDIR)\undo.obj \
$(OUTDIR)\userfunc.obj \ $(OUTDIR)\userfunc.obj \
@@ -1529,6 +1530,8 @@ $(OUTDIR)/tag.obj: $(OUTDIR) tag.c $(INCL)
$(OUTDIR)/term.obj: $(OUTDIR) term.c $(INCL) $(OUTDIR)/term.obj: $(OUTDIR) term.c $(INCL)
$(OUTDIR)/textprop.obj: $(OUTDIR) textprop.c $(INCL)
$(OUTDIR)/ui.obj: $(OUTDIR) ui.c $(INCL) $(OUTDIR)/ui.obj: $(OUTDIR) ui.c $(INCL)
$(OUTDIR)/undo.obj: $(OUTDIR) undo.c $(INCL) $(OUTDIR)/undo.obj: $(OUTDIR) undo.c $(INCL)
@@ -1667,6 +1670,7 @@ proto.h: \
proto/syntax.pro \ proto/syntax.pro \
proto/tag.pro \ proto/tag.pro \
proto/term.pro \ proto/term.pro \
proto/textprop.pro \
proto/ui.pro \ proto/ui.pro \
proto/undo.pro \ proto/undo.pro \
proto/userfunc.pro \ proto/userfunc.pro \

View File

@@ -1577,8 +1577,6 @@ include Make_all.mak
# TAGS_INCL: include files used for make tags # TAGS_INCL: include files used for make tags
# ALL_SRC: source files used for make depend and make lint # ALL_SRC: source files used for make depend and make lint
TAGS_INCL = *.h
BASIC_SRC = \ BASIC_SRC = \
arabic.c \ arabic.c \
beval.c \ beval.c \
@@ -1636,6 +1634,7 @@ BASIC_SRC = \
tag.c \ tag.c \
term.c \ term.c \
terminal.c \ terminal.c \
textprop.c \
ui.c \ ui.c \
undo.c \ undo.c \
userfunc.c \ userfunc.c \
@@ -1657,7 +1656,8 @@ SRC = $(BASIC_SRC) \
$(WORKSHOP_SRC) \ $(WORKSHOP_SRC) \
$(WSDEBUG_SRC) $(WSDEBUG_SRC)
TAGS_SRC = *.c *.cpp if_perl.xs TAGS_SRC = *.c *.cpp $(PERL_SRC) $(TERM_SRC) $(XDIFF_SRC)
TAGS_INCL = *.h $(TERM_DEPS) $(XDIFF_INCL)
EXTRA_SRC = hangulin.c if_lua.c if_mzsch.c auto/if_perl.c if_perlsfio.c \ EXTRA_SRC = hangulin.c if_lua.c if_mzsch.c auto/if_perl.c if_perlsfio.c \
if_python.c if_python3.c if_tcl.c if_ruby.c \ if_python.c if_python3.c if_tcl.c if_ruby.c \
@@ -1747,6 +1747,7 @@ OBJ_COMMON = \
objects/tag.o \ objects/tag.o \
objects/term.o \ objects/term.o \
objects/terminal.o \ objects/terminal.o \
objects/textprop.o \
objects/ui.o \ objects/ui.o \
objects/undo.o \ objects/undo.o \
objects/userfunc.o \ objects/userfunc.o \
@@ -1881,6 +1882,7 @@ PRO_AUTO = \
term.pro \ term.pro \
terminal.pro \ terminal.pro \
termlib.pro \ termlib.pro \
textprop.pro \
ui.pro \ ui.pro \
undo.pro \ undo.pro \
userfunc.pro \ userfunc.pro \
@@ -2092,7 +2094,7 @@ notags:
# Motif and Athena GUI # Motif and Athena GUI
# You can ignore error messages for missing files. # You can ignore error messages for missing files.
tags TAGS: notags tags TAGS: notags
$(TAGPRG) $(TAGS_SRC) $(TAGS_INCL) $(TERM_SRC) $(TERM_DEPS) $(TAGPRG) $(TAGS_SRC) $(TAGS_INCL)
# Make a highlight file for types. Requires Exuberant ctags and awk # Make a highlight file for types. Requires Exuberant ctags and awk
types: types.vim types: types.vim
@@ -3211,6 +3213,9 @@ objects/term.o: term.c
objects/terminal.o: terminal.c $(TERM_DEPS) objects/terminal.o: terminal.c $(TERM_DEPS)
$(CCC) -o $@ terminal.c $(CCC) -o $@ terminal.c
objects/textprop.o: textprop.c
$(CCC) -o $@ textprop.c
objects/ui.o: ui.c objects/ui.o: ui.c
$(CCC) -o $@ ui.c $(CCC) -o $@ ui.c
@@ -3602,6 +3607,10 @@ objects/terminal.o: terminal.c vim.h protodef.h auto/config.h feature.h os_unix.
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h farsi.h arabic.h libvterm/include/vterm.h \ proto.h globals.h farsi.h arabic.h libvterm/include/vterm.h \
libvterm/include/vterm_keycodes.h libvterm/include/vterm_keycodes.h
objects/textprop.o: textprop.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h farsi.h arabic.h
objects/ui.o: ui.c vim.h protodef.h auto/config.h feature.h os_unix.h \ objects/ui.o: ui.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \

View File

@@ -822,6 +822,9 @@ buf_freeall(buf_T *buf, int flags)
} }
#ifdef FEAT_SYN_HL #ifdef FEAT_SYN_HL
syntax_clear(&buf->b_s); /* reset syntax info */ syntax_clear(&buf->b_s); /* reset syntax info */
#endif
#ifdef FEAT_TEXT_PROP
clear_buf_prop_types(buf);
#endif #endif
buf->b_flags &= ~BF_READERR; /* a read error is no longer relevant */ buf->b_flags &= ~BF_READERR; /* a read error is no longer relevant */
} }

View File

@@ -10302,6 +10302,9 @@ ins_tab(void)
if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG))
for (temp = i; --temp >= 0; ) for (temp = i; --temp >= 0; )
replace_join(repl_off); replace_join(repl_off);
#ifdef FEAT_TEXT_PROP
curbuf->b_ml.ml_line_len -= i;
#endif
} }
#ifdef FEAT_NETBEANS_INTG #ifdef FEAT_NETBEANS_INTG
if (netbeans_active()) if (netbeans_active())

View File

@@ -771,6 +771,17 @@ static struct fst
{"prompt_setcallback", 2, 2, f_prompt_setcallback}, {"prompt_setcallback", 2, 2, f_prompt_setcallback},
{"prompt_setinterrupt", 2, 2, f_prompt_setinterrupt}, {"prompt_setinterrupt", 2, 2, f_prompt_setinterrupt},
{"prompt_setprompt", 2, 2, f_prompt_setprompt}, {"prompt_setprompt", 2, 2, f_prompt_setprompt},
#endif
#ifdef FEAT_TEXT_PROP
{"prop_add", 3, 3, f_prop_add},
{"prop_clear", 1, 3, f_prop_clear},
{"prop_list", 1, 2, f_prop_list},
{"prop_remove", 2, 3, f_prop_remove},
{"prop_type_add", 2, 2, f_prop_type_add},
{"prop_type_change", 2, 2, f_prop_type_change},
{"prop_type_delete", 1, 2, f_prop_type_delete},
{"prop_type_get", 1, 2, f_prop_type_get},
{"prop_type_list", 0, 1, f_prop_type_list},
#endif #endif
{"pumvisible", 0, 0, f_pumvisible}, {"pumvisible", 0, 0, f_pumvisible},
#ifdef FEAT_PYTHON3 #ifdef FEAT_PYTHON3
@@ -6478,6 +6489,9 @@ f_has(typval_T *argvars, typval_T *rettv)
#ifdef FEAT_TEXTOBJ #ifdef FEAT_TEXTOBJ
"textobjects", "textobjects",
#endif #endif
#ifdef FEAT_TEXT_PROP
"textprop",
#endif
#ifdef HAVE_TGETENT #ifdef HAVE_TGETENT
"tgetent", "tgetent",
#endif #endif

View File

@@ -769,6 +769,21 @@ may_add_char_to_search(int firstc, int *c, incsearch_state_T *is_state)
stuffcharReadbuff(*c); stuffcharReadbuff(*c);
*c = '\\'; *c = '\\';
} }
#ifdef FEAT_MBYTE
// add any composing characters
if (mb_char2len(*c) != mb_ptr2len(ml_get_cursor()))
{
int save_c = *c;
while (mb_char2len(*c) != mb_ptr2len(ml_get_cursor()))
{
curwin->w_cursor.col += mb_char2len(*c);
*c = gchar_cursor();
stuffcharReadbuff(*c);
}
*c = save_c;
}
#endif
return FAIL; return FAIL;
} }
} }

View File

@@ -501,6 +501,13 @@
# define FEAT_CONCEAL # define FEAT_CONCEAL
#endif #endif
/*
* +textprop Text properties
*/
#if defined(FEAT_EVAL) && defined(FEAT_SYN_HL)
# define FEAT_TEXT_PROP
#endif
/* /*
* +spell spell checking * +spell spell checking
* *

View File

@@ -2487,7 +2487,6 @@ ml_get_buf(
{ {
bhdr_T *hp; bhdr_T *hp;
DATA_BL *dp; DATA_BL *dp;
char_u *ptr;
static int recursive = 0; static int recursive = 0;
if (lnum > buf->b_ml.ml_line_count) /* invalid line number */ if (lnum > buf->b_ml.ml_line_count) /* invalid line number */
@@ -2518,6 +2517,10 @@ errorret:
*/ */
if (buf->b_ml.ml_line_lnum != lnum || mf_dont_release) if (buf->b_ml.ml_line_lnum != lnum || mf_dont_release)
{ {
unsigned start, end;
colnr_T len;
int idx;
ml_flush_line(buf); ml_flush_line(buf);
/* /*
@@ -2540,8 +2543,18 @@ errorret:
dp = (DATA_BL *)(hp->bh_data); dp = (DATA_BL *)(hp->bh_data);
ptr = (char_u *)dp + ((dp->db_index[lnum - buf->b_ml.ml_locked_low]) & DB_INDEX_MASK); idx = lnum - buf->b_ml.ml_locked_low;
buf->b_ml.ml_line_ptr = ptr; start = ((dp->db_index[idx]) & DB_INDEX_MASK);
// The text ends where the previous line starts. The first line ends
// at the end of the block.
if (idx == 0)
end = dp->db_txt_end;
else
end = ((dp->db_index[idx - 1]) & DB_INDEX_MASK);
len = end - start;
buf->b_ml.ml_line_ptr = (char_u *)dp + start;
buf->b_ml.ml_line_len = len;
buf->b_ml.ml_line_lnum = lnum; buf->b_ml.ml_line_lnum = lnum;
buf->b_ml.ml_flags &= ~ML_LINE_DIRTY; buf->b_ml.ml_flags &= ~ML_LINE_DIRTY;
} }
@@ -2614,20 +2627,21 @@ ml_append_buf(
static int static int
ml_append_int( ml_append_int(
buf_T *buf, buf_T *buf,
linenr_T lnum, /* append after this line (can be 0) */ linenr_T lnum, // append after this line (can be 0)
char_u *line, /* text of the new line */ char_u *line, // text of the new line
colnr_T len, /* length of line, including NUL, or 0 */ colnr_T len_arg, // length of line, including NUL, or 0
int newfile, /* flag, see above */ int newfile, // flag, see above
int mark) /* mark the new line */ int mark) // mark the new line
{ {
colnr_T len = len_arg; // length of line, including NUL, or 0
int i; int i;
int line_count; /* number of indexes in current block */ int line_count; // number of indexes in current block
int offset; int offset;
int from, to; int from, to;
int space_needed; /* space needed for new line */ int space_needed; // space needed for new line
int page_size; int page_size;
int page_count; int page_count;
int db_idx; /* index for lnum in data block */ int db_idx; // index for lnum in data block
bhdr_T *hp; bhdr_T *hp;
memfile_T *mfp; memfile_T *mfp;
DATA_BL *dp; DATA_BL *dp;
@@ -2642,8 +2656,8 @@ ml_append_int(
lowest_marked = lnum + 1; lowest_marked = lnum + 1;
if (len == 0) if (len == 0)
len = (colnr_T)STRLEN(line) + 1; /* space needed for the text */ len = (colnr_T)STRLEN(line) + 1; // space needed for the text
space_needed = len + INDEX_SIZE; /* space needed for text + index */ space_needed = len + INDEX_SIZE; // space needed for text + index
mfp = buf->b_ml.ml_mfp; mfp = buf->b_ml.ml_mfp;
page_size = mfp->mf_page_size; page_size = mfp->mf_page_size;
@@ -2728,7 +2742,8 @@ ml_append_int(
dp->db_index[i + 1] = dp->db_index[i] - len; dp->db_index[i + 1] = dp->db_index[i] - len;
dp->db_index[db_idx + 1] = offset - len; dp->db_index[db_idx + 1] = offset - len;
} }
else /* add line at the end */ else
// add line at the end (which is the start of the text)
dp->db_index[db_idx + 1] = dp->db_txt_start; dp->db_index[db_idx + 1] = dp->db_txt_start;
/* /*
@@ -3128,6 +3143,19 @@ ml_append_int(
int int
ml_replace(linenr_T lnum, char_u *line, int copy) ml_replace(linenr_T lnum, char_u *line, int copy)
{ {
colnr_T len = -1;
if (line != NULL)
len = STRLEN(line);
return ml_replace_len(lnum, line, len, copy);
}
int
ml_replace_len(linenr_T lnum, char_u *line_arg, colnr_T len_arg, int copy)
{
char_u *line = line_arg;
colnr_T len = len_arg;
if (line == NULL) /* just checking... */ if (line == NULL) /* just checking... */
return FAIL; return FAIL;
@@ -3135,7 +3163,7 @@ ml_replace(linenr_T lnum, char_u *line, int copy)
if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL)
return FAIL; return FAIL;
if (copy && (line = vim_strsave(line)) == NULL) /* allocate memory */ if (copy && (line = vim_strnsave(line, len)) == NULL) /* allocate memory */
return FAIL; return FAIL;
#ifdef FEAT_NETBEANS_INTG #ifdef FEAT_NETBEANS_INTG
if (netbeans_active()) if (netbeans_active())
@@ -3144,11 +3172,48 @@ ml_replace(linenr_T lnum, char_u *line, int copy)
netbeans_inserted(curbuf, lnum, 0, line, (int)STRLEN(line)); netbeans_inserted(curbuf, lnum, 0, line, (int)STRLEN(line));
} }
#endif #endif
if (curbuf->b_ml.ml_line_lnum != lnum) /* other line buffered */ if (curbuf->b_ml.ml_line_lnum != lnum)
ml_flush_line(curbuf); /* flush it */ {
else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) /* same line allocated */ // another line is buffered, flush it
ml_flush_line(curbuf);
#ifdef FEAT_TEXT_PROP
curbuf->b_ml.ml_flags &= ~ML_LINE_DIRTY;
if (has_any_text_properties(curbuf))
// Need to fetch the old line to copy over any text properties.
ml_get_buf(curbuf, lnum, TRUE);
#endif
}
#ifdef FEAT_TEXT_PROP
if (has_any_text_properties(curbuf))
{
size_t oldtextlen = STRLEN(curbuf->b_ml.ml_line_ptr) + 1;
if (oldtextlen < (size_t)curbuf->b_ml.ml_line_len)
{
char_u *newline;
size_t textproplen = curbuf->b_ml.ml_line_len - oldtextlen;
// Need to copy over text properties, stored after the text.
newline = alloc(len + 1 + textproplen);
if (newline != NULL)
{
mch_memmove(newline, line, len + 1);
mch_memmove(newline + len + 1, curbuf->b_ml.ml_line_ptr + oldtextlen, textproplen);
vim_free(line);
line = newline;
len += textproplen;
}
}
}
#endif
if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) /* same line allocated */
vim_free(curbuf->b_ml.ml_line_ptr); /* free it */ vim_free(curbuf->b_ml.ml_line_ptr); /* free it */
curbuf->b_ml.ml_line_ptr = line; curbuf->b_ml.ml_line_ptr = line;
curbuf->b_ml.ml_line_len = len + 1;
curbuf->b_ml.ml_line_lnum = lnum; curbuf->b_ml.ml_line_lnum = lnum;
curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY;
@@ -3490,7 +3555,7 @@ ml_flush_line(buf_T *buf)
old_len = dp->db_txt_end - start; old_len = dp->db_txt_end - start;
else /* text of previous line follows */ else /* text of previous line follows */
old_len = (dp->db_index[idx - 1] & DB_INDEX_MASK) - start; old_len = (dp->db_index[idx - 1] & DB_INDEX_MASK) - start;
new_len = (colnr_T)STRLEN(new_line) + 1; new_len = buf->b_ml.ml_line_len;
extra = new_len - old_len; /* negative if lines gets smaller */ extra = new_len - old_len; /* negative if lines gets smaller */
/* /*
@@ -5009,8 +5074,7 @@ ml_updatechunk(
*/ */
buf->b_ml.ml_usedchunks = 1; buf->b_ml.ml_usedchunks = 1;
buf->b_ml.ml_chunksize[0].mlcs_numlines = 1; buf->b_ml.ml_chunksize[0].mlcs_numlines = 1;
buf->b_ml.ml_chunksize[0].mlcs_totalsize = buf->b_ml.ml_chunksize[0].mlcs_totalsize = (long)buf->b_ml.ml_line_len;
(long)STRLEN(buf->b_ml.ml_line_ptr) + 1;
return; return;
} }

View File

@@ -2631,9 +2631,10 @@ del_bytes(
{ {
char_u *oldp, *newp; char_u *oldp, *newp;
colnr_T oldlen; colnr_T oldlen;
colnr_T newlen;
linenr_T lnum = curwin->w_cursor.lnum; linenr_T lnum = curwin->w_cursor.lnum;
colnr_T col = curwin->w_cursor.col; colnr_T col = curwin->w_cursor.col;
int was_alloced; int alloc_newp;
long movelen; long movelen;
int fixpos = fixpos_arg; int fixpos = fixpos_arg;
@@ -2710,6 +2711,7 @@ del_bytes(
count = oldlen - col; count = oldlen - col;
movelen = 1; movelen = 1;
} }
newlen = oldlen - count;
/* /*
* If the old line has been allocated the deletion can be done in the * If the old line has been allocated the deletion can be done in the
@@ -2720,24 +2722,34 @@ del_bytes(
*/ */
#ifdef FEAT_NETBEANS_INTG #ifdef FEAT_NETBEANS_INTG
if (netbeans_active()) if (netbeans_active())
was_alloced = FALSE; alloc_newp = TRUE;
else else
#endif #endif
was_alloced = ml_line_alloced(); /* check if oldp was allocated */ alloc_newp = !ml_line_alloced(); // check if oldp was allocated
if (was_alloced) if (!alloc_newp)
newp = oldp; /* use same allocated memory */ newp = oldp; // use same allocated memory
else else
{ /* need to allocate a new line */ { // need to allocate a new line
newp = alloc((unsigned)(oldlen + 1 - count)); newp = alloc((unsigned)(newlen + 1));
if (newp == NULL) if (newp == NULL)
return FAIL; return FAIL;
mch_memmove(newp, oldp, (size_t)col); mch_memmove(newp, oldp, (size_t)col);
} }
mch_memmove(newp + col, oldp + col + count, (size_t)movelen); mch_memmove(newp + col, oldp + col + count, (size_t)movelen);
if (!was_alloced) if (alloc_newp)
ml_replace(lnum, newp, FALSE); ml_replace(lnum, newp, FALSE);
#ifdef FEAT_TEXT_PROP
else
{
// Also move any following text properties.
if (oldlen + 1 < curbuf->b_ml.ml_line_len)
mch_memmove(newp + newlen + 1, oldp + oldlen + 1,
(size_t)curbuf->b_ml.ml_line_len - oldlen - 1);
curbuf->b_ml.ml_line_len -= count;
}
#endif
/* mark the buffer as changed and prepare for displaying */ // mark the buffer as changed and prepare for displaying
changed_bytes(lnum, curwin->w_cursor.col); changed_bytes(lnum, curwin->w_cursor.col);
return OK; return OK;

View File

@@ -1191,6 +1191,9 @@ free_all_mem(void)
# ifdef FEAT_CMDHIST # ifdef FEAT_CMDHIST
init_history(); init_history();
# endif # endif
#ifdef FEAT_TEXT_PROP
clear_global_prop_types();
#endif
#ifdef FEAT_QUICKFIX #ifdef FEAT_QUICKFIX
{ {

View File

@@ -183,6 +183,9 @@ void qsort(void *base, size_t elm_count, size_t elm_size, int (*cmp)(const void
# if defined(HAVE_TGETENT) && (defined(AMIGA) || defined(VMS)) # if defined(HAVE_TGETENT) && (defined(AMIGA) || defined(VMS))
# include "termlib.pro" # include "termlib.pro"
# endif # endif
# ifdef FEAT_TEXT_PROP
# include "textprop.pro"
# endif
# include "ui.pro" # include "ui.pro"
# include "undo.pro" # include "undo.pro"
# include "userfunc.pro" # include "userfunc.pro"

View File

@@ -24,6 +24,7 @@ int ml_line_alloced(void);
int ml_append(linenr_T lnum, char_u *line, colnr_T len, int newfile); int ml_append(linenr_T lnum, char_u *line, colnr_T len, int newfile);
int ml_append_buf(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, int newfile); int ml_append_buf(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, int newfile);
int ml_replace(linenr_T lnum, char_u *line, int copy); int ml_replace(linenr_T lnum, char_u *line, int copy);
int ml_replace_len(linenr_T lnum, char_u *line_arg, colnr_T len_arg, int copy);
int ml_delete(linenr_T lnum, int message); int ml_delete(linenr_T lnum, int message);
void ml_setmarked(linenr_T lnum); void ml_setmarked(linenr_T lnum);
linenr_T ml_firstmarked(void); linenr_T ml_firstmarked(void);

17
src/proto/textprop.pro Normal file
View File

@@ -0,0 +1,17 @@
/* textprop.c */
void f_prop_add(typval_T *argvars, typval_T *rettv);
int has_any_text_properties(buf_T *buf);
int get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change);
proptype_T *text_prop_type_by_id(buf_T *buf, int id);
void f_prop_clear(typval_T *argvars, typval_T *rettv);
void f_prop_list(typval_T *argvars, typval_T *rettv);
void f_prop_remove(typval_T *argvars, typval_T *rettv);
void prop_type_set(typval_T *argvars, int add);
void f_prop_type_add(typval_T *argvars, typval_T *rettv);
void f_prop_type_change(typval_T *argvars, typval_T *rettv);
void f_prop_type_delete(typval_T *argvars, typval_T *rettv);
void f_prop_type_get(typval_T *argvars, typval_T *rettv);
void f_prop_type_list(typval_T *argvars, typval_T *rettv);
void clear_global_prop_types(void);
void clear_buf_prop_types(buf_T *buf);
/* vim: set ft=c : */

View File

@@ -3128,6 +3128,15 @@ win_line(
int draw_color_col = FALSE; /* highlight colorcolumn */ int draw_color_col = FALSE; /* highlight colorcolumn */
int *color_cols = NULL; /* pointer to according columns array */ int *color_cols = NULL; /* pointer to according columns array */
#endif #endif
#ifdef FEAT_TEXT_PROP
int text_prop_count;
int text_prop_next = 0; // next text property to use
textprop_T *text_props = NULL;
int *text_prop_idxs = NULL;
int text_props_active = 0;
proptype_T *text_prop_type = NULL;
int text_prop_attr = 0;
#endif
#ifdef FEAT_SPELL #ifdef FEAT_SPELL
int has_spell = FALSE; /* this buffer has spell checking */ int has_spell = FALSE; /* this buffer has spell checking */
# define SPWORDLEN 150 # define SPWORDLEN 150
@@ -3144,7 +3153,7 @@ win_line(
static linenr_T capcol_lnum = 0; /* line number where "cap_col" used */ static linenr_T capcol_lnum = 0; /* line number where "cap_col" used */
int cur_checked_col = 0; /* checked column for current line */ int cur_checked_col = 0; /* checked column for current line */
#endif #endif
int extra_check = 0; // has syntax or linebreak int extra_check = 0; // has extra highlighting
#ifdef FEAT_MBYTE #ifdef FEAT_MBYTE
int multi_attr = 0; /* attributes desired by multibyte */ int multi_attr = 0; /* attributes desired by multibyte */
int mb_l = 1; /* multi-byte byte length */ int mb_l = 1; /* multi-byte byte length */
@@ -3784,6 +3793,30 @@ win_line(
} }
#endif #endif
#ifdef FEAT_TEXT_PROP
{
char_u *prop_start;
text_prop_count = get_text_props(wp->w_buffer, lnum,
&prop_start, FALSE);
if (text_prop_count > 0)
{
// Make a copy of the properties, so that they are properly
// aligned.
text_props = (textprop_T *)alloc(
text_prop_count * sizeof(textprop_T));
if (text_props != NULL)
mch_memmove(text_props, prop_start,
text_prop_count * sizeof(textprop_T));
// Allocate an array for the indexes.
text_prop_idxs = (int *)alloc(text_prop_count * sizeof(int));
area_highlighting = TRUE;
extra_check = TRUE;
}
}
#endif
off = (unsigned)(current_ScreenLine - ScreenLines); off = (unsigned)(current_ScreenLine - ScreenLines);
col = 0; col = 0;
#ifdef FEAT_RIGHTLEFT #ifdef FEAT_RIGHTLEFT
@@ -4283,6 +4316,11 @@ win_line(
else else
{ {
attr_pri = FALSE; attr_pri = FALSE;
#ifdef FEAT_TEXT_PROP
if (text_prop_type != NULL)
char_attr = text_prop_attr;
else
#endif
#ifdef FEAT_SYN_HL #ifdef FEAT_SYN_HL
if (has_syntax) if (has_syntax)
char_attr = syntax_attr; char_attr = syntax_attr;
@@ -4663,6 +4701,66 @@ win_line(
} }
#endif #endif
#ifdef FEAT_TEXT_PROP
if (text_props != NULL)
{
int pi;
// Check if any active property ends.
for (pi = 0; pi < text_props_active; ++pi)
{
int tpi = text_prop_idxs[pi];
if (col >= text_props[tpi].tp_col - 1
+ text_props[tpi].tp_len)
{
if (pi + 1 < text_props_active)
mch_memmove(text_prop_idxs + pi,
text_prop_idxs + pi + 1,
sizeof(int)
* (text_props_active - (pi + 1)));
--text_props_active;
--pi;
}
}
// Add any text property that starts in this column.
while (text_prop_next < text_prop_count
&& col >= text_props[text_prop_next].tp_col - 1)
text_prop_idxs[text_props_active++] = text_prop_next++;
text_prop_type = NULL;
if (text_props_active > 0)
{
int max_priority = INT_MIN;
int max_col = 0;
// Get the property type with the highest priority
// and/or starting last.
for (pi = 0; pi < text_props_active; ++pi)
{
int tpi = text_prop_idxs[pi];
proptype_T *pt;
pt = text_prop_type_by_id(
curwin->w_buffer, text_props[tpi].tp_type);
if (pt != NULL
&& (pt->pt_priority > max_priority
|| (pt->pt_priority == max_priority
&& text_props[tpi].tp_col >= max_col)))
{
text_prop_type = pt;
max_priority = pt->pt_priority;
max_col = text_props[tpi].tp_col;
}
}
if (text_prop_type != NULL)
text_prop_attr =
syn_id2attr(text_prop_type->pt_hl_id);
}
}
#endif
#ifdef FEAT_SPELL #ifdef FEAT_SPELL
/* Check spelling (unless at the end of the line). /* Check spelling (unless at the end of the line).
* Only do this when there is no syntax highlighting, the * Only do this when there is no syntax highlighting, the
@@ -6025,6 +6123,10 @@ win_line(
cap_col = 0; cap_col = 0;
} }
#endif #endif
#ifdef FEAT_TEXT_PROP
vim_free(text_props);
vim_free(text_prop_idxs);
#endif
vim_free(p_extra_free); vim_free(p_extra_free);
return row; return row;

View File

@@ -684,6 +684,7 @@ typedef struct memline
linenr_T ml_line_lnum; /* line number of cached line, 0 if not valid */ linenr_T ml_line_lnum; /* line number of cached line, 0 if not valid */
char_u *ml_line_ptr; /* pointer to cached line */ char_u *ml_line_ptr; /* pointer to cached line */
colnr_T ml_line_len; /* length of the cached line, including NUL */
bhdr_T *ml_locked; /* block used by last ml_get */ bhdr_T *ml_locked; /* block used by last ml_get */
linenr_T ml_locked_low; /* first line in ml_locked */ linenr_T ml_locked_low; /* first line in ml_locked */
@@ -696,6 +697,41 @@ typedef struct memline
#endif #endif
} memline_T; } memline_T;
/*
* Structure defining text properties. These stick with the text.
* When stored in memline they are after the text, ml_line_len is larger than
* STRLEN(ml_line_ptr) + 1.
*/
typedef struct textprop_S
{
colnr_T tp_col; // start column
colnr_T tp_len; // length in bytes
int tp_id; // identifier
int tp_type; // property type
int tp_flags; // TP_FLAG_ values
} textprop_T;
#define TP_FLAG_CONT_NEXT 1 // property continues in next line
#define TP_FLAG_CONT_PREV 2 // property was continued from prev line
/*
* Structure defining a property type.
*/
typedef struct proptype_S
{
int pt_id; // value used for tp_id
int pt_type; // number used for tp_type
int pt_hl_id; // highlighting
int pt_priority; // priority
int pt_flags; // PT_FLAG_ values
char_u pt_name[1]; // property type name, actually longer
} proptype_T;
#define PT_FLAG_INS_START_INCL 1 // insert at start included in property
#define PT_FLAG_INS_END_INCL 2 // insert at end included in property
#if defined(FEAT_SIGNS) || defined(PROTO) #if defined(FEAT_SIGNS) || defined(PROTO)
typedef struct signlist signlist_T; typedef struct signlist signlist_T;
@@ -2358,6 +2394,9 @@ struct file_buffer
dictitem_T b_bufvar; /* variable for "b:" Dictionary */ dictitem_T b_bufvar; /* variable for "b:" Dictionary */
dict_T *b_vars; /* internal variables, local to buffer */ dict_T *b_vars; /* internal variables, local to buffer */
#endif #endif
#ifdef FEAT_TEXT_PROP
hashtab_T *b_proptypes; /* text property types local to buffer */
#endif
#if defined(FEAT_BEVAL) && defined(FEAT_EVAL) #if defined(FEAT_BEVAL) && defined(FEAT_EVAL)
char_u *b_p_bexpr; /* 'balloonexpr' local value */ char_u *b_p_bexpr; /* 'balloonexpr' local value */

View File

@@ -177,6 +177,7 @@ NEW_TESTS = test_arabic.res \
test_terminal_fail.res \ test_terminal_fail.res \
test_textformat.res \ test_textformat.res \
test_textobjects.res \ test_textobjects.res \
test_textprop.res \
test_undo.res \ test_undo.res \
test_user_func.res \ test_user_func.res \
test_usercommands.res \ test_usercommands.res \

View File

@@ -0,0 +1,200 @@
" Tests for defining text property types and adding text properties to the
" buffer.
if !has('textprop')
finish
endif
func Test_proptype_global()
call prop_type_add('comment', {'highlight': 'Directory', 'priority': 123, 'start_incl': 1, 'end_incl': 1})
let proptypes = prop_type_list()
call assert_equal(1, len(proptypes))
call assert_equal('comment', proptypes[0])
let proptype = prop_type_get('comment')
call assert_equal('Directory', proptype['highlight'])
call assert_equal(123, proptype['priority'])
call assert_equal(1, proptype['start_incl'])
call assert_equal(1, proptype['end_incl'])
call prop_type_delete('comment')
call assert_equal(0, len(prop_type_list()))
call prop_type_add('one', {})
call assert_equal(1, len(prop_type_list()))
let proptype = prop_type_get('one')
call assert_false(has_key(proptype, 'highlight'))
call assert_equal(0, proptype['priority'])
call assert_equal(0, proptype['start_incl'])
call assert_equal(0, proptype['end_incl'])
call prop_type_add('two', {})
call assert_equal(2, len(prop_type_list()))
call prop_type_delete('one')
call assert_equal(1, len(prop_type_list()))
call prop_type_delete('two')
call assert_equal(0, len(prop_type_list()))
endfunc
func Test_proptype_buf()
let bufnr = bufnr('')
call prop_type_add('comment', {'bufnr': bufnr, 'highlight': 'Directory', 'priority': 123, 'start_incl': 1, 'end_incl': 1})
let proptypes = prop_type_list({'bufnr': bufnr})
call assert_equal(1, len(proptypes))
call assert_equal('comment', proptypes[0])
let proptype = prop_type_get('comment', {'bufnr': bufnr})
call assert_equal('Directory', proptype['highlight'])
call assert_equal(123, proptype['priority'])
call assert_equal(1, proptype['start_incl'])
call assert_equal(1, proptype['end_incl'])
call prop_type_delete('comment', {'bufnr': bufnr})
call assert_equal(0, len(prop_type_list({'bufnr': bufnr})))
call prop_type_add('one', {'bufnr': bufnr})
let proptype = prop_type_get('one', {'bufnr': bufnr})
call assert_false(has_key(proptype, 'highlight'))
call assert_equal(0, proptype['priority'])
call assert_equal(0, proptype['start_incl'])
call assert_equal(0, proptype['end_incl'])
call prop_type_add('two', {'bufnr': bufnr})
call assert_equal(2, len(prop_type_list({'bufnr': bufnr})))
call prop_type_delete('one', {'bufnr': bufnr})
call assert_equal(1, len(prop_type_list({'bufnr': bufnr})))
call prop_type_delete('two', {'bufnr': bufnr})
call assert_equal(0, len(prop_type_list({'bufnr': bufnr})))
endfunc
func AddPropTypes()
call prop_type_add('one', {})
call prop_type_add('two', {})
call prop_type_add('three', {})
call prop_type_add('whole', {})
endfunc
func DeletePropTypes()
call prop_type_delete('one')
call prop_type_delete('two')
call prop_type_delete('three')
call prop_type_delete('whole')
endfunc
func SetupPropsInFirstLine()
call setline(1, 'one two three')
call prop_add(1, 1, {'length': 3, 'id': 11, 'type': 'one'})
call prop_add(1, 5, {'length': 3, 'id': 12, 'type': 'two'})
call prop_add(1, 8, {'length': 5, 'id': 13, 'type': 'three'})
call prop_add(1, 1, {'length': 13, 'id': 14, 'type': 'whole'})
endfunc
let s:expected_props = [{'col': 1, 'length': 13, 'id': 14, 'type': 'whole', 'start': 1, 'end': 1},
\ {'col': 1, 'length': 3, 'id': 11, 'type': 'one', 'start': 1, 'end': 1},
\ {'col': 5, 'length': 3, 'id': 12, 'type': 'two', 'start': 1, 'end': 1},
\ {'col': 8, 'length': 5, 'id': 13, 'type': 'three', 'start': 1, 'end': 1},
\ ]
func Test_prop_add()
new
call AddPropTypes()
call SetupPropsInFirstLine()
call assert_equal(s:expected_props, prop_list(1))
call assert_fails("call prop_add(10, 1, {'length': 1, 'id': 14, 'type': 'whole'})", 'E966:')
call assert_fails("call prop_add(1, 22, {'length': 1, 'id': 14, 'type': 'whole'})", 'E964:')
" Insert a line above, text props must still be there.
call append(0, 'empty')
call assert_equal(s:expected_props, prop_list(2))
" Delete a line above, text props must still be there.
1del
call assert_equal(s:expected_props, prop_list(1))
call DeletePropTypes()
bwipe!
endfunc
func Test_prop_remove()
new
call AddPropTypes()
call SetupPropsInFirstLine()
let props = deepcopy(s:expected_props)
call assert_equal(props, prop_list(1))
" remove by id
call prop_remove({'id': 12}, 1)
unlet props[2]
call assert_equal(props, prop_list(1))
" remove by type
call prop_remove({'type': 'one'}, 1)
unlet props[1]
call assert_equal(props, prop_list(1))
call DeletePropTypes()
bwipe!
endfunc
func Test_prop_add_remove_buf()
new
let bufnr = bufnr('')
call AddPropTypes()
call setline(1, 'one two three')
wincmd w
call prop_add(1, 1, {'length': 3, 'id': 11, 'type': 'one', 'bufnr': bufnr})
call prop_add(1, 5, {'length': 3, 'id': 12, 'type': 'two', 'bufnr': bufnr})
call prop_add(1, 11, {'length': 3, 'id': 13, 'type': 'three', 'bufnr': bufnr})
let props = [
\ {'col': 1, 'length': 3, 'id': 11, 'type': 'one', 'start': 1, 'end': 1},
\ {'col': 5, 'length': 3, 'id': 12, 'type': 'two', 'start': 1, 'end': 1},
\ {'col': 11, 'length': 3, 'id': 13, 'type': 'three', 'start': 1, 'end': 1},
\]
call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
" remove by id
call prop_remove({'id': 12, 'bufnr': bufnr}, 1)
unlet props[1]
call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
" remove by type
call prop_remove({'type': 'one', 'bufnr': bufnr}, 1)
unlet props[0]
call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
call DeletePropTypes()
wincmd w
bwipe!
endfunc
func Test_prop_clear()
new
call AddPropTypes()
call SetupPropsInFirstLine()
call assert_equal(s:expected_props, prop_list(1))
call prop_clear(1)
call assert_equal([], prop_list(1))
call DeletePropTypes()
bwipe!
endfunc
func Test_prop_clear_buf()
new
call AddPropTypes()
call SetupPropsInFirstLine()
let bufnr = bufnr('')
wincmd w
call assert_equal(s:expected_props, prop_list(1, {'bufnr': bufnr}))
call prop_clear(1, 1, {'bufnr': bufnr})
call assert_equal([], prop_list(1, {'bufnr': bufnr}))
wincmd w
call DeletePropTypes()
bwipe!
endfunc
" TODO: screenshot test with highlighting

869
src/textprop.c Normal file
View File

@@ -0,0 +1,869 @@
/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* Text properties implementation.
*
* Text properties are attached to the text. They move with the text when
* text is inserted/deleted.
*
* Text properties have a user specified ID number, which can be unique.
* Text properties have a type, which can be used to specify highlighting.
*
* TODO:
* - Add an arrray for global_proptypes, to quickly lookup a proptype by ID
* - Add an arrray for b_proptypes, to quickly lookup a proptype by ID
* - adjust property column when text is inserted/deleted
* - support properties that continue over a line break
* - add mechanism to keep track of changed lines.
*/
#include "vim.h"
#if defined(FEAT_TEXT_PROP) || defined(PROTO)
/*
* In a hashtable item "hi_key" points to "pt_name" in a proptype_T.
* This avoids adding a pointer to the hashtable item.
* PT2HIKEY() converts a proptype pointer to a hashitem key pointer.
* HIKEY2PT() converts a hashitem key pointer to a proptype pointer.
* HI2PT() converts a hashitem pointer to a proptype pointer.
*/
#define PT2HIKEY(p) ((p)->pt_name)
#define HIKEY2PT(p) ((proptype_T *)((p) - offsetof(proptype_T, pt_name)))
#define HI2PT(hi) HIKEY2PT((hi)->hi_key)
// The global text property types.
static hashtab_T *global_proptypes = NULL;
// The last used text property type ID.
static int proptype_id = 0;
static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
/*
* Find a property type by name, return the hashitem.
* Returns NULL if the item can't be found.
*/
static hashitem_T *
find_prop_hi(char_u *name, buf_T *buf)
{
hashtab_T *ht;
hashitem_T *hi;
if (*name == NUL)
return NULL;
if (buf == NULL)
ht = global_proptypes;
else
ht = buf->b_proptypes;
if (ht == NULL)
return NULL;
hi = hash_find(ht, name);
if (HASHITEM_EMPTY(hi))
return NULL;
return hi;
}
/*
* Like find_prop_hi() but return the property type.
*/
static proptype_T *
find_prop(char_u *name, buf_T *buf)
{
hashitem_T *hi = find_prop_hi(name, buf);
if (hi == NULL)
return NULL;
return HI2PT(hi);
}
/*
* Lookup a property type by name. First in "buf" and when not found in the
* global types.
* When not found gives an error message and returns NULL.
*/
static proptype_T *
lookup_prop_type(char_u *name, buf_T *buf)
{
proptype_T *type = find_prop(name, buf);
if (type == NULL)
type = find_prop(name, NULL);
if (type == NULL)
EMSG2(_(e_type_not_exist), name);
return type;
}
/*
* Get an optional "bufnr" item from the dict in "arg".
* When the argument is not used or "bufnr" is not present then "buf" is
* unchanged.
* If "bufnr" is valid or not present return OK.
* When "arg" is not a dict or "bufnr" is invalide return FAIL.
*/
static int
get_bufnr_from_arg(typval_T *arg, buf_T **buf)
{
dictitem_T *di;
if (arg->v_type != VAR_DICT)
{
EMSG(_(e_dictreq));
return FAIL;
}
if (arg->vval.v_dict == NULL)
return OK; // NULL dict is like an empty dict
di = dict_find(arg->vval.v_dict, (char_u *)"bufnr", -1);
if (di != NULL)
{
*buf = get_buf_tv(&di->di_tv, FALSE);
if (*buf == NULL)
return FAIL;
}
return OK;
}
/*
* prop_add({lnum}, {col}, {props})
*/
void
f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
{
linenr_T lnum;
colnr_T col;
dict_T *dict;
colnr_T length = 1;
char_u *type_name;
proptype_T *type;
buf_T *buf = curbuf;
int id = 0;
char_u *newtext;
int proplen;
size_t textlen;
char_u *props;
char_u *newprops;
static textprop_T tmp_prop; // static to get it aligned.
int i;
lnum = get_tv_number(&argvars[0]);
col = get_tv_number(&argvars[1]);
if (col < 1)
{
EMSGN(_(e_invalid_col), (long)col);
return;
}
if (argvars[2].v_type != VAR_DICT)
{
EMSG(_(e_dictreq));
return;
}
dict = argvars[2].vval.v_dict;
if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL)
{
EMSG(_("E965: missing property type name"));
return;
}
type_name = get_dict_string(dict, (char_u *)"type", FALSE);
if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
{
// TODO: handle end_lnum
EMSG("Sorry, end_lnum not supported yet");
return;
}
if (dict_find(dict, (char_u *)"length", -1) != NULL)
length = get_dict_number(dict, (char_u *)"length");
else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
{
length = get_dict_number(dict, (char_u *)"end_col") - col;
if (length <= 0)
{
EMSG2(_(e_invargval), "end_col");
return;
}
}
if (dict_find(dict, (char_u *)"id", -1) != NULL)
id = get_dict_number(dict, (char_u *)"id");
if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
return;
type = lookup_prop_type(type_name, buf);
if (type == NULL)
return;
if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
{
EMSGN(_("E966: Invalid line number: %ld"), (long)lnum);
return;
}
// Fetch the line to get the ml_line_len field updated.
proplen = get_text_props(buf, lnum, &props, TRUE);
if (col >= (colnr_T)STRLEN(buf->b_ml.ml_line_ptr))
{
EMSGN(_(e_invalid_col), (long)col);
return;
}
// Allocate the new line with space for the new proprety.
newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
if (newtext == NULL)
return;
// Copy the text, including terminating NUL.
textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
// Find the index where to insert the new property.
// Since the text properties are not aligned properly when stored with the
// text, we need to copy them as bytes before using it as a struct.
for (i = 0; i < proplen; ++i)
{
mch_memmove(&tmp_prop, props + i * sizeof(proptype_T),
sizeof(proptype_T));
if (tmp_prop.tp_col >= col)
break;
}
newprops = newtext + textlen;
if (i > 0)
mch_memmove(newprops, props, sizeof(textprop_T) * i);
tmp_prop.tp_col = col;
tmp_prop.tp_len = length;
tmp_prop.tp_id = id;
tmp_prop.tp_type = type->pt_id;
tmp_prop.tp_flags = 0;
mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
sizeof(textprop_T));
if (i < proplen)
mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
props + i * sizeof(textprop_T),
sizeof(textprop_T) * (proplen - i));
if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
vim_free(buf->b_ml.ml_line_ptr);
buf->b_ml.ml_line_ptr = newtext;
buf->b_ml.ml_line_len += sizeof(textprop_T);
buf->b_ml.ml_flags |= ML_LINE_DIRTY;
redraw_buf_later(buf, NOT_VALID);
}
/*
* Return TRUE if any text properties are defined globally or for buffer
* 'buf".
*/
int
has_any_text_properties(buf_T *buf)
{
return buf->b_proptypes != NULL || global_proptypes != NULL;
}
/*
* Fetch the text properties for line "lnum" in buffer 'buf".
* Returns the number of text properties and, when non-zero, a pointer to the
* first one in "props" (note that it is not aligned, therefore the char_u
* pointer).
*/
int
get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change)
{
char_u *text;
size_t textlen;
size_t proplen;
// Be quick when no text property types are defined.
if (!has_any_text_properties(buf))
return 0;
// Fetch the line to get the ml_line_len field updated.
text = ml_get_buf(buf, lnum, will_change);
textlen = STRLEN(text) + 1;
proplen = buf->b_ml.ml_line_len - textlen;
if (proplen % sizeof(textprop_T) != 0)
{
IEMSG(_("E967: text property info corrupted"));
return 0;
}
if (proplen > 0)
*props = text + textlen;
return proplen / sizeof(textprop_T);
}
static proptype_T *
find_type_by_id(hashtab_T *ht, int id)
{
long todo;
hashitem_T *hi;
if (ht == NULL)
return NULL;
// TODO: Make this faster by keeping a list of types sorted on ID and use
// a binary search.
todo = (long)ht->ht_used;
for (hi = ht->ht_array; todo > 0; ++hi)
{
if (!HASHITEM_EMPTY(hi))
{
proptype_T *prop = HI2PT(hi);
if (prop->pt_id == id)
return prop;
--todo;
}
}
return NULL;
}
/*
* Find a property type by ID in "buf" or globally.
* Returns NULL if not found.
*/
proptype_T *
text_prop_type_by_id(buf_T *buf, int id)
{
proptype_T *type;
type = find_type_by_id(buf->b_proptypes, id);
if (type == NULL)
type = find_type_by_id(global_proptypes, id);
return type;
}
/*
* prop_clear({lnum} [, {lnum_end} [, {bufnr}]])
*/
void
f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED)
{
linenr_T start = get_tv_number(&argvars[0]);
linenr_T end = start;
linenr_T lnum;
buf_T *buf = curbuf;
if (argvars[1].v_type != VAR_UNKNOWN)
{
end = get_tv_number(&argvars[1]);
if (argvars[2].v_type != VAR_UNKNOWN)
{
if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
return;
}
}
if (start < 1 || end < 1)
{
EMSG(_(e_invrange));
return;
}
for (lnum = start; lnum <= end; ++lnum)
{
char_u *text;
size_t len;
if (lnum > buf->b_ml.ml_line_count)
break;
text = ml_get_buf(buf, lnum, FALSE);
len = STRLEN(text) + 1;
if ((size_t)buf->b_ml.ml_line_len > len)
{
if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
{
char_u *newtext = vim_strsave(text);
// need to allocate the line now
if (newtext == NULL)
return;
buf->b_ml.ml_line_ptr = newtext;
buf->b_ml.ml_flags |= ML_LINE_DIRTY;
}
buf->b_ml.ml_line_len = len;
}
}
redraw_buf_later(buf, NOT_VALID);
}
/*
* prop_list({lnum} [, {bufnr}])
*/
void
f_prop_list(typval_T *argvars, typval_T *rettv)
{
linenr_T lnum = get_tv_number(&argvars[0]);
buf_T *buf = curbuf;
if (argvars[1].v_type != VAR_UNKNOWN)
{
if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
return;
}
if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
{
EMSG(_(e_invrange));
return;
}
if (rettv_list_alloc(rettv) == OK)
{
char_u *text = ml_get_buf(buf, lnum, FALSE);
size_t textlen = STRLEN(text) + 1;
int count = (buf->b_ml.ml_line_len - textlen)
/ sizeof(textprop_T);
int i;
textprop_T prop;
proptype_T *pt;
for (i = 0; i < count; ++i)
{
dict_T *d = dict_alloc();
if (d == NULL)
break;
mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
sizeof(textprop_T));
dict_add_number(d, "col", prop.tp_col);
dict_add_number(d, "length", prop.tp_len);
dict_add_number(d, "id", prop.tp_id);
dict_add_number(d, "start", !(prop.tp_flags & TP_FLAG_CONT_PREV));
dict_add_number(d, "end", !(prop.tp_flags & TP_FLAG_CONT_NEXT));
pt = text_prop_type_by_id(buf, prop.tp_type);
if (pt != NULL)
dict_add_string(d, "type", pt->pt_name);
list_append_dict(rettv->vval.v_list, d);
}
}
}
/*
* prop_remove({props} [, {lnum} [, {lnum_end}]])
*/
void
f_prop_remove(typval_T *argvars, typval_T *rettv)
{
linenr_T start = 1;
linenr_T end = 0;
linenr_T lnum;
dict_T *dict;
buf_T *buf = curbuf;
dictitem_T *di;
int do_all = FALSE;
int id = -1;
int type_id = -1;
rettv->vval.v_number = 0;
if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
{
EMSG(_(e_invarg));
return;
}
if (argvars[1].v_type != VAR_UNKNOWN)
{
start = get_tv_number(&argvars[1]);
end = start;
if (argvars[2].v_type != VAR_UNKNOWN)
end = get_tv_number(&argvars[2]);
if (start < 1 || end < 1)
{
EMSG(_(e_invrange));
return;
}
}
dict = argvars[0].vval.v_dict;
di = dict_find(dict, (char_u *)"bufnr", -1);
if (di != NULL)
{
buf = get_buf_tv(&di->di_tv, FALSE);
if (buf == NULL)
return;
}
di = dict_find(dict, (char_u*)"all", -1);
if (di != NULL)
do_all = get_dict_number(dict, (char_u *)"all");
if (dict_find(dict, (char_u *)"id", -1) != NULL)
id = get_dict_number(dict, (char_u *)"id");
if (dict_find(dict, (char_u *)"type", -1))
{
char_u *name = get_dict_string(dict, (char_u *)"type", FALSE);
proptype_T *type = lookup_prop_type(name, buf);
if (type == NULL)
return;
type_id = type->pt_id;
}
if (id == -1 && type_id == -1)
{
EMSG(_("E968: Need at least one of 'id' or 'type'"));
return;
}
if (end == 0)
end = buf->b_ml.ml_line_count;
for (lnum = start; lnum <= end; ++lnum)
{
char_u *text;
size_t len;
if (lnum > buf->b_ml.ml_line_count)
break;
text = ml_get_buf(buf, lnum, FALSE);
len = STRLEN(text) + 1;
if ((size_t)buf->b_ml.ml_line_len > len)
{
static textprop_T textprop; // static because of alignment
unsigned idx;
for (idx = 0; idx < (buf->b_ml.ml_line_len - len)
/ sizeof(textprop_T); ++idx)
{
char_u *cur_prop = buf->b_ml.ml_line_ptr + len
+ idx * sizeof(textprop_T);
size_t taillen;
mch_memmove(&textprop, cur_prop, sizeof(textprop_T));
if (textprop.tp_id == id || textprop.tp_type == type_id)
{
if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
{
char_u *newptr = alloc(buf->b_ml.ml_line_len);
// need to allocate the line to be able to change it
if (newptr == NULL)
return;
mch_memmove(newptr, buf->b_ml.ml_line_ptr,
buf->b_ml.ml_line_len);
buf->b_ml.ml_line_ptr = newptr;
curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
}
taillen = buf->b_ml.ml_line_len - len
- (idx + 1) * sizeof(textprop_T);
if (taillen > 0)
mch_memmove(cur_prop, cur_prop + sizeof(textprop_T),
taillen);
buf->b_ml.ml_line_len -= sizeof(textprop_T);
--idx;
++rettv->vval.v_number;
if (!do_all)
break;
}
}
}
}
redraw_buf_later(buf, NOT_VALID);
}
/*
* Common for f_prop_type_add() and f_prop_type_change().
*/
void
prop_type_set(typval_T *argvars, int add)
{
char_u *name;
buf_T *buf = NULL;
dict_T *dict;
dictitem_T *di;
proptype_T *prop;
name = get_tv_string(&argvars[0]);
if (*name == NUL)
{
EMSG(_(e_invarg));
return;
}
if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
return;
dict = argvars[1].vval.v_dict;
prop = find_prop(name, buf);
if (add)
{
hashtab_T **htp;
if (prop != NULL)
{
EMSG2(_("E969: Property type %s already defined"), name);
return;
}
prop = (proptype_T *)alloc_clear(sizeof(proptype_T) + STRLEN(name));
if (prop == NULL)
return;
STRCPY(prop->pt_name, name);
prop->pt_id = ++proptype_id;
htp = buf == NULL ? &global_proptypes : &buf->b_proptypes;
if (*htp == NULL)
{
*htp = (hashtab_T *)alloc(sizeof(hashtab_T));
if (*htp == NULL)
return;
hash_init(*htp);
}
hash_add(buf == NULL ? global_proptypes : buf->b_proptypes,
PT2HIKEY(prop));
}
else
{
if (prop == NULL)
{
EMSG2(_(e_type_not_exist), name);
return;
}
}
if (dict != NULL)
{
di = dict_find(dict, (char_u *)"highlight", -1);
if (di != NULL)
{
char_u *highlight;
int hl_id = 0;
highlight = get_dict_string(dict, (char_u *)"highlight", TRUE);
if (highlight != NULL && *highlight != NUL)
hl_id = syn_name2id(highlight);
if (hl_id <= 0)
{
EMSG2(_("E970: Unknown highlight group name: '%s'"),
highlight == NULL ? (char_u *)"" : highlight);
return;
}
prop->pt_hl_id = hl_id;
}
di = dict_find(dict, (char_u *)"priority", -1);
if (di != NULL)
prop->pt_priority = get_tv_number(&di->di_tv);
di = dict_find(dict, (char_u *)"start_incl", -1);
if (di != NULL)
{
if (get_tv_number(&di->di_tv))
prop->pt_flags |= PT_FLAG_INS_START_INCL;
else
prop->pt_flags &= ~PT_FLAG_INS_START_INCL;
}
di = dict_find(dict, (char_u *)"end_incl", -1);
if (di != NULL)
{
if (get_tv_number(&di->di_tv))
prop->pt_flags |= PT_FLAG_INS_END_INCL;
else
prop->pt_flags &= ~PT_FLAG_INS_END_INCL;
}
}
}
/*
* prop_type_add({name}, {props})
*/
void
f_prop_type_add(typval_T *argvars, typval_T *rettv UNUSED)
{
prop_type_set(argvars, TRUE);
}
/*
* prop_type_change({name}, {props})
*/
void
f_prop_type_change(typval_T *argvars, typval_T *rettv UNUSED)
{
prop_type_set(argvars, FALSE);
}
/*
* prop_type_delete({name} [, {bufnr}])
*/
void
f_prop_type_delete(typval_T *argvars, typval_T *rettv UNUSED)
{
char_u *name;
buf_T *buf = NULL;
hashitem_T *hi;
name = get_tv_string(&argvars[0]);
if (*name == NUL)
{
EMSG(_(e_invarg));
return;
}
if (argvars[1].v_type != VAR_UNKNOWN)
{
if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
return;
}
hi = find_prop_hi(name, buf);
if (hi != NULL)
{
hashtab_T *ht;
if (buf == NULL)
ht = global_proptypes;
else
ht = buf->b_proptypes;
hash_remove(ht, hi);
}
}
/*
* prop_type_get({name} [, {bufnr}])
*/
void
f_prop_type_get(typval_T *argvars, typval_T *rettv UNUSED)
{
char_u *name = get_tv_string(&argvars[0]);
if (*name == NUL)
{
EMSG(_(e_invarg));
return;
}
if (rettv_dict_alloc(rettv) == OK)
{
proptype_T *prop = NULL;
buf_T *buf = NULL;
if (argvars[1].v_type != VAR_UNKNOWN)
{
if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
return;
}
prop = find_prop(name, buf);
if (prop != NULL)
{
dict_T *d = rettv->vval.v_dict;
if (prop->pt_hl_id > 0)
dict_add_string(d, "highlight", syn_id2name(prop->pt_hl_id));
dict_add_number(d, "priority", prop->pt_priority);
dict_add_number(d, "start_incl",
(prop->pt_flags & PT_FLAG_INS_START_INCL) ? 1 : 0);
dict_add_number(d, "end_incl",
(prop->pt_flags & PT_FLAG_INS_END_INCL) ? 1 : 0);
if (buf != NULL)
dict_add_number(d, "bufnr", buf->b_fnum);
}
}
}
static void
list_types(hashtab_T *ht, list_T *l)
{
long todo;
hashitem_T *hi;
todo = (long)ht->ht_used;
for (hi = ht->ht_array; todo > 0; ++hi)
{
if (!HASHITEM_EMPTY(hi))
{
proptype_T *prop = HI2PT(hi);
list_append_string(l, prop->pt_name, -1);
--todo;
}
}
}
/*
* prop_type_list([{bufnr}])
*/
void
f_prop_type_list(typval_T *argvars, typval_T *rettv UNUSED)
{
buf_T *buf = NULL;
if (rettv_list_alloc(rettv) == OK)
{
if (argvars[0].v_type != VAR_UNKNOWN)
{
if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
return;
}
if (buf == NULL)
{
if (global_proptypes != NULL)
list_types(global_proptypes, rettv->vval.v_list);
}
else if (buf->b_proptypes != NULL)
list_types(buf->b_proptypes, rettv->vval.v_list);
}
}
/*
* Free all property types in "ht".
*/
static void
clear_ht_prop_types(hashtab_T *ht)
{
long todo;
hashitem_T *hi;
if (ht == NULL)
return;
todo = (long)ht->ht_used;
for (hi = ht->ht_array; todo > 0; ++hi)
{
if (!HASHITEM_EMPTY(hi))
{
proptype_T *prop = HI2PT(hi);
vim_free(prop);
--todo;
}
}
hash_clear(ht);
vim_free(ht);
}
#if defined(EXITFREE) || defined(PROTO)
/*
* Free all property types for "buf".
*/
void
clear_global_prop_types(void)
{
clear_ht_prop_types(global_proptypes);
global_proptypes = NULL;
}
#endif
/*
* Free all property types for "buf".
*/
void
clear_buf_prop_types(buf_T *buf)
{
clear_ht_prop_types(buf->b_proptypes);
buf->b_proptypes = NULL;
}
#endif // FEAT_TEXT_PROP

View File

@@ -25,7 +25,7 @@
/* From user function to hashitem and back. */ /* From user function to hashitem and back. */
#define UF2HIKEY(fp) ((fp)->uf_name) #define UF2HIKEY(fp) ((fp)->uf_name)
#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) #define HIKEY2UF(p) ((ufunc_T *)((p) - offsetof(ufunc_T, uf_name)))
#define HI2UF(hi) HIKEY2UF((hi)->hi_key) #define HI2UF(hi) HIKEY2UF((hi)->hi_key)
#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]

View File

@@ -653,12 +653,6 @@ static char *(features[]) =
# else # else
"-terminfo", "-terminfo",
# endif # endif
#else /* unix always includes termcap support */
# ifdef HAVE_TGETENT
"+tgetent",
# else
"-tgetent",
# endif
#endif #endif
#ifdef FEAT_TERMRESPONSE #ifdef FEAT_TERMRESPONSE
"+termresponse", "+termresponse",
@@ -670,6 +664,19 @@ static char *(features[]) =
#else #else
"-textobjects", "-textobjects",
#endif #endif
#ifdef FEAT_TEXT_PROP
"+textprop",
#else
"-textprop",
#endif
#if !defined(UNIX)
/* unix always includes termcap support */
# ifdef HAVE_TGETENT
"+tgetent",
# else
"-tgetent",
# endif
#endif
#ifdef FEAT_TIMERS #ifdef FEAT_TIMERS
"+timers", "+timers",
#else #else
@@ -792,6 +799,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
579,
/**/ /**/
578, 578,
/**/ /**/