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

rokucommunity / vscode-brightscript-language / 30100866380

24 Jul 2026 02:24PM UTC coverage: 60.103% (+0.2%) from 59.908%
30100866380

Pull #802

github

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

2593 of 4745 branches covered (54.65%)

Branch coverage included in aggregate %.

64 of 102 new or added lines in 20 files covered. (62.75%)

4073 of 6346 relevant lines covered (64.18%)

43.71 hits per line

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

11.11
/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
import { createLogger } from '../logging';
1✔
9

10
const logger = createLogger('BrighterScriptPreviewCommand');
1✔
11

12
export const FILE_SCHEME = 'bs-preview';
1✔
13

14
export class BrighterScriptPreviewCommand {
1✔
15
    public static SELECTION_SYNC_DELAY = 300;
1✔
16

17
    public register(context: vscode.ExtensionContext) {
18

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

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

29
        //register BrighterScript transpile preview handler
30
        vscode.workspace.registerTextDocumentContentProvider(FILE_SCHEME, this);
16✔
31

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

267
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