Use service to load sketches

Signed-off-by: jbicker <jan.bicker@typefox.io>
This commit is contained in:
jbicker 2019-06-18 18:14:59 +02:00
parent 0c937212e2
commit 9d3cbf2ea0
6 changed files with 162 additions and 41 deletions

View File

@ -2,7 +2,8 @@ import { injectable, inject } from "inversify";
import { MenuContribution, MenuModelRegistry, MenuPath, CommandRegistry, Command } from "@theia/core";
import { CommonMenus } from "@theia/core/lib/browser";
import { ArduinoCommands } from "./arduino-commands";
import URI from "@theia/core/lib/common/uri";
import { SketchesService, Sketch } from "../common/protocol/sketches-service";
import { AWorkspaceService } from "./arduino-workspace-service";
export namespace ArduinoOpenSketchContextMenu {
export const PATH: MenuPath = ['arduino-open-sketch-context-menu'];
@ -11,29 +12,46 @@ export namespace ArduinoOpenSketchContextMenu {
export const EXAMPLE_SKETCHES_GROUP: MenuPath = [...PATH, '3_examples'];
}
export interface SketchMenuEntry {
name: string,
uri: URI
}
@injectable()
export class ArduinoFileMenuContribution implements MenuContribution {
@inject(CommandRegistry)
protected readonly commands: CommandRegistry;
@inject(SketchesService)
protected readonly sketches: SketchesService;
protected async getWorkspaceSketches(): Promise<SketchMenuEntry[]> {
return [
{
name: 'foo',
uri: new URI('this/is/a/test/uri/foo')
},
{
name: 'bar',
uri: new URI('this/is/a/test/uri/bar')
constructor(
@inject(AWorkspaceService) protected readonly workspaceService: AWorkspaceService,
@inject(MenuModelRegistry) protected readonly menuRegistry: MenuModelRegistry) {
workspaceService.onWorkspaceChanged(() => {
if (this.workspaceService.workspace) {
this.registerSketchesInMenu(menuRegistry);
}
]
})
}
protected registerSketchesInMenu(registry: MenuModelRegistry) {
this.getWorkspaceSketches().then(sketches => {
sketches.forEach(sketch => {
const command: Command = {
id: 'openSketch' + sketch.name
}
this.commands.registerCommand(command, {
execute: () => this.commands.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch)
});
registry.registerMenuAction(ArduinoOpenSketchContextMenu.WS_SKETCHES_GROUP, {
commandId: command.id,
label: sketch.name
});
})
})
}
protected async getWorkspaceSketches(): Promise<Sketch[]> {
const sketches = this.sketches.getSketches(this.workspaceService.workspace);
return sketches;
}
registerMenus(registry: MenuModelRegistry) {
@ -45,23 +63,5 @@ export class ArduinoFileMenuContribution implements MenuContribution {
commandId: ArduinoCommands.OPEN_FILE_NAVIGATOR.id,
label: 'Open...'
});
this.getWorkspaceSketches().then(sketches => {
sketches.forEach(sketch => {
const command: Command = {
id: 'openSketch' + sketch.name
}
this.commands.registerCommand(command, {
execute: () => this.commands.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch)
});
registry.registerMenuAction(ArduinoOpenSketchContextMenu.WS_SKETCHES_GROUP, {
commandId: command.id,
label: sketch.name
});
})
})
}
}

View File

@ -24,7 +24,9 @@ import { EditorManager } from '@theia/editor/lib/browser';
import { open, ContextMenuRenderer, OpenerService, Widget } from '@theia/core/lib/browser';
import { OpenFileDialogProps, FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
import { FileSystem } from '@theia/filesystem/lib/common';
import { ArduinoOpenSketchContextMenu, SketchMenuEntry } from './arduino-file-menu';
import { ArduinoOpenSketchContextMenu } from './arduino-file-menu';
import { Sketch, SketchesService } from '../common/protocol/sketches-service';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
@injectable()
export class ArduinoFrontendContribution extends DefaultFrontendApplicationContribution implements TabBarToolbarContribution, CommandContribution {
@ -68,15 +70,22 @@ export class ArduinoFrontendContribution extends DefaultFrontendApplicationContr
@inject(ContextMenuRenderer)
protected readonly contextMenuRenderer: ContextMenuRenderer;
@inject(FileDialogService)
@inject(FileDialogService)
protected readonly fileDialogService: FileDialogService;
@inject(FileSystem)
@inject(FileSystem)
protected readonly fileSystem: FileSystem;
@inject(OpenerService)
@inject(OpenerService)
protected readonly openerService: OpenerService;
@inject(SketchesService)
protected readonly sketches: SketchesService;
@inject(WindowService)
protected readonly windowService: WindowService;
@postConstruct()
protected async init(): Promise<void> {
// This is a hack. Otherwise, the backend services won't bind.
@ -165,7 +174,7 @@ export class ArduinoFrontendContribution extends DefaultFrontendApplicationContr
isEnabled: widget => this.isArduinoToolbar(widget),
execute: async (widget: Widget, event: React.MouseEvent<HTMLElement>) => {
const el = (event.target as HTMLElement).parentElement;
if(el) {
if (el) {
this.contextMenuRenderer.render(ArduinoOpenSketchContextMenu.PATH, {
x: el.getBoundingClientRect().left,
y: el.getBoundingClientRect().top + el.offsetHeight
@ -179,8 +188,24 @@ export class ArduinoFrontendContribution extends DefaultFrontendApplicationContr
})
registry.registerCommand(ArduinoCommands.OPEN_SKETCH, {
isEnabled: () => true,
execute: (sketch: SketchMenuEntry) => {
console.log("OPEN SOME SKETCH", sketch);
execute: async (sketch: Sketch) => {
// const url = new URL(window.location.href);
// if (this.workspaceService.workspace) {
// const wsUri = this.workspaceService.workspace.uri;
// const path = new URI(wsUri).path;
// url.hash = path + '?sketch=' + sketch.name
// }
// this.windowService.openNewWindow(url.toString());
const fileStat = await this.fileSystem.getFileStat(sketch.uri);
if (fileStat) {
const sketchFiles = await this.sketches.getSketchFiles(fileStat);
sketchFiles.forEach(sketchFile => {
const uri = new URI(sketchFile);
this.editorManager.open(uri);
});
}
}
})
registry.registerCommand(ArduinoCommands.NEW_SKETCH, new WorkspaceRootUriAwareCommandHandler(this.workspaceService, this.selectionService, {

View File

@ -12,6 +12,7 @@ import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
import { ArduinoLanguageGrammarContribution } from './language/arduino-language-grammar-contribution';
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
import { BoardsService, BoardsServicePath } from '../common/protocol/boards-service';
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
import { LibraryListWidgetFrontendContribution } from './library/list-widget-frontend-contribution';
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
import { BoardsListWidget } from './boards/boards-list-widget';
@ -70,6 +71,9 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
}));
bind(FrontendApplicationContribution).toService(LibraryListWidgetFrontendContribution);
// Sketch list service
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
// Boards Notification service for updating boards list
// TODO (post-PoC): move this to boards service/backend
bind(BoardsNotificationService).toSelf().inSingletonScope();
@ -106,9 +110,11 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
container.get(CoreService);
container.get(LibraryService);
container.get(BoardsService);
container.get(SketchesService);
return workspaceServiceExt;
});
bind(AWorkspaceService).toSelf().inSingletonScope();
rebind(WorkspaceService).to(AWorkspaceService).inSingletonScope();
bind(SketchFactory).toSelf().inSingletonScope();

View File

@ -0,0 +1,13 @@
import { FileStat } from "@theia/filesystem/lib/common";
export const SketchesServicePath = '/services/sketches-service';
export const SketchesService = Symbol('SketchesService');
export interface SketchesService {
getSketches(fileStat?: FileStat): Promise<Sketch[]>
getSketchFiles(fileStat: FileStat): Promise<string[]>
}
export interface Sketch {
name: string;
uri: string
}

View File

@ -17,6 +17,8 @@ import { ConnectionHandler, JsonRpcConnectionHandler } from '@theia/core';
import { ToolOutputServiceServerImpl } from './tool-output-service-impl';
import { DefaultWorkspaceServerExt } from './default-workspace-server-ext';
import { WorkspaceServer } from '@theia/workspace/lib/common';
import { SketchesServiceImpl } from './sketches-service-impl';
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ArduinoDaemon).toSelf().inSingletonScope();
@ -30,6 +32,14 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
});
bind(ConnectionContainerModule).toConstantValue(libraryServiceConnectionModule);
// Sketches service
const sketchesServiceConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
bind(SketchesServiceImpl).toSelf().inSingletonScope();
bind(SketchesService).toService(SketchesServiceImpl);
bindBackendService(SketchesServicePath, SketchesService);
});
bind(ConnectionContainerModule).toConstantValue(sketchesServiceConnectionModule);
// Boards service
const boardsServiceConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
bind(BoardsServiceImpl).toSelf().inSingletonScope();

View File

@ -0,0 +1,67 @@
import { injectable } from "inversify";
import { SketchesService, Sketch } from "../common/protocol/sketches-service";
import URI from "@theia/core/lib/common/uri";
import { FileStat } from "@theia/filesystem/lib/common";
import * as fs from 'fs';
import * as path from 'path';
export const ALLOWED_FILE_EXTENSIONS = [".c", ".cpp", ".h", ".hh", ".hpp", ".s", ".pde", ".ino"];
@injectable()
export class SketchesServiceImpl implements SketchesService {
async getSketches(fileStat?: FileStat): Promise<Sketch[]> {
const sketches: Sketch[] = [];
if (fileStat && fileStat.isDirectory) {
const sketchFolderPath = this.getPath(fileStat);
const files = fs.readdirSync(sketchFolderPath);
files.forEach(file => {
const filePath = path.join(sketchFolderPath, file);
if (this.isSketchFolder(filePath, file)) {
sketches.push({
name: file,
uri: filePath
});
}
});
}
return sketches;
}
/**
* Return all allowed files.
* File extensions: "c", "cpp", "h", "hh", "hpp", "s", "pde", "ino"
*/
async getSketchFiles(sketchDir: FileStat): Promise<string[]> {
const files: string[] = [];
const sketchDirPath = this.getPath(sketchDir);
const sketchDirContents = fs.readdirSync(sketchDirPath);
sketchDirContents.forEach(fileName => {
const filePath = path.join(sketchDirPath, fileName);
if (fs.existsSync(filePath) &&
fs.lstatSync(filePath).isFile() &&
ALLOWED_FILE_EXTENSIONS.indexOf(path.extname(filePath)) !== -1) {
files.push(filePath);
}
});
return files;
}
protected isSketchFolder(path: string, name: string): boolean {
if (fs.existsSync(path) && fs.lstatSync(path).isDirectory()) {
const files = fs.readdirSync(path);
for (let i = 0; i < files.length; i++) {
if (files[i] === name + '.ino') {
return true;
}
}
}
return false;
}
protected getPath(fileStat: FileStat) {
const fileStatUri = fileStat.uri;
const uri = new URI(fileStatUri);
return uri.path.toString();
}
}