mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-10 12:56:32 +00:00
Avoid opening duplicate editor tabs.
Customized the shell layout restorer: - If a resource is about to open in code editor and preview, do not open the preview. - If a resource is about to open in preview only, open a code editor instead. Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
555da878f4
commit
5edccb9c35
@ -1,78 +1,162 @@
|
|||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { notEmpty } from '@theia/core';
|
||||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
import { WidgetDescription } from '@theia/core/lib/browser';
|
||||||
import { ShellLayoutRestorer as TheiaShellLayoutRestorer } from '@theia/core/lib/browser/shell/shell-layout-restorer';
|
import { ShellLayoutRestorer as TheiaShellLayoutRestorer } from '@theia/core/lib/browser/shell/shell-layout-restorer';
|
||||||
import { EditorManager } from '@theia/editor/lib/browser';
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { EditorPreviewWidgetFactory } from '@theia/editor-preview/lib/browser/editor-preview-widget-factory';
|
||||||
|
import { EditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ShellLayoutRestorer extends TheiaShellLayoutRestorer {
|
export class ShellLayoutRestorer extends TheiaShellLayoutRestorer {
|
||||||
// The editor manager is unused in the layout restorer.
|
/**
|
||||||
// We inject the editor manager to achieve better logging when filtering duplicate editor tabs.
|
* Customized to filter out duplicate editor tabs.
|
||||||
// Feel free to remove it in later IDE2 releases if the duplicate editor tab issues do not occur anymore.
|
*/
|
||||||
@inject(EditorManager)
|
protected override parse<T>(
|
||||||
private readonly editorManager: EditorManager;
|
layoutData: string,
|
||||||
|
parseContext: TheiaShellLayoutRestorer.ParseContext
|
||||||
// Workaround for https://github.com/eclipse-theia/theia/issues/6579.
|
): T {
|
||||||
async storeLayoutAsync(app: FrontendApplication): Promise<void> {
|
return JSON.parse(layoutData, (property: string, value) => {
|
||||||
if (this.shouldStoreLayout) {
|
if (this.isWidgetsProperty(property)) {
|
||||||
try {
|
const widgets = parseContext.filteredArray();
|
||||||
this.logger.info('>>> Storing the layout...');
|
const descs = this.filterDescriptions(value); // <--- customization to filter out editor preview construction options.
|
||||||
const layoutData = app.shell.getLayoutData();
|
for (let i = 0; i < descs.length; i++) {
|
||||||
const serializedLayoutData = this.deflate(layoutData);
|
parseContext.push(async (context) => {
|
||||||
await this.storageService.setData(
|
widgets[i] = await this.convertToWidget(descs[i], context);
|
||||||
this.storageKey,
|
});
|
||||||
serializedLayoutData
|
}
|
||||||
);
|
return widgets;
|
||||||
this.logger.info('<<< The layout has been successfully stored.');
|
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||||
} catch (error) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await this.storageService.setData(this.storageKey, undefined);
|
const copy: any = {};
|
||||||
this.logger.error('Error during serialization of layout data', error);
|
for (const p in value) {
|
||||||
|
if (this.isWidgetProperty(p)) {
|
||||||
|
parseContext.push(async (context) => {
|
||||||
|
copy[p] = await this.convertToWidget(value[p], context);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
copy[p] = value[p];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
}
|
}
|
||||||
}
|
return value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
override async restoreLayout(app: FrontendApplication): Promise<boolean> {
|
/**
|
||||||
this.logger.info('>>> Restoring the layout state...');
|
* Workaround to avoid duplicate editor tabs on IDE2 startup.
|
||||||
const serializedLayoutData = await this.storageService.getData<string>(
|
*
|
||||||
this.storageKey
|
* This function filters all widget construction options with `editor-preview-widget`
|
||||||
);
|
* factory ID if another option has the same URI and `code-editor-opener` factory ID.
|
||||||
if (serializedLayoutData === undefined) {
|
* In other words, if a resource is about to open in the Code editor, the same resource won't open as a preview widget.
|
||||||
this.logger.info('<<< Nothing to restore.');
|
*
|
||||||
return false;
|
* The other bogus state that this function is fixes when there is a resource registered to open with the `editor-preview-widget`,
|
||||||
}
|
* but there is no `code-editor-opener` counterpart for the same resource URI. In this case, the `editor-preview-widget` will be replaced
|
||||||
|
* with the `code-editor-opener` and the `innerWidgetState` will be dropped.
|
||||||
const layoutData = await this.inflate(serializedLayoutData);
|
*
|
||||||
// workaround to remove duplicated tabs
|
* OK, but why is this happening? The breaking change was [here](https://github.com/eclipse-theia/theia/commit/e8e88b76673a6151d1fc12501dbe598be2358350#diff-7e1bdbcf59009518f9f3b76fe22cc8ab82d13ffa5e5e0a4262e492f25e505d98R29-R30)
|
||||||
console.log(
|
* in Theia when the editor manager of the code editor was bound to the preview editor.
|
||||||
'>>> Filtering persisted layout data to eliminate duplicate editor tabs...'
|
* For whatever reasons, the IDE2 started to use `@theia/editor-preview` extension from [this](https://github.com/arduino/arduino-ide/commit/fc0f67493b728f9202c9a04c7243d03b0d6ea0c7) commit. From this point, when an editor was opened,
|
||||||
);
|
* but the `preview` option was not set to explicit `false` the preview editor manager has created the widgets instead of the manager of the regular code editor.
|
||||||
const filesUri: string[] = [];
|
* This code must stay to be backward compatible.
|
||||||
if ((layoutData as any)?.mainPanel?.main?.widgets) {
|
*
|
||||||
(layoutData as any).mainPanel.main.widgets = (
|
* Example of resource with multiple opener:
|
||||||
layoutData as any
|
* ```json
|
||||||
).mainPanel.main.widgets.filter((widget: any) => {
|
* [
|
||||||
const uri = widget.getResourceUri().toString();
|
* {
|
||||||
if (filesUri.includes(uri)) {
|
* "constructionOptions": {
|
||||||
console.log(`[SKIP]: Already visited editor URI: '${uri}'.`);
|
* "factoryId": "editor-preview-widget",
|
||||||
return false;
|
* "options": {
|
||||||
|
* "kind": "navigatable",
|
||||||
|
* "uri": "file:///Users/a.kitta/Documents/Arduino/sketch_jun3b/sketch_jun3b.ino",
|
||||||
|
* "counter": 1,
|
||||||
|
* "preview": false
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* "innerWidgetState": "{\"isPreview\":false,\"editorState\":{\"cursorState\":[{\"inSelectionMode\":false,\"selectionStart\":{\"lineNumber\":10,\"column\":1},\"position\":{\"lineNumber\":10,\"column\":1}}],\"viewState\":{\"scrollLeft\":0,\"firstPosition\":{\"lineNumber\":1,\"column\":1},\"firstPositionDeltaTop\":0},\"contributionsState\":{\"editor.contrib.folding\":{\"lineCount\":10,\"provider\":\"indent\",\"foldedImports\":false},\"editor.contrib.wordHighlighter\":false}}}"
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "constructionOptions": {
|
||||||
|
* "factoryId": "code-editor-opener",
|
||||||
|
* "options": {
|
||||||
|
* "kind": "navigatable",
|
||||||
|
* "uri": "file:///Users/a.kitta/Documents/Arduino/sketch_jun3b/sketch_jun3b.ino",
|
||||||
|
* "counter": 0
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* "innerWidgetState": "{\"cursorState\":[{\"inSelectionMode\":false,\"selectionStart\":{\"lineNumber\":1,\"column\":1},\"position\":{\"lineNumber\":1,\"column\":1}}],\"viewState\":{\"scrollLeft\":0,\"firstPosition\":{\"lineNumber\":1,\"column\":1},\"firstPositionDeltaTop\":0},\"contributionsState\":{\"editor.contrib.folding\":{\"lineCount\":10,\"provider\":\"indent\",\"foldedImports\":false},\"editor.contrib.wordHighlighter\":false}}"
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Example with resource only with preview opener:
|
||||||
|
*
|
||||||
|
* ```json
|
||||||
|
* [
|
||||||
|
* {
|
||||||
|
* "constructionOptions": {
|
||||||
|
* "factoryId": "editor-preview-widget",
|
||||||
|
* "options": {
|
||||||
|
* "kind": "navigatable",
|
||||||
|
* "uri": "file:///c%3A/Users/per/Documents/Arduino/duptabs/duptabs.ino",
|
||||||
|
* "counter": 1,
|
||||||
|
* "preview": false
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* "innerWidgetState": "{\"isPreview\":false,\"editorState\":{\"cursorState\":[{\"inSelectionMode\":false,\"selectionStart\":{\"lineNumber\":1,\"column\":1},\"position\":{\"lineNumber\":1,\"column\":1}}],\"viewState\":{\"scrollLeft\":0,\"firstPosition\":{\"lineNumber\":1,\"column\":1},\"firstPositionDeltaTop\":0},\"contributionsState\":{\"editor.contrib.wordHighlighter\":false,\"editor.contrib.folding\":{\"lineCount\":11,\"provider\":\"indent\"}}}}"
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
private filterDescriptions(value: any): WidgetDescription[] {
|
||||||
|
const descriptions = value as WidgetDescription[];
|
||||||
|
const codeEditorUris = new Set<string>();
|
||||||
|
descriptions.forEach(({ constructionOptions }) => {
|
||||||
|
const { options, factoryId } = constructionOptions;
|
||||||
|
if (isResourceWidgetOptions(options)) {
|
||||||
|
const { uri } = options;
|
||||||
|
// resource about to open in code editor
|
||||||
|
if (factoryId === EditorWidgetFactory.ID) {
|
||||||
|
codeEditorUris.add(uri);
|
||||||
}
|
}
|
||||||
console.log(`[OK]: Visited editor URI: '${uri}'.`);
|
}
|
||||||
filesUri.push(uri);
|
});
|
||||||
return true;
|
return descriptions
|
||||||
});
|
.map((desc) => {
|
||||||
}
|
const { constructionOptions } = desc;
|
||||||
console.log('<<< Filtered the layout data before restoration.');
|
const { options, factoryId } = constructionOptions;
|
||||||
|
if (factoryId === EditorPreviewWidgetFactory.ID) {
|
||||||
await app.shell.setLayoutData(layoutData);
|
// resource about to open in preview editor
|
||||||
const allOpenedEditors = this.editorManager.all;
|
if (isResourceWidgetOptions(options)) {
|
||||||
// If any editor was visited during the layout data filtering,
|
const { uri } = options;
|
||||||
// but the editor manager does not know about opened editors, then
|
// if the resource is about to open in the code editor anyway, do not open the resource in a preview widget too.
|
||||||
// the IDE2 will show duplicate editors.
|
if (codeEditorUris.has(uri)) {
|
||||||
if (filesUri.length && !allOpenedEditors.length) {
|
console.log(
|
||||||
console.warn(
|
`Filtered a widget construction options to avoid duplicate editor tab. URI: ${options.uri}, factory ID: ${factoryId}.`
|
||||||
'Inconsistency detected between the editor manager and the restored layout data. Editors were detected to be open in the layout data from the previous session, but the editor manager does not know about the opened editor.'
|
);
|
||||||
);
|
return undefined;
|
||||||
}
|
} else {
|
||||||
this.logger.info('<<< The layout has been successfully restored.');
|
// if the preview construction options does not have the code editor counterpart, instead of dropping the preview construction option, a code editor option will be created on the fly.
|
||||||
return true;
|
return {
|
||||||
|
constructionOptions: {
|
||||||
|
factoryId: EditorWidgetFactory.ID,
|
||||||
|
options: {
|
||||||
|
uri,
|
||||||
|
kind: 'navigatable',
|
||||||
|
counter: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return desc;
|
||||||
|
})
|
||||||
|
.filter(notEmpty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
function isResourceWidgetOptions(options: any): options is { uri: string } {
|
||||||
|
return !!options && 'uri' in options && typeof options.uri === 'string';
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user