@@ -5,38 +5,16 @@ import { CancellationToken, TextDocument, Uri, WorkspaceEdit } from 'vscode';
55import { IApplicationShell , ICommandManager , IDocumentManager } from '../common/application/types' ;
66import { Commands , PYTHON_LANGUAGE , STANDARD_OUTPUT_CHANNEL } from '../common/constants' ;
77import { traceError } from '../common/logger' ;
8- import { IFileSystem } from '../common/platform/types' ;
98import * as internalScripts from '../common/process/internal/scripts' ;
10- import { IProcessServiceFactory , IPythonExecutionFactory } from '../common/process/types' ;
9+ import { IProcessServiceFactory , IPythonExecutionFactory , ObservableExecutionResult } from '../common/process/types' ;
1110import { IConfigurationService , IDisposableRegistry , IEditorUtils , IOutputChannel } from '../common/types' ;
11+ import { createDeferred } from '../common/utils/async' ;
1212import { noop } from '../common/utils/misc' ;
1313import { IServiceContainer } from '../ioc/types' ;
1414import { captureTelemetry } from '../telemetry' ;
1515import { EventName } from '../telemetry/constants' ;
1616import { ISortImportsEditingProvider } from './types' ;
1717
18- async function withRealFile < T > (
19- document : TextDocument ,
20- fs : IFileSystem ,
21- useFile : ( filename : string ) => Promise < T >
22- ) : Promise < [ string , T ] > {
23- const filename = document . uri . fsPath ;
24- const text = document . getText ( ) ;
25- if ( document . isDirty ) {
26- const tmpFile = await fs . createTemporaryFile ( path . extname ( filename ) ) ;
27- try {
28- await fs . writeFile ( tmpFile . filePath , text ) ;
29- const result = await useFile ( tmpFile . filePath ) ;
30- return [ text , result ] ;
31- } finally {
32- tmpFile . dispose ( ) ;
33- }
34- } else {
35- const result = await useFile ( filename ) ;
36- return [ text , result ] ;
37- }
38- }
39-
4018@injectable ( )
4119export class SortImportsEditingProvider implements ISortImportsEditingProvider {
4220 private readonly processServiceFactory : IProcessServiceFactory ;
@@ -45,6 +23,7 @@ export class SortImportsEditingProvider implements ISortImportsEditingProvider {
4523 private readonly documentManager : IDocumentManager ;
4624 private readonly configurationService : IConfigurationService ;
4725 private readonly editorUtils : IEditorUtils ;
26+
4827 public constructor ( @inject ( IServiceContainer ) private serviceContainer : IServiceContainer ) {
4928 this . shell = serviceContainer . get < IApplicationShell > ( IApplicationShell ) ;
5029 this . documentManager = serviceContainer . get < IDocumentManager > ( IDocumentManager ) ;
@@ -68,20 +47,14 @@ export class SortImportsEditingProvider implements ISortImportsEditingProvider {
6847 }
6948
7049 const execIsort = await this . getExecIsort ( document , uri , token ) ;
50+ if ( token && token . isCancellationRequested ) {
51+ return ;
52+ }
53+ const diffPatch = await execIsort ( document . getText ( ) ) ;
7154
72- // isort does have the ability to read from the process input stream and return the formatted code out of the output stream.
73- // However they don't support returning the diff of the formatted text when reading data from the input stream.
74- // Yes getting text formatted that way avoids having to create a temporary file, however the diffing will have
75- // to be done here in node (extension), i.e. extension cpu, i.e. less responsive solution.
76- const fs = this . serviceContainer . get < IFileSystem > ( IFileSystem ) ;
77- const [ text , diffPatch ] = await withRealFile ( document , fs , async ( filename : string ) => {
78- if ( token && token . isCancellationRequested ) {
79- return ;
80- }
81-
82- return execIsort ( filename ) ;
83- } ) ;
84- return diffPatch ? this . editorUtils . getWorkspaceEditsFromPatch ( text , diffPatch , document . uri ) : undefined ;
55+ return diffPatch
56+ ? this . editorUtils . getWorkspaceEditsFromPatch ( document . getText ( ) , diffPatch , document . uri )
57+ : undefined ;
8558 }
8659
8760 public registerCommands ( ) {
@@ -129,29 +102,74 @@ export class SortImportsEditingProvider implements ISortImportsEditingProvider {
129102 }
130103 }
131104
132- private async getExecIsort ( document : TextDocument , uri : Uri , token ?: CancellationToken ) {
105+ private async getExecIsort (
106+ document : TextDocument ,
107+ uri : Uri ,
108+ token ?: CancellationToken
109+ ) : Promise < ( documentText : string ) => Promise < string > > {
133110 const settings = this . configurationService . getSettings ( uri ) ;
134111 const _isort = settings . sortImports . path ;
135112 const isort = typeof _isort === 'string' && _isort . length > 0 ? _isort : undefined ;
136113 const isortArgs = settings . sortImports . args ;
137114
115+ // We pass the content of the file to be sorted via stdin. This avoids
116+ // saving the file (as well as a potential temporary file), but does
117+ // mean that we need another way to tell `isort` where to look for
118+ // configuration. We do that by setting the working directory to the
119+ // directory which contains the file.
120+ const filename = '-' ;
121+
122+ const spawnOptions = {
123+ token,
124+ throwOnStdErr : true ,
125+ cwd : path . dirname ( uri . fsPath )
126+ } ;
127+
138128 if ( isort ) {
139129 const procService = await this . processServiceFactory . create ( document . uri ) ;
140130 // Use isort directly instead of the internal script.
141- return async ( filename : string ) => {
131+ return async ( documentText : string ) => {
142132 const args = getIsortArgs ( filename , isortArgs ) ;
143- const proc = await procService . exec ( isort , args , { throwOnStdErr : true , token } ) ;
144- return proc . stdout ;
133+ const result = procService . execObservable ( isort , args , spawnOptions ) ;
134+ return this . communicateWithIsortProcess ( result , documentText ) ;
145135 } ;
146136 } else {
147137 const procService = await this . pythonExecutionFactory . create ( { resource : document . uri } ) ;
148- return async ( filename : string ) => {
138+ return async ( documentText : string ) => {
149139 const [ args , parse ] = internalScripts . sortImports ( filename , isortArgs ) ;
150- const proc = await procService . exec ( args , { throwOnStdErr : true , token } ) ;
151- return parse ( proc . stdout ) ;
140+ const result = procService . execObservable ( args , spawnOptions ) ;
141+ return parse ( await this . communicateWithIsortProcess ( result , documentText ) ) ;
152142 } ;
153143 }
154144 }
145+
146+ private async communicateWithIsortProcess (
147+ observableResult : ObservableExecutionResult < string > ,
148+ inputText : string
149+ ) : Promise < string > {
150+ // Configure our listening to the output from isort ...
151+ let outputBuffer = '' ;
152+ const isortOutput = createDeferred < string > ( ) ;
153+ observableResult . out . subscribe ( {
154+ next : ( output ) => {
155+ if ( output . source === 'stdout' ) {
156+ outputBuffer += output . out ;
157+ }
158+ } ,
159+ complete : ( ) => {
160+ isortOutput . resolve ( outputBuffer ) ;
161+ }
162+ } ) ;
163+
164+ // ... then send isort the document content ...
165+ observableResult . proc ?. stdin . write ( inputText ) ;
166+ observableResult . proc ?. stdin . end ( ) ;
167+
168+ // .. and finally wait for isort to do its thing
169+ await isortOutput . promise ;
170+
171+ return outputBuffer ;
172+ }
155173}
156174
157175function getIsortArgs ( filename : string , extraArgs ?: string [ ] ) : string [ ] {
0 commit comments