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

rokucommunity / vscode-brightscript-language / 30094850119

24 Jul 2026 12:55PM UTC coverage: 59.946% (+0.04%) from 59.908%
30094850119

Pull #802

github

web-flow
Merge 799ab4e3d into e465804e3
Pull Request #802: Route extension logs through the BrightScript Extension output channel

2592 of 4745 branches covered (54.63%)

Branch coverage included in aggregate %.

18 of 22 new or added lines in 3 files covered. (81.82%)

732 existing lines in 18 files now uncovered.

4035 of 6310 relevant lines covered (63.95%)

43.8 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

© 2026 Coveralls, Inc