• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / vscode-brightscript-language / #2719

24 Aug 2022 12:51PM UTC coverage: 41.691% (+0.02%) from 41.675%
#2719

push

TwitchBronBron
2.35.0

477 of 1427 branches covered (33.43%)

Branch coverage included in aggregate %.

1126 of 2418 relevant lines covered (46.57%)

7.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.15
/src/commands/BrighterScriptPreviewCommand.ts
1
import { Uri, Position, Range } from 'vscode';
1✔
2
import * as vscode from 'vscode';
1✔
3
import { extension } from '../extension';
4
import { util } from '../util';
1✔
5
import * as path from 'path';
1✔
6
import * as querystring from 'querystring';
1✔
7
import { SourceMapConsumer } from 'source-map';
1✔
8
import { languageServerManager } from '../LanguageServerManager';
1✔
9

10
export const FILE_SCHEME = 'bs-preview';
1✔
11

12
export class BrighterScriptPreviewCommand {
1✔
13
    public static SELECTION_SYNC_DELAY = 300;
1✔
14

15
    public register(context: vscode.ExtensionContext) {
16

17
        context.subscriptions.push(vscode.commands.registerCommand('brighterscript.showPreview', async (uri: vscode.Uri) => {
6✔
18
            uri = uri ?? vscode.window.activeTextEditor.document.uri;
×
19
            await this.openPreview(uri, vscode.window.activeTextEditor, false);
×
20
        }));
21

22
        context.subscriptions.push(vscode.commands.registerCommand('brighterscript.showPreviewToSide', async (uri: vscode.Uri) => {
6✔
23
            uri = uri ?? vscode.window.activeTextEditor.document.uri;
×
24
            await this.openPreview(uri, vscode.window.activeTextEditor, true);
×
25
        }));
26

27
        //register BrighterScript transpile preview handler
28
        vscode.workspace.registerTextDocumentContentProvider(FILE_SCHEME, this);
6✔
29

30
        //anytime the underlying file changed, tell vscode the preview needs to be regenerated
31
        vscode.workspace.onDidChangeTextDocument((e) => {
6✔
32
            if (this.isWatchingUri(e.document.uri)) {
×
33
                let uri = this.getBsPreviewUri(e.document.uri);
×
34
                util.keyedDebounce(`'textdoc-change:${uri.fsPath}`, () => {
×
35
                    this.onDidChangeEmitter.fire(uri);
×
36
                }, 500);
37
            }
38
        });
39

40
        // sync the preview and the source doc on mouse click
41
        vscode.window.onDidChangeTextEditorSelection((e) => {
6✔
42
            let uri = e.textEditor.document.uri;
×
43
            //if this is one of our source files
44
            if (this.activePreviews[uri.fsPath]) {
×
45

46
                util.keyedDebounce(`sync-preview:${uri.fsPath}`, async () => {
×
47
                    await this.syncPreviewLocation(uri);
×
48
                }, BrighterScriptPreviewCommand.SELECTION_SYNC_DELAY);
49

50
                //this is the preview file
51
            } else if (this.getSourcePathFromPreviewUri(uri)) {
×
52
                //TODO enable this once we figure out the bugs
53
                // util.keyedDebounce(`sync-source:${uri.fsPath}`, async () => {
54
                //     this.syncSourceLocation(uri);
55
                // }, BrighterScriptPreviewCommand.SELECTION_SYNC_DELAY);
56
            }
57
        });
58

59
        //whenever the source file is closed, dispose of our preview
60
        vscode.workspace.onDidCloseTextDocument(async (e) => {
6✔
61
            let activePreview = this.activePreviews[e?.uri?.fsPath];
×
62
            if (activePreview?.previewEditor?.document?.uri) {
×
63
                //close the preview by showing it and then closing the active editor
64
                await vscode.window.showTextDocument(activePreview.previewEditor.document.uri, { preview: true, preserveFocus: false });
×
65
                await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
×
66
            }
67
            delete this.activePreviews[e?.uri?.fsPath];
×
68
        });
69
    }
70

71
    /**
72
     * Synce a preview editor to the selected range in the source editor
73
     */
74
    private async syncPreviewLocation(uri: vscode.Uri) {
75
        let activePreview = this.activePreviews[uri.fsPath];
×
76
        let sourceMap = activePreview?.sourceMap;
×
77
        let sourceSelection = activePreview?.sourceEditor?.selection;
×
78

79
        if (sourceMap && sourceSelection) {
×
80
            try {
×
81
                let mappedRange = await SourceMapConsumer.with(sourceMap, null, (consumer) => {
×
82
                    let start = consumer.generatedPositionFor({
×
83
                        source: uri.fsPath,
84
                        line: sourceSelection.start.line + 1,
85
                        column: sourceSelection.start.character,
86
                        bias: SourceMapConsumer.LEAST_UPPER_BOUND
87
                    });
88
                    //if no location found, snap to the closest token
89
                    if (start.line === null || start.column === null) {
×
90
                        start = consumer.generatedPositionFor({
×
91
                            source: uri.fsPath,
92
                            line: sourceSelection.start.line + 1,
93
                            column: sourceSelection.start.character,
94
                            bias: SourceMapConsumer.GREATEST_LOWER_BOUND
95
                        });
96
                    }
97
                    let end = consumer.generatedPositionFor({
×
98
                        source: uri.fsPath,
99
                        line: sourceSelection.end.line + 1,
100
                        column: sourceSelection.end.character,
101
                        bias: SourceMapConsumer.LEAST_UPPER_BOUND
102
                    });
103
                    //if no location found, snap to the closest token
104
                    if (end.line === null || end.column === null) {
×
105
                        end = consumer.generatedPositionFor({
×
106
                            source: uri.fsPath,
107
                            line: sourceSelection.end.line + 1,
108
                            column: sourceSelection.end.character,
109
                            bias: SourceMapConsumer.GREATEST_LOWER_BOUND
110
                        });
111
                    }
112
                    return new Range(
×
113
                        start.line - 1,
114
                        start.column,
115
                        end.line - 1,
116
                        end.column
117
                    );
118
                });
119

120
                //scroll the preview editor to the source's clicked location
121
                activePreview.previewEditor.revealRange(mappedRange, vscode.TextEditorRevealType.InCenter);
×
122
                activePreview.previewEditor.selection = new vscode.Selection(mappedRange.start, mappedRange.end);
×
123
            } catch (e) {
124
                console.error(e);
×
125
            }
126
        }
127
    }
128

129
    /**
130
     * Sync a source editor to the selection in the preview editor
131
     */
132
    private async syncSourceLocation(uri: vscode.Uri) {
133
        let sourceFsPath = this.getSourcePathFromPreviewUri(uri);
×
134
        let activePreview = this.activePreviews[sourceFsPath];
×
135

136
        let previewSelection = activePreview?.previewEditor?.selection;
×
137
        if (activePreview?.sourceMap && previewSelection) {
×
138
            try {
×
139
                let mappedRange = await SourceMapConsumer.with(activePreview.sourceMap, null, (consumer) => {
×
140
                    let start = consumer.originalPositionFor({
×
141
                        line: previewSelection.start.line + 1,
142
                        column: previewSelection.start.character,
143
                        bias: SourceMapConsumer.LEAST_UPPER_BOUND
144
                    });
145
                    if (start.line === null || start.column === null) {
×
146
                        start = consumer.originalPositionFor({
×
147
                            line: previewSelection.start.line + 1,
148
                            column: previewSelection.start.character,
149
                            bias: SourceMapConsumer.GREATEST_LOWER_BOUND
150
                        });
151
                    }
152
                    let end = consumer.originalPositionFor({
×
153
                        line: previewSelection.end.line + 1,
154
                        column: previewSelection.end.character,
155
                        bias: SourceMapConsumer.LEAST_UPPER_BOUND
156
                    });
157
                    if (end.line === null || end.column === null) {
×
158
                        end = consumer.originalPositionFor({
×
159
                            line: previewSelection.end.line + 1,
160
                            column: previewSelection.end.character,
161
                            bias: SourceMapConsumer.GREATEST_LOWER_BOUND
162
                        });
163
                    }
164
                    return new Range(
×
165
                        start.line - 1,
166
                        start.column,
167
                        end.line - 1,
168
                        end.column
169
                    );
170
                });
171

172
                //scroll the preview editor to the source's clicked location
173
                activePreview.sourceEditor.revealRange(mappedRange, vscode.TextEditorRevealType.InCenter);
×
174
                activePreview.sourceEditor.selection = new vscode.Selection(mappedRange.start, mappedRange.end);
×
175
            } catch (e) {
176
                console.error(e);
×
177
            }
178
        }
179
    }
180

181
    private isWatchingUri(uri: vscode.Uri) {
182
        if (
×
183
            //we are watching this file
184
            this.activePreviews[uri.fsPath] &&
×
185
            //the file is not our preview scheme (this prevents an infinite loop)
186
            uri.scheme !== FILE_SCHEME) {
187
            return true;
×
188
        } else {
189
            return false;
×
190
        }
191
    }
192

193
    private activePreviews = {} as Record<string, {
1✔
194
        /**
195
             * The editor this "preview transpiled bs" command was initiated upon.
196
             */
197
        sourceEditor: vscode.TextEditor;
198
        /**
199
             * The editor that contains the preview doc
200
             */
201
        previewEditor: vscode.TextEditor;
202
        /**
203
             * the latest source map for the preview.
204
             */
205
        sourceMap: any;
206
    }>;
207

208
    /**
209
     * The handler for the command. Creates a custom URI so we can open it
210
     * with our TextDocumentContentProvider to show the transpiled code
211
     */
212
    public async openPreview(uri: Uri, sourceEditor: vscode.TextEditor, showToSide: boolean) {
213
        let activePreview: BrighterScriptPreviewCommand['activePreviews'][0];
214
        let previewDoc: vscode.TextDocument;
215
        if (!this.activePreviews[uri.fsPath]) {
×
216
            activePreview = (this.activePreviews[uri.fsPath] = {} as any);
×
217
            let customUri = this.getBsPreviewUri(uri);
×
218
            previewDoc = await vscode.workspace.openTextDocument(customUri);
×
219
        } else {
220
            activePreview = this.activePreviews[uri.fsPath];
×
221
            previewDoc = activePreview.previewEditor.document;
×
222
        }
223
        activePreview.sourceEditor = sourceEditor;
×
224
        activePreview.previewEditor = await vscode.window.showTextDocument(previewDoc, {
×
225
            preview: true,
226
            preserveFocus: true,
227
            viewColumn: showToSide ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active
×
228
        });
229
    }
230

231
    // emitter and its event
232
    public onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
1✔
233
    public onDidChange = this.onDidChangeEmitter.event;
1✔
234

235
    public async provideTextDocumentContent(uri: vscode.Uri) {
236
        let fsPath = this.getSourcePathFromPreviewUri(uri);
×
237
        let result = await languageServerManager.getTranspiledFileContents(fsPath);
×
238
        this.activePreviews[fsPath].sourceMap = result.map;
×
239
        return result.code;
×
240
    }
241

242
    /**
243
     * Get the fsPath from the uri. this handles both `file` and `bs-preview` schemes
244
     */
245
    private getSourcePathFromPreviewUri(uri: vscode.Uri) {
246
        if (uri.scheme === 'file') {
×
247
            return uri.fsPath;
×
248
        } else if (uri.scheme === FILE_SCHEME) {
×
249
            let parts = querystring.parse(uri.query);
×
250
            return parts.fsPath as string;
×
251
        } else {
252
            throw new Error('Cannot determine fsPath for uri: ' + uri.toString());
×
253
        }
254
    }
255

256
    /**
257
     * Given a uri, compute the bs-preview URI
258
     */
259
    private getBsPreviewUri(uri: vscode.Uri) {
260
        let fsPath = uri.fsPath;
×
261
        return Uri.parse(`${FILE_SCHEME}:(Transpiled) ${path.basename(fsPath)}?fsPath=${fsPath}`);
×
262
    }
263
}
264

265
export const brighterScriptPreviewCommand = new BrighterScriptPreviewCommand();
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc