Aligned the New and Save As... with the Java IDE.

From now on, sketches are created in the temp folder,
and will be moved to the `directories.user` location
when the user performs a manual `Save`.

A new sketch can be created with the `CtrlCmd+N` binding.

Closes: arduino/arduino-pro-ide#260
Closes: arduino/arduino-pro-ide#261

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta
2020-07-14 17:18:46 +02:00
parent 3082b3d643
commit 2bd9eef146
15 changed files with 341 additions and 70 deletions

View File

@@ -7,22 +7,22 @@ export namespace ArduinoCommands {
export const VERIFY: Command = {
id: 'arduino-verify',
label: 'Verify Sketch'
}
};
export const VERIFY_TOOLBAR: Command = {
id: 'arduino-verify-toolbar',
}
};
export const UPLOAD: Command = {
id: 'arduino-upload',
label: 'Upload Sketch'
}
};
export const UPLOAD_TOOLBAR: Command = {
id: 'arduino-upload-toolbar',
}
};
export const TOGGLE_COMPILE_FOR_DEBUG: Command = {
id: "arduino-toggle-compile-for-debug"
}
id: 'arduino-toggle-compile-for-debug'
};
export const SHOW_OPEN_CONTEXT_MENU: Command = {
id: 'arduino-show-open-context-menu',
@@ -32,39 +32,46 @@ export namespace ArduinoCommands {
export const OPEN_FILE_NAVIGATOR: Command = {
id: 'arduino-open-file-navigator'
}
};
export const OPEN_SKETCH: Command = {
id: 'arduino-open-file'
}
};
/**
* Unlike `OPEN_SKETCH`, it opens all files from a sketch folder. (ino, cpp, etc...)
*/
export const OPEN_SKETCH_FILES: Command = {
id: 'arduino-open-sketch-files'
}
};
export const SAVE_SKETCH: Command = {
id: 'arduino-save-file'
}
id: 'arduino-save-sketch'
};
export const SAVE_SKETCH_AS: Command = {
id: 'arduino-save-sketch-as'
};
export const NEW_SKETCH: Command = {
id: 'arduino-new-sketch',
label: 'New Sketch',
category
}
};
export const NEW_SKETCH_TOOLBAR: Command = {
id: 'arduino-new-sketch-toolbar'
};
export const OPEN_BOARDS_DIALOG: Command = {
id: 'arduino-open-boards-dialog'
}
};
export const TOGGLE_ADVANCED_MODE: Command = {
id: 'arduino-toggle-advanced-mode'
}
};
export const TOGGLE_ADVANCED_MODE_TOOLBAR: Command = {
id: "arduino-toggle-advanced-mode-toolbar"
}
id: 'arduino-toggle-advanced-mode-toolbar'
};
export const OPEN_CLI_CONFIG: Command = {
id: 'arduino-open-cli-config',

View File

@@ -1,4 +1,6 @@
import * as React from 'react';
import * as dateFormat from 'dateformat';
import { remote } from 'electron';
import { injectable, inject, postConstruct } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
@@ -8,8 +10,8 @@ import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/li
import { BoardsService, BoardsServiceClient, CoreService, Sketch, SketchesService, ToolOutputServiceClient } from '../common/protocol';
import { ArduinoCommands } from './arduino-commands';
import { BoardsServiceClientImpl } from './boards/boards-service-client-impl';
import { WorkspaceRootUriAwareCommandHandler, WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
import { SelectionService, MenuContribution, MenuModelRegistry, MAIN_MENU_BAR, MenuPath } from '@theia/core';
import { WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
import { SelectionService, MenuContribution, MenuModelRegistry, MAIN_MENU_BAR, MenuPath, notEmpty } from '@theia/core';
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
import { EditorManager, EditorMainMenu } from '@theia/editor/lib/browser';
import {
@@ -44,6 +46,7 @@ import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
import { ConfigService } from '../common/protocol/config-service';
import { BoardsConfigStore } from './boards/boards-config-store';
import { MainMenuManager } from './menu/main-menu-manager';
import { FileSystemExt } from '../common/protocol/filesystem-ext';
export namespace ArduinoMenus {
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
@@ -152,6 +155,9 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
@inject(MainMenuManager)
protected readonly mainMenuManager: MainMenuManager;
@inject(FileSystemExt)
protected readonly fileSystemExt: FileSystemExt;
protected application: FrontendApplication;
protected wsSketchCount: number = 0; // TODO: this does not belong here, does it?
@@ -194,24 +200,32 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
registry.registerItem({
id: ArduinoCommands.VERIFY.id,
command: ArduinoCommands.VERIFY_TOOLBAR.id,
tooltip: 'Verify'
tooltip: 'Verify',
priority: 1
});
registry.registerItem({
id: ArduinoCommands.UPLOAD.id,
command: ArduinoCommands.UPLOAD_TOOLBAR.id,
tooltip: 'Upload'
tooltip: 'Upload',
priority: 2
});
registry.registerItem({
id: ArduinoCommands.NEW_SKETCH.id,
command: ArduinoCommands.NEW_SKETCH_TOOLBAR.id,
tooltip: 'New',
priority: 4 // Note: priority 3 was reserved by debug.
});
registry.registerItem({
id: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id,
command: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id,
tooltip: 'Open',
priority: 2
priority: 5
});
registry.registerItem({
id: ArduinoCommands.SAVE_SKETCH.id,
command: ArduinoCommands.SAVE_SKETCH.id,
tooltip: 'Save',
priority: 2
priority: 6
});
registry.registerItem({
id: BoardsToolBarItem.TOOLBAR_ID,
@@ -220,14 +234,13 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
commands={this.commandRegistry}
boardsServiceClient={this.boardsServiceClientImpl} />,
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
priority: 2
priority: 6
});
registry.registerItem({
id: 'toggle-serial-monitor',
command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR,
tooltip: 'Toggle Serial Monitor'
tooltip: 'Serial Monitor'
});
registry.registerItem({
id: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
command: ArduinoCommands.TOGGLE_ADVANCED_MODE_TOOLBAR.id,
@@ -335,21 +348,65 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
}
});
registry.registerCommand(ArduinoCommands.NEW_SKETCH, new WorkspaceRootUriAwareCommandHandler(this.workspaceService, this.selectionService, {
execute: async uri => {
try {
// hack: sometimes we don't get the workspace root, but the currently active file: correct for that
if (uri.path.ext !== "") {
uri = uri.withPath(uri.path.dir.dir);
}
registry.registerCommand(ArduinoCommands.SAVE_SKETCH_AS, {
execute: async ({ execOnlyIfTemp }: { execOnlyIfTemp: boolean } = { execOnlyIfTemp: false }) => {
const sketches = (await Promise.all(this.workspaceService.tryGetRoots().map(({ uri }) => this.sketchService.getSketchFolder(uri)))).filter(notEmpty);
if (!sketches.length) {
return;
}
if (sketches.length > 1) {
console.log(`Multiple sketch folders were found in the workspace. Falling back to the first one. Sketch folders: ${JSON.stringify(sketches)}`);
}
const sketch = sketches[0];
const isTemp = await this.sketchService.isTemp(sketch);
if (!isTemp && !!execOnlyIfTemp) {
return;
}
const sketch = await this.sketchService.createNewSketch(uri.toString());
// If target does not exist, propose a `directories.user`/${sketch.name} path
// If target exists, propose `directories.user`/${sketch.name}_copy_${yyyymmddHHMMss}
const sketchDirUri = new URI((await this.configService.getConfiguration()).sketchDirUri);
const exists = await this.fileSystem.exists(sketchDirUri.resolve(sketch.name).toString());
const defaultUri = exists
? sketchDirUri.resolve(sketchDirUri.resolve(`${sketch.name}_copy_${dateFormat(new Date(), 'yyyymmddHHMMss')}`).toString())
: sketchDirUri.resolve(sketch.name);
const defaultPath = await this.fileSystem.getFsPath(defaultUri.toString())!;
const fsPath = await new Promise<string | undefined>(resolve => {
remote.dialog.showSaveDialog({
title: 'Save sketch folder as...',
defaultPath
}, (filename) => resolve(filename));
});
if (!fsPath) { // Canceled
return;
}
const destinationUri = await this.fileSystemExt.getUri(fsPath);
if (!destinationUri) {
return;
}
const workspaceUri = await this.sketchService.copy(sketch, { destinationUri });
if (workspaceUri) {
this.workspaceService.open(new URI(workspaceUri));
}
}
});
registry.registerCommand(ArduinoCommands.NEW_SKETCH, {
execute: async () => {
try {
const sketch = await this.sketchService.createNewSketch();
this.workspaceService.open(new URI(sketch.uri));
} catch (e) {
await this.messageService.error(e.toString());
}
}
}));
});
registry.registerCommand(ArduinoCommands.NEW_SKETCH_TOOLBAR, {
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
execute: async () => {
return registry.executeCommand(ArduinoCommands.NEW_SKETCH.id);
}
});
registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, {
execute: async () => {
@@ -481,7 +538,6 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(TerminalMenus.TERMINAL));
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(CommonMenus.VIEW));
}
registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch');
registry.registerMenuAction(ArduinoMenus.SKETCH, {
commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id,
@@ -517,6 +573,11 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
registry.registerMenuAction([...CommonMenus.FILE_SETTINGS_SUBMENU, '3_settings_cli'], {
commandId: ArduinoCommands.OPEN_CLI_CONFIG.id
});
registry.registerMenuAction(CommonMenus.FILE_SAVE, {
commandId: ArduinoCommands.SAVE_SKETCH_AS.id,
label: 'Save As...'
});
}
protected getMenuId(menuPath: string[]): string {
@@ -526,13 +587,22 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
}
registerKeybindings(keybindings: KeybindingRegistry): void {
keybindings.unregisterKeybinding('ctrlcmd+n'); // Unregister the keybinding for `New File`, will be used by `New Sketch`. (eclipse-theia/theia#8170)
keybindings.registerKeybinding({
command: ArduinoCommands.VERIFY.id,
keybinding: 'ctrlcmd+alt+v'
keybinding: 'CtrlCmd+Alt+V'
});
keybindings.registerKeybinding({
command: ArduinoCommands.UPLOAD.id,
keybinding: 'ctrlcmd+alt+u'
keybinding: 'CtrlCmd+Alt+U'
});
keybindings.registerKeybinding({
command: ArduinoCommands.NEW_SKETCH.id,
keybinding: 'CtrlCmd+N'
});
keybindings.registerKeybinding({
command: ArduinoCommands.SAVE_SKETCH_AS.id,
keybinding: 'CtrlCmd+Shift+S'
});
}

View File

@@ -78,6 +78,9 @@ import { CoreServiceClientImpl } from './core-service-client-impl';
import { BoardsDetailsMenuUpdater } from './boards/boards-details-menu-updater';
import { BoardsConfigStore } from './boards/boards-config-store';
import { ILogger } from '@theia/core';
import { FileSystemExt, FileSystemExtPath } from '../common/protocol/filesystem-ext';
import { WorkspaceFrontendContribution } from '@theia/workspace/lib/browser';
import { ArduinoWorkspaceFrontendContribution } from './customization/arduino-workspace-frontend-contribution';
const ElementQueries = require('css-element-queries/src/ElementQueries');
@@ -254,6 +257,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
rebind(ScmContribution).to(ArduinoScmContribution).inSingletonScope();
rebind(SearchInWorkspaceFrontendContribution).to(ArduinoSearchInWorkspaceContribution).inSingletonScope();
rebind(FrontendApplication).to(ArduinoFrontendApplication).inSingletonScope();
rebind(WorkspaceFrontendContribution).to(ArduinoWorkspaceFrontendContribution).inSingletonScope();
// Show a disconnected status bar, when the daemon is not available
bind(ArduinoApplicationConnectionStatusContribution).toSelf().inSingletonScope();
@@ -293,4 +297,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
WebSocketConnectionProvider.createProxy(context.container, ArduinoDaemonPath, client);
return client;
}).inSingletonScope();
// File-system extension
bind(FileSystemExt).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, FileSystemExtPath)).inSingletonScope();
});

View File

@@ -47,9 +47,7 @@ export class ArduinoWorkspaceService extends WorkspaceService {
await this.server.setMostRecentlyUsedWorkspace(uri);
return toOpen.uri;
}
const { sketchDirUri } = (await this.configService.getConfiguration());
this.logger.info(`No valid workspace URI found. Creating new sketch in ${sketchDirUri}`)
return (await this.sketchService.createNewSketch(sketchDirUri)).uri;
return (await this.sketchService.createNewSketch()).uri;
} catch (err) {
this.logger.fatal(`Failed to determine the sketch directory: ${err}`)
this.messageService.error(

View File

@@ -1,7 +1,10 @@
import { injectable, inject } from 'inversify';
import { ApplicationShell, Widget, Saveable, FocusTracker, Message } from '@theia/core/lib/browser';
import { EditorWidget } from '@theia/editor/lib/browser';
import { injectable, inject } from 'inversify';
import { EditorMode } from '../editor-mode';
import { CommandService } from '@theia/core';
import { ArduinoCommands } from '../arduino-commands';
@injectable()
export class ArduinoApplicationShell extends ApplicationShell {
@@ -9,6 +12,9 @@ export class ArduinoApplicationShell extends ApplicationShell {
@inject(EditorMode)
protected readonly editorMode: EditorMode;
@inject(CommandService)
protected readonly commandService: CommandService;
protected refreshBottomPanelToggleButton() {
if (this.editorMode.proMode) {
super.refreshBottomPanelToggleButton();
@@ -34,6 +40,11 @@ export class ArduinoApplicationShell extends ApplicationShell {
}
}
async save(): Promise<void> {
await super.save();
await this.commandService.executeCommand(ArduinoCommands.SAVE_SKETCH_AS.id, { execOnlyIfTemp: true });
}
private disableClose(widget: Widget | undefined): void {
if (widget instanceof EditorWidget) {
const onCloseRequest = (_: Message) => {

View File

@@ -0,0 +1,62 @@
import { injectable } from 'inversify';
import { isOSX } from '@theia/core/lib/common/os';
import { environment } from '@theia/application-package/lib/environment';
import { CommonMenus } from '@theia/core/lib/browser';
import { CommandRegistry } from '@theia/core/lib/common/command';
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
import { WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
import { WorkspaceFrontendContribution } from '@theia/workspace/lib/browser/workspace-frontend-contribution';
// TODO: https://github.com/eclipse-theia/theia/issues/8175
@injectable()
export class ArduinoWorkspaceFrontendContribution extends WorkspaceFrontendContribution {
registerCommands(registry: CommandRegistry): void {
super.registerCommands(registry);
registry.unregisterCommand(WorkspaceCommands.SAVE_AS);
registry.unregisterCommand(WorkspaceCommands.OPEN_FOLDER);
}
registerMenus(registry: MenuModelRegistry): void {
if (isOSX || !environment.electron.is()) {
registry.registerMenuAction(CommonMenus.FILE_OPEN, {
commandId: WorkspaceCommands.OPEN.id,
order: 'a00'
});
}
if (!isOSX && environment.electron.is()) {
registry.registerMenuAction(CommonMenus.FILE_OPEN, {
commandId: WorkspaceCommands.OPEN_FILE.id,
label: `${WorkspaceCommands.OPEN_FILE.dialogLabel}...`,
order: 'a01'
});
registry.registerMenuAction(CommonMenus.FILE_OPEN, {
commandId: WorkspaceCommands.OPEN_FOLDER.id,
label: `${WorkspaceCommands.OPEN_FOLDER.dialogLabel}...`,
order: 'a02'
});
}
registry.registerMenuAction(CommonMenus.FILE_OPEN, {
commandId: WorkspaceCommands.OPEN_WORKSPACE.id,
order: 'a10'
});
registry.registerMenuAction(CommonMenus.FILE_OPEN, {
commandId: WorkspaceCommands.OPEN_RECENT_WORKSPACE.id,
order: 'a20'
});
registry.registerMenuAction(CommonMenus.FILE_OPEN, {
commandId: WorkspaceCommands.SAVE_WORKSPACE_AS.id,
order: 'a30'
});
registry.registerMenuAction(CommonMenus.FILE_CLOSE, {
commandId: WorkspaceCommands.CLOSE.id
});
// `Save As`
// menus.registerMenuAction(CommonMenus.FILE_SAVE, {
// commandId: WorkspaceCommands.SAVE_AS.id,
// });
}
}

View File

@@ -42,7 +42,7 @@
mask-size: 800%;
}
.arduino-save-file-icon {
.arduino-save-sketch-icon {
-webkit-mask-position: 59px -4px;
mask-position: 59px -4px;
}
@@ -57,6 +57,11 @@
mask-position: 156px -4px;
}
.arduino-new-sketch-icon {
-webkit-mask-position: 124px -4px;
mask-position: 124px -4px;
}
.arduino-show-open-context-menu-icon {
-webkit-mask-position: 92px -4px;
mask-position: 92px -4px;