119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
import { DocumentSymbol, Range, SymbolKind } from 'vscode-languageserver-protocol'
|
|
import { TextDocument } from 'vscode-languageserver-textdocument'
|
|
|
|
/**
|
|
* A syntax parser that parse `class` and `method` only.
|
|
*/
|
|
export default class Parser {
|
|
private _curr = 0
|
|
private _symbols: DocumentSymbol[] = []
|
|
private currSymbol: DocumentSymbol | undefined
|
|
private len: number
|
|
private textDocument: TextDocument
|
|
constructor(private _content: string, private showDetail = false) {
|
|
this.len = _content.length
|
|
this.textDocument = TextDocument.create('test:///a', 'txt', 1, _content)
|
|
}
|
|
|
|
public parse(): DocumentSymbol[] {
|
|
while (this._curr <= this.len - 1) {
|
|
this.parseToken()
|
|
}
|
|
return this._symbols
|
|
}
|
|
|
|
/**
|
|
* Parse a symbol, reset currSymbol & _curr
|
|
*/
|
|
private parseToken(): void {
|
|
this.skipSpaces()
|
|
if (this.currSymbol) {
|
|
let endOffset = this.textDocument.offsetAt(this.currSymbol.range.end)
|
|
if (this._curr > endOffset) {
|
|
this.currSymbol = undefined
|
|
}
|
|
}
|
|
let remain = this.getLineRemain()
|
|
let ms = remain.match(/^(class)\s(\w+)\s\{\s*/)
|
|
if (ms) {
|
|
// find class
|
|
let start = this._curr + 6
|
|
let end = start + ms[2].length
|
|
let selectionRange = Range.create(this.textDocument.positionAt(start), this.textDocument.positionAt(end))
|
|
let endPosition = this.findMatchedIndex(this._curr + ms[0].length)
|
|
let range = Range.create(this.textDocument.positionAt(this._curr), this.textDocument.positionAt(endPosition))
|
|
let symbolInfo: DocumentSymbol = {
|
|
range,
|
|
selectionRange,
|
|
kind: SymbolKind.Class,
|
|
name: ms[2],
|
|
children: []
|
|
}
|
|
if (this.currSymbol && this.currSymbol.children) {
|
|
this.currSymbol.children.push(symbolInfo)
|
|
} else {
|
|
this._symbols.push(symbolInfo)
|
|
}
|
|
this.currSymbol = symbolInfo
|
|
} else if (this.currSymbol && this.currSymbol.kind == SymbolKind.Class) {
|
|
let ms = remain.match(/(\w+)\((.*)\)\s*\{/)
|
|
if (ms) {
|
|
// find method
|
|
let start = this._curr
|
|
let end = start + ms[1].length
|
|
let selectionRange = Range.create(this.textDocument.positionAt(start), this.textDocument.positionAt(end))
|
|
let endPosition = this.findMatchedIndex(this._curr + ms[0].length)
|
|
let range = Range.create(this.textDocument.positionAt(this._curr), this.textDocument.positionAt(endPosition))
|
|
let symbolInfo: DocumentSymbol = {
|
|
range,
|
|
selectionRange,
|
|
kind: SymbolKind.Method,
|
|
detail: this.showDetail ? `(${ms[2]})` : undefined,
|
|
name: ms[1]
|
|
}
|
|
if (this.currSymbol && this.currSymbol.children) {
|
|
this.currSymbol.children.push(symbolInfo)
|
|
} else {
|
|
this._symbols.push(symbolInfo)
|
|
}
|
|
}
|
|
}
|
|
this._curr = this._curr + remain.length + 1
|
|
}
|
|
|
|
private findMatchedIndex(start: number): number {
|
|
let level = 0
|
|
for (let i = start; i < this.len; i++) {
|
|
let ch = this._content[i]
|
|
if (ch == '{') {
|
|
level = level + 1
|
|
}
|
|
if (ch == '}') {
|
|
if (level == 0) return i
|
|
level = level - 1
|
|
}
|
|
}
|
|
throw new Error(`Can't find matched }`)
|
|
}
|
|
|
|
private getLineRemain(): string {
|
|
let chars = ''
|
|
for (let i = this._curr; i < this.len; i++) {
|
|
let ch = this._content[i]
|
|
if (ch == '\n') break
|
|
chars = chars + ch
|
|
}
|
|
return chars
|
|
}
|
|
|
|
private skipSpaces(): void {
|
|
for (let i = this._curr; i < this.len; i++) {
|
|
let ch = this._content[i]
|
|
if (!ch || /\S/.test(ch)) {
|
|
this._curr = i
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|