Skip to content

Commit

Permalink
Add document drop handler (#2448)
Browse files Browse the repository at this point in the history
  • Loading branch information
bwateratmsft committed Jul 11, 2023
1 parent a7126c9 commit 579c37e
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 52 deletions.
3 changes: 3 additions & 0 deletions ext/vscode/.vscode/cspell-dictionary.txt
Expand Up @@ -7,3 +7,6 @@ azureresources
walkthrough
walkthroughs
DEBUGTELEMETRY
appservice
containerapp
staticwebapp
43 changes: 23 additions & 20 deletions ext/vscode/src/language/AzureYamlDiagnosticProvider.ts
@@ -1,10 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
import { AzExtFsExtra, IActionContext, callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils';
import * as vscode from 'vscode';
import { documentDebounce } from './documentDebounce';
import { getAzureYamlProjectInformation } from './azureYamlUtils';
import { TelemetryId } from '../telemetry/telemetryId';

// Time between when the user stops typing and when we send diagnostics
const DiagnosticDelay = 1000;
Expand Down Expand Up @@ -33,31 +34,33 @@ export class AzureYamlDiagnosticProvider extends vscode.Disposable {
this.diagnosticCollection = diagnosticCollection;
}

public async provideDiagnostics(document: vscode.TextDocument, token?: vscode.CancellationToken): Promise<vscode.Diagnostic[] | undefined> {
const results: vscode.Diagnostic[] = [];
public provideDiagnostics(document: vscode.TextDocument, token?: vscode.CancellationToken): Promise<vscode.Diagnostic[] | undefined> {
return callWithTelemetryAndErrorHandling(TelemetryId.AzureYamlProvideDiagnostics, async (context: IActionContext) => {
const results: vscode.Diagnostic[] = [];

try {
const projectInformation = await getAzureYamlProjectInformation(document);
try {
const projectInformation = await getAzureYamlProjectInformation(document);

for (const project of projectInformation) {
if (await AzExtFsExtra.pathExists(project.projectUri)) {
continue;
}
for (const project of projectInformation) {
if (await AzExtFsExtra.pathExists(project.projectUri)) {
continue;
}

const diagnostic = new vscode.Diagnostic(
project.projectValueNodeRange,
vscode.l10n.t('The project path must be an existing folder or file path relative to the azure.yaml file.'),
vscode.DiagnosticSeverity.Error
);
const diagnostic = new vscode.Diagnostic(
project.projectValueNodeRange,
vscode.l10n.t('The project path must be an existing folder or file path relative to the azure.yaml file.'),
vscode.DiagnosticSeverity.Error
);

results.push(diagnostic);
results.push(diagnostic);
}
} catch {
// Best effort--the YAML extension will show parsing errors for us if it is present
}
} catch {
// Best effort--the YAML extension will show parsing errors for us if it is present
return results;
}

return results;
context.telemetry.measurements.diagnosticCount = results.length;
return results;
});
}

private async updateDiagnosticsFor(document: vscode.TextDocument, delay: boolean = true): Promise<void> {
Expand Down
40 changes: 40 additions & 0 deletions ext/vscode/src/language/AzureYamlDocumentDropEditProvider.ts
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { AzExtFsExtra, IActionContext, callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils';
import * as path from 'path';
import * as vscode from 'vscode';
import { getProjectRelativePath } from './azureYamlUtils';
import { TelemetryId } from '../telemetry/telemetryId';

export class AzureYamlDocumentDropEditProvider implements vscode.DocumentDropEditProvider {
public provideDocumentDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
return callWithTelemetryAndErrorHandling(TelemetryId.AzureYamlProvideDocumentDropEdits, async (context: IActionContext) => {
const maybeFolder = dataTransfer.get('text/uri-list')?.value;
const maybeFolderUri = vscode.Uri.parse(maybeFolder);

if (await AzExtFsExtra.pathExists(maybeFolderUri) && await AzExtFsExtra.isDirectory(maybeFolderUri)) {
const basename = path.basename(maybeFolderUri.fsPath);
const newRelativePath = getProjectRelativePath(document.uri, maybeFolderUri);

const initialWhitespace = position.character === 0 ? '\n\t' : '\n';

const snippet = new vscode.SnippetString(initialWhitespace)
.appendPlaceholder(basename).appendText(':\n')
.appendText(`\t\tproject: ${newRelativePath}\n`)
.appendText('\t\tlanguage: ')
.appendChoice(['dotnet', 'csharp', 'fsharp', 'py', 'python', 'js', 'ts', 'java'])
.appendText('\n')
.appendText('\t\thost: ')
.appendChoice(['appservice', 'containerapp', 'function', 'staticwebapp', 'aks'])
.appendText('\n');

context.telemetry.properties.editProvided = 'true';
return new vscode.DocumentDropEdit(snippet);
}

context.telemetry.properties.editProvided = 'false';
return undefined;
});
}
}
61 changes: 35 additions & 26 deletions ext/vscode/src/language/AzureYamlProjectRenameProvider.ts
@@ -1,9 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
import { AzExtFsExtra, IActionContext, callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils';
import * as vscode from 'vscode';
import { getAzureYamlProjectInformation, getProjectRelativePath } from './azureYamlUtils';
import { TelemetryId } from '../telemetry/telemetryId';

export class AzureYamlProjectRenameProvider extends vscode.Disposable {
public constructor() {
Expand All @@ -15,31 +16,39 @@ export class AzureYamlProjectRenameProvider extends vscode.Disposable {
});
}

public async provideWorkspaceEdits(oldUri: vscode.Uri, newUri: vscode.Uri, token: vscode.CancellationToken): Promise<vscode.WorkspaceEdit | undefined> {
// When a folder is renamed, only the folder is passed in as the old URI
// At the time this is called, the rename has not happened yet
if (!await AzExtFsExtra.isDirectory(oldUri)) {
return undefined;
}

const azureYamlUris = await vscode.workspace.findFiles('**/azure.{yml,yaml}', undefined, 1, token);
if (azureYamlUris.length === 0) {
return undefined;
}

const azureYamlUri = azureYamlUris[0];
const azureYaml = await vscode.workspace.openTextDocument(azureYamlUri);
const projectInformation = await getAzureYamlProjectInformation(azureYaml);

const projectToUpdate = projectInformation.find(pi => pi.projectUri.toString() === oldUri.toString());
if (!projectToUpdate) {
return undefined;
}

const newRelativePath = getProjectRelativePath(azureYamlUri, newUri);
const projectUriEdit = new vscode.WorkspaceEdit();
projectUriEdit.replace(azureYamlUri, projectToUpdate.projectValueNodeRange, newRelativePath);
return projectUriEdit;
public provideWorkspaceEdits(oldUri: vscode.Uri, newUri: vscode.Uri, token: vscode.CancellationToken): Promise<vscode.WorkspaceEdit | undefined> {
return callWithTelemetryAndErrorHandling(TelemetryId.AzureYamlProjectRenameProvideWorkspaceEdits, async (context: IActionContext) => {
// When a folder is renamed, only the folder is passed in as the old URI
// At the time this is called, the rename has not happened yet
if (!await AzExtFsExtra.isDirectory(oldUri)) {
context.telemetry.properties.result = 'Canceled';
context.telemetry.properties.cancelStep = 'SourceNotDirectory';
return undefined;
}

const azureYamlUris = await vscode.workspace.findFiles('**/azure.{yml,yaml}', undefined, 1, token);
if (azureYamlUris.length === 0) {
context.telemetry.properties.result = 'Canceled';
context.telemetry.properties.cancelStep = 'NoAzureYaml';
return undefined;
}

const azureYamlUri = azureYamlUris[0];
const azureYaml = await vscode.workspace.openTextDocument(azureYamlUri);
const projectInformation = await getAzureYamlProjectInformation(azureYaml);

const projectToUpdate = projectInformation.find(pi => pi.projectUri.toString() === oldUri.toString());
if (!projectToUpdate) {
context.telemetry.properties.result = 'Canceled';
context.telemetry.properties.cancelStep = 'ProjectNotInAzureYaml';
return undefined;
}

const newRelativePath = getProjectRelativePath(azureYamlUri, newUri);
const projectUriEdit = new vscode.WorkspaceEdit();
projectUriEdit.replace(azureYamlUri, projectToUpdate.projectValueNodeRange, newRelativePath);
return projectUriEdit;
});
}

private handleWillRenameFile(evt: vscode.FileWillRenameEvent): void {
Expand Down
5 changes: 5 additions & 0 deletions ext/vscode/src/language/languageFeatures.ts
Expand Up @@ -5,6 +5,7 @@ import * as vscode from 'vscode';
import ext from '../ext';
import { AzureYamlDiagnosticProvider } from './AzureYamlDiagnosticProvider';
import { AzureYamlProjectRenameProvider } from './AzureYamlProjectRenameProvider';
import { AzureYamlDocumentDropEditProvider } from './AzureYamlDocumentDropEditProvider';

export const AzureYamlSelector: vscode.DocumentSelector = { language: 'yaml', scheme: 'file', pattern: '**/azure.{yml,yaml}' };

Expand All @@ -16,4 +17,8 @@ export function registerLanguageFeatures(): void {
ext.context.subscriptions.push(
new AzureYamlProjectRenameProvider()
);

ext.context.subscriptions.push(
vscode.languages.registerDocumentDropEditProvider(AzureYamlSelector, new AzureYamlDocumentDropEditProvider())
);
}
21 changes: 15 additions & 6 deletions ext/vscode/src/telemetry/telemetryId.ts
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

// Defines telemetry IDs for custom telemetry events exposed by the extension.
// Note that command invocations are covered by the AzExtUtils library,
// Note that command invocations are covered by the AzExtUtils library,
// which automatically captures the duration and success/failure information for every command.
// Every event includes duration and success/failure information.
// Some additional data is captured for certain events; see comments for individual enum members.
Expand Down Expand Up @@ -57,18 +57,27 @@ export enum TelemetryId {

// Reported when 'env refresh' CLI command is invoked.
EnvRefreshCli = 'azure-dev.commands.cli.env-refresh.task',

// Reported when the product evaluates whether to prompt the user for a survey.
// We capture
// - whether the user was already offered the survey,
// We capture
// - whether the user was already offered the survey,
// - whether the user was prompted during current session (for any survey)
// - whether the user is eligible for given survey
// - whether the user is flighted for the survey
// - whether the user is flighted for the survey
SurveyCheck = 'azure-dev.survey-check',

// Captures the result of a survey prompt
SurveyPromptResponse = 'azure-dev.survey-prompt-response',

WorkspaceViewApplicationResolve = 'azure-dev.views.workspace.application.resolve',
WorkspaceViewEnvironmentResolve = 'azure-dev.views.workspace.environment.resolve'
WorkspaceViewEnvironmentResolve = 'azure-dev.views.workspace.environment.resolve',

// Reported when diagnostics are provided on an azure.yaml document
AzureYamlProvideDiagnostics = 'azure-dev.azureYaml.provideDiagnostics',

// Reported when the document drop edit provider is invoked
AzureYamlProvideDocumentDropEdits = 'azure-dev.azureYaml.provideDocumentDropEdits',

// Reported when the project rename provider is invoked
AzureYamlProjectRenameProvideWorkspaceEdits = 'azure-dev.azureYaml.projectRename.provideWorkspaceEdits',
}

0 comments on commit 579c37e

Please sign in to comment.