/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ import * as extensionFunctions from './extensionFunctions' ; import * as fs from 'fs' ; import { ViewColumn , OutputChannel , workspace , Uri , window , commands , env , ExtensionContext } from 'vscode' ; import * as path from 'path' ; import { DEFAULT_TUTORIAL_CATEGORY , didactTutorialsProvider , refreshTreeview , revealTreeItem } from './extension' ; import { TutorialNode } from './nodeProvider' ; export const DIDACT_DEFAULT_URL = 'didact.defaultUrl' ; export const DIDACT_REGISTERED_SETTING = 'didact.registered' ; export const DIDACT_NOTIFICATION_SETTING = 'didact.disableNotifications' ; export const DIDACT_COLUMN_SETTING = 'didact.lastColumnUsed' ; export const DIDACT_OPEN_AT_STARTUP = 'didact.openDefaultTutorialAtStartup' ; export const DIDACT_AUTO_INSTALL_DEFAULT_TUTORIALS = 'didact.autoAddDefaultTutorials' ; export const DIDACT_CLI_LINK_LF_SETTING = 'didact.edit.cliLinkLF' ; export const DIDACT_CLI_LINK_TEXT_SETTING = 'didact.edit.cliLinkText' ; export const DIDACT_APPEND_REGISTERED_SETTING = 'DIDACT_APPEND_REGISTRY' ; const CACHED_OUTPUT_CHANNELS = new Array < OutputChannel > ( ) ; export const DEFAULT_EXECUTE_LINK_TEXT = '^ execute' ; export interface ITutorial { name : string ; category : string ; sourceUri : string ; } export class Tutorial implements ITutorial { name : string ; category : string ; sourceUri : string ; constructor ( name , sourceUri , category ) { this . name = name ; this . category = category ; this . sourceUri = sourceUri ; } } export function getCachedOutputChannels ( ) { return CACHED_OUTPUT_CHANNELS ; } export function getCachedOutputChannel ( name ) { for ( const channel of CACHED_OUTPUT_CHANNELS ) { if ( channel . name === name ) { return channel ; } } return undefined ; } export function rememberOutputChannel ( channel ) { if ( ! getCachedOutputChannel ( channel . name ) ) { CACHED_OUTPUT_CHANNELS . push ( channel ) ; } } export function clearOutputChannels ( ) { for ( const channel of CACHED_OUTPUT_CHANNELS ) { channel . dispose ( ) ; } CACHED_OUTPUT_CHANNELS . length = 0 ; } export function pathEquals ( path1 , path2 ) { if ( process . platform !== 'linux' ) { path1 = path1 . toLowerCase ( ) ; path2 = path2 . toLowerCase ( ) ; } return path1 === path2 ; } export function getWorkspacePath ( ) { if ( workspace . workspaceFolders !== undefined && workspace . workspaceFolders . length > 0 ) { return workspace . workspaceFolders [ 0 ] . uri . fsPath ; } else { return undefined ; } } export function getValue ( input ) { if ( input ) { if ( Array . isArray ( input ) ) { return input [ 0 ] ; } else { return input ; } } return undefined ; } export function delay ( ms ) { return new Promise ( resolve => setTimeout ( resolve , ms ) ) ; } export function getDefaultUrl ( ) { return workspace . getConfiguration ( ) . get ( DIDACT_DEFAULT_URL ) ; } export function isDefaultNotificationDisabled ( ) { return workspace . getConfiguration ( ) . get ( DIDACT_NOTIFICATION_SETTING ) ; } export function getOpenAtStartupSetting ( ) { return workspace . getConfiguration ( ) . get ( DIDACT_OPEN_AT_STARTUP , false ) ; } export function getAutoInstallDefaultTutorialsSetting ( ) { return workspace . getConfiguration ( ) . get ( DIDACT_AUTO_INSTALL_DEFAULT_TUTORIALS , true ) ; } export function getRegisteredTutorials ( ) { return extensionFunctions . getContext ( ) . workspaceState . get ( DIDACT_REGISTERED_SETTING ) ; } export async function registerTutorialWithJSON ( jsonObject ) { const newTutorial = jsonObject as ITutorial ; return registerTutorialWithClass ( newTutorial ) ; } export async function registerTutorialWithClass ( newDidact , setFocusInTreeView = false , override = false ) { const newDidactAsString = JSON . stringify ( newDidact ) ; let existingRegistry = getRegisteredTutorials ( ) ; if ( ! existingRegistry ) { existingRegistry = [ newDidactAsString ] ; } else { const existingDidact = getTutorialForNameAndCategory ( newDidact . name , newDidact . category ) ; if ( ! existingDidact ) { existingRegistry . push ( newDidactAsString ) ; } else { if ( override ) { const oldIndex = existingRegistry . indexOf ( JSON . stringify ( existingDidact ) , 0 ) ; if ( oldIndex > - 1 ) { existingRegistry . splice ( oldIndex , 1 ) ; } existingRegistry . push ( newDidactAsString ) ; extensionFunctions . sendTextToOutputChannel ( `Didact tutorial with name ${ newDidact . name } and category ${ newDidact . category } already exists. The uri is updated from ${ existingDidact . sourceUri } to ${ newDidact . sourceUri }.` ) ; } else { extensionFunctions . sendTextToOutputChannel ( `Didact tutorial with name ${ newDidact . name } and category ${ newDidact . category } already exists.` ) ; } } } if ( setFocusInTreeView ) { await focusInTreeView ( existingRegistry , newDidact ) ; } } async function focusInTreeView ( existingRegistry , newDidact ) { await commands . executeCommand ( 'didact.tutorials.focus' ) ; await extensionFunctions . getContext ( ) . workspaceState . update ( DIDACT_REGISTERED_SETTING , existingRegistry ) ; refreshTreeview ( ) ; const tutorialNode = await didactTutorialsProvider . findTutorialNode ( newDidact . category , newDidact . name ) ; if ( tutorialNode ) { await revealTreeItem ( tutorialNode ) ; } } export async function registerTutorialWithArgs ( name , sourceUri , category , setFocusInTreeView = false , override = false ) { const newTutorial = new Tutorial ( name , sourceUri , category ) ; return registerTutorialWithClass ( newTutorial , setFocusInTreeView , override ) ; } export async function registerTutorialWithCategory ( name , sourceUri , category , setFocusInTreeView = false , override = false ) { return registerTutorialWithArgs ( name , sourceUri , category , setFocusInTreeView , override ) ; } export async function registerEmbeddedTutorials ( context , name , pathInExtension ) { const tutorialUri = Uri . file ( context . asAbsolutePath ( pathInExtension ) ) ; await registerTutorialWithCategory ( name , tutorialUri . fsPath , DEFAULT_TUTORIAL_CATEGORY , false , true ) ; } export function getDidactCategories ( ) { const existingRegistry = getRegisteredTutorials ( ) ; const didactCategories = [ ] ; if ( existingRegistry ) { for ( const entry of existingRegistry ) { const jsonObj = JSON . parse ( entry ) ; if ( jsonObj && jsonObj . category ) { if ( didactCategories . indexOf ( jsonObj . category ) === - 1 ) { didactCategories . push ( jsonObj . category ) ; } } } } return didactCategories ; } export function getDidactTutorials ( ) { const existingRegistry = getRegisteredTutorials ( ) ; const didactTutorials = [ ] ; if ( existingRegistry ) { for ( const entry of existingRegistry ) { const jsonObj = JSON . parse ( entry ) ; if ( jsonObj && jsonObj . category && jsonObj . name ) { didactTutorials . push ( jsonObj . name ) ; } } } return didactTutorials ; } export function getTutorialsForCategory ( category ) { const existingRegistry = getRegisteredTutorials ( ) ; const didactTutorials = [ ] ; if ( existingRegistry ) { for ( const entry of existingRegistry ) { const jsonObj = JSON . parse ( entry ) ; if ( jsonObj && jsonObj . category && jsonObj . name ) { const testCategory = jsonObj . category === category ; if ( testCategory ) { didactTutorials . push ( jsonObj . name ) ; } } } } return didactTutorials ; } export function getUriForDidactNameAndCategory ( name , category ) { const existingRegistry = getRegisteredTutorials ( ) ; if ( existingRegistry ) { for ( const entry of existingRegistry ) { const jsonObj = JSON . parse ( entry ) ; if ( jsonObj && jsonObj . category && jsonObj . name && jsonObj . sourceUri ) { const testName = jsonObj . name === name ; const testCategory = jsonObj . category === category ; if ( testName && testCategory ) { return jsonObj . sourceUri ; } } } } return undefined ; } export function getTutorialForNameAndCategory ( name , category ) { const existingRegistry = getRegisteredTutorials ( ) ; if ( existingRegistry ) { for ( const entry of existingRegistry ) { const jsonObj = JSON . parse ( entry ) ; if ( jsonObj && jsonObj . category && jsonObj . name && jsonObj . sourceUri ) { const testName = jsonObj . name === name ; const testCategory = jsonObj . category === category ; if ( testName && testCategory ) { return jsonObj ; } } } } return undefined ; } export async function clearRegisteredTutorials ( prompt = true ) { if ( prompt ) { const confirmDelete = `Are you sure you want to clear all registered Didact tutorials? This cannot be undone.` ; const result = await window . showWarningMessage ( confirmDelete , 'Yes' , 'No' ) ; if ( result === 'No' ) { return ; } } if ( workspace . getConfiguration ( ) ) { await extensionFunctions . getContext ( ) . workspaceState . update ( DIDACT_REGISTERED_SETTING , undefined ) ; console . log ( 'Didact configuration cleared' ) ; refreshTreeview ( ) ; } } export async function getCurrentFileSelectionPath ( ) { if ( window . activeTextEditor ) { return window . activeTextEditor . document . uri ; } else { await commands . executeCommand ( 'workbench.view.explorer' ) ; await commands . executeCommand ( 'copyFilePath' ) ; const copyPath = await env . clipboard . readText ( ) ; if ( fs . existsSync ( `"${ copyPath }"` ) && fs . lstatSync ( `"${ copyPath }"` ) . isFile ( ) ) { return Uri . file ( `"${ copyPath }"` ) ; } } throw new Error ( "Can not determine current file selection" ) ; } export function getLastColumnUsedSetting ( ) { let lastColumn = extensionFunctions . getContext ( ) . workspaceState . get ( DIDACT_COLUMN_SETTING ) ; if ( lastColumn === undefined ) { if ( window . activeTextEditor ) { lastColumn = window . activeTextEditor . viewColumn ; } if ( lastColumn === undefined ) { lastColumn = ViewColumn . One ; } } return lastColumn ; } export async function setLastColumnUsedSetting ( column ) { await extensionFunctions . getContext ( ) . workspaceState . update ( DIDACT_COLUMN_SETTING , column ) ; } export async function removeFilesAndFolders ( workspacename , filesAndFolders ) { if ( filesAndFolders && filesAndFolders . length > 0 ) { for ( const fileOrFolder of filesAndFolders ) { const testPath = path . resolve ( workspacename , fileOrFolder ) ; if ( testPath && fs . existsSync ( testPath ) ) { const delUri = Uri . file ( testPath ) ; await workspace . fs . delete ( delUri , { recursive : true } ) ; } } } } export async function updateRegisteredTutorials ( inJson ) { if ( workspace . getConfiguration ( ) ) { await extensionFunctions . getContext ( ) . workspaceState . update ( DIDACT_REGISTERED_SETTING , inJson ) ; console . log ( 'Didact configuration updated' ) ; } } export async function addNewTutorialWithNameAndCategoryForDidactUri ( uri , name ? , category ? ) { let tutorialName = undefined ; let tutorialCategory = undefined ; if ( name && category ) { tutorialName = name ; tutorialCategory = category ; } else { const categoriesForValidation = getDidactCategories ( ) ; const tutorialsForValidation = getDidactTutorials ( ) ; tutorialName = await getTutorialName ( tutorialsForValidation ) ; const selectedCategory = await quickPickCategory ( categoriesForValidation ) ; if ( selectedCategory === undefined ) { throw Error ( "Canceled out of Tutorial Category selection" ) ; } tutorialCategory = selectedCategory [ 0 ] ; } if ( ! uri ) { uri = await getCurrentFileSelectionPath ( ) ; } if ( tutorialName && tutorialCategory ) { if ( uri . scheme . toLowerCase ( ) === 'file' ) { await registerTutorialWithArgs ( tutorialName , uri . fsPath , tutorialCategory , true ) ; } else { await registerTutorialWithArgs ( tutorialName , uri . toString ( ) , tutorialCategory , true ) ; } } } async function getTutorialName ( tutorialsForValidation ) { const result = await window . showInputBox ( { value : 'New Tutorial' , placeHolder : 'Enter the name for your new tutorial. The name must be unique.' , ignoreFocusOut : true , validateInput : ( inputVal ) => { return validateTutorialNameInput ( inputVal , tutorialsForValidation ) ; } } ) ; if ( result === undefined ) { throw Error ( "Canceled out of Tutorial Name input box" ) ; } return result ?. trim ( ) ; } function validateTutorialNameInput ( value , tutorialsForValidation ) { if ( typeof value === "string" ) { if ( value . trim ( ) . length === 0 ) { return "Empty tutorial name is not allowed" ; } if ( value . startsWith ( ' ' ) ) { return "Spaces at start of tutorial name are not allowed" ; } if ( value . endsWith ( ' ' ) ) { return "Spaces at end of tutorial name are not allowed" ; } if ( tutorialsForValidation && tutorialsForValidation . indexOf ( value . trim ( ) ) > - 1 ) { return "Tutorial with that name already exists. Tutorial names must be unique." ; } return null ; } return `${ value } is invalid` ; } async function quickPickCategory ( categories , canSelectMany = false , acceptInput = true ) { const options = categories . map ( tag => ( { label : tag } ) ) ; return new Promise ( ( resolve ) => { const quickPick = window . createQuickPick ( ) ; let placeholder = "Select a Tutorial Category." ; if ( acceptInput ) { placeholder = "Select existing Category or type a new name. 'Enter' to confirm. 'Escape' to cancel." ; } quickPick . placeholder = placeholder ; quickPick . canSelectMany = canSelectMany ; quickPick . items = options ; quickPick . ignoreFocusOut = true ; let selectedItems = [ ] ; if ( canSelectMany ) { quickPick . onDidChangeSelection ( ( selected ) => { selectedItems = selected ; } ) ; } quickPick . onDidAccept ( ( ) => { if ( quickPick . value . trim ( ) . length > 0 || selectedItems . length > 0 || quickPick . activeItems . length > 0 ) { if ( canSelectMany ) { resolve ( selectedItems . map ( ( item ) => item . label ) ) ; } else { resolve ( quickPick . activeItems . map ( ( item ) => item . label ) ) ; } quickPick . hide ( ) ; } } ) ; if ( acceptInput ) { quickPick . onDidChangeValue ( ( ) => { if ( quickPick . value . trim ( ) . length === 0 ) { quickPick . value = '' ; quickPick . items = options ; } else { if ( categories . indexOf ( quickPick . value . trim ( ) ) === - 1 ) { quickPick . items = [ { label : quickPick . value . trim ( ) } , ... options ] ; } } } ) ; } quickPick . onDidHide ( ( ) => quickPick . dispose ( ) ) ; quickPick . show ( ) ; } ) ; } export async function removeTutorialByNameAndCategory ( node ) { const existingRegistry = getRegisteredTutorials ( ) ; let success = false ; if ( existingRegistry ) { let index = - 1 ; for ( const entry of existingRegistry ) { index ++ ; const jsonObj = JSON . parse ( entry ) ; if ( jsonObj && jsonObj . category && jsonObj . name && jsonObj . sourceUri ) { const testName = jsonObj . name === node . label ; const testCategory = jsonObj . category === node . category ; if ( testName && testCategory ) { existingRegistry . splice ( index , 1 ) ; success = true ; break ; } } } } if ( success ) { await extensionFunctions . getContext ( ) . workspaceState . update ( DIDACT_REGISTERED_SETTING , existingRegistry ) ; refreshTreeview ( ) ; } return success ; } export function getInsertLFForCLILinkSetting ( ) { return workspace . getConfiguration ( ) . get ( DIDACT_CLI_LINK_LF_SETTING , true ) ; } export function getLinkTextForCLILinkSetting ( ) { const value = workspace . getConfiguration ( ) . get ( DIDACT_CLI_LINK_TEXT_SETTING , DEFAULT_EXECUTE_LINK_TEXT ) ; if ( value && value . trim ( ) . length > 0 ) { return value ; } return DEFAULT_EXECUTE_LINK_TEXT ; } export async function setInsertLFForCLILinkSetting ( flag ) { await workspace . getConfiguration ( ) . update ( DIDACT_CLI_LINK_LF_SETTING , flag ) ; } export async function setLinkTextForCLILinkSetting ( text ) { await workspace . getConfiguration ( ) . update ( DIDACT_CLI_LINK_TEXT_SETTING , text ) ; } export function getFileExtension ( pathAsString ) { if ( pathAsString && pathAsString . trim ( ) . length > 0 ) { return pathAsString . split ( '.' ) . pop ( ) ; } return undefined ; } export function getAppendRegisteredSettingFromEnv ( ) { const envVar = process . env [ DIDACT_APPEND_REGISTERED_SETTING ] ; if ( ! envVar || ( typeof envVar !== 'string' ) ) { return ; } return envVar as string ; } export async function appendAdditionalTutorialsFromEnv ( ) { try { const appendTutorialsAtStartup = getAppendRegisteredSettingFromEnv ( ) ; if ( appendTutorialsAtStartup ) { await extensionFunctions . sendTextToOutputChannel ( `Didact tutorials appended at startup via ${ DIDACT_APPEND_REGISTERED_SETTING }` ) ; const jsonTutorials = JSON . parse ( appendTutorialsAtStartup ) ; for ( const jsonTutorial of jsonTutorials ) { await extensionFunctions . sendTextToOutputChannel ( `--Adding tutorial ${ jsonTutorial . sourceUri } as ${ jsonTutorial . name }/${ jsonTutorial . category }` ) ; await registerTutorialWithCategory ( jsonTutorial . name , jsonTutorial . sourceUri , jsonTutorial . category ) ; } } } catch ( ex ) { await extensionFunctions . sendTextToOutputChannel ( ex ) ; return Promise . reject ( ex ) ; } }