mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	vim-patch:9.1.1086: completion doesn't work with multi lines (#32377)
Problem:  completion doesn't work with multi lines
          (Łukasz Jan Niemier)
Solution: handle linebreaks in completion code as expected
          (glepnir)
fixes: vim/vim#2505
closes: vim/vim#15373
76bdb82527
			
			
This commit is contained in:
		| @@ -1539,7 +1539,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s | |||||||
|     ptr = line + v;  // "line" may have been updated |     ptr = line + v;  // "line" may have been updated | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if ((State & MODE_INSERT) && in_curline && ins_compl_win_active(wp)) { |   if ((State & MODE_INSERT) && ins_compl_win_active(wp) | ||||||
|  |       && (in_curline || ins_compl_lnum_in_range(lnum))) { | ||||||
|     area_highlighting = true; |     area_highlighting = true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -1787,8 +1788,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Check if ComplMatchIns highlight is needed. |         // Check if ComplMatchIns highlight is needed. | ||||||
|         if ((State & MODE_INSERT) && in_curline && ins_compl_win_active(wp)) { |         if ((State & MODE_INSERT) && ins_compl_win_active(wp) | ||||||
|           int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line)); |             && (in_curline || ins_compl_lnum_in_range(lnum))) { | ||||||
|  |           int ins_match_attr = ins_compl_col_range_attr(lnum, (int)(ptr - line)); | ||||||
|           if (ins_match_attr > 0) { |           if (ins_match_attr > 0) { | ||||||
|             search_attr = hl_combine_attr(search_attr, ins_match_attr); |             search_attr = hl_combine_attr(search_attr, ins_match_attr); | ||||||
|           } |           } | ||||||
|   | |||||||
| @@ -254,6 +254,7 @@ static pos_T compl_startpos; | |||||||
| /// Length in bytes of the text being completed (this is deleted to be replaced | /// Length in bytes of the text being completed (this is deleted to be replaced | ||||||
| /// by the match.) | /// by the match.) | ||||||
| static int compl_length = 0; | static int compl_length = 0; | ||||||
|  | static linenr_T compl_lnum = 0;         ///< lnum where the completion start | ||||||
| static colnr_T compl_col = 0;           ///< column where the text starts | static colnr_T compl_col = 0;           ///< column where the text starts | ||||||
|                                         ///< that is being completed |                                         ///< that is being completed | ||||||
| static colnr_T compl_ins_end_col = 0; | static colnr_T compl_ins_end_col = 0; | ||||||
| @@ -963,20 +964,47 @@ static void ins_compl_insert_bytes(char *p, int len) | |||||||
|  |  | ||||||
| /// Checks if the column is within the currently inserted completion text | /// Checks if the column is within the currently inserted completion text | ||||||
| /// column range. If it is, it returns a special highlight attribute. | /// column range. If it is, it returns a special highlight attribute. | ||||||
| /// -1 mean normal item. | /// -1 means normal item. | ||||||
| int ins_compl_col_range_attr(int col) | int ins_compl_col_range_attr(linenr_T lnum, int col) | ||||||
| { | { | ||||||
|   if (get_cot_flags() & kOptCotFlagFuzzy) { |   int attr; | ||||||
|  |   if ((get_cot_flags() & kOptCotFlagFuzzy) || (attr = syn_name2attr("ComplMatchIns")) == 0) { | ||||||
|     return -1; |     return -1; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (col >= (compl_col + (int)ins_compl_leader_len()) && col < compl_ins_end_col) { |   int start_col = compl_col + (int)ins_compl_leader_len(); | ||||||
|     return syn_name2attr("ComplMatchIns"); |   if (!ins_compl_has_multiple()) { | ||||||
|  |     return (col >= start_col && col < compl_ins_end_col) ? attr : -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Multiple lines | ||||||
|  |   if ((lnum == compl_lnum && col >= start_col && col < MAXCOL) | ||||||
|  |       || (lnum > compl_lnum && lnum < curwin->w_cursor.lnum) | ||||||
|  |       || (lnum == curwin->w_cursor.lnum && col <= compl_ins_end_col)) { | ||||||
|  |     return attr; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return -1; |   return -1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// Returns true if the current completion string contains newline characters, | ||||||
|  | /// indicating it's a multi-line completion. | ||||||
|  | static bool ins_compl_has_multiple(void) | ||||||
|  | { | ||||||
|  |   return vim_strchr(compl_shown_match->cp_str.data, '\n') != NULL; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Returns true if the given line number falls within the range of a multi-line | ||||||
|  | /// completion, i.e. between the starting line (compl_lnum) and current cursor | ||||||
|  | /// line. Always returns false for single-line completions. | ||||||
|  | bool ins_compl_lnum_in_range(linenr_T lnum) | ||||||
|  | { | ||||||
|  |   if (!ins_compl_has_multiple()) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   return lnum >= compl_lnum && lnum <= curwin->w_cursor.lnum; | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Reduce the longest common string for match "match". | /// Reduce the longest common string for match "match". | ||||||
| static void ins_compl_longest_match(compl_T *match) | static void ins_compl_longest_match(compl_T *match) | ||||||
| { | { | ||||||
| @@ -2777,6 +2805,7 @@ static void set_completion(colnr_T startcol, list_T *list) | |||||||
|     startcol = curwin->w_cursor.col; |     startcol = curwin->w_cursor.col; | ||||||
|   } |   } | ||||||
|   compl_col = startcol; |   compl_col = startcol; | ||||||
|  |   compl_lnum = curwin->w_cursor.lnum; | ||||||
|   compl_length = curwin->w_cursor.col - startcol; |   compl_length = curwin->w_cursor.col - startcol; | ||||||
|   // compl_pattern doesn't need to be set |   // compl_pattern doesn't need to be set | ||||||
|   compl_orig_text = cbuf_to_string(get_cursor_line_ptr() + compl_col, |   compl_orig_text = cbuf_to_string(get_cursor_line_ptr() + compl_col, | ||||||
| @@ -3737,6 +3766,26 @@ void ins_compl_delete(bool new_leader) | |||||||
|     curwin->w_cursor.col = compl_ins_end_col; |     curwin->w_cursor.col = compl_ins_end_col; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   char *remaining = NULL; | ||||||
|  |   if (curwin->w_cursor.lnum > compl_lnum) { | ||||||
|  |     if (curwin->w_cursor.col < get_cursor_line_len()) { | ||||||
|  |       char *line = get_cursor_line_ptr(); | ||||||
|  |       remaining = xstrdup(line + curwin->w_cursor.col); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     while (curwin->w_cursor.lnum > compl_lnum) { | ||||||
|  |       if (ml_delete(curwin->w_cursor.lnum, false) == FAIL) { | ||||||
|  |         if (remaining) { | ||||||
|  |           XFREE_CLEAR(remaining); | ||||||
|  |         } | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       curwin->w_cursor.lnum--; | ||||||
|  |     } | ||||||
|  |     // move cursor to end of line | ||||||
|  |     curwin->w_cursor.col = get_cursor_line_len(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if ((int)curwin->w_cursor.col > col) { |   if ((int)curwin->w_cursor.col > col) { | ||||||
|     if (stop_arrow() == FAIL) { |     if (stop_arrow() == FAIL) { | ||||||
|       return; |       return; | ||||||
| @@ -3745,6 +3794,13 @@ void ins_compl_delete(bool new_leader) | |||||||
|     compl_ins_end_col = curwin->w_cursor.col; |     compl_ins_end_col = curwin->w_cursor.col; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (remaining != NULL) { | ||||||
|  |     orig_col = curwin->w_cursor.col; | ||||||
|  |     ins_str(remaining); | ||||||
|  |     curwin->w_cursor.col = orig_col; | ||||||
|  |     xfree(remaining); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // TODO(vim): is this sufficient for redrawing?  Redrawing everything |   // TODO(vim): is this sufficient for redrawing?  Redrawing everything | ||||||
|   // causes flicker, thus we can't do that. |   // causes flicker, thus we can't do that. | ||||||
|   changed_cline_bef_curs(curwin); |   changed_cline_bef_curs(curwin); | ||||||
| @@ -3752,6 +3808,34 @@ void ins_compl_delete(bool new_leader) | |||||||
|   set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); |   set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// Insert a completion string that contains newlines. | ||||||
|  | /// The string is split and inserted line by line. | ||||||
|  | static void ins_compl_expand_multiple(char *str) | ||||||
|  | { | ||||||
|  |   char *start = str; | ||||||
|  |   char *curr = str; | ||||||
|  |   while (*curr != NUL) { | ||||||
|  |     if (*curr == '\n') { | ||||||
|  |       // Insert the text chunk before newline | ||||||
|  |       if (curr > start) { | ||||||
|  |         ins_char_bytes(start, (size_t)(curr - start)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Handle newline | ||||||
|  |       open_line(FORWARD, OPENLINE_KEEPTRAIL, false, NULL); | ||||||
|  |       start = curr + 1; | ||||||
|  |     } | ||||||
|  |     curr++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Handle remaining text after last newline (if any) | ||||||
|  |   if (curr > start) { | ||||||
|  |     ins_char_bytes(start, (size_t)(curr - start)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   compl_ins_end_col = curwin->w_cursor.col; | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Insert the new text being completed. | /// Insert the new text being completed. | ||||||
| /// "in_compl_func" is true when called from complete_check(). | /// "in_compl_func" is true when called from complete_check(). | ||||||
| /// "move_cursor" is used when 'completeopt' includes "preinsert" and when true | /// "move_cursor" is used when 'completeopt' includes "preinsert" and when true | ||||||
| @@ -3763,15 +3847,20 @@ void ins_compl_insert(bool in_compl_func, bool move_cursor) | |||||||
|   char *cp_str = compl_shown_match->cp_str.data; |   char *cp_str = compl_shown_match->cp_str.data; | ||||||
|   size_t cp_str_len = compl_shown_match->cp_str.size; |   size_t cp_str_len = compl_shown_match->cp_str.size; | ||||||
|   size_t leader_len = ins_compl_leader_len(); |   size_t leader_len = ins_compl_leader_len(); | ||||||
|  |   char *has_multiple = strchr(cp_str, '\n'); | ||||||
|  |  | ||||||
|   // Make sure we don't go over the end of the string, this can happen with |   // Make sure we don't go over the end of the string, this can happen with | ||||||
|   // illegal bytes. |   // illegal bytes. | ||||||
|   if (compl_len < (int)cp_str_len) { |   if (compl_len < (int)cp_str_len) { | ||||||
|  |     if (has_multiple) { | ||||||
|  |       ins_compl_expand_multiple(cp_str + compl_len); | ||||||
|  |     } else { | ||||||
|       ins_compl_insert_bytes(cp_str + compl_len, -1); |       ins_compl_insert_bytes(cp_str + compl_len, -1); | ||||||
|       if (preinsert && move_cursor) { |       if (preinsert && move_cursor) { | ||||||
|         curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len); |         curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|   compl_used_match = !(match_at_original_text(compl_shown_match) || preinsert); |   compl_used_match = !(match_at_original_text(compl_shown_match) || preinsert); | ||||||
|  |  | ||||||
|   dict_T *dict = ins_compl_dict_alloc(compl_shown_match); |   dict_T *dict = ins_compl_dict_alloc(compl_shown_match); | ||||||
| @@ -4551,6 +4640,7 @@ static int ins_compl_start(void) | |||||||
|   char *line = ml_get(curwin->w_cursor.lnum); |   char *line = ml_get(curwin->w_cursor.lnum); | ||||||
|   colnr_T curs_col = curwin->w_cursor.col; |   colnr_T curs_col = curwin->w_cursor.col; | ||||||
|   compl_pending = 0; |   compl_pending = 0; | ||||||
|  |   compl_lnum = curwin->w_cursor.lnum; | ||||||
|  |  | ||||||
|   if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT |   if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT | ||||||
|       && compl_cont_mode == ctrl_x_mode) { |       && compl_cont_mode == ctrl_x_mode) { | ||||||
| @@ -4602,6 +4692,7 @@ static int ins_compl_start(void) | |||||||
|       curbuf->b_p_com = old; |       curbuf->b_p_com = old; | ||||||
|       compl_length = 0; |       compl_length = 0; | ||||||
|       compl_col = curwin->w_cursor.col; |       compl_col = curwin->w_cursor.col; | ||||||
|  |       compl_lnum = curwin->w_cursor.lnum; | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
|     edit_submode_pre = NULL; |     edit_submode_pre = NULL; | ||||||
|   | |||||||
| @@ -5935,6 +5935,114 @@ describe('builtin popupmenu', function() | |||||||
|         ]]) |         ]]) | ||||||
|         feed('<C-E><Esc>') |         feed('<C-E><Esc>') | ||||||
|       end) |       end) | ||||||
|  |  | ||||||
|  |       -- oldtest: Test_pum_complete_with_special_characters() | ||||||
|  |       it('multi-line completion', function() | ||||||
|  |         exec([[ | ||||||
|  |           func Omni_test(findstart, base) | ||||||
|  |             if a:findstart | ||||||
|  |               return col(".") | ||||||
|  |             endif | ||||||
|  |             return [#{word: "func ()\n\t\nend", abbr: "function ()",}, #{word: "foobar"}, #{word: "你好\n\t\n我好"}] | ||||||
|  |           endfunc | ||||||
|  |           set omnifunc=Omni_test | ||||||
|  |         ]]) | ||||||
|  |  | ||||||
|  |         feed('S<C-X><C-O>') | ||||||
|  |         screen:expect([[ | ||||||
|  |           func ()                         | | ||||||
|  |                                           | | ||||||
|  |           end^                             | | ||||||
|  |           {s:function ()    }{1:                 }| | ||||||
|  |           {n:foobar         }{1:                 }| | ||||||
|  |           {n:你好^@  ^@我好 }{1:                 }| | ||||||
|  |           {1:~                               }|*13 | ||||||
|  |           {2:-- }{5:match 1 of 3}                 | | ||||||
|  |         ]]) | ||||||
|  |  | ||||||
|  |         feed('<C-N>') | ||||||
|  |         screen:expect([[ | ||||||
|  |           foobar^                          | | ||||||
|  |           {n:function ()    }{1:                 }| | ||||||
|  |           {s:foobar         }{1:                 }| | ||||||
|  |           {n:你好^@  ^@我好 }{1:                 }| | ||||||
|  |           {1:~                               }|*15 | ||||||
|  |           {2:-- }{5:match 2 of 3}                 | | ||||||
|  |         ]]) | ||||||
|  |         feed('<C-E><ESC>') | ||||||
|  |  | ||||||
|  |         feed('Shello  hero<ESC>hhhhha<C-X><C-O>') | ||||||
|  |         screen:expect([[ | ||||||
|  |           hello func ()                   | | ||||||
|  |                                           | | ||||||
|  |           end^ hero                        | | ||||||
|  |           {1:~    }{s: function ()    }{1:           }| | ||||||
|  |           {1:~    }{n: foobar         }{1:           }| | ||||||
|  |           {1:~    }{n: 你好^@  ^@我好 }{1:           }| | ||||||
|  |           {1:~                               }|*13 | ||||||
|  |           {2:-- }{5:match 1 of 3}                 | | ||||||
|  |         ]]) | ||||||
|  |  | ||||||
|  |         feed('<C-N>') | ||||||
|  |         screen:expect([[ | ||||||
|  |           hello foobar^ hero               | | ||||||
|  |           {1:~    }{n: function ()    }{1:           }| | ||||||
|  |           {1:~    }{s: foobar         }{1:           }| | ||||||
|  |           {1:~    }{n: 你好^@  ^@我好 }{1:           }| | ||||||
|  |           {1:~                               }|*15 | ||||||
|  |           {2:-- }{5:match 2 of 3}                 | | ||||||
|  |         ]]) | ||||||
|  |  | ||||||
|  |         feed('<C-N>') | ||||||
|  |         screen:expect([[ | ||||||
|  |           hello 你好                      | | ||||||
|  |                                           | | ||||||
|  |           我好^ hero                       | | ||||||
|  |           {1:~  }{n: function ()    }{1:             }| | ||||||
|  |           {1:~  }{n: foobar         }{1:             }| | ||||||
|  |           {1:~  }{s: 你好^@  ^@我好 }{1:             }| | ||||||
|  |           {1:~                               }|*13 | ||||||
|  |           {2:-- }{5:match 3 of 3}                 | | ||||||
|  |         ]]) | ||||||
|  |  | ||||||
|  |         feed('<C-N>') | ||||||
|  |         screen:expect([[ | ||||||
|  |           hello ^ hero                     | | ||||||
|  |           {1:~    }{n: function ()    }{1:           }| | ||||||
|  |           {1:~    }{n: foobar         }{1:           }| | ||||||
|  |           {1:~    }{n: 你好^@  ^@我好 }{1:           }| | ||||||
|  |           {1:~                               }|*15 | ||||||
|  |           {2:-- }{8:Back at original}             | | ||||||
|  |         ]]) | ||||||
|  |         feed('<C-E><ESC>') | ||||||
|  |  | ||||||
|  |         command(':hi ComplMatchIns guifg=red') | ||||||
|  |         feed('S<C-X><C-O>') | ||||||
|  |         screen:expect([[ | ||||||
|  |           {8:func ()}                         | | ||||||
|  |           {8:        }                        | | ||||||
|  |           {8:end}^                             | | ||||||
|  |           {s:function ()    }{1:                 }| | ||||||
|  |           {n:foobar         }{1:                 }| | ||||||
|  |           {n:你好^@  ^@我好 }{1:                 }| | ||||||
|  |           {1:~                               }|*13 | ||||||
|  |           {2:-- }{5:match 1 of 3}                 | | ||||||
|  |         ]]) | ||||||
|  |         feed('<C-E><ESC>') | ||||||
|  |  | ||||||
|  |         feed('Shello  hero<ESC>hhhhha<C-X><C-O>') | ||||||
|  |         screen:expect([[ | ||||||
|  |           hello {8:func ()}                   | | ||||||
|  |           {8:        }                        | | ||||||
|  |           {8:end^ }hero                        | | ||||||
|  |           {1:~    }{s: function ()    }{1:           }| | ||||||
|  |           {1:~    }{n: foobar         }{1:           }| | ||||||
|  |           {1:~    }{n: 你好^@  ^@我好 }{1:           }| | ||||||
|  |           {1:~                               }|*13 | ||||||
|  |           {2:-- }{5:match 1 of 3}                 | | ||||||
|  |         ]]) | ||||||
|  |         feed('<C-E><ESC>') | ||||||
|  |       end) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1885,4 +1885,59 @@ func Test_popup_completion_many_ctrlp() | |||||||
|   bw! |   bw! | ||||||
| endfunc | endfunc | ||||||
|  |  | ||||||
|  | func Test_pum_complete_with_special_characters() | ||||||
|  |   CheckScreendump | ||||||
|  |  | ||||||
|  |   let lines =<< trim END | ||||||
|  |     func Omni_test(findstart, base) | ||||||
|  |       if a:findstart | ||||||
|  |         return col(".") | ||||||
|  |       endif | ||||||
|  |       return [#{word: "func ()\n\t\nend", abbr: "function ()",}, #{word: "foobar"}, #{word: "你好\n\t\n我好"}] | ||||||
|  |     endfunc | ||||||
|  |     set omnifunc=Omni_test | ||||||
|  |   END | ||||||
|  |  | ||||||
|  |   call writefile(lines, 'Xpreviewscript', 'D') | ||||||
|  |   let buf = RunVimInTerminal('-S Xpreviewscript', #{rows: 12}) | ||||||
|  |   call term_sendkeys(buf, "S\<C-X>\<C-O>") | ||||||
|  |   call TermWait(buf, 50) | ||||||
|  |   call VerifyScreenDump(buf, 'Test_pum_with_special_characters_01', {}) | ||||||
|  |  | ||||||
|  |   call term_sendkeys(buf, "\<C-N>") | ||||||
|  |   call TermWait(buf, 50) | ||||||
|  |   call VerifyScreenDump(buf, 'Test_pum_with_special_characters_02', {}) | ||||||
|  |   call term_sendkeys(buf, "\<C-E>\<Esc>") | ||||||
|  |  | ||||||
|  |   call term_sendkeys(buf, "Shello  hero\<ESC>hhhhha\<C-X>\<C-O>") | ||||||
|  |   call TermWait(buf, 50) | ||||||
|  |   call VerifyScreenDump(buf, 'Test_pum_with_special_characters_03', {}) | ||||||
|  |  | ||||||
|  |   call term_sendkeys(buf, "\<C-N>") | ||||||
|  |   call TermWait(buf, 50) | ||||||
|  |   call VerifyScreenDump(buf, 'Test_pum_with_special_characters_04', {}) | ||||||
|  |  | ||||||
|  |   call term_sendkeys(buf, "\<C-N>") | ||||||
|  |   call TermWait(buf, 50) | ||||||
|  |   call VerifyScreenDump(buf, 'Test_pum_with_special_characters_05', {}) | ||||||
|  |  | ||||||
|  |   call term_sendkeys(buf, "\<C-N>") | ||||||
|  |   call TermWait(buf, 50) | ||||||
|  |   call VerifyScreenDump(buf, 'Test_pum_with_special_characters_06', {}) | ||||||
|  |   call term_sendkeys(buf, "\<C-E>\<Esc>") | ||||||
|  |  | ||||||
|  |   call term_sendkeys(buf, ":hi ComplMatchIns ctermfg=red\<CR>") | ||||||
|  |   call TermWait(buf, 50) | ||||||
|  |   call term_sendkeys(buf, "S\<C-X>\<C-O>") | ||||||
|  |   call VerifyScreenDump(buf, 'Test_pum_with_special_characters_07', {}) | ||||||
|  |   call term_sendkeys(buf, "\<C-E>\<Esc>") | ||||||
|  |  | ||||||
|  |   call term_sendkeys(buf, "Shello  hero\<ESC>hhhhha\<C-X>\<C-O>") | ||||||
|  |   call TermWait(buf, 50) | ||||||
|  |   call VerifyScreenDump(buf, 'Test_pum_with_special_characters_08', {}) | ||||||
|  |   call term_sendkeys(buf, "\<C-E>\<Esc>") | ||||||
|  |  | ||||||
|  |   call StopVimInTerminal(buf) | ||||||
|  | endfunc | ||||||
|  |  | ||||||
| " vim: shiftwidth=2 sts=2 expandtab | " vim: shiftwidth=2 sts=2 expandtab | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 glepnir
					glepnir