@@ -256,8 +256,8 @@ extension CodeStorageDelegate {
256256
257257extension CodeStorageDelegate {
258258
259- /// Tokenise the substring of the given text storage that contains the specified lines and store token tokens as part
260- /// of the line information.
259+ /// Tokenise the substring of the given text storage that contains the specified lines and store tokens as part of the
260+ /// line information.
261261 ///
262262 /// - Parameters:
263263 /// - originalRange: The character range that contains all characters that have changed.
@@ -435,11 +435,87 @@ extension CodeStorageDelegate {
435435 currentLine += 1
436436 }
437437
438+ requestSemanticTokens ( for: lines, in: textStorage)
439+
438440 if visualDebugging {
439441 textStorage. addAttribute ( . backgroundColor, value: visualDebuggingTrailingColour, range: highlightingRange)
440442 textStorage. addAttribute ( . backgroundColor, value: visualDebuggingLinesColour, range: range)
441443 }
442444 }
445+
446+ /// Query semantic tokens for the given lines from the language service (if available) and merge them into the token
447+ /// information for those lines (maintained in the line map),
448+ ///
449+ /// - Parameters:
450+ /// lines: The lines for which semantic token information is requested.
451+ /// textStorage: The text storage whose contents is being tokenised.
452+ ///
453+ func requestSemanticTokens( for lines: Range < Int > , in textStorage: NSTextStorage ) {
454+ guard let firstLine = lines. first else { return }
455+
456+ Task {
457+ do {
458+ if let semanticTokens = try await languageService? . tokens ( for: lines) {
459+
460+ guard lines. count == semanticTokens. count else {
461+ logger. error ( " Language service returned an array of incorrect length; expected \( lines. count) , but got \( semanticTokens. count) " )
462+ return
463+ }
464+
465+ // We need to avoid concurrent write access to the line map.
466+ await MainActor . run {
467+
468+ // Merge the semantic tokens into the syntactic tokens per line
469+ for i in 0 ..< lines. count {
470+ merge ( semanticTokens: semanticTokens [ i] , into: firstLine + i)
471+ }
472+
473+ // Request redrawing for those lines
474+ for layoutManager in textStorage. layoutManagers {
475+ layoutManager. invalidateDisplay ( forCharacterRange: lineMap. charRangeOf ( lines: lines) )
476+ }
477+ }
478+
479+ }
480+ } catch let error { logger. error ( " Failed to get semantic tokens for line range \( lines) : \( error. localizedDescription) " ) }
481+ }
482+ }
483+
484+ /// Merge semantic token information for one line into the line map.
485+ ///
486+ /// - Parameters:
487+ /// - semanticTokens: The semntic tokens to merge.
488+ /// - line: The line on which the tokens are located.
489+ ///
490+ /// NB: Currently, we only enrich the information of tokens that are already present as syntactic tokens.
491+ ///
492+ private func merge( semanticTokens: [ ( token: LanguageConfiguration . Token , range: NSRange ) ] , into line: Int ) {
493+ guard var info = lineMap. lookup ( line: line) ? . info else { return }
494+
495+ var remainingSemanticTokens = semanticTokens
496+ var tokens = info. tokens
497+ for i in 0 ..< tokens. count {
498+
499+ let token = tokens [ i]
500+ while let semanticToken = remainingSemanticTokens. first,
501+ semanticToken. range. location <= token. range. location
502+ {
503+ remainingSemanticTokens. removeFirst ( )
504+
505+ // We enrich identifier and operator tokens if the semantic token is an identifier, operator, or keyword.
506+ if semanticToken. range == token. range
507+ && ( token. token. isIdentifier || token. token. isOperator)
508+ && ( semanticToken. token. isIdentifier || semanticToken. token. isOperator || semanticToken. token == . keyword)
509+ {
510+ tokens [ i] = LanguageConfiguration . Tokeniser. Token ( token: semanticToken. token, range: token. range)
511+ }
512+ }
513+ }
514+
515+ // Store updated token array
516+ info. tokens = tokens
517+ lineMap. setInfoOf ( line: line, to: info)
518+ }
443519}
444520
445521
0 commit comments