From 1f7e06f990167421c6e6025ee57c2d8cb120ca37 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sat, 1 Aug 2020 14:07:39 +0200 Subject: [PATCH] aligned new file creation to the java ide. Signed-off-by: Akos Kitta --- .../browser/arduino-ide-frontend-module.ts | 8 +- .../theia/workspace/workspace-commands.ts | 81 +++++++++++++++++++ .../theia/workspace/workspace-input-dialog.ts | 39 +++++++++ .../src/common/protocol/sketches-service.ts | 7 ++ .../src/node/sketches-service-impl.ts | 8 +- 5 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts create mode 100644 arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 79d70dd1..a11696cd 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -87,7 +87,11 @@ import { BoardsDataMenuUpdater } from './boards/boards-data-menu-updater'; import { BoardsDataStore } from './boards/boards-data-store'; import { ILogger } from '@theia/core'; import { FileSystemExt, FileSystemExtPath } from '../common/protocol/filesystem-ext'; -import { WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution, FileMenuContribution as TheiaFileMenuContribution } from '@theia/workspace/lib/browser'; +import { + WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution, + FileMenuContribution as TheiaFileMenuContribution, + WorkspaceCommandContribution as TheiaWorkspaceCommandContribution +} from '@theia/workspace/lib/browser'; import { WorkspaceFrontendContribution, ArduinoFileMenuContribution } from './theia/workspace/workspace-frontend-contribution'; import { Contribution } from './contributions/contribution'; import { NewSketch } from './contributions/new-sketch'; @@ -106,6 +110,7 @@ import { QuitApp } from './contributions/quit-app'; import { SketchControl } from './contributions/sketch-control'; import { Settings } from './contributions/settings'; import { KeybindingRegistry } from './theia/core/keybindings'; +import { WorkspaceCommandContribution } from './theia/workspace/workspace-commands'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -283,6 +288,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TheiaCommonFrontendContribution).to(CommonFrontendContribution).inSingletonScope(); rebind(TheiaPreferencesContribution).to(PreferencesContribution).inSingletonScope(); rebind(TheiaKeybindingRegistry).to(KeybindingRegistry).inSingletonScope(); + rebind(TheiaWorkspaceCommandContribution).to(WorkspaceCommandContribution).inSingletonScope(); // Show a disconnected status bar, when the daemon is not available bind(ApplicationConnectionStatusContribution).toSelf().inSingletonScope(); diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts new file mode 100644 index 00000000..7de1fdb6 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts @@ -0,0 +1,81 @@ +import { injectable } from 'inversify'; +import URI from '@theia/core/lib/common/uri'; +import { open } from '@theia/core/lib/browser/opener-service'; +import { FileStat } from '@theia/filesystem/lib/common'; +import { CommandRegistry } from '@theia/core/lib/common/command'; +import { WorkspaceCommandContribution as TheiaWorkspaceCommandContribution, WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands'; +import { Extensions } from '../../../common/protocol'; +import { WorkspaceInputDialog } from './workspace-input-dialog'; + +@injectable() +export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribution { + + registerCommands(registry: CommandRegistry): void { + super.registerCommands(registry); + registry.unregisterCommand(WorkspaceCommands.NEW_FILE); + registry.registerCommand(WorkspaceCommands.NEW_FILE, this.newWorkspaceRootUriAwareCommandHandler({ + execute: uri => this.newFile(uri) + })); + } + + protected async newFile(uri: URI | undefined): Promise { + if (!uri) { + return; + } + const parent = await this.getDirectory(uri); + if (!parent) { + return; + } + + const parentUri = new URI(parent.uri); + const dialog = new WorkspaceInputDialog({ + title: 'Name for new file', + parentUri, + validate: name => this.validateFileName(name, parent, true) + }, this.labelProvider); + + const name = await dialog.open(); + const nameWithExt = this.appendInoExtensionMaybe(name); + if (nameWithExt) { + const fileUri = parentUri.resolve(nameWithExt); + await this.fileSystem.createFile(fileUri.toString()); + this.fireCreateNewFile({ parent: parentUri, uri: fileUri }); + open(this.openerService, fileUri); + } + } + + protected async validateFileName(name: string, parent: FileStat, recursive: boolean = false): Promise { + // In the Java IDE the followings are the rules: + // - `name` without an extension should default to `name.ino`. + // - `name` with a single trailing `.` also defaults to `name.ino`. + const nameWithExt = this.appendInoExtensionMaybe(name); + const errorMessage = await super.validateFileName(nameWithExt, parent, recursive); + if (errorMessage) { + return errorMessage; + } + + const extension = nameWithExt.split('.').pop(); + if (!extension) { + return 'Invalid file extension.'; + } + if (Extensions.ALL.indexOf(`.${extension}`) === -1) { + return `.${extension} is not a valid extension.`; + } + return ''; + } + + protected appendInoExtensionMaybe(name: string | undefined): string { + if (!name) { + return ''; + } + if (name.trim().length) { + if (name.indexOf('.') === -1) { + return `${name}.ino` + } + if (name.indexOf('.') === name.length - 1) { + return `${name.slice(0, -1)}.ino` + } + } + return name; + } +} diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts new file mode 100644 index 00000000..b975e839 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts @@ -0,0 +1,39 @@ +import { inject } from 'inversify'; +import { MaybePromise } from '@theia/core/lib/common/types'; +import { LabelProvider } from '@theia/core/lib/browser/label-provider'; +import { DialogError, DialogMode } from '@theia/core/lib/browser/dialogs'; +import { WorkspaceInputDialog as TheiaWorkspaceInputDialog, WorkspaceInputDialogProps } from '@theia/workspace/lib/browser/workspace-input-dialog'; + +export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog { + + protected wasTouched = false; + + constructor( + @inject(WorkspaceInputDialogProps) protected readonly props: WorkspaceInputDialogProps, + @inject(LabelProvider) protected readonly labelProvider: LabelProvider, + ) { + super(props, labelProvider); + this.appendCloseButton('Cancel'); + } + + protected appendParentPath(): void { + // NOOP + } + + isValid(value: string, mode: DialogMode): MaybePromise { + if (value !== '') { + this.wasTouched = true; + } + return super.isValid(value, mode); + } + + protected setErrorMessage(error: DialogError): void { + if (this.acceptButton) { + this.acceptButton.disabled = !DialogError.getResult(error); + } + if (this.wasTouched) { + this.errorMessageNode.innerText = DialogError.getMessage(error); + } + } + +} diff --git a/arduino-ide-extension/src/common/protocol/sketches-service.ts b/arduino-ide-extension/src/common/protocol/sketches-service.ts index 5438216a..4aeaaaef 100644 --- a/arduino-ide-extension/src/common/protocol/sketches-service.ts +++ b/arduino-ide-extension/src/common/protocol/sketches-service.ts @@ -55,3 +55,10 @@ export namespace Sketch { return !!arg && 'name' in arg && 'uri' in arg && typeof arg.name === 'string' && typeof arg.uri === 'string'; } } + +export namespace Extensions { + export const MAIN = ['.ino', '.pde']; + export const SOURCE = ['.c', '.cpp', '.s']; + export const ADDITIONAL = ['.h', '.c', '.hpp', '.hh', '.cpp', '.s']; + export const ALL = Array.from(new Set([...MAIN, ...SOURCE, ...ADDITIONAL])); +} diff --git a/arduino-ide-extension/src/node/sketches-service-impl.ts b/arduino-ide-extension/src/node/sketches-service-impl.ts index b779de4a..f7b0c5a0 100644 --- a/arduino-ide-extension/src/node/sketches-service-impl.ts +++ b/arduino-ide-extension/src/node/sketches-service-impl.ts @@ -8,19 +8,13 @@ import * as fs from './fs-extra'; import URI from '@theia/core/lib/common/uri'; import { FileUri, BackendApplicationContribution } from '@theia/core/lib/node'; import { ConfigService } from '../common/protocol/config-service'; -import { SketchesService, Sketch } from '../common/protocol/sketches-service'; +import { SketchesService, Sketch, Extensions } from '../common/protocol/sketches-service'; // As currently implemented on Linux, // the maximum number of symbolic links that will be followed while resolving a pathname is 40 const MAX_FILESYSTEM_DEPTH = 40; -export namespace Extensions { - export const MAIN = ['.ino', '.pde']; - export const SOURCE = ['.c', '.cpp', '.s']; - export const ADDITIONAL = ['.h', '.c', '.hpp', '.hh', '.cpp', '.s']; -} - // TODO: `fs`: use async API @injectable() export class SketchesServiceImpl implements SketchesService, BackendApplicationContribution {