@theia/monaco 1.63.0-next.52 → 1.63.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.
Files changed (34) hide show
  1. package/lib/browser/index.d.ts +1 -0
  2. package/lib/browser/index.d.ts.map +1 -1
  3. package/lib/browser/index.js +1 -0
  4. package/lib/browser/index.js.map +1 -1
  5. package/lib/browser/monaco-code-action-save-participant.d.ts +2 -5
  6. package/lib/browser/monaco-code-action-save-participant.d.ts.map +1 -1
  7. package/lib/browser/monaco-code-action-save-participant.js +4 -85
  8. package/lib/browser/monaco-code-action-save-participant.js.map +1 -1
  9. package/lib/browser/monaco-code-action-service.d.ts +41 -0
  10. package/lib/browser/monaco-code-action-service.d.ts.map +1 -0
  11. package/lib/browser/monaco-code-action-service.js +132 -0
  12. package/lib/browser/monaco-code-action-service.js.map +1 -0
  13. package/lib/browser/monaco-editor-model.d.ts +9 -4
  14. package/lib/browser/monaco-editor-model.d.ts.map +1 -1
  15. package/lib/browser/monaco-editor-model.js +5 -13
  16. package/lib/browser/monaco-editor-model.js.map +1 -1
  17. package/lib/browser/monaco-editor-provider.d.ts.map +1 -1
  18. package/lib/browser/monaco-editor-provider.js +5 -26
  19. package/lib/browser/monaco-editor-provider.js.map +1 -1
  20. package/lib/browser/monaco-frontend-module.d.ts.map +1 -1
  21. package/lib/browser/monaco-frontend-module.js +3 -0
  22. package/lib/browser/monaco-frontend-module.js.map +1 -1
  23. package/lib/browser/monaco-utilities.d.ts +3 -0
  24. package/lib/browser/monaco-utilities.d.ts.map +1 -0
  25. package/lib/browser/monaco-utilities.js +45 -0
  26. package/lib/browser/monaco-utilities.js.map +1 -0
  27. package/package.json +9 -9
  28. package/src/browser/index.ts +1 -0
  29. package/src/browser/monaco-code-action-save-participant.ts +9 -103
  30. package/src/browser/monaco-code-action-service.ts +187 -0
  31. package/src/browser/monaco-editor-model.ts +10 -15
  32. package/src/browser/monaco-editor-provider.ts +5 -29
  33. package/src/browser/monaco-frontend-module.ts +3 -0
  34. package/src/browser/monaco-utilities.ts +46 -0
@@ -0,0 +1,187 @@
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 { inject, injectable } from '@theia/core/shared/inversify';
19
+ import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
20
+ import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
21
+ import { CodeActionKind, CodeActionSet, CodeActionTriggerSource } from '@theia/monaco-editor-core/esm/vs/editor/contrib/codeAction/common/types';
22
+ import { applyCodeAction, ApplyCodeActionReason, getCodeActions } from '@theia/monaco-editor-core/esm/vs/editor/contrib/codeAction/browser/codeAction';
23
+ import { HierarchicalKind } from '@theia/monaco-editor-core/esm/vs/base/common/hierarchicalKind';
24
+ import { EditorPreferences } from '@theia/editor/lib/browser';
25
+ import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
26
+ import { CodeActionProvider, CodeActionTriggerType } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
27
+ import { IProgress } from '@theia/monaco-editor-core/esm/vs/platform/progress/common/progress';
28
+ import { IInstantiationService } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation';
29
+
30
+ export const MonacoCodeActionService = Symbol('MonacoCodeActionService');
31
+
32
+ /*---------------------------------------------------------------------------------------------
33
+ * Copyright (c) Microsoft Corporation. All rights reserved.
34
+ * Licensed under the MIT License. See License.txt in the project root for license information.
35
+ *--------------------------------------------------------------------------------------------*/
36
+ // Partially copied from https://github.com/microsoft/vscode/blob/f66e839a38dfe39ee66a86619a790f9c2336d698/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts#L272
37
+
38
+ export interface MonacoCodeActionService {
39
+ /**
40
+ * Gets all code actions that should be applied on save for the given model and language identifier.
41
+ * @param model The text model to get code actions for
42
+ * @param languageId The language identifier for preference lookup
43
+ * @param uri The URI string for preference scoping
44
+ * @param token Cancellation token
45
+ * @returns Array of code action sets to apply, or undefined if no actions should be applied
46
+ */
47
+ getAllCodeActionsOnSave(model: ITextModel, languageId: string, uri: string, token: CancellationToken): Promise<CodeActionSet[] | undefined>;
48
+
49
+ /**
50
+ * Applies the provided code actions for the given model.
51
+ * @param model The text model to apply code actions to
52
+ * @param codeActionSets Array of code action sets to apply
53
+ * @param token Cancellation token
54
+ */
55
+ applyCodeActions(model: ITextModel, codeActionSets: CodeActionSet[], token: CancellationToken): Promise<void>;
56
+
57
+ /**
58
+ * Applies all code actions that should be run on save for the given model and language identifier.
59
+ * This is a convenience method that retrieves all on-save code actions and applies them.
60
+ * @param model The text model to apply code actions to
61
+ * @param languageId The language identifier for preference lookup
62
+ * @param uri The URI string for preference scoping
63
+ * @param token Cancellation token
64
+ */
65
+ applyOnSaveCodeActions(model: ITextModel, languageId: string, uri: string, token: CancellationToken): Promise<void>;
66
+ }
67
+
68
+ @injectable()
69
+ export class MonacoCodeActionServiceImpl implements MonacoCodeActionService {
70
+ @inject(EditorPreferences)
71
+ protected readonly editorPreferences: EditorPreferences;
72
+
73
+ async applyOnSaveCodeActions(model: ITextModel, languageId: string, uri: string, token: CancellationToken): Promise<void> {
74
+ const codeActionSets = await this.getAllCodeActionsOnSave(model, languageId, uri, token);
75
+
76
+ if (!codeActionSets || token.isCancellationRequested) {
77
+ return;
78
+ }
79
+
80
+ await this.applyCodeActions(model, codeActionSets, token);
81
+ }
82
+
83
+ async getAllCodeActionsOnSave(model: ITextModel, languageId: string, uri: string, token: CancellationToken): Promise<CodeActionSet[] | undefined> {
84
+ const setting = this.editorPreferences.get({
85
+ preferenceName: 'editor.codeActionsOnSave',
86
+ overrideIdentifier: languageId
87
+ }, undefined, uri);
88
+
89
+ if (!setting) {
90
+ return undefined;
91
+ }
92
+
93
+ const settingItems: string[] = Array.isArray(setting)
94
+ ? setting
95
+ : Object.keys(setting).filter(x => setting[x]);
96
+
97
+ const codeActionsOnSave = this.createCodeActionsOnSave(settingItems);
98
+
99
+ if (!codeActionsOnSave.length) {
100
+ return undefined;
101
+ }
102
+
103
+ if (!Array.isArray(setting)) {
104
+ codeActionsOnSave.sort((a, b) => {
105
+ if (CodeActionKind.SourceFixAll.contains(a)) {
106
+ if (CodeActionKind.SourceFixAll.contains(b)) {
107
+ return 0;
108
+ }
109
+ return -1;
110
+ }
111
+ if (CodeActionKind.SourceFixAll.contains(b)) {
112
+ return 1;
113
+ }
114
+ return 0;
115
+ });
116
+ }
117
+
118
+ const excludedActions = Array.isArray(setting)
119
+ ? []
120
+ : Object.keys(setting)
121
+ .filter(x => setting[x] === false)
122
+ .map(x => new HierarchicalKind(x));
123
+
124
+ const codeActionSets: CodeActionSet[] = [];
125
+
126
+ for (const codeActionKind of codeActionsOnSave) {
127
+ const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludedActions, token);
128
+
129
+ if (token.isCancellationRequested) {
130
+ actionsToRun.dispose();
131
+ break;
132
+ }
133
+
134
+ codeActionSets.push(actionsToRun);
135
+ }
136
+
137
+ return codeActionSets;
138
+ }
139
+
140
+ async applyCodeActions(model: ITextModel, codeActionSets: CodeActionSet[], token: CancellationToken): Promise<void> {
141
+
142
+ const instantiationService = StandaloneServices.get(IInstantiationService);
143
+
144
+ for (const codeActionSet of codeActionSets) {
145
+ if (token.isCancellationRequested) {
146
+ codeActionSet.dispose();
147
+ return;
148
+ }
149
+
150
+ try {
151
+ for (const action of codeActionSet.validActions) {
152
+ await instantiationService.invokeFunction(applyCodeAction, action, ApplyCodeActionReason.OnSave, {}, token);
153
+ if (token.isCancellationRequested) {
154
+ return;
155
+ }
156
+ }
157
+ } catch {
158
+ // Failure to apply a code action should not block other on save actions
159
+ } finally {
160
+ codeActionSet.dispose();
161
+ }
162
+ }
163
+ }
164
+
165
+ private createCodeActionsOnSave(settingItems: readonly string[]): HierarchicalKind[] {
166
+ const kinds = settingItems.map(x => new HierarchicalKind(x));
167
+
168
+ // Remove subsets
169
+ return kinds.filter(kind => kinds.every(otherKind => otherKind.equals(kind) || !otherKind.contains(kind)));
170
+ }
171
+
172
+ private getActionsToRun(model: ITextModel, codeActionKind: HierarchicalKind, excludes: readonly HierarchicalKind[], token: CancellationToken): Promise<CodeActionSet> {
173
+ const { codeActionProvider } = StandaloneServices.get(ILanguageFeaturesService);
174
+
175
+ const progress: IProgress<CodeActionProvider> = {
176
+ report(item): void {
177
+ // empty
178
+ },
179
+ };
180
+
181
+ return getCodeActions(codeActionProvider, model, model.getFullModelRange(), {
182
+ type: CodeActionTriggerType.Auto,
183
+ triggerAction: CodeActionTriggerSource.OnSave,
184
+ filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true },
185
+ }, progress, token);
186
+ }
187
+ }
@@ -34,12 +34,17 @@ import { createTextBufferFactoryFromStream } from '@theia/monaco-editor-core/esm
34
34
  import { editorGeneratedPreferenceProperties } from '@theia/editor/lib/browser/editor-generated-preference-schema';
35
35
  import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
36
36
  import { BinaryBuffer } from '@theia/core/lib/common/buffer';
37
+ import { Listener, ListenerList } from '@theia/core';
37
38
 
38
39
  export {
39
40
  TextDocumentSaveReason
40
41
  };
41
42
 
42
- export type WillSaveMonacoModelListener = (model: MonacoEditorModel, token: CancellationToken, options?: SaveOptions) => Promise<void>;
43
+ export interface WillSaveMonacoModelEvent {
44
+ model: MonacoEditorModel,
45
+ token: CancellationToken,
46
+ options?: SaveOptions
47
+ }
43
48
 
44
49
  export interface MonacoModelContentChangedEvent {
45
50
  readonly model: MonacoEditorModel;
@@ -91,7 +96,8 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
91
96
 
92
97
  protected resourceVersion: ResourceVersion | undefined;
93
98
 
94
- protected readonly willSaveModelListeners: WillSaveMonacoModelListener[] = [];
99
+ protected readonly onWillSaveModelListeners: ListenerList<WillSaveMonacoModelEvent, Promise<void>> = new ListenerList;
100
+ readonly onModelWillSaveModel = this.onWillSaveModelListeners.registration;
95
101
 
96
102
  constructor(
97
103
  protected readonly resource: Resource,
@@ -105,6 +111,7 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
105
111
  this.toDispose.push(this.onDidChangeContentEmitter);
106
112
  this.toDispose.push(this.onDidSaveModelEmitter);
107
113
  this.toDispose.push(this.onDirtyChangedEmitter);
114
+ this.toDispose.push(this.onDidChangeEncodingEmitter);
108
115
  this.toDispose.push(this.onDidChangeValidEmitter);
109
116
  this.toDispose.push(Disposable.create(() => this.cancelSave()));
110
117
  this.toDispose.push(Disposable.create(() => this.cancelSync()));
@@ -582,20 +589,8 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
582
589
  }
583
590
  }
584
591
 
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);
591
- }
592
- });
593
- }
594
-
595
592
  protected async fireWillSaveModel(token: CancellationToken, options?: SaveOptions): Promise<void> {
596
- for (const listener of this.willSaveModelListeners) {
597
- await listener(this, token, options);
598
- }
593
+ await Listener.await({ model: this, token, options }, this.onWillSaveModelListeners);
599
594
  }
600
595
 
601
596
  protected fireDidSaveModel(): void {
@@ -45,6 +45,7 @@ import { SimpleMonacoEditor } from './simple-monaco-editor';
45
45
  import { ICodeEditorWidgetOptions } from '@theia/monaco-editor-core/esm/vs/editor/browser/widget/codeEditor/codeEditorWidget';
46
46
  import { timeoutReject } from '@theia/core/lib/common/promise-util';
47
47
  import { FileSystemPreferences } from '@theia/filesystem/lib/browser';
48
+ import { insertFinalNewline } from './monaco-utilities';
48
49
 
49
50
  export const MonacoEditorFactory = Symbol('MonacoEditorFactory');
50
51
  export interface MonacoEditorFactory {
@@ -226,7 +227,7 @@ export class MonacoEditorProvider {
226
227
  }));
227
228
  toDispose.push(editor.onLanguageChanged(() => this.updateMonacoEditorOptions(editor)));
228
229
  toDispose.push(editor.onDidChangeReadOnly(() => this.updateReadOnlyMessage(options, model.readOnly)));
229
- toDispose.push(editor.document.registerWillSaveModelListener((_, token, o) => this.runSaveParticipants(editor, token, o)));
230
+ toDispose.push(editor.document.onModelWillSaveModel(e => this.runSaveParticipants(editor, e.token, e.options)));
230
231
  return editor;
231
232
  }
232
233
 
@@ -552,38 +553,13 @@ export class MonacoEditorProvider {
552
553
  if (shouldRemoveWhiteSpace) {
553
554
  await editor.runAction('editor.action.trimTrailingWhitespace');
554
555
  }
555
- const insertFinalNewline = this.filePreferences.get({ preferenceName: 'files.insertFinalNewline', overrideIdentifier }, undefined, uri);
556
- if (insertFinalNewline) {
556
+ const shouldInsertFinalNewline = this.filePreferences.get({ preferenceName: 'files.insertFinalNewline', overrideIdentifier }, undefined, uri);
557
+ if (shouldInsertFinalNewline) {
557
558
  this.insertFinalNewline(model);
558
559
  }
559
560
  }
560
561
 
561
562
  protected insertFinalNewline(editorModel: MonacoEditorModel): void {
562
- const model = editorModel.textEditorModel;
563
- if (!model) {
564
- return;
565
- }
566
-
567
- const lines = model?.getLineCount();
568
- if (lines === 0) {
569
- return;
570
- }
571
-
572
- const lastLine = model?.getLineContent(lines);
573
- if (lastLine.trim() === '') {
574
- return;
575
- }
576
-
577
- const lastLineMaxColumn = model?.getLineMaxColumn(lines);
578
- const range = {
579
- startLineNumber: lines,
580
- startColumn: lastLineMaxColumn,
581
- endLineNumber: lines,
582
- endColumn: lastLineMaxColumn
583
- };
584
- model.applyEdits([{
585
- range,
586
- text: model?.getEOL()
587
- }]);
563
+ insertFinalNewline(editorModel);
588
564
  }
589
565
  }
@@ -80,6 +80,7 @@ import { ILogService } from '@theia/monaco-editor-core/esm/vs/platform/log/commo
80
80
  import { DefaultContentHoverWidgetPatcher } from './default-content-hover-widget-patcher';
81
81
  import { MonacoWorkspaceContextService } from './monaco-workspace-context-service';
82
82
  import { MonacoCodeActionSaveParticipant } from './monaco-code-action-save-participant';
83
+ import { MonacoCodeActionService, MonacoCodeActionServiceImpl } from './monaco-code-action-service';
83
84
 
84
85
  export default new ContainerModule((bind, unbind, isBound, rebind) => {
85
86
  bind(MonacoThemingService).toSelf().inSingletonScope();
@@ -92,6 +93,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
92
93
  bind(FrontendApplicationContribution).toService(MonacoFrontendApplicationContribution);
93
94
  bind(StylingParticipant).toService(MonacoFrontendApplicationContribution);
94
95
 
96
+ bind(MonacoCodeActionServiceImpl).toSelf().inSingletonScope();
97
+ bind(MonacoCodeActionService).toService(MonacoCodeActionServiceImpl);
95
98
  bind(MonacoCodeActionSaveParticipant).toSelf().inSingletonScope();
96
99
  bind(SaveParticipant).toService(MonacoCodeActionSaveParticipant);
97
100
 
@@ -0,0 +1,46 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 EclipseSource GmbH.
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 { MonacoEditorModel } from './monaco-editor-model';
18
+
19
+ export function insertFinalNewline(editorModel: MonacoEditorModel): void {
20
+ const model = editorModel.textEditorModel;
21
+ if (!model) {
22
+ return;
23
+ }
24
+
25
+ const lines = model?.getLineCount();
26
+ if (lines === 0) {
27
+ return;
28
+ }
29
+
30
+ const lastLine = model?.getLineContent(lines);
31
+ if (lastLine.trim() === '') {
32
+ return;
33
+ }
34
+
35
+ const lastLineMaxColumn = model?.getLineMaxColumn(lines);
36
+ const range = {
37
+ startLineNumber: lines,
38
+ startColumn: lastLineMaxColumn,
39
+ endLineNumber: lines,
40
+ endColumn: lastLineMaxColumn
41
+ };
42
+ model.applyEdits([{
43
+ range,
44
+ text: model?.getEOL()
45
+ }]);
46
+ }