diff --git a/packages/tree-extension/src/index.ts b/packages/tree-extension/src/index.ts index 6ba3a6ecc3e..918c2858d1e 100644 --- a/packages/tree-extension/src/index.ts +++ b/packages/tree-extension/src/index.ts @@ -37,6 +37,7 @@ import { caretDownIcon, folderIcon, runningIcon, + terminalIcon, } from '@jupyterlab/ui-components'; import { Menu, MenuBar } from '@lumino/widgets'; @@ -54,6 +55,9 @@ const FILE_BROWSER_FACTORY = 'FileBrowser'; * The namespace for command IDs. */ namespace CommandIDs { + // Create a terminal in the current file browser directory. + export const createNewTerminal = 'filebrowser:create-new-terminal'; + // The command to show the filebrowser widget in tree view. export const openDirectory = 'filebrowser:open-directory'; @@ -74,17 +78,39 @@ const createNew: JupyterFrontEndPlugin = { id: '@jupyter-notebook/tree-extension:new', description: 'Plugin to add extra commands to the file browser to create new notebooks, files, consoles and terminals.', - requires: [ITranslator], + requires: [IFileBrowserFactory, ITranslator], optional: [IToolbarWidgetRegistry], autoStart: true, activate: ( app: JupyterFrontEnd, + fileBrowserFactory: IFileBrowserFactory, translator: ITranslator, toolbarRegistry: IToolbarWidgetRegistry | null ) => { const { commands, serviceManager } = app; const trans = translator.load('notebook'); + commands.addCommand(CommandIDs.createNewTerminal, { + label: trans.__('Terminal'), + icon: terminalIcon, + isEnabled: () => commands.isEnabled('terminal:create-new'), + isVisible: () => commands.isVisible('terminal:create-new'), + execute: () => { + const browser = fileBrowserFactory.tracker.currentWidget; + if (browser) { + return commands.execute('terminal:create-new', { + cwd: browser.model.path, + }); + } + }, + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, + }); + const overflowOptions = { overflowMenuOptions: { isVisible: false }, }; @@ -105,7 +131,7 @@ const createNew: JupyterFrontEndPlugin = { } const baseCommands = [ - 'terminal:create-new', + CommandIDs.createNewTerminal, 'console:create', 'filebrowser:create-new-file', 'filebrowser:create-new-directory', diff --git a/ui-tests/test/tree.spec.ts b/ui-tests/test/tree.spec.ts index 74eba32df96..11bf7860f9c 100644 --- a/ui-tests/test/tree.spec.ts +++ b/ui-tests/test/tree.spec.ts @@ -37,6 +37,44 @@ test('should update url when navigating in filebrowser', async ({ expect(url.pathname).toEqual(`/tree/${tmpPath}/${SUBFOLDER}`); }); +test('should create a terminal in the current directory', async ({ + page, + tmpPath, +}) => { + const dir = `${tmpPath}/${SUBFOLDER}`; + const marker = 'terminal-cwd.txt'; + await page.contents.createDirectory(dir); + await page.filebrowser.refresh(); + await page.dblclick(`.jp-FileBrowser-listing >> text=${SUBFOLDER}`); + await page.waitForSelector(`.jp-FileBrowser-crumbs >> text=/${SUBFOLDER}/`); + + const [terminal] = await Promise.all([ + page.waitForEvent('popup'), + page.menu.clickMenuItem('New>Terminal'), + ]); + + const terminalPanel = terminal.locator('.jp-Terminal'); + await terminalPanel.waitFor(); + await terminalPanel.locator('.xterm-screen').click(); + const input = terminalPanel.locator('[aria-label="Terminal input"]'); + await input.waitFor({ state: 'attached' }); + await expect(input).toBeFocused(); + await terminal.keyboard.type(`pwd > ${marker}`); + await terminal.keyboard.press('Enter'); + + const markerItem = page.locator(`.jp-FileBrowser-listing >> text=${marker}`); + await expect + .poll( + async () => { + await page.filebrowser.refresh(); + return markerItem.count(); + }, + { timeout: 10_000 } + ) + .toBeGreaterThan(0); + await terminal.close(); +}); + test('Should redirect from notebooks route to tree route for directories', async ({ page, tmpPath,