-
Notifications
You must be signed in to change notification settings - Fork 7
refactor: store file content in atom #105
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
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
b98a014
refactor: make files-related atoms more atomic
sehyod a9b0b93
fix: run prettier
sehyod bdcdcbd
refactor: move core atoms in a core directory
sehyod f784da1
fix: update active pane atom on focus
sehyod 7cccf4e
fix: remove core atom usage
sehyod 9511cd1
Merge remote-tracking branch 'origin/main' into store-file-content-in…
sehyod f75acb1
fix: run prettier
sehyod 5a53da2
Merge remote-tracking branch 'origin/main' into store-file-content-in…
sehyod 1088ef4
refactor: add comments to core atoms
sehyod 2f6a56f
Set up import/no-restricted-paths
danvk 335431c
fix: address review comments
sehyod 0b67878
fix: return null for typing
sehyod b79ed3e
Merge remote-tracking branch 'origin/main' into store-file-content-in…
sehyod 7486851
Adopt panel.id instead of hard-coded PanelId
cguedes 8144127
Call write atoms in the MainPanelPane
cguedes 5178770
Remove use of Promise
cguedes dfa233d
Remove the _ from fore atoms
cguedes 3708749
Fix lint and export core atom
cguedes 3df67d2
Merge branch 'main' into store-file-content-in-atom
cguedes File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Core atoms | ||
|
|
||
| This directory contains "core" atoms. | ||
| These are primitive atoms, along with some useful action atoms to update their value. | ||
|
|
||
| Using these atoms directly can lead to invalid states, therefore they should never been used outside of the `atoms` directory of this project. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| /* eslint-disable no-underscore-dangle */ | ||
| import { atom } from 'jotai'; | ||
|
|
||
| import { PaneId, PaneState } from '../types/PaneGroup'; | ||
| import { paneGroupAtom } from './paneGroup'; | ||
|
|
||
| const DEFAULT_PANE: PaneId = 'LEFT'; | ||
|
|
||
| /** This core atom contains the id of the currently active pane. */ | ||
| export const activePaneIdAtom = atom<PaneId>(DEFAULT_PANE); | ||
|
|
||
| /** Read-only composed atom to get the active pane along with its id. */ | ||
| export const activePaneAtom = atom<PaneState & { id: PaneId }>((get) => { | ||
| const activePaneId = get(activePaneIdAtom); | ||
| return { ...get(paneGroupAtom)[activePaneId], id: activePaneId }; | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /* eslint-disable no-underscore-dangle */ | ||
| import { Atom, atom } from 'jotai'; | ||
| import { loadable } from 'jotai/utils'; | ||
| import { Loadable } from 'jotai/vanilla/utils/loadable'; | ||
|
|
||
| import { readFileContent } from '../../filesystem'; | ||
| import { FileContent } from '../types/FileContent'; | ||
| import { FileFileEntry, FileId } from '../types/FileEntry'; | ||
|
|
||
| type FileContentState = ReadonlyMap<FileId, Atom<Loadable<FileContent>>>; | ||
|
|
||
| /** | ||
| * This atom stores the atoms containing the content of open files. | ||
| * Each fileContent atom is a loadable atom, asynchronously reading files | ||
| */ | ||
| export const fileContentAtom = atom<FileContentState>(new Map()); | ||
|
|
||
| /** Loads file content in memory when opening a file */ | ||
| export const loadFile = atom(null, (get, set, file: FileFileEntry) => { | ||
| const currentOpenFiles = get(fileContentAtom); | ||
| const updatedMap = new Map(currentOpenFiles); | ||
| const fileAtom = loadable(atom(() => readFileContent(file))); | ||
| updatedMap.set(file.path, fileAtom); | ||
| set(fileContentAtom, updatedMap); | ||
| }); | ||
|
|
||
| /** Removes the content from memory when closing the file */ | ||
| export const unloadFile = atom(null, (get, set, fileId: FileId) => { | ||
| const currentOpenFiles = get(fileContentAtom); | ||
|
|
||
| if (!currentOpenFiles.has(fileId)) { | ||
| console.warn('File is not open ', fileId); | ||
| return; | ||
| } | ||
|
|
||
| const updatedMap = new Map(currentOpenFiles); | ||
| updatedMap.delete(fileId); | ||
| set(fileContentAtom, updatedMap); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /* eslint-disable no-underscore-dangle */ | ||
| import { atom } from 'jotai'; | ||
|
|
||
| import { FileFileEntry, FileId } from '../types/FileEntry'; | ||
|
|
||
| /** This atom contains data about open files */ | ||
| export const fileEntryAtom = atom<Map<FileId, FileFileEntry>>(new Map()); | ||
|
|
||
| /** Stores data for a file when opening it */ | ||
| export const addFileEntry = atom(null, (get, set, file: FileFileEntry) => { | ||
| const updatedFileEntries = new Map(get(fileEntryAtom)); | ||
| updatedFileEntries.set(file.path, file); | ||
| set(fileEntryAtom, updatedFileEntries); | ||
| }); | ||
|
|
||
| /** Removes data from memory when closing the file */ | ||
| export const removeFileEntry = atom(null, (get, set, fileId: FileId) => { | ||
| const updatedFileEntries = new Map(get(fileEntryAtom)); | ||
|
|
||
| if (!updatedFileEntries.has(fileId)) { | ||
| console.warn('File is not open ', fileId); | ||
| return; | ||
| } | ||
|
|
||
| updatedFileEntries.delete(fileId); | ||
| set(fileEntryAtom, updatedFileEntries); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| /* eslint-disable no-underscore-dangle */ | ||
| import { atom, Getter } from 'jotai'; | ||
|
|
||
| import { isNonNullish } from '../../lib/isNonNullish'; | ||
| import { PaneContent, PaneFileId, PaneId, PaneState } from '../types/PaneGroup'; | ||
| import { activePaneIdAtom } from './activePane'; | ||
| import { fileContentAtom } from './fileContent'; | ||
| import { fileEntryAtom } from './fileEntry'; | ||
|
|
||
| type PaneGroupState = Record<PaneId, PaneState>; | ||
|
|
||
| /** This atom contains data about the panes: the list of open files and the active file */ | ||
| export const paneGroupAtom = atom<PaneGroupState>({ | ||
| LEFT: { | ||
| openFiles: [], | ||
| }, | ||
| RIGHT: { | ||
| openFiles: [], | ||
| }, | ||
| }); | ||
|
|
||
| export function getPane(get: Getter, paneId: PaneId): PaneContent { | ||
| const panes = get(paneGroupAtom); | ||
| const fileEntries = get(fileEntryAtom); | ||
| const openFiles = get(fileContentAtom); | ||
| const pane = panes[paneId]; | ||
| return { | ||
| id: paneId, | ||
| files: pane.openFiles.map((id) => fileEntries.get(id)).filter(isNonNullish), | ||
| activeFile: pane.activeFile ? fileEntries.get(pane.activeFile) : undefined, | ||
| activeFileContent: pane.activeFile ? openFiles.get(pane.activeFile) : undefined, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Updates a given pane with partial attributes | ||
| */ | ||
| export const updatePaneGroup = atom( | ||
| null, | ||
| (get, set, { paneId, ...update }: { paneId: PaneId } & Partial<PaneState>) => { | ||
| const panes = get(paneGroupAtom); | ||
| set(paneGroupAtom, { | ||
| ...panes, | ||
| [paneId]: { | ||
| ...panes[paneId], | ||
| ...update, | ||
| }, | ||
| }); | ||
| }, | ||
| ); | ||
|
|
||
| /** | ||
| * Adds a file to the list of open files of the given pane | ||
| * Please note that this atom does not check that file exists or is loaded in memory | ||
| * */ | ||
| export const addFileToPane = atom(null, (get, set, { fileId, paneId }: PaneFileId) => { | ||
| const panes = get(paneGroupAtom); | ||
| set(updatePaneGroup, { | ||
| paneId, | ||
| openFiles: panes[paneId].openFiles.includes(fileId) | ||
| ? panes[paneId].openFiles // File was already open | ||
| : [...panes[paneId].openFiles, fileId], // Add file to the list of open files | ||
| }); | ||
| }); | ||
|
|
||
| /** Removes a file from the list of open files */ | ||
| export const removeFileFromPane = atom(null, (get, set, { fileId, paneId }: PaneFileId) => { | ||
| const panes = get(paneGroupAtom); | ||
|
|
||
| if (!panes[paneId].openFiles.includes(fileId)) { | ||
| console.warn('File is not open in the given pane ', fileId, paneId); | ||
| return; | ||
| } | ||
|
|
||
| const updatedOpenFiles = panes[paneId].openFiles.filter((_fileId) => _fileId !== fileId); | ||
| set(updatePaneGroup, { | ||
| paneId, | ||
| openFiles: updatedOpenFiles, | ||
| }); | ||
| }); | ||
|
|
||
| /** Updates the active file of the pane */ | ||
| export const selectFileInPaneAtom = atom(null, (get, set, { fileId, paneId }: PaneFileId) => { | ||
| const panes = get(paneGroupAtom); | ||
|
|
||
| if (!panes[paneId].openFiles.includes(fileId)) { | ||
| console.warn('File not open in the given pane ', fileId, paneId); | ||
| return; | ||
| } | ||
|
|
||
| set(activePaneIdAtom, paneId); | ||
| set(updatePaneGroup, { | ||
| paneId, | ||
| activeFile: fileId, | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { atom } from 'jotai'; | ||
|
|
||
| import { activePaneAtom, activePaneIdAtom } from './core/activePane'; | ||
| import { fileContentAtom, loadFile, unloadFile } from './core/fileContent'; | ||
| import { addFileEntry, removeFileEntry } from './core/fileEntry'; | ||
| import { addFileToPane, getPane, paneGroupAtom, removeFileFromPane, selectFileInPaneAtom } from './core/paneGroup'; | ||
| import { FileEntry, FileId } from './types/FileEntry'; | ||
| import { PaneFileId, PaneId } from './types/PaneGroup'; | ||
|
|
||
| export { selectFileInPaneAtom } from './core/paneGroup'; | ||
|
|
||
| /** Open a file in the active pane */ | ||
| export const openFileAtom = atom(null, (get, set, file: FileEntry) => { | ||
| if (file.isFolder) { | ||
| console.warn('Cannot open directory ', file.path); | ||
| return; | ||
| } | ||
| // Load file in memory | ||
| const currentOpenFiles = get(fileContentAtom); | ||
| if (!currentOpenFiles.has(file.path)) { | ||
| set(loadFile, file); | ||
| } | ||
|
|
||
| // Add to file entries atom | ||
| set(addFileEntry, file); | ||
|
|
||
| // Add file to panes state | ||
| const activePane = get(activePaneAtom); | ||
| const fileId = file.path; | ||
| if (!activePane.openFiles.includes(fileId)) { | ||
| set(addFileToPane, { fileId, paneId: activePane.id }); | ||
| } | ||
|
|
||
| // Select file in pane | ||
| set(selectFileInPaneAtom, { fileId, paneId: activePane.id }); | ||
| }); | ||
|
|
||
| /** Removes file from the given pane and unload content from memory if the file is not open in another pane */ | ||
| export const closeFileFromPaneAtom = atom(null, (get, set, { fileId, paneId }: PaneFileId) => { | ||
| const panes = get(paneGroupAtom); | ||
|
|
||
| set(removeFileFromPane, { fileId, paneId }); | ||
|
|
||
| // Unload file from memory if the file is no longer open anywhere | ||
| if ( | ||
| Object.entries(panes) | ||
| .filter(([_paneId]) => _paneId !== paneId) // Keep only other panes | ||
| .every(([, pane]) => !pane.openFiles.includes(fileId)) // Check that the file was not open in any other pane | ||
| ) { | ||
| set(removeFileEntry, fileId); | ||
| set(unloadFile, fileId); | ||
| } | ||
|
|
||
| // Select another file in the pane, if there are any | ||
| const updatedPanes = get(paneGroupAtom); | ||
| const updatedOpenFiles = updatedPanes[paneId].openFiles; | ||
| if (updatedOpenFiles.length > 0) { | ||
| set(selectFileInPaneAtom, { fileId: updatedOpenFiles[updatedOpenFiles.length - 1], paneId }); | ||
| } | ||
| }); | ||
|
|
||
| export const leftPaneAtom = atom((get) => getPane(get, 'LEFT')); | ||
| export const rightPaneAtom = atom((get) => getPane(get, 'RIGHT')); | ||
|
|
||
| interface SplitFilePayload { | ||
| fileId: FileId; | ||
| fromPaneId: PaneId; | ||
| toPaneId: PaneId; | ||
| } | ||
|
|
||
| export const splitFileToPaneAtom = atom(null, (_get, set, { fileId, fromPaneId, toPaneId }: SplitFilePayload) => { | ||
| set(removeFileFromPane, { paneId: fromPaneId, fileId }); | ||
| set(addFileToPane, { paneId: toPaneId, fileId }); | ||
| set(selectFileInPaneAtom, { paneId: toPaneId, fileId }); | ||
| }); | ||
|
|
||
| export const focusPaneAtom = atom(null, (_get, set, paneId: PaneId) => { | ||
| set(activePaneIdAtom, paneId); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessary for this PR, but user-visible error handling is something we should get a story in place for sooner rather than later. (From a user perspective, this will silently fail and no one will see the
console.warn.)