To: vim_dev@googlegroups.com Subject: Patch 8.1.0580 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.1.0580 Problem: Cannot attach properties to text. Solution: First part of adding text properties. Files: Filelist, runtime/doc/Makefile, runtime/doc/eval.txt, runtime/doc/textprop.txt, src/Make_all.mak, src/Make_cyg_ming.mak, src/Make_mvc.mak, src/Makefile, src/buffer.c, src/edit.c, src/evalfunc.c, src/feature.h, src/memline.c, src/misc1.c, src/misc2.c, src/proto.h, src/proto/memline.pro, src/proto/textprop.pro, src/screen.c, src/structs.h, src/testdir/Make_all.mak, src/testdir/test_textprop.vim, src/textprop.c, src/userfunc.c, src/version.c *** ../vim-8.1.0578/Filelist 2018-10-25 16:52:46.839887739 +0200 --- Filelist 2018-12-13 19:59:08.647713430 +0100 *************** *** 91,96 **** --- 91,97 ---- src/terminal.c \ src/term.h \ src/termlib.c \ + src/textprop.c \ src/ui.c \ src/undo.c \ src/userfunc.c \ *************** *** 198,203 **** --- 199,205 ---- src/proto/term.pro \ src/proto/terminal.pro \ src/proto/termlib.pro \ + src/proto/textprop.pro \ src/proto/ui.pro \ src/proto/undo.pro \ src/proto/userfunc.pro \ *** ../vim-8.1.0578/runtime/doc/Makefile 2017-11-24 20:49:13.000000000 +0100 --- runtime/doc/Makefile 2018-12-13 20:01:21.774863565 +0100 *************** *** 102,107 **** --- 102,108 ---- tagsrch.txt \ term.txt \ terminal.txt \ + textprop.txt \ tips.txt \ todo.txt \ uganda.txt \ *************** *** 238,243 **** --- 239,245 ---- tagsrch.html \ term.html \ terminal.html \ + textprop.html \ tips.html \ todo.html \ uganda.html \ *** ../vim-8.1.0578/runtime/doc/eval.txt 2018-11-22 03:07:30.940596247 +0100 --- runtime/doc/eval.txt 2018-12-13 22:11:02.636894993 +0100 *************** *** 2300,2309 **** pow({x}, {y}) Float {x} to the power of {y} prevnonblank({lnum}) Number line nr of non-blank line <= {lnum} printf({fmt}, {expr1}...) String format text - prompt_addtext({buf}, {expr}) none add text to a prompt buffer prompt_setcallback({buf}, {expr}) none set prompt callback function prompt_setinterrupt({buf}, {text}) none set prompt interrupt function prompt_setprompt({buf}, {text}) none set prompt text pumvisible() Number whether popup menu is visible pyeval({expr}) any evaluate |Python| expression py3eval({expr}) any evaluate |python3| expression --- 2314,2338 ---- pow({x}, {y}) Float {x} to the power of {y} prevnonblank({lnum}) Number line nr of non-blank line <= {lnum} printf({fmt}, {expr1}...) String format text prompt_setcallback({buf}, {expr}) none set prompt callback function prompt_setinterrupt({buf}, {text}) none set prompt interrupt function 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 pyeval({expr}) any evaluate |Python| expression py3eval({expr}) any evaluate |python3| expression *************** *** 6642,6647 **** --- 6673,6863 ---- The result is only visible if {buf} has 'buftype' set to "prompt". Example: > 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()* *** ../vim-8.1.0578/runtime/doc/textprop.txt 2018-12-13 22:11:43.932591369 +0100 --- runtime/doc/textprop.txt 2018-12-13 21:59:47.042285684 +0100 *************** *** 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: *** ../vim-8.1.0578/src/Make_all.mak 2018-11-30 22:40:09.098211991 +0100 --- src/Make_all.mak 2018-12-13 20:05:00.737457532 +0100 *************** *** 186,191 **** --- 186,192 ---- test_terminal_fail \ test_textformat \ test_textobjects \ + test_textprop \ test_timers \ test_true_false \ test_undo \ *** ../vim-8.1.0578/src/Make_cyg_ming.mak 2018-10-21 22:45:39.117688669 +0200 --- src/Make_cyg_ming.mak 2018-12-13 20:06:02.481059654 +0100 *************** *** 751,756 **** --- 751,757 ---- $(OUTDIR)/syntax.o \ $(OUTDIR)/tag.o \ $(OUTDIR)/term.o \ + $(OUTDIR)/textprop.o \ $(OUTDIR)/ui.o \ $(OUTDIR)/undo.o \ $(OUTDIR)/userfunc.o \ *************** *** 1090,1095 **** --- 1091,1099 ---- $(OUTDIR)/terminal.o: terminal.c $(INCL) $(TERM_DEPS) $(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="" \ -DVSNPRINTF=vim_vsnprintf \ *** ../vim-8.1.0578/src/Make_mvc.mak 2018-11-12 21:42:20.678152930 +0100 --- src/Make_mvc.mak 2018-12-13 20:07:31.472486033 +0100 *************** *** 754,759 **** --- 754,760 ---- $(OUTDIR)\syntax.obj \ $(OUTDIR)\tag.obj \ $(OUTDIR)\term.obj \ + $(OUTDIR)\textprop.obj \ $(OUTDIR)\ui.obj \ $(OUTDIR)\undo.obj \ $(OUTDIR)\userfunc.obj \ *************** *** 1529,1534 **** --- 1530,1537 ---- $(OUTDIR)/term.obj: $(OUTDIR) term.c $(INCL) + $(OUTDIR)/textprop.obj: $(OUTDIR) textprop.c $(INCL) + $(OUTDIR)/ui.obj: $(OUTDIR) ui.c $(INCL) $(OUTDIR)/undo.obj: $(OUTDIR) undo.c $(INCL) *************** *** 1667,1672 **** --- 1670,1676 ---- proto/syntax.pro \ proto/tag.pro \ proto/term.pro \ + proto/textprop.pro \ proto/ui.pro \ proto/undo.pro \ proto/userfunc.pro \ *** ../vim-8.1.0578/src/Makefile 2018-12-12 20:34:06.072356129 +0100 --- src/Makefile 2018-12-13 20:08:00.680336028 +0100 *************** *** 1577,1584 **** # TAGS_INCL: include files used for make tags # ALL_SRC: source files used for make depend and make lint - TAGS_INCL = *.h - BASIC_SRC = \ arabic.c \ beval.c \ --- 1579,1584 ---- *************** *** 1636,1641 **** --- 1636,1642 ---- tag.c \ term.c \ terminal.c \ + textprop.c \ ui.c \ undo.c \ userfunc.c \ *************** *** 1657,1663 **** $(WORKSHOP_SRC) \ $(WSDEBUG_SRC) ! TAGS_SRC = *.c *.cpp if_perl.xs 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 \ --- 1658,1665 ---- $(WORKSHOP_SRC) \ $(WSDEBUG_SRC) ! 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 \ if_python.c if_python3.c if_tcl.c if_ruby.c \ *************** *** 1747,1752 **** --- 1749,1755 ---- objects/tag.o \ objects/term.o \ objects/terminal.o \ + objects/textprop.o \ objects/ui.o \ objects/undo.o \ objects/userfunc.o \ *************** *** 1881,1886 **** --- 1884,1890 ---- term.pro \ terminal.pro \ termlib.pro \ + textprop.pro \ ui.pro \ undo.pro \ userfunc.pro \ *************** *** 2092,2098 **** # Motif and Athena GUI # You can ignore error messages for missing files. tags TAGS: notags ! $(TAGPRG) $(TAGS_SRC) $(TAGS_INCL) $(TERM_SRC) $(TERM_DEPS) # Make a highlight file for types. Requires Exuberant ctags and awk types: types.vim --- 2096,2102 ---- # Motif and Athena GUI # You can ignore error messages for missing files. tags TAGS: notags ! $(TAGPRG) $(TAGS_SRC) $(TAGS_INCL) # Make a highlight file for types. Requires Exuberant ctags and awk types: types.vim *************** *** 3211,3216 **** --- 3215,3223 ---- objects/terminal.o: terminal.c $(TERM_DEPS) $(CCC) -o $@ terminal.c + objects/textprop.o: textprop.c + $(CCC) -o $@ textprop.c + objects/ui.o: ui.c $(CCC) -o $@ ui.c *************** *** 3602,3607 **** --- 3609,3618 ---- 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 \ 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 \ 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 \ *** ../vim-8.1.0578/src/buffer.c 2018-11-20 13:32:30.276983764 +0100 --- src/buffer.c 2018-12-13 20:09:44.819785821 +0100 *************** *** 823,828 **** --- 823,831 ---- #ifdef FEAT_SYN_HL syntax_clear(&buf->b_s); /* reset syntax info */ #endif + #ifdef FEAT_TEXT_PROP + clear_buf_prop_types(buf); + #endif buf->b_flags &= ~BF_READERR; /* a read error is no longer relevant */ } *** ../vim-8.1.0578/src/edit.c 2018-11-22 03:07:30.944596219 +0100 --- src/edit.c 2018-12-13 20:09:58.939709543 +0100 *************** *** 10302,10307 **** --- 10302,10310 ---- if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) for (temp = i; --temp >= 0; ) replace_join(repl_off); + #ifdef FEAT_TEXT_PROP + curbuf->b_ml.ml_line_len -= i; + #endif } #ifdef FEAT_NETBEANS_INTG if (netbeans_active()) *** ../vim-8.1.0578/src/evalfunc.c 2018-12-08 13:57:38.553692769 +0100 --- src/evalfunc.c 2018-12-13 22:04:35.855854438 +0100 *************** *** 772,777 **** --- 772,788 ---- {"prompt_setinterrupt", 2, 2, f_prompt_setinterrupt}, {"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 {"pumvisible", 0, 0, f_pumvisible}, #ifdef FEAT_PYTHON3 {"py3eval", 1, 1, f_py3eval}, *************** *** 6478,6483 **** --- 6489,6497 ---- #ifdef FEAT_TEXTOBJ "textobjects", #endif + #ifdef FEAT_TEXT_PROP + "textprop", + #endif #ifdef HAVE_TGETENT "tgetent", #endif *** ../vim-8.1.0578/src/ex_getln.c 2018-11-30 21:57:50.719861887 +0100 --- src/ex_getln.c 2018-12-12 22:31:57.398336802 +0100 *************** *** 769,774 **** --- 769,789 ---- stuffcharReadbuff(*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; } } *** ../vim-8.1.0578/src/feature.h 2018-12-12 20:34:06.072356129 +0100 --- src/feature.h 2018-12-13 20:12:33.566852272 +0100 *************** *** 502,507 **** --- 502,514 ---- #endif /* + * +textprop Text properties + */ + #if defined(FEAT_EVAL) && defined(FEAT_SYN_HL) + # define FEAT_TEXT_PROP + #endif + + /* * +spell spell checking * * Disabled for EBCDIC: * Doesn't work (SIGSEGV). *** ../vim-8.1.0578/src/memline.c 2018-10-30 22:15:51.591158966 +0100 --- src/memline.c 2018-12-13 20:17:24.645156343 +0100 *************** *** 2487,2493 **** { bhdr_T *hp; DATA_BL *dp; - char_u *ptr; static int recursive = 0; if (lnum > buf->b_ml.ml_line_count) /* invalid line number */ --- 2487,2492 ---- *************** *** 2518,2523 **** --- 2517,2526 ---- */ if (buf->b_ml.ml_line_lnum != lnum || mf_dont_release) { + unsigned start, end; + colnr_T len; + int idx; + ml_flush_line(buf); /* *************** *** 2540,2547 **** dp = (DATA_BL *)(hp->bh_data); ! ptr = (char_u *)dp + ((dp->db_index[lnum - buf->b_ml.ml_locked_low]) & DB_INDEX_MASK); ! buf->b_ml.ml_line_ptr = ptr; buf->b_ml.ml_line_lnum = lnum; buf->b_ml.ml_flags &= ~ML_LINE_DIRTY; } --- 2543,2560 ---- dp = (DATA_BL *)(hp->bh_data); ! idx = lnum - buf->b_ml.ml_locked_low; ! 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_flags &= ~ML_LINE_DIRTY; } *************** *** 2614,2633 **** static int ml_append_int( buf_T *buf, ! linenr_T lnum, /* append after this line (can be 0) */ ! char_u *line, /* text of the new line */ ! colnr_T len, /* length of line, including NUL, or 0 */ ! int newfile, /* flag, see above */ ! int mark) /* mark the new line */ { int i; ! int line_count; /* number of indexes in current block */ int offset; int from, to; ! int space_needed; /* space needed for new line */ int page_size; int page_count; ! int db_idx; /* index for lnum in data block */ bhdr_T *hp; memfile_T *mfp; DATA_BL *dp; --- 2627,2647 ---- static int ml_append_int( buf_T *buf, ! linenr_T lnum, // append after this line (can be 0) ! char_u *line, // text of the new line ! colnr_T len_arg, // length of line, including NUL, or 0 ! int newfile, // flag, see above ! int mark) // mark the new line { + colnr_T len = len_arg; // length of line, including NUL, or 0 int i; ! int line_count; // number of indexes in current block int offset; int from, to; ! int space_needed; // space needed for new line int page_size; int page_count; ! int db_idx; // index for lnum in data block bhdr_T *hp; memfile_T *mfp; DATA_BL *dp; *************** *** 2642,2649 **** lowest_marked = lnum + 1; if (len == 0) ! len = (colnr_T)STRLEN(line) + 1; /* space needed for the text */ ! space_needed = len + INDEX_SIZE; /* space needed for text + index */ mfp = buf->b_ml.ml_mfp; page_size = mfp->mf_page_size; --- 2656,2663 ---- lowest_marked = lnum + 1; if (len == 0) ! len = (colnr_T)STRLEN(line) + 1; // space needed for the text ! space_needed = len + INDEX_SIZE; // space needed for text + index mfp = buf->b_ml.ml_mfp; page_size = mfp->mf_page_size; *************** *** 2728,2734 **** dp->db_index[i + 1] = dp->db_index[i] - len; dp->db_index[db_idx + 1] = offset - len; } ! else /* add line at the end */ dp->db_index[db_idx + 1] = dp->db_txt_start; /* --- 2742,2749 ---- dp->db_index[i + 1] = dp->db_index[i] - len; dp->db_index[db_idx + 1] = offset - len; } ! else ! // add line at the end (which is the start of the text) dp->db_index[db_idx + 1] = dp->db_txt_start; /* *************** *** 3128,3133 **** --- 3143,3161 ---- int 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... */ return FAIL; *************** *** 3135,3141 **** if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) return FAIL; ! if (copy && (line = vim_strsave(line)) == NULL) /* allocate memory */ return FAIL; #ifdef FEAT_NETBEANS_INTG if (netbeans_active()) --- 3163,3169 ---- if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) return FAIL; ! if (copy && (line = vim_strnsave(line, len)) == NULL) /* allocate memory */ return FAIL; #ifdef FEAT_NETBEANS_INTG if (netbeans_active()) *************** *** 3144,3154 **** netbeans_inserted(curbuf, lnum, 0, line, (int)STRLEN(line)); } #endif ! if (curbuf->b_ml.ml_line_lnum != lnum) /* other line buffered */ ! ml_flush_line(curbuf); /* flush it */ ! else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) /* same line allocated */ vim_free(curbuf->b_ml.ml_line_ptr); /* free it */ curbuf->b_ml.ml_line_ptr = line; curbuf->b_ml.ml_line_lnum = lnum; curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; --- 3172,3219 ---- netbeans_inserted(curbuf, lnum, 0, line, (int)STRLEN(line)); } #endif ! if (curbuf->b_ml.ml_line_lnum != lnum) ! { ! // 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 */ + 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_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; *************** *** 3490,3496 **** old_len = dp->db_txt_end - start; else /* text of previous line follows */ old_len = (dp->db_index[idx - 1] & DB_INDEX_MASK) - start; ! new_len = (colnr_T)STRLEN(new_line) + 1; extra = new_len - old_len; /* negative if lines gets smaller */ /* --- 3555,3561 ---- old_len = dp->db_txt_end - start; else /* text of previous line follows */ old_len = (dp->db_index[idx - 1] & DB_INDEX_MASK) - start; ! new_len = buf->b_ml.ml_line_len; extra = new_len - old_len; /* negative if lines gets smaller */ /* *************** *** 5009,5016 **** */ buf->b_ml.ml_usedchunks = 1; buf->b_ml.ml_chunksize[0].mlcs_numlines = 1; ! buf->b_ml.ml_chunksize[0].mlcs_totalsize = ! (long)STRLEN(buf->b_ml.ml_line_ptr) + 1; return; } --- 5074,5080 ---- */ buf->b_ml.ml_usedchunks = 1; buf->b_ml.ml_chunksize[0].mlcs_numlines = 1; ! buf->b_ml.ml_chunksize[0].mlcs_totalsize = (long)buf->b_ml.ml_line_len; return; } *** ../vim-8.1.0578/src/misc1.c 2018-12-09 15:00:47.985798600 +0100 --- src/misc1.c 2018-12-13 20:18:19.228829748 +0100 *************** *** 2631,2639 **** { char_u *oldp, *newp; colnr_T oldlen; linenr_T lnum = curwin->w_cursor.lnum; colnr_T col = curwin->w_cursor.col; ! int was_alloced; long movelen; int fixpos = fixpos_arg; --- 2631,2640 ---- { char_u *oldp, *newp; colnr_T oldlen; + colnr_T newlen; linenr_T lnum = curwin->w_cursor.lnum; colnr_T col = curwin->w_cursor.col; ! int alloc_newp; long movelen; int fixpos = fixpos_arg; *************** *** 2710,2715 **** --- 2711,2717 ---- count = oldlen - col; movelen = 1; } + newlen = oldlen - count; /* * If the old line has been allocated the deletion can be done in the *************** *** 2720,2743 **** */ #ifdef FEAT_NETBEANS_INTG if (netbeans_active()) ! was_alloced = FALSE; else #endif ! was_alloced = ml_line_alloced(); /* check if oldp was allocated */ ! if (was_alloced) ! newp = oldp; /* use same allocated memory */ else ! { /* need to allocate a new line */ ! newp = alloc((unsigned)(oldlen + 1 - count)); if (newp == NULL) return FAIL; mch_memmove(newp, oldp, (size_t)col); } mch_memmove(newp + col, oldp + col + count, (size_t)movelen); ! if (!was_alloced) ml_replace(lnum, newp, FALSE); ! /* mark the buffer as changed and prepare for displaying */ changed_bytes(lnum, curwin->w_cursor.col); return OK; --- 2722,2755 ---- */ #ifdef FEAT_NETBEANS_INTG if (netbeans_active()) ! alloc_newp = TRUE; else #endif ! alloc_newp = !ml_line_alloced(); // check if oldp was allocated ! if (!alloc_newp) ! newp = oldp; // use same allocated memory else ! { // need to allocate a new line ! newp = alloc((unsigned)(newlen + 1)); if (newp == NULL) return FAIL; mch_memmove(newp, oldp, (size_t)col); } mch_memmove(newp + col, oldp + col + count, (size_t)movelen); ! if (alloc_newp) 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 changed_bytes(lnum, curwin->w_cursor.col); return OK; *** ../vim-8.1.0578/src/misc2.c 2018-11-11 15:20:32.436704418 +0100 --- src/misc2.c 2018-12-13 20:19:36.988360977 +0100 *************** *** 1191,1196 **** --- 1191,1199 ---- # ifdef FEAT_CMDHIST init_history(); # endif + #ifdef FEAT_TEXT_PROP + clear_global_prop_types(); + #endif #ifdef FEAT_QUICKFIX { *** ../vim-8.1.0578/src/proto.h 2018-07-01 16:43:59.850736541 +0200 --- src/proto.h 2018-12-13 20:19:51.208274841 +0100 *************** *** 183,188 **** --- 183,191 ---- # if defined(HAVE_TGETENT) && (defined(AMIGA) || defined(VMS)) # include "termlib.pro" # endif + # ifdef FEAT_TEXT_PROP + # include "textprop.pro" + # endif # include "ui.pro" # include "undo.pro" # include "userfunc.pro" *** ../vim-8.1.0578/src/proto/memline.pro 2018-08-21 20:28:49.888006612 +0200 --- src/proto/memline.pro 2018-12-13 20:20:02.860204177 +0100 *************** *** 24,29 **** --- 24,30 ---- 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_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); void ml_setmarked(linenr_T lnum); linenr_T ml_firstmarked(void); *** ../vim-8.1.0578/src/proto/textprop.pro 2018-12-13 22:11:44.012590783 +0100 --- src/proto/textprop.pro 2018-12-13 20:25:39.330136512 +0100 *************** *** 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 : */ *** ../vim-8.1.0578/src/screen.c 2018-11-16 16:21:01.633310065 +0100 --- src/screen.c 2018-12-13 20:20:43.287958416 +0100 *************** *** 3128,3133 **** --- 3128,3142 ---- int draw_color_col = FALSE; /* highlight colorcolumn */ int *color_cols = NULL; /* pointer to according columns array */ #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 int has_spell = FALSE; /* this buffer has spell checking */ # define SPWORDLEN 150 *************** *** 3144,3150 **** static linenr_T capcol_lnum = 0; /* line number where "cap_col" used */ int cur_checked_col = 0; /* checked column for current line */ #endif ! int extra_check = 0; // has syntax or linebreak #ifdef FEAT_MBYTE int multi_attr = 0; /* attributes desired by multibyte */ int mb_l = 1; /* multi-byte byte length */ --- 3153,3159 ---- static linenr_T capcol_lnum = 0; /* line number where "cap_col" used */ int cur_checked_col = 0; /* checked column for current line */ #endif ! int extra_check = 0; // has extra highlighting #ifdef FEAT_MBYTE int multi_attr = 0; /* attributes desired by multibyte */ int mb_l = 1; /* multi-byte byte length */ *************** *** 3784,3789 **** --- 3793,3822 ---- } #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); col = 0; #ifdef FEAT_RIGHTLEFT *************** *** 4283,4288 **** --- 4316,4326 ---- else { attr_pri = FALSE; + #ifdef FEAT_TEXT_PROP + if (text_prop_type != NULL) + char_attr = text_prop_attr; + else + #endif #ifdef FEAT_SYN_HL if (has_syntax) char_attr = syntax_attr; *************** *** 4663,4668 **** --- 4701,4766 ---- } #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 /* Check spelling (unless at the end of the line). * Only do this when there is no syntax highlighting, the *************** *** 6025,6030 **** --- 6123,6132 ---- cap_col = 0; } #endif + #ifdef FEAT_TEXT_PROP + vim_free(text_props); + vim_free(text_prop_idxs); + #endif vim_free(p_extra_free); return row; *** ../vim-8.1.0578/src/structs.h 2018-11-10 17:33:23.087518814 +0100 --- src/structs.h 2018-12-13 20:21:41.959600249 +0100 *************** *** 684,689 **** --- 684,690 ---- linenr_T ml_line_lnum; /* line number of cached line, 0 if not valid */ 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 */ linenr_T ml_locked_low; /* first line in ml_locked */ *************** *** 696,701 **** --- 697,737 ---- #endif } 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) typedef struct signlist signlist_T; *************** *** 2358,2363 **** --- 2394,2402 ---- dictitem_T b_bufvar; /* variable for "b:" Dictionary */ dict_T *b_vars; /* internal variables, local to buffer */ #endif + #ifdef FEAT_TEXT_PROP + hashtab_T *b_proptypes; /* text property types local to buffer */ + #endif #if defined(FEAT_BEVAL) && defined(FEAT_EVAL) char_u *b_p_bexpr; /* 'balloonexpr' local value */ *** ../vim-8.1.0578/src/testdir/Make_all.mak 2018-11-30 22:40:09.098211991 +0100 --- src/testdir/Make_all.mak 2018-12-13 20:22:10.699424209 +0100 *************** *** 177,182 **** --- 177,183 ---- test_terminal_fail.res \ test_textformat.res \ test_textobjects.res \ + test_textprop.res \ test_undo.res \ test_user_func.res \ test_usercommands.res \ *** ../vim-8.1.0578/src/testdir/test_textprop.vim 2018-12-13 22:12:34.396222668 +0100 --- src/testdir/test_textprop.vim 2018-12-13 22:05:57.999204140 +0100 *************** *** 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 *** ../vim-8.1.0578/src/textprop.c 2018-12-13 22:12:34.400222638 +0100 --- src/textprop.c 2018-12-13 22:08:34.554000272 +0100 *************** *** 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 *** ../vim-8.1.0578/src/userfunc.c 2018-11-10 17:33:23.087518814 +0100 --- src/userfunc.c 2018-12-13 20:24:32.174552670 +0100 *************** *** 25,31 **** /* From user function to hashitem and back. */ #define UF2HIKEY(fp) ((fp)->uf_name) ! #define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) #define HI2UF(hi) HIKEY2UF((hi)->hi_key) #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] --- 25,31 ---- /* From user function to hashitem and back. */ #define UF2HIKEY(fp) ((fp)->uf_name) ! #define HIKEY2UF(p) ((ufunc_T *)((p) - offsetof(ufunc_T, uf_name))) #define HI2UF(hi) HIKEY2UF((hi)->hi_key) #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] *** ../vim-8.1.0578/src/version.c 2018-12-12 20:34:06.076356104 +0100 --- src/version.c 2018-12-13 20:39:19.480983935 +0100 *************** *** 653,664 **** # else "-terminfo", # endif - #else /* unix always includes termcap support */ - # ifdef HAVE_TGETENT - "+tgetent", - # else - "-tgetent", - # endif #endif #ifdef FEAT_TERMRESPONSE "+termresponse", --- 653,658 ---- *************** *** 670,675 **** --- 664,682 ---- #else "-textobjects", #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 "+timers", #else *** ../vim-8.1.0578/src/version.c 2018-12-12 20:34:06.076356104 +0100 --- src/version.c 2018-12-13 20:39:19.480983935 +0100 *************** *** 794,795 **** --- 801,804 ---- { /* Add new patch number below this line */ + /**/ + 579, /**/ -- EXPERIENCE - experience is a wonderful thing. It enables you to recognise a mistake when you make it again. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///