userdiff: add support for Swift

Add a built-in userdiff driver for the Swift programming language so that diff hunk headers and word diffs work out of the box for ".swift" files. The funcname pattern is built for Swift's own declaration grammar: an optional run of attributes ("@objc", "@available(iOS 13, *)", ...), followed by an optional run of lowercase modifiers ("public", "static", "final", ...), followed by a declaration keyword (func, class, struct, enum, protocol, extension, actor, init, deinit, subscript). The keyword is followed by a boundary that allows whitespace, "(" (init/subscript), "?" or "!" (failable init), or "<" (generics), while still acting as a word boundary so e.g. "initialize(" does not match. The word regex recognizes Swift identifiers, hexadecimal, octal, binary, integer and floating-point literals, and the language's operators. Signed-off-by: Shlok Kulshreshtha <diy2903@gmail.com> Acked-by: Johannes Sixt <j6t@kdbg.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Shlok Kulshreshtha committed Jul 21, 2026 at 12:27 UTC 113d62b141a80bf136268df51b4597dbec4355d0
15 files changed +86
Documentation/gitattributes.adoc
+2
@@ -914,6 +914,8 @@ patterns are available:
914 - `scheme` suitable for source code in most Lisp dialects,
915 including Scheme, Emacs Lisp, Common Lisp, and Clojure.
916
917 +- `swift` suitable for source code in the Swift language.
918 +
919 - `tex` suitable for source code for LaTeX documents.
920
921
t/t4018/swift-actor new
+5
@@ -0,0 +1,5 @@
1 +actor RIGHT {
2 + let a = 1
3 + // a comment
4 + let b = ChangeMe
5 +}
t/t4018/swift-attribute-with-args new
+7
@@ -0,0 +1,7 @@
1 +struct View {
2 + @available(iOS 13, *) public func RIGHT() {
3 + let a = 1
4 + // a comment
5 + print(ChangeMe)
6 + }
7 +}
t/t4018/swift-class new
+5
@@ -0,0 +1,5 @@
1 +class RIGHT {
2 + let a = 1
3 + // a comment
4 + let b = ChangeMe
5 +}
t/t4018/swift-enum new
+5
@@ -0,0 +1,5 @@
1 +enum RIGHT {
2 + case first
3 + // a comment
4 + case ChangeMe
5 +}
t/t4018/swift-extension new
+5
@@ -0,0 +1,5 @@
1 +extension RIGHT {
2 + static let a = 1
3 + // a comment
4 + static let b = ChangeMe
5 +}
t/t4018/swift-failable-init new
+7
@@ -0,0 +1,7 @@
1 +class Bar {
2 + init?(RIGHT: Int) {
3 + let x = 0
4 + // a comment
5 + print(ChangeMe)
6 + }
7 +}
t/t4018/swift-func new
+5
@@ -0,0 +1,5 @@
1 +func RIGHT(x: Int) -> Int {
2 + let y = x
3 + // a comment
4 + return ChangeMe
5 +}
t/t4018/swift-generic-subscript new
+7
@@ -0,0 +1,7 @@
1 +struct Container {
2 + subscript<RIGHT>(index: Int) -> Int {
3 + let a = 0
4 + // a comment
5 + return ChangeMe
6 + }
7 +}
t/t4018/swift-init new
+7
@@ -0,0 +1,7 @@
1 +class Foo {
2 + init(RIGHT: Int) {
3 + let x = 0
4 + // a comment
5 + print(ChangeMe)
6 + }
7 +}
t/t4018/swift-inline-attribute new
+7
@@ -0,0 +1,7 @@
1 +class Service {
2 + @objc func RIGHT() {
3 + let path = "/api"
4 + // a comment
5 + log(ChangeMe)
6 + }
7 +}
t/t4018/swift-modifiers new
+4
@@ -0,0 +1,4 @@
1 +public static func RIGHT() -> Int {
2 + // a comment
3 + return ChangeMe
4 +}
t/t4018/swift-protocol new
+5
@@ -0,0 +1,5 @@
1 +protocol RIGHT {
2 + var first: Int { get }
3 + // a comment
4 + var second: ChangeMe { get }
5 +}
t/t4018/swift-struct new
+5
@@ -0,0 +1,5 @@
1 +struct RIGHT {
2 + let a = 1
3 + // a comment
4 + let b = ChangeMe
5 +}
userdiff.c
+10
@@ -362,6 +362,16 @@ PATTERNS("scheme",
362 "\\|([^|\\\\]|\\\\.)*\\|"
363 /* All other words should be delimited by spaces or parentheses. */
364 "|([^][)(}{ \t])+"),
365 +PATTERNS("swift",
366 + "^[ \t]*((@[A-Za-z_][A-Za-z0-9_]*(\\([^()]*\\))?[ \t]+)*([a-z]+[ \t]+)*(func|init|deinit|subscript|class|struct|enum|protocol|extension|actor)[ \t(?!<].*)$",
367 + /* -- */
368 + "[a-zA-Z_][a-zA-Z0-9_]*"
369 + /* hexadecimal, octal, and binary literals */
370 + "|0[xX][0-9a-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+"
371 + /* integers and floating-point numbers */
372 + "|[0-9][0-9_]*([.][0-9_]+)?([eE][-+]?[0-9]+)?"
373 + /* unary and binary operators */
374 + "|[-+*/%<>=!&|^~?]=|&&|\\|\\||<<=?|>>=?|\\?\\?|\\.\\.[.<]|->"),
375 PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
376 "\\\\[a-zA-Z@]+|\\\\.|([a-zA-Z0-9]|[^\x01-\x7f])+"),
377 { .name = "default", .binary = -1 },