@@ -8,6 +8,7 @@ import * as path from "path";
8
8
import * as vscode from "vscode" ;
9
9
import { LanguageClient , NotificationType , Position , Range , RequestType } from "vscode-languageclient" ;
10
10
import { IFeature } from "../feature" ;
11
+ import { Logger } from "../logging" ;
11
12
12
13
export interface IExtensionCommand {
13
14
name : string ;
@@ -172,10 +173,11 @@ export class ExtensionCommandsFeature implements IFeature {
172
173
private languageClient : LanguageClient ;
173
174
private extensionCommands : IExtensionCommand [ ] = [ ] ;
174
175
175
- constructor ( ) {
176
+ constructor ( private log : Logger ) {
176
177
this . command = vscode . commands . registerCommand ( "PowerShell.ShowAdditionalCommands" , ( ) => {
177
178
if ( this . languageClient === undefined ) {
178
- // TODO: Log error message
179
+ this . log . writeAndShowError ( `<${ ExtensionCommandsFeature . name } >: ` +
180
+ "Unable to instantiate; language client undefined." ) ;
179
181
return ;
180
182
}
181
183
@@ -388,44 +390,127 @@ export class ExtensionCommandsFeature implements IFeature {
388
390
return promise ;
389
391
}
390
392
393
+ /**
394
+ * Save a file, possibly to a new path. If the save is not possible, return a completed response
395
+ * @param saveFileDetails the object detailing the path of the file to save and optionally its new path to save to
396
+ */
391
397
private async saveFile ( saveFileDetails : ISaveFileDetails ) : Promise < EditorOperationResponse > {
392
-
393
- // If the file to save can't be found, just complete the request
394
- if ( ! this . findTextDocument ( this . normalizeFilePath ( saveFileDetails . filePath ) ) ) {
395
- return EditorOperationResponse . Completed ;
398
+ // Try to interpret the filepath as a URI, defaulting to "file://" if we don't succeed
399
+ let currentFileUri : vscode . Uri ;
400
+ if ( saveFileDetails . filePath . startsWith ( "untitled" ) || saveFileDetails . filePath . startsWith ( "file" ) ) {
401
+ currentFileUri = vscode . Uri . parse ( saveFileDetails . filePath ) ;
402
+ } else {
403
+ currentFileUri = vscode . Uri . file ( saveFileDetails . filePath ) ;
396
404
}
397
405
398
- // If no newFile is given, just save the current file
399
- if ( ! saveFileDetails . newPath ) {
400
- const doc = await vscode . workspace . openTextDocument ( saveFileDetails . filePath ) ;
401
- if ( doc . isDirty ) {
402
- await doc . save ( ) ;
403
- }
404
-
405
- return EditorOperationResponse . Completed ;
406
+ let newFileAbsolutePath : string ;
407
+ switch ( currentFileUri . scheme ) {
408
+ case "file" :
409
+ // If the file to save can't be found, just complete the request
410
+ if ( ! this . findTextDocument ( this . normalizeFilePath ( currentFileUri . fsPath ) ) ) {
411
+ this . log . writeAndShowError ( `File to save not found: ${ currentFileUri . fsPath } .` ) ;
412
+ return EditorOperationResponse . Completed ;
413
+ }
414
+
415
+ // If no newFile is given, just save the current file
416
+ if ( ! saveFileDetails . newPath ) {
417
+ const doc = await vscode . workspace . openTextDocument ( currentFileUri . fsPath ) ;
418
+ if ( doc . isDirty ) {
419
+ await doc . save ( ) ;
420
+ }
421
+ return EditorOperationResponse . Completed ;
422
+ }
423
+
424
+ // Make sure we have an absolute path
425
+ if ( path . isAbsolute ( saveFileDetails . newPath ) ) {
426
+ newFileAbsolutePath = saveFileDetails . newPath ;
427
+ } else {
428
+ // If not, interpret the path as relative to the current file
429
+ newFileAbsolutePath = path . join ( path . dirname ( currentFileUri . fsPath ) , saveFileDetails . newPath ) ;
430
+ }
431
+ break ;
432
+
433
+ case "untitled" :
434
+ // We need a new name to save an untitled file
435
+ if ( ! saveFileDetails . newPath ) {
436
+ // TODO: Create a class handle vscode warnings and errors so we can warn easily
437
+ // without logging
438
+ this . log . writeAndShowWarning (
439
+ "Cannot save untitled file. Try SaveAs(\"path/to/file.ps1\") instead." ) ;
440
+ return EditorOperationResponse . Completed ;
441
+ }
442
+
443
+ // Make sure we have an absolute path
444
+ if ( path . isAbsolute ( saveFileDetails . newPath ) ) {
445
+ newFileAbsolutePath = saveFileDetails . newPath ;
446
+ } else {
447
+ // In fresh contexts, workspaceFolders is not defined...
448
+ if ( ! vscode . workspace . workspaceFolders || vscode . workspace . workspaceFolders . length === 0 ) {
449
+ this . log . writeAndShowWarning ( "Cannot save file to relative path: no workspaces are open. " +
450
+ "Try saving to an absolute path, or open a workspace." ) ;
451
+ return EditorOperationResponse . Completed ;
452
+ }
453
+
454
+ // If not, interpret the path as relative to the workspace root
455
+ const workspaceRootUri = vscode . workspace . workspaceFolders [ 0 ] . uri ;
456
+ // We don't support saving to a non-file URI-schemed workspace
457
+ if ( workspaceRootUri . scheme !== "file" ) {
458
+ this . log . writeAndShowWarning (
459
+ "Cannot save untitled file to a relative path in an untitled workspace. " +
460
+ "Try saving to an absolute path or opening a workspace folder." ) ;
461
+ return EditorOperationResponse . Completed ;
462
+ }
463
+ newFileAbsolutePath = path . join ( workspaceRootUri . fsPath , saveFileDetails . newPath ) ;
464
+ }
465
+ break ;
466
+
467
+ default :
468
+ // Other URI schemes are not supported
469
+ const msg = JSON . stringify ( saveFileDetails ) ;
470
+ this . log . writeVerbose (
471
+ `<${ ExtensionCommandsFeature . name } >: Saving a document with scheme '${ currentFileUri . scheme } ' ` +
472
+ `is currently unsupported. Message: '${ msg } '` ) ;
473
+ return EditorOperationResponse . Completed ;
406
474
}
407
475
408
- // Otherwise we want to save as a new file
409
-
410
- // First turn the path we were given into an absolute path
411
- // Relative paths are interpreted as relative to the original file
412
- const newFileAbsolutePath = path . isAbsolute ( saveFileDetails . newPath ) ?
413
- saveFileDetails . newPath :
414
- path . resolve ( path . dirname ( saveFileDetails . filePath ) , saveFileDetails . newPath ) ;
415
-
416
- // Retrieve the text out of the current document
417
- const oldDocument = await vscode . workspace . openTextDocument ( saveFileDetails . filePath ) ;
476
+ await this . saveDocumentContentToAbsolutePath ( currentFileUri , newFileAbsolutePath ) ;
477
+ return EditorOperationResponse . Completed ;
418
478
419
- // Write it to the new document path
420
- fs . writeFileSync ( newFileAbsolutePath , oldDocument . getText ( ) ) ;
479
+ }
421
480
422
- // Finally open the new document
423
- const newFileUri = vscode . Uri . file ( newFileAbsolutePath ) ;
424
- const newFile = await vscode . workspace . openTextDocument ( newFileUri ) ;
425
- vscode . window . showTextDocument ( newFile , { preview : true } ) ;
481
+ /**
482
+ * Take a document available to vscode at the given URI and save it to the given absolute path
483
+ * @param documentUri the URI of the vscode document to save
484
+ * @param destinationAbsolutePath the absolute path to save the document contents to
485
+ */
486
+ private async saveDocumentContentToAbsolutePath (
487
+ documentUri : vscode . Uri ,
488
+ destinationAbsolutePath : string ) : Promise < void > {
489
+ // Retrieve the text out of the current document
490
+ const oldDocument = await vscode . workspace . openTextDocument ( documentUri ) ;
491
+
492
+ // Write it to the new document path
493
+ try {
494
+ // TODO: Change this to be asyncronous
495
+ await new Promise < void > ( ( resolve , reject ) => {
496
+ fs . writeFile ( destinationAbsolutePath , oldDocument . getText ( ) , ( err ) => {
497
+ if ( err ) {
498
+ return reject ( err ) ;
499
+ }
500
+ return resolve ( ) ;
501
+ } ) ;
502
+ } ) ;
503
+ } catch ( e ) {
504
+ this . log . writeAndShowWarning ( `<${ ExtensionCommandsFeature . name } >: ` +
505
+ `Unable to save file to path '${ destinationAbsolutePath } ': ${ e } ` ) ;
506
+ return ;
507
+ }
426
508
427
- return EditorOperationResponse . Completed ;
428
- }
509
+ // Finally open the new document
510
+ const newFileUri = vscode . Uri . file ( destinationAbsolutePath ) ;
511
+ const newFile = await vscode . workspace . openTextDocument ( newFileUri ) ;
512
+ vscode . window . showTextDocument ( newFile , { preview : true } ) ;
513
+ }
429
514
430
515
private normalizeFilePath ( filePath : string ) : string {
431
516
const platform = os . platform ( ) ;
0 commit comments