Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ruff updates for DHE support #2280

Merged
merged 5 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions __mocks__/@astral-sh/ruff-wasm-web.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const init = jest.fn();
export const Workspace = jest.fn();
export default init;
4 changes: 4 additions & 0 deletions jest.config.base.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ module.exports = {
),
// Handle monaco worker files
'\\.worker.*$': 'identity-obj-proxy',
'^@astral-sh/ruff-wasm-web$': path.join(
__dirname,
'./__mocks__/@astral-sh/ruff-wasm-web.js'
),
// Handle pouchdb modules
'^pouchdb-browser$': path.join(
__dirname,
Expand Down
28 changes: 7 additions & 21 deletions packages/console/src/monaco/MonacoProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*/
import { PureComponent } from 'react';
import * as monaco from 'monaco-editor';
import throttle from 'lodash.throttle';
import Log from '@deephaven/log';
import type { dh } from '@deephaven/jsapi-types';
import init, { Workspace, type Diagnostic } from '@astral-sh/ruff-wasm-web';
Expand Down Expand Up @@ -64,7 +63,11 @@ class MonacoProviders extends PureComponent<
MonacoProviders.ruffSettings = settings;

// Ruff has not been initialized yet
if (MonacoProviders.ruffWorkspace == null) {
if (
MonacoProviders.ruffWorkspace == null &&
MonacoProviders.isRuffEnabled
) {
MonacoProviders.initRuff();
return;
}

Expand Down Expand Up @@ -239,7 +242,7 @@ class MonacoProviders extends PureComponent<
model: monaco.editor.ITextModel,
range: monaco.Range
): monaco.languages.ProviderResult<monaco.languages.CodeActionList> {
if (!MonacoProviders.ruffWorkspace) {
if (!MonacoProviders.isRuffEnabled || !MonacoProviders.ruffWorkspace) {
return {
actions: [],
dispose: () => {
Expand Down Expand Up @@ -397,7 +400,7 @@ class MonacoProviders extends PureComponent<
}

componentDidMount(): void {
const { language, session, model } = this.props;
const { language, session } = this.props;

this.registeredCompletionProvider =
monaco.languages.registerCompletionItemProvider(language, {
Expand All @@ -421,23 +424,6 @@ class MonacoProviders extends PureComponent<
}
);
}

if (language === 'python') {
if (MonacoProviders.ruffWorkspace == null) {
MonacoProviders.initRuff(); // This will also lint all open editors
} else {
MonacoProviders.lintPython(model);
}

const throttledLint = throttle(
(m: monaco.editor.ITextModel) => MonacoProviders.lintPython(m),
250
);

model.onDidChangeContent(() => {
throttledLint(model);
});
}
}

componentWillUnmount(): void {
Expand Down
40 changes: 40 additions & 0 deletions packages/console/src/monaco/MonacoUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { nanoid } from 'nanoid';
import throttle from 'lodash.throttle';
/**
* Exports a function for initializing monaco with the deephaven theme/config
*/
Expand Down Expand Up @@ -70,6 +71,24 @@ class MonacoUtils {
});
});

monaco.editor.onDidCreateModel(model => {
// Lint Python models on creation and on change
if (model.getLanguageId() === 'python') {
if (MonacoProviders.ruffWorkspace != null) {
MonacoProviders.lintPython(model);
}

const throttledLint = throttle(
(m: monaco.editor.ITextModel) => MonacoProviders.lintPython(m),
250
);

model.onDidChangeContent(() => {
throttledLint(model);
});
}
});

MonacoUtils.removeConflictingKeybindings();

log.debug('Monaco initialized.');
Expand Down Expand Up @@ -547,6 +566,27 @@ class MonacoUtils {
static isConsoleModel(model: monaco.editor.ITextModel): boolean {
return model.uri.toString().startsWith(CONSOLE_URI_PREFIX);
}

/**
* Checks if the editor has the formatDocument action registered.
* @param editor The monaco editor to check
* @returns If the editor has a document formatter registered
*/
static canFormat(editor: monaco.editor.IStandaloneCodeEditor): boolean {
return (
editor.getAction('editor.action.formatDocument')?.isSupported() === true
);
}

/**
* Runs the formatDocument action on the editor.
* @param editor The editor to format
*/
static async formatDocument(
editor: monaco.editor.IStandaloneCodeEditor
): Promise<void> {
await editor.getAction('editor.action.formatDocument')?.run();
}
}

export default MonacoUtils;
81 changes: 48 additions & 33 deletions packages/console/src/monaco/RuffSettingsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useRef, useState } from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import * as monaco from 'monaco-editor';
import { Workspace } from '@astral-sh/ruff-wasm-web';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
Expand Down Expand Up @@ -30,14 +30,10 @@ interface RuffSettingsModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (value: Record<string, unknown>) => void;
readOnly?: boolean;
defaultSettings?: Record<string, unknown>;
}

const FORMATTED_DEFAULT_SETTINGS = JSON.stringify(
RUFF_DEFAULT_SETTINGS,
null,
2
);

const RUFF_SETTINGS_URI = monaco.Uri.parse(
'inmemory://dh-config/ruff-settings.json'
);
Expand Down Expand Up @@ -71,11 +67,18 @@ export default function RuffSettingsModal({
isOpen,
onClose,
onSave,
readOnly = false,
defaultSettings = RUFF_DEFAULT_SETTINGS,
}: RuffSettingsModalProps): React.ReactElement | null {
const [isValid, setIsValid] = useState(false);
const [isDefault, setIsDefault] = useState(false);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();

const formattedDefaultSettings = useMemo(
() => JSON.stringify(defaultSettings, null, 2),
[defaultSettings]
);

const { data: ruffVersion } = usePromiseFactory(getRuffVersion);

const [model] = useState(() =>
Expand Down Expand Up @@ -103,20 +106,23 @@ export default function RuffSettingsModal({

const handleReset = useCallback((): void => {
assertNotNull(model);
model.setValue(FORMATTED_DEFAULT_SETTINGS);
}, [model]);

const validate = useCallback(val => {
try {
JSON.parse(val);
setIsValid(true);
} catch {
setIsValid(false);
}
setIsDefault(
editorRef.current?.getModel()?.getValue() === FORMATTED_DEFAULT_SETTINGS
);
}, []);
model.setValue(formattedDefaultSettings);
}, [model, formattedDefaultSettings]);

const validate = useCallback(
val => {
try {
JSON.parse(val);
setIsValid(true);
} catch {
setIsValid(false);
}
setIsDefault(
editorRef.current?.getModel()?.getValue() === formattedDefaultSettings
);
},
[formattedDefaultSettings]
);

const debouncedValidate = useDebouncedCallback(validate, 500, {
leading: true,
Expand Down Expand Up @@ -172,6 +178,7 @@ export default function RuffSettingsModal({
<Editor
onEditorInitialized={onEditorInitialized}
settings={{
readOnly,
value: text,
language: 'json',
folding: true,
Expand All @@ -183,18 +190,26 @@ export default function RuffSettingsModal({
/>
</ModalBody>
<ModalFooter>
<Button kind="secondary" data-dismiss="modal" onClick={handleClose}>
Cancel
</Button>
<Button
kind="primary"
data-dismiss="modal"
tooltip={!isValid ? 'Cannot save invalid JSON' : undefined}
disabled={!isValid}
onClick={handleSave}
>
Save
</Button>
{readOnly ? (
<Button kind="secondary" data-dismiss="modal" onClick={handleClose}>
Close
</Button>
) : (
<>
<Button kind="secondary" data-dismiss="modal" onClick={handleClose}>
Cancel
</Button>
<Button
kind="primary"
data-dismiss="modal"
tooltip={!isValid ? 'Cannot save invalid JSON' : undefined}
disabled={!isValid}
onClick={handleSave}
>
Save
</Button>
</>
)}
</ModalFooter>
</Modal>
);
Expand Down
17 changes: 4 additions & 13 deletions packages/dashboard-core-plugins/src/ConsolePlugin.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MonacoProviders, type ScriptEditor } from '@deephaven/console';
import { type ScriptEditor } from '@deephaven/console';
import {
assertIsDashboardPluginProps,
type DashboardPluginComponentProps,
Expand All @@ -7,15 +7,13 @@ import {
LayoutUtils,
type PanelComponent,
type PanelHydrateFunction,
useAppSelector,
useListener,
usePanelRegistration,
} from '@deephaven/dashboard';
import { FileUtils } from '@deephaven/file-explorer';
import { type CloseOptions, isComponent } from '@deephaven/golden-layout';
import Log from '@deephaven/log';
import { getNotebookSettings } from '@deephaven/redux';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { nanoid } from 'nanoid';
import { ConsoleEvent, NotebookEvent } from './events';
Expand All @@ -27,6 +25,7 @@ import {
NotebookPanel,
} from './panels';
import { setDashboardConsoleSettings } from './redux';
import useConfigureRuff from './useConfigureRuff';

const log = Log.module('ConsolePlugin');

Expand Down Expand Up @@ -76,15 +75,7 @@ export function ConsolePlugin(
new Map<string, string>()
);

const { python: { linter = {} } = {} } = useAppSelector(getNotebookSettings);
const { isEnabled: ruffEnabled = false, config: ruffConfig } = linter;
useEffect(
function setRuffSettings() {
MonacoProviders.isRuffEnabled = ruffEnabled;
MonacoProviders.setRuffSettings(ruffConfig);
},
[ruffEnabled, ruffConfig]
);
useConfigureRuff();

const dispatch = useDispatch();

Expand Down
1 change: 1 addition & 0 deletions packages/dashboard-core-plugins/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export { default as ControlType } from './controls/ControlType';
export { default as LinkerUtils } from './linker/LinkerUtils';
export type { Link } from './linker/LinkerUtils';
export { default as ToolType } from './linker/ToolType';
export * from './useConfigureRuff';
export * from './useLoadTablePlugin';

export * from './events';
Expand Down
21 changes: 11 additions & 10 deletions packages/dashboard-core-plugins/src/panels/NotebookPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import {
type DropdownAction,
LoadingOverlay,
} from '@deephaven/components';
import { ScriptEditor, ScriptEditorUtils, SHORTCUTS } from '@deephaven/console';
import {
MonacoUtils,
ScriptEditor,
ScriptEditorUtils,
SHORTCUTS,
} from '@deephaven/console';
import {
type FileStorage,
FileUtils,
Expand Down Expand Up @@ -1005,18 +1010,14 @@ class NotebookPanel extends Component<NotebookPanelProps, NotebookPanelState> {
}

canFormat(): boolean {
return (
this.notebook?.editor
?.getAction('editor.action.formatDocument')
?.isSupported() === true
);
return this.notebook?.editor != null
? MonacoUtils.canFormat(this.notebook.editor)
: false;
}

async handleFormat(): Promise<void> {
if (this.canFormat()) {
await this.notebook?.editor
?.getAction('editor.action.formatDocument')
?.run();
if (this.notebook?.editor != null) {
await MonacoUtils.formatDocument(this.notebook.editor);
}
}

Expand Down
23 changes: 23 additions & 0 deletions packages/dashboard-core-plugins/src/useConfigureRuff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect } from 'react';
import { MonacoProviders } from '@deephaven/console';
import { useAppSelector } from '@deephaven/dashboard';
import { getNotebookSettings } from '@deephaven/redux';

/**
* Hook to configure Ruff settings in Monaco.
* The enabled status and settings are read from redux.
* Any changes to the redux values will be applied to the Monaco providers.
*/
export function useConfigureRuff(): void {
const { python: { linter = {} } = {} } = useAppSelector(getNotebookSettings);
const { isEnabled: ruffEnabled = false, config: ruffConfig } = linter;
useEffect(
function setRuffSettings() {
MonacoProviders.isRuffEnabled = ruffEnabled;
MonacoProviders.setRuffSettings(ruffConfig); // Also inits Ruff if needed
},
[ruffEnabled, ruffConfig]
);
}

export default useConfigureRuff;
Loading