@@ -17,6 +17,7 @@ import {
1717 currentFile ,
1818 CurrentFile ,
1919 currentFileFromContent ,
20+ notNull ,
2021 outputChannel ,
2122 throttleRequests ,
2223} from "../utils" ;
@@ -69,12 +70,12 @@ export async function checkChangedOnServer(file: CurrentFile, force = false): Pr
6970 return mtime ;
7071}
7172
72- async function importFile ( file : CurrentFile , ignoreConflict ?: boolean ) : Promise < any > {
73+ async function importFile ( file : CurrentFile , ignoreConflict ?: boolean , skipDeplCheck = false ) : Promise < any > {
7374 const api = new AtelierAPI ( file . uri ) ;
74- if ( file . name . split ( "." ) . pop ( ) . toLowerCase ( ) === "cls" ) {
75+ if ( file . name . split ( "." ) . pop ( ) . toLowerCase ( ) === "cls" && ! skipDeplCheck ) {
7576 const result = await api . actionIndex ( [ file . name ] ) ;
7677 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 ") ;
7879 return Promise . reject ( ) ;
7980 }
8081 }
@@ -126,7 +127,7 @@ What do you want to do?`,
126127 // Clear cache entry
127128 workspaceState . update ( `${ file . uniqueId } :mtime` , undefined ) ;
128129 // Overwrite
129- return importFile ( file , true ) ;
130+ return importFile ( file , true , true ) ;
130131 case "Pull Server Changes" :
131132 outputChannel . appendLine ( `${ file . name } : Loading changes from server` ) ;
132133 outputChannel . show ( true ) ;
@@ -499,3 +500,224 @@ export async function compileExplorerItems(nodes: NodeBase[]): Promise<any> {
499500 } )
500501 ) ;
501502}
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