diff --git a/autoload/codefmt.vim b/autoload/codefmt.vim index 3897920..22758b7 100644 --- a/autoload/codefmt.vim +++ b/autoload/codefmt.vim @@ -1,4 +1,4 @@ -" Copyright 2014 Google Inc. All rights reserved. +" Copyright 2017 Google Inc. All rights reserved. " " Licensed under the Apache License, Version 2.0 (the "License"); " you may not use this file except in compliance with the License. @@ -99,495 +99,6 @@ function! codefmt#EnsureFormatter(formatter) abort endfunction -"" -" @private -" Formatter: js-beautify -function! codefmt#GetJsBeautifyFormatter() abort - let l:formatter = { - \ 'name': 'js-beautify', - \ 'setup_instructions': 'Install js-beautify ' . - \ '(https://www.npmjs.com/package/js-beautify).'} - - function l:formatter.IsAvailable() abort - return executable(s:plugin.Flag('js_beautify_executable')) - endfunction - - function l:formatter.AppliesToBuffer() abort - return &filetype is# 'css' || &filetype is# 'html' || &filetype is# 'json' || - \ &filetype is# 'javascript' - endfunction - - "" - " Reformat the current buffer with js-beautify or the binary named in - " @flag(js_beautify_executable), only targeting the range between {startline} and - " {endline}. - " @throws ShellError - function l:formatter.FormatRange(startline, endline) abort - let l:cmd = [s:plugin.Flag('js_beautify_executable'), '-f', '-'] - if &filetype != "" - let l:cmd = l:cmd + ['--type', &filetype] - endif - - call maktaba#ensure#IsNumber(a:startline) - call maktaba#ensure#IsNumber(a:endline) - - let l:lines = getline(1, line('$')) - " Hack range formatting by formatting range individually, ignoring context. - let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") - - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. - let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] - let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] - - call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) - endfunction - - return l:formatter -endfunction - - -function! s:ClangFormatHasAtLeastVersion(minimum_version) abort - if !exists('s:clang_format_version') - let l:executable = s:plugin.Flag('clang_format_executable') - let l:version_output = - \ maktaba#syscall#Create([l:executable, '--version']).Call().stdout - let l:version_string = matchstr(l:version_output, '\v\d+(.\d+)+') - let s:clang_format_version = map(split(l:version_string, '\.'), 'v:val + 0') - endif - let l:length = min([len(a:minimum_version), len(s:clang_format_version)]) - for i in range(l:length) - if a:minimum_version[i] < s:clang_format_version[i] - return 1 - elseif a:minimum_version[i] > s:clang_format_version[i] - return 0 - endif - endfor - return len(a:minimum_version) <= len(s:clang_format_version) -endfunction - - -"" -" @private -" Invalidates the cached clang-format version. -function! codefmt#InvalidateClangFormatVersion() abort - unlet! s:clang_format_version -endfunction - - -"" -" @private -" Formatter: clang-format -function! codefmt#GetClangFormatFormatter() abort - let l:formatter = { - \ 'name': 'clang-format', - \ 'setup_instructions': 'Install clang-format from ' . - \ 'http://clang.llvm.org/docs/ClangFormat.html and ' . - \ 'configure the clang_format_executable flag'} - - function l:formatter.IsAvailable() abort - return executable(s:plugin.Flag('clang_format_executable')) - endfunction - - function l:formatter.AppliesToBuffer() abort - if &filetype is# 'c' || &filetype is# 'cpp' || - \ &filetype is# 'proto' || &filetype is# 'javascript' || - \ &filetype is# 'typescript' - return 1 - endif - " Version 3.6 adds support for java - " http://llvm.org/releases/3.6.0/tools/clang/docs/ReleaseNotes.html - return &filetype is# 'java' && s:ClangFormatHasAtLeastVersion([3, 6]) - endfunction - - "" - " Reformat buffer with clang-format, only targeting [ranges] if given. - function l:formatter.FormatRanges(ranges) abort - let l:Style_value = s:plugin.Flag('clang_format_style') - if type(l:Style_value) is# type('') - let l:style = l:Style_value - elseif maktaba#value#IsCallable(l:Style_value) - let l:style = maktaba#function#Call(l:Style_value) - else - throw maktaba#error#WrongType( - \ 'clang_format_style flag must be string or callable. Found %s', - \ string(l:Style_value)) - endif - if empty(a:ranges) - return - endif - - let l:cmd = [ - \ s:plugin.Flag('clang_format_executable'), - \ '-style', l:style] - let l:fname = expand('%:p') - if !empty(l:fname) - let l:cmd += ['-assume-filename', l:fname] - endif - - for [l:startline, l:endline] in a:ranges - call maktaba#ensure#IsNumber(l:startline) - call maktaba#ensure#IsNumber(l:endline) - let l:cmd += ['-lines', l:startline . ':' . l:endline] - endfor - - " Version 3.4 introduced support for cursor tracking - " http://llvm.org/releases/3.4/tools/clang/docs/ClangFormat.html - let l:supports_cursor = s:ClangFormatHasAtLeastVersion([3, 4]) - if l:supports_cursor - " line2byte counts bytes from 1, and col counts from 1, so -2 - let l:cursor_pos = line2byte(line('.')) + col('.') - 2 - let l:cmd += ['-cursor', string(l:cursor_pos)] - endif - - let l:input = join(getline(1, line('$')), "\n") - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - - if !l:supports_cursor - call maktaba#buffer#Overwrite(1, line('$'), l:formatted[0:]) - else - call maktaba#buffer#Overwrite(1, line('$'), l:formatted[1:]) - try - let l:clang_format_output_json = maktaba#json#Parse(l:formatted[0]) - let l:new_cursor_pos = - \ maktaba#ensure#IsNumber(l:clang_format_output_json.Cursor) + 1 - execute 'goto' l:new_cursor_pos - catch - call maktaba#error#Warn('Unable to parse clang-format cursor pos: %s', - \ v:exception) - endtry - endif - endfunction - - return l:formatter -endfunction - - -"" -" @private -" Formatter: gofmt -function! codefmt#GetGofmtFormatter() abort - let l:formatter = { - \ 'name': 'gofmt', - \ 'setup_instructions': 'Install gofmt or goimports and ' . - \ 'configure the gofmt_executable flag'} - - function l:formatter.IsAvailable() abort - return executable(s:plugin.Flag('gofmt_executable')) - endfunction - - function l:formatter.AppliesToBuffer() abort - return &filetype is# 'go' - endfunction - - "" - " Reformat the current buffer with gofmt or the binary named in - " @flag(gofmt_executable), only targeting the range between {startline} and - " {endline}. - function l:formatter.FormatRange(startline, endline) abort - " Hack range formatting by formatting range individually, ignoring context. - let l:cmd = [ s:plugin.Flag('gofmt_executable') ] - call maktaba#ensure#IsNumber(a:startline) - call maktaba#ensure#IsNumber(a:endline) - let l:lines = getline(1, line('$')) - let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") - try - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. - let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] - - let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] - call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) - catch /ERROR(ShellError):/ - " Parse all the errors and stick them in the quickfix list. - let l:errors = [] - for l:line in split(v:exception, "\n") - let l:tokens = matchlist(l:line, - \ '\C\v^\:(\d+):(\d+):\s*(.*)') - if !empty(l:tokens) - call add(l:errors, { - \ 'filename': @%, - \ 'lnum': l:tokens[1] + a:startline - 1, - \ 'col': l:tokens[2], - \ 'text': l:tokens[3]}) - endif - endfor - - if empty(l:errors) - " Couldn't parse gofmt error format; display it all. - call maktaba#error#Shout('Error formatting file: %s', v:exception) - else - call setqflist(l:errors, 'r') - cc 1 - endif - endtry - endfunction - - return l:formatter -endfunction - -"" -" @private -" Formatter: dartfmt -function! codefmt#GetDartfmtFormatter() abort - let l:formatter = { - \ 'name': 'dartfmt', - \ 'setup_instructions': 'Install the Dart SDK from ' . - \ 'https://www.dartlang.org/tools/sdk/'} - - function l:formatter.IsAvailable() abort - return executable(s:plugin.Flag('dartfmt_executable')) - endfunction - - function l:formatter.AppliesToBuffer() abort - return &filetype is# 'dart' - endfunction - - "" - " Reformat the current buffer with dartfmt or the binary named in - " @flag(dartfmt_executable}, only targetting the range from {startline} to - " {endline} - function l:formatter.FormatRange(startline, endline) abort - " Hack range formatting by formatting range individually, ignoring context. - let l:cmd = [ s:plugin.Flag('dartfmt_executable') ] - " TODO When https://github.com/dart-lang/dart_style/issues/92 is implemented - " use those options. - call maktaba#ensure#IsNumber(a:startline) - call maktaba#ensure#IsNumber(a:endline) - let l:lines = getline(1, line('$')) - let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") - try - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. - let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] - - let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] - call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) - catch /ERROR(ShellError):/ - " Parse all the errors and stick them in the quickfix list. - let l:errors = [] - for l:line in split(v:exception, "\n") - let l:tokens = matchlist(l:line, - \ '\C\v^line (\d+), column (\d+) of stdin: (.*)') - if !empty(l:tokens) - call add(l:errors, { - \ 'filename': @%, - \ 'lnum': l:tokens[1] + a:startline - 1, - \ 'col': l:tokens[2], - \ 'text': l:tokens[3]}) - endif - endfor - - if empty(l:errors) - " Couldn't parse dartfmt error format; display it all. - call maktaba#error#Shout('Failed to format range; showing all errors: %s', v:exception) - else - let l:errorHeaderLines = split(v:exception, "\n")[1 : 5] - let l:errorHeader = join(l:errorHeaderLines, "\n") - call maktaba#error#Shout( - \ "Error formatting file:\n%s\n\nMore errors in the fixlist.", - \ l:errorHeader) - call setqflist(l:errors, 'r') - cc 1 - endif - endtry - endfunction - - return l:formatter -endfunction - -"" -" @private -" Formatter: autopep8 -function! codefmt#GetAutopep8Formatter() abort - let l:formatter = { - \ 'name': 'autopep8', - \ 'setup_instructions': 'Install autopep8 ' . - \ '(https://pypi.python.org/pypi/autopep8/).'} - - function l:formatter.IsAvailable() abort - return executable(s:plugin.Flag('autopep8_executable')) - endfunction - - function l:formatter.AppliesToBuffer() abort - return &filetype is# 'python' - endfunction - - "" - " Reformat the current buffer with autopep8 or the binary named in - " @flag(autopep8_executable), only targeting the range between {startline} and - " {endline}. - " @throws ShellError - function l:formatter.FormatRange(startline, endline) abort - let l:executable = s:plugin.Flag('autopep8_executable') - if !exists('s:autopep8_supports_range') - let l:version_call = - \ maktaba#syscall#Create([l:executable, '--version']).Call() - " In some cases version is written to stderr, in some to stdout - let l:version_output = empty(version_call.stderr) ? - \ version_call.stdout : version_call.stderr - let l:autopep8_version = - \ matchlist(l:version_output, '\m\Cautopep8 \(\d\+\)\.') - if empty(l:autopep8_version) - throw maktaba#error#Failure( - \ 'Unable to parse version from `%s --version`: %s', - \ l:executable, l:version_output) - else - let s:autopep8_supports_range = l:autopep8_version[1] >= 1 - endif - endif - - call maktaba#ensure#IsNumber(a:startline) - call maktaba#ensure#IsNumber(a:endline) - let l:lines = getline(1, line('$')) - - if s:autopep8_supports_range - let l:cmd = [l:executable, '--range', ''.a:startline, ''.a:endline, '-'] - let l:input = join(l:lines, "\n") - else - let l:cmd = [l:executable, '-'] - " Hack range formatting by formatting range individually, ignoring context. - let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") - endif - - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - - if s:autopep8_supports_range - let l:full_formatted = l:formatted - else - " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. - let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] - let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] - endif - - call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) - endfunction - - return l:formatter -endfunction - - -"" -" @private -" Formatter: yapf -function! codefmt#GetYAPFFormatter() abort - let l:formatter = { - \ 'name': 'yapf', - \ 'setup_instructions': 'Install yapf ' . - \ '(https://pypi.python.org/pypi/yapf/).'} - - function l:formatter.IsAvailable() abort - return executable(s:plugin.Flag('yapf_executable')) - endfunction - - function l:formatter.AppliesToBuffer() abort - return &filetype is# 'python' - endfunction - - "" - " Reformat the current buffer with yapf or the binary named in - " @flag(yapf_executable), only targeting the range between {startline} and - " {endline}. - " @throws ShellError - function l:formatter.FormatRange(startline, endline) abort - let l:executable = s:plugin.Flag('yapf_executable') - - call maktaba#ensure#IsNumber(a:startline) - call maktaba#ensure#IsNumber(a:endline) - let l:lines = getline(1, line('$')) - - let l:cmd = [l:executable, '--lines=' . a:startline . '-' . a:endline] - let l:input = join(l:lines, "\n") - - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call(0) - if v:shell_error == 1 " Indicates an error with parsing - call maktaba#error#Shout('Error formatting file: %s', l:result.stderr) - return - endif - let l:formatted = split(l:result.stdout, "\n") - - call maktaba#buffer#Overwrite(1, line('$'), l:formatted) - endfunction - - return l:formatter -endfunction - - -"" -" @private -" Formatter: gn -function! codefmt#GetGnFormatter() abort - let l:url = 'https://www.chromium.org/developers/how-tos/install-depot-tools' - let l:formatter = { - \ 'name': 'gn', - \ 'setup_instructions': 'install Chromium depot_tools (' . l:url . ')'} - - function l:formatter.IsAvailable() abort - return executable(s:plugin.Flag('gn_executable')) - endfunction - - function l:formatter.AppliesToBuffer() abort - return &filetype is# 'gn' - endfunction - - "" - " Run `gn format` to format the whole file. - " - " We implement Format(), and not FormatRange{,s}(), because gn doesn't - " provide a hook for formatting a range, and all gn files are supposed - " to be fully formatted anyway. - function l:formatter.Format() abort - let l:executable = s:plugin.Flag('gn_executable') - let l:cmd = [ l:executable, 'format', '--stdin' ] - let l:input = join(getline(1, line('$')), "\n") - - " gn itself prints errors to stdout, but if the error comes from the - " gn.py wrapper script, it is printed to stderr. Use stdout as the - " error message if stderr is empty. - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call(0) - if !empty(l:result.stdout) - let l:output = l:result.stdout - else - let l:output = l:result.stderr - endif - - " Other formatters generally catch failure as an exception, but - " v:exception contains stderr in that case, and gn prints errors to - " stdout, so we need to check for a shell error ourselves. - if !v:shell_error - let l:formatted = split(l:output, "\n") - call maktaba#buffer#Overwrite(1, line('$'), l:formatted) - else - let l:errors = [] - for line in split(l:output, "\n") - let l:tokens = matchlist(line, '\C\v^ERROR at :(\d+):(\d+):\s*(.*)') - if !empty(l:tokens) - call add(l:errors, { - \ "filename": @%, - \ "lnum": l:tokens[1], - \ "col": l:tokens[2], - \ "text": l:tokens[3]}) - endif - endfor - - if empty(l:errors) - " Couldn't parse errors; display the whole error message. - call maktaba#error#Shout('Error formatting file: %s', l:output) - else - call setqflist(l:errors, 'r') - cc 1 - endif - endif - endfunction - - return l:formatter -endfunction - "" " Checks whether {formatter} is available. " NOTE: If IsAvailable checks are disabled via @@ -610,6 +121,7 @@ function! codefmt#IsFormatterAvailable() abort \ !empty(get(b:, 'codefmt_formatter')) endfunction + function! s:GetSetupInstructions(formatter) abort let l:error = 'Formatter "'. a:formatter.name . '" is not available.' if has_key(a:formatter, 'setup_instructions') @@ -741,14 +253,6 @@ function! codefmt#GetSupportedFormatters(ArgLead, CmdLine, CursorPos) abort endfunction -"" -" @private -" Invalidates the cached autopep8 version detection info. -function! codefmt#InvalidateAutopep8Version() abort - unlet! s:autopep8_supports_range -endfunction - - "" " @private " Configures whether codefmt should bypass FORMATTER.IsAvailable checks and diff --git a/autoload/codefmt/autopep8.vim b/autoload/codefmt/autopep8.vim new file mode 100644 index 0000000..53d5a66 --- /dev/null +++ b/autoload/codefmt/autopep8.vim @@ -0,0 +1,96 @@ +" Copyright 2017 Google Inc. All rights reserved. +" +" Licensed under the Apache License, Version 2.0 (the "License"); +" you may not use this file except in compliance with the License. +" You may obtain a copy of the License at +" +" http://www.apache.org/licenses/LICENSE-2.0 +" +" Unless required by applicable law or agreed to in writing, software +" distributed under the License is distributed on an "AS IS" BASIS, +" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +" See the License for the specific language governing permissions and +" limitations under the License. + + +let s:plugin = maktaba#plugin#Get('codefmt') + + +"" +" @private +" Invalidates the cached autopep8 version detection info. +function! codefmt#autopep8#InvalidateVersion() abort + unlet! s:autopep8_supports_range +endfunction + + +"" +" @private +" Formatter: autopep8 +function! codefmt#autopep8#GetFormatter() abort + let l:formatter = { + \ 'name': 'autopep8', + \ 'setup_instructions': 'Install autopep8 ' . + \ '(https://pypi.python.org/pypi/autopep8/).'} + + function l:formatter.IsAvailable() abort + return executable(s:plugin.Flag('autopep8_executable')) + endfunction + + function l:formatter.AppliesToBuffer() abort + return &filetype is# 'python' + endfunction + + "" + " Reformat the current buffer with autopep8 or the binary named in + " @flag(autopep8_executable), only targeting the range between {startline} and + " {endline}. + " @throws ShellError + function l:formatter.FormatRange(startline, endline) abort + let l:executable = s:plugin.Flag('autopep8_executable') + if !exists('s:autopep8_supports_range') + let l:version_call = + \ maktaba#syscall#Create([l:executable, '--version']).Call() + " In some cases version is written to stderr, in some to stdout + let l:version_output = empty(version_call.stderr) ? + \ version_call.stdout : version_call.stderr + let l:autopep8_version = + \ matchlist(l:version_output, '\m\Cautopep8 \(\d\+\)\.') + if empty(l:autopep8_version) + throw maktaba#error#Failure( + \ 'Unable to parse version from `%s --version`: %s', + \ l:executable, l:version_output) + else + let s:autopep8_supports_range = l:autopep8_version[1] >= 1 + endif + endif + + call maktaba#ensure#IsNumber(a:startline) + call maktaba#ensure#IsNumber(a:endline) + let l:lines = getline(1, line('$')) + + if s:autopep8_supports_range + let l:cmd = [l:executable, '--range', ''.a:startline, ''.a:endline, '-'] + let l:input = join(l:lines, "\n") + else + let l:cmd = [l:executable, '-'] + " Hack range formatting by formatting range individually, ignoring context. + let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") + endif + + let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() + let l:formatted = split(l:result.stdout, "\n") + + if s:autopep8_supports_range + let l:full_formatted = l:formatted + else + " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. + let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] + let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] + endif + + call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) + endfunction + + return l:formatter +endfunction diff --git a/autoload/codefmt/buildifier.vim b/autoload/codefmt/buildifier.vim index dd6e05d..a1bcef1 100644 --- a/autoload/codefmt/buildifier.vim +++ b/autoload/codefmt/buildifier.vim @@ -14,7 +14,6 @@ let s:plugin = maktaba#plugin#Get('codefmt') -let s:registry = s:plugin.GetExtensionRegistry() "" diff --git a/autoload/codefmt/clangformat.vim b/autoload/codefmt/clangformat.vim new file mode 100644 index 0000000..73178d2 --- /dev/null +++ b/autoload/codefmt/clangformat.vim @@ -0,0 +1,133 @@ +" Copyright 2017 Google Inc. All rights reserved. +" +" Licensed under the Apache License, Version 2.0 (the "License"); +" you may not use this file except in compliance with the License. +" You may obtain a copy of the License at +" +" http://www.apache.org/licenses/LICENSE-2.0 +" +" Unless required by applicable law or agreed to in writing, software +" distributed under the License is distributed on an "AS IS" BASIS, +" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +" See the License for the specific language governing permissions and +" limitations under the License. + + +let s:plugin = maktaba#plugin#Get('codefmt') + + +function! s:ClangFormatHasAtLeastVersion(minimum_version) abort + if !exists('s:clang_format_version') + let l:executable = s:plugin.Flag('clang_format_executable') + let l:version_output = + \ maktaba#syscall#Create([l:executable, '--version']).Call().stdout + let l:version_string = matchstr(l:version_output, '\v\d+(.\d+)+') + let s:clang_format_version = map(split(l:version_string, '\.'), 'v:val + 0') + endif + let l:length = min([len(a:minimum_version), len(s:clang_format_version)]) + for i in range(l:length) + if a:minimum_version[i] < s:clang_format_version[i] + return 1 + elseif a:minimum_version[i] > s:clang_format_version[i] + return 0 + endif + endfor + return len(a:minimum_version) <= len(s:clang_format_version) +endfunction + + +"" +" @private +" Invalidates the cached clang-format version. +function! codefmt#clangformat#InvalidateVersion() abort + unlet! s:clang_format_version +endfunction + + +"" +" @private +" Formatter: clang-format +function! codefmt#clangformat#GetFormatter() abort + let l:formatter = { + \ 'name': 'clang-format', + \ 'setup_instructions': 'Install clang-format from ' . + \ 'http://clang.llvm.org/docs/ClangFormat.html and ' . + \ 'configure the clang_format_executable flag'} + + function l:formatter.IsAvailable() abort + return executable(s:plugin.Flag('clang_format_executable')) + endfunction + + function l:formatter.AppliesToBuffer() abort + if &filetype is# 'c' || &filetype is# 'cpp' || + \ &filetype is# 'proto' || &filetype is# 'javascript' || + \ &filetype is# 'typescript' + return 1 + endif + " Version 3.6 adds support for java + " http://llvm.org/releases/3.6.0/tools/clang/docs/ReleaseNotes.html + return &filetype is# 'java' && s:ClangFormatHasAtLeastVersion([3, 6]) + endfunction + + "" + " Reformat buffer with clang-format, only targeting [ranges] if given. + function l:formatter.FormatRanges(ranges) abort + let l:Style_value = s:plugin.Flag('clang_format_style') + if type(l:Style_value) is# type('') + let l:style = l:Style_value + elseif maktaba#value#IsCallable(l:Style_value) + let l:style = maktaba#function#Call(l:Style_value) + else + throw maktaba#error#WrongType( + \ 'clang_format_style flag must be string or callable. Found %s', + \ string(l:Style_value)) + endif + if empty(a:ranges) + return + endif + + let l:cmd = [ + \ s:plugin.Flag('clang_format_executable'), + \ '-style', l:style] + let l:fname = expand('%:p') + if !empty(l:fname) + let l:cmd += ['-assume-filename', l:fname] + endif + + for [l:startline, l:endline] in a:ranges + call maktaba#ensure#IsNumber(l:startline) + call maktaba#ensure#IsNumber(l:endline) + let l:cmd += ['-lines', l:startline . ':' . l:endline] + endfor + + " Version 3.4 introduced support for cursor tracking + " http://llvm.org/releases/3.4/tools/clang/docs/ClangFormat.html + let l:supports_cursor = s:ClangFormatHasAtLeastVersion([3, 4]) + if l:supports_cursor + " line2byte counts bytes from 1, and col counts from 1, so -2 + let l:cursor_pos = line2byte(line('.')) + col('.') - 2 + let l:cmd += ['-cursor', string(l:cursor_pos)] + endif + + let l:input = join(getline(1, line('$')), "\n") + let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() + let l:formatted = split(l:result.stdout, "\n") + + if !l:supports_cursor + call maktaba#buffer#Overwrite(1, line('$'), l:formatted[0:]) + else + call maktaba#buffer#Overwrite(1, line('$'), l:formatted[1:]) + try + let l:clang_format_output_json = maktaba#json#Parse(l:formatted[0]) + let l:new_cursor_pos = + \ maktaba#ensure#IsNumber(l:clang_format_output_json.Cursor) + 1 + execute 'goto' l:new_cursor_pos + catch + call maktaba#error#Warn('Unable to parse clang-format cursor pos: %s', + \ v:exception) + endtry + endif + endfunction + + return l:formatter +endfunction diff --git a/autoload/codefmt/dartfmt.vim b/autoload/codefmt/dartfmt.vim new file mode 100644 index 0000000..ba965c7 --- /dev/null +++ b/autoload/codefmt/dartfmt.vim @@ -0,0 +1,89 @@ +" Copyright 2017 Google Inc. All rights reserved. +" +" Licensed under the Apache License, Version 2.0 (the "License"); +" you may not use this file except in compliance with the License. +" You may obtain a copy of the License at +" +" http://www.apache.org/licenses/LICENSE-2.0 +" +" Unless required by applicable law or agreed to in writing, software +" distributed under the License is distributed on an "AS IS" BASIS, +" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +" See the License for the specific language governing permissions and +" limitations under the License. + + +let s:plugin = maktaba#plugin#Get('codefmt') + + +"" +" @private +" Formatter: dartfmt +function! codefmt#dartfmt#GetFormatter() abort + let l:formatter = { + \ 'name': 'dartfmt', + \ 'setup_instructions': 'Install the Dart SDK from ' . + \ 'https://www.dartlang.org/tools/sdk/'} + + function l:formatter.IsAvailable() abort + return executable(s:plugin.Flag('dartfmt_executable')) + endfunction + + function l:formatter.AppliesToBuffer() abort + return &filetype is# 'dart' + endfunction + + "" + " Reformat the current buffer with dartfmt or the binary named in + " @flag(dartfmt_executable}, only targetting the range from {startline} to + " {endline} + function l:formatter.FormatRange(startline, endline) abort + " Hack range formatting by formatting range individually, ignoring context. + let l:cmd = [ s:plugin.Flag('dartfmt_executable') ] + " TODO When https://github.com/dart-lang/dart_style/issues/92 is implemented + " use those options. + call maktaba#ensure#IsNumber(a:startline) + call maktaba#ensure#IsNumber(a:endline) + let l:lines = getline(1, line('$')) + let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") + try + let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() + let l:formatted = split(l:result.stdout, "\n") + " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. + let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] + + let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] + call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) + catch /ERROR(ShellError):/ + " Parse all the errors and stick them in the quickfix list. + let l:errors = [] + for l:line in split(v:exception, "\n") + let l:tokens = matchlist(l:line, + \ '\C\v^line (\d+), column (\d+) of stdin: (.*)') + if !empty(l:tokens) + call add(l:errors, { + \ 'filename': @%, + \ 'lnum': l:tokens[1] + a:startline - 1, + \ 'col': l:tokens[2], + \ 'text': l:tokens[3]}) + endif + endfor + + if empty(l:errors) + " Couldn't parse dartfmt error format; display it all. + call maktaba#error#Shout('Failed to format range; showing all errors: %s', v:exception) + else + let l:errorHeaderLines = split(v:exception, "\n")[1 : 5] + let l:errorHeader = join(l:errorHeaderLines, "\n") + call maktaba#error#Shout( + \ "Error formatting file:\n%s\n\nMore errors in the fixlist.", + \ l:errorHeader) + call setqflist(l:errors, 'r') + cc 1 + endif + endtry + endfunction + + return l:formatter +endfunction + diff --git a/autoload/codefmt/gn.vim b/autoload/codefmt/gn.vim new file mode 100644 index 0000000..3cfd9db --- /dev/null +++ b/autoload/codefmt/gn.vim @@ -0,0 +1,88 @@ +" Copyright 2017 Google Inc. All rights reserved. +" +" Licensed under the Apache License, Version 2.0 (the "License"); +" you may not use this file except in compliance with the License. +" You may obtain a copy of the License at +" +" http://www.apache.org/licenses/LICENSE-2.0 +" +" Unless required by applicable law or agreed to in writing, software +" distributed under the License is distributed on an "AS IS" BASIS, +" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +" See the License for the specific language governing permissions and +" limitations under the License. + + +let s:plugin = maktaba#plugin#Get('codefmt') + + +"" +" @private +" Formatter for gn, a chromium build tool. +" Formatter: gn +function! codefmt#gn#GetFormatter() abort + let l:url = 'https://www.chromium.org/developers/how-tos/install-depot-tools' + let l:formatter = { + \ 'name': 'gn', + \ 'setup_instructions': 'install Chromium depot_tools (' . l:url . ')'} + + function l:formatter.IsAvailable() abort + return executable(s:plugin.Flag('gn_executable')) + endfunction + + function l:formatter.AppliesToBuffer() abort + return &filetype is# 'gn' + endfunction + + "" + " Run `gn format` to format the whole file. + " + " We implement Format(), and not FormatRange{,s}(), because gn doesn't + " provide a hook for formatting a range, and all gn files are supposed + " to be fully formatted anyway. + function l:formatter.Format() abort + let l:executable = s:plugin.Flag('gn_executable') + let l:cmd = [ l:executable, 'format', '--stdin' ] + let l:input = join(getline(1, line('$')), "\n") + + " gn itself prints errors to stdout, but if the error comes from the + " gn.py wrapper script, it is printed to stderr. Use stdout as the + " error message if stderr is empty. + let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call(0) + if !empty(l:result.stdout) + let l:output = l:result.stdout + else + let l:output = l:result.stderr + endif + + " Other formatters generally catch failure as an exception, but + " v:exception contains stderr in that case, and gn prints errors to + " stdout, so we need to check for a shell error ourselves. + if !v:shell_error + let l:formatted = split(l:output, "\n") + call maktaba#buffer#Overwrite(1, line('$'), l:formatted) + else + let l:errors = [] + for line in split(l:output, "\n") + let l:tokens = matchlist(line, '\C\v^ERROR at :(\d+):(\d+):\s*(.*)') + if !empty(l:tokens) + call add(l:errors, { + \ "filename": @%, + \ "lnum": l:tokens[1], + \ "col": l:tokens[2], + \ "text": l:tokens[3]}) + endif + endfor + + if empty(l:errors) + " Couldn't parse errors; display the whole error message. + call maktaba#error#Shout('Error formatting file: %s', l:output) + else + call setqflist(l:errors, 'r') + cc 1 + endif + endif + endfunction + + return l:formatter +endfunction diff --git a/autoload/codefmt/gofmt.vim b/autoload/codefmt/gofmt.vim new file mode 100644 index 0000000..bab8317 --- /dev/null +++ b/autoload/codefmt/gofmt.vim @@ -0,0 +1,81 @@ +" Copyright 2017 Google Inc. All rights reserved. +" +" Licensed under the Apache License, Version 2.0 (the "License"); +" you may not use this file except in compliance with the License. +" You may obtain a copy of the License at +" +" http://www.apache.org/licenses/LICENSE-2.0 +" +" Unless required by applicable law or agreed to in writing, software +" distributed under the License is distributed on an "AS IS" BASIS, +" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +" See the License for the specific language governing permissions and +" limitations under the License. + + +let s:plugin = maktaba#plugin#Get('codefmt') + + +"" +" @private +" Formatter: gofmt +function! codefmt#gofmt#GetFormatter() abort + let l:formatter = { + \ 'name': 'gofmt', + \ 'setup_instructions': 'Install gofmt or goimports and ' . + \ 'configure the gofmt_executable flag'} + + function l:formatter.IsAvailable() abort + return executable(s:plugin.Flag('gofmt_executable')) + endfunction + + function l:formatter.AppliesToBuffer() abort + return &filetype is# 'go' + endfunction + + "" + " Reformat the current buffer with gofmt or the binary named in + " @flag(gofmt_executable), only targeting the range between {startline} and + " {endline}. + function l:formatter.FormatRange(startline, endline) abort + " Hack range formatting by formatting range individually, ignoring context. + let l:cmd = [ s:plugin.Flag('gofmt_executable') ] + call maktaba#ensure#IsNumber(a:startline) + call maktaba#ensure#IsNumber(a:endline) + let l:lines = getline(1, line('$')) + let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") + try + let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() + let l:formatted = split(l:result.stdout, "\n") + " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. + let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] + + let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] + call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) + catch /ERROR(ShellError):/ + " Parse all the errors and stick them in the quickfix list. + let l:errors = [] + for l:line in split(v:exception, "\n") + let l:tokens = matchlist(l:line, + \ '\C\v^\:(\d+):(\d+):\s*(.*)') + if !empty(l:tokens) + call add(l:errors, { + \ 'filename': @%, + \ 'lnum': l:tokens[1] + a:startline - 1, + \ 'col': l:tokens[2], + \ 'text': l:tokens[3]}) + endif + endfor + + if empty(l:errors) + " Couldn't parse gofmt error format; display it all. + call maktaba#error#Shout('Error formatting file: %s', v:exception) + else + call setqflist(l:errors, 'r') + cc 1 + endif + endtry + endfunction + + return l:formatter +endfunction diff --git a/autoload/codefmt/jsbeautify.vim b/autoload/codefmt/jsbeautify.vim new file mode 100644 index 0000000..f4df7fd --- /dev/null +++ b/autoload/codefmt/jsbeautify.vim @@ -0,0 +1,66 @@ +" Copyright 2017 Google Inc. All rights reserved. +" +" Licensed under the Apache License, Version 2.0 (the "License"); +" you may not use this file except in compliance with the License. +" You may obtain a copy of the License at +" +" http://www.apache.org/licenses/LICENSE-2.0 +" +" Unless required by applicable law or agreed to in writing, software +" distributed under the License is distributed on an "AS IS" BASIS, +" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +" See the License for the specific language governing permissions and +" limitations under the License. + + +let s:plugin = maktaba#plugin#Get('codefmt') + + +"" +" @private +" Formatter: js-beautify +function! codefmt#jsbeautify#GetFormatter() abort + let l:formatter = { + \ 'name': 'js-beautify', + \ 'setup_instructions': 'Install js-beautify ' . + \ '(https://www.npmjs.com/package/js-beautify).'} + + function l:formatter.IsAvailable() abort + return executable(s:plugin.Flag('js_beautify_executable')) + endfunction + + function l:formatter.AppliesToBuffer() abort + return &filetype is# 'css' || &filetype is# 'html' || &filetype is# 'json' || + \ &filetype is# 'javascript' + endfunction + + "" + " Reformat the current buffer with js-beautify or the binary named in + " @flag(js_beautify_executable), only targeting the range between {startline} and + " {endline}. + " @throws ShellError + function l:formatter.FormatRange(startline, endline) abort + let l:cmd = [s:plugin.Flag('js_beautify_executable'), '-f', '-'] + if &filetype != "" + let l:cmd = l:cmd + ['--type', &filetype] + endif + + call maktaba#ensure#IsNumber(a:startline) + call maktaba#ensure#IsNumber(a:endline) + + let l:lines = getline(1, line('$')) + " Hack range formatting by formatting range individually, ignoring context. + let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") + + let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() + let l:formatted = split(l:result.stdout, "\n") + " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. + let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] + let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] + + call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) + endfunction + + return l:formatter +endfunction + diff --git a/autoload/codefmt/yapf.vim b/autoload/codefmt/yapf.vim new file mode 100644 index 0000000..0ae79f3 --- /dev/null +++ b/autoload/codefmt/yapf.vim @@ -0,0 +1,62 @@ +" Copyright 2017 Google Inc. All rights reserved. +" +" Licensed under the Apache License, Version 2.0 (the "License"); +" you may not use this file except in compliance with the License. +" You may obtain a copy of the License at +" +" http://www.apache.org/licenses/LICENSE-2.0 +" +" Unless required by applicable law or agreed to in writing, software +" distributed under the License is distributed on an "AS IS" BASIS, +" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +" See the License for the specific language governing permissions and +" limitations under the License. + + +let s:plugin = maktaba#plugin#Get('codefmt') + + +"" +" @private +" Formatter: yapf +function! codefmt#yapf#GetFormatter() abort + let l:formatter = { + \ 'name': 'yapf', + \ 'setup_instructions': 'Install yapf ' . + \ '(https://pypi.python.org/pypi/yapf/).'} + + function l:formatter.IsAvailable() abort + return executable(s:plugin.Flag('yapf_executable')) + endfunction + + function l:formatter.AppliesToBuffer() abort + return &filetype is# 'python' + endfunction + + "" + " Reformat the current buffer with yapf or the binary named in + " @flag(yapf_executable), only targeting the range between {startline} and + " {endline}. + " @throws ShellError + function l:formatter.FormatRange(startline, endline) abort + let l:executable = s:plugin.Flag('yapf_executable') + + call maktaba#ensure#IsNumber(a:startline) + call maktaba#ensure#IsNumber(a:endline) + let l:lines = getline(1, line('$')) + + let l:cmd = [l:executable, '--lines=' . a:startline . '-' . a:endline] + let l:input = join(l:lines, "\n") + + let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call(0) + if v:shell_error == 1 " Indicates an error with parsing + call maktaba#error#Shout('Error formatting file: %s', l:result.stderr) + return + endif + let l:formatted = split(l:result.stdout, "\n") + + call maktaba#buffer#Overwrite(1, line('$'), l:formatted) + endfunction + + return l:formatter +endfunction diff --git a/instant/flags.vim b/instant/flags.vim index 21ab96f..6b82a49 100644 --- a/instant/flags.vim +++ b/instant/flags.vim @@ -46,7 +46,7 @@ call s:plugin.Flag('autopep8_executable', 'autopep8') " Invalidate cache of detected autopep8 version when this is changed, regardless " of {value} arg. call s:plugin.flags.autopep8_executable.AddCallback( - \ maktaba#function#FromExpr('codefmt#InvalidateAutopep8Version()'), 0) + \ maktaba#function#FromExpr('codefmt#autopep8#InvalidateVersion()'), 0) "" " The path to the clang-format executable. @@ -54,7 +54,7 @@ call s:plugin.Flag('clang_format_executable', 'clang-format') " Invalidate cache of detected clang-format version when this is changed, regardless " of {value} arg. call s:plugin.flags.clang_format_executable.AddCallback( - \ maktaba#function#FromExpr('codefmt#InvalidateClangFormatVersion()'), 0) + \ maktaba#function#FromExpr('codefmt#clangformat#InvalidateVersion()'), 0) "" " Formatting style for clang-format to use. Either a string or callable that diff --git a/plugin/register.vim b/plugin/register.vim index 7b72a36..25555e2 100644 --- a/plugin/register.vim +++ b/plugin/register.vim @@ -21,11 +21,11 @@ endif let s:registry = s:plugin.GetExtensionRegistry() call s:registry.SetValidator('codefmt#EnsureFormatter') -call s:registry.AddExtension(codefmt#GetJsBeautifyFormatter()) -call s:registry.AddExtension(codefmt#GetClangFormatFormatter()) -call s:registry.AddExtension(codefmt#GetGofmtFormatter()) -call s:registry.AddExtension(codefmt#GetDartfmtFormatter()) -call s:registry.AddExtension(codefmt#GetYAPFFormatter()) -call s:registry.AddExtension(codefmt#GetAutopep8Formatter()) -call s:registry.AddExtension(codefmt#GetGnFormatter()) +call s:registry.AddExtension(codefmt#jsbeautify#GetFormatter()) +call s:registry.AddExtension(codefmt#clangformat#GetFormatter()) +call s:registry.AddExtension(codefmt#gofmt#GetFormatter()) +call s:registry.AddExtension(codefmt#dartfmt#GetFormatter()) +call s:registry.AddExtension(codefmt#yapf#GetFormatter()) +call s:registry.AddExtension(codefmt#autopep8#GetFormatter()) +call s:registry.AddExtension(codefmt#gn#GetFormatter()) call s:registry.AddExtension(codefmt#buildifier#GetFormatter())