[LINT] Linted files + Added lint job to CI

This commit is contained in:
Gliniak
2024-03-12 08:59:10 +01:00
committed by Radosław Gliński
parent e8afad8f8a
commit b9061e6292
117 changed files with 1177 additions and 883 deletions

View File

@@ -1,121 +1,193 @@
#!/usr/bin/env python3
#
#===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#
# ===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#
#
# The LLVM Compiler Infrastructure
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
#
#===------------------------------------------------------------------------===#
r"""
ClangFormat Diff Reformatter
============================
# ===------------------------------------------------------------------------===#
"""
This script reads input from a unified diff and reformats all the changed
lines. This is useful to reformat all the lines touched by a specific patch.
Example usage for git/svn users:
git diff -U0 --no-color HEAD^ | clang-format-diff.py -p1 -i
svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i
git diff -U0 --no-color --relative HEAD^ | {clang_format_diff} -p1 -i
svn diff --diff-cmd=diff -x-U0 | {clang_format_diff} -i
It should be noted that the filename contained in the diff is used unmodified
to determine the source file to update. Users calling this script directly
should be careful to ensure that the path in the diff is correct relative to the
current working directory.
"""
from __future__ import absolute_import, division, print_function
import argparse
import difflib
import re
import string
import subprocess
import StringIO
import sys
if sys.version_info.major >= 3:
from io import StringIO
else:
from io import BytesIO as StringIO
def main():
parser = argparse.ArgumentParser(description=
'Reformat changed lines in diff. Without -i '
'option just output the diff that would be '
'introduced.')
parser.add_argument('-i', action='store_true', default=False,
help='apply edits to files instead of displaying a diff')
parser.add_argument('-p', metavar='NUM', default=0,
help='strip the smallest prefix containing P slashes')
parser.add_argument('-regex', metavar='PATTERN', default=None,
help='custom pattern selecting file paths to reformat '
'(case sensitive, overrides -iregex)')
parser.add_argument('-iregex', metavar='PATTERN', default=
r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc|js|ts|proto'
r'|protodevel|java)',
help='custom pattern selecting file paths to reformat '
'(case insensitive, overridden by -regex)')
parser.add_argument('-sort-includes', action='store_true', default=False,
help='let clang-format sort include blocks')
parser.add_argument('-v', '--verbose', action='store_true',
help='be more verbose, ineffective without -i')
parser.add_argument('-style',
help='formatting style to apply (LLVM, Google, Chromium, '
'Mozilla, WebKit)')
parser.add_argument('-binary', default='clang-format',
help='location of binary to use for clang-format')
args = parser.parse_args()
parser = argparse.ArgumentParser(
description=__doc__.format(clang_format_diff="%(prog)s"),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"-i",
action="store_true",
default=False,
help="apply edits to files instead of displaying a diff",
)
parser.add_argument(
"-p",
metavar="NUM",
default=0,
help="strip the smallest prefix containing P slashes",
)
parser.add_argument(
"-regex",
metavar="PATTERN",
default=None,
help="custom pattern selecting file paths to reformat "
"(case sensitive, overrides -iregex)",
)
parser.add_argument(
"-iregex",
metavar="PATTERN",
default=r".*\.(?:cpp|cc|c\+\+|cxx|cppm|ccm|cxxm|c\+\+m|c|cl|h|hh|hpp"
r"|hxx|m|mm|inc|js|ts|proto|protodevel|java|cs|json|s?vh?)",
help="custom pattern selecting file paths to reformat "
"(case insensitive, overridden by -regex)",
)
parser.add_argument(
"-sort-includes",
action="store_true",
default=False,
help="let clang-format sort include blocks",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="be more verbose, ineffective without -i",
)
parser.add_argument(
"-style",
help="formatting style to apply (LLVM, GNU, Google, Chromium, "
"Microsoft, Mozilla, WebKit)",
)
parser.add_argument(
"-fallback-style",
help="The name of the predefined style used as a"
"fallback in case clang-format is invoked with"
"-style=file, but can not find the .clang-format"
"file to use.",
)
parser.add_argument(
"-binary",
default="clang-format",
help="location of binary to use for clang-format",
)
args = parser.parse_args()
# Extract changed lines for each file.
filename = None
lines_by_file = {}
for line in sys.stdin:
match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
if match:
filename = match.group(2)
if filename == None:
continue
# Extract changed lines for each file.
filename = None
lines_by_file = {}
for line in sys.stdin:
match = re.search(r"^\+\+\+\ (.*?/){%s}(\S*)" % args.p, line)
if match:
filename = match.group(2)
if filename is None:
continue
if args.regex is not None:
if not re.match('^%s$' % args.regex, filename):
continue
else:
if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
continue
if args.regex is not None:
if not re.match("^%s$" % args.regex, filename):
continue
else:
if not re.match("^%s$" % args.iregex, filename, re.IGNORECASE):
continue
match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
if match:
start_line = int(match.group(1))
line_count = 1
if match.group(3):
line_count = int(match.group(3))
if line_count == 0:
continue
end_line = start_line + line_count - 1;
lines_by_file.setdefault(filename, []).extend(
['-lines', str(start_line) + ':' + str(end_line)])
match = re.search(r"^@@.*\+(\d+)(?:,(\d+))?", line)
if match:
start_line = int(match.group(1))
line_count = 1
if match.group(2):
line_count = int(match.group(2))
# The input is something like
#
# @@ -1, +0,0 @@
#
# which means no lines were added.
if line_count == 0:
continue
# Also format lines range if line_count is 0 in case of deleting
# surrounding statements.
end_line = start_line
if line_count != 0:
end_line += line_count - 1
lines_by_file.setdefault(filename, []).extend(
["-lines", str(start_line) + ":" + str(end_line)]
)
# Reformat files containing changes in place.
for filename, lines in lines_by_file.iteritems():
if args.i and args.verbose:
print 'Formatting', filename
command = [args.binary, filename]
if args.i:
command.append('-i')
if args.sort_includes:
command.append('-sort-includes')
command.extend(lines)
if args.style:
command.extend(['-style', args.style])
p = subprocess.Popen(command, stdout=subprocess.PIPE,
stderr=None, stdin=subprocess.PIPE)
stdout, stderr = p.communicate()
if p.returncode != 0:
sys.exit(p.returncode);
# Reformat files containing changes in place.
for filename, lines in lines_by_file.items():
if args.i and args.verbose:
print("Formatting {}".format(filename))
command = [args.binary, filename]
if args.i:
command.append("-i")
if args.sort_includes:
command.append("-sort-includes")
command.extend(lines)
if args.style:
command.extend(["-style", args.style])
if args.fallback_style:
command.extend(["-fallback-style", args.fallback_style])
if not args.i:
with open(filename) as f:
code = f.readlines()
formatted_code = StringIO.StringIO(stdout).readlines()
diff = difflib.unified_diff(code, formatted_code,
filename, filename,
'(before formatting)', '(after formatting)')
diff_string = string.join(diff, '')
if len(diff_string) > 0:
sys.stdout.write(diff_string)
try:
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=None,
stdin=subprocess.PIPE,
universal_newlines=True,
)
except OSError as e:
# Give the user more context when clang-format isn't
# found/isn't executable, etc.
raise RuntimeError(
'Failed to run "%s" - %s"' % (" ".join(command), e.strerror)
)
if __name__ == '__main__':
main()
stdout, stderr = p.communicate()
if p.returncode != 0:
sys.exit(p.returncode)
if not args.i:
with open(filename) as f:
code = f.readlines()
formatted_code = StringIO(stdout).readlines()
diff = difflib.unified_diff(
code,
formatted_code,
filename,
filename,
"(before formatting)",
"(after formatting)",
)
diff_string = "".join(diff)
if len(diff_string) > 0:
sys.stdout.write(diff_string)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -12,47 +12,62 @@
# It operates on the current, potentially unsaved buffer and does not create
# or save any files. To revert a formatting, just undo.
from __future__ import print_function
from __future__ import absolute_import, division, print_function
import sublime
import sublime_plugin
import subprocess
# Change this to the full path if clang-format is not on the path.
binary = 'clang-format'
binary = "clang-format"
# Change this to format according to other formatting styles. See the output of
# 'clang-format --help' for a list of supported styles. The default looks for
# a '.clang-format' or '_clang-format' file to indicate the style that should be
# used.
style = 'file'
style = None
class ClangFormatCommand(sublime_plugin.TextCommand):
def run(self, edit):
encoding = self.view.encoding()
if encoding == 'Undefined':
encoding = 'utf-8'
regions = []
command = [binary, '-style', style]
for region in self.view.sel():
regions.append(region)
region_offset = min(region.a, region.b)
region_length = abs(region.b - region.a)
command.extend(['-offset', str(region_offset),
'-length', str(region_length),
'-assume-filename', str(self.view.file_name())])
old_viewport_position = self.view.viewport_position()
buf = self.view.substr(sublime.Region(0, self.view.size()))
p = subprocess.Popen(command, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stdin=subprocess.PIPE)
output, error = p.communicate(buf.encode(encoding))
if error:
print(error)
self.view.replace(
edit, sublime.Region(0, self.view.size()),
output.decode(encoding))
self.view.sel().clear()
for region in regions:
self.view.sel().add(region)
# FIXME: Without the 10ms delay, the viewport sometimes jumps.
sublime.set_timeout(lambda: self.view.set_viewport_position(
old_viewport_position, False), 10)
def run(self, edit):
encoding = self.view.encoding()
if encoding == "Undefined":
encoding = "utf-8"
regions = []
command = [binary]
if style:
command.extend(["-style", style])
for region in self.view.sel():
regions.append(region)
region_offset = min(region.a, region.b)
region_length = abs(region.b - region.a)
command.extend(
[
"-offset",
str(region_offset),
"-length",
str(region_length),
"-assume-filename",
str(self.view.file_name()),
]
)
old_viewport_position = self.view.viewport_position()
buf = self.view.substr(sublime.Region(0, self.view.size()))
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
)
output, error = p.communicate(buf.encode(encoding))
if error:
print(error)
self.view.replace(
edit, sublime.Region(0, self.view.size()), output.decode(encoding)
)
self.view.sel().clear()
for region in regions:
self.view.sel().add(region)
# FIXME: Without the 10ms delay, the viewport sometimes jumps.
sublime.set_timeout(
lambda: self.view.set_viewport_position(old_viewport_position, False), 10
)

View File

@@ -1,7 +1,9 @@
;;; clang-format.el --- Format code using clang-format -*- lexical-binding: t; -*-
;; Version: 0.1.0
;; Keywords: tools, c
;; Package-Requires: ((cl-lib "0.3"))
;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
;;; Commentary:
@@ -45,17 +47,29 @@ A string containing the name or the full path of the executable."
:type '(file :must-match t)
:risky t)
(defcustom clang-format-style "file"
(defcustom clang-format-style nil
"Style argument to pass to clang-format.
By default clang-format will load the style configuration from
a file named .clang-format located in one of the parent directories
of the buffer."
:group 'clang-format
:type 'string
:type '(choice (string) (const nil))
:safe #'stringp)
(make-variable-buffer-local 'clang-format-style)
(defcustom clang-format-fallback-style "none"
"Fallback style to pass to clang-format.
This style will be used if clang-format-style is set to \"file\"
and no .clang-format is found in the directory of the buffer or
one of parent directories. Set to \"none\" to disable formatting
in such buffers."
:group 'clang-format
:type 'string
:safe #'stringp)
(make-variable-buffer-local 'clang-format-fallback-style)
(defun clang-format--extract (xml-node)
"Extract replacements and cursor information from XML-NODE."
(unless (and (listp xml-node) (eq (xml-node-name xml-node) 'replacements))
@@ -69,7 +83,7 @@ of the buffer."
(let* ((children (xml-node-children node))
(text (car children)))
(cl-case (xml-node-name node)
('replacement
(replacement
(let* ((offset (xml-get-attribute-or-nil node 'offset))
(length (xml-get-attribute-or-nil node 'length)))
(when (or (null offset) (null length))
@@ -80,7 +94,7 @@ of the buffer."
(setq offset (string-to-number offset))
(setq length (string-to-number length))
(push (list offset length text) replacements)))
('cursor
(cursor
(setq cursor (string-to-number text)))))))
;; Sort by decreasing offset, length.
@@ -119,10 +133,12 @@ is a zero-based file offset, assuming utf-8-unix coding."
(byte-to-position (1+ byte)))))
;;;###autoload
(defun clang-format-region (start end &optional style)
(defun clang-format-region (start end &optional style assume-file-name)
"Use clang-format to format the code between START and END according to STYLE.
If called interactively uses the region or the current statement if there
is no active region. If no style is given uses `clang-format-style'."
If called interactively uses the region or the current statement if there is no
no active region. If no STYLE is given uses `clang-format-style'. Use
ASSUME-FILE-NAME to locate a style config file, if no ASSUME-FILE-NAME is given
uses the function `buffer-file-name'."
(interactive
(if (use-region-p)
(list (region-beginning) (region-end))
@@ -131,6 +147,9 @@ is no active region. If no style is given uses `clang-format-style'."
(unless style
(setq style clang-format-style))
(unless assume-file-name
(setq assume-file-name (buffer-file-name (buffer-base-buffer))))
(let ((file-start (clang-format--bufferpos-to-filepos start 'approximate
'utf-8-unix))
(file-end (clang-format--bufferpos-to-filepos end 'approximate
@@ -144,16 +163,22 @@ is no active region. If no style is given uses `clang-format-style'."
;; always use utf-8-unix and ignore the buffer coding system.
(default-process-coding-system '(utf-8-unix . utf-8-unix)))
(unwind-protect
(let ((status (call-process-region
nil nil clang-format-executable
nil `(,temp-buffer ,temp-file) nil
"-output-replacements-xml"
"-assume-filename" (or (buffer-file-name) "")
"-style" style
"-offset" (number-to-string file-start)
"-length" (number-to-string (- file-end file-start))
"-cursor" (number-to-string cursor)))
(let ((status (apply #'call-process-region
nil nil clang-format-executable
nil `(,temp-buffer ,temp-file) nil
`("-output-replacements-xml"
;; Guard against a nil assume-file-name.
;; If the clang-format option -assume-filename
;; is given a blank string it will crash as per
;; the following bug report
;; https://bugs.llvm.org/show_bug.cgi?id=34667
,@(and assume-file-name
(list "-assume-filename" assume-file-name))
,@(and style (list "-style" style))
"-fallback-style" ,clang-format-fallback-style
"-offset" ,(number-to-string file-start)
"-length" ,(number-to-string (- file-end file-start))
"-cursor" ,(number-to-string cursor))))
(stderr (with-temp-buffer
(unless (zerop (cadr (insert-file-contents temp-file)))
(insert ": "))
@@ -181,10 +206,13 @@ is no active region. If no style is given uses `clang-format-style'."
(when (buffer-name temp-buffer) (kill-buffer temp-buffer)))))
;;;###autoload
(defun clang-format-buffer (&optional style)
"Use clang-format to format the current buffer according to STYLE."
(defun clang-format-buffer (&optional style assume-file-name)
"Use clang-format to format the current buffer according to STYLE.
If no STYLE is given uses `clang-format-style'. Use ASSUME-FILE-NAME
to locate a style config file. If no ASSUME-FILE-NAME is given uses
the function `buffer-file-name'."
(interactive)
(clang-format-region (point-min) (point-max) style))
(clang-format-region (point-min) (point-max) style assume-file-name))
;;;###autoload
(defalias 'clang-format 'clang-format-region)

View File

@@ -2,11 +2,19 @@
# - Change 'binary' if clang-format is not on the path (see below).
# - Add to your .vimrc:
#
# map <C-I> :pyf <path-to-this-file>/clang-format.py<cr>
# imap <C-I> <c-o>:pyf <path-to-this-file>/clang-format.py<cr>
# if has('python')
# map <C-I> :pyf <path-to-this-file>/clang-format.py<cr>
# imap <C-I> <c-o>:pyf <path-to-this-file>/clang-format.py<cr>
# elseif has('python3')
# map <C-I> :py3f <path-to-this-file>/clang-format.py<cr>
# imap <C-I> <c-o>:py3f <path-to-this-file>/clang-format.py<cr>
# endif
#
# The first line enables clang-format for NORMAL and VISUAL mode, the second
# line adds support for INSERT mode. Change "C-I" to another binding if you
# The if-elseif-endif conditional should pick either the python3 or python2
# integration depending on your vim setup.
#
# The first mapping enables clang-format for NORMAL and VISUAL mode, the second
# mapping adds support for INSERT mode. Change "C-I" to another binding if you
# need clang-format on a different key (C-I stands for Ctrl+i).
#
# With this integration you can press the bound key and clang-format will
@@ -20,15 +28,20 @@
# like:
# :function FormatFile()
# : let l:lines="all"
# : pyf <path-to-this-file>/clang-format.py
# : if has('python')
# : pyf <path-to-this-file>/clang-format.py
# : elseif has('python3')
# : py3f <path-to-this-file>/clang-format.py
# : endif
# :endfunction
#
# It operates on the current, potentially unsaved buffer and does not create
# or save any files. To revert a formatting, just undo.
from __future__ import print_function
from __future__ import absolute_import, division, print_function
import difflib
import json
import os.path
import platform
import subprocess
import sys
@@ -36,92 +49,120 @@ import vim
# set g:clang_format_path to the path to clang-format if it is not on the path
# Change this to the full path if clang-format is not on the path.
binary = 'clang-format'
binary = "clang-format"
if vim.eval('exists("g:clang_format_path")') == "1":
binary = vim.eval('g:clang_format_path')
binary = vim.eval("g:clang_format_path")
# Change this to format according to other formatting styles. See the output of
# 'clang-format --help' for a list of supported styles. The default looks for
# a '.clang-format' or '_clang-format' file to indicate the style that should be
# used.
style = 'file'
style = None
fallback_style = None
if vim.eval('exists("g:clang_format_fallback_style")') == "1":
fallback_style = vim.eval('g:clang_format_fallback_style')
fallback_style = vim.eval("g:clang_format_fallback_style")
def get_buffer(encoding):
if platform.python_version_tuple()[0] == '3':
return vim.current.buffer
return [ line.decode(encoding) for line in vim.current.buffer ]
if platform.python_version_tuple()[0] == "3":
return vim.current.buffer
return [line.decode(encoding) for line in vim.current.buffer]
def main():
# Get the current text.
encoding = vim.eval("&encoding")
buf = get_buffer(encoding)
text = '\n'.join(buf)
# Get the current text.
encoding = vim.eval("&encoding")
buf = get_buffer(encoding)
# Join the buffer into a single string with a terminating newline
text = ("\n".join(buf) + "\n").encode(encoding)
# Determine range to format.
if vim.eval('exists("l:lines")') == '1':
lines = vim.eval('l:lines')
elif vim.eval('exists("l:formatdiff")') == '1':
with open(vim.current.buffer.name, 'r') as f:
ondisk = f.read().splitlines();
sequence = difflib.SequenceMatcher(None, ondisk, vim.current.buffer)
lines = []
for op in reversed(sequence.get_opcodes()):
if op[0] not in ['equal', 'delete']:
lines += ['-lines', '%s:%s' % (op[3] + 1, op[4])]
if lines == []:
return
else:
lines = ['-lines', '%s:%s' % (vim.current.range.start + 1,
vim.current.range.end + 1)]
# Determine range to format.
if vim.eval('exists("l:lines")') == "1":
lines = ["-lines", vim.eval("l:lines")]
elif vim.eval('exists("l:formatdiff")') == "1" and os.path.exists(
vim.current.buffer.name
):
with open(vim.current.buffer.name, "r") as f:
ondisk = f.read().splitlines()
sequence = difflib.SequenceMatcher(None, ondisk, vim.current.buffer)
lines = []
for op in reversed(sequence.get_opcodes()):
if op[0] not in ["equal", "delete"]:
lines += ["-lines", "%s:%s" % (op[3] + 1, op[4])]
if lines == []:
return
else:
lines = [
"-lines",
"%s:%s" % (vim.current.range.start + 1, vim.current.range.end + 1),
]
# Determine the cursor position.
cursor = int(vim.eval('line2byte(line("."))+col(".")')) - 2
if cursor < 0:
print('Couldn\'t determine cursor position. Is your file empty?')
return
# Convert cursor (line, col) to bytes.
# Don't use line2byte: https://github.com/vim/vim/issues/5930
_, cursor_line, cursor_col, _ = vim.eval('getpos(".")') # 1-based
cursor_byte = 0
for line in text.split(b"\n")[: int(cursor_line) - 1]:
cursor_byte += len(line) + 1
cursor_byte += int(cursor_col) - 1
if cursor_byte < 0:
print("Couldn't determine cursor position. Is your file empty?")
return
# Avoid flashing an ugly, ugly cmd prompt on Windows when invoking clang-format.
startupinfo = None
if sys.platform.startswith('win32'):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# Avoid flashing an ugly, ugly cmd prompt on Windows when invoking clang-format.
startupinfo = None
if sys.platform.startswith("win32"):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# Call formatter.
command = [binary, '-style', style, '-cursor', str(cursor)]
if lines != 'all':
command += lines
if fallback_style:
command.extend(['-fallback-style', fallback_style])
if vim.current.buffer.name:
command.extend(['-assume-filename', vim.current.buffer.name])
p = subprocess.Popen(command,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE, startupinfo=startupinfo)
stdout, stderr = p.communicate(input=text.encode(encoding))
# If successful, replace buffer contents.
if stderr:
print(stderr)
if not stdout:
print(
'No output from clang-format (crashed?).\n'
'Please report to bugs.llvm.org.'
# Call formatter.
command = [binary, "-cursor", str(cursor_byte)]
if lines != ["-lines", "all"]:
command += lines
if style:
command.extend(["-style", style])
if fallback_style:
command.extend(["-fallback-style", fallback_style])
if vim.current.buffer.name:
command.extend(["-assume-filename", vim.current.buffer.name])
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=startupinfo,
)
else:
lines = stdout.decode(encoding).split('\n')
output = json.loads(lines[0])
lines = lines[1:]
sequence = difflib.SequenceMatcher(None, buf, lines)
for op in reversed(sequence.get_opcodes()):
if op[0] is not 'equal':
vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]]
if output.get('IncompleteFormat'):
print('clang-format: incomplete (syntax errors)')
vim.command('goto %d' % (output['Cursor'] + 1))
stdout, stderr = p.communicate(input=text)
# If successful, replace buffer contents.
if stderr:
print(stderr)
if not stdout:
print(
"No output from clang-format (crashed?).\n"
"Please report to bugs.llvm.org."
)
else:
header, content = stdout.split(b"\n", 1)
header = json.loads(header.decode("utf-8"))
# Strip off the trailing newline (added above).
# This maintains trailing empty lines present in the buffer if
# the -lines specification requests them to remain unchanged.
lines = content.decode(encoding).split("\n")[:-1]
sequence = difflib.SequenceMatcher(None, buf, lines)
for op in reversed(sequence.get_opcodes()):
if op[0] != "equal":
vim.current.buffer[op[1] : op[2]] = lines[op[3] : op[4]]
if header.get("IncompleteFormat"):
print("clang-format: incomplete (syntax errors)")
# Convert cursor bytes to (line, col)
# Don't use goto: https://github.com/vim/vim/issues/5930
cursor_byte = int(header["Cursor"])
prefix = content[0:cursor_byte]
cursor_line = 1 + prefix.count(b"\n")
cursor_column = 1 + len(prefix.rsplit(b"\n", 1)[-1])
vim.command("call cursor(%d, %d)" % (cursor_line, cursor_column))
main()

View File

@@ -2,28 +2,27 @@
#
#===- git-clang-format - ClangFormat Git Integration ---------*- python -*--===#
#
# The LLVM Compiler Infrastructure
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
#===------------------------------------------------------------------------===#
r"""
clang-format git integration
============================
This file provides a clang-format integration for git. Put it somewhere in your
path and ensure that it is executable. Then, "git clang-format" will invoke
clang-format on the changes in current files or a specific commit.
For further details, run:
git clang-format -h
Requires Python 2.7 or Python 3
"""
r"""
clang-format git integration
============================
from __future__ import print_function
This file provides a clang-format integration for git. Put it somewhere in your
path and ensure that it is executable. Then, "git clang-format" will invoke
clang-format on the changes in current files or a specific commit.
For further details, run:
git clang-format -h
Requires Python 2.7 or Python 3
"""
from __future__ import absolute_import, division, print_function
import argparse
import collections
import contextlib
@@ -33,12 +32,23 @@ import re
import subprocess
import sys
usage = 'git clang-format [OPTIONS] [<commit>] [<commit>] [--] [<file>...]'
usage = ('git clang-format [OPTIONS] [<commit>] [<commit>|--staged] '
'[--] [<file>...]')
desc = '''
If zero or one commits are given, run clang-format on all lines that differ
between the working directory and <commit>, which defaults to HEAD. Changes are
only applied to the working directory.
only applied to the working directory, or in the stage/index.
Examples:
To format staged changes, i.e everything that's been `git add`ed:
git clang-format
To also format everything touched in the most recent commit:
git clang-format HEAD~1
If you're on a branch off main, to format everything touched on your branch:
git clang-format main
If two commits are given (requires --diff), run clang-format on all lines in the
second <commit> that differ from the first <commit>.
@@ -46,7 +56,7 @@ second <commit> that differ from the first <commit>.
The following git-config settings set the default of the corresponding option:
clangFormat.binary
clangFormat.commit
clangFormat.extension
clangFormat.extensions
clangFormat.style
'''
@@ -78,12 +88,17 @@ def main():
'c', 'h', # C
'm', # ObjC
'mm', # ObjC++
'cc', 'cp', 'cpp', 'c++', 'cxx', 'hpp', # C++
'cc', 'cp', 'cpp', 'c++', 'cxx', 'hh', 'hpp', 'hxx', 'inc', # C++
'ccm', 'cppm', 'cxxm', 'c++m', # C++ Modules
'cu', 'cuh', # CUDA
# Other languages that clang-format supports
'proto', 'protodevel', # Protocol Buffers
'java', # Java
'js', # JavaScript
'ts', # TypeScript
'cs', # C Sharp
'json', # Json
'sv', 'svh', 'v', 'vh', # Verilog
])
p = argparse.ArgumentParser(
@@ -97,6 +112,8 @@ def main():
help='default commit to use if none is specified'),
p.add_argument('--diff', action='store_true',
help='print a diff instead of applying the changes')
p.add_argument('--diffstat', action='store_true',
help='print a diffstat instead of applying the changes')
p.add_argument('--extensions',
default=config.get('clangformat.extensions',
default_extensions),
@@ -108,11 +125,17 @@ def main():
help='select hunks interactively')
p.add_argument('-q', '--quiet', action='count', default=0,
help='print less information')
p.add_argument('--staged', '--cached', action='store_true',
help='format lines in the stage instead of the working dir')
p.add_argument('--style',
default=config.get('clangformat.style', None),
help='passed to clang-format'),
p.add_argument('-v', '--verbose', action='count', default=0,
help='print extra information')
p.add_argument('--diff_from_common_commit', action='store_true',
help=('diff from the last common commit for commits in '
'separate branches rather than the exact point of the '
'commits'))
# We gather all the remaining positional arguments into 'args' since we need
# to use some heuristics to determine whether or not <commit> was present.
# However, to print pretty messages, we make use of metavar and help.
@@ -126,58 +149,82 @@ def main():
del opts.quiet
commits, files = interpret_args(opts.args, dash_dash, opts.commit)
if len(commits) > 1:
if len(commits) > 2:
die('at most two commits allowed; %d given' % len(commits))
if len(commits) == 2:
if opts.staged:
die('--staged is not allowed when two commits are given')
if not opts.diff:
die('--diff is required when two commits are given')
else:
if len(commits) > 2:
die('at most two commits allowed; %d given' % len(commits))
changed_lines = compute_diff_and_extract_lines(commits, files)
elif opts.diff_from_common_commit:
die('--diff_from_common_commit is only allowed when two commits are given')
if os.path.dirname(opts.binary):
opts.binary = os.path.abspath(opts.binary)
changed_lines = compute_diff_and_extract_lines(commits,
files,
opts.staged,
opts.diff_from_common_commit)
if opts.verbose >= 1:
ignored_files = set(changed_lines)
filter_by_extension(changed_lines, opts.extensions.lower().split(','))
# The computed diff outputs absolute paths, so we must cd before accessing
# those files.
cd_to_toplevel()
filter_symlinks(changed_lines)
if opts.verbose >= 1:
ignored_files.difference_update(changed_lines)
if ignored_files:
print('Ignoring changes in the following files (wrong extension):')
print(
'Ignoring changes in the following files (wrong extension or symlink):')
for filename in ignored_files:
print(' %s' % filename)
if changed_lines:
print('Running clang-format on the following files:')
for filename in changed_lines:
print(' %s' % filename)
if not changed_lines:
print('no modified files to format')
return
# The computed diff outputs absolute paths, so we must cd before accessing
# those files.
cd_to_toplevel()
if opts.verbose >= 0:
print('no modified files to format')
return 0
if len(commits) > 1:
old_tree = commits[1]
new_tree = run_clang_format_and_save_to_tree(changed_lines,
revision=commits[1],
binary=opts.binary,
style=opts.style)
revision = old_tree
elif opts.staged:
old_tree = create_tree_from_index(changed_lines)
revision = ''
else:
old_tree = create_tree_from_workdir(changed_lines)
new_tree = run_clang_format_and_save_to_tree(changed_lines,
binary=opts.binary,
style=opts.style)
revision = None
new_tree = run_clang_format_and_save_to_tree(changed_lines,
revision,
binary=opts.binary,
style=opts.style)
if opts.verbose >= 1:
print('old tree: %s' % old_tree)
print('new tree: %s' % new_tree)
if old_tree == new_tree:
if opts.verbose >= 0:
print('clang-format did not modify any files')
elif opts.diff:
print_diff(old_tree, new_tree)
else:
changed_files = apply_changes(old_tree, new_tree, force=opts.force,
patch_mode=opts.patch)
if (opts.verbose >= 0 and not opts.patch) or opts.verbose >= 1:
print('changed files:')
for filename in changed_files:
print(' %s' % filename)
return 0
if opts.diff:
return print_diff(old_tree, new_tree)
if opts.diffstat:
return print_diffstat(old_tree, new_tree)
changed_files = apply_changes(old_tree, new_tree, force=opts.force,
patch_mode=opts.patch)
if (opts.verbose >= 0 and not opts.patch) or opts.verbose >= 1:
print('changed files:')
for filename in changed_files:
print(' %s' % filename)
return 1
def load_git_config(non_string_options=None):
@@ -191,7 +238,12 @@ def load_git_config(non_string_options=None):
out = {}
for entry in run('git', 'config', '--list', '--null').split('\0'):
if entry:
name, value = entry.split('\n', 1)
if '\n' in entry:
name, value = entry.split('\n', 1)
else:
# A setting with no '=' ('\n' with --null) is implicitly 'true'
name = entry
value = 'true'
if name in non_string_options:
value = run('git', 'config', non_string_options[name], name)
out[name] = value
@@ -261,9 +313,9 @@ def get_object_type(value):
return convert_string(stdout.strip())
def compute_diff_and_extract_lines(commits, files):
def compute_diff_and_extract_lines(commits, files, staged, diff_common_commit):
"""Calls compute_diff() followed by extract_lines()."""
diff_process = compute_diff(commits, files)
diff_process = compute_diff(commits, files, staged, diff_common_commit)
changed_lines = extract_lines(diff_process.stdout)
diff_process.stdout.close()
diff_process.wait()
@@ -273,17 +325,24 @@ def compute_diff_and_extract_lines(commits, files):
return changed_lines
def compute_diff(commits, files):
def compute_diff(commits, files, staged, diff_common_commit):
"""Return a subprocess object producing the diff from `commits`.
The return value's `stdin` file object will produce a patch with the
differences between the working directory and the first commit if a single
one was specified, or the difference between both specified commits, filtered
on `files` (if non-empty). Zero context lines are used in the patch."""
differences between the working directory (or stage if --staged is used) and
the first commit if a single one was specified, or the difference between
both specified commits, filtered on `files` (if non-empty).
Zero context lines are used in the patch."""
git_tool = 'diff-index'
if len(commits) > 1:
extra_args = []
if len(commits) == 2:
git_tool = 'diff-tree'
cmd = ['git', git_tool, '-p', '-U0'] + commits + ['--']
if diff_common_commit:
commits = [f'{commits[0]}...{commits[1]}']
elif staged:
extra_args += ['--cached']
cmd = ['git', git_tool, '-p', '-U0'] + extra_args + commits + ['--']
cmd.extend(files)
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdin.close()
@@ -304,15 +363,18 @@ def extract_lines(patch_file):
line = convert_string(line)
match = re.search(r'^\+\+\+\ [^/]+/(.*)', line)
if match:
filename = match.group(1).rstrip('\r\n')
filename = match.group(1).rstrip('\r\n\t')
match = re.search(r'^@@ -[0-9,]+ \+(\d+)(,(\d+))?', line)
if match:
start_line = int(match.group(1))
line_count = 1
if match.group(3):
line_count = int(match.group(3))
if line_count > 0:
matches.setdefault(filename, []).append(Range(start_line, line_count))
if line_count == 0:
line_count = 1
if start_line == 0:
continue
matches.setdefault(filename, []).append(Range(start_line, line_count))
return matches
@@ -330,6 +392,13 @@ def filter_by_extension(dictionary, allowed_extensions):
del dictionary[filename]
def filter_symlinks(dictionary):
"""Delete every key in `dictionary` that is a symlink."""
for filename in list(dictionary.keys()):
if os.path.islink(filename):
del dictionary[filename]
def cd_to_toplevel():
"""Change to the top level of the git repository."""
toplevel = run('git', 'rev-parse', '--show-toplevel')
@@ -343,11 +412,29 @@ def create_tree_from_workdir(filenames):
return create_tree(filenames, '--stdin')
def create_tree_from_index(filenames):
# Copy the environment, because the files have to be read from the original
# index.
env = os.environ.copy()
def index_contents_generator():
for filename in filenames:
git_ls_files_cmd = ['git', 'ls-files', '--stage', '-z', '--', filename]
git_ls_files = subprocess.Popen(git_ls_files_cmd, env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
stdout = git_ls_files.communicate()[0]
yield convert_string(stdout.split(b'\0')[0])
return create_tree(index_contents_generator(), '--index-info')
def run_clang_format_and_save_to_tree(changed_lines, revision=None,
binary='clang-format', style=None):
"""Run clang-format on each file and save the result to a git tree.
Returns the object ID (SHA-1) of the created tree."""
# Copy the environment when formatting the files in the index, because the
# files have to be read from the original index.
env = os.environ.copy() if revision == '' else None
def iteritems(container):
try:
return container.iteritems() # Python 2
@@ -355,11 +442,15 @@ def run_clang_format_and_save_to_tree(changed_lines, revision=None,
return container.items() # Python 3
def index_info_generator():
for filename, line_ranges in iteritems(changed_lines):
if revision:
git_metadata_cmd = ['git', 'ls-tree',
'%s:%s' % (revision, os.path.dirname(filename)),
os.path.basename(filename)]
git_metadata = subprocess.Popen(git_metadata_cmd, stdin=subprocess.PIPE,
if revision is not None:
if len(revision) > 0:
git_metadata_cmd = ['git', 'ls-tree',
'%s:%s' % (revision, os.path.dirname(filename)),
os.path.basename(filename)]
else:
git_metadata_cmd = ['git', 'ls-files', '--stage', '--', filename]
git_metadata = subprocess.Popen(git_metadata_cmd, env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
stdout = git_metadata.communicate()[0]
mode = oct(int(stdout.split()[0], 8))
@@ -371,7 +462,8 @@ def run_clang_format_and_save_to_tree(changed_lines, revision=None,
blob_id = clang_format_to_blob(filename, line_ranges,
revision=revision,
binary=binary,
style=style)
style=style,
env=env)
yield '%s %s\t%s' % (mode, blob_id, filename)
return create_tree(index_info_generator(), '--index-info')
@@ -397,11 +489,12 @@ def create_tree(input_lines, mode):
def clang_format_to_blob(filename, line_ranges, revision=None,
binary='clang-format', style=None):
binary='clang-format', style=None, env=None):
"""Run clang-format on the given file and save the result to a git blob.
Runs on the file in `revision` if not None, or on the file in the working
directory if `revision` is None.
directory if `revision` is None. Revision can be set to an empty string to run
clang-format on the file in the index.
Returns the object ID (SHA-1) of the created blob."""
clang_format_cmd = [binary]
@@ -410,10 +503,10 @@ def clang_format_to_blob(filename, line_ranges, revision=None,
clang_format_cmd.extend([
'-lines=%s:%s' % (start_line, start_line+line_count-1)
for start_line, line_count in line_ranges])
if revision:
if revision is not None:
clang_format_cmd.extend(['-assume-filename='+filename])
git_show_cmd = ['git', 'cat-file', 'blob', '%s:%s' % (revision, filename)]
git_show = subprocess.Popen(git_show_cmd, stdin=subprocess.PIPE,
git_show = subprocess.Popen(git_show_cmd, env=env, stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
git_show.stdin.close()
clang_format_stdin = git_show.stdout
@@ -485,9 +578,20 @@ def print_diff(old_tree, new_tree):
# We also only print modified files since `new_tree` only contains the files
# that were modified, so unmodified files would show as deleted without the
# filter.
subprocess.check_call(['git', 'diff', '--diff-filter=M', old_tree, new_tree,
'--'])
return subprocess.run(['git', 'diff', '--diff-filter=M',
'--exit-code', old_tree, new_tree]).returncode
def print_diffstat(old_tree, new_tree):
"""Print the diffstat between the two trees to stdout."""
# We use the porcelain 'diff' and not plumbing 'diff-tree' because the output
# is expected to be viewed by the user, and only the former does nice things
# like color and pagination.
#
# We also only print modified files since `new_tree` only contains the files
# that were modified, so unmodified files would show as deleted without the
# filter.
return subprocess.run(['git', 'diff', '--diff-filter=M', '--exit-code',
'--stat', old_tree, new_tree]).returncode
def apply_changes(old_tree, new_tree, force=False, patch_mode=False):
"""Apply the changes in `new_tree` to the working directory.
@@ -513,16 +617,16 @@ def apply_changes(old_tree, new_tree, force=False, patch_mode=False):
# better message, "Apply ... to index and worktree". This is not quite
# right, since it won't be applied to the user's index, but oh well.
with temporary_index_file(old_tree):
subprocess.check_call(['git', 'checkout', '--patch', new_tree])
subprocess.run(['git', 'checkout', '--patch', new_tree], check=True)
index_tree = old_tree
else:
with temporary_index_file(new_tree):
run('git', 'checkout-index', '-a', '-f')
run('git', 'checkout-index', '-f', '--', *changed_files)
return changed_files
def run(*args, **kwargs):
stdin = kwargs.pop('stdin', to_bytes(''))
stdin = kwargs.pop('stdin', '')
verbose = kwargs.pop('verbose', True)
strip = kwargs.pop('strip', True)
for name in kwargs:
@@ -576,4 +680,4 @@ def convert_string(bytes_input):
return str(bytes_input)
if __name__ == '__main__':
main()
sys.exit(main())