Skip to content

Commit e988e62

Browse files
authored
feat: refactor CodeEditor (#360)
* fix: attempt to fix eslint import recognition * fix: fix lint issues * feat: restructure code editor component * feat: restructure CodeEditor component * feat: use type safe connector for CodeEditor
1 parent be436a8 commit e988e62

File tree

19 files changed

+237
-137
lines changed

19 files changed

+237
-137
lines changed

web/.eslintrc.cjs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module.exports = {
2+
root: true,
23
env: {
34
browser: true,
45
es2021: true
@@ -21,7 +22,9 @@ module.exports = {
2122
"prettier",
2223
"unused-imports"
2324
],
25+
parser: "@typescript-eslint/parser",
2426
parserOptions: {
27+
project: true,
2528
ecmaVersion: "latest",
2629
ecmaFeatures: {
2730
jsx: true
@@ -50,7 +53,11 @@ module.exports = {
5053
},
5154
'import/resolver': {
5255
typescript: {
53-
alwaysTryTypes: true
56+
alwaysTryTypes: true,
57+
project: ["**/tsconfig.json"]
58+
},
59+
node: {
60+
extensions: ['.js', '.jsx', '.ts', '.tsx'],
5461
}
5562
}
5663
},

web/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
"@types/react": "^17.0.39",
1717
"@types/react-dom": "^17.0.11",
1818
"@types/react-router-dom": "^5.3.3",
19-
"@typescript-eslint/eslint-plugin": "^6.20.0",
20-
"@typescript-eslint/parser": "^6.20.0",
19+
"@typescript-eslint/eslint-plugin": "^7.16.0",
20+
"@typescript-eslint/parser": "^7.16.0",
2121
"@vitejs/plugin-react-swc": "^3.5.0",
2222
"@xterm/addon-canvas": "^0.6.0-beta.1",
2323
"@xterm/addon-fit": "^0.9.0-beta.1",

web/src/components/features/inspector/RunOutput/RunOutput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const RunOutput: React.FC<StateProps & {}> = ({ status, monaco, terminal }) => {
4646
}
4747
}, [theme])
4848
const fontFamily = useMemo(() => getFontFamily(monaco?.fontFamily ?? DEFAULT_FONT), [monaco])
49-
const isClean = !status || !status?.dirty
49+
const isClean = !status?.dirty
5050

5151
return (
5252
<div className="RunOutput" style={styles}>

web/src/components/features/workspace/CodeEditor/CodeEditor.tsx

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,23 @@ import { Spinner } from '@fluentui/react'
33
import MonacoEditor, { type Monaco } from '@monaco-editor/react'
44
import { KeyMod, KeyCode, type editor, type IKeyboardEvent } from 'monaco-editor'
55

6+
import apiClient from '~/services/api'
67
import { createVimModeAdapter, type StatusBarAdapter, type VimModeKeymap } from '~/plugins/vim/editor'
78
import { Analyzer } from '~/services/analyzer'
8-
import { TargetType } from '~/services/config'
9-
import { Connect, newMarkerAction, runFileDispatcher } from '~/store'
9+
import { type MonacoSettings, TargetType } from '~/services/config'
10+
import { connect, newMarkerAction, runFileDispatcher, type StateDispatch } from '~/store'
1011
import { type WorkspaceState, dispatchFormatFile, dispatchResetWorkspace, dispatchUpdateFile } from '~/store/workspace'
11-
import { getTimeNowUsageMarkers, wrapAsyncWithDebounce } from '../utils'
12-
import { attachCustomCommands } from '../commands'
13-
import { LANGUAGE_GOLANG, stateToOptions } from '../props'
12+
import { getTimeNowUsageMarkers, wrapAsyncWithDebounce } from './utils'
13+
import { attachCustomCommands } from './commands'
14+
import { LANGUAGE_GOLANG, stateToOptions } from './props'
15+
import { configureMonacoLoader } from './loader'
16+
import { registerGoLanguageProvider } from './autocomplete'
17+
import type { VimState } from '~/store/vim/state'
1418

1519
const ANALYZE_DEBOUNCE_TIME = 500
1620

17-
interface CodeEditorState {
18-
code?: string
19-
loading?: boolean
20-
}
21+
// ask monaco-editor/react to use our own Monaco instance.
22+
configureMonacoLoader()
2123

2224
const mapWorkspaceProps = ({ files, selectedFile }: WorkspaceState) => {
2325
if (!selectedFile) {
@@ -33,16 +35,22 @@ const mapWorkspaceProps = ({ files, selectedFile }: WorkspaceState) => {
3335
}
3436
}
3537

36-
@Connect(({ workspace, ...s }) => ({
37-
...mapWorkspaceProps(workspace),
38-
darkMode: s.settings.darkMode,
39-
vimModeEnabled: s.settings.enableVimMode,
40-
isServerEnvironment: s.runTarget.target === TargetType.Server,
41-
loading: s.status?.loading,
42-
options: s.monaco,
43-
vim: s.vim,
44-
}))
45-
export class CodeEditor extends React.Component<any, CodeEditorState> {
38+
interface CodeEditorState {
39+
code?: string
40+
loading?: boolean
41+
}
42+
43+
interface Props extends CodeEditorState {
44+
fileName: string
45+
darkMode: boolean
46+
vimModeEnabled: boolean
47+
isServerEnvironment: boolean
48+
options: MonacoSettings
49+
vim?: VimState | null
50+
dispatch: StateDispatch
51+
}
52+
53+
class CodeEditor extends React.Component<Props> {
4654
private analyzer?: Analyzer
4755
private editorInstance?: editor.IStandaloneCodeEditor
4856
private vimAdapter?: VimModeKeymap
@@ -57,6 +65,8 @@ export class CodeEditor extends React.Component<any, CodeEditorState> {
5765
this.editorInstance = editorInstance
5866
this.monaco = monacoInstance
5967

68+
registerGoLanguageProvider(apiClient)
69+
6070
editorInstance.onKeyDown((e) => this.onKeyDown(e))
6171
const [vimAdapter, statusAdapter] = createVimModeAdapter(this.props.dispatch, editorInstance)
6272
this.vimAdapter = vimAdapter
@@ -221,3 +231,13 @@ export class CodeEditor extends React.Component<any, CodeEditorState> {
221231
)
222232
}
223233
}
234+
235+
export const ConnectedCodeEditor = connect<CodeEditorState, {}>(({ workspace, ...s }) => ({
236+
...mapWorkspaceProps(workspace),
237+
darkMode: s.settings.darkMode,
238+
vimModeEnabled: s.settings.enableVimMode,
239+
isServerEnvironment: s.runTarget.target === TargetType.Server,
240+
loading: s.status?.loading,
241+
options: s.monaco,
242+
vim: s.vim,
243+
}))(CodeEditor)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as monaco from 'monaco-editor'
2+
import type { IAPIClient } from '~/services/api'
3+
4+
import { GoCompletionItemProvider } from './provider'
5+
6+
let alreadyRegistered = false
7+
export const registerGoLanguageProvider = (client: IAPIClient) => {
8+
if (alreadyRegistered) {
9+
console.warn('Go Language provider was already registered')
10+
return
11+
}
12+
13+
alreadyRegistered = true
14+
return monaco.languages.registerCompletionItemProvider('go', new GoCompletionItemProvider(client))
15+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Matches package (and method name)
2+
const COMPL_REGEXP = /([a-zA-Z0-9_]+)(\.([A-Za-z0-9_]+))?$/
3+
const R_GROUP_PKG = 1
4+
const R_GROUP_METHOD = 3
5+
6+
export const parseExpression = (expr: string) => {
7+
COMPL_REGEXP.lastIndex = 0 // Reset regex state
8+
const m = COMPL_REGEXP.exec(expr)
9+
if (!m) {
10+
return null
11+
}
12+
13+
const varName = m[R_GROUP_PKG]
14+
const propValue = m[R_GROUP_METHOD]
15+
16+
if (!propValue) {
17+
return { value: varName }
18+
}
19+
20+
return {
21+
packageName: varName,
22+
value: propValue,
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,12 @@
1-
import { loader } from '@monaco-editor/react'
2-
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
3-
import * as monaco from 'monaco-editor'
1+
import type * as monaco from 'monaco-editor'
42
import { type IAPIClient } from '~/services/api'
3+
import { wrapAsyncWithDebounce } from '../utils'
54
import snippets from './snippets'
6-
import { wrapAsyncWithDebounce } from './utils'
5+
import { parseExpression } from './parse'
76

8-
// Import aliases
9-
type CompletionList = monaco.languages.CompletionList
10-
type CompletionContext = monaco.languages.CompletionContext
11-
type ITextModel = monaco.editor.ITextModel
12-
type Position = monaco.Position
13-
type CancellationToken = monaco.CancellationToken
14-
15-
let alreadyRegistered = false
16-
17-
// Matches package (and method name)
18-
const COMPL_REGEXP = /([a-zA-Z0-9_]+)(\.([A-Za-z0-9_]+))?$/
19-
const R_GROUP_PKG = 1
20-
const R_GROUP_METHOD = 3
217
const SUGGESTIONS_DEBOUNCE_DELAY = 500
228

23-
const parseExpression = (expr: string) => {
24-
COMPL_REGEXP.lastIndex = 0 // Reset regex state
25-
const m = COMPL_REGEXP.exec(expr)
26-
if (!m) {
27-
return null
28-
}
29-
30-
const varName = m[R_GROUP_PKG]
31-
const propValue = m[R_GROUP_METHOD]
32-
33-
if (!propValue) {
34-
return { value: varName }
35-
}
36-
37-
return {
38-
packageName: varName,
39-
value: propValue,
40-
}
41-
}
42-
43-
self.MonacoEnvironment = {
44-
getWorker: () => {
45-
return new EditorWorker()
46-
},
47-
}
48-
49-
class GoCompletionItemProvider implements monaco.languages.CompletionItemProvider {
9+
export class GoCompletionItemProvider implements monaco.languages.CompletionItemProvider {
5010
private readonly getSuggestionFunc: IAPIClient['getSuggestions']
5111

5212
constructor(private readonly client: IAPIClient) {
@@ -57,11 +17,11 @@ class GoCompletionItemProvider implements monaco.languages.CompletionItemProvide
5717
}
5818

5919
async provideCompletionItems(
60-
model: ITextModel,
61-
position: Position,
62-
context: CompletionContext,
63-
token: CancellationToken,
64-
): Promise<CompletionList> {
20+
model: monaco.editor.ITextModel,
21+
position: monaco.Position,
22+
context: monaco.languages.CompletionContext,
23+
token: monaco.CancellationToken,
24+
): Promise<monaco.languages.CompletionList> {
6525
const val = model
6626
.getValueInRange({
6727
startLineNumber: position.lineNumber,
@@ -108,14 +68,3 @@ class GoCompletionItemProvider implements monaco.languages.CompletionItemProvide
10868
}
10969
}
11070
}
111-
112-
export const registerGoLanguageProvider = (client: IAPIClient) => {
113-
if (alreadyRegistered) {
114-
console.warn('Go Language provider was already registered')
115-
return
116-
}
117-
118-
alreadyRegistered = true
119-
loader.config({ monaco })
120-
return monaco.languages.registerCompletionItemProvider('go', new GoCompletionItemProvider(client))
121-
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { loader } from '@monaco-editor/react'
2+
import * as monaco from 'monaco-editor'
3+
4+
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
5+
6+
let loaderConfigured = false
7+
export const configureMonacoLoader = () => {
8+
if (loaderConfigured) {
9+
return
10+
}
11+
12+
loader.config({ monaco })
13+
loaderConfigured = true
14+
}
15+
16+
self.MonacoEnvironment = {
17+
getWorker: () => {
18+
return new EditorWorker()
19+
},
20+
}

0 commit comments

Comments
 (0)