From b92e0b6ae22dc105b74456cd6456f9dd39fd8a34 Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Wed, 25 May 2016 16:37:36 -0700 Subject: [PATCH 01/10] Add dotnet-test discovery and execution --- src/features/codeLensProvider.ts | 133 +++++---- src/features/commands.ts | 231 +++++++------- src/omnisharpUtils.ts | 7 + src/protocol.ts | 498 ++++++++++++++++--------------- 4 files changed, 469 insertions(+), 400 deletions(-) diff --git a/src/features/codeLensProvider.ts b/src/features/codeLensProvider.ts index 496b660d7..24661251e 100644 --- a/src/features/codeLensProvider.ts +++ b/src/features/codeLensProvider.ts @@ -13,71 +13,80 @@ import * as serverUtils from '../omnisharpUtils'; class OmniSharpCodeLens extends CodeLens { - fileName: string; + fileName: string; - constructor(fileName: string, range: Range) { - super(range); - this.fileName = fileName; - } + constructor(fileName: string, range: Range) { + super(range); + this.fileName = fileName; + } } export default class OmniSharpCodeLensProvider extends AbstractSupport implements CodeLensProvider { - private static filteredSymbolNames: { [name: string]: boolean } = { - 'Equals': true, - 'Finalize': true, - 'GetHashCode': true, - 'ToString': true - }; - - provideCodeLenses(document: TextDocument, token: CancellationToken): CodeLens[] | Thenable { - - return serverUtils.currentFileMembersAsTree(this._server, { Filename: document.fileName }, token).then(tree => { - let ret: CodeLens[] = []; - tree.TopLevelTypeDefinitions.forEach(node => OmniSharpCodeLensProvider._convertQuickFix(ret, document.fileName, node)); - return ret; - }); - } - - private static _convertQuickFix(bucket: CodeLens[], fileName:string, node: protocol.Node): void { - - if (node.Kind === 'MethodDeclaration' && OmniSharpCodeLensProvider.filteredSymbolNames[node.Location.Text]) { - return; - } - - let lens = new OmniSharpCodeLens(fileName, toRange(node.Location)); - bucket.push(lens); - - for (let child of node.ChildNodes) { - OmniSharpCodeLensProvider._convertQuickFix(bucket, fileName, child); - } - } - - resolveCodeLens(codeLens: CodeLens, token: CancellationToken): Thenable { - if (codeLens instanceof OmniSharpCodeLens) { - - let req = { - Filename: codeLens.fileName, - Line: codeLens.range.start.line + 1, - Column: codeLens.range.start.character + 1, - OnlyThisFile: false, - ExcludeDefinition: true - }; - - return serverUtils.findUsages(this._server, req, token).then(res => { - if (!res || !Array.isArray(res.QuickFixes)) { - return; - } - - let len = res.QuickFixes.length; - codeLens.command = { - title: len === 1 ? '1 reference' : `${len} references`, - command: 'editor.action.showReferences', - arguments: [Uri.file(req.Filename), codeLens.range.start, res.QuickFixes.map(toLocation)] - }; - - return codeLens; - }); - } - } + private static filteredSymbolNames: { [name: string]: boolean } = { + 'Equals': true, + 'Finalize': true, + 'GetHashCode': true, + 'ToString': true + }; + + provideCodeLenses(document: TextDocument, token: CancellationToken): CodeLens[] | Thenable { + + return serverUtils.currentFileMembersAsTree(this._server, { Filename: document.fileName }, token).then(tree => { + let ret: CodeLens[] = []; + tree.TopLevelTypeDefinitions.forEach(node => OmniSharpCodeLensProvider._convertQuickFix(ret, document.fileName, node)); + return ret; + }); + } + + private static _convertQuickFix(bucket: CodeLens[], fileName: string, node: protocol.Node): void { + + if (node.Kind === 'MethodDeclaration' && OmniSharpCodeLensProvider.filteredSymbolNames[node.Location.Text]) { + return; + } + + let lens = new OmniSharpCodeLens(fileName, toRange(node.Location)); + bucket.push(lens); + + for (let child of node.ChildNodes) { + OmniSharpCodeLensProvider._convertQuickFix(bucket, fileName, child); + } + + let testFeature = node.Features.find(value => value.startsWith('XunitTestMethod')); + if (testFeature) { + // this test method has a test feature + let testMethod = testFeature.split(':')[1]; + + bucket.push(new CodeLens(toRange(node.Location), { title: "run test", command: 'dotnet.test.run', arguments: [testMethod, fileName] })); + bucket.push(new CodeLens(toRange(node.Location), { title: "debug test", command: 'dotnet.test.debug', arguments: [testMethod, fileName] })); + } + } + + resolveCodeLens(codeLens: CodeLens, token: CancellationToken): Thenable { + if (codeLens instanceof OmniSharpCodeLens) { + + let req = { + Filename: codeLens.fileName, + Line: codeLens.range.start.line + 1, + Column: codeLens.range.start.character + 1, + OnlyThisFile: false, + ExcludeDefinition: true + }; + + return serverUtils.findUsages(this._server, req, token).then(res => { + if (!res || !Array.isArray(res.QuickFixes)) { + return; + } + + let len = res.QuickFixes.length; + codeLens.command = { + title: len === 1 ? '1 reference' : `${len} references`, + command: 'editor.action.showReferences', + arguments: [Uri.file(req.Filename), codeLens.range.start, res.QuickFixes.map(toLocation)] + }; + + return codeLens; + }); + } + } } diff --git a/src/features/commands.ts b/src/features/commands.ts index e3e71241b..d9da712a4 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -17,130 +17,157 @@ import * as vscode from 'vscode'; let channel = vscode.window.createOutputChannel('.NET'); export default function registerCommands(server: OmnisharpServer, extensionPath: string) { - let d1 = vscode.commands.registerCommand('o.restart', () => server.restart()); - let d2 = vscode.commands.registerCommand('o.pickProjectAndStart', () => pickProjectAndStart(server)); - let d3 = vscode.commands.registerCommand('o.showOutput', () => server.getChannel().show(vscode.ViewColumn.Three)); - let d4 = vscode.commands.registerCommand('dotnet.restore', () => dotnetRestoreAllProjects(server)); - + let d1 = vscode.commands.registerCommand('o.restart', () => server.restart()); + let d2 = vscode.commands.registerCommand('o.pickProjectAndStart', () => pickProjectAndStart(server)); + let d3 = vscode.commands.registerCommand('o.showOutput', () => server.getChannel().show(vscode.ViewColumn.Three)); + let d4 = vscode.commands.registerCommand('dotnet.restore', () => dotnetRestoreAllProjects(server)); + // register empty handler for csharp.installDebugger // running the command activates the extension, which is all we need for installation to kickoff let d5 = vscode.commands.registerCommand('csharp.downloadDebugger', () => { }); - - return vscode.Disposable.from(d1, d2, d3, d4, d5); + + return vscode.Disposable.from(d1, d2, d3, d4, d5, + vscode.commands.registerCommand('dotnet.test.run', (testMethod, fileName) => runDotnetTest(testMethod, fileName, server)), + vscode.commands.registerCommand('dotnet.test.debug', (testMethod, fileName) => debugDotnetTest(testMethod, fileName, server))); +} + +// Run test through dotnet-test command. This function can be moved to a separate structure +function runDotnetTest(testMethod: string, fileName: string, server: OmnisharpServer) { + vscode.window.showInformationMessage('run test! ' + testMethod + ' at ' + fileName); + serverUtils.runDotNetTest(server, { FileName: fileName, MethodName: testMethod }).then(response => { + vscode.window.showInformationMessage('Test ' + testMethod + response.Pass ? 'Passed' : 'Failed'); + }); +} + +// Run test through dotnet-test command with debugger attached +function debugDotnetTest(testMethod: string, fileName: string, server: OmnisharpServer) { + serverUtils.getTestStartInfo(server, { FileName: fileName, MethodName: testMethod }).then(response => { + vscode.commands.executeCommand( + 'vscode.startDebug', { + "name": ".NET test launch", + "type": "coreclr", + "request": "launch", + "program": response.Executable, + "args": [response.Argument], + "cwd": "${workspaceRoot}", + "stopAtEntry": false + } + ); + }); } function pickProjectAndStart(server: OmnisharpServer) { - return findLaunchTargets().then(targets => { - - let currentPath = server.getSolutionPathOrFolder(); - if (currentPath) { - for (let target of targets) { - if (target.target.fsPath === currentPath) { - target.label = `\u2713 ${target.label}`; - } - } - } - - return vscode.window.showQuickPick(targets, { - matchOnDescription: true, - placeHolder: `Select 1 of ${targets.length} projects` - }).then(target => { - if (target) { - return server.restart(target.target.fsPath); - } - }); - }); + return findLaunchTargets().then(targets => { + + let currentPath = server.getSolutionPathOrFolder(); + if (currentPath) { + for (let target of targets) { + if (target.target.fsPath === currentPath) { + target.label = `\u2713 ${target.label}`; + } + } + } + + return vscode.window.showQuickPick(targets, { + matchOnDescription: true, + placeHolder: `Select 1 of ${targets.length} projects` + }).then(target => { + if (target) { + return server.restart(target.target.fsPath); + } + }); + }); } interface Command { - label: string; - description: string; - execute(): Thenable; + label: string; + description: string; + execute(): Thenable; } function projectsToCommands(projects: protocol.DotNetProject[]): Promise[] { - return projects.map(project => { - let projectDirectory = project.Path; - - return fs.lstatAsync(projectDirectory).then(stats => { - if (stats.isFile()) { - projectDirectory = path.dirname(projectDirectory); - } - - return { - label: `dotnet restore - (${project.Name || path.basename(project.Path)})`, - description: projectDirectory, - execute() { - return runDotnetRestore(projectDirectory); - } - }; - }); - }); + return projects.map(project => { + let projectDirectory = project.Path; + + return fs.lstatAsync(projectDirectory).then(stats => { + if (stats.isFile()) { + projectDirectory = path.dirname(projectDirectory); + } + + return { + label: `dotnet restore - (${project.Name || path.basename(project.Path)})`, + description: projectDirectory, + execute() { + return runDotnetRestore(projectDirectory); + } + }; + }); + }); } export function dotnetRestoreAllProjects(server: OmnisharpServer) { - if (!server.isRunning()) { - return Promise.reject('OmniSharp server is not running.'); - } - - return serverUtils.requestWorkspaceInformation(server).then(info => { - - if (!('DotNet in info') || info.DotNet.Projects.length < 1) { - return Promise.reject("No .NET Core projects found"); - } - - let commandPromises = projectsToCommands(info.DotNet.Projects); - - return Promise.all(commandPromises).then(commands => { - return vscode.window.showQuickPick(commands); - }).then(command => { - if (command) { - return command.execute(); - } - }); - }); + if (!server.isRunning()) { + return Promise.reject('OmniSharp server is not running.'); + } + + return serverUtils.requestWorkspaceInformation(server).then(info => { + + if (!('DotNet in info') || info.DotNet.Projects.length < 1) { + return Promise.reject("No .NET Core projects found"); + } + + let commandPromises = projectsToCommands(info.DotNet.Projects); + + return Promise.all(commandPromises).then(commands => { + return vscode.window.showQuickPick(commands); + }).then(command => { + if (command) { + return command.execute(); + } + }); + }); } export function dotnetRestoreForProject(server: OmnisharpServer, fileName: string) { - if (!server.isRunning()) { - return Promise.reject('OmniSharp server is not running.'); - } - - return serverUtils.requestWorkspaceInformation(server).then(info => { - - if (!('DotNet in info') || info.DotNet.Projects.length < 1) { - return Promise.reject("No .NET Core projects found"); - } - - let directory = path.dirname(fileName); - - for (let project of info.DotNet.Projects) { - if (project.Path === directory) { - return runDotnetRestore(directory, fileName); - } - } - }); + if (!server.isRunning()) { + return Promise.reject('OmniSharp server is not running.'); + } + + return serverUtils.requestWorkspaceInformation(server).then(info => { + + if (!('DotNet in info') || info.DotNet.Projects.length < 1) { + return Promise.reject("No .NET Core projects found"); + } + + let directory = path.dirname(fileName); + + for (let project of info.DotNet.Projects) { + if (project.Path === directory) { + return runDotnetRestore(directory, fileName); + } + } + }); } function runDotnetRestore(cwd: string, fileName?: string) { - return new Promise((resolve, reject) => { - channel.clear(); - channel.show(); - - let cmd = 'dotnet restore'; - if (fileName) { - cmd = `${cmd} "${fileName}"` - } - - return cp.exec(cmd, {cwd: cwd, env: process.env}, (err, stdout, stderr) => { - channel.append(stdout.toString()); - channel.append(stderr.toString()); - if (err) { - channel.append('ERROR: ' + err); - } - }); - }); + return new Promise((resolve, reject) => { + channel.clear(); + channel.show(); + + let cmd = 'dotnet restore'; + if (fileName) { + cmd = `${cmd} "${fileName}"` + } + + return cp.exec(cmd, { cwd: cwd, env: process.env }, (err, stdout, stderr) => { + channel.append(stdout.toString()); + channel.append(stderr.toString()); + if (err) { + channel.append('ERROR: ' + err); + } + }); + }); } \ No newline at end of file diff --git a/src/omnisharpUtils.ts b/src/omnisharpUtils.ts index 409a51e23..d8733cb68 100644 --- a/src/omnisharpUtils.ts +++ b/src/omnisharpUtils.ts @@ -73,3 +73,10 @@ export function updateBuffer(server: OmnisharpServer, request: protocol.UpdateBu return server.makeRequest(protocol.Requests.UpdateBuffer, request); } +export function getTestStartInfo(server: OmnisharpServer, request: protocol.GetTestStartInfoRequest) { + return server.makeRequest(protocol.Requests.GetTestStartInfo, request); +} + +export function runDotNetTest(server: OmnisharpServer, request: protocol.RunDotNetTestRequest) { + return server.makeRequest(protocol.Requests.RunDotNetTest, request); +} \ No newline at end of file diff --git a/src/protocol.ts b/src/protocol.ts index 959d05a17..61ab87117 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -23,16 +23,19 @@ export module Requests { export const RemoveFromProject = '/removefromproject'; export const Rename = '/rename'; export const RunCodeAction = '/runcodeaction'; - export const SignatureHelp = '/signatureHelp'; + export const SignatureHelp = '/signatureHelp'; export const TypeLookup = '/typelookup'; export const UpdateBuffer = '/updatebuffer'; + + export const GetTestStartInfo = '/getteststartinfo'; + export const RunDotNetTest = '/rundotnettest'; } export interface Request { - Filename: string; - Line?: number; - Column?: number; - Buffer?: string; + Filename: string; + Line?: number; + Column?: number; + Buffer?: string; Changes?: LinePositionSpanTextChange[]; } @@ -49,394 +52,417 @@ export interface UpdateBufferRequest extends Request { } export interface ChangeBufferRequest { - FileName: string; - StartLine: number; - StartColumn: number; - EndLine: number; - EndColumn: number; - NewText: string; + FileName: string; + StartLine: number; + StartColumn: number; + EndLine: number; + EndColumn: number; + NewText: string; } export interface AddToProjectRequest extends Request { - //? + //? } export interface RemoveFromProjectRequest extends Request { - //? + //? } export interface FindUsagesRequest extends Request { - // MaxWidth: number; ? - OnlyThisFile: boolean; - ExcludeDefinition: boolean; + // MaxWidth: number; ? + OnlyThisFile: boolean; + ExcludeDefinition: boolean; } export interface FindSymbolsRequest extends Request { - Filter: string; + Filter: string; } export interface FormatRequest extends Request { - ExpandTab: boolean; + ExpandTab: boolean; } export interface CodeActionRequest extends Request { - CodeAction: number; - WantsTextChanges?: boolean; - SelectionStartColumn?: number; - SelectionStartLine?: number; - SelectionEndColumn?: number; - SelectionEndLine?: number; + CodeAction: number; + WantsTextChanges?: boolean; + SelectionStartColumn?: number; + SelectionStartLine?: number; + SelectionEndColumn?: number; + SelectionEndLine?: number; } export interface FormatResponse { - Buffer: string; + Buffer: string; } export interface TextChange { - NewText: string; - StartLine: number; - StartColumn: number; - EndLine: number; - EndColumn: number; + NewText: string; + StartLine: number; + StartColumn: number; + EndLine: number; + EndColumn: number; } export interface FormatAfterKeystrokeRequest extends Request { - Character: string; + Character: string; } export interface FormatRangeRequest extends Request { - EndLine: number; - EndColumn: number; + EndLine: number; + EndColumn: number; } export interface FormatRangeResponse { - Changes: TextChange[]; + Changes: TextChange[]; } export interface ResourceLocation { - FileName: string; - Line: number; - Column: number; + FileName: string; + Line: number; + Column: number; } export interface Error { - Message: string; - Line: number; - Column: number; - EndLine: number; - EndColumn: number; - FileName: string; + Message: string; + Line: number; + Column: number; + EndLine: number; + EndColumn: number; + FileName: string; } export interface ErrorResponse { - Errors: Error[]; + Errors: Error[]; } export interface QuickFix { - LogLevel: string; - FileName: string; - Line: number; - Column: number; - EndLine: number; - EndColumn: number; - Text: string; - Projects: string[]; + LogLevel: string; + FileName: string; + Line: number; + Column: number; + EndLine: number; + EndColumn: number; + Text: string; + Projects: string[]; } export interface SymbolLocation extends QuickFix { - Kind: string; + Kind: string; } export interface QuickFixResponse { - QuickFixes: QuickFix[]; + QuickFixes: QuickFix[]; } export interface FindSymbolsResponse { - QuickFixes: SymbolLocation[]; + QuickFixes: SymbolLocation[]; } export interface TypeLookupRequest extends Request { - IncludeDocumentation: boolean; + IncludeDocumentation: boolean; } export interface TypeLookupResponse { - Type: string; - Documentation: string; + Type: string; + Documentation: string; } export interface RunCodeActionResponse { - Text: string; - Changes: TextChange[]; + Text: string; + Changes: TextChange[]; } export interface GetCodeActionsResponse { - CodeActions: string[]; + CodeActions: string[]; } export interface Node { - ChildNodes: Node[]; - Location: QuickFix; - Kind: string; + ChildNodes: Node[]; + Location: QuickFix; + Kind: string; + Features: string[]; } export interface CurrentFileMembersAsTreeResponse { - TopLevelTypeDefinitions: Node[]; + TopLevelTypeDefinitions: Node[]; } export interface AutoCompleteRequest extends Request { - WordToComplete: string; - WantDocumentationForEveryCompletionResult?: boolean; - WantImportableTypes?: boolean; - WantMethodHeader?: boolean; - WantSnippet?: boolean; - WantReturnType?: boolean; - WantKind?: boolean; + WordToComplete: string; + WantDocumentationForEveryCompletionResult?: boolean; + WantImportableTypes?: boolean; + WantMethodHeader?: boolean; + WantSnippet?: boolean; + WantReturnType?: boolean; + WantKind?: boolean; } export interface AutoCompleteResponse { - CompletionText: string; - Description: string; - DisplayText: string; - RequiredNamespaceImport: string; - MethodHeader: string; - ReturnType: string; - Snippet: string; - Kind: string; + CompletionText: string; + Description: string; + DisplayText: string; + RequiredNamespaceImport: string; + MethodHeader: string; + ReturnType: string; + Snippet: string; + Kind: string; } export interface ProjectInformationResponse { - MsBuildProject: MSBuildProject; - DnxProject: DnxProject; + MsBuildProject: MSBuildProject; + DnxProject: DnxProject; } export interface WorkspaceInformationResponse { - MsBuild: MsBuildWorkspaceInformation; - Dnx: DnxWorkspaceInformation; + MsBuild: MsBuildWorkspaceInformation; + Dnx: DnxWorkspaceInformation; DotNet: DotNetWorkspaceInformation; - ScriptCs: ScriptCsContext; + ScriptCs: ScriptCsContext; } export interface MsBuildWorkspaceInformation { - SolutionPath: string; - Projects: MSBuildProject[]; + SolutionPath: string; + Projects: MSBuildProject[]; } export interface ScriptCsContext { - CsxFiles: { [n: string]: string }; - References: { [n: string]: string }; - Usings: { [n: string]: string }; - ScriptPacks: { [n: string]: string }; - Path: string; + CsxFiles: { [n: string]: string }; + References: { [n: string]: string }; + Usings: { [n: string]: string }; + ScriptPacks: { [n: string]: string }; + Path: string; } export interface MSBuildProject { - ProjectGuid: string; - Path: string; - AssemblyName: string; - TargetPath: string; - TargetFramework: string; - SourceFiles: string[]; + ProjectGuid: string; + Path: string; + AssemblyName: string; + TargetPath: string; + TargetFramework: string; + SourceFiles: string[]; } export interface DnxWorkspaceInformation { - RuntimePath: string; - DesignTimeHostPort: number; - Projects: DnxProject[]; + RuntimePath: string; + DesignTimeHostPort: number; + Projects: DnxProject[]; } export interface DnxProject { - Path: string; - Name: string; - Commands: { [name: string]: string; }; - Configurations: string[]; - ProjectSearchPaths: string[]; - Frameworks: DnxFramework[]; - GlobalJsonPath: string; - SourceFiles: string[]; + Path: string; + Name: string; + Commands: { [name: string]: string; }; + Configurations: string[]; + ProjectSearchPaths: string[]; + Frameworks: DnxFramework[]; + GlobalJsonPath: string; + SourceFiles: string[]; } export interface DnxFramework { - Name: string; - FriendlyName: string; - ShortName: string; + Name: string; + FriendlyName: string; + ShortName: string; } export interface DotNetWorkspaceInformation { - Projects: DotNetProject[]; - RuntimePath: string; + Projects: DotNetProject[]; + RuntimePath: string; } export interface DotNetProject { - Path: string; - Name: string; - ProjectSearchPaths: string[]; - Configurations: DotNetConfiguration[]; - Frameworks: DotNetFramework[]; - SourceFiles: string[]; + Path: string; + Name: string; + ProjectSearchPaths: string[]; + Configurations: DotNetConfiguration[]; + Frameworks: DotNetFramework[]; + SourceFiles: string[]; } export interface DotNetConfiguration { - Name: string; - CompilationOutputPath: string; - CompilationOutputAssemblyFile: string; - CompilationOutputPdbFile: string; - EmitEntryPoint?: boolean; + Name: string; + CompilationOutputPath: string; + CompilationOutputAssemblyFile: string; + CompilationOutputPdbFile: string; + EmitEntryPoint?: boolean; } export interface DotNetFramework { - Name: string; - FriendlyName: string; - ShortName: string; + Name: string; + FriendlyName: string; + ShortName: string; } export interface RenameRequest extends Request { - RenameTo: string; - WantsTextChanges?: boolean; + RenameTo: string; + WantsTextChanges?: boolean; } export interface ModifiedFileResponse { - FileName: string; - Buffer: string; - Changes: TextChange[]; + FileName: string; + Buffer: string; + Changes: TextChange[]; } export interface RenameResponse { - Changes: ModifiedFileResponse[]; + Changes: ModifiedFileResponse[]; } export interface SignatureHelp { - Signatures: SignatureHelpItem[]; - ActiveSignature: number; - ActiveParameter: number; + Signatures: SignatureHelpItem[]; + ActiveSignature: number; + ActiveParameter: number; } export interface SignatureHelpItem { - Name: string; - Label: string; - Documentation: string; - Parameters: SignatureHelpParameter[]; + Name: string; + Label: string; + Documentation: string; + Parameters: SignatureHelpParameter[]; } export interface SignatureHelpParameter { - Name: string; - Label: string; - Documentation: string; + Name: string; + Label: string; + Documentation: string; } export interface MSBuildProjectDiagnostics { - FileName: string; - Warnings: MSBuildDiagnosticsMessage[]; - Errors: MSBuildDiagnosticsMessage[]; + FileName: string; + Warnings: MSBuildDiagnosticsMessage[]; + Errors: MSBuildDiagnosticsMessage[]; } export interface MSBuildDiagnosticsMessage { - LogLevel: string; - FileName: string; - Text: string; - StartLine: number; - StartColumn: number; - EndLine: number; - EndColumn: number; + LogLevel: string; + FileName: string; + Text: string; + StartLine: number; + StartColumn: number; + EndLine: number; + EndColumn: number; } export interface ErrorMessage { - Text: string; - FileName: string; - Line: number; - Column: number; + Text: string; + FileName: string; + Line: number; + Column: number; } export interface PackageRestoreMessage { - FileName: string; - Succeeded: boolean; + FileName: string; + Succeeded: boolean; } export interface UnresolvedDependenciesMessage { - FileName: string; - UnresolvedDependencies: PackageDependency[]; + FileName: string; + UnresolvedDependencies: PackageDependency[]; } export interface PackageDependency { - Name: string; - Version: string; + Name: string; + Version: string; } export namespace V2 { - + export module Requests { export const GetCodeActions = '/v2/getcodeactions'; export const RunCodeAction = '/v2/runcodeaction'; } - export interface Point { - Line: number; - Column: number; - } - - export interface Range { - Start: Point; - End: Point; - } - - export interface GetCodeActionsRequest extends Request { - Selection: Range; - } - - export interface OmniSharpCodeAction { - Identifier: string; - Name: string; - } - - export interface GetCodeActionsResponse { - CodeActions: OmniSharpCodeAction[]; - } - - export interface RunCodeActionRequest extends Request { - Identifier: string; - Selection: Range; - WantsTextChanges: boolean; - } - - export interface RunCodeActionResponse { - Changes: ModifiedFileResponse[]; - } - - - export interface MSBuildProjectDiagnostics { - FileName: string; - Warnings: MSBuildDiagnosticsMessage[]; - Errors: MSBuildDiagnosticsMessage[]; - } - - export interface MSBuildDiagnosticsMessage { - LogLevel: string; - FileName: string; - Text: string; - StartLine: number; - StartColumn: number; - EndLine: number; - EndColumn: number; - } - - export interface ErrorMessage { - Text: string; - FileName: string; - Line: number; - Column: number; - } - - export interface PackageRestoreMessage { - FileName: string; - Succeeded: boolean; - } - - export interface UnresolvedDependenciesMessage { - FileName: string; - UnresolvedDependencies: PackageDependency[]; - } - - export interface PackageDependency { - Name: string; - Version: string; - } + export interface Point { + Line: number; + Column: number; + } + + export interface Range { + Start: Point; + End: Point; + } + + export interface GetCodeActionsRequest extends Request { + Selection: Range; + } + + export interface OmniSharpCodeAction { + Identifier: string; + Name: string; + } + + export interface GetCodeActionsResponse { + CodeActions: OmniSharpCodeAction[]; + } + + export interface RunCodeActionRequest extends Request { + Identifier: string; + Selection: Range; + WantsTextChanges: boolean; + } + + export interface RunCodeActionResponse { + Changes: ModifiedFileResponse[]; + } + + + export interface MSBuildProjectDiagnostics { + FileName: string; + Warnings: MSBuildDiagnosticsMessage[]; + Errors: MSBuildDiagnosticsMessage[]; + } + + export interface MSBuildDiagnosticsMessage { + LogLevel: string; + FileName: string; + Text: string; + StartLine: number; + StartColumn: number; + EndLine: number; + EndColumn: number; + } + + export interface ErrorMessage { + Text: string; + FileName: string; + Line: number; + Column: number; + } + + export interface PackageRestoreMessage { + FileName: string; + Succeeded: boolean; + } + + export interface UnresolvedDependenciesMessage { + FileName: string; + UnresolvedDependencies: PackageDependency[]; + } + + export interface PackageDependency { + Name: string; + Version: string; + } +} + +// dotnet-test endpoints + +export interface GetTestStartInfoRequest { + FileName: string; + MethodName: string; +} + +export interface GetTestStartInfoResponse { + Executable: string; + Argument: string; +} + +export interface RunDotNetTestRequest { + FileName: string; + MethodName: string; +} + +export interface RunDotNetTestResponse { + Failure: string; + Pass: boolean; } \ No newline at end of file From 7193f187aae2be9dfdc5ca5d2950fdde6e709a01 Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Wed, 25 May 2016 22:13:10 -0700 Subject: [PATCH 02/10] split arguments to prevent long argument issue --- src/features/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/commands.ts b/src/features/commands.ts index d9da712a4..34c20ecd7 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -48,7 +48,7 @@ function debugDotnetTest(testMethod: string, fileName: string, server: Omnisharp "type": "coreclr", "request": "launch", "program": response.Executable, - "args": [response.Argument], + "args": response.Argument.split(' '), "cwd": "${workspaceRoot}", "stopAtEntry": false } From fe2e7a48c8790e1494e1356f030ab247795bccd6 Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Thu, 26 May 2016 00:19:51 -0700 Subject: [PATCH 03/10] test result feedback --- src/features/commands.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/features/commands.ts b/src/features/commands.ts index 34c20ecd7..224e5ae02 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -33,9 +33,13 @@ export default function registerCommands(server: OmnisharpServer, extensionPath: // Run test through dotnet-test command. This function can be moved to a separate structure function runDotnetTest(testMethod: string, fileName: string, server: OmnisharpServer) { - vscode.window.showInformationMessage('run test! ' + testMethod + ' at ' + fileName); serverUtils.runDotNetTest(server, { FileName: fileName, MethodName: testMethod }).then(response => { - vscode.window.showInformationMessage('Test ' + testMethod + response.Pass ? 'Passed' : 'Failed'); + if (response.Pass) { + vscode.window.showInformationMessage('Test passed'); + } + else { + vscode.window.showErrorMessage('Test failed'); + } }); } From 0302243db195c2120965f4dc548faa62c9490845 Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Thu, 26 May 2016 11:21:24 -0700 Subject: [PATCH 04/10] move dotnet test logic to its own file --- src/features/commands.ts | 34 ++--------------------- src/features/dotnetTest.ts | 57 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 31 deletions(-) create mode 100644 src/features/dotnetTest.ts diff --git a/src/features/commands.ts b/src/features/commands.ts index 224e5ae02..881aac806 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -13,6 +13,7 @@ import * as fs from 'fs-extra-promise'; import * as path from 'path'; import * as protocol from '../protocol'; import * as vscode from 'vscode'; +import * as dotnetTest from './dotnetTest' let channel = vscode.window.createOutputChannel('.NET'); @@ -27,37 +28,8 @@ export default function registerCommands(server: OmnisharpServer, extensionPath: let d5 = vscode.commands.registerCommand('csharp.downloadDebugger', () => { }); return vscode.Disposable.from(d1, d2, d3, d4, d5, - vscode.commands.registerCommand('dotnet.test.run', (testMethod, fileName) => runDotnetTest(testMethod, fileName, server)), - vscode.commands.registerCommand('dotnet.test.debug', (testMethod, fileName) => debugDotnetTest(testMethod, fileName, server))); -} - -// Run test through dotnet-test command. This function can be moved to a separate structure -function runDotnetTest(testMethod: string, fileName: string, server: OmnisharpServer) { - serverUtils.runDotNetTest(server, { FileName: fileName, MethodName: testMethod }).then(response => { - if (response.Pass) { - vscode.window.showInformationMessage('Test passed'); - } - else { - vscode.window.showErrorMessage('Test failed'); - } - }); -} - -// Run test through dotnet-test command with debugger attached -function debugDotnetTest(testMethod: string, fileName: string, server: OmnisharpServer) { - serverUtils.getTestStartInfo(server, { FileName: fileName, MethodName: testMethod }).then(response => { - vscode.commands.executeCommand( - 'vscode.startDebug', { - "name": ".NET test launch", - "type": "coreclr", - "request": "launch", - "program": response.Executable, - "args": response.Argument.split(' '), - "cwd": "${workspaceRoot}", - "stopAtEntry": false - } - ); - }); + dotnetTest.registerDotNetTestRunCommand(server), + dotnetTest.registerDotNetTestDebugCommand(server)); } function pickProjectAndStart(server: OmnisharpServer) { diff --git a/src/features/dotnetTest.ts b/src/features/dotnetTest.ts new file mode 100644 index 000000000..26b0e86c7 --- /dev/null +++ b/src/features/dotnetTest.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import {OmnisharpServer} from '../omnisharpServer'; +import * as vscode from 'vscode'; +import * as serverUtils from "../omnisharpUtils"; + +export function registerDotNetTestRunCommand(server: OmnisharpServer): vscode.Disposable { + return vscode.commands.registerCommand( + 'dotnet.test.run', + (testMethod, fileName) => runDotnetTest(testMethod, fileName, server)); +} + +export function registerDotNetTestDebugCommand(server: OmnisharpServer): vscode.Disposable { + return vscode.commands.registerCommand( + 'dotnet.test.debug', + (testMethod, fileName) => debugDotnetTest(testMethod, fileName, server)); +} + +// Run test through dotnet-test command. This function can be moved to a separate structure +export function runDotnetTest(testMethod: string, fileName: string, server: OmnisharpServer) { + serverUtils + .runDotNetTest(server, { FileName: fileName, MethodName: testMethod }) + .then( + response => { + if (response.Pass) { + vscode.window.showInformationMessage('Test passed'); + } + else { + vscode.window.showErrorMessage('Test failed'); + } + }, + reason => { + vscode.window.showErrorMessage('Fail to run test because ' + reason + '.'); + }); +} + +// Run test through dotnet-test command with debugger attached +export function debugDotnetTest(testMethod: string, fileName: string, server: OmnisharpServer) { + serverUtils.getTestStartInfo(server, { FileName: fileName, MethodName: testMethod }).then(response => { + vscode.commands.executeCommand( + 'vscode.startDebug', { + "name": ".NET test launch", + "type": "coreclr", + "request": "launch", + "program": response.Executable, + "args": response.Argument.split(' '), + "cwd": "${workspaceRoot}", + "stopAtEntry": false + } + ).then(undefined, reason => { vscode.window.showErrorMessage('Failed to debug test because ' + reason + '.') }); + }); +} \ No newline at end of file From 1efd3b41edf69b16c232e954b77630e13d2a74cd Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Thu, 26 May 2016 11:42:00 -0700 Subject: [PATCH 05/10] move code from codelens provider to dotnetTest --- src/features/codeLensProvider.ts | 10 ++-------- src/features/dotnetTest.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/features/codeLensProvider.ts b/src/features/codeLensProvider.ts index 24661251e..fe421daed 100644 --- a/src/features/codeLensProvider.ts +++ b/src/features/codeLensProvider.ts @@ -8,6 +8,7 @@ import {CancellationToken, CodeLens, Range, Uri, TextDocument, CodeLensProvider} from 'vscode'; import {toRange, toLocation} from '../typeConvertion'; import AbstractSupport from './abstractProvider'; +import {updateCodeLensForTest} from './dotnetTest'; import * as protocol from '../protocol'; import * as serverUtils from '../omnisharpUtils'; @@ -52,14 +53,7 @@ export default class OmniSharpCodeLensProvider extends AbstractSupport implement OmniSharpCodeLensProvider._convertQuickFix(bucket, fileName, child); } - let testFeature = node.Features.find(value => value.startsWith('XunitTestMethod')); - if (testFeature) { - // this test method has a test feature - let testMethod = testFeature.split(':')[1]; - - bucket.push(new CodeLens(toRange(node.Location), { title: "run test", command: 'dotnet.test.run', arguments: [testMethod, fileName] })); - bucket.push(new CodeLens(toRange(node.Location), { title: "debug test", command: 'dotnet.test.debug', arguments: [testMethod, fileName] })); - } + updateCodeLensForTest(bucket, fileName, node); } resolveCodeLens(codeLens: CodeLens, token: CancellationToken): Thenable { diff --git a/src/features/dotnetTest.ts b/src/features/dotnetTest.ts index 26b0e86c7..3883add22 100644 --- a/src/features/dotnetTest.ts +++ b/src/features/dotnetTest.ts @@ -6,8 +6,10 @@ 'use strict'; import {OmnisharpServer} from '../omnisharpServer'; +import {toRange} from '../typeConvertion'; import * as vscode from 'vscode'; import * as serverUtils from "../omnisharpUtils"; +import * as protocol from '../protocol'; export function registerDotNetTestRunCommand(server: OmnisharpServer): vscode.Disposable { return vscode.commands.registerCommand( @@ -54,4 +56,20 @@ export function debugDotnetTest(testMethod: string, fileName: string, server: Om } ).then(undefined, reason => { vscode.window.showErrorMessage('Failed to debug test because ' + reason + '.') }); }); +} + +export function updateCodeLensForTest(bucket: vscode.CodeLens[], fileName: string, node: protocol.Node) { + let testFeature = node.Features.find(value => value.startsWith('XunitTestMethod')); + if (testFeature) { + // this test method has a test feature + let testMethod = testFeature.split(':')[1]; + + bucket.push(new vscode.CodeLens( + toRange(node.Location), + { title: "run test", command: 'dotnet.test.run', arguments: [testMethod, fileName] })); + + bucket.push(new vscode.CodeLens( + toRange(node.Location), + { title: "debug test", command: 'dotnet.test.debug', arguments: [testMethod, fileName] })); + } } \ No newline at end of file From 60e9de7c9dd6ce4b78567ac806b5cd4d44de19b0 Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Thu, 26 May 2016 12:03:12 -0700 Subject: [PATCH 06/10] disable debug trigger if startDebug command doesn't exist --- src/features/dotnetTest.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/features/dotnetTest.ts b/src/features/dotnetTest.ts index 3883add22..4ccfd64ef 100644 --- a/src/features/dotnetTest.ts +++ b/src/features/dotnetTest.ts @@ -11,6 +11,15 @@ import * as vscode from 'vscode'; import * as serverUtils from "../omnisharpUtils"; import * as protocol from '../protocol'; +let enableDebug = false; + +// check if debugger start is enable +vscode.commands.getCommands().then(commands => { + if (commands.find(c => c == "vscode.startDebug")) { + enableDebug = true; + } +}); + export function registerDotNetTestRunCommand(server: OmnisharpServer): vscode.Disposable { return vscode.commands.registerCommand( 'dotnet.test.run', @@ -54,7 +63,11 @@ export function debugDotnetTest(testMethod: string, fileName: string, server: Om "cwd": "${workspaceRoot}", "stopAtEntry": false } - ).then(undefined, reason => { vscode.window.showErrorMessage('Failed to debug test because ' + reason + '.') }); + ).then( + response => { + vscode.window.showInformationMessage('call back from debugger start command') + }, + reason => { vscode.window.showErrorMessage('Failed to debug test because ' + reason + '.') }); }); } @@ -68,8 +81,10 @@ export function updateCodeLensForTest(bucket: vscode.CodeLens[], fileName: strin toRange(node.Location), { title: "run test", command: 'dotnet.test.run', arguments: [testMethod, fileName] })); - bucket.push(new vscode.CodeLens( - toRange(node.Location), - { title: "debug test", command: 'dotnet.test.debug', arguments: [testMethod, fileName] })); + if (enableDebug) { + bucket.push(new vscode.CodeLens( + toRange(node.Location), + { title: "debug test", command: 'dotnet.test.debug', arguments: [testMethod, fileName] })); + } } } \ No newline at end of file From ca485c8ca080d6cf85356b267a11b1c656ecb8ee Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Fri, 27 May 2016 11:43:37 -0700 Subject: [PATCH 07/10] Update endpoint names for test scenarios --- src/protocol.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocol.ts b/src/protocol.ts index 61ab87117..72eb60858 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -27,8 +27,8 @@ export module Requests { export const TypeLookup = '/typelookup'; export const UpdateBuffer = '/updatebuffer'; - export const GetTestStartInfo = '/getteststartinfo'; - export const RunDotNetTest = '/rundotnettest'; + export const GetTestStartInfo = '/v2/getteststartinfo'; + export const RunDotNetTest = '/v2/runtest'; } export interface Request { From bc3222cbbe9ff640060d29f1f9bbab24f56c0511 Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Fri, 27 May 2016 17:11:12 -0700 Subject: [PATCH 08/10] Move isDebugEnable switch into OmniSharpServer --- src/features/codeLensProvider.ts | 10 ++-- src/features/dotnetTest.ts | 31 +++++------- src/omnisharpServer.ts | 86 ++++++++++++++++++-------------- src/protocol.ts | 7 ++- 4 files changed, 72 insertions(+), 62 deletions(-) diff --git a/src/features/codeLensProvider.ts b/src/features/codeLensProvider.ts index fe421daed..1c24c1e1b 100644 --- a/src/features/codeLensProvider.ts +++ b/src/features/codeLensProvider.ts @@ -32,15 +32,15 @@ export default class OmniSharpCodeLensProvider extends AbstractSupport implement }; provideCodeLenses(document: TextDocument, token: CancellationToken): CodeLens[] | Thenable { - + let request = { Filename: document.fileName }; return serverUtils.currentFileMembersAsTree(this._server, { Filename: document.fileName }, token).then(tree => { let ret: CodeLens[] = []; - tree.TopLevelTypeDefinitions.forEach(node => OmniSharpCodeLensProvider._convertQuickFix(ret, document.fileName, node)); + tree.TopLevelTypeDefinitions.forEach(node => this._convertQuickFix(ret, document.fileName, node)); return ret; }); } - private static _convertQuickFix(bucket: CodeLens[], fileName: string, node: protocol.Node): void { + private _convertQuickFix(bucket: CodeLens[], fileName: string, node: protocol.Node): void { if (node.Kind === 'MethodDeclaration' && OmniSharpCodeLensProvider.filteredSymbolNames[node.Location.Text]) { return; @@ -50,10 +50,10 @@ export default class OmniSharpCodeLensProvider extends AbstractSupport implement bucket.push(lens); for (let child of node.ChildNodes) { - OmniSharpCodeLensProvider._convertQuickFix(bucket, fileName, child); + this._convertQuickFix(bucket, fileName, child); } - updateCodeLensForTest(bucket, fileName, node); + updateCodeLensForTest(bucket, fileName, node, this._server.isDebugEnable()); } resolveCodeLens(codeLens: CodeLens, token: CancellationToken): Thenable { diff --git a/src/features/dotnetTest.ts b/src/features/dotnetTest.ts index 4ccfd64ef..3060c0080 100644 --- a/src/features/dotnetTest.ts +++ b/src/features/dotnetTest.ts @@ -11,15 +11,6 @@ import * as vscode from 'vscode'; import * as serverUtils from "../omnisharpUtils"; import * as protocol from '../protocol'; -let enableDebug = false; - -// check if debugger start is enable -vscode.commands.getCommands().then(commands => { - if (commands.find(c => c == "vscode.startDebug")) { - enableDebug = true; - } -}); - export function registerDotNetTestRunCommand(server: OmnisharpServer): vscode.Disposable { return vscode.commands.registerCommand( 'dotnet.test.run', @@ -64,27 +55,29 @@ export function debugDotnetTest(testMethod: string, fileName: string, server: Om "stopAtEntry": false } ).then( - response => { - vscode.window.showInformationMessage('call back from debugger start command') - }, - reason => { vscode.window.showErrorMessage('Failed to debug test because ' + reason + '.') }); + response => { }, + reason => { vscode.window.showErrorMessage('Failed to start debugger on test because ' + reason + '.') }); }); } -export function updateCodeLensForTest(bucket: vscode.CodeLens[], fileName: string, node: protocol.Node) { - let testFeature = node.Features.find(value => value.startsWith('XunitTestMethod')); +export function updateCodeLensForTest(bucket: vscode.CodeLens[], fileName: string, node: protocol.Node, isDebugEnable: boolean) { + // backward compatible check: Features property doesn't present on older version OmniSharp + if (node.Features == undefined) { + return; + } + + let testFeature = node.Features.find(value => value.Name == 'XunitTestMethod'); if (testFeature) { // this test method has a test feature - let testMethod = testFeature.split(':')[1]; bucket.push(new vscode.CodeLens( toRange(node.Location), - { title: "run test", command: 'dotnet.test.run', arguments: [testMethod, fileName] })); + { title: "run test", command: 'dotnet.test.run', arguments: [testFeature.Data, fileName] })); - if (enableDebug) { + if (isDebugEnable) { bucket.push(new vscode.CodeLens( toRange(node.Location), - { title: "debug test", command: 'dotnet.test.debug', arguments: [testMethod, fileName] })); + { title: "debug test", command: 'dotnet.test.debug', arguments: [testFeature.Data, fileName] })); } } } \ No newline at end of file diff --git a/src/omnisharpServer.ts b/src/omnisharpServer.ts index 96d6c946e..33bf07224 100644 --- a/src/omnisharpServer.ts +++ b/src/omnisharpServer.ts @@ -14,6 +14,7 @@ import {Disposable, CancellationToken, OutputChannel, workspace, window} from 'v import {ErrorMessage, UnresolvedDependenciesMessage, MSBuildProjectDiagnostics, ProjectInformationResponse} from './protocol'; import getLaunchTargets, {LaunchTarget} from './launchTargetFinder'; import TelemetryReporter from 'vscode-extension-telemetry'; +import * as vscode from 'vscode' enum ServerState { Starting, @@ -31,29 +32,29 @@ interface Request { module Events { export const StateChanged = 'stateChanged'; - + export const StdOut = 'stdout'; export const StdErr = 'stderr'; - + export const Error = 'Error'; export const ServerError = 'ServerError'; - + export const UnresolvedDependencies = 'UnresolvedDependencies'; export const PackageRestoreStarted = 'PackageRestoreStarted'; export const PackageRestoreFinished = 'PackageRestoreFinished'; - + export const ProjectChanged = 'ProjectChanged'; export const ProjectAdded = 'ProjectAdded'; export const ProjectRemoved = 'ProjectRemoved'; - + export const MsBuildProjectDiagnostics = 'MsBuildProjectDiagnostics'; - + export const BeforeServerStart = 'BeforeServerStart'; export const ServerStart = 'ServerStart'; export const ServerStop = 'ServerStop'; - + export const MultipleLaunchTargets = 'server:MultipleLaunchTargets'; - + export const Started = 'started'; } @@ -65,7 +66,7 @@ class Delays { idleDelays: number = 0; // 501-1500 milliseconds nonFocusDelays: number = 0; // 1501-3000 milliseconds bigDelays: number = 0; // 3000+ milliseconds - + public report(elapsedTime: number) { if (elapsedTime <= 25) { this.immediateDelays += 1; @@ -89,8 +90,8 @@ class Delays { this.bigDelays += 1; } } - - public toMeasures(): {[key: string]: number} { + + public toMeasures(): { [key: string]: number } { return { immedateDelays: this.immediateDelays, nearImmediateDelays: this.nearImmediateDelays, @@ -115,6 +116,8 @@ export abstract class OmnisharpServer { private _isProcessingQueue = false; private _channel: OutputChannel; + private _isDebugEnable: boolean = false; + protected _serverProcess: ChildProcess; protected _extraArgv: string[]; @@ -132,28 +135,28 @@ export abstract class OmnisharpServer { return this._state; } - private _setState(value: ServerState) : void { + private _setState(value: ServerState): void { if (typeof value !== 'undefined' && value !== this._state) { this._state = value; this._fireEvent(Events.StateChanged, this._state); } } - + private _recordRequestDelay(requestName: string, elapsedTime: number) { let delays = this._requestDelays[requestName]; if (!delays) { delays = new Delays(); this._requestDelays[requestName] = delays; } - + delays.report(elapsedTime); } - + public reportAndClearTelemetry() { for (var path in this._requestDelays) { const eventName = 'omnisharp' + path; const measures = this._requestDelays[path].toMeasures(); - + this._reporter.sendTelemetryEvent(eventName, null, measures); } @@ -168,6 +171,10 @@ export abstract class OmnisharpServer { return this._channel; } + public isDebugEnable(): boolean { + return this._isDebugEnable; + } + // --- eventing public onStdout(listener: (e: string) => any, thisArg?: any) { @@ -186,7 +193,7 @@ export abstract class OmnisharpServer { return this._addListener(Events.ServerError, listener, thisArg); } - public onUnresolvedDependencies(listener: (e: UnresolvedDependenciesMessage) => any, thisArg?:any) { + public onUnresolvedDependencies(listener: (e: UnresolvedDependenciesMessage) => any, thisArg?: any) { return this._addListener(Events.UnresolvedDependencies, listener, thisArg); } @@ -214,7 +221,7 @@ export abstract class OmnisharpServer { return this._addListener(Events.MsBuildProjectDiagnostics, listener, thisArg); } - public onBeforeServerStart(listener: (e:string) => any) { + public onBeforeServerStart(listener: (e: string) => any) { return this._addListener(Events.BeforeServerStart, listener); } @@ -271,6 +278,13 @@ export abstract class OmnisharpServer { this._setState(ServerState.Started); this._fireEvent(Events.ServerStart, solutionPath); return this._doConnect(); + }).then(_ => { + return vscode.commands.getCommands() + .then(commands => { + if (commands.find(c => c == "vscode.startDebug")) { + this._isDebugEnable = true; + } + }); }).then(_ => { this._processQueue(); }, err => { @@ -300,7 +314,7 @@ export abstract class OmnisharpServer { return reject(err); } }); - + killer.on('exit', resolve); killer.on('error', reject); }); @@ -309,7 +323,7 @@ export abstract class OmnisharpServer { this._serverProcess.kill('SIGTERM'); ret = Promise.resolve(undefined); } - + return ret.then(_ => { this._start = null; this._serverProcess = null; @@ -327,7 +341,7 @@ export abstract class OmnisharpServer { } } - public autoStart(preferredPath:string): Thenable { + public autoStart(preferredPath: string): Thenable { return getLaunchTargets().then(targets => { if (targets.length === 0) { return new Promise((resolve, reject) => { @@ -368,13 +382,13 @@ export abstract class OmnisharpServer { if (this._getState() !== ServerState.Started) { return Promise.reject('server has been stopped or not started'); } - + let startTime: number; let request: Request; - + let promise = new Promise((resolve, reject) => { startTime = Date.now(); - + request = { path, data, @@ -382,9 +396,9 @@ export abstract class OmnisharpServer { onError: err => reject(err), _enqueued: Date.now() }; - + this._queue.push(request); - + if (this._getState() === ServerState.Started && !this._isProcessingQueue) { this._processQueue(); } @@ -406,7 +420,7 @@ export abstract class OmnisharpServer { let endTime = Date.now(); let elapsedTime = endTime - startTime; this._recordRequestDelay(path, elapsedTime); - + return response; }); } @@ -504,19 +518,17 @@ export class StdioOmnisharpServer extends OmnisharpServer { // timeout logic const handle = setTimeout(() => { - if (listener) - { + if (listener) { listener.dispose(); } - + reject(new Error('Failed to start OmniSharp')); }, StdioOmnisharpServer.StartupTimeout); // handle started-event listener = this.onOmnisharpStart(() => { - if (listener) - { - listener.dispose(); + if (listener) { + listener.dispose(); } clearTimeout(handle); resolve(this); @@ -552,17 +564,17 @@ export class StdioOmnisharpServer extends OmnisharpServer { switch (packet.Type) { case 'response': - this._handleResponsePacket( packet); + this._handleResponsePacket(packet); break; case 'event': - this._handleEventPacket( packet); + this._handleEventPacket(packet); break; default: console.warn('unknown packet: ', packet); break; } }; - + this._rl.addListener('line', onLineReceived); this._callOnStop.push(() => this._rl.removeListener('line', onLineReceived)); } @@ -590,7 +602,7 @@ export class StdioOmnisharpServer extends OmnisharpServer { if (packet.Event === 'log') { // handle log events - const entry = <{ LogLevel: string; Name: string; Message: string; }> packet.Body; + const entry = <{ LogLevel: string; Name: string; Message: string; }>packet.Body; this._fireEvent(Events.StdOut, `[${entry.LogLevel}:${entry.Name}] ${entry.Message}\n`); return; } else { diff --git a/src/protocol.ts b/src/protocol.ts index 72eb60858..beb54ceb5 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -176,11 +176,16 @@ export interface GetCodeActionsResponse { CodeActions: string[]; } +export interface SyntaxFeature { + Name: string; + Data: string; +} + export interface Node { ChildNodes: Node[]; Location: QuickFix; Kind: string; - Features: string[]; + Features: SyntaxFeature[]; } export interface CurrentFileMembersAsTreeResponse { From 5c9ee8ee460e730ac8eebd156a9fb5a164fc9239 Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Fri, 27 May 2016 18:49:06 -0700 Subject: [PATCH 09/10] Add test output channel --- src/features/dotnetTest.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/features/dotnetTest.ts b/src/features/dotnetTest.ts index 3060c0080..abfd37c2d 100644 --- a/src/features/dotnetTest.ts +++ b/src/features/dotnetTest.ts @@ -11,6 +11,16 @@ import * as vscode from 'vscode'; import * as serverUtils from "../omnisharpUtils"; import * as protocol from '../protocol'; +let _testOutputChannel: vscode.OutputChannel = undefined; + +function getTestOutputChannel(): vscode.OutputChannel { + if (_testOutputChannel == undefined) { + _testOutputChannel = vscode.window.createOutputChannel(".NET Test Log"); + } + + return _testOutputChannel; +} + export function registerDotNetTestRunCommand(server: OmnisharpServer): vscode.Disposable { return vscode.commands.registerCommand( 'dotnet.test.run', @@ -25,15 +35,17 @@ export function registerDotNetTestDebugCommand(server: OmnisharpServer): vscode. // Run test through dotnet-test command. This function can be moved to a separate structure export function runDotnetTest(testMethod: string, fileName: string, server: OmnisharpServer) { + getTestOutputChannel().show(); + getTestOutputChannel().appendLine('Running test ' + testMethod + '...'); serverUtils .runDotNetTest(server, { FileName: fileName, MethodName: testMethod }) .then( response => { if (response.Pass) { - vscode.window.showInformationMessage('Test passed'); + getTestOutputChannel().appendLine('Test passed \n'); } else { - vscode.window.showErrorMessage('Test failed'); + getTestOutputChannel().appendLine('Test failed \n'); } }, reason => { From 62b9fe2ba4c5c8c516d67899581b11df28d08b00 Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Tue, 31 May 2016 09:58:32 -0700 Subject: [PATCH 10/10] Address review comments --- src/features/commands.ts | 8 +++++--- src/features/dotnetTest.ts | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/features/commands.ts b/src/features/commands.ts index 881aac806..7036e9e49 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -27,9 +27,11 @@ export default function registerCommands(server: OmnisharpServer, extensionPath: // running the command activates the extension, which is all we need for installation to kickoff let d5 = vscode.commands.registerCommand('csharp.downloadDebugger', () => { }); - return vscode.Disposable.from(d1, d2, d3, d4, d5, - dotnetTest.registerDotNetTestRunCommand(server), - dotnetTest.registerDotNetTestDebugCommand(server)); + // register two commands for running and debugging xunit tests + let d6 = dotnetTest.registerDotNetTestRunCommand(server); + let d7 = dotnetTest.registerDotNetTestDebugCommand(server); + + return vscode.Disposable.from(d1, d2, d3, d4, d5, d6, d7); } function pickProjectAndStart(server: OmnisharpServer) { diff --git a/src/features/dotnetTest.ts b/src/features/dotnetTest.ts index abfd37c2d..65e2c7407 100644 --- a/src/features/dotnetTest.ts +++ b/src/features/dotnetTest.ts @@ -49,7 +49,7 @@ export function runDotnetTest(testMethod: string, fileName: string, server: Omni } }, reason => { - vscode.window.showErrorMessage('Fail to run test because ' + reason + '.'); + vscode.window.showErrorMessage(`Failed to run test because ${reason}.`); }); } @@ -68,7 +68,7 @@ export function debugDotnetTest(testMethod: string, fileName: string, server: Om } ).then( response => { }, - reason => { vscode.window.showErrorMessage('Failed to start debugger on test because ' + reason + '.') }); + reason => { vscode.window.showErrorMessage(`Failed to start debugger on test because ${reason}.`) }); }); }