2023-09-05 16:02:27 -07:00
|
|
|
// Extends the codemirror Parser for kcl.
|
|
|
|
|
|
|
|
import {
|
|
|
|
Parser,
|
|
|
|
Input,
|
|
|
|
TreeFragment,
|
|
|
|
PartialParse,
|
|
|
|
Tree,
|
|
|
|
NodeType,
|
|
|
|
NodeSet,
|
|
|
|
} from '@lezer/common'
|
2024-02-19 12:33:16 -08:00
|
|
|
import { LanguageServerClient } from 'editor/plugins/lsp'
|
|
|
|
import { posToOffset } from 'editor/plugins/lsp/util'
|
2023-09-05 16:02:27 -07:00
|
|
|
import { SemanticToken } from './semantic_tokens'
|
|
|
|
import { DocInput } from '@codemirror/language'
|
|
|
|
import { tags, styleTags } from '@lezer/highlight'
|
|
|
|
|
|
|
|
export default class KclParser extends Parser {
|
|
|
|
private client: LanguageServerClient
|
|
|
|
|
|
|
|
constructor(client: LanguageServerClient) {
|
|
|
|
super()
|
|
|
|
this.client = client
|
|
|
|
}
|
|
|
|
|
|
|
|
createParse(
|
|
|
|
input: Input,
|
|
|
|
fragments: readonly TreeFragment[],
|
|
|
|
ranges: readonly { from: number; to: number }[]
|
|
|
|
): PartialParse {
|
|
|
|
let parse: PartialParse = new Context(this, input, fragments, ranges)
|
|
|
|
return parse
|
|
|
|
}
|
|
|
|
|
|
|
|
getTokenTypes(): string[] {
|
|
|
|
return this.client.getServerCapabilities().semanticTokensProvider!.legend
|
|
|
|
.tokenTypes
|
|
|
|
}
|
|
|
|
|
|
|
|
getSemanticTokens(): SemanticToken[] {
|
|
|
|
return this.client.getSemanticTokens()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Context implements PartialParse {
|
|
|
|
private parser: KclParser
|
|
|
|
private input: DocInput
|
|
|
|
private fragments: readonly TreeFragment[]
|
|
|
|
private ranges: readonly { from: number; to: number }[]
|
|
|
|
|
|
|
|
private nodeTypes: { [key: string]: NodeType }
|
|
|
|
stoppedAt: number = 0
|
|
|
|
|
|
|
|
private semanticTokens: SemanticToken[] = []
|
|
|
|
private currentLine: number = 0
|
|
|
|
private currentColumn: number = 0
|
|
|
|
private nodeSet: NodeSet
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
/// The parser configuration used.
|
|
|
|
parser: KclParser,
|
|
|
|
input: Input,
|
|
|
|
fragments: readonly TreeFragment[],
|
|
|
|
ranges: readonly { from: number; to: number }[]
|
|
|
|
) {
|
|
|
|
this.parser = parser
|
|
|
|
this.input = input as DocInput
|
|
|
|
this.fragments = fragments
|
|
|
|
this.ranges = ranges
|
|
|
|
|
|
|
|
// Iterate over the semantic token types and create a node type for each.
|
|
|
|
this.nodeTypes = {}
|
|
|
|
let nodeArray: NodeType[] = []
|
|
|
|
this.parser.getTokenTypes().forEach((tokenType, index) => {
|
|
|
|
const nodeType = NodeType.define({
|
|
|
|
id: index,
|
|
|
|
name: tokenType,
|
|
|
|
// props: [this.styleTags],
|
|
|
|
})
|
|
|
|
this.nodeTypes[tokenType] = nodeType
|
|
|
|
nodeArray.push(nodeType)
|
|
|
|
})
|
|
|
|
|
|
|
|
this.semanticTokens = this.parser.getSemanticTokens()
|
|
|
|
const styles = styleTags({
|
|
|
|
number: tags.number,
|
|
|
|
variable: tags.variableName,
|
|
|
|
operator: tags.operator,
|
|
|
|
keyword: tags.keyword,
|
|
|
|
string: tags.string,
|
|
|
|
comment: tags.comment,
|
|
|
|
function: tags.function(tags.variableName),
|
|
|
|
})
|
|
|
|
this.nodeSet = new NodeSet(nodeArray).extend(styles)
|
|
|
|
}
|
|
|
|
|
|
|
|
get parsedPos(): number {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
advance(): Tree | null {
|
|
|
|
if (this.semanticTokens.length === 0) {
|
|
|
|
return new Tree(NodeType.none, [], [], 0)
|
|
|
|
}
|
|
|
|
const tree = this.createTree(this.semanticTokens[0], 0)
|
|
|
|
this.stoppedAt = this.input.doc.length
|
|
|
|
return tree
|
|
|
|
}
|
|
|
|
|
|
|
|
createTree(token: SemanticToken, index: number): Tree {
|
|
|
|
const changedLine = token.delta_line !== 0
|
|
|
|
this.currentLine += token.delta_line
|
|
|
|
if (changedLine) {
|
|
|
|
this.currentColumn = 0
|
|
|
|
}
|
|
|
|
this.currentColumn += token.delta_start
|
|
|
|
|
|
|
|
// Let's get our position relative to the start of the file.
|
|
|
|
let currentPosition = posToOffset(this.input.doc, {
|
|
|
|
line: this.currentLine,
|
|
|
|
character: this.currentColumn,
|
|
|
|
})
|
|
|
|
|
|
|
|
const nodeType = this.nodeSet.types[this.nodeTypes[token.token_type].id]
|
|
|
|
|
|
|
|
if (currentPosition === undefined) {
|
|
|
|
// This is bad and weird.
|
|
|
|
return new Tree(nodeType, [], [], token.length)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (index >= this.semanticTokens.length - 1) {
|
|
|
|
// We have no children.
|
|
|
|
return new Tree(nodeType, [], [], token.length)
|
|
|
|
}
|
|
|
|
|
|
|
|
const nextIndex = index + 1
|
|
|
|
const nextToken = this.semanticTokens[nextIndex]
|
|
|
|
const changedLineNext = nextToken.delta_line !== 0
|
|
|
|
const nextLine = this.currentLine + nextToken.delta_line
|
|
|
|
const nextColumn = changedLineNext
|
|
|
|
? nextToken.delta_start
|
|
|
|
: this.currentColumn + nextToken.delta_start
|
|
|
|
const nextPosition = posToOffset(this.input.doc, {
|
|
|
|
line: nextLine,
|
|
|
|
character: nextColumn,
|
|
|
|
})
|
|
|
|
|
|
|
|
if (nextPosition === undefined) {
|
|
|
|
// This is bad and weird.
|
|
|
|
return new Tree(nodeType, [], [], token.length)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Let's get the
|
|
|
|
|
|
|
|
return new Tree(
|
|
|
|
nodeType,
|
|
|
|
[this.createTree(nextToken, nextIndex)],
|
|
|
|
|
|
|
|
// The positions (offsets relative to the start of this tree) of the children.
|
|
|
|
[nextPosition - currentPosition],
|
|
|
|
token.length
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
stopAt(pos: number) {
|
|
|
|
this.stoppedAt = pos
|
|
|
|
}
|
|
|
|
}
|