Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { DisposableStore } from '../../../../base/common/lifecycle.js';
7
import { getCodeEditor } from '../../../../editor/browser/editorBrowser.js';
8
import { localize, localize2 } from '../../../../nls.js';
9
import { Registry } from '../../../../platform/registry/common/platform.js';
10
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js';
11
import { IEditorService } from '../../../services/editor/common/editorService.js';
12
import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';
13
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from '../../../services/statusbar/browser/statusbar.js';
14
import { ILanguageDetectionService, LanguageDetectionHintConfig, LanguageDetectionLanguageEventSource } from '../../../services/languageDetection/common/languageDetectionWorkerService.js';
15
import { ThrottledDelayer } from '../../../../base/common/async.js';
16
import { ILanguageService } from '../../../../editor/common/languages/language.js';
17
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
18
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
19
import { registerAction2, Action2 } from '../../../../platform/actions/common/actions.js';
20
import { INotificationService } from '../../../../platform/notification/common/notification.js';
21
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
22
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
23
import { NOTEBOOK_EDITOR_EDITABLE } from '../../notebook/common/notebookContextKeys.js';
24
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
25
import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js';
26
import { Schemas } from '../../../../base/common/network.js';
27
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
28
29
const detectLanguageCommandId = 'editor.detectLanguage';
30
31
class LanguageDetectionStatusContribution implements IWorkbenchContribution {
32
33
private static readonly _id = 'status.languageDetectionStatus';
34
35
private readonly _disposables = new DisposableStore();
36
private _combinedEntry?: IStatusbarEntryAccessor;
37
private _delayer = new ThrottledDelayer(1000);
38
private readonly _renderDisposables = new DisposableStore();
39
40
constructor(
41
@ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService,
42
@IStatusbarService private readonly _statusBarService: IStatusbarService,
43
@IConfigurationService private readonly _configurationService: IConfigurationService,
44
@IEditorService private readonly _editorService: IEditorService,
45
@ILanguageService private readonly _languageService: ILanguageService,
46
@IKeybindingService private readonly _keybindingService: IKeybindingService,
47
) {
48
_editorService.onDidActiveEditorChange(() => this._update(true), this, this._disposables);
49
this._update(false);
50
}
51
52
dispose(): void {
53
this._disposables.dispose();
54
this._delayer.dispose();
55
this._combinedEntry?.dispose();
56
this._renderDisposables.dispose();
57
}
58
59
private _update(clear: boolean): void {
60
if (clear) {
61
this._combinedEntry?.dispose();
62
this._combinedEntry = undefined;
63
}
64
this._delayer.trigger(() => this._doUpdate());
65
}
66
67
private async _doUpdate(): Promise<void> {
68
const editor = getCodeEditor(this._editorService.activeTextEditorControl);
69
70
this._renderDisposables.clear();
71
72
// update when editor language changes
73
editor?.onDidChangeModelLanguage(() => this._update(true), this, this._renderDisposables);
74
editor?.onDidChangeModelContent(() => this._update(false), this, this._renderDisposables);
75
const editorModel = editor?.getModel();
76
const editorUri = editorModel?.uri;
77
const existingId = editorModel?.getLanguageId();
78
const enablementConfig = this._configurationService.getValue<LanguageDetectionHintConfig>('workbench.editor.languageDetectionHints');
79
const enabled = typeof enablementConfig === 'object' && enablementConfig?.untitledEditors;
80
const disableLightbulb = !enabled || editorUri?.scheme !== Schemas.untitled || !existingId;
81
82
if (disableLightbulb || !editorUri) {
83
this._combinedEntry?.dispose();
84
this._combinedEntry = undefined;
85
} else {
86
const lang = await this._languageDetectionService.detectLanguage(editorUri);
87
const skip: Record<string, string | undefined> = { 'jsonc': 'json' };
88
const existing = editorModel.getLanguageId();
89
if (lang && lang !== existing && skip[existing] !== lang) {
90
const detectedName = this._languageService.getLanguageName(lang) || lang;
91
let tooltip = localize('status.autoDetectLanguage', "Accept Detected Language: {0}", detectedName);
92
const keybinding = this._keybindingService.lookupKeybinding(detectLanguageCommandId);
93
const label = keybinding?.getLabel();
94
if (label) {
95
tooltip += ` (${label})`;
96
}
97
98
const props: IStatusbarEntry = {
99
name: localize('langDetection.name', "Language Detection"),
100
ariaLabel: localize('langDetection.aria', "Change to Detected Language: {0}", lang),
101
tooltip,
102
command: detectLanguageCommandId,
103
text: '$(lightbulb-autofix)',
104
};
105
if (!this._combinedEntry) {
106
this._combinedEntry = this._statusBarService.addEntry(props, LanguageDetectionStatusContribution._id, StatusbarAlignment.RIGHT, { location: { id: 'status.editor.mode', priority: 100.1 }, alignment: StatusbarAlignment.RIGHT, compact: true });
107
} else {
108
this._combinedEntry.update(props);
109
}
110
} else {
111
this._combinedEntry?.dispose();
112
this._combinedEntry = undefined;
113
}
114
}
115
}
116
}
117
118
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LanguageDetectionStatusContribution, LifecyclePhase.Restored);
119
120
121
registerAction2(class extends Action2 {
122
123
constructor() {
124
super({
125
id: detectLanguageCommandId,
126
title: localize2('detectlang', "Detect Language from Content"),
127
f1: true,
128
precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE.toNegated(), EditorContextKeys.editorTextFocus),
129
keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib }
130
});
131
}
132
133
async run(accessor: ServicesAccessor): Promise<void> {
134
const editorService = accessor.get(IEditorService);
135
const languageDetectionService = accessor.get(ILanguageDetectionService);
136
const editor = getCodeEditor(editorService.activeTextEditorControl);
137
const notificationService = accessor.get(INotificationService);
138
const editorUri = editor?.getModel()?.uri;
139
if (editorUri) {
140
const lang = await languageDetectionService.detectLanguage(editorUri);
141
if (lang) {
142
editor.getModel()?.setLanguage(lang, LanguageDetectionLanguageEventSource);
143
} else {
144
notificationService.warn(localize('noDetection', "Unable to detect editor language"));
145
}
146
}
147
}
148
});
149
150