Reputation: 2333
From the mighty PEP 8:
[P]lease limit all lines to a maximum of 79 characters. For flowing long blocks of text (docstrings or comments), limiting the length to 72 characters is recommended.
When editing Python code in Vim, I set my textwidth
to 79, and Vim automatically wraps long lines of Python code for me when I hit the character limit. But in comments and docstrings, I need to wrap text at 72 characters instead.
Is there any way to make Vim automatically set textwidth
to 72 when I'm in a comment or docstring, and set it back when I'm done?
Upvotes: 32
Views: 3895
Reputation: 32926
I've first tried to work with Eric Naeseth's solution and a few patches for Python syntax files, but unfortunately it was really too slow to be of any use on my machine and on my Python code.
As such, I've ported an equivalent to vim9script language.
If I keep the script minimum, it would look like:
vim9script
# Define the default value for the various options
# Rely on a command from my lh-lib-vim library plugin
LetIfUndef g:style.textwidth.comment = 72
LetIfUndef g:style.textwidth.docstring = 72
LetIfUndef g:style.textwidth.default = 79 # Should fetch &tw elsewhere
def SynID(l: number, c: number): string
return synIDattr(synID(l, c, 0), "name")
enddef
def Compute_tw(ft: string): number
var lin = line('.')
final syn = SynID(lin, col('.'))
if syn =~? 'comment'
return lh#ft#option#get('style.textwidth.comment', ft, &tw)
elseif ft == 'python' && syn =~? '\vstring|^$' # Special case for docstrings
while lin >= 1
final line = getline(lin)
if line =~ '\v^\s*$' | lin -= 1 | continue | endif
if SynID(lin, col([lin, "$"]) - 1) !~? '\vString|pythonTripleQuotes'
break
endif
if match(line, "\\('''\\|\"\"\"\\)") > -1
# Assume that any longstring is a docstring
return lh#ft#option#get('style.textwidth.docstring', ft, &tw)
endif
lin -= 1
endwhile
endif
return lh#ft#option#get('style.textwidth.default', ft, &tw)
enddef
def Update_tw(ft: string)
call setbufvar('%', '&tw', Compute_tw(ft))
enddef
# autocommand registration
augroup WatchSyntax
au!
autocmd! CursorMoved,CursorMovedI,BufEnter <buffer> call Update_tw(&ft)
augroup END
PS: there are a few bits that are still messy (default value for &tw, support of other languages...)
Upvotes: 0
Reputation: 261
The accepted answer is great! It does not, however, support the habit I have for formatting/editing comments: I make my edits and then use the gqj command, which is essentially, "reformat the current line combined with the next". Then I hit '.' to repeat that for each line (the command itself advances the cursor to the next line). I don't know the vim scripting language very well, so someone may be able to add support for this to the accepted answer. In the meantime, what I have done is map a function key (F6) to change the textwidth to 72, format the line and then change the textwidth back to 79.
nmap <F6> :set textwidth=72<CR>gqj:set textwidth=79<CR>
Now, when I'm in a docstring, I just make the edit, (ESC) and then hit F6 repeatedly until all the lines are properly formatted.
I added my map command and the accepted answer script to my .vim/after/ftplugin/python.vim.
Upvotes: 6
Reputation: 2333
So, I've never done any Vim scripting before, but based on this question about doing something similar in C and this tip for checking if you're currently in a comment, I've hacked together a solution.
By default, this uses the PEP8-suggested widths of 79 characters for normal lines and 72 characters for comments, but you can override them by let
ting g:python_normal_text_width
or g:python_comment_text_width
variables, respectively. (Personally, I wrap normal lines at 78 characters.)
Drop this baby in your .vimrc and you should be good to go. I may package this up as a plugin later.
function! GetPythonTextWidth()
if !exists('g:python_normal_text_width')
let normal_text_width = 79
else
let normal_text_width = g:python_normal_text_width
endif
if !exists('g:python_comment_text_width')
let comment_text_width = 72
else
let comment_text_width = g:python_comment_text_width
endif
let cur_syntax = synIDattr(synIDtrans(synID(line("."), col("."), 0)), "name")
if cur_syntax == "Comment"
return comment_text_width
elseif cur_syntax == "String"
" Check to see if we're in a docstring
let lnum = line(".")
while lnum >= 1 && (synIDattr(synIDtrans(synID(lnum, col([lnum, "$"]) - 1, 0)), "name") == "String" || match(getline(lnum), '\v^\s*$') > -1)
if match(getline(lnum), "\\('''\\|\"\"\"\\)") > -1
" Assume that any longstring is a docstring
return comment_text_width
endif
let lnum -= 1
endwhile
endif
return normal_text_width
endfunction
augroup pep8
au!
autocmd CursorMoved,CursorMovedI * :if &ft == 'python' | :exe 'setlocal textwidth='.GetPythonTextWidth() | :endif
augroup END
Upvotes: 18