Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
14 | pmbaty | 1 | # This file is a minimal clang-format vim-integration. To install: |
2 | # - Change 'binary' if clang-format is not on the path (see below). |
||
3 | # - Add to your .vimrc: |
||
4 | # |
||
5 | # if has('python') |
||
6 | # map <C-I> :pyf <path-to-this-file>/clang-format.py<cr> |
||
7 | # imap <C-I> <c-o>:pyf <path-to-this-file>/clang-format.py<cr> |
||
8 | # elseif has('python3') |
||
9 | # map <C-I> :py3f <path-to-this-file>/clang-format.py<cr> |
||
10 | # imap <C-I> <c-o>:py3f <path-to-this-file>/clang-format.py<cr> |
||
11 | # endif |
||
12 | # |
||
13 | # The if-elseif-endif conditional should pick either the python3 or python2 |
||
14 | # integration depending on your vim setup. |
||
15 | # |
||
16 | # The first mapping enables clang-format for NORMAL and VISUAL mode, the second |
||
17 | # mapping adds support for INSERT mode. Change "C-I" to another binding if you |
||
18 | # need clang-format on a different key (C-I stands for Ctrl+i). |
||
19 | # |
||
20 | # With this integration you can press the bound key and clang-format will |
||
21 | # format the current line in NORMAL and INSERT mode or the selected region in |
||
22 | # VISUAL mode. The line or region is extended to the next bigger syntactic |
||
23 | # entity. |
||
24 | # |
||
25 | # You can also pass in the variable "l:lines" to choose the range for |
||
26 | # formatting. This variable can either contain "<start line>:<end line>" or |
||
27 | # "all" to format the full file. So, to format the full file, write a function |
||
28 | # like: |
||
29 | # :function FormatFile() |
||
30 | # : let l:lines="all" |
||
31 | # : if has('python') |
||
32 | # : pyf <path-to-this-file>/clang-format.py |
||
33 | # : elseif has('python3') |
||
34 | # : py3f <path-to-this-file>/clang-format.py |
||
35 | # : endif |
||
36 | # :endfunction |
||
37 | # |
||
38 | # It operates on the current, potentially unsaved buffer and does not create |
||
39 | # or save any files. To revert a formatting, just undo. |
||
40 | from __future__ import absolute_import, division, print_function |
||
41 | |||
42 | import difflib |
||
43 | import json |
||
44 | import os.path |
||
45 | import platform |
||
46 | import subprocess |
||
47 | import sys |
||
48 | import vim |
||
49 | |||
50 | # set g:clang_format_path to the path to clang-format if it is not on the path |
||
51 | # Change this to the full path if clang-format is not on the path. |
||
52 | binary = 'clang-format' |
||
53 | if vim.eval('exists("g:clang_format_path")') == "1": |
||
54 | binary = vim.eval('g:clang_format_path') |
||
55 | |||
56 | # Change this to format according to other formatting styles. See the output of |
||
57 | # 'clang-format --help' for a list of supported styles. The default looks for |
||
58 | # a '.clang-format' or '_clang-format' file to indicate the style that should be |
||
59 | # used. |
||
60 | style = None |
||
61 | fallback_style = None |
||
62 | if vim.eval('exists("g:clang_format_fallback_style")') == "1": |
||
63 | fallback_style = vim.eval('g:clang_format_fallback_style') |
||
64 | |||
65 | def get_buffer(encoding): |
||
66 | if platform.python_version_tuple()[0] == '3': |
||
67 | return vim.current.buffer |
||
68 | return [ line.decode(encoding) for line in vim.current.buffer ] |
||
69 | |||
70 | def main(): |
||
71 | # Get the current text. |
||
72 | encoding = vim.eval("&encoding") |
||
73 | buf = get_buffer(encoding) |
||
74 | # Join the buffer into a single string with a terminating newline |
||
75 | text = ('\n'.join(buf) + '\n').encode(encoding) |
||
76 | |||
77 | # Determine range to format. |
||
78 | if vim.eval('exists("l:lines")') == '1': |
||
79 | lines = ['-lines', vim.eval('l:lines')] |
||
80 | elif vim.eval('exists("l:formatdiff")') == '1' and \ |
||
81 | os.path.exists(vim.current.buffer.name): |
||
82 | with open(vim.current.buffer.name, 'r') as f: |
||
83 | ondisk = f.read().splitlines(); |
||
84 | sequence = difflib.SequenceMatcher(None, ondisk, vim.current.buffer) |
||
85 | lines = [] |
||
86 | for op in reversed(sequence.get_opcodes()): |
||
87 | if op[0] not in ['equal', 'delete']: |
||
88 | lines += ['-lines', '%s:%s' % (op[3] + 1, op[4])] |
||
89 | if lines == []: |
||
90 | return |
||
91 | else: |
||
92 | lines = ['-lines', '%s:%s' % (vim.current.range.start + 1, |
||
93 | vim.current.range.end + 1)] |
||
94 | |||
95 | # Convert cursor (line, col) to bytes. |
||
96 | # Don't use line2byte: https://github.com/vim/vim/issues/5930 |
||
97 | _, cursor_line, cursor_col, _ = vim.eval('getpos(".")') # 1-based |
||
98 | cursor_byte = 0 |
||
99 | for line in text.split(b'\n')[:int(cursor_line) - 1]: |
||
100 | cursor_byte += len(line) + 1 |
||
101 | cursor_byte += int(cursor_col) - 1 |
||
102 | if cursor_byte < 0: |
||
103 | print('Couldn\'t determine cursor position. Is your file empty?') |
||
104 | return |
||
105 | |||
106 | # Avoid flashing an ugly, ugly cmd prompt on Windows when invoking clang-format. |
||
107 | startupinfo = None |
||
108 | if sys.platform.startswith('win32'): |
||
109 | startupinfo = subprocess.STARTUPINFO() |
||
110 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW |
||
111 | startupinfo.wShowWindow = subprocess.SW_HIDE |
||
112 | |||
113 | # Call formatter. |
||
114 | command = [binary, '-cursor', str(cursor_byte)] |
||
115 | if lines != ['-lines', 'all']: |
||
116 | command += lines |
||
117 | if style: |
||
118 | command.extend(['-style', style]) |
||
119 | if fallback_style: |
||
120 | command.extend(['-fallback-style', fallback_style]) |
||
121 | if vim.current.buffer.name: |
||
122 | command.extend(['-assume-filename', vim.current.buffer.name]) |
||
123 | p = subprocess.Popen(command, |
||
124 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
||
125 | stdin=subprocess.PIPE, startupinfo=startupinfo) |
||
126 | stdout, stderr = p.communicate(input=text) |
||
127 | |||
128 | # If successful, replace buffer contents. |
||
129 | if stderr: |
||
130 | print(stderr) |
||
131 | |||
132 | if not stdout: |
||
133 | print( |
||
134 | 'No output from clang-format (crashed?).\n' |
||
135 | 'Please report to bugs.llvm.org.' |
||
136 | ) |
||
137 | else: |
||
138 | header, content = stdout.split(b'\n', 1) |
||
139 | header = json.loads(header.decode('utf-8')) |
||
140 | # Strip off the trailing newline (added above). |
||
141 | # This maintains trailing empty lines present in the buffer if |
||
142 | # the -lines specification requests them to remain unchanged. |
||
143 | lines = content.decode(encoding).split('\n')[:-1] |
||
144 | sequence = difflib.SequenceMatcher(None, buf, lines) |
||
145 | for op in reversed(sequence.get_opcodes()): |
||
146 | if op[0] != 'equal': |
||
147 | vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]] |
||
148 | if header.get('IncompleteFormat'): |
||
149 | print('clang-format: incomplete (syntax errors)') |
||
150 | # Convert cursor bytes to (line, col) |
||
151 | # Don't use goto: https://github.com/vim/vim/issues/5930 |
||
152 | cursor_byte = int(header['Cursor']) |
||
153 | prefix = content[0:cursor_byte] |
||
154 | cursor_line = 1 + prefix.count(b'\n') |
||
155 | cursor_column = 1 + len(prefix.rsplit(b'\n', 1)[-1]) |
||
156 | vim.command('call cursor(%d, %d)' % (cursor_line, cursor_column)) |
||
157 | |||
158 | main() |