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