Merge branch 'concurrent-server' into future

This commit is contained in:
Dustin Campbell 2016-11-10 01:45:21 -08:00
Родитель f183afe95c 07d572d5e8
Коммит b4155ec6d8
15 изменённых файлов: 461 добавлений и 273 удалений

Просмотреть файл

@ -9,7 +9,7 @@ import * as fs from 'fs-extra-promise';
import * as path from 'path';
import * as vscode from 'vscode';
import * as tasks from 'vscode-tasks';
import {OmnisharpServer} from './omnisharp/server';
import {OmniSharpServer} from './omnisharp/server';
import * as serverUtils from './omnisharp/utils';
import * as protocol from './omnisharp/protocol'
@ -393,7 +393,7 @@ export enum AddAssetResult {
Cancelled
}
export function addAssetsIfNecessary(server: OmnisharpServer): Promise<AddAssetResult> {
export function addAssetsIfNecessary(server: OmniSharpServer): Promise<AddAssetResult> {
return new Promise<AddAssetResult>((resolve, reject) => {
if (!vscode.workspace.rootPath) {
return resolve(AddAssetResult.NotApplicable);
@ -492,7 +492,7 @@ function shouldGenerateAssets(paths: Paths) {
});
}
export function generateAssets(server: OmnisharpServer) {
export function generateAssets(server: OmniSharpServer) {
serverUtils.requestWorkspaceInformation(server).then(info => {
if (info.DotNet && info.DotNet.Projects.length > 0) {
getOperations().then(operations => {

Просмотреть файл

@ -5,15 +5,15 @@
'use strict';
import {OmnisharpServer} from '../omnisharp/server';
import {OmniSharpServer} from '../omnisharp/server';
import {Disposable} from 'vscode';
export default class AbstractProvider {
protected _server: OmnisharpServer;
protected _server: OmniSharpServer;
protected _disposables: Disposable[];
constructor(server: OmnisharpServer) {
constructor(server: OmniSharpServer) {
this._server = server;
this._disposables = [];
}

Просмотреть файл

@ -6,10 +6,10 @@
'use strict';
import {Disposable, Uri, workspace} from 'vscode';
import {OmnisharpServer} from '../omnisharp/server';
import {OmniSharpServer} from '../omnisharp/server';
import * as serverUtils from '../omnisharp/utils';
function forwardDocumentChanges(server: OmnisharpServer): Disposable {
function forwardDocumentChanges(server: OmniSharpServer): Disposable {
return workspace.onDidChangeTextDocument(event => {
@ -29,7 +29,7 @@ function forwardDocumentChanges(server: OmnisharpServer): Disposable {
});
}
function forwardFileChanges(server: OmnisharpServer): Disposable {
function forwardFileChanges(server: OmniSharpServer): Disposable {
function onFileSystemEvent(uri: Uri): void {
if (!server.isRunning()) {
@ -52,7 +52,7 @@ function forwardFileChanges(server: OmnisharpServer): Disposable {
return Disposable.from(watcher, d1, d2, d3);
}
export default function forwardChanges(server: OmnisharpServer): Disposable {
export default function forwardChanges(server: OmniSharpServer): Disposable {
// combine file watching and text document watching
return Disposable.from(

Просмотреть файл

@ -6,7 +6,7 @@
'use strict';
import {CodeActionProvider, CodeActionContext, Command, CancellationToken, TextDocument, WorkspaceEdit, TextEdit, Range, Uri, workspace, commands} from 'vscode';
import {OmnisharpServer} from '../omnisharp/server';
import {OmniSharpServer} from '../omnisharp/server';
import AbstractProvider from './abstractProvider';
import * as protocol from '../omnisharp/protocol';
import {toRange2} from '../omnisharp/typeConvertion';
@ -17,7 +17,7 @@ export default class OmnisharpCodeActionProvider extends AbstractProvider implem
private _disabled: boolean;
private _commandId: string;
constructor(server: OmnisharpServer) {
constructor(server: OmniSharpServer) {
super(server);
this._commandId = 'omnisharp.runCodeAction';

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
import {OmnisharpServer} from '../omnisharp/server';
import {OmniSharpServer} from '../omnisharp/server';
import * as serverUtils from '../omnisharp/utils';
import {findLaunchTargets} from '../omnisharp/launcher';
import * as cp from 'child_process';
@ -19,7 +19,7 @@ import {generateAssets} from '../assets';
let channel = vscode.window.createOutputChannel('.NET');
export default function registerCommands(server: OmnisharpServer, extensionPath: string) {
export default function registerCommands(server: OmniSharpServer, extensionPath: string) {
let d1 = vscode.commands.registerCommand('o.restart', () => restartOmniSharp(server));
let d2 = vscode.commands.registerCommand('o.pickProjectAndStart', () => pickProjectAndStart(server));
let d3 = vscode.commands.registerCommand('o.showOutput', () => server.getChannel().show(vscode.ViewColumn.Three));
@ -44,7 +44,7 @@ export default function registerCommands(server: OmnisharpServer, extensionPath:
return vscode.Disposable.from(d1, d2, d3, d4, d5, d6, d7, d8, d9);
}
function restartOmniSharp(server: OmnisharpServer) {
function restartOmniSharp(server: OmniSharpServer) {
if (server.isRunning()) {
server.restart();
}
@ -53,7 +53,7 @@ function restartOmniSharp(server: OmnisharpServer) {
}
}
function pickProjectAndStart(server: OmnisharpServer) {
function pickProjectAndStart(server: OmniSharpServer) {
return findLaunchTargets().then(targets => {
@ -103,7 +103,7 @@ function projectsToCommands(projects: protocol.DotNetProject[]): Promise<Command
});
}
export function dotnetRestoreAllProjects(server: OmnisharpServer) {
export function dotnetRestoreAllProjects(server: OmniSharpServer) {
if (!server.isRunning()) {
return Promise.reject('OmniSharp server is not running.');
@ -127,7 +127,7 @@ export function dotnetRestoreAllProjects(server: OmnisharpServer) {
});
}
export function dotnetRestoreForProject(server: OmnisharpServer, fileName: string) {
export function dotnetRestoreForProject(server: OmniSharpServer, fileName: string) {
if (!server.isRunning()) {
return Promise.reject('OmniSharp server is not running.');

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
import {OmnisharpServer} from '../omnisharp/server';
import {OmniSharpServer} from '../omnisharp/server';
import AbstractSupport from './abstractProvider';
import * as protocol from '../omnisharp/protocol';
import * as serverUtils from '../omnisharp/utils';
@ -15,11 +15,11 @@ import {Disposable, Uri, CancellationTokenSource, TextDocument, Diagnostic, Diag
export class Advisor {
private _disposable: Disposable;
private _server: OmnisharpServer;
private _server: OmniSharpServer;
private _packageRestoreCounter: number = 0;
private _projectSourceFileCounts: { [path: string]: number } = Object.create(null);
constructor(server: OmnisharpServer) {
constructor(server: OmniSharpServer) {
this._server = server;
let d1 = server.onProjectChange(this._onProjectChange, this);
@ -112,7 +112,7 @@ export class Advisor {
}
}
export default function reportDiagnostics(server: OmnisharpServer, advisor: Advisor): Disposable {
export default function reportDiagnostics(server: OmniSharpServer, advisor: Advisor): Disposable {
return new DiagnosticsProvider(server, advisor);
}
@ -124,7 +124,7 @@ class DiagnosticsProvider extends AbstractSupport {
private _projectValidation: CancellationTokenSource;
private _diagnostics: DiagnosticCollection;
constructor(server: OmnisharpServer, validationAdvisor: Advisor) {
constructor(server: OmniSharpServer, validationAdvisor: Advisor) {
super(server);
this._validationAdvisor = validationAdvisor;
this._diagnostics = languages.createDiagnosticCollection('csharp');

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
import {OmnisharpServer} from '../omnisharp/server';
import {OmniSharpServer} from '../omnisharp/server';
import {toRange} from '../omnisharp/typeConvertion';
import * as vscode from 'vscode';
import * as serverUtils from "../omnisharp/utils";
@ -21,20 +21,20 @@ function getTestOutputChannel(): vscode.OutputChannel {
return _testOutputChannel;
}
export function registerDotNetTestRunCommand(server: OmnisharpServer): vscode.Disposable {
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 {
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) {
export function runDotnetTest(testMethod: string, fileName: string, server: OmniSharpServer) {
getTestOutputChannel().show();
getTestOutputChannel().appendLine('Running test ' + testMethod + '...');
serverUtils
@ -54,7 +54,7 @@ export function runDotnetTest(testMethod: string, fileName: string, server: Omni
}
// Run test through dotnet-test command with debugger attached
export function debugDotnetTest(testMethod: string, fileName: string, server: OmnisharpServer) {
export function debugDotnetTest(testMethod: string, fileName: string, server: OmniSharpServer) {
serverUtils.getTestStartInfo(server, { FileName: fileName, MethodName: testMethod }).then(response => {
vscode.commands.executeCommand(
'vscode.startDebug', {

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
import * as vscode from 'vscode';
import {OmnisharpServer} from '../omnisharp/server';
import {OmniSharpServer} from '../omnisharp/server';
import {dotnetRestoreForProject} from './commands';
import {basename} from 'path';
import * as protocol from '../omnisharp/protocol';
@ -13,7 +13,7 @@ import * as serverUtils from '../omnisharp/utils';
const debounce = require('lodash.debounce');
export default function reportStatus(server: OmnisharpServer) {
export default function reportStatus(server: OmniSharpServer) {
return vscode.Disposable.from(
reportServerStatus(server),
forwardOutput(server),
@ -41,7 +41,7 @@ class Status {
}
}
export function reportDocumentStatus(server: OmnisharpServer): vscode.Disposable {
export function reportDocumentStatus(server: OmniSharpServer): vscode.Disposable {
let disposables: vscode.Disposable[] = [];
let localDisposables: vscode.Disposable[];
@ -202,7 +202,7 @@ export function reportDocumentStatus(server: OmnisharpServer): vscode.Disposable
// ---- server status
export function reportServerStatus(server: OmnisharpServer): vscode.Disposable{
export function reportServerStatus(server: OmniSharpServer): vscode.Disposable{
function appendLine(value: string = '') {
server.getChannel().appendLine(value);
@ -275,20 +275,14 @@ function showMessageSoon() {
// --- mirror output in channel
function forwardOutput(server: OmnisharpServer) {
function forwardOutput(server: OmniSharpServer) {
const logChannel = server.getChannel();
const timing200Pattern = /^\[INFORMATION:OmniSharp.Middleware.LoggingMiddleware\] \/\w+: 200 \d+ms/;
function forward(message: string) {
// strip stuff like: /codecheck: 200 339ms
if(!timing200Pattern.test(message)) {
logChannel.append(message);
}
logChannel.append(message);
}
return vscode.Disposable.from(
server.onStdout(forward),
server.onStderr(forward));
}
}

Просмотреть файл

@ -23,7 +23,7 @@ import SignatureHelpProvider from '../features/signatureHelpProvider';
import registerCommands from '../features/commands';
import forwardChanges from '../features/changeForwarding';
import reportStatus from '../features/status';
import {StdioOmnisharpServer} from './server';
import {OmniSharpServer} from './server';
import {Options} from './options';
import {addAssetsIfNecessary, AddAssetResult} from '../assets';
@ -33,7 +33,7 @@ export function activate(context: vscode.ExtensionContext, reporter: TelemetryRe
scheme: 'file' // only files from disk
};
const server = new StdioOmnisharpServer(reporter);
const server = new OmniSharpServer(reporter);
const advisor = new Advisor(server); // create before server is started
const disposables: vscode.Disposable[] = [];
const localDisposables: vscode.Disposable[] = [];

Просмотреть файл

@ -144,7 +144,7 @@ export function launchOmniSharp(cwd: string, args: string[]): Promise<LaunchResu
.then(result => {
// async error - when target not not ENEOT
result.process.on('error', err => {
reject(err)
reject(err);
});
// success after a short freeing event loop
@ -228,9 +228,9 @@ function launchNixMono(launchPath: string, cwd: string, args: string[]): Promise
return canLaunchMono()
.then(() => {
let argsCopy = args.slice(0); // create copy of details args
args.unshift(launchPath);
argsCopy.unshift(launchPath);
let process = spawn('mono', args, {
let process = spawn('mono', argsCopy, {
detached: false,
cwd: cwd
});

Просмотреть файл

@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as protocol from './protocol';
const priorityCommands = [
protocol.Requests.ChangeBuffer,
protocol.Requests.FormatAfterKeystroke,
protocol.Requests.FormatRange,
protocol.Requests.UpdateBuffer
];
const normalCommands = [
protocol.Requests.AutoComplete,
protocol.Requests.FilesChanged,
protocol.Requests.FindSymbols,
protocol.Requests.FindUsages,
protocol.Requests.GetCodeActions,
protocol.Requests.GoToDefinition,
protocol.Requests.RunCodeAction,
protocol.Requests.SignatureHelp,
protocol.Requests.TypeLookup
];
const prioritySet = new Set<string>(priorityCommands);
const normalSet = new Set<string>(normalCommands);
const deferredSet = new Set<string>();
const nonDeferredSet = new Set<string>();
for (let command of priorityCommands) {
nonDeferredSet.add(command);
}
for (let command of normalCommands) {
nonDeferredSet.add(command);
}
export function isPriorityCommand(command: string) {
return prioritySet.has(command);
}
export function isNormalCommand(command: string) {
return normalSet.has(command);
}
export function isDeferredCommand(command: string) {
if (deferredSet.has(command)) {
return true;
}
if (nonDeferredSet.has(command)) {
return false;
}
deferredSet.add(command);
return true;
}

Просмотреть файл

@ -29,6 +29,32 @@ export module Requests {
export const Metadata = '/metadata';
}
export namespace WireProtocol {
export interface Packet {
Type: string;
Seq: number;
}
export interface RequestPacket extends Packet {
Command: string;
Arguments: any;
}
export interface ResponsePacket extends Packet {
Command: string;
Request_seq: number;
Running: boolean;
Success: boolean;
Message: string;
Body: any;
}
export interface EventPacket extends Packet {
Event: string;
Body: any;
}
}
export interface Request {
Filename: string;
Line?: number;

Просмотреть файл

@ -0,0 +1,186 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Logger } from '../logger';
import * as prioritization from './prioritization';
export interface Request {
command: string;
data?: any;
onSuccess(value: any): void;
onError(err: any): void;
startTime?: number;
endTime?: number;
}
/**
* This data structure manages a queue of requests that have been made and requests that have been
* sent to the OmniSharp server and are waiting on a response.
*/
class RequestQueue {
private _pending: Request[] = [];
private _waiting: Map<number, Request> = new Map<number, Request>();
public constructor(
private _name: string,
private _maxSize: number,
private _logger: Logger,
private _makeRequest: (request: Request) => number) {
}
/**
* Enqueue a new request.
*/
public enqueue(request: Request) {
this._logger.appendLine(`Enqueue ${this._name} request for ${request.command}.`);
this._pending.push(request);
}
/**
* Dequeue a request that has completed.
*/
public dequeue(id: number) {
const request = this._waiting.get(id);
if (request) {
this._waiting.delete(id);
this._logger.appendLine(`Dequeue ${this._name} request for ${request.command} (${id}).`);
}
return request;
}
public cancelRequest(request: Request) {
let index = this._pending.indexOf(request);
if (index !== -1) {
this._pending.splice(index, 1);
// Note: This calls reject() on the promise returned by OmniSharpServer.makeRequest
request.onError(new Error(`Pending request cancelled: ${request.command}`));
}
// TODO: Handle cancellation of a request already waiting on the OmniSharp server.
}
/**
* Returns true if there are any requests pending to be sent to the OmniSharp server.
*/
public hasPending() {
return this._pending.length > 0;
}
/**
* Returns true if the maximum number of requests waiting on the OmniSharp server has been reached.
*/
public isFull() {
return this._waiting.size >= this._maxSize;
}
/**
* Process any pending requests and send them to the OmniSharp server.
*/
public processPending() {
if (this._pending.length === 0) {
return;
}
this._logger.appendLine(`Processing ${this._name} queue`);
this._logger.increaseIndent();
const slots = this._maxSize - this._waiting.size;
for (let i = 0; i < slots && this._pending.length > 0; i++) {
const item = this._pending.shift();
item.startTime = Date.now();
const id = this._makeRequest(item);
this._waiting.set(id, item);
if (this.isFull()) {
break;
}
}
this._logger.decreaseIndent();
}
}
export class RequestQueueCollection {
private _isProcessing: boolean;
private _priorityQueue: RequestQueue;
private _normalQueue: RequestQueue;
private _deferredQueue: RequestQueue;
public constructor(
logger: Logger,
concurrency: number,
makeRequest: (request: Request) => number
) {
this._priorityQueue = new RequestQueue('Priority', 1, logger, makeRequest);
this._normalQueue = new RequestQueue('Normal', concurrency, logger, makeRequest);
this._deferredQueue = new RequestQueue('Deferred', Math.max(Math.floor(concurrency / 4), 2), logger, makeRequest);
}
private getQueue(command: string) {
if (prioritization.isPriorityCommand(command)) {
return this._priorityQueue;
}
else if (prioritization.isNormalCommand(command)) {
return this._normalQueue;
}
else {
return this._deferredQueue;
}
}
public enqueue(request: Request) {
const queue = this.getQueue(request.command);
queue.enqueue(request);
this.drain();
}
public dequeue(command: string, seq: number) {
const queue = this.getQueue(command);
return queue.dequeue(seq);
}
public cancelRequest(request: Request) {
const queue = this.getQueue(request.command);
queue.cancelRequest(request);
}
public drain() {
if (this._isProcessing) {
return false;
}
if (this._priorityQueue.isFull()) {
return false;
}
if (this._normalQueue.isFull() && this._deferredQueue.isFull()) {
return false;
}
this._isProcessing = true;
if (this._priorityQueue.hasPending()) {
this._priorityQueue.processPending();
this._isProcessing = false;
return;
}
if (this._normalQueue.hasPending()) {
this._normalQueue.processPending();
}
if (this._deferredQueue.hasPending()) {
this._deferredQueue.processPending();
}
this._isProcessing = false;
}
}

Просмотреть файл

@ -3,20 +3,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {EventEmitter} from 'events';
import {ChildProcess, exec} from 'child_process';
import {dirname} from 'path';
import {ReadLine, createInterface} from 'readline';
import {launchOmniSharp} from './launcher';
import * as protocol from './protocol';
import {Options} from './options';
import {Logger} from '../logger';
import {DelayTracker} from './delayTracker';
import {LaunchTarget, findLaunchTargets} from './launcher';
import {PlatformInformation} from '../platform';
import { EventEmitter } from 'events';
import { ChildProcess, exec } from 'child_process';
import { ReadLine, createInterface } from 'readline';
import { launchOmniSharp } from './launcher';
import { Options } from './options';
import { Logger } from '../logger';
import { DelayTracker } from './delayTracker';
import { LaunchTarget, findLaunchTargets } from './launcher';
import { Request, RequestQueueCollection } from './requestQueue';
import TelemetryReporter from 'vscode-extension-telemetry';
import * as path from 'path';
import * as protocol from './protocol';
import * as vscode from 'vscode';
enum ServerState {
@ -25,14 +23,6 @@ enum ServerState {
Stopped
}
interface Request {
path: string;
data?: any;
onSuccess(value: any): void;
onError(err: any): void;
_enqueued: number;
}
module Events {
export const StateChanged = 'stateChanged';
@ -64,7 +54,14 @@ module Events {
const TelemetryReportingDelay = 2 * 60 * 1000; // two minutes
export abstract class OmnisharpServer {
export class OmniSharpServer {
private static _nextId = 1;
private _debugMode: boolean = false;
private _readLine: ReadLine;
private _disposables: vscode.Disposable[] = [];
private _reporter: TelemetryReporter;
private _delayTrackers: { [requestName: string]: DelayTracker };
@ -73,23 +70,26 @@ export abstract class OmnisharpServer {
private _eventBus = new EventEmitter();
private _state: ServerState = ServerState.Stopped;
private _launchTarget: LaunchTarget;
private _queue: Request[] = [];
private _isProcessingQueue = false;
private _requestQueue: RequestQueueCollection;
private _channel: vscode.OutputChannel;
protected _logger: Logger;
private _logger: Logger;
private _isDebugEnable: boolean = false;
protected _serverProcess: ChildProcess;
protected _extraArgs: string[];
protected _options: Options;
private _serverProcess: ChildProcess;
private _options: Options;
constructor(reporter: TelemetryReporter) {
this._extraArgs = [];
this._reporter = reporter;
this._channel = vscode.window.createOutputChannel('OmniSharp Log');
this._logger = new Logger(message => this._channel.append(message));
const logger = this._debugMode
? this._logger
: new Logger(message => { });
this._requestQueue = new RequestQueueCollection(logger, 8, request => this._makeRequest(request));
}
public isRunning(): boolean {
@ -120,9 +120,9 @@ export abstract class OmnisharpServer {
private _reportTelemetry() {
const delayTrackers = this._delayTrackers;
for (const path in delayTrackers) {
const tracker = delayTrackers[path];
const eventName = 'omnisharp' + path;
for (const requestName in delayTrackers) {
const tracker = delayTrackers[requestName];
const eventName = 'omnisharp' + requestName;
if (tracker.hasMeasures()) {
const measures = tracker.getMeasures();
tracker.clearMeasures();
@ -233,10 +233,11 @@ export abstract class OmnisharpServer {
this._launchTarget = launchTarget;
const solutionPath = launchTarget.target;
const cwd = dirname(solutionPath);
const cwd = path.dirname(solutionPath);
let args = [
'-s', solutionPath,
'--hostPID', process.pid.toString(),
'--stdio',
'DotNet:enablePackageRestore=false',
'--encoding', 'utf-8'
];
@ -247,8 +248,6 @@ export abstract class OmnisharpServer {
args.push('-v');
}
args = args.concat(this._extraArgs);
this._logger.appendLine(`Starting OmniSharp server at ${new Date().toLocaleString()}`);
this._logger.increaseIndent();
this._logger.appendLine(`Target: ${solutionPath}`);
@ -288,17 +287,19 @@ export abstract class OmnisharpServer {
// Start telemetry reporting
this._telemetryIntervalId = setInterval(() => this._reportTelemetry(), TelemetryReportingDelay);
}).then(() => {
this._processQueue();
this._requestQueue.drain();
}).catch(err => {
this._fireEvent(Events.ServerError, err);
return this.stop();
});
}
protected abstract _doConnect(): Promise<void>;
public stop(): Promise<void> {
while (this._disposables.length) {
this._disposables.pop().dispose();
}
let cleanupPromise: Promise<void>;
if (this._telemetryIntervalId !== undefined) {
@ -393,12 +394,14 @@ export abstract class OmnisharpServer {
// --- requests et al
public makeRequest<TResponse>(path: string, data?: any, token?: vscode.CancellationToken): Promise<TResponse> {
public makeRequest<TResponse>(command: string, data?: any, token?: vscode.CancellationToken): Promise<TResponse> {
if (this._getState() !== ServerState.Started) {
return Promise.reject<TResponse>('server has been stopped or not started');
}
console.log(`makeRequest: command=${command}`);
let startTime: number;
let request: Request;
@ -406,136 +409,47 @@ export abstract class OmnisharpServer {
startTime = Date.now();
request = {
path,
command,
data,
onSuccess: value => resolve(value),
onError: err => reject(err),
_enqueued: Date.now()
onError: err => reject(err)
};
this._queue.push(request);
if (this._getState() === ServerState.Started && !this._isProcessingQueue) {
this._processQueue();
}
this._requestQueue.enqueue(request);
});
if (token) {
token.onCancellationRequested(() => {
let idx = this._queue.indexOf(request);
if (idx !== -1) {
this._queue.splice(idx, 1);
let err = new Error('Canceled');
err.message = 'Canceled';
request.onError(err);
}
this._requestQueue.cancelRequest(request);
});
}
return promise.then(response => {
let endTime = Date.now();
let elapsedTime = endTime - startTime;
this._recordRequestDelay(path, elapsedTime);
this._recordRequestDelay(command, elapsedTime);
return response;
});
}
private _processQueue(): void {
if (this._queue.length === 0) {
// nothing to do
this._isProcessingQueue = false;
return;
}
// signal that we are working on it
this._isProcessingQueue = true;
// send next request and recurse when done
const thisRequest = this._queue.shift();
this._makeNextRequest(thisRequest.path, thisRequest.data).then(value => {
thisRequest.onSuccess(value);
this._processQueue();
}, err => {
thisRequest.onError(err);
this._processQueue();
}).catch(err => {
console.error(err);
this._processQueue();
});
}
protected abstract _makeNextRequest(path: string, data: any): Promise<any>;
}
namespace WireProtocol {
export interface Packet {
Type: string;
Seq: number;
}
export interface RequestPacket extends Packet {
Command: string;
Arguments: any;
}
export interface ResponsePacket extends Packet {
Command: string;
Request_seq: number;
Running: boolean;
Success: boolean;
Message: string;
Body: any;
}
export interface EventPacket extends Packet {
Event: string;
Body: any;
}
}
export class StdioOmnisharpServer extends OmnisharpServer {
private static _seqPool = 1;
private _rl: ReadLine;
private _activeRequest: { [seq: number]: { onSuccess: Function; onError: Function; } } = Object.create(null);
private _callOnStop: Function[] = [];
constructor(reporter: TelemetryReporter) {
super(reporter);
// extra argv
this._extraArgs.push('--stdio');
}
public stop(): Promise<void> {
while (this._callOnStop.length) {
this._callOnStop.pop()();
}
return super.stop();
}
protected _doConnect(): Promise<void> {
private _doConnect(): Promise<void> {
this._serverProcess.stderr.on('data', (data: any) => {
this._fireEvent('stderr', String(data));
});
this._rl = createInterface({
this._readLine = createInterface({
input: this._serverProcess.stdout,
output: this._serverProcess.stdin,
terminal: false
});
const p = new Promise<void>((resolve, reject) => {
const promise = new Promise<void>((resolve, reject) => {
let listener: vscode.Disposable;
// Convert the timeout from the seconds to milliseconds, which is required by setTimeout().
const timeoutDuration = this._options.projectLoadTimeout * 1000
const timeoutDuration = this._options.projectLoadTimeout * 1000;
// timeout logic
const handle = setTimeout(() => {
@ -551,104 +465,112 @@ export class StdioOmnisharpServer extends OmnisharpServer {
if (listener) {
listener.dispose();
}
clearTimeout(handle);
resolve();
});
});
this._startListening();
const lineReceived = this._onLineReceived.bind(this);
return p;
this._readLine.addListener('line', lineReceived);
this._disposables.push(new vscode.Disposable(() => {
this._readLine.removeListener('line', lineReceived);
}));
return promise;
}
private _startListening(): void {
const onLineReceived = (line: string) => {
if (line[0] !== '{') {
this._logger.appendLine(line);
return;
}
let packet: WireProtocol.Packet;
try {
packet = JSON.parse(line);
}
catch (e) {
// not json
return;
}
if (!packet.Type) {
// bogous packet
return;
}
switch (packet.Type) {
case 'response':
this._handleResponsePacket(<WireProtocol.ResponsePacket>packet);
break;
case 'event':
this._handleEventPacket(<WireProtocol.EventPacket>packet);
break;
default:
console.warn('unknown packet: ', packet);
break;
}
};
this._rl.addListener('line', onLineReceived);
this._callOnStop.push(() => this._rl.removeListener('line', onLineReceived));
}
private _handleResponsePacket(packet: WireProtocol.ResponsePacket): void {
const requestSeq = packet.Request_seq,
entry = this._activeRequest[requestSeq];
if (!entry) {
console.warn('Received a response WITHOUT a request', packet);
private _onLineReceived(line: string) {
if (line[0] !== '{') {
this._logger.appendLine(line);
return;
}
delete this._activeRequest[requestSeq];
let packet: protocol.WireProtocol.Packet;
try {
packet = JSON.parse(line);
}
catch (err) {
// This isn't JSON
return;
}
if (!packet.Type) {
// Bogus packet
return;
}
switch (packet.Type) {
case 'response':
this._handleResponsePacket(<protocol.WireProtocol.ResponsePacket>packet);
break;
case 'event':
this._handleEventPacket(<protocol.WireProtocol.EventPacket>packet);
break;
default:
console.warn(`Unknown packet type: ${packet.Type}`);
break;
}
}
private _handleResponsePacket(packet: protocol.WireProtocol.ResponsePacket) {
const request = this._requestQueue.dequeue(packet.Command, packet.Request_seq);
if (!request) {
this._logger.appendLine(`Received response for ${packet.Command} but could not find request.`);
return;
}
if (packet.Success) {
entry.onSuccess(packet.Body);
} else {
entry.onError(packet.Message || packet.Body);
request.onSuccess(packet.Body);
}
else {
request.onError(packet.Message || packet.Body);
}
this._requestQueue.drain();
}
private _handleEventPacket(packet: WireProtocol.EventPacket): void {
private _handleEventPacket(packet: protocol.WireProtocol.EventPacket): void {
if (packet.Event === 'log') {
// handle log events
const entry = <{ LogLevel: string; Name: string; Message: string; }>packet.Body;
this._logger.appendLine(`[${entry.LogLevel}:${entry.Name}] ${entry.Message}`);
return;
} else {
this._logOutput(entry.LogLevel, entry.Name, entry.Message);
}
else {
// fwd all other events
this._fireEvent(packet.Event, packet.Body);
}
}
protected _makeNextRequest(path: string, data: any): Promise<any> {
private _makeRequest(request: Request) {
const id = OmniSharpServer._nextId++;
const thisRequestPacket: WireProtocol.RequestPacket = {
const requestPacket: protocol.WireProtocol.RequestPacket = {
Type: 'request',
Seq: StdioOmnisharpServer._seqPool++,
Command: path,
Arguments: data
Seq: id,
Command: request.command,
Arguments: request.data
};
return new Promise<any>((resolve, reject) => {
if (this._debugMode) {
this._logger.appendLine(`Making request: ${request.command} (${id})`);
}
this._activeRequest[thisRequestPacket.Seq] = {
onSuccess: value => resolve(value),
onError: err => reject(err)
};
this._serverProcess.stdin.write(JSON.stringify(requestPacket) + '\n');
this._serverProcess.stdin.write(JSON.stringify(thisRequestPacket) + '\n');
});
return id;
}
private _logOutput(logLevel: string, name: string, message: string) {
const timing200Pattern = /^\[INFORMATION:OmniSharp.Middleware.LoggingMiddleware\] \/[\/\w]+: 200 \d+ms/;
const output = `[${logLevel}:${name}] ${message}`;
// strip stuff like: /codecheck: 200 339ms
if (this._debugMode || !timing200Pattern.test(output)) {
this._logger.appendLine(output);
}
}
}

Просмотреть файл

@ -5,82 +5,82 @@
'use strict';
import {OmnisharpServer} from './server';
import {OmniSharpServer} from './server';
import * as protocol from './protocol';
import * as vscode from 'vscode';
export function autoComplete(server: OmnisharpServer, request: protocol.AutoCompleteRequest) {
export function autoComplete(server: OmniSharpServer, request: protocol.AutoCompleteRequest) {
return server.makeRequest<protocol.AutoCompleteResponse[]>(protocol.Requests.AutoComplete, request);
}
export function codeCheck(server: OmnisharpServer, request: protocol.Request, token: vscode.CancellationToken) {
export function codeCheck(server: OmniSharpServer, request: protocol.Request, token: vscode.CancellationToken) {
return server.makeRequest<protocol.QuickFixResponse>(protocol.Requests.CodeCheck, request, token);
}
export function currentFileMembersAsTree(server: OmnisharpServer, request: protocol.Request, token: vscode.CancellationToken) {
export function currentFileMembersAsTree(server: OmniSharpServer, request: protocol.Request, token: vscode.CancellationToken) {
return server.makeRequest<protocol.CurrentFileMembersAsTreeResponse>(protocol.Requests.CurrentFileMembersAsTree, request, token);
}
export function filesChanged(server: OmnisharpServer, requests: protocol.Request[]) {
export function filesChanged(server: OmniSharpServer, requests: protocol.Request[]) {
return server.makeRequest<void>(protocol.Requests.FilesChanged, requests);
}
export function findSymbols(server: OmnisharpServer, request: protocol.FindSymbolsRequest, token: vscode.CancellationToken) {
export function findSymbols(server: OmniSharpServer, request: protocol.FindSymbolsRequest, token: vscode.CancellationToken) {
return server.makeRequest<protocol.FindSymbolsResponse>(protocol.Requests.FindSymbols, request, token);
}
export function findUsages(server: OmnisharpServer, request: protocol.FindUsagesRequest, token: vscode.CancellationToken) {
export function findUsages(server: OmniSharpServer, request: protocol.FindUsagesRequest, token: vscode.CancellationToken) {
return server.makeRequest<protocol.QuickFixResponse>(protocol.Requests.FindUsages, request, token);
}
export function formatAfterKeystroke(server: OmnisharpServer, request: protocol.FormatAfterKeystrokeRequest, token: vscode.CancellationToken) {
export function formatAfterKeystroke(server: OmniSharpServer, request: protocol.FormatAfterKeystrokeRequest, token: vscode.CancellationToken) {
return server.makeRequest<protocol.FormatRangeResponse>(protocol.Requests.FormatAfterKeystroke, request, token);
}
export function formatRange(server: OmnisharpServer, request: protocol.FormatRangeRequest, token: vscode.CancellationToken) {
export function formatRange(server: OmniSharpServer, request: protocol.FormatRangeRequest, token: vscode.CancellationToken) {
return server.makeRequest<protocol.FormatRangeResponse>(protocol.Requests.FormatRange, request, token);
}
export function getCodeActions(server: OmnisharpServer, request: protocol.V2.GetCodeActionsRequest, token: vscode.CancellationToken) {
export function getCodeActions(server: OmniSharpServer, request: protocol.V2.GetCodeActionsRequest, token: vscode.CancellationToken) {
return server.makeRequest<protocol.V2.GetCodeActionsResponse>(protocol.V2.Requests.GetCodeActions, request, token);
}
export function goToDefinition(server: OmnisharpServer, request: protocol.GoToDefinitionRequest, token: vscode.CancellationToken) {
export function goToDefinition(server: OmniSharpServer, request: protocol.GoToDefinitionRequest, token: vscode.CancellationToken) {
return server.makeRequest<protocol.GoToDefinitionResponse>(protocol.Requests.GoToDefinition, request);
}
export function rename(server: OmnisharpServer, request: protocol.RenameRequest, token: vscode.CancellationToken) {
export function rename(server: OmniSharpServer, request: protocol.RenameRequest, token: vscode.CancellationToken) {
return server.makeRequest<protocol.RenameResponse>(protocol.Requests.Rename, request, token);
}
export function requestWorkspaceInformation(server: OmnisharpServer) {
export function requestWorkspaceInformation(server: OmniSharpServer) {
return server.makeRequest<protocol.WorkspaceInformationResponse>(protocol.Requests.Projects);
}
export function runCodeAction(server: OmnisharpServer, request: protocol.V2.RunCodeActionRequest) {
export function runCodeAction(server: OmniSharpServer, request: protocol.V2.RunCodeActionRequest) {
return server.makeRequest<protocol.V2.RunCodeActionResponse>(protocol.V2.Requests.RunCodeAction, request);
}
export function signatureHelp(server: OmnisharpServer, request: protocol.Request, token: vscode.CancellationToken) {
export function signatureHelp(server: OmniSharpServer, request: protocol.Request, token: vscode.CancellationToken) {
return server.makeRequest<protocol.SignatureHelp>(protocol.Requests.SignatureHelp, request, token);
}
export function typeLookup(server: OmnisharpServer, request: protocol.TypeLookupRequest, token: vscode.CancellationToken) {
export function typeLookup(server: OmniSharpServer, request: protocol.TypeLookupRequest, token: vscode.CancellationToken) {
return server.makeRequest<protocol.TypeLookupResponse>(protocol.Requests.TypeLookup, request, token);
}
export function updateBuffer(server: OmnisharpServer, request: protocol.UpdateBufferRequest) {
export function updateBuffer(server: OmniSharpServer, request: protocol.UpdateBufferRequest) {
return server.makeRequest<boolean>(protocol.Requests.UpdateBuffer, request);
}
export function getMetadata(server: OmnisharpServer, request: protocol.MetadataRequest) {
export function getMetadata(server: OmniSharpServer, request: protocol.MetadataRequest) {
return server.makeRequest<protocol.MetadataResponse>(protocol.Requests.Metadata, request);
}
export function getTestStartInfo(server: OmnisharpServer, request: protocol.V2.GetTestStartInfoRequest) {
export function getTestStartInfo(server: OmniSharpServer, request: protocol.V2.GetTestStartInfoRequest) {
return server.makeRequest<protocol.V2.GetTestStartInfoResponse>(protocol.V2.Requests.GetTestStartInfo, request);
}
export function runDotNetTest(server: OmnisharpServer, request: protocol.V2.RunDotNetTestRequest) {
export function runDotNetTest(server: OmniSharpServer, request: protocol.V2.RunDotNetTestRequest) {
return server.makeRequest<protocol.V2.RunDotNetTestResponse>(protocol.V2.Requests.RunDotNetTest, request);
}