Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: recursive Folding Block #555

Merged
merged 18 commits into from
Dec 5, 2023
Merged
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