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