Skip to content

Commit 7a051d6

Browse files
authored
Enable auto language detection for chat code generation. (microsoft#210600)
* Enable auto language detection for chat code generation.
1 parent 75aea74 commit 7a051d6

File tree

7 files changed

+100
-22
lines changed

7 files changed

+100
-22
lines changed

src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ async function startChat(accessor: ServicesAccessor, context: INotebookActionCon
377377
} else {
378378
const newCell = await insertNewCell(accessor, context, CellKind.Code, 'below', true);
379379
if (newCell) {
380+
newCell.enableAutoLanguageDetection();
380381
await context.notebookEditor.revealFirstLineIfOutsideViewport(newCell);
381382
const codeEditor = context.notebookEditor.codeEditors.find(ce => ce[0] === newCell)?.[1];
382383
if (codeEditor) {

src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ export interface ICellViewModel extends IGenericCellViewModel {
276276
updateEditState(state: CellEditState, source: string): void;
277277
deltaModelDecorations(oldDecorations: readonly string[], newDecorations: readonly IModelDeltaDecoration[]): string[];
278278
getCellDecorationRange(id: string): Range | null;
279+
enableAutoLanguageDetection(): void;
279280
}
280281

281282
export interface IEditableCellViewModel extends ICellViewModel {

src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/se
1818
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1919
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
2020
import { IWordWrapTransientState, readTransientState, writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap';
21+
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
2122
import { CellEditState, CellFocusMode, CursorAtBoundary, CursorAtLineBoundary, IEditableCellViewModel, INotebookCellDecorationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
2223
import { NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions';
2324
import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents';
@@ -272,6 +273,14 @@ export abstract class BaseCellViewModel extends Disposable {
272273
});
273274

274275
this._editorListeners.push(this._textEditor.onDidChangeCursorSelection(() => { this._onDidChangeState.fire({ selectionChanged: true }); }));
276+
const inlineChatController = InlineChatController.get(this._textEditor);
277+
if (inlineChatController) {
278+
this._editorListeners.push(inlineChatController.onWillStartSession(() => {
279+
if (this.textBuffer.getLength() === 0) {
280+
this.enableAutoLanguageDetection();
281+
}
282+
}));
283+
}
275284
// this._editorListeners.push(this._textEditor.onKeyDown(e => this.handleKeyDown(e)));
276285
this._onDidChangeState.fire({ selectionChanged: true });
277286
this._onDidChangeEditorAttachState.fire();
@@ -315,6 +324,10 @@ export abstract class BaseCellViewModel extends Disposable {
315324
return this.model.getTextLength();
316325
}
317326

327+
enableAutoLanguageDetection() {
328+
this.model.enableAutoLanguageDetection();
329+
}
330+
318331
private saveViewState(): void {
319332
if (!this._textEditor) {
320333
return;

src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'
1717
import { ILanguageService } from 'vs/editor/common/languages/language';
1818
import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel';
1919
import { CellInternalMetadataChangedEvent, CellKind, ICell, ICellDto2, ICellOutput, IOutputDto, IOutputItemDto, NotebookCellCollapseState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
20+
import { ThrottledDelayer } from 'vs/base/common/async';
21+
import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
2022

2123
export class NotebookCellTextModel extends Disposable implements ICell {
2224
private readonly _onDidChangeOutputs = this._register(new Emitter<NotebookCellOutputsSplice>());
@@ -85,25 +87,9 @@ export class NotebookCellTextModel extends Disposable implements ICell {
8587
return;
8688
}
8789

88-
const newLanguageId = this._languageService.getLanguageIdByLanguageName(newLanguage);
89-
90-
if (newLanguageId === null) {
91-
return;
92-
}
9390

94-
if (this._textModel) {
95-
const languageId = this._languageService.createById(newLanguageId);
96-
this._textModel.setLanguage(languageId.languageId);
97-
}
98-
99-
if (this._language === newLanguage) {
100-
return;
101-
}
102-
103-
this._language = newLanguage;
104-
this._hash = null;
105-
this._onDidChangeLanguage.fire(newLanguage);
106-
this._onDidChangeContent.fire('language');
91+
this._hasLanguageSetExplicitly = true;
92+
this._setLanguageInternal(newLanguage);
10793
}
10894

10995
public get mime(): string | undefined {
@@ -138,6 +124,7 @@ export class NotebookCellTextModel extends Disposable implements ICell {
138124
if (!this._textModel) {
139125
this._onDidChangeContent.fire('content');
140126
}
127+
this.autoDetectLanguage();
141128
}));
142129

143130
return this._textBuffer;
@@ -195,6 +182,11 @@ export class NotebookCellTextModel extends Disposable implements ICell {
195182
this.language = newLanguage;
196183
}
197184
}
185+
private static readonly AUTO_DETECT_LANGUAGE_THROTTLE_DELAY = 600;
186+
private readonly autoDetectLanguageThrottler = this._register(new ThrottledDelayer<void>(NotebookCellTextModel.AUTO_DETECT_LANGUAGE_THROTTLE_DELAY));
187+
private _autoLanguageDetectionEnabled: boolean = false;
188+
private _hasLanguageSetExplicitly: boolean = false;
189+
get hasLanguageSetExplicitly(): boolean { return this._hasLanguageSetExplicitly; }
198190

199191
constructor(
200192
readonly uri: URI,
@@ -208,14 +200,67 @@ export class NotebookCellTextModel extends Disposable implements ICell {
208200
internalMetadata: NotebookCellInternalMetadata | undefined,
209201
public readonly collapseState: NotebookCellCollapseState | undefined,
210202
public readonly transientOptions: TransientOptions,
211-
private readonly _languageService: ILanguageService
203+
private readonly _languageService: ILanguageService,
204+
private readonly _languageDetectionService: ILanguageDetectionService | undefined = undefined
212205
) {
213206
super();
214207
this._outputs = outputs.map(op => new NotebookCellOutputTextModel(op));
215208
this._metadata = metadata ?? {};
216209
this._internalMetadata = internalMetadata ?? {};
217210
}
218211

212+
enableAutoLanguageDetection() {
213+
this._autoLanguageDetectionEnabled = true;
214+
this.autoDetectLanguage();
215+
}
216+
217+
async autoDetectLanguage(): Promise<void> {
218+
if (this._autoLanguageDetectionEnabled) {
219+
this.autoDetectLanguageThrottler.trigger(() => this._doAutoDetectLanguage());
220+
}
221+
}
222+
223+
private async _doAutoDetectLanguage(): Promise<void> {
224+
if (this.hasLanguageSetExplicitly) {
225+
return;
226+
}
227+
228+
const newLanguage = await this._languageDetectionService?.detectLanguage(this.uri);
229+
if (!newLanguage) {
230+
return;
231+
}
232+
233+
if (this._textModel
234+
&& this._textModel.getLanguageId() === this._languageService.getLanguageIdByLanguageName(newLanguage)
235+
&& this._textModel.getLanguageId() === this._languageService.getLanguageIdByLanguageName(this.language)) {
236+
return;
237+
}
238+
239+
this._setLanguageInternal(newLanguage);
240+
}
241+
242+
private _setLanguageInternal(newLanguage: string) {
243+
const newLanguageId = this._languageService.getLanguageIdByLanguageName(newLanguage);
244+
245+
if (newLanguageId === null) {
246+
return;
247+
}
248+
249+
if (this._textModel) {
250+
const languageId = this._languageService.createById(newLanguageId);
251+
this._textModel.setLanguage(languageId.languageId);
252+
}
253+
254+
if (this._language === newLanguage) {
255+
return;
256+
}
257+
258+
this._language = newLanguage;
259+
this._hash = null;
260+
this._onDidChangeLanguage.fire(newLanguage);
261+
this._onDidChangeContent.fire('language');
262+
}
263+
219264
resetTextBuffer(textBuffer: model.ITextBuffer) {
220265
this._textBuffer = textBuffer;
221266
}

src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language';
2020
import { ITextModel } from 'vs/editor/common/model';
2121
import { TextModel } from 'vs/editor/common/model/textModel';
2222
import { isDefined } from 'vs/base/common/types';
23+
import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
2324

2425

2526
class StackOperation implements IWorkspaceUndoRedoElement {
@@ -213,6 +214,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
213214
@IUndoRedoService private readonly _undoService: IUndoRedoService,
214215
@IModelService private readonly _modelService: IModelService,
215216
@ILanguageService private readonly _languageService: ILanguageService,
217+
@ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService
216218
) {
217219
super();
218220
this.transientOptions = options;
@@ -285,7 +287,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
285287
const cellHandle = this._cellhandlePool++;
286288
const cellUri = CellUri.generate(this.uri, cellHandle);
287289
const collapseState = this._getDefaultCollapseState(cell);
288-
return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.mime, cell.cellKind, cell.outputs, cell.metadata, cell.internalMetadata, collapseState, this.transientOptions, this._languageService);
290+
return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.mime, cell.cellKind, cell.outputs, cell.metadata, cell.internalMetadata, collapseState, this.transientOptions, this._languageService, this._languageDetectionService);
289291
});
290292

291293
for (let i = 0; i < mainCells.length; i++) {
@@ -722,7 +724,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
722724
const cell = new NotebookCellTextModel(
723725
cellUri, cellHandle,
724726
cellDto.source, cellDto.language, cellDto.mime, cellDto.cellKind, cellDto.outputs || [], cellDto.metadata, cellDto.internalMetadata, collapseState, this.transientOptions,
725-
this._languageService
727+
this._languageService,
728+
this._languageDetectionService
726729
);
727730
const textModel = this._modelService.getModel(cellUri);
728731
if (textModel && textModel instanceof TextModel) {

src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { IBaseCellEditorOptions } from 'vs/workbench/contrib/notebook/browser/no
3131
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
3232
import { mainWindow } from 'vs/base/browser/window';
3333
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
34+
import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
3435

3536
suite('NotebookViewModel', () => {
3637
ensureNoDisposablesAreLeakedInTestSuite();
@@ -42,6 +43,7 @@ suite('NotebookViewModel', () => {
4243
let undoRedoService: IUndoRedoService;
4344
let modelService: IModelService;
4445
let languageService: ILanguageService;
46+
let languageDetectionService: ILanguageDetectionService;
4547
let notebookExecutionStateService: INotebookExecutionStateService;
4648

4749
suiteSetup(() => {
@@ -52,6 +54,7 @@ suite('NotebookViewModel', () => {
5254
undoRedoService = instantiationService.get(IUndoRedoService);
5355
modelService = instantiationService.get(IModelService);
5456
languageService = instantiationService.get(ILanguageService);
57+
languageDetectionService = instantiationService.get(ILanguageDetectionService);
5558
notebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
5659

5760
instantiationService.stub(IConfigurationService, new TestConfigurationService());
@@ -61,7 +64,7 @@ suite('NotebookViewModel', () => {
6164
suiteTeardown(() => disposables.dispose());
6265

6366
test('ctor', function () {
64-
const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService);
67+
const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService, languageDetectionService);
6568
const model = new NotebookEditorTestModel(notebook);
6669
const options = new NotebookOptions(mainWindow, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService), false);
6770
const eventDispatcher = new NotebookEventDispatcher();

src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices
6868
import { IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
6969
import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl';
7070
import { INotebookCellOutlineProviderFactory, NotebookCellOutlineProviderFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory';
71+
import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
7172

7273
export class TestCell extends NotebookCellTextModel {
7374
constructor(
@@ -202,6 +203,17 @@ export function setupInstantiationService(disposables: DisposableStore) {
202203
instantiationService.stub(ICodeEditorService, disposables.add(new TestCodeEditorService(testThemeService)));
203204
instantiationService.stub(IInlineChatService, instantiationService.createInstance(InlineChatServiceImpl));
204205
instantiationService.stub(INotebookCellOutlineProviderFactory, instantiationService.createInstance(NotebookCellOutlineProviderFactory));
206+
207+
instantiationService.stub(ILanguageDetectionService, new class MockLanguageDetectionService implements ILanguageDetectionService {
208+
_serviceBrand: undefined;
209+
isEnabledForLanguage(languageId: string): boolean {
210+
return false;
211+
}
212+
async detectLanguage(resource: URI, supportedLangs?: string[] | undefined): Promise<string | undefined> {
213+
return undefined;
214+
}
215+
});
216+
205217
return instantiationService;
206218
}
207219

0 commit comments

Comments
 (0)