neoclide/coc.nvim · error
`range` cannot span multiple lines
Error message
`range` cannot span multiple lines
What it means
The semantic tokens protocol encodes tokens as single-line deltas, so every token range must start and end on the same line. _push validates range.start.line === range.end.line and throws when a range spans multiple lines.
Source
Thrown at src/model/semanticTokensBuilder.ts:82
if (typeof arg4 === 'undefined') {
arg4 = 0
}
// 1st overload
return this._pushEncoded(arg0, arg1, arg2, arg3, arg4)
}
if (Range.is(arg0) && typeof arg1 === 'string' && isStrArrayOrUndefined(arg2)) {
// 2nd overload
return this._push(arg0, arg1, arg2)
}
throw new Error('Illegal argument')
}
private _push(range: Range, tokenType: string, tokenModifiers?: string[]): void {
if (!this._hasLegend) {
throw new Error('Legend must be provided in constructor')
}
if (range.start.line !== range.end.line) {
throw new Error('`range` cannot span multiple lines')
}
if (!this._tokenTypeStrToInt.has(tokenType)) {
throw new Error('`tokenType` is not in the provided legend')
}
const line = range.start.line
const char = range.start.character
const length = range.end.character - range.start.character
const nTokenType = this._tokenTypeStrToInt.get(tokenType)!
let nTokenModifiers = 0
if (tokenModifiers) {
for (const tokenModifier of tokenModifiers) {
if (!this._tokenModifierStrToInt.has(tokenModifier)) {
throw new Error('`tokenModifier` is not in the provided legend')
}
const nTokenModifier = this._tokenModifierStrToInt.get(tokenModifier)!
nTokenModifiers |= (1 << nTokenModifier) >>> 0
}
}View on GitHub (pinned to 50e974d969)
Solutions
- Split multi-line tokens into one token per line before pushing
- Clamp the range to a single line: use range.start.line for both endpoints with the length limited to that line's content
- Fix the token producer (grammar/matcher) so matches never cross lines
Example fix
// before
builder.push(new Range(start, endMultiLine), 'comment')
// after
for (let line = start.line; line <= end.line; line++) {
const lineEnd = line === end.line ? end : doc.lineAt(line).range.end
const lineStart = line === start.line ? start : new Position(line, 0)
if (!lineStart.isEqual(lineEnd)) builder.push(new Range(lineStart, lineEnd), 'comment')
} Defensive patterns
Strategy: validation
Validate before calling
if (range.start.line !== range.end.line) {
// split into per-line ranges before pushing
} Type guard
function isSingleLine(r: Range): boolean {
return r.start.line === r.end.line
} Try / catch
try {
builder.push(range, tokenType)
} catch (e) {
if (e.message.includes('cannot span multiple lines')) {
splitAndPushPerLine(range, tokenType, builder)
}
} Prevention
- Normalize all ranges to single-line before pushing
- Configure grammars/matchers so tokens never cross lines
- Add an assertion in your token producer that start.line === end.line
When it happens
Trigger: Calling push() with a Range whose start.line differs from end.line — e.g. tokenizing a multiline comment/string/region as one token instead of splitting it per line.
Common situations: Textmate/Tree-sitter grammars that yield multiline captures; highlighting block comments or heredocs; incorrectly computing a token range from a multi-line match.
Related errors
- Semantic tokens highlight not enabled for current filetype:
- SemanticTokens provider not found for ${this.doc.uri}
- Unable to perform semantic highlights for current buffer.
- Not valid protocol with ${urlInput}, should be http: or http
- Illegal argument
AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31).
Data as JSON: /api/errors/22b8e56fadc74ae1.
Report an issue: GitHub.