From 1d4750bc40a78d429e41a323bd8220519a7c7e8f Mon Sep 17 00:00:00 2001 From: Osamu Aoki Date: Thu, 26 Aug 2021 22:53:35 +0900 Subject: [PATCH 1/7] remove tailing whitespaces Signed-off-by: Osamu Aoki --- ftplugin/python_matchit.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ftplugin/python_matchit.vim b/ftplugin/python_matchit.vim index cd10ee7..bf50cad 100644 --- a/ftplugin/python_matchit.vim +++ b/ftplugin/python_matchit.vim @@ -3,7 +3,7 @@ " Last Change: Thu 02 Oct 2003 12:12:20 PM EDT " Maintainer: Benji Fisher, Ph.D. " Version: 0.5, for Vim 6.1 -" URL: http://www.vim.org/scripts/script.php?script_id=386 +" URL: http://www.vim.org/scripts/script.php?script_id=386 " allow user to prevent loading and prevent duplicate loading if exists("b:loaded_py_match") || &cp @@ -43,7 +43,7 @@ let s:loaded_functions = 1 " keywords that start a block: let s:ini1 = 'try\|if' " These are special, because the matching words may not have the same indent: -let s:ini2 = 'for\|while' +let s:ini2 = 'for\|while' " keywords that continue or end a block: let s:tail1 = 'except\|finally' let s:tail1 = s:tail1 . '\|elif\|else' @@ -128,7 +128,7 @@ fun! s:PyMatch(type, mode) range " There are no "tail1" keywords below startline in this block. Go to " the start of the block. let next = (text =~ '^\s*\%(' . s:ini1 . '\)') ? - \ currline : s:StartOfBlock(currline) + \ currline : s:StartOfBlock(currline) endif execute next return s:CleanUp('', a:mode, '$') From 60d607cc714304f0cc5be3d1046d60c48d9dd50d Mon Sep 17 00:00:00 2001 From: Osamu Aoki Date: Thu, 26 Aug 2021 23:08:38 +0900 Subject: [PATCH 2/7] rename and add for consistent set of names names with suffix 'x' are for ones excluding 'else' Signed-off-by: Osamu Aoki --- ftplugin/python_matchit.vim | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ftplugin/python_matchit.vim b/ftplugin/python_matchit.vim index bf50cad..70106cd 100644 --- a/ftplugin/python_matchit.vim +++ b/ftplugin/python_matchit.vim @@ -46,11 +46,15 @@ let s:ini1 = 'try\|if' let s:ini2 = 'for\|while' " keywords that continue or end a block: let s:tail1 = 'except\|finally' +let s:tail1x = s:tail1 . '\|elif' let s:tail1 = s:tail1 . '\|elif\|else' " These go with s:ini2 : -let s:tail2 = 'break\|continue' +let s:tail2x = 'break\|continue' +let s:tail2 = 'break\|continue\|else' " all keywords: +let s:all1x = s:ini1 . '\|' . s:tail1x let s:all1 = s:ini1 . '\|' . s:tail1 +let s:all2x = s:ini2 . '\|' . s:tail2x let s:all2 = s:ini2 . '\|' . s:tail2 fun! s:PyMatch(type, mode) range @@ -96,15 +100,15 @@ fun! s:PyMatch(type, mode) range if a:type == '%' || a:type == 'g%' let text = getline(currline) if strpart(text, 0, col(".")) =~ '\S\s' - \ || text !~ '^\s*\%(' . s:all1 . '\|' . s:all2 . '\)' + \ || text !~ '^\s*\%(' . s:all1 . '\|' . s:all2x . '\)' " cursor not on the first WORD or no keyword so bail out if a:type == '%' normal! % endif return s:CleanUp('', a:mode) endif - " If it matches s:all2, we need to find the "for" or "while". - if text =~ '^\s*\%(' . s:all2 . '\)' + " If it matches s:all2x, we need to find the "for" or "while". + if text =~ '^\s*\%(' . s:all2x . '\)' let topline = currline while getline(topline) !~ '^\s*\%(' . s:ini2 . '\)' let temp = s:StartOfBlock(topline) @@ -136,17 +140,17 @@ fun! s:PyMatch(type, mode) range " If called as %, look down for "break" or "continue" or up for " "for" or "while". - if a:type == '%' && text =~ '^\s*\%(' . s:all2 . '\)' + if a:type == '%' && text =~ '^\s*\%(' . s:all2x . '\)' let next = s:NonComment(+1, currline) while next > 0 && indent(next) > topindent - \ && getline(next) !~ '^\s*\%(' . s:tail2 . '\)' + \ && getline(next) !~ '^\s*\%(' . s:tail2x . '\)' " Skip over nested "for" or "while" blocks: if getline(next) =~ '^\s*\%(' . s:ini2 . '\)' let next = s:EndOfBlock(next) endif let next = s:NonComment(+1, next) endwhile - if indent(next) > topindent && getline(next) =~ '^\s*\%(' . s:tail2 . '\)' + if indent(next) > topindent && getline(next) =~ '^\s*\%(' . s:tail2x . '\)' execute next else " There are no "tail2" keywords below v:startline, so go to topline. execute topline @@ -172,7 +176,7 @@ fun! s:PyMatch(type, mode) range endif " If called as g%, look up for "for" or "while" or down for any. - if a:type == 'g%' && text =~ '^\s*\%(' . s:all2 . '\)' + if a:type == 'g%' && text =~ '^\s*\%(' . s:all2x . '\)' " Start at topline . If we started on a "for" or "while" then topline is " the same as currline, and we want the last "break" or "continue" in the " block. Otherwise, we want the last one before currline. @@ -180,7 +184,7 @@ fun! s:PyMatch(type, mode) range let currline = topline let next = s:NonComment(+1, currline) while next < botline && indent(next) > topindent - if getline(next) =~ '^\s*\%(' . s:tail2 . '\)' + if getline(next) =~ '^\s*\%(' . s:tail2x . '\)' let currline = next elseif getline(next) =~ '^\s*\%(' . s:ini2 . '\)' " Skip over nested "for" or "while" blocks: From 29e7d7346d0a6b75bcf74587766a9763b5383df1 Mon Sep 17 00:00:00 2001 From: Osamu Aoki Date: Thu, 26 Aug 2021 23:50:37 +0900 Subject: [PATCH 3/7] 0.5.1 modification for 'else' Signed-off-by: Osamu Aoki --- ftplugin/python_matchit.vim | 46 +++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/ftplugin/python_matchit.vim b/ftplugin/python_matchit.vim index 70106cd..2618609 100644 --- a/ftplugin/python_matchit.vim +++ b/ftplugin/python_matchit.vim @@ -1,8 +1,9 @@ " Python filetype plugin for matching with % key " Language: Python (ft=python) -" Last Change: Thu 02 Oct 2003 12:12:20 PM EDT -" Maintainer: Benji Fisher, Ph.D. -" Version: 0.5, for Vim 6.1 +" Last Change: Thu 26 Aug 2021 02:46:38 PM UTC +" Maintainer: Osamu Aoki +" Original Author/Maintainer: Benji Fisher, Ph.D. +" Version: 0.5.1, for Vim 8.2 " URL: http://www.vim.org/scripts/script.php?script_id=386 " allow user to prevent loading and prevent duplicate loading @@ -40,11 +41,16 @@ let s:loaded_functions = 1 " line, if they have the same indent. " " Recognize try, except, finally and if, elif, else . +" +" One annoying thing is else is used not only with if/try but also with +" for/while. This 2021 modification address this. +" " keywords that start a block: let s:ini1 = 'try\|if' " These are special, because the matching words may not have the same indent: let s:ini2 = 'for\|while' " keywords that continue or end a block: +let s:tailx = 'else' let s:tail1 = 'except\|finally' let s:tail1x = s:tail1 . '\|elif' let s:tail1 = s:tail1 . '\|elif\|else' @@ -99,6 +105,7 @@ fun! s:PyMatch(type, mode) range " If called as % or g%, decide whether to bail out. if a:type == '%' || a:type == 'g%' let text = getline(currline) + " Non-python case for '%' and 'g%' if strpart(text, 0, col(".")) =~ '\S\s' \ || text !~ '^\s*\%(' . s:all1 . '\|' . s:all2x . '\)' " cursor not on the first WORD or no keyword so bail out @@ -107,6 +114,7 @@ fun! s:PyMatch(type, mode) range endif return s:CleanUp('', a:mode) endif + " Sure "for" or "while" group case for "%' and 'g%' " If it matches s:all2x, we need to find the "for" or "while". if text =~ '^\s*\%(' . s:all2x . '\)' let topline = currline @@ -121,8 +129,9 @@ fun! s:PyMatch(type, mode) range endif endif + " Sure "if" or "try" group case for '%' " If called as %, look down for "elif" or "else" or up for "if". - if a:type == '%' && text =~ '^\s*\%('. s:all1 .'\)' + if a:type == '%' && text =~ '^\s*\%('. s:all1x .'\)' let next = s:NonComment(+1, currline) while next > 0 && indent(next) > startindent let next = s:NonComment(+1, next) @@ -138,6 +147,7 @@ fun! s:PyMatch(type, mode) range return s:CleanUp('', a:mode, '$') endif + " Sure "for" or "while" group case for "%' and 'g%' " If called as %, look down for "break" or "continue" or up for " "for" or "while". if a:type == '%' && text =~ '^\s*\%(' . s:all2x . '\)' @@ -158,8 +168,9 @@ fun! s:PyMatch(type, mode) range return s:CleanUp('', a:mode, '$') endif + " Sure "if" or "try" group case for 'g%' " If called as g%, look up for "if" or "elif" or "else" or down for any. - if a:type == 'g%' && text =~ '^\s*\%('. s:all1 .'\)' + if a:type == 'g%' && text =~ '^\s*\%('. s:all1x .'\)' " If we started at the top of the block, go down to the end of the block. if text =~ '^\s*\(' . s:ini1 . '\)' let next = s:EndOfBlock(currline) @@ -175,6 +186,7 @@ fun! s:PyMatch(type, mode) range return s:CleanUp('', a:mode, '$') endif + " Sure "for" or "while" group case for "%' and 'g%' " If called as g%, look up for "for" or "while" or down for any. if a:type == 'g%' && text =~ '^\s*\%(' . s:all2x . '\)' " Start at topline . If we started on a "for" or "while" then topline is @@ -196,6 +208,30 @@ fun! s:PyMatch(type, mode) range return s:CleanUp('', a:mode, '$') endif + " Sure "else" group case for '%' + " If called as %, look up for "if" "try" "while" "for". + if a:type == '%' && text =~ '^\s*\%('. s:tailx .'\)' + let next = s:NonComment(+1, currline) + " Go to the start of the block. + let next = (text =~ '^\s*\%(' . s:ini1 . '\|' . s:ini2 . '\)') ? + \ currline : s:StartOfBlock(currline) + execute next + return s:CleanUp('', a:mode, '$') + endif + + " Sure "else" group case for 'g%' + " If called as g%, look up for "if" or "elif" or "try" "while" "for". + if a:type == 'g%' && text =~ '^\s*\%('. s:tailx .'\)' + let next = s:NonComment(-1, currline) + while next > 0 && indent(next) > startindent + let next = s:NonComment(-1, next) + endwhile + if indent(next) == startindent && getline(next) =~ '^\s*\%(' . s:all1x . '\|' . s:ini2 '\)' + execute next + endif + return s:CleanUp('', a:mode, '$') + endif + endfun " Return the line number of the next non-comment, or 0 if there is none. From b76377d99733cd0537cfa54c85864818f71fb01a Mon Sep 17 00:00:00 2001 From: Osamu Aoki Date: Fri, 27 Aug 2021 00:09:25 +0900 Subject: [PATCH 4/7] Bug fix for StartOfBlock We need to be able to get StartOfBlock for for/while Reorder for/while Signed-off-by: Osamu Aoki --- ftplugin/python_matchit.vim | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/ftplugin/python_matchit.vim b/ftplugin/python_matchit.vim index 2618609..439ac68 100644 --- a/ftplugin/python_matchit.vim +++ b/ftplugin/python_matchit.vim @@ -147,7 +147,25 @@ fun! s:PyMatch(type, mode) range return s:CleanUp('', a:mode, '$') endif - " Sure "for" or "while" group case for "%' and 'g%' + " Sure "if" or "try" group case for 'g%' + " If called as g%, look up for "if" or "elif" or "else" or down for any. + if a:type == 'g%' && text =~ '^\s*\%('. s:all1x .'\)' + " If we started at the top of the block, go down to the end of the block. + if text =~ '^\s*\(' . s:ini1 . '\)' + let next = s:EndOfBlock(currline) + else + let next = s:NonComment(-1, currline) + endif + while next > 0 && indent(next) > startindent + let next = s:NonComment(-1, next) + endwhile + if indent(next) == startindent && getline(next) =~ '^\s*\%('.s:all1.'\)' + execute next + endif + return s:CleanUp('', a:mode, '$') + endif + + " Sure "for" or "while" group case for "%' " If called as %, look down for "break" or "continue" or up for " "for" or "while". if a:type == '%' && text =~ '^\s*\%(' . s:all2x . '\)' @@ -168,25 +186,7 @@ fun! s:PyMatch(type, mode) range return s:CleanUp('', a:mode, '$') endif - " Sure "if" or "try" group case for 'g%' - " If called as g%, look up for "if" or "elif" or "else" or down for any. - if a:type == 'g%' && text =~ '^\s*\%('. s:all1x .'\)' - " If we started at the top of the block, go down to the end of the block. - if text =~ '^\s*\(' . s:ini1 . '\)' - let next = s:EndOfBlock(currline) - else - let next = s:NonComment(-1, currline) - endif - while next > 0 && indent(next) > startindent - let next = s:NonComment(-1, next) - endwhile - if indent(next) == startindent && getline(next) =~ '^\s*\%('.s:all1.'\)' - execute next - endif - return s:CleanUp('', a:mode, '$') - endif - - " Sure "for" or "while" group case for "%' and 'g%' + " Sure "for" or "while" group case for 'g%' " If called as g%, look up for "for" or "while" or down for any. if a:type == 'g%' && text =~ '^\s*\%(' . s:all2x . '\)' " Start at topline . If we started on a "for" or "while" then topline is @@ -264,7 +264,7 @@ fun! s:StartOfBlock(start) while prevline > 0 if indent(prevline) < startindent || \ tailflag && indent(prevline) == startindent && - \ getline(prevline) =~ '^\s*\(' . s:ini1 . '\)' + \ getline(prevline) =~ '^\s*\(' . s:ini1 . '\|' . s:ini2 . '\)' " Found the start of block! return prevline endif From 07b7ef17db168b9770416a5c84fd8921ac2f9e31 Mon Sep 17 00:00:00 2001 From: Osamu Aoki Date: Fri, 27 Aug 2021 00:20:42 +0900 Subject: [PATCH 5/7] Bug fix for while/for to else Signed-off-by: Osamu Aoki --- ftplugin/python_matchit.vim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ftplugin/python_matchit.vim b/ftplugin/python_matchit.vim index 439ac68..ebd6c1f 100644 --- a/ftplugin/python_matchit.vim +++ b/ftplugin/python_matchit.vim @@ -166,7 +166,7 @@ fun! s:PyMatch(type, mode) range endif " Sure "for" or "while" group case for "%' - " If called as %, look down for "break" or "continue" or up for + " If called as %, look down for "break" or "continue" or "else" or up for " "for" or "while". if a:type == '%' && text =~ '^\s*\%(' . s:all2x . '\)' let next = s:NonComment(+1, currline) @@ -180,6 +180,8 @@ fun! s:PyMatch(type, mode) range endwhile if indent(next) > topindent && getline(next) =~ '^\s*\%(' . s:tail2x . '\)' execute next + elseif indent(next) == topindent && getline(next) =~ '^\s*\%(' . s:tailx . '\)' + execute next else " There are no "tail2" keywords below v:startline, so go to topline. execute topline endif From 3a5e332fa995e826a77e9f4a58647273f99c74c3 Mon Sep 17 00:00:00 2001 From: Osamu Aoki Date: Fri, 27 Aug 2021 00:37:37 +0900 Subject: [PATCH 6/7] Revised README.md recording history Signed-off-by: Osamu Aoki --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index daf995a..ea26a1e 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,11 @@ vim-python-matchit This is a clone of the [Python matchit](http://www.vim.org/scripts/script.php?script_id=386) plugin from vim.org, based on script version `0.5`. + +This is cloned from https://github.com/voithos/vim-python-matchit as +https://github.com/osamuaoki/vim-python-matchit + +'for'/'while'-block can take 'else' in Python. This mod 0.5.1 addresses this situation. + +Osamu + From f025f6a20cd942eab95aea3e902ada089c2a297b Mon Sep 17 00:00:00 2001 From: Osamu Aoki Date: Sat, 28 Aug 2021 04:59:39 +0900 Subject: [PATCH 7/7] Update README.md to be clear Signed-off-by: Osamu Aoki --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ea26a1e..1829915 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,50 @@ vim-python-matchit ================== -This is a clone of the [Python matchit](http://www.vim.org/scripts/script.php?script_id=386) -plugin from vim.org, based on script version `0.5`. +This is a modified clone of the +[Python matchit](http://www.vim.org/scripts/script.php?script_id=386) plugin +from vim.org, based on script version `0.5`. -This is cloned from https://github.com/voithos/vim-python-matchit as -https://github.com/osamuaoki/vim-python-matchit +This is originally cloned from https://github.com/voithos/vim-python-matchit as +https://github.com/osamuaoki/vim-python-matchit and modified. -'for'/'while'-block can take 'else' in Python. This mod 0.5.1 addresses this situation. +In python, 'for'/'while'-block can take 'else' which was not supported in the +original 0.5. This mod, 0.5.1, addresses this situation. -Osamu +## Originally created by +Benji Fisher + +## Modified by +Osamu Aoki + +## script type +ftplugin + +## description +This script redefines the % motion so that (in addition to its usual behavior) +it cycles through if/elif/else, try/except/catch/else, for/continue/break/else, +and while/continue/break/else structures. The script also +defines g% to cycle in the opposite direction. Two other motions, [% and ]%, +go to the start and end of the current block, respectively. + +All of these motions should work in Normal, Visual, and Operator-pending +modes. For example, d]% should delete (characterwise) until the end of the +current block; v]%d should do the same, going through Visual mode so that +you can see what is being deleted; and V]%d makes it linewise. +## install details +Copy the file to your ftplugin/ directory. If, for some reason, you want to +change the name of the file, copy it to ftplugin/python/ or :source it from +ftplugin/python.vim . + +If you use the native Vim package management and use `git` and `git submodule` +to manage `~/.vim/` contents like me, install this under `~/.vim/pack/*/opt/`. +(`*` can be any name) + +Then in your vimrc (`~/.vim/vimrc`), add + +``` +packadd! vim-python-matchit +``` + +Osamu