forked from microsoft/vscode-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbaseFormatter.ts
More file actions
120 lines (109 loc) · 5.84 KB
/
baseFormatter.ts
File metadata and controls
120 lines (109 loc) · 5.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import * as fs from 'fs-extra';
import * as path from 'path';
import * as vscode from 'vscode';
import { IWorkspaceService } from '../common/application/types';
import { STANDARD_OUTPUT_CHANNEL } from '../common/constants';
import '../common/extensions';
import { isNotInstalledError } from '../common/helpers';
import { IPythonToolExecutionService } from '../common/process/types';
import { IInstaller, IOutputChannel, Product } from '../common/types';
import { IServiceContainer } from '../ioc/types';
import { getTempFileWithDocumentContents, getTextEditsFromPatch } from './../common/editor';
import { IFormatterHelper } from './types';
export abstract class BaseFormatter {
protected readonly outputChannel: vscode.OutputChannel;
protected readonly workspace: IWorkspaceService;
private readonly helper: IFormatterHelper;
constructor(public Id: string, private product: Product, protected serviceContainer: IServiceContainer) {
this.outputChannel = serviceContainer.get<vscode.OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
this.helper = serviceContainer.get<IFormatterHelper>(IFormatterHelper);
this.workspace = serviceContainer.get<IWorkspaceService>(IWorkspaceService);
}
public abstract formatDocument(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, range?: vscode.Range): Thenable<vscode.TextEdit[]>;
protected getDocumentPath(document: vscode.TextDocument, fallbackPath: string) {
if (path.basename(document.uri.fsPath) === document.uri.fsPath) {
return fallbackPath;
}
return path.dirname(document.fileName);
}
protected getWorkspaceUri(document: vscode.TextDocument) {
const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri);
if (workspaceFolder) {
return workspaceFolder.uri;
}
const folders = this.workspace.workspaceFolders;
if (Array.isArray(folders) && folders.length > 0) {
return folders[0].uri;
}
return vscode.Uri.file(__dirname);
}
protected async provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, args: string[], cwd?: string): Promise<vscode.TextEdit[]> {
this.outputChannel.clear();
if (typeof cwd !== 'string' || cwd.length === 0) {
cwd = this.getWorkspaceUri(document).fsPath;
}
// autopep8 and yapf have the ability to read from the process input stream and return the formatted code out of the output stream.
// However they don't support returning the diff of the formatted text when reading data from the input stream.
// Yet getting text formatted that way avoids having to create a temporary file, however the diffing will have
// to be done here in node (extension), i.e. extension CPU, i.e. less responsive solution.
const tempFile = await this.createTempFile(document);
if (this.checkCancellation(document.fileName, tempFile, token)) {
return [];
}
const executionInfo = this.helper.getExecutionInfo(this.product, args, document.uri);
executionInfo.args.push(tempFile);
const pythonToolsExecutionService = this.serviceContainer.get<IPythonToolExecutionService>(IPythonToolExecutionService);
const promise = pythonToolsExecutionService.exec(executionInfo, { cwd, throwOnStdErr: true, token }, document.uri)
.then(output => output.stdout)
.then(data => {
if (this.checkCancellation(document.fileName, tempFile, token)) {
return [] as vscode.TextEdit[];
}
return getTextEditsFromPatch(document.getText(), data);
})
.catch(error => {
if (this.checkCancellation(document.fileName, tempFile, token)) {
return [] as vscode.TextEdit[];
}
// tslint:disable-next-line:no-empty
this.handleError(this.Id, error, document.uri).catch(() => { });
return [] as vscode.TextEdit[];
})
.then(edits => {
this.deleteTempFile(document.fileName, tempFile).ignoreErrors();
return edits;
});
vscode.window.setStatusBarMessage(`Formatting with ${this.Id}`, promise);
return promise;
}
protected async handleError(expectedFileName: string, error: Error, resource?: vscode.Uri) {
let customError = `Formatting with ${this.Id} failed.`;
if (isNotInstalledError(error)) {
const installer = this.serviceContainer.get<IInstaller>(IInstaller);
const isInstalled = await installer.isInstalled(this.product, resource);
if (!isInstalled) {
customError += `\nYou could either install the '${this.Id}' formatter, turn it off or use another formatter.`;
installer.promptToInstall(this.product, resource).catch(ex => console.error('Python Extension: promptToInstall', ex));
}
}
this.outputChannel.appendLine(`\n${customError}\n${error}`);
}
private async createTempFile(document: vscode.TextDocument): Promise<string> {
return document.isDirty
? getTempFileWithDocumentContents(document)
: document.fileName;
}
private deleteTempFile(originalFile: string, tempFile: string): Promise<void> {
if (originalFile !== tempFile) {
return fs.unlink(tempFile);
}
return Promise.resolve();
}
private checkCancellation(originalFile: string, tempFile: string, token?: vscode.CancellationToken): boolean {
if (token && token.isCancellationRequested) {
this.deleteTempFile(originalFile, tempFile).ignoreErrors();
return true;
}
return false;
}
}