Skip to content

Commit

Permalink
working notebook cell language select
Browse files Browse the repository at this point in the history
Also fixing notebook enter in notebook cell editors

Signed-off-by: Jonah Iden <jonah.iden@typefox.io>
  • Loading branch information
jonah-iden committed Apr 17, 2024
1 parent fea794f commit 9eb3432
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 40 deletions.
40 changes: 7 additions & 33 deletions packages/editor/src/browser/editor-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@

import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
import { CommandContribution, CommandRegistry, Command } from '@theia/core/lib/common';
import URI from '@theia/core/lib/common/uri';
import { CommonCommands, PreferenceService, LabelProvider, ApplicationShell, QuickInputService, QuickPickValue, QuickPickItemOrSeparator } from '@theia/core/lib/browser';
import { CommonCommands, PreferenceService, LabelProvider, ApplicationShell, QuickInputService, QuickPickValue } from '@theia/core/lib/browser';
import { EditorManager } from './editor-manager';
import { EditorPreferences } from './editor-preferences';
import { ResourceProvider, MessageService } from '@theia/core';
import { LanguageService, Language } from '@theia/core/lib/browser/language-service';
import { LanguageService } from '@theia/core/lib/browser/language-service';
import { SUPPORTED_ENCODINGS } from '@theia/core/lib/browser/supported-encodings';
import { EncodingMode } from './editor';
import { nls } from '@theia/core/lib/common/nls';
import { EditorLanguageQuickPickService } from './editor-language-quick-pick-service';

export namespace EditorCommands {

Expand Down Expand Up @@ -237,6 +237,9 @@ export class EditorCommandContribution implements CommandContribution {
@inject(ResourceProvider)
protected readonly resourceProvider: ResourceProvider;

@inject(EditorLanguageQuickPickService)
protected readonly codeLanguageQuickPickService: EditorLanguageQuickPickService;

@postConstruct()
protected init(): void {
this.editorPreferences.onPreferenceChanged(e => {
Expand Down Expand Up @@ -293,12 +296,7 @@ export class EditorCommandContribution implements CommandContribution {
return;
}
const current = editor.document.languageId;
const items: Array<QuickPickValue<'autoDetect' | Language> | QuickPickItemOrSeparator> = [
{ label: nls.localizeByDefault('Auto Detect'), value: 'autoDetect' },
{ type: 'separator', label: nls.localizeByDefault('languages (identifier)') },
... (this.languages.languages.map(language => this.toQuickPickLanguage(language, current))).sort((e, e2) => e.label.localeCompare(e2.label))
];
const selectedMode = await this.quickInputService?.showQuickPick(items, { placeholder: nls.localizeByDefault('Select Language Mode') });
const selectedMode = await this.codeLanguageQuickPickService.pickEditorLanguage(current);
if (selectedMode && ('value' in selectedMode)) {
if (selectedMode.value === 'autoDetect') {
editor.detectLanguage();
Expand Down Expand Up @@ -379,30 +377,6 @@ export class EditorCommandContribution implements CommandContribution {
}
}

protected toQuickPickLanguage(value: Language, current: string): QuickPickValue<Language> {
const languageUri = this.toLanguageUri(value);
const icon = this.labelProvider.getIcon(languageUri);
const iconClasses = icon !== '' ? [icon + ' file-icon'] : undefined;
const configured = current === value.id;
return {
value,
label: value.name,
description: nls.localizeByDefault(`({0})${configured ? ' - Configured Language' : ''}`, value.id),
iconClasses
};
}
protected toLanguageUri(language: Language): URI {
const extension = language.extensions.values().next();
if (extension.value) {
return new URI('file:///' + extension.value);
}
const filename = language.filenames.values().next();
if (filename.value) {
return new URI('file:///' + filename.value);
}
return new URI('file:///.txt');
}

protected isAutoSaveOn(): boolean {
const autoSave = this.preferencesService.get(EditorCommandContribution.AUTOSAVE_PREFERENCE);
return autoSave !== 'off';
Expand Down
3 changes: 3 additions & 0 deletions packages/editor/src/browser/editor-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { QuickEditorService } from './quick-editor-service';
import { EditorLanguageStatusService } from './language-status/editor-language-status-service';
import { EditorLineNumberContribution } from './editor-linenumber-contribution';
import { UndoRedoService } from './undo-redo-service';
import { EditorLanguageQuickPickService } from './editor-language-quick-pick-service';

export default new ContainerModule(bind => {
bindEditorPreferences(bind);
Expand Down Expand Up @@ -84,4 +85,6 @@ export default new ContainerModule(bind => {
bind(EditorAccess).to(ActiveEditorAccess).inSingletonScope().whenTargetNamed(EditorAccess.ACTIVE);

bind(UndoRedoService).toSelf().inSingletonScope();

bind(EditorLanguageQuickPickService).toSelf().inSingletonScope();
});
69 changes: 69 additions & 0 deletions packages/editor/src/browser/editor-language-quick-pick-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

// *****************************************************************************
// Copyright (C) 2024 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { inject, injectable } from '@theia/core/shared/inversify';
import { Language, LanguageService } from '@theia/core/lib/browser/language-service';
import { nls, QuickInputService, QuickPickItemOrSeparator, QuickPickValue, URI } from '@theia/core';
import { LabelProvider } from '@theia/core/lib/browser';

@injectable()
export class EditorLanguageQuickPickService {
@inject(LanguageService)
protected readonly languages: LanguageService;

@inject(QuickInputService)
protected readonly quickInputService: QuickInputService;

@inject(LabelProvider)
protected readonly labelProvider: LabelProvider;

async pickEditorLanguage(current: string): Promise<QuickPickValue<'autoDetect' | Language> | undefined> {
const items: Array<QuickPickValue<'autoDetect' | Language> | QuickPickItemOrSeparator> = [
{ label: nls.localizeByDefault('Auto Detect'), value: 'autoDetect' },
{ type: 'separator', label: nls.localizeByDefault('languages (identifier)') },
... (this.languages.languages.map(language => this.toQuickPickLanguage(language, current))).sort((e, e2) => e.label.localeCompare(e2.label))
];
const selectedMode = await this.quickInputService?.showQuickPick(items, { placeholder: nls.localizeByDefault('Select Language Mode') });
return (selectedMode && 'value' in selectedMode) ? selectedMode : undefined;
}

protected toQuickPickLanguage(value: Language, current: string): QuickPickValue<Language> {
const languageUri = this.toLanguageUri(value);
const icon = this.labelProvider.getIcon(languageUri);
const iconClasses = icon !== '' ? [icon + ' file-icon'] : undefined;
const configured = current === value.id;
return {
value,
label: value.name,
description: nls.localizeByDefault(`({0})${configured ? ' - Configured Language' : ''}`, value.id),
iconClasses
};
}

protected toLanguageUri(language: Language): URI {
const extension = language.extensions.values().next();
if (extension.value) {
return new URI('file:///' + extension.value);
}
const filename = language.filenames.values().next();
if (filename.value) {
return new URI('file:///' + filename.value);
}
return new URI('file:///.txt');
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { CellEditType, CellKind } from '../../common';
import { NotebookEditorWidgetService } from '../service/notebook-editor-widget-service';
import { NotebookCommands } from './notebook-actions-contribution';
import { changeCellType } from './cell-operations';
import { EditorLanguageQuickPickService } from '@theia/editor/lib/browser/editor-language-quick-pick-service';

export namespace NotebookCellCommands {
/** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel */
Expand Down Expand Up @@ -131,6 +132,12 @@ export namespace NotebookCellCommands {
label: 'Expand Cell Output',
});

export const CHANGE_CELL_LANGUAGE = Command.toDefaultLocalizedCommand({
id: 'notebook.cell.changeLanguage',
category: 'Notebook',
label: 'Change Cell Language',
});

}

@injectable()
Expand All @@ -145,6 +152,9 @@ export class NotebookCellActionContribution implements MenuContribution, Command
@inject(NotebookEditorWidgetService)
protected notebookEditorWidgetService: NotebookEditorWidgetService;

@inject(EditorLanguageQuickPickService)
protected languageQuickPickService: EditorLanguageQuickPickService;

@postConstruct()
protected init(): void {
NotebookContextKeys.initNotebookContextKeys(this.contextKeyService);
Expand Down Expand Up @@ -359,6 +369,24 @@ export class NotebookCellActionContribution implements MenuContribution, Command
}
});

commands.registerCommand(NotebookCellCommands.CHANGE_CELL_LANGUAGE, {
isVisible: () => !!this.notebookEditorWidgetService.focusedEditor?.model?.selectedCell,
execute: async (notebook?: NotebookModel, cell?: NotebookCellModel) => {
const selectedCell = cell ?? this.notebookEditorWidgetService.focusedEditor?.model?.selectedCell;
const activeNotebook = notebook ?? this.notebookEditorWidgetService.focusedEditor?.model;
if (selectedCell && activeNotebook) {
const language = await this.languageQuickPickService.pickEditorLanguage(selectedCell.language);
if (language?.value && language.value !== 'autoDetect') {
this.notebookEditorWidgetService.focusedEditor?.model?.applyEdits([{
editType: CellEditType.CellLanguage,
index: activeNotebook.cells.indexOf(selectedCell),
language: language.value.id
}], true);
}
}
}
});

}

protected editableCellCommandHandler(execute: (notebookModel: NotebookModel, cell: NotebookCellModel, output?: NotebookCellOutputModel) => void): CommandHandler {
Expand All @@ -382,7 +410,7 @@ export class NotebookCellActionContribution implements MenuContribution, Command
{
command: NotebookCellCommands.EDIT_COMMAND.id,
keybinding: 'Enter',
when: `${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
},
{
command: NotebookCellCommands.STOP_EDIT_COMMAND.id,
Expand Down
9 changes: 7 additions & 2 deletions packages/notebook/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,13 @@
flex-grow: 1;
}

.notebook-cell-status-right {
margin: 0 5px;
.notebook-cell-language-label {
padding: 0 5px;
}

.notebook-cell-language-label:hover {
cursor: pointer;
background-color: var(--theia-toolbar-hoverBackground);
}

.notebook-cell-status-item {
Expand Down
4 changes: 4 additions & 0 deletions packages/notebook/src/browser/view/notebook-cell-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
this.editor?.getControl().updateOptions(options);
}));

this.toDispose.push(this.props.cell.onDidChangeLanguage(language => {
this.editor?.setLanguage(language);
}));

this.toDispose.push(this.props.notebookModel.onDidChangeSelectedCell(() => {
if (this.props.notebookModel.selectedCell !== this.props.cell && this.editor?.getControl().hasTextFocus()) {
if (document.activeElement && 'blur' in document.activeElement) {
Expand Down
22 changes: 18 additions & 4 deletions packages/notebook/src/browser/view/notebook-code-cell-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import { NotebookModel } from '../view-model/notebook-model';
import { CellEditor } from './notebook-cell-editor';
import { CellRenderer } from './notebook-cell-list-view';
import { NotebookCellToolbarFactory } from './notebook-cell-toolbar-factory';
import { NotebookCellActionContribution } from '../contributions/notebook-cell-actions-contribution';
import { NotebookCellActionContribution, NotebookCellCommands } from '../contributions/notebook-cell-actions-contribution';
import { CellExecution, NotebookExecutionStateService } from '../service/notebook-execution-state-service';
import { codicon } from '@theia/core/lib/browser';
import { NotebookCellExecutionState } from '../../common';
import { DisposableCollection, nls } from '@theia/core';
import { CommandRegistry, DisposableCollection, nls } from '@theia/core';
import { NotebookContextManager } from '../service/notebook-context-manager';
import { NotebookViewportService } from './notebook-viewport-service';
import { EditorPreferences } from '@theia/editor/lib/browser';
Expand Down Expand Up @@ -61,6 +61,9 @@ export class NotebookCodeCellRenderer implements CellRenderer {
@inject(EditorPreferences)
protected readonly editorPreferences: EditorPreferences;

@inject(CommandRegistry)
protected readonly commandRegistry: CommandRegistry;

protected fontInfo: BareFontInfo | undefined;

render(notebookModel: NotebookModel, cell: NotebookCellModel, handle: number): React.ReactNode {
Expand All @@ -76,7 +79,10 @@ export class NotebookCodeCellRenderer implements CellRenderer {
notebookContextManager={this.notebookContextManager}
notebookViewportService={this.notebookViewportService}
fontInfo={this.getOrCreateMonacoFontInfo()} />
<NotebookCodeCellStatus cell={cell} executionStateService={this.executionStateService} onClick={() => cell.requestFocusEditor()}></NotebookCodeCellStatus>
<NotebookCodeCellStatus cell={cell} notebook={notebookModel}
commandRegistry={this.commandRegistry}
executionStateService={this.executionStateService}
onClick={() => cell.requestFocusEditor()} />
</div >
</div >
<div className='theia-notebook-cell-with-sidebar'>
Expand Down Expand Up @@ -108,7 +114,9 @@ export class NotebookCodeCellRenderer implements CellRenderer {
}

export interface NotebookCodeCellStatusProps {
notebook: NotebookModel;
cell: NotebookCellModel;
commandRegistry: CommandRegistry;
executionStateService: NotebookExecutionStateService;
onClick: () => void;
}
Expand Down Expand Up @@ -146,6 +154,10 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
}
}
}));

this.toDispose.push(props.cell.onDidChangeLanguage(() => {
this.forceUpdate();
}));
}

override componentWillUnmount(): void {
Expand All @@ -158,7 +170,9 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
{this.renderExecutionState()}
</div>
<div className='notebook-cell-status-right'>
<span>{this.props.cell.language}</span>
<span className='notebook-cell-language-label' onClick={() => {
this.props.commandRegistry.executeCommand(NotebookCellCommands.CHANGE_CELL_LANGUAGE.id, this.props.notebook, this.props.cell);
}}>{this.props.cell.language}</span>
</div>
</div>;
}
Expand Down

0 comments on commit 9eb3432

Please sign in to comment.