From 41ecf7764d0b0022484543a3f61629edf20eabf9 Mon Sep 17 00:00:00 2001 From: David Briscoe Date: Wed, 25 Feb 2015 13:18:57 -0800 Subject: [PATCH 1/6] Use a single method to set indentation Add s:SetIndent() so we can set our desired indentation consistently. It always sets expandtab and sets widths only if valid (positive nonzero). I'm following the advice laid out on the 'tabstop' help about when softtabstop is set and ensuring some settings have the same value. That documentation recommends using softtabstop with noexpandtab and not with expandtab. (Whereas detectindent used to do the opposite.) TODO: I'm no longer convinced that my reading is correct in this case since softtabstop isn't useful when tabstop and shiftwidth are the same. Maybe I should always set it? --- plugin/detectindent.vim | 55 ++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/plugin/detectindent.vim b/plugin/detectindent.vim index d903948..22dd976 100644 --- a/plugin/detectindent.vim +++ b/plugin/detectindent.vim @@ -56,6 +56,33 @@ fun! s:GetValue(option) endif endfun +fun! s:SetIndent(expandtab, desired_tabstop) + let &l:expandtab = a:expandtab + + " Only modify tabs if we have a valid value. + if a:desired_tabstop > 0 + " See advice on `:help 'tabstop'` for logic of which values are set for + " what values of expandtab. + + let &l:tabstop = a:desired_tabstop + if v:version >= 704 + " Zero automatically keeps in sync with tabstop in Vim 7.4+. + setl shiftwidth=0 + else + let &l:shiftwidth = a:desired_tabstop + endif + + if !a:expandtab + if v:version >= 704 + " Negative value automatically keeps in sync with shiftwidth in Vim 7.4+. + setl softtabstop=-1 + else + let &l:softtabstop = a:desired_tabstop + endif + endif + endif +endfun + fun! DetectIndent() let l:has_leading_tabs = 0 let l:has_leading_spaces = 0 @@ -138,24 +165,21 @@ fun! DetectIndent() if l:has_leading_tabs && ! l:has_leading_spaces " tabs only, no spaces let l:verbose_msg = "Detected tabs only and no spaces" - setl noexpandtab if s:GetValue("detectindent_preferred_indent") - let &l:shiftwidth = g:detectindent_preferred_indent - let &l:tabstop = g:detectindent_preferred_indent + call s:SetIndent(0, g:detectindent_preferred_indent) + else + setl noexpandtab endif elseif l:has_leading_spaces && ! l:has_leading_tabs " spaces only, no tabs let l:verbose_msg = "Detected spaces only and no tabs" - setl expandtab - let &l:shiftwidth = l:shortest_leading_spaces_run - let &l:softtabstop = l:shortest_leading_spaces_run + call s:SetIndent(1, l:shortest_leading_spaces_run) elseif l:has_leading_spaces && l:has_leading_tabs && ! s:GetValue("detectindent_preferred_when_mixed") " spaces and tabs let l:verbose_msg = "Detected spaces and tabs" - setl noexpandtab - let &l:shiftwidth = l:shortest_leading_spaces_run + call s:SetIndent(0, l:shortest_leading_spaces_run) " mmmm, time to guess how big tabs are if l:longest_leading_spaces_run <= 2 @@ -169,20 +193,7 @@ fun! DetectIndent() else " no spaces, no tabs let l:verbose_msg = s:GetValue("detectindent_preferred_when_mixed") ? "preferred_when_mixed is active" : "Detected no spaces and no tabs" - if s:GetValue("detectindent_preferred_indent") && - \ (s:GetValue("detectindent_preferred_expandtab")) - setl expandtab - let &l:shiftwidth = g:detectindent_preferred_indent - let &l:softtabstop = g:detectindent_preferred_indent - elseif s:GetValue("detectindent_preferred_indent") - setl noexpandtab - let &l:shiftwidth = g:detectindent_preferred_indent - let &l:tabstop = g:detectindent_preferred_indent - elseif s:GetValue("detectindent_preferred_expandtab") - setl expandtab - else - setl noexpandtab - endif + call s:SetIndent(s:GetValue("detectindent_preferred_expandtab"), s:GetValue("detectindent_preferred_indent")) endif From 294ae712971352b7154fa87da13024830bf898b5 Mon Sep 17 00:00:00 2001 From: David Briscoe Date: Wed, 25 Feb 2015 13:32:03 -0800 Subject: [PATCH 2/6] Do not use shiftwidth=0 shiftwidth=0 keeps it in sync with tabstop, but that breaks many indentation plugins that read 'sw' instead of calling the new shiftwidth(). See https://github.com/tpope/vim-sleuth/issues/25 Or try indenting this vimscript file on Vim 7.4. --- plugin/detectindent.vim | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plugin/detectindent.vim b/plugin/detectindent.vim index 22dd976..1b2604e 100644 --- a/plugin/detectindent.vim +++ b/plugin/detectindent.vim @@ -65,12 +65,11 @@ fun! s:SetIndent(expandtab, desired_tabstop) " what values of expandtab. let &l:tabstop = a:desired_tabstop - if v:version >= 704 - " Zero automatically keeps in sync with tabstop in Vim 7.4+. - setl shiftwidth=0 - else - let &l:shiftwidth = a:desired_tabstop - endif + " NOTE: shiftwidth=0 keeps it in sync with tabstop, but that breaks + " many indentation plugins that read 'sw' instead of calling the new + " shiftwidth(). See + " https://github.com/tpope/vim-sleuth/issues/25 + let &l:shiftwidth = a:desired_tabstop if !a:expandtab if v:version >= 704 From 33f4522c80c1a155d0e4f4f8fc15c7145c6dcd5a Mon Sep 17 00:00:00 2001 From: David Briscoe Date: Wed, 25 Feb 2015 13:41:28 -0800 Subject: [PATCH 3/6] Better conform to tabstop help guidelines Always set softtabstop. This is more consistent with previous behavior and from reading the softtabstop help seems to make more sense that we set all of these values to the same thing. In the case where we find both tabs and spaces, now we set expandtab as described in #2: 2. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use 'expandtab'. This way you will always insert spaces. The formatting will never be messed up when 'tabstop' is changed. Alternatively, we could follow #1 and set tabstop=8 (instead of guessing) and use noexpandtab: 1. Always keep 'tabstop' at 8, set 'softtabstop' and 'shiftwidth' to 4 (or 3 or whatever you prefer) and use 'noexpandtab'. Then Vim will use a mix of tabs and spaces, but typing and will behave like a tab appears every 4 (or 3) characters. That's what vim-sleuth does, so why copy their recipe? Guessing is probably more useful to users. --- plugin/detectindent.vim | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/plugin/detectindent.vim b/plugin/detectindent.vim index 1b2604e..a23fe9e 100644 --- a/plugin/detectindent.vim +++ b/plugin/detectindent.vim @@ -61,8 +61,8 @@ fun! s:SetIndent(expandtab, desired_tabstop) " Only modify tabs if we have a valid value. if a:desired_tabstop > 0 - " See advice on `:help 'tabstop'` for logic of which values are set for - " what values of expandtab. + " See `:help 'tabstop'`. We generally adhere to #1 or #4, but when + " guessing what to do for mixed tabs and spaces we use #2. let &l:tabstop = a:desired_tabstop " NOTE: shiftwidth=0 keeps it in sync with tabstop, but that breaks @@ -71,13 +71,11 @@ fun! s:SetIndent(expandtab, desired_tabstop) " https://github.com/tpope/vim-sleuth/issues/25 let &l:shiftwidth = a:desired_tabstop - if !a:expandtab - if v:version >= 704 - " Negative value automatically keeps in sync with shiftwidth in Vim 7.4+. - setl softtabstop=-1 - else - let &l:softtabstop = a:desired_tabstop - endif + if v:version >= 704 + " Negative value automatically keeps in sync with shiftwidth in Vim 7.4+. + setl softtabstop=-1 + else + let &l:softtabstop = a:desired_tabstop endif endif endfun @@ -178,7 +176,7 @@ fun! DetectIndent() elseif l:has_leading_spaces && l:has_leading_tabs && ! s:GetValue("detectindent_preferred_when_mixed") " spaces and tabs let l:verbose_msg = "Detected spaces and tabs" - call s:SetIndent(0, l:shortest_leading_spaces_run) + call s:SetIndent(1, l:shortest_leading_spaces_run) " mmmm, time to guess how big tabs are if l:longest_leading_spaces_run <= 2 From e6442a0222a2283c95d661bb86f40bf569835a2d Mon Sep 17 00:00:00 2001 From: David Briscoe Date: Wed, 4 Oct 2017 18:25:45 -0700 Subject: [PATCH 4/6] Ensure tab settings match on tabs-only file Default detectindent_preferred_indent behavior (0) is to retain current tabstop. But on a file containing only tabs, and user's default is to use shiftwidth=4, we end up with softtabstop=4, shiftwidth=4, tabstop=8, noexpandtabs Which means we insert tabs, but indent with >> inserts only half a tab (inserting 4 spaces). This madness occurs in python files containing tabs with g:python_recommended_style undefined or 1. --- plugin/detectindent.vim | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugin/detectindent.vim b/plugin/detectindent.vim index a23fe9e..9f43e45 100644 --- a/plugin/detectindent.vim +++ b/plugin/detectindent.vim @@ -162,11 +162,13 @@ fun! DetectIndent() if l:has_leading_tabs && ! l:has_leading_spaces " tabs only, no spaces let l:verbose_msg = "Detected tabs only and no spaces" - if s:GetValue("detectindent_preferred_indent") - call s:SetIndent(0, g:detectindent_preferred_indent) - else - setl noexpandtab + let indent = s:GetValue("detectindent_preferred_indent") + if indent == 0 + " Default behavior is to retain current tabstop. Still need to set + " it to ensure softtabstop, shiftwidth, tabstop are in sync. + let indent = &l:tabstop endif + call s:SetIndent(0, indent) elseif l:has_leading_spaces && ! l:has_leading_tabs " spaces only, no tabs From 3d330a4503b7dcfdf14dce43a81b726bc36428c7 Mon Sep 17 00:00:00 2001 From: David Briscoe Date: Tue, 29 Aug 2017 16:18:00 -0700 Subject: [PATCH 5/6] Add option to use syntax to detect non C comments Previously when a file doesn't support C-style comments, we didn't detect any comments. Now we try hard to detect comments in all languages with syntax support. Using syntax means we don't need new code for new languages. However, syntax checking is very slow. Vim may take seconds to load large vimscript files (HasCommentSyntax was called 3000 times and it took like 8 seconds). Navigating Gblame was unacceptably slow since each revision was a new buffer that had to be re-detected. So syntax checking is a buffer-local option that can be turned on when you don't have a better way for checking comments (and they're always confusing DetectIndent). --- doc/detectindent.txt | 6 ++++++ plugin/detectindent.vim | 21 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/doc/detectindent.txt b/doc/detectindent.txt index 524b8b6..dcb9503 100644 --- a/doc/detectindent.txt +++ b/doc/detectindent.txt @@ -37,6 +37,12 @@ Author: Ciaran McCreesh To set limit for number of lines that will be analysed set: > :let g:detectindent_max_lines_to_analyse = 1024 +< in your |vimrc| file. Low values will improve performance at the cost + of accuracy. + + To ignore indent in comments by checking vim's syntax highlighting to + improve accuracy at a significant speed penalty: > + :let g:detectindent_check_comment_syntax = 1 < in your |vimrc| file. To override |detectindent_preferred_expandtab| for specific filetypes diff --git a/plugin/detectindent.vim b/plugin/detectindent.vim index 9f43e45..18f43bc 100644 --- a/plugin/detectindent.vim +++ b/plugin/detectindent.vim @@ -20,6 +20,9 @@ " " To use preferred values instead of guessing: " :let g:detectindent_preferred_when_mixed = 1 " +" " To reduce the number of lines inspected: +" :let g:detectindent_max_lines_to_analyse = 100 +" " Requirements: Untested on Vim versions below 6.2 if exists("loaded_detectindent") @@ -31,6 +34,10 @@ if !exists('g:detectindent_verbosity') let g:detectindent_verbosity = 1 endif +" Ignore comment lines via syntax (slow but accurate): +let g:detectindent_check_comment_syntax = get(g:, 'detectindent_check_comment_syntax', 0) + + fun! HasCStyleComments() return index(["c", "cpp", "java", "javascript", "php", "vala"], &ft) != -1 endfun @@ -44,6 +51,16 @@ fun! IsCommentEnd(line) return HasCStyleComments() && a:line =~ '\*/' endfun +fun! s:HasCommentSyntax(line_number, line_text) " {{{1 + " Some languages (lua) don't define space before a comment as part of the + " comment so look at the first nonblank character. + let nonblank_col = substitute(a:line_text, "^\s*\zs.*", "", "") + let transparent = 1 + let id = synID(a:line_number, len(nonblank_col), transparent) + let syntax = synIDattr(id, 'name') + return syntax =~? 'string\|comment' +endfun + fun! IsCommentLine(line) return HasCStyleComments() && a:line =~ '^\s\+//' endfun @@ -96,6 +113,8 @@ fun! DetectIndent() " remember initial values for comparison let b:detectindent_cursettings = {'expandtab': &et, 'shiftwidth': &sw, 'tabstop': &ts, 'softtabstop': &sts} endif + + let can_check_syntax = s:GetValue('detectindent_check_comment_syntax') let l:idx_end = line("$") let l:idx = 1 @@ -114,7 +133,7 @@ fun! DetectIndent() endif " Skip comment lines since they are not dependable. - if IsCommentLine(l:line) + if IsCommentLine(l:line) || (can_check_syntax && s:HasCommentSyntax(l:idx, l:line)) let l:idx = l:idx + 1 continue endif From 7af9a04a798d44dd9ce461865e73855fe4b0466a Mon Sep 17 00:00:00 2001 From: David Briscoe Date: Thu, 2 Jan 2020 13:18:30 -0800 Subject: [PATCH 6/6] Use 'comments' to determine comment markers Replace c-style comments special case with 'comments' vim variable and blacklist. 'comments' is very useful since it covers so many languages cases. If something doesn't work correctly, we can add it to the blacklist. The "&comments aren't reliable" comment comes from the initial import of detectindent, so I can't tell what problem was being worked around. --- doc/detectindent.txt | 13 +++++ plugin/detectindent.vim | 109 +++++++++++++++++++++++++++++++++------- 2 files changed, 104 insertions(+), 18 deletions(-) diff --git a/doc/detectindent.txt b/doc/detectindent.txt index dcb9503..a5dbc9f 100644 --- a/doc/detectindent.txt +++ b/doc/detectindent.txt @@ -45,6 +45,14 @@ Author: Ciaran McCreesh :let g:detectindent_check_comment_syntax = 1 < in your |vimrc| file. + DetectIndent uses vim's 'comments' variable to find and ignore + comments. To ignore this variable when detecting comments for a + filetype, you can add the filetype to this variable (example to ignore + in python files): > + :let g:detectindent_comments_blacklist = ['python'] +< You'll probably want to enable |detectindent_check_comment_syntax| so + indentation in comments is ignored. + To override |detectindent_preferred_expandtab| for specific filetypes (example: use 4-character tabstops with tabs for python) set: > :let b:detectindent_preferred_expandtab = 0 @@ -56,6 +64,11 @@ Author: Ciaran McCreesh ============================================================================== 3. DetectIndent ChangeLog *detectindent-changelog* + v1.3 (2020-01-02) + * Add 'comments' support. + * Turn off syntax detection for comments by default. + v1.2 (2017-08-29) + * Add syntax detection for comments. v1.1 (20150225) * Add preferred_when_mixed. * Add buffer-local options. diff --git a/plugin/detectindent.vim b/plugin/detectindent.vim index 18f43bc..6bfec9d 100644 --- a/plugin/detectindent.vim +++ b/plugin/detectindent.vim @@ -37,19 +37,92 @@ endif " Ignore comment lines via syntax (slow but accurate): let g:detectindent_check_comment_syntax = get(g:, 'detectindent_check_comment_syntax', 0) +" Ignore 'comments' when detecting comment blocks. +let g:detectindent_comments_blacklist = get(g:, 'detectindent_comments_blacklist', []) + + + +let s:comment_marker_none = 0 +let s:comment_marker_start = 1 +let s:comment_marker_end = 2 +let s:comment_marker_line = 3 +function! s:BuildEmptyMarkerDict() + let dict = { 'has_block': 0, 'has_line': 0 } + function! dict.get_matching_marker(line) dict abort + if self.has_line && stridx(a:line, self.line) > -1 + return s:comment_marker_line + elseif self.has_block + let has_start = stridx(a:line, self.start) > -1 + let has_end = stridx(a:line, self.end) > -1 + if has_start && has_end + return s:comment_marker_line + elseif has_start + return s:comment_marker_start + elseif has_end + return s:comment_marker_end + endif + end + return s:comment_marker_none + endf + + function! dict.IsCommentStart(line) dict abort + return self.get_matching_marker(a:line) == s:comment_marker_start + endf + function! dict.IsCommentEnd(line) dict abort + return self.get_matching_marker(a:line) == s:comment_marker_end + endf + function! dict.IsCommentLine(line) dict abort + return self.get_matching_marker(a:line) == s:comment_marker_line + endf + return dict +endf + +function! s:GetCommentMarkers() + if !exists("b:detectindent_comment_markers") + let b:detectindent_comment_markers = s:BuildEmptyMarkerDict() + let is_blacklisted = index(g:detectindent_comments_blacklist, &filetype) >= 0 + if !is_blacklisted + " &commentstring is usually single-line comments, so we need to look + " at &comments which looks like this: + " s:--[[,m: ,e:]],:-- + let dict = {} + for part in split(&comments, ',') + let flag_to_str = split(part, ':') + let num_elements = len(flag_to_str) + if num_elements == 2 + " Two-part are sometimes beginning and end. + " ignore parts[1] -- the number of characters to indent + let parts = split(flag_to_str[0], '\zs') + let dict[parts[0]] = flag_to_str[1] + elseif num_elements == 1 + " One part are always single-line comments. + let dict['line'] = flag_to_str[0] + endif + endfor + + let comment_start = get(dict, 's', '') + if len(comment_start) > 0 + " Start and end should always exist together. + let b:detectindent_comment_markers.start = comment_start + let b:detectindent_comment_markers.end = dict.e + let b:detectindent_comment_markers.has_block = 1 + endif -fun! HasCStyleComments() - return index(["c", "cpp", "java", "javascript", "php", "vala"], &ft) != -1 -endfun - -fun! IsCommentStart(line) - " &comments aren't reliable - return HasCStyleComments() && a:line =~ '/\*' -endfun + let line = get(dict, 'line', '') + if len(line) > 0 + let b:detectindent_comment_markers.line = line + let b:detectindent_comment_markers.has_line = 1 + endif + endif + endif + return b:detectindent_comment_markers +endf -fun! IsCommentEnd(line) - return HasCStyleComments() && a:line =~ '\*/' -endfun +" For easy testing of problematic lines. +"~ function! Debug_GetCommentMarkers(line) +"~ let markers = s:GetCommentMarkers() +"~ return markers.get_matching_marker(a:line) +"~ endf fun! s:HasCommentSyntax(line_number, line_text) " {{{1 " Some languages (lua) don't define space before a comment as part of the @@ -61,10 +134,6 @@ fun! s:HasCommentSyntax(line_number, line_text) " {{{1 return syntax =~? 'string\|comment' endfun -fun! IsCommentLine(line) - return HasCStyleComments() && a:line =~ '^\s\+//' -endfun - fun! s:GetValue(option) if exists('b:'. a:option) return get(b:, a:option) @@ -115,7 +184,11 @@ fun! DetectIndent() endif let can_check_syntax = s:GetValue('detectindent_check_comment_syntax') + let markers = s:GetCommentMarkers() + " There's lots of junk at the start of files that would be nice to skip, + " but we need to start from the top to ensure we know if we're in a + " comment block. let l:idx_end = line("$") let l:idx = 1 while l:idx <= l:idx_end @@ -123,8 +196,8 @@ fun! DetectIndent() " try to skip over comment blocks, they can give really screwy indent " settings in c/c++ files especially - if IsCommentStart(l:line) - while l:idx <= l:idx_end && ! IsCommentEnd(l:line) + if markers.IsCommentStart(l:line) + while l:idx <= l:idx_end && markers.IsCommentEnd(l:line) let l:idx = l:idx + 1 let l:line = getline(l:idx) endwhile @@ -133,7 +206,7 @@ fun! DetectIndent() endif " Skip comment lines since they are not dependable. - if IsCommentLine(l:line) || (can_check_syntax && s:HasCommentSyntax(l:idx, l:line)) + if markers.IsCommentLine(l:line) || (can_check_syntax && s:HasCommentSyntax(l:idx, l:line)) let l:idx = l:idx + 1 continue endif