diff --git a/Package.resolved b/Package.resolved index 8c415b416..73b6733a6 100644 --- a/Package.resolved +++ b/Package.resolved @@ -36,6 +36,24 @@ "revision" : "beb15168446b6ac4b0c451b64a2bfaa621341b7e" } }, + { + "identity" : "tree-sitter-c-sharp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/lukepistrol/tree-sitter-c-sharp.git", + "state" : { + "branch" : "feature/spm", + "revision" : "18d9d8241d64e6ec2514f84d81528e44723da56a" + } + }, + { + "identity" : "tree-sitter-cpp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/lukepistrol/tree-sitter-cpp.git", + "state" : { + "branch" : "feature/spm", + "revision" : "ff2cb310c6ee2f7d63de99fff6713f83915aa880" + } + }, { "identity" : "tree-sitter-css", "kind" : "remoteSourceControl", @@ -81,6 +99,15 @@ "revision" : "4140d566eb446658187cdd0b72de7581cc686408" } }, + { + "identity" : "tree-sitter-javascript", + "kind" : "remoteSourceControl", + "location" : "https://github.com/lukepistrol/tree-sitter-javascript.git", + "state" : { + "branch" : "feature/spm", + "revision" : "0e6310fa29b0b71daf2c38b08f538571bd79357b" + } + }, { "identity" : "tree-sitter-json", "kind" : "remoteSourceControl", @@ -90,6 +117,15 @@ "revision" : "07fd294fe05a3d726ab9191e504db5f7648fa3fd" } }, + { + "identity" : "tree-sitter-php", + "kind" : "remoteSourceControl", + "location" : "https://github.com/lukepistrol/tree-sitter-php.git", + "state" : { + "branch" : "feature/spm", + "revision" : "e068da18701e00b5bebcbebdfad0c6205fad8b9b" + } + }, { "identity" : "tree-sitter-python", "kind" : "remoteSourceControl", @@ -108,6 +144,15 @@ "revision" : "e0923bfc25cadb2d5fd0a07c3b55e4b2d1501cb8" } }, + { + "identity" : "tree-sitter-rust", + "kind" : "remoteSourceControl", + "location" : "https://github.com/lukepistrol/tree-sitter-rust.git", + "state" : { + "branch" : "feature/spm", + "revision" : "ae1dc741893fa72afaa1f6f3a5bc59d074a77e96" + } + }, { "identity" : "tree-sitter-swift", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index aec039229..1edcc570e 100644 --- a/Package.swift +++ b/Package.swift @@ -16,6 +16,8 @@ let package = Package( .package(url: "https://github.com/krzyzanowskim/STTextView", branch: "main"), .package(url: "https://github.com/ChimeHQ/SwiftTreeSitter", from: "0.6.0"), .package(url: "https://github.com/lukepistrol/tree-sitter-c.git", branch: "feature/spm"), + .package(url: "https://github.com/lukepistrol/tree-sitter-cpp.git", branch: "feature/spm"), + .package(url: "https://github.com/lukepistrol/tree-sitter-c-sharp.git", branch: "feature/spm"), .package(url: "https://github.com/lukepistrol/tree-sitter-css.git", branch: "feature/spm"), .package(url: "https://github.com/mattmassicotte/tree-sitter-go.git", branch: "feature/swift"), .package(url: "https://github.com/camdencheek/tree-sitter-go-mod.git", branch: "main"), @@ -37,6 +39,8 @@ let package = Package( "STTextView", "SwiftTreeSitter", .product(name: "TreeSitterC", package: "tree-sitter-c"), + .product(name: "TreeSitterCPP", package: "tree-sitter-cpp"), + .product(name: "TreeSitterCSharp", package: "tree-sitter-c-sharp"), .product(name: "TreeSitterCSS", package: "tree-sitter-css"), .product(name: "TreeSitterGo", package: "tree-sitter-go"), .product(name: "TreeSitterGoMod", package: "tree-sitter-go-mod"), diff --git a/README.md b/README.md index 5eb5e8894..ec9bfa22b 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ struct ContentView: View { - [ ] Agda - [ ] Bash - [x] C -- [ ] C++ -- [ ] C# +- [x] C++ +- [x] C# - [ ] CodeQL - [x] CSS - [ ] Embedded template (ERB, EJS) diff --git a/Sources/CodeEditTextView/CodeLanguage/CodeLanguage.swift b/Sources/CodeEditTextView/CodeLanguage/CodeLanguage.swift index 757f51e79..85e787526 100644 --- a/Sources/CodeEditTextView/CodeLanguage/CodeLanguage.swift +++ b/Sources/CodeEditTextView/CodeLanguage/CodeLanguage.swift @@ -10,6 +10,8 @@ import tree_sitter import SwiftTreeSitter import TreeSitterC +import TreeSitterCPP +import TreeSitterCSharp import TreeSitterCSS import TreeSitterGo import TreeSitterGoMod @@ -26,22 +28,25 @@ import TreeSitterYAML /// A structure holding metadata for code languages public struct CodeLanguage { - /// The ID of the ``CodeLanguage`` + /// The ID of the language public let id: TreeSitterLanguage - /// The display name of the ``CodeLanguage`` + /// The display name of the language public let displayName: String - /// A set of file extensions for the ``CodeLanguage`` + /// A set of file extensions for the language public let extensions: Set - /// The query URL for the ``CodeLanguage`` if available + /// The query URL for the language if available public var queryURL: URL? { Bundle.main.resourceURL? .appendingPathComponent(bundle) .appendingPathComponent(highlights) } + /// The query URL of a language this language inherits from. (e.g.: C for C++) + public var parentQueryURL: URL? + private var bundle: String { "TreeSitter\(displayName)_TreeSitter\(displayName).bundle" } @@ -50,7 +55,7 @@ public struct CodeLanguage { "Contents/Resources/queries/highlights.scm" } - /// The tree-sitter language for the ``CodeLanguage`` if available + /// The tree-sitter language for the language if available public var language: Language? { guard let ts_language = ts_language else { return nil } return Language(language: ts_language) @@ -60,6 +65,10 @@ public struct CodeLanguage { switch id { case .c: return tree_sitter_c() + case .cpp: + return tree_sitter_cpp() + case .cSharp: + return tree_sitter_c_sharp() case .css: return tree_sitter_css() case .go: @@ -91,11 +100,11 @@ public struct CodeLanguage { } } - /// Gets the corresponding ``CodeLanguage`` for the given file URL + /// Gets the corresponding language for the given file URL /// - /// Uses the `pathExtension` URL component to detect the ``CodeLanguage`` - /// - Parameter url: The URL to get the ``CodeLanguage`` for. - /// - Returns: A ``CodeLanguage`` structure + /// Uses the `pathExtension` URL component to detect the language + /// - Parameter url: The URL to get the language for. + /// - Returns: A language structure public static func detectLanguageFrom(url: URL) -> CodeLanguage { let fileExtension = url.pathExtension.lowercased() let fileName = url.pathComponents.last?.lowercased() @@ -108,7 +117,7 @@ public struct CodeLanguage { } } - /// The default ``CodeLanguage`` (plain text) + /// The default language (plain text) public static let `default` = CodeLanguage( id: .plainText, displayName: "Plain Text", @@ -118,9 +127,11 @@ public struct CodeLanguage { public extension CodeLanguage { - /// A collection of available ``CodeLanguage`` structures. + /// A collection of available language structures. static let knownLanguages: [CodeLanguage] = [ .c, + .cpp, + .cSharp, .css, .go, .goMod, @@ -136,45 +147,54 @@ public extension CodeLanguage { .yaml ] - /// A ``CodeLanguage`` structure for `C` + /// A language structure for `C` static let c: CodeLanguage = .init(id: .c, displayName: "C", extensions: ["c", "h", "o"]) - /// A ``CodeLanguage`` structure for `CSS` + /// A language structure for `C++` + static let cpp: CodeLanguage = .init(id: .cpp, + displayName: "CPP", + extensions: ["cpp", "h", "cc"], + parentQueryURL: CodeLanguage.c.queryURL) + + /// A language structure for `C#` + static let cSharp: CodeLanguage = .init(id: .cSharp, displayName: "CSharp", extensions: ["cs"]) + + /// A language structure for `CSS` static let css: CodeLanguage = .init(id: .css, displayName: "CSS", extensions: ["css"]) - /// A ``CodeLanguage`` structure for `Go` + /// A language structure for `Go` static let go: CodeLanguage = .init(id: .go, displayName: "Go", extensions: ["go"]) - /// A ``CodeLanguage`` structure for `GoMod` + /// A language structure for `GoMod` static let goMod: CodeLanguage = .init(id: .goMod, displayName: "GoMod", extensions: ["mod"]) - /// A ``CodeLanguage`` structure for `HTML` + /// A language structure for `HTML` static let html: CodeLanguage = .init(id: .html, displayName: "HTML", extensions: ["html", "htm"]) - /// A ``CodeLanguage`` structure for `Java` + /// A language structure for `Java` static let java: CodeLanguage = .init(id: .java, displayName: "Java", extensions: ["java"]) - /// A ``CodeLanguage`` structure for `JavaScript` + /// A language structure for `JavaScript` static let javascript: CodeLanguage = .init(id: .javascript, displayName: "JS", extensions: ["js"]) - /// A ``CodeLanguage`` structure for `JSON` + /// A language structure for `JSON` static let json: CodeLanguage = .init(id: .json, displayName: "JSON", extensions: ["json"]) - /// A ``CodeLanguage`` structure for `PHP` + /// A language structure for `PHP` static let php: CodeLanguage = .init(id: .php, displayName: "PHP", extensions: ["php"]) - /// A ``CodeLanguage`` structure for `Python` + /// A language structure for `Python` static let python: CodeLanguage = .init(id: .python, displayName: "Python", extensions: ["py"]) - /// A ``CodeLanguage`` structure for `Ruby` + /// A language structure for `Ruby` static let ruby: CodeLanguage = .init(id: .ruby, displayName: "Ruby", extensions: ["rb"]) - /// A ``CodeLanguage`` structure for `Rust` + /// A language structure for `Rust` static let rust: CodeLanguage = .init(id: .rust, displayName: "Rust", extensions: ["rs"]) - /// A ``CodeLanguage`` structure for `Swift` + /// A language structure for `Swift` static let swift: CodeLanguage = .init(id: .swift, displayName: "Swift", extensions: ["swift"]) - /// A ``CodeLanguage`` structure for `YAML` + /// A language structure for `YAML` static let yaml: CodeLanguage = .init(id: .yaml, displayName: "YAML", extensions: ["yml", "yaml"]) } diff --git a/Sources/CodeEditTextView/CodeLanguage/TreeSitterLanguage.swift b/Sources/CodeEditTextView/CodeLanguage/TreeSitterLanguage.swift index b1160ebce..283fabc61 100644 --- a/Sources/CodeEditTextView/CodeLanguage/TreeSitterLanguage.swift +++ b/Sources/CodeEditTextView/CodeLanguage/TreeSitterLanguage.swift @@ -10,6 +10,8 @@ import Foundation /// A collection of languages that are supported by `tree-sitter` public enum TreeSitterLanguage: String { case c + case cpp + case cSharp case css case go case goMod diff --git a/Sources/CodeEditTextView/CodeLanguage/TreeSitterModel.swift b/Sources/CodeEditTextView/CodeLanguage/TreeSitterModel.swift index aa8673b28..c71150180 100644 --- a/Sources/CodeEditTextView/CodeLanguage/TreeSitterModel.swift +++ b/Sources/CodeEditTextView/CodeLanguage/TreeSitterModel.swift @@ -22,6 +22,10 @@ public class TreeSitterModel { switch language { case .c: return cQuery + case .cpp: + return cppQuery + case .cSharp: + return cSharpQuery case .css: return cssQuery case .go: @@ -58,6 +62,16 @@ public class TreeSitterModel { return queryFor(.c) }() + /// Query for `C++` files. + public lazy var cppQuery: Query? = { + return queryFor(.cpp) + }() + + /// Query for `C#` files. + public lazy var cSharpQuery: Query? = { + return queryFor(.cSharp) + }() + /// Query for `CSS` files. public lazy var cssQuery: Query? = { return queryFor(.css) @@ -126,7 +140,21 @@ public class TreeSitterModel { private func queryFor(_ codeLanguage: CodeLanguage) -> Query? { guard let language = codeLanguage.language, let url = codeLanguage.queryURL else { return nil } - return try? language.query(contentsOf: url) + if let parentURL = codeLanguage.parentQueryURL, + let data = combinedQueryData(for: [url, parentURL]) { + return try? Query(language: language, data: data) + } else { + return try? language.query(contentsOf: url) + } + } + + private func combinedQueryData(for fileURLs: [URL]) -> Data? { + let rawQuery = fileURLs.compactMap { try? String(contentsOf: $0) }.joined(separator: "\n") + if !rawQuery.isEmpty { + return rawQuery.data(using: .utf8) + } else { + return nil + } } private init() {}