Clear code mirror history on file change (#4510)

* clear history when loading a new file

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2024-11-18 18:08:22 -08:00
committed by GitHub
parent f71fafdece
commit f826afb32d
4 changed files with 216 additions and 8 deletions

View File

@ -1135,3 +1135,189 @@ _test.describe('Deleting items from the file pane', () => {
} }
) )
}) })
_test.describe(
'Undo and redo do not keep history when navigating between files',
() => {
_test(
`open a file, change something, open a different file, hitting undo should do nothing`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(testDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(testDir, 'other.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectCard = page.getByText('testProject')
const otherFile = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'other.kcl' }) })
await _test.step(
'Open project and make a change to the file',
async () => {
await projectCard.click()
await u.waitForPageLoad()
// Get the text in the code locator.
const originalText = await u.codeLocator.innerText()
// Click in the editor and add some new lines.
await u.codeLocator.click()
await page.keyboard.type(`sketch001 = startSketchOn('XY')
some other shit`)
// Ensure the content in the editor changed.
const newContent = await u.codeLocator.innerText()
expect(originalText !== newContent)
}
)
await _test.step('navigate to other.kcl', async () => {
await u.openFilePanel()
await otherFile.click()
await u.waitForPageLoad()
await u.openKclCodePanel()
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
})
await _test.step('hit undo', async () => {
// Get the original content of the file.
const originalText = await u.codeLocator.innerText()
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
})
}
)
_test(
`open a file, change something, undo it, open a different file, hitting redo should do nothing`,
{ tag: '@electron' },
// Skip on windows i think the keybindings are different for redo.
async ({ browserName }, testInfo) => {
test.skip(process.platform === 'win32', 'Skip on windows')
const { page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(testDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(testDir, 'other.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectCard = page.getByText('testProject')
const otherFile = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'other.kcl' }) })
const badContent = 'this shit'
await _test.step(
'Open project and make a change to the file',
async () => {
await projectCard.click()
await u.waitForPageLoad()
// Get the text in the code locator.
const originalText = await u.codeLocator.innerText()
// Click in the editor and add some new lines.
await u.codeLocator.click()
await page.keyboard.type(badContent)
// Ensure the content in the editor changed.
const newContent = await u.codeLocator.innerText()
expect(originalText !== newContent)
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
// Hit redo.
await page.keyboard.down('Shift')
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.keyboard.up('Shift')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).toContainText(badContent)
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
}
)
await _test.step('navigate to other.kcl', async () => {
await u.openFilePanel()
await otherFile.click()
await u.waitForPageLoad()
await u.openKclCodePanel()
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
await expect(u.codeLocator).not.toContainText(badContent)
})
await _test.step('hit redo', async () => {
// Get the original content of the file.
const originalText = await u.codeLocator.innerText()
// Now hit redo
await page.keyboard.down('Shift')
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.keyboard.up('Shift')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
})
}
)
}
)

View File

@ -43,6 +43,7 @@ import {
completionKeymap, completionKeymap,
} from '@codemirror/autocomplete' } from '@codemirror/autocomplete'
import CodeEditor from './CodeEditor' import CodeEditor from './CodeEditor'
import { codeManagerHistoryCompartment } from 'lang/codeManager'
export const editorShortcutMeta = { export const editorShortcutMeta = {
formatCode: { formatCode: {
@ -89,7 +90,7 @@ export const KclEditorPane = () => {
cursorBlinkRate: cursorBlinking.current ? 1200 : 0, cursorBlinkRate: cursorBlinking.current ? 1200 : 0,
}), }),
lineHighlightField, lineHighlightField,
history(), codeManagerHistoryCompartment.of(history()),
closeBrackets(), closeBrackets(),
codeFolding(), codeFolding(),
keymap.of([ keymap.of([
@ -121,7 +122,6 @@ export const KclEditorPane = () => {
lineNumbers(), lineNumbers(),
highlightActiveLineGutter(), highlightActiveLineGutter(),
highlightSpecialChars(), highlightSpecialChars(),
history(),
foldGutter(), foldGutter(),
EditorState.allowMultipleSelections.of(true), EditorState.allowMultipleSelections.of(true),
indentOnInput(), indentOnInput(),

View File

@ -6,14 +6,17 @@ import { isDesktop } from 'lib/isDesktop'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { editorManager } from 'lib/singletons' import { editorManager } from 'lib/singletons'
import { Annotation, Transaction } from '@codemirror/state' import { Annotation, Transaction } from '@codemirror/state'
import { KeyBinding } from '@codemirror/view' import { EditorView, KeyBinding } from '@codemirror/view'
import { recast, Program } from 'lang/wasm' import { recast, Program } from 'lang/wasm'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { Compartment } from '@codemirror/state'
import { history } from '@codemirror/commands'
const PERSIST_CODE_KEY = 'persistCode' const PERSIST_CODE_KEY = 'persistCode'
const codeManagerUpdateAnnotation = Annotation.define<boolean>() const codeManagerUpdateAnnotation = Annotation.define<boolean>()
export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(true) export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(true)
export const codeManagerHistoryCompartment = new Compartment()
export default class CodeManager { export default class CodeManager {
private _code: string = bracket private _code: string = bracket
@ -90,9 +93,12 @@ export default class CodeManager {
/** /**
* Update the code in the editor. * Update the code in the editor.
*/ */
updateCodeEditor(code: string): void { updateCodeEditor(code: string, clearHistory?: boolean): void {
this.code = code this.code = code
if (editorManager.editorView) { if (editorManager.editorView) {
if (clearHistory) {
clearCodeMirrorHistory(editorManager.editorView)
}
editorManager.editorView.dispatch({ editorManager.editorView.dispatch({
changes: { changes: {
from: 0, from: 0,
@ -101,7 +107,7 @@ export default class CodeManager {
}, },
annotations: [ annotations: [
codeManagerUpdateEvent, codeManagerUpdateEvent,
Transaction.addToHistory.of(true), Transaction.addToHistory.of(!clearHistory),
], ],
}) })
} }
@ -110,11 +116,11 @@ export default class CodeManager {
/** /**
* Update the code, state, and the code the code mirror editor sees. * Update the code, state, and the code the code mirror editor sees.
*/ */
updateCodeStateEditor(code: string): void { updateCodeStateEditor(code: string, clearHistory?: boolean): void {
if (this._code !== code) { if (this._code !== code) {
this.code = code this.code = code
this.#updateState(code) this.#updateState(code)
this.updateCodeEditor(code) this.updateCodeEditor(code, clearHistory)
} }
} }
@ -167,3 +173,17 @@ function safeLSSetItem(key: string, value: string) {
if (typeof window === 'undefined') return if (typeof window === 'undefined') return
localStorage?.setItem(key, value) localStorage?.setItem(key, value)
} }
function clearCodeMirrorHistory(view: EditorView) {
// Clear history
view.dispatch({
effects: [codeManagerHistoryCompartment.reconfigure([])],
annotations: [codeManagerUpdateEvent],
})
// Add history back
view.dispatch({
effects: [codeManagerHistoryCompartment.reconfigure([history()])],
annotations: [codeManagerUpdateEvent],
})
}

View File

@ -124,7 +124,9 @@ export const fileLoader: LoaderFunction = async (
// We explicitly do not write to the file here since we are loading from // We explicitly do not write to the file here since we are loading from
// the file system and not the editor. // the file system and not the editor.
codeManager.updateCurrentFilePath(currentFilePath) codeManager.updateCurrentFilePath(currentFilePath)
codeManager.updateCodeStateEditor(code) // We pass true on the end here to clear the code editor history.
// This way undo and redo are not super weird when opening new files.
codeManager.updateCodeStateEditor(code, true)
} }
// Set the file system manager to the project path // Set the file system manager to the project path