diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts index e0923b99..b167b72b 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts @@ -1,21 +1,34 @@ -import { injectable } from 'inversify'; +import { inject, 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 { CommandRegistry, CommandService } from '@theia/core/lib/common/command'; import { WorkspaceCommandContribution as TheiaWorkspaceCommandContribution, WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands'; -import { Extensions } from '../../../common/protocol'; +import { Sketch } from '../../../common/protocol'; import { WorkspaceInputDialog } from './workspace-input-dialog'; +import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { SaveAsSketch } from '../../contributions/save-as-sketch'; +import { SingleTextInputDialog } from '@theia/core/lib/browser'; @injectable() export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribution { + @inject(SketchesServiceClientImpl) + protected readonly sketchesServiceClient: SketchesServiceClientImpl; + + @inject(CommandService) + protected readonly commandService: CommandService; + registerCommands(registry: CommandRegistry): void { super.registerCommands(registry); registry.unregisterCommand(WorkspaceCommands.NEW_FILE); registry.registerCommand(WorkspaceCommands.NEW_FILE, this.newWorkspaceRootUriAwareCommandHandler({ execute: uri => this.newFile(uri) })); + registry.unregisterCommand(WorkspaceCommands.FILE_RENAME); + registry.registerCommand(WorkspaceCommands.FILE_RENAME, this.newUriAwareCommandHandler({ + execute: uri => this.renameFile(uri) + })); } protected async newFile(uri: URI | undefined): Promise { @@ -57,7 +70,7 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut if (!extension) { return 'Invalid filename.'; // XXX: this should not happen as we forcefully append `.ino` if it's not there. } - if (Extensions.ALL.indexOf(`.${extension}`) === -1) { + if (Sketch.Extensions.ALL.indexOf(`.${extension}`) === -1) { return `.${extension} is not a valid extension.`; } return ''; @@ -77,4 +90,44 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut } return name; } + + protected async renameFile(uri: URI | undefined): Promise { + if (!uri) { + return; + } + const sketch = await this.sketchesServiceClient.currentSketch(); + if (!sketch) { + return; + } + if (uri.toString() === sketch.mainFileUri) { + await this.commandService.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, { execOnlyIfTemp: false, openAfterMove: true }); + return; + } + const parent = await this.getParent(uri); + if (!parent) { + return; + } + const initialValue = uri.path.base; + const dialog = new SingleTextInputDialog({ + title: 'New name for file', + initialValue, + initialSelectionRange: { + start: 0, + end: uri.path.name.length + }, + validate: (name, mode) => { + if (initialValue === name && mode === 'preview') { + return false; + } + return this.validateFileName(name, parent, false); + } + }); + const fileName = await dialog.open(); + if (fileName) { + const oldUri = uri; + const newUri = uri.parent.resolve(fileName); + this.fileSystem.move(oldUri.toString(), newUri.toString()); + } + } + } diff --git a/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts b/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts index 009c9645..266edd9d 100644 --- a/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts +++ b/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts @@ -1,5 +1,4 @@ import { inject, injectable } from 'inversify'; -import URI from '@theia/core/lib/common/uri'; import { notEmpty } from '@theia/core/lib/common/objects'; import { FileSystem } from '@theia/filesystem/lib/common'; import { MessageService } from '@theia/core/lib/common/message-service'; @@ -35,7 +34,7 @@ export class SketchesServiceClientImpl { async currentSketchFile(): Promise { const sketch = await this.currentSketch(); if (sketch) { - const uri = new URI(sketch.uri).resolve(`${sketch.name}.ino`).toString(); + const uri = sketch.mainFileUri; const exists = await this.fileSystem.exists(uri); if (!exists) { this.messageService.warn(`Could not find sketch file: ${uri}`); diff --git a/arduino-ide-extension/src/common/protocol/sketches-service.ts b/arduino-ide-extension/src/common/protocol/sketches-service.ts index 4aeaaaef..1ceac6d2 100644 --- a/arduino-ide-extension/src/common/protocol/sketches-service.ts +++ b/arduino-ide-extension/src/common/protocol/sketches-service.ts @@ -54,11 +54,15 @@ export namespace Sketch { export function is(arg: any): arg is 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])); + } + export function isInSketch(uri: string, sketch: Sketch): boolean { + const { mainFileUri, otherSketchFileUris, additionalFileUris } = sketch; + return [mainFileUri, ...otherSketchFileUris, additionalFileUris].indexOf(uri) !== -1; + } } -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 f7b0c5a0..13402873 100644 --- a/arduino-ide-extension/src/node/sketches-service-impl.ts +++ b/arduino-ide-extension/src/node/sketches-service-impl.ts @@ -8,7 +8,7 @@ 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, Extensions } from '../common/protocol/sketches-service'; +import { SketchesService, Sketch } from '../common/protocol/sketches-service'; // As currently implemented on Linux, @@ -81,7 +81,7 @@ export class SketchesServiceImpl implements SketchesService, BackendApplicationC if (stat.isDirectory()) { sketchFolder = sketchPath; // Allowed extensions are .ino and .pde (but not both) - for (const extension of Extensions.MAIN) { + for (const extension of Sketch.Extensions.MAIN) { const candidateSketchFile = path.join(sketchPath, `${path.basename(sketchPath)}${extension}`); const candidateExists = await fs.exists(candidateSketchFile); if (candidateExists) { @@ -137,8 +137,8 @@ export class SketchesServiceImpl implements SketchesService, BackendApplicationC return undefined; } const ext = path.extname(fsPath); - const isMain = Extensions.MAIN.indexOf(ext) !== -1; - const isAdditional = Extensions.ADDITIONAL.indexOf(ext) !== -1; + const isMain = Sketch.Extensions.MAIN.indexOf(ext) !== -1; + const isAdditional = Sketch.Extensions.ADDITIONAL.indexOf(ext) !== -1; if (!isMain && !isAdditional) { return undefined; } @@ -177,11 +177,11 @@ export class SketchesServiceImpl implements SketchesService, BackendApplicationC const otherSketchFiles: string[] = []; for (const p of Array.from(paths)) { const ext = path.extname(p); - if (Extensions.MAIN.indexOf(ext) !== -1) { + if (Sketch.Extensions.MAIN.indexOf(ext) !== -1) { if (path.dirname(p) === sketchFolderPath) { otherSketchFiles.push(p); } - } else if (Extensions.ADDITIONAL.indexOf(ext) !== -1) { + } else if (Sketch.Extensions.ADDITIONAL.indexOf(ext) !== -1) { // XXX: this is a caveat with the CLI, we do not know the `buildPath`. // https://github.com/arduino/arduino-cli/blob/0483882b4f370c288d5318913657bbaa0325f534/arduino/sketch/sketch.go#L108-L110 additionalFiles.push(p);