11import * as fs from 'fs' ;
22import * as path from 'path' ;
33import * as vscode from 'vscode' ;
4- import { OutputChannel , Uri } from 'vscode' ;
4+ import { OutputChannel , TextEdit , Uri } from 'vscode' ;
55import { STANDARD_OUTPUT_CHANNEL } from '../common/constants' ;
66import { isNotInstalledError } from '../common/helpers' ;
7+ import { IProcessService , IPythonExecutionFactory } from '../common/process/types' ;
78import { IInstaller , IOutputChannel , Product } from '../common/types' ;
9+ import { IEnvironmentVariablesProvider } from '../common/variables/types' ;
810import { IServiceContainer } from '../ioc/types' ;
911import { getTempFileWithDocumentContents , getTextEditsFromPatch } from './../common/editor' ;
10- import { execPythonFile } from './../common/utils ' ;
12+ import { IFormatterHelper } from './types ' ;
1113
1214export abstract class BaseFormatter {
1315 protected readonly outputChannel : OutputChannel ;
16+ private readonly helper : IFormatterHelper ;
1417 constructor ( public Id : string , private product : Product , private serviceContainer : IServiceContainer ) {
1518 this . outputChannel = this . serviceContainer . get < OutputChannel > ( IOutputChannel , STANDARD_OUTPUT_CHANNEL ) ;
19+ this . helper = this . serviceContainer . get < IFormatterHelper > ( IFormatterHelper ) ;
1620 }
1721
1822 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 {
3236 }
3337 return vscode . Uri . file ( __dirname ) ;
3438 }
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 [ ] > {
3640 this . outputChannel . clear ( ) ;
3741 if ( typeof cwd !== 'string' || cwd . length === 0 ) {
3842 cwd = this . getWorkspaceUri ( document ) . fsPath ;
3943 }
4044
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.
4347 // 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.
4549 const tmpFileCreated = document . isDirty ;
4650 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 ) {
6353 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+ } ) ;
6592 vscode . window . setStatusBarMessage ( `Formatting with ${ this . Id } ` , promise ) ;
6693 return promise ;
6794 }
6895
69- protected handleError ( expectedFileName : string , fileName : string , error : Error , resource ?: Uri ) {
96+ protected async handleError ( expectedFileName : string , error : Error , resource ?: Uri ) {
7097 let customError = `Formatting with ${ this . Id } failed.` ;
7198
7299 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 ) {
84103 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 ) ) ;
87105 }
88106 }
89107
0 commit comments