Skip to content

Commit 541e112

Browse files
refactor(vscode): reimplement Focus Mode base on folding ranges (#5634)
1 parent e6cf377 commit 541e112

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

extensions/vscode/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from 'reactive-vscode';
1717
import * as vscode from 'vscode';
1818
import { config } from './lib/config';
19+
import * as focusMode from './lib/focusMode';
1920
import * as interpolationDecorators from './lib/interpolationDecorators';
2021
import * as reactivityVisualization from './lib/reactivityVisualization';
2122
import * as welcome from './lib/welcome';
@@ -101,6 +102,7 @@ export = defineExtension(() => {
101102
activateAutoInsertion(selectors, client);
102103
activateDocumentDropEdit(selectors, client);
103104

105+
focusMode.activate(context, selectors, client);
104106
interpolationDecorators.activate(context, selectors, client);
105107
reactivityVisualization.activate(context, selectors);
106108
welcome.activate(context);

extensions/vscode/lib/focusMode.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import type { BaseLanguageClient } from '@volar/vscode';
2+
import * as vscode from 'vscode';
3+
import { config } from './config';
4+
5+
const tagUnfocusDecorations = Array.from({ length: 8 }).map((_, i) =>
6+
vscode.window.createTextEditorDecorationType({
7+
opacity: Math.pow(0.7, i).toString(),
8+
isWholeLine: true,
9+
})
10+
);
11+
12+
export async function activate(
13+
context: vscode.ExtensionContext,
14+
selector: vscode.DocumentSelector,
15+
client: BaseLanguageClient,
16+
) {
17+
await client.start();
18+
19+
let timeout: ReturnType<typeof setTimeout> | undefined;
20+
21+
const editor2Decorations = new Map<vscode.TextEditor, {
22+
currentTagDecIndex: number;
23+
targetTagDecIndex: number;
24+
tagRanges: [number, number][];
25+
}>();
26+
27+
setInterval(() => {
28+
for (const [editor, info] of Array.from(editor2Decorations)) {
29+
if (info.currentTagDecIndex !== info.targetTagDecIndex) {
30+
const lastTagDecIndex = info.currentTagDecIndex;
31+
32+
if (info.targetTagDecIndex > info.currentTagDecIndex) {
33+
info.currentTagDecIndex++;
34+
}
35+
else {
36+
info.currentTagDecIndex--;
37+
}
38+
39+
if (info.currentTagDecIndex > 0) {
40+
editor.setDecorations(
41+
tagUnfocusDecorations[info.currentTagDecIndex]!,
42+
info.tagRanges.map(range =>
43+
new vscode.Range(new vscode.Position(range[0], 0), new vscode.Position(range[1], 0))
44+
),
45+
);
46+
}
47+
editor.setDecorations(tagUnfocusDecorations[lastTagDecIndex]!, []);
48+
}
49+
if (info.currentTagDecIndex === 0 && info.targetTagDecIndex === 0) {
50+
editor2Decorations.delete(editor);
51+
}
52+
}
53+
}, 24);
54+
55+
context.subscriptions.push(
56+
vscode.window.onDidChangeVisibleTextEditors(editors => {
57+
for (const [editor, info] of editor2Decorations) {
58+
if (!editors.includes(editor)) {
59+
info.targetTagDecIndex = 0;
60+
}
61+
}
62+
}),
63+
vscode.window.onDidChangeTextEditorSelection(editor => {
64+
updateDecorations(editor.textEditor);
65+
}),
66+
vscode.workspace.onDidChangeTextDocument(() => {
67+
const editor = vscode.window.activeTextEditor;
68+
if (editor) {
69+
clearTimeout(timeout);
70+
timeout = setTimeout(() => updateDecorations(editor), 100);
71+
}
72+
}),
73+
vscode.workspace.onDidChangeConfiguration(e => {
74+
if (e.affectsConfiguration('vue.editor.focusMode')) {
75+
for (const editor of vscode.window.visibleTextEditors) {
76+
updateDecorations(editor);
77+
}
78+
}
79+
}),
80+
);
81+
82+
async function updateDecorations(editor: vscode.TextEditor) {
83+
if (!vscode.languages.match(selector, editor.document)) {
84+
return;
85+
}
86+
87+
const foldingRanges = await vscode.commands.executeCommand<vscode.FoldingRange[] | undefined>(
88+
'vscode.executeFoldingRangeProvider',
89+
editor.document.uri,
90+
);
91+
if (!foldingRanges) {
92+
return;
93+
}
94+
95+
const rootRanges: vscode.FoldingRange[] = [];
96+
const stack: vscode.FoldingRange[] = [];
97+
98+
for (const range of foldingRanges) {
99+
while (stack.length && stack[stack.length - 1]!.end < range.start) {
100+
stack.pop();
101+
}
102+
if (stack.length === 0) {
103+
rootRanges.push({
104+
start: range.start,
105+
end: range.end + 1,
106+
});
107+
}
108+
stack.push(range);
109+
}
110+
111+
const info = editor2Decorations.get(editor) ?? {
112+
currentTagDecIndex: 0,
113+
targetTagDecIndex: 0,
114+
tagRanges: [],
115+
};
116+
editor2Decorations.set(editor, info);
117+
118+
info.tagRanges.length = 0;
119+
120+
const currentLine = editor.selection.active.line;
121+
let inBlock = false;
122+
123+
for (const rootRange of rootRanges) {
124+
if (rootRange.end - rootRange.start <= 1) {
125+
info.tagRanges.push([rootRange.start, rootRange.end]);
126+
}
127+
else {
128+
info.tagRanges.push([rootRange.start, rootRange.start]);
129+
info.tagRanges.push([rootRange.end, rootRange.end]);
130+
inBlock ||= currentLine >= rootRange.start + 1 && currentLine <= rootRange.end - 1;
131+
}
132+
}
133+
134+
if (config.editor.focusMode && inBlock) {
135+
info.targetTagDecIndex = tagUnfocusDecorations.length - 1;
136+
}
137+
else {
138+
info.targetTagDecIndex = 0;
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)