diff --git a/CodeEdit/Features/Documents/CodeFileDocument/CodeFileDocument.swift b/CodeEdit/Features/Documents/CodeFileDocument/CodeFileDocument.swift
index 8fd36004b..2b3fcfd24 100644
--- a/CodeEdit/Features/Documents/CodeFileDocument/CodeFileDocument.swift
+++ b/CodeEdit/Features/Documents/CodeFileDocument/CodeFileDocument.swift
@@ -50,9 +50,6 @@ final class CodeFileDocument: NSDocument, ObservableObject {
     /// See ``CodeEditSourceEditor/CombineCoordinator``.
     @Published var contentCoordinator: CombineCoordinator = CombineCoordinator()
 
-    /// Set by ``LanguageServer`` when initialized.
-    @Published var lspCoordinator: LSPContentCoordinator?
-
     /// Used to override detected languages.
     @Published var language: CodeLanguage?
 
@@ -65,6 +62,9 @@ final class CodeFileDocument: NSDocument, ObservableObject {
     /// Document-specific overridden line wrap preference.
     @Published var wrapLines: Bool?
 
+    /// Set up by ``LanguageServer``, conforms this type to ``LanguageServerDocument``.
+    @Published var languageServerObjects: LanguageServerDocumentObjects<CodeFileDocument> = .init()
+
     /// The type of data this file document contains.
     ///
     /// If its text content is not nil, a `text` UTType is returned.
@@ -83,9 +83,6 @@ final class CodeFileDocument: NSDocument, ObservableObject {
         return type
     }
 
-    /// A stable string to use when identifying documents with language servers.
-    var languageServerURI: String? { fileURL?.absolutePath }
-
     /// Specify options for opening the file such as the initial cursor positions.
     /// Nulled by ``CodeFileView`` on first load.
     var openOptions: OpenOptions?
@@ -208,6 +205,10 @@ final class CodeFileDocument: NSDocument, ObservableObject {
         }
     }
 
+    /// Determines the code language of the document.
+    /// Use ``CodeFileDocument/language`` for the default value before using this. That property is used to override
+    /// the file's language.
+    /// - Returns: The detected code language.
     func getLanguage() -> CodeLanguage {
         guard let url = fileURL else {
             return .default
@@ -223,3 +224,13 @@ final class CodeFileDocument: NSDocument, ObservableObject {
         fileURL?.findWorkspace()
     }
 }
+
+// MARK: LanguageServerDocument
+
+extension CodeFileDocument: LanguageServerDocument {
+    /// A stable string to use when identifying documents with language servers.
+    /// Needs to be a valid URI, so always returns with the `file://` prefix to indicate it's a file URI.
+    var languageServerURI: String? {
+        fileURL?.lspURI
+    }
+}
diff --git a/CodeEdit/Features/Editor/Views/CodeFileView.swift b/CodeEdit/Features/Editor/Views/CodeFileView.swift
index eeb8586cf..ae5e167ad 100644
--- a/CodeEdit/Features/Editor/Views/CodeFileView.swift
+++ b/CodeEdit/Features/Editor/Views/CodeFileView.swift
@@ -19,9 +19,13 @@ struct CodeFileView: View {
     /// The current cursor positions in the view
     @State private var cursorPositions: [CursorPosition] = []
 
+    @State private var treeSitterClient: TreeSitterClient = TreeSitterClient()
+
     /// Any coordinators passed to the view.
     private var textViewCoordinators: [TextViewCoordinator]
 
+    @State private var highlightProviders: [any HighlightProviding] = []
+
     @AppSettings(\.textEditing.defaultTabWidth)
     var defaultTabWidth
     @AppSettings(\.textEditing.indentOption)
@@ -62,9 +66,10 @@ struct CodeFileView: View {
 
     init(codeFile: CodeFileDocument, textViewCoordinators: [TextViewCoordinator] = [], isEditable: Bool = true) {
         self._codeFile = .init(wrappedValue: codeFile)
+
         self.textViewCoordinators = textViewCoordinators
             + [codeFile.contentCoordinator]
-            + [codeFile.lspCoordinator].compactMap({ $0 })
+            + [codeFile.languageServerObjects.textCoordinator].compactMap({ $0 })
         self.isEditable = isEditable
 
         if let openOptions = codeFile.openOptions {
@@ -72,6 +77,8 @@ struct CodeFileView: View {
             self.cursorPositions = openOptions.cursorPositions
         }
 
+        updateHighlightProviders()
+
         codeFile
             .contentCoordinator
             .textUpdatePublisher
@@ -119,7 +126,7 @@ struct CodeFileView: View {
             editorOverscroll: overscroll.overscrollPercentage,
             cursorPositions: $cursorPositions,
             useThemeBackground: useThemeBackground,
-            highlightProviders: [treeSitter],
+            highlightProviders: highlightProviders,
             contentInsets: edgeInsets.nsEdgeInsets,
             additionalTextInsets: NSEdgeInsets(top: 2, left: 0, bottom: 0, right: 0),
             isEditable: isEditable,
@@ -144,6 +151,10 @@ struct CodeFileView: View {
         .onChange(of: settingsFont) { newFontSetting in
             font = newFontSetting.current
         }
+        .onReceive(codeFile.$languageServerObjects) { languageServerObjects in
+            // This will not be called in single-file views (for now) but is safe to listen to either way
+            updateHighlightProviders(lspHighlightProvider: languageServerObjects.highlightProvider)
+        }
     }
 
     /// Determines the style of bracket emphasis based on the `bracketEmphasis` setting and the current theme.
@@ -166,6 +177,12 @@ struct CodeFileView: View {
             return .underline(color: color)
         }
     }
+
+    /// Updates the highlight providers array.
+    /// - Parameter lspHighlightProvider: The language server provider, if available.
+    private func updateHighlightProviders(lspHighlightProvider: HighlightProviding? = nil) {
+        highlightProviders = [lspHighlightProvider].compactMap({ $0 }) + [treeSitterClient]
+    }
 }
 
 // This extension is kept here because it should not be used elsewhere in the app and may cause confusion
diff --git a/CodeEdit/Features/LSP/Editor/LSPContentCoordinator.swift b/CodeEdit/Features/LSP/Features/DocumentSync/LSPContentCoordinator.swift
similarity index 87%
rename from CodeEdit/Features/LSP/Editor/LSPContentCoordinator.swift
rename to CodeEdit/Features/LSP/Features/DocumentSync/LSPContentCoordinator.swift
index dc17481e6..aed2a7ada 100644
--- a/CodeEdit/Features/LSP/Editor/LSPContentCoordinator.swift
+++ b/CodeEdit/Features/LSP/Features/DocumentSync/LSPContentCoordinator.swift
@@ -19,7 +19,7 @@ import LanguageServerProtocol
 /// Language servers expect edits to be sent in chunks (and it helps reduce processing overhead). To do this, this class
 /// keeps an async stream around for the duration of its lifetime. The stream is sent edit notifications, which are then
 /// chunked into 250ms timed groups before being sent to the ``LanguageServer``.
-class LSPContentCoordinator: TextViewCoordinator, TextViewDelegate {
+class LSPContentCoordinator<DocumentType: LanguageServerDocument>: TextViewCoordinator, TextViewDelegate {
     // Required to avoid a large_tuple lint error
     private struct SequenceElement: Sendable {
         let uri: String
@@ -28,25 +28,27 @@ class LSPContentCoordinator: TextViewCoordinator, TextViewDelegate {
     }
 
     private var editedRange: LSPRange?
-    private var stream: AsyncStream<SequenceElement>?
     private var sequenceContinuation: AsyncStream<SequenceElement>.Continuation?
     private var task: Task<Void, Never>?
 
-    weak var languageServer: LanguageServer?
+    weak var languageServer: LanguageServer<DocumentType>?
     var documentURI: String
 
     /// Initializes a content coordinator, and begins an async stream of updates
-    init(documentURI: String, languageServer: LanguageServer) {
+    init(documentURI: String, languageServer: LanguageServer<DocumentType>) {
         self.documentURI = documentURI
         self.languageServer = languageServer
-        self.stream = AsyncStream { continuation in
-            self.sequenceContinuation = continuation
-        }
+
+        setUpUpdatesTask()
     }
 
     func setUpUpdatesTask() {
         task?.cancel()
-        guard let stream else { return }
+        // Create this stream here so it's always set up when the text view is set up, rather than only once on init.
+        let stream = AsyncStream { continuation in
+            self.sequenceContinuation = continuation
+        }
+
         task = Task.detached { [weak self] in
             // Send edit events every 250ms
             for await events in stream.chunked(by: .repeating(every: .milliseconds(250), clock: .continuous)) {
diff --git a/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenHighlightProvider.swift b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenHighlightProvider.swift
new file mode 100644
index 000000000..2e391fba4
--- /dev/null
+++ b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenHighlightProvider.swift
@@ -0,0 +1,172 @@
+//
+//  SemanticTokenHighlightProvider.swift
+//  CodeEdit
+//
+//  Created by Khan Winter on 12/26/24.
+//
+
+import Foundation
+import LanguageServerProtocol
+import CodeEditSourceEditor
+import CodeEditTextView
+import CodeEditLanguages
+
+/// Provides semantic token information from a language server for a source editor view.
+///
+/// This class works in tangent with the ``LanguageServer`` class to ensure we don't unnecessarily request new tokens
+/// if the document isn't updated. The ``LanguageServer`` will call the
+/// ``SemanticTokenHighlightProvider/documentDidChange`` method, which in turn refreshes the semantic token storage.
+///
+/// That behavior may not be intuitive due to the
+/// ``SemanticTokenHighlightProvider/applyEdit(textView:range:delta:completion:)`` method. One might expect this class
+/// to respond to that method immediately, but it does not. It instead stores the completion passed in that method until
+/// it can respond to the edit with invalidated indices.
+final class SemanticTokenHighlightProvider<
+    Storage: GenericSemanticTokenStorage,
+    DocumentType: LanguageServerDocument
+>: HighlightProviding {
+    enum HighlightError: Error {
+        case lspRangeFailure
+    }
+
+    typealias EditCallback = @MainActor (Result<IndexSet, any Error>) -> Void
+    typealias HighlightCallback = @MainActor (Result<[HighlightRange], any Error>) -> Void
+
+    private let tokenMap: SemanticTokenMap
+    private let documentURI: String
+    private weak var languageServer: LanguageServer<DocumentType>?
+    private weak var textView: TextView?
+
+    private var lastEditCallback: EditCallback?
+    private var pendingHighlightCallbacks: [HighlightCallback] = []
+    private var storage: Storage
+
+    var documentRange: NSRange {
+        textView?.documentRange ?? .zero
+    }
+
+    init(tokenMap: SemanticTokenMap, languageServer: LanguageServer<DocumentType>, documentURI: String) {
+        self.tokenMap = tokenMap
+        self.languageServer = languageServer
+        self.documentURI = documentURI
+        self.storage = Storage()
+    }
+
+    // MARK: - Language Server Content Lifecycle
+
+    /// Called when the language server finishes sending a document update.
+    ///
+    /// This method first checks if this object has any semantic tokens. If not, requests new tokens and responds to the
+    /// `pendingHighlightCallbacks` queue with cancellation errors, causing the highlighter to re-query those indices.
+    ///
+    /// If this object already has some tokens, it determines whether or not we can request a token delta and
+    /// performs the request.
+    func documentDidChange() async throws {
+        guard let languageServer, let textView else {
+            return
+        }
+
+        guard storage.hasReceivedData else {
+            // We have no semantic token info, request it!
+            try await requestTokens(languageServer: languageServer, textView: textView)
+            await MainActor.run {
+                for callback in pendingHighlightCallbacks {
+                    callback(.failure(HighlightProvidingError.operationCancelled))
+                }
+                pendingHighlightCallbacks.removeAll()
+            }
+            return
+        }
+
+        // The document was updated. Update our token cache and send the invalidated ranges for the editor to handle.
+        if let lastResultId = storage.lastResultId {
+            try await requestDeltaTokens(languageServer: languageServer, textView: textView, lastResultId: lastResultId)
+            return
+        }
+
+        try await requestTokens(languageServer: languageServer, textView: textView)
+    }
+
+    // MARK: - LSP Token Requests
+
+    /// Requests and applies a token delta. Requires a previous response identifier.
+    private func requestDeltaTokens(
+        languageServer: LanguageServer<DocumentType>,
+        textView: TextView,
+        lastResultId: String
+    ) async throws {
+        guard let response = try await languageServer.requestSemanticTokens(
+            for: documentURI,
+            previousResultId: lastResultId
+        ) else {
+            return
+        }
+        switch response {
+        case let .optionA(tokenData):
+            await applyEntireResponse(tokenData, callback: lastEditCallback)
+        case let .optionB(deltaData):
+            await applyDeltaResponse(deltaData, callback: lastEditCallback, textView: textView)
+        }
+    }
+
+    /// Requests and applies tokens for an entire document. This does not require a previous response id, and should be
+    /// used in place of `requestDeltaTokens` when that's the case.
+    private func requestTokens(languageServer: LanguageServer<DocumentType>, textView: TextView) async throws {
+        guard let response = try await languageServer.requestSemanticTokens(for: documentURI) else {
+            return
+        }
+        await applyEntireResponse(response, callback: lastEditCallback)
+    }
+
+    // MARK: - Apply LSP Response
+
+    /// Applies a delta response from the LSP to our storage.
+    private func applyDeltaResponse(_ data: SemanticTokensDelta, callback: EditCallback?, textView: TextView?) async {
+        let lspRanges = storage.applyDelta(data)
+        lastEditCallback = nil // Don't use this callback again.
+        await MainActor.run {
+            let ranges = lspRanges.compactMap { textView?.nsRangeFrom($0) }
+            callback?(.success(IndexSet(ranges: ranges)))
+        }
+    }
+
+    private func applyEntireResponse(_ data: SemanticTokens, callback: EditCallback?) async {
+        storage.setData(data)
+        lastEditCallback = nil // Don't use this callback again.
+        await callback?(.success(IndexSet(integersIn: documentRange)))
+    }
+
+    // MARK: - Highlight Provider Conformance
+
+    func setUp(textView: TextView, codeLanguage: CodeLanguage) {
+        // Send off a request to get the initial token data
+        self.textView = textView
+        Task {
+            try await self.documentDidChange()
+        }
+    }
+
+    func applyEdit(textView: TextView, range: NSRange, delta: Int, completion: @escaping EditCallback) {
+        if let lastEditCallback {
+            lastEditCallback(.success(IndexSet())) // Don't throw a cancellation error
+        }
+        lastEditCallback = completion
+    }
+
+    func queryHighlightsFor(textView: TextView, range: NSRange, completion: @escaping HighlightCallback) {
+        guard storage.hasReceivedData else {
+            pendingHighlightCallbacks.append(completion)
+            return
+        }
+
+        guard let lspRange = textView.lspRangeFrom(nsRange: range) else {
+            completion(.failure(HighlightError.lspRangeFailure))
+            return
+        }
+        let rawTokens = storage.getTokensFor(range: lspRange)
+        let highlights = tokenMap
+            .decode(tokens: rawTokens, using: textView)
+            .filter({ $0.capture != nil || !$0.modifiers.isEmpty })
+        completion(.success(highlights))
+    }
+}
diff --git a/CodeEdit/Features/LSP/Editor/SemanticTokenMap.swift b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenMap.swift
similarity index 80%
rename from CodeEdit/Features/LSP/Editor/SemanticTokenMap.swift
rename to CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenMap.swift
index 5a196cf60..317068a2d 100644
--- a/CodeEdit/Features/LSP/Editor/SemanticTokenMap.swift
+++ b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenMap.swift
@@ -45,20 +45,31 @@ struct SemanticTokenMap: Sendable { // swiftlint:enable line_length
     /// Decodes the compressed semantic token data into a `HighlightRange` type for use in an editor.
     /// This is marked main actor to prevent runtime errors, due to the use of the actor-isolated `rangeProvider`.
     /// - Parameters:
-    ///   - tokens: Semantic tokens from a language server.
+    ///   - tokens: Encoded semantic tokens type from a language server.
     ///   - rangeProvider: The provider to use to translate token ranges to text view ranges.
     /// - Returns: An array of decoded highlight ranges.
     @MainActor
     func decode(tokens: SemanticTokens, using rangeProvider: SemanticTokenMapRangeProvider) -> [HighlightRange] {
-        tokens.decode().compactMap { token in
+        return decode(tokens: tokens.decode(), using: rangeProvider)
+    }
+
+    /// Decodes the compressed semantic token data into a `HighlightRange` type for use in an editor.
+    /// This is marked main actor to prevent runtime errors, due to the use of the actor-isolated `rangeProvider`.
+    /// - Parameters:
+    ///   - tokens: Decoded semantic tokens from a language server.
+    ///   - rangeProvider: The provider to use to translate token ranges to text view ranges.
+    /// - Returns: An array of decoded highlight ranges.
+    @MainActor
+    func decode(tokens: [SemanticToken], using rangeProvider: SemanticTokenMapRangeProvider) -> [HighlightRange] {
+        tokens.compactMap { token in
             guard let range = rangeProvider.nsRangeFrom(line: token.line, char: token.char, length: token.length) else {
                 return nil
             }
 
+            // Only modifiers are bit packed, capture types are given as a simple index into the ``tokenTypeMap``
             let modifiers = decodeModifier(token.modifiers)
 
-            // Capture types are indicated by the index of the set bit.
-            let type = token.type > 0 ? Int(token.type.trailingZeroBitCount) : -1 // Don't try to decode 0
+            let type = Int(token.type)
             let capture = tokenTypeMap.indices.contains(type) ? tokenTypeMap[type] : nil
 
             return HighlightRange(
diff --git a/CodeEdit/Features/LSP/Editor/SemanticTokenMapRangeProvider.swift b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenMapRangeProvider.swift
similarity index 100%
rename from CodeEdit/Features/LSP/Editor/SemanticTokenMapRangeProvider.swift
rename to CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenMapRangeProvider.swift
diff --git a/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/GenericSemanticTokenStorage.swift b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/GenericSemanticTokenStorage.swift
new file mode 100644
index 000000000..ecfcb3932
--- /dev/null
+++ b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/GenericSemanticTokenStorage.swift
@@ -0,0 +1,25 @@
+//
+//  GenericSemanticTokenStorage.swift
+//  CodeEdit
+//
+//  Created by Khan Winter on 12/26/24.
+//
+
+import Foundation
+import LanguageServerProtocol
+import CodeEditSourceEditor
+
+/// Defines a protocol for an object to provide storage for semantic tokens.
+/// 
+/// There is only one concrete type that conforms to this in CE, but this protocol is useful in testing.
+/// See ``SemanticTokenStorage``.
+protocol GenericSemanticTokenStorage: AnyObject {
+    var lastResultId: String? { get }
+    var hasReceivedData: Bool { get }
+
+    init()
+
+    func getTokensFor(range: LSPRange) -> [SemanticToken]
+    func setData(_ data: borrowing SemanticTokens)
+    func applyDelta(_ deltas: SemanticTokensDelta) -> [SemanticTokenRange]
+}
diff --git a/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/SemanticTokenRange.swift b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/SemanticTokenRange.swift
new file mode 100644
index 000000000..6a7bfff6d
--- /dev/null
+++ b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/SemanticTokenRange.swift
@@ -0,0 +1,13 @@
+//
+//  SemanticTokenRange.swift
+//  CodeEdit
+//
+//  Created by Khan Winter on 12/26/24.
+//
+
+/// Represents the range of a semantic token.
+struct SemanticTokenRange {
+    let line: UInt32
+    let char: UInt32
+    let length: UInt32
+}
diff --git a/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/SemanticTokenStorage.swift b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/SemanticTokenStorage.swift
new file mode 100644
index 000000000..3faeae250
--- /dev/null
+++ b/CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/SemanticTokenStorage.swift
@@ -0,0 +1,180 @@
+//
+//  SemanticTokenStorage.swift
+//  CodeEdit
+//
+//  Created by Khan Winter on 12/26/24.
+//
+
+import Foundation
+import LanguageServerProtocol
+import CodeEditSourceEditor
+
+/// This class provides storage for semantic token data.
+///
+/// The LSP spec requires that clients keep the original compressed data to apply delta edits. Delta updates may
+/// appear as a delta to a single number in the compressed array. This class maintains the current state of compressed
+/// tokens and their decoded counterparts. It supports applying delta updates from the language server.
+///
+/// See ``SemanticTokenHighlightProvider`` for it's connection to the editor view.
+final class SemanticTokenStorage: GenericSemanticTokenStorage {
+    /// Represents compressed semantic token data received from a language server.
+    struct CurrentState {
+        let resultId: String?
+        let tokenData: [UInt32]
+        let tokens: [SemanticToken]
+    }
+
+    /// The last received result identifier.
+    var lastResultId: String? {
+        state?.resultId
+    }
+
+    /// Indicates if the storage object has received any data.
+    /// Once `setData` has been called, this returns `true`.
+    /// Other operations will fail without any data in the storage object.
+    var hasReceivedData: Bool {
+        state != nil
+    }
+
+    var state: CurrentState?
+
+    /// Create an empty storage object.
+    init() {
+        state = nil
+    }
+
+    // MARK: - Storage Conformance
+
+    /// Finds all tokens in the given range.
+    /// - Parameter range: The range to query.
+    /// - Returns: All tokens found in the range.
+    func getTokensFor(range: LSPRange) -> [SemanticToken] {
+        guard let state = state, !state.tokens.isEmpty else {
+            return []
+        }
+        var tokens: [SemanticToken] = []
+
+        // Perform a binary search
+        guard var idx = findLowerBound(in: range, data: state.tokens[...]) else {
+            return []
+        }
+
+        while idx < state.tokens.count && state.tokens[idx].startPosition < range.end {
+            tokens.append(state.tokens[idx])
+            idx += 1
+        }
+
+        return tokens
+    }
+
+    /// Clear the current state and set a new one.
+    /// - Parameter data: The semantic tokens to set as the current state.
+    func setData(_ data: borrowing SemanticTokens) {
+        state = CurrentState(resultId: data.resultId, tokenData: data.data, tokens: data.decode())
+    }
+
+    /// Apply a delta object from a language server and returns all token ranges that may need re-drawing.
+    ///
+    /// To calculate invalidated ranges:
+    /// - Grabs all semantic tokens that *will* be updated and invalidates their ranges
+    /// - Loops over all inserted tokens and invalidates their ranges
+    /// This may result in duplicated ranges. It's up to the caller to de-duplicate if necessary. See
+    /// ``SemanticTokenStorage/invalidatedRanges(startIdx:length:data:)``.
+    ///
+    /// - Parameter deltas: The deltas to apply.
+    /// - Returns: Ranges invalidated by the applied deltas.
+    func applyDelta(_ deltas: SemanticTokensDelta) -> [SemanticTokenRange] {
+        assert(state != nil, "State should be set before applying any deltas.")
+        guard var tokenData = state?.tokenData else { return [] }
+        var invalidatedSet: [SemanticTokenRange] = []
+
+        // Apply in reverse order (end to start)
+        for edit in deltas.edits.sorted(by: { $0.start > $1.start }) {
+            invalidatedSet.append(
+                contentsOf: invalidatedRanges(startIdx: edit.start, length: edit.deleteCount, data: tokenData[...])
+            )
+
+            // Apply to our copy of the tokens array
+            if edit.deleteCount > 0 {
+                tokenData.replaceSubrange(Int(edit.start)..<Int(edit.start + edit.deleteCount), with: edit.data ?? [])
+            } else {
+                tokenData.insert(contentsOf: edit.data ?? [], at: Int(edit.start))
+            }
+
+            if edit.data != nil {
+                invalidatedSet.append(
+                    contentsOf: invalidatedRanges(
+                        startIdx: edit.start,
+                        length: UInt(edit.data?.count ?? 0),
+                        data: tokenData[...]
+                    )
+                )
+            }
+        }
+
+        // Re-decode the updated token data and set the updated state
+        let decodedTokens = SemanticTokens(data: tokenData).decode()
+        state = CurrentState(resultId: deltas.resultId, tokenData: tokenData, tokens: decodedTokens)
+        return invalidatedSet
+    }
+
+    // MARK: - Invalidated Indices
+
+    /// Calculate what document ranges are invalidated due to changes in the compressed token data.
+    ///
+    /// This overestimates invalidated ranges by assuming all tokens touched by a change are invalid. All this does is
+    /// find what tokens are being updated by a delta and return them.
+    ///
+    /// - Parameters:
+    ///   - startIdx: The start index of the compressed token data an edits start at.
+    ///   - length: The length of any edits.
+    ///   - data: A reference to the compressed token data.
+    /// - Returns: All token ranges included in the range of the edit.
+    func invalidatedRanges(startIdx: UInt, length: UInt, data: ArraySlice<UInt32>) -> [SemanticTokenRange] {
+        var ranges: [SemanticTokenRange] = []
+        var idx = startIdx - (startIdx % 5)
+        while idx < startIdx + length {
+            ranges.append(
+                SemanticTokenRange(
+                    line: data[Int(idx)],
+                    char: data[Int(idx + 1)],
+                    length: data[Int(idx + 2)]
+                )
+            )
+            idx += 5
+        }
+        return ranges
+    }
+
+    // MARK: - Binary Search
+
+    /// Finds the lowest index of a `SemanticToken` that is entirely within the specified range.
+    /// - Complexity: Runs an **O(log n)** binary search on the data array.
+    /// - Parameters:
+    ///   - range: The range to search in, *not* inclusive.
+    ///   - data: The tokens to search. Takes an array slice to avoid unnecessary copying. This must be ordered by
+    ///           `startPosition`.
+    /// - Returns: The index in the data array of the lowest data element that lies within the given range, or `nil`
+    ///            if none are found.
+    func findLowerBound(in range: LSPRange, data: ArraySlice<SemanticToken>) -> Int? {
+        var low = 0
+        var high = data.count
+
+        // Find the first token with startPosition >= range.start.
+        while low < high {
+            let mid = low + (high - low) / 2
+            if data[mid].startPosition < range.start {
+                low = mid + 1
+            } else {
+                high = mid
+            }
+        }
+
+        // Return the item at `low` if it's valid.
+        if low < data.count && data[low].startPosition >= range.start && data[low].endPosition < range.end {
+            return low
+        }
+
+        return nil
+    }
+}
diff --git a/CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+DocumentSync.swift b/CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+DocumentSync.swift
index 563604aa7..be69c6647 100644
--- a/CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+DocumentSync.swift
+++ b/CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+DocumentSync.swift
@@ -12,7 +12,7 @@ extension LanguageServer {
     /// Tells the language server we've opened a document and would like to begin working with it.
     /// - Parameter document: The code document to open.
     /// - Throws: Throws errors produced by the language server connection.
-    func openDocument(_ document: CodeFileDocument) async throws {
+    func openDocument(_ document: DocumentType) async throws {
         do {
             guard resolveOpenCloseSupport(), let content = await getIsolatedDocumentContent(document) else {
                 return
@@ -29,7 +29,7 @@ extension LanguageServer {
             )
             try await lspInstance.textDocumentDidOpen(DidOpenTextDocumentParams(textDocument: textDocument))
 
-            await updateIsolatedDocument(document, coordinator: openFiles.contentCoordinator(for: document))
+            await updateIsolatedDocument(document)
         } catch {
             logger.warning("addDocument: Error \(error)")
             throw error
@@ -41,9 +41,12 @@ extension LanguageServer {
     /// - Throws: Throws errors produced by the language server connection.
     func closeDocument(_ uri: String) async throws {
         do {
-            guard resolveOpenCloseSupport() && openFiles.document(for: uri) != nil else { return }
+            guard resolveOpenCloseSupport(), let document = openFiles.document(for: uri) else { return }
             logger.debug("Closing document \(uri, privacy: .private)")
+
             openFiles.removeDocument(for: uri)
+            await clearIsolatedDocument(document)
+
             let params = DidCloseTextDocumentParams(textDocument: TextDocumentIdentifier(uri: uri))
             try await lspInstance.textDocumentDidClose(params)
         } catch {
@@ -78,10 +81,11 @@ extension LanguageServer {
     func documentChanged(uri: String, changes: [DocumentChange]) async throws {
         do {
             logger.debug("Document updated, \(uri, privacy: .private)")
+            guard let document = openFiles.document(for: uri) else { return }
+
             switch resolveDocumentSyncKind() {
             case .full:
-                guard let document = openFiles.document(for: uri),
-                      let content = await getIsolatedDocumentContent(document) else {
+                guard let content = await getIsolatedDocumentContent(document) else {
                     return
                 }
                 let changeEvent = TextDocumentContentChangeEvent(range: nil, rangeLength: nil, text: content.string)
@@ -100,6 +104,10 @@ extension LanguageServer {
             case .none:
                 return
             }
+
+            // Let the semantic token provider know about the update.
+            // Note for future: If a related LSP object need notifying about document changes, do it here.
+            try await document.languageServerObjects.highlightProvider?.documentDidChange()
         } catch {
             logger.warning("closeDocument: Error \(error)")
             throw error
@@ -110,18 +118,25 @@ extension LanguageServer {
 
     /// Helper function for grabbing a document's content from the main actor.
     @MainActor
-    private func getIsolatedDocumentContent(_ document: CodeFileDocument) -> DocumentContent? {
+    private func getIsolatedDocumentContent(_ document: DocumentType) -> DocumentContent? {
         guard let uri = document.languageServerURI,
-              let language = document.getLanguage().lspLanguage,
               let content = document.content?.string else {
             return nil
         }
-        return DocumentContent(uri: uri, language: language, string: content)
+        return DocumentContent(uri: uri, language: document.getLanguage().id.rawValue, string: content)
+    }
+
+    @MainActor
+    private func updateIsolatedDocument(_ document: DocumentType) {
+        document.languageServerObjects = LanguageServerDocumentObjects(
+            textCoordinator: openFiles.contentCoordinator(for: document),
+            highlightProvider: openFiles.semanticHighlighter(for: document)
+        )
     }
 
     @MainActor
-    private func updateIsolatedDocument(_ document: CodeFileDocument, coordinator: LSPContentCoordinator?) {
-        document.lspCoordinator = coordinator
+    private func clearIsolatedDocument(_ document: DocumentType) {
+        document.languageServerObjects = LanguageServerDocumentObjects()
     }
 
     // swiftlint:disable line_length
@@ -156,7 +171,7 @@ extension LanguageServer {
     // Used to avoid a lint error (`large_tuple`) for the return type of `getIsolatedDocumentContent`
     fileprivate struct DocumentContent {
         let uri: String
-        let language: LanguageIdentifier
+        let language: String
         let string: String
     }
 }
diff --git a/CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+SemanticTokens.swift b/CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+SemanticTokens.swift
index 02cb29947..b95098d02 100644
--- a/CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+SemanticTokens.swift
+++ b/CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+SemanticTokens.swift
@@ -9,12 +9,9 @@ import Foundation
 import LanguageServerProtocol
 
 extension LanguageServer {
-    /// Setup and test the validity of a rename operation at a given location
     func requestSemanticTokens(for documentURI: String) async throws -> SemanticTokensResponse {
         do {
-            let params = SemanticTokensParams(
-                textDocument: TextDocumentIdentifier(uri: documentURI)
-            )
+            let params = SemanticTokensParams(textDocument: TextDocumentIdentifier(uri: documentURI))
             return try await lspInstance.semanticTokensFull(params)
         } catch {
             logger.warning("requestSemanticTokens full: Error \(error)")
@@ -22,19 +19,6 @@ extension LanguageServer {
         }
     }
 
-    func requestSemanticTokens(
-        for documentURI: String,
-        forRange range: LSPRange
-    ) async throws -> SemanticTokensResponse {
-        do {
-            let params = SemanticTokensRangeParams(textDocument: TextDocumentIdentifier(uri: documentURI), range: range)
-            return try await lspInstance.semanticTokensRange(params)
-        } catch {
-            logger.warning("requestSemanticTokens range: Error \(error)")
-            throw error
-        }
-    }
-
     func requestSemanticTokens(
         for documentURI: String,
         previousResultId: String
diff --git a/CodeEdit/Features/LSP/LanguageServer/LanguageServer.swift b/CodeEdit/Features/LSP/LanguageServer/LanguageServer.swift
index eab8be550..a7c48bb25 100644
--- a/CodeEdit/Features/LSP/LanguageServer/LanguageServer.swift
+++ b/CodeEdit/Features/LSP/LanguageServer/LanguageServer.swift
@@ -11,8 +11,11 @@ import LanguageClient
 import LanguageServerProtocol
 import OSLog
 
-class LanguageServer {
-    static let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "", category: "LanguageServer")
+/// A client for language servers.
+class LanguageServer<DocumentType: LanguageServerDocument> {
+    static var logger: Logger { // types with associated types cannot have constant static properties
+        Logger(subsystem: Bundle.main.bundleIdentifier ?? "", category: "LanguageServer")
+    }
     let logger: Logger
 
     /// Identifies which language the server belongs to
@@ -25,7 +28,7 @@ class LanguageServer {
     /// Tracks documents and their associated objects.
     /// Use this property when adding new objects that need to track file data, or have a state associated with the
     /// language server and a document. For example, the content coordinator.
-    let openFiles: LanguageServerFileMap
+    let openFiles: LanguageServerFileMap<DocumentType>
 
     /// Maps the language server's highlight config to one CodeEdit can read. See ``SemanticTokenMap``.
     let highlightMap: SemanticTokenMap?
@@ -152,13 +155,13 @@ class LanguageServer {
                 // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokensClientCapabilities
                 semanticTokens: SemanticTokensClientCapabilities(
                     dynamicRegistration: false,
-                    requests: .init(range: true, delta: true),
+                    requests: .init(range: false, delta: true),
                     tokenTypes: SemanticTokenTypes.allStrings,
                     tokenModifiers: SemanticTokenModifiers.allStrings,
                     formats: [.relative],
                     overlappingTokenSupport: true,
                     multilineTokenSupport: true,
-                    serverCancelSupport: true,
+                    serverCancelSupport: false,
                     augmentsSyntaxTokens: true
                 )
             )
@@ -218,7 +221,7 @@ class LanguageServer {
                 processId: nil,
                 locale: nil,
                 rootPath: nil,
-                rootUri: workspacePath,
+                rootUri: "file://" + workspacePath, // Make it a URI
                 initializationOptions: [],
                 capabilities: capabilities,
                 trace: nil,
diff --git a/CodeEdit/Features/LSP/LanguageServer/LanguageServerFileMap.swift b/CodeEdit/Features/LSP/LanguageServer/LanguageServerFileMap.swift
index c681e894a..fd71a06b7 100644
--- a/CodeEdit/Features/LSP/LanguageServer/LanguageServerFileMap.swift
+++ b/CodeEdit/Features/LSP/LanguageServer/LanguageServerFileMap.swift
@@ -9,39 +9,55 @@ import Foundation
 import LanguageServerProtocol
 
 /// Tracks data associated with files and language servers.
-class LanguageServerFileMap {
+class LanguageServerFileMap<DocumentType: LanguageServerDocument> {
+    typealias HighlightProviderType = SemanticTokenHighlightProvider<SemanticTokenStorage, DocumentType>
+
     /// Extend this struct as more objects are associated with a code document.
     private struct DocumentObject {
         let uri: String
         var documentVersion: Int
-        var contentCoordinator: LSPContentCoordinator
+        var contentCoordinator: LSPContentCoordinator<DocumentType>
+        var semanticHighlighter: HighlightProviderType?
     }
 
-    private var trackedDocuments: NSMapTable<NSString, CodeFileDocument>
+    private var trackedDocuments: NSMapTable<NSString, DocumentType>
     private var trackedDocumentData: [String: DocumentObject] = [:]
 
     init() {
-        trackedDocuments = NSMapTable<NSString, CodeFileDocument>(valueOptions: [.weakMemory])
+        trackedDocuments = NSMapTable<NSString, DocumentType>(valueOptions: [.weakMemory])
     }
 
     // MARK: - Track & Remove Documents
 
-    func addDocument(_ document: CodeFileDocument, for server: LanguageServer) {
+    func addDocument(_ document: DocumentType, for server: LanguageServer<DocumentType>) {
         guard let uri = document.languageServerURI else { return }
         trackedDocuments.setObject(document, forKey: uri as NSString)
-        trackedDocumentData[uri] = DocumentObject(
+        var docData = DocumentObject(
             uri: uri,
             documentVersion: 0,
-            contentCoordinator: LSPContentCoordinator(documentURI: uri, languageServer: server)
+            contentCoordinator: LSPContentCoordinator(
+                documentURI: uri,
+                languageServer: server
+            ),
+            semanticHighlighter: nil
         )
+
+        if let tokenMap = server.highlightMap {
+            docData.semanticHighlighter = HighlightProviderType(
+                tokenMap: tokenMap,
+                languageServer: server,
+                documentURI: uri
+            )
+        }
+
+        trackedDocumentData[uri] = docData
     }
 
-    func document(for uri: DocumentUri) -> CodeFileDocument? {
-        let url = URL(filePath: uri)
-        return trackedDocuments.object(forKey: url.absolutePath as NSString)
+    func document(for uri: DocumentUri) -> DocumentType? {
+        return trackedDocuments.object(forKey: uri as NSString)
     }
 
-    func removeDocument(for document: CodeFileDocument) {
+    func removeDocument(for document: DocumentType) {
         guard let uri = document.languageServerURI else { return }
         removeDocument(for: uri)
     }
@@ -53,7 +69,7 @@ class LanguageServerFileMap {
 
     // MARK: - Version Number Tracking
 
-    func incrementVersion(for document: CodeFileDocument) -> Int {
+    func incrementVersion(for document: DocumentType) -> Int {
         guard let uri = document.languageServerURI else { return 0 }
         return incrementVersion(for: uri)
     }
@@ -63,7 +79,7 @@ class LanguageServerFileMap {
         return trackedDocumentData[uri]?.documentVersion ?? 0
     }
 
-    func documentVersion(for document: CodeFileDocument) -> Int? {
+    func documentVersion(for document: DocumentType) -> Int? {
         guard let uri = document.languageServerURI else { return nil }
         return documentVersion(for: uri)
     }
@@ -74,12 +90,19 @@ class LanguageServerFileMap {
 
     // MARK: - Content Coordinator
 
-    func contentCoordinator(for document: CodeFileDocument) -> LSPContentCoordinator? {
+    func contentCoordinator(for document: DocumentType) -> LSPContentCoordinator<DocumentType>? {
         guard let uri = document.languageServerURI else { return nil }
         return contentCoordinator(for: uri)
     }
 
-    func contentCoordinator(for uri: DocumentUri) -> LSPContentCoordinator? {
+    func contentCoordinator(for uri: DocumentUri) -> LSPContentCoordinator<DocumentType>? {
         trackedDocumentData[uri]?.contentCoordinator
     }
+
+    // MARK: - Semantic Highlighter
+
+    func semanticHighlighter(for document: DocumentType) -> HighlightProviderType? {
+        guard let uri = document.languageServerURI else { return nil }
+        return trackedDocumentData[uri]?.semanticHighlighter
+    }
 }
diff --git a/CodeEdit/Features/LSP/LanguageServerDocument.swift b/CodeEdit/Features/LSP/LanguageServerDocument.swift
new file mode 100644
index 000000000..8b4b09a47
--- /dev/null
+++ b/CodeEdit/Features/LSP/LanguageServerDocument.swift
@@ -0,0 +1,23 @@
+//
+//  LanguageServerDocument.swift
+//  CodeEdit
+//
+//  Created by Khan Winter on 2/12/25.
+//
+
+import AppKit
+import CodeEditLanguages
+
+/// A set of properties a language server sets when a document is registered.
+struct LanguageServerDocumentObjects<DocumentType: LanguageServerDocument> {
+    var textCoordinator: LSPContentCoordinator<DocumentType>?
+    var highlightProvider: SemanticTokenHighlightProvider<SemanticTokenStorage, DocumentType>?
+}
+
+/// A protocol that allows a language server to register objects on a text document.
+protocol LanguageServerDocument: AnyObject {
+    var content: NSTextStorage? { get }
+    var languageServerURI: String? { get }
+    var languageServerObjects: LanguageServerDocumentObjects<Self> { get set }
+    func getLanguage() -> CodeLanguage
+}
diff --git a/CodeEdit/Features/LSP/Service/LSPService.swift b/CodeEdit/Features/LSP/Service/LSPService.swift
index 5110c6c3e..df74fb139 100644
--- a/CodeEdit/Features/LSP/Service/LSPService.swift
+++ b/CodeEdit/Features/LSP/Service/LSPService.swift
@@ -42,7 +42,7 @@ import CodeEditLanguages
 ///     do {
 ///         guard var languageClient = self.languageClient(for: .python) else {
 ///             print("Failed to get client")
-///             throw ServerManagerError.languageClientNotFound
+///             throw LSPServiceError.languageClientNotFound
 ///         }
 ///
 ///         let testFilePathStr = ""
@@ -54,7 +54,7 @@ import CodeEditLanguages
 ///         // Completion example
 ///         let textPosition = Position(line: 32, character: 18)  // Lines and characters start at 0
 ///         let completions = try await languageClient.requestCompletion(
-///             document: testFileURL.absoluteString,
+///             document: testFileURL.lspURI,
 ///             position: textPosition
 ///         )
 ///         switch completions {
@@ -99,6 +99,8 @@ import CodeEditLanguages
 /// ```
 @MainActor
 final class LSPService: ObservableObject {
+    typealias LanguageServerType = LanguageServer<CodeFileDocument>
+
     let logger: Logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "", category: "LSPService")
 
     struct ClientKey: Hashable, Equatable {
@@ -112,7 +114,7 @@ final class LSPService: ObservableObject {
     }
 
     /// Holds the active language clients
-    var languageClients: [ClientKey: LanguageServer] = [:]
+    var languageClients: [ClientKey: LanguageServerType] = [:]
     /// Holds the language server configurations for all the installed language servers
     var languageConfigs: [LanguageIdentifier: LanguageServerBinary] = [:]
     /// Holds all the event listeners for each active language client
@@ -162,10 +164,16 @@ final class LSPService: ObservableObject {
     }
 
     /// Gets the language client for the specified language
-    func languageClient(for languageId: LanguageIdentifier, workspacePath: String) -> LanguageServer? {
+    func languageClient(for languageId: LanguageIdentifier, workspacePath: String) -> LanguageServerType? {
         return languageClients[ClientKey(languageId, workspacePath)]
     }
 
+    func languageClient(forDocument url: URL) -> LanguageServerType? {
+        languageClients.values.first(where: { $0.openFiles.document(for: url.lspURI) != nil })
+    }
+
+    // MARK: - Start Server
+
     /// Given a language and workspace path, will attempt to start the language server
     /// - Parameters:
     ///   - languageId: The ID of the language server to start.
@@ -174,14 +182,14 @@ final class LSPService: ObservableObject {
     func startServer(
         for languageId: LanguageIdentifier,
         workspacePath: String
-    ) async throws -> LanguageServer {
+    ) async throws -> LanguageServerType {
         guard let serverBinary = languageConfigs[languageId] else {
             logger.error("Couldn't find language sever binary for \(languageId.rawValue)")
             throw LSPError.binaryNotFound
         }
 
         logger.info("Starting \(languageId.rawValue) language server")
-        let server = try await LanguageServer.createServer(
+        let server = try await LanguageServerType.createServer(
             for: languageId,
             with: serverBinary,
             workspacePath: workspacePath
@@ -193,6 +201,8 @@ final class LSPService: ObservableObject {
         return server
     }
 
+    // MARK: - Document Management
+
     /// Notify all relevant language clients that a document was opened.
     /// - Note: Must be invoked after the contents of the file are available.
     /// - Parameter document: The code document that was opened.
@@ -203,7 +213,7 @@ final class LSPService: ObservableObject {
             return
         }
         Task {
-            let languageServer: LanguageServer
+            let languageServer: LanguageServerType
             do {
                 if let server = self.languageClients[ClientKey(lspLanguage, workspacePath)] {
                     languageServer = server
@@ -228,21 +238,19 @@ final class LSPService: ObservableObject {
     /// Notify all relevant language clients that a document was closed.
     /// - Parameter url: The url of the document that was closed
     func closeDocument(_ url: URL) {
-        guard let languageClient = languageClients.first(where: {
-                  $0.value.openFiles.document(for: url.absolutePath) != nil
-              })?.value else {
-            return
-        }
+        guard let languageClient = languageClient(forDocument: url) else { return }
         Task {
             do {
-                try await languageClient.closeDocument(url.absolutePath)
+                try await languageClient.closeDocument(url.lspURI)
             } catch {
                 // swiftlint:disable:next line_length
-                logger.error("Failed to close document: \(url.absolutePath, privacy: .private), language: \(languageClient.languageId.rawValue). Error \(error)")
+                logger.error("Failed to close document: \(url.lspURI, privacy: .private), language: \(languageClient.languageId.rawValue). Error \(error)")
             }
         }
     }
 
+    // MARK: - Close Workspace
+
     /// Close all language clients for a workspace.
     ///
     /// This is intentionally synchronous so we can exit from the workspace document's ``WorkspaceDocument/close()``
@@ -266,6 +274,8 @@ final class LSPService: ObservableObject {
         }
     }
 
+    // MARK: - Stop Servers
+
     /// Attempts to stop a running language server. Throws an error if the server is not found
     /// or if the language server throws an error while trying to shutdown.
     /// - Parameters:
@@ -274,7 +284,7 @@ final class LSPService: ObservableObject {
     func stopServer(forLanguage languageId: LanguageIdentifier, workspacePath: String) async throws {
         guard let server = server(for: languageId, workspacePath: workspacePath) else {
             logger.error("Server not found for language \(languageId.rawValue) during stop operation")
-            throw ServerManagerError.serverNotFound
+            throw LSPServiceError.serverNotFound
         }
         do {
             try await server.shutdownAndExit()
@@ -309,12 +319,3 @@ final class LSPService: ObservableObject {
         eventListeningTasks.removeAll()
     }
 }
-
-// MARK: - Errors
-
-enum ServerManagerError: Error {
-    case serverNotFound
-    case serverStartFailed
-    case serverStopFailed
-    case languageClientNotFound
-}
diff --git a/CodeEdit/Features/LSP/Service/LSPServiceError.swift b/CodeEdit/Features/LSP/Service/LSPServiceError.swift
new file mode 100644
index 000000000..d542e4d75
--- /dev/null
+++ b/CodeEdit/Features/LSP/Service/LSPServiceError.swift
@@ -0,0 +1,13 @@
+//
+//  LSPServiceError.swift
+//  CodeEdit
+//
+//  Created by Khan Winter on 3/24/25.
+//
+
+enum LSPServiceError: Error {
+    case serverNotFound
+    case serverStartFailed
+    case serverStopFailed
+    case languageClientNotFound
+}
diff --git a/CodeEdit/Utils/Extensions/SemanticToken/SemanticToken+Position.swift b/CodeEdit/Utils/Extensions/SemanticToken/SemanticToken+Position.swift
new file mode 100644
index 000000000..0e700938a
--- /dev/null
+++ b/CodeEdit/Utils/Extensions/SemanticToken/SemanticToken+Position.swift
@@ -0,0 +1,18 @@
+//
+//  SemanticToken+Position.swift
+//  CodeEdit
+//
+//  Created by Khan Winter on 12/26/24.
+//
+
+import LanguageServerProtocol
+
+extension SemanticToken {
+    var startPosition: Position {
+        Position(line: Int(line), character: Int(char))
+    }
+
+    var endPosition: Position {
+        Position(line: Int(line), character: Int(char + length))
+    }
+}
diff --git a/CodeEdit/Utils/Extensions/TextView/TextView+SemanticTokenRangeProvider.swift b/CodeEdit/Utils/Extensions/TextView/TextView+SemanticTokenRangeProvider.swift
index f41060423..976f9970f 100644
--- a/CodeEdit/Utils/Extensions/TextView/TextView+SemanticTokenRangeProvider.swift
+++ b/CodeEdit/Utils/Extensions/TextView/TextView+SemanticTokenRangeProvider.swift
@@ -7,8 +7,13 @@
 
 import Foundation
 import CodeEditTextView
+import LanguageServerProtocol
 
 extension TextView: SemanticTokenMapRangeProvider {
+    func nsRangeFrom(_ range: SemanticTokenRange) -> NSRange? {
+        nsRangeFrom(line: range.line, char: range.char, length: range.length)
+    }
+
     func nsRangeFrom(line: UInt32, char: UInt32, length: UInt32) -> NSRange? {
         guard let line = layoutManager.textLineForIndex(Int(line)) else {
             return nil
diff --git a/CodeEdit/Utils/Extensions/URL/URL+LSPURI.swift b/CodeEdit/Utils/Extensions/URL/URL+LSPURI.swift
new file mode 100644
index 000000000..f29f9057c
--- /dev/null
+++ b/CodeEdit/Utils/Extensions/URL/URL+LSPURI.swift
@@ -0,0 +1,18 @@
+//
+//  URL+LSPURI.swift
+//  CodeEdit
+//
+//  Created by Khan Winter on 3/24/25.
+//
+
+import Foundation
+
+extension URL {
+    /// A stable string to use when identifying documents with language servers.
+    /// Needs to be a valid URI, so always returns with the `file://` prefix to indicate it's a file URI.
+    ///
+    /// Use this whenever possible when using USLs in LSP processing if not using the ``LanguageServerDocument`` type.
+    var lspURI: String {
+        return "file://" + absolutePath
+    }
+}
diff --git a/CodeEditTests/Features/LSP/LanguageServer+DocumentTests.swift b/CodeEditTests/Features/LSP/LanguageServer+CodeFileDocument.swift
similarity index 80%
rename from CodeEditTests/Features/LSP/LanguageServer+DocumentTests.swift
rename to CodeEditTests/Features/LSP/LanguageServer+CodeFileDocument.swift
index d5bee0c13..236f2a721 100644
--- a/CodeEditTests/Features/LSP/LanguageServer+DocumentTests.swift
+++ b/CodeEditTests/Features/LSP/LanguageServer+CodeFileDocument.swift
@@ -1,5 +1,5 @@
 //
-//  LanguageServer+DocumentTests.swift
+//  LanguageServer+CodeFileDocument.swift
 //  CodeEditTests
 //
 //  Created by Khan Winter on 9/9/24.
@@ -13,10 +13,16 @@ import LanguageServerProtocol
 
 @testable import CodeEdit
 
-final class LanguageServerDocumentTests: XCTestCase {
+/// This is an integration test for notifications relating to the ``CodeFileDocument`` class.
+/// 
+/// For *unit* tests with the language server class, add tests to the `LanguageServer+DocumentObjects` test class as
+/// it's cleaner and makes correct use of the mock document type.
+final class LanguageServerCodeFileDocumentTests: XCTestCase {
     // Test opening documents in CodeEdit triggers creating a language server,
     // further opened documents don't create new servers
 
+    typealias LanguageServerType = LanguageServer<CodeFileDocument>
+
     var tempTestDir: URL!
 
     override func setUp() {
@@ -44,7 +50,7 @@ final class LanguageServerDocumentTests: XCTestCase {
         }
     }
 
-    func makeTestServer() async throws -> (connection: BufferingServerConnection, server: LanguageServer) {
+    func makeTestServer() async throws -> (connection: BufferingServerConnection, server: LanguageServerType) {
         let bufferingConnection = BufferingServerConnection()
         var capabilities = ServerCapabilities()
         capabilities.textDocumentSync = .optionA(
@@ -56,12 +62,12 @@ final class LanguageServerDocumentTests: XCTestCase {
                 save: nil
             )
         )
-        let server = LanguageServer(
+        let server = LanguageServerType(
             languageId: .swift,
             binary: .init(execPath: "", args: [], env: nil),
             lspInstance: InitializingServer(
                 server: bufferingConnection,
-                initializeParamsProvider: LanguageServer.getInitParams(workspacePath: tempTestDir.path())
+                initializeParamsProvider: LanguageServerType.getInitParams(workspacePath: tempTestDir.path())
             ),
             serverCapabilities: capabilities,
             rootPath: tempTestDir
@@ -81,7 +87,7 @@ final class LanguageServerDocumentTests: XCTestCase {
     }
 
     func openCodeFile(
-        for server: LanguageServer,
+        for server: LanguageServerType,
         connection: BufferingServerConnection,
         file: CEWorkspaceFile,
         syncOption: TwoTypeOption<TextDocumentSyncOptions, TextDocumentSyncKind>?
@@ -95,8 +101,11 @@ final class LanguageServerDocumentTests: XCTestCase {
         // This is usually sent from the LSPService
         try await server.openDocument(codeFile)
 
-        await waitForClientEventCount(
-            3,
+        await waitForClientState(
+            (
+                [.initialize],
+                [.initialized, .textDocumentDidOpen]
+            ),
             connection: connection,
             description: "Initialized (2) and opened (1) notification count"
         )
@@ -108,15 +117,18 @@ final class LanguageServerDocumentTests: XCTestCase {
         return codeFile
     }
 
-    func waitForClientEventCount(_ count: Int, connection: BufferingServerConnection, description: String) async {
+    func waitForClientState(
+        _ expectedValue: ([ClientRequest.Method], [ClientNotification.Method]),
+        connection: BufferingServerConnection,
+        description: String
+    ) async {
         let expectation = expectation(description: description)
 
         await withTaskGroup(of: Void.self) { group in
+            group.addTask { await self.fulfillment(of: [expectation], timeout: 2) }
             group.addTask {
-                await self.fulfillment(of: [expectation], timeout: 2)
-            }
-            group.addTask {
-                for await events in connection.clientEventSequence where events.0.count + events.1.count == count {
+                for await events in connection.clientEventSequence
+                where events.0.map(\.method) == expectedValue.0 && events.1.map(\.method) == expectedValue.1 {
                     expectation.fulfill()
                     return
                 }
@@ -124,6 +136,8 @@ final class LanguageServerDocumentTests: XCTestCase {
         }
     }
 
+    // MARK: - Open Close
+
     @MainActor
     func testOpenCloseFileNotifications() async throws {
         // Set up test server
@@ -153,30 +167,30 @@ final class LanguageServerDocumentTests: XCTestCase {
         file.fileDocument = codeFile
         CodeEditDocumentController.shared.addDocument(codeFile)
 
-        await waitForClientEventCount(3, connection: connection, description: "Pre-close event count")
+        await waitForClientState(
+            (
+                [.initialize],
+                [.initialized, .textDocumentDidOpen]
+            ),
+            connection: connection,
+            description: "Pre-close event count"
+        )
 
         // This should then trigger a documentDidClose event
         codeFile.close()
 
-        await waitForClientEventCount(4, connection: connection, description: "Post-close event count")
-
-        XCTAssertEqual(
-            connection.clientRequests.map { $0.method },
-            [
-                ClientRequest.Method.initialize,
-            ]
-        )
-
-        XCTAssertEqual(
-            connection.clientNotifications.map { $0.method },
-            [
-                ClientNotification.Method.initialized,
-                ClientNotification.Method.textDocumentDidOpen,
-                ClientNotification.Method.textDocumentDidClose
-            ]
+        await waitForClientState(
+            (
+                [.initialize],
+                [.initialized, .textDocumentDidOpen, .textDocumentDidClose]
+            ),
+            connection: connection,
+            description: "Post-close event count"
         )
     }
 
+    // MARK: - Test Document Edit
+
     /// Assert the changed contents received by the buffered connection
     func assertExpectedContentChanges(connection: BufferingServerConnection, changes: [String]) {
         var foundChangeContents: [String] = []
@@ -184,9 +198,7 @@ final class LanguageServerDocumentTests: XCTestCase {
         for notification in connection.clientNotifications {
             switch notification {
             case let .textDocumentDidChange(params):
-                foundChangeContents.append(contentsOf: params.contentChanges.map { event in
-                    event.text
-                })
+                foundChangeContents.append(contentsOf: params.contentChanges.map(\.text))
             default:
                 continue
             }
@@ -231,18 +243,17 @@ final class LanguageServerDocumentTests: XCTestCase {
             textView.replaceCharacters(in: NSRange(location: 39, length: 0), with: "World")
 
             // Added one notification
-            await waitForClientEventCount(4, connection: connection, description: "Edited notification count")
+            await waitForClientState(
+                (
+                    [.initialize],
+                    [.initialized, .textDocumentDidOpen, .textDocumentDidChange]
+                ),
+                connection: connection,
+                description: "Edited notification count"
+            )
 
             // Make sure our text view is intact
             XCTAssertEqual(textView.string, #"func testFunction() -> String { "Hello World" }"#)
-            XCTAssertEqual(
-                [
-                    ClientNotification.Method.initialized,
-                    ClientNotification.Method.textDocumentDidOpen,
-                    ClientNotification.Method.textDocumentDidChange
-                ],
-                connection.clientNotifications.map { $0.method }
-            )
 
             // Expect only one change due to throttling.
             assertExpectedContentChanges(
@@ -289,18 +300,17 @@ final class LanguageServerDocumentTests: XCTestCase {
             textView.replaceCharacters(in: NSRange(location: 39, length: 0), with: "World")
 
             // Throttling means we should receive one edited notification + init notification + didOpen + init request
-            await waitForClientEventCount(4, connection: connection, description: "Edited notification count")
+            await waitForClientState(
+                (
+                    [.initialize],
+                    [.initialized, .textDocumentDidOpen, .textDocumentDidChange]
+                ),
+                connection: connection,
+                description: "Edited notification count"
+            )
 
             // Make sure our text view is intact
             XCTAssertEqual(textView.string, #"func testFunction() -> String { "Hello World" }"#)
-            XCTAssertEqual(
-                [
-                    ClientNotification.Method.initialized,
-                    ClientNotification.Method.textDocumentDidOpen,
-                    ClientNotification.Method.textDocumentDidChange
-                ],
-                connection.clientNotifications.map { $0.method }
-            )
 
             // Expect three content changes.
             assertExpectedContentChanges(
diff --git a/CodeEditTests/Features/LSP/LanguageServer+DocumentObjects.swift b/CodeEditTests/Features/LSP/LanguageServer+DocumentObjects.swift
new file mode 100644
index 000000000..76b2e8cf3
--- /dev/null
+++ b/CodeEditTests/Features/LSP/LanguageServer+DocumentObjects.swift
@@ -0,0 +1,80 @@
+//
+//  LanguageServer+DocumentObjects.swift
+//  CodeEditTests
+//
+//  Created by Khan Winter on 2/12/25.
+//
+
+import XCTest
+import CodeEditTextView
+import CodeEditSourceEditor
+import CodeEditLanguages
+import LanguageClient
+import LanguageServerProtocol
+
+@testable import CodeEdit
+
+final class LanguageServerDocumentObjectsTests: XCTestCase {
+    final class MockDocumentType: LanguageServerDocument {
+        var content: NSTextStorage?
+        var languageServerURI: String?
+        var languageServerObjects: LanguageServerDocumentObjects<MockDocumentType>
+
+        init() {
+            self.content = NSTextStorage(string: "hello world")
+            self.languageServerURI = "/test/file/path"
+            self.languageServerObjects = .init()
+        }
+
+        func getLanguage() -> CodeLanguage {
+            .swift
+        }
+    }
+
+    typealias LanguageServerType = LanguageServer<MockDocumentType>
+
+    var document: MockDocumentType!
+    var server: LanguageServerType!
+
+    // MARK: - Set Up
+
+    override func setUp() async throws {
+        var capabilities = ServerCapabilities()
+        capabilities.textDocumentSync = .optionA(.init(openClose: true, change: .full))
+        capabilities.semanticTokensProvider = .optionA(.init(legend: .init(tokenTypes: [], tokenModifiers: [])))
+        server = LanguageServerType(
+            languageId: .swift,
+            binary: .init(execPath: "", args: [], env: nil),
+            lspInstance: InitializingServer(
+                server: BufferingServerConnection(),
+                initializeParamsProvider: LanguageServerType.getInitParams(workspacePath: "/")
+            ),
+            serverCapabilities: capabilities,
+            rootPath: URL(fileURLWithPath: "")
+        )
+        _ = try await server.lspInstance.initializeIfNeeded()
+        document = MockDocumentType()
+    }
+
+    // MARK: - Tests
+
+    func testOpenDocumentRegistersObjects() async throws {
+        try await server.openDocument(document)
+        XCTAssertNotNil(document.languageServerObjects.highlightProvider)
+        XCTAssertNotNil(document.languageServerObjects.textCoordinator)
+        XCTAssertNotNil(server.openFiles.document(for: document.languageServerURI ?? ""))
+    }
+
+    func testCloseDocumentClearsObjects() async throws {
+        guard let languageServerURI = document.languageServerURI else {
+            XCTFail("Language server URI missing on a mock object")
+            return
+        }
+        try await server.openDocument(document)
+        XCTAssertNotNil(server.openFiles.document(for: languageServerURI))
+
+        try await server.closeDocument(languageServerURI)
+        XCTAssertNil(document.languageServerObjects.highlightProvider)
+        XCTAssertNil(document.languageServerObjects.textCoordinator)
+    }
+}
diff --git a/CodeEditTests/Features/LSP/SemanticTokenMapTests.swift b/CodeEditTests/Features/LSP/SemanticTokens/SemanticTokenMapTests.swift
similarity index 79%
rename from CodeEditTests/Features/LSP/SemanticTokenMapTests.swift
rename to CodeEditTests/Features/LSP/SemanticTokens/SemanticTokenMapTests.swift
index 4c941de1a..a9ec5c5a3 100644
--- a/CodeEditTests/Features/LSP/SemanticTokenMapTests.swift
+++ b/CodeEditTests/Features/LSP/SemanticTokens/SemanticTokenMapTests.swift
@@ -10,7 +10,7 @@ import CodeEditSourceEditor
 import LanguageServerProtocol
 @testable import CodeEdit
 
-final class SemanticTokenMapTestsTests: XCTestCase {
+final class SemanticTokenMapTests: XCTestCase {
     // Ignores the line parameter and just returns a range from the char and length for testing
     struct MockRangeProvider: SemanticTokenMapRangeProvider {
         func nsRangeFrom(line: UInt32, char: UInt32, length: UInt32) -> NSRange? {
@@ -53,10 +53,10 @@ final class SemanticTokenMapTestsTests: XCTestCase {
 
         // Test decode tokens
         let tokens = SemanticTokens(tokens: [
-            SemanticToken(line: 0, char: 0, length: 1, type: 0, modifiers: 0b11),     // First two indices set
+            SemanticToken(line: 0, char: 0, length: 1, type: 1000000, modifiers: 0b11), // First two indices set
             SemanticToken(line: 0, char: 1, length: 2, type: 0, modifiers: 0b100100), // 6th and 3rd indices set
-            SemanticToken(line: 0, char: 4, length: 1, type: 0b1, modifiers: 0b101),
-            SemanticToken(line: 0, char: 5, length: 1, type: 0b100, modifiers: 0b1010),
+            SemanticToken(line: 0, char: 4, length: 1, type: 1, modifiers: 0b101),
+            SemanticToken(line: 0, char: 5, length: 1, type: 4, modifiers: 0b1010),
             SemanticToken(line: 0, char: 7, length: 10, type: 0, modifiers: 0)
         ])
         let decoded = map.decode(tokens: tokens, using: mockProvider)
@@ -69,10 +69,10 @@ final class SemanticTokenMapTestsTests: XCTestCase {
         XCTAssertEqual(decoded[4].range, NSRange(location: 7, length: 10), "Decoded range")
 
         XCTAssertEqual(decoded[0].capture, nil, "No Decoded Capture")
-        XCTAssertEqual(decoded[1].capture, nil, "No Decoded Capture")
-        XCTAssertEqual(decoded[2].capture, .include, "Decoded Capture")
-        XCTAssertEqual(decoded[3].capture, .keyword, "Decoded Capture")
-        XCTAssertEqual(decoded[4].capture, nil, "No Decoded Capture")
+        XCTAssertEqual(decoded[1].capture, .include, "No Decoded Capture")
+        XCTAssertEqual(decoded[2].capture, .constructor, "Decoded Capture")
+        XCTAssertEqual(decoded[3].capture, .comment, "Decoded Capture")
+        XCTAssertEqual(decoded[4].capture, .include, "No Decoded Capture")
 
         XCTAssertEqual(decoded[0].modifiers, [.declaration, .definition], "Decoded Modifiers")
         XCTAssertEqual(decoded[1].modifiers, [.readonly, .defaultLibrary], "Decoded Modifiers")
@@ -92,10 +92,10 @@ final class SemanticTokenMapTestsTests: XCTestCase {
 
         // Test decode tokens
         let tokens = SemanticTokens(tokens: [
-            SemanticToken(line: 0, char: 0, length: 1, type: 0, modifiers: 0b11),     // First two indices set
+            SemanticToken(line: 0, char: 0, length: 1, type: 100, modifiers: 0b11),     // First two indices set
             SemanticToken(line: 0, char: 1, length: 2, type: 0, modifiers: 0b100100), // 6th and 3rd indices set
-            SemanticToken(line: 0, char: 4, length: 1, type: 0b1, modifiers: 0b101),
-            SemanticToken(line: 0, char: 5, length: 1, type: 0b100, modifiers: 0b1010),
+            SemanticToken(line: 0, char: 4, length: 1, type: 1, modifiers: 0b101),
+            SemanticToken(line: 0, char: 5, length: 1, type: 4, modifiers: 0b1010),
             SemanticToken(line: 0, char: 7, length: 10, type: 0, modifiers: 0)
         ])
         let decoded = map.decode(tokens: tokens, using: mockProvider)
@@ -108,10 +108,10 @@ final class SemanticTokenMapTestsTests: XCTestCase {
         XCTAssertEqual(decoded[4].range, NSRange(location: 7, length: 10), "Decoded range")
 
         XCTAssertEqual(decoded[0].capture, nil, "No Decoded Capture")
-        XCTAssertEqual(decoded[1].capture, nil, "No Decoded Capture")
-        XCTAssertEqual(decoded[2].capture, .include, "Decoded Capture")
-        XCTAssertEqual(decoded[3].capture, .keyword, "Decoded Capture")
-        XCTAssertEqual(decoded[4].capture, nil, "No Decoded Capture")
+        XCTAssertEqual(decoded[1].capture, .include, "No Decoded Capture")
+        XCTAssertEqual(decoded[2].capture, .constructor, "Decoded Capture")
+        XCTAssertEqual(decoded[3].capture, .comment, "Decoded Capture")
+        XCTAssertEqual(decoded[4].capture, .include, "No Decoded Capture")
 
         XCTAssertEqual(decoded[0].modifiers, [.declaration, .definition], "Decoded Modifiers")
         XCTAssertEqual(decoded[1].modifiers, [.readonly, .defaultLibrary], "Decoded Modifiers")
diff --git a/CodeEditTests/Features/LSP/SemanticTokens/SemanticTokenStorageTests.swift b/CodeEditTests/Features/LSP/SemanticTokens/SemanticTokenStorageTests.swift
new file mode 100644
index 000000000..f2d0179ca
--- /dev/null
+++ b/CodeEditTests/Features/LSP/SemanticTokens/SemanticTokenStorageTests.swift
@@ -0,0 +1,199 @@
+//
+//  SemanticTokenStorageTests.swift
+//  CodeEdit
+//
+//  Created by Khan Winter on 12/26/24.
+//
+
+import Foundation
+import Testing
+import CodeEditSourceEditor
+import LanguageServerProtocol
+@testable import CodeEdit
+
+// For easier comparison while setting semantic tokens
+extension SemanticToken: @retroactive Equatable {
+    public static func == (lhs: SemanticToken, rhs: SemanticToken) -> Bool {
+        lhs.type == rhs.type
+        && lhs.modifiers == rhs.modifiers
+        && lhs.line == rhs.line
+        && lhs.char == rhs.char
+        && lhs.length == rhs.length
+    }
+}
+
+@Suite
+struct SemanticTokenStorageTests {
+    let storage = SemanticTokenStorage()
+
+    let semanticTokens = [
+        SemanticToken(line: 0, char: 0, length: 10, type: 0, modifiers: 0),
+        SemanticToken(line: 1, char: 2, length: 5, type: 2, modifiers: 3),
+        SemanticToken(line: 3, char: 8, length: 10, type: 1, modifiers: 0)
+    ]
+
+    @Test
+    func initialState() async throws {
+        #expect(storage.state == nil)
+        #expect(storage.hasReceivedData == false)
+        #expect(storage.lastResultId == nil)
+    }
+
+    @Test
+    func setData() async throws {
+        storage.setData(
+            SemanticTokens(
+                resultId: "1234",
+                tokens: semanticTokens
+            )
+        )
+
+        let state = try #require(storage.state)
+        #expect(state.tokens == semanticTokens)
+        #expect(state.resultId == "1234")
+
+        #expect(storage.lastResultId == "1234")
+        #expect(storage.hasReceivedData == true)
+    }
+
+    @Test
+    func overwriteDataRepeatedly() async throws {
+        let dataToApply: [(String?, [SemanticToken])] = [
+            (nil, semanticTokens),
+            ("1", []),
+            ("2", semanticTokens.dropLast()),
+            ("3", semanticTokens)
+        ]
+        for (resultId, tokens) in dataToApply {
+            storage.setData(SemanticTokens(resultId: resultId, tokens: tokens))
+            let state = try #require(storage.state)
+            #expect(state.tokens == tokens)
+            #expect(state.resultId == resultId)
+            #expect(storage.lastResultId == resultId)
+            #expect(storage.hasReceivedData == true)
+        }
+    }
+
+    @Suite("ApplyDeltas")
+    struct TokensDeltasTests {
+        struct DeltaEdit {
+            let start: Int
+            let deleteCount: Int
+            let data: [Int]
+
+            func makeString() -> String {
+                let dataString = data.map { String($0) }.joined(separator: ",")
+                return "{\"start\": \(start), \"deleteCount\": \(deleteCount), \"data\": [\(dataString)] }"
+            }
+        }
+
+        func makeDelta(resultId: String, edits: [DeltaEdit]) throws -> SemanticTokensDelta {
+            // This is unfortunate, but there's no public initializer for these structs.
+            // So we have to decode them from JSON strings
+            let editsString = edits.map { $0.makeString() }.joined(separator: ",")
+            let deltasJSON = "{ \"resultId\": \"\(resultId)\", \"edits\": [\(editsString)] }"
+            let decoder = JSONDecoder()
+            let deltas = try decoder.decode(SemanticTokensDelta.self, from: Data(deltasJSON.utf8))
+            return deltas
+        }
+
+        let storage: SemanticTokenStorage
+
+        let semanticTokens = [
+            SemanticToken(line: 0, char: 0, length: 10, type: 0, modifiers: 0),
+            SemanticToken(line: 1, char: 2, length: 5, type: 2, modifiers: 3),
+            SemanticToken(line: 3, char: 8, length: 10, type: 1, modifiers: 0)
+        ]
+
+        init() {
+            storage = SemanticTokenStorage()
+            storage.setData(SemanticTokens(tokens: semanticTokens))
+            #expect(storage.state?.tokens == semanticTokens)
+        }
+
+        @Test
+        func applyEmptyDeltasNoChange() throws {
+            let deltas = try makeDelta(resultId: "1", edits: [])
+
+            _ = storage.applyDelta(deltas)
+
+            let state = try #require(storage.state)
+            #expect(state.tokens.count == 3)
+            #expect(state.resultId == "1")
+            #expect(state.tokens == semanticTokens)
+        }
+
+        @Test
+        func applyInsertDeltas() throws {
+            let deltas = try makeDelta(resultId: "1", edits: [.init(start: 0, deleteCount: 0, data: [0, 2, 3, 0, 1])])
+
+            _ = storage.applyDelta(deltas)
+
+            let state = try #require(storage.state)
+            #expect(state.tokens.count == 4)
+            #expect(storage.lastResultId == "1")
+
+            // Should have inserted one at the beginning
+            #expect(state.tokens[0].line == 0)
+            #expect(state.tokens[0].char == 2)
+            #expect(state.tokens[0].length == 3)
+            #expect(state.tokens[0].modifiers == 1)
+
+            // We inserted a delta into the space before this one (at char 2) so this one starts at the same spot
+            #expect(state.tokens[1] == SemanticToken(line: 0, char: 2, length: 10, type: 0, modifiers: 0))
+            #expect(state.tokens[2] == semanticTokens[1])
+            #expect(state.tokens[3] == semanticTokens[2])
+        }
+
+        @Test
+        func applyDeleteOneDeltas() throws {
+            // Delete the second token (semanticTokens[1]) from the initial state.
+            // Each token is represented by 5 numbers, so token[1] starts at raw data index 5.
+            let deltas = try makeDelta(resultId: "2", edits: [.init(start: 5, deleteCount: 5, data: [])])
+            _ = storage.applyDelta(deltas)
+
+            let state = try #require(storage.state)
+            #expect(state.tokens.count == 2)
+            #expect(state.resultId == "2")
+            // The remaining tokens should be the first and third tokens, except we deleted one line between them
+            // so the third token's line is less one
+            #expect(state.tokens[0] == semanticTokens[0])
+            #expect(state.tokens[1] == SemanticToken(line: 2, char: 8, length: 10, type: 1, modifiers: 0))
+        }
+
+        @Test
+        func applyDeleteManyDeltas() throws {
+            // Delete the first two tokens from the initial state.
+            // Token[0] and token[1] together use 10 integers.
+            let deltas = try makeDelta(resultId: "3", edits: [.init(start: 0, deleteCount: 10, data: [])])
+            _ = storage.applyDelta(deltas)
+
+            let state = try #require(storage.state)
+            #expect(state.tokens.count == 1)
+            #expect(state.resultId == "3")
+            // The only remaining token should be the original third token.
+            #expect(state.tokens[0] == SemanticToken(line: 2, char: 8, length: 10, type: 1, modifiers: 0))
+        }
+
+        @Test
+        func applyInsertAndDeleteDeltas() throws {
+            // Combined test: insert a token at the beginning and delete the last token.
+            // Edit 1: Insert a new token at the beginning.
+            let insertion = DeltaEdit(start: 0, deleteCount: 0, data: [0, 2, 3, 0, 1])
+            // Edit 2: Delete the token that starts at raw data index 10 (the third token in the original state).
+            let deletion = DeltaEdit(start: 10, deleteCount: 5, data: [])
+            let deltas = try makeDelta(resultId: "4", edits: [insertion, deletion])
+            _ = storage.applyDelta(deltas)
+
+            let state = try #require(storage.state)
+            #expect(state.tokens.count == 3)
+            #expect(storage.lastResultId == "4")
+            // The new inserted token becomes the first token.
+            #expect(state.tokens[0] == SemanticToken(line: 0, char: 2, length: 3, type: 0, modifiers: 1))
+            // The original first token is shifted (its character offset increased by 2).
+            #expect(state.tokens[1] == SemanticToken(line: 0, char: 2, length: 10, type: 0, modifiers: 0))
+            // The second token from the original state remains unchanged.
+            #expect(state.tokens[2] == semanticTokens[1])
+        }
+    }
+}