import * as vscode from 'vscode' ; import { JSFIDDLE_SCHEME } from './fiddleRepository' ; import { FiddleSourceControl , CONFIGURATION_FILE } from './fiddleSourceControl' ; import { JSFiddleDocumentContentProvider } from './fiddleDocumentContentProvider' ; import * as path from 'path' ; import * as afs from './afs' ; import { FiddleConfiguration , parseFiddleId } from './fiddleConfiguration' ; import { firstIndex , UTF8 } from './util' ; const SOURCE_CONTROL_OPEN_COMMAND = 'extension.source-control.open' ; let jsFiddleDocumentContentProvider ; const fiddleSourceControlRegister = new Map < vscode . Uri , FiddleSourceControl > ( ) ; export async function activate ( context ) { console . log ( 'Congratulations, your extension "source-control-sample" is now active!' ) ; jsFiddleDocumentContentProvider = new JSFiddleDocumentContentProvider ( ) ; try { await initializeFromConfigurationFile ( context ) ; } catch ( err ) { console . log ( 'Failed to initialize a Fiddle workspace.' ) ; vscode . window . showErrorMessage ( err ) ; } const openCommand = vscode . commands . registerCommand ( SOURCE_CONTROL_OPEN_COMMAND , ( fiddleId ? , workspaceUri ? ) => { tryOpenFiddle ( context , fiddleId , workspaceUri ) ; } ) ; context . subscriptions . push ( openCommand ) ; context . subscriptions . push ( vscode . workspace . registerTextDocumentContentProvider ( JSFIDDLE_SCHEME , jsFiddleDocumentContentProvider ) ) ; context . subscriptions . push ( vscode . commands . registerCommand ( "extension.source-control.refresh" , async ( sourceControlPane ) => { const sourceControl = await pickSourceControl ( sourceControlPane ) ; if ( sourceControl ) { sourceControl . refresh ( ) ; } } ) ) ; context . subscriptions . push ( vscode . commands . registerCommand ( "extension.source-control.discard" , async ( sourceControlPane ) => { const sourceControl = await pickSourceControl ( sourceControlPane ) ; if ( sourceControl ) { sourceControl . resetFilesToCheckedOutVersion ( ) ; } } ) ) ; context . subscriptions . push ( vscode . commands . registerCommand ( "extension.source-control.commit" , async ( sourceControlPane ) => { const sourceControl = await pickSourceControl ( sourceControlPane ) ; if ( sourceControl ) { sourceControl . commitAll ( ) ; } } ) ) ; context . subscriptions . push ( vscode . commands . registerCommand ( "extension.source-control.checkout" , async ( sourceControl , newVersion ? ) => { sourceControl = sourceControl || await pickSourceControl ( null ) ; if ( sourceControl ) { sourceControl . tryCheckout ( newVersion ) ; } } ) ) ; context . subscriptions . push ( vscode . commands . registerCommand ( "extension.source-control.browse" , async ( sourceControlPane ) => { const sourceControl = await pickSourceControl ( sourceControlPane ) ; if ( sourceControl ) { sourceControl . openInBrowser ( ) ; } } ) ) ; context . subscriptions . push ( vscode . workspace . onDidChangeWorkspaceFolders ( e => { try { e . added . forEach ( wf => { initializeFolderFromConfigurationFile ( wf , context ) ; } ) ; } catch ( ex ) { vscode . window . showErrorMessage ( ex . message ) ; } finally { e . removed . forEach ( wf => { unregisterFiddleSourceControl ( wf . uri ) ; } ) ; } } ) ) ; } async function pickSourceControl ( sourceControlPane ) { if ( sourceControlPane ) { return fiddleSourceControlRegister . get ( sourceControlPane . rootUri ) ; } if ( fiddleSourceControlRegister . size === 0 ) { return undefined ; } else if ( fiddleSourceControlRegister . size === 1 ) { return [ ... fiddleSourceControlRegister . values ( ) ] [ 0 ] ; } else { const picks = [ ... fiddleSourceControlRegister . values ( ) ] . map ( fsc => new RepositoryPick ( fsc ) ) ; if ( vscode . window . activeTextEditor ) { const activeWorkspaceFolder = vscode . workspace . getWorkspaceFolder ( vscode . window . activeTextEditor . document . uri ) ; const activeSourceControl = activeWorkspaceFolder && fiddleSourceControlRegister . get ( activeWorkspaceFolder . uri ) ; const activeIndex = firstIndex ( picks , pick => pick . fiddleSourceControl === activeSourceControl ) ; if ( activeIndex > - 1 ) { picks . unshift ( ... picks . splice ( activeIndex , 1 ) ) ; } } const pick = await vscode . window . showQuickPick ( picks , { placeHolder : 'Select repository' } ) ; return pick && pick . fiddleSourceControl ; } } async function tryOpenFiddle ( context , fiddleId ? , workspaceUri ? ) { try { await openFiddle ( context , fiddleId , workspaceUri ) ; } catch ( ex ) { vscode . window . showErrorMessage ( ex ) ; console . log ( ex ) ; } } async function openFiddle ( context , fiddleId ? , workspaceUri ? ) { if ( workspaceUri && fiddleSourceControlRegister . has ( workspaceUri ) ) { vscode . window . showErrorMessage ( "Another Fiddle was already open in this workspace. Open a new workspace first." ) ; } if ( ! fiddleId ) { fiddleId = ( await vscode . window . showInputBox ( { prompt : 'Paste JSFiddle ID and optionally version' , placeHolder : 'slug or slug/version, e.g. u8B29/1' , value : 'demo' } ) ) || '' ; } const workspaceFolder = workspaceUri ? vscode . workspace . getWorkspaceFolder ( workspaceUri ) : await selectWorkspaceFolder ( context , fiddleId ) ; if ( ! await clearWorkspaceFolder ( workspaceFolder . uri ) ) { return ; } vscode . commands . executeCommand ( "workbench.view.explorer" ) ; const fiddleSourceControl = await FiddleSourceControl . fromFiddleId ( fiddleId , context , workspaceFolder , true ) ; registerFiddleSourceControl ( fiddleSourceControl , context ) ; showFiddleInEditor ( fiddleSourceControl ) ; } async function showFiddleInEditor ( fiddleSourceControl ) { await openDocumentInColumn ( fiddleSourceControl . getRepository ( ) . createLocalResourcePath ( 'html' ) , vscode . ViewColumn . One ) ; await openDocumentInColumn ( fiddleSourceControl . getRepository ( ) . createLocalResourcePath ( 'js' ) , vscode . ViewColumn . Two ) ; await openDocumentInColumn ( fiddleSourceControl . getRepository ( ) . createLocalResourcePath ( 'css' ) , vscode . ViewColumn . Three ) ; } function registerFiddleSourceControl ( fiddleSourceControl , context ) { jsFiddleDocumentContentProvider . updated ( fiddleSourceControl . getFiddle ( ) ) ; fiddleSourceControl . onRepositoryChange ( fiddle => jsFiddleDocumentContentProvider . updated ( fiddle ) ) ; if ( fiddleSourceControlRegister . has ( fiddleSourceControl . getWorkspaceFolder ( ) . uri ) ) { const previousSourceControl = fiddleSourceControlRegister . get ( fiddleSourceControl . getWorkspaceFolder ( ) . uri ) ! ; previousSourceControl . dispose ( ) ; } fiddleSourceControlRegister . set ( fiddleSourceControl . getWorkspaceFolder ( ) . uri , fiddleSourceControl ) ; context . subscriptions . push ( fiddleSourceControl ) ; } function unregisterFiddleSourceControl ( folderUri ) { if ( fiddleSourceControlRegister . has ( folderUri ) ) { const previousSourceControl = fiddleSourceControlRegister . get ( folderUri ) ! ; previousSourceControl . dispose ( ) ; fiddleSourceControlRegister . delete ( folderUri ) ; } } param context async function initializeFromConfigurationFile ( context ) { if ( ! vscode . workspace . workspaceFolders ) { return ; } const folderPromises = vscode . workspace . workspaceFolders . map ( async ( folder ) => await initializeFolderFromConfigurationFile ( folder , context ) ) ; await Promise . all ( folderPromises ) ; } async function initializeFolderFromConfigurationFile ( folder , context ) { const configurationPath = path . join ( folder . uri . fsPath , CONFIGURATION_FILE ) ; const configFileExists = await afs . exists ( configurationPath ) ; if ( configFileExists ) { const data = await afs . readFile ( configurationPath ) ; const fiddleConfiguration = < FiddleConfiguration > JSON . parse ( data . toString ( UTF8 ) ) ; const fiddleSourceControl = await FiddleSourceControl . fromConfiguration ( fiddleConfiguration , folder , context , ! fiddleConfiguration . downloaded ) ; registerFiddleSourceControl ( fiddleSourceControl , context ) ; if ( ! fiddleConfiguration . downloaded ) { showFiddleInEditor ( fiddleSourceControl ) ; } } } async function selectWorkspaceFolder ( context , fiddleId ) { let selectedFolder ; let workspaceFolderUri ; let workspaceFolderIndex ; let folderOpeningMode ; const folderPicks = [ newFolderPick ] ; if ( vscode . workspace . workspaceFolders && vscode . workspace . workspaceFolders . length > 0 ) { folderPicks . push ( newWorkspaceFolderPick ) ; for ( const wf of vscode . workspace . workspaceFolders ) { const content = await afs . readdir ( wf . uri . fsPath ) ; folderPicks . push ( new ExistingWorkspaceFolderPick ( wf , content ) ) ; } } const selectedFolderPick = folderPicks . length === 1 ? folderPicks [ 0 ] : await vscode . window . showQuickPick ( folderPicks , { canPickMany : false , ignoreFocusOut : true , placeHolder : 'Pick workspace folder to create files in.' } ) ; if ( ! selectedFolderPick ) { return null ; } if ( selectedFolderPick instanceof ExistingWorkspaceFolderPick ) { selectedFolder = selectedFolderPick . workspaceFolder ; workspaceFolderIndex = selectedFolder . index ; workspaceFolderUri = selectedFolder . uri ; } folderOpeningMode = selectedFolderPick . folderOpeningMode ; if ( ! workspaceFolderUri && ! selectedFolder ) { const folderUris = await vscode . window . showOpenDialog ( { canSelectFolders : true , canSelectFiles : false , canSelectMany : false , openLabel : 'Select folder' } ) ; if ( ! folderUris ) { return null ; } workspaceFolderUri = folderUris [ 0 ] ; workspaceFolderIndex = vscode . workspace . workspaceFolders && firstIndex ( vscode . workspace . workspaceFolders , ( folder1 ) => folder1 . uri . toString ( ) === workspaceFolderUri ! . toString ( ) ) ; } if ( ! await clearWorkspaceFolder ( workspaceFolderUri ) ) { return null ; } const fiddleConfiguration = parseFiddleId ( fiddleId ) ; FiddleSourceControl . saveConfiguration ( workspaceFolderUri , fiddleConfiguration ) ; if ( folderOpeningMode === FolderOpeningMode . AddToWorkspace || folderOpeningMode === undefined ) { const workSpacesToReplace = typeof workspaceFolderIndex === 'number' && workspaceFolderIndex > - 1 ? 1 : 0 ; if ( workspaceFolderIndex === undefined || workspaceFolderIndex < 0 ) { workspaceFolderIndex = 0 ; } if ( workspaceFolderUri ) { vscode . workspace . updateWorkspaceFolders ( workspaceFolderIndex , workSpacesToReplace , { uri : workspaceFolderUri } ) ; } } else if ( folderOpeningMode === FolderOpeningMode . OpenFolder ) { vscode . commands . executeCommand ( "vscode.openFolder" , workspaceFolderUri ) ; } return selectedFolder ; } async function clearWorkspaceFolder ( workspaceFolderUri ) { if ( ! workspaceFolderUri ) { return undefined ; } const existingWorkspaceFiles = await afs . readdir ( workspaceFolderUri . fsPath ) ; if ( existingWorkspaceFiles . length > 0 ) { const answer = await vscode . window . showQuickPick ( [ "Yes" , "No" ] , { placeHolder : `Remove ${ existingWorkspaceFiles . length } file(s) from the folder ${ workspaceFolderUri . fsPath } before cloning the remote repository?` } ) ; if ( ! answer ) { return false ; } if ( answer === "Yes" ) { existingWorkspaceFiles . forEach ( async ( filename ) => await afs . unlink ( path . join ( workspaceFolderUri . fsPath , filename ) ) ) ; } } return true ; } class RepositoryPick implements vscode . QuickPickItem { constructor ( public readonly fiddleSourceControl ) { } get label ( ) : string { return this . fiddleSourceControl . getSourceControl ( ) . label ; } } async function openDocumentInColumn ( fileName , column ) { const uri = vscode . Uri . file ( fileName ) ; const doc = await vscode . workspace . openTextDocument ( uri ) ; await vscode . window . showTextDocument ( doc , { viewColumn : column } ) ; } abstract class WorkspaceFolderPick implements vscode . QuickPickItem { abstract get label ( ) : string ; constructor ( public folderOpeningMode ) { } } class ExistingWorkspaceFolderPick extends WorkspaceFolderPick { constructor ( public readonly workspaceFolder , private content ) { super ( FolderOpeningMode . AddToWorkspace ) ; } get label ( ) : string { return this . workspaceFolder . name ; } get description ( ) : string { return this . workspaceFolder . uri . fsPath ; } get detail ( ) : string { return this . content . length ? `${ this . content . length } files/directories may need to be removed..` : null ; } } class NewWorkspaceFolderPick extends WorkspaceFolderPick { constructor ( public label , folderOpeningMode ) { super ( folderOpeningMode ) ; } } enum FolderOpeningMode { AddToWorkspace , OpenFolder } const newWorkspaceFolderPick = new NewWorkspaceFolderPick ( "Select/create a local folder to add to this workspace..." , FolderOpeningMode . AddToWorkspace ) ; const newFolderPick = new NewWorkspaceFolderPick ( "Select/create a local folder..." , FolderOpeningMode . OpenFolder ) ;