@theia/monaco 1.62.0-next.3 → 1.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/monaco-code-action-save-participant.d.ts +14 -0
- package/lib/browser/monaco-code-action-save-participant.d.ts.map +1 -0
- package/lib/browser/monaco-code-action-save-participant.js +129 -0
- package/lib/browser/monaco-code-action-save-participant.js.map +1 -0
- package/lib/browser/monaco-editor-model.d.ts +8 -13
- package/lib/browser/monaco-editor-model.d.ts.map +1 -1
- package/lib/browser/monaco-editor-model.js +21 -57
- package/lib/browser/monaco-editor-model.js.map +1 -1
- package/lib/browser/monaco-editor-provider.d.ts +18 -7
- package/lib/browser/monaco-editor-provider.d.ts.map +1 -1
- package/lib/browser/monaco-editor-provider.js +98 -51
- package/lib/browser/monaco-editor-provider.js.map +1 -1
- package/lib/browser/monaco-frontend-module.d.ts.map +1 -1
- package/lib/browser/monaco-frontend-module.js +4 -0
- package/lib/browser/monaco-frontend-module.js.map +1 -1
- package/lib/browser/monaco-menu.d.ts.map +1 -1
- package/lib/browser/monaco-menu.js +5 -4
- package/lib/browser/monaco-menu.js.map +1 -1
- package/lib/browser/monaco-workspace.d.ts +1 -4
- package/lib/browser/monaco-workspace.d.ts.map +1 -1
- package/lib/browser/monaco-workspace.js +0 -6
- package/lib/browser/monaco-workspace.js.map +1 -1
- package/package.json +9 -9
- package/src/browser/monaco-code-action-save-participant.ts +143 -0
- package/src/browser/monaco-editor-model.ts +25 -74
- package/src/browser/monaco-editor-provider.ts +115 -55
- package/src/browser/monaco-frontend-module.ts +6 -1
- package/src/browser/monaco-menu.ts +5 -4
- package/src/browser/monaco-workspace.ts +1 -9
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theia/monaco",
|
|
3
|
-
"version": "1.62.0
|
|
3
|
+
"version": "1.62.0",
|
|
4
4
|
"description": "Theia - Monaco Extension",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@theia/core": "1.62.0
|
|
7
|
-
"@theia/editor": "1.62.0
|
|
8
|
-
"@theia/filesystem": "1.62.0
|
|
9
|
-
"@theia/markers": "1.62.0
|
|
6
|
+
"@theia/core": "1.62.0",
|
|
7
|
+
"@theia/editor": "1.62.0",
|
|
8
|
+
"@theia/filesystem": "1.62.0",
|
|
9
|
+
"@theia/markers": "1.62.0",
|
|
10
10
|
"@theia/monaco-editor-core": "1.96.302",
|
|
11
|
-
"@theia/outline-view": "1.62.0
|
|
12
|
-
"@theia/workspace": "1.62.0
|
|
11
|
+
"@theia/outline-view": "1.62.0",
|
|
12
|
+
"@theia/workspace": "1.62.0",
|
|
13
13
|
"fast-plist": "^0.1.2",
|
|
14
14
|
"idb": "^4.0.5",
|
|
15
15
|
"jsonc-parser": "^2.2.0",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"watch": "theiaext watch"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@theia/ext-scripts": "1.
|
|
55
|
+
"@theia/ext-scripts": "1.62.0"
|
|
56
56
|
},
|
|
57
57
|
"nyc": {
|
|
58
58
|
"extends": "../../configs/nyc.json"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "a4e035ccc26fde24c2769a466cb18e3b54c517c5"
|
|
61
61
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 STMicroelectronics and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { CancellationToken } from '@theia/core';
|
|
18
|
+
import { SaveOptions, SaveReason } from '@theia/core/lib/browser';
|
|
19
|
+
import { MonacoEditor } from './monaco-editor';
|
|
20
|
+
import { SaveParticipant, SAVE_PARTICIPANT_DEFAULT_ORDER } from './monaco-editor-provider';
|
|
21
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
22
|
+
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
|
23
|
+
import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
|
|
24
|
+
import { CodeActionKind, CodeActionSet, CodeActionTriggerSource } from '@theia/monaco-editor-core/esm/vs/editor/contrib/codeAction/common/types';
|
|
25
|
+
import { applyCodeAction, ApplyCodeActionReason, getCodeActions } from '@theia/monaco-editor-core/esm/vs/editor/contrib/codeAction/browser/codeAction';
|
|
26
|
+
|
|
27
|
+
import { HierarchicalKind } from '@theia/monaco-editor-core/esm/vs/base/common/hierarchicalKind';
|
|
28
|
+
import { EditorPreferences } from '@theia/editor/lib/browser';
|
|
29
|
+
import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
|
30
|
+
import { CodeActionProvider, CodeActionTriggerType } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
|
31
|
+
import { IProgress } from '@theia/monaco-editor-core/esm/vs/platform/progress/common/progress';
|
|
32
|
+
import { IInstantiationService } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation';
|
|
33
|
+
/*---------------------------------------------------------------------------------------------
|
|
34
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
35
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
36
|
+
*--------------------------------------------------------------------------------------------*/
|
|
37
|
+
// Partially copied from https://github.com/microsoft/vscode/blob/f66e839a38dfe39ee66a86619a790f9c2336d698/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts#L272
|
|
38
|
+
@injectable()
|
|
39
|
+
export class MonacoCodeActionSaveParticipant implements SaveParticipant {
|
|
40
|
+
@inject(EditorPreferences)
|
|
41
|
+
protected readonly editorPreferences: EditorPreferences;
|
|
42
|
+
|
|
43
|
+
readonly order = SAVE_PARTICIPANT_DEFAULT_ORDER;
|
|
44
|
+
|
|
45
|
+
async applyChangesOnSave(editor: MonacoEditor, cancellationToken: CancellationToken, options?: SaveOptions): Promise<void> {
|
|
46
|
+
if (options?.saveReason !== SaveReason.Manual) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const setting = this.editorPreferences.get({
|
|
51
|
+
preferenceName: 'editor.codeActionsOnSave',
|
|
52
|
+
overrideIdentifier: editor.document.textEditorModel.getLanguageId()
|
|
53
|
+
}, undefined, editor.document.textEditorModel.uri.toString());
|
|
54
|
+
|
|
55
|
+
if (!setting) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const settingItems: string[] = Array.isArray(setting)
|
|
60
|
+
? setting
|
|
61
|
+
: Object.keys(setting).filter(x => setting[x]);
|
|
62
|
+
|
|
63
|
+
const codeActionsOnSave = this.createCodeActionsOnSave(settingItems);
|
|
64
|
+
|
|
65
|
+
if (!codeActionsOnSave.length) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!Array.isArray(setting)) {
|
|
70
|
+
codeActionsOnSave.sort((a, b) => {
|
|
71
|
+
if (CodeActionKind.SourceFixAll.contains(a)) {
|
|
72
|
+
if (CodeActionKind.SourceFixAll.contains(b)) {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
return -1;
|
|
76
|
+
}
|
|
77
|
+
if (CodeActionKind.SourceFixAll.contains(b)) {
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
return 0;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const excludedActions = Array.isArray(setting)
|
|
85
|
+
? []
|
|
86
|
+
: Object.keys(setting)
|
|
87
|
+
.filter(x => setting[x] === false)
|
|
88
|
+
.map(x => new HierarchicalKind(x));
|
|
89
|
+
|
|
90
|
+
await this.applyOnSaveActions(editor.document.textEditorModel, codeActionsOnSave, excludedActions, cancellationToken);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private createCodeActionsOnSave(settingItems: readonly string[]): HierarchicalKind[] {
|
|
94
|
+
const kinds = settingItems.map(x => new HierarchicalKind(x));
|
|
95
|
+
|
|
96
|
+
// Remove subsets
|
|
97
|
+
return kinds.filter(kind => kinds.every(otherKind => otherKind.equals(kind) || !otherKind.contains(kind)));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly HierarchicalKind[],
|
|
101
|
+
excludes: readonly HierarchicalKind[], token: CancellationToken): Promise<void> {
|
|
102
|
+
|
|
103
|
+
const instantiationService = StandaloneServices.get(IInstantiationService);
|
|
104
|
+
|
|
105
|
+
for (const codeActionKind of codeActionsOnSave) {
|
|
106
|
+
const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token);
|
|
107
|
+
|
|
108
|
+
if (token.isCancellationRequested) {
|
|
109
|
+
actionsToRun.dispose();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
for (const action of actionsToRun.validActions) {
|
|
115
|
+
await instantiationService.invokeFunction(applyCodeAction, action, ApplyCodeActionReason.OnSave, {}, token);
|
|
116
|
+
if (token.isCancellationRequested) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// Failure to apply a code action should not block other on save actions
|
|
122
|
+
} finally {
|
|
123
|
+
actionsToRun.dispose();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private getActionsToRun(model: ITextModel, codeActionKind: HierarchicalKind, excludes: readonly HierarchicalKind[], token: CancellationToken): Promise<CodeActionSet> {
|
|
129
|
+
const { codeActionProvider } = StandaloneServices.get(ILanguageFeaturesService);
|
|
130
|
+
|
|
131
|
+
const progress: IProgress<CodeActionProvider> = {
|
|
132
|
+
report(item): void {
|
|
133
|
+
// empty
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return getCodeActions(codeActionProvider, model, model.getFullModelRange(), {
|
|
138
|
+
type: CodeActionTriggerType.Auto,
|
|
139
|
+
triggerAction: CodeActionTriggerSource.OnSave,
|
|
140
|
+
filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true },
|
|
141
|
+
}, progress, token);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -20,11 +20,11 @@ import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposa
|
|
|
20
20
|
import { Emitter, Event } from '@theia/core/lib/common/event';
|
|
21
21
|
import { CancellationTokenSource, CancellationToken } from '@theia/core/lib/common/cancellation';
|
|
22
22
|
import { Resource, ResourceError, ResourceVersion } from '@theia/core/lib/common/resource';
|
|
23
|
-
import { Saveable, SaveOptions } from '@theia/core/lib/browser/saveable';
|
|
23
|
+
import { Saveable, SaveOptions, SaveReason } from '@theia/core/lib/browser/saveable';
|
|
24
24
|
import { MonacoToProtocolConverter } from './monaco-to-protocol-converter';
|
|
25
25
|
import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter';
|
|
26
26
|
import { ILogger, Loggable, Log } from '@theia/core/lib/common/logger';
|
|
27
|
-
import {
|
|
27
|
+
import { ITextBufferFactory, ITextModel, ITextSnapshot } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
|
28
28
|
import { IResolvedTextEditorModel } from '@theia/monaco-editor-core/esm/vs/editor/common/services/resolverService';
|
|
29
29
|
import * as monaco from '@theia/monaco-editor-core';
|
|
30
30
|
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
|
@@ -39,12 +39,7 @@ export {
|
|
|
39
39
|
TextDocumentSaveReason
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
export
|
|
43
|
-
readonly model: MonacoEditorModel;
|
|
44
|
-
readonly reason: TextDocumentSaveReason;
|
|
45
|
-
readonly options?: SaveOptions;
|
|
46
|
-
waitUntil(thenable: Thenable<IIdentifiedSingleEditOperation[]>): void;
|
|
47
|
-
}
|
|
42
|
+
export type WillSaveMonacoModelListener = (model: MonacoEditorModel, token: CancellationToken, options?: SaveOptions) => Promise<void>;
|
|
48
43
|
|
|
49
44
|
export interface MonacoModelContentChangedEvent {
|
|
50
45
|
readonly model: MonacoEditorModel;
|
|
@@ -83,9 +78,6 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
|
|
|
83
78
|
protected readonly onDidSaveModelEmitter = new Emitter<ITextModel>();
|
|
84
79
|
readonly onDidSaveModel = this.onDidSaveModelEmitter.event;
|
|
85
80
|
|
|
86
|
-
protected readonly onWillSaveModelEmitter = new Emitter<WillSaveMonacoModelEvent>();
|
|
87
|
-
readonly onWillSaveModel = this.onWillSaveModelEmitter.event;
|
|
88
|
-
|
|
89
81
|
protected readonly onDidChangeValidEmitter = new Emitter<void>();
|
|
90
82
|
readonly onDidChangeValid = this.onDidChangeValidEmitter.event;
|
|
91
83
|
|
|
@@ -99,6 +91,8 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
|
|
|
99
91
|
|
|
100
92
|
protected resourceVersion: ResourceVersion | undefined;
|
|
101
93
|
|
|
94
|
+
protected readonly willSaveModelListeners: WillSaveMonacoModelListener[] = [];
|
|
95
|
+
|
|
102
96
|
constructor(
|
|
103
97
|
protected readonly resource: Resource,
|
|
104
98
|
protected readonly m2p: MonacoToProtocolConverter,
|
|
@@ -110,7 +104,6 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
|
|
|
110
104
|
this.toDispose.push(this.toDisposeOnAutoSave);
|
|
111
105
|
this.toDispose.push(this.onDidChangeContentEmitter);
|
|
112
106
|
this.toDispose.push(this.onDidSaveModelEmitter);
|
|
113
|
-
this.toDispose.push(this.onWillSaveModelEmitter);
|
|
114
107
|
this.toDispose.push(this.onDirtyChangedEmitter);
|
|
115
108
|
this.toDispose.push(this.onDidChangeValidEmitter);
|
|
116
109
|
this.toDispose.push(Disposable.create(() => this.cancelSave()));
|
|
@@ -154,7 +147,7 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
|
|
|
154
147
|
if (mode === EncodingMode.Decode) {
|
|
155
148
|
return this.sync();
|
|
156
149
|
}
|
|
157
|
-
return this.scheduleSave(
|
|
150
|
+
return this.scheduleSave(this.cancelSave(), true, { saveReason: SaveReason.Manual });
|
|
158
151
|
}
|
|
159
152
|
|
|
160
153
|
getEncoding(): string | undefined {
|
|
@@ -386,7 +379,10 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
|
|
|
386
379
|
}
|
|
387
380
|
|
|
388
381
|
save(options?: SaveOptions): Promise<void> {
|
|
389
|
-
return this.scheduleSave(
|
|
382
|
+
return this.scheduleSave(undefined, undefined, {
|
|
383
|
+
saveReason: TextDocumentSaveReason.Manual,
|
|
384
|
+
...options
|
|
385
|
+
});
|
|
390
386
|
}
|
|
391
387
|
|
|
392
388
|
protected pendingOperation = Promise.resolve();
|
|
@@ -485,8 +481,8 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
|
|
|
485
481
|
return this.saveCancellationTokenSource.token;
|
|
486
482
|
}
|
|
487
483
|
|
|
488
|
-
protected scheduleSave(
|
|
489
|
-
return this.run(() => this.doSave(
|
|
484
|
+
protected scheduleSave(token: CancellationToken = this.cancelSave(), overwriteEncoding?: boolean, options?: SaveOptions): Promise<void> {
|
|
485
|
+
return this.run(() => this.doSave(token, overwriteEncoding, options));
|
|
490
486
|
}
|
|
491
487
|
|
|
492
488
|
protected ignoreContentChanges = false;
|
|
@@ -546,18 +542,18 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
|
|
|
546
542
|
}
|
|
547
543
|
}
|
|
548
544
|
|
|
549
|
-
protected async doSave(
|
|
545
|
+
protected async doSave(token: CancellationToken, overwriteEncoding?: boolean, options?: SaveOptions): Promise<void> {
|
|
550
546
|
if (token.isCancellationRequested || !this.resource.saveContents) {
|
|
551
547
|
return;
|
|
552
548
|
}
|
|
553
549
|
|
|
554
|
-
await this.fireWillSaveModel(
|
|
550
|
+
await this.fireWillSaveModel(token, options);
|
|
555
551
|
if (token.isCancellationRequested) {
|
|
556
552
|
return;
|
|
557
553
|
}
|
|
558
554
|
|
|
559
555
|
const changes = [...this.contentChanges];
|
|
560
|
-
if ((changes.length === 0 && !this.resource.initiallyDirty) && !overwriteEncoding &&
|
|
556
|
+
if ((changes.length === 0 && !this.resource.initiallyDirty) && !overwriteEncoding && options?.saveReason !== TextDocumentSaveReason.Manual) {
|
|
561
557
|
return;
|
|
562
558
|
}
|
|
563
559
|
|
|
@@ -586,64 +582,19 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
|
|
|
586
582
|
}
|
|
587
583
|
}
|
|
588
584
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
if (
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
const waitables: EditContributor[] = [];
|
|
597
|
-
const { version } = this;
|
|
598
|
-
|
|
599
|
-
const event = {
|
|
600
|
-
model: this, reason, options,
|
|
601
|
-
waitUntil: (thenable: EditContributor) => {
|
|
602
|
-
if (Object.isFrozen(waitables)) {
|
|
603
|
-
throw new Error('waitUntil cannot be called asynchronously.');
|
|
604
|
-
}
|
|
605
|
-
waitables.push(thenable);
|
|
606
|
-
}
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
// Fire.
|
|
610
|
-
try {
|
|
611
|
-
listener(event);
|
|
612
|
-
} catch (err) {
|
|
613
|
-
console.error(err);
|
|
614
|
-
return true;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Asynchronous calls to `waitUntil` should fail.
|
|
618
|
-
Object.freeze(waitables);
|
|
619
|
-
|
|
620
|
-
// Wait for all promises.
|
|
621
|
-
const edits = await Promise.all(waitables).then(allOperations =>
|
|
622
|
-
([] as monaco.editor.IIdentifiedSingleEditOperation[]).concat(...allOperations)
|
|
623
|
-
);
|
|
624
|
-
if (token.isCancellationRequested) {
|
|
625
|
-
return false;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// In a perfect world, we should only apply edits if document is clean.
|
|
629
|
-
if (version !== this.version) {
|
|
630
|
-
console.error('onWillSave listeners should provide edits, not directly alter the document.');
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// Finally apply edits provided by this listener before firing the next.
|
|
634
|
-
if (edits && edits.length > 0) {
|
|
635
|
-
this.applyEdits(edits, {
|
|
636
|
-
ignoreDirty: true,
|
|
637
|
-
});
|
|
585
|
+
registerWillSaveModelListener(listener: WillSaveMonacoModelListener): Disposable {
|
|
586
|
+
this.willSaveModelListeners.push(listener);
|
|
587
|
+
return Disposable.create(() => {
|
|
588
|
+
const index = this.willSaveModelListeners.indexOf(listener);
|
|
589
|
+
if (index >= 0) {
|
|
590
|
+
this.willSaveModelListeners.splice(index, 1);
|
|
638
591
|
}
|
|
639
|
-
|
|
640
|
-
return true;
|
|
641
592
|
});
|
|
593
|
+
}
|
|
642
594
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
console.error(e);
|
|
595
|
+
protected async fireWillSaveModel(token: CancellationToken, options?: SaveOptions): Promise<void> {
|
|
596
|
+
for (const listener of this.willSaveModelListeners) {
|
|
597
|
+
await listener(this, token, options);
|
|
647
598
|
}
|
|
648
599
|
}
|
|
649
600
|
|
|
@@ -18,26 +18,23 @@
|
|
|
18
18
|
import URI from '@theia/core/lib/common/uri';
|
|
19
19
|
import { EditorPreferenceChange, EditorPreferences, TextEditor, DiffNavigator } from '@theia/editor/lib/browser';
|
|
20
20
|
import { DiffUris } from '@theia/core/lib/browser/diff-uris';
|
|
21
|
-
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
22
|
-
import { DisposableCollection, deepClone, Disposable } from '@theia/core/lib/common';
|
|
23
|
-
import { TextDocumentSaveReason } from '@theia/core/shared/vscode-languageserver-protocol';
|
|
21
|
+
import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
|
|
22
|
+
import { DisposableCollection, deepClone, Disposable, CancellationToken } from '@theia/core/lib/common';
|
|
24
23
|
import { MonacoDiffEditor } from './monaco-diff-editor';
|
|
25
24
|
import { MonacoDiffNavigatorFactory } from './monaco-diff-navigator-factory';
|
|
26
25
|
import { EditorServiceOverrides, MonacoEditor, MonacoEditorServices } from './monaco-editor';
|
|
27
|
-
import { MonacoEditorModel,
|
|
26
|
+
import { MonacoEditorModel, TextDocumentSaveReason } from './monaco-editor-model';
|
|
28
27
|
import { MonacoWorkspace } from './monaco-workspace';
|
|
29
28
|
import { ContributionProvider } from '@theia/core';
|
|
30
|
-
import { KeybindingRegistry, OpenerService, open, WidgetOpenerOptions, FormatType } from '@theia/core/lib/browser';
|
|
29
|
+
import { KeybindingRegistry, OpenerService, open, WidgetOpenerOptions, SaveOptions, FormatType } from '@theia/core/lib/browser';
|
|
31
30
|
import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding';
|
|
32
31
|
import { HttpOpenHandlerOptions } from '@theia/core/lib/browser/http-open-handler';
|
|
33
32
|
import { MonacoToProtocolConverter } from './monaco-to-protocol-converter';
|
|
34
33
|
import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter';
|
|
35
|
-
import { FileSystemPreferences } from '@theia/filesystem/lib/browser';
|
|
36
34
|
import * as monaco from '@theia/monaco-editor-core';
|
|
37
35
|
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
|
38
36
|
import { IOpenerService, OpenExternalOptions, OpenInternalOptions } from '@theia/monaco-editor-core/esm/vs/platform/opener/common/opener';
|
|
39
37
|
import { IKeybindingService } from '@theia/monaco-editor-core/esm/vs/platform/keybinding/common/keybinding';
|
|
40
|
-
import { timeoutReject } from '@theia/core/lib/common/promise-util';
|
|
41
38
|
import { IContextMenuService } from '@theia/monaco-editor-core/esm/vs/platform/contextview/browser/contextView';
|
|
42
39
|
import { KeyCodeChord } from '@theia/monaco-editor-core/esm/vs/base/common/keybindings';
|
|
43
40
|
import { IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
|
@@ -46,6 +43,8 @@ import { IReference } from '@theia/monaco-editor-core/esm/vs/base/common/lifecyc
|
|
|
46
43
|
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
|
|
47
44
|
import { SimpleMonacoEditor } from './simple-monaco-editor';
|
|
48
45
|
import { ICodeEditorWidgetOptions } from '@theia/monaco-editor-core/esm/vs/editor/browser/widget/codeEditor/codeEditorWidget';
|
|
46
|
+
import { timeoutReject } from '@theia/core/lib/common/promise-util';
|
|
47
|
+
import { FileSystemPreferences } from '@theia/filesystem/lib/browser';
|
|
49
48
|
|
|
50
49
|
export const MonacoEditorFactory = Symbol('MonacoEditorFactory');
|
|
51
50
|
export interface MonacoEditorFactory {
|
|
@@ -53,6 +52,17 @@ export interface MonacoEditorFactory {
|
|
|
53
52
|
create(model: MonacoEditorModel, defaultOptions: MonacoEditor.IOptions, defaultOverrides: EditorServiceOverrides): Promise<MonacoEditor>;
|
|
54
53
|
}
|
|
55
54
|
|
|
55
|
+
export const SaveParticipant = Symbol('SaveParticipant');
|
|
56
|
+
|
|
57
|
+
export interface SaveParticipant {
|
|
58
|
+
readonly order: number;
|
|
59
|
+
applyChangesOnSave(
|
|
60
|
+
editor: MonacoEditor,
|
|
61
|
+
cancellationToken: CancellationToken,
|
|
62
|
+
options?: SaveOptions): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
export const SAVE_PARTICIPANT_DEFAULT_ORDER = 0;
|
|
65
|
+
|
|
56
66
|
@injectable()
|
|
57
67
|
export class MonacoEditorProvider {
|
|
58
68
|
|
|
@@ -68,9 +78,13 @@ export class MonacoEditorProvider {
|
|
|
68
78
|
|
|
69
79
|
@inject(OpenerService)
|
|
70
80
|
protected readonly openerService: OpenerService;
|
|
81
|
+
@inject(ContributionProvider)
|
|
82
|
+
@named(SaveParticipant)
|
|
83
|
+
protected readonly saveProviderContributions: ContributionProvider<SaveParticipant>;
|
|
71
84
|
|
|
72
85
|
@inject(FileSystemPreferences)
|
|
73
86
|
protected readonly filePreferences: FileSystemPreferences;
|
|
87
|
+
protected saveParticipants: SaveParticipant[];
|
|
74
88
|
|
|
75
89
|
protected _current: MonacoEditor | undefined;
|
|
76
90
|
/**
|
|
@@ -210,7 +224,7 @@ export class MonacoEditorProvider {
|
|
|
210
224
|
}));
|
|
211
225
|
toDispose.push(editor.onLanguageChanged(() => this.updateMonacoEditorOptions(editor)));
|
|
212
226
|
toDispose.push(editor.onDidChangeReadOnly(() => this.updateReadOnlyMessage(options, model.readOnly)));
|
|
213
|
-
editor.document.
|
|
227
|
+
toDispose.push(editor.document.registerWillSaveModelListener((_, token, o) => this.runSaveParticipants(editor, token, o)));
|
|
214
228
|
return editor;
|
|
215
229
|
}
|
|
216
230
|
|
|
@@ -239,46 +253,6 @@ export class MonacoEditorProvider {
|
|
|
239
253
|
}
|
|
240
254
|
}
|
|
241
255
|
|
|
242
|
-
protected shouldFormat(editor: MonacoEditor, event: WillSaveMonacoModelEvent): boolean {
|
|
243
|
-
if (event.reason !== TextDocumentSaveReason.Manual) {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
if (event.options?.formatType) {
|
|
247
|
-
switch (event.options.formatType) {
|
|
248
|
-
case FormatType.ON: return true;
|
|
249
|
-
case FormatType.OFF: return false;
|
|
250
|
-
case FormatType.DIRTY: return editor.document.dirty;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return true;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
protected async formatOnSave(editor: MonacoEditor, event: WillSaveMonacoModelEvent): Promise<monaco.editor.IIdentifiedSingleEditOperation[]> {
|
|
257
|
-
if (!this.shouldFormat(editor, event)) {
|
|
258
|
-
return [];
|
|
259
|
-
}
|
|
260
|
-
const edits: monaco.editor.IIdentifiedSingleEditOperation[] = [];
|
|
261
|
-
const overrideIdentifier = editor.document.languageId;
|
|
262
|
-
const uri = editor.uri.toString();
|
|
263
|
-
const formatOnSave = this.editorPreferences.get({ preferenceName: 'editor.formatOnSave', overrideIdentifier }, undefined, uri);
|
|
264
|
-
if (formatOnSave) {
|
|
265
|
-
const formatOnSaveTimeout = this.editorPreferences.get({ preferenceName: 'editor.formatOnSaveTimeout', overrideIdentifier }, undefined, uri)!;
|
|
266
|
-
await Promise.race([
|
|
267
|
-
timeoutReject(formatOnSaveTimeout, `Aborted format on save after ${formatOnSaveTimeout}ms`),
|
|
268
|
-
editor.runAction('editor.action.formatDocument')
|
|
269
|
-
]);
|
|
270
|
-
}
|
|
271
|
-
const shouldRemoveWhiteSpace = this.filePreferences.get({ preferenceName: 'files.trimTrailingWhitespace', overrideIdentifier }, undefined, uri);
|
|
272
|
-
if (shouldRemoveWhiteSpace) {
|
|
273
|
-
await editor.runAction('editor.action.trimTrailingWhitespace');
|
|
274
|
-
}
|
|
275
|
-
const insertFinalNewline = this.filePreferences.get({ preferenceName: 'files.insertFinalNewline', overrideIdentifier }, undefined, uri);
|
|
276
|
-
if (insertFinalNewline) {
|
|
277
|
-
edits.push(...this.insertFinalNewline(editor));
|
|
278
|
-
}
|
|
279
|
-
return edits;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
256
|
protected get diffPreferencePrefixes(): string[] {
|
|
283
257
|
return [...this.preferencePrefixes, 'diffEditor.'];
|
|
284
258
|
}
|
|
@@ -497,20 +471,106 @@ export class MonacoEditorProvider {
|
|
|
497
471
|
);
|
|
498
472
|
}
|
|
499
473
|
|
|
500
|
-
|
|
501
|
-
|
|
474
|
+
@postConstruct()
|
|
475
|
+
init(): void {
|
|
476
|
+
this.saveParticipants = this.saveProviderContributions.getContributions().slice().sort((left, right) => left.order - right.order);
|
|
477
|
+
this.registerSaveParticipant({
|
|
478
|
+
order: 1000,
|
|
479
|
+
applyChangesOnSave: (
|
|
480
|
+
editor: MonacoEditor,
|
|
481
|
+
cancellationToken: monaco.CancellationToken,
|
|
482
|
+
options: SaveOptions): Promise<void> => this.formatOnSave(editor, editor.document, cancellationToken, options)
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
registerSaveParticipant(saveParticipant: SaveParticipant): Disposable {
|
|
487
|
+
if (this.saveParticipants.find(value => value === saveParticipant)) {
|
|
488
|
+
throw new Error('Save participant already registered');
|
|
489
|
+
}
|
|
490
|
+
this.saveParticipants.push(saveParticipant);
|
|
491
|
+
this.saveParticipants.sort((left, right) => left.order - right.order);
|
|
492
|
+
return Disposable.create(() => {
|
|
493
|
+
const index = this.saveParticipants.indexOf(saveParticipant);
|
|
494
|
+
if (index >= 0) {
|
|
495
|
+
this.saveParticipants.splice(index, 1);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
protected shouldFormat(model: MonacoEditorModel, options: SaveOptions): boolean {
|
|
501
|
+
if (options.saveReason !== TextDocumentSaveReason.Manual) {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
switch (options.formatType) {
|
|
505
|
+
case FormatType.ON: return true;
|
|
506
|
+
case FormatType.OFF: return false;
|
|
507
|
+
case FormatType.DIRTY: return model.dirty;
|
|
508
|
+
}
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async runSaveParticipants(editor: MonacoEditor, cancellationToken: CancellationToken, options?: SaveOptions): Promise<void> {
|
|
513
|
+
const initialState = editor.document.createSnapshot();
|
|
514
|
+
for (const participant of this.saveParticipants) {
|
|
515
|
+
if (cancellationToken.isCancellationRequested) {
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
const snapshot = editor.document.createSnapshot();
|
|
519
|
+
try {
|
|
520
|
+
await participant.applyChangesOnSave(editor, cancellationToken, options);
|
|
521
|
+
} catch (e) {
|
|
522
|
+
console.error(e);
|
|
523
|
+
editor.document.applySnapshot(snapshot);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (cancellationToken.isCancellationRequested) {
|
|
527
|
+
editor.document.applySnapshot(initialState);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
protected async formatOnSave(
|
|
532
|
+
editor: MonacoEditor,
|
|
533
|
+
model: MonacoEditorModel,
|
|
534
|
+
cancellationToken: CancellationToken,
|
|
535
|
+
options: SaveOptions): Promise<void> {
|
|
536
|
+
if (!this.shouldFormat(model, options)) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const overrideIdentifier = model.languageId;
|
|
541
|
+
const uri = model.uri.toString();
|
|
542
|
+
const formatOnSave = this.editorPreferences.get({ preferenceName: 'editor.formatOnSave', overrideIdentifier }, undefined, uri);
|
|
543
|
+
if (formatOnSave) {
|
|
544
|
+
const formatOnSaveTimeout = this.editorPreferences.get({ preferenceName: 'editor.formatOnSaveTimeout', overrideIdentifier }, undefined, uri)!;
|
|
545
|
+
await Promise.race([
|
|
546
|
+
timeoutReject(formatOnSaveTimeout, `Aborted format on save after ${formatOnSaveTimeout}ms`),
|
|
547
|
+
await editor.runAction('editor.action.formatDocument')
|
|
548
|
+
]);
|
|
549
|
+
}
|
|
550
|
+
const shouldRemoveWhiteSpace = this.filePreferences.get({ preferenceName: 'files.trimTrailingWhitespace', overrideIdentifier }, undefined, uri);
|
|
551
|
+
if (shouldRemoveWhiteSpace) {
|
|
552
|
+
await editor.runAction('editor.action.trimTrailingWhitespace');
|
|
553
|
+
}
|
|
554
|
+
const insertFinalNewline = this.filePreferences.get({ preferenceName: 'files.insertFinalNewline', overrideIdentifier }, undefined, uri);
|
|
555
|
+
if (insertFinalNewline) {
|
|
556
|
+
this.insertFinalNewline(model);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
protected insertFinalNewline(editorModel: MonacoEditorModel): void {
|
|
561
|
+
const model = editorModel.textEditorModel;
|
|
502
562
|
if (!model) {
|
|
503
|
-
return
|
|
563
|
+
return;
|
|
504
564
|
}
|
|
505
565
|
|
|
506
566
|
const lines = model?.getLineCount();
|
|
507
567
|
if (lines === 0) {
|
|
508
|
-
return
|
|
568
|
+
return;
|
|
509
569
|
}
|
|
510
570
|
|
|
511
571
|
const lastLine = model?.getLineContent(lines);
|
|
512
572
|
if (lastLine.trim() === '') {
|
|
513
|
-
return
|
|
573
|
+
return;
|
|
514
574
|
}
|
|
515
575
|
|
|
516
576
|
const lastLineMaxColumn = model?.getLineMaxColumn(lines);
|
|
@@ -520,9 +580,9 @@ export class MonacoEditorProvider {
|
|
|
520
580
|
endLineNumber: lines,
|
|
521
581
|
endColumn: lastLineMaxColumn
|
|
522
582
|
};
|
|
523
|
-
|
|
583
|
+
model.applyEdits([{
|
|
524
584
|
range,
|
|
525
585
|
text: model?.getEOL()
|
|
526
|
-
}];
|
|
586
|
+
}]);
|
|
527
587
|
}
|
|
528
588
|
}
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
WidgetStatusBarContribution
|
|
26
26
|
} from '@theia/core/lib/browser';
|
|
27
27
|
import { TextEditorProvider, DiffNavigatorProvider, TextEditor } from '@theia/editor/lib/browser';
|
|
28
|
-
import { MonacoEditorProvider, MonacoEditorFactory } from './monaco-editor-provider';
|
|
28
|
+
import { MonacoEditorProvider, MonacoEditorFactory, SaveParticipant } from './monaco-editor-provider';
|
|
29
29
|
import { MonacoEditorMenuContribution } from './monaco-menu';
|
|
30
30
|
import { MonacoEditorCommandHandlers } from './monaco-command';
|
|
31
31
|
import { MonacoKeybindingContribution } from './monaco-keybinding';
|
|
@@ -79,6 +79,7 @@ import { ActiveMonacoUndoRedoHandler, FocusedMonacoUndoRedoHandler } from './mon
|
|
|
79
79
|
import { ILogService } from '@theia/monaco-editor-core/esm/vs/platform/log/common/log';
|
|
80
80
|
import { DefaultContentHoverWidgetPatcher } from './default-content-hover-widget-patcher';
|
|
81
81
|
import { MonacoWorkspaceContextService } from './monaco-workspace-context-service';
|
|
82
|
+
import { MonacoCodeActionSaveParticipant } from './monaco-code-action-save-participant';
|
|
82
83
|
|
|
83
84
|
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|
84
85
|
bind(MonacoThemingService).toSelf().inSingletonScope();
|
|
@@ -91,6 +92,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|
|
91
92
|
bind(FrontendApplicationContribution).toService(MonacoFrontendApplicationContribution);
|
|
92
93
|
bind(StylingParticipant).toService(MonacoFrontendApplicationContribution);
|
|
93
94
|
|
|
95
|
+
bind(MonacoCodeActionSaveParticipant).toSelf().inSingletonScope();
|
|
96
|
+
bind(SaveParticipant).toService(MonacoCodeActionSaveParticipant);
|
|
97
|
+
|
|
94
98
|
bind(MonacoToProtocolConverter).toSelf().inSingletonScope();
|
|
95
99
|
bind(ProtocolToMonacoConverter).toSelf().inSingletonScope();
|
|
96
100
|
|
|
@@ -122,6 +126,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|
|
122
126
|
bindContributionProvider(bind, MonacoEditorFactory);
|
|
123
127
|
bindContributionProvider(bind, MonacoEditorModelFactory);
|
|
124
128
|
bindContributionProvider(bind, MonacoEditorModelFilter);
|
|
129
|
+
bindContributionProvider(bind, SaveParticipant);
|
|
125
130
|
bind(MonacoCommandService).toSelf().inTransientScope();
|
|
126
131
|
|
|
127
132
|
bind(TextEditorProvider).toProvider(context =>
|