Skip to content

Commit 2c2c7e4

Browse files
committed
[A] region and tag support #43 #41
- region and tag symbols - syntax highlight for region and tag symbols - foldingRange for region symbols
1 parent 3f3f66e commit 2c2c7e4

File tree

9 files changed

+503
-14
lines changed

9 files changed

+503
-14
lines changed

package.json

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,25 @@
101101
"struct": "var(--vscode-symbolIcon-structForeground)",
102102
"event": "var(--vscode-symbolIcon-eventForeground)",
103103
"operator": "var(--vscode-symbolIcon-operatorForeground)",
104-
"typeparameter": "var(--vscode-symbolIcon-typeParameterForeground)"
104+
"typeparameter": "var(--vscode-symbolIcon-typeParameterForeground)",
105+
"__om_Tag__": "var(--vscode-symbolIcon-packageForeground)",
106+
"__om_Region__": "var(--vscode-symbolIcon-packageForeground)"
105107
},
106108
"description": "override colors of specific symbols and some ui components.",
107109
"properties": {
110+
"__om_Tag__": {
111+
"type": "string",
112+
"title": "Tag",
113+
"description": "The color of the custom tag symbols in the outline."
114+
},
115+
"__om_Region__": {
116+
"type": "string",
117+
"title": "Region",
118+
"description": "The color of the custom region symbols in the outline."
119+
},
108120
"visibleRange": {
109121
"type": "string",
122+
"title": "Visible Range",
110123
"description": "The color of the visible range in the outline."
111124
},
112125
"focusingItem": {
@@ -190,6 +203,31 @@
190203
}
191204
}
192205
},
206+
"outline-map.region.enabled": {
207+
"type": "boolean",
208+
"default": true,
209+
"markdownDescription": "Enable custom region and tag support. By default, regions start with the tag `#region <name>`and end with the tag `#endregion <name>`. This extension does not do semantic analysis for specific languages, and content that is not part of the comments section may also be recognized as a tag"
210+
},
211+
"outline-map.region.highlight": {
212+
"type": "boolean",
213+
"default": true,
214+
"markdownDescription": "Enable semantic highlighting for custom regions and tags in the editor."
215+
},
216+
"outline-map.region.startRegion": {
217+
"type": "string",
218+
"default": "#region",
219+
"description": "The start region tag. The word after the tag will be seen as the region name."
220+
},
221+
"outline-map.region.endRegion": {
222+
"type": "string",
223+
"default": "#endregion",
224+
"description": "The end region tag. The word after the tag will be seen as the region name to close the region. If no name is specified, the last opened region will be closed."
225+
},
226+
"outline-map.region.tag": {
227+
"type": "string",
228+
"default": "#tag",
229+
"description": "The start tag. The word after the tag will be seen as the tag name."
230+
},
193231
"outline-map.follow": {
194232
"type": "string",
195233
"enum": [
@@ -300,7 +338,7 @@
300338
"category": "outline-map"
301339
}
302340
],
303-
"keybindings":[
341+
"keybindings": [
304342
{
305343
"command": "outline-map.toggleSearch",
306344
"key": "Alt+l"

src/common.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,34 @@ export class SymbolNode {
4646
};
4747
this.children = [];
4848
this.selector = ['#outline-root'];
49+
this.mapCustomSymbol();
4950
}
5051

52+
53+
/**
54+
* Create custom symbol kinds.
55+
*
56+
* The custom symbol kinds are used to represent the regions and tags in the document.
57+
* The kind information is stored in the detail of the DocumentSymbol by the RegionSymbolProvider.
58+
*
59+
* This function should be called after the DocumentSymbol is mapped to a SymbolNode.
60+
*/
61+
mapCustomSymbol() {
62+
// RegionSymbolProvider use 'Number' as a placeholder for regions and tags.
63+
if (this.kind !== 'Number') {
64+
return;
65+
}
66+
if (this.detail.startsWith('__om_Region__')) {
67+
this.kind = '__om_Region__';
68+
this.detail = this.detail.slice(13);
69+
}
70+
else if (this.detail.startsWith('__om_Tag__')) {
71+
this.kind = '__om_Tag__';
72+
this.detail = this.detail.slice(10);
73+
}
74+
}
75+
76+
5177
/**
5278
* Creates a tree of SymbolNodes from a list of DocumentSymbols.
5379
* @param docSymbols The list of DocumentSymbols to create the tree from.
@@ -59,9 +85,38 @@ export class SymbolNode {
5985
const root: SymbolNode[] = [];
6086
const hiddenItem = config.hiddenItem();
6187

88+
// A known issue of vscode is that the DocumentSymbols returned by
89+
// command 'vscode.executeDocumentSymbolProvider' does not include the
90+
// information of the provider of the symbols.
91+
//
92+
// When a document has multiple symbol providers,
93+
// the symbols from different providers are not hierarchical.
94+
//
95+
// This function tries to reconstruct the tree.
96+
// It will move some siblings to be children of a node
97+
// if the range of the node contains the range of the siblings
98+
/**
99+
* Reconstruct the tree of DocumentSymbols.
100+
* @param docSymbols The list of DocumentSymbols to reconstruct, should be sorted by start line.
101+
*/
102+
function reconstructTree(docSymbols: DocumentSymbol[]) {
103+
for (let i = 0; i < docSymbols.length; i++) {
104+
const docSymbol = docSymbols[i];
105+
for (let j = i + 1; j < docSymbols.length; j++) {
106+
const sibling = docSymbols[j];
107+
if (docSymbol.range.contains(sibling.range)) {
108+
docSymbol.children.push(sibling);
109+
docSymbols.splice(j, 1);
110+
j--;
111+
}
112+
}
113+
}
114+
}
115+
62116
// Inner function to recursively transform the DocumentSymbols into SymbolNodes
63117
function recursiveTransform(docSymbols: DocumentSymbol[], parent: SymbolNode | SymbolNode[]) {
64118
docSymbols.sort((a, b) => a.range.start.line - b.range.start.line);
119+
reconstructTree(docSymbols);
65120
docSymbols.forEach((docSymbol) => {
66121
const node = new SymbolNode(docSymbol);
67122
if (hiddenItem.includes(node.kind.toLowerCase())) {

src/extension/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,9 @@ export const config = {
1616
customFont: () => getConfig('customFont') as string,
1717
customCSS: () => getConfig('customCSS') as string,
1818
debug: () => getConfig('debug') as boolean,
19+
regionEnabled: () => getConfig('enabled', 'outline-map.region') as boolean,
20+
regionHighlight: () => getConfig('highlight', 'outline-map.region') as boolean,
21+
regionStart: () => getConfig('startRegion', 'outline-map.region') as string,
22+
regionEnd: () => getConfig('endRegion', 'outline-map.region') as string,
23+
tag: () => getConfig('tag', 'outline-map.region') as string,
1924
};

src/extension/extension.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { commandList } from './commands';
1616
import { ScrollMsg } from '../common';
1717
import { config } from './config';
1818
import { debounce, throttle } from '../utils';
19+
import { RegionProvider, tokensLegend } from './region';
1920

2021
// called when extension is activated
2122
// extension is activated the very first time the command is executed
@@ -77,6 +78,16 @@ export function activate(context: ExtensionContext) {
7778

7879
...commandList.map(command => commands.registerCommand(command.name, command.fn.bind(null, outlineView))),
7980
);
81+
82+
if (config.regionEnabled()) {
83+
const regionSymbolProvider = new RegionProvider();
84+
languages.registerDocumentSymbolProvider({ scheme: 'file' }, regionSymbolProvider);
85+
languages.registerFoldingRangeProvider({ scheme: 'file' }, regionSymbolProvider);
86+
if (config.regionHighlight()) {
87+
languages.registerDocumentSemanticTokensProvider({ scheme: 'file' }, regionSymbolProvider, tokensLegend);
88+
}
89+
}
90+
8091
}
8192

8293
// called when extension is deactivated

src/extension/outline.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { config } from './config';
2-
import { commands, DocumentSymbol, ExtensionContext, SymbolKind, TextDocument, Uri, WebviewView, WebviewViewProvider, window, Range, Selection, Position, Diagnostic, DiagnosticSeverity } from 'vscode';
2+
import { commands, DocumentSymbol, ExtensionContext, TextDocument, Uri, WebviewView, WebviewViewProvider, window, Range, Selection, Position, Diagnostic, DiagnosticSeverity } from 'vscode';
33
import { Msg, UpdateMsg, Op, UpdateOp, DeleteOp, InsertOp, SymbolNode, MoveOp, PinStatus } from '../common';
44

55

0 commit comments

Comments
 (0)