Skip to content

Commit

Permalink
feat: recursive Folding Block (#555)
Browse files Browse the repository at this point in the history
  • Loading branch information
jingjiajie authored Dec 5, 2023
1 parent 77678f4 commit df9594b
Show file tree
Hide file tree
Showing 6 changed files with 513 additions and 165 deletions.
11 changes: 10 additions & 1 deletion server/src/sas/CodeZoneManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,9 @@ export class CodeZoneManager {
block = this._syntaxProvider.getFoldingBlock(
tmpContext.line,
tmpContext.col,
false,
true,
true,
);
if (block) {
if (this._inBlock(block, token)! < 0 && !this._endedReally(block)) {
Expand Down Expand Up @@ -2428,7 +2431,13 @@ export class CodeZoneManager {
const tmpLine = pos.line,
tmpCol = pos.col;
let token = this._token(tmpLine, tmpCol)!;
const block = this._syntaxProvider.getFoldingBlock(tmpLine, tmpCol);
const block = this._syntaxProvider.getFoldingBlock(
tmpLine,
tmpCol,
false,
true,
true,
);
/* first check type to determine zone, some special conditions
* 1) for bringing up auto completion popup by shortcut,
* 2) input at the end of a line in comment or literal
Expand Down
8 changes: 7 additions & 1 deletion server/src/sas/FormatOnTypeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ export class FormatOnTypeProvider {
}
// Otherwise, need to decrease indent of current line
const foldingBlock: FoldingBlock | null =
this.syntaxProvider.getFoldingBlock(line, semicolonCol);
this.syntaxProvider.getFoldingBlock(
line,
semicolonCol,
false,
true,
true,
);
let blockStartLine;
if (!foldingBlock) {
const lastNotEmptyLine = this._getLastNotEmptyLine(line - 1);
Expand Down
149 changes: 100 additions & 49 deletions server/src/sas/LanguageServiceProvider.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Copyright © 2022, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { TextDocument } from "vscode-languageserver-textdocument";
import { FoldingRange } from "vscode-languageserver";
import { Range, TextDocument } from "vscode-languageserver-textdocument";
import { DocumentSymbol, SymbolKind } from "vscode-languageserver-types";

import { CompletionProvider } from "./CompletionProvider";
import { FormatOnTypeProvider } from "./FormatOnTypeProvider";
import { FoldingBlock } from "./LexerEx";
import { Model } from "./Model";
import type { LibService } from "./SyntaxDataProvider";
import { SyntaxProvider } from "./SyntaxProvider";
Expand Down Expand Up @@ -38,8 +40,14 @@ function getType(type: string) {
return legend.tokenTypes.indexOf(type);
}

// DATA, PROC, MACRO
const SymbolKinds = [SymbolKind.Struct, SymbolKind.Function, SymbolKind.Module];
// DATA, PROC, MACRO, GBL, CUSTOM
const SymbolKinds = [
SymbolKind.Struct,
SymbolKind.Function,
SymbolKind.Module,
SymbolKind.Module,
SymbolKind.Module,
];

export class LanguageServiceProvider {
private model;
Expand Down Expand Up @@ -118,65 +126,108 @@ export class LanguageServiceProvider {
return data;
}

getFoldingBlocks(): DocumentSymbol[] {
getDocumentSymbols(): DocumentSymbol[] {
const lineCount = this.model.getLineCount();
const result = [];
let customBlock;
const result: DocumentSymbol[] = [];

for (let i = 0; i < lineCount; i++) {
const block = this.syntaxProvider.getFoldingBlock(i);

if (block && block.startLine === i) {
const range = {
start: { line: block.startLine, character: block.startCol },
end: { line: block.endFoldingLine, character: block.endFoldingCol },
};
result.push({
name:
block.type === 1 ? this._getProcName(block.startLine) : block.name,
kind: SymbolKinds[block.type],
range,
selectionRange: range,
});
i = block.endFoldingLine;
const rootBlock = this.syntaxProvider.getFoldingBlock(
i,
undefined,
false,
false,
true,
);
if (rootBlock && rootBlock.startLine === i) {
const docSymbol: DocumentSymbol = this._buildDocumentSymbol(rootBlock);
result.push(docSymbol);
i = rootBlock.endFoldingLine;
continue;
}
let token = this.syntaxProvider.getSyntax(i)[0];
if (token && token.style === "text") {
token = this.syntaxProvider.getSyntax(i)[1];
}
if (token && /comment/.test(token.style)) {
if (/^\s*[%/]?\*\s*region\b/i.test(this.model.getLine(i))) {
customBlock = {
start: { line: i, character: 0 },
end: {
line: this.model.getLineCount(),
character: 0,
},
};
} else if (
customBlock &&
/^\s*[%/]?\*\s*endregion\b/i.test(this.model.getLine(i))
) {
customBlock.end = {
line: i,
character: this.model.getColumnCount(i),
};
}
return result;
}

private _buildDocumentSymbol(
block: FoldingBlock,
parent?: DocumentSymbol,
): DocumentSymbol {
const range: Range = {
start: { line: block.startLine, character: block.startCol },
end: { line: block.endFoldingLine, character: block.endFoldingCol },
};
const docSymbol: DocumentSymbol = {
name: block.type === 1 ? this._getProcName(block.startLine) : block.name,
kind: SymbolKinds[block.type],
range,
selectionRange: range,
children: [],
};
if (parent) {
parent.children!.push(docSymbol);
}
for (const innerBlock of block.innerBlocks) {
this._buildDocumentSymbol(innerBlock, docSymbol);
}
return docSymbol;
}

getFoldingRanges(): FoldingRange[] {
const lineCount = this.model.getLineCount();
const result: FoldingRange[] = [];

for (let i = 0; i < lineCount; i++) {
const rootBlock = this.syntaxProvider.getFoldingBlock(
i,
undefined,
false,
false,
true,
);
if (rootBlock && rootBlock.startLine === i) {
const blocks: FoldingBlock[] = this._flattenFoldingBlockTree(rootBlock);
for (const block of blocks) {
result.push({
name: "custom",
kind: SymbolKind.Module,
range: customBlock,
selectionRange: customBlock,
startLine: block.startLine,
endLine: block.endFoldingLine,
});
customBlock = undefined;
}
i = rootBlock.endFoldingLine;
continue;
}
}
return result;
}

getFoldingBlock(line: number, col: number) {
return this.syntaxProvider.getFoldingBlock(line, col, true);
// DFS
private _flattenFoldingBlockTree(rootBlock: FoldingBlock): FoldingBlock[] {
const stack: FoldingBlock[] = [rootBlock];
const resultList: FoldingBlock[] = [];
while (stack.length > 0) {
const curBlock: FoldingBlock = stack.pop()!;
resultList.push(curBlock);
for (let i = curBlock.innerBlocks.length - 1; i >= 0; i--) {
const innerBlock = curBlock.innerBlocks[i];
stack.push(innerBlock);
}
}
return resultList;
}

getFoldingBlock(
line: number,
col: number,
strict?: boolean,
ignoreCustomBlock?: boolean,
ignoreGlobalBlock?: boolean,
) {
return this.syntaxProvider.getFoldingBlock(
line,
col,
strict,
ignoreCustomBlock,
ignoreGlobalBlock,
);
}

setLibService(fn: LibService): void {
Expand Down
Loading

0 comments on commit df9594b

Please sign in to comment.