1
1
import * as fs from 'fs' ;
2
2
import * as path from 'path' ;
3
3
import * as vscode from 'vscode' ;
4
- import { OutputChannel , Uri } from 'vscode' ;
4
+ import { OutputChannel , TextEdit , Uri } from 'vscode' ;
5
5
import { STANDARD_OUTPUT_CHANNEL } from '../common/constants' ;
6
6
import { isNotInstalledError } from '../common/helpers' ;
7
+ import { IProcessService , IPythonExecutionFactory } from '../common/process/types' ;
7
8
import { IInstaller , IOutputChannel , Product } from '../common/types' ;
9
+ import { IEnvironmentVariablesProvider } from '../common/variables/types' ;
8
10
import { IServiceContainer } from '../ioc/types' ;
9
11
import { getTempFileWithDocumentContents , getTextEditsFromPatch } from './../common/editor' ;
10
- import { execPythonFile } from './../common/utils ' ;
12
+ import { IFormatterHelper } from './types ' ;
11
13
12
14
export abstract class BaseFormatter {
13
15
protected readonly outputChannel : OutputChannel ;
16
+ private readonly helper : IFormatterHelper ;
14
17
constructor ( public Id : string , private product : Product , private serviceContainer : IServiceContainer ) {
15
18
this . outputChannel = this . serviceContainer . get < OutputChannel > ( IOutputChannel , STANDARD_OUTPUT_CHANNEL ) ;
19
+ this . helper = this . serviceContainer . get < IFormatterHelper > ( IFormatterHelper ) ;
16
20
}
17
21
18
22
public abstract formatDocument ( document : vscode . TextDocument , options : vscode . FormattingOptions , token : vscode . CancellationToken , range ?: vscode . Range ) : Thenable < vscode . TextEdit [ ] > ;
@@ -32,58 +36,72 @@ export abstract class BaseFormatter {
32
36
}
33
37
return vscode . Uri . file ( __dirname ) ;
34
38
}
35
- protected provideDocumentFormattingEdits ( document : vscode . TextDocument , options : vscode . FormattingOptions , token : vscode . CancellationToken , command : string , args : string [ ] , cwd ?: string ) : Thenable < vscode . TextEdit [ ] > {
39
+ protected async provideDocumentFormattingEdits ( document : vscode . TextDocument , options : vscode . FormattingOptions , token : vscode . CancellationToken , args : string [ ] , cwd ?: string ) : Promise < vscode . TextEdit [ ] > {
36
40
this . outputChannel . clear ( ) ;
37
41
if ( typeof cwd !== 'string' || cwd . length === 0 ) {
38
42
cwd = this . getWorkspaceUri ( document ) . fsPath ;
39
43
}
40
44
41
- // autopep8 and yapf have the ability to read from the process input stream and return the formatted code out of the output stream
42
- // However they don't support returning the diff of the formatted text when reading data from the input stream
45
+ // autopep8 and yapf have the ability to read from the process input stream and return the formatted code out of the output stream.
46
+ // However they don't support returning the diff of the formatted text when reading data from the input stream.
43
47
// Yes getting text formatted that way avoids having to create a temporary file, however the diffing will have
44
- // to be done here in node (extension), i.e. extension cpu, i.e. les responsive solution
48
+ // to be done here in node (extension), i.e. extension cpu, i.e. les responsive solution.
45
49
const tmpFileCreated = document . isDirty ;
46
50
const filePromise = tmpFileCreated ? getTempFileWithDocumentContents ( document ) : Promise . resolve ( document . fileName ) ;
47
- const promise = filePromise . then ( filePath => {
48
- if ( token && token . isCancellationRequested ) {
49
- return [ filePath , '' ] ;
50
- }
51
- return Promise . all < string > ( [ Promise . resolve ( filePath ) , execPythonFile ( document . uri , command , args . concat ( [ filePath ] ) , cwd ! ) ] ) ;
52
- } ) . then ( data => {
53
- // Delete the temporary file created
54
- if ( tmpFileCreated ) {
55
- fs . unlink ( data [ 0 ] ) ;
56
- }
57
- if ( token && token . isCancellationRequested ) {
58
- return [ ] ;
59
- }
60
- return getTextEditsFromPatch ( document . getText ( ) , data [ 1 ] ) ;
61
- } ) . catch ( error => {
62
- this . handleError ( this . Id , command , error , document . uri ) ;
51
+ const filePath = await filePromise ;
52
+ if ( token && token . isCancellationRequested ) {
63
53
return [ ] ;
64
- } ) ;
54
+ }
55
+
56
+ let executionPromise : Promise < string > ;
57
+ const executionInfo = this . helper . getExecutionInfo ( this . product , args , document . uri ) ;
58
+ // Check if required to run as a module or executable.
59
+ if ( executionInfo . moduleName ) {
60
+ executionPromise = this . serviceContainer . get < IPythonExecutionFactory > ( IPythonExecutionFactory ) . create ( document . uri )
61
+ . then ( pythonExecutionService => pythonExecutionService . execModule ( executionInfo . moduleName ! , executionInfo . args . concat ( [ filePath ] ) , { cwd, throwOnStdErr : true , token } ) )
62
+ . then ( output => output . stdout ) ;
63
+ } else {
64
+ const executionService = this . serviceContainer . get < IProcessService > ( IProcessService ) ;
65
+ executionPromise = this . serviceContainer . get < IEnvironmentVariablesProvider > ( IEnvironmentVariablesProvider ) . getEnvironmentVariables ( true , document . uri )
66
+ . then ( env => executionService . exec ( executionInfo . execPath ! , executionInfo . args . concat ( [ filePath ] ) , { cwd, env, throwOnStdErr : true , token } ) )
67
+ . then ( output => output . stdout ) ;
68
+ }
69
+
70
+ const promise = executionPromise
71
+ . then ( data => {
72
+ if ( token && token . isCancellationRequested ) {
73
+ return [ ] as TextEdit [ ] ;
74
+ }
75
+ return getTextEditsFromPatch ( document . getText ( ) , data ) ;
76
+ } )
77
+ . catch ( error => {
78
+ if ( token && token . isCancellationRequested ) {
79
+ return [ ] as TextEdit [ ] ;
80
+ }
81
+ // tslint:disable-next-line:no-empty
82
+ this . handleError ( this . Id , error , document . uri ) . catch ( ( ) => { } ) ;
83
+ return [ ] as TextEdit [ ] ;
84
+ } )
85
+ . then ( edits => {
86
+ // Delete the temporary file created
87
+ if ( tmpFileCreated ) {
88
+ fs . unlink ( filePath ) ;
89
+ }
90
+ return edits ;
91
+ } ) ;
65
92
vscode . window . setStatusBarMessage ( `Formatting with ${ this . Id } ` , promise ) ;
66
93
return promise ;
67
94
}
68
95
69
- protected handleError ( expectedFileName : string , fileName : string , error : Error , resource ?: Uri ) {
96
+ protected async handleError ( expectedFileName : string , error : Error , resource ?: Uri ) {
70
97
let customError = `Formatting with ${ this . Id } failed.` ;
71
98
72
99
if ( isNotInstalledError ( error ) ) {
73
- // Check if we have some custom arguments such as "pylint --load-plugins pylint_django"
74
- // Such settings are no longer supported
75
- const stuffAfterFileName = fileName . substring ( fileName . toUpperCase ( ) . lastIndexOf ( expectedFileName ) + expectedFileName . length ) ;
76
-
77
- // Ok if we have a space after the file name, this means we have some arguments defined and this isn't supported
78
- if ( stuffAfterFileName . trim ( ) . indexOf ( ' ' ) > 0 ) {
79
- // tslint:disable-next-line:prefer-template
80
- customError = `Formatting failed, custom arguments in the 'python.formatting.${ this . Id } Path' is not supported.\n` +
81
- `Custom arguments to the formatter can be defined in 'python.formatter.${ this . Id } Args' setting of settings.json.` ;
82
- } else {
83
- const installer = this . serviceContainer . get < IInstaller > ( IInstaller ) ;
100
+ const installer = this . serviceContainer . get < IInstaller > ( IInstaller ) ;
101
+ const isInstalled = await installer . isInstalled ( this . product , resource ) ;
102
+ if ( isInstalled ) {
84
103
customError += `\nYou could either install the '${ this . Id } ' formatter, turn it off or use another formatter.` ;
85
- installer . promptToInstall ( this . product , resource )
86
- . catch ( ex => console . error ( 'Python Extension: promptToInstall' , ex ) ) ;
104
+ installer . promptToInstall ( this . product , resource ) . catch ( ex => console . error ( 'Python Extension: promptToInstall' , ex ) ) ;
87
105
}
88
106
}
89
107
0 commit comments