From fa204e07a05e00305833629933e1bf7ca304699f Mon Sep 17 00:00:00 2001 From: "Kenneth Benzie (Benie)" Date: Wed, 24 Mar 2021 23:37:19 +0000 Subject: [PATCH] Move formatexpr config in-tree, out of plugin Introduces the `format` Python module which provides `clang_format()` and `yapf()` functions which efficiently (compared to vimscript) invoke `clang-format` or `yapf` respectively then apply the minimal number of changes using `difflib.SequenceMatcher`. Additionally, in order to invoke these Python functions add |autoload| functions `format#clang_format()` and `format#yapf()` which can be directly used by the 'formatexpr' setting. Finally, add |ftplugin| files which set 'formatexpr' when the |autoload| functions are available. --- autoload/format.vim | 13 +++ ftplugin/c.vim | 3 + ftplugin/cpp.vim | 3 + ftplugin/java.vim | 3 + ftplugin/javascript.vim | 3 + ftplugin/objc.vim | 3 + ftplugin/objcpp.vim | 3 + ftplugin/proto.vim | 3 + ftplugin/python.vim | 3 + plugin/format.vim | 8 ++ pythonx/__pycache__/format.cpython-39.pyc | Bin 0 -> 3001 bytes pythonx/format.py | 117 ++++++++++++++++++++++ vimrc | 3 - 13 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 autoload/format.vim create mode 100644 ftplugin/c.vim create mode 100644 ftplugin/cpp.vim create mode 100644 ftplugin/java.vim create mode 100644 ftplugin/javascript.vim create mode 100644 ftplugin/objc.vim create mode 100644 ftplugin/objcpp.vim create mode 100644 ftplugin/proto.vim create mode 100644 ftplugin/python.vim create mode 100644 plugin/format.vim create mode 100644 pythonx/__pycache__/format.cpython-39.pyc create mode 100644 pythonx/format.py diff --git a/autoload/format.vim b/autoload/format.vim new file mode 100644 index 0000000..6e4d72f --- /dev/null +++ b/autoload/format.vim @@ -0,0 +1,13 @@ +if !has('pythonx') + finish +endif + +pythonx import format + +function! format#clang_format() abort + pythonx format.clang_format() +endfunction + +function! format#yapf() abort + pythonx format.yapf() +endfunction diff --git a/ftplugin/c.vim b/ftplugin/c.vim new file mode 100644 index 0000000..b54898c --- /dev/null +++ b/ftplugin/c.vim @@ -0,0 +1,3 @@ +if exists('*format#clang_format') + set formatexpr=format#clang_format() +endif diff --git a/ftplugin/cpp.vim b/ftplugin/cpp.vim new file mode 100644 index 0000000..b54898c --- /dev/null +++ b/ftplugin/cpp.vim @@ -0,0 +1,3 @@ +if exists('*format#clang_format') + set formatexpr=format#clang_format() +endif diff --git a/ftplugin/java.vim b/ftplugin/java.vim new file mode 100644 index 0000000..b54898c --- /dev/null +++ b/ftplugin/java.vim @@ -0,0 +1,3 @@ +if exists('*format#clang_format') + set formatexpr=format#clang_format() +endif diff --git a/ftplugin/javascript.vim b/ftplugin/javascript.vim new file mode 100644 index 0000000..b54898c --- /dev/null +++ b/ftplugin/javascript.vim @@ -0,0 +1,3 @@ +if exists('*format#clang_format') + set formatexpr=format#clang_format() +endif diff --git a/ftplugin/objc.vim b/ftplugin/objc.vim new file mode 100644 index 0000000..b54898c --- /dev/null +++ b/ftplugin/objc.vim @@ -0,0 +1,3 @@ +if exists('*format#clang_format') + set formatexpr=format#clang_format() +endif diff --git a/ftplugin/objcpp.vim b/ftplugin/objcpp.vim new file mode 100644 index 0000000..b54898c --- /dev/null +++ b/ftplugin/objcpp.vim @@ -0,0 +1,3 @@ +if exists('*format#clang_format') + set formatexpr=format#clang_format() +endif diff --git a/ftplugin/proto.vim b/ftplugin/proto.vim new file mode 100644 index 0000000..b54898c --- /dev/null +++ b/ftplugin/proto.vim @@ -0,0 +1,3 @@ +if exists('*format#clang_format') + set formatexpr=format#clang_format() +endif diff --git a/ftplugin/python.vim b/ftplugin/python.vim new file mode 100644 index 0000000..c0ef5a8 --- /dev/null +++ b/ftplugin/python.vim @@ -0,0 +1,3 @@ +if exists('*format#yapf') + set formatexpr=format#yapf() +endif diff --git a/plugin/format.vim b/plugin/format.vim new file mode 100644 index 0000000..e5b073c --- /dev/null +++ b/plugin/format.vim @@ -0,0 +1,8 @@ +if !has('pythonx') + finish +endif + +let g:clang_format_path = get(g:, 'clang_format_path', 'clang-format') +let g:clang_format_style = get(g:, 'clang_format_style', 'google') +let g:yapf_path = get(g:, 'yapf_path', 'yapf') +let g:yapf_style = get(g:, 'yapf_style', 'pep8') diff --git a/pythonx/__pycache__/format.cpython-39.pyc b/pythonx/__pycache__/format.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6b53dd68955cfe916d5b009316a111f21578def GIT binary patch literal 3001 zcmaJ@TW=f36`t8Um%7`Iqc#mTL7j>*NI4FW7J^a4u;oSwN(+_^0o$~j6=z5-z1(GH zhmyrCU+Uzo`2$T0kdN(8Y5&2z_Q~jLo{S>NIkTiJ8Er|-{dVS@@B3!sBN_F8(ZNd9MPuVBdA}2CSn5cWPW4 zH}A!1dKm)%5qZpm&PWKJ$pDZdgZ!w^JF*q7YqUg_qoZLgkH?#7lJVO|XOeHd+j_SV z+zdK-s;e*1`2tbXe(9X-q}gcj(mmPfvfHalR8I=i|Xv zahA#WS%uM}(HgtGT;{VS>P>KA#Y>@<%Vtn6{Ak*HP$AEt4KM}qf~JS$+6d@zVJ)~Knaiw;pvb$3-O zWGp1!KXiGs^d&6#3WOqm$W zngS0>V`fZDy~+lTSilhnc;S|2&z{)QE^WZRQ#!>TmxP1!nZ%7l=K0H;C+AY8{tyo1wU3(O7Z`v_8T6*346gk*y*Q7G*y z6ar^-kUfhZx*vDZA@~ba7@bgb=+r!pRVQ<}w}vv``1luA1TIXxDifsX3Vg6%OG?;P3gCPh9XV@qtOR^yEE_R!!!ZK%z})c1{yrC%xLEs-wF1rCB^7yDDn<3il7^V zawGhVH>(^vT}+-LQUoaGakWCK=05mA#)2d8VxWz5QKP z$H0uTqyrxwAc}dCRYXFRi))Z6hl3)6Qt3D2=emb+?14+8LmPHQnkEjpbH53cFL4vI z*LZ|f{1j609%N+60uFg}4MfaAL4&{XAIG-n#t*(>q4a^&Y5HDb7Vhal(#(1U6?jVF zBS9K{vkyWwGl6GzNvF;X9@_il_pQ%>GsugH4Uz@yduoceOH$gUgMu|*XO`C2#vSs- z+YI?&j!z#|kHI;FSTKJU;FY@c6ju+-|0ae8+c_-|qYG5BpjvwokL}Rr_|XMo<+7yP zO_QT=^#Hmc68yKZ>>LB+wA{Ulk|+2He1vfp*5NUW@*zg309g#cJE05TmAFcoGVX;& z@&TmVq0xu3fBW?ap-!fd3Ve`=vq66Xaj4XDNubxoo0#3g>_g1%V21Xn+U~OsA4+&R tE4bimrgia^0$(^yBLfdn-ks`$^VcY!T461C;Pl_{%r(pVg?G(+=SPSV6E^?= literal 0 HcmV?d00001 diff --git a/pythonx/format.py b/pythonx/format.py new file mode 100644 index 0000000..2ceebc2 --- /dev/null +++ b/pythonx/format.py @@ -0,0 +1,117 @@ +"""Python formatexpr for clang-format & yapf""" + +import json +import sys +import subprocess +import difflib + +import vim + + +class FormatError(Exception): + """A formatting error.""" + + +def clang_format(): + """Call clang-format on the current text object.""" + clang_format = vim.vars['clang_format_path'] + # TODO: The cursor position before gq is invoked is not preserved in the + # jump list, this expression will return the cursor position at the + # beginning of the motion or text object. + # Is there a way to get the cursor position immediately before gq is + # invoked? So that we can pass the position to clang-format in order for + # it to return the updated position to continue editing. + # Query the current absolute cursor positon. + cursor = int(vim.eval('line2byte(".") + col(".")')) - 2 + if cursor < 0: + return + # Determine the lines to format. + startline = int(vim.eval('v:lnum')) + endline = startline + int(vim.eval('v:count')) - 1 + lines = f'{startline}:{endline}' + fallback_style = vim.vars['clang_format_style'] + # Construct the clang-format command to call. + command = [ + clang_format, '-style', 'file', '-cursor', + str(cursor), '-lines', lines, '-fallback-style', fallback_style + ] + if vim.current.buffer.name: + command += ['-assume-filename', vim.current.buffer.name] + # Call the clang-format command. + output = call(command) + if not output: + return + # Read the clang-format json header. + header = json.loads(output[0]) + if header.get('IncompleteFormat'): + raise FormatError('clang-format: incomplete (syntax errors).') + # Replace the formatted regions. + replace_regions(output[1:]) + # Set the updated cursor position. + vim.command('goto %d' % (header['Cursor'] + 1)) + + +def yapf(): + """Call yapf on the current text object.""" + yapf = vim.vars['yapf_path'] + # Determine the lines to format. + start = int(vim.eval('v:lnum')) + end = start + int(vim.eval('v:count')) + lines = '{0}-{1}'.format(start, end) + style = vim.vars['yapf_style'] + # Construct the clang-format command to call. + command = [yapf, '--style', style, '--lines', lines] + # TODO: Since yapf is a Python package, we could import the module and + # call it directly instead. It would then be possible to output better + # error messages and act on the buffer directly. + # Call the yapf command. + output = call(command) + if not output: + return + # Replace the formatted regions. + replace_regions(output[:-1]) + + +def call(command): + """Call the command to format the text. + + Arguments: + command (list): Formatting command to call. + text (str): Text to be passed to stdin of command. + + Returns: + list: The output of the formatter split on new lines. + None: If the subprocess failed. + """ + # Don't open a cmd prompt window on Windows. + startupinfo = None + if sys.platform.startswith('win32'): + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = subprocess.SW_HIDE + # Call the formatting command. + process = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + startupinfo=startupinfo) + stdout, stderr = process.communicate( + input='\n'.join(vim.current.buffer).encode('utf-8')) + if stderr: + raise FormatError(stderr) + if not stdout: + raise FormatError('No output from {0}.'.format(command[0])) + # Split the lines into a list of elements. + return stdout.decode('utf-8').split('\n') + + +def replace_regions(lines): + """Replace formatted regions in the current buffer. + + Arguments: + lines (list): The formatted buffer lines. + """ + matcher = difflib.SequenceMatcher(None, vim.current.buffer, lines) + for tag, i1, i2, j1, j2 in reversed(matcher.get_opcodes()): + if tag != 'equal': + vim.current.buffer[i1:i2] = lines[j1:j2] diff --git a/vimrc b/vimrc index 8c0dc1d..b30e37a 100644 --- a/vimrc +++ b/vimrc @@ -59,9 +59,6 @@ let g:ale_cmake_cmakelint_options = " Version control differences in the sign column Pack 'mhinz/vim-signify' -" format.vim - format with text objects -Pack 'git@bitbucket.org:infektor/format.vim.git' - " vim-textobj-user - library for creating text objects Pack 'kana/vim-textobj-user' " vim-textobj-entire - Entire file text object