CodeViewerKit is a minimal, read-only source-code viewer for SwiftUI on iOS,
iPadOS, and macOS. It uses native TextKit views for selection and scrolling,
with viewport-based layout by default instead of rendering a complete document
as one SwiftUI Text value. Noncontiguous layout keeps distant jumps through
very large files responsive on every supported platform.
- progressive native Tree-sitter highlighting through consumer-supplied grammars;
- appearance-aware plain text while highlighting is prepared;
- logical line numbers that stay aligned when a source line wraps;
- Menlo with a monospaced system fallback;
- native selection and horizontal and vertical scrolling;
- Command-Plus and Command-Minus font scaling;
- configurable progressive, complete, or size-adaptive native text layout;
- shared highlighting cache for navigation-heavy apps.
- Swift 6.0 or newer
- Xcode 16 or newer
- iOS or iPadOS 18 or newer
- macOS 15 or newer
In Xcode, choose File > Add Package Dependencies and enter:
https://github.com/cavs1910/CodeViewerKit.git
Or add the package to Package.swift:
dependencies: [
.package(
url: "https://github.com/cavs1910/CodeViewerKit.git",
from: "0.6.4"
)
]Add CodeViewerKit plus the grammar packages your application chooses. CVK
does not declare or bundle a language catalog.
Create one CodeHighlightStore at a stable ownership boundary. Reusing it lets
prepared documents survive navigation and avoids repeating highlighting work.
import CodeViewerKit
import SwiftUI
import TreeSitterSwift
import TreeSitterSwiftQueries
struct ContentView: View {
@State private var highlights = CodeHighlightStore(grammars: [
CodeGrammar(
identifier: "swift",
language: tree_sitter_swift(),
queryURLs: [TreeSitterSwiftQueries.Query.highlightsFileURL]
)
])
private let source = """
import SwiftUI
struct GreetingView: View {
var body: some View {
Text("Hello, world!")
}
}
"""
var body: some View {
CodeViewer(
documentID: "greeting-view",
sourceCode: source,
highlightStore: highlights
)
}
}documentID controls native view continuity. Keep it stable while updating the
same document so selection and scroll position are preserved. Change it when
showing a different document so the viewer starts at the beginning.
Every grammar is supplied by the consuming application at compile time. A convenient SwiftPM catalog is TreeSitterLanguages, whose parsers and query libraries are separate products. CVK only needs the parser pointer, identifier, aliases, and highlight-query URLs:
import CodeViewerKit
import TreeSitterJSON
import TreeSitterJSONQueries
let jsonGrammar = CodeGrammar(
identifier: "json",
language: tree_sitter_json(),
queryURLs: [TreeSitterJSONQueries.Query.highlightsFileURL]
)
let highlights = CodeHighlightStore(grammars: [jsonGrammar])The catalog is optional. A new language can ship its generated Tree-sitter C
parser and .scm queries in any Swift package and construct CodeGrammar
through the same public initializer. No CodeViewerKit update is required.
Pass an explicit linked Tree-sitter language in quotes, or use .automatic
without quotes to detect a language from the source contents:
CodeViewer(
documentID: "script",
sourceCode: pythonSource,
highlightStore: highlights,
language: "python"
)
CodeViewer(
documentID: "detected-source",
sourceCode: unknownSource,
highlightStore: highlights,
language: .automatic
)
CodeViewer(
documentID: "shell-script",
sourceCode: shellSource,
highlightStore: highlights,
language: "shell"
)Identifiers and aliases belong to the supplied CodeGrammar, so CVK places no
limit on the available languages. Unknown or unregistered identifiers render
as plain text without loading a second engine.
Prefer an explicit language when it is known. Automatic detection performs additional work and can be ambiguous for short snippets.
Long lines use horizontal scrolling by default. Pass lineWrapping: .word to
wrap them at word boundaries instead:
CodeViewer(
documentID: "wrapped-example",
sourceCode: source,
highlightStore: highlights,
lineWrapping: .word
)On macOS, each legacy scrollbar is hidden automatically when the content fits along its axis.
Layout is automatic by default: sources up to 64,000 UTF-16 code units receive complete layout, while larger sources remain progressive so distant jumps do not synchronously typeset every intervening line. Progressive layout can refine the scrollbar extent the first time a previously unlaid range becomes visible.
Use complete layout when an exact initial scrollbar extent is more important
than the up-front layout cost. Automatic mode lets the application choose that
tradeoff by the source's UTF-16 length, which is the native NSTextStorage
unit:
CodeViewer(
documentID: "adaptive-layout",
sourceCode: source,
highlightStore: highlights,
lineWrapping: .word,
layoutPreparation: .automatic(maximumUTF16Length: 64_000)
)Sources at or below the configured limit receive complete layout. Larger
sources keep progressive, viewport-driven layout. Pass .complete or
.progressive to select either behavior without a size threshold.
The viewer is visually neutral: apply your own frame, material, or Liquid Glass
container around it. Source ranges without a syntax color use black in light
mode and white in dark mode; override that fallback with plainTextColor:
CodeViewer(
documentID: "example",
sourceCode: source,
highlightStore: highlights,
plainTextColor: .secondary
)Add CodeViewerCommands to the app scene to enable Command-Plus and
Command-Minus for the focused viewer:
@main
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
CodeViewerCommands()
}
}
}CodeViewerKit is intentionally read-only. Editing, language-server integration, diagnostics, minimaps, and dynamically installed grammars are outside its current scope.
The shared renderer core owns document updates, Menlo styling, logical line indexing, gutter geometry, visible marker resolution, and coalesced redraws. Small AppKit and UIKit adapters configure the native TextKit views and handle the platform-specific selection and scrolling behavior.
Highlighting is provided by Tree-sitter through SwiftTreeSitter. Grammar ownership remains entirely outside CVK, so an application links only the external parsers it registers. The complete document is parsed away from the main actor, then token colors are queried and applied progressively from the start of the native attributed string. Each batch updates only its own ranges, without replacing the text or changing the current scroll position. Results are cached by document, contents, language, and appearance.
See CONTRIBUTING.md for the local build and validation flow.
CodeViewerKit is available under the Apache License 2.0. Dependency licenses are listed in THIRD_PARTY_NOTICES.md.