diff --git a/package-lock.json b/package-lock.json
index 0d10132f12..6604a7cee0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26338,6 +26338,124 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/rollup-plugin-visualizer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.3.tgz",
+ "integrity": "sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw==",
+ "dev": true,
+ "dependencies": {
+ "open": "^8.0.0",
+ "picomatch": "^4.0.2",
+ "source-map": "^0.7.4",
+ "yargs": "^17.5.1"
+ },
+ "bin": {
+ "rollup-plugin-visualizer": "dist/bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "rolldown": "1.x || ^1.0.0-beta",
+ "rollup": "2.x || 3.x || 4.x"
+ },
+ "peerDependenciesMeta": {
+ "rolldown": {
+ "optional": true
+ },
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "dev": true,
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/run-async": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@@ -30595,7 +30713,8 @@
"@deephaven/eslint-config": "file:../eslint-config",
"@deephaven/mocks": "file:../mocks",
"@deephaven/prettier-config": "file:../prettier-config",
- "@deephaven/stylelint-config": "file:../stylelint-config"
+ "@deephaven/stylelint-config": "file:../stylelint-config",
+ "rollup-plugin-visualizer": "^6.0.3"
}
},
"packages/embed-widget/node_modules/@deephaven/jsapi-types": {
@@ -33196,7 +33315,8 @@
"nanoid": "5.0.7",
"react": "^17.0.2",
"react-dom": "^17.0.2",
- "react-redux": "^7.2.4"
+ "react-redux": "^7.2.4",
+ "rollup-plugin-visualizer": "^6.0.3"
},
"dependencies": {
"@deephaven/jsapi-types": {
@@ -51180,6 +51300,81 @@
"fsevents": "~2.3.2"
}
},
+ "rollup-plugin-visualizer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.3.tgz",
+ "integrity": "sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw==",
+ "dev": true,
+ "requires": {
+ "open": "^8.0.0",
+ "picomatch": "^4.0.2",
+ "source-map": "^0.7.4",
+ "yargs": "^17.5.1"
+ },
+ "dependencies": {
+ "cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "dev": true,
+ "requires": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ }
+ },
+ "picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true
+ },
+ "y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "requires": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ }
+ },
+ "yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true
+ }
+ }
+ },
"run-async": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
diff --git a/packages/code-studio/src/main/AppMainContainer.tsx b/packages/code-studio/src/main/AppMainContainer.tsx
index a30fb05fdd..880bd6e78d 100644
--- a/packages/code-studio/src/main/AppMainContainer.tsx
+++ b/packages/code-studio/src/main/AppMainContainer.tsx
@@ -46,7 +46,7 @@ import {
emitCycleToPreviousTab,
} from '@deephaven/dashboard';
import {
- ConsolePlugin,
+ // ConsolePlugin,
InputFilterEvent,
MarkdownEvent,
NotebookEvent,
@@ -93,7 +93,7 @@ import {
createExportLogsContextAction,
} from '@deephaven/app-utils';
import JSZip from 'jszip';
-import SettingsMenu from '../settings/SettingsMenu';
+import { LazySettingsMenu } from '../settings/LazySettingsMenu';
import AppControlsMenu from './AppControlsMenu';
import { getLayoutStorage, getServerConfigValues } from '../redux';
import './AppMainContainer.scss';
@@ -1053,21 +1053,21 @@ export class AppMainContainer extends Component<
)
}
plugins={[
- ,
+ // ,
...dashboardPlugins,
]}
/>
-
{isRuffSettingsOpen && (
- import('./SettingsMenu'));
+
+export function LazySettingsMenu(props: SettingsMenuProps): JSX.Element {
+ return (
+ }
+ >
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
+
+
+ );
+}
+
+export default LazySettingsMenu;
diff --git a/packages/code-studio/src/settings/SettingsMenu.tsx b/packages/code-studio/src/settings/SettingsMenu.tsx
index 34f72a5ece..1f74e1ddcf 100644
--- a/packages/code-studio/src/settings/SettingsMenu.tsx
+++ b/packages/code-studio/src/settings/SettingsMenu.tsx
@@ -47,7 +47,7 @@ import AdvancedSectionContent from './AdvancedSectionContent';
import ThemeSectionContent from './ThemeSectionContent';
import EditorSectionContent from './EditorSectionContent';
-interface SettingsMenuProps {
+export interface SettingsMenuProps {
serverConfigValues: ServerConfigValues;
pluginData: PluginModuleMap;
user: User;
diff --git a/packages/console/src/ConsoleInput.tsx b/packages/console/src/ConsoleInput.tsx
index 5d6707a6d6..200038332c 100644
--- a/packages/console/src/ConsoleInput.tsx
+++ b/packages/console/src/ConsoleInput.tsx
@@ -1,6 +1,6 @@
import React, { PureComponent, type ReactElement, type RefObject } from 'react';
import classNames from 'classnames';
-import * as monaco from 'monaco-editor';
+import type * as Monaco from 'monaco-editor';
import Log from '@deephaven/log';
import {
assertNotNull,
@@ -38,7 +38,7 @@ interface ConsoleInputProps {
interface ConsoleInputState {
commandEditorHeight: number;
isFocused: boolean;
- model: monaco.editor.ITextModel | null;
+ model: Monaco.editor.ITextModel | null;
}
/**
@@ -103,7 +103,7 @@ export class ConsoleInput extends PureComponent<
commandContainer: RefObject;
- commandEditor?: monaco.editor.IStandaloneCodeEditor;
+ commandEditor?: Monaco.editor.IStandaloneCodeEditor;
commandHistoryIndex: number | null;
@@ -156,12 +156,13 @@ export class ConsoleInput extends PureComponent<
}
}
- initCommandEditor(): void {
+ async initCommandEditor(): Promise {
const { language, session } = this.props;
+ const monaco = await MonacoUtils.lazyMonaco();
const model = monaco.editor.createModel(
'',
language,
- MonacoUtils.generateConsoleUri()
+ await MonacoUtils.generateConsoleUri()
);
const commandSettings = {
copyWithSyntaxHighlighting: false,
diff --git a/packages/console/src/common/Code.tsx b/packages/console/src/common/Code.tsx
index 8786207884..33cddd9b69 100644
--- a/packages/console/src/common/Code.tsx
+++ b/packages/console/src/common/Code.tsx
@@ -1,5 +1,4 @@
import React, { useEffect, useState, type ReactNode } from 'react';
-import * as monaco from 'monaco-editor';
import { useTheme } from '@deephaven/components';
interface CodeProps {
@@ -15,6 +14,7 @@ function Code({ children, language }: CodeProps): JSX.Element {
let isCanceled = false;
async function colorize() {
if (children != null && activeThemes != null) {
+ const monaco = await import('monaco-editor');
const result = await monaco.editor.colorize(
children.toString(),
language,
diff --git a/packages/console/src/log/LogView.tsx b/packages/console/src/log/LogView.tsx
index ee7b79b25e..1936d2c374 100644
--- a/packages/console/src/log/LogView.tsx
+++ b/packages/console/src/log/LogView.tsx
@@ -8,7 +8,7 @@ import { vsGear, dhTrashUndo } from '@deephaven/icons';
import { assertNotNull } from '@deephaven/utils';
import type { dh } from '@deephaven/jsapi-types';
import { type Placement } from 'popper.js';
-import * as monaco from 'monaco-editor';
+import type * as Monaco from 'monaco-editor';
import ConsoleUtils from '../common/ConsoleUtils';
import LogLevel from './LogLevel';
import './LogView.scss';
@@ -84,10 +84,10 @@ class LogView extends PureComponent {
componentDidMount(): void {
this.resetLogLevels();
- this.initMonaco();
- this.startListening();
-
- window.addEventListener('resize', this.handleResize);
+ this.initMonaco().then(() => {
+ this.startListening();
+ window.addEventListener('resize', this.handleResize);
+ });
}
componentDidUpdate(prevProps: LogViewProps, prevState: LogViewState): void {
@@ -117,7 +117,7 @@ class LogView extends PureComponent {
cancelListener?: () => void | null;
- editor?: monaco.editor.IStandaloneCodeEditor;
+ editor?: Monaco.editor.IStandaloneCodeEditor;
editorContainer: HTMLDivElement | null;
@@ -211,7 +211,8 @@ class LogView extends PureComponent {
}
}
- initMonaco(): void {
+ async initMonaco(): Promise {
+ const monaco = await MonacoUtils.lazyMonaco();
assertNotNull(this.editorContainer);
this.editor = monaco.editor.create(this.editorContainer, {
copyWithSyntaxHighlighting: false,
diff --git a/packages/console/src/monaco/LazyRuffSettingsModal.tsx b/packages/console/src/monaco/LazyRuffSettingsModal.tsx
new file mode 100644
index 0000000000..c60a19172a
--- /dev/null
+++ b/packages/console/src/monaco/LazyRuffSettingsModal.tsx
@@ -0,0 +1,25 @@
+import React, { lazy, Suspense } from 'react';
+import { usePromiseFactory } from '@deephaven/react-hooks';
+import type { RuffSettingsModalProps } from './RuffSettingsModal';
+import MonacoUtils from './MonacoUtils';
+
+const RuffSettingsModal = lazy(() => import('./RuffSettingsModal'));
+
+export function LazyRuffSettingsModal(
+ props: RuffSettingsModalProps
+): JSX.Element | null {
+ const { data: monaco } = usePromiseFactory(MonacoUtils.lazyMonaco, []);
+
+ if (monaco == null) {
+ return null;
+ }
+
+ return (
+ }>
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
+
+
+ );
+}
+
+export default LazyRuffSettingsModal;
diff --git a/packages/console/src/monaco/MonacoProviders.test.tsx b/packages/console/src/monaco/MonacoProviders.test.tsx
index 80f10d19d2..177115f187 100644
--- a/packages/console/src/monaco/MonacoProviders.test.tsx
+++ b/packages/console/src/monaco/MonacoProviders.test.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { render } from '@testing-library/react';
-import * as monaco from 'monaco-editor';
+// import * as monaco from 'monaco-editor';
import dh from '@deephaven/jsapi-shim';
import type { DocumentRange, Position } from '@deephaven/jsapi-types';
import MonacoProviders from './MonacoProviders';
diff --git a/packages/console/src/monaco/MonacoProviders.tsx b/packages/console/src/monaco/MonacoProviders.tsx
index 54cee45677..d0edcc3b57 100644
--- a/packages/console/src/monaco/MonacoProviders.tsx
+++ b/packages/console/src/monaco/MonacoProviders.tsx
@@ -2,7 +2,7 @@
* Completion provider for a code session
*/
import { PureComponent } from 'react';
-import * as monaco from 'monaco-editor';
+import type * as Monaco from 'monaco-editor';
import Log from '@deephaven/log';
import type { dh } from '@deephaven/jsapi-types';
import init, { Workspace, type Diagnostic } from '@astral-sh/ruff-wasm-web';
@@ -12,7 +12,7 @@ import MonacoUtils from './MonacoUtils';
const log = Log.module('MonacoCompletionProvider');
interface MonacoProviderProps {
- model: monaco.editor.ITextModel;
+ model: Monaco.editor.ITextModel;
session: dh.IdeSession;
language: string;
}
@@ -46,10 +46,10 @@ class MonacoProviders extends PureComponent<
return MonacoProviders.initRuffPromise;
}
- MonacoProviders.initRuffPromise = init({}).then(() => {
+ MonacoProviders.initRuffPromise = init({}).then(async () => {
log.debug('Initialized Ruff', Workspace.version());
MonacoProviders.isRuffInitialized = true;
- MonacoProviders.updateRuffWorkspace();
+ await MonacoProviders.updateRuffWorkspace();
});
return MonacoProviders.initRuffPromise;
@@ -59,7 +59,7 @@ class MonacoProviders extends PureComponent<
* Updates the current ruff workspace with MonacoProviders.ruffSettings.
* Re-lints all Python models after updating.
*/
- static updateRuffWorkspace(): void {
+ static async updateRuffWorkspace(): Promise {
if (!MonacoProviders.isRuffInitialized) {
return;
}
@@ -76,7 +76,7 @@ class MonacoProviders extends PureComponent<
}
/* eslint-enable no-console */
- MonacoProviders.lintAllPython();
+ await MonacoProviders.lintAllPython();
}
/**
@@ -96,7 +96,7 @@ class MonacoProviders extends PureComponent<
MonacoProviders.updateRuffWorkspace();
}
- static getDiagnostics(model: monaco.editor.ITextModel): Diagnostic[] {
+ static getDiagnostics(model: Monaco.editor.ITextModel): Diagnostic[] {
if (!MonacoProviders.ruffWorkspace) {
return [];
}
@@ -111,7 +111,9 @@ class MonacoProviders extends PureComponent<
return diagnostics;
}
- static lintAllPython(): void {
+ static async lintAllPython(): Promise {
+ const monaco = await MonacoUtils.lazyMonaco();
+
if (!MonacoProviders.isRuffEnabled) {
monaco.editor.removeAllMarkers('ruff');
return;
@@ -123,7 +125,7 @@ class MonacoProviders extends PureComponent<
.forEach(MonacoProviders.lintPython);
}
- static lintPython(model: monaco.editor.ITextModel): void {
+ static async lintPython(model: Monaco.editor.ITextModel): Promise {
if (!MonacoProviders.isRuffEnabled) {
return;
}
@@ -135,6 +137,8 @@ class MonacoProviders extends PureComponent<
const diagnostics = MonacoProviders.getDiagnostics(model);
log.debug(`Linting Python document: ${model.uri.toString()}`, diagnostics);
+ const monaco = await MonacoUtils.lazyMonaco();
+
monaco.editor.setModelMarkers(
model,
'ruff',
@@ -165,7 +169,8 @@ class MonacoProviders extends PureComponent<
* @param kind The LSP kind
* @returns Monaco kind
*/
- static lspToMonacoKind(kind: number | undefined): number {
+ static async lspToMonacoKind(kind: number | undefined): Promise {
+ const monaco = await MonacoUtils.lazyMonaco();
const monacoKinds = monaco.languages.CompletionItemKind;
switch (kind) {
case 1:
@@ -230,7 +235,7 @@ class MonacoProviders extends PureComponent<
* @param range The LSP document range to convert
* @returns The corresponding monaco range
*/
- static lspToMonacoRange(range: dh.lsp.Range): monaco.IRange {
+ static lspToMonacoRange(range: dh.lsp.Range): Monaco.IRange {
const { start, end } = range;
// Monaco expects the columns/ranges to start at 1. LSP starts at 0
@@ -250,7 +255,7 @@ class MonacoProviders extends PureComponent<
* @returns The corresponding LSP position
*/
static monacoToLspPosition(
- position: monaco.IPosition
+ position: Monaco.IPosition
): Pick {
// Monaco 1-indexes Position. LSP 0-indexes Position
return {
@@ -259,175 +264,188 @@ class MonacoProviders extends PureComponent<
};
}
- static handlePythonCodeActionRequest(
- model: monaco.editor.ITextModel,
- range: monaco.Range
- ): monaco.languages.ProviderResult {
- if (!MonacoProviders.isRuffEnabled || !MonacoProviders.ruffWorkspace) {
- return {
- actions: [],
- dispose: () => {
- /* no-op */
- },
- };
- }
-
- const diagnostics = MonacoProviders.getDiagnostics(model).filter(d => {
- const diagnosticRange = new monaco.Range(
- d.location.row,
- d.location.column,
- d.end_location.row,
- d.end_location.column
- );
- return (
- d.code != null && // Syntax errors have no code and can't be fixed/disabled
- diagnosticRange.intersectRanges(range)
- );
- });
-
- const fixActions: monaco.languages.CodeAction[] = diagnostics
- .filter(({ fix }) => fix != null)
- .map(d => {
- let title = 'Fix';
- if (d.fix != null) {
- if (d.fix.message != null && d.fix.message !== '') {
- title = `${d.code}: ${d.fix.message}`;
- } else {
- title = `Fix ${d.code}`;
- }
- }
+ static async createPythonCodeActionRequestHandler(): Promise<
+ (
+ model: Monaco.editor.ITextModel,
+ range: Monaco.Range
+ ) => Monaco.languages.ProviderResult
+ > {
+ const monaco = await MonacoUtils.lazyMonaco();
+
+ return function handlePythonCodeActionRequest(
+ model: Monaco.editor.ITextModel,
+ range: Monaco.Range
+ ): Monaco.languages.ProviderResult {
+ if (!MonacoProviders.isRuffEnabled || !MonacoProviders.ruffWorkspace) {
return {
- title,
- id: `fix-${d.code}`,
- kind: 'quickfix',
- edit: d.fix
- ? {
- edits: d.fix.edits.map(edit => ({
- resource: model.uri,
- versionId: model.getVersionId(),
- textEdit: {
- range: {
- startLineNumber: edit.location.row,
- startColumn: edit.location.column,
- endLineNumber: edit.end_location.row,
- endColumn: edit.end_location.column,
- },
- text: edit.content ?? '',
- },
- })),
- }
- : undefined,
+ actions: [],
+ dispose: () => {
+ /* no-op */
+ },
};
+ }
+
+ const diagnostics = MonacoProviders.getDiagnostics(model).filter(d => {
+ const diagnosticRange = new monaco.Range(
+ d.location.row,
+ d.location.column,
+ d.end_location.row,
+ d.end_location.column
+ );
+ return (
+ d.code != null && // Syntax errors have no code and can't be fixed/disabled
+ diagnosticRange.intersectRanges(range)
+ );
});
- const seenCodes = new Set();
- const duplicateCodes = new Set();
- diagnostics.forEach(d => {
- if (d.code == null) {
- return;
- }
- if (seenCodes.has(d.code)) {
- duplicateCodes.add(d.code);
- }
- seenCodes.add(d.code);
- });
+ const fixActions: Monaco.languages.CodeAction[] = diagnostics
+ .filter(({ fix }) => fix != null)
+ .map(d => {
+ let title = 'Fix';
+ if (d.fix != null) {
+ if (d.fix.message != null && d.fix.message !== '') {
+ title = `${d.code}: ${d.fix.message}`;
+ } else {
+ title = `Fix ${d.code}`;
+ }
+ }
+ return {
+ title,
+ id: `fix-${d.code}`,
+ kind: 'quickfix',
+ edit: d.fix
+ ? {
+ edits: d.fix.edits.map(edit => ({
+ resource: model.uri,
+ versionId: model.getVersionId(),
+ textEdit: {
+ range: {
+ startLineNumber: edit.location.row,
+ startColumn: edit.location.column,
+ endLineNumber: edit.end_location.row,
+ endColumn: edit.end_location.column,
+ },
+ text: edit.content ?? '',
+ },
+ })),
+ }
+ : undefined,
+ };
+ });
- const disableLineActions: monaco.languages.CodeAction[] = diagnostics
- .map(d => {
+ const seenCodes = new Set();
+ const duplicateCodes = new Set();
+ diagnostics.forEach(d => {
if (d.code == null) {
- // The nulls are already filtered out, but TS doesn't know that
- return [];
+ return;
}
- const line = model.getLineContent(d.location.row);
- const lastToken = monaco.editor
- .tokenize(line, model.getLanguageId())[0]
- .at(-1);
- const lineEdit = {
- range: {
- startLineNumber: d.location.row,
- startColumn: line.length + 1,
- endLineNumber: d.location.row,
- endColumn: line.length + 1,
- },
- text: ` # noqa: ${d.code}`,
- };
- if (lastToken != null && lastToken.type.startsWith('comment')) {
- // Already a comment at the end of the line
- lineEdit.text = `# noqa: ${d.code} `;
- if (line.startsWith('# noqa:', lastToken.offset)) {
- // Already another suppressed rule on the line
- lineEdit.range.startColumn = lastToken.offset + 1;
- lineEdit.range.endColumn = lastToken.offset + 9; // "# noqa: " length + 1 to offset
- } else {
- lineEdit.range.startColumn = lastToken.offset + 1;
- lineEdit.range.endColumn = line.startsWith('# ', lastToken.offset)
- ? lastToken.offset + 3 // "# " + 1 to offset
- : lastToken.offset + 2; // "#" + 1 to offset
- }
+ if (seenCodes.has(d.code)) {
+ duplicateCodes.add(d.code);
}
- return [
- {
- title: `Disable ${d.code} for ${
- duplicateCodes.has(d.code)
- ? `line ${d.location.row}`
- : 'this line'
- }`,
- kind: 'quickfix',
- edit: {
- edits: [
- {
- resource: model.uri,
- versionId: model.getVersionId(),
- textEdit: lineEdit,
- },
- ],
- },
- },
- ];
- })
- .flat()
- .filter(
- // Remove actions with duplicate titles as you can't disable the same rule on a line twice
- (action, i, arr) => arr.find(a => a.title === action.title) === action
- );
+ seenCodes.add(d.code);
+ });
- const disableGlobalActions: monaco.languages.CodeAction[] = [
- ...seenCodes,
- ].map(code => ({
- title: `Disable ${code} for this file`,
- kind: 'quickfix',
- edit: {
- edits: [
- {
- resource: model.uri,
- versionId: model.getVersionId(),
- textEdit: {
- range: {
- startLineNumber: 1,
- startColumn: 1,
- endLineNumber: 1,
- endColumn: 1,
+ const disableLineActions: Monaco.languages.CodeAction[] = diagnostics
+ .map(d => {
+ if (d.code == null) {
+ // The nulls are already filtered out, but TS doesn't know that
+ return [];
+ }
+ const line = model.getLineContent(d.location.row);
+ const lastToken = monaco.editor
+ .tokenize(line, model.getLanguageId())[0]
+ .at(-1);
+ const lineEdit = {
+ range: {
+ startLineNumber: d.location.row,
+ startColumn: line.length + 1,
+ endLineNumber: d.location.row,
+ endColumn: line.length + 1,
+ },
+ text: ` # noqa: ${d.code}`,
+ };
+ if (lastToken != null && lastToken.type.startsWith('comment')) {
+ // Already a comment at the end of the line
+ lineEdit.text = `# noqa: ${d.code} `;
+ if (line.startsWith('# noqa:', lastToken.offset)) {
+ // Already another suppressed rule on the line
+ lineEdit.range.startColumn = lastToken.offset + 1;
+ lineEdit.range.endColumn = lastToken.offset + 9; // "# noqa: " length + 1 to offset
+ } else {
+ lineEdit.range.startColumn = lastToken.offset + 1;
+ lineEdit.range.endColumn = line.startsWith('# ', lastToken.offset)
+ ? lastToken.offset + 3 // "# " + 1 to offset
+ : lastToken.offset + 2; // "#" + 1 to offset
+ }
+ }
+ return [
+ {
+ title: `Disable ${d.code} for ${
+ duplicateCodes.has(d.code)
+ ? `line ${d.location.row}`
+ : 'this line'
+ }`,
+ kind: 'quickfix',
+ edit: {
+ edits: [
+ {
+ resource: model.uri,
+ versionId: model.getVersionId(),
+ textEdit: lineEdit,
+ },
+ ],
},
- text: `# ruff: noqa: ${code}\n`,
},
- },
- ],
- },
- }));
+ ];
+ })
+ .flat()
+ .filter(
+ // Remove actions with duplicate titles as you can't disable the same rule on a line twice
+ (action, i, arr) => arr.find(a => a.title === action.title) === action
+ );
+
+ const disableGlobalActions: Monaco.languages.CodeAction[] = [
+ ...seenCodes,
+ ].map(code => ({
+ title: `Disable ${code} for this file`,
+ kind: 'quickfix',
+ edit: {
+ edits: [
+ {
+ resource: model.uri,
+ versionId: model.getVersionId(),
+ textEdit: {
+ range: {
+ startLineNumber: 1,
+ startColumn: 1,
+ endLineNumber: 1,
+ endColumn: 1,
+ },
+ text: `# ruff: noqa: ${code}\n`,
+ },
+ },
+ ],
+ },
+ }));
- return {
- actions: [...fixActions, ...disableLineActions, ...disableGlobalActions],
- dispose: () => {
- /* no-op */
- },
+ return {
+ actions: [
+ ...fixActions,
+ ...disableLineActions,
+ ...disableGlobalActions,
+ ],
+ dispose: () => {
+ /* no-op */
+ },
+ };
};
}
static handlePythonFormatRequest(
- model: monaco.editor.ITextModel,
- options: monaco.languages.FormattingOptions,
- token: monaco.CancellationToken
- ): monaco.languages.ProviderResult {
+ model: Monaco.editor.ITextModel,
+ options: Monaco.languages.FormattingOptions,
+ token: Monaco.CancellationToken
+ ): Monaco.languages.ProviderResult {
if (!MonacoProviders.ruffWorkspace) {
return;
}
@@ -453,28 +471,30 @@ class MonacoProviders extends PureComponent<
componentDidMount(): void {
const { language, session } = this.props;
- this.registeredCompletionProvider =
- monaco.languages.registerCompletionItemProvider(language, {
- provideCompletionItems: this.handleCompletionRequest,
- triggerCharacters: ['.', '"', "'"],
- });
-
- if (session.getSignatureHelp != null) {
- this.registeredSignatureProvider =
- monaco.languages.registerSignatureHelpProvider(language, {
- provideSignatureHelp: this.handleSignatureRequest,
- signatureHelpTriggerCharacters: ['(', ','],
+ MonacoUtils.lazyMonaco().then(monaco => {
+ this.registeredCompletionProvider =
+ monaco.languages.registerCompletionItemProvider(language, {
+ provideCompletionItems: this.handleCompletionRequest,
+ triggerCharacters: ['.', '"', "'"],
});
- }
- if (session.getHover != null) {
- this.registeredHoverProvider = monaco.languages.registerHoverProvider(
- language,
- {
- provideHover: this.handleHoverRequest,
- }
- );
- }
+ if (session.getSignatureHelp != null) {
+ this.registeredSignatureProvider =
+ monaco.languages.registerSignatureHelpProvider(language, {
+ provideSignatureHelp: this.handleSignatureRequest,
+ signatureHelpTriggerCharacters: ['(', ','],
+ });
+ }
+
+ if (session.getHover != null) {
+ this.registeredHoverProvider = monaco.languages.registerHoverProvider(
+ language,
+ {
+ provideHover: this.handleHoverRequest,
+ }
+ );
+ }
+ });
}
componentWillUnmount(): void {
@@ -483,17 +503,17 @@ class MonacoProviders extends PureComponent<
this.registeredHoverProvider?.dispose();
}
- registeredCompletionProvider?: monaco.IDisposable;
+ registeredCompletionProvider?: Monaco.IDisposable;
- registeredSignatureProvider?: monaco.IDisposable;
+ registeredSignatureProvider?: Monaco.IDisposable;
- registeredHoverProvider?: monaco.IDisposable;
+ registeredHoverProvider?: Monaco.IDisposable;
handleCompletionRequest(
- model: monaco.editor.ITextModel,
- position: monaco.Position,
- context: monaco.languages.CompletionContext
- ): monaco.languages.ProviderResult {
+ model: Monaco.editor.ITextModel,
+ position: Monaco.Position,
+ context: Monaco.languages.CompletionContext
+ ): Monaco.languages.ProviderResult {
const { model: propModel, session } = this.props;
if (model !== propModel) {
return null;
@@ -512,10 +532,13 @@ class MonacoProviders extends PureComponent<
log.debug('Requested completion items', params);
const monacoCompletionItems = completionItems
- .then(items => {
+ .then(async items => {
log.debug('Completion items received: ', params, items);
- const suggestions = items.map(item => {
+ const suggestions: Monaco.languages.CompletionItem[] = [];
+
+ // eslint-disable-next-line no-restricted-syntax
+ for (const item of items) {
const {
label,
kind,
@@ -527,9 +550,10 @@ class MonacoProviders extends PureComponent<
insertTextFormat,
} = item;
- return {
+ suggestions.push({
label,
- kind: MonacoProviders.lspToMonacoKind(kind),
+ // eslint-disable-next-line no-await-in-loop
+ kind: await MonacoProviders.lspToMonacoKind(kind),
detail,
documentation:
documentation?.kind === 'markdown'
@@ -543,8 +567,8 @@ class MonacoProviders extends PureComponent<
// Why microsoft is using almost-but-not-LSP apis is beyond me....
insertTextRules: insertTextFormat === 2 ? 4 : insertTextFormat,
range: MonacoProviders.lspToMonacoRange(textEdit.range),
- };
- });
+ });
+ }
return {
incomplete: true,
@@ -560,12 +584,12 @@ class MonacoProviders extends PureComponent<
}
handleSignatureRequest(
- model: monaco.editor.ITextModel,
- position: monaco.Position,
- token: monaco.CancellationToken,
- context: monaco.languages.SignatureHelpContext
- ): monaco.languages.ProviderResult {
- const defaultResult: monaco.languages.SignatureHelpResult = {
+ model: Monaco.editor.ITextModel,
+ position: Monaco.Position,
+ token: Monaco.CancellationToken,
+ context: Monaco.languages.SignatureHelpContext
+ ): Monaco.languages.ProviderResult {
+ const defaultResult: Monaco.languages.SignatureHelpResult = {
value: {
signatures: [],
activeSignature: 0,
@@ -637,9 +661,9 @@ class MonacoProviders extends PureComponent<
}
handleHoverRequest(
- model: monaco.editor.ITextModel,
- position: monaco.Position
- ): monaco.languages.ProviderResult {
+ model: Monaco.editor.ITextModel,
+ position: Monaco.Position
+ ): Monaco.languages.ProviderResult {
const { model: propModel, session } = this.props;
if (model !== propModel || session.getHover == null) {
return null;
diff --git a/packages/console/src/monaco/MonacoThemeProvider.tsx b/packages/console/src/monaco/MonacoThemeProvider.tsx
index 27c2dd1baf..6020f34106 100644
--- a/packages/console/src/monaco/MonacoThemeProvider.tsx
+++ b/packages/console/src/monaco/MonacoThemeProvider.tsx
@@ -1,6 +1,6 @@
import { type ReactNode, useEffect } from 'react';
import { useTheme } from '@deephaven/components';
-import MonacoUtils from './MonacoUtils';
+// import MonacoUtils from './MonacoUtils';
export interface MonacoThemeProviderProps {
children: ReactNode;
@@ -14,7 +14,7 @@ export function MonacoThemeProvider({
useEffect(
function refreshMonacoTheme() {
if (activeThemes != null) {
- MonacoUtils.initTheme();
+ // MonacoUtils.initTheme();
}
},
[activeThemes]
diff --git a/packages/console/src/monaco/MonacoUtils.test.ts b/packages/console/src/monaco/MonacoUtils.test.ts
index 915334197c..2325729fef 100644
--- a/packages/console/src/monaco/MonacoUtils.test.ts
+++ b/packages/console/src/monaco/MonacoUtils.test.ts
@@ -1,5 +1,5 @@
/* eslint-disable no-bitwise */
-import * as monaco from 'monaco-editor';
+// import * as monaco from 'monaco-editor';
import { Shortcut, KEY, MODIFIER } from '@deephaven/components';
import { TestUtils } from '@deephaven/test-utils';
import MonacoUtils from './MonacoUtils';
diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts
index 3ee5b00d3a..b2bff52065 100644
--- a/packages/console/src/monaco/MonacoUtils.ts
+++ b/packages/console/src/monaco/MonacoUtils.ts
@@ -11,12 +11,8 @@ import {
import type { dh } from '@deephaven/jsapi-types';
import { assertNotNull } from '@deephaven/utils';
import { find as linkifyFind } from 'linkifyjs';
-import * as monaco from 'monaco-editor';
-import type { Environment } from 'monaco-editor';
-// @ts-ignore
-import { KeyCodeUtils } from 'monaco-editor/esm/vs/base/common/keyCodes.js';
+import type * as Monaco from 'monaco-editor';
import Log from '@deephaven/log';
-import MonacoThemeRaw from './MonacoTheme.module.scss';
import PyLang from './lang/python';
import GroovyLang from './lang/groovy';
import ScalaLang from './lang/scala';
@@ -24,6 +20,7 @@ import DbLang from './lang/db';
import LogLang from './lang/log';
import { type Language } from './lang/Language';
import MonacoProviders from './MonacoProviders';
+import MonacoThemeRaw from './MonacoTheme.module.scss';
const log = Log.module('MonacoUtils');
@@ -31,19 +28,28 @@ const CONSOLE_URI_PREFIX = 'inmemory://dh-console/';
declare global {
interface Window {
- MonacoEnvironment?: Environment;
+ MonacoEnvironment?: Monaco.Environment;
}
}
class MonacoUtils {
+ /**
+ * Lazy load the monaco module
+ * @returns Promise to the monaco module
+ */
+ static async lazyMonaco(): Promise {
+ log.debug('Lazy loading Monaco...');
+ return import('monaco-editor');
+ }
+
/**
* Initializes Monaco for the environment
* @param getWorker The getWorker function Monaco should use
* The workers should be provided by the caller and bundled by their build system (e.g. Vite, Webpack)
*/
- static init({
+ static async init({
getWorker,
- }: { getWorker?: Environment['getWorker'] } = {}): void {
+ }: { getWorker?: Monaco.Environment['getWorker'] } = {}): Promise {
log.debug('Initializing Monaco...');
if (getWorker !== undefined) {
@@ -52,15 +58,18 @@ class MonacoUtils {
const { initTheme, registerLanguages } = MonacoUtils;
- initTheme();
+ await initTheme();
+
+ await registerLanguages([DbLang, PyLang, GroovyLang, LogLang, ScalaLang]);
- registerLanguages([DbLang, PyLang, GroovyLang, LogLang, ScalaLang]);
+ const monaco = await MonacoUtils.lazyMonaco();
- monaco.languages.onLanguage('python', () => {
+ monaco.languages.onLanguage('python', async () => {
monaco.languages.registerCodeActionProvider(
'python',
{
- provideCodeActions: MonacoProviders.handlePythonCodeActionRequest,
+ provideCodeActions:
+ await MonacoProviders.createPythonCodeActionRequestHandler(),
},
{ providedCodeActionKinds: ['quickfix'] }
);
@@ -71,15 +80,15 @@ class MonacoUtils {
});
});
- monaco.editor.onDidCreateModel(model => {
+ monaco.editor.onDidCreateModel(async model => {
// Lint Python models on creation and on change
if (model.getLanguageId() === 'python') {
if (MonacoProviders.ruffWorkspace != null) {
- MonacoProviders.lintPython(model);
+ await MonacoProviders.lintPython(model);
}
const throttledLint = throttle(
- (m: monaco.editor.ITextModel) => MonacoProviders.lintPython(m),
+ (m: Monaco.editor.ITextModel) => MonacoProviders.lintPython(m),
250
);
@@ -89,7 +98,7 @@ class MonacoUtils {
}
});
- MonacoUtils.removeConflictingKeybindings();
+ await MonacoUtils.removeConflictingKeybindings();
log.debug('Monaco initialized.');
}
@@ -97,9 +106,11 @@ class MonacoUtils {
/**
* Initialize current Monaco theme based on the current DH theme.
*/
- static initTheme(): void {
+ static async initTheme(): Promise {
const { removeHashtag } = MonacoUtils;
+ const monaco = await MonacoUtils.lazyMonaco();
+
const MonacoTheme = resolveCssVariablesInRecord(MonacoThemeRaw);
log.debug2('Monaco theme:', MonacoThemeRaw);
log.debug2('Monaco theme derived:', MonacoTheme);
@@ -263,7 +274,7 @@ class MonacoUtils {
* Register the getWorker function for Monaco
* @param getWorker The getWorker function for Monaco
*/
- static registerGetWorker(getWorker: Environment['getWorker']): void {
+ static registerGetWorker(getWorker: Monaco.Environment['getWorker']): void {
window.MonacoEnvironment = {
...window.MonacoEnvironment,
getWorker,
@@ -279,7 +290,9 @@ class MonacoUtils {
return color?.substring(1) ?? '';
}
- static registerLanguages(languages: Language[]): void {
+ static async registerLanguages(languages: Language[]): Promise {
+ const monaco = await MonacoUtils.lazyMonaco();
+
// First override the default loader for any language we have a custom definition for
// https://github.com/Microsoft/monaco-editor/issues/252#issuecomment-482786867
const languageIds = languages.map(({ id }) => id);
@@ -292,13 +305,17 @@ class MonacoUtils {
});
// Then register our language definitions
- languages.forEach(language => {
- MonacoUtils.registerLanguage(language);
- });
+ // eslint-disable-next-line no-restricted-syntax
+ for (const language of languages) {
+ // eslint-disable-next-line no-await-in-loop
+ await MonacoUtils.registerLanguage(language);
+ }
}
- static registerLanguage(language: Language): void {
+ static async registerLanguage(language: Language): Promise {
log.debug2('Registering language: ', language.id);
+
+ const monaco = await MonacoUtils.lazyMonaco();
monaco.languages.register(language);
monaco.languages.onLanguage(language.id, () => {
@@ -312,10 +329,16 @@ class MonacoUtils {
* @param editor The editor to set the EOL for
* @param eolSequence EOL sequence
*/
- static setEOL(
- editor: monaco.editor.IStandaloneCodeEditor,
- eolSequence = monaco.editor.EndOfLineSequence.LF
- ): void {
+ static async setEOL(
+ editor: Monaco.editor.IStandaloneCodeEditor,
+ eolSequence?: Monaco.editor.EndOfLineSequence
+ ): Promise {
+ if (eolSequence == null) {
+ // eslint-disable-next-line no-param-reassign
+ eolSequence = (await MonacoUtils.lazyMonaco()).editor.EndOfLineSequence
+ .LF;
+ }
+
editor.getModel()?.setEOL(eolSequence);
}
@@ -326,9 +349,9 @@ class MonacoUtils {
* @returns A cleanup function for disposing of the created listeners
*/
static openDocument(
- editor: monaco.editor.IStandaloneCodeEditor,
+ editor: Monaco.editor.IStandaloneCodeEditor,
session: dh.IdeSession
- ): monaco.IDisposable {
+ ): Monaco.IDisposable {
const model = editor.getModel();
assertNotNull(model);
const didOpenDocumentParams = {
@@ -385,7 +408,7 @@ class MonacoUtils {
}
static closeDocument(
- editor: monaco.editor.IStandaloneCodeEditor,
+ editor: Monaco.editor.IStandaloneCodeEditor,
session: dh.IdeSession
): void {
const model = editor.getModel();
@@ -405,7 +428,7 @@ class MonacoUtils {
* @param editor The editor the register the paste handler for
*/
static registerPasteHandler(
- editor: monaco.editor.IStandaloneCodeEditor
+ editor: Monaco.editor.IStandaloneCodeEditor
): void {
editor.onDidPaste(pasteEvent => {
const smartQuotes = /“|”/g;
@@ -443,7 +466,9 @@ class MonacoUtils {
* them. Note that this is a global configuration, so all editor instances will
* be impacted.
*/
- static removeConflictingKeybindings(): void {
+ static async removeConflictingKeybindings(): Promise {
+ const monaco = await MonacoUtils.lazyMonaco();
+
// All editor instances share a global keybinding registry which is where
// default keybindings are set. There doesn't appear to be a way to remove
// default bindings, but we can add new ones that will override the existing
@@ -479,7 +504,7 @@ class MonacoUtils {
* combination like `monaco.KeyMod.Alt | monaco.KeyMod.KeyJ`
*/
static disableKeyBindings(
- editor: monaco.editor.IStandaloneCodeEditor,
+ editor: Monaco.editor.IStandaloneCodeEditor,
keybindings: number[]
): void {
editor.addAction({
@@ -491,13 +516,21 @@ class MonacoUtils {
});
}
- static getMonacoKeyCodeFromShortcut(shortcut: Shortcut): number {
+ static async getMonacoKeyCodeFromShortcut(
+ shortcut: Shortcut
+ ): Promise {
const { keyState } = shortcut;
const { keyValue } = keyState;
if (keyValue === null) {
return 0;
}
+ const monaco = await MonacoUtils.lazyMonaco();
+ const KeyCodeUtils = await import(
+ // @ts-ignore
+ 'monaco-editor/esm/vs/base/common/keyCodes.js'
+ );
+
const isMac = MonacoUtils.isMacPlatform();
if (isMac) {
@@ -521,32 +554,40 @@ class MonacoUtils {
);
}
- static provideLinks(model: monaco.editor.ITextModel): {
- links: monaco.languages.ILink[];
- } {
- const newTokens: monaco.languages.ILink[] = [];
-
- for (let i = 1; i <= model.getLineCount(); i += 1) {
- const lineText = model.getLineContent(i);
- const originalTokens = linkifyFind(lineText);
+ static async createProvideLinks(): Promise<
+ Monaco.languages.LinkProvider['provideLinks']
+ > {
+ const monaco = await MonacoUtils.lazyMonaco();
- const tokens = originalTokens.filter(token => {
- if (token.type === 'url') {
- return /^https?:\/\//.test(token.value);
- }
- return true;
- });
- // map the tokens to the ranges - you know the line number now, use the token start/end as the startColumn/endColumn
- tokens.forEach(token => {
- newTokens.push({
- url: token.href,
- range: new monaco.Range(i, token.start + 1, i, token.end + 1),
+ return (
+ model: Monaco.editor.ITextModel
+ ): {
+ links: Monaco.languages.ILink[];
+ } => {
+ const newTokens: Monaco.languages.ILink[] = [];
+
+ for (let i = 1; i <= model.getLineCount(); i += 1) {
+ const lineText = model.getLineContent(i);
+ const originalTokens = linkifyFind(lineText);
+
+ const tokens = originalTokens.filter(token => {
+ if (token.type === 'url') {
+ return /^https?:\/\//.test(token.value);
+ }
+ return true;
});
- });
- }
+ // map the tokens to the ranges - you know the line number now, use the token start/end as the startColumn/endColumn
+ tokens.forEach(token => {
+ newTokens.push({
+ url: token.href,
+ range: new monaco.Range(i, token.start + 1, i, token.end + 1),
+ });
+ });
+ }
- return {
- links: newTokens,
+ return {
+ links: newTokens,
+ };
};
}
@@ -554,7 +595,8 @@ class MonacoUtils {
* Generates a console URI for use with monaco.
* @returns A new console URI
*/
- static generateConsoleUri(): monaco.Uri {
+ static async generateConsoleUri(): Promise {
+ const monaco = await MonacoUtils.lazyMonaco();
return monaco.Uri.parse(`${CONSOLE_URI_PREFIX}${nanoid()}`);
}
@@ -563,7 +605,7 @@ class MonacoUtils {
* @param model The monaco model to check
* @returns If the model is a console model
*/
- static isConsoleModel(model: monaco.editor.ITextModel): boolean {
+ static isConsoleModel(model: Monaco.editor.ITextModel): boolean {
return model.uri.toString().startsWith(CONSOLE_URI_PREFIX);
}
@@ -572,7 +614,7 @@ class MonacoUtils {
* @param editor The monaco editor to check
* @returns If the editor has a document formatter registered
*/
- static canFormat(editor: monaco.editor.IStandaloneCodeEditor): boolean {
+ static canFormat(editor: Monaco.editor.IStandaloneCodeEditor): boolean {
return (
editor.getAction('editor.action.formatDocument')?.isSupported() === true
);
@@ -583,7 +625,7 @@ class MonacoUtils {
* @param editor The editor to format
*/
static async formatDocument(
- editor: monaco.editor.IStandaloneCodeEditor
+ editor: Monaco.editor.IStandaloneCodeEditor
): Promise {
await editor.getAction('editor.action.formatDocument')?.run();
}
diff --git a/packages/console/src/monaco/RuffSettingsModal.tsx b/packages/console/src/monaco/RuffSettingsModal.tsx
index d09e02f5ca..dbdda2ce89 100644
--- a/packages/console/src/monaco/RuffSettingsModal.tsx
+++ b/packages/console/src/monaco/RuffSettingsModal.tsx
@@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
-import * as monaco from 'monaco-editor';
+import type * as Monaco from 'monaco-editor';
import { Workspace } from '@astral-sh/ruff-wasm-web';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
@@ -25,7 +25,7 @@ import ruffSchema from './ruffSchema';
import './RuffSettingsModal.scss';
import MonacoProviders from './MonacoProviders';
-interface RuffSettingsModalProps {
+export interface RuffSettingsModalProps {
text: string;
isOpen: boolean;
onClose: () => void;
@@ -34,11 +34,14 @@ interface RuffSettingsModalProps {
defaultSettings?: Record;
}
-const RUFF_SETTINGS_URI = monaco.Uri.parse(
- 'inmemory://dh-config/ruff-settings.json'
-);
+// const RUFF_SETTINGS_URI = Monaco.Uri.parse(
+// 'inmemory://dh-config/ruff-settings.json'
+// );
-function registerRuffSchema(): void {
+function registerRuffSchema(
+ monaco: typeof Monaco,
+ ruffSettingsUri: Monaco.Uri
+): void {
const { schemas = [] } =
monaco.languages.json.jsonDefaults.diagnosticsOptions;
@@ -49,7 +52,7 @@ function registerRuffSchema(): void {
...schemas,
{
uri: 'json://ruff-schema',
- fileMatch: [RUFF_SETTINGS_URI.toString()],
+ fileMatch: [ruffSettingsUri.toString()],
schema: ruffSchema,
},
],
@@ -65,14 +68,17 @@ async function getRuffVersion(): Promise {
export default function RuffSettingsModal({
text,
isOpen,
+ monaco,
onClose,
onSave,
readOnly = false,
defaultSettings = RUFF_DEFAULT_SETTINGS,
-}: RuffSettingsModalProps): React.ReactElement | null {
+}: RuffSettingsModalProps & {
+ monaco: typeof Monaco;
+}): React.ReactElement | null {
const [isValid, setIsValid] = useState(false);
const [isDefault, setIsDefault] = useState(false);
- const editorRef = useRef();
+ const editorRef = useRef();
const formattedDefaultSettings = useMemo(
() => JSON.stringify(defaultSettings, null, 2),
@@ -81,8 +87,13 @@ export default function RuffSettingsModal({
const { data: ruffVersion } = usePromiseFactory(getRuffVersion);
+ const ruffSettingsUri = useMemo(
+ () => monaco.Uri.parse('inmemory://dh-config/ruff-settings.json'),
+ [monaco]
+ );
+
const [model] = useState(() =>
- monaco.editor.createModel(text, 'json', RUFF_SETTINGS_URI)
+ monaco.editor.createModel(text, 'json', ruffSettingsUri)
);
const handleClose = useCallback((): void => {
@@ -129,17 +140,17 @@ export default function RuffSettingsModal({
});
const onEditorInitialized = useCallback(
- (editor: monaco.editor.IStandaloneCodeEditor): void => {
+ (editor: Monaco.editor.IStandaloneCodeEditor): void => {
editorRef.current = editor;
model.onDidChangeContent(() => {
debouncedValidate(model.getValue());
});
- registerRuffSchema();
+ registerRuffSchema(monaco, ruffSettingsUri);
debouncedValidate(model.getValue());
},
- [debouncedValidate, model]
+ [debouncedValidate, model, monaco, ruffSettingsUri]
);
if (!isOpen) {
diff --git a/packages/console/src/monaco/index.ts b/packages/console/src/monaco/index.ts
index 3402c7077f..d0cee9cc67 100644
--- a/packages/console/src/monaco/index.ts
+++ b/packages/console/src/monaco/index.ts
@@ -3,4 +3,5 @@ export { default as MonacoProviders } from './MonacoProviders';
export { default as MonacoTheme } from './MonacoTheme.module.scss';
export * from './MonacoThemeProvider';
export { default as RuffSettingsModal } from './RuffSettingsModal';
+export { LazyRuffSettingsModal } from './LazyRuffSettingsModal';
export { default as RUFF_DEFAULT_SETTINGS } from './RuffDefaultSettings';
diff --git a/packages/console/src/notebook/Editor.tsx b/packages/console/src/notebook/Editor.tsx
index f3762ccbc4..0d62b6f398 100644
--- a/packages/console/src/notebook/Editor.tsx
+++ b/packages/console/src/notebook/Editor.tsx
@@ -3,16 +3,16 @@
*/
import React, { Component, type ReactElement } from 'react';
import classNames from 'classnames';
-import * as monaco from 'monaco-editor';
+import type * as Monaco from 'monaco-editor';
import { assertNotNull } from '@deephaven/utils';
import MonacoUtils from '../monaco/MonacoUtils';
import './Editor.scss';
export interface EditorProps {
className: string;
- onEditorInitialized: (editor: monaco.editor.IStandaloneCodeEditor) => void;
- onEditorWillDestroy: (editor: monaco.editor.IStandaloneCodeEditor) => void;
- settings: monaco.editor.IStandaloneEditorConstructionOptions;
+ onEditorInitialized: (editor: Monaco.editor.IStandaloneCodeEditor) => void;
+ onEditorWillDestroy: (editor: Monaco.editor.IStandaloneCodeEditor) => void;
+ settings: Monaco.editor.IStandaloneEditorConstructionOptions;
}
class Editor extends Component> {
@@ -46,10 +46,11 @@ class Editor extends Component> {
container: HTMLDivElement | null;
- editor?: monaco.editor.IStandaloneCodeEditor;
+ editor?: Monaco.editor.IStandaloneCodeEditor;
- setLanguage(language: string): void {
+ async setLanguage(language: string): Promise {
if (this.editor) {
+ const monaco = await MonacoUtils.lazyMonaco();
const model = this.editor.getModel();
assertNotNull(model);
monaco.editor.setModelLanguage(model, language);
@@ -74,7 +75,7 @@ class Editor extends Component> {
this.editor?.layout();
}
- initEditor(): void {
+ async initEditor(): Promise {
const { onEditorInitialized } = this.props;
let { settings } = this.props;
settings = {
@@ -96,6 +97,7 @@ class Editor extends Component> {
};
assertNotNull(this.container);
+ const monaco = await MonacoUtils.lazyMonaco();
this.editor = monaco.editor.create(this.container, settings);
this.editor.addAction({
@@ -117,7 +119,7 @@ class Editor extends Component> {
this.editor.layout();
monaco.languages.registerLinkProvider('plaintext', {
- provideLinks: MonacoUtils.provideLinks,
+ provideLinks: await MonacoUtils.createProvideLinks(),
});
onEditorInitialized(this.editor);
diff --git a/packages/console/src/notebook/ScriptEditor.tsx b/packages/console/src/notebook/ScriptEditor.tsx
index 7d245c1881..f7cb1ad600 100644
--- a/packages/console/src/notebook/ScriptEditor.tsx
+++ b/packages/console/src/notebook/ScriptEditor.tsx
@@ -66,40 +66,7 @@ class ScriptEditor extends Component {
}
componentDidUpdate(prevProps: ScriptEditorProps): void {
- const { sessionLanguage, settings } = this.props;
-
- const language = settings?.language;
-
- const languageChanged = language !== prevProps.settings?.language;
- if (languageChanged) {
- log.debug('Set language', language);
- this.setLanguage(language);
- }
-
- const sessionDisconnected =
- sessionLanguage == null && prevProps.sessionLanguage != null;
- const languageMatch = language === sessionLanguage;
- const prevLanguageMatch =
- prevProps.settings?.language === prevProps.sessionLanguage;
- if (
- sessionDisconnected ||
- (sessionLanguage !== undefined && prevLanguageMatch && !languageMatch)
- ) {
- // Session disconnected or language changed from matching the session language to non-matching
- log.debug('De-init completion');
- this.deInitCodeCompletion();
- }
-
- const sessionConnected =
- sessionLanguage != null && prevProps.sessionLanguage == null;
- if (
- (sessionConnected && languageMatch) ||
- (sessionLanguage !== undefined && !prevLanguageMatch && languageMatch)
- ) {
- // Session connected with a matching language or notebook language changed to matching
- log.debug('Init completion');
- this.initCodeCompletion();
- }
+ this.handleEditorUpdated(prevProps);
}
componentWillUnmount(): void {
@@ -141,7 +108,9 @@ class ScriptEditor extends Component {
return ScriptEditorUtils.outdentCode(model.getValueInRange(wholeLineRange));
}
- handleEditorInitialized(innerEditor: editor.IStandaloneCodeEditor): void {
+ async handleEditorInitialized(
+ innerEditor: editor.IStandaloneCodeEditor
+ ): Promise {
const {
focusOnMount,
onChange,
@@ -156,12 +125,12 @@ class ScriptEditor extends Component {
this.editor = innerEditor;
this.setState({ model: this.editor.getModel() });
- MonacoUtils.setEOL(innerEditor);
+ await MonacoUtils.setEOL(innerEditor);
MonacoUtils.registerPasteHandler(innerEditor);
// Always initialize context actions when the editor is created to ensure that unwanted default
// OS shortcuts are overridden by custom shortcuts.
- this.initContextActions();
+ await this.initContextActions();
if (session != null && settings && sessionLanguage === settings.language) {
this.initCodeCompletion();
@@ -175,6 +144,43 @@ class ScriptEditor extends Component {
onEditorInitialized(this.editor);
}
+ async handleEditorUpdated(prevProps: ScriptEditorProps): Promise {
+ const { sessionLanguage, settings } = this.props;
+
+ const language = settings?.language;
+
+ const languageChanged = language !== prevProps.settings?.language;
+ if (languageChanged) {
+ log.debug('Set language', language);
+ await this.setLanguage(language);
+ }
+
+ const sessionDisconnected =
+ sessionLanguage == null && prevProps.sessionLanguage != null;
+ const languageMatch = language === sessionLanguage;
+ const prevLanguageMatch =
+ prevProps.settings?.language === prevProps.sessionLanguage;
+ if (
+ sessionDisconnected ||
+ (sessionLanguage !== undefined && prevLanguageMatch && !languageMatch)
+ ) {
+ // Session disconnected or language changed from matching the session language to non-matching
+ log.debug('De-init completion');
+ this.deInitCodeCompletion();
+ }
+
+ const sessionConnected =
+ sessionLanguage != null && prevProps.sessionLanguage == null;
+ if (
+ (sessionConnected && languageMatch) ||
+ (sessionLanguage !== undefined && !prevLanguageMatch && languageMatch)
+ ) {
+ // Session connected with a matching language or notebook language changed to matching
+ log.debug('Init completion');
+ this.initCodeCompletion();
+ }
+ }
+
handleEditorWillDestroy(innerEditor: editor.IStandaloneCodeEditor): void {
log.debug('handleEditorWillDestroy');
const { onEditorWillDestroy } = this.props;
@@ -225,7 +231,7 @@ class ScriptEditor extends Component {
onRunCommand(command);
}
- initContextActions(): void {
+ async initContextActions(): Promise {
if (this.contextActionCleanups.length > 0) {
log.error('Context actions already initialized.');
return;
@@ -241,7 +247,9 @@ class ScriptEditor extends Component {
id: 'run-code',
label: 'Run',
keybindings: [
- MonacoUtils.getMonacoKeyCodeFromShortcut(SHORTCUTS.NOTEBOOK.RUN),
+ await MonacoUtils.getMonacoKeyCodeFromShortcut(
+ SHORTCUTS.NOTEBOOK.RUN
+ ),
],
contextMenuGroupId: 'navigation',
contextMenuOrder: 1.5,
@@ -257,7 +265,7 @@ class ScriptEditor extends Component {
id: 'run-selected-code',
label: 'Run Selected',
keybindings: [
- MonacoUtils.getMonacoKeyCodeFromShortcut(
+ await MonacoUtils.getMonacoKeyCodeFromShortcut(
SHORTCUTS.NOTEBOOK.RUN_SELECTED
),
],
@@ -350,9 +358,9 @@ class ScriptEditor extends Component {
}
}
- setLanguage(language?: string): void {
+ async setLanguage(language?: string): Promise {
if (this.editorComponent.current && language !== undefined) {
- this.editorComponent.current.setLanguage(language);
+ await this.editorComponent.current.setLanguage(language);
}
}
diff --git a/packages/embed-widget/package.json b/packages/embed-widget/package.json
index f9dd1054a9..aab9a01d6c 100644
--- a/packages/embed-widget/package.json
+++ b/packages/embed-widget/package.json
@@ -45,7 +45,8 @@
"@deephaven/eslint-config": "file:../eslint-config",
"@deephaven/mocks": "file:../mocks",
"@deephaven/prettier-config": "file:../prettier-config",
- "@deephaven/stylelint-config": "file:../stylelint-config"
+ "@deephaven/stylelint-config": "file:../stylelint-config",
+ "rollup-plugin-visualizer": "^6.0.3"
},
"publishConfig": {
"access": "public"
diff --git a/packages/embed-widget/vite.config.ts b/packages/embed-widget/vite.config.ts
index 16c8c2ed84..64aedffd13 100644
--- a/packages/embed-widget/vite.config.ts
+++ b/packages/embed-widget/vite.config.ts
@@ -1,5 +1,6 @@
/* eslint-disable import/no-extraneous-dependencies */
import { defineConfig, loadEnv } from 'vite';
+import { visualizer } from 'rollup-plugin-visualizer';
import react from '@vitejs/plugin-react-swc';
import path from 'path';
@@ -85,6 +86,28 @@ export default defineConfig(({ mode }) => {
outDir: env.VITE_BUILD_PATH,
emptyOutDir: true,
sourcemap: true,
+ // modulePreload: {
+ // resolveDependencies: (
+ // filename: string,
+ // deps: string[],
+ // context: {
+ // hostId: string;
+ // hostType: 'html' | 'js';
+ // }
+ // ) => {
+ // return [];
+ // // eslint-disable-next-line no-param-reassign
+ // deps = deps.filter(dep => !dep.includes('monaco-'));
+ // console.log('[TESTING]', filename, deps, context);
+ // return deps;
+ // },
+ // },
+ // TODO: This "should" disable module preload. It seems to work for
+ // monaco.js but not the .css. There seems to still be something eagerly
+ // loading it, but haven't been able to track it down yet. We probably
+ // don't actually want to disable this. In theory it will be fixed if we
+ // figure out what is importing it eagerly.
+ modulePreload: false,
rollupOptions: {
output: {
manualChunks: id => {
@@ -100,9 +123,15 @@ export default defineConfig(({ mode }) => {
}
if (id.includes('node_modules')) {
+ if (id.includes('monaco-editor')) {
+ return 'monaco';
+ }
if (id.includes('plotly.js')) {
return 'plotly';
}
+ if (id.includes('mathjax')) {
+ return 'mathjax';
+ }
return 'vendor';
}
},
@@ -117,6 +146,63 @@ export default defineConfig(({ mode }) => {
},
},
},
- plugins: [react()],
+ plugins: [
+ react(),
+ {
+ /**
+ * Plugin to log monaco imports. Notes:
+ * 1. All imports show in `chunk.imports` array (even lazy ones)
+ * 2. Lazy imports show in `chunk.dynamicImports` array
+ *
+ * Goal would be for Monaco to always be lazy imported at least for the
+ * main chunk. As-is, it shows up a number of times only in the
+ * `chunk.imports`. Haven't been able to figure out why.
+ */
+ name: 'log-chunk-deps',
+ generateBundle(options, bundle) {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const [fileName, chunk] of Object.entries(bundle)) {
+ const isMonaco = i => i.includes('monaco-');
+
+ if (
+ chunk.type === 'chunk' &&
+ (chunk.imports.some(isMonaco) ||
+ chunk.dynamicImports.some(isMonaco))
+ ) {
+ console.log(
+ `Chunk: ${fileName}${chunk.isEntry ? ', isEntry' : ''}`
+ );
+ console.log(
+ JSON.stringify(
+ {
+ imports: chunk.imports,
+ dynamicImports: chunk.dynamicImports,
+ },
+ null,
+ 2
+ )
+ );
+ }
+ }
+ },
+ },
+ // Different visualizations of the bundle
+ (
+ [
+ // 'flamegraph',
+ // 'list',
+ // 'network',
+ // 'raw-data',
+ // 'sunburst',
+ 'treemap',
+ ] as const
+ ).map(template =>
+ visualizer({
+ open: true,
+ filename: `stats-${template}.html`,
+ template,
+ })
+ ),
+ ],
};
});
diff --git a/packages/iris-grid/src/sidebar/InputEditor.tsx b/packages/iris-grid/src/sidebar/InputEditor.tsx
index ae10feba05..824e978308 100644
--- a/packages/iris-grid/src/sidebar/InputEditor.tsx
+++ b/packages/iris-grid/src/sidebar/InputEditor.tsx
@@ -1,14 +1,15 @@
import React, { Component, type ReactElement } from 'react';
-import * as monaco from 'monaco-editor';
+import type * as Monaco from 'monaco-editor';
import classNames from 'classnames';
import './InputEditor.scss';
+import { MonacoUtils } from '@deephaven/console';
interface InputEditorProps {
className?: string;
placeholder?: string;
value: string;
onContentChanged: (value?: string) => void;
- editorSettings: Partial;
+ editorSettings: Partial;
editorIndex: number;
onTab: (editorIndex: number, shiftKey: boolean) => void;
invalid: boolean;
@@ -58,9 +59,9 @@ export class InputEditor extends Component {
editorContainer: HTMLDivElement | null;
- editor?: monaco.editor.IStandaloneCodeEditor;
+ editor?: Monaco.editor.IStandaloneCodeEditor;
- initEditor(): void {
+ async initEditor(): Promise {
const { value, editorSettings } = this.props;
const inputEditorSettings = {
copyWithSyntaxHighlighting: 'false',
@@ -90,10 +91,11 @@ export class InputEditor extends Component {
automaticLayout: true,
autoClosingBrackets: 'beforeWhitespace',
...editorSettings,
- } as monaco.editor.IStandaloneEditorConstructionOptions;
+ } as Monaco.editor.IStandaloneEditorConstructionOptions;
if (!this.editorContainer) {
throw new Error('editorContainer is null');
}
+ const monaco = await MonacoUtils.lazyMonaco();
this.editor = monaco.editor.create(
this.editorContainer,
inputEditorSettings
@@ -146,7 +148,7 @@ export class InputEditor extends Component {
this.editor?.focus();
}
- handleKeyDown(event: monaco.IKeyboardEvent): void {
+ handleKeyDown(event: Monaco.IKeyboardEvent): void {
const { onTab, editorIndex } = this.props;
if (event.code === 'Tab') {
event.stopPropagation();