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: persist editor font size #374

Merged
merged 4 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Binary file removed web/public/fonts/Recursive_VF_1.085.woff2
Binary file not shown.
55 changes: 44 additions & 11 deletions web/src/components/features/workspace/CodeEditor/CodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import React from 'react'
import { Spinner } from '@fluentui/react'
import MonacoEditor, { type Monaco } from '@monaco-editor/react'
import { KeyMod, KeyCode, type editor, type IKeyboardEvent, type IDisposable } from 'monaco-editor'
import * as monaco from 'monaco-editor'

import apiClient from '~/services/api'
import { createVimModeAdapter, type StatusBarAdapter, type VimModeKeymap } from '~/plugins/vim/editor'
import { Analyzer } from '~/services/analyzer'
import { type MonacoSettings, TargetType } from '~/services/config'
import { connect, newMarkerAction, runFileDispatcher, type StateDispatch } from '~/store'
import {
connect,
newMarkerAction,
newMonacoParamsChangeDispatcher,
runFileDispatcher,
type StateDispatch,
} from '~/store'
import { type WorkspaceState, dispatchFormatFile, dispatchResetWorkspace, dispatchUpdateFile } from '~/store/workspace'
import { getTimeNowUsageMarkers, wrapAsyncWithDebounce } from './utils'
import { getTimeNowUsageMarkers, asyncDebounce, debounce } from './utils'
import { attachCustomCommands } from './commands'
import { LANGUAGE_GOLANG, stateToOptions } from './props'
import { configureMonacoLoader } from './loader'
Expand Down Expand Up @@ -52,17 +58,25 @@ interface Props extends CodeEditorState {

class CodeEditor extends React.Component<Props> {
private analyzer?: Analyzer
private editorInstance?: editor.IStandaloneCodeEditor
private editorInstance?: monaco.editor.IStandaloneCodeEditor
private vimAdapter?: VimModeKeymap
private vimCommandAdapter?: StatusBarAdapter
private monaco?: Monaco
private disposables?: IDisposable[]
private disposables?: monaco.IDisposable[]

private readonly debouncedAnalyzeFunc = wrapAsyncWithDebounce(async (fileName: string, code: string) => {
private readonly debouncedAnalyzeFunc = asyncDebounce(async (fileName: string, code: string) => {
return await this.doAnalyze(fileName, code)
}, ANALYZE_DEBOUNCE_TIME)

editorDidMount(editorInstance: editor.IStandaloneCodeEditor, monacoInstance: Monaco) {
private readonly persistFontSize = debounce((fontSize: number) => {
this.props.dispatch(
newMonacoParamsChangeDispatcher({
fontSize,
}),
)
}, 1000)

editorDidMount(editorInstance: monaco.editor.IStandaloneCodeEditor, monacoInstance: Monaco) {
this.disposables = registerGoLanguageProviders(apiClient, this.props.dispatch)
this.editorInstance = editorInstance
this.monaco = monacoInstance
Expand All @@ -72,6 +86,15 @@ class CodeEditor extends React.Component<Props> {
this.vimAdapter = vimAdapter
this.vimCommandAdapter = statusAdapter

// Font should be set only once during boot as when font size changes
// by zoom and editor config object is updated - this cause infinite
// font change calls with random values.
if (this.props.options.fontSize) {
editorInstance.updateOptions({
fontSize: this.props.options.fontSize,
})
}

if (this.props.vimModeEnabled) {
console.log('Vim mode enabled')
this.vimAdapter.attach()
Expand All @@ -96,7 +119,7 @@ class CodeEditor extends React.Component<Props> {
id: 'run-code',
label: 'Build And Run Code',
contextMenuGroupId: 'navigation',
keybindings: [KeyMod.CtrlCmd | KeyCode.Enter],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
run: (ed, ...args) => {
this.props.dispatch(runFileDispatcher)
},
Expand All @@ -105,13 +128,23 @@ class CodeEditor extends React.Component<Props> {
id: 'format-code',
label: 'Format Code (goimports)',
contextMenuGroupId: 'navigation',
keybindings: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyF],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF],
run: (ed, ...args) => {
this.props.dispatch(dispatchFormatFile())
},
},
]

// Persist font size on zoom
this.disposables.push(
editorInstance.onDidChangeConfiguration((e) => {
if (e.hasChanged(monaco.editor.EditorOption.fontSize)) {
const newFontSize = editorInstance.getOption(monaco.editor.EditorOption.fontSize)
this.persistFontSize(newFontSize)
}
}),
)

// Register custom actions
actions.forEach((action) => editorInstance.addAction(action))
attachCustomCommands(editorInstance)
Expand Down Expand Up @@ -166,7 +199,7 @@ class CodeEditor extends React.Component<Props> {
this.editorInstance.dispose()
}

onChange(newValue: string | undefined, _: editor.IModelContentChangedEvent) {
onChange(newValue: string | undefined, _: monaco.editor.IModelContentChangedEvent) {
if (!newValue) {
return
}
Expand Down Expand Up @@ -206,7 +239,7 @@ class CodeEditor extends React.Component<Props> {
this.props.dispatch(newMarkerAction(fileName, markers))
}

private onKeyDown(e: IKeyboardEvent) {
private onKeyDown(e: monaco.IKeyboardEvent) {
const { vimModeEnabled, vim } = this.props
if (!vimModeEnabled || !vim?.commandStarted) {
return
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type * as monaco from 'monaco-editor'
import { type IAPIClient } from '~/services/api'
import { wrapAsyncWithDebounce } from '../../utils'
import { asyncDebounce } from '../../utils'
import snippets from './snippets'
import { parseExpression } from './parse'

Expand All @@ -10,7 +10,7 @@ export class GoCompletionItemProvider implements monaco.languages.CompletionItem
private readonly getSuggestionFunc: IAPIClient['getSuggestions']

constructor(private readonly client: IAPIClient) {
this.getSuggestionFunc = wrapAsyncWithDebounce(
this.getSuggestionFunc = asyncDebounce(
async (query) => await client.getSuggestions(query),
SUGGESTIONS_DEBOUNCE_DELAY,
)
Expand Down
4 changes: 4 additions & 0 deletions web/src/components/features/workspace/CodeEditor/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const DEMO_CODE = [

// stateToOptions converts MonacoState to IEditorOptions
export const stateToOptions = (state: MonacoSettings): monaco.editor.IEditorOptions => {
// fontSize here is intentionally ignored as monaco-editor starts to infinitly change
// font size to random values for unknown reason.
//
// Font size apply moved to componentDidMount.
const {
selectOnLineNumbers,
mouseWheelZoom,
Expand Down
17 changes: 16 additions & 1 deletion web/src/components/features/workspace/CodeEditor/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const getTimeNowUsageMarkers = (
* @param fn Function
* @param delay Debounce time
*/
export const wrapAsyncWithDebounce = <T>(fn: (...args) => Promise<T>, delay: number) => {
export const asyncDebounce = <T>(fn: (...args) => Promise<T>, delay: number) => {
let lastTimeoutId: NodeJS.Timeout | null = null

return async (...args) => {
Expand All @@ -72,3 +72,18 @@ export const wrapAsyncWithDebounce = <T>(fn: (...args) => Promise<T>, delay: num
})
}
}

/**
* Wraps passed function with a debouncer
*/
export const debounce = <T>(fn: (...args) => T, delay: number) => {
let lastTimeoutId: NodeJS.Timeout | null = null

return (...args) => {
if (lastTimeoutId) {
clearTimeout(lastTimeoutId)
}

lastTimeoutId = setTimeout(() => fn(...args), delay)
}
}
2 changes: 2 additions & 0 deletions web/src/services/config/monaco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DEFAULT_FONT } from '../fonts'

export interface MonacoSettings {
fontFamily: string
fontSize?: number
fontLigatures: boolean
cursorBlinking: 'blink' | 'smooth' | 'phase' | 'expand' | 'solid'
cursorStyle: 'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin'
Expand All @@ -13,6 +14,7 @@ export interface MonacoSettings {
}

export const defaultMonacoSettings: MonacoSettings = {
fontSize: 12,
fontFamily: DEFAULT_FONT,
fontLigatures: false,
cursorBlinking: 'blink',
Expand Down
9 changes: 0 additions & 9 deletions web/src/services/fonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,6 @@ const fontsList: Record<string, FontDeclaration> = {
},
],
},
Recursive: {
label: 'Recursive',
family: 'Recursive',
fonts: [
{
src: [{ url: '/fonts/Recursive_VF_1.085.woff2', format: 'woff2' }],
},
],
},
'Zed-Mono': {
label: 'Zed Mono',
family: 'Zed Mono',
Expand Down
6 changes: 2 additions & 4 deletions web/src/store/actions/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { type MonacoSettings, type RunTargetConfig } from '~/services/config'
import { ActionType } from './actions'
import { type PanelState, type SettingsState } from '../state'

export type MonacoParamsChanges<T = any> = {
[k in keyof MonacoSettings | string]: T
}
export type MonacoParamsChanges = Partial<MonacoSettings>

export const newToggleThemeAction = () => ({
type: ActionType.TOGGLE_THEME,
Expand All @@ -17,7 +15,7 @@ export const newRunTargetChangeAction = (cfg: RunTargetConfig) => ({
payload: cfg,
})

export const newMonacoParamsChangeAction = <T>(changes: MonacoParamsChanges<T>) => ({
export const newMonacoParamsChangeAction = (changes: MonacoParamsChanges) => ({
type: ActionType.MONACO_SETTINGS_CHANGE,
payload: changes,
})
Expand Down
Loading