Skip to content

Commit

Permalink
editor: fix replacing file when open in editor
Browse files Browse the repository at this point in the history
When we import/replace a file that is open in an editor, instead of
modifying the file in storage, we need to modify the file in the editor.
This allows the modification to be pushed on the undo stack so that
the user can undo the change in case it wrote over any of their recent
changes.

Issue: pybricks/support#975
  • Loading branch information
dlech committed May 18, 2023
1 parent 3fc275e commit 0d96c1f
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

## [Unreleased]

### Fixed
- Fixed importing/replacing file when file is open in editor ([support#975]).

[support#975]: https://github.com/pybricks/support/issues/975


## [2.2.0-beta.4] - 2023-05-16

### Added
Expand Down
13 changes: 12 additions & 1 deletion src/editor/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
// Copyright (c) 2022-2023 The Pybricks Authors

import { createAction } from '../actions';
import { UUID } from '../fileStorage';
Expand Down Expand Up @@ -121,3 +121,14 @@ export const editorGoto = createAction((uuid: UUID, line: number) => ({
uuid,
line,
}));

/**
* Requests to replace the value a file in the editor.
* @param uuid The file UUID.
* @param value The new value for the contents of the file.
*/
export const editorReplaceFile = createAction((uuid: UUID, value: string) => ({
type: 'editor.action.replaceFile',
uuid,
value,
}));
33 changes: 32 additions & 1 deletion src/editor/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import type { DatabaseChangeType, IDatabaseChange } from 'dexie-observable/api';
import * as monaco from 'monaco-editor';
import { EventChannel, buffers, eventChannel } from 'redux-saga';
import { EventChannel, Task, buffers, eventChannel } from 'redux-saga';
import {
call,
cancelled,
Expand Down Expand Up @@ -62,6 +62,7 @@ import {
editorGetValueResponse,
editorGoto,
editorOpenFile,
editorReplaceFile,
} from './actions';
import { EditorError } from './error';
import { ActiveFileHistoryManager, OpenFileManager } from './lib';
Expand Down Expand Up @@ -92,6 +93,28 @@ function* handleModelDidChange(
}
}

function handleReplaceFile(
editor: monaco.editor.ICodeEditor,
model: monaco.editor.ITextModel,
action: ReturnType<typeof editorReplaceFile>,
) {
// model might not be open in editor, but it doesn't hurt to do this in the
// open editor
editor.pushUndoStop();
// pushEditOperations says it expects a cursorComputer, but doesn't seem to need one.
model.pushEditOperations(
[],
[
{
range: model.getFullModelRange(),
text: action.value,
},
],
undefined as unknown as monaco.editor.ICursorStateComputer,
);
editor.pushUndoStop();
}

function* handleEditorOpenFile(
editor: monaco.editor.ICodeEditor,
openFiles: OpenFileManager,
Expand Down Expand Up @@ -161,6 +184,14 @@ function* handleEditorOpenFile(
// https://github.com/redux-saga/redux-saga/issues/620#issuecomment-259161095
yield* fork(handleModelDidChange, 1000, didChangeModelChan, model);

const replaceFileTask: Task = yield* takeEvery(
editorReplaceFile.when((a) => a.uuid === action.uuid),
handleReplaceFile,
editor,
model,
);
defer.push(() => replaceFileTask.cancel());

openFiles.add(action.uuid, model, didLoad.viewState);
defer.push(() => openFiles.remove(action.uuid));

Expand Down
28 changes: 19 additions & 9 deletions src/explorer/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
editorDidActivateFile,
editorDidCloseFile,
editorDidFailToActivateFile,
editorReplaceFile,
} from '../editor/actions';
import { EditorError } from '../editor/error';
import { getPybricksMicroPythonFileTemplate } from '../editor/pybricksMicroPython';
Expand Down Expand Up @@ -243,17 +244,26 @@ function* importPythonFile(
fileName = accepted.newName;
}

yield* put(fileStorageWriteFile(fileName, sourceFileContents));
const existingFileInfo = existingFiles.find((x) => x.path === fileName);
const openFileUuids = yield* select((s: RootState) => s.editor.openFileUuids);

const { didFailToWrite } = yield* race({
didWrite: take(fileStorageDidWriteFile.when((a) => a.path === fileName)),
didFailToWrite: take(
fileStorageDidFailToWriteFile.when((a) => a.path === fileName),
),
});
// If the file is open, modify contents in the editor so preserve undo
// history, otherwise write directly to storage.
if (existingFileInfo && openFileUuids.includes(existingFileInfo.uuid)) {
yield* put(editorReplaceFile(existingFileInfo.uuid, sourceFileContents));
} else {
yield* put(fileStorageWriteFile(fileName, sourceFileContents));

if (didFailToWrite) {
throw didFailToWrite.error;
const { didFailToWrite } = yield* race({
didWrite: take(fileStorageDidWriteFile.when((a) => a.path === fileName)),
didFailToWrite: take(
fileStorageDidFailToWriteFile.when((a) => a.path === fileName),
),
});

if (didFailToWrite) {
throw didFailToWrite.error;
}
}
}

Expand Down

0 comments on commit 0d96c1f

Please sign in to comment.