diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index f1cc0e84..524b15ce 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -11,6 +11,7 @@ import { ArduinoCommands } from './arduino-commands'; import { ConnectedBoards } from './components/connected-boards'; import { CoreService } from '../common/protocol/core-service'; import { WorkspaceServiceExt } from './workspace-service-ext'; +import { ToolOutputServiceClient } from '../common/protocol/tool-output-service'; @injectable() @@ -28,6 +29,9 @@ export class ArduinoFrontendContribution extends DefaultFrontendApplicationContr @inject(WorkspaceServiceExt) protected readonly workspaceServiceExt: WorkspaceServiceExt; + @inject(ToolOutputServiceClient) + protected readonly toolOutputServiceClient: ToolOutputServiceClient; + @postConstruct() protected async init(): Promise { // This is a hack. Otherwise, the backend services won't bind. diff --git a/arduino-ide-extension/src/browser/arduino-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-frontend-module.ts index 0d4b3f1c..5ce6fa32 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-frontend-module.ts @@ -17,8 +17,11 @@ import { BoardsListWidget } from './boards/boards-list-widget'; import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution'; import { WorkspaceServiceExt, WorkspaceServiceExtPath } from './workspace-service-ext'; import { WorkspaceServiceExtImpl } from './workspace-service-ext-impl'; +import { ToolOutputServiceClient } from '../common/protocol/tool-output-service'; import '../../src/browser/style/index.css'; +import { ToolOutputService } from '../common/protocol/tool-output-service'; +import { ToolOutputServiceClientImpl } from './tool-output/client-service-impl'; export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => { // Commands and toolbar items @@ -58,6 +61,14 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un .toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, CoreServicePath)) .inSingletonScope(); + // Tool output service client + bind(ToolOutputServiceClientImpl).toSelf().inSingletonScope(); + bind(ToolOutputServiceClient).toDynamicValue(context => { + const client = context.container.get(ToolOutputServiceClientImpl); + WebSocketConnectionProvider.createProxy(context.container, ToolOutputService.SERVICE_PATH, client); + return client; + }).inSingletonScope(); + // The workspace service extension bind(WorkspaceServiceExt).to(WorkspaceServiceExtImpl).inSingletonScope().onActivation(({ container }, workspaceServiceExt) => { WebSocketConnectionProvider.createProxy(container, WorkspaceServiceExtPath, workspaceServiceExt); diff --git a/arduino-ide-extension/src/browser/style/list-widget.css b/arduino-ide-extension/src/browser/style/list-widget.css index 54a041dc..23466806 100644 --- a/arduino-ide-extension/src/browser/style/list-widget.css +++ b/arduino-ide-extension/src/browser/style/list-widget.css @@ -52,6 +52,7 @@ padding: 4px; font-size: 10px; font-weight: bold; + max-height: calc(1em + 4px); } .component-list-item .footer { diff --git a/arduino-ide-extension/src/browser/tool-output/client-service-impl.ts b/arduino-ide-extension/src/browser/tool-output/client-service-impl.ts new file mode 100644 index 00000000..0a81a399 --- /dev/null +++ b/arduino-ide-extension/src/browser/tool-output/client-service-impl.ts @@ -0,0 +1,16 @@ +import { ToolOutputServiceClient } from "../../common/protocol/tool-output-service"; +import { injectable, inject } from "inversify"; +import { OutputChannelManager } from "@theia/output/lib/common/output-channel"; + +@injectable() +export class ToolOutputServiceClientImpl implements ToolOutputServiceClient { + + @inject(OutputChannelManager) + protected readonly outputChannelManager: OutputChannelManager; + + onNewOutput(tool: string, chunk: string): void { + const channel = this.outputChannelManager.getChannel(`Arduino: ${tool}`); + channel.append(chunk); + } + +} \ No newline at end of file diff --git a/arduino-ide-extension/src/common/protocol/core-service.ts b/arduino-ide-extension/src/common/protocol/core-service.ts index 2fb0d1c5..3e443264 100644 --- a/arduino-ide-extension/src/common/protocol/core-service.ts +++ b/arduino-ide-extension/src/common/protocol/core-service.ts @@ -1,7 +1,7 @@ export const CoreServicePath = '/services/core-service'; export const CoreService = Symbol('CoreService'); export interface CoreService { - compile(options: CoreService.Compile.Options): Promise; + compile(options: CoreService.Compile.Options): Promise; upload(): Promise; } diff --git a/arduino-ide-extension/src/common/protocol/tool-output-service.ts b/arduino-ide-extension/src/common/protocol/tool-output-service.ts new file mode 100644 index 00000000..21e26064 --- /dev/null +++ b/arduino-ide-extension/src/common/protocol/tool-output-service.ts @@ -0,0 +1,16 @@ +import { JsonRpcServer } from "@theia/core"; + +export const ToolOutputServiceServer = Symbol("ToolOutputServiceServer"); +export interface ToolOutputServiceServer extends JsonRpcServer { + publishNewOutput(tool: string, chunk: string): void; + disposeClient(client: ToolOutputServiceClient): void; +} + +export const ToolOutputServiceClient = Symbol("ToolOutputServiceClient"); +export interface ToolOutputServiceClient { + onNewOutput(tool: string, chunk: string): void; +} + +export namespace ToolOutputService { + export const SERVICE_PATH = "/tool-output-service"; +} diff --git a/arduino-ide-extension/src/node/arduino-backend-module.ts b/arduino-ide-extension/src/node/arduino-backend-module.ts index df3f7b83..fe9c1b42 100644 --- a/arduino-ide-extension/src/node/arduino-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-backend-module.ts @@ -12,6 +12,9 @@ import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connec import { WorkspaceServiceExtPath, WorkspaceServiceExt } from '../browser/workspace-service-ext'; import { CoreClientProviderImpl } from './core-client-provider-impl'; import { CoreClientProviderPath, CoreClientProvider } from './core-client-provider'; +import { ToolOutputService, ToolOutputServiceClient, ToolOutputServiceServer } from '../common/protocol/tool-output-service'; +import { ConnectionHandler, JsonRpcConnectionHandler } from '@theia/core'; +import { ToolOutputServiceServerImpl } from './tool-output-service-impl'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ArduinoDaemon).toSelf().inSingletonScope(); @@ -51,6 +54,17 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { }); bind(ConnectionContainerModule).toConstantValue(connectionConnectionModule); + // Tool output service -> feedback from the daemon, compile and flash + bind(ToolOutputServiceServer).to(ToolOutputServiceServerImpl).inSingletonScope(); + bind(ConnectionHandler).toDynamicValue(context => + new JsonRpcConnectionHandler(ToolOutputService.SERVICE_PATH, client => { + const server = context.container.get(ToolOutputServiceServer); + server.setClient(client); + client.onDidCloseConnection(() => server.disposeClient(client)); + return server; + }) + ).inSingletonScope(); + // Bind the workspace service extension to the backend per Theia connection. // So that we can access the workspace roots of the frontend. const workspaceServiceExtConnectionModule = ConnectionContainerModule.create(({ bindFrontendService }) => { diff --git a/arduino-ide-extension/src/node/arduino-daemon.ts b/arduino-ide-extension/src/node/arduino-daemon.ts index 490603dc..87937a09 100644 --- a/arduino-ide-extension/src/node/arduino-daemon.ts +++ b/arduino-ide-extension/src/node/arduino-daemon.ts @@ -6,6 +6,7 @@ import { ILogger } from '@theia/core/lib/common/logger'; import { BackendApplicationContribution } from '@theia/core/lib/node'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { DaemonLog } from './daemon-log'; +import { ToolOutputServiceServer } from '../common/protocol/tool-output-service'; const EXECUTABLE_PATH = resolve(join(__dirname, '..', '..', 'build', `arduino-cli.${os.platform()}`)) @@ -16,6 +17,9 @@ export class ArduinoDaemon implements BackendApplicationContribution { @named('daemon') protected readonly logger: ILogger + @inject(ToolOutputServiceServer) + protected readonly toolOutputService: ToolOutputServiceServer; + protected isReady = new Deferred(); async onStart() { @@ -28,10 +32,16 @@ export class ArduinoDaemon implements BackendApplicationContribution { console.log(stdout); }); if (daemon.stdout) { - daemon.stdout.on('data', data => DaemonLog.log(this.logger, data.toString())); + daemon.stdout.on('data', data => { + this.toolOutputService.publishNewOutput("daeomn", data.toString()); + DaemonLog.log(this.logger, data.toString()); + }); } if (daemon.stderr) { - daemon.stderr.on('data', data => DaemonLog.log(this.logger, data.toString())); + daemon.stderr.on('data', data => { + this.toolOutputService.publishNewOutput("daeomn error", data.toString()); + DaemonLog.log(this.logger, data.toString()); + }); } if (daemon.stderr) { daemon.on('exit', (code, signal) => DaemonLog.log(this.logger, `Daemon exited with code: ${code}. Signal was: ${signal}.`)); diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index ea98b600..65624b25 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -66,10 +66,10 @@ export class BoardsServiceImpl implements BoardsService { const [ platform, boardName ] = board.id.split(":"); const req = new PlatformInstallReq(); + req.setInstance(instance); req.setArchitecture(boardName); req.setPlatformPackage(platform); req.setVersion(board.availableVersions[0]); - req.setInstance(instance); console.info("Starting board installation", board); const resp = client.platformInstall(req); diff --git a/arduino-ide-extension/src/node/core-client-provider-impl.ts b/arduino-ide-extension/src/node/core-client-provider-impl.ts index e6a3c1e2..85da6698 100644 --- a/arduino-ide-extension/src/node/core-client-provider-impl.ts +++ b/arduino-ide-extension/src/node/core-client-provider-impl.ts @@ -84,6 +84,24 @@ export class CoreClientProviderImpl implements CoreClientProvider { updateResp.on('end', resolve); }); } + + // { + // const installBuiltinPkgReq = new PlatformInstallReq(); + // installBuiltinPkgReq.setInstance(instance); + // installBuiltinPkgReq.setPlatformPackage("builtin"); + // const resp = client.platformInstall(installBuiltinPkgReq); + // resp.on('data', (r: PlatformInstallResp) => { + // const prog = r.getProgress(); + // if (prog) { + // console.info(`downloading ${prog.getFile()}: ${prog.getCompleted()}%`) + // } + // }); + // await new Promise((resolve, reject) => { + // resp.on('end', resolve); + // resp.on('error', reject); + // }); + // } + // TODO: revisit this!!! // `updateResp.on('data'` is called only when running, for instance, `compile`. It does not run eagerly. // await new Promise((resolve, reject) => { diff --git a/arduino-ide-extension/src/node/core-service-impl.ts b/arduino-ide-extension/src/node/core-service-impl.ts index c464a1e8..538cd74d 100644 --- a/arduino-ide-extension/src/node/core-service-impl.ts +++ b/arduino-ide-extension/src/node/core-service-impl.ts @@ -1,11 +1,11 @@ import { inject, injectable } from 'inversify'; import { FileSystem } from '@theia/filesystem/lib/common/filesystem'; import { CoreService } from '../common/protocol/core-service'; -import { CompileReq } from './cli-protocol/compile_pb'; +import { CompileReq, CompileResp } from './cli-protocol/compile_pb'; import { BoardsService } from '../common/protocol/boards-service'; import { CoreClientProvider } from './core-client-provider'; -import { PlatformInstallReq } from './cli-protocol/core_pb'; -import { LibraryInstallReq } from './cli-protocol/lib_pb'; +import * as path from 'path'; +import { ToolOutputServiceServer } from '../common/protocol/tool-output-service'; @injectable() export class CoreServiceImpl implements CoreService { @@ -19,13 +19,17 @@ export class CoreServiceImpl implements CoreService { @inject(BoardsService) protected readonly boardsService: BoardsService; - async compile(options: CoreService.Compile.Options): Promise { + @inject(ToolOutputServiceServer) + protected readonly toolOutputService: ToolOutputServiceServer; + + async compile(options: CoreService.Compile.Options): Promise { console.log('compile', options); const { uri } = options; - const sketchpath = await this.fileSystem.getFsPath(options.uri); - if (!sketchpath) { + const sketchFilePath = await this.fileSystem.getFsPath(options.uri); + if (!sketchFilePath) { throw new Error(`Cannot resolve filesystem path for URI: ${uri}.`); } + const sketchpath = path.dirname(sketchFilePath); const { client, instance } = await this.coreClientProvider.getClient(uri); // const boards = await this.boardsService.connectedBoards(); @@ -34,38 +38,30 @@ export class CoreServiceImpl implements CoreService { // } // https://github.com/cmaglie/arduino-cli/blob/bd5e78701e7546787649d3cca6b21c5d22d0e438/cli/compile/compile.go#L78-L88 - const installLibReq = new LibraryInstallReq(); - installLibReq.setInstance(instance); - installLibReq.setName('arduino:samd'); - const installResp = client.libraryInstall(installLibReq); - const xxx = await new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - installResp.on('data', (chunk: Buffer) => chunks.push(chunk)); - installResp.on('error', error => reject(error)); - installResp.on('end', () => resolve(Buffer.concat(chunks).toString('utf8').trim())) - }); - console.log('xxx', xxx); - const compilerReq = new CompileReq(); compilerReq.setInstance(instance); compilerReq.setSketchpath(sketchpath); - compilerReq.setFqbn('arduino:samd'/*boards.current.name*/); + compilerReq.setFqbn('arduino:avr:uno'/*boards.current.name*/); // request.setShowproperties(false); - // request.setPreprocess(false); + compilerReq.setPreprocess(false); // request.setBuildcachepath(''); - // request.setBuildpath(''); + // compilerReq.setBuildpath('/tmp/build'); + // compilerReq.setShowproperties(true); // request.setBuildpropertiesList([]); // request.setWarnings('none'); - // request.setVerbose(true); - // request.setQuiet(false); + compilerReq.setVerbose(true); + compilerReq.setQuiet(false); // request.setVidpid(''); // request.setExportfile(''); + const result = client.compile(compilerReq); - return new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - result.on('data', (chunk: Buffer) => chunks.push(chunk)); + return new Promise((resolve, reject) => { + result.on('data', (cr: CompileResp) => { + this.toolOutputService.publishNewOutput("compile", new Buffer(cr.getOutStream_asU8()).toString()); + console.error(cr.getErrStream().toString()); + }); result.on('error', error => reject(error)); - result.on('end', () => resolve(Buffer.concat(chunks).toString('utf8').trim())) + result.on('end', () => resolve()); }); } diff --git a/arduino-ide-extension/src/node/tool-output-service-impl.ts b/arduino-ide-extension/src/node/tool-output-service-impl.ts new file mode 100644 index 00000000..f81eb447 --- /dev/null +++ b/arduino-ide-extension/src/node/tool-output-service-impl.ts @@ -0,0 +1,28 @@ +import { injectable } from "inversify"; +import { ToolOutputServiceServer, ToolOutputServiceClient } from "../common/protocol/tool-output-service"; + +@injectable() +export class ToolOutputServiceServerImpl implements ToolOutputServiceServer { + protected clients: ToolOutputServiceClient[] = []; + + publishNewOutput(tool: string, chunk: string): void { + this.clients.forEach(c => c.onNewOutput(tool, chunk)); + } + + setClient(client: ToolOutputServiceClient | undefined): void { + if (!client) { + return; + } + + this.clients.push(client); + } + + disposeClient(client: ToolOutputServiceClient): void { + + } + + dispose(): void { + this.clients = []; + } + +}