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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/client/common/application/documentManager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// tslint:disable:no-any
// tslint:disable:no-any unified-signatures

import { injectable } from 'inversify';
import { Event, TextDocument, TextDocumentShowOptions, TextEditor, TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent, TextEditorViewColumnChangeEvent, Uri, ViewColumn, window, workspace } from 'vscode';
import { Event, TextDocument, TextDocumentShowOptions, TextEditor, TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent, TextEditorViewColumnChangeEvent, Uri, ViewColumn, window, workspace, WorkspaceEdit } from 'vscode';
import { IDocumentManager } from './types';

@injectable()
Expand All @@ -18,7 +18,7 @@ export class DocumentManager implements IDocumentManager {
public get visibleTextEditors(): TextEditor[] {
return window.visibleTextEditors;
}
public get onDidChangeActiveTextEditor(): Event<TextEditor> {
public get onDidChangeActiveTextEditor(): Event<TextEditor | undefined> {
return window.onDidChangeActiveTextEditor;
}
public get onDidChangeVisibleTextEditors(): Event<TextEditor[]> {
Expand Down Expand Up @@ -47,4 +47,14 @@ export class DocumentManager implements IDocumentManager {
public showTextDocument(uri: any, options?: any, preserveFocus?: any): Thenable<TextEditor> {
return window.showTextDocument(uri, options, preserveFocus);
}

public openTextDocument(uri: Uri): Thenable<TextDocument>;
public openTextDocument(fileName: string): Thenable<TextDocument>;
public openTextDocument(options?: { language?: string; content?: string }): Thenable<TextDocument>;
public openTextDocument(arg?: any): Thenable<TextDocument> {
return workspace.openTextDocument(arg);
}
public applyEdit(edit: WorkspaceEdit): Thenable<boolean> {
return workspace.applyEdit(edit);
}
}
55 changes: 52 additions & 3 deletions src/client/common/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
CancellationToken, ConfigurationChangeEvent, DebugConfiguration, DebugSession, Disposable, Event, FileSystemWatcher, GlobPattern, InputBoxOptions, MessageItem,
MessageOptions, OpenDialogOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions,
StatusBarAlignment, StatusBarItem, Terminal, TerminalOptions, TextDocument, TextDocumentShowOptions, TextEditor,
TextEditorEdit, TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent, TextEditorViewColumnChangeEvent, Uri, ViewColumn, WorkspaceConfiguration, WorkspaceFolder,
WorkspaceFolderPickOptions, WorkspaceFoldersChangeEvent
TextEditorEdit, TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent, TextEditorViewColumnChangeEvent, Uri, ViewColumn, WorkspaceConfiguration, WorkspaceEdit,
WorkspaceFolder, WorkspaceFolderPickOptions, WorkspaceFoldersChangeEvent
} from 'vscode';

export const IApplicationShell = Symbol('IApplicationShell');
Expand Down Expand Up @@ -336,7 +336,7 @@ export interface IDocumentManager {
* has changed. *Note* that the event also fires when the active editor changes
* to `undefined`.
*/
readonly onDidChangeActiveTextEditor: Event<TextEditor>;
readonly onDidChangeActiveTextEditor: Event<TextEditor | undefined>;

/**
* An [event](#Event) which fires when the array of [visible editors](#window.visibleTextEditors)
Expand Down Expand Up @@ -405,6 +405,55 @@ export interface IDocumentManager {
* @return A promise that resolves to an [editor](#TextEditor).
*/
showTextDocument(uri: Uri, options?: TextDocumentShowOptions): Thenable<TextEditor>;
/**
* Opens a document. Will return early if this document is already open. Otherwise
* the document is loaded and the [didOpen](#workspace.onDidOpenTextDocument)-event fires.
*
* The document is denoted by an [uri](#Uri). Depending on the [scheme](#Uri.scheme) the
* following rules apply:
* * `file`-scheme: Open a file on disk, will be rejected if the file does not exist or cannot be loaded.
* * `untitled`-scheme: A new file that should be saved on disk, e.g. `untitled:c:\frodo\new.js`. The language
* will be derived from the file name.
* * For all other schemes the registered text document content [providers](#TextDocumentContentProvider) are consulted.
*
* *Note* that the lifecycle of the returned document is owned by the editor and not by the extension. That means an
* [`onDidClose`](#workspace.onDidCloseTextDocument)-event can occur at any time after opening it.
*
* @param uri Identifies the resource to open.
* @return A promise that resolves to a [document](#TextDocument).
*/
openTextDocument(uri: Uri): Thenable<TextDocument>;

/**
* A short-hand for `openTextDocument(Uri.file(fileName))`.
*
* @see [openTextDocument](#openTextDocument)
* @param fileName A name of a file on disk.
* @return A promise that resolves to a [document](#TextDocument).
*/
openTextDocument(fileName: string): Thenable<TextDocument>;

/**
* Opens an untitled text document. The editor will prompt the user for a file
* path when the document is to be saved. The `options` parameter allows to
* specify the *language* and/or the *content* of the document.
*
* @param options Options to control how the document will be created.
* @return A promise that resolves to a [document](#TextDocument).
*/
openTextDocument(options?: { language?: string; content?: string }): Thenable<TextDocument>;
/**
* Make changes to one or many resources as defined by the given
* [workspace edit](#WorkspaceEdit).
*
* When applying a workspace edit, the editor implements an 'all-or-nothing'-strategy,
* that means failure to load one document or make changes to one document will cause
* the edit to be rejected.
*
* @param edit A workspace edit.
* @return A thenable that resolves when the edit could be applied.
*/
applyEdit(edit: WorkspaceEdit): Thenable<boolean>;
}

export const IWorkspaceService = Symbol('IWorkspaceService');
Expand Down
68 changes: 58 additions & 10 deletions src/client/common/editor.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as dmp from 'diff-match-patch';
import * as fs from 'fs';
import * as fs from 'fs-extra';
import { injectable } from 'inversify';
import * as md5 from 'md5';
import { EOL } from 'os';
import * as path from 'path';
import { Position, Range, TextDocument, TextEdit, WorkspaceEdit } from 'vscode';
import * as vscode from 'vscode';
import { Position, Range, TextDocument, TextEdit, Uri, WorkspaceEdit } from 'vscode';
import { IEditorUtils } from './types';

// Code borrowed from goFormat.ts (Go Extension for VS Code)
enum EditAction {
Expand All @@ -16,17 +17,17 @@ enum EditAction {
const NEW_LINE_LENGTH = EOL.length;

class Patch {
public diffs: dmp.Diff[];
public start1: number;
public start2: number;
public length1: number;
public length2: number;
public diffs!: dmp.Diff[];
public start1!: number;
public start2!: number;
public length1!: number;
public length2!: number;
}

class Edit {
public action: EditAction;
public start: Position;
public end: Position;
public end!: Position;
public text: string;

constructor(action: number, start: Position) {
Expand Down Expand Up @@ -120,7 +121,7 @@ export function getWorkspaceEditsFromPatch(filePatches: string[], workspaceRoot?
}

const fileSource = fs.readFileSync(fileName).toString('utf8');
const fileUri = vscode.Uri.file(fileName);
const fileUri = Uri.file(fileName);

// Add line feeds and build the text edits
patches.forEach(p => {
Expand Down Expand Up @@ -322,3 +323,50 @@ function patch_fromText(textline): Patch[] {
}
return patches;
}

@injectable()
export class EditorUtils implements IEditorUtils {
public getWorkspaceEditsFromPatch(originalContents: string, patch: string, uri: Uri): WorkspaceEdit {
const workspaceEdit = new WorkspaceEdit();
if (patch.startsWith('---')) {
// Strip the first two lines
patch = patch.substring(patch.indexOf('@@'));
}
if (patch.length === 0) {
return workspaceEdit;
}
// Remove the text added by unified_diff
// # Work around missing newline (http://bugs.python.org/issue2142).
patch = patch.replace(/\\ No newline at end of file[\r\n]/, '');

const d = new dmp.diff_match_patch();
const patches = patch_fromText.call(d, patch);
if (!Array.isArray(patches) || patches.length === 0) {
throw new Error('Unable to parse Patch string');
}

// Add line feeds and build the text edits
patches.forEach(p => {
p.diffs.forEach(diff => {
diff[1] += EOL;
});
getTextEditsInternal(originalContents, p.diffs, p.start1).forEach(edit => {
switch (edit.action) {
case EditAction.Delete:
workspaceEdit.delete(uri, new Range(edit.start, edit.end));
break;
case EditAction.Insert:
workspaceEdit.insert(uri, edit.start, edit.text);
break;
case EditAction.Replace:
workspaceEdit.replace(uri, new Range(edit.start, edit.end), edit.text);
break;
default:
break;
}
});
});

return workspaceEdit;
}
}
8 changes: 4 additions & 4 deletions src/client/common/platform/fileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export class FileSystem implements IFileSystem {
return fs.readFile(filePath).then(buffer => buffer.toString());
}

public async writeFile(filePath: string, data: {}): Promise<void> {
await fs.writeFile(filePath, data, { encoding: 'utf8' });
}

public directoryExists(filePath: string): Promise<boolean> {
return this.objectExists(filePath, (stats) => stats.isDirectory());
}
Expand Down Expand Up @@ -168,8 +172,4 @@ export class FileSystem implements IFileSystem {
});
});
}

public writeFile(filePath: string, data: {}): Promise<void> {
return fs.writeFile(filePath, data);
}
}
8 changes: 5 additions & 3 deletions src/client/common/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from './application/types';
import { WorkspaceService } from './application/workspace';
import { ConfigurationService } from './configuration/service';
import { EditorUtils } from './editor';
import { FeatureDeprecationManager } from './featureDeprecationManager';
import { ProductInstaller } from './installer/productInstaller';
import { Logger } from './logger';
Expand All @@ -34,9 +35,9 @@ import {
} from './terminal/types';
import {
IBrowserService, IConfigurationService, ICurrentProcess,
IFeatureDeprecationManager, IInstaller, ILogger,
IPathUtils, IPersistentStateFactory,
IRandom, Is64Bit, IsWindows
IEditorUtils, IFeatureDeprecationManager, IInstaller,
ILogger, IPathUtils,
IPersistentStateFactory, IRandom, Is64Bit, IsWindows
} from './types';
import { Random } from './utils';

Expand All @@ -60,6 +61,7 @@ export function registerTypes(serviceManager: IServiceManager) {
serviceManager.addSingleton<IDebugService>(IDebugService, DebugService);
serviceManager.addSingleton<IApplicationEnvironment>(IApplicationEnvironment, ApplicationEnvironment);
serviceManager.addSingleton<IBrowserService>(IBrowserService, BrowserService);
serviceManager.addSingleton<IEditorUtils>(IEditorUtils, EditorUtils);

serviceManager.addSingleton<ITerminalHelper>(ITerminalHelper, TerminalHelper);
serviceManager.addSingleton<ITerminalActivationCommandProvider>(
Expand Down
10 changes: 8 additions & 2 deletions src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Licensed under the MIT License.

import { Socket } from 'net';
import { ConfigurationTarget, DiagnosticSeverity, Disposable, ExtensionContext, OutputChannel, Uri } from 'vscode';
import { ConfigurationTarget, DiagnosticSeverity, Disposable, ExtensionContext, OutputChannel, Uri, WorkspaceEdit } from 'vscode';

import { EnvironmentVariables } from './variables/types';
export const IOutputChannel = Symbol('IOutputChannel');
Expand All @@ -12,7 +12,7 @@ export const IDocumentSymbolProvider = Symbol('IDocumentSymbolProvider');
export const IsWindows = Symbol('IS_WINDOWS');
export const Is64Bit = Symbol('Is64Bit');
export const IDisposableRegistry = Symbol('IDiposableRegistry');
export type IDisposableRegistry = Disposable[];
export type IDisposableRegistry = { push(disposable: Disposable): void };
export const IMemento = Symbol('IGlobalMemento');
export const GLOBAL_MEMENTO = Symbol('IGlobalMemento');
export const WORKSPACE_MEMENTO = Symbol('IWorkspaceMemento');
Expand Down Expand Up @@ -303,3 +303,9 @@ export interface IFeatureDeprecationManager extends Disposable {
initialize(): void;
registerDeprecation(deprecatedInfo: DeprecatedFeatureInfo): void;
}

export const IEditorUtils = Symbol('IEditorUtils');
export interface IEditorUtils {
// getTextEditor(uri: Uri): Promise<{ editor: TextEditor; dispose?(): void }>;
getWorkspaceEditsFromPatch(originalContents: string, patch: string, uri: Uri): WorkspaceEdit;
}
7 changes: 5 additions & 2 deletions src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ import { PythonFormattingEditProvider } from './providers/formatProvider';
import { LinterProvider } from './providers/linterProvider';
import { PythonRenameProvider } from './providers/renameProvider';
import { ReplProvider } from './providers/replProvider';
import { registerTypes as providersRegisterTypes } from './providers/serviceRegistry';
import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider';
import { TerminalProvider } from './providers/terminalProvider';
import { ISortImportsEditingProvider } from './providers/types';
import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider';
import * as sortImports from './sortImports';
import { sendTelemetryEvent } from './telemetry';
import { EDITOR_LOAD } from './telemetry/constants';
import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry';
Expand Down Expand Up @@ -92,7 +93,8 @@ export async function activate(context: ExtensionContext) {
const activationService = serviceContainer.get<IExtensionActivationService>(IExtensionActivationService);
await activationService.activate();

sortImports.activate(context, standardOutputChannel, serviceManager);
const sortImports = serviceContainer.get<ISortImportsEditingProvider>(ISortImportsEditingProvider);
sortImports.registerCommands();

serviceManager.get<ICodeExecutionManager>(ICodeExecutionManager).registerCommands();
sendStartupTelemetry(activated, serviceContainer).ignoreErrors();
Expand Down Expand Up @@ -190,6 +192,7 @@ function registerServices(context: ExtensionContext, serviceManager: ServiceMana
debugConfigurationRegisterTypes(serviceManager);
debuggerRegisterTypes(serviceManager);
appRegisterTypes(serviceManager);
providersRegisterTypes(serviceManager);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect to find all types of providers being registered here (formatProvider, signatureProvider... etc). Is that the intention here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DI was added later. So we do not have others registered in the container.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the intention then, good to know.

}

async function sendStartupTelemetry(activatedPromise: Promise<void>, serviceContainer: IServiceContainer) {
Expand Down
Loading