Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"outFiles": [
"${workspaceFolder}/extension/dist/**/*.js"
],
"preLaunchTask": "npm: watch extension",
"preLaunchTask": "tasks: watch extension",
"cwd": "${workspaceFolder}/extension"
},
{
Expand All @@ -25,7 +25,7 @@
"outFiles": [
"${workspaceFolder}/extension/dist/**/*.js"
],
"preLaunchTask": "npm: watch extension",
"preLaunchTask": "tasks: watch extension",
"env": {
"ASPIRE_CLI_STOP_ON_ENTRY": "true"
},
Expand All @@ -41,7 +41,7 @@
"outFiles": [
"${workspaceFolder}/extension/dist/**/*.js"
],
"preLaunchTask": "npm: watch extension",
"preLaunchTask": "tasks: watch extension",
"env": {
"ASPIRE_APPHOST_STOP_ON_ENTRY": "true"
},
Expand All @@ -57,7 +57,7 @@
"outFiles": [
"${workspaceFolder}/extension/dist/**/*.js"
],
"preLaunchTask": "npm: watch extension",
"preLaunchTask": "tasks: watch extension",
"env": {
"ASPIRE_CLI_STOP_ON_ENTRY": "true",
"ASPIRE_APPHOST_STOP_ON_ENTRY": "true"
Expand Down
18 changes: 18 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "yarn: install extension",
"type": "shell",
"command": "yarn install",
"options": {
"cwd": "${workspaceFolder}/extension"
},
"problemMatcher": []
},
{
"type": "npm",
"script": "watch",
Expand All @@ -19,6 +28,15 @@
"label": "npm: watch extension",
"path": "extension",
},
{
"label": "tasks: watch extension",
"dependsOrder": "sequence",
"dependsOn": [
"yarn: install extension",
"npm: watch extension"
],
"problemMatcher": []
},
{
"type": "npm",
"script": "watch-tests",
Expand Down
4 changes: 2 additions & 2 deletions extension/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
],
"preLaunchTask": "npm: watch extension"
"preLaunchTask": "tasks: watch extension"
},
{
"name": "Run Extension (cli stop on entry)",
Expand All @@ -24,7 +24,7 @@
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
],
"preLaunchTask": "npm: watch extension",
"preLaunchTask": "tasks: watch extension",
"env": {
"ASPIRE_CLI_STOP_ON_ENTRY": "true"
}
Expand Down
18 changes: 18 additions & 0 deletions extension/.vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "yarn: install extension",
"type": "shell",
"command": "yarn install",
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": []
},
{
"type": "npm",
"script": "watch",
Expand All @@ -18,6 +27,15 @@
},
"label": "npm: watch extension"
},
{
"label": "tasks: watch extension",
"dependsOrder": "sequence",
"dependsOn": [
"yarn: install extension",
"npm: watch extension"
],
"problemMatcher": []
},
{
"type": "npm",
"script": "watch-tests",
Expand Down
2 changes: 2 additions & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,9 @@
"express": "5.2.1",
"jsonc-parser": "3.3.1",
"node-forge": "1.4.0",
"tree-sitter-c-sharp": "0.23.5",
"vscode-jsonrpc": "8.2.1",
"web-tree-sitter": "0.26.9",
"ws": "8.20.0"
},
"resolutions": {
Expand Down
25 changes: 18 additions & 7 deletions extension/src/editor/AppHostFilePresenceWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ export class AppHostFilePresenceWatcher implements vscode.Disposable {
private readonly _disposables: vscode.Disposable[] = [];
private _lastValue = false;
private _changeTimer: NodeJS.Timeout | undefined;
private _updateVersion = 0;
private _updateTask: Promise<void> = Promise.resolve();

constructor(private readonly _repository: AppHostDataRepository) {
this._disposables.push(
vscode.window.onDidChangeVisibleTextEditors(() => this._update()),
vscode.window.onDidChangeVisibleTextEditors(() => this._queueUpdate()),
vscode.workspace.onDidChangeTextDocument(e => this._onTextDocumentChanged(e)),
);
this._update();
this._queueUpdate();
}

private _onTextDocumentChanged(event: vscode.TextDocumentChangeEvent): void {
Expand All @@ -47,22 +49,31 @@ export class AppHostFilePresenceWatcher implements vscode.Disposable {
}
this._changeTimer = setTimeout(() => {
this._changeTimer = undefined;
this._update();
this._queueUpdate();
}, AppHostFilePresenceWatcher._changeDebounceMs);
}

private _update(): void {
const value = this._anyVisibleEditorIsAppHost();
private _queueUpdate(): void {
const version = ++this._updateVersion;
this._updateTask = this._update(version);
}

private async _update(version: number): Promise<void> {
const value = await this._anyVisibleEditorIsAppHost();
if (version !== this._updateVersion) {
return;
}

if (value === this._lastValue) {
return;
}
this._lastValue = value;
this._repository.setAppHostFileOpen(value);
}
Comment on lines +61 to 72

private _anyVisibleEditorIsAppHost(): boolean {
private async _anyVisibleEditorIsAppHost(): Promise<boolean> {
for (const editor of vscode.window.visibleTextEditors) {
if (getParserForDocument(editor.document)) {
if (await getParserForDocument(editor.document)) {
return true;
}
}
Expand Down
29 changes: 20 additions & 9 deletions extension/src/editor/AspireCodeLensProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import { getParserForDocument } from './parsers/AppHostResourceParser';
import { AppHostResourceParser, getParserForDocument } from './parsers/AppHostResourceParser';
// Import parsers to trigger self-registration
import './parsers/csharpAppHostParser';
import './parsers/jsTsAppHostParser';
Expand Down Expand Up @@ -46,17 +46,28 @@ export class AspireCodeLensProvider implements vscode.CodeLensProvider {
);
}

provideCodeLenses(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.CodeLens[] {
provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CodeLens[]> {
return this._provideCodeLensesAsync(document, token);
}

private async _provideCodeLensesAsync(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[] | undefined> {
if (!vscode.workspace.getConfiguration('aspire').get<boolean>('enableCodeLens', true)) {
return [];
}

const parser = getParserForDocument(document);
const parser = await getParserForDocument(document);
if (token.isCancellationRequested) {
return undefined;
}

if (!parser) {
return [];
}

const resources = parser.parseResources(document);
const resources = await parser.parseResources(document);
if (token.isCancellationRequested) {
return undefined;
}

const appHosts = this._treeProvider.appHosts;
const workspaceResources = this._treeProvider.workspaceResources;
Expand All @@ -74,7 +85,7 @@ export class AspireCodeLensProvider implements vscode.CodeLensProvider {
// Builder-statement lenses (Open Dashboard + View Logs) appear only when this
// document maps to a concretely-running AppHost — independent of whether any
// Add* resource calls were found in the file.
this._addBuilderStatementLenses(lenses, document, parser, workspaceAppHostPath, workspaceResources);
await this._addBuilderStatementLenses(lenses, document, parser, workspaceAppHostPath, workspaceResources);

if (resources.length === 0) {
return lenses;
Expand Down Expand Up @@ -133,14 +144,14 @@ export class AspireCodeLensProvider implements vscode.CodeLensProvider {
}));
}

private _addBuilderStatementLenses(
private async _addBuilderStatementLenses(
lenses: vscode.CodeLens[],
document: vscode.TextDocument,
parser: { findBuilderStatementLine?(document: vscode.TextDocument): number | undefined },
parser: AppHostResourceParser,
workspaceAppHostPath: string,
workspaceResources: readonly ResourceJson[],
): void {
const builderLine = parser.findBuilderStatementLine?.(document);
): Promise<void> {
const builderLine = await parser.findBuilderStatementLine?.(document);
if (builderLine === undefined) {
return;
}
Expand Down
42 changes: 32 additions & 10 deletions extension/src/editor/AspireGutterDecorationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ function classifyState(state: string, stateStyle: string, healthStatus: string,

export class AspireGutterDecorationProvider implements vscode.Disposable {
private readonly _disposables: vscode.Disposable[] = [];
private readonly _updateVersions = new WeakMap<vscode.TextEditor, number>();
private _debounceTimer: ReturnType<typeof setTimeout> | undefined;
private _nextUpdateVersion = 0;
private _isDisposed = false;

constructor(private readonly _treeProvider: AspireAppHostTreeProvider) {
this._disposables.push(
Expand All @@ -129,27 +132,33 @@ export class AspireGutterDecorationProvider implements vscode.Disposable {
this._debounceTimer = undefined;
for (const editor of vscode.window.visibleTextEditors) {
if (editor.document === document) {
this._applyDecorations(editor);
void this._applyDecorations(editor);
}
}
}, 250);
}

private _updateAllVisibleEditors(): void {
for (const editor of vscode.window.visibleTextEditors) {
this._applyDecorations(editor);
void this._applyDecorations(editor);
}
}

private _applyDecorations(editor: vscode.TextEditor): void {
private async _applyDecorations(editor: vscode.TextEditor): Promise<void> {
const version = ++this._nextUpdateVersion;
this._updateVersions.set(editor, version);
if (!vscode.workspace.getConfiguration('aspire').get<boolean>('enableGutterDecorations', true)) {
this._clearDecorations(editor);
this._clearDecorations(editor, version);
return;
}

const parser = await getParserForDocument(editor.document);
if (!this._isCurrentUpdate(editor, version)) {
return;
}

const parser = getParserForDocument(editor.document);
if (!parser) {
this._clearDecorations(editor);
this._clearDecorations(editor, version);
return;
}

Expand All @@ -159,13 +168,17 @@ export class AspireGutterDecorationProvider implements vscode.Disposable {
const globalAppHost = this._resolveGlobalAppHostForDocument(editor.document, appHosts);
const workspaceAppHostMatchesDocument = workspaceAppHostPath !== '' && this._documentMatchesAppHostPath(editor.document, workspaceAppHostPath);
if (globalAppHost === undefined && (!workspaceAppHostMatchesDocument || workspaceResources.length === 0)) {
this._clearDecorations(editor);
this._clearDecorations(editor, version);
return;
}

const resources = await parser.parseResources(editor.document);
if (!this._isCurrentUpdate(editor, version)) {
return;
}

const resources = parser.parseResources(editor.document);
if (resources.length === 0) {
this._clearDecorations(editor);
this._clearDecorations(editor, version);
return;
}

Expand Down Expand Up @@ -219,13 +232,22 @@ export class AspireGutterDecorationProvider implements vscode.Disposable {
return matchesAppHostPathOrDirectory(document.uri.fsPath, appHostPath);
}

private _clearDecorations(editor: vscode.TextEditor): void {
private _clearDecorations(editor: vscode.TextEditor, version: number): void {
if (!this._isCurrentUpdate(editor, version)) {
return;
}

for (const type of Object.values(decorationTypes)) {
editor.setDecorations(type, []);
}
}

private _isCurrentUpdate(editor: vscode.TextEditor, version: number): boolean {
return !this._isDisposed && this._updateVersions.get(editor) === version;
}

dispose(): void {
this._isDisposed = true;
if (this._debounceTimer) {
clearTimeout(this._debounceTimer);
}
Expand Down
16 changes: 11 additions & 5 deletions extension/src/editor/parsers/AppHostResourceParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ export interface AppHostResourceParser {
getSupportedExtensions(): string[];

/** Returns true if the given document is an AppHost file for this language. */
isAppHostFile(document: vscode.TextDocument): boolean;
isAppHostFile(document: vscode.TextDocument): Promise<boolean>;

/** Parse resource definitions from the document. */
parseResources(document: vscode.TextDocument): ParsedResource[];
parseResources(document: vscode.TextDocument): Promise<ParsedResource[]>;

/**
* Locates the line containing the builder construction statement
* (e.g. `var builder = DistributedApplication.CreateBuilder(args);` for C#,
* `const builder = createBuilder();` for TS/JS).
* Returns the 0-based line of the start of the statement, or `undefined` if not found.
*/
findBuilderStatementLine?(document: vscode.TextDocument): number | undefined;
findBuilderStatementLine?(document: vscode.TextDocument): Promise<number | undefined>;
}

const _parsers: AppHostResourceParser[] = [];
Expand All @@ -44,9 +44,15 @@ export function registerParser(parser: AppHostResourceParser): void {
_parsers.push(parser);
}

export function getParserForDocument(document: vscode.TextDocument): AppHostResourceParser | undefined {
export async function getParserForDocument(document: vscode.TextDocument): Promise<AppHostResourceParser | undefined> {
const ext = getFileExtension(document.uri.fsPath);
return _parsers.find(p => p.getSupportedExtensions().includes(ext) && p.isAppHostFile(document));
for (const parser of _parsers) {
if (parser.getSupportedExtensions().includes(ext) && await parser.isAppHostFile(document)) {
return parser;
}
}

return undefined;
}

export function getAllParsers(): readonly AppHostResourceParser[] {
Expand Down
Loading
Loading