@@ -17,6 +17,7 @@ import {
17
17
currentFile ,
18
18
CurrentFile ,
19
19
currentFileFromContent ,
20
+ notNull ,
20
21
outputChannel ,
21
22
throttleRequests ,
22
23
} from "../utils" ;
@@ -69,12 +70,12 @@ export async function checkChangedOnServer(file: CurrentFile, force = false): Pr
69
70
return mtime ;
70
71
}
71
72
72
- async function importFile ( file : CurrentFile , ignoreConflict ?: boolean ) : Promise < any > {
73
+ async function importFile ( file : CurrentFile , ignoreConflict ?: boolean , skipDeplCheck = false ) : Promise < any > {
73
74
const api = new AtelierAPI ( file . uri ) ;
74
- if ( file . name . split ( "." ) . pop ( ) . toLowerCase ( ) === "cls" ) {
75
+ if ( file . name . split ( "." ) . pop ( ) . toLowerCase ( ) === "cls" && ! skipDeplCheck ) {
75
76
const result = await api . actionIndex ( [ file . name ] ) ;
76
77
if ( result . result . content [ 0 ] . content . depl ) {
77
- vscode . window . showErrorMessage ( " Cannot import over a deployed class ") ;
78
+ vscode . window . showErrorMessage ( ` Cannot import ${ file . name } because it is deployed on the server.` , "Dismiss ") ;
78
79
return Promise . reject ( ) ;
79
80
}
80
81
}
@@ -126,7 +127,7 @@ What do you want to do?`,
126
127
// Clear cache entry
127
128
workspaceState . update ( `${ file . uniqueId } :mtime` , undefined ) ;
128
129
// Overwrite
129
- return importFile ( file , true ) ;
130
+ return importFile ( file , true , true ) ;
130
131
case "Pull Server Changes" :
131
132
outputChannel . appendLine ( `${ file . name } : Loading changes from server` ) ;
132
133
outputChannel . show ( true ) ;
@@ -499,3 +500,224 @@ export async function compileExplorerItems(nodes: NodeBase[]): Promise<any> {
499
500
} )
500
501
) ;
501
502
}
503
+
504
+ /** Import file `name` to server `api`. Used for importing local files that are not used as part of a client-side editing workspace. */
505
+ async function importFileFromContent (
506
+ name : string ,
507
+ content : string ,
508
+ api : AtelierAPI ,
509
+ ignoreConflict ?: boolean ,
510
+ skipDeplCheck = false
511
+ ) : Promise < void > {
512
+ if ( name . split ( "." ) . pop ( ) . toLowerCase ( ) === "cls" && ! skipDeplCheck ) {
513
+ const result = await api . actionIndex ( [ name ] ) ;
514
+ if ( result . result . content [ 0 ] . content . depl ) {
515
+ vscode . window . showErrorMessage ( `Cannot import ${ name } because it is deployed on the server.` , "Dismiss" ) ;
516
+ return Promise . reject ( ) ;
517
+ }
518
+ }
519
+ ignoreConflict = ignoreConflict || config ( "overwriteServerChanges" ) ;
520
+ return api
521
+ . putDoc (
522
+ name ,
523
+ {
524
+ content : content . split ( / \r ? \n / ) ,
525
+ enc : false ,
526
+ // We don't have an mtime for this file because it's outside a local workspace folder
527
+ mtime : 0 ,
528
+ } ,
529
+ ignoreConflict
530
+ )
531
+ . then ( ( ) => {
532
+ return ;
533
+ } )
534
+ . catch ( ( error ) => {
535
+ if ( error ?. statusCode == 409 ) {
536
+ return vscode . window
537
+ . showErrorMessage (
538
+ `Failed to import '${ name } ' because it already exists on the server. Overwrite server copy?` ,
539
+ "Yes" ,
540
+ "No"
541
+ )
542
+ . then ( ( action ) => {
543
+ if ( action == "Yes" ) {
544
+ return importFileFromContent ( name , content , api , true , true ) ;
545
+ } else {
546
+ return Promise . reject ( ) ;
547
+ }
548
+ } ) ;
549
+ } else {
550
+ if ( error && error . errorText && error . errorText !== "" ) {
551
+ outputChannel . appendLine ( "\n" + error . errorText ) ;
552
+ vscode . window
553
+ . showErrorMessage (
554
+ `Failed to save file '${ name } ' on the server. Check 'ObjectScript' output channel for details.` ,
555
+ "Show" ,
556
+ "Dismiss"
557
+ )
558
+ . then ( ( action ) => {
559
+ if ( action === "Show" ) {
560
+ outputChannel . show ( true ) ;
561
+ }
562
+ } ) ;
563
+ } else {
564
+ vscode . window . showErrorMessage ( `Failed to save file '${ name } ' on the server.` , "Dismiss" ) ;
565
+ }
566
+ return Promise . reject ( ) ;
567
+ }
568
+ } ) ;
569
+ }
570
+
571
+ /** Import files from the local file system into a server-namespace from an `isfs` workspace folder. */
572
+ export async function importLocalFilesToServerSideFolder ( wsFolderUri : vscode . Uri ) : Promise < any > {
573
+ if (
574
+ ! (
575
+ wsFolderUri instanceof vscode . Uri &&
576
+ wsFolderUri . scheme == FILESYSTEM_SCHEMA &&
577
+ ( vscode . workspace . workspaceFolders != undefined
578
+ ? vscode . workspace . workspaceFolders . findIndex (
579
+ ( wsFolder ) => wsFolder . uri . toString ( ) == wsFolderUri . toString ( )
580
+ ) != - 1
581
+ : false )
582
+ )
583
+ ) {
584
+ // Need an isfs workspace folder URI
585
+ return ;
586
+ }
587
+ if ( vscode . workspace . workspaceFile . scheme != "file" ) {
588
+ vscode . window . showErrorMessage (
589
+ "'Import Local Files...' command is not supported for unsaved workspaces." ,
590
+ "Dismiss"
591
+ ) ;
592
+ return ;
593
+ }
594
+ const api = new AtelierAPI ( wsFolderUri ) ;
595
+ // Prompt the user for files to import
596
+ let uris = await vscode . window . showOpenDialog ( {
597
+ canSelectFiles : true ,
598
+ canSelectFolders : false ,
599
+ canSelectMany : true ,
600
+ openLabel : "Import" ,
601
+ filters : {
602
+ "InterSystems Files" : [ "cls" , "mac" , "int" , "inc" ] ,
603
+ } ,
604
+ // Need a default URI with file scheme or the open dialog
605
+ // will show the virtual files from the workspace folder
606
+ defaultUri : vscode . workspace . workspaceFile ,
607
+ } ) ;
608
+ if ( ! Array . isArray ( uris ) || uris . length == 0 ) {
609
+ // No files to import
610
+ return ;
611
+ }
612
+ // Filter out non-ISC files
613
+ uris = uris . filter ( ( uri ) => [ "cls" , "mac" , "int" , "inc" ] . includes ( uri . path . split ( "." ) . pop ( ) . toLowerCase ( ) ) ) ;
614
+ if ( uris . length == 0 ) {
615
+ vscode . window . showErrorMessage ( "No classes or routines were selected." , "Dismiss" ) ;
616
+ return ;
617
+ }
618
+ // Import the files
619
+ return Promise . allSettled < string > (
620
+ uris . map (
621
+ throttleRequests ( ( uri : vscode . Uri ) =>
622
+ vscode . workspace . fs
623
+ . readFile ( uri )
624
+ . then ( ( contentBytes ) => new TextDecoder ( ) . decode ( contentBytes ) )
625
+ . then ( ( content ) => {
626
+ // Determine the name of this file
627
+ let docName = "" ;
628
+ let ext = "" ;
629
+ if ( uri . path . split ( "." ) . pop ( ) . toLowerCase ( ) == "cls" ) {
630
+ // Allow Unicode letters
631
+ const match = content . match ( / ^ [ \t ] * C l a s s [ \t ] + ( % ? [ \p{ L} \d ] + (?: \. [ \p{ L} \d ] + ) + ) / imu) ;
632
+ if ( match ) {
633
+ [ , docName , ext = "cls" ] = match ;
634
+ }
635
+ } else {
636
+ const match = content . match ( / ^ R O U T I N E ( [ ^ \s ] + ) (?: \s * \[ \s * T y p e \s * = \s * \b ( [ a - z ] { 3 } ) \b ) ? / i) ;
637
+ if ( match ) {
638
+ [ , docName , ext = "mac" ] = match ;
639
+ } else {
640
+ const basename = uri . path . split ( "/" ) . pop ( ) ;
641
+ docName = basename . slice ( 0 , basename . lastIndexOf ( "." ) ) ;
642
+ ext = basename . slice ( basename . lastIndexOf ( "." ) + 1 ) ;
643
+ }
644
+ }
645
+ if ( docName != "" && ext != "" ) {
646
+ docName += `.${ ext . toLowerCase ( ) } ` ;
647
+ return importFileFromContent ( docName , content , api ) . then ( ( ) => {
648
+ outputChannel . appendLine ( "Imported file: " + uri . path . split ( "/" ) . pop ( ) ) ;
649
+ return docName ;
650
+ } ) ;
651
+ } else {
652
+ vscode . window . showErrorMessage (
653
+ `Cannot determine document name for file ${ uri . toString ( true ) } .` ,
654
+ "Dismiss"
655
+ ) ;
656
+ return Promise . reject ( ) ;
657
+ }
658
+ } )
659
+ )
660
+ )
661
+ ) . then ( ( results ) => {
662
+ const imported = results . map ( ( result ) => ( result . status == "fulfilled" ? result . value : null ) ) . filter ( notNull ) ;
663
+ // Prompt the user for compilation
664
+ if ( imported . length ) {
665
+ return vscode . window
666
+ . showInformationMessage (
667
+ `Imported ${ imported . length == 1 ? imported [ 0 ] : `${ imported . length } files` } . Compile ${
668
+ imported . length > 1 ? "them" : "it"
669
+ } ?`,
670
+ "Yes" ,
671
+ "No"
672
+ )
673
+ . then ( ( response ) => {
674
+ if ( response == "Yes" ) {
675
+ // Compile the imported files
676
+ return vscode . window . withProgress (
677
+ {
678
+ cancellable : true ,
679
+ location : vscode . ProgressLocation . Notification ,
680
+ title : `Compiling: ${ imported . length == 1 ? imported [ 0 ] : imported . length + " files" } ` ,
681
+ } ,
682
+ ( progress , token : vscode . CancellationToken ) =>
683
+ api
684
+ . asyncCompile ( imported , token , config ( "compileFlags" ) )
685
+ . then ( ( data ) => {
686
+ const info = imported . length > 1 ? "" : `${ imported [ 0 ] } : ` ;
687
+ if ( data . status && data . status . errors && data . status . errors . length ) {
688
+ throw new Error ( `${ info } Compile error` ) ;
689
+ } else if ( ! config ( "suppressCompileMessages" ) ) {
690
+ vscode . window . showInformationMessage ( `${ info } Compilation succeeded.` , "Dismiss" ) ;
691
+ }
692
+ } )
693
+ . catch ( ( ) => {
694
+ if ( ! config ( "suppressCompileErrorMessages" ) ) {
695
+ vscode . window
696
+ . showErrorMessage (
697
+ "Compilation failed. Check 'ObjectScript' output channel for details." ,
698
+ "Show" ,
699
+ "Dismiss"
700
+ )
701
+ . then ( ( action ) => {
702
+ if ( action === "Show" ) {
703
+ outputChannel . show ( true ) ;
704
+ }
705
+ } ) ;
706
+ }
707
+ } )
708
+ . finally ( ( ) => {
709
+ // Refresh the files explorer to show the new files
710
+ vscode . commands . executeCommand ( "workbench.files.action.refreshFilesExplorer" ) ;
711
+ } )
712
+ ) ;
713
+ } else {
714
+ // Refresh the files explorer to show the new files
715
+ vscode . commands . executeCommand ( "workbench.files.action.refreshFilesExplorer" ) ;
716
+ return Promise . resolve ( ) ;
717
+ }
718
+ } ) ;
719
+ } else {
720
+ return Promise . resolve ( ) ;
721
+ }
722
+ } ) ;
723
+ }
0 commit comments