From 2dc5b141dab679784b6bde3e15baffa135a56019 Mon Sep 17 00:00:00 2001 From: Felipe Morales Date: Thu, 13 Jul 2017 17:20:06 -0400 Subject: [PATCH 01/58] tutor: some tweaks to begginer's tutorial --- runtime/tutor/en/vim-01-beginner.tutor | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor index 47d4ed06a1..e88603fedb 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor +++ b/runtime/tutor/en/vim-01-beginner.tutor @@ -18,9 +18,9 @@ be saved. Don't worry about messing things up; just remember that pressing []() and then [u](u) will undo the latest change. This tutorial is interactive, and there are a few things you should know. -Pressing []() over text highlighted [like this](holy-grail) will take you to some relevant -help (hopefully), and pressing K over any word will try to do so too. Sometimes -you will be required to modify text like +Pressing []() over text highlighted [like this](holy-grail) will take you to some +relevant help (hopefully), and pressing K over any word will try to do so too. +Sometimes you will be required to modify text like ---> this here {expect:this here} Once you have done the changes correctly, the ✗ sign at the left will change to ✓. I imagine you can already see how neat Vim can be ;) @@ -947,8 +947,10 @@ There are many resources online to learn more about vim. Here's a bunch of them: - Vim Video-Tutorials by Derek Wyatt: http://derekwyatt.org/vim/tutorials/ - *Learn Vimscript the Hard Way*: http://learnvimscriptthehardway.stevelosh.com/ - *7 Habits of Effective Text Editing*: http://www.moolenaar.net/habits.html +- *vim-galore*: https://github.com/mhinz/vim-galore -If you prefer a book, *Practival Vim* by Drew Neil is recommended often. +If you prefer a book, *Practical Vim* by Drew Neil is recommended often (the sequel, *Modern +Vim*, includes material specific to nvim!). This tutorial was written by Michael C. Pierce and Robert K. Ware, Colorado School of Mines using ideas supplied by Charles Smith, Colorado State From ec67d0706524a23768c8a964a7940a34f057edb8 Mon Sep 17 00:00:00 2001 From: Felipe Morales Date: Thu, 13 Jul 2017 19:04:15 -0400 Subject: [PATCH 02/58] tutor: allow metadata to exist outside of the documents. this makes 'expect' regions simpler to handle. --- runtime/autoload/tutor.vim | 46 ++++++++++- runtime/ftplugin/tutor.vim | 21 ++++-- runtime/tutor/en/vim-01-beginner.tutor | 84 ++++++++++----------- runtime/tutor/en/vim-01-beginner.tutor.json | 49 ++++++++++++ 4 files changed, 151 insertions(+), 49 deletions(-) create mode 100644 runtime/tutor/en/vim-01-beginner.tutor.json diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim index 43d8a87886..1f0668c1b3 100644 --- a/runtime/autoload/tutor.vim +++ b/runtime/autoload/tutor.vim @@ -209,7 +209,15 @@ function! tutor#InfoText() return join(l:info_parts, " ") endfunction -" Marks {{{1 +function! tutor#LoadMetadata() + try + let b:tutor_metadata = json_decode(join(readfile(expand('%').'.json'), "\n")) + catch + endtry +endfunction + +" Marks: {{{1 +" Internal: {{{2 function! tutor#PlaceXMarks() call cursor(1, 1) let b:tutor_sign_id = 1 @@ -241,13 +249,47 @@ function! tutor#CheckText(text) endif endfunction -function! tutor#OnTextChanged() +function! tutor#XmarksOnTextChanged() let l:text = getline('.') if match(l:text, '^--->') > -1 call tutor#CheckText(l:text) endif endfunction +" External: {{{2 +function! tutor#ApplyMarks() + if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect') + let b:tutor_sign_id = 1 + for expct in keys(b:tutor_metadata['expect']) + let lnum = eval(expct) + call matchaddpos('tutorSampleText', [lnum]) + call tutor#CheckLine(lnum) + endfor + endif +endfunction + +function! tutor#ApplyMarksOnChanged() + if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect') + let lnum = line('.') + if index(keys(b:tutor_metadata['expect']), string(lnum)) > -1 + call tutor#CheckLine(lnum) + endif + endif +endfunction + +function! tutor#CheckLine(line) + if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect') + let bufn = bufnr('%') + let ctext = getline(a:line) + if b:tutor_metadata['expect'][string(a:line)] == -1 || ctext ==# b:tutor_metadata['expect'][string(a:line)] + exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorok buffer=".bufn + else + exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorbad buffer=".bufn + endif + let b:tutor_sign_id+=1 + endif +endfunction + " Tutor Cmd: {{{1 function! s:Locale() diff --git a/runtime/ftplugin/tutor.vim b/runtime/ftplugin/tutor.vim index 1579753170..4cfde4c2df 100644 --- a/runtime/ftplugin/tutor.vim +++ b/runtime/ftplugin/tutor.vim @@ -28,18 +28,29 @@ setlocal nowrap setlocal statusline=%{toupper(expand('%:t:r'))}\ tutorial%= setlocal statusline+=%{tutor#InfoText()} +" Load metadata if it exists: {{{1 +if filereadable(expand('%').'.json') + call tutor#LoadMetadata() +endif + " Mappings: {{{1 call tutor#SetNormalMappings() -call tutor#SetSampleTextMappings() +if exists('b:tutor_metadata') && b:tutor_metadata['settings']['use_maps'] == 1 + call tutor#SetSampleTextMappings() +endif " Checks: {{{1 sign define tutorok text=✓ texthl=tutorOK sign define tutorbad text=✗ texthl=tutorX -if !exists('g:tutor_debug') || g:tutor_debug == 0 - call tutor#PlaceXMarks() - autocmd! TextChanged call tutor#OnTextChanged() - autocmd! TextChangedI call tutor#OnTextChanged() +if !exists('g:tutor_debug') || g:tutor_debug == 0 + if !(exists('b:tutor_metadata') && b:tutor_metadata['settings']['check_internal_expects'] == '0') + call tutor#PlaceXMarks() + autocmd! TextChanged,TextChangedI call tutor#XmarksOnTextChanged() + else + call tutor#ApplyMarks() + autocmd! TextChanged,TextChangedI call tutor#ApplyMarksOnChanged() + endif endif diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor index e88603fedb..fee7de21d2 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor +++ b/runtime/tutor/en/vim-01-beginner.tutor @@ -18,10 +18,10 @@ be saved. Don't worry about messing things up; just remember that pressing []() and then [u](u) will undo the latest change. This tutorial is interactive, and there are a few things you should know. -Pressing []() over text highlighted [like this](holy-grail) will take you to some +Pressing []() over text highlighted [like this](holy-grail) will take you to some relevant help (hopefully), and pressing K over any word will try to do so too. Sometimes you will be required to modify text like ----> this here {expect:this here} +this here Once you have done the changes correctly, the ✗ sign at the left will change to ✓. I imagine you can already see how neat Vim can be ;) Other times, you'll be prompted to run a command (I'll explain this later): @@ -99,7 +99,7 @@ NOTE: [:q!](:q) discards any changes you made. In a few lessons you 4. Repeat steps 2 through 4 until the sentence is correct. ----> The ccow jumpedd ovverr thhe mooon. {expect:The cow jumped over the moon.} +The ccow jumpedd ovverr thhe mooon. 5. Now that the line is correct, go on to Lesson 1.4. @@ -119,8 +119,8 @@ NOTE: As you go through this tutor, do not try to memorize, learn by usage. 4. As each error is fixed press to return to Normal mode. Repeat steps 2 through 4 to correct the sentence. ----> There is text misng this . {expect:There is some text missing from this line.} ----> There is some text missing from this line. {expect:There is some text missing from this line.} +There is text misng this . +There is some text missing from this line. 5. When you are comfortable inserting text move to lesson 1.5. @@ -138,10 +138,10 @@ NOTE: As you go through this tutor, do not try to memorize, learn by usage. 4. Move the cursor to the second line marked ---> and repeat steps 2 and 3 to correct this sentence. ----> There is some text missing from th {expect:There is some text missing from this line.} ----> There is some text missing from this line. {expect:There is some text missing from this line.} ----> There is also some text miss {expect:There is also some text missing here.} ----> There is also some text missing here. {expect:There is also some text missing here.} +There is some text missing from th +There is some text missing from this line. +There is also some text miss +There is also some text missing here. 5. When you are comfortable appending text move to lesson 1.6. @@ -212,7 +212,7 @@ Now continue with Lesson 2. 4. Type [d](d)[w](w) to make the word disappear. ----> There are a some words fun that don't belong paper in this sentence. {expect:There are some words that don't belong in this sentence.} +There are a some words fun that don't belong paper in this sentence. 5. Repeat steps 3 and 4 until the sentence is correct and go to Lesson 2.2. @@ -228,7 +228,7 @@ Now continue with Lesson 2. 4. Type `d$`{normal} to delete to the end of the line. ----> Somebody typed the end of this line twice. end of this line twice. {expect:ANYTHING} +Somebody typed the end of this line twice. end of this line twice. 5. Move on to Lesson 2.3 to understand what is happening. @@ -268,7 +268,7 @@ NOTE: Pressing just the motion while in Normal mode without an operator will 5. Repeat steps 2 and 3 with different numbers. ----> This is just a line with words you can move around in. {expect:ANYTHING} +This is just a line with words you can move around in. 6. Move on to Lesson 2.5. @@ -287,7 +287,7 @@ insert a count before the motion to delete more: 3. Repeat steps 1 and 2 with a different count to delete the consecutive UPPER CASE words with one command ----> this ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. {expect:this line of words is cleaned up.} +this ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. # Lesson 2.6: OPERATING ON LINES @@ -301,13 +301,13 @@ insert a count before the motion to delete more: 3. Now move to the fourth line. 4. Type `2dd`{normal} to delete two lines. ----> 1) Roses are red, {expect:ANYTHING} ----> 2) Mud is fun, {expect:ANYTHING} ----> 3) Violets are blue, {expect:ANYTHING} ----> 4) I have a car, {expect:ANYTHING} ----> 5) Clocks tell time, {expect:ANYTHING} ----> 6) Sugar is sweet {expect:ANYTHING} ----> 7) And so are you. {expect:ANYTHING} +1) Roses are red, +2) Mud is fun, +3) Violets are blue, +4) I have a car, +5) Clocks tell time, +6) Sugar is sweet +7) And so are you. # Lesson 2.7: THE UNDO COMMAND @@ -322,7 +322,7 @@ insert a count before the motion to delete more: 6. Now type `u`{normal} a few times to undo the U and preceding commands. 7. Now type ``{normal} a few times to redo the commands (undo the undo's). ----> Fiix the errors oon thhis line and reeplace them witth undo. {expect:Fix the errors on this line and replace them with undo.} +Fiix the errors oon thhis line and reeplace them witth undo. 8. These are very useful commands. Now move on to the Lesson 2 Summary. @@ -362,10 +362,10 @@ insert a count before the motion to delete more: 5. Repeat steps 2 through 4 to put all the lines in correct order. ----> d) Can you learn too? {expect:ANYTHING} ----> b) Violets are blue, {expect:ANYTHING} ----> c) Intelligence is learned, {expect:ANYTHING} ----> a) Roses are red, {expect:ANYTHING} +d) Can you learn too? +b) Violets are blue, +c) Intelligence is learned, +a) Roses are red, # Lesson 3.2: THE REPLACE COMMAND @@ -379,8 +379,8 @@ insert a count before the motion to delete more: 4. Repeat steps 2 and 3 until the first line is equal to the second one. ----> Whan this lime was tuoed in, someone presswd some wrojg keys! {expect:When this line was typed in, someone pressed some wrong keys!} ----> When this line was typed in, someone pressed some wrong keys! {expect:When this line was typed in, someone pressed some wrong keys!} +Whan this lime was tuoed in, someone presswd some wrojg keys! +When this line was typed in, someone pressed some wrong keys! 5. Now move on to Lesson 3.3. @@ -400,8 +400,8 @@ NOTE: Remember that you should be learning by doing, not memorization. 5. Repeat steps 3 and 4 until the first sentence is the same as the second. ----> This lubw has a few wptfd that mrrf changing usf the change operator. {expect:This line has a few words that need changing using the change operator.} ----> This line has a few words that need changing using the change operator. {expect:This line has a few words that need changing using the change operator.} +This lubw has a few wptfd that mrrf changing usf the change operator. +This line has a few words that need changing using the change operator. Notice that [c](c)e deletes the word and places you in Insert mode. @@ -421,8 +421,8 @@ Notice that [c](c)e deletes the word and places you in Insert mode. 5. Type `c$`{normal} and type the rest of the line like the second and press ``{normal}. ----> The end of this line needs some help to make it like the second. {expect:The end of this line needs to be corrected using the c$ command.} ----> The end of this line needs to be corrected using the c$ command. {expect:The end of this line needs to be corrected using the c$ command.} +The end of this line needs some help to make it like the second. +The end of this line needs to be corrected using the c$ command. NOTE: You can use the Backspace key to correct mistakes while typing. @@ -484,7 +484,7 @@ NOTE: You may see the cursor position in the lower right corner of the screen 5. To go back to where you came from press ``{normal} (Keep Ctrl down while pressing the letter o). Repeat to go back further. ``{normal} goes forward. ----> "errroor" is not the way to spell error; errroor is an error. {expect:ANYTHING} +"errroor" is not the way to spell error; errroor is an error. NOTE: When the search reaches the end of the file it will continue at the start, unless the ['wrapscan']('wrapscan') option has been reset. @@ -503,7 +503,7 @@ NOTE: When the search reaches the end of the file it will continue at the 5. Move the cursor to another (,),[,],{ or } and see what `%`{normal} does. ----> This ( is a test line with ('s, ['s ] and {'s } in it. )) {expect:ANYTHING} +This ( is a test line with ('s, ['s ] and {'s } in it. )) NOTE: This is very useful in debugging a program with unmatched parentheses! @@ -528,7 +528,7 @@ NOTE: This is very useful in debugging a program with unmatched parentheses! Adding the g [flag](:s_flags) means to substitute globally in the line, change all occurrences of "thee" in the line. ----> thee best time to see thee flowers is in thee spring. {expect:the best time to see the flowers is in the spring.} +thee best time to see thee flowers is in thee spring. 4. To change every occurrence of a character string between two lines, type ~~~ cmd @@ -719,12 +719,12 @@ NOTE: You can also read the output of an external command. For example, 3. Now type some text and press ``{normal} to exit Insert mode. ----> After typing o the cursor is placed on the open line in Insert mode. {expect:ANYTHING} +After typing o the cursor is placed on the open line in Insert mode. 4. To open up a line ABOVE the cursor, simply type a [capital O](O), rather than a lowercase `o`{normal}. Try this on the line below. ----> Open up a line above this by typing O while the cursor is on this line. {expect:ANYTHING} +Open up a line above this by typing O while the cursor is on this line. # Lesson 6.2: THE APPEND COMMAND @@ -741,8 +741,8 @@ NOTE: You can also read the output of an external command. For example, 5. Use `e`{normal} to move to the next incomplete word and repeat steps 3 and 4. ----> This li will allow you to pract appendi text to a line. {expect:This line will allow you to practice appending text to a line.} ----> This line will allow you to practice appending text to a line. {expect:This line will allow you to practice appending text to a line.} +This li will allow you to pract appendi text to a line. +This line will allow you to practice appending text to a line. NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only difference is where the characters are inserted. @@ -762,8 +762,8 @@ NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only differ 4. Repeat the steps to replace the remaining "xxx". ----> Adding 123 to xxx gives you xxx. {expect:Adding 123 to 456 gives you 579.} ----> Adding 123 to 456 gives you 579. {expect:Adding 123 to 456 gives you 579.} +Adding 123 to xxx gives you xxx. +Adding 123 to 456 gives you 579. NOTE: Replace mode is like Insert mode, but every typed character deletes an existing character. @@ -785,8 +785,8 @@ NOTE: Replace mode is like Insert mode, but every typed character deletes an 6. Use Visual mode to select " item.", yank it with `y`{normal}, move to the end of the next line with `j$`{normal} and put the text there with `p`{normal}. ----> a) this is the first item. ----> b) {expect: b) this is the second item} +a) this is the first item. + b) NOTE: you can also use `y`{normal} as an operator; `yw`{normal} yanks one word. diff --git a/runtime/tutor/en/vim-01-beginner.tutor.json b/runtime/tutor/en/vim-01-beginner.tutor.json new file mode 100644 index 0000000000..5bf50581ab --- /dev/null +++ b/runtime/tutor/en/vim-01-beginner.tutor.json @@ -0,0 +1,49 @@ +{ + "settings" : { + "use_maps": 0, + "check_internal_expects": 0 + }, + "expect": { + "24": -1, + "102": "The cow jumped over the moon.", + "122": "There is some text missing from this line.", + "123": "There is some text missing from this line.", + "141": "There is some text missing from this line.", + "142": "There is some text missing from this line.", + "143": "There is also some text missing here.", + "144": "There is also some text missing here.", + "215": "There are some words that don't belong in this sentence.", + "231": "Somebody typed the end of this line twice.", + "271": -1, + "290": "this line of words is cleaned up.", + "304": -1, + "305": -1, + "306": -1, + "307": -1, + "308": -1, + "309": -1, + "310": -1, + "325": "Fix the errors on this line and replace them with undo.", + "365": -1, + "366": -1, + "367": -1, + "368": -1, + "382": "When this line was typed in, someone pressed some wrong keys!", + "383": "When this line was typed in, someone pressed some wrong keys!", + "403": "This line has a few words that need changing using the change operator.", + "404": "This line has a few words that need changing using the change operator.", + "424": "The end of this line needs to be corrected using the c$ command.", + "425": "The end of this line needs to be corrected using the c$ command.", + "487": -1, + "506": -1, + "531": "the best time to see the flowers is in the spring.", + "722": -1, + "727": -1, + "744": "This line will allow you to practice appending text to a line.", + "745": "This line will allow you to practice appending text to a line.", + "765": "Adding 123 to 456 gives you 579.", + "766": "Adding 123 to 456 gives you 579.", + "788": "a) this is the first item.", + "789": " b) this is the second item." + } +} From c235ee3f059d43a6099d7983b68b433abdd7e395 Mon Sep 17 00:00:00 2001 From: Felipe Morales Date: Thu, 14 Jul 2016 23:38:47 +0200 Subject: [PATCH 03/58] tutor: don't enable folds by default --- runtime/ftplugin/tutor.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/ftplugin/tutor.vim b/runtime/ftplugin/tutor.vim index 4cfde4c2df..660c1afaf5 100644 --- a/runtime/ftplugin/tutor.vim +++ b/runtime/ftplugin/tutor.vim @@ -19,11 +19,11 @@ setlocal noundofile setlocal keywordprg=:help setlocal iskeyword=@,-,_ -setlocal foldmethod=expr +" The user will have to enable the folds himself, but we provide the foldexpr +" function. +setlocal foldmethod=manual setlocal foldexpr=tutor#TutorFolds() -setlocal foldcolumn=1 setlocal foldlevel=4 -setlocal nowrap setlocal statusline=%{toupper(expand('%:t:r'))}\ tutorial%= setlocal statusline+=%{tutor#InfoText()} From cb0282ad986e5b496aa89e3a78984a1307344ff4 Mon Sep 17 00:00:00 2001 From: Felipe Morales Date: Fri, 14 Jul 2017 19:31:47 -0400 Subject: [PATCH 04/58] tutor: update tutor-mode tutorial --- runtime/tutor/tutor.tutor | 118 +++++++++++++++------------------ runtime/tutor/tutor.tutor.json | 35 ++++++++++ 2 files changed, 88 insertions(+), 65 deletions(-) create mode 100644 runtime/tutor/tutor.tutor.json diff --git a/runtime/tutor/tutor.tutor b/runtime/tutor/tutor.tutor index 1ad64a18ff..c937bd686a 100644 --- a/runtime/tutor/tutor.tutor +++ b/runtime/tutor/tutor.tutor @@ -60,27 +60,27 @@ is displayed like 1. Format the line below so it becomes a lesson description: ----> This is text with important information {expect:This is text with **important information**} ----> This is text with **important information** {expect:This is text with **important information**} +This is text with important information +This is text with **important information** Note: Some words (e.g., NOTE, IMPORTANT, tip, ATTENTION, etc.) will also be highlighted. You don't need to mark them specially. 2. Turn the line below into a TODO item: ----> Document '&variable' {expect:TODO: Document '&variable'} ----> TODO: Document '&variable' {expect:TODO: Document '&variable'} +Document '&variable' +TODO: Document '&variable' ### Headers *headers* 3. Practice fixing the lines below: ----> This is a level 1 header {expect:# This is a level 1 header} ----> # This is a level 1 header {expect:# This is a level 1 header} ----> This is a level 3 header {expect:### This is a level 3 header} ----> ### This is a level 3 header {expect:### This is a level 3 header} ----> This is a header with a label {expect:# This is a header with a label {*label*}} ----> # This is a header with a label {*label*} {expect:# This is a header with a label {*label*}} +This is a level 1 header +# This is a level 1 header +This is a level 3 header +### This is a level 3 header +This is a header with a label +# This is a header with a label {*label*} 4. Now, create a 4th level section here, and add a label like in the previous exercise: @@ -105,8 +105,8 @@ If the target of a link matches a help topic, opening it will open it. 5. Fix the following line: ----> A link to help for the 'breakindent' option {expect:A link to help for the ['breakindent']('breakindent') option} ----> A link to help for the ['breakindent']('breakindent') option {expect:A link to help for the ['breakindent']('breakindent') option} +A link to help for the 'breakindent' option +A link to help for the ['breakindent']('breakindent') option #### Anchor links @@ -120,8 +120,8 @@ and are hidden by default. Links to them look like 6. Add the appropiate link: ----> A link to the Links section {expect:A link to the [Links](*links*) section} ----> A link to the [Links](*links*) section {expect:A link to the [Links](*links*) section} +A link to the Links section +A link to the [Links](*links*) section 7. Now, create a link to the section you created on exercise 4 above. @@ -136,8 +136,8 @@ You can also have links to other tutorials. For this, you'll write the anchor in 7. Create a link to this tutorial: ----> A link to the vim-tutor-mode tutorial {expect:A link to [the vim-tutor-mode tutorial](@tutor:tutor)} ----> A link to [the vim-tutor-mode tutorial](@tutor:tutor) {expect:A link to [the vim-tutor-mode tutorial](@tutor:tutor)} +A link to the vim-tutor-mode tutorial +A link to [the vim-tutor-mode tutorial](@tutor:tutor) ### Codeblocks *codeblocks* @@ -154,13 +154,13 @@ echom "hello" 8. Copy the viml section below ----> {expect:~~~ viml} ----> {expect:echom "the value of &number is".string(&number)} ----> {expect:~~~} ----> ~~~ viml {expect:~~~ viml} ----> echom "the value of &number is".string(&number) {expect:echom "the value of &number is".string(&number)} ----> ~~~ {expect:~~~} + + + +~~~ viml +echom 'the value of &number is'.string(&number) +~~~ You can inline viml code using "\`" and "\`{vim}": @@ -185,13 +185,13 @@ Note: you can also write `norm` or `normal`. 9. Copy the normal section below ----> {expect:~~~ normal} ----> {expect:d2w} ----> {expect:~~~} ----> ~~~ normal {expect:~~~ normal} ----> d2w {expect:d2w} ----> ~~~ {expect:~~~} + + + +~~~ normal +d2w +~~~ You can also inline normal commands by using "\`" and "\`{normal}": @@ -203,10 +203,11 @@ is displayed: 10. Complete the line as shown ----> d {expect:«d2w»} ----> «d2w» {expect:«d2w»} +d +`d2w`{normal} -Commands to run in the system shell can be highlighted by indenting a line starting with "$". +Commands to run in the system shell can be highlighted by indenting a line +starting with "$". ~~~ sh $ vim --version @@ -215,45 +216,32 @@ Commands to run in the system shell can be highlighted by indenting a line start ## INTERACTIVE ELEMENTS *interactive* As visible in this very document, vim-tutor-mode includes some interactive -elements, to provide feedback to the user about his progress. These elements -all have the syntax - - \---> TEXT {CLAUSE} - -where \---> must start at the beginning of the line. If TEXT satisfies CLAUSE, -a ✓ sign will appear to the left. A ✗ sign is displayed otherwise. The CLAUSE -itself is hidden unless debug mode is set or ['conceallevel']('conceallevel') -is 2. +elements to provide feedback to the user about his progress. If the text in +these elements satisfies some set condition, a ✓ sign will appear in the gutter +to the left. Otherwise, a ✗ sign is displayed. ### expect *expect* -The basic clause is "expect", which is satisfied if TEXT is the same as the -content of the clause. For example +"expect" lines check that the contents of the line are identical to some preset text +(like in the exercises above). - \---> TEXT {expect:TEXT} +These elements are specified in separate JSON files like this -is satisfied, but +~~~ json +{ + "expect": { + "1": "This is how this line should look.", + "2": "This is how this line should look.", + "3": -1 + } +} +~~~ - \---> OTHER TEXT {expect:TEXT} +These files contain an "expect" dictionary, for which the keys are line numbers and +the values are the expected text. A value of -1 means that the condition for the line +will always be satisfied, no matter what (this is useful for letting the user play a bit). -is not. +This is an "expect" line that is always satisfied. Try changing it. -13. Make both lines the same: - ----> this is not right {expect:---> this is right} |expect:---> this is right {expect:---> this is right}| ----> ---> this is right {expect:---> this is right} |expect:---> this is right {expect:---> this is right}| - - -If the content of a expect clause is ANYTHING, no checks will be performed. This is -useful to create a line that is highlighted you want the user to play with. - - \---> TEXT {expect:ANYTHING} - -is displayed - ----> this is free text {expect:ANYTHING} - -14. Turn the line below into free text: - ----> this is some text |expect:---> this is some text {expect:ANYTHING}| ----> ---> this is some text {expect:ANYTHING} |expect:---> this is some text {expect:ANYTHING}| +These files conventionally have the same name as the tutorial document with the `.json` +extension appended (for a full example, see the file that corresponds to this tutorial). diff --git a/runtime/tutor/tutor.tutor.json b/runtime/tutor/tutor.tutor.json new file mode 100644 index 0000000000..bf3eae8586 --- /dev/null +++ b/runtime/tutor/tutor.tutor.json @@ -0,0 +1,35 @@ +{ + "expect": { + "63": "This is text with **important information**", + "64": "This is text with **important information**", + "71": "Document '&variable'", + "72": "Document '&variable'", + "78": "# This is a level 1 header", + "79": "# This is a level 1 header", + "80": "### This is a level 3 header", + "81": "### This is a level 3 header", + "82": "# This is a header with a label {*label*}", + "83": "# This is a header with a label {*label*}", + "108": "A link to help for the ['breakindent']('breakindent') option", + "109": "A link to help for the ['breakindent']('breakindent') option", + "123": "A link to the [Links](*links*) section", + "124": "A link to the [Links](*links*) section", + "139": "A link to [the vim-tutor-mode tutorial](@tutor:tutor)", + "140": "A link to [the vim-tutor-mode tutorial](@tutor:tutor)", + "157": "~~~ viml", + "158": "echom 'the value of &number is'.string(&number)", + "159": "~~~", + "161": "~~~ viml", + "162": "echom 'the value of &number is'.string(&number)", + "163": "~~~", + "188": "~~~ normal", + "189": "d2w", + "190": "~~~", + "192": "~~~ normal", + "193": "d2w", + "194": "~~~", + "206": "`d2w`{normal}", + "207": "`d2w`{normal}", + "244": -1 + } +} From 8e48d7c19e0a01d17fbda1c21a8fa69c039cec7a Mon Sep 17 00:00:00 2001 From: Felipe Morales Date: Thu, 13 Jul 2017 19:02:53 -0400 Subject: [PATCH 05/58] tutor: disable old method for setting "expect" regions tutor: remove movement mappings --- runtime/autoload/tutor.vim | 163 -------------------- runtime/ftplugin/tutor.vim | 12 +- runtime/tutor/en/vim-01-beginner.tutor.json | 4 - 3 files changed, 2 insertions(+), 177 deletions(-) diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim index 1f0668c1b3..2217fabeda 100644 --- a/runtime/autoload/tutor.vim +++ b/runtime/autoload/tutor.vim @@ -17,30 +17,6 @@ endfunction " Mappings: {{{1 -function! s:CheckMaps() - nmap -endfunction - -function! s:MapKeyWithRedirect(key, cmd) - if maparg(a:key) !=# '' - redir => l:keys - silent call s:CheckMaps() - redir END - let l:key_list = split(l:keys, '\n') - - let l:raw_map = filter(copy(l:key_list), "v:val =~# '\\* ".a:key."'") - if len(l:raw_map) == 0 - exe "nnoremap ".a:key." ".a:cmd - return - endif - let l:map_data = split(l:raw_map[0], '\s*') - - exe "nnoremap ".l:map_data[0]." ".a:cmd - else - exe "nnoremap ".a:key." ".a:cmd - endif -endfunction - function! tutor#MouseDoubleClick() if foldclosed(line('.')) > -1 normal! zo @@ -60,111 +36,11 @@ function! tutor#InjectCommand() endfunction function! tutor#SetNormalMappings() - call s:MapKeyWithRedirect('l', 'tutor#ForwardSkipConceal(v:count1)') - call s:MapKeyWithRedirect('h', 'tutor#BackwardSkipConceal(v:count1)') - call s:MapKeyWithRedirect('', 'tutor#ForwardSkipConceal(v:count1)') - call s:MapKeyWithRedirect('', 'tutor#BackwardSkipConceal(v:count1)') - nnoremap :call tutor#FollowLink(0) nnoremap <2-LeftMouse> :call tutor#MouseDoubleClick() nnoremap >> :call tutor#InjectCommand() endfunction -function! tutor#SetSampleTextMappings() - noremap A :if match(getline('.'), '^--->') > -1 \| call search('\s{\@=', 'Wc') \| startinsert \| else \| startinsert! \| endif - noremap $ :if match(getline('.'), '^--->') > -1 \| call search('.\s{\@=', 'Wc') \| else \| call search('$', 'Wc') \| endif - onoremap $ :if match(getline('.'), '^--->') > -1 \| call search('.\s{\@=', 'Wc') \| else \| call search('$', 'Wc') \| endif - noremap ^ :if match(getline('.'), '^--->') > -1 \| call search('\(--->\s\)\@<=.', 'bcW') \| else \| call search('^', 'bcW') \|endif - onoremap ^ :if match(getline('.'), '^--->') > -1 \| call search('\(--->\s\)\@<=.', 'bcW') \| else \| call search('^', 'bcW') \|endif - nmap 0 ^ - nmap ^ - nmap $ - imap ^:startinsert - imap $:startinsert - noremap I :exe "normal! 0" \| startinsert -endfunction - -" Navigation: {{{1 - -" taken from http://stackoverflow.com/a/24224578 - -function! tutor#ForwardSkipConceal(count) - let cnt=a:count - let mvcnt=0 - let c=col('.') - let l=line('.') - let lc=col('$') - let line=getline('.') - while cnt - if c>=lc - let mvcnt+=cnt - break - endif - if stridx(&concealcursor, 'n')==-1 - let isconcealed=0 - else - let [isconcealed, cchar, group] = synconcealed(l, c) - endif - if isconcealed - let cnt-=strchars(cchar) - let oldc=c - let c+=1 - while c < lc - let [isconcealed2, cchar2, group2] = synconcealed(l, c) - if !isconcealed2 || cchar2 != cchar - break - endif - let c+= 1 - endwhile - let mvcnt+=strchars(line[oldc-1:c-2]) - else - let cnt-=1 - let mvcnt+=1 - let c+=len(matchstr(line[c-1:], '.')) - endif - endwhile - return mvcnt.'l' -endfunction - -function! tutor#BackwardSkipConceal(count) - let cnt=a:count - let mvcnt=0 - let c=col('.') - let l=line('.') - let lc=0 - let line=getline('.') - while cnt - if c<=1 - let mvcnt+=cnt - break - endif - if stridx(&concealcursor, 'n')==-1 || c == 0 - let isconcealed=0 - else - let [isconcealed, cchar, group]=synconcealed(l, c-1) - endif - if isconcealed - let cnt-=strchars(cchar) - let oldc=c - let c-=1 - while c>1 - let [isconcealed2, cchar2, group2] = synconcealed(l, c-1) - if !isconcealed2 || cchar2 != cchar - break - endif - let c-=1 - endwhile - let c = max([c, 1]) - let mvcnt+=strchars(line[c-1:oldc-2]) - else - let cnt-=1 - let mvcnt+=1 - let c-=len(matchstr(line[:c-2], '.$')) - endif - endwhile - return mvcnt.'h' -endfunction - " Hypertext: {{{1 function! tutor#FollowLink(force) @@ -217,46 +93,7 @@ function! tutor#LoadMetadata() endfunction " Marks: {{{1 -" Internal: {{{2 -function! tutor#PlaceXMarks() - call cursor(1, 1) - let b:tutor_sign_id = 1 - while search('^--->', 'W') > 0 - call tutor#CheckText(getline('.')) - let b:tutor_sign_id+=1 - endwhile - call cursor(1, 1) -endfunction -function! tutor#CheckText(text) - if match(a:text, '{expect:ANYTHING}\s*$') == -1 - if match(getline('.'), '^--->\s*$') > -1 - exe "sign place ".b:tutor_sign_id." line=".line('.')." name=tutorbad buffer=".bufnr('%') - else - if match(getline('.'), '|expect:.\+|') == -1 - let l:cur_text = matchstr(a:text, '---> \zs.\{-}\ze {expect:') - let l:expected_text = matchstr(a:text, '{expect:\zs.*\ze}\s*$') - else - let l:cur_text = matchstr(a:text, '---> \zs.\{-}\ze |expect:') - let l:expected_text = matchstr(a:text, '|expect:\zs.*\ze|\s*$') - endif - if l:cur_text ==# l:expected_text - exe "sign place ".b:tutor_sign_id." line=".line('.')." name=tutorok buffer=".bufnr('%') - else - exe "sign place ".b:tutor_sign_id." line=".line('.')." name=tutorbad buffer=".bufnr('%') - endif - endif - endif -endfunction - -function! tutor#XmarksOnTextChanged() - let l:text = getline('.') - if match(l:text, '^--->') > -1 - call tutor#CheckText(l:text) - endif -endfunction - -" External: {{{2 function! tutor#ApplyMarks() if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect') let b:tutor_sign_id = 1 diff --git a/runtime/ftplugin/tutor.vim b/runtime/ftplugin/tutor.vim index 660c1afaf5..ec55472b78 100644 --- a/runtime/ftplugin/tutor.vim +++ b/runtime/ftplugin/tutor.vim @@ -36,9 +36,6 @@ endif " Mappings: {{{1 call tutor#SetNormalMappings() -if exists('b:tutor_metadata') && b:tutor_metadata['settings']['use_maps'] == 1 - call tutor#SetSampleTextMappings() -endif " Checks: {{{1 @@ -46,11 +43,6 @@ sign define tutorok text=✓ texthl=tutorOK sign define tutorbad text=✗ texthl=tutorX if !exists('g:tutor_debug') || g:tutor_debug == 0 - if !(exists('b:tutor_metadata') && b:tutor_metadata['settings']['check_internal_expects'] == '0') - call tutor#PlaceXMarks() - autocmd! TextChanged,TextChangedI call tutor#XmarksOnTextChanged() - else - call tutor#ApplyMarks() - autocmd! TextChanged,TextChangedI call tutor#ApplyMarksOnChanged() - endif + call tutor#ApplyMarks() + autocmd! TextChanged,TextChangedI call tutor#ApplyMarksOnChanged() endif diff --git a/runtime/tutor/en/vim-01-beginner.tutor.json b/runtime/tutor/en/vim-01-beginner.tutor.json index 5bf50581ab..3f55971a09 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor.json +++ b/runtime/tutor/en/vim-01-beginner.tutor.json @@ -1,8 +1,4 @@ { - "settings" : { - "use_maps": 0, - "check_internal_expects": 0 - }, "expect": { "24": -1, "102": "The cow jumped over the moon.", From 37af8599307819c66da287ed3cb0babf6295dbfc Mon Sep 17 00:00:00 2001 From: Felipe Morales Date: Fri, 14 Jul 2017 19:46:31 -0400 Subject: [PATCH 06/58] tutor: update syntax sampletext regions no longer supported make sure tutorExpect is available don't conceal code region delimiters --- runtime/autoload/tutor.vim | 3 ++- runtime/syntax/tutor.vim | 22 ++++++++-------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim index 2217fabeda..c03326c7fb 100644 --- a/runtime/autoload/tutor.vim +++ b/runtime/autoload/tutor.vim @@ -95,11 +95,12 @@ endfunction " Marks: {{{1 function! tutor#ApplyMarks() + hi! link tutorExpect Special if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect') let b:tutor_sign_id = 1 for expct in keys(b:tutor_metadata['expect']) let lnum = eval(expct) - call matchaddpos('tutorSampleText', [lnum]) + call matchaddpos('tutorExpect', [lnum]) call tutor#CheckLine(lnum) endfor endif diff --git a/runtime/syntax/tutor.vim b/runtime/syntax/tutor.vim index bce9189660..fbf159582a 100644 --- a/runtime/syntax/tutor.vim +++ b/runtime/syntax/tutor.vim @@ -31,26 +31,20 @@ syn keyword tutorMarks TODO NOTE IMPORTANT TIP ATTENTION EXERCISE syn keyword tutorMarks todo note tip attention exercise syn keyword tutorMarks Todo Note Tip Excersise -syn match tutorTextMark /\\\@/ conceal cchar=→ -syn region tutorSampleText start=/^\(--->\)\@=/ end=/$/ keepend contains=@SPELL -syn match tutorSampleTextMark /^--->/ contained containedin=tutorSampleText conceal cchar=→ -syn match tutorSampleTextExpect /\}\@ Date: Sat, 15 Jul 2017 18:00:00 +0200 Subject: [PATCH 07/58] test: mouse_spec: remove obnoxious wait times (#7029) helpers.skip_fragile() already skips the problematic tests on the ASan build. But the 15s timeout plus 5s 'mousetime' cause the tests to take 1+ minutes anyways. --- test/functional/ui/mouse_spec.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index 35af34015d..3daf92eea0 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -13,9 +13,6 @@ describe('ui/mouse/input', function() clear() meths.set_option('mouse', 'a') meths.set_option('listchars', 'eol:$') - -- set mousetime to very high value to ensure that even in valgrind/travis, - -- nvim will still pick multiple clicks - meths.set_option('mousetime', 5000) screen = Screen.new(25, 5) screen:attach() screen:set_default_attr_ids({ @@ -119,7 +116,6 @@ describe('ui/mouse/input', function() sel = { bold=true }, fill = { reverse=true } }) - screen.timeout = 15000 end) it('in tabline on filler space moves tab to the end', function() From d8fe63199f10c944312e802010bec13b5950120a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 15 Jul 2017 18:13:49 +0200 Subject: [PATCH 08/58] intro: change byline to "by al." (#6984) Several people have suggested that the "by Bram" byline is misleading, it implies that Bram is actively involved with the project. Up to now we left it as an homage. Bram agreed that it is misleading, and suggested a mention somewhere other than the intro. --- runtime/doc/intro.txt | 3 +-- runtime/doc/nvim.txt | 2 ++ src/nvim/version.c | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index 88d04aa76b..b9cc94ce5f 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -249,8 +249,7 @@ Vi "the original". Without further remarks this is the version of Vi that appeared in Sun OS 4.x. ":version" returns "Version 3.7, 6/7/85". Sometimes other versions are referred to. Only runs under Unix. Source code only available with a - license. More information on Vi can be found through: - http://vi-editor.org [doesn't currently work...] + license. *Nvi* Nvi The "New" Vi. The version of Vi that comes with BSD 4.4 and FreeBSD. Very good compatibility with the original Vi, with a few extensions. diff --git a/runtime/doc/nvim.txt b/runtime/doc/nvim.txt index 29059f83d9..f3f4305ad5 100644 --- a/runtime/doc/nvim.txt +++ b/runtime/doc/nvim.txt @@ -6,6 +6,8 @@ Nvim *nvim* *nvim-intro* +Nvim is based on Vim by Bram Moolenaar. + If you are new to Vim see |help.txt|, or type ":Tutor". If you already use Vim see |nvim-from-vim| for a quickstart. diff --git a/src/nvim/version.c b/src/nvim/version.c index 44e9d5e26c..c7b8220776 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -993,7 +993,7 @@ void intro_message(int colon) static char *(lines[]) = { N_(NVIM_VERSION_LONG), "", - N_("by Bram Moolenaar et al."), + N_("by al."), N_("Nvim is open source and freely distributable"), N_("https://neovim.io/community"), "", From 4dee942e732d41ad62b732c0a39719d9405bc928 Mon Sep 17 00:00:00 2001 From: Andy Russell Date: Sat, 15 Jul 2017 14:51:51 -0400 Subject: [PATCH 09/58] provider/clipboard.vim: capture/display errors (#6684) TODO: handle errors in the `get`. systemlist() should take an options dictionary like jobstart(), which may specify a stderr handler. References #6565 --- runtime/autoload/provider/clipboard.vim | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index a67681d28e..47f4271091 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -137,16 +137,24 @@ function! s:clipboard.set(lines, regtype, reg) abort let argv = split(s:copy[a:reg], " ") let selection.detach = s:cache_enabled let selection.cwd = "/" + call extend(selection, { + \ 'on_stdout': function('s:set_errhandler'), + \ 'on_stderr': function('s:set_errhandler'), + \ }) let jobid = jobstart(argv, selection) - if jobid <= 0 - echohl WarningMsg - echo "clipboard: error when invoking provider" - echohl None - return 0 + if jobid > 0 + call jobsend(jobid, a:lines) + call jobclose(jobid, 'stdin') + let selection.owner = jobid + endif +endfunction + +function! s:set_errhandler(job_id, data, event) abort + if a:job_id <= 0 + echohl WarningMsg + echo 'clipboard: error when invoking provider: ' . join(a:data) + echohl None endif - call jobsend(jobid, a:lines) - call jobclose(jobid, 'stdin') - let selection.owner = jobid endfunction function! provider#clipboard#Call(method, args) abort From 33efad7dbc26750486d1910157345c508684125e Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sun, 16 Jul 2017 14:23:56 +0200 Subject: [PATCH 10/58] vim-patch:8.0.0017 Problem: Cannot get the number of the current quickfix or location list. Solution: Use the current list if "nr" in "what" is zero. (Yegappan Lakshmanan) Remove debug command from test. https://github.com/vim/vim/commit/890680ca6364386fabb271c85e0755bcaa6a33c1 --- runtime/doc/eval.txt | 3 ++- src/nvim/quickfix.c | 9 ++++++--- src/nvim/testdir/test_quickfix.vim | 9 ++++++--- src/nvim/version.c | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index cca62f1469..8882043162 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4273,7 +4273,8 @@ getqflist([{what}]) *getqflist()* If the optional {what} dictionary argument is supplied, then returns only the items listed in {what} as a dictionary. The following string items are supported in {what}: - nr get information for this quickfix list + nr get information for this quickfix list; zero + means the current quickfix list title get the list title winid get the |window-ID| (if opened) all all of the above quickfix properties diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index bd5dfa92cc..6797632008 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4025,9 +4025,12 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) { // Use the specified quickfix/location list if (di->di_tv.v_type == VAR_NUMBER) { - qf_idx = (int)di->di_tv.vval.v_number - 1; - if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { - return FAIL; + // for zero use the current list + if (di->di_tv.vval.v_number != 0) { + qf_idx = (int)di->di_tv.vval.v_number - 1; + if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { + return FAIL; + } } flags |= QF_GETLIST_NR; } else { diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 75ab01f013..64d0173965 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1427,12 +1427,10 @@ function! Test_two_windows() laddexpr 'one.txt:3:one one one' let loc_one = getloclist(one_id) -echo string(loc_one) call assert_equal('Xone/a/one.txt', bufname(loc_one[1].bufnr)) call assert_equal(3, loc_one[1].lnum) let loc_two = getloclist(two_id) -echo string(loc_two) call assert_equal('Xtwo/a/two.txt', bufname(loc_two[1].bufnr)) call assert_equal(5, loc_two[1].lnum) @@ -1532,6 +1530,11 @@ function Xproperty_tests(cchar) call assert_equal('N1', g:Xgetlist({'all':1}).title) call g:Xsetlist([], ' ', {'title' : 'N2'}) call assert_equal(qfnr + 1, g:Xgetlist({'all':1}).nr) + + let res = g:Xgetlist({'nr': 0}) + call assert_equal(qfnr + 1, res.nr) + call assert_equal(['nr'], keys(res)) + call g:Xsetlist([], ' ', {'title' : 'N3'}) call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title) @@ -1544,7 +1547,7 @@ function Xproperty_tests(cchar) call assert_equal({}, g:Xgetlist({'abc':1})) if a:cchar == 'l' - call assert_equal({}, getloclist(99, ['title'])) + call assert_equal({}, getloclist(99, {'title': 1})) endif endfunction diff --git a/src/nvim/version.c b/src/nvim/version.c index c7b8220776..a8b1e4d22d 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -712,7 +712,7 @@ static const int included_patches[] = { // 20, 19, // 18, - // 17, + 17, // 16 NA // 15 NA // 14 NA From c00300ecdd694cad53da74256e52675f79a3fd78 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sun, 16 Jul 2017 16:58:25 +0200 Subject: [PATCH 11/58] vim-patch:8.0.0026 Problem: Error format with %W, %C and %Z does not work. (Gerd Wachsmuth) Solution: Skip code when qf_multiignore is set. (Lcd) https://github.com/vim/vim/commit/9b4579481892a62e7e002498b9eddaaf75bbda49 --- src/nvim/quickfix.c | 62 +++++++++++++++--------------- src/nvim/testdir/test_quickfix.vim | 23 +++++++++++ src/nvim/version.c | 2 +- 3 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 6797632008..29beb35495 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -875,36 +875,38 @@ restofline: qi->qf_multiignore = false; // reset continuation } else if (vim_strchr((char_u *)"CZ", idx) != NULL) { // continuation of multi-line msg - qfline_T *qfprev = qi->qf_lists[qi->qf_curlist].qf_last; - if (qfprev == NULL) { - return QF_FAIL; - } - if (*fields->errmsg && !qi->qf_multiignore) { - size_t len = STRLEN(qfprev->qf_text); - qfprev->qf_text = xrealloc(qfprev->qf_text, - len + STRLEN(fields->errmsg) + 2); - qfprev->qf_text[len] = '\n'; - STRCPY(qfprev->qf_text + len + 1, fields->errmsg); - } - if (qfprev->qf_nr == -1) { - qfprev->qf_nr = fields->enr; - } - if (vim_isprintc(fields->type) && !qfprev->qf_type) { - qfprev->qf_type = fields->type; // only printable chars allowed - } - if (!qfprev->qf_lnum) { - qfprev->qf_lnum = fields->lnum; - } - if (!qfprev->qf_col) { - qfprev->qf_col = fields->col; - } - qfprev->qf_viscol = fields->use_viscol; - if (!qfprev->qf_fnum) { - qfprev->qf_fnum = qf_get_fnum(qi, qi->qf_directory, - *fields->namebuf || qi->qf_directory - ? fields->namebuf - : qi->qf_currfile && fields->valid - ? qi->qf_currfile : 0); + if (!qi->qf_multiignore) { + qfline_T *qfprev = qi->qf_lists[qi->qf_curlist].qf_last; + if (qfprev == NULL) { + return QF_FAIL; + } + if (*fields->errmsg && !qi->qf_multiignore) { + size_t len = STRLEN(qfprev->qf_text); + qfprev->qf_text = xrealloc(qfprev->qf_text, + len + STRLEN(fields->errmsg) + 2); + qfprev->qf_text[len] = '\n'; + STRCPY(qfprev->qf_text + len + 1, fields->errmsg); + } + if (qfprev->qf_nr == -1) { + qfprev->qf_nr = fields->enr; + } + if (vim_isprintc(fields->type) && !qfprev->qf_type) { + qfprev->qf_type = fields->type; // only printable chars allowed + } + if (!qfprev->qf_lnum) { + qfprev->qf_lnum = fields->lnum; + } + if (!qfprev->qf_col) { + qfprev->qf_col = fields->col; + } + qfprev->qf_viscol = fields->use_viscol; + if (!qfprev->qf_fnum) { + qfprev->qf_fnum = qf_get_fnum(qi, qi->qf_directory, + *fields->namebuf || qi->qf_directory + ? fields->namebuf + : qi->qf_currfile && fields->valid + ? qi->qf_currfile : 0); + } } if (idx == 'Z') { qi->qf_multiline = qi->qf_multiignore = false; diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 64d0173965..e2966ec5c4 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -816,6 +816,29 @@ function! Test_efm_dirstack() call delete('habits1.txt') endfunction +" Test for resync after continuing an ignored message +function! Xefm_ignore_continuations(cchar) + call s:setup_commands(a:cchar) + + let save_efm = &efm + + let &efm = + \ '%Eerror %m %l,' . + \ '%-Wignored %m %l,' . + \ '%+Cmore ignored %m %l,' . + \ '%Zignored end' + Xgetexpr ['ignored warning 1', 'more ignored continuation 2', 'ignored end', 'error resync 4'] + let l = map(g:Xgetlist(), '[v:val.text, v:val.valid, v:val.lnum, v:val.type]') + call assert_equal([['resync', 1, 4, 'E']], l) + + let &efm = save_efm +endfunction + +function! Test_efm_ignore_continuations() + call Xefm_ignore_continuations('c') + call Xefm_ignore_continuations('l') +endfunction + " Tests for invalid error format specifies function Xinvalid_efm_Tests(cchar) call s:setup_commands(a:cchar) diff --git a/src/nvim/version.c b/src/nvim/version.c index a8b1e4d22d..efda51547e 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -703,7 +703,7 @@ static const int included_patches[] = { // 29 NA // 28 NA // 27 NA - // 26, + 26, // 25, // 24 NA // 23, From ffa2e4354986cc12b8d44781708f0b9d51b84f31 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sun, 16 Jul 2017 17:28:14 +0200 Subject: [PATCH 12/58] vim-patch:8.0.0078 Problem: Accessing freed memory in quickfix. Solution: Reset pointer when freeing 'errorformat'. (Domenique Pelle) https://github.com/vim/vim/commit/63bed3d319b5d90765dbdae93a3579b6322d79fb --- src/nvim/quickfix.c | 5 ++++- src/nvim/testdir/test_quickfix.vim | 10 ++++++++++ src/nvim/version.c | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 29beb35495..6814ca855b 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -383,6 +383,8 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr, return 0; } +static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls + static void free_efm_list(efm_T **efm_first) { for (efm_T *efm_ptr = *efm_first; efm_ptr != NULL; efm_ptr = *efm_first) { @@ -390,6 +392,8 @@ static void free_efm_list(efm_T **efm_first) vim_regfree(efm_ptr->prog); xfree(efm_ptr); } + + fmt_start = NULL; } // Parse 'errorformat' option @@ -671,7 +675,6 @@ static int qf_parse_line(qf_info_T *qi, char_u *linebuf, size_t linelen, efm_T *fmt_first, qffields_T *fields) { efm_T *fmt_ptr; - static efm_T *fmt_start = NULL; // cached across calls size_t len; int i; int idx = 0; diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index e2966ec5c4..316ef6f221 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1630,3 +1630,13 @@ function! Test_Autocmd_Exception() set efm&vim endfunction + +function Test_caddbuffer() + " This used to cause a memory access in freed memory + let save_efm = &efm + set efm=%EEEE%m,%WWWW,%+CCCC%>%#,%GGGG%.# + cgetexpr ['WWWW', 'EEEE', 'CCCC'] + let &efm = save_efm + cad + bwipe! +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index efda51547e..d10d78d695 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -651,7 +651,7 @@ static const int included_patches[] = { // 81, // 80 NA // 79, - // 78, + 78, // 77 NA // 76 NA // 75, From 875c356a83b07573b87b2995478fc3b0703bc023 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sun, 16 Jul 2017 17:32:32 +0200 Subject: [PATCH 13/58] vim-patch:8.0.0079 Problem: Accessing freed memory in quickfix. (Domenique Pelle) Solution: Do not free the current list when adding to it. https://github.com/vim/vim/commit/2b946c9f9b0e0fd805fb8f3e4c16e0a68ae13129 --- src/nvim/quickfix.c | 12 +- src/nvim/testdir/test_quickfix.vim | 249 +++++++++++++++-------------- src/nvim/version.c | 2 +- 3 files changed, 139 insertions(+), 124 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 6814ca855b..44469a77df 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -972,6 +972,7 @@ qf_init_ext( NULL, 0, 0 }; qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; qfline_T *old_last = NULL; + bool adding = false; static efm_T *fmt_first = NULL; char_u *efm; static char_u *last_efm = NULL; @@ -997,6 +998,7 @@ qf_init_ext( qf_new_list(qi, qf_title); } else if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { // Adding to existing list, use last entry. + adding = true; old_last = qi->qf_lists[qi->qf_curlist].qf_last; } @@ -1113,10 +1115,12 @@ qf_init_ext( } EMSG(_(e_readerrf)); error2: - qf_free(qi, qi->qf_curlist); - qi->qf_listcount--; - if (qi->qf_curlist > 0) { - qi->qf_curlist--; + if (!adding) { + qf_free(qi, qi->qf_curlist); + qi->qf_listcount--; + if (qi->qf_curlist > 0) { + qi->qf_curlist--; + } } qf_init_end: if (state.fd != NULL) { diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 316ef6f221..08a749f65e 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -6,7 +6,7 @@ endif set encoding=utf-8 -function! s:setup_commands(cchar) +func s:setup_commands(cchar) if a:cchar == 'c' command! -nargs=* -bang Xlist clist command! -nargs=* Xgetexpr cgetexpr @@ -68,10 +68,10 @@ function! s:setup_commands(cchar) let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) endif -endfunction +endfunc " Tests for the :clist and :llist commands -function XlistTests(cchar) +func XlistTests(cchar) call s:setup_commands(a:cchar) " With an empty list, command should return error @@ -128,17 +128,17 @@ function XlistTests(cchar) let l = split(result, "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) -endfunction +endfunc -function Test_clist() +func Test_clist() call XlistTests('c') call XlistTests('l') -endfunction +endfunc " Tests for the :colder, :cnewer, :lolder and :lnewer commands " Note that this test assumes that a quickfix/location list is " already set by the caller. -function XageTests(cchar) +func XageTests(cchar) call s:setup_commands(a:cchar) " Jumping to a non existent list should return error @@ -171,20 +171,20 @@ function XageTests(cchar) Xnewer 2 let l = g:Xgetlist() call assert_equal('Line3', l[0].text) -endfunction +endfunc -function Test_cage() +func Test_cage() let list = [{'bufnr': 1, 'lnum': 1}] call setqflist(list) call XageTests('c') call setloclist(0, list) call XageTests('l') -endfunction +endfunc " Tests for the :cwindow, :lwindow :cclose, :lclose, :copen and :lopen " commands -function XwindowTests(cchar) +func XwindowTests(cchar) call s:setup_commands(a:cchar) " Create a list with no valid entries @@ -227,16 +227,16 @@ function XwindowTests(cchar) " Calling cwindow should close the quickfix window with no valid errors Xwindow call assert_true(winnr('$') == 1) -endfunction +endfunc -function Test_cwindow() +func Test_cwindow() call XwindowTests('c') call XwindowTests('l') -endfunction +endfunc " Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile " commands. -function XfileTests(cchar) +func XfileTests(cchar) call s:setup_commands(a:cchar) call writefile(['Xtestfile1:700:10:Line 700', @@ -275,16 +275,16 @@ function XfileTests(cchar) \ l[1].lnum == 333 && l[1].col == 88 && l[1].text ==# 'Line 333') call delete('Xqftestfile1') -endfunction +endfunc -function Test_cfile() +func Test_cfile() call XfileTests('c') call XfileTests('l') -endfunction +endfunc " Tests for the :cbuffer, :lbuffer, :caddbuffer, :laddbuffer, :cgetbuffer and " :lgetbuffer commands. -function XbufferTests(cchar) +func XbufferTests(cchar) call s:setup_commands(a:cchar) enew! @@ -316,26 +316,26 @@ function XbufferTests(cchar) \ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750') enew! -endfunction +endfunc -function Test_cbuffer() +func Test_cbuffer() call XbufferTests('c') call XbufferTests('l') -endfunction +endfunc -function XexprTests(cchar) +func XexprTests(cchar) call s:setup_commands(a:cchar) call assert_fails('Xexpr 10', 'E777:') -endfunction +endfunc -function Test_cexpr() +func Test_cexpr() call XexprTests('c') call XexprTests('l') -endfunction +endfunc " Tests for :cnext, :cprev, :cfirst, :clast commands -function Xtest_browse(cchar) +func Xtest_browse(cchar) call s:setup_commands(a:cchar) call s:create_test_file('Xqftestfile1') @@ -366,14 +366,14 @@ function Xtest_browse(cchar) call delete('Xqftestfile1') call delete('Xqftestfile2') -endfunction +endfunc -function Test_browse() +func Test_browse() call Xtest_browse('c') call Xtest_browse('l') -endfunction +endfunc -function! s:test_xhelpgrep(cchar) +func s:test_xhelpgrep(cchar) call s:setup_commands(a:cchar) Xhelpgrep quickfix Xopen @@ -385,9 +385,9 @@ function! s:test_xhelpgrep(cchar) call assert_true(w:quickfix_title =~ title_text, w:quickfix_title) " This wipes out the buffer, make sure that doesn't cause trouble. Xclose -endfunction +endfunc -function Test_helpgrep() +func Test_helpgrep() call s:test_xhelpgrep('c') helpclose call s:test_xhelpgrep('l') @@ -425,7 +425,7 @@ func Test_vimgreptitle() augroup! QfBufWinEnter endfunc -function XqfTitleTests(cchar) +func XqfTitleTests(cchar) call s:setup_commands(a:cchar) Xgetexpr ['file:1:1:message'] @@ -444,16 +444,16 @@ function XqfTitleTests(cchar) endif call assert_equal(title, w:quickfix_title) Xclose -endfunction +endfunc " Tests for quickfix window's title -function Test_qf_title() +func Test_qf_title() call XqfTitleTests('c') call XqfTitleTests('l') -endfunction +endfunc " Tests for 'errorformat' -function Test_efm() +func Test_efm() let save_efm = &efm set efm=%EEEE%m,%WWWW%m,%+CCCC%.%#,%-GGGG%.%# cgetexpr ['WWWW', 'EEEE', 'CCCC'] @@ -466,7 +466,7 @@ function Test_efm() let l = strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]'))) call assert_equal("[['W', 1], ['ZZZZ', 0], ['E^@CCCC', 1], ['YYYY', 0]]", l) let &efm = save_efm -endfunction +endfunc " This will test for problems in quickfix: " A. incorrectly copying location lists which caused the location list to show @@ -477,7 +477,7 @@ endfunction " window it belongs to. " " Set up the test environment: -function! ReadTestProtocol(name) +func ReadTestProtocol(name) let base = substitute(a:name, '\v^test://(.*)%(\.[^.]+)?', '\1', '') let word = substitute(base, '\v(.*)\..*', '\1', '') @@ -496,9 +496,9 @@ function! ReadTestProtocol(name) setl nomodifiable setl readonly exe 'doautocmd BufRead ' . substitute(a:name, '\v^test://(.*)', '\1', '') -endfunction +endfunc -function Test_locationlist() +func Test_locationlist() enew augroup testgroup @@ -578,15 +578,15 @@ function Test_locationlist() wincmd n | only augroup! testgroup -endfunction + endfunc -function Test_locationlist_curwin_was_closed() +func Test_locationlist_curwin_was_closed() augroup testgroup au! autocmd BufReadCmd test_curwin.txt call R(expand("")) augroup END - function! R(n) + func! R(n) quit endfunc @@ -597,9 +597,9 @@ function Test_locationlist_curwin_was_closed() call assert_fails('lrewind', 'E924:') augroup! testgroup -endfunction + endfunc -function Test_locationlist_cross_tab_jump() +func Test_locationlist_cross_tab_jump() call writefile(['loclistfoo'], 'loclistfoo') call writefile(['loclistbar'], 'loclistbar') set switchbuf=usetab @@ -613,10 +613,10 @@ function Test_locationlist_cross_tab_jump() set switchbuf&vim call delete('loclistfoo') call delete('loclistbar') -endfunction +endfunc " More tests for 'errorformat' -function! Test_efm1() +func Test_efm1() if !has('unix') " The 'errorformat' setting is different on non-Unix systems. " This test works only on Unix-like systems. @@ -734,10 +734,10 @@ function! Test_efm1() call delete('Xerrorfile1') call delete('Xerrorfile2') call delete('Xtestfile') -endfunction + endfunc " Test for quickfix directory stack support -function! s:dir_stack_tests(cchar) +func s:dir_stack_tests(cchar) call s:setup_commands(a:cchar) let save_efm=&efm @@ -779,10 +779,10 @@ function! s:dir_stack_tests(cchar) call assert_equal(5, qf[11].lnum) let &efm=save_efm -endfunction +endfunc " Tests for %D and %X errorformat options -function! Test_efm_dirstack() +func Test_efm_dirstack() " Create the directory stack and files call mkdir('dir1') call mkdir('dir1/a') @@ -814,10 +814,10 @@ function! Test_efm_dirstack() call delete('dir1', 'rf') call delete('dir2', 'rf') call delete('habits1.txt') -endfunction +endfunc " Test for resync after continuing an ignored message -function! Xefm_ignore_continuations(cchar) +func Xefm_ignore_continuations(cchar) call s:setup_commands(a:cchar) let save_efm = &efm @@ -832,15 +832,15 @@ function! Xefm_ignore_continuations(cchar) call assert_equal([['resync', 1, 4, 'E']], l) let &efm = save_efm -endfunction +endfunc -function! Test_efm_ignore_continuations() +func Test_efm_ignore_continuations() call Xefm_ignore_continuations('c') call Xefm_ignore_continuations('l') -endfunction +endfunc " Tests for invalid error format specifies -function Xinvalid_efm_Tests(cchar) +func Xinvalid_efm_Tests(cchar) call s:setup_commands(a:cchar) let save_efm = &efm @@ -873,17 +873,17 @@ function Xinvalid_efm_Tests(cchar) call assert_fails('Xexpr ["Entering dir abc", "abc.txt:1:Hello world"]', 'E379:') let &efm = save_efm -endfunction +endfunc -function Test_invalid_efm() +func Test_invalid_efm() call Xinvalid_efm_Tests('c') call Xinvalid_efm_Tests('l') -endfunction +endfunc " TODO: " Add tests for the following formats in 'errorformat' " %r %O -function! Test_efm2() +func Test_efm2() let save_efm = &efm " Test for %s format in efm @@ -969,19 +969,19 @@ function! Test_efm2() call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) let &efm = save_efm -endfunction +endfunc -function XquickfixChangedByAutocmd(cchar) +func XquickfixChangedByAutocmd(cchar) call s:setup_commands(a:cchar) if a:cchar == 'c' let ErrorNr = 'E925' - function! ReadFunc() + func! ReadFunc() colder cgetexpr [] endfunc else let ErrorNr = 'E926' - function! ReadFunc() + func! ReadFunc() lolder lgetexpr [] endfunc @@ -1004,10 +1004,10 @@ function XquickfixChangedByAutocmd(cchar) augroup! testgroup endfunc -function Test_quickfix_was_changed_by_autocmd() +func Test_quickfix_was_changed_by_autocmd() call XquickfixChangedByAutocmd('c') call XquickfixChangedByAutocmd('l') -endfunction +endfunc func Test_caddbuffer_to_empty() helpgr quickfix @@ -1029,7 +1029,7 @@ func Test_cgetexpr_works() endfunc " Tests for the setqflist() and setloclist() functions -function SetXlistTests(cchar, bnum) +func SetXlistTests(cchar, bnum) call s:setup_commands(a:cchar) call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 1}, @@ -1064,9 +1064,9 @@ function SetXlistTests(cchar, bnum) call g:Xsetlist([]) let l = g:Xgetlist() call assert_equal(0, len(l)) -endfunction +endfunc -function Test_setqflist() +func Test_setqflist() new Xtestfile | only let bnum = bufnr('%') call setline(1, range(1,5)) @@ -1076,9 +1076,9 @@ function Test_setqflist() enew! call delete('Xtestfile') -endfunction +endfunc -function Xlist_empty_middle(cchar) +func Xlist_empty_middle(cchar) call s:setup_commands(a:cchar) " create three quickfix lists @@ -1101,12 +1101,12 @@ function Xlist_empty_middle(cchar) call assert_equal(matchlen, len(g:Xgetlist())) endfunc -function Test_setqflist_empty_middle() +func Test_setqflist_empty_middle() call Xlist_empty_middle('c') call Xlist_empty_middle('l') -endfunction +endfunc -function Xlist_empty_older(cchar) +func Xlist_empty_older(cchar) call s:setup_commands(a:cchar) " create three quickfix lists @@ -1127,14 +1127,14 @@ function Xlist_empty_older(cchar) call assert_equal(twolen, len(g:Xgetlist())) Xnewer call assert_equal(threelen, len(g:Xgetlist())) -endfunction +endfunc -function Test_setqflist_empty_older() +func Test_setqflist_empty_older() call Xlist_empty_older('c') call Xlist_empty_older('l') -endfunction +endfunc -function! XquickfixSetListWithAct(cchar) +func XquickfixSetListWithAct(cchar) call s:setup_commands(a:cchar) let list1 = [{'filename': 'fnameA', 'text': 'A'}, @@ -1208,12 +1208,12 @@ function! XquickfixSetListWithAct(cchar) call assert_fails("call g:Xsetlist(list1, 0)", 'E928:') endfunc -function Test_quickfix_set_list_with_act() +func Test_quickfix_set_list_with_act() call XquickfixSetListWithAct('c') call XquickfixSetListWithAct('l') -endfunction +endfunc -function XLongLinesTests(cchar) +func XLongLinesTests(cchar) let l = g:Xgetlist() call assert_equal(4, len(l)) @@ -1231,9 +1231,9 @@ function XLongLinesTests(cchar) call assert_equal(10, len(l[3].text)) call g:Xsetlist([], 'r') -endfunction +endfunc -function s:long_lines_tests(cchar) +func s:long_lines_tests(cchar) call s:setup_commands(a:cchar) let testfile = 'samples/quickfix.txt' @@ -1254,22 +1254,22 @@ function s:long_lines_tests(cchar) exe 'edit' testfile exe 'Xbuffer' bufnr('%') call XLongLinesTests(a:cchar) -endfunction +endfunc -function Test_long_lines() +func Test_long_lines() call s:long_lines_tests('c') call s:long_lines_tests('l') -endfunction +endfunc -function! s:create_test_file(filename) +func s:create_test_file(filename) let l = [] for i in range(1, 20) call add(l, 'Line' . i) endfor call writefile(l, a:filename) -endfunction +endfunc -function! Test_switchbuf() +func Test_switchbuf() call s:create_test_file('Xqftestfile1') call s:create_test_file('Xqftestfile2') call s:create_test_file('Xqftestfile3') @@ -1356,9 +1356,9 @@ function! Test_switchbuf() call delete('Xqftestfile1') call delete('Xqftestfile2') call delete('Xqftestfile3') -endfunction +endfunc -function! Xadjust_qflnum(cchar) +func Xadjust_qflnum(cchar) call s:setup_commands(a:cchar) enew | only @@ -1383,17 +1383,17 @@ function! Xadjust_qflnum(cchar) enew! call delete(fname) -endfunction +endfunc -function! Test_adjust_lnum() +func Test_adjust_lnum() call setloclist(0, []) call Xadjust_qflnum('c') call setqflist([]) call Xadjust_qflnum('l') -endfunction +endfunc " Tests for the :grep/:lgrep and :grepadd/:lgrepadd commands -function! s:test_xgrep(cchar) +func s:test_xgrep(cchar) call s:setup_commands(a:cchar) " The following lines are used for the grep test. Don't remove. @@ -1412,9 +1412,9 @@ function! s:test_xgrep(cchar) set makeef=Temp_File_## silent Xgrepadd GrepAdd_Test_Text: test_quickfix.vim call assert_true(len(g:Xgetlist()) == 6) -endfunction +endfunc -function! Test_grep() +func Test_grep() if !has('unix') " The grepprg may not be set on non-Unix systems return @@ -1422,9 +1422,9 @@ function! Test_grep() call s:test_xgrep('c') call s:test_xgrep('l') -endfunction +endfunc -function! Test_two_windows() +func Test_two_windows() " Use one 'errorformat' for two windows. Add an expression to each of them, " make sure they each keep their own state. set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f' @@ -1465,7 +1465,7 @@ function! Test_two_windows() call delete('Xtwo', 'rf') endfunc -function XbottomTests(cchar) +func XbottomTests(cchar) call s:setup_commands(a:cchar) call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) @@ -1481,12 +1481,12 @@ function XbottomTests(cchar) endfunc " Tests for the :cbottom and :lbottom commands -function Test_cbottom() +func Test_cbottom() call XbottomTests('c') call XbottomTests('l') -endfunction +endfunc -function HistoryTest(cchar) +func HistoryTest(cchar) call s:setup_commands(a:cchar) call assert_fails(a:cchar . 'older 99', 'E380:') @@ -1526,7 +1526,7 @@ func Test_duplicate_buf() endfunc " Quickfix/Location list set/get properties tests -function Xproperty_tests(cchar) +func Xproperty_tests(cchar) call s:setup_commands(a:cchar) " Error cases @@ -1572,19 +1572,19 @@ function Xproperty_tests(cchar) if a:cchar == 'l' call assert_equal({}, getloclist(99, {'title': 1})) endif -endfunction + endfunc -function Test_qf_property() +func Test_qf_property() call Xproperty_tests('c') call Xproperty_tests('l') -endfunction + endfunc " Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands -function QfAutoCmdHandler(loc, cmd) +func QfAutoCmdHandler(loc, cmd) call add(g:acmds, a:loc . a:cmd) -endfunction +endfunc -function Test_Autocmd() +func Test_Autocmd() autocmd QuickFixCmdPre * call QfAutoCmdHandler('pre', expand('')) autocmd QuickFixCmdPost * call QfAutoCmdHandler('post', expand('')) @@ -1612,9 +1612,9 @@ function Test_Autocmd() \ 'precaddbuffer', \ 'postcaddbuffer'] call assert_equal(l, g:acmds) -endfunction +endfunc -function! Test_Autocmd_Exception() +func Test_Autocmd_Exception() set efm=%m lgetexpr '?' @@ -1629,14 +1629,25 @@ function! Test_Autocmd_Exception() call assert_equal('1', getloclist(0)[0].text) set efm&vim -endfunction +endfunc -function Test_caddbuffer() - " This used to cause a memory access in freed memory +func Test_caddbuffer_wrong() + " This used to cause a memory access in freed memory. let save_efm = &efm set efm=%EEEE%m,%WWWW,%+CCCC%>%#,%GGGG%.# cgetexpr ['WWWW', 'EEEE', 'CCCC'] let &efm = save_efm - cad + caddbuffer bwipe! endfunc + +func Test_caddexpr_wrong() + " This used to cause a memory access in freed memory. + cbuffer + cbuffer + copen + let save_efm = &efm + set efm=% + call assert_fails('caddexpr ""', 'E376:') + let &efm = save_efm +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index d10d78d695..62af3ab34d 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -650,7 +650,7 @@ static const int included_patches[] = { // 82 NA // 81, // 80 NA - // 79, + 79, 78, // 77 NA // 76 NA From e29ec131d67f3410dba85ab13ba4c617e5768269 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sun, 16 Jul 2017 17:39:53 +0200 Subject: [PATCH 14/58] vim-patch:8.0.0084 Problem: Using freed memory when adding to a quickfix list. (Domenique Pelle) Solution: Clear the directory name. https://github.com/vim/vim/commit/7618e00d3b8bfe064cfc524640d754607361f9df --- src/nvim/quickfix.c | 4 +++- src/nvim/testdir/test_quickfix.vim | 22 ++++++++++++++++++++++ src/nvim/version.c | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 44469a77df..f17075f0c4 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1417,7 +1417,7 @@ void copy_loclist(win_T *from, win_T *to) to->w_llist->qf_curlist = qi->qf_curlist; /* current list */ } -// Get buffer number for file "directory.fname". +// Get buffer number for file "directory/fname". // Also sets the b_has_qf_entry flag. static int qf_get_fnum(qf_info_T *qi, char_u *directory, char_u *fname) { @@ -2371,7 +2371,9 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_index = 0; qf_clean_dir_stack(&qi->qf_dir_stack); + qi->qf_directory = NULL; qf_clean_dir_stack(&qi->qf_file_stack); + qi->qf_currfile = NULL; } /* diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 08a749f65e..aff5fc2eed 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1651,3 +1651,25 @@ func Test_caddexpr_wrong() call assert_fails('caddexpr ""', 'E376:') let &efm = save_efm endfunc + +func Test_dirstack_cleanup() + " This used to cause a memory access in freed memory. + let save_efm = &efm + lexpr '0' + lopen + fun X(c) + let save_efm=&efm + set efm=%D%f + if a:c == 'c' + caddexpr '::' + else + laddexpr ':0:0' + endif + let &efm=save_efm + endfun + call X('c') + call X('l') + call setqflist([], 'r') + caddbuffer + let &efm = save_efm +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 62af3ab34d..2beb13edf8 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -645,7 +645,7 @@ static const int included_patches[] = { // 87 NA // 86, // 85, - // 84, + 84, 83, // 82 NA // 81, From 3241bce6116cdd17f93651f12b5908e57e1d3136 Mon Sep 17 00:00:00 2001 From: Felipe Morales Date: Sat, 15 Jul 2017 09:14:26 -0400 Subject: [PATCH 15/58] tutor: reorganize --- runtime/autoload/tutor.vim | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim index c03326c7fb..56e2283465 100644 --- a/runtime/autoload/tutor.vim +++ b/runtime/autoload/tutor.vim @@ -15,14 +15,25 @@ function! tutor#SetupVim() endif endfunction +" Loads metadata file, if available +function! tutor#LoadMetadata() + let b:tutor_metadata = json_decode(join(readfile(expand('%').'.json'), "\n")) +endfunction + " Mappings: {{{1 +function! tutor#SetNormalMappings() + nnoremap :call tutor#FollowLink(0) + nnoremap <2-LeftMouse> :call tutor#MouseDoubleClick() + nnoremap >> :call tutor#InjectCommand() +endfunction + function! tutor#MouseDoubleClick() if foldclosed(line('.')) > -1 normal! zo else if match(getline('.'), '^#\{1,} ') > -1 - normal! zc + silent normal! zc else call tutor#FollowLink(0) endif @@ -35,14 +46,6 @@ function! tutor#InjectCommand() redraw | echohl WarningMsg | echon "tutor: ran" | echohl None | echon " " | echohl Statement | echon l:cmd endfunction -function! tutor#SetNormalMappings() - nnoremap :call tutor#FollowLink(0) - nnoremap <2-LeftMouse> :call tutor#MouseDoubleClick() - nnoremap >> :call tutor#InjectCommand() -endfunction - -" Hypertext: {{{1 - function! tutor#FollowLink(force) let l:stack_s = join(map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")'), '') if l:stack_s =~# 'tutorLink' @@ -85,12 +88,6 @@ function! tutor#InfoText() return join(l:info_parts, " ") endfunction -function! tutor#LoadMetadata() - try - let b:tutor_metadata = json_decode(join(readfile(expand('%').'.json'), "\n")) - catch - endtry -endfunction " Marks: {{{1 From 9ffa22b7efefebf49a42364de21bb895adabd2de Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sun, 16 Jul 2017 20:49:07 +0200 Subject: [PATCH 16/58] vim-patch:8.0.0023 Problem: "gd" and "gD" may find a match in a comment or string. Solution: Ignore matches in comments and strings. (Anton Lindqvist) https://github.com/vim/vim/commit/226630a030c0d41145e1109f09633360fc9c999d --- src/nvim/normal.c | 68 ++++++-- src/nvim/testdir/test_goto.vim | 281 +++++++++++++++++++++++++++++++-- src/nvim/version.c | 2 +- 3 files changed, 328 insertions(+), 23 deletions(-) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index d891c74fd2..c1676780d8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3657,6 +3657,39 @@ nv_gd ( } } +// Return true if line[offset] is not inside a C-style comment or string, false +// otherwise. +static bool is_ident(char_u *line, int offset) +{ + bool incomment = false; + int instring = 0; + int prev = 0; + + for (int i = 0; i < offset && line[i] != NUL; i++) { + if (instring != 0) { + if (prev != '\\' && line[i] == instring) { + instring = 0; + } + } else if ((line[i] == '"' || line[i] == '\'') && !incomment) { + instring = line[i]; + } else { + if (incomment) { + if (prev == '*' && line[i] == '/') { + incomment = false; + } + } else if (prev == '/' && line[i] == '*') { + incomment = true; + } else if (prev == '/' && line[i] == '/') { + return false; + } + } + + prev = line[i]; + } + + return incomment == false && instring == 0; +} + /* * Search for variable declaration of "ptr[len]". * When "locally" is true in the current function ("gd"), otherwise in the @@ -3683,6 +3716,7 @@ find_decl ( bool retval = true; bool incll; int searchflags = flags_arg; + bool valid; pat = xmalloc(len + 7); @@ -3717,6 +3751,7 @@ find_decl ( /* Search forward for the identifier, ignore comment lines. */ clearpos(&found_pos); for (;; ) { + valid = false; t = searchit(curwin, curbuf, &curwin->w_cursor, FORWARD, pat, 1L, searchflags, RE_LAST, (linenr_T)0, NULL); if (curwin->w_cursor.lnum >= old_pos.lnum) @@ -3747,20 +3782,35 @@ find_decl ( curwin->w_cursor.col = 0; continue; } - if (!locally) /* global search: use first match found */ + valid = is_ident(get_cursor_line_ptr(), curwin->w_cursor.col); + + // If the current position is not a valid identifier and a previous match is + // present, favor that one instead. + if (!valid && found_pos.lnum != 0) { + curwin->w_cursor = found_pos; break; - if (curwin->w_cursor.lnum >= par_pos.lnum) { - /* If we previously found a valid position, use it. */ - if (found_pos.lnum != 0) + } + // global search: use first match found + if (valid && !locally) { + break; + } + if (valid && curwin->w_cursor.lnum >= par_pos.lnum) { + // If we previously found a valid position, use it. + if (found_pos.lnum != 0) { curwin->w_cursor = found_pos; + } break; } - // For finding a local variable and the match is before the "{" search - // to find a later match. For K&R style function declarations this - // skips the function header without types. Remove SEARCH_START from - // flags to avoid getting stuck at one position. - found_pos = curwin->w_cursor; + // For finding a local variable and the match is before the "{" or + // inside a comment, continue searching. For K&R style function + // declarations this skips the function header without types. + if (!valid) { + clearpos(&found_pos); + } else { + found_pos = curwin->w_cursor; + } + // Remove SEARCH_START from flags to avoid getting stuck at one position. searchflags &= ~SEARCH_START; } diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim index b6ac5720c3..909e29e27e 100644 --- a/src/nvim/testdir/test_goto.vim +++ b/src/nvim/testdir/test_goto.vim @@ -1,22 +1,277 @@ " Test commands that jump somewhere. -func Test_geeDEE() +" Create a new buffer using "lines" and place the cursor on the word after the +" first occurrence of return and invoke "cmd". The cursor should now be +" positioned at the given line and col. +func XTest_goto_decl(cmd, lines, line, col) new - call setline(1, ["Filename x;", "", "int Filename", "int func() {", "Filename y;"]) - /y;/ - normal gD - call assert_equal(1, line('.')) + call setline(1, a:lines) + /return/ + normal! W + execute 'norm! ' . a:cmd + call assert_equal(a:line, line('.')) + call assert_equal(a:col, col('.')) quit! endfunc -func Test_gee_dee() - new - call setline(1, ["int x;", "", "int func(int x)", "{", " return x;", "}"]) - /return/ - normal $hgd - call assert_equal(3, line('.')) - call assert_equal(14, col('.')) - quit! +func Test_gD() + let lines = [ + \ 'int x;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 1, 5) +endfunc + +func Test_gD_too() + let lines = [ + \ 'Filename x;', + \ '', + \ 'int Filename', + \ 'int func() {', + \ ' Filename x;', + \ ' return x;', + \ ] + call XTest_goto_decl('gD', lines, 1, 10) +endfunc + +func Test_gD_comment() + let lines = [ + \ '/* int x; */', + \ 'int x;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 2, 5) +endfunc + +func Test_gD_inline_comment() + let lines = [ + \ 'int y /* , x */;', + \ 'int x;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 2, 5) +endfunc + +func Test_gD_string() + let lines = [ + \ 'char *s[] = "x";', + \ 'int x = 1;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 2, 5) +endfunc + +func Test_gD_string_same_line() + let lines = [ + \ 'char *s[] = "x", int x = 1;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 1, 22) +endfunc + +func Test_gD_char() + let lines = [ + \ "char c = 'x';", + \ 'int x = 1;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 2, 5) +endfunc + +func Test_gd() + let lines = [ + \ 'int x;', + \ '', + \ 'int func(int x)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 3, 14) +endfunc + +func Test_gd_not_local() + let lines = [ + \ 'int func1(void)', + \ '{', + \ ' return x;', + \ '}', + \ '', + \ 'int func2(int x)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 3, 10) +endfunc + +func Test_gd_kr_style() + let lines = [ + \ 'int func(x)', + \ ' int x;', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 2, 7) +endfunc + +func Test_gd_missing_braces() + let lines = [ + \ 'def func1(a)', + \ ' a + 1', + \ 'end', + \ '', + \ 'a = 1', + \ '', + \ 'def func2()', + \ ' return a', + \ 'end', + \ ] + call XTest_goto_decl('gd', lines, 1, 11) +endfunc + +func Test_gd_comment() + let lines = [ + \ 'int func(void)', + \ '{', + \ ' /* int x; */', + \ ' int x;', + \ ' return x;', + \ '}', + \] + call XTest_goto_decl('gd', lines, 4, 7) +endfunc + +func Test_gd_comment_in_string() + let lines = [ + \ 'int func(void)', + \ '{', + \ ' char *s ="//"; int x;', + \ ' int x;', + \ ' return x;', + \ '}', + \] + call XTest_goto_decl('gd', lines, 3, 22) +endfunc + +func Test_gd_string_in_comment() + set comments= + let lines = [ + \ 'int func(void)', + \ '{', + \ ' /* " */ int x;', + \ ' int x;', + \ ' return x;', + \ '}', + \] + call XTest_goto_decl('gd', lines, 3, 15) + set comments& +endfunc + +func Test_gd_inline_comment() + let lines = [ + \ 'int func(/* x is an int */ int x)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 1, 32) +endfunc + +func Test_gd_inline_comment_only() + let lines = [ + \ 'int func(void) /* one lonely x */', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 3, 10) +endfunc + +func Test_gd_inline_comment_body() + let lines = [ + \ 'int func(void)', + \ '{', + \ ' int y /* , x */;', + \ '', + \ ' for (/* int x = 0 */; y < 2; y++);', + \ '', + \ ' int x = 0;', + \ '', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 7, 7) +endfunc + +func Test_gd_trailing_multiline_comment() + let lines = [ + \ 'int func(int x) /* x is an int */', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 1, 14) +endfunc + +func Test_gd_trailing_comment() + let lines = [ + \ 'int func(int x) // x is an int', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 1, 14) +endfunc + +func Test_gd_string() + let lines = [ + \ 'int func(void)', + \ '{', + \ ' char *s = "x";', + \ ' int x = 1;', + \ '', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 4, 7) +endfunc + +func Test_gd_string_only() + let lines = [ + \ 'int func(void)', + \ '{', + \ ' char *s = "x";', + \ '', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 5, 10) endfunc " Check that setting 'cursorline' does not change curswant diff --git a/src/nvim/version.c b/src/nvim/version.c index c7b8220776..0bc1655a74 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -706,7 +706,7 @@ static const int included_patches[] = { // 26, // 25, // 24 NA - // 23, + 23, // 22 NA // 21, // 20, From 9cf0415761bab62e8dad95b965fb6a14d00a4f4b Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sun, 16 Jul 2017 21:43:17 +0200 Subject: [PATCH 17/58] vim-patch:8.0.0025 Problem: Inconsistent use of spaces vs tabs in gd test. Solution: Use tabs. (Anton Lindqvist) https://github.com/vim/vim/commit/936c48f8ca82a0257640c8c9d0792538f5a7e813 --- src/nvim/testdir/test_goto.vim | 292 ++++++++++++++++----------------- src/nvim/version.c | 2 +- 2 files changed, 147 insertions(+), 147 deletions(-) diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim index 909e29e27e..2573401707 100644 --- a/src/nvim/testdir/test_goto.vim +++ b/src/nvim/testdir/test_goto.vim @@ -16,13 +16,13 @@ endfunc func Test_gD() let lines = [ - \ 'int x;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'int x;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gD', lines, 1, 5) endfunc @@ -40,237 +40,237 @@ endfunc func Test_gD_comment() let lines = [ - \ '/* int x; */', - \ 'int x;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ '/* int x; */', + \ 'int x;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gD', lines, 2, 5) endfunc func Test_gD_inline_comment() let lines = [ - \ 'int y /* , x */;', - \ 'int x;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'int y /* , x */;', + \ 'int x;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gD', lines, 2, 5) endfunc func Test_gD_string() let lines = [ - \ 'char *s[] = "x";', - \ 'int x = 1;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'char *s[] = "x";', + \ 'int x = 1;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gD', lines, 2, 5) endfunc func Test_gD_string_same_line() let lines = [ - \ 'char *s[] = "x", int x = 1;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'char *s[] = "x", int x = 1;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gD', lines, 1, 22) endfunc func Test_gD_char() let lines = [ - \ "char c = 'x';", - \ 'int x = 1;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ "char c = 'x';", + \ 'int x = 1;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gD', lines, 2, 5) endfunc func Test_gd() let lines = [ - \ 'int x;', - \ '', - \ 'int func(int x)', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'int x;', + \ '', + \ 'int func(int x)', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gd', lines, 3, 14) endfunc func Test_gd_not_local() let lines = [ - \ 'int func1(void)', - \ '{', - \ ' return x;', - \ '}', - \ '', - \ 'int func2(int x)', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'int func1(void)', + \ '{', + \ ' return x;', + \ '}', + \ '', + \ 'int func2(int x)', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gd', lines, 3, 10) endfunc func Test_gd_kr_style() let lines = [ - \ 'int func(x)', - \ ' int x;', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'int func(x)', + \ ' int x;', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gd', lines, 2, 7) endfunc func Test_gd_missing_braces() let lines = [ - \ 'def func1(a)', - \ ' a + 1', - \ 'end', - \ '', - \ 'a = 1', - \ '', - \ 'def func2()', - \ ' return a', - \ 'end', - \ ] + \ 'def func1(a)', + \ ' a + 1', + \ 'end', + \ '', + \ 'a = 1', + \ '', + \ 'def func2()', + \ ' return a', + \ 'end', + \ ] call XTest_goto_decl('gd', lines, 1, 11) endfunc func Test_gd_comment() let lines = [ - \ 'int func(void)', - \ '{', - \ ' /* int x; */', - \ ' int x;', - \ ' return x;', - \ '}', - \] + \ 'int func(void)', + \ '{', + \ ' /* int x; */', + \ ' int x;', + \ ' return x;', + \ '}', + \] call XTest_goto_decl('gd', lines, 4, 7) endfunc func Test_gd_comment_in_string() let lines = [ - \ 'int func(void)', - \ '{', - \ ' char *s ="//"; int x;', - \ ' int x;', - \ ' return x;', - \ '}', - \] + \ 'int func(void)', + \ '{', + \ ' char *s ="//"; int x;', + \ ' int x;', + \ ' return x;', + \ '}', + \] call XTest_goto_decl('gd', lines, 3, 22) endfunc func Test_gd_string_in_comment() set comments= let lines = [ - \ 'int func(void)', - \ '{', - \ ' /* " */ int x;', - \ ' int x;', - \ ' return x;', - \ '}', - \] + \ 'int func(void)', + \ '{', + \ ' /* " */ int x;', + \ ' int x;', + \ ' return x;', + \ '}', + \] call XTest_goto_decl('gd', lines, 3, 15) set comments& endfunc func Test_gd_inline_comment() let lines = [ - \ 'int func(/* x is an int */ int x)', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'int func(/* x is an int */ int x)', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gd', lines, 1, 32) endfunc func Test_gd_inline_comment_only() let lines = [ - \ 'int func(void) /* one lonely x */', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'int func(void) /* one lonely x */', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gd', lines, 3, 10) endfunc func Test_gd_inline_comment_body() let lines = [ - \ 'int func(void)', - \ '{', - \ ' int y /* , x */;', - \ '', - \ ' for (/* int x = 0 */; y < 2; y++);', - \ '', - \ ' int x = 0;', - \ '', - \ ' return x;', - \ '}', - \ ] + \ 'int func(void)', + \ '{', + \ ' int y /* , x */;', + \ '', + \ ' for (/* int x = 0 */; y < 2; y++);', + \ '', + \ ' int x = 0;', + \ '', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gd', lines, 7, 7) endfunc func Test_gd_trailing_multiline_comment() let lines = [ - \ 'int func(int x) /* x is an int */', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'int func(int x) /* x is an int */', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gd', lines, 1, 14) endfunc func Test_gd_trailing_comment() let lines = [ - \ 'int func(int x) // x is an int', - \ '{', - \ ' return x;', - \ '}', - \ ] + \ 'int func(int x) // x is an int', + \ '{', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gd', lines, 1, 14) endfunc func Test_gd_string() let lines = [ - \ 'int func(void)', - \ '{', - \ ' char *s = "x";', - \ ' int x = 1;', - \ '', - \ ' return x;', - \ '}', - \ ] + \ 'int func(void)', + \ '{', + \ ' char *s = "x";', + \ ' int x = 1;', + \ '', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gd', lines, 4, 7) endfunc func Test_gd_string_only() let lines = [ - \ 'int func(void)', - \ '{', - \ ' char *s = "x";', - \ '', - \ ' return x;', - \ '}', - \ ] + \ 'int func(void)', + \ '{', + \ ' char *s = "x";', + \ '', + \ ' return x;', + \ '}', + \ ] call XTest_goto_decl('gd', lines, 5, 10) endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 0bc1655a74..b6a0ca3e1b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -704,7 +704,7 @@ static const int included_patches[] = { // 28 NA // 27 NA // 26, - // 25, + 25, // 24 NA 23, // 22 NA From 24a0d4e122024071195ac8a2828fba3184c8e12e Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Mon, 17 Jul 2017 22:38:03 +0800 Subject: [PATCH 18/58] vim-patch:8.0.0004 (#7044) Problem: A string argument for function() that is not a function name results in an error message with NULL. (Christian Brabandt) Solution: Use the argument for the error message. https://github.com/vim/vim/commit/5582ef14384525e8cec86016876d97a6b32dd548 --- src/nvim/testdir/test_expr.vim | 3 +++ src/nvim/version.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 82c5d21bd0..710eae9b8b 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -418,6 +418,9 @@ func Test_function_with_funcref() let s:fref = function(s:f) call assert_equal(v:t_string, s:fref('x')) call assert_fails("call function('s:f')", 'E700:') + + call assert_fails("call function('foo()')", 'E475:') + call assert_fails("call function('foo()')", 'foo()') endfunc func Test_funcref() diff --git a/src/nvim/version.c b/src/nvim/version.c index c7b8220776..06efcc5bfc 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -725,7 +725,7 @@ static const int included_patches[] = { // 7 NA 6, // 5 NA - // 4, + 4, // 3, 2, 1, From 26124b480028354d7d81d93c0e45ca516effdaa6 Mon Sep 17 00:00:00 2001 From: Andy Russell Date: Sun, 16 Jul 2017 14:46:38 -0400 Subject: [PATCH 19/58] provider/clipboard.vim: fix logic issue #7042 Closes #7039 --- runtime/autoload/provider/clipboard.vim | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 47f4271091..86006497d9 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -16,6 +16,12 @@ function! s:selection.on_exit(jobid, data, event) abort endif endfunction +function! s:selection.on_stderr(jobid, data, event) abort + echohl WarningMsg + echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(a:data) + echohl None +endfunction + let s:selections = { '*': s:selection, '+': copy(s:selection)} function! s:try_cmd(cmd, ...) abort @@ -135,24 +141,17 @@ function! s:clipboard.set(lines, regtype, reg) abort end let selection.data = [a:lines, a:regtype] let argv = split(s:copy[a:reg], " ") + let selection.argv = argv let selection.detach = s:cache_enabled let selection.cwd = "/" - call extend(selection, { - \ 'on_stdout': function('s:set_errhandler'), - \ 'on_stderr': function('s:set_errhandler'), - \ }) let jobid = jobstart(argv, selection) if jobid > 0 call jobsend(jobid, a:lines) call jobclose(jobid, 'stdin') let selection.owner = jobid - endif -endfunction - -function! s:set_errhandler(job_id, data, event) abort - if a:job_id <= 0 + else echohl WarningMsg - echo 'clipboard: error when invoking provider: ' . join(a:data) + echomsg 'clipboard: failed to execute: '.(s:copy[a:reg]) echohl None endif endfunction From b98ea04226d5ae7f2d8f9650101f87a848145ca4 Mon Sep 17 00:00:00 2001 From: lonerover Date: Sat, 15 Jul 2017 21:02:15 +0800 Subject: [PATCH 20/58] vim-patch:8.0.0003 Problem: getwinvar() returns wrong Value of boolean and number options, especially non big endian systems. (James McCoy) Solution: Cast the pointer to long or int. (closes vim/vim#1060) https://github.com/vim/vim/commit/789a5c0e3d27f09456678f0cfb6c1bd2d8ab4a35 --- src/nvim/option.c | 5 ++++- src/nvim/testdir/test_bufwintabinfo.vim | 13 +++++++++++++ src/nvim/version.c | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index 37b37e2859..40fae18aaf 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -7039,8 +7039,11 @@ dict_T *get_winbuf_options(const int bufopt) if (opt->flags & P_STRING) { tv_dict_add_str(d, opt->fullname, strlen(opt->fullname), *(const char **)varp); + } else if (opt->flags & P_NUM) { + tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), + *(long *)varp); } else { - tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), *varp); + tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), *(int *)varp); } } } diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index 5c916e2dd7..1c9350c416 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -87,9 +87,17 @@ function Test_get_buf_options() endfunc function Test_get_win_options() + if has('folding') + set foldlevel=999 + endif + set list let opts = getwinvar(1, '&') call assert_equal(v:t_dict, type(opts)) call assert_equal(0, opts.linebreak) + call assert_equal(1, opts.list) + if has('folding') + call assert_equal(999, opts.foldlevel) + endif if has('signs') call assert_equal('auto', opts.signcolumn) endif @@ -97,7 +105,12 @@ function Test_get_win_options() let opts = gettabwinvar(1, 1, '&') call assert_equal(v:t_dict, type(opts)) call assert_equal(0, opts.linebreak) + call assert_equal(1, opts.list) if has('signs') call assert_equal('auto', opts.signcolumn) endif + set list& + if has('folding') + set foldlevel=0 + endif endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 2200c5e04a..a7d6885b4c 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -726,7 +726,7 @@ static const int included_patches[] = { 6, // 5 NA 4, - // 3, + 3, 2, 1, 0 From 710546c5e93cd433bd2eb566df52b58e3583c386 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Mon, 17 Jul 2017 15:53:23 +0200 Subject: [PATCH 21/58] vim-patch:8.0.0085 Problem: Using freed memory with recursive function call. (Dominique Pelle) Solution: Make a copy of the function name. https://github.com/vim/vim/commit/8a01f969c198eeb655ad2f96f2796a6f6f4a1924 --- src/nvim/eval.c | 6 ++++ src/nvim/testdir/test_nested_function.vim | 36 +++++++++++++++-------- src/nvim/version.c | 2 +- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e5bb7f1b38..08b3d1dbd7 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4239,11 +4239,17 @@ static int eval7( // use its contents. s = deref_func_name((const char *)s, &len, &partial, !evaluate); + // Need to make a copy, in case evaluating the arguments makes + // the name invalid. + s = xmemdupz(s, len); + // Invoke the function. ret = get_func_tv(s, len, rettv, arg, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &len, evaluate, partial, NULL); + xfree(s); + // If evaluate is false rettv->v_type was not set in // get_func_tv, but it's needed in handle_subscript() to parse // what follows. So set it here. diff --git a/src/nvim/testdir/test_nested_function.vim b/src/nvim/testdir/test_nested_function.vim index f881730529..7e301ed33e 100644 --- a/src/nvim/testdir/test_nested_function.vim +++ b/src/nvim/testdir/test_nested_function.vim @@ -1,32 +1,42 @@ "Tests for nested functions " -function! NestedFunc() - fu! Func1() +func NestedFunc() + func! Func1() let g:text .= 'Func1 ' - endfunction + endfunc call Func1() - fu! s:func2() + func! s:func2() let g:text .= 's:func2 ' - endfunction + endfunc call s:func2() - fu! s:_func3() + func! s:_func3() let g:text .= 's:_func3 ' - endfunction + endfunc call s:_func3() let fn = 'Func4' - fu! {fn}() + func! {fn}() let g:text .= 'Func4 ' - endfunction + endfunc call {fn}() let fn = 'func5' - fu! s:{fn}() + func! s:{fn}() let g:text .= 's:func5' - endfunction + endfunc call s:{fn}() -endfunction +endfunc -function! Test_nested_functions() +func Test_nested_functions() let g:text = '' call NestedFunc() call assert_equal('Func1 s:func2 s:_func3 Func4 s:func5', g:text) endfunction + +func Test_nested_argument() + func g:X() + let g:Y = function('sort') + endfunc + let g:Y = function('sort') + echo g:Y([], g:X()) + delfunc g:X + unlet g:Y +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index a7d6885b4c..9b5c5eefb8 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -644,7 +644,7 @@ static const int included_patches[] = { // 88, // 87 NA // 86, - // 85, + 85, 84, 83, // 82 NA From 962e8cc1dd4d1b901b249e8434ac4db6554d3e05 Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Sun, 23 Jul 2017 00:59:14 +0800 Subject: [PATCH 22/58] vim-patch:8.0.0052 (#7057) Problem: Conceal test passes even without the bug fix. Solution: Add a redraw command. (Christian Brabandt) https://github.com/vim/vim/commit/35a1f59d635d9a655e1267c18f7cc757afd0d5b0 --- src/nvim/testdir/test_matchadd_conceal.vim | 1 + src/nvim/version.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim index c788689e33..c11f1a84a9 100644 --- a/src/nvim/testdir/test_matchadd_conceal.vim +++ b/src/nvim/testdir/test_matchadd_conceal.vim @@ -277,6 +277,7 @@ function! Test_matchadd_and_syn_conceal() call assert_notequal(screenattr(1, 11) , screenattr(1, 12)) call assert_equal(screenattr(1, 11) , screenattr(1, 32)) call matchadd('CheckedByCoq', '\%<2l\%>9c\%<16c') + redraw! call assert_equal(expect, s:screenline(1)) call assert_notequal(screenattr(1, 10) , screenattr(1, 11)) call assert_notequal(screenattr(1, 11) , screenattr(1, 12)) diff --git a/src/nvim/version.c b/src/nvim/version.c index 9b5c5eefb8..b325052cd2 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -677,7 +677,7 @@ static const int included_patches[] = { // 55 NA // 54 NA 53, - // 52, + 52, // 51 NA // 50 NA 49, From 8fb599029f41b74e6c34b680a0817ff19f544cf3 Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Sun, 23 Jul 2017 01:03:15 +0800 Subject: [PATCH 23/58] vim-patch:8.0.0031 (#7050) Problem: After ":bwipeout" 'fileformat' is not set to the right default. Solution: Get the default from 'fileformats'. (Mike Williams) https://github.com/vim/vim/commit/e8ef3a093453b73594e15462d4de50b011c8ba66 --- src/nvim/option.c | 17 ++++++++++++++++- src/nvim/testdir/test_alot.vim | 1 + src/nvim/testdir/test_fileformat.vim | 17 +++++++++++++++++ src/nvim/version.c | 2 +- 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 src/nvim/testdir/test_fileformat.vim diff --git a/src/nvim/option.c b/src/nvim/option.c index 40fae18aaf..75b56b4eb4 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -5718,7 +5718,22 @@ void buf_copy_options(buf_T *buf, int flags) free_buf_options(buf, TRUE); buf->b_p_ro = FALSE; /* don't copy readonly */ buf->b_p_fenc = vim_strsave(p_fenc); - buf->b_p_ff = vim_strsave(p_ff); + switch (*p_ffs) { + case 'm': + buf->b_p_ff = vim_strsave((char_u *)FF_MAC); + break; + case 'd': + buf->b_p_ff = vim_strsave((char_u *)FF_DOS); + break; + case 'u': + buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + break; + default: + buf->b_p_ff = vim_strsave(p_ff); + } + if (buf->b_p_ff != NULL) { + buf->b_start_ffc = *buf->b_p_ff; + } buf->b_p_bh = empty_option; buf->b_p_bt = empty_option; } else diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 1103778107..d55170c27c 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -14,6 +14,7 @@ source test_float_func.vim source test_functions.vim source test_goto.vim source test_jumps.vim +source test_fileformat.vim source test_lambda.vim source test_menu.vim source test_mapping.vim diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim new file mode 100644 index 0000000000..584f20cdfc --- /dev/null +++ b/src/nvim/testdir/test_fileformat.vim @@ -0,0 +1,17 @@ +" Test behavior of fileformat after bwipeout of last buffer + +func Test_fileformat_after_bw() + bwipeout + set fileformat& + if &fileformat == 'dos' + let test_fileformats = 'unix' + elseif &fileformat == 'unix' + let test_fileformats = 'mac' + else " must be mac + let test_fileformats = 'dos' + endif + exec 'set fileformats='.test_fileformats + bwipeout! + call assert_equal(test_fileformats, &fileformat) + set fileformats& +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index b325052cd2..61d947017e 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -698,7 +698,7 @@ static const int included_patches[] = { // 34, 33, 32, - // 31, + 31, // 30 NA // 29 NA // 28 NA From 253f6f3bbfd16b7aeb2f72714c1d5d1c88a3478a Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Sun, 23 Jul 2017 01:04:45 +0800 Subject: [PATCH 24/58] vim-patch:8.0.0038 (#7051) Problem: OPEN_CHR_FILES not defined for FreeBSD using Debian userland files. Solution: Check for __FreeBSD_kernel__. (James McCoy, closes vim/vim#1166) https://github.com/vim/vim/commit/ca291aec99b60fe81eaab36aa718e51421bb88d5 --- src/nvim/version.c | 2 +- src/nvim/vim.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/version.c b/src/nvim/version.c index 61d947017e..d372779c75 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -691,7 +691,7 @@ static const int included_patches[] = { 41, 40, // 39 NA - // 38, + 38, 37, // 36 NA 35, diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 5d2c27a2f4..c71ca411ea 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -319,7 +319,7 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() // Lowest number used for window ID. Cannot have this many windows per tab. #define LOWEST_WIN_ID 1000 -#if defined(__FreeBSD__) && defined(S_ISCHR) +#if (defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) && defined(S_ISCHR) # define OPEN_CHR_FILES #endif From 13f5bc9586cd84eac758a7e109fc88587f858cf0 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 16 Jul 2017 18:09:48 +0200 Subject: [PATCH 25/58] log: all UI events, not just UI-bridge Rename ui_bridge.c:UI_CALL to UI_BRIDGE_CALL. --- src/nvim/README.md | 32 ++++++++++++++--------- src/nvim/generators/gen_api_ui_events.lua | 3 ++- src/nvim/ui.c | 25 ++++++++++++++++++ src/nvim/ui_bridge.c | 28 +++----------------- 4 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/nvim/README.md b/src/nvim/README.md index 3032913500..660e3d1010 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,14 +1,19 @@ -## Source code overview -This document is an overview of how Nvim works internally, focusing on parts -that are different from Vim. Since Nvim inherited from Vim, some information in -[its README](https://raw.githubusercontent.com/vim/vim/master/src/README.txt) -still applies. +Module-specific details are documented at the top of each module (e.g. +terminal.c, screen.c). -For module-specific details, read the source code. Some files are extensively -commented at the top (e.g. terminal.c, screen.c). +See also `:help development`. -### Source file name conventions +UI Debugging +------------ + +At `DEBUG_LOG_LEVEL`, all UI events are logged. + + rm -rf build/ + make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" + +Filename conventions +-------------------- The source files use extensions to hint about their purpose. @@ -19,10 +24,12 @@ The source files use extensions to hint about their purpose. - `*.h.generated.h` - exported functions’ declarations. - `*.c.generated.h` - static functions’ declarations. -### Top-level program loops +Nvim lifecycle +-------------- -Let's understand what a Vim-like program does by analyzing the workflow of -a typical editing session: +Following describes how Nvim processes input. + +Consider a typical Vim-like editing session: 01. Vim dispays the welcome screen 02. User types: `:` @@ -154,7 +161,8 @@ modes managed by the `state_enter` loop: - insert mode: `insert_{enter,check,execute}()`(`edit.c`) - terminal mode: `terminal_{enter,execute}()`(`terminal.c`) -### Async event support +Async event support +------------------- One of the features Nvim added is the support for handling arbitrary asynchronous events, which can include: diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index acdb25ca67..d2b90db707 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -119,7 +119,7 @@ for i = 1, #events do write_signature(bridge_output, ev, 'UI *ui') bridge_output:write('\n{\n') bridge_output:write(send) - bridge_output:write(' UI_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') + bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') end end @@ -128,6 +128,7 @@ for i = 1, #events do call_output:write('\n{\n') if ev.remote_only then write_arglist(call_output, ev, false) + call_output:write(' UI_LOG('..ev.name..', 0);\n') call_output:write(' ui_event("'..ev.name..'", args);\n') else call_output:write(' UI_CALL') diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 0a2154438f..a60c061949 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -8,6 +8,7 @@ #include #include "nvim/vim.h" +#include "nvim/log.h" #include "nvim/ui.h" #include "nvim/charset.h" #include "nvim/cursor.h" @@ -59,6 +60,27 @@ static int busy = 0; static int height, width; static int old_mode_idx = -1; +#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL +# define UI_LOG(funname, ...) +#else +static size_t uilog_seen = 0; +static char uilog_last_event[1024] = { 0 }; +# define UI_LOG(funname, ...) \ + do { \ + if (strequal(uilog_last_event, STR(funname))) { \ + uilog_seen++; \ + } else { \ + if (uilog_seen > 0) { \ + do_log(DEBUG_LOG_LEVEL, "ui", 0, true, \ + "%s (+%zu times...)", uilog_last_event, uilog_seen); \ + } \ + DLOG("ui: " STR(funname)); \ + uilog_seen = 0; \ + xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \ + } \ + } while (0) +#endif + // UI_CALL invokes a function on all registered UI instances. The functions can // have 0-5 arguments (configurable by SELECT_NTH). // @@ -67,6 +89,7 @@ static int old_mode_idx = -1; # define UI_CALL(funname, ...) \ do { \ flush_cursor_update(); \ + UI_LOG(funname, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ UI_CALL_MORE(funname, __VA_ARGS__); \ @@ -76,6 +99,7 @@ static int old_mode_idx = -1; # define UI_CALL(...) \ do { \ flush_cursor_update(); \ + UI_LOG(__VA_ARGS__, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \ @@ -85,6 +109,7 @@ static int old_mode_idx = -1; #define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore) #define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6 #define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) +// Resolves to UI_CALL_MORE or UI_CALL_ZERO. #define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) #define UI_CALL_MORE(method, ...) if (ui->method) ui->method(ui, __VA_ARGS__) #define UI_CALL_ZERO(method) if (ui->method) ui->method(ui) diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 0165db7c0c..5585886612 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -24,30 +24,10 @@ #define UI(b) (((UIBridgeData *)b)->ui) -#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL -static size_t uilog_seen = 0; -static argv_callback uilog_event = NULL; -#define UI_CALL(ui, name, argc, ...) \ - do { \ - if (uilog_event == ui_bridge_##name##_event) { \ - uilog_seen++; \ - } else { \ - if (uilog_seen > 0) { \ - DLOG("UI bridge: ...%zu times", uilog_seen); \ - } \ - DLOG("UI bridge: " STR(name)); \ - uilog_seen = 0; \ - uilog_event = ui_bridge_##name##_event; \ - } \ - ((UIBridgeData *)ui)->scheduler( \ - event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)); \ - } while (0) -#else // Schedule a function call on the UI bridge thread. -#define UI_CALL(ui, name, argc, ...) \ +#define UI_BRIDGE_CALL(ui, name, argc, ...) \ ((UIBridgeData *)ui)->scheduler( \ event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)) -#endif #define INT2PTR(i) ((void *)(intptr_t)i) #define PTR2INT(p) ((Integer)(intptr_t)p) @@ -128,7 +108,7 @@ static void ui_bridge_stop(UI *b) { UIBridgeData *bridge = (UIBridgeData *)b; bool stopped = bridge->stopped = false; - UI_CALL(b, stop, 1, b); + UI_BRIDGE_CALL(b, stop, 1, b); for (;;) { uv_mutex_lock(&bridge->mutex); stopped = bridge->stopped; @@ -154,7 +134,7 @@ static void ui_bridge_highlight_set(UI *b, HlAttrs attrs) { HlAttrs *a = xmalloc(sizeof(HlAttrs)); *a = attrs; - UI_CALL(b, highlight_set, 2, b, a); + UI_BRIDGE_CALL(b, highlight_set, 2, b, a); } static void ui_bridge_highlight_set_event(void **argv) { @@ -167,7 +147,7 @@ static void ui_bridge_suspend(UI *b) { UIBridgeData *data = (UIBridgeData *)b; uv_mutex_lock(&data->mutex); - UI_CALL(b, suspend, 1, b); + UI_BRIDGE_CALL(b, suspend, 1, b); data->ready = false; // suspend the main thread until CONTINUE is called by the UI thread while (!data->ready) { From 502af39f6261e1d1b71df7fa3169e943707ee7f2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 19 Jul 2017 02:24:12 +0200 Subject: [PATCH 26/58] log: channel registration --- src/nvim/msgpack_rpc/channel.c | 44 +++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 68ac35bc4e..d7748925aa 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -117,12 +117,11 @@ void channel_teardown(void) /// Creates an API channel by starting a process and connecting to its /// stdin/stdout. stderr is handled by the job infrastructure. /// -/// @param argv The argument vector for the process. [consumed] -/// @return The channel id (> 0), on success. -/// 0, on error. +/// @return Channel id (> 0), on success. 0, on error. uint64_t channel_from_process(Process *proc, uint64_t id) { - Channel *channel = register_channel(kChannelTypeProc, id, proc->events); + Channel *channel = register_channel(kChannelTypeProc, id, proc->events, + proc->argv); incref(channel); // process channels are only closed by the exit_cb channel->data.proc = proc; @@ -138,7 +137,7 @@ uint64_t channel_from_process(Process *proc, uint64_t id) /// @param watcher The SocketWatcher ready to accept the connection void channel_from_connection(SocketWatcher *watcher) { - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, NULL); socket_watcher_accept(watcher, &channel->data.stream); incref(channel); // close channel only after the stream is closed channel->data.stream.internal_close_cb = close_cb; @@ -161,7 +160,7 @@ uint64_t channel_connect(bool tcp, const char *address, xfree(path); } - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, NULL); if (!socket_connect(&main_loop, &channel->data.stream, tcp, address, timeout, error)) { decref(channel); @@ -333,7 +332,7 @@ bool channel_close(uint64_t id) /// Neovim void channel_from_stdio(void) { - Channel *channel = register_channel(kChannelTypeStdio, 0, NULL); + Channel *channel = register_channel(kChannelTypeStdio, 0, NULL, NULL); incref(channel); // stdio channels are only closed on exit // read stream rstream_init_fd(&main_loop, &channel->data.std.in, 0, CHANNEL_BUFFER_SIZE); @@ -346,7 +345,7 @@ void channel_from_stdio(void) /// when an instance connects to its own named pipe. uint64_t channel_create_internal(void) { - Channel *channel = register_channel(kChannelTypeInternal, 0, NULL); + Channel *channel = register_channel(kChannelTypeInternal, 0, NULL, NULL); incref(channel); // internal channel lives until process exit return channel->id; } @@ -746,7 +745,7 @@ static void close_cb(Stream *stream, void *data) } static Channel *register_channel(ChannelType type, uint64_t id, - MultiQueue *events) + MultiQueue *events, char **argv) { Channel *rv = xmalloc(sizeof(Channel)); rv->events = events ? events : multiqueue_new_child(main_loop.events); @@ -761,6 +760,33 @@ static Channel *register_channel(ChannelType type, uint64_t id, kv_init(rv->call_stack); kv_init(rv->delayed_notifications); pmap_put(uint64_t)(channels, rv->id, rv); + +#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL + switch(type) { + case kChannelTypeSocket: + DLOG("register ch %" PRIu64 " type=socket", rv->id); + break; + case kChannelTypeProc: { + char buf[IOSIZE]; + buf[0] = '\0'; + char **p = argv; + while (p != NULL && *p != NULL) { + xstrlcat(buf, *p, sizeof(buf)); + xstrlcat(buf, " ", sizeof(buf)); + p++; + } + DLOG("register ch %" PRIu64 " type=proc argv=%s", rv->id, buf); + } + break; + case kChannelTypeStdio: + DLOG("register ch %" PRIu64 " type=stdio", rv->id); + break; + case kChannelTypeInternal: + DLOG("register ch %" PRIu64 " type=internal", rv->id); + break; + } +#endif + return rv; } From ad07e9c7fcabeb991f0849c9e11b5b225cf470fd Mon Sep 17 00:00:00 2001 From: ckelsel Date: Sun, 23 Jul 2017 10:12:05 +0800 Subject: [PATCH 27/58] vim-patch:8.0.0069 Problem: Compiler warning for self-comparison. Solution: Define ONE_WINDOW and add vim/vim#ifdef. https://github.com/vim/vim/commit/a1f4cb93ba50ea9e40cd4b1f5592b8a6d1398660 --- src/nvim/buffer.c | 2 +- src/nvim/ex_cmds.c | 2 +- src/nvim/ex_docmd.c | 2 +- src/nvim/globals.h | 1 + src/nvim/move.c | 4 ++-- src/nvim/quickfix.c | 2 +- src/nvim/screen.c | 4 ++-- src/nvim/version.c | 2 +- src/nvim/window.c | 8 ++++---- 9 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 90a564bb6a..efb8af50a4 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -4429,7 +4429,7 @@ do_arg_all ( } } /* don't close last window */ - if (firstwin == lastwin + if (ONE_WINDOW && (first_tabpage->tp_next == NULL || !had_tab)) use_firstwin = TRUE; else { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 0987cb3915..81cee5d535 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2806,7 +2806,7 @@ void ex_z(exarg_T *eap) * 'scroll' */ if (eap->forceit) bigness = curwin->w_height; - else if (firstwin == lastwin) + else if (ONE_WINDOW) bigness = curwin->w_p_scr * 2; else bigness = curwin->w_height - 3; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 5d7246581c..0a44392080 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5976,7 +5976,7 @@ static void ex_quit(exarg_T *eap) // specified. Example: // :h|wincmd w|1q - don't quit // :h|wincmd w|q - quit - if (only_one_window() && (firstwin == lastwin || eap->addr_count == 0)) { + if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) { getout(0); } /* close window; may free buffer */ diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 6d1bd1de12..f08812600f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -494,6 +494,7 @@ EXTERN int updating_screen INIT(= FALSE); EXTERN win_T *firstwin; /* first window */ EXTERN win_T *lastwin; /* last window */ EXTERN win_T *prevwin INIT(= NULL); /* previous window */ +# define ONE_WINDOW (firstwin == lastwin) /* * When using this macro "break" only breaks out of the inner loop. Use "goto" * to break out of the tabpage loop. diff --git a/src/nvim/move.c b/src/nvim/move.c index d5be4cb8c3..81d46a7f17 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1763,7 +1763,7 @@ int onepage(int dir, long count) loff.fill = 0; if (dir == FORWARD) { - if (firstwin == lastwin && p_window > 0 && p_window < Rows - 1) { + if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { /* Vi compatible scrolling */ if (p_window <= 2) ++curwin->w_topline; @@ -1797,7 +1797,7 @@ int onepage(int dir, long count) max_topfill(); continue; } - if (firstwin == lastwin && p_window > 0 && p_window < Rows - 1) { + if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { /* Vi compatible scrolling (sort of) */ if (p_window <= 2) --curwin->w_topline; diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index f17075f0c4..4997209556 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1878,7 +1878,7 @@ win_found: * If there is only one window and it is the quickfix window, create a * new one above the quickfix window. */ - if (((firstwin == lastwin) && bt_quickfix(curbuf)) || !usable_win) { + if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) { flags = WSP_ABOVE; if (ll_ref != NULL) flags |= WSP_NEWLOC; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a8353153fd..bcc996679b 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -991,7 +991,7 @@ static void win_update(win_T *wp) * first. */ if (mid_start == 0) { mid_end = wp->w_height; - if (lastwin == firstwin) { + if (ONE_WINDOW) { /* Clear the screen when it was not done by win_del_lines() or * win_ins_lines() above, "screen_cleared" is FALSE or MAYBE * then. */ @@ -7160,7 +7160,7 @@ static int fillchar_status(int *attr, win_T *wp) * window differs, or the fillchars differ, or this is not the * current window */ if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) - || !is_curwin || firstwin == lastwin) + || !is_curwin || ONE_WINDOW) || (fill_stl != fill_stlnc))) { return fill; } diff --git a/src/nvim/version.c b/src/nvim/version.c index d372779c75..4e780f31ad 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -660,7 +660,7 @@ static const int included_patches[] = { // 72 NA // 71 NA // 70 NA - // 69, + 69, 68, // 67 NA 66, diff --git a/src/nvim/window.c b/src/nvim/window.c index 29f5412ba0..7517d4e120 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -193,7 +193,7 @@ newwindow: /* cursor to previous window with wrap around */ case 'W': CHECK_CMDWIN - if (firstwin == lastwin && Prenum != 1) /* just one window */ + if (ONE_WINDOW && Prenum != 1) /* just one window */ beep_flush(); else { if (Prenum) { /* go to specified window */ @@ -1271,7 +1271,7 @@ static void win_rotate(int upwards, int count) frame_T *frp; int n; - if (firstwin == lastwin) { /* nothing to do */ + if (ONE_WINDOW) { /* nothing to do */ beep_flush(); return; } @@ -2194,7 +2194,7 @@ winframe_remove ( /* * If there is only one window there is nothing to remove. */ - if (tp == NULL ? firstwin == lastwin : tp->tp_firstwin == tp->tp_lastwin) + if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) return NULL; /* @@ -2331,7 +2331,7 @@ win_altframe ( frame_T *frp; int b; - if (tp == NULL ? firstwin == lastwin : tp->tp_firstwin == tp->tp_lastwin) + if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) /* Last window in this tab page, will go to next tab page. */ return alt_tabpage()->tp_curwin->w_frame; From b656159fcfded40839c222ae1c4e86873c82f295 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Sun, 23 Jul 2017 11:11:56 +0800 Subject: [PATCH 28/58] vim-patch:8.0.0073 Problem: More comparisons between firstwin and lastwin. Solution: Use ONE_WINDOW for consistency. (Hirohito Higashi) https://github.com/vim/vim/commit/459ca563128f2edb7e3bb190090bbb755a56dd55 --- src/nvim/buffer.c | 4 ++-- src/nvim/ex_docmd.c | 4 ++-- src/nvim/memory.c | 2 +- src/nvim/option.c | 6 +++--- src/nvim/version.c | 2 +- src/nvim/window.c | 14 +++++++------- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index efb8af50a4..f5d8b6ad30 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1203,7 +1203,7 @@ do_buffer ( */ while (buf == curbuf && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) - && (firstwin != lastwin || first_tabpage->tp_next != NULL)) { + && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) { if (win_close(curwin, FALSE) == FAIL) break; } @@ -4593,7 +4593,7 @@ void ex_buffer_all(exarg_T *eap) - tabline_height() : wp->w_width != Columns) || (had_tab > 0 && wp != firstwin)) - && firstwin != lastwin + && !ONE_WINDOW && !(wp->w_closing || wp->w_buffer->b_locked > 0) ) { win_close(wp, FALSE); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0a44392080..62fa26be3c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6176,9 +6176,9 @@ void tabpage_close(int forceit) { /* First close all the windows but the current one. If that worked then * close the last window in this tab, that will close it. */ - if (lastwin != firstwin) + if (!ONE_WINDOW) close_others(TRUE, forceit); - if (lastwin == firstwin) + if (ONE_WINDOW) ex_win_close(forceit, curwin, NULL); } diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 74c58fb203..328b96fd5c 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -585,7 +585,7 @@ void free_all_mem(void) p_ea = false; if (first_tabpage->tp_next != NULL) do_cmdline_cmd("tabonly!"); - if (firstwin != lastwin) + if (!ONE_WINDOW) do_cmdline_cmd("only!"); /* Free all spell info. */ diff --git a/src/nvim/option.c b/src/nvim/option.c index 75b56b4eb4..a6ae022e79 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4078,7 +4078,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } /* Change window height NOW */ - if (lastwin != firstwin) { + if (!ONE_WINDOW) { if (pp == &p_wh && curwin->w_height < p_wh) win_setheight((int)p_wh); if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) @@ -4107,7 +4107,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } /* Change window width NOW */ - if (lastwin != firstwin && curwin->w_width < p_wiw) + if (!ONE_WINDOW && curwin->w_width < p_wiw) win_setwidth((int)p_wiw); } /* 'winminwidth' */ @@ -5239,7 +5239,7 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) void comp_col(void) { - int last_has_status = (p_ls == 2 || (p_ls == 1 && firstwin != lastwin)); + int last_has_status = (p_ls == 2 || (p_ls == 1 && !ONE_WINDOW)); sc_col = 0; ru_col = 0; diff --git a/src/nvim/version.c b/src/nvim/version.c index 4e780f31ad..d27b2b2012 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -656,7 +656,7 @@ static const int included_patches[] = { // 76 NA // 75, // 74, - // 73, + 73, // 72 NA // 71 NA // 70 NA diff --git a/src/nvim/window.c b/src/nvim/window.c index 7517d4e120..faf5bceb56 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -574,7 +574,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) oldwin = curwin; /* add a status line when p_ls == 1 and splitting the first window */ - if (lastwin == firstwin && p_ls == 1 && oldwin->w_status_height == 0) { + if (ONE_WINDOW && p_ls == 1 && oldwin->w_status_height == 0) { if (oldwin->w_height <= p_wmh && new_wp == NULL) { EMSG(_(e_noroom)); return FAIL; @@ -1182,7 +1182,7 @@ static void win_exchange(long Prenum) win_T *wp2; int temp; - if (lastwin == firstwin) { /* just one window */ + if (ONE_WINDOW) { /* just one window */ beep_flush(); return; } @@ -1343,7 +1343,7 @@ static void win_totop(int size, int flags) int dir; int height = curwin->w_height; - if (lastwin == firstwin) { + if (ONE_WINDOW) { beep_flush(); return; } @@ -1728,7 +1728,7 @@ void close_windows(buf_T *buf, int keep_curwin) ++RedrawingDisabled; - for (win_T *wp = firstwin; wp != NULL && lastwin != firstwin; ) { + for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW; ) { if (wp->w_buffer == buf && (!keep_curwin || wp != curwin) && !(wp->w_closing || wp->w_buffer->b_locked > 0)) { if (win_close(wp, false) == FAIL) { @@ -1810,7 +1810,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev_curtab) FUNC_ATTR_NONNULL_ARG(1) { - if (firstwin != lastwin) { + if (!ONE_WINDOW) { return false; } buf_T *old_curbuf = curbuf; @@ -2851,7 +2851,7 @@ close_others ( win_close(wp, !P_HID(wp->w_buffer) && !bufIsChanged(wp->w_buffer)); } - if (message && lastwin != firstwin) + if (message && !ONE_WINDOW) EMSG(_("E445: Other window contains changes")); } @@ -5173,7 +5173,7 @@ last_status ( { /* Don't make a difference between horizontal or vertical split. */ last_status_rec(topframe, (p_ls == 2 - || (p_ls == 1 && (morewin || lastwin != firstwin)))); + || (p_ls == 1 && (morewin || !ONE_WINDOW)))); } static void last_status_rec(frame_T *fr, int statusline) From bc6a3fe78445f41851f30c6f55e11b6b1ed5feaf Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 19 Jul 2017 10:27:34 +0200 Subject: [PATCH 29/58] log: caller provides the source details --- src/nvim/eval.c | 16 ++++++++-- src/nvim/msgpack_rpc/channel.c | 57 ++++++++++++++-------------------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 08b3d1dbd7..3ec8deb666 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -15141,7 +15141,8 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } const char *error = NULL; - uint64_t id = channel_connect(tcp, address, 50, &error); + eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); + uint64_t id = channel_connect(tcp, address, 50, (char *)IObuff, &error); if (error) { EMSG2(_("connection failed: %s"), error); @@ -22449,8 +22450,9 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv) if (data->rpc) { - // the rpc channel takes over the in and out streams - channel_from_process(proc, data->id); + eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); + // RPC channel takes over the in/out streams. + channel_from_process(proc, data->id, (char *)IObuff); } else { wstream_init(proc->in, 0); if (proc->out) { @@ -22775,3 +22777,11 @@ bool eval_has_provider(const char *name) return false; } + +/// Writes ":" to `buf[bufsize]`. +void eval_format_source_name_line(char *buf, size_t bufsize) +{ + snprintf(buf, bufsize, "%s:%" PRIdLINENR, + (sourcing_name ? sourcing_name : (char_u *)"?"), + (sourcing_name ? sourcing_lnum : 0)); +} diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index d7748925aa..6fd1af1ba6 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -117,11 +117,15 @@ void channel_teardown(void) /// Creates an API channel by starting a process and connecting to its /// stdin/stdout. stderr is handled by the job infrastructure. /// +/// @param proc process object +/// @param id (optional) channel id +/// @param source description of source function, rplugin name, TCP addr, etc +/// /// @return Channel id (> 0), on success. 0, on error. -uint64_t channel_from_process(Process *proc, uint64_t id) +uint64_t channel_from_process(Process *proc, uint64_t id, char *source) { Channel *channel = register_channel(kChannelTypeProc, id, proc->events, - proc->argv); + source); incref(channel); // process channels are only closed by the exit_cb channel->data.proc = proc; @@ -137,7 +141,8 @@ uint64_t channel_from_process(Process *proc, uint64_t id) /// @param watcher The SocketWatcher ready to accept the connection void channel_from_connection(SocketWatcher *watcher) { - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, NULL); + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, + watcher->addr); socket_watcher_accept(watcher, &channel->data.stream); incref(channel); // close channel only after the stream is closed channel->data.stream.internal_close_cb = close_cb; @@ -147,8 +152,9 @@ void channel_from_connection(SocketWatcher *watcher) rstream_start(&channel->data.stream, receive_msgpack, channel); } -uint64_t channel_connect(bool tcp, const char *address, - int timeout, const char **error) +/// @param source description of source function, rplugin name, TCP addr, etc +uint64_t channel_connect(bool tcp, const char *address, int timeout, + char *source, const char **error) { if (!tcp) { char *path = fix_fname(address); @@ -160,7 +166,7 @@ uint64_t channel_connect(bool tcp, const char *address, xfree(path); } - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, NULL); + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, source); if (!socket_connect(&main_loop, &channel->data.stream, tcp, address, timeout, error)) { decref(channel); @@ -328,8 +334,7 @@ bool channel_close(uint64_t id) return true; } -/// Creates an API channel from stdin/stdout. This is used when embedding -/// Neovim +/// Creates an API channel from stdin/stdout. Used to embed Nvim. void channel_from_stdio(void) { Channel *channel = register_channel(kChannelTypeStdio, 0, NULL, NULL); @@ -744,9 +749,12 @@ static void close_cb(Stream *stream, void *data) decref(data); } +/// @param source description of source function, rplugin name, TCP addr, etc static Channel *register_channel(ChannelType type, uint64_t id, - MultiQueue *events, char **argv) + MultiQueue *events, char *source) { + // Jobs and channels share the same id namespace. + assert(id == 0 || !pmap_get(uint64_t)(channels, id)); Channel *rv = xmalloc(sizeof(Channel)); rv->events = events ? events : multiqueue_new_child(main_loop.events); rv->type = type; @@ -761,31 +769,12 @@ static Channel *register_channel(ChannelType type, uint64_t id, kv_init(rv->delayed_notifications); pmap_put(uint64_t)(channels, rv->id, rv); -#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL - switch(type) { - case kChannelTypeSocket: - DLOG("register ch %" PRIu64 " type=socket", rv->id); - break; - case kChannelTypeProc: { - char buf[IOSIZE]; - buf[0] = '\0'; - char **p = argv; - while (p != NULL && *p != NULL) { - xstrlcat(buf, *p, sizeof(buf)); - xstrlcat(buf, " ", sizeof(buf)); - p++; - } - DLOG("register ch %" PRIu64 " type=proc argv=%s", rv->id, buf); - } - break; - case kChannelTypeStdio: - DLOG("register ch %" PRIu64 " type=stdio", rv->id); - break; - case kChannelTypeInternal: - DLOG("register ch %" PRIu64 " type=internal", rv->id); - break; - } -#endif + ILOG("new channel %" PRIu64 " (%s): %s", rv->id, + (type == kChannelTypeProc ? "proc" + : (type == kChannelTypeSocket ? "socket" + : (type == kChannelTypeStdio ? "stdio" + : (type == kChannelTypeInternal ? "internal" : "?")))), + (source ? source : "?")); return rv; } From 4b390fafc910eb3e0acf17d5eb8a702b5fdd12ce Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 22 Jul 2017 21:24:26 +0200 Subject: [PATCH 30/58] log: termios info to DEBUG instead of INFO --- src/nvim/tui/tui.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index ab78a72909..0975b87ea3 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1007,7 +1007,7 @@ static void tui_flush(UI *ui) size_t nrevents = loop_size(data->loop); if (nrevents > TOO_MANY_EVENTS) { - ILOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents); + WLOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents); // Back-pressure: UI events may accumulate much faster than the terminal // device can serve them. Even if SIGINT/CTRL-C is received, user must still // wait for the TUI event-queue to drain, and if there are ~millions of @@ -1690,7 +1690,7 @@ static const char *tui_get_stty_erase(void) if (tcgetattr(input_global_fd(), &t) != -1) { stty_erase[0] = (char)t.c_cc[VERASE]; stty_erase[1] = '\0'; - ILOG("stty/termios:erase=%s", stty_erase); + DLOG("stty/termios:erase=%s", stty_erase); } #endif return stty_erase; @@ -1707,12 +1707,12 @@ static const char *tui_tk_ti_getstr(const char *name, const char *value, } if (strequal(name, "key_backspace")) { - ILOG("libtermkey:kbs=%s", value); + DLOG("libtermkey:kbs=%s", value); if (stty_erase[0] != 0) { return stty_erase; } } else if (strequal(name, "key_dc")) { - ILOG("libtermkey:kdch1=%s", value); + DLOG("libtermkey:kdch1=%s", value); // Vim: "If and are now the same, redefine ." if (value != NULL && strequal(stty_erase, value)) { return stty_erase[0] == DEL ? CTRL_H_STR : DEL_STR; From 5fc775e2efd1adf4985d9abcb1d970dcaa944555 Mon Sep 17 00:00:00 2001 From: Yichao Zhou Date: Thu, 13 Apr 2017 22:06:19 +0200 Subject: [PATCH 31/58] log.h: LOG_CALLSTACK --- src/nvim/log.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/nvim/log.h b/src/nvim/log.h index 221f0bbaf6..48be7606e7 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -61,6 +61,30 @@ __VA_ARGS__) #endif +#if defined(__linux__) +# include +# define LOG_CALLSTACK(prefix) \ + do { \ + void *trace[100]; \ + int trace_size = backtrace(trace, 100); \ + \ + char exe[1024]; \ + ssize_t elen = readlink("/proc/self/exe", exe, sizeof(exe) - 1); \ + exe[elen] = 0; \ + \ + for (int i = 1; i < trace_size; i++) { \ + char buf[256]; \ + snprintf(buf, sizeof(buf), "addr2line -e %s -f -p %p", exe, trace[i]); \ + FILE *fp = popen(buf, "r"); \ + while (fgets(buf, sizeof(buf) - 1, fp) != NULL) { \ + buf[strlen(buf)-1] = 0; \ + do_log(DEBUG_LOG_LEVEL, __func__, __LINE__, true, prefix "%s", buf); \ + } \ + fclose(fp); \ + } \ + } while (0) +#endif + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "log.h.generated.h" #endif From eb2473e9ea0a875b79612f84d1fae32b670d9b88 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 23 Jul 2017 17:00:30 +0200 Subject: [PATCH 32/58] log: log_callstack() --- src/nvim/README.md | 19 +++++++++++++------ src/nvim/log.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/log.h | 22 +--------------------- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/nvim/README.md b/src/nvim/README.md index 660e3d1010..1c1c3c364e 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,13 +1,20 @@ +Nvim core source +================ -Module-specific details are documented at the top of each module (e.g. -terminal.c, screen.c). +Module-specific details are documented at the top of each module (`terminal.c`, +`screen.c`, ...). -See also `:help development`. +See `:help development` for more guidelines. -UI Debugging ------------- +Logs +---- -At `DEBUG_LOG_LEVEL`, all UI events are logged. +Low-level log messages sink to `$NVIM_LOG_FILE`. + +You can use `LOG_CALLSTACK()` anywhere in the source to log the current +stacktrace. (Currently Linux-only.) + +UI events are logged at level 0 (`DEBUG_LOG_LEVEL`). rm -rf build/ make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" diff --git a/src/nvim/log.c b/src/nvim/log.c index f1dbe61dda..252fe5438d 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -173,6 +173,53 @@ FILE *open_log_file(void) return stderr; } +#if defined(__linux__) +# include +void log_callstack(const char *const func_name, const int line_num) +{ + void *trace[100]; + int trace_size = backtrace(trace, ARRAY_SIZE(trace)); + + char exepath[MAXPATHL] = { 0 }; + size_t exepathlen = MAXPATHL; + if (os_exepath(exepath, &exepathlen) != 0) { + abort(); + } + assert(24 + exepathlen < IOSIZE); // Must fit in `cmdbuf` below. + + do_log(DEBUG_LOG_LEVEL, func_name, line_num, true, "trace:"); + + char cmdbuf[IOSIZE + (20 * ARRAY_SIZE(trace))]; + snprintf(cmdbuf, sizeof(cmdbuf), "addr2line -e %s -f -p", exepath); + for (int i = 1; i < trace_size; i++) { + char buf[20]; // 64-bit pointer 0xNNNNNNNNNNNNNNNN with leading space. + snprintf(buf, sizeof(buf), " %p", trace[i]); + xstrlcat(cmdbuf, buf, sizeof(cmdbuf)); + } + // Now we have a command string like: + // addr2line -e /path/to/exe -f -p 0x123 0x456 ... + + log_lock(); + FILE *log_file = open_log_file(); + if (log_file == NULL) { + goto end; + } + + FILE *fp = popen(cmdbuf, "r"); + char linebuf[IOSIZE]; + while (fgets(linebuf, sizeof(linebuf) - 1, fp) != NULL) { + fprintf(log_file, " %s", linebuf); + } + pclose(fp); + + if (log_file != stderr && log_file != stdout) { + fclose(log_file); + } +end: + log_unlock(); +} +#endif + static bool do_log_to_file(FILE *log_file, int log_level, const char *func_name, int line_num, bool eol, const char* fmt, ...) diff --git a/src/nvim/log.h b/src/nvim/log.h index 48be7606e7..2bd18f5776 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -62,27 +62,7 @@ #endif #if defined(__linux__) -# include -# define LOG_CALLSTACK(prefix) \ - do { \ - void *trace[100]; \ - int trace_size = backtrace(trace, 100); \ - \ - char exe[1024]; \ - ssize_t elen = readlink("/proc/self/exe", exe, sizeof(exe) - 1); \ - exe[elen] = 0; \ - \ - for (int i = 1; i < trace_size; i++) { \ - char buf[256]; \ - snprintf(buf, sizeof(buf), "addr2line -e %s -f -p %p", exe, trace[i]); \ - FILE *fp = popen(buf, "r"); \ - while (fgets(buf, sizeof(buf) - 1, fp) != NULL) { \ - buf[strlen(buf)-1] = 0; \ - do_log(DEBUG_LOG_LEVEL, __func__, __LINE__, true, prefix "%s", buf); \ - } \ - fclose(fp); \ - } \ - } while (0) +# define LOG_CALLSTACK() log_callstack(__func__, __LINE__) #endif #ifdef INCLUDE_GENERATED_DECLARATIONS From 86f2c473dc0e5b3c886d71b2fee99e1f3c9849b6 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 24 Jul 2017 07:41:01 +0800 Subject: [PATCH 33/58] fix lint --- src/nvim/buffer.c | 18 ++++++++++-------- src/nvim/ex_cmds.c | 14 ++++++++------ src/nvim/ex_docmd.c | 12 +++++++----- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index f5d8b6ad30..724a8578ac 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1204,7 +1204,7 @@ do_buffer ( while (buf == curbuf && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) { - if (win_close(curwin, FALSE) == FAIL) + if (win_close(curwin, false) == FAIL) break; } @@ -4428,15 +4428,17 @@ do_arg_all ( continue; } } - /* don't close last window */ + // don't close last window if (ONE_WINDOW - && (first_tabpage->tp_next == NULL || !had_tab)) - use_firstwin = TRUE; - else { + && (first_tabpage->tp_next == NULL || !had_tab)) { + use_firstwin = true; + } else { win_close(wp, !P_HID(buf) && !bufIsChanged(buf)); - /* check if autocommands removed the next window */ - if (!win_valid(wpnext)) - wpnext = firstwin; /* start all over... */ + // check if autocommands removed the next window + if (!win_valid(wpnext)) { + // start all over... + wpnext = firstwin; + } } } } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 81cee5d535..a555fb77e8 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2802,16 +2802,18 @@ void ex_z(exarg_T *eap) int j; linenr_T lnum = eap->line2; - /* Vi compatible: ":z!" uses display height, without a count uses - * 'scroll' */ - if (eap->forceit) + // Vi compatible: ":z!" uses display height, without a count uses + // 'scroll' + if (eap->forceit) { bigness = curwin->w_height; - else if (ONE_WINDOW) + } else if (ONE_WINDOW) { bigness = curwin->w_p_scr * 2; - else + } else { bigness = curwin->w_height - 3; - if (bigness < 1) + } + if (bigness < 1) { bigness = 1; + } x = eap->arg; kind = x; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 62fa26be3c..d2eb881bc6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6174,12 +6174,14 @@ static void ex_tabonly(exarg_T *eap) */ void tabpage_close(int forceit) { - /* First close all the windows but the current one. If that worked then - * close the last window in this tab, that will close it. */ - if (!ONE_WINDOW) - close_others(TRUE, forceit); - if (ONE_WINDOW) + // First close all the windows but the current one. If that worked then + // close the last window in this tab, that will close it. + if (!ONE_WINDOW) { + close_others(true, forceit); + } + if (ONE_WINDOW) { ex_win_close(forceit, curwin, NULL); + } } /* From 58d8d91ec16b9f7eb3ea31ecae73c6c374e9b847 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 24 Jul 2017 10:27:22 -0400 Subject: [PATCH 34/58] provider: Extra pythonx's stderr handling to common functions --- runtime/autoload/provider.vim | 18 ++++++++++++++++++ runtime/autoload/provider/pythonx.vim | 16 ++++------------ 2 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 runtime/autoload/provider.vim diff --git a/runtime/autoload/provider.vim b/runtime/autoload/provider.vim new file mode 100644 index 0000000000..a4d5241b57 --- /dev/null +++ b/runtime/autoload/provider.vim @@ -0,0 +1,18 @@ +" Common functionality for providers + +let s:stderr = {} + +function! provider#stderr_collector(chan_id, data, event) dict + let stderr = get(s:stderr, a:chan_id, ['']) + let stderr[-1] .= a:data[0] + call extend(stderr, a:data[1:]) + let s:stderr[a:chan_id] = stderr +endfunction + +function! provider#clear_stderr(chan_id) + silent! call delete(s:stderr, a:chan_id) +endfunction + +function! provider#get_stderr(chan_id) + return get(s:stderr, a:chan_id, []) +endfunction diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim index 2f64c22c71..7285ed43ea 100644 --- a/runtime/autoload/provider/pythonx.vim +++ b/runtime/autoload/provider/pythonx.vim @@ -5,17 +5,7 @@ endif let s:loaded_pythonx_provider = 1 -let s:stderr = {} -let s:job_opts = {'rpc': v:true} - -" TODO(bfredl): this logic is common and should be builtin -function! s:job_opts.on_stderr(chan_id, data, event) - let stderr = get(s:stderr, a:chan_id, ['']) - let last = remove(stderr, -1) - let a:data[0] = last.a:data[0] - call extend(stderr, a:data) - let s:stderr[a:chan_id] = stderr -endfunction +let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')} function! provider#pythonx#Require(host) abort let ver = (a:host.orig_name ==# 'python') ? 2 : 3 @@ -38,9 +28,11 @@ function! provider#pythonx#Require(host) abort catch echomsg v:throwpoint echomsg v:exception - for row in get(s:stderr, channel_id, []) + for row in provider#get_stderr(channel_id) echomsg row endfor + finally + call provider#clear_stderr(channel_id) endtry throw remote#host#LoadErrorForHost(a:host.orig_name, \ '$NVIM_PYTHON_LOG_FILE') From 722806a1154a004494ef911c2b77a2f35a8a4a62 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 24 Jul 2017 10:32:07 -0400 Subject: [PATCH 35/58] provider: clipboard: Only report stderr if the job failed Closes #7054 --- runtime/autoload/provider/clipboard.vim | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 86006497d9..8eb694e9fa 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -6,7 +6,7 @@ let s:paste = {} " When caching is enabled, store the jobid of the xclip/xsel process keeping " ownership of the selection, so we know how long the cache is valid. -let s:selection = { 'owner': 0, 'data': [] } +let s:selection = { 'owner': 0, 'data': [], 'on_stderr': function('provider#stderr_collector') } function! s:selection.on_exit(jobid, data, event) abort " At this point this nvim instance might already have launched @@ -14,12 +14,13 @@ function! s:selection.on_exit(jobid, data, event) abort if self.owner == a:jobid let self.owner = 0 endif -endfunction - -function! s:selection.on_stderr(jobid, data, event) abort - echohl WarningMsg - echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(a:data) - echohl None + if a:data != 0 + let stderr = provider#get_stderr(a:jobid) + echohl WarningMsg + echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(stderr) + echohl None + endif + call provider#clear_stderr(a:jobid) endfunction let s:selections = { '*': s:selection, '+': copy(s:selection)} From 811c45163c5613981c8e5abdca40e28c1b61d076 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Tue, 25 Jul 2017 18:30:28 +0200 Subject: [PATCH 36/58] vim-patch:8.0.0034 Problem: No completion for ":messages". Solution: Complete "clear" argument. (Hirohito Higashi) https://github.com/vim/vim/commit/9e507ca8a3e1535e62de4bd86374b0fcd18ef5b8 --- runtime/doc/eval.txt | 2 ++ runtime/doc/map.txt | 1 + src/nvim/ex_docmd.c | 16 ++++++++++++++++ src/nvim/ex_getln.c | 1 + src/nvim/testdir/test_cmdline.vim | 5 +++++ src/nvim/version.c | 2 +- src/nvim/vim.h | 1 + 7 files changed, 27 insertions(+), 1 deletion(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 8882043162..3e75a42a62 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4046,7 +4046,9 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* locale locale names (as output of locale -a) mapping mapping name menu menus + messages |:messages| suboptions option options + packadd optional package |pack-add| names shellcmd Shell command sign |:sign| suboptions syntax syntax file names |'syntax'| diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index bfcf621cb8..944f7474be 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -1202,6 +1202,7 @@ completion can be enabled: -complete=locale locale names (as output of locale -a) -complete=mapping mapping name -complete=menu menus + -complete=messages |:messages| suboptions -complete=option options -complete=packadd optional package |pack-add| names -complete=shellcmd Shell command diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d2eb881bc6..99a0b86d2b 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -3440,6 +3440,11 @@ const char * set_one_cmd_context( xp->xp_pattern = (char_u *)arg; break; + case CMD_messages: + xp->xp_context = EXPAND_MESSAGES; + xp->xp_pattern = (char_u *)arg; + break; + case CMD_history: xp->xp_context = EXPAND_HISTORY; xp->xp_pattern = (char_u *)arg; @@ -4874,6 +4879,7 @@ static struct { #endif { EXPAND_MAPPINGS, "mapping" }, { EXPAND_MENUS, "menu" }, + { EXPAND_MESSAGES, "messages" }, { EXPAND_OWNSYNTAX, "syntax" }, { EXPAND_SYNTIME, "syntime" }, { EXPAND_SETTINGS, "option" }, @@ -9595,6 +9601,16 @@ char_u *get_behave_arg(expand_T *xp, int idx) return NULL; } +// Function given to ExpandGeneric() to obtain the possible arguments of the +// ":messages {clear}" command. +char_u *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx == 0) { + return (char_u *)"clear"; + } + return NULL; +} + static TriState filetype_detect = kNone; static TriState filetype_plugin = kNone; static TriState filetype_indent = kNone; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 0ba6c79a71..1d81a39dfe 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4050,6 +4050,7 @@ ExpandFromContext ( } tab[] = { { EXPAND_COMMANDS, get_command_name, false, true }, { EXPAND_BEHAVE, get_behave_arg, true, true }, + { EXPAND_MESSAGES, get_messages_arg, true, true }, { EXPAND_HISTORY, get_history_arg, true, true }, { EXPAND_USER_COMMANDS, get_user_commands, false, true }, { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 0c9d1297d6..f7a6aba6e7 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -130,6 +130,11 @@ func Test_getcompletion() let l = getcompletion('dark', 'highlight') call assert_equal([], l) + let l = getcompletion('', 'messages') + call assert_true(index(l, 'clear') >= 0) + let l = getcompletion('not', 'messages') + call assert_equal([], l) + if has('cscope') let l = getcompletion('', 'cscope') let cmds = ['add', 'find', 'help', 'kill', 'reset', 'show'] diff --git a/src/nvim/version.c b/src/nvim/version.c index 62e8919913..44348c4d98 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -695,7 +695,7 @@ static const int included_patches[] = { 37, // 36 NA 35, - // 34, + 34, 33, 32, 31, diff --git a/src/nvim/vim.h b/src/nvim/vim.h index c71ca411ea..62ffc7433e 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -163,6 +163,7 @@ enum { EXPAND_SYNTIME, EXPAND_USER_ADDR_TYPE, EXPAND_PACKADD, + EXPAND_MESSAGES, }; From 09470eb673e7582e6103173754fff85ee0e1d232 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Tue, 25 Jul 2017 18:53:47 +0200 Subject: [PATCH 37/58] vim-patch:8.0.0075 Problem: Using number for exception type lacks type checking. Solution: Use an enum. https://github.com/vim/vim/commit/8a5883b7488e492419dde7e1637cc72f2d566ba4 --- src/nvim/ex_docmd.c | 2 -- src/nvim/ex_eval.c | 19 ++++++++----------- src/nvim/ex_eval.h | 27 ++++++++++++++------------- src/nvim/version.c | 2 +- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d2eb881bc6..61c35a494b 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -844,8 +844,6 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, break; case ET_INTERRUPT: break; - default: - p = vim_strsave((char_u *)_(e_internal)); } saved_sourcing_name = sourcing_name; diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 5d664b94a8..c029df2f13 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -374,10 +374,9 @@ int do_intthrow(struct condstack *cstack) return TRUE; } -/* - * Get an exception message that is to be stored in current_exception->value. - */ -char_u *get_exception_string(void *value, int type, char_u *cmdname, int *should_free) +// Get an exception message that is to be stored in current_exception->value. +char_u *get_exception_string(void *value, except_type_T type, char_u *cmdname, + int *should_free) { char_u *ret, *mesg; char_u *p, *val; @@ -435,13 +434,11 @@ char_u *get_exception_string(void *value, int type, char_u *cmdname, int *should } -/* - * Throw a new exception. Return FAIL when out of memory or it was tried to - * throw an illegal user exception. "value" is the exception string for a - * user or interrupt exception, or points to a message list in case of an - * error exception. - */ -static int throw_exception(void *value, int type, char_u *cmdname) +// Throw a new exception. Return FAIL when out of memory or it was tried to +// throw an illegal user exception. "value" is the exception string for a +// user or interrupt exception, or points to a message list in case of an +// error exception. +static int throw_exception(void *value, except_type_T type, char_u *cmdname) { except_T *excp; int should_free; diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h index f61e01d25b..d5f8737bf3 100644 --- a/src/nvim/ex_eval.h +++ b/src/nvim/ex_eval.h @@ -89,27 +89,28 @@ struct msglist { struct msglist *next; /* next of several messages in a row */ }; +// The exception types. +typedef enum +{ + ET_USER, // exception caused by ":throw" command + ET_ERROR, // error exception + ET_INTERRUPT // interrupt exception triggered by Ctrl-C +} except_type_T; + /* * Structure describing an exception. * (don't use "struct exception", it's used by the math library). */ typedef struct vim_exception except_T; struct vim_exception { - int type; /* exception type */ - char_u *value; /* exception value */ - struct msglist *messages; /* message(s) causing error exception */ - char_u *throw_name; /* name of the throw point */ - linenr_T throw_lnum; /* line number of the throw point */ - except_T *caught; /* next exception on the caught stack */ + except_type_T type; // exception type + char_u *value; // exception value + struct msglist *messages; // message(s) causing error exception + char_u *throw_name; // name of the throw point + linenr_T throw_lnum; // line number of the throw point + except_T *caught; // next exception on the caught stack }; -/* - * The exception types. - */ -#define ET_USER 0 /* exception caused by ":throw" command */ -#define ET_ERROR 1 /* error exception */ -#define ET_INTERRUPT 2 /* interrupt exception triggered by Ctrl-C */ - /* * Structure to save the error/interrupt/exception state between calls to * enter_cleanup() and leave_cleanup(). Must be allocated as an automatic diff --git a/src/nvim/version.c b/src/nvim/version.c index 62e8919913..be3ee12262 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -654,7 +654,7 @@ static const int included_patches[] = { 78, // 77 NA // 76 NA - // 75, + 75, // 74, 73, // 72 NA From d94e39a517d5ccf895c6f1b95bfb621954f3bbec Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Tue, 25 Jul 2017 18:47:10 +0200 Subject: [PATCH 38/58] vim-patch:8.0.0062 Problem: No digraph for HORIZONTAL ELLIPSIS. Solution: Use ",.". (Hans Ginzel, closes vim/vim#1226) https://github.com/vim/vim/commit/81615517249bb78cba9c37c9834b787c1b265521 --- runtime/doc/digraph.txt | 3 ++- src/nvim/digraph.c | 1 + src/nvim/version.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/runtime/doc/digraph.txt b/runtime/doc/digraph.txt index 89351c5b4f..43f8ccab06 100644 --- a/runtime/doc/digraph.txt +++ b/runtime/doc/digraph.txt @@ -143,7 +143,7 @@ a standard meaning: Two 2 Hook Nine 9 Horn - Equals = Cyrillic (= used as second char) + Equals = Cyrillic (= used as second char) Asterisk * Greek Percent sign % Greek/Cyrillic special Plus + smalls: Arabic, capitals: Hebrew @@ -922,6 +922,7 @@ char digraph hex dec official name ~ † /- 2020 8224 DAGGER ‡ /= 2021 8225 DOUBLE DAGGER ‥ .. 2025 8229 TWO DOT LEADER +… ,. 2026 8230 HORIZONTAL ELLIPSIS ‰ %0 2030 8240 PER MILLE SIGN ′ 1' 2032 8242 PRIME ″ 2' 2033 8243 DOUBLE PRIME diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 32e71f61f7..bfb1b94738 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -793,6 +793,7 @@ static digr_T digraphdefault[] = { '/', '-', 0x2020 }, { '/', '=', 0x2021 }, { '.', '.', 0x2025 }, + { ',', '.', 0x2026 }, { '%', '0', 0x2030 }, { '1', '\'', 0x2032 }, { '2', '\'', 0x2033 }, diff --git a/src/nvim/version.c b/src/nvim/version.c index 62e8919913..bc478c4123 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -667,7 +667,7 @@ static const int included_patches[] = { // 65 NA 64, // 63, - // 62, + 62, // 61 NA 60, // 59 NA From 55c821184d82cd8d36d7085f74f9625a362fa7a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20H=C3=B6ltje?= Date: Wed, 26 Jul 2017 05:11:28 -0400 Subject: [PATCH 39/58] git: ignore .nvimlog (#7074) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index fb506305b6..3a8994a5f6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ tags /src/nvim/po/vim.pot /src/nvim/po/*.ck +# generated by tests with $NVIM_LOG_FILE set. +/.nvimlog + # Files generated by scripts/vim-patch.sh /.vim-src/ From 861ba1ddd4c922d222348671ee46f09afc165fcd Mon Sep 17 00:00:00 2001 From: Nikolai Aleksandrovich Pavlov Date: Thu, 27 Jul 2017 04:52:22 +0300 Subject: [PATCH 40/58] runtime: Fix where desktop file and icon are installed (#7072) With `xdg-*` utilities CMAKE_INSTALL_PREFIX is incorrectly ignored. Taken from [nvim-qt][1]. For some reason it only checks for !APPLE. [1]: https://github.com/equalsraf/neovim-qt/blob/b26596d164394765a0f757c65e266f5f77587521/src/gui/CMakeLists.txt#L48-L55 --- runtime/CMakeLists.txt | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 69498dc1a1..a9efc90b87 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -100,20 +100,6 @@ add_custom_target( ${GENERATED_PACKAGE_TAGS} ) -# Optional targets for nvim.desktop file and icon. -find_program(XDG_MENU_PRG xdg-desktop-menu) -find_program(XDG_ICON_PRG xdg-icon-resource) -if(XDG_MENU_PRG) - add_custom_target(desktop-file - COMMAND xdg-desktop-menu install --novendor ${PROJECT_SOURCE_DIR}/runtime/nvim.desktop) - # add_dependencies(runtime desktop-file) -endif() -if(XDG_ICON_PRG) - add_custom_target(desktop-icon - COMMAND xdg-icon-resource install --novendor --size 128 ${PROJECT_SOURCE_DIR}/runtime/nvim.png) - # add_dependencies(runtime desktop-icon) -endif() - # CMake is painful here. It will create the destination using the user's # current umask, and we don't want that. And we don't just want to install # the target directory, as it will mess with existing permissions. So this @@ -128,6 +114,16 @@ install_helper( FILES ${GENERATED_SYN_VIM} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/syntax/vim) +if(NOT APPLE) + install_helper( + FILES ${CMAKE_CURRENT_SOURCE_DIR}/nvim.desktop + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) + + install_helper( + FILES ${CMAKE_CURRENT_SOURCE_DIR}/nvim.png + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pixmaps) +endif() + file(GLOB_RECURSE RUNTIME_PROGRAMS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.awk *.sh *.bat) From 8eb54c5deca228fa5a34d22cb9f91d956eed773c Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Thu, 27 Jul 2017 09:56:25 +0800 Subject: [PATCH 41/58] vim-patch:8.0.0088 (#7080) Problem: When a test fails in Setup or Teardown the problem is not reported. Solution: Add a try/catch. (Hirohito Higashi) https://github.com/vim/vim/commit/cc28e2d05d05552d8b72a520be8a193f3d9822d4 --- src/nvim/testdir/runtest.vim | 12 ++++++++++-- src/nvim/version.c | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 117ba52eb6..39ffabc024 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -79,7 +79,11 @@ let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' func RunTheTest(test) echo 'Executing ' . a:test if exists("*SetUp") - call SetUp() + try + call SetUp() + catch + call add(v:errors, 'Caught exception in SetUp() before ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) + endtry endif call add(s:messages, 'Executing ' . a:test) @@ -94,7 +98,11 @@ func RunTheTest(test) endtry if exists("*TearDown") - call TearDown() + try + call TearDown() + catch + call add(v:errors, 'Caught exception in TearDown() after ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) + endtry endif " Close any extra windows and make the current one not modified. diff --git a/src/nvim/version.c b/src/nvim/version.c index d07a1c2d68..b585c3ad9a 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -641,7 +641,7 @@ static const int included_patches[] = { // 91, // 90, // 89 NA - // 88, + 88, // 87 NA // 86, 85, From 3abf4c72b4323e8cff35b77b9a5c2154c85aa185 Mon Sep 17 00:00:00 2001 From: John Szakmeister Date: Thu, 27 Jul 2017 06:48:00 -0400 Subject: [PATCH 42/58] build: disable array bounds checking in gcc < 4.8.5 Thanks to James McCoy (jamessan) for finding the source of this issue. --- CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 141fa81e16..d1d6cd237f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,6 +240,16 @@ if(HAS_DIAG_COLOR_FLAG) add_definitions(-fdiagnostics-color=auto) endif() +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.8.5") + # The array-bounds testing is broken in some versions of GCC, so we disable + # it in everything prior to 4.8.5. + # See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56273 for details about + # the bug. + add_definitions(-Wno-array-bounds) + endif() +endif() + option(TRAVIS_CI_BUILD "Travis/QuickBuild CI. Extra flags will be set." OFF) if(TRAVIS_CI_BUILD) From dc685387a3d60e9ea3d09c80c74d4613b618cf14 Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Tue, 21 Mar 2017 03:21:53 +0100 Subject: [PATCH 43/58] viml: introduce menu_get() function #6322 menu_get({path}, {modes}). See :h menu_get. --- runtime/doc/eval.txt | 40 ++++ src/nvim/eval.c | 14 ++ src/nvim/eval.lua | 7 +- src/nvim/ex_cmds.lua | 2 +- src/nvim/getchar.h | 15 +- src/nvim/mbyte.c | 13 +- src/nvim/menu.c | 296 +++++++++++++++-------- src/nvim/menu.h | 47 ++-- test/functional/ex_cmds/menu_spec.lua | 327 ++++++++++++++++++++++++++ 9 files changed, 629 insertions(+), 132 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 3e75a42a62..3a27b2d7a0 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -5508,6 +5508,46 @@ max({expr}) Return the maximum value of all items in {expr}. items in {expr} cannot be used as a Number this results in an error. An empty |List| or |Dictionary| results in zero. +menu_get({path}, {modes}) *menu_get()* + Returns a |Dictionary| with all the submenu of {path} (set to + an empty string to match all menus). Only the commands matching {modes} are + returned ('a' for all, 'i' for insert see |creating-menus|). + + For instance, executing: +> + nnoremenu &Test.Test inormal + inoremenu Test.Test insert + vnoremenu Test.Test x + echo menu_get("") +< +should produce an output with a similar structure: +> + [ { + "hidden": 0, + "name": "Test", + "priority": 500, + "shortcut": 84, + "submenus": [ { + "hidden": 0, + "mappings": { + i": { + "enabled": 1, + "noremap": 1, + "rhs": "insert", + "sid": 1, + "silent": 0 + }, + n": { ... }, + s": { ... }, + v": { ... } + }, + "name": "Test", + "priority": 500, + "shortcut": 0 + } ] + } ] +< + *min()* min({expr}) Return the minimum value of all items in {expr}. {expr} can be a list or a dictionary. For a dictionary, diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 3ec8deb666..2988225110 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -47,6 +47,7 @@ #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/menu.h" #include "nvim/message.h" #include "nvim/misc1.h" #include "nvim/keymap.h" @@ -8173,6 +8174,19 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } + +/// "menu_get(path [, modes])" function +static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv); + int modes = MENU_ALL_MODES; + if (argvars[1].v_type == VAR_STRING) { + const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); + modes = get_menu_cmd_modes(strmodes, false, NULL, NULL); + } + menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); +} + /* * "extend(list, list [, idx])" function * "extend(dict, dict [, action])" function diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 30766a0734..08baae4086 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -2,10 +2,10 @@ -- -- Keys: -- --- args Number of arguments, list with maximum and minimum number of arguments --- or list with a minimum number of arguments only. Defaults to zero +-- args Number of arguments, list with maximum and minimum number of arguments +-- or list with a minimum number of arguments only. Defaults to zero -- arguments. --- func Name of the C function which implements the VimL function. Defaults to +-- func Name of the C function which implements the VimL function. Defaults to -- `f_{funcname}`. local varargs = function(nr) @@ -208,6 +208,7 @@ return { matchstr={args={2, 4}}, matchstrpos={args={2,4}}, max={args=1}, + menu_get={args={1, 2}}, min={args=1}, mkdir={args={1, 3}}, mode={args={0, 1}}, diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 7203fbd97d..46f7a7bc40 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -1,4 +1,4 @@ -bit = require 'bit' +local bit = require 'bit' -- Description of the values below is contained in ex_cmds_defs.h file. local RANGE = 0x001 diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index 28584e0534..e634273e0d 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -5,12 +5,15 @@ #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" -/* Values for "noremap" argument of ins_typebuf(). Also used for - * map->m_noremap and menu->noremap[]. */ -#define REMAP_YES 0 /* allow remapping */ -#define REMAP_NONE -1 /* no remapping */ -#define REMAP_SCRIPT -2 /* remap script-local mappings only */ -#define REMAP_SKIP -3 /* no remapping for first char */ +/// Values for "noremap" argument of ins_typebuf(). Also used for +/// map->m_noremap and menu->noremap[]. +/// @addtogroup REMAP_VALUES +/// @{ +#define REMAP_YES 0 ///< allow remapping +#define REMAP_NONE -1 ///< no remapping +#define REMAP_SCRIPT -2 ///< remap script-local mappings only +#define REMAP_SKIP -3 ///< no remapping for first char +/// @} #define KEYLEN_PART_KEY -1 /* keylen value for incomplete key-code */ #define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */ diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 2acfb896d8..4440300640 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -932,12 +932,13 @@ int utf_char2len(int c) return 6; } -/* - * Convert Unicode character "c" to UTF-8 string in "buf[]". - * Returns the number of bytes. - * This does not include composing characters. - */ -int utf_char2bytes(int c, char_u *buf) +/// Convert Unicode character to UTF-8 string +/// +/// @param c character to convert to \p buf +/// @param[out] buf UTF-8 string generated from \p c, does not add \0 +/// @return the number of bytes (between 1 and 6) +/// @note This does not include composing characters. +int utf_char2bytes(int c, char_u *const buf) { if (c < 0x80) { /* 7 bits */ buf[0] = c; diff --git a/src/nvim/menu.c b/src/nvim/menu.c index c8e6012e5c..91b615be30 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -26,7 +26,7 @@ #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/ui.h" - +#include "nvim/eval/typval.h" #define MENUDEPTH 10 /* maximum depth of menus */ @@ -38,8 +38,8 @@ -/* The character for each menu mode */ -static char_u menu_mode_chars[] = {'n', 'v', 's', 'o', 'i', 'c', 't'}; +/// The character for each menu mode +static char_u menu_mode_chars[] = { 'n', 'v', 's', 'o', 'i', 'c', 't' }; static char_u e_notsubmenu[] = N_( "E327: Part of menu-item path is not sub-menu"); @@ -47,17 +47,14 @@ static char_u e_othermode[] = N_("E328: Menu only exists in another mode"); static char_u e_nomenu[] = N_("E329: No menu \"%s\""); -/* - * Do the :menu command and relatives. - */ -void -ex_menu ( - exarg_T *eap /* Ex command arguments */ -) +/// Do the :menu command and relatives. +/// @param eap Ex command arguments +void +ex_menu(exarg_T *eap) { char_u *menu_path; int modes; - char_u *map_to; + char_u *map_to; // command mapped to the menu entry int noremap; bool silent = false; int unmenu; @@ -93,9 +90,12 @@ ex_menu ( } - /* Locate an optional "icon=filename" argument. */ + // Locate an optional "icon=filename" argument + // Kept just the command parsing from vim for compativility but no further + // processing is done if (STRNCMP(arg, "icon=", 5) == 0) { arg += 5; + // icon = arg; while (*arg != NUL && *arg != ' ') { if (*arg == '\\') STRMOVE(arg, arg + 1); @@ -107,12 +107,12 @@ ex_menu ( } } - /* - * Fill in the priority table. - */ - for (p = arg; *p; ++p) - if (!ascii_isdigit(*p) && *p != '.') + // Fill in the priority table. + for (p = arg; *p; p++) { + if (!ascii_isdigit(*p) && *p != '.') { break; + } + } if (ascii_iswhite(*p)) { for (i = 0; i < MENUDEPTH && !ascii_iswhite(*arg); ++i) { pri_tab[i] = getdigits_long(&arg); @@ -226,8 +226,7 @@ ex_menu ( menuarg.modes = modes; menuarg.noremap[0] = noremap; menuarg.silent[0] = silent; - add_menu_path(menu_path, &menuarg, pri_tab, map_to - ); + add_menu_path(menu_path, &menuarg, pri_tab, map_to); /* * For the PopUp menu, add a menu for each mode separately. @@ -252,16 +251,18 @@ theend: ; } -/* - * Add the menu with the given name to the menu hierarchy - */ -static int -add_menu_path ( - char_u *menu_path, - vimmenu_T *menuarg, /* passes modes, iconfile, iconidx, - icon_builtin, silent[0], noremap[0] */ - long *pri_tab, - char_u *call_data + +/// Add the menu with the given name to the menu hierarchy +/// +/// @param[out] menuarg menu entry +/// @param[] pri_tab priority table +/// @param[in] call_data Right hand side command +static int +add_menu_path( + const char_u *const menu_path, + vimmenu_T *menuarg, + const long *const pri_tab, + const char_u *const call_data ) { char_u *path_name; @@ -296,8 +297,9 @@ add_menu_path ( if (map_to != NULL) { en_name = name; name = map_to; - } else + } else { en_name = NULL; + } dname = menu_text(name, NULL, NULL); if (*dname == NUL) { /* Only a mnemonic or accelerator is not valid. */ @@ -311,14 +313,15 @@ add_menu_path ( while (menu != NULL) { if (menu_name_equal(name, menu) || menu_name_equal(dname, menu)) { if (*next_name == NUL && menu->children != NULL) { - if (!sys_menu) + if (!sys_menu) { EMSG(_("E330: Menu path must not lead to a sub-menu")); + } goto erret; } - if (*next_name != NUL && menu->children == NULL - ) { - if (!sys_menu) + if (*next_name != NUL && menu->children == NULL) { + if (!sys_menu) { EMSG(_(e_notsubmenu)); + } goto erret; } break; @@ -352,7 +355,7 @@ add_menu_path ( menu->modes = modes; menu->enabled = MENU_ALL_MODES; menu->name = vim_strsave(name); - /* separate mnemonic and accelerator text from actual menu name */ + // separate mnemonic and accelerator text from actual menu name menu->dname = menu_text(name, &menu->mnemonic, &menu->actext); if (en_name != NULL) { menu->en_name = vim_strsave(en_name); @@ -364,9 +367,7 @@ add_menu_path ( menu->priority = pri_tab[pri_idx]; menu->parent = parent; - /* - * Add after menu that has lower priority. - */ + // Add after menu that has lower priority. menu->next = *lower_pri; *lower_pri = menu; @@ -392,8 +393,9 @@ add_menu_path ( name = next_name; xfree(dname); dname = NULL; - if (pri_tab[pri_idx + 1] != -1) - ++pri_idx; + if (pri_tab[pri_idx + 1] != -1) { + pri_idx++; + } } xfree(path_name); @@ -419,8 +421,7 @@ add_menu_path ( // Don't do this for "". c = 0; d = 0; - if (amenu && call_data != NULL && *call_data != NUL - ) { + if (amenu && call_data != NULL && *call_data != NUL) { switch (1 << i) { case MENU_VISUAL_MODE: case MENU_SELECT_MODE: @@ -438,9 +439,9 @@ add_menu_path ( if (c != 0) { menu->strings[i] = xmalloc(STRLEN(call_data) + 5 ); menu->strings[i][0] = c; - if (d == 0) + if (d == 0) { STRCPY(menu->strings[i] + 1, call_data); - else { + } else { menu->strings[i][1] = d; STRCPY(menu->strings[i] + 2, call_data); } @@ -452,8 +453,9 @@ add_menu_path ( menu->strings[i][len + 1] = Ctrl_G; menu->strings[i][len + 2] = NUL; } - } else + } else { menu->strings[i] = p; + } menu->noremap[i] = menuarg->noremap[0]; menu->silent[i] = menuarg->silent[0]; } @@ -657,20 +659,109 @@ static void free_menu_string(vimmenu_T *menu, int idx) menu->strings[idx] = NULL; } -/* - * Show the mapping associated with a menu item or hierarchy in a sub-menu. - */ -static int show_menus(char_u *path_name, int modes) +/// Export menus +/// +/// @param[in] menu if null, starts from root_menu +/// @param modes, a choice of \ref MENU_MODES +/// @return a dict with name/commands +/// @see menu_get +static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) +{ + dict_T *dict; + char buf[sizeof(menu->mnemonic)]; + int mnemonic_len; + + if (!menu || (menu->modes & modes) == 0x0) { + return NULL; + } + + dict = tv_dict_alloc(); + tv_dict_add_str(dict, S_LEN("name"), (char *)menu->dname); + tv_dict_add_nr(dict, S_LEN("priority"), (int)menu->priority); + tv_dict_add_nr(dict, S_LEN("hidden"), menu_is_hidden(menu->dname)); + + if (menu->mnemonic) { + mnemonic_len = utf_char2bytes(menu->mnemonic, (u_char *)buf); + buf[mnemonic_len] = '\0'; + tv_dict_add_str(dict, S_LEN("shortcut"), buf); + } + + if (menu->modes & MENU_TIP_MODE && menu->strings[MENU_INDEX_TIP]) { + tv_dict_add_str(dict, S_LEN("tooltip"), + (char *)menu->strings[MENU_INDEX_TIP]); + } + + if (!menu->children) { + // leaf menu + dict_T *commands = tv_dict_alloc(); + tv_dict_add_dict(dict, S_LEN("mappings"), commands); + + for (int bit = 0; bit < MENU_MODES; bit++) { + if ((menu->modes & modes & (1 << bit)) != 0) { + dict_T *impl = tv_dict_alloc(); + if (*menu->strings[bit] == NUL) { + tv_dict_add_str(impl, S_LEN("rhs"), (char *)""); + } else { + tv_dict_add_str(impl, S_LEN("rhs"), (char *)menu->strings[bit]); + } + tv_dict_add_nr(impl, S_LEN("silent"), menu->silent[bit]); + tv_dict_add_nr(impl, S_LEN("enabled"), + (menu->enabled & (1 << bit)) ? 1 : 0); + tv_dict_add_nr(impl, S_LEN("noremap"), + (menu->noremap[bit] & REMAP_NONE) ? 1 : 0); + tv_dict_add_nr(impl, S_LEN("sid"), + (menu->noremap[bit] & REMAP_SCRIPT) ? 1 : 0); + tv_dict_add_dict(commands, (char *)&menu_mode_chars[bit], 1, impl); + } + } + } else { + // visit recursively all children + list_T *children_list = tv_list_alloc(); + for (menu = menu->children; menu != NULL; menu = menu->next) { + dict_T *dic = menu_get_recursive(menu, modes); + if (dict && tv_dict_len(dict) > 0) { + tv_list_append_dict(children_list, dic); + } + } + tv_dict_add_list(dict, S_LEN("submenus"), children_list); + } + return dict; +} + + +/// Export menus matching path \p path_name +/// +/// @param path_name +/// @param modes supported modes, see \ref MENU_MODES +/// @param[in,out] list must be allocated +/// @return false if could not find path_name +bool menu_get(char_u *const path_name, int modes, list_T *list) { - char_u *p; - char_u *name; vimmenu_T *menu; - vimmenu_T *parent = NULL; + menu = find_menu(root_menu, path_name, modes); + if (!menu) { + return false; + } + for (; menu != NULL; menu = menu->next) { + dict_T *dict = menu_get_recursive(menu, modes); + if (dict && tv_dict_len(dict) > 0) { + tv_list_append_dict(list, dict); + } + } + return true; +} - menu = root_menu; - name = path_name = vim_strsave(path_name); - /* First, find the (sub)menu with the given name */ +/// Find menu matching required name and modes +/// +/// @param menu top menu to start looking from +/// @param name path towards the menu +/// @return menu if \p name is null, found menu or NULL +vimmenu_T * +find_menu(vimmenu_T *menu, char_u * name, int modes) +{ + char_u *p; + while (*name) { p = menu_name_skip(name); while (menu != NULL) { @@ -678,39 +769,46 @@ static int show_menus(char_u *path_name, int modes) /* Found menu */ if (*p != NUL && menu->children == NULL) { EMSG(_(e_notsubmenu)); - xfree(path_name); - return FAIL; + return NULL; } else if ((menu->modes & modes) == 0x0) { EMSG(_(e_othermode)); - xfree(path_name); - return FAIL; + return NULL; } break; } menu = menu->next; } + if (menu == NULL) { EMSG2(_(e_nomenu), name); - xfree(path_name); - return FAIL; + return NULL; } name = p; - parent = menu; menu = menu->children; } - xfree(path_name); + return menu; +} + +/// Show the mapping associated with a menu item or hierarchy in a sub-menu. +static int show_menus(char_u *const path_name, int modes) +{ + vimmenu_T *menu; + + // First, find the (sub)menu with the given name + menu = find_menu(root_menu, path_name, modes); + if (!menu) { + return FAIL; + } /* Now we have found the matching menu, and we list the mappings */ /* Highlight title */ MSG_PUTS_TITLE(_("\n--- Menus ---")); - show_menus_recursive(parent, modes, 0); + show_menus_recursive(menu->parent, modes, 0); return OK; } -/* - * Recursively show the mappings associated with the menus under the given one - */ +/// Recursively show the mappings associated with the menus under the given one static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) { int i; @@ -993,12 +1091,13 @@ char_u *get_menu_names(expand_T *xp, int idx) return str; } -/* - * Skip over this element of the menu path and return the start of the next - * element. Any \ and ^Vs are removed from the current element. - * "name" may be modified. - */ -char_u *menu_name_skip(char_u *name) + +/// Skip over this element of the menu path and return the start of the next +/// element. Any \ and ^Vs are removed from the current element. +/// +/// @param name may be modified. +/// @return start of the next element +char_u *menu_name_skip(char_u *const name) { char_u *p; @@ -1018,16 +1117,16 @@ char_u *menu_name_skip(char_u *name) * Return TRUE when "name" matches with menu "menu". The name is compared in * two ways: raw menu name and menu name without '&'. ignore part after a TAB. */ -static int menu_name_equal(char_u *name, vimmenu_T *menu) +static bool menu_name_equal(const char_u *const name, vimmenu_T *const menu) { if (menu->en_name != NULL && (menu_namecmp(name, menu->en_name) || menu_namecmp(name, menu->en_dname))) - return TRUE; + return true; return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname); } -static int menu_namecmp(char_u *name, char_u *mname) +static bool menu_namecmp(const char_u *const name, const char_u *const mname) { int i; @@ -1038,18 +1137,20 @@ static int menu_namecmp(char_u *name, char_u *mname) && (mname[i] == NUL || mname[i] == TAB); } -/* - * Return the modes specified by the given menu command (eg :menu! returns - * MENU_CMDLINE_MODE | MENU_INSERT_MODE). - * If "noremap" is not NULL, then the flag it points to is set according to - * whether the command is a "nore" command. - * If "unmenu" is not NULL, then the flag it points to is set according to - * whether the command is an "unmenu" command. - */ -static int -get_menu_cmd_modes ( - char_u *cmd, - int forceit, /* Was there a "!" after the command? */ + +/// converts a string into a combination of \ref MENU_MODES +/// (eg :menu! returns MENU_CMDLINE_MODE | MENU_INSERT_MODE) +/// +/// @param[in] cmd a string like 'n' (normal) or 'a' (all) +/// @param[in] forceit Was there a "!" after the command? +/// @param[out] If "noremap" is not NULL, then the flag it points to is set +/// according to whether the command is a "nore" command. +/// @param[out] unmenu is not NULL, then the flag it points to is set according +/// to whether the command is an "unmenu" command. +int +get_menu_cmd_modes( + const char_u * cmd, + bool forceit, int *noremap, int *unmenu ) @@ -1090,12 +1191,15 @@ get_menu_cmd_modes ( } /* FALLTHROUGH */ default: - --cmd; - if (forceit) /* menu!! */ + cmd--; + if (forceit) { + // menu!! modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE; - else /* menu */ + } else { + // menu modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE; + } } if (noremap != NULL) @@ -1201,12 +1305,14 @@ int menu_is_separator(char_u *name) return name[0] == '-' && name[STRLEN(name) - 1] == '-'; } -/* - * Return TRUE if the menu is hidden: Starts with ']' - */ + +/// True if a popup menu or starts with \ref MNU_HIDDEN_CHAR +/// +/// @return true if the menu is hidden static int menu_is_hidden(char_u *name) { - return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL); + return (name[0] == MNU_HIDDEN_CHAR) + || (menu_is_popup(name) && name[5] != NUL); } /* diff --git a/src/nvim/menu.h b/src/nvim/menu.h index a84b7d812e..5ff979f2bf 100644 --- a/src/nvim/menu.h +++ b/src/nvim/menu.h @@ -6,7 +6,9 @@ #include "nvim/types.h" // for char_u and expand_T #include "nvim/ex_cmds_defs.h" // for exarg_T -/* Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode */ +/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode +/// \addtogroup MENU_INDEX +/// @{ #define MENU_INDEX_INVALID -1 #define MENU_INDEX_NORMAL 0 #define MENU_INDEX_VISUAL 1 @@ -16,8 +18,12 @@ #define MENU_INDEX_CMDLINE 5 #define MENU_INDEX_TIP 6 #define MENU_MODES 7 +/// @} +/// note MENU_INDEX_TIP is not a 'real' mode -/* Menu modes */ +/// Menu modes +/// \addtogroup MENU_MODES +/// @{ #define MENU_NORMAL_MODE (1 << MENU_INDEX_NORMAL) #define MENU_VISUAL_MODE (1 << MENU_INDEX_VISUAL) #define MENU_SELECT_MODE (1 << MENU_INDEX_SELECT) @@ -26,31 +32,30 @@ #define MENU_CMDLINE_MODE (1 << MENU_INDEX_CMDLINE) #define MENU_TIP_MODE (1 << MENU_INDEX_TIP) #define MENU_ALL_MODES ((1 << MENU_INDEX_TIP) - 1) -/*note MENU_INDEX_TIP is not a 'real' mode*/ +/// @} -/* Start a menu name with this to not include it on the main menu bar */ +/// Start a menu name with this to not include it on the main menu bar #define MNU_HIDDEN_CHAR ']' typedef struct VimMenu vimmenu_T; struct VimMenu { - int modes; /* Which modes is this menu visible for? */ - int enabled; /* for which modes the menu is enabled */ - char_u *name; /* Name of menu, possibly translated */ - char_u *dname; /* Displayed Name ("name" without '&') */ - char_u *en_name; /* "name" untranslated, NULL when "name" - * was not translated */ - char_u *en_dname; /* "dname" untranslated, NULL when "dname" - * was not translated */ - int mnemonic; /* mnemonic key (after '&') */ - char_u *actext; /* accelerator text (after TAB) */ - long priority; /* Menu order priority */ - char_u *strings[MENU_MODES]; /* Mapped string for each mode */ - int noremap[MENU_MODES]; /* A REMAP_ flag for each mode */ - bool silent[MENU_MODES]; /* A silent flag for each mode */ - vimmenu_T *children; /* Children of sub-menu */ - vimmenu_T *parent; /* Parent of menu */ - vimmenu_T *next; /* Next item in menu */ + int modes; ///< Which modes is this menu visible for + int enabled; ///< for which modes the menu is enabled + char_u *name; ///< Name of menu, possibly translated + char_u *dname; ///< Displayed Name ("name" without '&') + char_u *en_name; ///< "name" untranslated, NULL when + ///< was not translated + char_u *en_dname; ///< NULL when "dname" untranslated + int mnemonic; ///< mnemonic key (after '&') + char_u *actext; ///< accelerator text (after TAB) + long priority; ///< Menu order priority + char_u *strings[MENU_MODES]; ///< Mapped string for each mode + int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode + bool silent[MENU_MODES]; ///< A silent flag for each mode + vimmenu_T *children; ///< Children of sub-menu + vimmenu_T *parent; ///< Parent of menu + vimmenu_T *next; ///< Next item in menu }; diff --git a/test/functional/ex_cmds/menu_spec.lua b/test/functional/ex_cmds/menu_spec.lua index 57198600b9..eca45efcf1 100644 --- a/test/functional/ex_cmds/menu_spec.lua +++ b/test/functional/ex_cmds/menu_spec.lua @@ -2,6 +2,8 @@ local helpers = require('test.functional.helpers')(after_each) local clear, command, nvim = helpers.clear, helpers.command, helpers.nvim local expect, feed = helpers.expect, helpers.feed local eq, eval = helpers.eq, helpers.eval +local funcs = helpers.funcs + describe(':emenu', function() @@ -56,3 +58,328 @@ describe(':emenu', function() eq('thiscmdmode', eval('getcmdline()')) end) end) + +describe('menu_get', function() + + before_each(function() + clear() + command('nnoremenu &Test.Test inormal') + command('inoremenu Test.Test insert') + command('vnoremenu Test.Test x') + command('cnoremenu Test.Test cmdmode') + command('menu Test.Nested.test level1') + command('menu Test.Nested.Nested2 level2') + + command('nnoremenu