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:
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -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(),
|
||||||
|
@ -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],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user