diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts
index c863903259..3ffb922dea 100644
--- a/integration/__tests__/cluster-pages.tests.ts
+++ b/integration/__tests__/cluster-pages.tests.ts
@@ -448,8 +448,9 @@ describe("Lens cluster pages", () => {
await app.client.click(".Icon.new-dock-tab");
await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource");
await app.client.click("li.MenuItem.create-resource-tab");
- await app.client.waitForVisible(".CreateResource div.ace_content");
+ await app.client.waitForVisible(".CreateResource div.react-monaco-editor-container");
// Write pod manifest to editor
+ await app.client.click(".CreateResource div.react-monaco-editor-container");
await app.client.keys("apiVersion: v1\n");
await app.client.keys("kind: Pod\n");
await app.client.keys("metadata:\n");
diff --git a/package.json b/package.json
index f500810e98..1bad46ade7 100644
--- a/package.json
+++ b/package.json
@@ -222,6 +222,7 @@
"moment": "^2.29.1",
"moment-timezone": "^0.5.33",
"node-fetch": "^2.6.1",
+ "monaco-editor": "^0.26.1",
"node-pty": "^0.10.1",
"npm": "^6.14.8",
"openid-client": "^3.15.2",
@@ -230,6 +231,7 @@
"proper-lockfile": "^4.1.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
+ "react-monaco-editor": "^0.44.0",
"react-router": "^5.2.0",
"react-virtualized-auto-sizer": "^1.0.5",
"readable-stream": "^3.6.0",
@@ -317,7 +319,6 @@
"@types/webpack-node-externals": "^1.7.1",
"@typescript-eslint/eslint-plugin": "^4.29.0",
"@typescript-eslint/parser": "^4.29.1",
- "ace-builds": "^1.4.12",
"ansi_up": "^5.0.0",
"chart.js": "^2.9.4",
"circular-dependency-plugin": "^5.2.2",
diff --git a/src/common/__tests__/search-store.test.ts b/src/common/__tests__/search-store.test.ts
index b4c99b7e19..abd6cd1510 100644
--- a/src/common/__tests__/search-store.test.ts
+++ b/src/common/__tests__/search-store.test.ts
@@ -23,6 +23,8 @@ import { SearchStore } from "../search-store";
import { Console } from "console";
import { stdout, stderr } from "process";
+jest.mock("react-monaco-editor", () => null);
+
jest.mock("electron", () => ({
app: {
getPath: () => "/foo",
diff --git a/src/common/routes/preferences.ts b/src/common/routes/preferences.ts
index bae7e6257e..1c19e6e733 100644
--- a/src/common/routes/preferences.ts
+++ b/src/common/routes/preferences.ts
@@ -38,6 +38,10 @@ export const kubernetesRoute: RouteProps = {
path: `${preferencesRoute.path}/kubernetes`
};
+export const editorRoute: RouteProps = {
+ path: `${preferencesRoute.path}/editor`
+};
+
export const telemetryRoute: RouteProps = {
path: `${preferencesRoute.path}/telemetry`
};
@@ -50,5 +54,6 @@ export const preferencesURL = buildURL(preferencesRoute.path);
export const appURL = buildURL(appRoute.path);
export const proxyURL = buildURL(proxyRoute.path);
export const kubernetesURL = buildURL(kubernetesRoute.path);
+export const editorURL = buildURL(editorRoute.path);
export const telemetryURL = buildURL(telemetryRoute.path);
export const extensionURL = buildURL(extensionRoute.path);
diff --git a/src/common/user-store/preferences-helpers.ts b/src/common/user-store/preferences-helpers.ts
index 4363511120..59f201f55f 100644
--- a/src/common/user-store/preferences-helpers.ts
+++ b/src/common/user-store/preferences-helpers.ts
@@ -24,6 +24,8 @@ import path from "path";
import os from "os";
import { ThemeStore } from "../../renderer/theme.store";
import { ObservableToggleSet } from "../utils";
+import type {monaco} from "react-monaco-editor";
+import merge from "lodash/merge";
export interface KubeconfigSyncEntry extends KubeconfigSyncValue {
filePath: string;
@@ -31,6 +33,20 @@ export interface KubeconfigSyncEntry extends KubeconfigSyncValue {
export interface KubeconfigSyncValue { }
+export interface EditorConfiguration {
+ miniMap?: monaco.editor.IEditorMinimapOptions;
+ lineNumbers?: monaco.editor.LineNumbersType;
+ tabSize?: number;
+}
+
+export const defaultEditorConfig: EditorConfiguration = {
+ lineNumbers: "on",
+ miniMap: {
+ enabled: true
+ },
+ tabSize: 2
+};
+
interface PreferenceDescription {
fromStore(val: T | undefined): R;
toStore(val: R): T | undefined;
@@ -222,6 +238,15 @@ const syncKubeconfigEntries: PreferenceDescription = {
+ fromStore(val) {
+ return merge(defaultEditorConfig, val);
+ },
+ toStore(val) {
+ return val;
+ },
+};
+
type PreferencesModelType = typeof DESCRIPTORS[field] extends PreferenceDescription ? T : never;
type UserStoreModelType = typeof DESCRIPTORS[field] extends PreferenceDescription ? T : never;
@@ -248,4 +273,5 @@ export const DESCRIPTORS = {
openAtLogin,
hiddenTableColumns,
syncKubeconfigEntries,
+ editorConfiguration,
};
diff --git a/src/common/user-store/user-store.ts b/src/common/user-store/user-store.ts
index 9cc5cd06e9..75e207a2ca 100644
--- a/src/common/user-store/user-store.ts
+++ b/src/common/user-store/user-store.ts
@@ -30,8 +30,9 @@ import { appEventBus } from "../event-bus";
import path from "path";
import { fileNameMigration } from "../../migrations/user-store";
import { ObservableToggleSet, toJS } from "../../renderer/utils";
-import { DESCRIPTORS, KubeconfigSyncValue, UserPreferencesModel } from "./preferences-helpers";
+import { DESCRIPTORS, KubeconfigSyncValue, UserPreferencesModel, EditorConfiguration } from "./preferences-helpers";
import logger from "../../main/logger";
+import type {monaco} from "react-monaco-editor";
export interface UserStoreModel {
lastSeenAppVersion: string;
@@ -68,7 +69,7 @@ export class UserStore extends BaseStore /* implements UserStore
@observable shell?: string;
@observable downloadBinariesPath?: string;
@observable kubectlBinariesPath?: string;
-
+
/**
* Download kubectl binaries matching cluster version
*/
@@ -81,6 +82,11 @@ export class UserStore extends BaseStore /* implements UserStore
*/
hiddenTableColumns = observable.map>();
+ /**
+ * Monaco editor configs
+ */
+ @observable editorConfiguration:EditorConfiguration = {tabSize: null, miniMap: null, lineNumbers: null};
+
/**
* The set of file/folder paths to be synced
*/
@@ -109,7 +115,29 @@ export class UserStore extends BaseStore /* implements UserStore
});
}, {
fireImmediately: true,
- });
+ });
+ }
+
+ // Returns monaco editor options for selected editor type (the place, where a particular instance of the editor is mounted)
+ getEditorOptions(): monaco.editor.IStandaloneEditorConstructionOptions {
+ return {
+ automaticLayout: true,
+ tabSize: this.editorConfiguration.tabSize,
+ minimap: this.editorConfiguration.miniMap,
+ lineNumbers: this.editorConfiguration.lineNumbers
+ };
+ }
+
+ setEditorLineNumbers(lineNumbers: monaco.editor.LineNumbersType) {
+ this.editorConfiguration.lineNumbers = lineNumbers;
+ }
+
+ setEditorTabSize(tabSize: number) {
+ this.editorConfiguration.tabSize = tabSize;
+ }
+
+ enableEditorMinimap(miniMap: boolean ) {
+ this.editorConfiguration.miniMap.enabled = miniMap;
}
/**
@@ -182,6 +210,7 @@ export class UserStore extends BaseStore /* implements UserStore
this.openAtLogin = DESCRIPTORS.openAtLogin.fromStore(preferences?.openAtLogin);
this.hiddenTableColumns.replace(DESCRIPTORS.hiddenTableColumns.fromStore(preferences?.hiddenTableColumns));
this.syncKubeconfigEntries.replace(DESCRIPTORS.syncKubeconfigEntries.fromStore(preferences?.syncKubeconfigEntries));
+ this.editorConfiguration = DESCRIPTORS.editorConfiguration.fromStore(preferences?.editorConfiguration);
}
toJSON(): UserStoreModel {
@@ -202,6 +231,7 @@ export class UserStore extends BaseStore /* implements UserStore
openAtLogin: DESCRIPTORS.openAtLogin.toStore(this.openAtLogin),
hiddenTableColumns: DESCRIPTORS.hiddenTableColumns.toStore(this.hiddenTableColumns),
syncKubeconfigEntries: DESCRIPTORS.syncKubeconfigEntries.toStore(this.syncKubeconfigEntries),
+ editorConfiguration: DESCRIPTORS.editorConfiguration.toStore(this.editorConfiguration),
},
};
diff --git a/src/extensions/registries/__tests__/page-registry.test.ts b/src/extensions/registries/__tests__/page-registry.test.ts
index 9a64d95b6b..5aa1b53a36 100644
--- a/src/extensions/registries/__tests__/page-registry.test.ts
+++ b/src/extensions/registries/__tests__/page-registry.test.ts
@@ -29,6 +29,8 @@ import { ThemeStore } from "../../../renderer/theme.store";
import { TerminalStore } from "../../renderer-api/components";
import { UserStore } from "../../../common/user-store";
+jest.mock("react-monaco-editor", () => null);
+
jest.mock("electron", () => ({
app: {
getPath: () => "tmp",
diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx
index 3e7daa5404..b6b97cfc6b 100644
--- a/src/renderer/bootstrap.tsx
+++ b/src/renderer/bootstrap.tsx
@@ -28,6 +28,7 @@ import * as ReactRouter from "react-router";
import * as ReactRouterDom from "react-router-dom";
import * as LensExtensionsCommonApi from "../extensions/common-api";
import * as LensExtensionsRendererApi from "../extensions/renderer-api";
+import { monaco } from "react-monaco-editor";
import { render, unmountComponentAtNode } from "react-dom";
import { delay } from "../common/utils";
import { isMac, isDevelopment } from "../common/vars";
@@ -49,6 +50,7 @@ import { FilesystemProvisionerStore } from "../main/extension-filesystem";
import { ThemeStore } from "./theme.store";
import { SentryInit } from "../common/sentry";
import { TerminalStore } from "./components/dock/terminal.store";
+import cloudsMidnight from "./monaco-themes/Clouds Midnight.json";
configurePackages();
@@ -102,6 +104,12 @@ export async function bootstrap(App: AppComponent) {
ExtensionsStore.createInstance();
FilesystemProvisionerStore.createInstance();
+ // define Monaco Editor themes
+ const { base, ...params } = cloudsMidnight;
+ const baseTheme = base as monaco.editor.BuiltinTheme;
+
+ monaco.editor.defineTheme("clouds-midnight", {base: baseTheme, ...params});
+
// ThemeStore depends on: UserStore
ThemeStore.createInstance();
diff --git a/src/renderer/components/+add-cluster/add-cluster.scss b/src/renderer/components/+add-cluster/add-cluster.scss
index a8eaebf410..faac757c40 100644
--- a/src/renderer/components/+add-cluster/add-cluster.scss
+++ b/src/renderer/components/+add-cluster/add-cluster.scss
@@ -23,7 +23,7 @@
--flex-gap: #{$unit * 2};
$spacing: $padding * 2;
- .AceEditor {
+ .MonacoEditor {
min-height: 600px;
max-height: 600px;
border: 1px solid var(--colorVague);
diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx
index 5e59767081..e987c1d4bb 100644
--- a/src/renderer/components/+add-cluster/add-cluster.tsx
+++ b/src/renderer/components/+add-cluster/add-cluster.tsx
@@ -34,11 +34,13 @@ import { appEventBus } from "../../../common/event-bus";
import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers";
import { docsUrl } from "../../../common/vars";
import { navigate } from "../../navigation";
-import { getCustomKubeConfigPath, iter } from "../../utils";
-import { AceEditor } from "../ace-editor";
+import { getCustomKubeConfigPath, cssNames, iter } from "../../utils";
import { Button } from "../button";
import { Notifications } from "../notifications";
import { SettingLayout } from "../layout/setting-layout";
+import MonacoEditor from "react-monaco-editor";
+import { ThemeStore } from "../../theme.store";
+import { UserStore } from "../../../common/user-store";
interface Option {
config: KubeConfig;
@@ -114,10 +116,11 @@ export class AddCluster extends React.Component {
Read more about adding clusters here.
-
{
this.customConfig = value;
diff --git a/src/renderer/components/+apps-releases/release-details.scss b/src/renderer/components/+apps-releases/release-details.scss
index a4bd53610c..112ee6b578 100644
--- a/src/renderer/components/+apps-releases/release-details.scss
+++ b/src/renderer/components/+apps-releases/release-details.scss
@@ -82,11 +82,11 @@
}
.values {
- .AceEditor {
+ .MonacoEditor {
min-height: 300px;
}
- .AceEditor + .Button {
+ .MonacoEditor + .Button {
align-self: flex-start;
}
}
diff --git a/src/renderer/components/+apps-releases/release-details.tsx b/src/renderer/components/+apps-releases/release-details.tsx
index e3e68ffb53..ae66171932 100644
--- a/src/renderer/components/+apps-releases/release-details.tsx
+++ b/src/renderer/components/+apps-releases/release-details.tsx
@@ -35,7 +35,6 @@ import { cssNames, stopPropagation } from "../../utils";
import { disposeOnUnmount, observer } from "mobx-react";
import { Spinner } from "../spinner";
import { Table, TableCell, TableHead, TableRow } from "../table";
-import { AceEditor } from "../ace-editor";
import { Button } from "../button";
import { releaseStore } from "./release.store";
import { Notifications } from "../notifications";
@@ -47,6 +46,8 @@ import { secretsStore } from "../+config-secrets/secrets.store";
import { Secret } from "../../../common/k8s-api/endpoints";
import { getDetailsUrl } from "../kube-detail-params";
import { Checkbox } from "../checkbox";
+import MonacoEditor from "react-monaco-editor";
+import { UserStore } from "../../../common/user-store";
interface Props {
release: HelmRelease;
@@ -158,15 +159,16 @@ export class ReleaseDetails extends Component {
onChange={value => this.showOnlyUserSuppliedValues = value}
disabled={valuesLoading}
/>
- this.values = text}
- className={cssNames({ loading: valuesLoading })}
- readOnly={valuesLoading || this.showOnlyUserSuppliedValues}
+ theme={ThemeStore.getInstance().activeTheme.monacoTheme}
+ className={cssNames("MonacoEditor", {loading: valuesLoading})}
+ options={{readOnly: valuesLoading || this.showOnlyUserSuppliedValues, ...UserStore.getInstance().getEditorOptions()}}
>
{valuesLoading && }
-
+