diff --git a/CHANGELOG.md b/CHANGELOG.md index c61a33067b6b0..d8f933fb5be20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v0.3.17 - [plug-in] added `languages.registerCodeLensProvider` Plug-in API - [core] `ctrl+alt+a` and `ctrl+alt+d` to switch tabs left/right +- [core] added `theia.applicationName` to application `package.json` and improved window title ## v0.3.16 diff --git a/dev-packages/application-manager/src/generator/frontend-generator.ts b/dev-packages/application-manager/src/generator/frontend-generator.ts index d7bc317d916f7..b2f0ade84d733 100644 --- a/dev-packages/application-manager/src/generator/frontend-generator.ts +++ b/dev-packages/application-manager/src/generator/frontend-generator.ts @@ -104,12 +104,13 @@ process.env.LC_NUMERIC = 'C'; const { join } = require('path'); const { isMaster } = require('cluster'); const { fork } = require('child_process'); -const { app, BrowserWindow, ipcMain } = require('electron'); +const { app, BrowserWindow, ipcMain, Menu } = require('electron'); +const applicationName = \`${this.pck.props.frontend.config.applicationName}\`; const windows = []; function createNewWindow(theUrl) { - const newWindow = new BrowserWindow({ width: 1024, height: 728, show: !!theUrl }); + const newWindow = new BrowserWindow({ width: 1024, height: 728, show: !!theUrl, title: applicationName }); if (windows.length === 0) { newWindow.webContents.on('new-window', (event, url, frameName, disposition, options) => { // If the first electron window isn't visible, then all other new windows will remain invisible. @@ -117,6 +118,7 @@ function createNewWindow(theUrl) { options.show = true; options.width = 1024; options.height = 728; + options.title = applicationName; }); } windows.push(newWindow); @@ -147,6 +149,9 @@ if (isMaster) { createNewWindow(url); }); app.on('ready', () => { + // Remove the default electron menus, waiting for the application to set its own. + Menu.setApplicationMenu(Menu.buildFromTemplate([])); + // Check whether we are in bundled application or development mode. // @ts-ignore const devMode = process.defaultApp || /node_modules[\/]electron[\/]/.test(process.execPath); diff --git a/dev-packages/application-package/src/application-props.ts b/dev-packages/application-package/src/application-props.ts index 0b4aa6bd55d01..5502e1bc486c5 100644 --- a/dev-packages/application-package/src/application-props.ts +++ b/dev-packages/application-package/src/application-props.ts @@ -69,7 +69,9 @@ export namespace ApplicationProps { config: {} }, frontend: { - config: {} + config: { + applicationName: 'Theia' + } } }; @@ -93,6 +95,11 @@ export interface FrontendApplicationConfig extends ApplicationConfig { */ readonly defaultTheme?: string; + /** + * The name of the application. `Theia` by default. + */ + readonly applicationName: string; + } /** diff --git a/examples/browser/package.json b/examples/browser/package.json index 4c3e5e3ccd1b7..56820c5248533 100644 --- a/examples/browser/package.json +++ b/examples/browser/package.json @@ -2,6 +2,13 @@ "private": true, "name": "@theia/example-browser", "version": "0.3.16", + "theia": { + "frontend": { + "config": { + "applicationName": "Theia Browser Example" + } + } + }, "dependencies": { "@theia/callhierarchy": "^0.3.16", "@theia/console": "^0.3.16", diff --git a/examples/electron/package.json b/examples/electron/package.json index a57c90c67df97..cd63b89cd65fb 100644 --- a/examples/electron/package.json +++ b/examples/electron/package.json @@ -3,7 +3,12 @@ "name": "@theia/example-electron", "version": "0.3.16", "theia": { - "target": "electron" + "target": "electron", + "frontend": { + "config": { + "applicationName": "Theia Electron Example" + } + } }, "dependencies": { "@theia/callhierarchy": "^0.3.16", diff --git a/packages/preferences/src/browser/preference-service.spec.ts b/packages/preferences/src/browser/preference-service.spec.ts index 17ec79c8e74b4..c9f7b0c205820 100644 --- a/packages/preferences/src/browser/preference-service.spec.ts +++ b/packages/preferences/src/browser/preference-service.spec.ts @@ -46,6 +46,7 @@ import { MockWorkspaceServer } from '@theia/workspace/lib/common/test/mock-works import { MockWindowService } from '@theia/core/lib/browser/window/test/mock-window-service'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { WorkspacePreferences, createWorkspacePreferences } from '@theia/workspace/lib/browser/workspace-preferences'; +import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; import * as sinon from 'sinon'; import URI from '@theia/core/lib/common/uri'; @@ -60,7 +61,7 @@ const tempPath = temp.track().openSync().path; const mockUserPreferenceEmitter = new Emitter(); const mockWorkspacePreferenceEmitter = new Emitter(); -before(async () => { +function testContainerSetup() { testContainer = new Container(); bindPreferenceSchemaProvider(testContainer.bind.bind(testContainer)); @@ -130,12 +131,16 @@ before(async () => { /* Logger mock */ testContainer.bind(ILogger).to(MockLogger); -}); +} describe('Preference Service', function () { before(() => { disableJSDOM = enableJSDOM(); + FrontendApplicationConfigProvider.set({ + 'applicationName': 'test', + }); + testContainerSetup(); }); after(() => { diff --git a/packages/workspace/src/browser/workspace-service.spec.ts b/packages/workspace/src/browser/workspace-service.spec.ts index 5ce7e8e01a763..1e3ff4056e28c 100644 --- a/packages/workspace/src/browser/workspace-service.spec.ts +++ b/packages/workspace/src/browser/workspace-service.spec.ts @@ -14,9 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom'; +let disableJSDOM = enableJSDOM(); + import { Container } from 'inversify'; import { WorkspaceService } from './workspace-service'; import { FileSystem, FileStat } from '@theia/filesystem/lib/common'; +import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; import { FileSystemNode } from '@theia/filesystem/lib/node/node-filesystem'; import { FileSystemWatcher, FileChangeEvent, FileChangeType } from '@theia/filesystem/lib/browser/filesystem-watcher'; import { DefaultWindowService, WindowService } from '@theia/core/lib/browser/window/window-service'; @@ -30,6 +34,8 @@ import * as chai from 'chai'; import URI from '@theia/core/lib/common/uri'; const expect = chai.expect; +disableJSDOM(); + const folderA = Object.freeze({ uri: 'file:///home/folderA', lastModification: 0, @@ -60,6 +66,17 @@ describe('WorkspaceService', () => { let mockILogger: ILogger; let mockPref: WorkspacePreferences; + before(() => { + disableJSDOM = enableJSDOM(); + FrontendApplicationConfigProvider.set({ + 'applicationName': 'test', + }); + }); + + after(() => { + disableJSDOM(); + }); + beforeEach(() => { mockPreferenceValues = {}; mockFilesystem = sinon.createStubInstance(FileSystemNode); @@ -88,6 +105,7 @@ describe('WorkspaceService', () => { wsService = testContainer.get(WorkspaceService); }); + afterEach(() => { wsService['toDisposeOnWorkspace'].dispose(); toRestore.forEach(res => { diff --git a/packages/workspace/src/browser/workspace-service.ts b/packages/workspace/src/browser/workspace-service.ts index e7f10d4590e29..f11bd4e4c8282 100644 --- a/packages/workspace/src/browser/workspace-service.ts +++ b/packages/workspace/src/browser/workspace-service.ts @@ -26,6 +26,7 @@ import { ILogger, Disposable, DisposableCollection, Emitter, Event } from '@thei import { WorkspacePreferences } from './workspace-preferences'; import * as jsoncparser from 'jsonc-parser'; import * as Ajv from 'ajv'; +import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; export const THEIA_EXT = 'theia-workspace'; export const VSCODE_EXT = 'code-workspace'; @@ -64,6 +65,8 @@ export class WorkspaceService implements FrontendApplicationContribution { @inject(WorkspacePreferences) protected preferences: WorkspacePreferences; + protected applicationName = FrontendApplicationConfigProvider.get().applicationName; + @postConstruct() protected async init(): Promise { const workspaceUri = await this.server.getMostRecentlyUsedWorkspace(); @@ -168,19 +171,24 @@ export class WorkspaceService implements FrontendApplicationContribution { } } - protected updateTitle(): void { + protected formatTitle(title?: string): string { + const name = this.applicationName; + return title ? `${title} — ${name}` : name; + } + + protected updateTitle() { + let title: string | undefined; if (this._workspace) { const uri = new URI(this._workspace.uri); const displayName = uri.displayName; if (!this._workspace.isDirectory && (displayName.endsWith(`.${THEIA_EXT}`) || displayName.endsWith(`.${VSCODE_EXT}`))) { - document.title = displayName.slice(0, displayName.lastIndexOf('.')); + title = displayName.slice(0, displayName.lastIndexOf('.')); } else { - document.title = displayName; + title = displayName; } - } else { - document.title = window.location.href; } + document.title = this.formatTitle(title); } /**