Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix all providers support #3440

Merged
merged 24 commits into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b03447b
First e2e version of fix all.
savpek Jul 9, 2019
cb213b4
On very very basic level fix all works.
savpek Jul 16, 2019
96eb096
Very simple version that fetches actions and executes selected.
savpek Aug 2, 2019
09d9333
Support for scopes in fix all operations.
savpek Aug 3, 2019
8704413
Fix for scoping when fix all actions are get
savpek Sep 21, 2019
609a690
Fix all command support
savpek Sep 29, 2019
d110c07
Version that works with fix all on save
savpek Oct 4, 2019
48b4e36
Moved to providers
savpek Oct 15, 2019
0fd12d4
Merge
savpek Dec 5, 2019
1bda57c
Merge remote-tracking branch 'upstream/master' into feature/fix-all-p…
savpek Dec 5, 2019
4a93dde
Merge and linting fixes
savpek Dec 5, 2019
be7218a
Naming fix and tested version that merges changes to file from existi…
savpek Dec 5, 2019
a5b4861
Update to support wantsTextChanges and actions flags
savpek Dec 5, 2019
101a267
Merge
savpek Dec 6, 2019
bf80498
Mergefixes
savpek Dec 6, 2019
a236607
Fix for protocol.ts merge
savpek Dec 6, 2019
d772483
Merge branch 'master' into feature/fix-all-providers
savpek Feb 2, 2020
1e259ae
Merge branch 'master' into feature/fix-all-providers
savpek Feb 6, 2020
3b55590
Merge branch 'master' into feature/fix-all-providers
savpek Mar 18, 2020
3b24764
Merge branch 'master' into feature/fix-all-providers
savpek Jun 5, 2020
2f8c675
Merge branch 'master' into feature/fix-all-providers
JoeRobich Aug 10, 2020
d6593b8
Merge branch 'master' into feature/fix-all-providers
JoeRobich Aug 10, 2020
27017f5
Merge branch 'master' into feature/fix-all-providers
JoeRobich Aug 11, 2020
3769538
Merge branch 'master' into feature/fix-all-providers
JoeRobich Aug 12, 2020
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
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,21 @@
"title": "Select Project",
"category": "OmniSharp"
},
{
"command": "o.fixAll.solution",
"title": "Fix all code issues in solution",
"category": "OmniSharp"
},
{
"command": "o.fixAll.project",
"title": "Fix all code issues in project",
"category": "OmniSharp"
},
{
"command": "o.fixAll.document",
"title": "Fix all code issues in document",
"category": "OmniSharp"
},
{
"command": "o.reanalyze.allProjects",
"title": "Analyze all projects",
Expand Down
3 changes: 1 addition & 2 deletions src/features/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function registerCommands(context: vscode.ExtensionContext, serve
disposable.add(vscode.commands.registerCommand('o.restart', async () => restartOmniSharp(context, server, optionProvider)));
disposable.add(vscode.commands.registerCommand('o.pickProjectAndStart', async () => pickProjectAndStart(server, optionProvider)));
disposable.add(vscode.commands.registerCommand('o.showOutput', () => eventStream.post(new ShowOmniSharpChannel())));

disposable.add(vscode.commands.registerCommand('dotnet.restore.project', async () => pickProjectAndDotnetRestore(server, eventStream)));
disposable.add(vscode.commands.registerCommand('dotnet.restore.all', async () => dotnetRestoreAllProjects(server, eventStream)));

Expand All @@ -43,10 +44,8 @@ export default function registerCommands(context: vscode.ExtensionContext, serve
let attachItemsProvider = DotNetAttachItemsProviderFactory.Get();
let attacher = new AttachPicker(attachItemsProvider);
disposable.add(vscode.commands.registerCommand('csharp.listProcess', async () => attacher.ShowAttachEntries()));

// Register command for generating tasks.json and launch.json assets.
disposable.add(vscode.commands.registerCommand('dotnet.generateAssets', async (selectedIndex) => generateAssets(server, selectedIndex)));

// Register command for remote process picker for attach
disposable.add(vscode.commands.registerCommand('csharp.listRemoteProcess', async (args) => RemoteAttachPicker.ShowAttachEntries(args, platformInfo)));

Expand Down
10 changes: 10 additions & 0 deletions src/features/fixAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import AbstractProvider from "./abstractProvider";

export class FixAll extends AbstractProvider
{
}
144 changes: 144 additions & 0 deletions src/features/fixAllProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import * as serverUtils from '../omnisharp/utils';
import * as protocol from '../omnisharp/protocol';
import { OmniSharpServer } from '../omnisharp/server';
import { FixAllScope, FixAllItem, FileModificationType } from '../omnisharp/protocol';
import { Uri } from 'vscode';
import CompositeDisposable from '../CompositeDisposable';
import AbstractProvider from './abstractProvider';
import { toRange2 } from '../omnisharp/typeConversion';
import { LanguageMiddlewareFeature } from '../omnisharp/LanguageMiddlewareFeature';

export class FixAllProvider extends AbstractProvider implements vscode.CodeActionProvider {
public constructor(private server: OmniSharpServer, languageMiddlewareFeature: LanguageMiddlewareFeature) {
super(server, languageMiddlewareFeature);
let disposable = new CompositeDisposable();
disposable.add(vscode.commands.registerCommand('o.fixAll.solution', async () => this.fixAllMenu(server, protocol.FixAllScope.Solution)));
disposable.add(vscode.commands.registerCommand('o.fixAll.project', async () => this.fixAllMenu(server, protocol.FixAllScope.Project)));
disposable.add(vscode.commands.registerCommand('o.fixAll.document', async () => this.fixAllMenu(server, protocol.FixAllScope.Document)));
this.addDisposables(disposable);
}

public async provideCodeActions(
document: vscode.TextDocument,
_range: vscode.Range | vscode.Selection,
context: vscode.CodeActionContext,
_token: vscode.CancellationToken,
): Promise<vscode.CodeAction[]> {
console.log(context);
if (!context.only) {
return [];
}

if (context.only.value === "source.fixAll.csharp") {
await this.applyFixes(document.fileName, FixAllScope.Document, undefined);
}

return [];
}

private async fixAllMenu(server: OmniSharpServer, scope: protocol.FixAllScope): Promise<void> {
let availableFixes = await serverUtils.getFixAll(server, { FileName: vscode.window.activeTextEditor.document.fileName, Scope: scope });

let targets = availableFixes.Items.map(x => `${x.Id}: ${x.Message}`);

if (scope === protocol.FixAllScope.Document) {
targets = ["Fix all issues", ...targets];
}

return vscode.window.showQuickPick(targets, {
matchOnDescription: true,
placeHolder: `Select fix all action`
}).then(async selectedAction => {
let filter: FixAllItem[] = undefined;

if (selectedAction === undefined) {
return;
}

if (selectedAction !== "Fix all issues") {
let actionTokens = selectedAction.split(":");
filter = [{ Id: actionTokens[0], Message: actionTokens[1] }];
}

await this.applyFixes(vscode.window.activeTextEditor.document.fileName, scope, filter);
});
}

private async applyFixes(fileName: string, scope: FixAllScope, fixAllFilter: FixAllItem[]): Promise<boolean | string | {}> {
let response = await serverUtils.runFixAll(this.server, { FileName: fileName, Scope: scope, FixAllFilter: fixAllFilter, WantsAllCodeActionOperations: true, WantsTextChanges: true });

if (response && Array.isArray(response.Changes)) {
let edit = new vscode.WorkspaceEdit();

let fileToOpen: Uri = null;
let renamedFiles: Uri[] = [];

for (let change of response.Changes) {
if (change.ModificationType == FileModificationType.Renamed)
{
// The file was renamed. Omnisharp has already persisted
// the right changes to disk. We don't need to try to
// apply text changes (and will skip this file if we see an edit)
renamedFiles.push(Uri.file(change.FileName));
}
}

for (let change of response.Changes) {
if (change.ModificationType == FileModificationType.Opened)
{
// The CodeAction requested that we open a file.
// Record that file name and keep processing CodeActions.
// If a CodeAction requests that we open multiple files
// we only open the last one (what would it mean to open multiple files?)
fileToOpen = vscode.Uri.file(change.FileName);
}

if (change.ModificationType == FileModificationType.Modified)
{
let uri = vscode.Uri.file(change.FileName);
if (renamedFiles.some(r => r == uri))
{
// This file got renamed. Omnisharp has already
// persisted the new file with any applicable changes.
continue;
}

let edits: vscode.TextEdit[] = [];
for (let textChange of change.Changes) {
edits.push(vscode.TextEdit.replace(toRange2(textChange), textChange.NewText));
}

edit.set(uri, edits);
}
}

let applyEditPromise = vscode.workspace.applyEdit(edit);

// Unfortunately, the textEditor.Close() API has been deprecated
// and replaced with a command that can only close the active editor.
// If files were renamed that weren't the active editor, their tabs will
// be left open and marked as "deleted" by VS Code
let next = applyEditPromise;
if (renamedFiles.some(r => r.fsPath == vscode.window.activeTextEditor.document.uri.fsPath))
{
next = applyEditPromise.then(_ =>
{
return vscode.commands.executeCommand("workbench.action.closeActiveEditor");
});
}

return fileToOpen != null
? next.then(_ =>
{
return vscode.commands.executeCommand("vscode.open", fileToOpen);
})
: next;
}
}
}
2 changes: 2 additions & 0 deletions src/omnisharp/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import trackVirtualDocuments from '../features/virtualDocumentTracker';
import { StructureProvider } from '../features/structureProvider';
import { OmniSharpMonoResolver } from './OmniSharpMonoResolver';
import { getMonoVersion } from '../utils/getMonoVersion';
import { FixAllProvider } from '../features/fixAllProvider';
import { LanguageMiddlewareFeature } from './LanguageMiddlewareFeature';
import SemanticTokensProvider from '../features/semanticTokensProvider';

Expand Down Expand Up @@ -95,6 +96,7 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an
const codeActionProvider = new CodeActionProvider(server, optionProvider, languageMiddlewareFeature);
localDisposables.add(codeActionProvider);
localDisposables.add(vscode.languages.registerCodeActionsProvider(documentSelector, codeActionProvider));
localDisposables.add(vscode.languages.registerCodeActionsProvider(documentSelector, new FixAllProvider(server, languageMiddlewareFeature)));
localDisposables.add(reportDiagnostics(server, advisor, languageMiddlewareFeature));
localDisposables.add(forwardChanges(server));
localDisposables.add(trackVirtualDocuments(server, eventStream));
Expand Down
34 changes: 34 additions & 0 deletions src/omnisharp/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export module Requests {
export const TypeLookup = '/typelookup';
export const UpdateBuffer = '/updatebuffer';
export const Metadata = '/metadata';
export const RunFixAll = '/runfixall';
export const GetFixAll = '/getfixall';
export const ReAnalyze = '/reanalyze';
export const QuickInfo = '/quickinfo';
}
Expand Down Expand Up @@ -254,6 +256,20 @@ export interface GetCodeActionsResponse {
CodeActions: string[];
}

export interface RunFixAllActionResponse {
Text: string;
Changes: ModifiedFileResponse[];
}

export interface FixAllItem {
Id: string;
Message: string;
}

export interface GetFixAllResponse {
Items: FixAllItem[];
}

export interface SyntaxFeature {
Name: string;
Data: string;
Expand Down Expand Up @@ -474,6 +490,24 @@ export enum FileChangeType {
DirectoryDelete = "DirectoryDelete"
}

export enum FixAllScope {
Document = "Document",
Project = "Project",
Solution = "Solution"
}

export interface GetFixAllRequest extends FileBasedRequest {
Scope: FixAllScope;
FixAllFilter?: FixAllItem[];
}

export interface RunFixAllRequest extends FileBasedRequest {
Scope: FixAllScope;
FixAllFilter?: FixAllItem[];
WantsTextChanges: boolean;
WantsAllCodeActionOperations: boolean;
}

export interface QuickInfoRequest extends Request {
}

Expand Down
8 changes: 8 additions & 0 deletions src/omnisharp/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ export async function findSymbols(server: OmniSharpServer, request: protocol.Fin
return server.makeRequest<protocol.FindSymbolsResponse>(protocol.Requests.FindSymbols, request, token);
}

export async function runFixAll(server: OmniSharpServer, request: protocol.RunFixAllRequest): Promise<protocol.RunFixAllActionResponse> {
return server.makeRequest<protocol.RunFixAllActionResponse>(protocol.Requests.RunFixAll, request);
}

export async function getFixAll(server: OmniSharpServer, request: protocol.GetFixAllRequest): Promise<protocol.GetFixAllResponse> {
return server.makeRequest<protocol.GetFixAllResponse>(protocol.Requests.GetFixAll, request);
}

export async function findUsages(server: OmniSharpServer, request: protocol.FindUsagesRequest, token: vscode.CancellationToken) {
return server.makeRequest<protocol.QuickFixResponse>(protocol.Requests.FindUsages, request, token);
}
Expand Down