From 4f4edcfa8a9e81ec139c89864a712e38c7620db7 Mon Sep 17 00:00:00 2001 From: Thomas Castiglione Date: Wed, 28 Aug 2024 10:51:29 +0800 Subject: [PATCH] use consistent line endings --- .editorconfig | 2 + .github/workflows/ci.yml | 90 +- .prettierrc | 10 +- CHANGELOG.md | 157 +- DESIGN.md | 176 +- TODO.md | 70 +- src-tauri/gen/schemas/acl-manifests.json | 3288 +++++++++++++++++- src-tauri/gen/schemas/capabilities.json | 21 +- src-tauri/gen/schemas/plugin-manifests.json | 3402 ++++++++++++++++++- src-tauri/src/callbacks.rs | 272 +- src-tauri/src/config/gg.toml | 40 +- src-tauri/src/config/mod.rs | 208 +- src-tauri/src/handler.rs | 80 +- src-tauri/src/menu.rs | 1036 +++--- src-tauri/src/messages/mod.rs | 454 +-- src-tauri/src/messages/mutations.rs | 526 +-- src-tauri/src/messages/queries.rs | 574 ++-- src-tauri/src/windows.rs | 200 +- src-tauri/src/worker/mod.rs | 218 +- src-tauri/src/worker/mutations.rs | 2146 ++++++------ src-tauri/src/worker/queries.rs | 1186 +++---- src-tauri/src/worker/session.rs | 732 ++-- src-tauri/src/worker/tests/mod.rs | 298 +- src-tauri/src/worker/tests/mutations.rs | 658 ++-- src-tauri/src/worker/tests/queries.rs | 268 +- src-tauri/src/worker/tests/session.rs | 786 ++--- src/GraphLine.svelte | 214 +- src/GraphLog.svelte | 262 +- src/GraphNode.svelte | 80 +- src/LogPane.svelte | 430 +-- src/RevisionPane.svelte | 692 ++-- src/controls/ActionWidget.svelte | 162 +- src/controls/AuthorSpan.svelte | 100 +- src/controls/BoundQuery.svelte | 72 +- src/controls/BranchSpan.svelte | 36 +- src/controls/CheckWidget.svelte | 52 +- src/controls/Chip.svelte | 100 +- src/controls/Icon.svelte | 78 +- src/controls/IdSpan.svelte | 80 +- src/controls/ListWidget.svelte | 318 +- src/ipc.ts | 310 +- src/mutators/BinaryMutator.ts | 406 +-- src/mutators/ChangeMutator.ts | 96 +- src/mutators/RefMutator.ts | 374 +- src/mutators/RevisionMutator.ts | 288 +- src/objects/BranchObject.svelte | 156 +- src/objects/ChangeObject.svelte | 138 +- src/objects/Object.svelte | 292 +- src/objects/RevisionObject.svelte | 344 +- src/objects/TagObject.svelte | 30 +- src/objects/Zone.svelte | 172 +- src/shell/ErrorDialog.svelte | 38 +- src/shell/InputDialog.svelte | 176 +- src/shell/ModalDialog.svelte | 184 +- src/shell/ModalOverlay.svelte | 66 +- src/shell/Pane.svelte | 64 +- src/shell/Settings.ts | 4 +- src/shell/StatusBar.svelte | 326 +- src/stores.ts | 54 +- 59 files changed, 14900 insertions(+), 8192 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..874bf8b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*] +end_of_line = lf \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dde4f5c..267bb54 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,45 +1,45 @@ -name: 'ci' - -on: - push: - branches: - - master - -jobs: - svelte-check: - runs-on: "ubuntu-22.04" - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: install packages - run: npm install --package-lock=false - - - name: run check - run: npm run check - - cargo-test: - runs-on: "ubuntu-22.04" - env: - RUST_BACKTRACE: "1" - steps: - - uses: actions/checkout@v4 - - - uses: awalsh128/cache-apt-pkgs-action@v1 - with: - packages: libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - version: 1.0 - - - uses: dtolnay/rust-toolchain@stable - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: "src-tauri" - - - name: run tests - run: cd src-tauri && cargo test - +name: 'ci' + +on: + push: + branches: + - master + +jobs: + svelte-check: + runs-on: "ubuntu-22.04" + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: install packages + run: npm install --package-lock=false + + - name: run check + run: npm run check + + cargo-test: + runs-on: "ubuntu-22.04" + env: + RUST_BACKTRACE: "1" + steps: + - uses: actions/checkout@v4 + + - uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + version: 1.0 + + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: "src-tauri" + + - name: run tests + run: cd src-tauri && cargo test + diff --git a/.prettierrc b/.prettierrc index 787539b..cecaf5c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,6 @@ -{ - "tabWidth": 4, - "bracketSameLine": true, - "svelteBracketNewLine": false, - "printWidth": 120 +{ + "tabWidth": 4, + "bracketSameLine": true, + "svelteBracketNewLine": false, + "printWidth": 120 } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 22b046b..af75509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,78 +1,79 @@ -# GG Changelog - -## [0.20.0](releases/tag/v0.20.0) -This version is based on Jujutsu 0.20. - -### Fixed -- `gg.queries.log-page-size` setting was not being respected. - -## [0.18.0](releases/tag/v0.18.0) -This version is based on Jujutsu 0.18. - -## [0.17.0](releases/tag/v0.17.0) -This version is compatible with Jujutsu 0.17. - -## [0.16.0](releases/tag/v0.16.0) -This version is compatible with Jujutsu 0.16. - -### Added -- File diffs displayed in the revision pane; also, the file list is now keyboard-selectable. -- Backout command, which creates the changes necessary to undo a revision in the working copy. -- Consistent author/timestamp formatting, with tooltips for more detail. - -### Fixed -- Right-pane scrollbar wasn't responding to clicks. -- Various design improvements. - -## [0.15.3](releases/tag/v0.15.3) - -### Added -- Relatively comprehensive branch management - create, delete, rename, forget, push and fetch. -- Display Git remotes in the status bar, with commands to push or fetch all their branches. -- Display Git tags (readonly; they aren't really a Jujutsu concept). -- Display edges to commits that aren't in the queried revset, by drawing a line to nowhere. -- Detect changes made by other Jujutsu clients and merge the operation log automatically. -- Improved keyboard support and focus behaviour. -- Window title includes the workspace path (when one is open). -- On Windows, the taskbar icon has a jump list with links to recent workspaces. -- New config options: - * `gg.queries.log-page-size` for tuning performance on large repositories. - * `gg.ui.mark-unpushed-branches` to control whether local-only branches are called out. - -### Fixed -- GG now understands divergent changes, and can act on commits that have a shared change id. - Note that if you do anything to such commits other than abandoning them, you're likely to - create even more divergent commits! -- The AppImage build wasn't picking up the working directory correctly. This is fixed, and - you can also specify a workspace to open on the commandline as an alternative. -- Watchman support (core.fsmonitor) was not enabled. -- Various design improvements. - -## [0.15.2](releases/tag/v0.15.2) - -### Fixed -- Right click -> Abandon revision... again. - -## [0.15.1](releases/tag/v0.15.1) - -### Fixed -- Several buttons had stopped working due to IPC changes: - * The Squash/Restore buttons on the right pane. - * Right click -> Abandon revision. - * Right click -> Squash into parent. - * Right click -> Restore from parent. - -## [0.15.0](releases/tag/v0.15.0) -Initial experimental release. This version is compatible with Jujutsu 0.15. - -### Added -- Open, reload and snapshot repositories. -- Graph-based log displaying summaries, author and status. -- Log queries in Jujutsu's [revset language](https://martinvonz.github.io/jj/latest/revsets/). -- Revision view with file-level change details and editing commands. -- Drag and drop to move, remove and recombine revisions/files/branches. -- Context menus for common operations. -- Transactional operations with single-level undo. -- Light and dark themes. -- Codesigned binaries for MacOS and Windows. -- Completely untested binaries for Linux. +# GG Changelog + +## [0.20.0](releases/tag/v0.20.0) +This version is based on Jujutsu 0.20. + +### Fixed +- `gg.queries.log-page-size` setting was not being respected. +- Removed <CR> character which rendered as a circle in the author display on some Linux systems. + +## [0.18.0](releases/tag/v0.18.0) +This version is based on Jujutsu 0.18. + +## [0.17.0](releases/tag/v0.17.0) +This version is compatible with Jujutsu 0.17. + +## [0.16.0](releases/tag/v0.16.0) +This version is compatible with Jujutsu 0.16. + +### Added +- File diffs displayed in the revision pane; also, the file list is now keyboard-selectable. +- Backout command, which creates the changes necessary to undo a revision in the working copy. +- Consistent author/timestamp formatting, with tooltips for more detail. + +### Fixed +- Right-pane scrollbar wasn't responding to clicks. +- Various design improvements. + +## [0.15.3](releases/tag/v0.15.3) + +### Added +- Relatively comprehensive branch management - create, delete, rename, forget, push and fetch. +- Display Git remotes in the status bar, with commands to push or fetch all their branches. +- Display Git tags (readonly; they aren't really a Jujutsu concept). +- Display edges to commits that aren't in the queried revset, by drawing a line to nowhere. +- Detect changes made by other Jujutsu clients and merge the operation log automatically. +- Improved keyboard support and focus behaviour. +- Window title includes the workspace path (when one is open). +- On Windows, the taskbar icon has a jump list with links to recent workspaces. +- New config options: + * `gg.queries.log-page-size` for tuning performance on large repositories. + * `gg.ui.mark-unpushed-branches` to control whether local-only branches are called out. + +### Fixed +- GG now understands divergent changes, and can act on commits that have a shared change id. + Note that if you do anything to such commits other than abandoning them, you're likely to + create even more divergent commits! +- The AppImage build wasn't picking up the working directory correctly. This is fixed, and + you can also specify a workspace to open on the commandline as an alternative. +- Watchman support (core.fsmonitor) was not enabled. +- Various design improvements. + +## [0.15.2](releases/tag/v0.15.2) + +### Fixed +- Right click -> Abandon revision... again. + +## [0.15.1](releases/tag/v0.15.1) + +### Fixed +- Several buttons had stopped working due to IPC changes: + * The Squash/Restore buttons on the right pane. + * Right click -> Abandon revision. + * Right click -> Squash into parent. + * Right click -> Restore from parent. + +## [0.15.0](releases/tag/v0.15.0) +Initial experimental release. This version is compatible with Jujutsu 0.15. + +### Added +- Open, reload and snapshot repositories. +- Graph-based log displaying summaries, author and status. +- Log queries in Jujutsu's [revset language](https://martinvonz.github.io/jj/latest/revsets/). +- Revision view with file-level change details and editing commands. +- Drag and drop to move, remove and recombine revisions/files/branches. +- Context menus for common operations. +- Transactional operations with single-level undo. +- Light and dark themes. +- Codesigned binaries for MacOS and Windows. +- Completely untested binaries for Linux. diff --git a/DESIGN.md b/DESIGN.md index 5aaea31..be9cb5b 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,89 +1,89 @@ -Design Principles ------------------ -The primary metaphor is *direct manipulation*. GG aims to present a view of the repository's -conceptual contents - revisions, changes to files, synced refs and maybe more - which can be -modified, using right-click and drag-drop, to 'edit' the repo as a whole. - -Jujutsu CLI commands sometimes have a lot of options (`rebase`) or are partially redundant -for convenience (`move`, `squash`). This is good for scripting, but some use cases demand -interactivity - reordering multiple commits, for example. Hopefully, `gg` can complement `jj` -by providing decomposed means to achieve some of the same tasks, with immediate visual feedback. - -The UI uses a couple of key conventions for discoverability: -- An *actionable object* is represented by an icon followed by a line of text. These are - drag sources, drop targets and context menu hosts. -- Chrome and labels are greyscale; anything interactable uses specific colours to indicate - categories of widget or object states. - -Architectural Choices ---------------------- -In order to create a quality desktop app, a pure webapp is out of scope. However, significant -portions of the code could be reused in a client server app, and we won't introduce *needless* -coupling. `mod worker` and `ipc.ts` are key abstraction boundaries which keep Tauri-specific -code in its own glue layers. - -Each window has a worker thread which owns `Session` data. A session can be in multiple states, -including: -- `WorkerSession` - Opening/reopening a workspace -- `WorkspaceSession` - Workspace open, able to execute mutations -- `QuerySession` - Paged query in progress, able to fetch efficiently - -IPC is divided into four categories, which is probably one too many: -- Client->Server **triggers** cause the backend to perform native UI actions. -- Client->Server **queries** request information from the session without affecting state. -- Client->Server **mutations** modify session state in a structured fashion. -- Server->Client and Client->Client **events** are broadcast to push information to the UI. - -Drag & drop capabilities are implemented by `objects/Object.svelte`, a draggable item, and -`objects/Zone.svelte`, a droppable region. Policy is centralised in `mutators/BinaryMutator.ts`. - -Branch Objects --------------- -The representation of branches, in JJ and GG, is a bit complicated; there are multiple state axes. -A repository can have zero or more **remotes**. -A **local branch** can track zero or more of the remotes. (Technically, remote *branches*.) -A **remote branch** can be any of *tracked* (a flag on the ref), *synced* (if it points to the same -commit as a local branch of the same name), and *absent* (if there's a local branch with *no* ref, -in which case it will be deleted by the CLI on push. - -GG attempts to simplify the display of branches by combining refs in the UI. Taking advantage of -Jujutsu's model, which guarantees that a branch name identifies the same branch across remotes, a -local branch and the tracked remote branches with which it is currently synced are be combined into -a single UI object. Remote branches are displayed separately if they're unsynced, untracked or absent. - -Consequently, the commands available for a branch as displayed in the UI have polymorphic effect: -1) "Track": Applies to any remote branch that is not already tracked. -2) "Untrack": - - For a *tracking local/combined branch*, untracks all remotes. - - For an *unsynced remote branch*, untracks one remote. -3) "Push": Applies to local branches tracking any remotes. -4) "Push to remote...": Applies to local branches when any remotes exist. -5) "Fetch": Downloads for a specific branch only. - - For a *tracking local/combined branch*, fetches from all remotes. - - For a *remote branch*, fetches from its remote. -6) "Fetch from remote...": Applies to local branches when any trackable remotes exist. -7) "Rename...": Renames a local branch, without affecting remote branches. - - For a *nontracking local branch*, just renames. - - For a *tracking/combined branch*, untracks first. -8) "Delete": Applies to a user-visible object, not combined objects. - - For a *local/combined branch*, deletes the local ref. - - For a *remote branch*, forgets the remote ref (which also clears pending deletes.) - -Multiple-dispatch commands: -1) "Move": Drop local branch onto revision. Sets the ref to a commit, potentially de- or re-syncing it. -2) "Track": Drop remote branch onto local of the same name. -3) "Delete": Drag almost any branch out, with polymorphic effect (see above). - -Displaying the branch state is a bit fuzzy. The idea is to convey the most useful bits of information at -a glance, and leave the rest to tooltips or context menus. Most branches display in the -"modify" state; "add" and "remove" are used only for *unsynced* branches, with unsynced locals being "add" -and unsynced or absent remotes "remove". - -This is vaguely analogous to the more straightforward use of modify/add/remove for file changes, adapted to -the fact that many branch states are "normal"; the mental shorthand is that add/green means that pushing will -cause a remote to set this ref, and remove/red means the remote will no longer contain this ref (at this pointer). - -Additionally, a dashed border (like the dashed lines used for elided commits) has a special meaning, also -fuzzy: this ref is "disconnected", either local-only or remote-only. Disconnected local branches are ones -which have no remotes (in a repo that does have remotes); disconnected remote branches are ones which will +Design Principles +----------------- +The primary metaphor is *direct manipulation*. GG aims to present a view of the repository's +conceptual contents - revisions, changes to files, synced refs and maybe more - which can be +modified, using right-click and drag-drop, to 'edit' the repo as a whole. + +Jujutsu CLI commands sometimes have a lot of options (`rebase`) or are partially redundant +for convenience (`move`, `squash`). This is good for scripting, but some use cases demand +interactivity - reordering multiple commits, for example. Hopefully, `gg` can complement `jj` +by providing decomposed means to achieve some of the same tasks, with immediate visual feedback. + +The UI uses a couple of key conventions for discoverability: +- An *actionable object* is represented by an icon followed by a line of text. These are + drag sources, drop targets and context menu hosts. +- Chrome and labels are greyscale; anything interactable uses specific colours to indicate + categories of widget or object states. + +Architectural Choices +--------------------- +In order to create a quality desktop app, a pure webapp is out of scope. However, significant +portions of the code could be reused in a client server app, and we won't introduce *needless* +coupling. `mod worker` and `ipc.ts` are key abstraction boundaries which keep Tauri-specific +code in its own glue layers. + +Each window has a worker thread which owns `Session` data. A session can be in multiple states, +including: +- `WorkerSession` - Opening/reopening a workspace +- `WorkspaceSession` - Workspace open, able to execute mutations +- `QuerySession` - Paged query in progress, able to fetch efficiently + +IPC is divided into four categories, which is probably one too many: +- Client->Server **triggers** cause the backend to perform native UI actions. +- Client->Server **queries** request information from the session without affecting state. +- Client->Server **mutations** modify session state in a structured fashion. +- Server->Client and Client->Client **events** are broadcast to push information to the UI. + +Drag & drop capabilities are implemented by `objects/Object.svelte`, a draggable item, and +`objects/Zone.svelte`, a droppable region. Policy is centralised in `mutators/BinaryMutator.ts`. + +Branch Objects +-------------- +The representation of branches, in JJ and GG, is a bit complicated; there are multiple state axes. +A repository can have zero or more **remotes**. +A **local branch** can track zero or more of the remotes. (Technically, remote *branches*.) +A **remote branch** can be any of *tracked* (a flag on the ref), *synced* (if it points to the same +commit as a local branch of the same name), and *absent* (if there's a local branch with *no* ref, +in which case it will be deleted by the CLI on push. + +GG attempts to simplify the display of branches by combining refs in the UI. Taking advantage of +Jujutsu's model, which guarantees that a branch name identifies the same branch across remotes, a +local branch and the tracked remote branches with which it is currently synced are be combined into +a single UI object. Remote branches are displayed separately if they're unsynced, untracked or absent. + +Consequently, the commands available for a branch as displayed in the UI have polymorphic effect: +1) "Track": Applies to any remote branch that is not already tracked. +2) "Untrack": + - For a *tracking local/combined branch*, untracks all remotes. + - For an *unsynced remote branch*, untracks one remote. +3) "Push": Applies to local branches tracking any remotes. +4) "Push to remote...": Applies to local branches when any remotes exist. +5) "Fetch": Downloads for a specific branch only. + - For a *tracking local/combined branch*, fetches from all remotes. + - For a *remote branch*, fetches from its remote. +6) "Fetch from remote...": Applies to local branches when any trackable remotes exist. +7) "Rename...": Renames a local branch, without affecting remote branches. + - For a *nontracking local branch*, just renames. + - For a *tracking/combined branch*, untracks first. +8) "Delete": Applies to a user-visible object, not combined objects. + - For a *local/combined branch*, deletes the local ref. + - For a *remote branch*, forgets the remote ref (which also clears pending deletes.) + +Multiple-dispatch commands: +1) "Move": Drop local branch onto revision. Sets the ref to a commit, potentially de- or re-syncing it. +2) "Track": Drop remote branch onto local of the same name. +3) "Delete": Drag almost any branch out, with polymorphic effect (see above). + +Displaying the branch state is a bit fuzzy. The idea is to convey the most useful bits of information at +a glance, and leave the rest to tooltips or context menus. Most branches display in the +"modify" state; "add" and "remove" are used only for *unsynced* branches, with unsynced locals being "add" +and unsynced or absent remotes "remove". + +This is vaguely analogous to the more straightforward use of modify/add/remove for file changes, adapted to +the fact that many branch states are "normal"; the mental shorthand is that add/green means that pushing will +cause a remote to set this ref, and remove/red means the remote will no longer contain this ref (at this pointer). + +Additionally, a dashed border (like the dashed lines used for elided commits) has a special meaning, also +fuzzy: this ref is "disconnected", either local-only or remote-only. Disconnected local branches are ones +which have no remotes (in a repo that does have remotes); disconnected remote branches are ones which will be deleted on push (with an absent local ref). \ No newline at end of file diff --git a/TODO.md b/TODO.md index f5a5745..9bab5f9 100644 --- a/TODO.md +++ b/TODO.md @@ -1,35 +1,35 @@ -Known Issues ------------- -* "Open..." menu command sometimes opens multiple dialogues. -* Mutations can fail due to ambiguity when there are other writers; this should update the UI. Maybe a special From impl for resolve_change. -* Windows codesigning will break in August 2024; the CI needs a new approach. -* On Webkit (macos and linux), horizontal scrollbars in diffs are too tall. -* Visual issues on Xubuntu 22.04: - - menu leaves a white background when there's no repo loaded - no xdamage maybe? - - there's a weird bullet (looks like an uncoloured rev icon) in the sig area - - fonts are kind of awful - -Planned Features ----------------- -> The best laid schemes o' mice an' men / Gang aft a-gley. - -* Hunk selection/operations. Maybe a change/hunk menu. -* Alternate drag modes for copy/duplicate, perhaps rebase-all-descendants. -* Optimise revdetail loads - we already have the header available. -* Multiselection, viewing and operating on revsets or changesets. -* Undo/redo stack, possibly with a menu of recent ops. -* Some way to access the resolve (mergetool) workflow. Difftools too, although this is less useful. -* More stuff in the log - timestamps, commit ids... this might have to be configurable. -* Progress bar, particularly for git and snapshot operations. -* Structured op descriptions - extracted ids etc, maybe via tags. This would benefit from being in JJ core. -* "Onboarding" features - init/clone/colocate. -* Relative timestamps should update on refocus. - -UI Expansion ------------- -With some dynamic way to show extra panes, replace content, open new windows &c, more useful features would be possible: - -* View the repo at past ops. -* View a revision at past evolutions (possibly this could be folded into the log). -* Config UI, both for core stuff and gg's own settings. -* Revision pinning for split/comparison workflows. +Known Issues +------------ +* "Open..." menu command sometimes opens multiple dialogues. +* Mutations can fail due to ambiguity when there are other writers; this should update the UI. Maybe a special From impl for resolve_change. +* Windows codesigning will break in August 2024; the CI needs a new approach. +* On Webkit (macos and linux), horizontal scrollbars in diffs are too tall. +* Visual issues on Xubuntu 22.04: + - menu leaves a white background when there's no repo loaded - no xdamage maybe? + - there's a weird bullet (looks like an uncoloured rev icon) in the sig area + - fonts are kind of awful + +Planned Features +---------------- +> The best laid schemes o' mice an' men / Gang aft a-gley. + +* Hunk selection/operations. Maybe a change/hunk menu. +* Alternate drag modes for copy/duplicate, perhaps rebase-all-descendants. +* Optimise revdetail loads - we already have the header available. +* Multiselection, viewing and operating on revsets or changesets. +* Undo/redo stack, possibly with a menu of recent ops. +* Some way to access the resolve (mergetool) workflow. Difftools too, although this is less useful. +* More stuff in the log - timestamps, commit ids... this might have to be configurable. +* Progress bar, particularly for git and snapshot operations. +* Structured op descriptions - extracted ids etc, maybe via tags. This would benefit from being in JJ core. +* "Onboarding" features - init/clone/colocate. +* Relative timestamps should update on refocus. + +UI Expansion +------------ +With some dynamic way to show extra panes, replace content, open new windows &c, more useful features would be possible: + +* View the repo at past ops. +* View a revision at past evolutions (possibly this could be folded into the log). +* Config UI, both for core stuff and gg's own settings. +* Revision pinning for split/comparison workflows. diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json index c8439cc..723863d 100644 --- a/src-tauri/gen/schemas/acl-manifests.json +++ b/src-tauri/gen/schemas/acl-manifests.json @@ -1 +1,3287 @@ -{"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-ask","allow-confirm","allow-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]}},"allow-message":{"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"deny-ask":{"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]}},"deny-message":{"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}}},"permission_sets":{},"global_scope_schema":null},"log":{"default_permission":{"identifier":"default","description":"Allows the log command","permissions":["allow-log"]},"permissions":{"allow-log":{"identifier":"allow-log","description":"Enables the log command without any pre-configured scope.","commands":{"allow":["log"],"deny":[]}},"deny-log":{"identifier":"deny-log","description":"Denies the log command without any pre-configured scope.","commands":{"allow":[],"deny":["log"]}}},"permission_sets":{},"global_scope_schema":null},"shell":{"default_permission":{"identifier":"default","description":"This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n","permissions":["allow-open"]},"permissions":{"allow-execute":{"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]}},"allow-kill":{"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-spawn":{"identifier":"allow-spawn","description":"Enables the spawn command without any pre-configured scope.","commands":{"allow":["spawn"],"deny":[]}},"allow-stdin-write":{"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]}},"deny-execute":{"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]}},"deny-kill":{"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-spawn":{"identifier":"deny-spawn","description":"Denies the spawn command without any pre-configured scope.","commands":{"allow":[],"deny":["spawn"]}},"deny-stdin-write":{"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","definitions":{"ShellAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"raw":{"default":false,"description":"Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.","type":"boolean"},"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"A command allowed to be executed by the webview API.","properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellAllowedArgs"}],"description":"The allowed arguments for the command execution."},"cmd":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["args","cmd","name","sidecar"],"title":"Entry","type":"object"}},"window-state":{"default_permission":{"identifier":"default","description":"This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n","permissions":["allow-filename","allow-restore-state","allow-save-window-state"]},"permissions":{"allow-filename":{"identifier":"allow-filename","description":"Enables the filename command without any pre-configured scope.","commands":{"allow":["filename"],"deny":[]}},"allow-restore-state":{"identifier":"allow-restore-state","description":"Enables the restore_state command without any pre-configured scope.","commands":{"allow":["restore_state"],"deny":[]}},"allow-save-window-state":{"identifier":"allow-save-window-state","description":"Enables the save_window_state command without any pre-configured scope.","commands":{"allow":["save_window_state"],"deny":[]}},"deny-filename":{"identifier":"deny-filename","description":"Denies the filename command without any pre-configured scope.","commands":{"allow":[],"deny":["filename"]}},"deny-restore-state":{"identifier":"deny-restore-state","description":"Denies the restore_state command without any pre-configured scope.","commands":{"allow":[],"deny":["restore_state"]}},"deny-save-window-state":{"identifier":"deny-save-window-state","description":"Denies the save_window_state command without any pre-configured scope.","commands":{"allow":[],"deny":["save_window_state"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file +{ + "core:app": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-version", + "allow-name", + "allow-tauri-version" + ] + }, + "permissions": { + "allow-app-hide": { + "identifier": "allow-app-hide", + "description": "Enables the app_hide command without any pre-configured scope.", + "commands": { + "allow": [ + "app_hide" + ], + "deny": [] + } + }, + "allow-app-show": { + "identifier": "allow-app-show", + "description": "Enables the app_show command without any pre-configured scope.", + "commands": { + "allow": [ + "app_show" + ], + "deny": [] + } + }, + "allow-default-window-icon": { + "identifier": "allow-default-window-icon", + "description": "Enables the default_window_icon command without any pre-configured scope.", + "commands": { + "allow": [ + "default_window_icon" + ], + "deny": [] + } + }, + "allow-name": { + "identifier": "allow-name", + "description": "Enables the name command without any pre-configured scope.", + "commands": { + "allow": [ + "name" + ], + "deny": [] + } + }, + "allow-tauri-version": { + "identifier": "allow-tauri-version", + "description": "Enables the tauri_version command without any pre-configured scope.", + "commands": { + "allow": [ + "tauri_version" + ], + "deny": [] + } + }, + "allow-version": { + "identifier": "allow-version", + "description": "Enables the version command without any pre-configured scope.", + "commands": { + "allow": [ + "version" + ], + "deny": [] + } + }, + "deny-app-hide": { + "identifier": "deny-app-hide", + "description": "Denies the app_hide command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "app_hide" + ] + } + }, + "deny-app-show": { + "identifier": "deny-app-show", + "description": "Denies the app_show command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "app_show" + ] + } + }, + "deny-default-window-icon": { + "identifier": "deny-default-window-icon", + "description": "Denies the default_window_icon command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "default_window_icon" + ] + } + }, + "deny-name": { + "identifier": "deny-name", + "description": "Denies the name command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "name" + ] + } + }, + "deny-tauri-version": { + "identifier": "deny-tauri-version", + "description": "Denies the tauri_version command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "tauri_version" + ] + } + }, + "deny-version": { + "identifier": "deny-version", + "description": "Denies the version command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "version" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "core:event": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-listen", + "allow-unlisten", + "allow-emit", + "allow-emit-to" + ] + }, + "permissions": { + "allow-emit": { + "identifier": "allow-emit", + "description": "Enables the emit command without any pre-configured scope.", + "commands": { + "allow": [ + "emit" + ], + "deny": [] + } + }, + "allow-emit-to": { + "identifier": "allow-emit-to", + "description": "Enables the emit_to command without any pre-configured scope.", + "commands": { + "allow": [ + "emit_to" + ], + "deny": [] + } + }, + "allow-listen": { + "identifier": "allow-listen", + "description": "Enables the listen command without any pre-configured scope.", + "commands": { + "allow": [ + "listen" + ], + "deny": [] + } + }, + "allow-unlisten": { + "identifier": "allow-unlisten", + "description": "Enables the unlisten command without any pre-configured scope.", + "commands": { + "allow": [ + "unlisten" + ], + "deny": [] + } + }, + "deny-emit": { + "identifier": "deny-emit", + "description": "Denies the emit command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "emit" + ] + } + }, + "deny-emit-to": { + "identifier": "deny-emit-to", + "description": "Denies the emit_to command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "emit_to" + ] + } + }, + "deny-listen": { + "identifier": "deny-listen", + "description": "Denies the listen command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "listen" + ] + } + }, + "deny-unlisten": { + "identifier": "deny-unlisten", + "description": "Denies the unlisten command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "unlisten" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "core:image": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-new", + "allow-from-bytes", + "allow-from-path", + "allow-rgba", + "allow-size" + ] + }, + "permissions": { + "allow-from-bytes": { + "identifier": "allow-from-bytes", + "description": "Enables the from_bytes command without any pre-configured scope.", + "commands": { + "allow": [ + "from_bytes" + ], + "deny": [] + } + }, + "allow-from-path": { + "identifier": "allow-from-path", + "description": "Enables the from_path command without any pre-configured scope.", + "commands": { + "allow": [ + "from_path" + ], + "deny": [] + } + }, + "allow-new": { + "identifier": "allow-new", + "description": "Enables the new command without any pre-configured scope.", + "commands": { + "allow": [ + "new" + ], + "deny": [] + } + }, + "allow-rgba": { + "identifier": "allow-rgba", + "description": "Enables the rgba command without any pre-configured scope.", + "commands": { + "allow": [ + "rgba" + ], + "deny": [] + } + }, + "allow-size": { + "identifier": "allow-size", + "description": "Enables the size command without any pre-configured scope.", + "commands": { + "allow": [ + "size" + ], + "deny": [] + } + }, + "deny-from-bytes": { + "identifier": "deny-from-bytes", + "description": "Denies the from_bytes command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "from_bytes" + ] + } + }, + "deny-from-path": { + "identifier": "deny-from-path", + "description": "Denies the from_path command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "from_path" + ] + } + }, + "deny-new": { + "identifier": "deny-new", + "description": "Denies the new command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "new" + ] + } + }, + "deny-rgba": { + "identifier": "deny-rgba", + "description": "Denies the rgba command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "rgba" + ] + } + }, + "deny-size": { + "identifier": "deny-size", + "description": "Denies the size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "size" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "core:menu": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-new", + "allow-append", + "allow-prepend", + "allow-insert", + "allow-remove", + "allow-remove-at", + "allow-items", + "allow-get", + "allow-popup", + "allow-create-default", + "allow-set-as-app-menu", + "allow-set-as-window-menu", + "allow-text", + "allow-set-text", + "allow-is-enabled", + "allow-set-enabled", + "allow-set-accelerator", + "allow-set-as-windows-menu-for-nsapp", + "allow-set-as-help-menu-for-nsapp", + "allow-is-checked", + "allow-set-checked", + "allow-set-icon" + ] + }, + "permissions": { + "allow-append": { + "identifier": "allow-append", + "description": "Enables the append command without any pre-configured scope.", + "commands": { + "allow": [ + "append" + ], + "deny": [] + } + }, + "allow-create-default": { + "identifier": "allow-create-default", + "description": "Enables the create_default command without any pre-configured scope.", + "commands": { + "allow": [ + "create_default" + ], + "deny": [] + } + }, + "allow-get": { + "identifier": "allow-get", + "description": "Enables the get command without any pre-configured scope.", + "commands": { + "allow": [ + "get" + ], + "deny": [] + } + }, + "allow-insert": { + "identifier": "allow-insert", + "description": "Enables the insert command without any pre-configured scope.", + "commands": { + "allow": [ + "insert" + ], + "deny": [] + } + }, + "allow-is-checked": { + "identifier": "allow-is-checked", + "description": "Enables the is_checked command without any pre-configured scope.", + "commands": { + "allow": [ + "is_checked" + ], + "deny": [] + } + }, + "allow-is-enabled": { + "identifier": "allow-is-enabled", + "description": "Enables the is_enabled command without any pre-configured scope.", + "commands": { + "allow": [ + "is_enabled" + ], + "deny": [] + } + }, + "allow-items": { + "identifier": "allow-items", + "description": "Enables the items command without any pre-configured scope.", + "commands": { + "allow": [ + "items" + ], + "deny": [] + } + }, + "allow-new": { + "identifier": "allow-new", + "description": "Enables the new command without any pre-configured scope.", + "commands": { + "allow": [ + "new" + ], + "deny": [] + } + }, + "allow-popup": { + "identifier": "allow-popup", + "description": "Enables the popup command without any pre-configured scope.", + "commands": { + "allow": [ + "popup" + ], + "deny": [] + } + }, + "allow-prepend": { + "identifier": "allow-prepend", + "description": "Enables the prepend command without any pre-configured scope.", + "commands": { + "allow": [ + "prepend" + ], + "deny": [] + } + }, + "allow-remove": { + "identifier": "allow-remove", + "description": "Enables the remove command without any pre-configured scope.", + "commands": { + "allow": [ + "remove" + ], + "deny": [] + } + }, + "allow-remove-at": { + "identifier": "allow-remove-at", + "description": "Enables the remove_at command without any pre-configured scope.", + "commands": { + "allow": [ + "remove_at" + ], + "deny": [] + } + }, + "allow-set-accelerator": { + "identifier": "allow-set-accelerator", + "description": "Enables the set_accelerator command without any pre-configured scope.", + "commands": { + "allow": [ + "set_accelerator" + ], + "deny": [] + } + }, + "allow-set-as-app-menu": { + "identifier": "allow-set-as-app-menu", + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "commands": { + "allow": [ + "set_as_app_menu" + ], + "deny": [] + } + }, + "allow-set-as-help-menu-for-nsapp": { + "identifier": "allow-set-as-help-menu-for-nsapp", + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "commands": { + "allow": [ + "set_as_help_menu_for_nsapp" + ], + "deny": [] + } + }, + "allow-set-as-window-menu": { + "identifier": "allow-set-as-window-menu", + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "commands": { + "allow": [ + "set_as_window_menu" + ], + "deny": [] + } + }, + "allow-set-as-windows-menu-for-nsapp": { + "identifier": "allow-set-as-windows-menu-for-nsapp", + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "commands": { + "allow": [ + "set_as_windows_menu_for_nsapp" + ], + "deny": [] + } + }, + "allow-set-checked": { + "identifier": "allow-set-checked", + "description": "Enables the set_checked command without any pre-configured scope.", + "commands": { + "allow": [ + "set_checked" + ], + "deny": [] + } + }, + "allow-set-enabled": { + "identifier": "allow-set-enabled", + "description": "Enables the set_enabled command without any pre-configured scope.", + "commands": { + "allow": [ + "set_enabled" + ], + "deny": [] + } + }, + "allow-set-icon": { + "identifier": "allow-set-icon", + "description": "Enables the set_icon command without any pre-configured scope.", + "commands": { + "allow": [ + "set_icon" + ], + "deny": [] + } + }, + "allow-set-text": { + "identifier": "allow-set-text", + "description": "Enables the set_text command without any pre-configured scope.", + "commands": { + "allow": [ + "set_text" + ], + "deny": [] + } + }, + "allow-text": { + "identifier": "allow-text", + "description": "Enables the text command without any pre-configured scope.", + "commands": { + "allow": [ + "text" + ], + "deny": [] + } + }, + "deny-append": { + "identifier": "deny-append", + "description": "Denies the append command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "append" + ] + } + }, + "deny-create-default": { + "identifier": "deny-create-default", + "description": "Denies the create_default command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "create_default" + ] + } + }, + "deny-get": { + "identifier": "deny-get", + "description": "Denies the get command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "get" + ] + } + }, + "deny-insert": { + "identifier": "deny-insert", + "description": "Denies the insert command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "insert" + ] + } + }, + "deny-is-checked": { + "identifier": "deny-is-checked", + "description": "Denies the is_checked command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_checked" + ] + } + }, + "deny-is-enabled": { + "identifier": "deny-is-enabled", + "description": "Denies the is_enabled command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_enabled" + ] + } + }, + "deny-items": { + "identifier": "deny-items", + "description": "Denies the items command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "items" + ] + } + }, + "deny-new": { + "identifier": "deny-new", + "description": "Denies the new command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "new" + ] + } + }, + "deny-popup": { + "identifier": "deny-popup", + "description": "Denies the popup command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "popup" + ] + } + }, + "deny-prepend": { + "identifier": "deny-prepend", + "description": "Denies the prepend command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "prepend" + ] + } + }, + "deny-remove": { + "identifier": "deny-remove", + "description": "Denies the remove command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "remove" + ] + } + }, + "deny-remove-at": { + "identifier": "deny-remove-at", + "description": "Denies the remove_at command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "remove_at" + ] + } + }, + "deny-set-accelerator": { + "identifier": "deny-set-accelerator", + "description": "Denies the set_accelerator command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_accelerator" + ] + } + }, + "deny-set-as-app-menu": { + "identifier": "deny-set-as-app-menu", + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_as_app_menu" + ] + } + }, + "deny-set-as-help-menu-for-nsapp": { + "identifier": "deny-set-as-help-menu-for-nsapp", + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_as_help_menu_for_nsapp" + ] + } + }, + "deny-set-as-window-menu": { + "identifier": "deny-set-as-window-menu", + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_as_window_menu" + ] + } + }, + "deny-set-as-windows-menu-for-nsapp": { + "identifier": "deny-set-as-windows-menu-for-nsapp", + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_as_windows_menu_for_nsapp" + ] + } + }, + "deny-set-checked": { + "identifier": "deny-set-checked", + "description": "Denies the set_checked command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_checked" + ] + } + }, + "deny-set-enabled": { + "identifier": "deny-set-enabled", + "description": "Denies the set_enabled command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_enabled" + ] + } + }, + "deny-set-icon": { + "identifier": "deny-set-icon", + "description": "Denies the set_icon command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_icon" + ] + } + }, + "deny-set-text": { + "identifier": "deny-set-text", + "description": "Denies the set_text command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_text" + ] + } + }, + "deny-text": { + "identifier": "deny-text", + "description": "Denies the text command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "text" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "core:path": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-resolve-directory", + "allow-resolve", + "allow-normalize", + "allow-join", + "allow-dirname", + "allow-extname", + "allow-basename", + "allow-is-absolute" + ] + }, + "permissions": { + "allow-basename": { + "identifier": "allow-basename", + "description": "Enables the basename command without any pre-configured scope.", + "commands": { + "allow": [ + "basename" + ], + "deny": [] + } + }, + "allow-dirname": { + "identifier": "allow-dirname", + "description": "Enables the dirname command without any pre-configured scope.", + "commands": { + "allow": [ + "dirname" + ], + "deny": [] + } + }, + "allow-extname": { + "identifier": "allow-extname", + "description": "Enables the extname command without any pre-configured scope.", + "commands": { + "allow": [ + "extname" + ], + "deny": [] + } + }, + "allow-is-absolute": { + "identifier": "allow-is-absolute", + "description": "Enables the is_absolute command without any pre-configured scope.", + "commands": { + "allow": [ + "is_absolute" + ], + "deny": [] + } + }, + "allow-join": { + "identifier": "allow-join", + "description": "Enables the join command without any pre-configured scope.", + "commands": { + "allow": [ + "join" + ], + "deny": [] + } + }, + "allow-normalize": { + "identifier": "allow-normalize", + "description": "Enables the normalize command without any pre-configured scope.", + "commands": { + "allow": [ + "normalize" + ], + "deny": [] + } + }, + "allow-resolve": { + "identifier": "allow-resolve", + "description": "Enables the resolve command without any pre-configured scope.", + "commands": { + "allow": [ + "resolve" + ], + "deny": [] + } + }, + "allow-resolve-directory": { + "identifier": "allow-resolve-directory", + "description": "Enables the resolve_directory command without any pre-configured scope.", + "commands": { + "allow": [ + "resolve_directory" + ], + "deny": [] + } + }, + "deny-basename": { + "identifier": "deny-basename", + "description": "Denies the basename command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "basename" + ] + } + }, + "deny-dirname": { + "identifier": "deny-dirname", + "description": "Denies the dirname command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "dirname" + ] + } + }, + "deny-extname": { + "identifier": "deny-extname", + "description": "Denies the extname command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "extname" + ] + } + }, + "deny-is-absolute": { + "identifier": "deny-is-absolute", + "description": "Denies the is_absolute command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_absolute" + ] + } + }, + "deny-join": { + "identifier": "deny-join", + "description": "Denies the join command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "join" + ] + } + }, + "deny-normalize": { + "identifier": "deny-normalize", + "description": "Denies the normalize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "normalize" + ] + } + }, + "deny-resolve": { + "identifier": "deny-resolve", + "description": "Denies the resolve command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "resolve" + ] + } + }, + "deny-resolve-directory": { + "identifier": "deny-resolve-directory", + "description": "Denies the resolve_directory command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "resolve_directory" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "core:resources": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-close" + ] + }, + "permissions": { + "allow-close": { + "identifier": "allow-close", + "description": "Enables the close command without any pre-configured scope.", + "commands": { + "allow": [ + "close" + ], + "deny": [] + } + }, + "deny-close": { + "identifier": "deny-close", + "description": "Denies the close command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "close" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "core:tray": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-new", + "allow-get-by-id", + "allow-remove-by-id", + "allow-set-icon", + "allow-set-menu", + "allow-set-tooltip", + "allow-set-title", + "allow-set-visible", + "allow-set-temp-dir-path", + "allow-set-icon-as-template", + "allow-set-show-menu-on-left-click" + ] + }, + "permissions": { + "allow-get-by-id": { + "identifier": "allow-get-by-id", + "description": "Enables the get_by_id command without any pre-configured scope.", + "commands": { + "allow": [ + "get_by_id" + ], + "deny": [] + } + }, + "allow-new": { + "identifier": "allow-new", + "description": "Enables the new command without any pre-configured scope.", + "commands": { + "allow": [ + "new" + ], + "deny": [] + } + }, + "allow-remove-by-id": { + "identifier": "allow-remove-by-id", + "description": "Enables the remove_by_id command without any pre-configured scope.", + "commands": { + "allow": [ + "remove_by_id" + ], + "deny": [] + } + }, + "allow-set-icon": { + "identifier": "allow-set-icon", + "description": "Enables the set_icon command without any pre-configured scope.", + "commands": { + "allow": [ + "set_icon" + ], + "deny": [] + } + }, + "allow-set-icon-as-template": { + "identifier": "allow-set-icon-as-template", + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "commands": { + "allow": [ + "set_icon_as_template" + ], + "deny": [] + } + }, + "allow-set-menu": { + "identifier": "allow-set-menu", + "description": "Enables the set_menu command without any pre-configured scope.", + "commands": { + "allow": [ + "set_menu" + ], + "deny": [] + } + }, + "allow-set-show-menu-on-left-click": { + "identifier": "allow-set-show-menu-on-left-click", + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "commands": { + "allow": [ + "set_show_menu_on_left_click" + ], + "deny": [] + } + }, + "allow-set-temp-dir-path": { + "identifier": "allow-set-temp-dir-path", + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "commands": { + "allow": [ + "set_temp_dir_path" + ], + "deny": [] + } + }, + "allow-set-title": { + "identifier": "allow-set-title", + "description": "Enables the set_title command without any pre-configured scope.", + "commands": { + "allow": [ + "set_title" + ], + "deny": [] + } + }, + "allow-set-tooltip": { + "identifier": "allow-set-tooltip", + "description": "Enables the set_tooltip command without any pre-configured scope.", + "commands": { + "allow": [ + "set_tooltip" + ], + "deny": [] + } + }, + "allow-set-visible": { + "identifier": "allow-set-visible", + "description": "Enables the set_visible command without any pre-configured scope.", + "commands": { + "allow": [ + "set_visible" + ], + "deny": [] + } + }, + "deny-get-by-id": { + "identifier": "deny-get-by-id", + "description": "Denies the get_by_id command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "get_by_id" + ] + } + }, + "deny-new": { + "identifier": "deny-new", + "description": "Denies the new command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "new" + ] + } + }, + "deny-remove-by-id": { + "identifier": "deny-remove-by-id", + "description": "Denies the remove_by_id command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "remove_by_id" + ] + } + }, + "deny-set-icon": { + "identifier": "deny-set-icon", + "description": "Denies the set_icon command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_icon" + ] + } + }, + "deny-set-icon-as-template": { + "identifier": "deny-set-icon-as-template", + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_icon_as_template" + ] + } + }, + "deny-set-menu": { + "identifier": "deny-set-menu", + "description": "Denies the set_menu command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_menu" + ] + } + }, + "deny-set-show-menu-on-left-click": { + "identifier": "deny-set-show-menu-on-left-click", + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_show_menu_on_left_click" + ] + } + }, + "deny-set-temp-dir-path": { + "identifier": "deny-set-temp-dir-path", + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_temp_dir_path" + ] + } + }, + "deny-set-title": { + "identifier": "deny-set-title", + "description": "Denies the set_title command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_title" + ] + } + }, + "deny-set-tooltip": { + "identifier": "deny-set-tooltip", + "description": "Denies the set_tooltip command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_tooltip" + ] + } + }, + "deny-set-visible": { + "identifier": "deny-set-visible", + "description": "Denies the set_visible command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_visible" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "core:webview": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-webview-position", + "allow-webview-size", + "allow-internal-toggle-devtools" + ] + }, + "permissions": { + "allow-create-webview": { + "identifier": "allow-create-webview", + "description": "Enables the create_webview command without any pre-configured scope.", + "commands": { + "allow": [ + "create_webview" + ], + "deny": [] + } + }, + "allow-create-webview-window": { + "identifier": "allow-create-webview-window", + "description": "Enables the create_webview_window command without any pre-configured scope.", + "commands": { + "allow": [ + "create_webview_window" + ], + "deny": [] + } + }, + "allow-internal-toggle-devtools": { + "identifier": "allow-internal-toggle-devtools", + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "commands": { + "allow": [ + "internal_toggle_devtools" + ], + "deny": [] + } + }, + "allow-print": { + "identifier": "allow-print", + "description": "Enables the print command without any pre-configured scope.", + "commands": { + "allow": [ + "print" + ], + "deny": [] + } + }, + "allow-reparent": { + "identifier": "allow-reparent", + "description": "Enables the reparent command without any pre-configured scope.", + "commands": { + "allow": [ + "reparent" + ], + "deny": [] + } + }, + "allow-set-webview-focus": { + "identifier": "allow-set-webview-focus", + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "commands": { + "allow": [ + "set_webview_focus" + ], + "deny": [] + } + }, + "allow-set-webview-position": { + "identifier": "allow-set-webview-position", + "description": "Enables the set_webview_position command without any pre-configured scope.", + "commands": { + "allow": [ + "set_webview_position" + ], + "deny": [] + } + }, + "allow-set-webview-size": { + "identifier": "allow-set-webview-size", + "description": "Enables the set_webview_size command without any pre-configured scope.", + "commands": { + "allow": [ + "set_webview_size" + ], + "deny": [] + } + }, + "allow-set-webview-zoom": { + "identifier": "allow-set-webview-zoom", + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "commands": { + "allow": [ + "set_webview_zoom" + ], + "deny": [] + } + }, + "allow-webview-close": { + "identifier": "allow-webview-close", + "description": "Enables the webview_close command without any pre-configured scope.", + "commands": { + "allow": [ + "webview_close" + ], + "deny": [] + } + }, + "allow-webview-position": { + "identifier": "allow-webview-position", + "description": "Enables the webview_position command without any pre-configured scope.", + "commands": { + "allow": [ + "webview_position" + ], + "deny": [] + } + }, + "allow-webview-size": { + "identifier": "allow-webview-size", + "description": "Enables the webview_size command without any pre-configured scope.", + "commands": { + "allow": [ + "webview_size" + ], + "deny": [] + } + }, + "deny-create-webview": { + "identifier": "deny-create-webview", + "description": "Denies the create_webview command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "create_webview" + ] + } + }, + "deny-create-webview-window": { + "identifier": "deny-create-webview-window", + "description": "Denies the create_webview_window command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "create_webview_window" + ] + } + }, + "deny-internal-toggle-devtools": { + "identifier": "deny-internal-toggle-devtools", + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "internal_toggle_devtools" + ] + } + }, + "deny-print": { + "identifier": "deny-print", + "description": "Denies the print command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "print" + ] + } + }, + "deny-reparent": { + "identifier": "deny-reparent", + "description": "Denies the reparent command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "reparent" + ] + } + }, + "deny-set-webview-focus": { + "identifier": "deny-set-webview-focus", + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_webview_focus" + ] + } + }, + "deny-set-webview-position": { + "identifier": "deny-set-webview-position", + "description": "Denies the set_webview_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_webview_position" + ] + } + }, + "deny-set-webview-size": { + "identifier": "deny-set-webview-size", + "description": "Denies the set_webview_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_webview_size" + ] + } + }, + "deny-set-webview-zoom": { + "identifier": "deny-set-webview-zoom", + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_webview_zoom" + ] + } + }, + "deny-webview-close": { + "identifier": "deny-webview-close", + "description": "Denies the webview_close command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "webview_close" + ] + } + }, + "deny-webview-position": { + "identifier": "deny-webview-position", + "description": "Denies the webview_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "webview_position" + ] + } + }, + "deny-webview-size": { + "identifier": "deny-webview-size", + "description": "Denies the webview_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "webview_size" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "core:window": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-scale-factor", + "allow-inner-position", + "allow-outer-position", + "allow-inner-size", + "allow-outer-size", + "allow-is-fullscreen", + "allow-is-minimized", + "allow-is-maximized", + "allow-is-focused", + "allow-is-decorated", + "allow-is-resizable", + "allow-is-maximizable", + "allow-is-minimizable", + "allow-is-closable", + "allow-is-visible", + "allow-title", + "allow-current-monitor", + "allow-primary-monitor", + "allow-monitor-from-point", + "allow-available-monitors", + "allow-cursor-position", + "allow-theme", + "allow-internal-toggle-maximize" + ] + }, + "permissions": { + "allow-available-monitors": { + "identifier": "allow-available-monitors", + "description": "Enables the available_monitors command without any pre-configured scope.", + "commands": { + "allow": [ + "available_monitors" + ], + "deny": [] + } + }, + "allow-center": { + "identifier": "allow-center", + "description": "Enables the center command without any pre-configured scope.", + "commands": { + "allow": [ + "center" + ], + "deny": [] + } + }, + "allow-close": { + "identifier": "allow-close", + "description": "Enables the close command without any pre-configured scope.", + "commands": { + "allow": [ + "close" + ], + "deny": [] + } + }, + "allow-create": { + "identifier": "allow-create", + "description": "Enables the create command without any pre-configured scope.", + "commands": { + "allow": [ + "create" + ], + "deny": [] + } + }, + "allow-current-monitor": { + "identifier": "allow-current-monitor", + "description": "Enables the current_monitor command without any pre-configured scope.", + "commands": { + "allow": [ + "current_monitor" + ], + "deny": [] + } + }, + "allow-cursor-position": { + "identifier": "allow-cursor-position", + "description": "Enables the cursor_position command without any pre-configured scope.", + "commands": { + "allow": [ + "cursor_position" + ], + "deny": [] + } + }, + "allow-destroy": { + "identifier": "allow-destroy", + "description": "Enables the destroy command without any pre-configured scope.", + "commands": { + "allow": [ + "destroy" + ], + "deny": [] + } + }, + "allow-hide": { + "identifier": "allow-hide", + "description": "Enables the hide command without any pre-configured scope.", + "commands": { + "allow": [ + "hide" + ], + "deny": [] + } + }, + "allow-inner-position": { + "identifier": "allow-inner-position", + "description": "Enables the inner_position command without any pre-configured scope.", + "commands": { + "allow": [ + "inner_position" + ], + "deny": [] + } + }, + "allow-inner-size": { + "identifier": "allow-inner-size", + "description": "Enables the inner_size command without any pre-configured scope.", + "commands": { + "allow": [ + "inner_size" + ], + "deny": [] + } + }, + "allow-internal-toggle-maximize": { + "identifier": "allow-internal-toggle-maximize", + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "commands": { + "allow": [ + "internal_toggle_maximize" + ], + "deny": [] + } + }, + "allow-is-closable": { + "identifier": "allow-is-closable", + "description": "Enables the is_closable command without any pre-configured scope.", + "commands": { + "allow": [ + "is_closable" + ], + "deny": [] + } + }, + "allow-is-decorated": { + "identifier": "allow-is-decorated", + "description": "Enables the is_decorated command without any pre-configured scope.", + "commands": { + "allow": [ + "is_decorated" + ], + "deny": [] + } + }, + "allow-is-focused": { + "identifier": "allow-is-focused", + "description": "Enables the is_focused command without any pre-configured scope.", + "commands": { + "allow": [ + "is_focused" + ], + "deny": [] + } + }, + "allow-is-fullscreen": { + "identifier": "allow-is-fullscreen", + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "commands": { + "allow": [ + "is_fullscreen" + ], + "deny": [] + } + }, + "allow-is-maximizable": { + "identifier": "allow-is-maximizable", + "description": "Enables the is_maximizable command without any pre-configured scope.", + "commands": { + "allow": [ + "is_maximizable" + ], + "deny": [] + } + }, + "allow-is-maximized": { + "identifier": "allow-is-maximized", + "description": "Enables the is_maximized command without any pre-configured scope.", + "commands": { + "allow": [ + "is_maximized" + ], + "deny": [] + } + }, + "allow-is-minimizable": { + "identifier": "allow-is-minimizable", + "description": "Enables the is_minimizable command without any pre-configured scope.", + "commands": { + "allow": [ + "is_minimizable" + ], + "deny": [] + } + }, + "allow-is-minimized": { + "identifier": "allow-is-minimized", + "description": "Enables the is_minimized command without any pre-configured scope.", + "commands": { + "allow": [ + "is_minimized" + ], + "deny": [] + } + }, + "allow-is-resizable": { + "identifier": "allow-is-resizable", + "description": "Enables the is_resizable command without any pre-configured scope.", + "commands": { + "allow": [ + "is_resizable" + ], + "deny": [] + } + }, + "allow-is-visible": { + "identifier": "allow-is-visible", + "description": "Enables the is_visible command without any pre-configured scope.", + "commands": { + "allow": [ + "is_visible" + ], + "deny": [] + } + }, + "allow-maximize": { + "identifier": "allow-maximize", + "description": "Enables the maximize command without any pre-configured scope.", + "commands": { + "allow": [ + "maximize" + ], + "deny": [] + } + }, + "allow-minimize": { + "identifier": "allow-minimize", + "description": "Enables the minimize command without any pre-configured scope.", + "commands": { + "allow": [ + "minimize" + ], + "deny": [] + } + }, + "allow-monitor-from-point": { + "identifier": "allow-monitor-from-point", + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "commands": { + "allow": [ + "monitor_from_point" + ], + "deny": [] + } + }, + "allow-outer-position": { + "identifier": "allow-outer-position", + "description": "Enables the outer_position command without any pre-configured scope.", + "commands": { + "allow": [ + "outer_position" + ], + "deny": [] + } + }, + "allow-outer-size": { + "identifier": "allow-outer-size", + "description": "Enables the outer_size command without any pre-configured scope.", + "commands": { + "allow": [ + "outer_size" + ], + "deny": [] + } + }, + "allow-primary-monitor": { + "identifier": "allow-primary-monitor", + "description": "Enables the primary_monitor command without any pre-configured scope.", + "commands": { + "allow": [ + "primary_monitor" + ], + "deny": [] + } + }, + "allow-request-user-attention": { + "identifier": "allow-request-user-attention", + "description": "Enables the request_user_attention command without any pre-configured scope.", + "commands": { + "allow": [ + "request_user_attention" + ], + "deny": [] + } + }, + "allow-scale-factor": { + "identifier": "allow-scale-factor", + "description": "Enables the scale_factor command without any pre-configured scope.", + "commands": { + "allow": [ + "scale_factor" + ], + "deny": [] + } + }, + "allow-set-always-on-bottom": { + "identifier": "allow-set-always-on-bottom", + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "commands": { + "allow": [ + "set_always_on_bottom" + ], + "deny": [] + } + }, + "allow-set-always-on-top": { + "identifier": "allow-set-always-on-top", + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "commands": { + "allow": [ + "set_always_on_top" + ], + "deny": [] + } + }, + "allow-set-closable": { + "identifier": "allow-set-closable", + "description": "Enables the set_closable command without any pre-configured scope.", + "commands": { + "allow": [ + "set_closable" + ], + "deny": [] + } + }, + "allow-set-content-protected": { + "identifier": "allow-set-content-protected", + "description": "Enables the set_content_protected command without any pre-configured scope.", + "commands": { + "allow": [ + "set_content_protected" + ], + "deny": [] + } + }, + "allow-set-cursor-grab": { + "identifier": "allow-set-cursor-grab", + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "commands": { + "allow": [ + "set_cursor_grab" + ], + "deny": [] + } + }, + "allow-set-cursor-icon": { + "identifier": "allow-set-cursor-icon", + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "commands": { + "allow": [ + "set_cursor_icon" + ], + "deny": [] + } + }, + "allow-set-cursor-position": { + "identifier": "allow-set-cursor-position", + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "commands": { + "allow": [ + "set_cursor_position" + ], + "deny": [] + } + }, + "allow-set-cursor-visible": { + "identifier": "allow-set-cursor-visible", + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "commands": { + "allow": [ + "set_cursor_visible" + ], + "deny": [] + } + }, + "allow-set-decorations": { + "identifier": "allow-set-decorations", + "description": "Enables the set_decorations command without any pre-configured scope.", + "commands": { + "allow": [ + "set_decorations" + ], + "deny": [] + } + }, + "allow-set-effects": { + "identifier": "allow-set-effects", + "description": "Enables the set_effects command without any pre-configured scope.", + "commands": { + "allow": [ + "set_effects" + ], + "deny": [] + } + }, + "allow-set-focus": { + "identifier": "allow-set-focus", + "description": "Enables the set_focus command without any pre-configured scope.", + "commands": { + "allow": [ + "set_focus" + ], + "deny": [] + } + }, + "allow-set-fullscreen": { + "identifier": "allow-set-fullscreen", + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "commands": { + "allow": [ + "set_fullscreen" + ], + "deny": [] + } + }, + "allow-set-icon": { + "identifier": "allow-set-icon", + "description": "Enables the set_icon command without any pre-configured scope.", + "commands": { + "allow": [ + "set_icon" + ], + "deny": [] + } + }, + "allow-set-ignore-cursor-events": { + "identifier": "allow-set-ignore-cursor-events", + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "commands": { + "allow": [ + "set_ignore_cursor_events" + ], + "deny": [] + } + }, + "allow-set-max-size": { + "identifier": "allow-set-max-size", + "description": "Enables the set_max_size command without any pre-configured scope.", + "commands": { + "allow": [ + "set_max_size" + ], + "deny": [] + } + }, + "allow-set-maximizable": { + "identifier": "allow-set-maximizable", + "description": "Enables the set_maximizable command without any pre-configured scope.", + "commands": { + "allow": [ + "set_maximizable" + ], + "deny": [] + } + }, + "allow-set-min-size": { + "identifier": "allow-set-min-size", + "description": "Enables the set_min_size command without any pre-configured scope.", + "commands": { + "allow": [ + "set_min_size" + ], + "deny": [] + } + }, + "allow-set-minimizable": { + "identifier": "allow-set-minimizable", + "description": "Enables the set_minimizable command without any pre-configured scope.", + "commands": { + "allow": [ + "set_minimizable" + ], + "deny": [] + } + }, + "allow-set-position": { + "identifier": "allow-set-position", + "description": "Enables the set_position command without any pre-configured scope.", + "commands": { + "allow": [ + "set_position" + ], + "deny": [] + } + }, + "allow-set-progress-bar": { + "identifier": "allow-set-progress-bar", + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "commands": { + "allow": [ + "set_progress_bar" + ], + "deny": [] + } + }, + "allow-set-resizable": { + "identifier": "allow-set-resizable", + "description": "Enables the set_resizable command without any pre-configured scope.", + "commands": { + "allow": [ + "set_resizable" + ], + "deny": [] + } + }, + "allow-set-shadow": { + "identifier": "allow-set-shadow", + "description": "Enables the set_shadow command without any pre-configured scope.", + "commands": { + "allow": [ + "set_shadow" + ], + "deny": [] + } + }, + "allow-set-size": { + "identifier": "allow-set-size", + "description": "Enables the set_size command without any pre-configured scope.", + "commands": { + "allow": [ + "set_size" + ], + "deny": [] + } + }, + "allow-set-size-constraints": { + "identifier": "allow-set-size-constraints", + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "commands": { + "allow": [ + "set_size_constraints" + ], + "deny": [] + } + }, + "allow-set-skip-taskbar": { + "identifier": "allow-set-skip-taskbar", + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "commands": { + "allow": [ + "set_skip_taskbar" + ], + "deny": [] + } + }, + "allow-set-title": { + "identifier": "allow-set-title", + "description": "Enables the set_title command without any pre-configured scope.", + "commands": { + "allow": [ + "set_title" + ], + "deny": [] + } + }, + "allow-set-title-bar-style": { + "identifier": "allow-set-title-bar-style", + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "commands": { + "allow": [ + "set_title_bar_style" + ], + "deny": [] + } + }, + "allow-set-visible-on-all-workspaces": { + "identifier": "allow-set-visible-on-all-workspaces", + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "commands": { + "allow": [ + "set_visible_on_all_workspaces" + ], + "deny": [] + } + }, + "allow-show": { + "identifier": "allow-show", + "description": "Enables the show command without any pre-configured scope.", + "commands": { + "allow": [ + "show" + ], + "deny": [] + } + }, + "allow-start-dragging": { + "identifier": "allow-start-dragging", + "description": "Enables the start_dragging command without any pre-configured scope.", + "commands": { + "allow": [ + "start_dragging" + ], + "deny": [] + } + }, + "allow-start-resize-dragging": { + "identifier": "allow-start-resize-dragging", + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "commands": { + "allow": [ + "start_resize_dragging" + ], + "deny": [] + } + }, + "allow-theme": { + "identifier": "allow-theme", + "description": "Enables the theme command without any pre-configured scope.", + "commands": { + "allow": [ + "theme" + ], + "deny": [] + } + }, + "allow-title": { + "identifier": "allow-title", + "description": "Enables the title command without any pre-configured scope.", + "commands": { + "allow": [ + "title" + ], + "deny": [] + } + }, + "allow-toggle-maximize": { + "identifier": "allow-toggle-maximize", + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "commands": { + "allow": [ + "toggle_maximize" + ], + "deny": [] + } + }, + "allow-unmaximize": { + "identifier": "allow-unmaximize", + "description": "Enables the unmaximize command without any pre-configured scope.", + "commands": { + "allow": [ + "unmaximize" + ], + "deny": [] + } + }, + "allow-unminimize": { + "identifier": "allow-unminimize", + "description": "Enables the unminimize command without any pre-configured scope.", + "commands": { + "allow": [ + "unminimize" + ], + "deny": [] + } + }, + "deny-available-monitors": { + "identifier": "deny-available-monitors", + "description": "Denies the available_monitors command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "available_monitors" + ] + } + }, + "deny-center": { + "identifier": "deny-center", + "description": "Denies the center command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "center" + ] + } + }, + "deny-close": { + "identifier": "deny-close", + "description": "Denies the close command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "close" + ] + } + }, + "deny-create": { + "identifier": "deny-create", + "description": "Denies the create command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "create" + ] + } + }, + "deny-current-monitor": { + "identifier": "deny-current-monitor", + "description": "Denies the current_monitor command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "current_monitor" + ] + } + }, + "deny-cursor-position": { + "identifier": "deny-cursor-position", + "description": "Denies the cursor_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "cursor_position" + ] + } + }, + "deny-destroy": { + "identifier": "deny-destroy", + "description": "Denies the destroy command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "destroy" + ] + } + }, + "deny-hide": { + "identifier": "deny-hide", + "description": "Denies the hide command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "hide" + ] + } + }, + "deny-inner-position": { + "identifier": "deny-inner-position", + "description": "Denies the inner_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "inner_position" + ] + } + }, + "deny-inner-size": { + "identifier": "deny-inner-size", + "description": "Denies the inner_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "inner_size" + ] + } + }, + "deny-internal-toggle-maximize": { + "identifier": "deny-internal-toggle-maximize", + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "internal_toggle_maximize" + ] + } + }, + "deny-is-closable": { + "identifier": "deny-is-closable", + "description": "Denies the is_closable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_closable" + ] + } + }, + "deny-is-decorated": { + "identifier": "deny-is-decorated", + "description": "Denies the is_decorated command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_decorated" + ] + } + }, + "deny-is-focused": { + "identifier": "deny-is-focused", + "description": "Denies the is_focused command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_focused" + ] + } + }, + "deny-is-fullscreen": { + "identifier": "deny-is-fullscreen", + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_fullscreen" + ] + } + }, + "deny-is-maximizable": { + "identifier": "deny-is-maximizable", + "description": "Denies the is_maximizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_maximizable" + ] + } + }, + "deny-is-maximized": { + "identifier": "deny-is-maximized", + "description": "Denies the is_maximized command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_maximized" + ] + } + }, + "deny-is-minimizable": { + "identifier": "deny-is-minimizable", + "description": "Denies the is_minimizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_minimizable" + ] + } + }, + "deny-is-minimized": { + "identifier": "deny-is-minimized", + "description": "Denies the is_minimized command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_minimized" + ] + } + }, + "deny-is-resizable": { + "identifier": "deny-is-resizable", + "description": "Denies the is_resizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_resizable" + ] + } + }, + "deny-is-visible": { + "identifier": "deny-is-visible", + "description": "Denies the is_visible command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_visible" + ] + } + }, + "deny-maximize": { + "identifier": "deny-maximize", + "description": "Denies the maximize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "maximize" + ] + } + }, + "deny-minimize": { + "identifier": "deny-minimize", + "description": "Denies the minimize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "minimize" + ] + } + }, + "deny-monitor-from-point": { + "identifier": "deny-monitor-from-point", + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "monitor_from_point" + ] + } + }, + "deny-outer-position": { + "identifier": "deny-outer-position", + "description": "Denies the outer_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "outer_position" + ] + } + }, + "deny-outer-size": { + "identifier": "deny-outer-size", + "description": "Denies the outer_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "outer_size" + ] + } + }, + "deny-primary-monitor": { + "identifier": "deny-primary-monitor", + "description": "Denies the primary_monitor command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "primary_monitor" + ] + } + }, + "deny-request-user-attention": { + "identifier": "deny-request-user-attention", + "description": "Denies the request_user_attention command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "request_user_attention" + ] + } + }, + "deny-scale-factor": { + "identifier": "deny-scale-factor", + "description": "Denies the scale_factor command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "scale_factor" + ] + } + }, + "deny-set-always-on-bottom": { + "identifier": "deny-set-always-on-bottom", + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_always_on_bottom" + ] + } + }, + "deny-set-always-on-top": { + "identifier": "deny-set-always-on-top", + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_always_on_top" + ] + } + }, + "deny-set-closable": { + "identifier": "deny-set-closable", + "description": "Denies the set_closable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_closable" + ] + } + }, + "deny-set-content-protected": { + "identifier": "deny-set-content-protected", + "description": "Denies the set_content_protected command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_content_protected" + ] + } + }, + "deny-set-cursor-grab": { + "identifier": "deny-set-cursor-grab", + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_cursor_grab" + ] + } + }, + "deny-set-cursor-icon": { + "identifier": "deny-set-cursor-icon", + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_cursor_icon" + ] + } + }, + "deny-set-cursor-position": { + "identifier": "deny-set-cursor-position", + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_cursor_position" + ] + } + }, + "deny-set-cursor-visible": { + "identifier": "deny-set-cursor-visible", + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_cursor_visible" + ] + } + }, + "deny-set-decorations": { + "identifier": "deny-set-decorations", + "description": "Denies the set_decorations command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_decorations" + ] + } + }, + "deny-set-effects": { + "identifier": "deny-set-effects", + "description": "Denies the set_effects command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_effects" + ] + } + }, + "deny-set-focus": { + "identifier": "deny-set-focus", + "description": "Denies the set_focus command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_focus" + ] + } + }, + "deny-set-fullscreen": { + "identifier": "deny-set-fullscreen", + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_fullscreen" + ] + } + }, + "deny-set-icon": { + "identifier": "deny-set-icon", + "description": "Denies the set_icon command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_icon" + ] + } + }, + "deny-set-ignore-cursor-events": { + "identifier": "deny-set-ignore-cursor-events", + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_ignore_cursor_events" + ] + } + }, + "deny-set-max-size": { + "identifier": "deny-set-max-size", + "description": "Denies the set_max_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_max_size" + ] + } + }, + "deny-set-maximizable": { + "identifier": "deny-set-maximizable", + "description": "Denies the set_maximizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_maximizable" + ] + } + }, + "deny-set-min-size": { + "identifier": "deny-set-min-size", + "description": "Denies the set_min_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_min_size" + ] + } + }, + "deny-set-minimizable": { + "identifier": "deny-set-minimizable", + "description": "Denies the set_minimizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_minimizable" + ] + } + }, + "deny-set-position": { + "identifier": "deny-set-position", + "description": "Denies the set_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_position" + ] + } + }, + "deny-set-progress-bar": { + "identifier": "deny-set-progress-bar", + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_progress_bar" + ] + } + }, + "deny-set-resizable": { + "identifier": "deny-set-resizable", + "description": "Denies the set_resizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_resizable" + ] + } + }, + "deny-set-shadow": { + "identifier": "deny-set-shadow", + "description": "Denies the set_shadow command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_shadow" + ] + } + }, + "deny-set-size": { + "identifier": "deny-set-size", + "description": "Denies the set_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_size" + ] + } + }, + "deny-set-size-constraints": { + "identifier": "deny-set-size-constraints", + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_size_constraints" + ] + } + }, + "deny-set-skip-taskbar": { + "identifier": "deny-set-skip-taskbar", + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_skip_taskbar" + ] + } + }, + "deny-set-title": { + "identifier": "deny-set-title", + "description": "Denies the set_title command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_title" + ] + } + }, + "deny-set-title-bar-style": { + "identifier": "deny-set-title-bar-style", + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_title_bar_style" + ] + } + }, + "deny-set-visible-on-all-workspaces": { + "identifier": "deny-set-visible-on-all-workspaces", + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_visible_on_all_workspaces" + ] + } + }, + "deny-show": { + "identifier": "deny-show", + "description": "Denies the show command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "show" + ] + } + }, + "deny-start-dragging": { + "identifier": "deny-start-dragging", + "description": "Denies the start_dragging command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "start_dragging" + ] + } + }, + "deny-start-resize-dragging": { + "identifier": "deny-start-resize-dragging", + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "start_resize_dragging" + ] + } + }, + "deny-theme": { + "identifier": "deny-theme", + "description": "Denies the theme command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "theme" + ] + } + }, + "deny-title": { + "identifier": "deny-title", + "description": "Denies the title command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "title" + ] + } + }, + "deny-toggle-maximize": { + "identifier": "deny-toggle-maximize", + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "toggle_maximize" + ] + } + }, + "deny-unmaximize": { + "identifier": "deny-unmaximize", + "description": "Denies the unmaximize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "unmaximize" + ] + } + }, + "deny-unminimize": { + "identifier": "deny-unminimize", + "description": "Denies the unminimize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "unminimize" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "dialog": { + "default_permission": { + "identifier": "default", + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n", + "permissions": [ + "allow-ask", + "allow-confirm", + "allow-message", + "allow-save", + "allow-open" + ] + }, + "permissions": { + "allow-ask": { + "identifier": "allow-ask", + "description": "Enables the ask command without any pre-configured scope.", + "commands": { + "allow": [ + "ask" + ], + "deny": [] + } + }, + "allow-confirm": { + "identifier": "allow-confirm", + "description": "Enables the confirm command without any pre-configured scope.", + "commands": { + "allow": [ + "confirm" + ], + "deny": [] + } + }, + "allow-message": { + "identifier": "allow-message", + "description": "Enables the message command without any pre-configured scope.", + "commands": { + "allow": [ + "message" + ], + "deny": [] + } + }, + "allow-open": { + "identifier": "allow-open", + "description": "Enables the open command without any pre-configured scope.", + "commands": { + "allow": [ + "open" + ], + "deny": [] + } + }, + "allow-save": { + "identifier": "allow-save", + "description": "Enables the save command without any pre-configured scope.", + "commands": { + "allow": [ + "save" + ], + "deny": [] + } + }, + "deny-ask": { + "identifier": "deny-ask", + "description": "Denies the ask command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "ask" + ] + } + }, + "deny-confirm": { + "identifier": "deny-confirm", + "description": "Denies the confirm command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "confirm" + ] + } + }, + "deny-message": { + "identifier": "deny-message", + "description": "Denies the message command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "message" + ] + } + }, + "deny-open": { + "identifier": "deny-open", + "description": "Denies the open command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "open" + ] + } + }, + "deny-save": { + "identifier": "deny-save", + "description": "Denies the save command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "save" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "log": { + "default_permission": { + "identifier": "default", + "description": "Allows the log command", + "permissions": [ + "allow-log" + ] + }, + "permissions": { + "allow-log": { + "identifier": "allow-log", + "description": "Enables the log command without any pre-configured scope.", + "commands": { + "allow": [ + "log" + ], + "deny": [] + } + }, + "deny-log": { + "identifier": "deny-log", + "description": "Denies the log command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "log" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "shell": { + "default_permission": { + "identifier": "default", + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", + "permissions": [ + "allow-open" + ] + }, + "permissions": { + "allow-execute": { + "identifier": "allow-execute", + "description": "Enables the execute command without any pre-configured scope.", + "commands": { + "allow": [ + "execute" + ], + "deny": [] + } + }, + "allow-kill": { + "identifier": "allow-kill", + "description": "Enables the kill command without any pre-configured scope.", + "commands": { + "allow": [ + "kill" + ], + "deny": [] + } + }, + "allow-open": { + "identifier": "allow-open", + "description": "Enables the open command without any pre-configured scope.", + "commands": { + "allow": [ + "open" + ], + "deny": [] + } + }, + "allow-spawn": { + "identifier": "allow-spawn", + "description": "Enables the spawn command without any pre-configured scope.", + "commands": { + "allow": [ + "spawn" + ], + "deny": [] + } + }, + "allow-stdin-write": { + "identifier": "allow-stdin-write", + "description": "Enables the stdin_write command without any pre-configured scope.", + "commands": { + "allow": [ + "stdin_write" + ], + "deny": [] + } + }, + "deny-execute": { + "identifier": "deny-execute", + "description": "Denies the execute command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "execute" + ] + } + }, + "deny-kill": { + "identifier": "deny-kill", + "description": "Denies the kill command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "kill" + ] + } + }, + "deny-open": { + "identifier": "deny-open", + "description": "Denies the open command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "open" + ] + } + }, + "deny-spawn": { + "identifier": "deny-spawn", + "description": "Denies the spawn command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "spawn" + ] + } + }, + "deny-stdin-write": { + "identifier": "deny-stdin-write", + "description": "Denies the stdin_write command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "stdin_write" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ShellAllowedArg": { + "anyOf": [ + { + "description": "A non-configurable argument that is passed to the command in the order it was specified.", + "type": "string" + }, + { + "additionalProperties": false, + "description": "A variable that is set while calling the command from the webview API.", + "properties": { + "raw": { + "default": false, + "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", + "type": "boolean" + }, + "validator": { + "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", + "type": "string" + } + }, + "required": [ + "validator" + ], + "type": "object" + } + ], + "description": "A command argument allowed to be executed by the webview API." + }, + "ShellAllowedArgs": { + "anyOf": [ + { + "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", + "type": "boolean" + }, + { + "description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.", + "items": { + "$ref": "#/definitions/ShellAllowedArg" + }, + "type": "array" + } + ], + "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration." + } + }, + "description": "A command allowed to be executed by the webview API.", + "properties": { + "args": { + "allOf": [ + { + "$ref": "#/definitions/ShellAllowedArgs" + } + ], + "description": "The allowed arguments for the command execution." + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "required": [ + "args", + "cmd", + "name", + "sidecar" + ], + "title": "Entry", + "type": "object" + } + }, + "window-state": { + "default_permission": { + "identifier": "default", + "description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n", + "permissions": [ + "allow-filename", + "allow-restore-state", + "allow-save-window-state" + ] + }, + "permissions": { + "allow-filename": { + "identifier": "allow-filename", + "description": "Enables the filename command without any pre-configured scope.", + "commands": { + "allow": [ + "filename" + ], + "deny": [] + } + }, + "allow-restore-state": { + "identifier": "allow-restore-state", + "description": "Enables the restore_state command without any pre-configured scope.", + "commands": { + "allow": [ + "restore_state" + ], + "deny": [] + } + }, + "allow-save-window-state": { + "identifier": "allow-save-window-state", + "description": "Enables the save_window_state command without any pre-configured scope.", + "commands": { + "allow": [ + "save_window_state" + ], + "deny": [] + } + }, + "deny-filename": { + "identifier": "deny-filename", + "description": "Denies the filename command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "filename" + ] + } + }, + "deny-restore-state": { + "identifier": "deny-restore-state", + "description": "Denies the restore_state command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "restore_state" + ] + } + }, + "deny-save-window-state": { + "identifier": "deny-save-window-state", + "description": "Denies the save_window_state command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "save_window_state" + ] + } + } + }, + "permission_sets": {}, + "global_scope_schema": null + } +} \ No newline at end of file diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json index e2061df..15661d0 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1,20 @@ -{"main-capability":{"identifier":"main-capability","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","shell:allow-open"]}} \ No newline at end of file +{ + "main-capability": { + "identifier": "main-capability", + "description": "Capability for the main window", + "local": true, + "windows": [ + "main" + ], + "permissions": [ + "core:path:default", + "core:event:default", + "core:window:default", + "core:app:default", + "core:resources:default", + "core:menu:default", + "core:tray:default", + "shell:allow-open" + ] + } +} \ No newline at end of file diff --git a/src-tauri/gen/schemas/plugin-manifests.json b/src-tauri/gen/schemas/plugin-manifests.json index da0b03f..ed37e82 100644 --- a/src-tauri/gen/schemas/plugin-manifests.json +++ b/src-tauri/gen/schemas/plugin-manifests.json @@ -1 +1,3401 @@ -{"app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version"]},"permissions":{"allow-app-hide":{"version":null,"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]},"scope":{}},"allow-app-show":{"version":null,"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]},"scope":{}},"allow-name":{"version":null,"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]},"scope":{}},"allow-tauri-version":{"version":null,"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]},"scope":{}},"allow-version":{"version":null,"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]},"scope":{}},"deny-app-hide":{"version":null,"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]},"scope":{}},"deny-app-show":{"version":null,"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]},"scope":{}},"deny-name":{"version":null,"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]},"scope":{}},"deny-tauri-version":{"version":null,"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]},"scope":{}},"deny-version":{"version":null,"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":null,"permissions":{"allow-ask":{"version":null,"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]},"scope":{}},"allow-confirm":{"version":null,"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]},"scope":{}},"allow-message":{"version":null,"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]},"scope":{}},"allow-open":{"version":null,"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]},"scope":{}},"allow-save":{"version":null,"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]},"scope":{}},"deny-ask":{"version":null,"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]},"scope":{}},"deny-confirm":{"version":null,"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]},"scope":{}},"deny-message":{"version":null,"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]},"scope":{}},"deny-open":{"version":null,"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]},"scope":{}},"deny-save":{"version":null,"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"version":null,"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]},"scope":{}},"allow-emit-to":{"version":null,"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]},"scope":{}},"allow-listen":{"version":null,"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]},"scope":{}},"allow-unlisten":{"version":null,"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]},"scope":{}},"deny-emit":{"version":null,"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]},"scope":{}},"deny-emit-to":{"version":null,"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]},"scope":{}},"deny-listen":{"version":null,"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]},"scope":{}},"deny-unlisten":{"version":null,"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":[]},"permissions":{"allow-append":{"version":null,"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]},"scope":{}},"allow-create-default":{"version":null,"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]},"scope":{}},"allow-get":{"version":null,"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]},"scope":{}},"allow-insert":{"version":null,"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]},"scope":{}},"allow-is-checked":{"version":null,"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]},"scope":{}},"allow-is-enabled":{"version":null,"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]},"scope":{}},"allow-items":{"version":null,"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]},"scope":{}},"allow-new":{"version":null,"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]},"scope":{}},"allow-popup":{"version":null,"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]},"scope":{}},"allow-prepend":{"version":null,"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]},"scope":{}},"allow-remove":{"version":null,"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]},"scope":{}},"allow-remove-at":{"version":null,"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]},"scope":{}},"allow-set-accelerator":{"version":null,"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]},"scope":{}},"allow-set-as-app-menu":{"version":null,"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]},"scope":{}},"allow-set-as-help-menu-for-nsapp":{"version":null,"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]},"scope":{}},"allow-set-as-window-menu":{"version":null,"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]},"scope":{}},"allow-set-as-windows-menu-for-nsapp":{"version":null,"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]},"scope":{}},"allow-set-checked":{"version":null,"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]},"scope":{}},"allow-set-enabled":{"version":null,"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]},"scope":{}},"allow-set-icon":{"version":null,"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]},"scope":{}},"allow-set-text":{"version":null,"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]},"scope":{}},"allow-text":{"version":null,"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]},"scope":{}},"deny-append":{"version":null,"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]},"scope":{}},"deny-create-default":{"version":null,"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]},"scope":{}},"deny-get":{"version":null,"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]},"scope":{}},"deny-insert":{"version":null,"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]},"scope":{}},"deny-is-checked":{"version":null,"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]},"scope":{}},"deny-is-enabled":{"version":null,"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]},"scope":{}},"deny-items":{"version":null,"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]},"scope":{}},"deny-new":{"version":null,"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]},"scope":{}},"deny-popup":{"version":null,"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]},"scope":{}},"deny-prepend":{"version":null,"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]},"scope":{}},"deny-remove":{"version":null,"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]},"scope":{}},"deny-remove-at":{"version":null,"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]},"scope":{}},"deny-set-accelerator":{"version":null,"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]},"scope":{}},"deny-set-as-app-menu":{"version":null,"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]},"scope":{}},"deny-set-as-help-menu-for-nsapp":{"version":null,"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]},"scope":{}},"deny-set-as-window-menu":{"version":null,"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]},"scope":{}},"deny-set-as-windows-menu-for-nsapp":{"version":null,"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]},"scope":{}},"deny-set-checked":{"version":null,"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]},"scope":{}},"deny-set-enabled":{"version":null,"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]},"scope":{}},"deny-set-icon":{"version":null,"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]},"scope":{}},"deny-set-text":{"version":null,"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]},"scope":{}},"deny-text":{"version":null,"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"version":null,"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]},"scope":{}},"allow-dirname":{"version":null,"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]},"scope":{}},"allow-extname":{"version":null,"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]},"scope":{}},"allow-is-absolute":{"version":null,"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]},"scope":{}},"allow-join":{"version":null,"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]},"scope":{}},"allow-normalize":{"version":null,"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]},"scope":{}},"allow-resolve":{"version":null,"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]},"scope":{}},"allow-resolve-directory":{"version":null,"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]},"scope":{}},"deny-basename":{"version":null,"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]},"scope":{}},"deny-dirname":{"version":null,"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]},"scope":{}},"deny-extname":{"version":null,"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]},"scope":{}},"deny-is-absolute":{"version":null,"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]},"scope":{}},"deny-join":{"version":null,"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]},"scope":{}},"deny-normalize":{"version":null,"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]},"scope":{}},"deny-resolve":{"version":null,"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]},"scope":{}},"deny-resolve-directory":{"version":null,"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-close"]},"permissions":{"allow-close":{"version":null,"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]},"scope":{}},"deny-close":{"version":null,"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"shell":{"default_permission":null,"permissions":{"allow-execute":{"version":null,"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]},"scope":{}},"allow-kill":{"version":null,"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]},"scope":{}},"allow-open":{"version":null,"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]},"scope":{}},"allow-stdin-write":{"version":null,"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]},"scope":{}},"deny-execute":{"version":null,"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]},"scope":{}},"deny-kill":{"version":null,"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]},"scope":{}},"deny-open":{"version":null,"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]},"scope":{}},"deny-stdin-write":{"version":null,"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]},"scope":{}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","definitions":{"ShellAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"A command allowed to be executed by the webview API.","properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellAllowedArgs"}],"description":"The allowed arguments for the command execution."},"command":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["args","command","name","sidecar"],"title":"Entry","type":"object"}},"tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":[]},"permissions":{"allow-new":{"version":null,"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]},"scope":{}},"allow-set-icon":{"version":null,"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]},"scope":{}},"allow-set-icon-as-template":{"version":null,"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]},"scope":{}},"allow-set-menu":{"version":null,"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]},"scope":{}},"allow-set-show-menu-on-left-click":{"version":null,"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]},"scope":{}},"allow-set-temp-dir-path":{"version":null,"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]},"scope":{}},"allow-set-title":{"version":null,"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]},"scope":{}},"allow-set-tooltip":{"version":null,"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]},"scope":{}},"allow-set-visible":{"version":null,"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]},"scope":{}},"deny-new":{"version":null,"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]},"scope":{}},"deny-set-icon":{"version":null,"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]},"scope":{}},"deny-set-icon-as-template":{"version":null,"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]},"scope":{}},"deny-set-menu":{"version":null,"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]},"scope":{}},"deny-set-show-menu-on-left-click":{"version":null,"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]},"scope":{}},"deny-set-temp-dir-path":{"version":null,"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]},"scope":{}},"deny-set-title":{"version":null,"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]},"scope":{}},"deny-set-tooltip":{"version":null,"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]},"scope":{}},"deny-set-visible":{"version":null,"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-create-webview":{"version":null,"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]},"scope":{}},"allow-create-webview-window":{"version":null,"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]},"scope":{}},"allow-internal-toggle-devtools":{"version":null,"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]},"scope":{}},"allow-print":{"version":null,"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]},"scope":{}},"allow-set-webview-focus":{"version":null,"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]},"scope":{}},"allow-set-webview-position":{"version":null,"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]},"scope":{}},"allow-set-webview-size":{"version":null,"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]},"scope":{}},"allow-webview-close":{"version":null,"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]},"scope":{}},"allow-webview-position":{"version":null,"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]},"scope":{}},"allow-webview-size":{"version":null,"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]},"scope":{}},"deny-create-webview":{"version":null,"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]},"scope":{}},"deny-create-webview-window":{"version":null,"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]},"scope":{}},"deny-internal-toggle-devtools":{"version":null,"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]},"scope":{}},"deny-print":{"version":null,"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]},"scope":{}},"deny-set-webview-focus":{"version":null,"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]},"scope":{}},"deny-set-webview-position":{"version":null,"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]},"scope":{}},"deny-set-webview-size":{"version":null,"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]},"scope":{}},"deny-webview-close":{"version":null,"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]},"scope":{}},"deny-webview-position":{"version":null,"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]},"scope":{}},"deny-webview-size":{"version":null,"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-title","allow-current-monitor","allow-primary-monitor","allow-available-monitors","allow-theme","allow-internal-toggle-maximize","allow-internal-on-mousemove","allow-internal-on-mousedown"]},"permissions":{"allow-available-monitors":{"version":null,"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]},"scope":{}},"allow-center":{"version":null,"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]},"scope":{}},"allow-close":{"version":null,"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]},"scope":{}},"allow-create":{"version":null,"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]},"scope":{}},"allow-current-monitor":{"version":null,"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]},"scope":{}},"allow-destroy":{"version":null,"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]},"scope":{}},"allow-hide":{"version":null,"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]},"scope":{}},"allow-inner-position":{"version":null,"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]},"scope":{}},"allow-inner-size":{"version":null,"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]},"scope":{}},"allow-internal-on-mousedown":{"version":null,"identifier":"allow-internal-on-mousedown","description":"Enables the internal_on_mousedown command without any pre-configured scope.","commands":{"allow":["internal_on_mousedown"],"deny":[]},"scope":{}},"allow-internal-on-mousemove":{"version":null,"identifier":"allow-internal-on-mousemove","description":"Enables the internal_on_mousemove command without any pre-configured scope.","commands":{"allow":["internal_on_mousemove"],"deny":[]},"scope":{}},"allow-internal-toggle-maximize":{"version":null,"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]},"scope":{}},"allow-is-closable":{"version":null,"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]},"scope":{}},"allow-is-decorated":{"version":null,"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]},"scope":{}},"allow-is-focused":{"version":null,"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]},"scope":{}},"allow-is-fullscreen":{"version":null,"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]},"scope":{}},"allow-is-maximizable":{"version":null,"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]},"scope":{}},"allow-is-maximized":{"version":null,"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]},"scope":{}},"allow-is-minimizable":{"version":null,"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]},"scope":{}},"allow-is-minimized":{"version":null,"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]},"scope":{}},"allow-is-resizable":{"version":null,"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]},"scope":{}},"allow-is-visible":{"version":null,"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]},"scope":{}},"allow-maximize":{"version":null,"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]},"scope":{}},"allow-minimize":{"version":null,"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]},"scope":{}},"allow-outer-position":{"version":null,"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]},"scope":{}},"allow-outer-size":{"version":null,"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]},"scope":{}},"allow-primary-monitor":{"version":null,"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]},"scope":{}},"allow-request-user-attention":{"version":null,"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]},"scope":{}},"allow-scale-factor":{"version":null,"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]},"scope":{}},"allow-set-always-on-bottom":{"version":null,"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]},"scope":{}},"allow-set-always-on-top":{"version":null,"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]},"scope":{}},"allow-set-closable":{"version":null,"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]},"scope":{}},"allow-set-content-protected":{"version":null,"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]},"scope":{}},"allow-set-cursor-grab":{"version":null,"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]},"scope":{}},"allow-set-cursor-icon":{"version":null,"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]},"scope":{}},"allow-set-cursor-position":{"version":null,"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]},"scope":{}},"allow-set-cursor-visible":{"version":null,"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]},"scope":{}},"allow-set-decorations":{"version":null,"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]},"scope":{}},"allow-set-effects":{"version":null,"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]},"scope":{}},"allow-set-focus":{"version":null,"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]},"scope":{}},"allow-set-fullscreen":{"version":null,"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]},"scope":{}},"allow-set-icon":{"version":null,"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]},"scope":{}},"allow-set-ignore-cursor-events":{"version":null,"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]},"scope":{}},"allow-set-max-size":{"version":null,"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]},"scope":{}},"allow-set-maximizable":{"version":null,"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]},"scope":{}},"allow-set-min-size":{"version":null,"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]},"scope":{}},"allow-set-minimizable":{"version":null,"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]},"scope":{}},"allow-set-position":{"version":null,"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]},"scope":{}},"allow-set-progress-bar":{"version":null,"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]},"scope":{}},"allow-set-resizable":{"version":null,"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]},"scope":{}},"allow-set-shadow":{"version":null,"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]},"scope":{}},"allow-set-size":{"version":null,"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]},"scope":{}},"allow-set-skip-taskbar":{"version":null,"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]},"scope":{}},"allow-set-title":{"version":null,"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]},"scope":{}},"allow-set-visible-on-all-workspaces":{"version":null,"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]},"scope":{}},"allow-show":{"version":null,"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]},"scope":{}},"allow-start-dragging":{"version":null,"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]},"scope":{}},"allow-theme":{"version":null,"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]},"scope":{}},"allow-title":{"version":null,"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]},"scope":{}},"allow-toggle-maximize":{"version":null,"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]},"scope":{}},"allow-unmaximize":{"version":null,"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]},"scope":{}},"allow-unminimize":{"version":null,"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]},"scope":{}},"deny-available-monitors":{"version":null,"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]},"scope":{}},"deny-center":{"version":null,"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]},"scope":{}},"deny-close":{"version":null,"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]},"scope":{}},"deny-create":{"version":null,"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]},"scope":{}},"deny-current-monitor":{"version":null,"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]},"scope":{}},"deny-destroy":{"version":null,"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]},"scope":{}},"deny-hide":{"version":null,"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]},"scope":{}},"deny-inner-position":{"version":null,"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]},"scope":{}},"deny-inner-size":{"version":null,"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]},"scope":{}},"deny-internal-on-mousedown":{"version":null,"identifier":"deny-internal-on-mousedown","description":"Denies the internal_on_mousedown command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_on_mousedown"]},"scope":{}},"deny-internal-on-mousemove":{"version":null,"identifier":"deny-internal-on-mousemove","description":"Denies the internal_on_mousemove command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_on_mousemove"]},"scope":{}},"deny-internal-toggle-maximize":{"version":null,"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]},"scope":{}},"deny-is-closable":{"version":null,"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]},"scope":{}},"deny-is-decorated":{"version":null,"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]},"scope":{}},"deny-is-focused":{"version":null,"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]},"scope":{}},"deny-is-fullscreen":{"version":null,"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]},"scope":{}},"deny-is-maximizable":{"version":null,"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]},"scope":{}},"deny-is-maximized":{"version":null,"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]},"scope":{}},"deny-is-minimizable":{"version":null,"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]},"scope":{}},"deny-is-minimized":{"version":null,"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]},"scope":{}},"deny-is-resizable":{"version":null,"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]},"scope":{}},"deny-is-visible":{"version":null,"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]},"scope":{}},"deny-maximize":{"version":null,"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]},"scope":{}},"deny-minimize":{"version":null,"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]},"scope":{}},"deny-outer-position":{"version":null,"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]},"scope":{}},"deny-outer-size":{"version":null,"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]},"scope":{}},"deny-primary-monitor":{"version":null,"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]},"scope":{}},"deny-request-user-attention":{"version":null,"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]},"scope":{}},"deny-scale-factor":{"version":null,"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]},"scope":{}},"deny-set-always-on-bottom":{"version":null,"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]},"scope":{}},"deny-set-always-on-top":{"version":null,"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]},"scope":{}},"deny-set-closable":{"version":null,"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]},"scope":{}},"deny-set-content-protected":{"version":null,"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]},"scope":{}},"deny-set-cursor-grab":{"version":null,"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]},"scope":{}},"deny-set-cursor-icon":{"version":null,"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]},"scope":{}},"deny-set-cursor-position":{"version":null,"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]},"scope":{}},"deny-set-cursor-visible":{"version":null,"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]},"scope":{}},"deny-set-decorations":{"version":null,"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]},"scope":{}},"deny-set-effects":{"version":null,"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]},"scope":{}},"deny-set-focus":{"version":null,"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]},"scope":{}},"deny-set-fullscreen":{"version":null,"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]},"scope":{}},"deny-set-icon":{"version":null,"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]},"scope":{}},"deny-set-ignore-cursor-events":{"version":null,"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]},"scope":{}},"deny-set-max-size":{"version":null,"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]},"scope":{}},"deny-set-maximizable":{"version":null,"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]},"scope":{}},"deny-set-min-size":{"version":null,"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]},"scope":{}},"deny-set-minimizable":{"version":null,"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]},"scope":{}},"deny-set-position":{"version":null,"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]},"scope":{}},"deny-set-progress-bar":{"version":null,"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]},"scope":{}},"deny-set-resizable":{"version":null,"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]},"scope":{}},"deny-set-shadow":{"version":null,"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]},"scope":{}},"deny-set-size":{"version":null,"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]},"scope":{}},"deny-set-skip-taskbar":{"version":null,"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]},"scope":{}},"deny-set-title":{"version":null,"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]},"scope":{}},"deny-set-visible-on-all-workspaces":{"version":null,"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]},"scope":{}},"deny-show":{"version":null,"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]},"scope":{}},"deny-start-dragging":{"version":null,"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]},"scope":{}},"deny-theme":{"version":null,"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]},"scope":{}},"deny-title":{"version":null,"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]},"scope":{}},"deny-toggle-maximize":{"version":null,"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]},"scope":{}},"deny-unmaximize":{"version":null,"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]},"scope":{}},"deny-unminimize":{"version":null,"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"window-state":{"default_permission":null,"permissions":{"allow-restore-window-state":{"version":null,"identifier":"allow-restore-window-state","description":"Enables the restore_window_state command without any pre-configured scope.","commands":{"allow":["restore_window_state"],"deny":[]},"scope":{}},"allow-save-window-state":{"version":null,"identifier":"allow-save-window-state","description":"Enables the save_window_state command without any pre-configured scope.","commands":{"allow":["save_window_state"],"deny":[]},"scope":{}},"deny-restore-window-state":{"version":null,"identifier":"deny-restore-window-state","description":"Denies the restore_window_state command without any pre-configured scope.","commands":{"allow":[],"deny":["restore_window_state"]},"scope":{}},"deny-save-window-state":{"version":null,"identifier":"deny-save-window-state","description":"Denies the save_window_state command without any pre-configured scope.","commands":{"allow":[],"deny":["save_window_state"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file +{ + "app": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-version", + "allow-name", + "allow-tauri-version" + ] + }, + "permissions": { + "allow-app-hide": { + "version": null, + "identifier": "allow-app-hide", + "description": "Enables the app_hide command without any pre-configured scope.", + "commands": { + "allow": [ + "app_hide" + ], + "deny": [] + }, + "scope": {} + }, + "allow-app-show": { + "version": null, + "identifier": "allow-app-show", + "description": "Enables the app_show command without any pre-configured scope.", + "commands": { + "allow": [ + "app_show" + ], + "deny": [] + }, + "scope": {} + }, + "allow-name": { + "version": null, + "identifier": "allow-name", + "description": "Enables the name command without any pre-configured scope.", + "commands": { + "allow": [ + "name" + ], + "deny": [] + }, + "scope": {} + }, + "allow-tauri-version": { + "version": null, + "identifier": "allow-tauri-version", + "description": "Enables the tauri_version command without any pre-configured scope.", + "commands": { + "allow": [ + "tauri_version" + ], + "deny": [] + }, + "scope": {} + }, + "allow-version": { + "version": null, + "identifier": "allow-version", + "description": "Enables the version command without any pre-configured scope.", + "commands": { + "allow": [ + "version" + ], + "deny": [] + }, + "scope": {} + }, + "deny-app-hide": { + "version": null, + "identifier": "deny-app-hide", + "description": "Denies the app_hide command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "app_hide" + ] + }, + "scope": {} + }, + "deny-app-show": { + "version": null, + "identifier": "deny-app-show", + "description": "Denies the app_show command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "app_show" + ] + }, + "scope": {} + }, + "deny-name": { + "version": null, + "identifier": "deny-name", + "description": "Denies the name command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "name" + ] + }, + "scope": {} + }, + "deny-tauri-version": { + "version": null, + "identifier": "deny-tauri-version", + "description": "Denies the tauri_version command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "tauri_version" + ] + }, + "scope": {} + }, + "deny-version": { + "version": null, + "identifier": "deny-version", + "description": "Denies the version command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "version" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "dialog": { + "default_permission": null, + "permissions": { + "allow-ask": { + "version": null, + "identifier": "allow-ask", + "description": "Enables the ask command without any pre-configured scope.", + "commands": { + "allow": [ + "ask" + ], + "deny": [] + }, + "scope": {} + }, + "allow-confirm": { + "version": null, + "identifier": "allow-confirm", + "description": "Enables the confirm command without any pre-configured scope.", + "commands": { + "allow": [ + "confirm" + ], + "deny": [] + }, + "scope": {} + }, + "allow-message": { + "version": null, + "identifier": "allow-message", + "description": "Enables the message command without any pre-configured scope.", + "commands": { + "allow": [ + "message" + ], + "deny": [] + }, + "scope": {} + }, + "allow-open": { + "version": null, + "identifier": "allow-open", + "description": "Enables the open command without any pre-configured scope.", + "commands": { + "allow": [ + "open" + ], + "deny": [] + }, + "scope": {} + }, + "allow-save": { + "version": null, + "identifier": "allow-save", + "description": "Enables the save command without any pre-configured scope.", + "commands": { + "allow": [ + "save" + ], + "deny": [] + }, + "scope": {} + }, + "deny-ask": { + "version": null, + "identifier": "deny-ask", + "description": "Denies the ask command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "ask" + ] + }, + "scope": {} + }, + "deny-confirm": { + "version": null, + "identifier": "deny-confirm", + "description": "Denies the confirm command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "confirm" + ] + }, + "scope": {} + }, + "deny-message": { + "version": null, + "identifier": "deny-message", + "description": "Denies the message command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "message" + ] + }, + "scope": {} + }, + "deny-open": { + "version": null, + "identifier": "deny-open", + "description": "Denies the open command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "open" + ] + }, + "scope": {} + }, + "deny-save": { + "version": null, + "identifier": "deny-save", + "description": "Denies the save command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "save" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "event": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-listen", + "allow-unlisten", + "allow-emit", + "allow-emit-to" + ] + }, + "permissions": { + "allow-emit": { + "version": null, + "identifier": "allow-emit", + "description": "Enables the emit command without any pre-configured scope.", + "commands": { + "allow": [ + "emit" + ], + "deny": [] + }, + "scope": {} + }, + "allow-emit-to": { + "version": null, + "identifier": "allow-emit-to", + "description": "Enables the emit_to command without any pre-configured scope.", + "commands": { + "allow": [ + "emit_to" + ], + "deny": [] + }, + "scope": {} + }, + "allow-listen": { + "version": null, + "identifier": "allow-listen", + "description": "Enables the listen command without any pre-configured scope.", + "commands": { + "allow": [ + "listen" + ], + "deny": [] + }, + "scope": {} + }, + "allow-unlisten": { + "version": null, + "identifier": "allow-unlisten", + "description": "Enables the unlisten command without any pre-configured scope.", + "commands": { + "allow": [ + "unlisten" + ], + "deny": [] + }, + "scope": {} + }, + "deny-emit": { + "version": null, + "identifier": "deny-emit", + "description": "Denies the emit command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "emit" + ] + }, + "scope": {} + }, + "deny-emit-to": { + "version": null, + "identifier": "deny-emit-to", + "description": "Denies the emit_to command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "emit_to" + ] + }, + "scope": {} + }, + "deny-listen": { + "version": null, + "identifier": "deny-listen", + "description": "Denies the listen command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "listen" + ] + }, + "scope": {} + }, + "deny-unlisten": { + "version": null, + "identifier": "deny-unlisten", + "description": "Denies the unlisten command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "unlisten" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "menu": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [] + }, + "permissions": { + "allow-append": { + "version": null, + "identifier": "allow-append", + "description": "Enables the append command without any pre-configured scope.", + "commands": { + "allow": [ + "append" + ], + "deny": [] + }, + "scope": {} + }, + "allow-create-default": { + "version": null, + "identifier": "allow-create-default", + "description": "Enables the create_default command without any pre-configured scope.", + "commands": { + "allow": [ + "create_default" + ], + "deny": [] + }, + "scope": {} + }, + "allow-get": { + "version": null, + "identifier": "allow-get", + "description": "Enables the get command without any pre-configured scope.", + "commands": { + "allow": [ + "get" + ], + "deny": [] + }, + "scope": {} + }, + "allow-insert": { + "version": null, + "identifier": "allow-insert", + "description": "Enables the insert command without any pre-configured scope.", + "commands": { + "allow": [ + "insert" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-checked": { + "version": null, + "identifier": "allow-is-checked", + "description": "Enables the is_checked command without any pre-configured scope.", + "commands": { + "allow": [ + "is_checked" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-enabled": { + "version": null, + "identifier": "allow-is-enabled", + "description": "Enables the is_enabled command without any pre-configured scope.", + "commands": { + "allow": [ + "is_enabled" + ], + "deny": [] + }, + "scope": {} + }, + "allow-items": { + "version": null, + "identifier": "allow-items", + "description": "Enables the items command without any pre-configured scope.", + "commands": { + "allow": [ + "items" + ], + "deny": [] + }, + "scope": {} + }, + "allow-new": { + "version": null, + "identifier": "allow-new", + "description": "Enables the new command without any pre-configured scope.", + "commands": { + "allow": [ + "new" + ], + "deny": [] + }, + "scope": {} + }, + "allow-popup": { + "version": null, + "identifier": "allow-popup", + "description": "Enables the popup command without any pre-configured scope.", + "commands": { + "allow": [ + "popup" + ], + "deny": [] + }, + "scope": {} + }, + "allow-prepend": { + "version": null, + "identifier": "allow-prepend", + "description": "Enables the prepend command without any pre-configured scope.", + "commands": { + "allow": [ + "prepend" + ], + "deny": [] + }, + "scope": {} + }, + "allow-remove": { + "version": null, + "identifier": "allow-remove", + "description": "Enables the remove command without any pre-configured scope.", + "commands": { + "allow": [ + "remove" + ], + "deny": [] + }, + "scope": {} + }, + "allow-remove-at": { + "version": null, + "identifier": "allow-remove-at", + "description": "Enables the remove_at command without any pre-configured scope.", + "commands": { + "allow": [ + "remove_at" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-accelerator": { + "version": null, + "identifier": "allow-set-accelerator", + "description": "Enables the set_accelerator command without any pre-configured scope.", + "commands": { + "allow": [ + "set_accelerator" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-as-app-menu": { + "version": null, + "identifier": "allow-set-as-app-menu", + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "commands": { + "allow": [ + "set_as_app_menu" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-as-help-menu-for-nsapp": { + "version": null, + "identifier": "allow-set-as-help-menu-for-nsapp", + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "commands": { + "allow": [ + "set_as_help_menu_for_nsapp" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-as-window-menu": { + "version": null, + "identifier": "allow-set-as-window-menu", + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "commands": { + "allow": [ + "set_as_window_menu" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-as-windows-menu-for-nsapp": { + "version": null, + "identifier": "allow-set-as-windows-menu-for-nsapp", + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "commands": { + "allow": [ + "set_as_windows_menu_for_nsapp" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-checked": { + "version": null, + "identifier": "allow-set-checked", + "description": "Enables the set_checked command without any pre-configured scope.", + "commands": { + "allow": [ + "set_checked" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-enabled": { + "version": null, + "identifier": "allow-set-enabled", + "description": "Enables the set_enabled command without any pre-configured scope.", + "commands": { + "allow": [ + "set_enabled" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-icon": { + "version": null, + "identifier": "allow-set-icon", + "description": "Enables the set_icon command without any pre-configured scope.", + "commands": { + "allow": [ + "set_icon" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-text": { + "version": null, + "identifier": "allow-set-text", + "description": "Enables the set_text command without any pre-configured scope.", + "commands": { + "allow": [ + "set_text" + ], + "deny": [] + }, + "scope": {} + }, + "allow-text": { + "version": null, + "identifier": "allow-text", + "description": "Enables the text command without any pre-configured scope.", + "commands": { + "allow": [ + "text" + ], + "deny": [] + }, + "scope": {} + }, + "deny-append": { + "version": null, + "identifier": "deny-append", + "description": "Denies the append command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "append" + ] + }, + "scope": {} + }, + "deny-create-default": { + "version": null, + "identifier": "deny-create-default", + "description": "Denies the create_default command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "create_default" + ] + }, + "scope": {} + }, + "deny-get": { + "version": null, + "identifier": "deny-get", + "description": "Denies the get command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "get" + ] + }, + "scope": {} + }, + "deny-insert": { + "version": null, + "identifier": "deny-insert", + "description": "Denies the insert command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "insert" + ] + }, + "scope": {} + }, + "deny-is-checked": { + "version": null, + "identifier": "deny-is-checked", + "description": "Denies the is_checked command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_checked" + ] + }, + "scope": {} + }, + "deny-is-enabled": { + "version": null, + "identifier": "deny-is-enabled", + "description": "Denies the is_enabled command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_enabled" + ] + }, + "scope": {} + }, + "deny-items": { + "version": null, + "identifier": "deny-items", + "description": "Denies the items command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "items" + ] + }, + "scope": {} + }, + "deny-new": { + "version": null, + "identifier": "deny-new", + "description": "Denies the new command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "new" + ] + }, + "scope": {} + }, + "deny-popup": { + "version": null, + "identifier": "deny-popup", + "description": "Denies the popup command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "popup" + ] + }, + "scope": {} + }, + "deny-prepend": { + "version": null, + "identifier": "deny-prepend", + "description": "Denies the prepend command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "prepend" + ] + }, + "scope": {} + }, + "deny-remove": { + "version": null, + "identifier": "deny-remove", + "description": "Denies the remove command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "remove" + ] + }, + "scope": {} + }, + "deny-remove-at": { + "version": null, + "identifier": "deny-remove-at", + "description": "Denies the remove_at command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "remove_at" + ] + }, + "scope": {} + }, + "deny-set-accelerator": { + "version": null, + "identifier": "deny-set-accelerator", + "description": "Denies the set_accelerator command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_accelerator" + ] + }, + "scope": {} + }, + "deny-set-as-app-menu": { + "version": null, + "identifier": "deny-set-as-app-menu", + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_as_app_menu" + ] + }, + "scope": {} + }, + "deny-set-as-help-menu-for-nsapp": { + "version": null, + "identifier": "deny-set-as-help-menu-for-nsapp", + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_as_help_menu_for_nsapp" + ] + }, + "scope": {} + }, + "deny-set-as-window-menu": { + "version": null, + "identifier": "deny-set-as-window-menu", + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_as_window_menu" + ] + }, + "scope": {} + }, + "deny-set-as-windows-menu-for-nsapp": { + "version": null, + "identifier": "deny-set-as-windows-menu-for-nsapp", + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_as_windows_menu_for_nsapp" + ] + }, + "scope": {} + }, + "deny-set-checked": { + "version": null, + "identifier": "deny-set-checked", + "description": "Denies the set_checked command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_checked" + ] + }, + "scope": {} + }, + "deny-set-enabled": { + "version": null, + "identifier": "deny-set-enabled", + "description": "Denies the set_enabled command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_enabled" + ] + }, + "scope": {} + }, + "deny-set-icon": { + "version": null, + "identifier": "deny-set-icon", + "description": "Denies the set_icon command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_icon" + ] + }, + "scope": {} + }, + "deny-set-text": { + "version": null, + "identifier": "deny-set-text", + "description": "Denies the set_text command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_text" + ] + }, + "scope": {} + }, + "deny-text": { + "version": null, + "identifier": "deny-text", + "description": "Denies the text command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "text" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "path": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-resolve-directory", + "allow-resolve", + "allow-normalize", + "allow-join", + "allow-dirname", + "allow-extname", + "allow-basename", + "allow-is-absolute" + ] + }, + "permissions": { + "allow-basename": { + "version": null, + "identifier": "allow-basename", + "description": "Enables the basename command without any pre-configured scope.", + "commands": { + "allow": [ + "basename" + ], + "deny": [] + }, + "scope": {} + }, + "allow-dirname": { + "version": null, + "identifier": "allow-dirname", + "description": "Enables the dirname command without any pre-configured scope.", + "commands": { + "allow": [ + "dirname" + ], + "deny": [] + }, + "scope": {} + }, + "allow-extname": { + "version": null, + "identifier": "allow-extname", + "description": "Enables the extname command without any pre-configured scope.", + "commands": { + "allow": [ + "extname" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-absolute": { + "version": null, + "identifier": "allow-is-absolute", + "description": "Enables the is_absolute command without any pre-configured scope.", + "commands": { + "allow": [ + "is_absolute" + ], + "deny": [] + }, + "scope": {} + }, + "allow-join": { + "version": null, + "identifier": "allow-join", + "description": "Enables the join command without any pre-configured scope.", + "commands": { + "allow": [ + "join" + ], + "deny": [] + }, + "scope": {} + }, + "allow-normalize": { + "version": null, + "identifier": "allow-normalize", + "description": "Enables the normalize command without any pre-configured scope.", + "commands": { + "allow": [ + "normalize" + ], + "deny": [] + }, + "scope": {} + }, + "allow-resolve": { + "version": null, + "identifier": "allow-resolve", + "description": "Enables the resolve command without any pre-configured scope.", + "commands": { + "allow": [ + "resolve" + ], + "deny": [] + }, + "scope": {} + }, + "allow-resolve-directory": { + "version": null, + "identifier": "allow-resolve-directory", + "description": "Enables the resolve_directory command without any pre-configured scope.", + "commands": { + "allow": [ + "resolve_directory" + ], + "deny": [] + }, + "scope": {} + }, + "deny-basename": { + "version": null, + "identifier": "deny-basename", + "description": "Denies the basename command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "basename" + ] + }, + "scope": {} + }, + "deny-dirname": { + "version": null, + "identifier": "deny-dirname", + "description": "Denies the dirname command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "dirname" + ] + }, + "scope": {} + }, + "deny-extname": { + "version": null, + "identifier": "deny-extname", + "description": "Denies the extname command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "extname" + ] + }, + "scope": {} + }, + "deny-is-absolute": { + "version": null, + "identifier": "deny-is-absolute", + "description": "Denies the is_absolute command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_absolute" + ] + }, + "scope": {} + }, + "deny-join": { + "version": null, + "identifier": "deny-join", + "description": "Denies the join command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "join" + ] + }, + "scope": {} + }, + "deny-normalize": { + "version": null, + "identifier": "deny-normalize", + "description": "Denies the normalize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "normalize" + ] + }, + "scope": {} + }, + "deny-resolve": { + "version": null, + "identifier": "deny-resolve", + "description": "Denies the resolve command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "resolve" + ] + }, + "scope": {} + }, + "deny-resolve-directory": { + "version": null, + "identifier": "deny-resolve-directory", + "description": "Denies the resolve_directory command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "resolve_directory" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "resources": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-close" + ] + }, + "permissions": { + "allow-close": { + "version": null, + "identifier": "allow-close", + "description": "Enables the close command without any pre-configured scope.", + "commands": { + "allow": [ + "close" + ], + "deny": [] + }, + "scope": {} + }, + "deny-close": { + "version": null, + "identifier": "deny-close", + "description": "Denies the close command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "close" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "shell": { + "default_permission": null, + "permissions": { + "allow-execute": { + "version": null, + "identifier": "allow-execute", + "description": "Enables the execute command without any pre-configured scope.", + "commands": { + "allow": [ + "execute" + ], + "deny": [] + }, + "scope": {} + }, + "allow-kill": { + "version": null, + "identifier": "allow-kill", + "description": "Enables the kill command without any pre-configured scope.", + "commands": { + "allow": [ + "kill" + ], + "deny": [] + }, + "scope": {} + }, + "allow-open": { + "version": null, + "identifier": "allow-open", + "description": "Enables the open command without any pre-configured scope.", + "commands": { + "allow": [ + "open" + ], + "deny": [] + }, + "scope": {} + }, + "allow-stdin-write": { + "version": null, + "identifier": "allow-stdin-write", + "description": "Enables the stdin_write command without any pre-configured scope.", + "commands": { + "allow": [ + "stdin_write" + ], + "deny": [] + }, + "scope": {} + }, + "deny-execute": { + "version": null, + "identifier": "deny-execute", + "description": "Denies the execute command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "execute" + ] + }, + "scope": {} + }, + "deny-kill": { + "version": null, + "identifier": "deny-kill", + "description": "Denies the kill command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "kill" + ] + }, + "scope": {} + }, + "deny-open": { + "version": null, + "identifier": "deny-open", + "description": "Denies the open command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "open" + ] + }, + "scope": {} + }, + "deny-stdin-write": { + "version": null, + "identifier": "deny-stdin-write", + "description": "Denies the stdin_write command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "stdin_write" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ShellAllowedArg": { + "anyOf": [ + { + "description": "A non-configurable argument that is passed to the command in the order it was specified.", + "type": "string" + }, + { + "additionalProperties": false, + "description": "A variable that is set while calling the command from the webview API.", + "properties": { + "validator": { + "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax", + "type": "string" + } + }, + "required": [ + "validator" + ], + "type": "object" + } + ], + "description": "A command argument allowed to be executed by the webview API." + }, + "ShellAllowedArgs": { + "anyOf": [ + { + "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", + "type": "boolean" + }, + { + "description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.", + "items": { + "$ref": "#/definitions/ShellAllowedArg" + }, + "type": "array" + } + ], + "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration." + } + }, + "description": "A command allowed to be executed by the webview API.", + "properties": { + "args": { + "allOf": [ + { + "$ref": "#/definitions/ShellAllowedArgs" + } + ], + "description": "The allowed arguments for the command execution." + }, + "command": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "required": [ + "args", + "command", + "name", + "sidecar" + ], + "title": "Entry", + "type": "object" + } + }, + "tray": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [] + }, + "permissions": { + "allow-new": { + "version": null, + "identifier": "allow-new", + "description": "Enables the new command without any pre-configured scope.", + "commands": { + "allow": [ + "new" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-icon": { + "version": null, + "identifier": "allow-set-icon", + "description": "Enables the set_icon command without any pre-configured scope.", + "commands": { + "allow": [ + "set_icon" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-icon-as-template": { + "version": null, + "identifier": "allow-set-icon-as-template", + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "commands": { + "allow": [ + "set_icon_as_template" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-menu": { + "version": null, + "identifier": "allow-set-menu", + "description": "Enables the set_menu command without any pre-configured scope.", + "commands": { + "allow": [ + "set_menu" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-show-menu-on-left-click": { + "version": null, + "identifier": "allow-set-show-menu-on-left-click", + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "commands": { + "allow": [ + "set_show_menu_on_left_click" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-temp-dir-path": { + "version": null, + "identifier": "allow-set-temp-dir-path", + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "commands": { + "allow": [ + "set_temp_dir_path" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-title": { + "version": null, + "identifier": "allow-set-title", + "description": "Enables the set_title command without any pre-configured scope.", + "commands": { + "allow": [ + "set_title" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-tooltip": { + "version": null, + "identifier": "allow-set-tooltip", + "description": "Enables the set_tooltip command without any pre-configured scope.", + "commands": { + "allow": [ + "set_tooltip" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-visible": { + "version": null, + "identifier": "allow-set-visible", + "description": "Enables the set_visible command without any pre-configured scope.", + "commands": { + "allow": [ + "set_visible" + ], + "deny": [] + }, + "scope": {} + }, + "deny-new": { + "version": null, + "identifier": "deny-new", + "description": "Denies the new command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "new" + ] + }, + "scope": {} + }, + "deny-set-icon": { + "version": null, + "identifier": "deny-set-icon", + "description": "Denies the set_icon command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_icon" + ] + }, + "scope": {} + }, + "deny-set-icon-as-template": { + "version": null, + "identifier": "deny-set-icon-as-template", + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_icon_as_template" + ] + }, + "scope": {} + }, + "deny-set-menu": { + "version": null, + "identifier": "deny-set-menu", + "description": "Denies the set_menu command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_menu" + ] + }, + "scope": {} + }, + "deny-set-show-menu-on-left-click": { + "version": null, + "identifier": "deny-set-show-menu-on-left-click", + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_show_menu_on_left_click" + ] + }, + "scope": {} + }, + "deny-set-temp-dir-path": { + "version": null, + "identifier": "deny-set-temp-dir-path", + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_temp_dir_path" + ] + }, + "scope": {} + }, + "deny-set-title": { + "version": null, + "identifier": "deny-set-title", + "description": "Denies the set_title command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_title" + ] + }, + "scope": {} + }, + "deny-set-tooltip": { + "version": null, + "identifier": "deny-set-tooltip", + "description": "Denies the set_tooltip command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_tooltip" + ] + }, + "scope": {} + }, + "deny-set-visible": { + "version": null, + "identifier": "deny-set-visible", + "description": "Denies the set_visible command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_visible" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "webview": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-webview-position", + "allow-webview-size", + "allow-internal-toggle-devtools" + ] + }, + "permissions": { + "allow-create-webview": { + "version": null, + "identifier": "allow-create-webview", + "description": "Enables the create_webview command without any pre-configured scope.", + "commands": { + "allow": [ + "create_webview" + ], + "deny": [] + }, + "scope": {} + }, + "allow-create-webview-window": { + "version": null, + "identifier": "allow-create-webview-window", + "description": "Enables the create_webview_window command without any pre-configured scope.", + "commands": { + "allow": [ + "create_webview_window" + ], + "deny": [] + }, + "scope": {} + }, + "allow-internal-toggle-devtools": { + "version": null, + "identifier": "allow-internal-toggle-devtools", + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "commands": { + "allow": [ + "internal_toggle_devtools" + ], + "deny": [] + }, + "scope": {} + }, + "allow-print": { + "version": null, + "identifier": "allow-print", + "description": "Enables the print command without any pre-configured scope.", + "commands": { + "allow": [ + "print" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-webview-focus": { + "version": null, + "identifier": "allow-set-webview-focus", + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "commands": { + "allow": [ + "set_webview_focus" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-webview-position": { + "version": null, + "identifier": "allow-set-webview-position", + "description": "Enables the set_webview_position command without any pre-configured scope.", + "commands": { + "allow": [ + "set_webview_position" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-webview-size": { + "version": null, + "identifier": "allow-set-webview-size", + "description": "Enables the set_webview_size command without any pre-configured scope.", + "commands": { + "allow": [ + "set_webview_size" + ], + "deny": [] + }, + "scope": {} + }, + "allow-webview-close": { + "version": null, + "identifier": "allow-webview-close", + "description": "Enables the webview_close command without any pre-configured scope.", + "commands": { + "allow": [ + "webview_close" + ], + "deny": [] + }, + "scope": {} + }, + "allow-webview-position": { + "version": null, + "identifier": "allow-webview-position", + "description": "Enables the webview_position command without any pre-configured scope.", + "commands": { + "allow": [ + "webview_position" + ], + "deny": [] + }, + "scope": {} + }, + "allow-webview-size": { + "version": null, + "identifier": "allow-webview-size", + "description": "Enables the webview_size command without any pre-configured scope.", + "commands": { + "allow": [ + "webview_size" + ], + "deny": [] + }, + "scope": {} + }, + "deny-create-webview": { + "version": null, + "identifier": "deny-create-webview", + "description": "Denies the create_webview command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "create_webview" + ] + }, + "scope": {} + }, + "deny-create-webview-window": { + "version": null, + "identifier": "deny-create-webview-window", + "description": "Denies the create_webview_window command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "create_webview_window" + ] + }, + "scope": {} + }, + "deny-internal-toggle-devtools": { + "version": null, + "identifier": "deny-internal-toggle-devtools", + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "internal_toggle_devtools" + ] + }, + "scope": {} + }, + "deny-print": { + "version": null, + "identifier": "deny-print", + "description": "Denies the print command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "print" + ] + }, + "scope": {} + }, + "deny-set-webview-focus": { + "version": null, + "identifier": "deny-set-webview-focus", + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_webview_focus" + ] + }, + "scope": {} + }, + "deny-set-webview-position": { + "version": null, + "identifier": "deny-set-webview-position", + "description": "Denies the set_webview_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_webview_position" + ] + }, + "scope": {} + }, + "deny-set-webview-size": { + "version": null, + "identifier": "deny-set-webview-size", + "description": "Denies the set_webview_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_webview_size" + ] + }, + "scope": {} + }, + "deny-webview-close": { + "version": null, + "identifier": "deny-webview-close", + "description": "Denies the webview_close command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "webview_close" + ] + }, + "scope": {} + }, + "deny-webview-position": { + "version": null, + "identifier": "deny-webview-position", + "description": "Denies the webview_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "webview_position" + ] + }, + "scope": {} + }, + "deny-webview-size": { + "version": null, + "identifier": "deny-webview-size", + "description": "Denies the webview_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "webview_size" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "window": { + "default_permission": { + "identifier": "default", + "description": "Default permissions for the plugin.", + "permissions": [ + "allow-scale-factor", + "allow-inner-position", + "allow-outer-position", + "allow-inner-size", + "allow-outer-size", + "allow-is-fullscreen", + "allow-is-minimized", + "allow-is-maximized", + "allow-is-focused", + "allow-is-decorated", + "allow-is-resizable", + "allow-is-maximizable", + "allow-is-minimizable", + "allow-is-closable", + "allow-is-visible", + "allow-title", + "allow-current-monitor", + "allow-primary-monitor", + "allow-available-monitors", + "allow-theme", + "allow-internal-toggle-maximize", + "allow-internal-on-mousemove", + "allow-internal-on-mousedown" + ] + }, + "permissions": { + "allow-available-monitors": { + "version": null, + "identifier": "allow-available-monitors", + "description": "Enables the available_monitors command without any pre-configured scope.", + "commands": { + "allow": [ + "available_monitors" + ], + "deny": [] + }, + "scope": {} + }, + "allow-center": { + "version": null, + "identifier": "allow-center", + "description": "Enables the center command without any pre-configured scope.", + "commands": { + "allow": [ + "center" + ], + "deny": [] + }, + "scope": {} + }, + "allow-close": { + "version": null, + "identifier": "allow-close", + "description": "Enables the close command without any pre-configured scope.", + "commands": { + "allow": [ + "close" + ], + "deny": [] + }, + "scope": {} + }, + "allow-create": { + "version": null, + "identifier": "allow-create", + "description": "Enables the create command without any pre-configured scope.", + "commands": { + "allow": [ + "create" + ], + "deny": [] + }, + "scope": {} + }, + "allow-current-monitor": { + "version": null, + "identifier": "allow-current-monitor", + "description": "Enables the current_monitor command without any pre-configured scope.", + "commands": { + "allow": [ + "current_monitor" + ], + "deny": [] + }, + "scope": {} + }, + "allow-destroy": { + "version": null, + "identifier": "allow-destroy", + "description": "Enables the destroy command without any pre-configured scope.", + "commands": { + "allow": [ + "destroy" + ], + "deny": [] + }, + "scope": {} + }, + "allow-hide": { + "version": null, + "identifier": "allow-hide", + "description": "Enables the hide command without any pre-configured scope.", + "commands": { + "allow": [ + "hide" + ], + "deny": [] + }, + "scope": {} + }, + "allow-inner-position": { + "version": null, + "identifier": "allow-inner-position", + "description": "Enables the inner_position command without any pre-configured scope.", + "commands": { + "allow": [ + "inner_position" + ], + "deny": [] + }, + "scope": {} + }, + "allow-inner-size": { + "version": null, + "identifier": "allow-inner-size", + "description": "Enables the inner_size command without any pre-configured scope.", + "commands": { + "allow": [ + "inner_size" + ], + "deny": [] + }, + "scope": {} + }, + "allow-internal-on-mousedown": { + "version": null, + "identifier": "allow-internal-on-mousedown", + "description": "Enables the internal_on_mousedown command without any pre-configured scope.", + "commands": { + "allow": [ + "internal_on_mousedown" + ], + "deny": [] + }, + "scope": {} + }, + "allow-internal-on-mousemove": { + "version": null, + "identifier": "allow-internal-on-mousemove", + "description": "Enables the internal_on_mousemove command without any pre-configured scope.", + "commands": { + "allow": [ + "internal_on_mousemove" + ], + "deny": [] + }, + "scope": {} + }, + "allow-internal-toggle-maximize": { + "version": null, + "identifier": "allow-internal-toggle-maximize", + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "commands": { + "allow": [ + "internal_toggle_maximize" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-closable": { + "version": null, + "identifier": "allow-is-closable", + "description": "Enables the is_closable command without any pre-configured scope.", + "commands": { + "allow": [ + "is_closable" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-decorated": { + "version": null, + "identifier": "allow-is-decorated", + "description": "Enables the is_decorated command without any pre-configured scope.", + "commands": { + "allow": [ + "is_decorated" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-focused": { + "version": null, + "identifier": "allow-is-focused", + "description": "Enables the is_focused command without any pre-configured scope.", + "commands": { + "allow": [ + "is_focused" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-fullscreen": { + "version": null, + "identifier": "allow-is-fullscreen", + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "commands": { + "allow": [ + "is_fullscreen" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-maximizable": { + "version": null, + "identifier": "allow-is-maximizable", + "description": "Enables the is_maximizable command without any pre-configured scope.", + "commands": { + "allow": [ + "is_maximizable" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-maximized": { + "version": null, + "identifier": "allow-is-maximized", + "description": "Enables the is_maximized command without any pre-configured scope.", + "commands": { + "allow": [ + "is_maximized" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-minimizable": { + "version": null, + "identifier": "allow-is-minimizable", + "description": "Enables the is_minimizable command without any pre-configured scope.", + "commands": { + "allow": [ + "is_minimizable" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-minimized": { + "version": null, + "identifier": "allow-is-minimized", + "description": "Enables the is_minimized command without any pre-configured scope.", + "commands": { + "allow": [ + "is_minimized" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-resizable": { + "version": null, + "identifier": "allow-is-resizable", + "description": "Enables the is_resizable command without any pre-configured scope.", + "commands": { + "allow": [ + "is_resizable" + ], + "deny": [] + }, + "scope": {} + }, + "allow-is-visible": { + "version": null, + "identifier": "allow-is-visible", + "description": "Enables the is_visible command without any pre-configured scope.", + "commands": { + "allow": [ + "is_visible" + ], + "deny": [] + }, + "scope": {} + }, + "allow-maximize": { + "version": null, + "identifier": "allow-maximize", + "description": "Enables the maximize command without any pre-configured scope.", + "commands": { + "allow": [ + "maximize" + ], + "deny": [] + }, + "scope": {} + }, + "allow-minimize": { + "version": null, + "identifier": "allow-minimize", + "description": "Enables the minimize command without any pre-configured scope.", + "commands": { + "allow": [ + "minimize" + ], + "deny": [] + }, + "scope": {} + }, + "allow-outer-position": { + "version": null, + "identifier": "allow-outer-position", + "description": "Enables the outer_position command without any pre-configured scope.", + "commands": { + "allow": [ + "outer_position" + ], + "deny": [] + }, + "scope": {} + }, + "allow-outer-size": { + "version": null, + "identifier": "allow-outer-size", + "description": "Enables the outer_size command without any pre-configured scope.", + "commands": { + "allow": [ + "outer_size" + ], + "deny": [] + }, + "scope": {} + }, + "allow-primary-monitor": { + "version": null, + "identifier": "allow-primary-monitor", + "description": "Enables the primary_monitor command without any pre-configured scope.", + "commands": { + "allow": [ + "primary_monitor" + ], + "deny": [] + }, + "scope": {} + }, + "allow-request-user-attention": { + "version": null, + "identifier": "allow-request-user-attention", + "description": "Enables the request_user_attention command without any pre-configured scope.", + "commands": { + "allow": [ + "request_user_attention" + ], + "deny": [] + }, + "scope": {} + }, + "allow-scale-factor": { + "version": null, + "identifier": "allow-scale-factor", + "description": "Enables the scale_factor command without any pre-configured scope.", + "commands": { + "allow": [ + "scale_factor" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-always-on-bottom": { + "version": null, + "identifier": "allow-set-always-on-bottom", + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "commands": { + "allow": [ + "set_always_on_bottom" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-always-on-top": { + "version": null, + "identifier": "allow-set-always-on-top", + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "commands": { + "allow": [ + "set_always_on_top" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-closable": { + "version": null, + "identifier": "allow-set-closable", + "description": "Enables the set_closable command without any pre-configured scope.", + "commands": { + "allow": [ + "set_closable" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-content-protected": { + "version": null, + "identifier": "allow-set-content-protected", + "description": "Enables the set_content_protected command without any pre-configured scope.", + "commands": { + "allow": [ + "set_content_protected" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-cursor-grab": { + "version": null, + "identifier": "allow-set-cursor-grab", + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "commands": { + "allow": [ + "set_cursor_grab" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-cursor-icon": { + "version": null, + "identifier": "allow-set-cursor-icon", + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "commands": { + "allow": [ + "set_cursor_icon" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-cursor-position": { + "version": null, + "identifier": "allow-set-cursor-position", + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "commands": { + "allow": [ + "set_cursor_position" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-cursor-visible": { + "version": null, + "identifier": "allow-set-cursor-visible", + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "commands": { + "allow": [ + "set_cursor_visible" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-decorations": { + "version": null, + "identifier": "allow-set-decorations", + "description": "Enables the set_decorations command without any pre-configured scope.", + "commands": { + "allow": [ + "set_decorations" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-effects": { + "version": null, + "identifier": "allow-set-effects", + "description": "Enables the set_effects command without any pre-configured scope.", + "commands": { + "allow": [ + "set_effects" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-focus": { + "version": null, + "identifier": "allow-set-focus", + "description": "Enables the set_focus command without any pre-configured scope.", + "commands": { + "allow": [ + "set_focus" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-fullscreen": { + "version": null, + "identifier": "allow-set-fullscreen", + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "commands": { + "allow": [ + "set_fullscreen" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-icon": { + "version": null, + "identifier": "allow-set-icon", + "description": "Enables the set_icon command without any pre-configured scope.", + "commands": { + "allow": [ + "set_icon" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-ignore-cursor-events": { + "version": null, + "identifier": "allow-set-ignore-cursor-events", + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "commands": { + "allow": [ + "set_ignore_cursor_events" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-max-size": { + "version": null, + "identifier": "allow-set-max-size", + "description": "Enables the set_max_size command without any pre-configured scope.", + "commands": { + "allow": [ + "set_max_size" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-maximizable": { + "version": null, + "identifier": "allow-set-maximizable", + "description": "Enables the set_maximizable command without any pre-configured scope.", + "commands": { + "allow": [ + "set_maximizable" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-min-size": { + "version": null, + "identifier": "allow-set-min-size", + "description": "Enables the set_min_size command without any pre-configured scope.", + "commands": { + "allow": [ + "set_min_size" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-minimizable": { + "version": null, + "identifier": "allow-set-minimizable", + "description": "Enables the set_minimizable command without any pre-configured scope.", + "commands": { + "allow": [ + "set_minimizable" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-position": { + "version": null, + "identifier": "allow-set-position", + "description": "Enables the set_position command without any pre-configured scope.", + "commands": { + "allow": [ + "set_position" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-progress-bar": { + "version": null, + "identifier": "allow-set-progress-bar", + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "commands": { + "allow": [ + "set_progress_bar" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-resizable": { + "version": null, + "identifier": "allow-set-resizable", + "description": "Enables the set_resizable command without any pre-configured scope.", + "commands": { + "allow": [ + "set_resizable" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-shadow": { + "version": null, + "identifier": "allow-set-shadow", + "description": "Enables the set_shadow command without any pre-configured scope.", + "commands": { + "allow": [ + "set_shadow" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-size": { + "version": null, + "identifier": "allow-set-size", + "description": "Enables the set_size command without any pre-configured scope.", + "commands": { + "allow": [ + "set_size" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-skip-taskbar": { + "version": null, + "identifier": "allow-set-skip-taskbar", + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "commands": { + "allow": [ + "set_skip_taskbar" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-title": { + "version": null, + "identifier": "allow-set-title", + "description": "Enables the set_title command without any pre-configured scope.", + "commands": { + "allow": [ + "set_title" + ], + "deny": [] + }, + "scope": {} + }, + "allow-set-visible-on-all-workspaces": { + "version": null, + "identifier": "allow-set-visible-on-all-workspaces", + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "commands": { + "allow": [ + "set_visible_on_all_workspaces" + ], + "deny": [] + }, + "scope": {} + }, + "allow-show": { + "version": null, + "identifier": "allow-show", + "description": "Enables the show command without any pre-configured scope.", + "commands": { + "allow": [ + "show" + ], + "deny": [] + }, + "scope": {} + }, + "allow-start-dragging": { + "version": null, + "identifier": "allow-start-dragging", + "description": "Enables the start_dragging command without any pre-configured scope.", + "commands": { + "allow": [ + "start_dragging" + ], + "deny": [] + }, + "scope": {} + }, + "allow-theme": { + "version": null, + "identifier": "allow-theme", + "description": "Enables the theme command without any pre-configured scope.", + "commands": { + "allow": [ + "theme" + ], + "deny": [] + }, + "scope": {} + }, + "allow-title": { + "version": null, + "identifier": "allow-title", + "description": "Enables the title command without any pre-configured scope.", + "commands": { + "allow": [ + "title" + ], + "deny": [] + }, + "scope": {} + }, + "allow-toggle-maximize": { + "version": null, + "identifier": "allow-toggle-maximize", + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "commands": { + "allow": [ + "toggle_maximize" + ], + "deny": [] + }, + "scope": {} + }, + "allow-unmaximize": { + "version": null, + "identifier": "allow-unmaximize", + "description": "Enables the unmaximize command without any pre-configured scope.", + "commands": { + "allow": [ + "unmaximize" + ], + "deny": [] + }, + "scope": {} + }, + "allow-unminimize": { + "version": null, + "identifier": "allow-unminimize", + "description": "Enables the unminimize command without any pre-configured scope.", + "commands": { + "allow": [ + "unminimize" + ], + "deny": [] + }, + "scope": {} + }, + "deny-available-monitors": { + "version": null, + "identifier": "deny-available-monitors", + "description": "Denies the available_monitors command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "available_monitors" + ] + }, + "scope": {} + }, + "deny-center": { + "version": null, + "identifier": "deny-center", + "description": "Denies the center command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "center" + ] + }, + "scope": {} + }, + "deny-close": { + "version": null, + "identifier": "deny-close", + "description": "Denies the close command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "close" + ] + }, + "scope": {} + }, + "deny-create": { + "version": null, + "identifier": "deny-create", + "description": "Denies the create command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "create" + ] + }, + "scope": {} + }, + "deny-current-monitor": { + "version": null, + "identifier": "deny-current-monitor", + "description": "Denies the current_monitor command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "current_monitor" + ] + }, + "scope": {} + }, + "deny-destroy": { + "version": null, + "identifier": "deny-destroy", + "description": "Denies the destroy command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "destroy" + ] + }, + "scope": {} + }, + "deny-hide": { + "version": null, + "identifier": "deny-hide", + "description": "Denies the hide command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "hide" + ] + }, + "scope": {} + }, + "deny-inner-position": { + "version": null, + "identifier": "deny-inner-position", + "description": "Denies the inner_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "inner_position" + ] + }, + "scope": {} + }, + "deny-inner-size": { + "version": null, + "identifier": "deny-inner-size", + "description": "Denies the inner_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "inner_size" + ] + }, + "scope": {} + }, + "deny-internal-on-mousedown": { + "version": null, + "identifier": "deny-internal-on-mousedown", + "description": "Denies the internal_on_mousedown command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "internal_on_mousedown" + ] + }, + "scope": {} + }, + "deny-internal-on-mousemove": { + "version": null, + "identifier": "deny-internal-on-mousemove", + "description": "Denies the internal_on_mousemove command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "internal_on_mousemove" + ] + }, + "scope": {} + }, + "deny-internal-toggle-maximize": { + "version": null, + "identifier": "deny-internal-toggle-maximize", + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "internal_toggle_maximize" + ] + }, + "scope": {} + }, + "deny-is-closable": { + "version": null, + "identifier": "deny-is-closable", + "description": "Denies the is_closable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_closable" + ] + }, + "scope": {} + }, + "deny-is-decorated": { + "version": null, + "identifier": "deny-is-decorated", + "description": "Denies the is_decorated command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_decorated" + ] + }, + "scope": {} + }, + "deny-is-focused": { + "version": null, + "identifier": "deny-is-focused", + "description": "Denies the is_focused command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_focused" + ] + }, + "scope": {} + }, + "deny-is-fullscreen": { + "version": null, + "identifier": "deny-is-fullscreen", + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_fullscreen" + ] + }, + "scope": {} + }, + "deny-is-maximizable": { + "version": null, + "identifier": "deny-is-maximizable", + "description": "Denies the is_maximizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_maximizable" + ] + }, + "scope": {} + }, + "deny-is-maximized": { + "version": null, + "identifier": "deny-is-maximized", + "description": "Denies the is_maximized command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_maximized" + ] + }, + "scope": {} + }, + "deny-is-minimizable": { + "version": null, + "identifier": "deny-is-minimizable", + "description": "Denies the is_minimizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_minimizable" + ] + }, + "scope": {} + }, + "deny-is-minimized": { + "version": null, + "identifier": "deny-is-minimized", + "description": "Denies the is_minimized command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_minimized" + ] + }, + "scope": {} + }, + "deny-is-resizable": { + "version": null, + "identifier": "deny-is-resizable", + "description": "Denies the is_resizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_resizable" + ] + }, + "scope": {} + }, + "deny-is-visible": { + "version": null, + "identifier": "deny-is-visible", + "description": "Denies the is_visible command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "is_visible" + ] + }, + "scope": {} + }, + "deny-maximize": { + "version": null, + "identifier": "deny-maximize", + "description": "Denies the maximize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "maximize" + ] + }, + "scope": {} + }, + "deny-minimize": { + "version": null, + "identifier": "deny-minimize", + "description": "Denies the minimize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "minimize" + ] + }, + "scope": {} + }, + "deny-outer-position": { + "version": null, + "identifier": "deny-outer-position", + "description": "Denies the outer_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "outer_position" + ] + }, + "scope": {} + }, + "deny-outer-size": { + "version": null, + "identifier": "deny-outer-size", + "description": "Denies the outer_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "outer_size" + ] + }, + "scope": {} + }, + "deny-primary-monitor": { + "version": null, + "identifier": "deny-primary-monitor", + "description": "Denies the primary_monitor command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "primary_monitor" + ] + }, + "scope": {} + }, + "deny-request-user-attention": { + "version": null, + "identifier": "deny-request-user-attention", + "description": "Denies the request_user_attention command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "request_user_attention" + ] + }, + "scope": {} + }, + "deny-scale-factor": { + "version": null, + "identifier": "deny-scale-factor", + "description": "Denies the scale_factor command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "scale_factor" + ] + }, + "scope": {} + }, + "deny-set-always-on-bottom": { + "version": null, + "identifier": "deny-set-always-on-bottom", + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_always_on_bottom" + ] + }, + "scope": {} + }, + "deny-set-always-on-top": { + "version": null, + "identifier": "deny-set-always-on-top", + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_always_on_top" + ] + }, + "scope": {} + }, + "deny-set-closable": { + "version": null, + "identifier": "deny-set-closable", + "description": "Denies the set_closable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_closable" + ] + }, + "scope": {} + }, + "deny-set-content-protected": { + "version": null, + "identifier": "deny-set-content-protected", + "description": "Denies the set_content_protected command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_content_protected" + ] + }, + "scope": {} + }, + "deny-set-cursor-grab": { + "version": null, + "identifier": "deny-set-cursor-grab", + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_cursor_grab" + ] + }, + "scope": {} + }, + "deny-set-cursor-icon": { + "version": null, + "identifier": "deny-set-cursor-icon", + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_cursor_icon" + ] + }, + "scope": {} + }, + "deny-set-cursor-position": { + "version": null, + "identifier": "deny-set-cursor-position", + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_cursor_position" + ] + }, + "scope": {} + }, + "deny-set-cursor-visible": { + "version": null, + "identifier": "deny-set-cursor-visible", + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_cursor_visible" + ] + }, + "scope": {} + }, + "deny-set-decorations": { + "version": null, + "identifier": "deny-set-decorations", + "description": "Denies the set_decorations command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_decorations" + ] + }, + "scope": {} + }, + "deny-set-effects": { + "version": null, + "identifier": "deny-set-effects", + "description": "Denies the set_effects command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_effects" + ] + }, + "scope": {} + }, + "deny-set-focus": { + "version": null, + "identifier": "deny-set-focus", + "description": "Denies the set_focus command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_focus" + ] + }, + "scope": {} + }, + "deny-set-fullscreen": { + "version": null, + "identifier": "deny-set-fullscreen", + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_fullscreen" + ] + }, + "scope": {} + }, + "deny-set-icon": { + "version": null, + "identifier": "deny-set-icon", + "description": "Denies the set_icon command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_icon" + ] + }, + "scope": {} + }, + "deny-set-ignore-cursor-events": { + "version": null, + "identifier": "deny-set-ignore-cursor-events", + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_ignore_cursor_events" + ] + }, + "scope": {} + }, + "deny-set-max-size": { + "version": null, + "identifier": "deny-set-max-size", + "description": "Denies the set_max_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_max_size" + ] + }, + "scope": {} + }, + "deny-set-maximizable": { + "version": null, + "identifier": "deny-set-maximizable", + "description": "Denies the set_maximizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_maximizable" + ] + }, + "scope": {} + }, + "deny-set-min-size": { + "version": null, + "identifier": "deny-set-min-size", + "description": "Denies the set_min_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_min_size" + ] + }, + "scope": {} + }, + "deny-set-minimizable": { + "version": null, + "identifier": "deny-set-minimizable", + "description": "Denies the set_minimizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_minimizable" + ] + }, + "scope": {} + }, + "deny-set-position": { + "version": null, + "identifier": "deny-set-position", + "description": "Denies the set_position command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_position" + ] + }, + "scope": {} + }, + "deny-set-progress-bar": { + "version": null, + "identifier": "deny-set-progress-bar", + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_progress_bar" + ] + }, + "scope": {} + }, + "deny-set-resizable": { + "version": null, + "identifier": "deny-set-resizable", + "description": "Denies the set_resizable command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_resizable" + ] + }, + "scope": {} + }, + "deny-set-shadow": { + "version": null, + "identifier": "deny-set-shadow", + "description": "Denies the set_shadow command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_shadow" + ] + }, + "scope": {} + }, + "deny-set-size": { + "version": null, + "identifier": "deny-set-size", + "description": "Denies the set_size command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_size" + ] + }, + "scope": {} + }, + "deny-set-skip-taskbar": { + "version": null, + "identifier": "deny-set-skip-taskbar", + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_skip_taskbar" + ] + }, + "scope": {} + }, + "deny-set-title": { + "version": null, + "identifier": "deny-set-title", + "description": "Denies the set_title command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_title" + ] + }, + "scope": {} + }, + "deny-set-visible-on-all-workspaces": { + "version": null, + "identifier": "deny-set-visible-on-all-workspaces", + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "set_visible_on_all_workspaces" + ] + }, + "scope": {} + }, + "deny-show": { + "version": null, + "identifier": "deny-show", + "description": "Denies the show command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "show" + ] + }, + "scope": {} + }, + "deny-start-dragging": { + "version": null, + "identifier": "deny-start-dragging", + "description": "Denies the start_dragging command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "start_dragging" + ] + }, + "scope": {} + }, + "deny-theme": { + "version": null, + "identifier": "deny-theme", + "description": "Denies the theme command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "theme" + ] + }, + "scope": {} + }, + "deny-title": { + "version": null, + "identifier": "deny-title", + "description": "Denies the title command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "title" + ] + }, + "scope": {} + }, + "deny-toggle-maximize": { + "version": null, + "identifier": "deny-toggle-maximize", + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "toggle_maximize" + ] + }, + "scope": {} + }, + "deny-unmaximize": { + "version": null, + "identifier": "deny-unmaximize", + "description": "Denies the unmaximize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "unmaximize" + ] + }, + "scope": {} + }, + "deny-unminimize": { + "version": null, + "identifier": "deny-unminimize", + "description": "Denies the unminimize command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "unminimize" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": null + }, + "window-state": { + "default_permission": null, + "permissions": { + "allow-restore-window-state": { + "version": null, + "identifier": "allow-restore-window-state", + "description": "Enables the restore_window_state command without any pre-configured scope.", + "commands": { + "allow": [ + "restore_window_state" + ], + "deny": [] + }, + "scope": {} + }, + "allow-save-window-state": { + "version": null, + "identifier": "allow-save-window-state", + "description": "Enables the save_window_state command without any pre-configured scope.", + "commands": { + "allow": [ + "save_window_state" + ], + "deny": [] + }, + "scope": {} + }, + "deny-restore-window-state": { + "version": null, + "identifier": "deny-restore-window-state", + "description": "Denies the restore_window_state command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "restore_window_state" + ] + }, + "scope": {} + }, + "deny-save-window-state": { + "version": null, + "identifier": "deny-save-window-state", + "description": "Denies the save_window_state command without any pre-configured scope.", + "commands": { + "allow": [], + "deny": [ + "save_window_state" + ] + }, + "scope": {} + } + }, + "permission_sets": {}, + "global_scope_schema": null + } +} \ No newline at end of file diff --git a/src-tauri/src/callbacks.rs b/src-tauri/src/callbacks.rs index f37a340..7fa6fde 100644 --- a/src-tauri/src/callbacks.rs +++ b/src-tauri/src/callbacks.rs @@ -1,136 +1,136 @@ -//! Sometimes callbacks are buried deep in library code, requiring user input. -//! This module offers an overcomplicated and fragile solution. - -use std::{ - collections::HashMap, - path::{Path, PathBuf}, - sync::mpsc::channel, -}; - -use anyhow::Result; -use jj_lib::{git::RemoteCallbacks, repo::MutableRepo}; -use tauri::{Emitter, Manager, Window}; - -use crate::{ - messages::{InputField, InputRequest}, - worker::WorkerCallbacks, - AppState, -}; - -pub struct FrontendCallbacks(pub Window); - -impl WorkerCallbacks for FrontendCallbacks { - fn with_git( - &self, - repo: &mut MutableRepo, - f: &dyn Fn(&mut MutableRepo, RemoteCallbacks<'_>) -> Result<()>, - ) -> Result<()> { - let mut cb = RemoteCallbacks::default(); - - let get_ssh_keys = &mut get_ssh_keys; - cb.get_ssh_keys = Some(get_ssh_keys); - - let get_password = &mut |url: &str, username: &str| { - self.request_input( - format!("Please enter a password for {} at {}", username, url), - ["Password"], - ) - .and_then(|mut fields| fields.remove("Password")) - }; - cb.get_password = Some(get_password); - - let get_username_password = &mut |url: &str| { - self.request_input( - format!("Please enter a username and password for {}", url), - ["Username", "Password"], - ) - .and_then(|mut fields| { - fields.remove("Username").and_then(|username| { - fields - .remove("Password") - .map(|password| (username, password)) - }) - }) - }; - cb.get_username_password = Some(get_username_password); - - f(repo, cb) - } - - fn select_remote(&self, choices: &[&str]) -> Option { - let response = self.request_input( - format!("Select a remote"), - [InputField { - label: "Select Remote".into(), - choices: choices.iter().map(|choice| choice.to_string()).collect(), - }], - ); - - response.and_then(|mut fields| fields.remove("Select Remote").to_owned()) - } -} - -impl FrontendCallbacks { - fn request_input, U: Into>( - &self, - detail: String, - fields: T, - ) -> Option> { - log::debug!("request input"); - - // initialise a channel to receive responses - let (tx, rx) = channel(); - self.0.state::().set_input(self.0.label(), tx); - - // send the request - match self.0.emit( - "gg://input", - InputRequest { - title: String::from("Git Login"), - detail, - fields: fields.into_iter().map(|field| field.into()).collect(), - }, - ) { - Ok(_) => (), - Err(err) => { - log::error!("input request failed: emit failed: {err}"); - return None; - } - } - - // wait for the response - match rx.recv() { - Ok(response) => { - if response.cancel { - log::error!("input request failed: input cancelled"); - None - } else { - Some(response.fields) - } - } - Err(err) => { - log::error!("input request failed: {err}"); - None - } - } - } -} - -// simplistic, but it's the same as the version in jj_cli::git_util -fn get_ssh_keys(_username: &str) -> Vec { - let mut paths = vec![]; - if let Some(home_dir) = dirs::home_dir() { - let ssh_dir = Path::new(&home_dir).join(".ssh"); - for filename in ["id_ed25519_sk", "id_ed25519", "id_rsa"] { - let key_path = ssh_dir.join(filename); - if key_path.is_file() { - log::info!("found ssh key {key_path:?}"); - paths.push(key_path); - } - } - } - if paths.is_empty() { - log::info!("no ssh key found"); - } - paths -} +//! Sometimes callbacks are buried deep in library code, requiring user input. +//! This module offers an overcomplicated and fragile solution. + +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::mpsc::channel, +}; + +use anyhow::Result; +use jj_lib::{git::RemoteCallbacks, repo::MutableRepo}; +use tauri::{Emitter, Manager, Window}; + +use crate::{ + messages::{InputField, InputRequest}, + worker::WorkerCallbacks, + AppState, +}; + +pub struct FrontendCallbacks(pub Window); + +impl WorkerCallbacks for FrontendCallbacks { + fn with_git( + &self, + repo: &mut MutableRepo, + f: &dyn Fn(&mut MutableRepo, RemoteCallbacks<'_>) -> Result<()>, + ) -> Result<()> { + let mut cb = RemoteCallbacks::default(); + + let get_ssh_keys = &mut get_ssh_keys; + cb.get_ssh_keys = Some(get_ssh_keys); + + let get_password = &mut |url: &str, username: &str| { + self.request_input( + format!("Please enter a password for {} at {}", username, url), + ["Password"], + ) + .and_then(|mut fields| fields.remove("Password")) + }; + cb.get_password = Some(get_password); + + let get_username_password = &mut |url: &str| { + self.request_input( + format!("Please enter a username and password for {}", url), + ["Username", "Password"], + ) + .and_then(|mut fields| { + fields.remove("Username").and_then(|username| { + fields + .remove("Password") + .map(|password| (username, password)) + }) + }) + }; + cb.get_username_password = Some(get_username_password); + + f(repo, cb) + } + + fn select_remote(&self, choices: &[&str]) -> Option { + let response = self.request_input( + format!("Select a remote"), + [InputField { + label: "Select Remote".into(), + choices: choices.iter().map(|choice| choice.to_string()).collect(), + }], + ); + + response.and_then(|mut fields| fields.remove("Select Remote").to_owned()) + } +} + +impl FrontendCallbacks { + fn request_input, U: Into>( + &self, + detail: String, + fields: T, + ) -> Option> { + log::debug!("request input"); + + // initialise a channel to receive responses + let (tx, rx) = channel(); + self.0.state::().set_input(self.0.label(), tx); + + // send the request + match self.0.emit( + "gg://input", + InputRequest { + title: String::from("Git Login"), + detail, + fields: fields.into_iter().map(|field| field.into()).collect(), + }, + ) { + Ok(_) => (), + Err(err) => { + log::error!("input request failed: emit failed: {err}"); + return None; + } + } + + // wait for the response + match rx.recv() { + Ok(response) => { + if response.cancel { + log::error!("input request failed: input cancelled"); + None + } else { + Some(response.fields) + } + } + Err(err) => { + log::error!("input request failed: {err}"); + None + } + } + } +} + +// simplistic, but it's the same as the version in jj_cli::git_util +fn get_ssh_keys(_username: &str) -> Vec { + let mut paths = vec![]; + if let Some(home_dir) = dirs::home_dir() { + let ssh_dir = Path::new(&home_dir).join(".ssh"); + for filename in ["id_ed25519_sk", "id_ed25519", "id_rsa"] { + let key_path = ssh_dir.join(filename); + if key_path.is_file() { + log::info!("found ssh key {key_path:?}"); + paths.push(key_path); + } + } + } + if paths.is_empty() { + log::info!("no ssh key found"); + } + paths +} diff --git a/src-tauri/src/config/gg.toml b/src-tauri/src/config/gg.toml index 46f2dde..233d86e 100644 --- a/src-tauri/src/config/gg.toml +++ b/src-tauri/src/config/gg.toml @@ -1,20 +1,20 @@ -[gg.queries] -# Number of commits to load per call -log-page-size = 1000 - -# Some query settings will default to false instead of true if a repo has this many commits. -large-repo-heuristic = 100000 - -# Take a snapshot when the window gains focus; slow in large checkouts. -# When disabled, snapshots will still be created if you run commands. -# auto-snapshot = - -[gg.ui] -# Stores a list of recently opened directories for shell integration -recent-workspaces = [] - -# When set, branches that are local-only or remote-only will be visually indicated. -mark-unpushed-branches = true - -# "light" or "dark". If not set, your OS settings will be used. -# theme-override = +[gg.queries] +# Number of commits to load per call +log-page-size = 1000 + +# Some query settings will default to false instead of true if a repo has this many commits. +large-repo-heuristic = 100000 + +# Take a snapshot when the window gains focus; slow in large checkouts. +# When disabled, snapshots will still be created if you run commands. +# auto-snapshot = + +[gg.ui] +# Stores a list of recently opened directories for shell integration +recent-workspaces = [] + +# When set, branches that are local-only or remote-only will be visually indicated. +mark-unpushed-branches = true + +# "light" or "dark". If not set, your OS settings will be used. +# theme-override = diff --git a/src-tauri/src/config/mod.rs b/src-tauri/src/config/mod.rs index 2ff93eb..612c3eb 100644 --- a/src-tauri/src/config/mod.rs +++ b/src-tauri/src/config/mod.rs @@ -1,104 +1,104 @@ -use std::path::Path; - -use anyhow::{anyhow, Result}; -use config::{Config, ConfigError}; -use itertools::Itertools; -use jj_cli::config::LayeredConfigs; -use jj_lib::{ - revset::RevsetAliasesMap, - settings::{ConfigResultExt, UserSettings}, -}; - -pub trait GGSettings { - fn query_log_page_size(&self) -> usize; - fn query_large_repo_heuristic(&self) -> i64; - fn query_auto_snapshot(&self) -> Option; - fn ui_theme_override(&self) -> Option; - fn ui_mark_unpushed_branches(&self) -> bool; - #[allow(dead_code)] - fn ui_recent_workspaces(&self) -> Vec; -} - -impl GGSettings for UserSettings { - fn query_log_page_size(&self) -> usize { - self.config() - .get_int("gg.queries.log-page-size") - .unwrap_or(1000) as usize - } - - fn query_large_repo_heuristic(&self) -> i64 { - self.config() - .get_int("gg.queries.large-repo-heuristic") - .unwrap_or(100000) - } - - fn query_auto_snapshot(&self) -> Option { - self.config().get_bool("gg.queries.auto-snapshot").ok() - } - - fn ui_theme_override(&self) -> Option { - self.config().get_string("gg.ui.theme-override").ok() - } - - fn ui_mark_unpushed_branches(&self) -> bool { - self.config() - .get_bool("gg.ui.mark-unpushed-branches") - .unwrap_or(true) - } - - fn ui_recent_workspaces(&self) -> Vec { - let paths: Result, ConfigError> = self - .config() - .get_array("gg.ui.recent-workspaces") - .unwrap_or(vec![]) - .into_iter() - .map(|value| value.into_string()) - .collect(); - paths.unwrap_or(vec![]) - } -} - -pub fn read_config(repo_path: &Path) -> Result<(UserSettings, RevsetAliasesMap)> { - let defaults = Config::builder() - .add_source(jj_cli::config::default_config()) - .add_source(config::File::from_str( - include_str!("../config/gg.toml"), - config::FileFormat::Toml, - )) - .build()?; - - let mut configs = LayeredConfigs::from_environment(defaults); - configs.read_user_config()?; - configs.read_repo_config(repo_path)?; - - let settings = build_settings(&configs); - let aliases_map = build_aliases_map(&configs)?; - - Ok((settings, aliases_map)) -} - -fn build_settings(configs: &LayeredConfigs) -> UserSettings { - let config = configs.merge(); - UserSettings::from_config(config) -} - -fn build_aliases_map(layered_configs: &LayeredConfigs) -> Result { - const TABLE_KEY: &str = "revset-aliases"; - let mut aliases_map = RevsetAliasesMap::new(); - // Load from all config layers in order. 'f(x)' in default layer should be - // overridden by 'f(a)' in user. - for (_, config) in layered_configs.sources() { - let table = if let Some(table) = config.get_table(TABLE_KEY).optional()? { - table - } else { - continue; - }; - for (decl, value) in table.into_iter().sorted_by(|a, b| a.0.cmp(&b.0)) { - value - .into_string() - .map_err(|e| anyhow!(e)) - .and_then(|v| aliases_map.insert(&decl, v).map_err(|e| anyhow!(e)))?; - } - } - Ok(aliases_map) -} +use std::path::Path; + +use anyhow::{anyhow, Result}; +use config::{Config, ConfigError}; +use itertools::Itertools; +use jj_cli::config::LayeredConfigs; +use jj_lib::{ + revset::RevsetAliasesMap, + settings::{ConfigResultExt, UserSettings}, +}; + +pub trait GGSettings { + fn query_log_page_size(&self) -> usize; + fn query_large_repo_heuristic(&self) -> i64; + fn query_auto_snapshot(&self) -> Option; + fn ui_theme_override(&self) -> Option; + fn ui_mark_unpushed_branches(&self) -> bool; + #[allow(dead_code)] + fn ui_recent_workspaces(&self) -> Vec; +} + +impl GGSettings for UserSettings { + fn query_log_page_size(&self) -> usize { + self.config() + .get_int("gg.queries.log-page-size") + .unwrap_or(1000) as usize + } + + fn query_large_repo_heuristic(&self) -> i64 { + self.config() + .get_int("gg.queries.large-repo-heuristic") + .unwrap_or(100000) + } + + fn query_auto_snapshot(&self) -> Option { + self.config().get_bool("gg.queries.auto-snapshot").ok() + } + + fn ui_theme_override(&self) -> Option { + self.config().get_string("gg.ui.theme-override").ok() + } + + fn ui_mark_unpushed_branches(&self) -> bool { + self.config() + .get_bool("gg.ui.mark-unpushed-branches") + .unwrap_or(true) + } + + fn ui_recent_workspaces(&self) -> Vec { + let paths: Result, ConfigError> = self + .config() + .get_array("gg.ui.recent-workspaces") + .unwrap_or(vec![]) + .into_iter() + .map(|value| value.into_string()) + .collect(); + paths.unwrap_or(vec![]) + } +} + +pub fn read_config(repo_path: &Path) -> Result<(UserSettings, RevsetAliasesMap)> { + let defaults = Config::builder() + .add_source(jj_cli::config::default_config()) + .add_source(config::File::from_str( + include_str!("../config/gg.toml"), + config::FileFormat::Toml, + )) + .build()?; + + let mut configs = LayeredConfigs::from_environment(defaults); + configs.read_user_config()?; + configs.read_repo_config(repo_path)?; + + let settings = build_settings(&configs); + let aliases_map = build_aliases_map(&configs)?; + + Ok((settings, aliases_map)) +} + +fn build_settings(configs: &LayeredConfigs) -> UserSettings { + let config = configs.merge(); + UserSettings::from_config(config) +} + +fn build_aliases_map(layered_configs: &LayeredConfigs) -> Result { + const TABLE_KEY: &str = "revset-aliases"; + let mut aliases_map = RevsetAliasesMap::new(); + // Load from all config layers in order. 'f(x)' in default layer should be + // overridden by 'f(a)' in user. + for (_, config) in layered_configs.sources() { + let table = if let Some(table) = config.get_table(TABLE_KEY).optional()? { + table + } else { + continue; + }; + for (decl, value) in table.into_iter().sorted_by(|a, b| a.0.cmp(&b.0)) { + value + .into_string() + .map_err(|e| anyhow!(e)) + .and_then(|v| aliases_map.insert(&decl, v).map_err(|e| anyhow!(e)))?; + } + } + Ok(aliases_map) +} diff --git a/src-tauri/src/handler.rs b/src-tauri/src/handler.rs index 9821797..2250d39 100644 --- a/src-tauri/src/handler.rs +++ b/src-tauri/src/handler.rs @@ -1,40 +1,40 @@ -macro_rules! fatal { - ($result:expr) => { - match $result { - Ok(_) => (), - Err(err) => { - log::error!("{}: {:#}", stringify!($result), err); - panic!("{}: {:#}", stringify!($result), err); - } - } - }; -} - -macro_rules! nonfatal { - ($result:expr) => { - match $result { - Ok(x) => x, - Err(err) => { - log::error!("{}: {:#}", stringify!($result), err); - return; - } - } - }; -} - -#[allow(dead_code, unused_macros)] -macro_rules! optional { - ($result:expr) => { - match $result { - Ok(_) => (), - Err(err) => { - log::warn!("{}: {:#}", stringify!($result), err); - } - } - }; -} - -pub(crate) use fatal; -pub(crate) use nonfatal; -#[allow(unused_imports)] -pub(crate) use optional; +macro_rules! fatal { + ($result:expr) => { + match $result { + Ok(_) => (), + Err(err) => { + log::error!("{}: {:#}", stringify!($result), err); + panic!("{}: {:#}", stringify!($result), err); + } + } + }; +} + +macro_rules! nonfatal { + ($result:expr) => { + match $result { + Ok(x) => x, + Err(err) => { + log::error!("{}: {:#}", stringify!($result), err); + return; + } + } + }; +} + +#[allow(dead_code, unused_macros)] +macro_rules! optional { + ($result:expr) => { + match $result { + Ok(_) => (), + Err(err) => { + log::warn!("{}: {:#}", stringify!($result), err); + } + } + }; +} + +pub(crate) use fatal; +pub(crate) use nonfatal; +#[allow(unused_imports)] +pub(crate) use optional; diff --git a/src-tauri/src/menu.rs b/src-tauri/src/menu.rs index fd87bea..0c22569 100644 --- a/src-tauri/src/menu.rs +++ b/src-tauri/src/menu.rs @@ -1,518 +1,518 @@ -use anyhow::{anyhow, Context, Result}; -#[cfg(target_os = "macos")] -use tauri::menu::AboutMetadata; -use tauri::{ - menu::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu}, - AppHandle, Emitter, Manager, Window, Wry, -}; -use tauri_plugin_dialog::DialogExt; - -use crate::{ - handler, - messages::{Operand, RevHeader, StoreRef}, - AppState, -}; - -pub fn build_main(app_handle: &AppHandle) -> tauri::Result> { - #[cfg(target_os = "macos")] - let pkg_info = app_handle.package_info(); - #[cfg(target_os = "macos")] - let config = app_handle.config(); - #[cfg(target_os = "macos")] - let about_metadata = AboutMetadata { - name: Some("GG".into()), - version: Some(pkg_info.version.to_string()), - copyright: config.bundle.copyright.clone(), - authors: config.bundle.publisher.clone().map(|p| vec![p]), - ..Default::default() - }; - - let repo_menu = Submenu::with_items( - app_handle, - "Repository", - true, - &[ - &MenuItem::with_id( - app_handle, - "menu_repo_open", - "Open...", - true, - Some("cmdorctrl+o"), - )?, - &MenuItem::with_id(app_handle, "menu_repo_reopen", "Reopen", true, Some("f5"))?, - &PredefinedMenuItem::close_window(app_handle, Some("Close"))?, - ], - )?; - - let revision_menu = Submenu::with_id_and_items( - app_handle, - "revision", - "Revision", - true, - &[ - &MenuItem::with_id( - app_handle, - "menu_revision_new", - "New child", - true, - Some("cmdorctrl+n"), - )?, - &MenuItem::with_id( - app_handle, - "menu_revision_edit", - "Edit as working copy", - true, - None::<&str>, - )?, - &MenuItem::with_id( - app_handle, - "menu_revision_backout", - "Backout into working copy", - true, - None::<&str>, - )?, - &MenuItem::with_id( - app_handle, - "menu_revision_duplicate", - "Duplicate", - true, - None::<&str>, - )?, - &MenuItem::with_id( - app_handle, - "menu_revision_abandon", - "Abandon", - true, - None::<&str>, - )?, - &PredefinedMenuItem::separator(app_handle)?, - &MenuItem::with_id( - app_handle, - "menu_revision_squash", - "Squash into parent", - true, - None::<&str>, - )?, - &MenuItem::with_id( - app_handle, - "menu_revision_restore", - "Restore from parent", - true, - None::<&str>, - )?, - &PredefinedMenuItem::separator(app_handle)?, - &MenuItem::with_id( - app_handle, - "menu_revision_branch", - "Create branch", - true, - None::<&str>, - )?, - ], - )?; - - let edit_menu = Submenu::with_items( - app_handle, - "Edit", - true, - &[ - &PredefinedMenuItem::undo(app_handle, None)?, - &PredefinedMenuItem::redo(app_handle, None)?, - &PredefinedMenuItem::separator(app_handle)?, - &PredefinedMenuItem::cut(app_handle, None)?, - &PredefinedMenuItem::copy(app_handle, None)?, - &PredefinedMenuItem::paste(app_handle, None)?, - &PredefinedMenuItem::select_all(app_handle, None)?, - ], - )?; - - let menu = Menu::with_items( - app_handle, - &[ - #[cfg(target_os = "macos")] - &Submenu::with_items( - app_handle, - pkg_info.name.clone(), - true, - &[ - &PredefinedMenuItem::about(app_handle, None, Some(about_metadata))?, - &PredefinedMenuItem::separator(app_handle)?, - &PredefinedMenuItem::services(app_handle, None)?, - &PredefinedMenuItem::separator(app_handle)?, - &PredefinedMenuItem::hide(app_handle, None)?, - &PredefinedMenuItem::hide_others(app_handle, None)?, - &PredefinedMenuItem::separator(app_handle)?, - &PredefinedMenuItem::quit(app_handle, None)?, - ], - )?, - &repo_menu, - &revision_menu, - &edit_menu, - ], - )?; - - Ok(menu) -} - -pub fn build_context( - app_handle: &AppHandle, -) -> Result<(Menu, Menu, Menu), tauri::Error> { - let revision_menu = Menu::with_items( - app_handle, - &[ - &MenuItem::with_id(app_handle, "revision_new", "New child", true, None::<&str>)?, - &MenuItem::with_id( - app_handle, - "revision_edit", - "Edit as working copy", - true, - None::<&str>, - )?, - &MenuItem::with_id( - app_handle, - "revision_backout", - "Backout into working copy", - true, - None::<&str>, - )?, - &MenuItem::with_id( - app_handle, - "revision_duplicate", - "Duplicate", - true, - None::<&str>, - )?, - &MenuItem::with_id( - app_handle, - "revision_abandon", - "Abandon", - true, - None::<&str>, - )?, - &PredefinedMenuItem::separator(app_handle)?, - &MenuItem::with_id( - app_handle, - "revision_squash", - "Squash into parent", - true, - None::<&str>, - )?, - &MenuItem::with_id( - app_handle, - "revision_restore", - "Restore from parent", - true, - None::<&str>, - )?, - &PredefinedMenuItem::separator(app_handle)?, - &MenuItem::with_id( - app_handle, - "revision_branch", - "Create branch", - true, - None::<&str>, - )?, - ], - )?; - - let tree_menu = Menu::with_items( - app_handle, - &[ - &MenuItem::with_id( - app_handle, - "tree_squash", - "Squash into parent", - true, - None::<&str>, - )?, - &MenuItem::with_id( - app_handle, - "tree_restore", - "Restore from parent", - true, - None::<&str>, - )?, - ], - )?; - - let ref_menu = Menu::with_items( - app_handle, - &[ - &MenuItem::with_id(app_handle, "branch_track", "Track", true, None::<&str>)?, - &MenuItem::with_id(app_handle, "branch_untrack", "Untrack", true, None::<&str>)?, - &PredefinedMenuItem::separator(app_handle)?, - &MenuItem::with_id(app_handle, "branch_push_all", "Push", true, None::<&str>)?, - &MenuItem::with_id( - app_handle, - "branch_push_single", - "Push to remote...", - true, - None::<&str>, - )?, - &MenuItem::with_id(app_handle, "branch_fetch_all", "Fetch", true, None::<&str>)?, - &MenuItem::with_id( - app_handle, - "branch_fetch_single", - "Fetch from remote...", - true, - None::<&str>, - )?, - &PredefinedMenuItem::separator(app_handle)?, - &MenuItem::with_id(app_handle, "branch_rename", "Rename...", true, None::<&str>)?, - &MenuItem::with_id(app_handle, "branch_delete", "Delete", true, None::<&str>)?, - ], - )?; - - Ok((revision_menu, tree_menu, ref_menu)) -} - -// enables global menu items based on currently selected revision -pub fn handle_selection(menu: Menu, selection: Option) -> Result<()> { - let revision_submenu = menu - .get("revision") - .ok_or(anyhow!("Revision menu not found"))?; - let revision_submenu = revision_submenu.as_submenu_unchecked(); - - match selection { - None => { - revision_submenu.enable("menu_revision_new", false)?; - revision_submenu.enable("menu_revision_edit", false)?; - revision_submenu.enable("menu_revision_duplicate", false)?; - revision_submenu.enable("menu_revision_abandon", false)?; - revision_submenu.enable("menu_revision_squash", false)?; - revision_submenu.enable("menu_revision_restore", false)?; - } - Some(rev) => { - revision_submenu.enable("menu_revision_new", true)?; - revision_submenu.enable( - "menu_revision_edit", - !rev.is_immutable && !rev.is_working_copy, - )?; - revision_submenu.enable("menu_revision_backout", true)?; - revision_submenu.enable("menu_revision_duplicate", true)?; - revision_submenu.enable("menu_revision_abandon", !rev.is_immutable)?; - revision_submenu.enable( - "menu_revision_squash", - !rev.is_immutable && rev.parent_ids.len() == 1, - )?; - revision_submenu.enable( - "menu_revision_restore", - !rev.is_immutable && rev.parent_ids.len() == 1, - )?; - revision_submenu.enable("menu_revision_branch", true)?; - } - }; - - Ok(()) -} - -// enables context menu items for a revision and shows the menu -pub fn handle_context(window: Window, ctx: Operand) -> Result<()> { - log::debug!("handling context {ctx:?}"); - - let state = window.state::(); - let guard = state.0.lock().expect("state mutex poisoned"); - - match ctx { - Operand::Revision { header } => { - let context_menu = &guard - .get(window.label()) - .expect("session not found") - .revision_menu; - - context_menu.enable("revision_new", true)?; - context_menu.enable( - "revision_edit", - !header.is_immutable && !header.is_working_copy, - )?; - context_menu.enable("revision_backout", true)?; - context_menu.enable("revision_duplicate", true)?; - context_menu.enable("revision_abandon", !header.is_immutable)?; - context_menu.enable( - "revision_squash", - !header.is_immutable && header.parent_ids.len() == 1, - )?; - context_menu.enable( - "revision_restore", - !header.is_immutable && header.parent_ids.len() == 1, - )?; - context_menu.enable("revision_branch", true)?; - - window.popup_menu(context_menu)?; - } - Operand::Change { header, .. } => { - let context_menu = &guard - .get(window.label()) - .expect("session not found") - .tree_menu; - - context_menu.enable( - "tree_squash", - !header.is_immutable && header.parent_ids.len() == 1, - )?; - context_menu.enable( - "tree_restore", - !header.is_immutable && header.parent_ids.len() == 1, - )?; - - window.popup_menu(context_menu)?; - } - Operand::Ref { r#ref, .. } => { - let context_menu = &guard - .get(window.label()) - .expect("session not found") - .ref_menu; - - // give remotes a local, or undelete them - context_menu.enable( - "branch_track", - matches!( - r#ref, - StoreRef::RemoteBranch { - is_tracked: false, - .. - } - ), - )?; - - // remove a local's remotes, or a remote from its local - context_menu.enable( - "branch_untrack", - matches!( - r#ref, - StoreRef::LocalBranch { - ref tracking_remotes, - .. - } if !tracking_remotes.is_empty() - ) || matches!( - r#ref, - StoreRef::RemoteBranch { - is_synced: false, // we can *see* the remote ref, and - is_tracked: true, // it has a local, and - is_absent: false, // that local is somewhere else - .. - } - ), - )?; - - // push a local to its remotes, or finish a CLI delete - context_menu.enable("branch_push_all", - matches!(r#ref, StoreRef::LocalBranch { ref tracking_remotes, .. } if !tracking_remotes.is_empty()) || - matches!(r#ref, StoreRef::RemoteBranch { is_tracked: true, is_absent: true, .. }))?; - - // push a local to a selected remote, tracking first if necessary - context_menu.enable("branch_push_single", - matches!(r#ref, StoreRef::LocalBranch { potential_remotes, .. } if potential_remotes > 0))?; - - // fetch a local's remotes, or just a remote (unless we're deleting it; that would be silly) - context_menu.enable("branch_fetch_all", - matches!(r#ref, StoreRef::LocalBranch { ref tracking_remotes, .. } if !tracking_remotes.is_empty()) || - matches!(r#ref, StoreRef::RemoteBranch { is_tracked, is_absent, .. } if (!is_tracked || !is_absent)))?; - - // fetch a local, tracking first if necessary - context_menu.enable("branch_fetch_single", - matches!(r#ref, StoreRef::LocalBranch { available_remotes, .. } if available_remotes > 0))?; - - // rename a local, which also untracks remotes - context_menu.enable( - "branch_rename", - matches!(r#ref, StoreRef::LocalBranch { .. }), - )?; - - // remove a local, or make a remote absent - context_menu.enable( - "branch_delete", - !matches!( - r#ref, - StoreRef::RemoteBranch { - is_absent: true, - is_tracked: true, - .. - } - ), - )?; - - window.popup_menu(context_menu)?; - } - _ => (), // no popup required - }; - - Ok(()) -} - -pub fn handle_event(window: &Window, event: MenuEvent) -> Result<()> { - log::debug!("handling event {event:?}"); - - match event.id.0.as_str() { - "menu_repo_open" => repo_open(window), - "menu_repo_reopen" => repo_reopen(window), - "menu_revision_new" => window.emit("gg://menu/revision", "new")?, - "menu_revision_edit" => window.emit("gg://menu/revision", "edit")?, - "menu_revision_backout" => window.emit("gg://menu/revision", "backout")?, - "menu_revision_duplicate" => window.emit("gg://menu/revision", "duplicate")?, - "menu_revision_abandon" => window.emit("gg://menu/revision", "abandon")?, - "menu_revision_squash" => window.emit("gg://menu/revision", "squash")?, - "menu_revision_restore" => window.emit("gg://menu/revision", "restore")?, - "menu_revision_branch" => window.emit("gg://menu/revision", "branch")?, - "revision_new" => window.emit("gg://context/revision", "new")?, - "revision_edit" => window.emit("gg://context/revision", "edit")?, - "revision_backout" => window.emit("gg://context/revision", "backout")?, - "revision_duplicate" => window.emit("gg://context/revision", "duplicate")?, - "revision_abandon" => window.emit("gg://context/revision", "abandon")?, - "revision_squash" => window.emit("gg://context/revision", "squash")?, - "revision_restore" => window.emit("gg://context/revision", "restore")?, - "revision_branch" => window.emit("gg://context/revision", "branch")?, - "tree_squash" => window.emit("gg://context/tree", "squash")?, - "tree_restore" => window.emit("gg://context/tree", "restore")?, - "branch_track" => window.emit("gg://context/branch", "track")?, - "branch_untrack" => window.emit("gg://context/branch", "untrack")?, - "branch_push_all" => window.emit("gg://context/branch", "push-all")?, - "branch_push_single" => window.emit("gg://context/branch", "push-single")?, - "branch_fetch_all" => window.emit("gg://context/branch", "fetch-all")?, - "branch_fetch_single" => window.emit("gg://context/branch", "fetch-single")?, - "branch_rename" => window.emit("gg://context/branch", "rename")?, - "branch_delete" => window.emit("gg://context/branch", "delete")?, - _ => (), - }; - - Ok(()) -} - -pub fn repo_open(window: &Window) { - let window = window.clone(); - window.dialog().file().pick_folder(move |picked| { - if let Some(cwd) = picked { - handler::fatal!( - crate::try_open_repository(&window, Some(cwd)).context("try_open_repository") - ); - } - }); -} - -fn repo_reopen(window: &Window) { - handler::fatal!(crate::try_open_repository(window, None).context("try_open_repository")); -} - -trait Enabler { - fn enable(&self, id: &str, value: bool) -> tauri::Result<()>; -} - -impl Enabler for Menu { - fn enable(&self, id: &str, value: bool) -> tauri::Result<()> { - if let Some(item) = self.get(id).as_ref().and_then(|item| item.as_menuitem()) { - item.set_enabled(value) - } else { - Ok(()) - } - } -} - -impl Enabler for Submenu { - fn enable(&self, id: &str, value: bool) -> tauri::Result<()> { - if let Some(item) = self.get(id).as_ref().and_then(|item| item.as_menuitem()) { - item.set_enabled(value) - } else { - Ok(()) - } - } -} +use anyhow::{anyhow, Context, Result}; +#[cfg(target_os = "macos")] +use tauri::menu::AboutMetadata; +use tauri::{ + menu::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu}, + AppHandle, Emitter, Manager, Window, Wry, +}; +use tauri_plugin_dialog::DialogExt; + +use crate::{ + handler, + messages::{Operand, RevHeader, StoreRef}, + AppState, +}; + +pub fn build_main(app_handle: &AppHandle) -> tauri::Result> { + #[cfg(target_os = "macos")] + let pkg_info = app_handle.package_info(); + #[cfg(target_os = "macos")] + let config = app_handle.config(); + #[cfg(target_os = "macos")] + let about_metadata = AboutMetadata { + name: Some("GG".into()), + version: Some(pkg_info.version.to_string()), + copyright: config.bundle.copyright.clone(), + authors: config.bundle.publisher.clone().map(|p| vec![p]), + ..Default::default() + }; + + let repo_menu = Submenu::with_items( + app_handle, + "Repository", + true, + &[ + &MenuItem::with_id( + app_handle, + "menu_repo_open", + "Open...", + true, + Some("cmdorctrl+o"), + )?, + &MenuItem::with_id(app_handle, "menu_repo_reopen", "Reopen", true, Some("f5"))?, + &PredefinedMenuItem::close_window(app_handle, Some("Close"))?, + ], + )?; + + let revision_menu = Submenu::with_id_and_items( + app_handle, + "revision", + "Revision", + true, + &[ + &MenuItem::with_id( + app_handle, + "menu_revision_new", + "New child", + true, + Some("cmdorctrl+n"), + )?, + &MenuItem::with_id( + app_handle, + "menu_revision_edit", + "Edit as working copy", + true, + None::<&str>, + )?, + &MenuItem::with_id( + app_handle, + "menu_revision_backout", + "Backout into working copy", + true, + None::<&str>, + )?, + &MenuItem::with_id( + app_handle, + "menu_revision_duplicate", + "Duplicate", + true, + None::<&str>, + )?, + &MenuItem::with_id( + app_handle, + "menu_revision_abandon", + "Abandon", + true, + None::<&str>, + )?, + &PredefinedMenuItem::separator(app_handle)?, + &MenuItem::with_id( + app_handle, + "menu_revision_squash", + "Squash into parent", + true, + None::<&str>, + )?, + &MenuItem::with_id( + app_handle, + "menu_revision_restore", + "Restore from parent", + true, + None::<&str>, + )?, + &PredefinedMenuItem::separator(app_handle)?, + &MenuItem::with_id( + app_handle, + "menu_revision_branch", + "Create branch", + true, + None::<&str>, + )?, + ], + )?; + + let edit_menu = Submenu::with_items( + app_handle, + "Edit", + true, + &[ + &PredefinedMenuItem::undo(app_handle, None)?, + &PredefinedMenuItem::redo(app_handle, None)?, + &PredefinedMenuItem::separator(app_handle)?, + &PredefinedMenuItem::cut(app_handle, None)?, + &PredefinedMenuItem::copy(app_handle, None)?, + &PredefinedMenuItem::paste(app_handle, None)?, + &PredefinedMenuItem::select_all(app_handle, None)?, + ], + )?; + + let menu = Menu::with_items( + app_handle, + &[ + #[cfg(target_os = "macos")] + &Submenu::with_items( + app_handle, + pkg_info.name.clone(), + true, + &[ + &PredefinedMenuItem::about(app_handle, None, Some(about_metadata))?, + &PredefinedMenuItem::separator(app_handle)?, + &PredefinedMenuItem::services(app_handle, None)?, + &PredefinedMenuItem::separator(app_handle)?, + &PredefinedMenuItem::hide(app_handle, None)?, + &PredefinedMenuItem::hide_others(app_handle, None)?, + &PredefinedMenuItem::separator(app_handle)?, + &PredefinedMenuItem::quit(app_handle, None)?, + ], + )?, + &repo_menu, + &revision_menu, + &edit_menu, + ], + )?; + + Ok(menu) +} + +pub fn build_context( + app_handle: &AppHandle, +) -> Result<(Menu, Menu, Menu), tauri::Error> { + let revision_menu = Menu::with_items( + app_handle, + &[ + &MenuItem::with_id(app_handle, "revision_new", "New child", true, None::<&str>)?, + &MenuItem::with_id( + app_handle, + "revision_edit", + "Edit as working copy", + true, + None::<&str>, + )?, + &MenuItem::with_id( + app_handle, + "revision_backout", + "Backout into working copy", + true, + None::<&str>, + )?, + &MenuItem::with_id( + app_handle, + "revision_duplicate", + "Duplicate", + true, + None::<&str>, + )?, + &MenuItem::with_id( + app_handle, + "revision_abandon", + "Abandon", + true, + None::<&str>, + )?, + &PredefinedMenuItem::separator(app_handle)?, + &MenuItem::with_id( + app_handle, + "revision_squash", + "Squash into parent", + true, + None::<&str>, + )?, + &MenuItem::with_id( + app_handle, + "revision_restore", + "Restore from parent", + true, + None::<&str>, + )?, + &PredefinedMenuItem::separator(app_handle)?, + &MenuItem::with_id( + app_handle, + "revision_branch", + "Create branch", + true, + None::<&str>, + )?, + ], + )?; + + let tree_menu = Menu::with_items( + app_handle, + &[ + &MenuItem::with_id( + app_handle, + "tree_squash", + "Squash into parent", + true, + None::<&str>, + )?, + &MenuItem::with_id( + app_handle, + "tree_restore", + "Restore from parent", + true, + None::<&str>, + )?, + ], + )?; + + let ref_menu = Menu::with_items( + app_handle, + &[ + &MenuItem::with_id(app_handle, "branch_track", "Track", true, None::<&str>)?, + &MenuItem::with_id(app_handle, "branch_untrack", "Untrack", true, None::<&str>)?, + &PredefinedMenuItem::separator(app_handle)?, + &MenuItem::with_id(app_handle, "branch_push_all", "Push", true, None::<&str>)?, + &MenuItem::with_id( + app_handle, + "branch_push_single", + "Push to remote...", + true, + None::<&str>, + )?, + &MenuItem::with_id(app_handle, "branch_fetch_all", "Fetch", true, None::<&str>)?, + &MenuItem::with_id( + app_handle, + "branch_fetch_single", + "Fetch from remote...", + true, + None::<&str>, + )?, + &PredefinedMenuItem::separator(app_handle)?, + &MenuItem::with_id(app_handle, "branch_rename", "Rename...", true, None::<&str>)?, + &MenuItem::with_id(app_handle, "branch_delete", "Delete", true, None::<&str>)?, + ], + )?; + + Ok((revision_menu, tree_menu, ref_menu)) +} + +// enables global menu items based on currently selected revision +pub fn handle_selection(menu: Menu, selection: Option) -> Result<()> { + let revision_submenu = menu + .get("revision") + .ok_or(anyhow!("Revision menu not found"))?; + let revision_submenu = revision_submenu.as_submenu_unchecked(); + + match selection { + None => { + revision_submenu.enable("menu_revision_new", false)?; + revision_submenu.enable("menu_revision_edit", false)?; + revision_submenu.enable("menu_revision_duplicate", false)?; + revision_submenu.enable("menu_revision_abandon", false)?; + revision_submenu.enable("menu_revision_squash", false)?; + revision_submenu.enable("menu_revision_restore", false)?; + } + Some(rev) => { + revision_submenu.enable("menu_revision_new", true)?; + revision_submenu.enable( + "menu_revision_edit", + !rev.is_immutable && !rev.is_working_copy, + )?; + revision_submenu.enable("menu_revision_backout", true)?; + revision_submenu.enable("menu_revision_duplicate", true)?; + revision_submenu.enable("menu_revision_abandon", !rev.is_immutable)?; + revision_submenu.enable( + "menu_revision_squash", + !rev.is_immutable && rev.parent_ids.len() == 1, + )?; + revision_submenu.enable( + "menu_revision_restore", + !rev.is_immutable && rev.parent_ids.len() == 1, + )?; + revision_submenu.enable("menu_revision_branch", true)?; + } + }; + + Ok(()) +} + +// enables context menu items for a revision and shows the menu +pub fn handle_context(window: Window, ctx: Operand) -> Result<()> { + log::debug!("handling context {ctx:?}"); + + let state = window.state::(); + let guard = state.0.lock().expect("state mutex poisoned"); + + match ctx { + Operand::Revision { header } => { + let context_menu = &guard + .get(window.label()) + .expect("session not found") + .revision_menu; + + context_menu.enable("revision_new", true)?; + context_menu.enable( + "revision_edit", + !header.is_immutable && !header.is_working_copy, + )?; + context_menu.enable("revision_backout", true)?; + context_menu.enable("revision_duplicate", true)?; + context_menu.enable("revision_abandon", !header.is_immutable)?; + context_menu.enable( + "revision_squash", + !header.is_immutable && header.parent_ids.len() == 1, + )?; + context_menu.enable( + "revision_restore", + !header.is_immutable && header.parent_ids.len() == 1, + )?; + context_menu.enable("revision_branch", true)?; + + window.popup_menu(context_menu)?; + } + Operand::Change { header, .. } => { + let context_menu = &guard + .get(window.label()) + .expect("session not found") + .tree_menu; + + context_menu.enable( + "tree_squash", + !header.is_immutable && header.parent_ids.len() == 1, + )?; + context_menu.enable( + "tree_restore", + !header.is_immutable && header.parent_ids.len() == 1, + )?; + + window.popup_menu(context_menu)?; + } + Operand::Ref { r#ref, .. } => { + let context_menu = &guard + .get(window.label()) + .expect("session not found") + .ref_menu; + + // give remotes a local, or undelete them + context_menu.enable( + "branch_track", + matches!( + r#ref, + StoreRef::RemoteBranch { + is_tracked: false, + .. + } + ), + )?; + + // remove a local's remotes, or a remote from its local + context_menu.enable( + "branch_untrack", + matches!( + r#ref, + StoreRef::LocalBranch { + ref tracking_remotes, + .. + } if !tracking_remotes.is_empty() + ) || matches!( + r#ref, + StoreRef::RemoteBranch { + is_synced: false, // we can *see* the remote ref, and + is_tracked: true, // it has a local, and + is_absent: false, // that local is somewhere else + .. + } + ), + )?; + + // push a local to its remotes, or finish a CLI delete + context_menu.enable("branch_push_all", + matches!(r#ref, StoreRef::LocalBranch { ref tracking_remotes, .. } if !tracking_remotes.is_empty()) || + matches!(r#ref, StoreRef::RemoteBranch { is_tracked: true, is_absent: true, .. }))?; + + // push a local to a selected remote, tracking first if necessary + context_menu.enable("branch_push_single", + matches!(r#ref, StoreRef::LocalBranch { potential_remotes, .. } if potential_remotes > 0))?; + + // fetch a local's remotes, or just a remote (unless we're deleting it; that would be silly) + context_menu.enable("branch_fetch_all", + matches!(r#ref, StoreRef::LocalBranch { ref tracking_remotes, .. } if !tracking_remotes.is_empty()) || + matches!(r#ref, StoreRef::RemoteBranch { is_tracked, is_absent, .. } if (!is_tracked || !is_absent)))?; + + // fetch a local, tracking first if necessary + context_menu.enable("branch_fetch_single", + matches!(r#ref, StoreRef::LocalBranch { available_remotes, .. } if available_remotes > 0))?; + + // rename a local, which also untracks remotes + context_menu.enable( + "branch_rename", + matches!(r#ref, StoreRef::LocalBranch { .. }), + )?; + + // remove a local, or make a remote absent + context_menu.enable( + "branch_delete", + !matches!( + r#ref, + StoreRef::RemoteBranch { + is_absent: true, + is_tracked: true, + .. + } + ), + )?; + + window.popup_menu(context_menu)?; + } + _ => (), // no popup required + }; + + Ok(()) +} + +pub fn handle_event(window: &Window, event: MenuEvent) -> Result<()> { + log::debug!("handling event {event:?}"); + + match event.id.0.as_str() { + "menu_repo_open" => repo_open(window), + "menu_repo_reopen" => repo_reopen(window), + "menu_revision_new" => window.emit("gg://menu/revision", "new")?, + "menu_revision_edit" => window.emit("gg://menu/revision", "edit")?, + "menu_revision_backout" => window.emit("gg://menu/revision", "backout")?, + "menu_revision_duplicate" => window.emit("gg://menu/revision", "duplicate")?, + "menu_revision_abandon" => window.emit("gg://menu/revision", "abandon")?, + "menu_revision_squash" => window.emit("gg://menu/revision", "squash")?, + "menu_revision_restore" => window.emit("gg://menu/revision", "restore")?, + "menu_revision_branch" => window.emit("gg://menu/revision", "branch")?, + "revision_new" => window.emit("gg://context/revision", "new")?, + "revision_edit" => window.emit("gg://context/revision", "edit")?, + "revision_backout" => window.emit("gg://context/revision", "backout")?, + "revision_duplicate" => window.emit("gg://context/revision", "duplicate")?, + "revision_abandon" => window.emit("gg://context/revision", "abandon")?, + "revision_squash" => window.emit("gg://context/revision", "squash")?, + "revision_restore" => window.emit("gg://context/revision", "restore")?, + "revision_branch" => window.emit("gg://context/revision", "branch")?, + "tree_squash" => window.emit("gg://context/tree", "squash")?, + "tree_restore" => window.emit("gg://context/tree", "restore")?, + "branch_track" => window.emit("gg://context/branch", "track")?, + "branch_untrack" => window.emit("gg://context/branch", "untrack")?, + "branch_push_all" => window.emit("gg://context/branch", "push-all")?, + "branch_push_single" => window.emit("gg://context/branch", "push-single")?, + "branch_fetch_all" => window.emit("gg://context/branch", "fetch-all")?, + "branch_fetch_single" => window.emit("gg://context/branch", "fetch-single")?, + "branch_rename" => window.emit("gg://context/branch", "rename")?, + "branch_delete" => window.emit("gg://context/branch", "delete")?, + _ => (), + }; + + Ok(()) +} + +pub fn repo_open(window: &Window) { + let window = window.clone(); + window.dialog().file().pick_folder(move |picked| { + if let Some(cwd) = picked { + handler::fatal!( + crate::try_open_repository(&window, Some(cwd)).context("try_open_repository") + ); + } + }); +} + +fn repo_reopen(window: &Window) { + handler::fatal!(crate::try_open_repository(window, None).context("try_open_repository")); +} + +trait Enabler { + fn enable(&self, id: &str, value: bool) -> tauri::Result<()>; +} + +impl Enabler for Menu { + fn enable(&self, id: &str, value: bool) -> tauri::Result<()> { + if let Some(item) = self.get(id).as_ref().and_then(|item| item.as_menuitem()) { + item.set_enabled(value) + } else { + Ok(()) + } + } +} + +impl Enabler for Submenu { + fn enable(&self, id: &str, value: bool) -> tauri::Result<()> { + if let Some(item) = self.get(id).as_ref().and_then(|item| item.as_menuitem()) { + item.set_enabled(value) + } else { + Ok(()) + } + } +} diff --git a/src-tauri/src/messages/mod.rs b/src-tauri/src/messages/mod.rs index 4f0af88..b386bc9 100644 --- a/src-tauri/src/messages/mod.rs +++ b/src-tauri/src/messages/mod.rs @@ -1,227 +1,227 @@ -//! Message types used to communicate between backend and frontend - -mod mutations; -mod queries; - -pub use mutations::*; -pub use queries::*; - -use std::{collections::HashMap, path::Path}; - -use anyhow::{anyhow, Result}; -use serde::{Deserialize, Serialize}; -#[cfg(feature = "ts-rs")] -use ts_rs::TS; - -/// Utility type used to abstract crlf/
/etc -#[derive(Serialize, Deserialize, Clone, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct MultilineString { - pub lines: Vec, -} - -impl<'a, T> From for MultilineString -where - T: Into<&'a str>, -{ - fn from(value: T) -> Self { - MultilineString { - lines: value.into().split("\n").map(|l| l.to_owned()).collect(), - } - } -} - -/// Utility type used for platform-specific display -#[derive(Serialize, Deserialize, Debug, Clone)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct DisplayPath(pub String); - -impl> From for DisplayPath { - fn from(value: T) -> Self { - DisplayPath( - dunce::simplified(value.as_ref()) - .to_string_lossy() - .to_string(), - ) - } -} - -/// Utility type used for round-tripping -#[derive(Serialize, Deserialize, Debug, Clone)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct TreePath { - pub repo_path: String, - pub relative_path: DisplayPath, -} - -#[derive(Serialize, Clone)] -#[serde(tag = "type")] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] - -pub enum RepoConfig { - #[allow(dead_code)] // used by frontend - Initial, - Workspace { - absolute_path: DisplayPath, - git_remotes: Vec, - default_query: String, - latest_query: String, - status: RepoStatus, - theme_override: Option, - mark_unpushed_branches: bool, - }, - #[allow(dead_code)] // used by frontend - TimeoutError, - LoadError { - absolute_path: DisplayPath, - message: String, - }, - WorkerError { - message: String, - }, -} - -#[derive(Serialize, Clone, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct RepoStatus { - pub operation_description: String, - pub working_copy: CommitId, -} - -/// Branch or tag name with metadata. -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(tag = "type")] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub enum StoreRef { - LocalBranch { - branch_name: String, - has_conflict: bool, - /// Synchronized with all tracking remotes - is_synced: bool, - /// Actual and potential remotes - tracking_remotes: Vec, - available_remotes: usize, - potential_remotes: usize, - }, - RemoteBranch { - branch_name: String, - remote_name: String, - has_conflict: bool, - /// Tracking remote ref is synchronized with local ref - is_synced: bool, - /// Has local ref - is_tracked: bool, - /// Local ref has been deleted - is_absent: bool, - }, - Tag { - tag_name: String, - }, -} - -impl StoreRef { - pub fn as_branch(&self) -> Result<&str> { - match self { - StoreRef::LocalBranch { branch_name, .. } => Ok(&branch_name), - StoreRef::RemoteBranch { branch_name, .. } => Ok(&branch_name), - _ => Err(anyhow!("not a local branch")), - } - } -} - -/// Refers to one of the repository's manipulatable objects -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(tag = "type")] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub enum Operand { - Repository, - Revision { - header: RevHeader, - }, - Merge { - header: RevHeader, - }, - Parent { - header: RevHeader, - child: RevHeader, - }, - Change { - header: RevHeader, - path: TreePath, // someday: hunks - }, - Ref { - header: RevHeader, - r#ref: StoreRef, - }, -} - -#[derive(Serialize, Debug, Clone)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct InputRequest { - pub title: String, - pub detail: String, - pub fields: Vec, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct InputResponse { - pub cancel: bool, - pub fields: HashMap, -} - -#[derive(Serialize, Debug, Clone)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct InputField { - pub label: String, - pub choices: Vec, -} - -impl From<&str> for InputField { - fn from(label: &str) -> Self { - InputField { - label: label.to_owned(), - choices: vec![], - } - } -} +//! Message types used to communicate between backend and frontend + +mod mutations; +mod queries; + +pub use mutations::*; +pub use queries::*; + +use std::{collections::HashMap, path::Path}; + +use anyhow::{anyhow, Result}; +use serde::{Deserialize, Serialize}; +#[cfg(feature = "ts-rs")] +use ts_rs::TS; + +/// Utility type used to abstract crlf/
/etc +#[derive(Serialize, Deserialize, Clone, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct MultilineString { + pub lines: Vec, +} + +impl<'a, T> From for MultilineString +where + T: Into<&'a str>, +{ + fn from(value: T) -> Self { + MultilineString { + lines: value.into().split("\n").map(|l| l.to_owned()).collect(), + } + } +} + +/// Utility type used for platform-specific display +#[derive(Serialize, Deserialize, Debug, Clone)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct DisplayPath(pub String); + +impl> From for DisplayPath { + fn from(value: T) -> Self { + DisplayPath( + dunce::simplified(value.as_ref()) + .to_string_lossy() + .to_string(), + ) + } +} + +/// Utility type used for round-tripping +#[derive(Serialize, Deserialize, Debug, Clone)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct TreePath { + pub repo_path: String, + pub relative_path: DisplayPath, +} + +#[derive(Serialize, Clone)] +#[serde(tag = "type")] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] + +pub enum RepoConfig { + #[allow(dead_code)] // used by frontend + Initial, + Workspace { + absolute_path: DisplayPath, + git_remotes: Vec, + default_query: String, + latest_query: String, + status: RepoStatus, + theme_override: Option, + mark_unpushed_branches: bool, + }, + #[allow(dead_code)] // used by frontend + TimeoutError, + LoadError { + absolute_path: DisplayPath, + message: String, + }, + WorkerError { + message: String, + }, +} + +#[derive(Serialize, Clone, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct RepoStatus { + pub operation_description: String, + pub working_copy: CommitId, +} + +/// Branch or tag name with metadata. +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(tag = "type")] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub enum StoreRef { + LocalBranch { + branch_name: String, + has_conflict: bool, + /// Synchronized with all tracking remotes + is_synced: bool, + /// Actual and potential remotes + tracking_remotes: Vec, + available_remotes: usize, + potential_remotes: usize, + }, + RemoteBranch { + branch_name: String, + remote_name: String, + has_conflict: bool, + /// Tracking remote ref is synchronized with local ref + is_synced: bool, + /// Has local ref + is_tracked: bool, + /// Local ref has been deleted + is_absent: bool, + }, + Tag { + tag_name: String, + }, +} + +impl StoreRef { + pub fn as_branch(&self) -> Result<&str> { + match self { + StoreRef::LocalBranch { branch_name, .. } => Ok(&branch_name), + StoreRef::RemoteBranch { branch_name, .. } => Ok(&branch_name), + _ => Err(anyhow!("not a local branch")), + } + } +} + +/// Refers to one of the repository's manipulatable objects +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "type")] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub enum Operand { + Repository, + Revision { + header: RevHeader, + }, + Merge { + header: RevHeader, + }, + Parent { + header: RevHeader, + child: RevHeader, + }, + Change { + header: RevHeader, + path: TreePath, // someday: hunks + }, + Ref { + header: RevHeader, + r#ref: StoreRef, + }, +} + +#[derive(Serialize, Debug, Clone)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct InputRequest { + pub title: String, + pub detail: String, + pub fields: Vec, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct InputResponse { + pub cancel: bool, + pub fields: HashMap, +} + +#[derive(Serialize, Debug, Clone)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct InputField { + pub label: String, + pub choices: Vec, +} + +impl From<&str> for InputField { + fn from(label: &str) -> Self { + InputField { + label: label.to_owned(), + choices: vec![], + } + } +} diff --git a/src-tauri/src/messages/mutations.rs b/src-tauri/src/messages/mutations.rs index 9953c25..0717766 100644 --- a/src-tauri/src/messages/mutations.rs +++ b/src-tauri/src/messages/mutations.rs @@ -1,263 +1,263 @@ -use super::*; - -/// Common result type for mutating commands -#[derive(Serialize, Clone, Debug)] -#[serde(tag = "type")] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub enum MutationResult { - Unchanged, - Updated { - new_status: RepoStatus, - }, - UpdatedSelection { - new_status: RepoStatus, - new_selection: RevHeader, - }, - PreconditionError { - message: String, - }, - InternalError { - message: MultilineString, - }, -} - -/// Makes a revision the working copy -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct CheckoutRevision { - pub id: RevId, -} - -/// Creates a new revision and makes it the working copy -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct CreateRevision { - pub parent_ids: Vec, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct InsertRevision { - pub id: RevId, - pub after_id: RevId, - pub before_id: RevId, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct MoveRevision { - pub id: RevId, - pub parent_ids: Vec, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct MoveSource { - pub id: RevId, - pub parent_ids: Vec, -} - -/// Updates a revision's description -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct DescribeRevision { - pub id: RevId, - pub new_description: String, - pub reset_author: bool, -} - -/// Creates a copy of the selected revisions with the same parents and content -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct DuplicateRevisions { - pub ids: Vec, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct AbandonRevisions { - pub ids: Vec, -} - -/// Adds changes to the working copy which reverse the effect of the selected revisions -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct BackoutRevisions { - pub ids: Vec, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct MoveChanges { - pub from_id: RevId, - pub to_id: CommitId, // limitation: we don't know parent chids because they are more expensive to look up - pub paths: Vec, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct CopyChanges { - pub from_id: CommitId, // limitation: we don't know parent chids because they are more expensive to look up - pub to_id: RevId, - pub paths: Vec, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct TrackBranch { - pub r#ref: StoreRef, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct UntrackBranch { - pub r#ref: StoreRef, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct RenameBranch { - pub r#ref: StoreRef, - pub new_name: String, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct CreateRef { - pub id: RevId, - pub r#ref: StoreRef, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct DeleteRef { - pub r#ref: StoreRef, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct MoveRef { - pub r#ref: StoreRef, - pub to_id: RevId, -} - -/// XXX pushes all branches of a remote, all remotes of a branch, or one remotes of a branch - split up? -#[derive(Deserialize, Debug)] -#[serde(tag = "type")] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub enum GitPush { - AllBranches { - remote_name: String, - }, - AllRemotes { - branch_ref: StoreRef, - }, - RemoteBranch { - remote_name: String, - branch_ref: StoreRef, - }, -} - -#[derive(Deserialize, Debug)] -#[serde(tag = "type")] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub enum GitFetch { - AllBranches { - remote_name: String, - }, - AllRemotes { - branch_ref: StoreRef, - }, - RemoteBranch { - remote_name: String, - branch_ref: StoreRef, - }, -} - -#[derive(Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct UndoOperation; +use super::*; + +/// Common result type for mutating commands +#[derive(Serialize, Clone, Debug)] +#[serde(tag = "type")] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub enum MutationResult { + Unchanged, + Updated { + new_status: RepoStatus, + }, + UpdatedSelection { + new_status: RepoStatus, + new_selection: RevHeader, + }, + PreconditionError { + message: String, + }, + InternalError { + message: MultilineString, + }, +} + +/// Makes a revision the working copy +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct CheckoutRevision { + pub id: RevId, +} + +/// Creates a new revision and makes it the working copy +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct CreateRevision { + pub parent_ids: Vec, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct InsertRevision { + pub id: RevId, + pub after_id: RevId, + pub before_id: RevId, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct MoveRevision { + pub id: RevId, + pub parent_ids: Vec, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct MoveSource { + pub id: RevId, + pub parent_ids: Vec, +} + +/// Updates a revision's description +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct DescribeRevision { + pub id: RevId, + pub new_description: String, + pub reset_author: bool, +} + +/// Creates a copy of the selected revisions with the same parents and content +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct DuplicateRevisions { + pub ids: Vec, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct AbandonRevisions { + pub ids: Vec, +} + +/// Adds changes to the working copy which reverse the effect of the selected revisions +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct BackoutRevisions { + pub ids: Vec, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct MoveChanges { + pub from_id: RevId, + pub to_id: CommitId, // limitation: we don't know parent chids because they are more expensive to look up + pub paths: Vec, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct CopyChanges { + pub from_id: CommitId, // limitation: we don't know parent chids because they are more expensive to look up + pub to_id: RevId, + pub paths: Vec, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct TrackBranch { + pub r#ref: StoreRef, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct UntrackBranch { + pub r#ref: StoreRef, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct RenameBranch { + pub r#ref: StoreRef, + pub new_name: String, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct CreateRef { + pub id: RevId, + pub r#ref: StoreRef, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct DeleteRef { + pub r#ref: StoreRef, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct MoveRef { + pub r#ref: StoreRef, + pub to_id: RevId, +} + +/// XXX pushes all branches of a remote, all remotes of a branch, or one remotes of a branch - split up? +#[derive(Deserialize, Debug)] +#[serde(tag = "type")] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub enum GitPush { + AllBranches { + remote_name: String, + }, + AllRemotes { + branch_ref: StoreRef, + }, + RemoteBranch { + remote_name: String, + branch_ref: StoreRef, + }, +} + +#[derive(Deserialize, Debug)] +#[serde(tag = "type")] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub enum GitFetch { + AllBranches { + remote_name: String, + }, + AllRemotes { + branch_ref: StoreRef, + }, + RemoteBranch { + remote_name: String, + branch_ref: StoreRef, + }, +} + +#[derive(Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct UndoOperation; diff --git a/src-tauri/src/messages/queries.rs b/src-tauri/src/messages/queries.rs index 1698718..92c022b 100644 --- a/src-tauri/src/messages/queries.rs +++ b/src-tauri/src/messages/queries.rs @@ -1,287 +1,287 @@ -use chrono::{offset::LocalResult, DateTime, FixedOffset, Local, TimeZone, Utc}; -use jj_lib::backend::{Signature, Timestamp}; - -use super::*; - -/// A change or commit id with a disambiguated prefix -#[allow(dead_code)] // the frontend needs these structs kept in sync -pub trait Id { - fn hex(&self) -> &String; - fn prefix(&self) -> &String; - fn rest(&self) -> &String; -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(tag = "type")] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct CommitId { - pub hex: String, - pub prefix: String, - pub rest: String, -} - -impl Id for CommitId { - fn hex(&self) -> &String { - &self.hex - } - fn prefix(&self) -> &String { - &self.prefix - } - fn rest(&self) -> &String { - &self.rest - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(tag = "type")] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct ChangeId { - pub hex: String, - pub prefix: String, - pub rest: String, -} - -impl Id for ChangeId { - fn hex(&self) -> &String { - &self.hex - } - fn prefix(&self) -> &String { - &self.prefix - } - fn rest(&self) -> &String { - &self.rest - } -} - -/// A pair of ids representing the ui's view of a revision. -/// The worker may use one or both depending on policy. -#[derive(Serialize, Deserialize, Clone, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct RevId { - pub change: ChangeId, - pub commit: CommitId, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct RevHeader { - pub id: RevId, - pub description: MultilineString, - pub author: RevAuthor, - pub has_conflict: bool, - pub is_working_copy: bool, - pub is_immutable: bool, - pub refs: Vec, - pub parent_ids: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct RevAuthor { - pub email: String, - pub name: String, - pub timestamp: chrono::DateTime, -} - -impl TryFrom<&Signature> for RevAuthor { - type Error = anyhow::Error; - - fn try_from(value: &Signature) -> Result { - Ok(RevAuthor { - name: value.name.clone(), - email: value.email.clone(), - timestamp: format_timestamp(&value.timestamp)?.with_timezone(&Local), - }) - } -} - -#[derive(Serialize, Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct RevChange { - pub kind: ChangeKind, - pub path: TreePath, - pub has_conflict: bool, - pub hunks: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct RevConflict { - pub path: TreePath, - pub hunk: ChangeHunk, -} - -#[derive(Serialize, Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub enum ChangeKind { - None, - Added, - Deleted, - Modified, -} - -#[derive(Serialize, Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct ChangeHunk { - pub location: HunkLocation, - pub lines: MultilineString, -} - -#[derive(Serialize, Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct HunkLocation { - pub from_file: FileRange, - pub to_file: FileRange, -} - -#[derive(Serialize, Deserialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct FileRange { - pub start: usize, - pub len: usize, -} - -#[derive(Serialize, Debug)] -#[serde(tag = "type")] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub enum RevResult { - NotFound { - id: RevId, - }, - Detail { - header: RevHeader, - parents: Vec, - changes: Vec, - conflicts: Vec, - }, -} - -#[derive(Serialize, Clone, Copy, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct LogCoordinates(pub usize, pub usize); - -#[derive(Serialize, Debug)] -#[serde(tag = "type")] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub enum LogLine { - FromNode { - source: LogCoordinates, - target: LogCoordinates, - indirect: bool, - }, - ToNode { - source: LogCoordinates, - target: LogCoordinates, - indirect: bool, - }, - ToIntersection { - source: LogCoordinates, - target: LogCoordinates, - indirect: bool, - }, - ToMissing { - source: LogCoordinates, - target: LogCoordinates, - indirect: bool, - }, -} - -#[derive(Serialize, Debug)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct LogRow { - pub revision: RevHeader, - pub location: LogCoordinates, - pub padding: usize, - pub lines: Vec, -} - -#[derive(Serialize)] -#[cfg_attr( - feature = "ts-rs", - derive(TS), - ts(export, export_to = "../src/messages/") -)] -pub struct LogPage { - pub rows: Vec, - pub has_more: bool, -} - -// similar to time_util::datetime_from_timestamp, which is not pub -fn format_timestamp(context: &Timestamp) -> Result> { - let utc = match Utc.timestamp_opt( - context.timestamp.0.div_euclid(1000), - (context.timestamp.0.rem_euclid(1000)) as u32 * 1000000, - ) { - LocalResult::None => { - return Err(anyhow!("no UTC instant exists for timestamp")); - } - LocalResult::Single(x) => x, - LocalResult::Ambiguous(y, _z) => y, - }; - - let tz = FixedOffset::east_opt(context.tz_offset * 60) - .or_else(|| FixedOffset::east_opt(0)) - .ok_or(anyhow!("timezone offset out of bounds"))?; - - Ok(utc.with_timezone(&tz)) -} +use chrono::{offset::LocalResult, DateTime, FixedOffset, Local, TimeZone, Utc}; +use jj_lib::backend::{Signature, Timestamp}; + +use super::*; + +/// A change or commit id with a disambiguated prefix +#[allow(dead_code)] // the frontend needs these structs kept in sync +pub trait Id { + fn hex(&self) -> &String; + fn prefix(&self) -> &String; + fn rest(&self) -> &String; +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(tag = "type")] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct CommitId { + pub hex: String, + pub prefix: String, + pub rest: String, +} + +impl Id for CommitId { + fn hex(&self) -> &String { + &self.hex + } + fn prefix(&self) -> &String { + &self.prefix + } + fn rest(&self) -> &String { + &self.rest + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(tag = "type")] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct ChangeId { + pub hex: String, + pub prefix: String, + pub rest: String, +} + +impl Id for ChangeId { + fn hex(&self) -> &String { + &self.hex + } + fn prefix(&self) -> &String { + &self.prefix + } + fn rest(&self) -> &String { + &self.rest + } +} + +/// A pair of ids representing the ui's view of a revision. +/// The worker may use one or both depending on policy. +#[derive(Serialize, Deserialize, Clone, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct RevId { + pub change: ChangeId, + pub commit: CommitId, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct RevHeader { + pub id: RevId, + pub description: MultilineString, + pub author: RevAuthor, + pub has_conflict: bool, + pub is_working_copy: bool, + pub is_immutable: bool, + pub refs: Vec, + pub parent_ids: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct RevAuthor { + pub email: String, + pub name: String, + pub timestamp: chrono::DateTime, +} + +impl TryFrom<&Signature> for RevAuthor { + type Error = anyhow::Error; + + fn try_from(value: &Signature) -> Result { + Ok(RevAuthor { + name: value.name.clone(), + email: value.email.clone(), + timestamp: format_timestamp(&value.timestamp)?.with_timezone(&Local), + }) + } +} + +#[derive(Serialize, Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct RevChange { + pub kind: ChangeKind, + pub path: TreePath, + pub has_conflict: bool, + pub hunks: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct RevConflict { + pub path: TreePath, + pub hunk: ChangeHunk, +} + +#[derive(Serialize, Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub enum ChangeKind { + None, + Added, + Deleted, + Modified, +} + +#[derive(Serialize, Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct ChangeHunk { + pub location: HunkLocation, + pub lines: MultilineString, +} + +#[derive(Serialize, Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct HunkLocation { + pub from_file: FileRange, + pub to_file: FileRange, +} + +#[derive(Serialize, Deserialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct FileRange { + pub start: usize, + pub len: usize, +} + +#[derive(Serialize, Debug)] +#[serde(tag = "type")] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub enum RevResult { + NotFound { + id: RevId, + }, + Detail { + header: RevHeader, + parents: Vec, + changes: Vec, + conflicts: Vec, + }, +} + +#[derive(Serialize, Clone, Copy, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct LogCoordinates(pub usize, pub usize); + +#[derive(Serialize, Debug)] +#[serde(tag = "type")] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub enum LogLine { + FromNode { + source: LogCoordinates, + target: LogCoordinates, + indirect: bool, + }, + ToNode { + source: LogCoordinates, + target: LogCoordinates, + indirect: bool, + }, + ToIntersection { + source: LogCoordinates, + target: LogCoordinates, + indirect: bool, + }, + ToMissing { + source: LogCoordinates, + target: LogCoordinates, + indirect: bool, + }, +} + +#[derive(Serialize, Debug)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct LogRow { + pub revision: RevHeader, + pub location: LogCoordinates, + pub padding: usize, + pub lines: Vec, +} + +#[derive(Serialize)] +#[cfg_attr( + feature = "ts-rs", + derive(TS), + ts(export, export_to = "../src/messages/") +)] +pub struct LogPage { + pub rows: Vec, + pub has_more: bool, +} + +// similar to time_util::datetime_from_timestamp, which is not pub +fn format_timestamp(context: &Timestamp) -> Result> { + let utc = match Utc.timestamp_opt( + context.timestamp.0.div_euclid(1000), + (context.timestamp.0.rem_euclid(1000)) as u32 * 1000000, + ) { + LocalResult::None => { + return Err(anyhow!("no UTC instant exists for timestamp")); + } + LocalResult::Single(x) => x, + LocalResult::Ambiguous(y, _z) => y, + }; + + let tz = FixedOffset::east_opt(context.tz_offset * 60) + .or_else(|| FixedOffset::east_opt(0)) + .ok_or(anyhow!("timezone offset out of bounds"))?; + + Ok(utc.with_timezone(&tz)) +} diff --git a/src-tauri/src/windows.rs b/src-tauri/src/windows.rs index cb70b82..ebe3bd7 100644 --- a/src-tauri/src/windows.rs +++ b/src-tauri/src/windows.rs @@ -1,100 +1,100 @@ -use std::path::Path; - -use anyhow::{anyhow, Result}; -use windows::core::{w, Interface, BSTR, HSTRING, PROPVARIANT, PWSTR}; -use windows::Win32::Foundation::MAX_PATH; -use windows::Win32::System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER}; -use windows::Win32::System::Console::{AttachConsole, ATTACH_PARENT_PROCESS}; -use windows::Win32::UI::Shell::Common::{IObjectArray, IObjectCollection}; -use windows::Win32::UI::Shell::PropertiesSystem::{ - IPropertyStore, PSGetPropertyKeyFromName, PROPERTYKEY, -}; -use windows::Win32::UI::Shell::{ - DestinationList, EnumerableObjectCollection, ICustomDestinationList, IShellLinkW, ShellLink, -}; - -pub fn reattach_console() { - // safety: FFI - let _ = unsafe { AttachConsole(ATTACH_PARENT_PROCESS) }; -} - -#[cfg_attr(not(windows), allow(dead_code))] -pub fn update_jump_list(recent: &mut Vec, path: &String) -> Result<()> { - // create a jump list - // safety: FFI - let jump_list: ICustomDestinationList = - unsafe { CoCreateInstance(&DestinationList, None, CLSCTX_INPROC_SERVER)? }; - - // initialise the list and honour removals requested by the user - let mut max_destinations = 0u32; - let mut destination_path = vec![0u16; MAX_PATH as usize]; - - // safety: GetArguments() calls len() on the provided slice, and produces a null-terminated string for PWSTR::to_string() - unsafe { - let removed_destinations: IObjectArray = jump_list.BeginList(&mut max_destinations)?; - for i in 0..removed_destinations.GetCount()? { - let removed_link: IShellLinkW = removed_destinations.GetAt(i)?; // safety: i <= GetCount() - removed_link.GetArguments(&mut destination_path)?; - let removed_path_wstr = PWSTR::from_raw(destination_path.as_mut_ptr()); - if !removed_path_wstr.is_null() { - let removed_path = removed_path_wstr.to_string()?; - recent.retain(|x| *x != removed_path); - } - } - }; - - // add the new path as most-recent and trim to the configured max size - recent.retain(|x| x != path); - recent.insert(0, path.to_owned()); - - // safety: FFI - let items: IObjectCollection = - unsafe { CoCreateInstance(&EnumerableObjectCollection, None, CLSCTX_INPROC_SERVER)? }; - - // turn the paths into IShellLinks - for path in recent { - let path_wstr: HSTRING = (&*path).into(); - let exe_wstr: HSTRING = std::env::current_exe()?.as_os_str().into(); - let dir_wstr: HSTRING = Path::new(path) - .file_name() - .ok_or(anyhow!("repo path is not a directory"))? - .into(); - let dir_wstr = BSTR::from_wide(dir_wstr.as_wide())?; - - // safety: FFI - unsafe { - let link = create_directory_link(exe_wstr, path_wstr, dir_wstr)?; - items.AddObject(&link)?; - } - } - - // add a custom category - // safety: FFI - unsafe { - let array: IObjectArray = items.cast()?; - jump_list.AppendCategory(w!("Recent"), &array)?; - jump_list.CommitList()?; - } - - Ok(()) -} - -// safety: no invariants, it's all FFI -unsafe fn create_directory_link(path: HSTRING, args: HSTRING, title: BSTR) -> Result { - let link: IShellLinkW = CoCreateInstance(&ShellLink, None, CLSCTX_INPROC_SERVER)?; - link.SetPath(&path)?; // launch ourselves... - link.SetIconLocation(w!("%SystemRoot%\\System32\\shell32.dll"), 3)?; // ...with the icon for a directory... - link.SetArguments(&args)?; // ...the directory as an argument... - link.SetDescription(&args)?; // ...and a tooltip containing just the directory name - - // the actual display string must be set as a property because IShellLink is primarily for shortcuts - let title_value = PROPVARIANT::from(title); - let mut title_key = PROPERTYKEY::default(); - PSGetPropertyKeyFromName(w!("System.Title"), &mut title_key)?; - - let store: IPropertyStore = link.cast()?; - store.SetValue(&title_key, &title_value)?; - store.Commit()?; - - Ok(link) -} +use std::path::Path; + +use anyhow::{anyhow, Result}; +use windows::core::{w, Interface, BSTR, HSTRING, PROPVARIANT, PWSTR}; +use windows::Win32::Foundation::MAX_PATH; +use windows::Win32::System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER}; +use windows::Win32::System::Console::{AttachConsole, ATTACH_PARENT_PROCESS}; +use windows::Win32::UI::Shell::Common::{IObjectArray, IObjectCollection}; +use windows::Win32::UI::Shell::PropertiesSystem::{ + IPropertyStore, PSGetPropertyKeyFromName, PROPERTYKEY, +}; +use windows::Win32::UI::Shell::{ + DestinationList, EnumerableObjectCollection, ICustomDestinationList, IShellLinkW, ShellLink, +}; + +pub fn reattach_console() { + // safety: FFI + let _ = unsafe { AttachConsole(ATTACH_PARENT_PROCESS) }; +} + +#[cfg_attr(not(windows), allow(dead_code))] +pub fn update_jump_list(recent: &mut Vec, path: &String) -> Result<()> { + // create a jump list + // safety: FFI + let jump_list: ICustomDestinationList = + unsafe { CoCreateInstance(&DestinationList, None, CLSCTX_INPROC_SERVER)? }; + + // initialise the list and honour removals requested by the user + let mut max_destinations = 0u32; + let mut destination_path = vec![0u16; MAX_PATH as usize]; + + // safety: GetArguments() calls len() on the provided slice, and produces a null-terminated string for PWSTR::to_string() + unsafe { + let removed_destinations: IObjectArray = jump_list.BeginList(&mut max_destinations)?; + for i in 0..removed_destinations.GetCount()? { + let removed_link: IShellLinkW = removed_destinations.GetAt(i)?; // safety: i <= GetCount() + removed_link.GetArguments(&mut destination_path)?; + let removed_path_wstr = PWSTR::from_raw(destination_path.as_mut_ptr()); + if !removed_path_wstr.is_null() { + let removed_path = removed_path_wstr.to_string()?; + recent.retain(|x| *x != removed_path); + } + } + }; + + // add the new path as most-recent and trim to the configured max size + recent.retain(|x| x != path); + recent.insert(0, path.to_owned()); + + // safety: FFI + let items: IObjectCollection = + unsafe { CoCreateInstance(&EnumerableObjectCollection, None, CLSCTX_INPROC_SERVER)? }; + + // turn the paths into IShellLinks + for path in recent { + let path_wstr: HSTRING = (&*path).into(); + let exe_wstr: HSTRING = std::env::current_exe()?.as_os_str().into(); + let dir_wstr: HSTRING = Path::new(path) + .file_name() + .ok_or(anyhow!("repo path is not a directory"))? + .into(); + let dir_wstr = BSTR::from_wide(dir_wstr.as_wide())?; + + // safety: FFI + unsafe { + let link = create_directory_link(exe_wstr, path_wstr, dir_wstr)?; + items.AddObject(&link)?; + } + } + + // add a custom category + // safety: FFI + unsafe { + let array: IObjectArray = items.cast()?; + jump_list.AppendCategory(w!("Recent"), &array)?; + jump_list.CommitList()?; + } + + Ok(()) +} + +// safety: no invariants, it's all FFI +unsafe fn create_directory_link(path: HSTRING, args: HSTRING, title: BSTR) -> Result { + let link: IShellLinkW = CoCreateInstance(&ShellLink, None, CLSCTX_INPROC_SERVER)?; + link.SetPath(&path)?; // launch ourselves... + link.SetIconLocation(w!("%SystemRoot%\\System32\\shell32.dll"), 3)?; // ...with the icon for a directory... + link.SetArguments(&args)?; // ...the directory as an argument... + link.SetDescription(&args)?; // ...and a tooltip containing just the directory name + + // the actual display string must be set as a property because IShellLink is primarily for shortcuts + let title_value = PROPVARIANT::from(title); + let mut title_key = PROPERTYKEY::default(); + PSGetPropertyKeyFromName(w!("System.Title"), &mut title_key)?; + + let store: IPropertyStore = link.cast()?; + store.SetValue(&title_key, &title_value)?; + store.Commit()?; + + Ok(link) +} diff --git a/src-tauri/src/worker/mod.rs b/src-tauri/src/worker/mod.rs index 856d1a3..5e40b52 100644 --- a/src-tauri/src/worker/mod.rs +++ b/src-tauri/src/worker/mod.rs @@ -1,109 +1,109 @@ -//! Worker per window, owning repo data (jj-lib is not thread-safe) -//! The worker thread is a state machine, running different handle functions based on loaded data - -mod gui_util; -mod mutations; -mod queries; -mod session; -#[cfg(all(test, not(feature = "ts-rs")))] -mod tests; - -use std::{ - env::{self, VarError}, - fmt::Debug, - fs, - path::PathBuf, -}; - -use anyhow::{anyhow, Error, Result}; -use jj_lib::{git::RemoteCallbacks, repo::MutableRepo}; - -use crate::messages; -use gui_util::WorkspaceSession; -pub use session::{Session, SessionEvent}; - -/// implemented by structured-change commands -pub trait Mutation: Debug { - fn describe(&self) -> String { - std::any::type_name::().to_owned() - } - - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result; - - #[cfg(test)] - fn execute_unboxed(self, ws: &mut WorkspaceSession) -> Result - where - Self: Sized, - { - Box::new(self).execute(ws) - } -} - -/// implemented by UI layers to request user input and receive progress -pub trait WorkerCallbacks { - fn with_git( - &self, - repo: &mut MutableRepo, - f: &dyn Fn(&mut MutableRepo, RemoteCallbacks<'_>) -> Result<()>, - ) -> Result<()>; - - fn select_remote(&self, choices: &[&str]) -> Option; -} - -struct NoCallbacks; - -impl WorkerCallbacks for NoCallbacks { - fn with_git( - &self, - repo: &mut MutableRepo, - f: &dyn Fn(&mut MutableRepo, RemoteCallbacks<'_>) -> Result<()>, - ) -> Result<()> { - f(repo, RemoteCallbacks::default()) - } - - fn select_remote(&self, choices: &[&str]) -> Option { - choices.get(0).map(|choice| choice.to_string()) - } -} - -/// state that doesn't depend on jj-lib borrowings -pub struct WorkerSession { - pub force_log_page_size: Option, - pub latest_query: Option, - pub callbacks: Box, - pub working_directory: Option, -} - -impl WorkerSession { - pub fn new(callbacks: T, workspace: Option) -> Self { - WorkerSession { - callbacks: Box::new(callbacks), - working_directory: workspace, - ..Default::default() - } - } - - // AppImage runs the executable from somewhere weird, but sets OWD=cwd() first. - pub fn get_cwd(&self) -> Result { - self.working_directory - .as_ref() - .map(|cwd| Ok(fs::canonicalize(cwd.clone())?)) - .or_else(|| match env::var("OWD") { - Ok(var) => Some(Ok(PathBuf::from(var))), - Err(VarError::NotPresent) => None, - Err(err) => Some(Err(anyhow!(err))), - }) - .unwrap_or_else(|| env::current_dir().map_err(Error::new)) - } -} - -impl Default for WorkerSession { - fn default() -> Self { - WorkerSession { - force_log_page_size: None, - latest_query: None, - callbacks: Box::new(NoCallbacks), - working_directory: None, - } - } -} +//! Worker per window, owning repo data (jj-lib is not thread-safe) +//! The worker thread is a state machine, running different handle functions based on loaded data + +mod gui_util; +mod mutations; +mod queries; +mod session; +#[cfg(all(test, not(feature = "ts-rs")))] +mod tests; + +use std::{ + env::{self, VarError}, + fmt::Debug, + fs, + path::PathBuf, +}; + +use anyhow::{anyhow, Error, Result}; +use jj_lib::{git::RemoteCallbacks, repo::MutableRepo}; + +use crate::messages; +use gui_util::WorkspaceSession; +pub use session::{Session, SessionEvent}; + +/// implemented by structured-change commands +pub trait Mutation: Debug { + fn describe(&self) -> String { + std::any::type_name::().to_owned() + } + + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result; + + #[cfg(test)] + fn execute_unboxed(self, ws: &mut WorkspaceSession) -> Result + where + Self: Sized, + { + Box::new(self).execute(ws) + } +} + +/// implemented by UI layers to request user input and receive progress +pub trait WorkerCallbacks { + fn with_git( + &self, + repo: &mut MutableRepo, + f: &dyn Fn(&mut MutableRepo, RemoteCallbacks<'_>) -> Result<()>, + ) -> Result<()>; + + fn select_remote(&self, choices: &[&str]) -> Option; +} + +struct NoCallbacks; + +impl WorkerCallbacks for NoCallbacks { + fn with_git( + &self, + repo: &mut MutableRepo, + f: &dyn Fn(&mut MutableRepo, RemoteCallbacks<'_>) -> Result<()>, + ) -> Result<()> { + f(repo, RemoteCallbacks::default()) + } + + fn select_remote(&self, choices: &[&str]) -> Option { + choices.get(0).map(|choice| choice.to_string()) + } +} + +/// state that doesn't depend on jj-lib borrowings +pub struct WorkerSession { + pub force_log_page_size: Option, + pub latest_query: Option, + pub callbacks: Box, + pub working_directory: Option, +} + +impl WorkerSession { + pub fn new(callbacks: T, workspace: Option) -> Self { + WorkerSession { + callbacks: Box::new(callbacks), + working_directory: workspace, + ..Default::default() + } + } + + // AppImage runs the executable from somewhere weird, but sets OWD=cwd() first. + pub fn get_cwd(&self) -> Result { + self.working_directory + .as_ref() + .map(|cwd| Ok(fs::canonicalize(cwd.clone())?)) + .or_else(|| match env::var("OWD") { + Ok(var) => Some(Ok(PathBuf::from(var))), + Err(VarError::NotPresent) => None, + Err(err) => Some(Err(anyhow!(err))), + }) + .unwrap_or_else(|| env::current_dir().map_err(Error::new)) + } +} + +impl Default for WorkerSession { + fn default() -> Self { + WorkerSession { + force_log_page_size: None, + latest_query: None, + callbacks: Box::new(NoCallbacks), + working_directory: None, + } + } +} diff --git a/src-tauri/src/worker/mutations.rs b/src-tauri/src/worker/mutations.rs index 9a6a8ba..bd7dbc0 100644 --- a/src-tauri/src/worker/mutations.rs +++ b/src-tauri/src/worker/mutations.rs @@ -1,1073 +1,1073 @@ -use std::fmt::Display; - -use anyhow::{anyhow, Context, Result}; -use indexmap::IndexMap; -use itertools::Itertools; -use jj_lib::{ - backend::{BackendError, CommitId}, - commit::Commit, - git::{self, GitBranchPushTargets, REMOTE_NAME_FOR_LOCAL_GIT_REPO}, - matchers::{EverythingMatcher, FilesMatcher, Matcher}, - object_id::ObjectId, - op_store::{RefTarget, RemoteRef, RemoteRefState}, - op_walk, - refs::{self, BranchPushAction, BranchPushUpdate, LocalAndRemoteRef}, - repo::Repo, - repo_path::RepoPath, - revset::{self, RevsetIteratorExt}, - rewrite, - settings::UserSettings, - str_util::StringPattern, -}; - -use super::{gui_util::WorkspaceSession, Mutation}; -use crate::messages::{ - AbandonRevisions, BackoutRevisions, CheckoutRevision, CopyChanges, CreateRef, CreateRevision, - DeleteRef, DescribeRevision, DuplicateRevisions, GitFetch, GitPush, InsertRevision, - MoveChanges, MoveRef, MoveRevision, MoveSource, MutationResult, RenameBranch, StoreRef, - TrackBranch, TreePath, UndoOperation, UntrackBranch, -}; - -macro_rules! precondition { - ($($args:tt)*) => { - return Ok(MutationResult::PreconditionError { message: format!($($args)*) }) - } -} - -impl Mutation for AbandonRevisions { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let abandoned_ids = self - .ids - .into_iter() - .map(|id| CommitId::try_from_hex(&id.hex).expect("frontend-validated id")) - .collect_vec(); - - if ws.check_immutable(abandoned_ids.clone())? { - precondition!("Some revisions are immutable"); - } - - for id in &abandoned_ids { - tx.mut_repo().record_abandoned_commit(id.clone()); - } - tx.mut_repo().rebase_descendants(&ws.settings)?; - - let transaction_description = if abandoned_ids.len() == 1 { - format!("abandon commit {}", abandoned_ids[0].hex()) - } else { - format!( - "abandon commit {} and {} more", - abandoned_ids[0].hex(), - abandoned_ids.len() - 1 - ) - }; - - match ws.finish_transaction(tx, transaction_description)? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for BackoutRevisions { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - if self.ids.len() != 1 { - precondition!("Not implemented for >1 rev"); - } - - let mut tx = ws.start_transaction()?; - - let working_copy = ws.get_commit(ws.wc_id())?; - let reverted = ws.resolve_multiple_changes(self.ids)?; - let reverted_parents: Result, BackendError> = reverted[0].parents().collect(); - - let old_base_tree = rewrite::merge_commit_trees(tx.mut_repo(), &reverted_parents?)?; - let new_base_tree = working_copy.tree()?; - let old_tree = reverted[0].tree()?; - let new_tree = new_base_tree.merge(&old_tree, &old_base_tree)?; - - tx.mut_repo() - .rewrite_commit(&ws.settings, &&working_copy) - .set_tree_id(new_tree.id()) - .write()?; - - match ws.finish_transaction(tx, format!("back out commit {}", reverted[0].id().hex()))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for CheckoutRevision { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let edited = ws.resolve_single_change(&self.id)?; - - if ws.check_immutable(vec![edited.id().clone()])? { - precondition!("Revision is immutable"); - } - - if edited.id() == ws.wc_id() { - return Ok(MutationResult::Unchanged); - } - - tx.mut_repo().edit(ws.id().clone(), &edited)?; - - match ws.finish_transaction(tx, format!("edit commit {}", edited.id().hex()))? { - Some(new_status) => { - let new_selection = ws.format_header(&edited, Some(false))?; - Ok(MutationResult::UpdatedSelection { - new_status, - new_selection, - }) - } - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for CreateRevision { - fn execute<'a>(self: Box, ws: &'a mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let parents_revset = ws.evaluate_revset_changes( - &self - .parent_ids - .into_iter() - .map(|id| id.change) - .collect_vec(), - )?; - - let parent_ids = parents_revset.iter().collect_vec(); - let parent_commits = ws.resolve_multiple(parents_revset)?; - let merged_tree = rewrite::merge_commit_trees(tx.repo(), &parent_commits)?; - - let new_commit = tx - .mut_repo() - .new_commit(&ws.settings, parent_ids, merged_tree.id()) - .write()?; - - tx.mut_repo().edit(ws.id().clone(), &new_commit)?; - - match ws.finish_transaction(tx, "new empty commit")? { - Some(new_status) => { - let new_selection = ws.format_header(&new_commit, Some(false))?; - Ok(MutationResult::UpdatedSelection { - new_status, - new_selection, - }) - } - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for DescribeRevision { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let described = ws.resolve_single_change(&self.id)?; - - if ws.check_immutable(vec![described.id().clone()])? { - precondition!("Revision {} is immutable", self.id.change.prefix); - } - - if self.new_description == described.description() && !self.reset_author { - return Ok(MutationResult::Unchanged); - } - - let mut commit_builder = tx - .mut_repo() - .rewrite_commit(&ws.settings, &described) - .set_description(self.new_description); - - if self.reset_author { - let new_author = commit_builder.committer().clone(); - commit_builder = commit_builder.set_author(new_author); - } - - commit_builder.write()?; - - match ws.finish_transaction(tx, format!("describe commit {}", described.id().hex()))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for DuplicateRevisions { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let clonees = ws.resolve_multiple_changes(self.ids)?; // in reverse topological order - let num_clonees = clonees.len(); - let mut clones: IndexMap = IndexMap::new(); - - // toposort ensures that parents are duplicated first - for clonee in clonees.into_iter().rev() { - let clone_parents: Result, _> = clonee - .parents() - .map_ok(|parent| { - if let Some(cloned_parent) = clones.get(&parent) { - cloned_parent - } else { - &parent - } - .id() - .clone() - }) - .collect(); - let clone = tx - .mut_repo() - .rewrite_commit(&ws.settings, &clonee) - .generate_new_change_id() - .set_parents(clone_parents?) - .write()?; - clones.insert(clonee, clone); - } - - match ws.finish_transaction(tx, format!("duplicating {} commit(s)", num_clonees))? { - Some(new_status) => { - if num_clonees == 1 { - let new_commit = clones - .get_index(0) - .ok_or(anyhow!("single source should have single copy"))? - .1; - let new_selection = ws.format_header(new_commit, None)?; - Ok(MutationResult::UpdatedSelection { - new_status, - new_selection, - }) - } else { - Ok(MutationResult::Updated { new_status }) - } - } - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for InsertRevision { - fn execute<'a>(self: Box, ws: &'a mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let target = ws - .resolve_single_change(&self.id) - .context("resolve change_id")?; - let before = ws - .resolve_single_change(&self.before_id) - .context("resolve before_id")?; - let after = ws - .resolve_single_change(&self.after_id) - .context("resolve after_id")?; - - if ws.check_immutable(vec![target.id().clone(), before.id().clone()])? { - precondition!("Some revisions are immutable"); - } - - // rebase the target's children - let rebased_children = ws.disinherit_children(&mut tx, &target)?; - - // update after, which may have been a descendant of target - let after_id = rebased_children - .get(after.id()) - .unwrap_or(after.id()) - .clone(); - - // rebase the target (which now has no children), then the new post-target tree atop it - let rebased_id = target.id().hex(); - let target = rewrite::rebase_commit(&ws.settings, tx.mut_repo(), target, vec![after_id])?; - rewrite::rebase_commit( - &ws.settings, - tx.mut_repo(), - before, - vec![target.id().clone()], - )?; - - match ws.finish_transaction(tx, format!("rebase commit {}", rebased_id))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for MoveRevision { - fn execute<'a>(self: Box, ws: &'a mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let target = ws.resolve_single_change(&self.id)?; - let parents = ws.resolve_multiple_changes(self.parent_ids)?; - - if ws.check_immutable(vec![target.id().clone()])? { - precondition!("Revision {} is immutable", self.id.change.prefix); - } - - // rebase the target's children - let rebased_children = ws.disinherit_children(&mut tx, &target)?; - - // update parents, which may have been descendants of the target - let parent_ids: Vec<_> = parents - .iter() - .map(|new_parent| { - rebased_children - .get(new_parent.id()) - .unwrap_or(new_parent.id()) - .clone() - }) - .collect(); - - // rebase the target itself - let rebased_id = target.id().hex(); - rewrite::rebase_commit(&ws.settings, tx.mut_repo(), target, parent_ids)?; - - match ws.finish_transaction(tx, format!("rebase commit {}", rebased_id))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for MoveSource { - fn execute<'a>(self: Box, ws: &'a mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let target = ws.resolve_single_change(&self.id)?; - let parent_ids = ws - .resolve_multiple_commits(&self.parent_ids)? - .into_iter() - .map(|commit| commit.id().clone()) - .collect(); - - if ws.check_immutable(vec![target.id().clone()])? { - precondition!("Revision {} is immutable", self.id.change.prefix); - } - - // just rebase the target, which will also rebase its descendants - let rebased_id = target.id().hex(); - rewrite::rebase_commit(&ws.settings, tx.mut_repo(), target, parent_ids)?; - - match ws.finish_transaction(tx, format!("rebase commit {}", rebased_id))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for MoveChanges { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let from = ws.resolve_single_change(&self.from_id)?; - let mut to = ws.resolve_single_commit(&self.to_id)?; - let matcher = build_matcher(&self.paths); - - if ws.check_immutable(vec![from.id().clone(), to.id().clone()])? { - precondition!("Revisions are immutable"); - } - - // construct a split tree and a remainder tree by copying changes from child to parent and from parent to child - let from_tree = from.tree()?; - let from_parents: Result, _> = from.parents().collect(); - let parent_tree = rewrite::merge_commit_trees(tx.repo(), &from_parents?)?; - let split_tree_id = rewrite::restore_tree(&from_tree, &parent_tree, matcher.as_ref())?; - let split_tree = tx.repo().store().get_root_tree(&split_tree_id)?; - let remainder_tree_id = rewrite::restore_tree(&parent_tree, &from_tree, matcher.as_ref())?; - let remainder_tree = tx.repo().store().get_root_tree(&remainder_tree_id)?; - - // abandon or rewrite source - let abandon_source = remainder_tree.id() == parent_tree.id(); - if abandon_source { - tx.mut_repo().record_abandoned_commit(from.id().clone()); - } else { - tx.mut_repo() - .rewrite_commit(&ws.settings, &from) - .set_tree_id(remainder_tree.id().clone()) - .write()?; - } - - // rebase descendants of source, which may include destination - if tx.repo().index().is_ancestor(from.id(), to.id()) { - let rebase_map = tx.mut_repo().rebase_descendants_return_map(&ws.settings)?; - let rebased_to_id = rebase_map - .get(to.id()) - .ok_or(anyhow!("descendant to_commit not found in rebase map"))? - .clone(); - to = tx.mut_repo().store().get_commit(&rebased_to_id)?; - } - - // apply changes to destination - let to_tree = to.tree()?; - let new_to_tree = to_tree.merge(&parent_tree, &split_tree)?; - let description = combine_messages(&from, &to, abandon_source); - tx.mut_repo() - .rewrite_commit(&ws.settings, &to) - .set_tree_id(new_to_tree.id().clone()) - .set_description(description) - .write()?; - - match ws.finish_transaction( - tx, - format!("move changes from {} to {}", from.id().hex(), to.id().hex()), - )? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for CopyChanges { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let from_tree = ws.resolve_single_commit(&self.from_id)?.tree()?; - let to = ws.resolve_single_change(&self.to_id)?; - let matcher = build_matcher(&self.paths); - - if ws.check_immutable(vec![to.id().clone()])? { - precondition!("Revisions are immutable"); - } - - // construct a restore tree - the destination with some portions overwritten by the source - let to_tree = to.tree()?; - let new_to_tree_id = rewrite::restore_tree(&from_tree, &to_tree, matcher.as_ref())?; - if &new_to_tree_id == to.tree_id() { - Ok(MutationResult::Unchanged) - } else { - tx.mut_repo() - .rewrite_commit(&ws.settings, &to) - .set_tree_id(new_to_tree_id) - .write()?; - - tx.mut_repo().rebase_descendants(&ws.settings)?; - - match ws.finish_transaction(tx, format!("restore into commit {}", to.id().hex()))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } - } -} - -impl Mutation for TrackBranch { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - match self.r#ref { - StoreRef::Tag { tag_name } => { - precondition!("{} is a tag and cannot be tracked", tag_name); - } - StoreRef::LocalBranch { branch_name, .. } => { - precondition!("{} is a local branch and cannot be tracked", branch_name); - } - StoreRef::RemoteBranch { - branch_name, - remote_name, - .. - } => { - let mut tx = ws.start_transaction()?; - - let remote_ref: &jj_lib::op_store::RemoteRef = - ws.view().get_remote_branch(&branch_name, &remote_name); - - if remote_ref.is_tracking() { - precondition!("{branch_name}@{remote_name} is already tracked"); - } - - tx.mut_repo() - .track_remote_branch(&branch_name, &remote_name); - - match ws.finish_transaction(tx, format!("track remote branch {}", branch_name))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } - } - } -} - -impl Mutation for UntrackBranch { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let mut untracked = Vec::new(); - match self.r#ref { - StoreRef::Tag { tag_name } => { - precondition!("{} is a tag and cannot be untracked", tag_name); - } - StoreRef::LocalBranch { branch_name, .. } => { - // untrack all remotes - for ((name, remote), remote_ref) in ws.view().remote_branches_matching( - &StringPattern::exact(branch_name), - &StringPattern::everything(), - ) { - if remote != REMOTE_NAME_FOR_LOCAL_GIT_REPO && remote_ref.is_tracking() { - tx.mut_repo().untrack_remote_branch(name, remote); - untracked.push(format!("{name}@{remote}")); - } - } - } - StoreRef::RemoteBranch { - branch_name, - remote_name, - .. - } => { - let remote_ref: &jj_lib::op_store::RemoteRef = - ws.view().get_remote_branch(&branch_name, &remote_name); - - if !remote_ref.is_tracking() { - precondition!("{branch_name}@{remote_name} is not tracked"); - } - - tx.mut_repo() - .untrack_remote_branch(&branch_name, &remote_name); - untracked.push(format!("{branch_name}@{remote_name}")); - } - } - - match ws.finish_transaction( - tx, - format!("untrack remote {}", combine_branches(&untracked)), - )? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for RenameBranch { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let old_name = self.r#ref.as_branch()?; - - let ref_target = ws.view().get_local_branch(old_name).clone(); - if ref_target.is_absent() { - precondition!("No such branch: {}", old_name); - } - - if ws.view().get_local_branch(&self.new_name).is_present() { - precondition!("Branch already exists: {}", &self.new_name); - } - - let mut tx = ws.start_transaction()?; - - tx.mut_repo() - .set_local_branch_target(&self.new_name, ref_target); - tx.mut_repo() - .set_local_branch_target(old_name, RefTarget::absent()); - - match ws.finish_transaction(tx, format!("rename {} to {}", old_name, self.new_name))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for CreateRef { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let commit = ws.resolve_single_change(&self.id)?; - - match self.r#ref { - StoreRef::RemoteBranch { - branch_name, - remote_name, - .. - } => { - precondition!( - "{}@{} is a remote branch and cannot be created", - branch_name, - remote_name - ); - } - StoreRef::LocalBranch { branch_name, .. } => { - let existing_branch = ws.view().get_local_branch(&branch_name); - if existing_branch.is_present() { - precondition!("{} already exists", branch_name); - } - - tx.mut_repo() - .set_local_branch_target(&branch_name, RefTarget::normal(commit.id().clone())); - - match ws.finish_transaction( - tx, - format!( - "create {} pointing to commit {}", - branch_name, - ws.format_commit_id(commit.id()).hex - ), - )? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } - StoreRef::Tag { tag_name, .. } => { - let existing_tag = ws.view().get_tag(&tag_name); - if existing_tag.is_present() { - precondition!("{} already exists", tag_name); - } - - tx.mut_repo() - .set_tag_target(&tag_name, RefTarget::normal(commit.id().clone())); - - match ws.finish_transaction( - tx, - format!( - "create {} pointing to commit {}", - tag_name, - ws.format_commit_id(commit.id()).hex - ), - )? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } - } - } -} - -impl Mutation for DeleteRef { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - match self.r#ref { - StoreRef::RemoteBranch { - branch_name, - remote_name, - .. - } => { - let mut tx = ws.start_transaction()?; - - // forget the branch entirely - when target is absent, it's removed from the view - let remote_ref = RemoteRef { - target: RefTarget::absent(), - state: RemoteRefState::New, - }; - - tx.mut_repo() - .set_remote_branch(&branch_name, &remote_name, remote_ref); - - match ws - .finish_transaction(tx, format!("forget {}@{}", branch_name, remote_name))? - { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } - StoreRef::LocalBranch { branch_name, .. } => { - let mut tx = ws.start_transaction()?; - - tx.mut_repo() - .set_local_branch_target(&branch_name, RefTarget::absent()); - - match ws.finish_transaction(tx, format!("forget {}", branch_name))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } - StoreRef::Tag { tag_name } => { - let mut tx = ws.start_transaction()?; - - tx.mut_repo().set_tag_target(&tag_name, RefTarget::absent()); - - match ws.finish_transaction(tx, format!("forget tag {}", tag_name))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } - } - } -} - -// does not currently enforce fast-forwards -impl Mutation for MoveRef { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let commit = ws.resolve_single_change(&self.to_id)?; - - match self.r#ref { - StoreRef::RemoteBranch { - branch_name, - remote_name, - .. - } => { - precondition!("Branch is remote: {branch_name}@{remote_name}") - } - StoreRef::LocalBranch { branch_name, .. } => { - let old_target = ws.view().get_local_branch(&branch_name); - if old_target.is_absent() { - precondition!("No such branch: {branch_name}"); - } - - tx.mut_repo() - .set_local_branch_target(&branch_name, RefTarget::normal(commit.id().clone())); - - match ws.finish_transaction( - tx, - format!("point {} to commit {}", branch_name, commit.id().hex()), - )? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } - StoreRef::Tag { tag_name } => { - let old_target = ws.view().get_tag(&tag_name); - if old_target.is_absent() { - precondition!("No such tag: {tag_name}"); - } - - tx.mut_repo() - .set_tag_target(&tag_name, RefTarget::normal(commit.id().clone())); - - match ws.finish_transaction( - tx, - format!("point {} to commit {}", tag_name, commit.id().hex()), - )? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } - } - } -} - -impl Mutation for GitPush { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let git_repo = match ws.git_repo()? { - Some(git_repo) => git_repo, - None => precondition!("No git backend"), - }; - - // determine branches to push, recording the old and new commits - let mut remote_branch_updates: Vec<(&str, Vec<(String, refs::BranchPushUpdate)>)> = - Vec::new(); - let remote_branch_refs: Vec<_> = match &*self { - GitPush::AllBranches { ref remote_name } => { - let mut branch_updates = Vec::new(); - for (branch_name, targets) in ws.view().local_remote_branches(&remote_name) { - if !targets.remote_ref.is_tracking() { - continue; - } - - match classify_branch_push(branch_name, &remote_name, targets) { - Err(message) => return Ok(MutationResult::PreconditionError { message }), - Ok(None) => (), - Ok(Some(update)) => branch_updates.push((branch_name.to_owned(), update)), - } - } - remote_branch_updates.push((remote_name, branch_updates)); - - ws.view().remote_branches(&remote_name).collect() - } - GitPush::AllRemotes { branch_ref } => { - let branch_name = branch_ref.as_branch()?; - - let mut remote_branch_refs = Vec::new(); - for (remote_name, group) in ws - .view() - .all_remote_branches() - .filter_map(|((branch, remote), remote_ref)| { - if remote_ref.is_tracking() && branch == branch_name { - Some((remote, remote_ref)) - } else { - None - } - }) - .chunk_by(|(remote_name, _)| *remote_name) - .into_iter() - { - let mut branch_updates = Vec::new(); - for (_, remote_ref) in group { - let targets = LocalAndRemoteRef { - local_target: ws.view().get_local_branch(branch_name), - remote_ref, - }; - match classify_branch_push(branch_name, &remote_name, targets) { - Err(message) => { - return Ok(MutationResult::PreconditionError { message }) - } - Ok(None) => (), - Ok(Some(update)) => { - branch_updates.push((branch_name.to_owned(), update)) - } - } - remote_branch_refs.push((remote_name, remote_ref)); - } - remote_branch_updates.push((remote_name, branch_updates)); - } - - remote_branch_refs - } - GitPush::RemoteBranch { - ref remote_name, - ref branch_ref, - } => { - let branch_name = branch_ref.as_branch()?; - let local_target = ws.view().get_local_branch(branch_name); - let remote_ref = ws.view().get_remote_branch(branch_name, remote_name); - - match classify_branch_push( - branch_name, - remote_name, - LocalAndRemoteRef { - local_target, - remote_ref, - }, - ) { - Err(message) => return Ok(MutationResult::PreconditionError { message }), - Ok(None) => (), - Ok(Some(update)) => { - remote_branch_updates - .push((remote_name, vec![(branch_name.to_owned(), update)])); - } - } - - vec![( - branch_name, - ws.view().get_remote_branch(branch_name, &remote_name), - )] - } - }; - - // check for conflicts - let mut new_heads = vec![]; - for (_, branch_updates) in &mut remote_branch_updates { - for (_, update) in branch_updates { - if let Some(new_target) = &update.new_target { - new_heads.push(new_target.clone()); - } - } - } - - let mut old_heads = remote_branch_refs - .into_iter() - .flat_map(|(_, old_head)| old_head.target.added_ids()) - .cloned() - .collect_vec(); - if old_heads.is_empty() { - old_heads.push(ws.repo().store().root_commit_id().clone()); - } - - for commit in revset::walk_revs(ws.repo(), &new_heads, &old_heads)? - .iter() - .commits(ws.repo().store()) - { - let commit = commit?; - let mut reasons = vec![]; - if commit.description().is_empty() { - reasons.push("it has no description"); - } - if commit.author().name.is_empty() - || commit.author().name == UserSettings::USER_NAME_PLACEHOLDER - || commit.author().email.is_empty() - || commit.author().email == UserSettings::USER_EMAIL_PLACEHOLDER - || commit.committer().name.is_empty() - || commit.committer().name == UserSettings::USER_NAME_PLACEHOLDER - || commit.committer().email.is_empty() - || commit.committer().email == UserSettings::USER_EMAIL_PLACEHOLDER - { - reasons.push("it has no author and/or committer set"); - } - if commit.has_conflict()? { - reasons.push("it has conflicts"); - } - if !reasons.is_empty() { - precondition!( - "Won't push revision {} since {}", - ws.format_change_id(commit.change_id()).prefix, - reasons.join(" and ") - ); - } - } - - // push to each remote - for (remote_name, branch_updates) in remote_branch_updates.into_iter() { - let targets = GitBranchPushTargets { branch_updates }; - - ws.session.callbacks.with_git(tx.mut_repo(), &|repo, cb| { - Ok(git::push_branches( - repo, - &git_repo, - &remote_name, - &targets, - cb, - )?) - })?; - } - - match ws.finish_transaction( - tx, - match *self { - GitPush::AllBranches { remote_name } => { - format!("push all tracked branches to git remote {}", remote_name) - } - GitPush::AllRemotes { branch_ref } => { - format!( - "push {} to all tracked git remotes", - branch_ref.as_branch()? - ) - } - GitPush::RemoteBranch { - remote_name, - branch_ref, - } => { - format!( - "push {} to git remote {}", - branch_ref.as_branch()?, - remote_name - ) - } - }, - )? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -impl Mutation for GitFetch { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let mut tx = ws.start_transaction()?; - - let git_repo = match ws.git_repo()? { - Some(git_repo) => git_repo, - None => precondition!("No git backend"), - }; - - let mut remote_patterns = Vec::new(); - match *self { - GitFetch::AllBranches { remote_name } => { - remote_patterns.push((remote_name, None)); - } - GitFetch::AllRemotes { branch_ref } => { - let branch_name = branch_ref.as_branch()?; - for remote_name in git_repo - .remotes()? - .into_iter() - .filter_map(|remote| remote.map(|remote| remote.to_owned())) - { - remote_patterns.push((remote_name, Some(branch_name.to_owned()))); - } - } - GitFetch::RemoteBranch { - remote_name, - branch_ref, - } => { - let branch_name = branch_ref.as_branch()?; - remote_patterns.push((remote_name, Some(branch_name.to_owned()))); - } - } - - for (remote_name, pattern) in remote_patterns { - ws.session.callbacks.with_git(tx.mut_repo(), &|repo, cb| { - git::fetch( - repo, - &git_repo, - &remote_name, - &[pattern - .clone() - .map(StringPattern::exact) - .unwrap_or_else(StringPattern::everything)], - cb, - &ws.settings.git_settings(), - )?; - Ok(()) - })?; - } - - match ws.finish_transaction(tx, format!("fetch from git remote(s)"))? { - Some(new_status) => Ok(MutationResult::Updated { new_status }), - None => Ok(MutationResult::Unchanged), - } - } -} - -// this is another case where it would be nice if we could reuse jj-cli's error messages -impl Mutation for UndoOperation { - fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { - let head_op = op_walk::resolve_op_with_repo(ws.repo(), "@")?; // XXX this should be behind an abstraction, maybe reused in snapshot - let mut parent_ops = head_op.parents(); - - let Some(parent_op) = parent_ops.next().transpose()? else { - precondition!("Cannot undo repo initialization"); - }; - - if parent_ops.next().is_some() { - precondition!("Cannot undo a merge operation"); - }; - - let mut tx = ws.start_transaction()?; - let repo_loader = tx.base_repo().loader(); - let head_repo = repo_loader.load_at(&head_op)?; - let parent_repo = repo_loader.load_at(&parent_op)?; - tx.mut_repo().merge(&head_repo, &parent_repo); - let restored_view = tx.repo().view().store_view().clone(); - tx.mut_repo().set_view(restored_view); - - match ws.finish_transaction(tx, format!("undo operation {}", head_op.id().hex()))? { - Some(new_status) => { - let working_copy = ws.get_commit(ws.wc_id())?; - let new_selection = ws.format_header(&working_copy, None)?; - Ok(MutationResult::UpdatedSelection { - new_status, - new_selection, - }) - } - None => Ok(MutationResult::Unchanged), - } - } -} - -fn combine_messages(source: &Commit, destination: &Commit, abandon_source: bool) -> String { - if abandon_source { - if source.description().is_empty() { - destination.description().to_owned() - } else if destination.description().is_empty() { - source.description().to_owned() - } else { - destination.description().to_owned() + "\n" + source.description() - } - } else { - destination.description().to_owned() - } -} - -fn combine_branches(branch_names: &[impl Display]) -> String { - match branch_names { - [branch_name] => format!("branch {}", branch_name), - branch_names => format!("branches {}", branch_names.iter().join(", ")), - } -} - -fn build_matcher(paths: &Vec) -> Box { - if paths.is_empty() { - Box::new(EverythingMatcher) - } else { - Box::new(FilesMatcher::new( - paths - .iter() - .map(|p| RepoPath::from_internal_string(&p.repo_path)), - )) - } -} - -fn classify_branch_push( - branch_name: &str, - remote_name: &str, - targets: LocalAndRemoteRef, -) -> Result, String> { - let push_action = refs::classify_branch_push_action(targets); - match push_action { - BranchPushAction::AlreadyMatches => Ok(None), - BranchPushAction::Update(update) => Ok(Some(update)), - BranchPushAction::LocalConflicted => Err(format!("Branch {} is conflicted.", branch_name)), - BranchPushAction::RemoteConflicted => Err(format!( - "Branch {}@{} is conflicted. Try fetching first.", - branch_name, remote_name - )), - BranchPushAction::RemoteUntracked => Err(format!( - "Non-tracking remote branch {}@{} exists. Try tracking it first.", - branch_name, remote_name - )), - } -} +use std::fmt::Display; + +use anyhow::{anyhow, Context, Result}; +use indexmap::IndexMap; +use itertools::Itertools; +use jj_lib::{ + backend::{BackendError, CommitId}, + commit::Commit, + git::{self, GitBranchPushTargets, REMOTE_NAME_FOR_LOCAL_GIT_REPO}, + matchers::{EverythingMatcher, FilesMatcher, Matcher}, + object_id::ObjectId, + op_store::{RefTarget, RemoteRef, RemoteRefState}, + op_walk, + refs::{self, BranchPushAction, BranchPushUpdate, LocalAndRemoteRef}, + repo::Repo, + repo_path::RepoPath, + revset::{self, RevsetIteratorExt}, + rewrite, + settings::UserSettings, + str_util::StringPattern, +}; + +use super::{gui_util::WorkspaceSession, Mutation}; +use crate::messages::{ + AbandonRevisions, BackoutRevisions, CheckoutRevision, CopyChanges, CreateRef, CreateRevision, + DeleteRef, DescribeRevision, DuplicateRevisions, GitFetch, GitPush, InsertRevision, + MoveChanges, MoveRef, MoveRevision, MoveSource, MutationResult, RenameBranch, StoreRef, + TrackBranch, TreePath, UndoOperation, UntrackBranch, +}; + +macro_rules! precondition { + ($($args:tt)*) => { + return Ok(MutationResult::PreconditionError { message: format!($($args)*) }) + } +} + +impl Mutation for AbandonRevisions { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let abandoned_ids = self + .ids + .into_iter() + .map(|id| CommitId::try_from_hex(&id.hex).expect("frontend-validated id")) + .collect_vec(); + + if ws.check_immutable(abandoned_ids.clone())? { + precondition!("Some revisions are immutable"); + } + + for id in &abandoned_ids { + tx.mut_repo().record_abandoned_commit(id.clone()); + } + tx.mut_repo().rebase_descendants(&ws.settings)?; + + let transaction_description = if abandoned_ids.len() == 1 { + format!("abandon commit {}", abandoned_ids[0].hex()) + } else { + format!( + "abandon commit {} and {} more", + abandoned_ids[0].hex(), + abandoned_ids.len() - 1 + ) + }; + + match ws.finish_transaction(tx, transaction_description)? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for BackoutRevisions { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + if self.ids.len() != 1 { + precondition!("Not implemented for >1 rev"); + } + + let mut tx = ws.start_transaction()?; + + let working_copy = ws.get_commit(ws.wc_id())?; + let reverted = ws.resolve_multiple_changes(self.ids)?; + let reverted_parents: Result, BackendError> = reverted[0].parents().collect(); + + let old_base_tree = rewrite::merge_commit_trees(tx.mut_repo(), &reverted_parents?)?; + let new_base_tree = working_copy.tree()?; + let old_tree = reverted[0].tree()?; + let new_tree = new_base_tree.merge(&old_tree, &old_base_tree)?; + + tx.mut_repo() + .rewrite_commit(&ws.settings, &&working_copy) + .set_tree_id(new_tree.id()) + .write()?; + + match ws.finish_transaction(tx, format!("back out commit {}", reverted[0].id().hex()))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for CheckoutRevision { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let edited = ws.resolve_single_change(&self.id)?; + + if ws.check_immutable(vec![edited.id().clone()])? { + precondition!("Revision is immutable"); + } + + if edited.id() == ws.wc_id() { + return Ok(MutationResult::Unchanged); + } + + tx.mut_repo().edit(ws.id().clone(), &edited)?; + + match ws.finish_transaction(tx, format!("edit commit {}", edited.id().hex()))? { + Some(new_status) => { + let new_selection = ws.format_header(&edited, Some(false))?; + Ok(MutationResult::UpdatedSelection { + new_status, + new_selection, + }) + } + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for CreateRevision { + fn execute<'a>(self: Box, ws: &'a mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let parents_revset = ws.evaluate_revset_changes( + &self + .parent_ids + .into_iter() + .map(|id| id.change) + .collect_vec(), + )?; + + let parent_ids = parents_revset.iter().collect_vec(); + let parent_commits = ws.resolve_multiple(parents_revset)?; + let merged_tree = rewrite::merge_commit_trees(tx.repo(), &parent_commits)?; + + let new_commit = tx + .mut_repo() + .new_commit(&ws.settings, parent_ids, merged_tree.id()) + .write()?; + + tx.mut_repo().edit(ws.id().clone(), &new_commit)?; + + match ws.finish_transaction(tx, "new empty commit")? { + Some(new_status) => { + let new_selection = ws.format_header(&new_commit, Some(false))?; + Ok(MutationResult::UpdatedSelection { + new_status, + new_selection, + }) + } + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for DescribeRevision { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let described = ws.resolve_single_change(&self.id)?; + + if ws.check_immutable(vec![described.id().clone()])? { + precondition!("Revision {} is immutable", self.id.change.prefix); + } + + if self.new_description == described.description() && !self.reset_author { + return Ok(MutationResult::Unchanged); + } + + let mut commit_builder = tx + .mut_repo() + .rewrite_commit(&ws.settings, &described) + .set_description(self.new_description); + + if self.reset_author { + let new_author = commit_builder.committer().clone(); + commit_builder = commit_builder.set_author(new_author); + } + + commit_builder.write()?; + + match ws.finish_transaction(tx, format!("describe commit {}", described.id().hex()))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for DuplicateRevisions { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let clonees = ws.resolve_multiple_changes(self.ids)?; // in reverse topological order + let num_clonees = clonees.len(); + let mut clones: IndexMap = IndexMap::new(); + + // toposort ensures that parents are duplicated first + for clonee in clonees.into_iter().rev() { + let clone_parents: Result, _> = clonee + .parents() + .map_ok(|parent| { + if let Some(cloned_parent) = clones.get(&parent) { + cloned_parent + } else { + &parent + } + .id() + .clone() + }) + .collect(); + let clone = tx + .mut_repo() + .rewrite_commit(&ws.settings, &clonee) + .generate_new_change_id() + .set_parents(clone_parents?) + .write()?; + clones.insert(clonee, clone); + } + + match ws.finish_transaction(tx, format!("duplicating {} commit(s)", num_clonees))? { + Some(new_status) => { + if num_clonees == 1 { + let new_commit = clones + .get_index(0) + .ok_or(anyhow!("single source should have single copy"))? + .1; + let new_selection = ws.format_header(new_commit, None)?; + Ok(MutationResult::UpdatedSelection { + new_status, + new_selection, + }) + } else { + Ok(MutationResult::Updated { new_status }) + } + } + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for InsertRevision { + fn execute<'a>(self: Box, ws: &'a mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let target = ws + .resolve_single_change(&self.id) + .context("resolve change_id")?; + let before = ws + .resolve_single_change(&self.before_id) + .context("resolve before_id")?; + let after = ws + .resolve_single_change(&self.after_id) + .context("resolve after_id")?; + + if ws.check_immutable(vec![target.id().clone(), before.id().clone()])? { + precondition!("Some revisions are immutable"); + } + + // rebase the target's children + let rebased_children = ws.disinherit_children(&mut tx, &target)?; + + // update after, which may have been a descendant of target + let after_id = rebased_children + .get(after.id()) + .unwrap_or(after.id()) + .clone(); + + // rebase the target (which now has no children), then the new post-target tree atop it + let rebased_id = target.id().hex(); + let target = rewrite::rebase_commit(&ws.settings, tx.mut_repo(), target, vec![after_id])?; + rewrite::rebase_commit( + &ws.settings, + tx.mut_repo(), + before, + vec![target.id().clone()], + )?; + + match ws.finish_transaction(tx, format!("rebase commit {}", rebased_id))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for MoveRevision { + fn execute<'a>(self: Box, ws: &'a mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let target = ws.resolve_single_change(&self.id)?; + let parents = ws.resolve_multiple_changes(self.parent_ids)?; + + if ws.check_immutable(vec![target.id().clone()])? { + precondition!("Revision {} is immutable", self.id.change.prefix); + } + + // rebase the target's children + let rebased_children = ws.disinherit_children(&mut tx, &target)?; + + // update parents, which may have been descendants of the target + let parent_ids: Vec<_> = parents + .iter() + .map(|new_parent| { + rebased_children + .get(new_parent.id()) + .unwrap_or(new_parent.id()) + .clone() + }) + .collect(); + + // rebase the target itself + let rebased_id = target.id().hex(); + rewrite::rebase_commit(&ws.settings, tx.mut_repo(), target, parent_ids)?; + + match ws.finish_transaction(tx, format!("rebase commit {}", rebased_id))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for MoveSource { + fn execute<'a>(self: Box, ws: &'a mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let target = ws.resolve_single_change(&self.id)?; + let parent_ids = ws + .resolve_multiple_commits(&self.parent_ids)? + .into_iter() + .map(|commit| commit.id().clone()) + .collect(); + + if ws.check_immutable(vec![target.id().clone()])? { + precondition!("Revision {} is immutable", self.id.change.prefix); + } + + // just rebase the target, which will also rebase its descendants + let rebased_id = target.id().hex(); + rewrite::rebase_commit(&ws.settings, tx.mut_repo(), target, parent_ids)?; + + match ws.finish_transaction(tx, format!("rebase commit {}", rebased_id))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for MoveChanges { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let from = ws.resolve_single_change(&self.from_id)?; + let mut to = ws.resolve_single_commit(&self.to_id)?; + let matcher = build_matcher(&self.paths); + + if ws.check_immutable(vec![from.id().clone(), to.id().clone()])? { + precondition!("Revisions are immutable"); + } + + // construct a split tree and a remainder tree by copying changes from child to parent and from parent to child + let from_tree = from.tree()?; + let from_parents: Result, _> = from.parents().collect(); + let parent_tree = rewrite::merge_commit_trees(tx.repo(), &from_parents?)?; + let split_tree_id = rewrite::restore_tree(&from_tree, &parent_tree, matcher.as_ref())?; + let split_tree = tx.repo().store().get_root_tree(&split_tree_id)?; + let remainder_tree_id = rewrite::restore_tree(&parent_tree, &from_tree, matcher.as_ref())?; + let remainder_tree = tx.repo().store().get_root_tree(&remainder_tree_id)?; + + // abandon or rewrite source + let abandon_source = remainder_tree.id() == parent_tree.id(); + if abandon_source { + tx.mut_repo().record_abandoned_commit(from.id().clone()); + } else { + tx.mut_repo() + .rewrite_commit(&ws.settings, &from) + .set_tree_id(remainder_tree.id().clone()) + .write()?; + } + + // rebase descendants of source, which may include destination + if tx.repo().index().is_ancestor(from.id(), to.id()) { + let rebase_map = tx.mut_repo().rebase_descendants_return_map(&ws.settings)?; + let rebased_to_id = rebase_map + .get(to.id()) + .ok_or(anyhow!("descendant to_commit not found in rebase map"))? + .clone(); + to = tx.mut_repo().store().get_commit(&rebased_to_id)?; + } + + // apply changes to destination + let to_tree = to.tree()?; + let new_to_tree = to_tree.merge(&parent_tree, &split_tree)?; + let description = combine_messages(&from, &to, abandon_source); + tx.mut_repo() + .rewrite_commit(&ws.settings, &to) + .set_tree_id(new_to_tree.id().clone()) + .set_description(description) + .write()?; + + match ws.finish_transaction( + tx, + format!("move changes from {} to {}", from.id().hex(), to.id().hex()), + )? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for CopyChanges { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let from_tree = ws.resolve_single_commit(&self.from_id)?.tree()?; + let to = ws.resolve_single_change(&self.to_id)?; + let matcher = build_matcher(&self.paths); + + if ws.check_immutable(vec![to.id().clone()])? { + precondition!("Revisions are immutable"); + } + + // construct a restore tree - the destination with some portions overwritten by the source + let to_tree = to.tree()?; + let new_to_tree_id = rewrite::restore_tree(&from_tree, &to_tree, matcher.as_ref())?; + if &new_to_tree_id == to.tree_id() { + Ok(MutationResult::Unchanged) + } else { + tx.mut_repo() + .rewrite_commit(&ws.settings, &to) + .set_tree_id(new_to_tree_id) + .write()?; + + tx.mut_repo().rebase_descendants(&ws.settings)?; + + match ws.finish_transaction(tx, format!("restore into commit {}", to.id().hex()))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } + } +} + +impl Mutation for TrackBranch { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + match self.r#ref { + StoreRef::Tag { tag_name } => { + precondition!("{} is a tag and cannot be tracked", tag_name); + } + StoreRef::LocalBranch { branch_name, .. } => { + precondition!("{} is a local branch and cannot be tracked", branch_name); + } + StoreRef::RemoteBranch { + branch_name, + remote_name, + .. + } => { + let mut tx = ws.start_transaction()?; + + let remote_ref: &jj_lib::op_store::RemoteRef = + ws.view().get_remote_branch(&branch_name, &remote_name); + + if remote_ref.is_tracking() { + precondition!("{branch_name}@{remote_name} is already tracked"); + } + + tx.mut_repo() + .track_remote_branch(&branch_name, &remote_name); + + match ws.finish_transaction(tx, format!("track remote branch {}", branch_name))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } + } + } +} + +impl Mutation for UntrackBranch { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let mut untracked = Vec::new(); + match self.r#ref { + StoreRef::Tag { tag_name } => { + precondition!("{} is a tag and cannot be untracked", tag_name); + } + StoreRef::LocalBranch { branch_name, .. } => { + // untrack all remotes + for ((name, remote), remote_ref) in ws.view().remote_branches_matching( + &StringPattern::exact(branch_name), + &StringPattern::everything(), + ) { + if remote != REMOTE_NAME_FOR_LOCAL_GIT_REPO && remote_ref.is_tracking() { + tx.mut_repo().untrack_remote_branch(name, remote); + untracked.push(format!("{name}@{remote}")); + } + } + } + StoreRef::RemoteBranch { + branch_name, + remote_name, + .. + } => { + let remote_ref: &jj_lib::op_store::RemoteRef = + ws.view().get_remote_branch(&branch_name, &remote_name); + + if !remote_ref.is_tracking() { + precondition!("{branch_name}@{remote_name} is not tracked"); + } + + tx.mut_repo() + .untrack_remote_branch(&branch_name, &remote_name); + untracked.push(format!("{branch_name}@{remote_name}")); + } + } + + match ws.finish_transaction( + tx, + format!("untrack remote {}", combine_branches(&untracked)), + )? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for RenameBranch { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let old_name = self.r#ref.as_branch()?; + + let ref_target = ws.view().get_local_branch(old_name).clone(); + if ref_target.is_absent() { + precondition!("No such branch: {}", old_name); + } + + if ws.view().get_local_branch(&self.new_name).is_present() { + precondition!("Branch already exists: {}", &self.new_name); + } + + let mut tx = ws.start_transaction()?; + + tx.mut_repo() + .set_local_branch_target(&self.new_name, ref_target); + tx.mut_repo() + .set_local_branch_target(old_name, RefTarget::absent()); + + match ws.finish_transaction(tx, format!("rename {} to {}", old_name, self.new_name))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for CreateRef { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let commit = ws.resolve_single_change(&self.id)?; + + match self.r#ref { + StoreRef::RemoteBranch { + branch_name, + remote_name, + .. + } => { + precondition!( + "{}@{} is a remote branch and cannot be created", + branch_name, + remote_name + ); + } + StoreRef::LocalBranch { branch_name, .. } => { + let existing_branch = ws.view().get_local_branch(&branch_name); + if existing_branch.is_present() { + precondition!("{} already exists", branch_name); + } + + tx.mut_repo() + .set_local_branch_target(&branch_name, RefTarget::normal(commit.id().clone())); + + match ws.finish_transaction( + tx, + format!( + "create {} pointing to commit {}", + branch_name, + ws.format_commit_id(commit.id()).hex + ), + )? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } + StoreRef::Tag { tag_name, .. } => { + let existing_tag = ws.view().get_tag(&tag_name); + if existing_tag.is_present() { + precondition!("{} already exists", tag_name); + } + + tx.mut_repo() + .set_tag_target(&tag_name, RefTarget::normal(commit.id().clone())); + + match ws.finish_transaction( + tx, + format!( + "create {} pointing to commit {}", + tag_name, + ws.format_commit_id(commit.id()).hex + ), + )? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } + } + } +} + +impl Mutation for DeleteRef { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + match self.r#ref { + StoreRef::RemoteBranch { + branch_name, + remote_name, + .. + } => { + let mut tx = ws.start_transaction()?; + + // forget the branch entirely - when target is absent, it's removed from the view + let remote_ref = RemoteRef { + target: RefTarget::absent(), + state: RemoteRefState::New, + }; + + tx.mut_repo() + .set_remote_branch(&branch_name, &remote_name, remote_ref); + + match ws + .finish_transaction(tx, format!("forget {}@{}", branch_name, remote_name))? + { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } + StoreRef::LocalBranch { branch_name, .. } => { + let mut tx = ws.start_transaction()?; + + tx.mut_repo() + .set_local_branch_target(&branch_name, RefTarget::absent()); + + match ws.finish_transaction(tx, format!("forget {}", branch_name))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } + StoreRef::Tag { tag_name } => { + let mut tx = ws.start_transaction()?; + + tx.mut_repo().set_tag_target(&tag_name, RefTarget::absent()); + + match ws.finish_transaction(tx, format!("forget tag {}", tag_name))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } + } + } +} + +// does not currently enforce fast-forwards +impl Mutation for MoveRef { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let commit = ws.resolve_single_change(&self.to_id)?; + + match self.r#ref { + StoreRef::RemoteBranch { + branch_name, + remote_name, + .. + } => { + precondition!("Branch is remote: {branch_name}@{remote_name}") + } + StoreRef::LocalBranch { branch_name, .. } => { + let old_target = ws.view().get_local_branch(&branch_name); + if old_target.is_absent() { + precondition!("No such branch: {branch_name}"); + } + + tx.mut_repo() + .set_local_branch_target(&branch_name, RefTarget::normal(commit.id().clone())); + + match ws.finish_transaction( + tx, + format!("point {} to commit {}", branch_name, commit.id().hex()), + )? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } + StoreRef::Tag { tag_name } => { + let old_target = ws.view().get_tag(&tag_name); + if old_target.is_absent() { + precondition!("No such tag: {tag_name}"); + } + + tx.mut_repo() + .set_tag_target(&tag_name, RefTarget::normal(commit.id().clone())); + + match ws.finish_transaction( + tx, + format!("point {} to commit {}", tag_name, commit.id().hex()), + )? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } + } + } +} + +impl Mutation for GitPush { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let git_repo = match ws.git_repo()? { + Some(git_repo) => git_repo, + None => precondition!("No git backend"), + }; + + // determine branches to push, recording the old and new commits + let mut remote_branch_updates: Vec<(&str, Vec<(String, refs::BranchPushUpdate)>)> = + Vec::new(); + let remote_branch_refs: Vec<_> = match &*self { + GitPush::AllBranches { ref remote_name } => { + let mut branch_updates = Vec::new(); + for (branch_name, targets) in ws.view().local_remote_branches(&remote_name) { + if !targets.remote_ref.is_tracking() { + continue; + } + + match classify_branch_push(branch_name, &remote_name, targets) { + Err(message) => return Ok(MutationResult::PreconditionError { message }), + Ok(None) => (), + Ok(Some(update)) => branch_updates.push((branch_name.to_owned(), update)), + } + } + remote_branch_updates.push((remote_name, branch_updates)); + + ws.view().remote_branches(&remote_name).collect() + } + GitPush::AllRemotes { branch_ref } => { + let branch_name = branch_ref.as_branch()?; + + let mut remote_branch_refs = Vec::new(); + for (remote_name, group) in ws + .view() + .all_remote_branches() + .filter_map(|((branch, remote), remote_ref)| { + if remote_ref.is_tracking() && branch == branch_name { + Some((remote, remote_ref)) + } else { + None + } + }) + .chunk_by(|(remote_name, _)| *remote_name) + .into_iter() + { + let mut branch_updates = Vec::new(); + for (_, remote_ref) in group { + let targets = LocalAndRemoteRef { + local_target: ws.view().get_local_branch(branch_name), + remote_ref, + }; + match classify_branch_push(branch_name, &remote_name, targets) { + Err(message) => { + return Ok(MutationResult::PreconditionError { message }) + } + Ok(None) => (), + Ok(Some(update)) => { + branch_updates.push((branch_name.to_owned(), update)) + } + } + remote_branch_refs.push((remote_name, remote_ref)); + } + remote_branch_updates.push((remote_name, branch_updates)); + } + + remote_branch_refs + } + GitPush::RemoteBranch { + ref remote_name, + ref branch_ref, + } => { + let branch_name = branch_ref.as_branch()?; + let local_target = ws.view().get_local_branch(branch_name); + let remote_ref = ws.view().get_remote_branch(branch_name, remote_name); + + match classify_branch_push( + branch_name, + remote_name, + LocalAndRemoteRef { + local_target, + remote_ref, + }, + ) { + Err(message) => return Ok(MutationResult::PreconditionError { message }), + Ok(None) => (), + Ok(Some(update)) => { + remote_branch_updates + .push((remote_name, vec![(branch_name.to_owned(), update)])); + } + } + + vec![( + branch_name, + ws.view().get_remote_branch(branch_name, &remote_name), + )] + } + }; + + // check for conflicts + let mut new_heads = vec![]; + for (_, branch_updates) in &mut remote_branch_updates { + for (_, update) in branch_updates { + if let Some(new_target) = &update.new_target { + new_heads.push(new_target.clone()); + } + } + } + + let mut old_heads = remote_branch_refs + .into_iter() + .flat_map(|(_, old_head)| old_head.target.added_ids()) + .cloned() + .collect_vec(); + if old_heads.is_empty() { + old_heads.push(ws.repo().store().root_commit_id().clone()); + } + + for commit in revset::walk_revs(ws.repo(), &new_heads, &old_heads)? + .iter() + .commits(ws.repo().store()) + { + let commit = commit?; + let mut reasons = vec![]; + if commit.description().is_empty() { + reasons.push("it has no description"); + } + if commit.author().name.is_empty() + || commit.author().name == UserSettings::USER_NAME_PLACEHOLDER + || commit.author().email.is_empty() + || commit.author().email == UserSettings::USER_EMAIL_PLACEHOLDER + || commit.committer().name.is_empty() + || commit.committer().name == UserSettings::USER_NAME_PLACEHOLDER + || commit.committer().email.is_empty() + || commit.committer().email == UserSettings::USER_EMAIL_PLACEHOLDER + { + reasons.push("it has no author and/or committer set"); + } + if commit.has_conflict()? { + reasons.push("it has conflicts"); + } + if !reasons.is_empty() { + precondition!( + "Won't push revision {} since {}", + ws.format_change_id(commit.change_id()).prefix, + reasons.join(" and ") + ); + } + } + + // push to each remote + for (remote_name, branch_updates) in remote_branch_updates.into_iter() { + let targets = GitBranchPushTargets { branch_updates }; + + ws.session.callbacks.with_git(tx.mut_repo(), &|repo, cb| { + Ok(git::push_branches( + repo, + &git_repo, + &remote_name, + &targets, + cb, + )?) + })?; + } + + match ws.finish_transaction( + tx, + match *self { + GitPush::AllBranches { remote_name } => { + format!("push all tracked branches to git remote {}", remote_name) + } + GitPush::AllRemotes { branch_ref } => { + format!( + "push {} to all tracked git remotes", + branch_ref.as_branch()? + ) + } + GitPush::RemoteBranch { + remote_name, + branch_ref, + } => { + format!( + "push {} to git remote {}", + branch_ref.as_branch()?, + remote_name + ) + } + }, + )? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +impl Mutation for GitFetch { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let mut tx = ws.start_transaction()?; + + let git_repo = match ws.git_repo()? { + Some(git_repo) => git_repo, + None => precondition!("No git backend"), + }; + + let mut remote_patterns = Vec::new(); + match *self { + GitFetch::AllBranches { remote_name } => { + remote_patterns.push((remote_name, None)); + } + GitFetch::AllRemotes { branch_ref } => { + let branch_name = branch_ref.as_branch()?; + for remote_name in git_repo + .remotes()? + .into_iter() + .filter_map(|remote| remote.map(|remote| remote.to_owned())) + { + remote_patterns.push((remote_name, Some(branch_name.to_owned()))); + } + } + GitFetch::RemoteBranch { + remote_name, + branch_ref, + } => { + let branch_name = branch_ref.as_branch()?; + remote_patterns.push((remote_name, Some(branch_name.to_owned()))); + } + } + + for (remote_name, pattern) in remote_patterns { + ws.session.callbacks.with_git(tx.mut_repo(), &|repo, cb| { + git::fetch( + repo, + &git_repo, + &remote_name, + &[pattern + .clone() + .map(StringPattern::exact) + .unwrap_or_else(StringPattern::everything)], + cb, + &ws.settings.git_settings(), + )?; + Ok(()) + })?; + } + + match ws.finish_transaction(tx, format!("fetch from git remote(s)"))? { + Some(new_status) => Ok(MutationResult::Updated { new_status }), + None => Ok(MutationResult::Unchanged), + } + } +} + +// this is another case where it would be nice if we could reuse jj-cli's error messages +impl Mutation for UndoOperation { + fn execute(self: Box, ws: &mut WorkspaceSession) -> Result { + let head_op = op_walk::resolve_op_with_repo(ws.repo(), "@")?; // XXX this should be behind an abstraction, maybe reused in snapshot + let mut parent_ops = head_op.parents(); + + let Some(parent_op) = parent_ops.next().transpose()? else { + precondition!("Cannot undo repo initialization"); + }; + + if parent_ops.next().is_some() { + precondition!("Cannot undo a merge operation"); + }; + + let mut tx = ws.start_transaction()?; + let repo_loader = tx.base_repo().loader(); + let head_repo = repo_loader.load_at(&head_op)?; + let parent_repo = repo_loader.load_at(&parent_op)?; + tx.mut_repo().merge(&head_repo, &parent_repo); + let restored_view = tx.repo().view().store_view().clone(); + tx.mut_repo().set_view(restored_view); + + match ws.finish_transaction(tx, format!("undo operation {}", head_op.id().hex()))? { + Some(new_status) => { + let working_copy = ws.get_commit(ws.wc_id())?; + let new_selection = ws.format_header(&working_copy, None)?; + Ok(MutationResult::UpdatedSelection { + new_status, + new_selection, + }) + } + None => Ok(MutationResult::Unchanged), + } + } +} + +fn combine_messages(source: &Commit, destination: &Commit, abandon_source: bool) -> String { + if abandon_source { + if source.description().is_empty() { + destination.description().to_owned() + } else if destination.description().is_empty() { + source.description().to_owned() + } else { + destination.description().to_owned() + "\n" + source.description() + } + } else { + destination.description().to_owned() + } +} + +fn combine_branches(branch_names: &[impl Display]) -> String { + match branch_names { + [branch_name] => format!("branch {}", branch_name), + branch_names => format!("branches {}", branch_names.iter().join(", ")), + } +} + +fn build_matcher(paths: &Vec) -> Box { + if paths.is_empty() { + Box::new(EverythingMatcher) + } else { + Box::new(FilesMatcher::new( + paths + .iter() + .map(|p| RepoPath::from_internal_string(&p.repo_path)), + )) + } +} + +fn classify_branch_push( + branch_name: &str, + remote_name: &str, + targets: LocalAndRemoteRef, +) -> Result, String> { + let push_action = refs::classify_branch_push_action(targets); + match push_action { + BranchPushAction::AlreadyMatches => Ok(None), + BranchPushAction::Update(update) => Ok(Some(update)), + BranchPushAction::LocalConflicted => Err(format!("Branch {} is conflicted.", branch_name)), + BranchPushAction::RemoteConflicted => Err(format!( + "Branch {}@{} is conflicted. Try fetching first.", + branch_name, remote_name + )), + BranchPushAction::RemoteUntracked => Err(format!( + "Non-tracking remote branch {}@{} exists. Try tracking it first.", + branch_name, remote_name + )), + } +} diff --git a/src-tauri/src/worker/queries.rs b/src-tauri/src/worker/queries.rs index fd79977..158516e 100644 --- a/src-tauri/src/worker/queries.rs +++ b/src-tauri/src/worker/queries.rs @@ -1,593 +1,593 @@ -use std::{ - io::Write, - iter::{Peekable, Skip}, - ops::Range, -}; - -use anyhow::{anyhow, Result}; - -use futures_util::{try_join, StreamExt}; -use gix::bstr::ByteVec; -use itertools::Itertools; -use jj_lib::{ - backend::CommitId, - conflicts::{self, MaterializedTreeValue}, - diff::{self, Diff, DiffHunk}, - graph::{GraphEdge, GraphEdgeType, TopoGroupedGraphIterator}, - matchers::EverythingMatcher, - merged_tree::TreeDiffStream, - repo::Repo, - repo_path::RepoPath, - revset::Revset, - rewrite, -}; -use pollster::FutureExt; - -use crate::messages::{ - ChangeHunk, ChangeKind, FileRange, HunkLocation, LogCoordinates, LogLine, LogPage, LogRow, - MultilineString, RevChange, RevConflict, RevId, RevResult, -}; - -use super::WorkspaceSession; - -struct LogStem { - source: LogCoordinates, - target: CommitId, - indirect: bool, - was_inserted: bool, - known_immutable: bool, -} - -/// state used for init or restart of a query -pub struct QueryState { - /// max number of rows per page - page_size: usize, - /// number of rows already yielded - next_row: usize, - /// ongoing vertical lines; nodes will be placed on or around these - stems: Vec>, -} - -impl QueryState { - pub fn new(page_size: usize) -> QueryState { - QueryState { - page_size, - next_row: 0, - stems: Vec::new(), - } - } -} - -/// live instance of a query -pub struct QuerySession<'q, 'w: 'q> { - pub ws: &'q WorkspaceSession<'w>, - pub state: QueryState, - iter: Peekable< - Skip< - TopoGroupedGraphIterator< - CommitId, - Box>)> + 'q>, - >, - >, - >, - is_immutable: Box bool + 'q>, -} - -impl<'q, 'w> QuerySession<'q, 'w> { - pub fn new( - ws: &'q WorkspaceSession<'w>, - revset: &'q dyn Revset, - state: QueryState, - ) -> QuerySession<'q, 'w> { - let iter = TopoGroupedGraphIterator::new(revset.iter_graph()) - .skip(state.next_row) - .peekable(); - - let immutable_revset = ws.evaluate_immutable().unwrap(); - let is_immutable = immutable_revset.containing_fn(); - - QuerySession { - ws, - iter, - state, - is_immutable, - } - } - - pub fn get_page(&mut self) -> Result { - let mut rows: Vec = Vec::with_capacity(self.state.page_size); // output rows to draw - let mut row = self.state.next_row; - let max = row + self.state.page_size; - - let root_id = self.ws.repo().store().root_commit_id().clone(); - - while let Some((commit_id, commit_edges)) = self.iter.next() { - // output lines to draw for the current row - let mut lines: Vec = Vec::new(); - - // find an existing stem targeting the current node - let mut column = self.state.stems.len(); - let mut stem_known_immutable = false; - let mut padding = 0; // used to offset the commit summary past some edges - - if let Some(slot) = self.find_stem_for_commit(&commit_id) { - column = slot; - padding = self.state.stems.len() - column - 1; - } - - // terminate any existing stem, removing it from the end or leaving a gap - if column < self.state.stems.len() { - if let Some(terminated_stem) = &self.state.stems[column] { - stem_known_immutable = terminated_stem.known_immutable; - lines.push(if terminated_stem.was_inserted { - LogLine::FromNode { - indirect: terminated_stem.indirect, - source: terminated_stem.source, - target: LogCoordinates(column, row), - } - } else { - LogLine::ToNode { - indirect: terminated_stem.indirect, - source: terminated_stem.source, - target: LogCoordinates(column, row), - } - }); - } - self.state.stems[column] = None; - } - // otherwise, slot into any gaps that might exist - else { - for (slot, stem) in self.state.stems.iter().enumerate() { - if stem.is_none() { - column = slot; - padding = self.state.stems.len() - slot - 1; - break; - } - } - } - - let known_immutable = if stem_known_immutable { - Some(true) - } else { - Some((self.is_immutable)(&commit_id)) - }; - - let header = self - .ws - .format_header(&self.ws.get_commit(&commit_id)?, known_immutable)?; - - // remove empty stems on the right edge - let empty_stems = self - .state - .stems - .iter() - .rev() - .take_while(|stem| stem.is_none()) - .count(); - self.state - .stems - .truncate(self.state.stems.len() - empty_stems); - - // merge edges into existing stems or add new ones to the right - let mut next_missing: Option = None; - 'edges: for edge in commit_edges.iter() { - if edge.edge_type == GraphEdgeType::Missing { - if edge.target == root_id { - continue; - } else { - next_missing = Some(edge.target.clone()); - } - } - - let indirect = edge.edge_type != GraphEdgeType::Direct; - - for (slot, stem) in self.state.stems.iter().enumerate() { - if let Some(stem) = stem { - if stem.target == edge.target { - lines.push(LogLine::ToIntersection { - indirect, - source: LogCoordinates(column, row), - target: LogCoordinates(slot, row + 1), - }); - continue 'edges; - } - } - } - - for stem in self.state.stems.iter_mut() { - if stem.is_none() { - *stem = Some(LogStem { - source: LogCoordinates(column, row), - target: edge.target.clone(), - indirect, - was_inserted: true, - known_immutable: header.is_immutable, - }); - continue 'edges; - } - } - - self.state.stems.push(Some(LogStem { - source: LogCoordinates(column, row), - target: edge.target.clone(), - indirect, - was_inserted: false, - known_immutable: header.is_immutable, - })); - } - - rows.push(LogRow { - revision: header, - location: LogCoordinates(column, row), - padding, - lines, - }); - row = row + 1; - - // terminate any temporary stems created for missing edges - match next_missing - .take() - .and_then(|id| self.find_stem_for_commit(&id)) - { - Some(slot) => { - if let Some(terminated_stem) = &self.state.stems[slot] { - rows.last_mut().unwrap().lines.push(LogLine::ToMissing { - indirect: terminated_stem.indirect, - source: LogCoordinates(column, row - 1), - target: LogCoordinates(slot, row), - }); - } - self.state.stems[slot] = None; - row = row + 1; - } - None => (), - }; - - if row == max { - break; - } - } - - self.state.next_row = row; - Ok(LogPage { - rows, - has_more: self.iter.peek().is_some(), - }) - } - - fn find_stem_for_commit(&self, id: &CommitId) -> Option { - for (slot, stem) in self.state.stems.iter().enumerate() { - if let Some(LogStem { target, .. }) = stem { - if target == id { - return Some(slot); - } - } - } - - None - } -} - -#[cfg(test)] -pub fn query_log(ws: &WorkspaceSession, revset_str: &str, max_results: usize) -> Result { - let state = QueryState::new(max_results); - let revset = ws.evaluate_revset_str(revset_str)?; - let mut session = QuerySession::new(ws, &*revset, state); - session.get_page() -} - -// XXX this is reloading the header, which the client already has -pub fn query_revision(ws: &WorkspaceSession, id: RevId) -> Result { - let commit = match ws.resolve_optional_id(&id)? { - Some(commit) => commit, - None => return Ok(RevResult::NotFound { id }), - }; - - let commit_parents: Result, _> = commit.parents().collect(); - let parent_tree = rewrite::merge_commit_trees(ws.repo(), &commit_parents?)?; - let tree = commit.tree()?; - - let mut conflicts = Vec::new(); - for (path, entry) in parent_tree.entries() { - if let Ok(entry) = entry { - if !entry.is_resolved() { - match conflicts::materialize_tree_value(ws.repo().store(), &path, entry) - .block_on()? - { - MaterializedTreeValue::Conflict { contents, .. } => { - let mut hunks = get_unified_hunks(3, &contents, &[])?; - - conflicts.push(RevConflict { - path: ws.format_path(path), - hunk: hunks.pop().unwrap(), - }); - } - _ => { - log::warn!("nonresolved tree entry did not materialise as conflict"); - } - } - } - } - } - - let mut changes = Vec::new(); - let tree_diff = parent_tree.diff_stream(&tree, &EverythingMatcher); - format_tree_changes(ws, &mut changes, tree_diff).block_on()?; - - let header = ws.format_header(&commit, None)?; - - let parents = commit - .parents() - .map_ok(|p| { - ws.format_header( - &p, - if header.is_immutable { - Some(true) - } else { - None - }, - ) - }) - .collect::, _>>()? - .into_iter() - .collect::, _>>()?; - - Ok(RevResult::Detail { - header, - parents, - changes, - conflicts, - }) -} - -pub fn query_remotes( - ws: &WorkspaceSession, - tracking_branch: Option, -) -> Result> { - let git_repo = match ws.git_repo()? { - Some(git_repo) => git_repo, - None => return Err(anyhow!("No git backend")), - }; - - let all_remotes: Vec = git_repo - .remotes()? - .into_iter() - .filter_map(|remote| remote.map(|remote| remote.to_owned())) - .collect(); - - let matching_remotes = match tracking_branch { - Some(branch_name) => all_remotes - .into_iter() - .filter(|remote_name| { - let remote_ref = ws.view().get_remote_branch(&branch_name, &remote_name); - !remote_ref.is_absent() && remote_ref.is_tracking() - }) - .collect(), - None => all_remotes, - }; - - Ok(matching_remotes) -} - -async fn format_tree_changes( - ws: &WorkspaceSession<'_>, - changes: &mut Vec, - mut tree_diff: TreeDiffStream<'_>, -) -> Result<()> { - let store = ws.repo().store(); - - while let Some((path, diff)) = tree_diff.next().await { - let (before, after) = diff?; - - let kind = if before.is_present() && after.is_present() { - ChangeKind::Modified - } else if before.is_absent() { - ChangeKind::Added - } else { - ChangeKind::Deleted - }; - - let has_conflict = !after.is_resolved(); - - let before_future = conflicts::materialize_tree_value(store, &path, before); - let after_future = conflicts::materialize_tree_value(store, &path, after); - let (before_value, after_value) = try_join!(before_future, after_future)?; - - let hunks = get_value_hunks(3, &path, before_value, after_value)?; - - changes.push(RevChange { - path: ws.format_path(path), - kind, - has_conflict, - hunks, - }); - } - Ok(()) -} - -fn get_value_hunks( - num_context_lines: usize, - path: &RepoPath, - left_value: MaterializedTreeValue, - right_value: MaterializedTreeValue, -) -> Result> { - if left_value.is_absent() { - let right_part = get_value_contents(path, right_value)?; - get_unified_hunks(num_context_lines, &[], &right_part) - } else if right_value.is_present() { - let left_part = get_value_contents(&path, left_value)?; - let right_part = get_value_contents(&path, right_value)?; - get_unified_hunks(num_context_lines, &left_part, &right_part) - } else { - let left_part = get_value_contents(&path, left_value)?; - get_unified_hunks(num_context_lines, &left_part, &[]) - } -} - -fn get_value_contents(path: &RepoPath, value: MaterializedTreeValue) -> Result> { - match value { - MaterializedTreeValue::Absent => Err(anyhow!( - "Absent path {path:?} in diff should have been handled by caller" - )), - MaterializedTreeValue::File { mut reader, .. } => { - let mut contents = vec![]; - reader.read_to_end(&mut contents)?; - - let start = &contents[..8000.min(contents.len())]; // same heuristic git uses - let is_binary = start.contains(&b'\0'); - if is_binary { - contents.clear(); - contents.push_str("(binary)"); - } - Ok(contents) - } - MaterializedTreeValue::Symlink { target, .. } => Ok(target.into_bytes()), - MaterializedTreeValue::GitSubmodule(_) => Ok("(submodule)".to_owned().into_bytes()), - MaterializedTreeValue::Conflict { contents, .. } => Ok(contents), - MaterializedTreeValue::Tree(_) => Err(anyhow!("Unexpected tree in diff at path {path:?}")), - MaterializedTreeValue::AccessDenied(error) => Err(anyhow!(error)), - } -} - -fn get_unified_hunks( - num_context_lines: usize, - left_content: &[u8], - right_content: &[u8], -) -> Result> { - let mut hunks = Vec::new(); - - for hunk in unified_diff_hunks(left_content, right_content, num_context_lines) { - let location = HunkLocation { - from_file: FileRange { - start: hunk.left_line_range.start, - len: hunk.left_line_range.len(), - }, - to_file: FileRange { - start: hunk.right_line_range.start, - len: hunk.right_line_range.len(), - }, - }; - - let mut lines = Vec::new(); - for (line_type, content) in hunk.lines { - let mut formatter: Vec = vec![]; - match line_type { - DiffLineType::Context => { - write!(formatter, " ")?; - } - DiffLineType::Removed => { - write!(formatter, "-")?; - } - DiffLineType::Added => { - write!(formatter, "+")?; - } - } - formatter.write(content)?; - lines.push(std::str::from_utf8(&formatter)?.into()); - } - - hunks.push(ChangeHunk { - location, - lines: MultilineString { lines }, - }); - } - - Ok(hunks) -} - -/**************************/ -/* from jj_cli::diff_util */ -/**************************/ - -#[derive(PartialEq)] -enum DiffLineType { - Context, - Removed, - Added, -} - -struct UnifiedDiffHunk<'content> { - left_line_range: Range, - right_line_range: Range, - lines: Vec<(DiffLineType, &'content [u8])>, -} - -fn unified_diff_hunks<'content>( - left_content: &'content [u8], - right_content: &'content [u8], - num_context_lines: usize, -) -> Vec> { - let mut hunks = vec![]; - let mut current_hunk = UnifiedDiffHunk { - left_line_range: 1..1, - right_line_range: 1..1, - lines: vec![], - }; - let mut show_context_after = false; - let diff = Diff::for_tokenizer([left_content, right_content], &diff::find_line_ranges); - for hunk in diff.hunks() { - match hunk { - DiffHunk::Matching(content) => { - let lines = content.split_inclusive(|b| *b == b'\n').collect_vec(); - // Number of context lines to print after the previous non-matching hunk. - let num_after_lines = lines.len().min(if show_context_after { - num_context_lines - } else { - 0 - }); - current_hunk.left_line_range.end += num_after_lines; - current_hunk.right_line_range.end += num_after_lines; - for line in lines.iter().take(num_after_lines) { - current_hunk.lines.push((DiffLineType::Context, line)); - } - let num_skip_lines = lines - .len() - .saturating_sub(num_after_lines) - .saturating_sub(num_context_lines); - if num_skip_lines > 0 { - let left_start = current_hunk.left_line_range.end + num_skip_lines; - let right_start = current_hunk.right_line_range.end + num_skip_lines; - if !current_hunk.lines.is_empty() { - hunks.push(current_hunk); - } - current_hunk = UnifiedDiffHunk { - left_line_range: left_start..left_start, - right_line_range: right_start..right_start, - lines: vec![], - }; - } - let num_before_lines = lines.len() - num_after_lines - num_skip_lines; - current_hunk.left_line_range.end += num_before_lines; - current_hunk.right_line_range.end += num_before_lines; - for line in lines.iter().skip(num_after_lines + num_skip_lines) { - current_hunk.lines.push((DiffLineType::Context, line)); - } - } - DiffHunk::Different(content) => { - show_context_after = true; - let left_lines = content[0].split_inclusive(|b| *b == b'\n').collect_vec(); - let right_lines = content[1].split_inclusive(|b| *b == b'\n').collect_vec(); - if !left_lines.is_empty() { - current_hunk.left_line_range.end += left_lines.len(); - for line in left_lines { - current_hunk.lines.push((DiffLineType::Removed, line)); - } - } - if !right_lines.is_empty() { - current_hunk.right_line_range.end += right_lines.len(); - for line in right_lines { - current_hunk.lines.push((DiffLineType::Added, line)); - } - } - } - } - } - if !current_hunk - .lines - .iter() - .all(|(diff_type, _line)| *diff_type == DiffLineType::Context) - { - hunks.push(current_hunk); - } - hunks -} +use std::{ + io::Write, + iter::{Peekable, Skip}, + ops::Range, +}; + +use anyhow::{anyhow, Result}; + +use futures_util::{try_join, StreamExt}; +use gix::bstr::ByteVec; +use itertools::Itertools; +use jj_lib::{ + backend::CommitId, + conflicts::{self, MaterializedTreeValue}, + diff::{self, Diff, DiffHunk}, + graph::{GraphEdge, GraphEdgeType, TopoGroupedGraphIterator}, + matchers::EverythingMatcher, + merged_tree::TreeDiffStream, + repo::Repo, + repo_path::RepoPath, + revset::Revset, + rewrite, +}; +use pollster::FutureExt; + +use crate::messages::{ + ChangeHunk, ChangeKind, FileRange, HunkLocation, LogCoordinates, LogLine, LogPage, LogRow, + MultilineString, RevChange, RevConflict, RevId, RevResult, +}; + +use super::WorkspaceSession; + +struct LogStem { + source: LogCoordinates, + target: CommitId, + indirect: bool, + was_inserted: bool, + known_immutable: bool, +} + +/// state used for init or restart of a query +pub struct QueryState { + /// max number of rows per page + page_size: usize, + /// number of rows already yielded + next_row: usize, + /// ongoing vertical lines; nodes will be placed on or around these + stems: Vec>, +} + +impl QueryState { + pub fn new(page_size: usize) -> QueryState { + QueryState { + page_size, + next_row: 0, + stems: Vec::new(), + } + } +} + +/// live instance of a query +pub struct QuerySession<'q, 'w: 'q> { + pub ws: &'q WorkspaceSession<'w>, + pub state: QueryState, + iter: Peekable< + Skip< + TopoGroupedGraphIterator< + CommitId, + Box>)> + 'q>, + >, + >, + >, + is_immutable: Box bool + 'q>, +} + +impl<'q, 'w> QuerySession<'q, 'w> { + pub fn new( + ws: &'q WorkspaceSession<'w>, + revset: &'q dyn Revset, + state: QueryState, + ) -> QuerySession<'q, 'w> { + let iter = TopoGroupedGraphIterator::new(revset.iter_graph()) + .skip(state.next_row) + .peekable(); + + let immutable_revset = ws.evaluate_immutable().unwrap(); + let is_immutable = immutable_revset.containing_fn(); + + QuerySession { + ws, + iter, + state, + is_immutable, + } + } + + pub fn get_page(&mut self) -> Result { + let mut rows: Vec = Vec::with_capacity(self.state.page_size); // output rows to draw + let mut row = self.state.next_row; + let max = row + self.state.page_size; + + let root_id = self.ws.repo().store().root_commit_id().clone(); + + while let Some((commit_id, commit_edges)) = self.iter.next() { + // output lines to draw for the current row + let mut lines: Vec = Vec::new(); + + // find an existing stem targeting the current node + let mut column = self.state.stems.len(); + let mut stem_known_immutable = false; + let mut padding = 0; // used to offset the commit summary past some edges + + if let Some(slot) = self.find_stem_for_commit(&commit_id) { + column = slot; + padding = self.state.stems.len() - column - 1; + } + + // terminate any existing stem, removing it from the end or leaving a gap + if column < self.state.stems.len() { + if let Some(terminated_stem) = &self.state.stems[column] { + stem_known_immutable = terminated_stem.known_immutable; + lines.push(if terminated_stem.was_inserted { + LogLine::FromNode { + indirect: terminated_stem.indirect, + source: terminated_stem.source, + target: LogCoordinates(column, row), + } + } else { + LogLine::ToNode { + indirect: terminated_stem.indirect, + source: terminated_stem.source, + target: LogCoordinates(column, row), + } + }); + } + self.state.stems[column] = None; + } + // otherwise, slot into any gaps that might exist + else { + for (slot, stem) in self.state.stems.iter().enumerate() { + if stem.is_none() { + column = slot; + padding = self.state.stems.len() - slot - 1; + break; + } + } + } + + let known_immutable = if stem_known_immutable { + Some(true) + } else { + Some((self.is_immutable)(&commit_id)) + }; + + let header = self + .ws + .format_header(&self.ws.get_commit(&commit_id)?, known_immutable)?; + + // remove empty stems on the right edge + let empty_stems = self + .state + .stems + .iter() + .rev() + .take_while(|stem| stem.is_none()) + .count(); + self.state + .stems + .truncate(self.state.stems.len() - empty_stems); + + // merge edges into existing stems or add new ones to the right + let mut next_missing: Option = None; + 'edges: for edge in commit_edges.iter() { + if edge.edge_type == GraphEdgeType::Missing { + if edge.target == root_id { + continue; + } else { + next_missing = Some(edge.target.clone()); + } + } + + let indirect = edge.edge_type != GraphEdgeType::Direct; + + for (slot, stem) in self.state.stems.iter().enumerate() { + if let Some(stem) = stem { + if stem.target == edge.target { + lines.push(LogLine::ToIntersection { + indirect, + source: LogCoordinates(column, row), + target: LogCoordinates(slot, row + 1), + }); + continue 'edges; + } + } + } + + for stem in self.state.stems.iter_mut() { + if stem.is_none() { + *stem = Some(LogStem { + source: LogCoordinates(column, row), + target: edge.target.clone(), + indirect, + was_inserted: true, + known_immutable: header.is_immutable, + }); + continue 'edges; + } + } + + self.state.stems.push(Some(LogStem { + source: LogCoordinates(column, row), + target: edge.target.clone(), + indirect, + was_inserted: false, + known_immutable: header.is_immutable, + })); + } + + rows.push(LogRow { + revision: header, + location: LogCoordinates(column, row), + padding, + lines, + }); + row = row + 1; + + // terminate any temporary stems created for missing edges + match next_missing + .take() + .and_then(|id| self.find_stem_for_commit(&id)) + { + Some(slot) => { + if let Some(terminated_stem) = &self.state.stems[slot] { + rows.last_mut().unwrap().lines.push(LogLine::ToMissing { + indirect: terminated_stem.indirect, + source: LogCoordinates(column, row - 1), + target: LogCoordinates(slot, row), + }); + } + self.state.stems[slot] = None; + row = row + 1; + } + None => (), + }; + + if row == max { + break; + } + } + + self.state.next_row = row; + Ok(LogPage { + rows, + has_more: self.iter.peek().is_some(), + }) + } + + fn find_stem_for_commit(&self, id: &CommitId) -> Option { + for (slot, stem) in self.state.stems.iter().enumerate() { + if let Some(LogStem { target, .. }) = stem { + if target == id { + return Some(slot); + } + } + } + + None + } +} + +#[cfg(test)] +pub fn query_log(ws: &WorkspaceSession, revset_str: &str, max_results: usize) -> Result { + let state = QueryState::new(max_results); + let revset = ws.evaluate_revset_str(revset_str)?; + let mut session = QuerySession::new(ws, &*revset, state); + session.get_page() +} + +// XXX this is reloading the header, which the client already has +pub fn query_revision(ws: &WorkspaceSession, id: RevId) -> Result { + let commit = match ws.resolve_optional_id(&id)? { + Some(commit) => commit, + None => return Ok(RevResult::NotFound { id }), + }; + + let commit_parents: Result, _> = commit.parents().collect(); + let parent_tree = rewrite::merge_commit_trees(ws.repo(), &commit_parents?)?; + let tree = commit.tree()?; + + let mut conflicts = Vec::new(); + for (path, entry) in parent_tree.entries() { + if let Ok(entry) = entry { + if !entry.is_resolved() { + match conflicts::materialize_tree_value(ws.repo().store(), &path, entry) + .block_on()? + { + MaterializedTreeValue::Conflict { contents, .. } => { + let mut hunks = get_unified_hunks(3, &contents, &[])?; + + conflicts.push(RevConflict { + path: ws.format_path(path), + hunk: hunks.pop().unwrap(), + }); + } + _ => { + log::warn!("nonresolved tree entry did not materialise as conflict"); + } + } + } + } + } + + let mut changes = Vec::new(); + let tree_diff = parent_tree.diff_stream(&tree, &EverythingMatcher); + format_tree_changes(ws, &mut changes, tree_diff).block_on()?; + + let header = ws.format_header(&commit, None)?; + + let parents = commit + .parents() + .map_ok(|p| { + ws.format_header( + &p, + if header.is_immutable { + Some(true) + } else { + None + }, + ) + }) + .collect::, _>>()? + .into_iter() + .collect::, _>>()?; + + Ok(RevResult::Detail { + header, + parents, + changes, + conflicts, + }) +} + +pub fn query_remotes( + ws: &WorkspaceSession, + tracking_branch: Option, +) -> Result> { + let git_repo = match ws.git_repo()? { + Some(git_repo) => git_repo, + None => return Err(anyhow!("No git backend")), + }; + + let all_remotes: Vec = git_repo + .remotes()? + .into_iter() + .filter_map(|remote| remote.map(|remote| remote.to_owned())) + .collect(); + + let matching_remotes = match tracking_branch { + Some(branch_name) => all_remotes + .into_iter() + .filter(|remote_name| { + let remote_ref = ws.view().get_remote_branch(&branch_name, &remote_name); + !remote_ref.is_absent() && remote_ref.is_tracking() + }) + .collect(), + None => all_remotes, + }; + + Ok(matching_remotes) +} + +async fn format_tree_changes( + ws: &WorkspaceSession<'_>, + changes: &mut Vec, + mut tree_diff: TreeDiffStream<'_>, +) -> Result<()> { + let store = ws.repo().store(); + + while let Some((path, diff)) = tree_diff.next().await { + let (before, after) = diff?; + + let kind = if before.is_present() && after.is_present() { + ChangeKind::Modified + } else if before.is_absent() { + ChangeKind::Added + } else { + ChangeKind::Deleted + }; + + let has_conflict = !after.is_resolved(); + + let before_future = conflicts::materialize_tree_value(store, &path, before); + let after_future = conflicts::materialize_tree_value(store, &path, after); + let (before_value, after_value) = try_join!(before_future, after_future)?; + + let hunks = get_value_hunks(3, &path, before_value, after_value)?; + + changes.push(RevChange { + path: ws.format_path(path), + kind, + has_conflict, + hunks, + }); + } + Ok(()) +} + +fn get_value_hunks( + num_context_lines: usize, + path: &RepoPath, + left_value: MaterializedTreeValue, + right_value: MaterializedTreeValue, +) -> Result> { + if left_value.is_absent() { + let right_part = get_value_contents(path, right_value)?; + get_unified_hunks(num_context_lines, &[], &right_part) + } else if right_value.is_present() { + let left_part = get_value_contents(&path, left_value)?; + let right_part = get_value_contents(&path, right_value)?; + get_unified_hunks(num_context_lines, &left_part, &right_part) + } else { + let left_part = get_value_contents(&path, left_value)?; + get_unified_hunks(num_context_lines, &left_part, &[]) + } +} + +fn get_value_contents(path: &RepoPath, value: MaterializedTreeValue) -> Result> { + match value { + MaterializedTreeValue::Absent => Err(anyhow!( + "Absent path {path:?} in diff should have been handled by caller" + )), + MaterializedTreeValue::File { mut reader, .. } => { + let mut contents = vec![]; + reader.read_to_end(&mut contents)?; + + let start = &contents[..8000.min(contents.len())]; // same heuristic git uses + let is_binary = start.contains(&b'\0'); + if is_binary { + contents.clear(); + contents.push_str("(binary)"); + } + Ok(contents) + } + MaterializedTreeValue::Symlink { target, .. } => Ok(target.into_bytes()), + MaterializedTreeValue::GitSubmodule(_) => Ok("(submodule)".to_owned().into_bytes()), + MaterializedTreeValue::Conflict { contents, .. } => Ok(contents), + MaterializedTreeValue::Tree(_) => Err(anyhow!("Unexpected tree in diff at path {path:?}")), + MaterializedTreeValue::AccessDenied(error) => Err(anyhow!(error)), + } +} + +fn get_unified_hunks( + num_context_lines: usize, + left_content: &[u8], + right_content: &[u8], +) -> Result> { + let mut hunks = Vec::new(); + + for hunk in unified_diff_hunks(left_content, right_content, num_context_lines) { + let location = HunkLocation { + from_file: FileRange { + start: hunk.left_line_range.start, + len: hunk.left_line_range.len(), + }, + to_file: FileRange { + start: hunk.right_line_range.start, + len: hunk.right_line_range.len(), + }, + }; + + let mut lines = Vec::new(); + for (line_type, content) in hunk.lines { + let mut formatter: Vec = vec![]; + match line_type { + DiffLineType::Context => { + write!(formatter, " ")?; + } + DiffLineType::Removed => { + write!(formatter, "-")?; + } + DiffLineType::Added => { + write!(formatter, "+")?; + } + } + formatter.write(content)?; + lines.push(std::str::from_utf8(&formatter)?.into()); + } + + hunks.push(ChangeHunk { + location, + lines: MultilineString { lines }, + }); + } + + Ok(hunks) +} + +/**************************/ +/* from jj_cli::diff_util */ +/**************************/ + +#[derive(PartialEq)] +enum DiffLineType { + Context, + Removed, + Added, +} + +struct UnifiedDiffHunk<'content> { + left_line_range: Range, + right_line_range: Range, + lines: Vec<(DiffLineType, &'content [u8])>, +} + +fn unified_diff_hunks<'content>( + left_content: &'content [u8], + right_content: &'content [u8], + num_context_lines: usize, +) -> Vec> { + let mut hunks = vec![]; + let mut current_hunk = UnifiedDiffHunk { + left_line_range: 1..1, + right_line_range: 1..1, + lines: vec![], + }; + let mut show_context_after = false; + let diff = Diff::for_tokenizer([left_content, right_content], &diff::find_line_ranges); + for hunk in diff.hunks() { + match hunk { + DiffHunk::Matching(content) => { + let lines = content.split_inclusive(|b| *b == b'\n').collect_vec(); + // Number of context lines to print after the previous non-matching hunk. + let num_after_lines = lines.len().min(if show_context_after { + num_context_lines + } else { + 0 + }); + current_hunk.left_line_range.end += num_after_lines; + current_hunk.right_line_range.end += num_after_lines; + for line in lines.iter().take(num_after_lines) { + current_hunk.lines.push((DiffLineType::Context, line)); + } + let num_skip_lines = lines + .len() + .saturating_sub(num_after_lines) + .saturating_sub(num_context_lines); + if num_skip_lines > 0 { + let left_start = current_hunk.left_line_range.end + num_skip_lines; + let right_start = current_hunk.right_line_range.end + num_skip_lines; + if !current_hunk.lines.is_empty() { + hunks.push(current_hunk); + } + current_hunk = UnifiedDiffHunk { + left_line_range: left_start..left_start, + right_line_range: right_start..right_start, + lines: vec![], + }; + } + let num_before_lines = lines.len() - num_after_lines - num_skip_lines; + current_hunk.left_line_range.end += num_before_lines; + current_hunk.right_line_range.end += num_before_lines; + for line in lines.iter().skip(num_after_lines + num_skip_lines) { + current_hunk.lines.push((DiffLineType::Context, line)); + } + } + DiffHunk::Different(content) => { + show_context_after = true; + let left_lines = content[0].split_inclusive(|b| *b == b'\n').collect_vec(); + let right_lines = content[1].split_inclusive(|b| *b == b'\n').collect_vec(); + if !left_lines.is_empty() { + current_hunk.left_line_range.end += left_lines.len(); + for line in left_lines { + current_hunk.lines.push((DiffLineType::Removed, line)); + } + } + if !right_lines.is_empty() { + current_hunk.right_line_range.end += right_lines.len(); + for line in right_lines { + current_hunk.lines.push((DiffLineType::Added, line)); + } + } + } + } + } + if !current_hunk + .lines + .iter() + .all(|(diff_type, _line)| *diff_type == DiffLineType::Context) + { + hunks.push(current_hunk); + } + hunks +} diff --git a/src-tauri/src/worker/session.rs b/src-tauri/src/worker/session.rs index cd53c7e..e21213a 100644 --- a/src-tauri/src/worker/session.rs +++ b/src-tauri/src/worker/session.rs @@ -1,366 +1,366 @@ -use std::{ - panic::{catch_unwind, AssertUnwindSafe}, - path::PathBuf, - sync::mpsc::{Receiver, Sender}, -}; - -use anyhow::{anyhow, Context, Result}; -use jj_cli::config::{write_config_value_to_file, ConfigNamePathBuf, ConfigSource}; - -use super::{ - gui_util::WorkspaceSession, - queries::{self, QueryState}, - Mutation, WorkerSession, -}; -use crate::{ - config::{read_config, GGSettings}, - handler, messages, -}; - -/// implemented by states of the event loop -pub trait Session { - type Transition; - fn handle_events(self, rx: &Receiver) -> Result; -} - -/// messages sent to a worker from other threads. most come with a channel allowing a response -#[derive(Debug)] -pub enum SessionEvent { - #[allow(dead_code)] // used by tests - EndSession, - OpenWorkspace { - tx: Sender>, - wd: Option, - }, - QueryRevision { - tx: Sender>, - id: messages::RevId, - }, - QueryRemotes { - tx: Sender>>, - tracking_branch: Option, - }, - QueryLog { - tx: Sender>, - query: String, - }, - QueryLogNextPage { - tx: Sender>, - }, - ExecuteSnapshot { - tx: Sender>, - }, - ExecuteMutation { - tx: Sender, - mutation: Box, - }, - ReadConfigArray { - tx: Sender>>, - key: Vec, - }, - WriteConfigArray { - scope: ConfigSource, - key: Vec, - values: Vec, - }, -} - -/// transitions for a workspace session -pub enum WorkspaceResult { - Reopen(Sender>, Option), // workspace -> workspace - SessionComplete, // workspace -> worker -} - -/// transition for a query session -pub struct QueryResult(SessionEvent, QueryState); // query -> workspace - -/// event loop state for a workspace session -#[derive(Default)] -struct WorkspaceState { - pub unhandled_event: Option, - pub unpaged_query: Option, -} - -impl Session for WorkerSession { - type Transition = (); - - fn handle_events(mut self, rx: &Receiver) -> Result<()> { - let mut latest_wd: Option = None; - - loop { - let evt = rx.recv(); - log::debug!("WorkerSession handling {evt:?}"); - match evt { - Ok(SessionEvent::EndSession) => return Ok(()), - Ok(SessionEvent::ExecuteSnapshot { .. }) => (), - Ok(SessionEvent::OpenWorkspace { mut tx, mut wd }) => loop { - let resolved_wd = match wd.clone().or(latest_wd) { - Some(wd) => wd, - None => match self.get_cwd() { - Ok(wd) => wd, - Err(err) => { - latest_wd = None; - tx.send(Ok(messages::RepoConfig::LoadError { - absolute_path: PathBuf::new().into(), - message: format!("{err:#}"), - }))?; - break; - } - }, - }; - - let mut ws = match self.load_directory(&resolved_wd) { - Ok(ws) => ws, - Err(err) => { - latest_wd = None; - tx.send(Ok(messages::RepoConfig::LoadError { - absolute_path: resolved_wd.into(), - message: format!("{err:#}"), - }))?; - break; - } - }; - - latest_wd = Some(resolved_wd); - - ws.import_and_snapshot(false)?; - - tx.send(ws.format_config())?; - - match ws.handle_events(rx).context("WorkspaceSession")? { - WorkspaceResult::Reopen(new_tx, new_cwd) => (tx, wd) = (new_tx, new_cwd), - WorkspaceResult::SessionComplete => return Ok(()), - } - }, - Ok(evt) => { - log::error!( - "WorkerSession::handle_events(): repo not loaded when receiving {evt:?}" - ); - return Err(anyhow::anyhow!( - "A repo must be loaded before any other operations" - )); - } - Err(err) => { - log::error!("WorkerSession::handle_events(): {err}"); - return Err(anyhow!(err)); - } - }; - } - } -} - -impl Session for WorkspaceSession<'_> { - type Transition = WorkspaceResult; - - fn handle_events(mut self, rx: &Receiver) -> Result { - let mut state = WorkspaceState::default(); - - loop { - let next_event = if state.unhandled_event.is_some() { - state.unhandled_event.take().unwrap() - } else { - let evt = rx.recv(); - log::debug!("WorkspaceSession handling {evt:?}"); - evt? - }; - - match next_event { - SessionEvent::EndSession => return Ok(WorkspaceResult::SessionComplete), - SessionEvent::OpenWorkspace { tx, wd: cwd } => { - return Ok(WorkspaceResult::Reopen(tx, cwd)); - } - SessionEvent::QueryRevision { tx, id } => { - tx.send(queries::query_revision(&self, id))? - } - SessionEvent::QueryRemotes { - tx, - tracking_branch, - } => tx.send(queries::query_remotes(&self, tracking_branch))?, - SessionEvent::QueryLog { - tx, - query: revset_string, - } => { - let log_page_size = self - .session - .force_log_page_size - .unwrap_or(self.settings.query_log_page_size()); - handle_query( - &mut state, - &self, - tx, - rx, - Some(&revset_string), - Some(QueryState::new(log_page_size)), - )?; - - self.session.latest_query = Some(revset_string); - } - SessionEvent::QueryLogNextPage { tx } => { - let revset_string = self.session.latest_query.as_ref().map(|x| x.as_str()); - handle_query(&mut state, &self, tx, rx, revset_string, None)?; - } - SessionEvent::ExecuteSnapshot { tx } => { - let updated_head = self.load_at_head()?; // alternatively, this could be folded into snapshot so that it's done by all mutations - if self.import_and_snapshot(false)? || updated_head { - tx.send(Some(self.format_status()))?; - } else { - tx.send(None)?; - } - } - SessionEvent::ExecuteMutation { tx, mutation } => { - let name = mutation.as_ref().describe(); - match catch_unwind(AssertUnwindSafe(|| { - mutation.execute(&mut self).with_context(|| name.clone()) - })) { - Ok(result) => { - tx.send(match result { - Ok(result) => result, - Err(err) => { - log::error!("{err:?}"); - messages::MutationResult::InternalError { - message: (&*format!("{err:?}")).into(), - } - } - })?; - } - Err(panic) => { - let mut message = match panic.downcast::<&str>() { - Ok(v) => *v, - _ => "panic!()", - } - .to_owned(); - message.insert_str(0, ": "); - message.insert_str(0, &name); - log::error!("{message}"); - tx.send(messages::MutationResult::InternalError { - message: (&*message).into(), - })?; - } - } - } - SessionEvent::ReadConfigArray { key, tx } => { - let name: ConfigNamePathBuf = key.iter().collect(); - - tx.send( - name.lookup_value(self.settings.config()) - .and_then(|value| value.into_array()) - .and_then(|values| { - values - .into_iter() - .map(|value| value.into_string()) - .collect() - }) - .context("read config"), - )?; - } - SessionEvent::WriteConfigArray { scope, key, values } => { - let name = key.iter().collect(); - - let path = match scope { - ConfigSource::User => jj_cli::config::new_config_path() - .map_err(|err| anyhow!(err)) - .and_then(|path| { - path.ok_or(anyhow!("No repo config path found to edit")) - }), - ConfigSource::Repo => { - Ok(self.repo().loader().repo_path().join("config.toml")) - } - _ => Err(anyhow!("Can't get path for config source {scope:?}")), - } - .and_then(|path| { - let mut parseable_value = String::from("['"); - parseable_value.push_str(&values.join("','")); - parseable_value.push_str("']"); - write_config_value_to_file(&name, &parseable_value, &path) - .map_err(|err| anyhow!("{err:?}")) - }); - - handler::optional!(path); - - (self.settings, self.aliases_map) = read_config(self.repo().repo_path())?; - } - }; - } - } -} - -impl Session for queries::QuerySession<'_, '_> { - type Transition = QueryResult; - - fn handle_events(mut self, rx: &Receiver) -> Result { - loop { - let evt = rx.recv(); - log::debug!("LogQuery handling {evt:?}"); - match evt { - Ok(SessionEvent::QueryRevision { tx, id }) => { - tx.send(queries::query_revision(&self.ws, id))? - } - Ok(SessionEvent::QueryRemotes { - tx, - tracking_branch, - }) => tx.send(queries::query_remotes(&self.ws, tracking_branch))?, - Ok(SessionEvent::QueryLogNextPage { tx }) => tx.send(self.get_page())?, - Ok(unhandled) => return Ok(QueryResult(unhandled, self.state)), - Err(err) => return Err(anyhow!(err)), - }; - } - } -} - -/// helper function for transitioning from workspace state to query state -fn handle_query( - state: &mut WorkspaceState, - ws: &WorkspaceSession, - tx: Sender>, - rx: &Receiver, - revset_str: Option<&str>, - query_state: Option, -) -> Result<()> { - let query_state = match query_state.or_else(|| state.unpaged_query.take()) { - Some(x) => x, - None => { - tx.send(Err(anyhow!( - "page requested without query in progress or new query" - )))?; - - state.unhandled_event = None; - state.unpaged_query = None; - return Ok(()); - } - }; - - let revset_str = match revset_str { - Some(x) => x, - None => { - tx.send(Err(anyhow!("page requested without query in progress")))?; - - state.unhandled_event = None; - state.unpaged_query = None; - return Ok(()); - } - }; - - let revset = match ws - .evaluate_revset_str(revset_str) - .context("evaluate revset") - { - Ok(x) => x, - Err(err) => { - tx.send(Err(err))?; - - state.unhandled_event = None; - state.unpaged_query = None; - return Ok(()); - } - }; - - let mut query = queries::QuerySession::new(ws, &*revset, query_state); - let page = query.get_page(); - tx.send(page)?; - - let QueryResult(next_event, next_query) = query.handle_events(rx).context("LogQuery")?; - - state.unhandled_event = Some(next_event); - state.unpaged_query = Some(next_query); - Ok(()) -} +use std::{ + panic::{catch_unwind, AssertUnwindSafe}, + path::PathBuf, + sync::mpsc::{Receiver, Sender}, +}; + +use anyhow::{anyhow, Context, Result}; +use jj_cli::config::{write_config_value_to_file, ConfigNamePathBuf, ConfigSource}; + +use super::{ + gui_util::WorkspaceSession, + queries::{self, QueryState}, + Mutation, WorkerSession, +}; +use crate::{ + config::{read_config, GGSettings}, + handler, messages, +}; + +/// implemented by states of the event loop +pub trait Session { + type Transition; + fn handle_events(self, rx: &Receiver) -> Result; +} + +/// messages sent to a worker from other threads. most come with a channel allowing a response +#[derive(Debug)] +pub enum SessionEvent { + #[allow(dead_code)] // used by tests + EndSession, + OpenWorkspace { + tx: Sender>, + wd: Option, + }, + QueryRevision { + tx: Sender>, + id: messages::RevId, + }, + QueryRemotes { + tx: Sender>>, + tracking_branch: Option, + }, + QueryLog { + tx: Sender>, + query: String, + }, + QueryLogNextPage { + tx: Sender>, + }, + ExecuteSnapshot { + tx: Sender>, + }, + ExecuteMutation { + tx: Sender, + mutation: Box, + }, + ReadConfigArray { + tx: Sender>>, + key: Vec, + }, + WriteConfigArray { + scope: ConfigSource, + key: Vec, + values: Vec, + }, +} + +/// transitions for a workspace session +pub enum WorkspaceResult { + Reopen(Sender>, Option), // workspace -> workspace + SessionComplete, // workspace -> worker +} + +/// transition for a query session +pub struct QueryResult(SessionEvent, QueryState); // query -> workspace + +/// event loop state for a workspace session +#[derive(Default)] +struct WorkspaceState { + pub unhandled_event: Option, + pub unpaged_query: Option, +} + +impl Session for WorkerSession { + type Transition = (); + + fn handle_events(mut self, rx: &Receiver) -> Result<()> { + let mut latest_wd: Option = None; + + loop { + let evt = rx.recv(); + log::debug!("WorkerSession handling {evt:?}"); + match evt { + Ok(SessionEvent::EndSession) => return Ok(()), + Ok(SessionEvent::ExecuteSnapshot { .. }) => (), + Ok(SessionEvent::OpenWorkspace { mut tx, mut wd }) => loop { + let resolved_wd = match wd.clone().or(latest_wd) { + Some(wd) => wd, + None => match self.get_cwd() { + Ok(wd) => wd, + Err(err) => { + latest_wd = None; + tx.send(Ok(messages::RepoConfig::LoadError { + absolute_path: PathBuf::new().into(), + message: format!("{err:#}"), + }))?; + break; + } + }, + }; + + let mut ws = match self.load_directory(&resolved_wd) { + Ok(ws) => ws, + Err(err) => { + latest_wd = None; + tx.send(Ok(messages::RepoConfig::LoadError { + absolute_path: resolved_wd.into(), + message: format!("{err:#}"), + }))?; + break; + } + }; + + latest_wd = Some(resolved_wd); + + ws.import_and_snapshot(false)?; + + tx.send(ws.format_config())?; + + match ws.handle_events(rx).context("WorkspaceSession")? { + WorkspaceResult::Reopen(new_tx, new_cwd) => (tx, wd) = (new_tx, new_cwd), + WorkspaceResult::SessionComplete => return Ok(()), + } + }, + Ok(evt) => { + log::error!( + "WorkerSession::handle_events(): repo not loaded when receiving {evt:?}" + ); + return Err(anyhow::anyhow!( + "A repo must be loaded before any other operations" + )); + } + Err(err) => { + log::error!("WorkerSession::handle_events(): {err}"); + return Err(anyhow!(err)); + } + }; + } + } +} + +impl Session for WorkspaceSession<'_> { + type Transition = WorkspaceResult; + + fn handle_events(mut self, rx: &Receiver) -> Result { + let mut state = WorkspaceState::default(); + + loop { + let next_event = if state.unhandled_event.is_some() { + state.unhandled_event.take().unwrap() + } else { + let evt = rx.recv(); + log::debug!("WorkspaceSession handling {evt:?}"); + evt? + }; + + match next_event { + SessionEvent::EndSession => return Ok(WorkspaceResult::SessionComplete), + SessionEvent::OpenWorkspace { tx, wd: cwd } => { + return Ok(WorkspaceResult::Reopen(tx, cwd)); + } + SessionEvent::QueryRevision { tx, id } => { + tx.send(queries::query_revision(&self, id))? + } + SessionEvent::QueryRemotes { + tx, + tracking_branch, + } => tx.send(queries::query_remotes(&self, tracking_branch))?, + SessionEvent::QueryLog { + tx, + query: revset_string, + } => { + let log_page_size = self + .session + .force_log_page_size + .unwrap_or(self.settings.query_log_page_size()); + handle_query( + &mut state, + &self, + tx, + rx, + Some(&revset_string), + Some(QueryState::new(log_page_size)), + )?; + + self.session.latest_query = Some(revset_string); + } + SessionEvent::QueryLogNextPage { tx } => { + let revset_string = self.session.latest_query.as_ref().map(|x| x.as_str()); + handle_query(&mut state, &self, tx, rx, revset_string, None)?; + } + SessionEvent::ExecuteSnapshot { tx } => { + let updated_head = self.load_at_head()?; // alternatively, this could be folded into snapshot so that it's done by all mutations + if self.import_and_snapshot(false)? || updated_head { + tx.send(Some(self.format_status()))?; + } else { + tx.send(None)?; + } + } + SessionEvent::ExecuteMutation { tx, mutation } => { + let name = mutation.as_ref().describe(); + match catch_unwind(AssertUnwindSafe(|| { + mutation.execute(&mut self).with_context(|| name.clone()) + })) { + Ok(result) => { + tx.send(match result { + Ok(result) => result, + Err(err) => { + log::error!("{err:?}"); + messages::MutationResult::InternalError { + message: (&*format!("{err:?}")).into(), + } + } + })?; + } + Err(panic) => { + let mut message = match panic.downcast::<&str>() { + Ok(v) => *v, + _ => "panic!()", + } + .to_owned(); + message.insert_str(0, ": "); + message.insert_str(0, &name); + log::error!("{message}"); + tx.send(messages::MutationResult::InternalError { + message: (&*message).into(), + })?; + } + } + } + SessionEvent::ReadConfigArray { key, tx } => { + let name: ConfigNamePathBuf = key.iter().collect(); + + tx.send( + name.lookup_value(self.settings.config()) + .and_then(|value| value.into_array()) + .and_then(|values| { + values + .into_iter() + .map(|value| value.into_string()) + .collect() + }) + .context("read config"), + )?; + } + SessionEvent::WriteConfigArray { scope, key, values } => { + let name = key.iter().collect(); + + let path = match scope { + ConfigSource::User => jj_cli::config::new_config_path() + .map_err(|err| anyhow!(err)) + .and_then(|path| { + path.ok_or(anyhow!("No repo config path found to edit")) + }), + ConfigSource::Repo => { + Ok(self.repo().loader().repo_path().join("config.toml")) + } + _ => Err(anyhow!("Can't get path for config source {scope:?}")), + } + .and_then(|path| { + let mut parseable_value = String::from("['"); + parseable_value.push_str(&values.join("','")); + parseable_value.push_str("']"); + write_config_value_to_file(&name, &parseable_value, &path) + .map_err(|err| anyhow!("{err:?}")) + }); + + handler::optional!(path); + + (self.settings, self.aliases_map) = read_config(self.repo().repo_path())?; + } + }; + } + } +} + +impl Session for queries::QuerySession<'_, '_> { + type Transition = QueryResult; + + fn handle_events(mut self, rx: &Receiver) -> Result { + loop { + let evt = rx.recv(); + log::debug!("LogQuery handling {evt:?}"); + match evt { + Ok(SessionEvent::QueryRevision { tx, id }) => { + tx.send(queries::query_revision(&self.ws, id))? + } + Ok(SessionEvent::QueryRemotes { + tx, + tracking_branch, + }) => tx.send(queries::query_remotes(&self.ws, tracking_branch))?, + Ok(SessionEvent::QueryLogNextPage { tx }) => tx.send(self.get_page())?, + Ok(unhandled) => return Ok(QueryResult(unhandled, self.state)), + Err(err) => return Err(anyhow!(err)), + }; + } + } +} + +/// helper function for transitioning from workspace state to query state +fn handle_query( + state: &mut WorkspaceState, + ws: &WorkspaceSession, + tx: Sender>, + rx: &Receiver, + revset_str: Option<&str>, + query_state: Option, +) -> Result<()> { + let query_state = match query_state.or_else(|| state.unpaged_query.take()) { + Some(x) => x, + None => { + tx.send(Err(anyhow!( + "page requested without query in progress or new query" + )))?; + + state.unhandled_event = None; + state.unpaged_query = None; + return Ok(()); + } + }; + + let revset_str = match revset_str { + Some(x) => x, + None => { + tx.send(Err(anyhow!("page requested without query in progress")))?; + + state.unhandled_event = None; + state.unpaged_query = None; + return Ok(()); + } + }; + + let revset = match ws + .evaluate_revset_str(revset_str) + .context("evaluate revset") + { + Ok(x) => x, + Err(err) => { + tx.send(Err(err))?; + + state.unhandled_event = None; + state.unpaged_query = None; + return Ok(()); + } + }; + + let mut query = queries::QuerySession::new(ws, &*revset, query_state); + let page = query.get_page(); + tx.send(page)?; + + let QueryResult(next_event, next_query) = query.handle_events(rx).context("LogQuery")?; + + state.unhandled_event = Some(next_event); + state.unpaged_query = Some(next_query); + Ok(()) +} diff --git a/src-tauri/src/worker/tests/mod.rs b/src-tauri/src/worker/tests/mod.rs index 4aab983..b6ee05b 100644 --- a/src-tauri/src/worker/tests/mod.rs +++ b/src-tauri/src/worker/tests/mod.rs @@ -1,149 +1,149 @@ -use crate::{ - messages::{ChangeId, CommitId, RevId}, - worker::WorkerSession, -}; -use anyhow::Result; -use jj_lib::{backend::TreeValue, repo_path::RepoPath}; -use std::{ - fs::{self, File}, - path::PathBuf, -}; -use tempfile::{tempdir, TempDir}; -use zip::ZipArchive; - -mod mutations; -mod queries; -mod session; - -fn mkrepo() -> TempDir { - let repo_dir = tempdir().unwrap(); - let mut archive_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - archive_path.push("resources/test-repo.zip"); - let archive_file = File::open(&archive_path).unwrap(); - let mut archive = ZipArchive::new(archive_file).unwrap(); - - archive.extract(repo_dir.path()).unwrap(); - - repo_dir -} - -fn mkid(xid: &str, cid: &str) -> RevId { - RevId { - change: ChangeId { - hex: xid.to_owned(), - prefix: xid.to_owned(), - rest: "".to_owned(), - }, - commit: CommitId { - hex: cid.to_owned(), - prefix: cid.to_owned(), - rest: "".to_owned(), - }, - } -} - -mod revs { - use crate::messages::RevId; - - use super::mkid; - - pub fn working_copy() -> RevId { - mkid("nnloouly", "56018b94eb61a9acddc58ad7974aa51c3368eadd") - } - - pub fn main_branch() -> RevId { - mkid("mnkoropy", "87e9c6c03e1b727ff712d962c03b32fffb704bc0") - } - - pub fn conflict_branch() -> RevId { - mkid("nwrnuwyp", "880abeefdd3ac344e2a0901c5f486d02d34053da") - } - - pub fn resolve_conflict() -> RevId { - mkid("rrxroxys", "db297552443bcafc0f0715b7ace7fb4488d7954d") - } -} - -#[test] -fn wc_path_is_visible() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let ws = session.load_directory(repo.path())?; - - let commit = ws.get_commit(ws.wc_id())?; - let value = commit - .tree()? - .path_value(RepoPath::from_internal_string("a.txt"))?; - - assert!(value.is_resolved()); - assert!(value - .first() - .as_ref() - .is_some_and(|x| matches!(x, TreeValue::File { .. }))); - - Ok(()) -} - -#[test] -fn snapshot_updates_wc_if_changed() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - let old_wc = ws.wc_id().clone(); - - assert!(!ws.import_and_snapshot(true)?); - assert_eq!(&old_wc, ws.wc_id()); - - fs::write(repo.path().join("new.txt"), []).unwrap(); - - assert!(ws.import_and_snapshot(true)?); - assert_ne!(&old_wc, ws.wc_id()); - - Ok(()) -} - -#[test] -fn transaction_updates_wc_if_snapshot() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - let old_wc = ws.wc_id().clone(); - - fs::write(repo.path().join("new.txt"), []).unwrap(); - - let tx = ws.start_transaction()?; - ws.finish_transaction(tx, "do nothing")?; - - assert_ne!(&old_wc, ws.wc_id()); - - Ok(()) -} - -#[test] -fn transaction_snapshot_path_is_visible() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - fs::write(repo.path().join("new.txt"), []).unwrap(); - - let tx = ws.start_transaction()?; - ws.finish_transaction(tx, "do nothing")?; - - let commit = ws.get_commit(ws.wc_id())?; - let value = commit - .tree()? - .path_value(RepoPath::from_internal_string("new.txt"))?; - - assert!(value.is_resolved()); - assert!(value - .first() - .as_ref() - .is_some_and(|x| matches!(x, TreeValue::File { .. }))); - - Ok(()) -} +use crate::{ + messages::{ChangeId, CommitId, RevId}, + worker::WorkerSession, +}; +use anyhow::Result; +use jj_lib::{backend::TreeValue, repo_path::RepoPath}; +use std::{ + fs::{self, File}, + path::PathBuf, +}; +use tempfile::{tempdir, TempDir}; +use zip::ZipArchive; + +mod mutations; +mod queries; +mod session; + +fn mkrepo() -> TempDir { + let repo_dir = tempdir().unwrap(); + let mut archive_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + archive_path.push("resources/test-repo.zip"); + let archive_file = File::open(&archive_path).unwrap(); + let mut archive = ZipArchive::new(archive_file).unwrap(); + + archive.extract(repo_dir.path()).unwrap(); + + repo_dir +} + +fn mkid(xid: &str, cid: &str) -> RevId { + RevId { + change: ChangeId { + hex: xid.to_owned(), + prefix: xid.to_owned(), + rest: "".to_owned(), + }, + commit: CommitId { + hex: cid.to_owned(), + prefix: cid.to_owned(), + rest: "".to_owned(), + }, + } +} + +mod revs { + use crate::messages::RevId; + + use super::mkid; + + pub fn working_copy() -> RevId { + mkid("nnloouly", "56018b94eb61a9acddc58ad7974aa51c3368eadd") + } + + pub fn main_branch() -> RevId { + mkid("mnkoropy", "87e9c6c03e1b727ff712d962c03b32fffb704bc0") + } + + pub fn conflict_branch() -> RevId { + mkid("nwrnuwyp", "880abeefdd3ac344e2a0901c5f486d02d34053da") + } + + pub fn resolve_conflict() -> RevId { + mkid("rrxroxys", "db297552443bcafc0f0715b7ace7fb4488d7954d") + } +} + +#[test] +fn wc_path_is_visible() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let ws = session.load_directory(repo.path())?; + + let commit = ws.get_commit(ws.wc_id())?; + let value = commit + .tree()? + .path_value(RepoPath::from_internal_string("a.txt"))?; + + assert!(value.is_resolved()); + assert!(value + .first() + .as_ref() + .is_some_and(|x| matches!(x, TreeValue::File { .. }))); + + Ok(()) +} + +#[test] +fn snapshot_updates_wc_if_changed() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + let old_wc = ws.wc_id().clone(); + + assert!(!ws.import_and_snapshot(true)?); + assert_eq!(&old_wc, ws.wc_id()); + + fs::write(repo.path().join("new.txt"), []).unwrap(); + + assert!(ws.import_and_snapshot(true)?); + assert_ne!(&old_wc, ws.wc_id()); + + Ok(()) +} + +#[test] +fn transaction_updates_wc_if_snapshot() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + let old_wc = ws.wc_id().clone(); + + fs::write(repo.path().join("new.txt"), []).unwrap(); + + let tx = ws.start_transaction()?; + ws.finish_transaction(tx, "do nothing")?; + + assert_ne!(&old_wc, ws.wc_id()); + + Ok(()) +} + +#[test] +fn transaction_snapshot_path_is_visible() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + fs::write(repo.path().join("new.txt"), []).unwrap(); + + let tx = ws.start_transaction()?; + ws.finish_transaction(tx, "do nothing")?; + + let commit = ws.get_commit(ws.wc_id())?; + let value = commit + .tree()? + .path_value(RepoPath::from_internal_string("new.txt"))?; + + assert!(value.is_resolved()); + assert!(value + .first() + .as_ref() + .is_some_and(|x| matches!(x, TreeValue::File { .. }))); + + Ok(()) +} diff --git a/src-tauri/src/worker/tests/mutations.rs b/src-tauri/src/worker/tests/mutations.rs index 08b0777..d08777a 100644 --- a/src-tauri/src/worker/tests/mutations.rs +++ b/src-tauri/src/worker/tests/mutations.rs @@ -1,329 +1,329 @@ -use super::{mkrepo, revs}; -use crate::{ - messages::{ - AbandonRevisions, CheckoutRevision, CopyChanges, CreateRevision, DescribeRevision, - DuplicateRevisions, InsertRevision, MoveChanges, MoveSource, MutationResult, RevResult, - TreePath, - }, - worker::{queries, Mutation, WorkerSession}, -}; -use anyhow::Result; -use assert_matches::assert_matches; -use std::fs; - -#[test] -fn abandon_revisions() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let page = queries::query_log(&ws, "all()", 100)?; - assert_eq!(12, page.rows.len()); - - AbandonRevisions { - ids: vec![revs::resolve_conflict().commit], - } - .execute_unboxed(&mut ws)?; - - let page = queries::query_log(&ws, "all()", 100)?; - assert_eq!(11, page.rows.len()); - - Ok(()) -} - -#[test] -fn checkout_revision() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let head_rev = queries::query_revision(&ws, revs::working_copy())?; - let conflict_rev = queries::query_revision(&ws, revs::conflict_branch())?; - assert_matches!(head_rev, RevResult::Detail { header, .. } if header.is_working_copy); - assert_matches!(conflict_rev, RevResult::Detail { header, .. } if !header.is_working_copy); - - let result = CheckoutRevision { - id: revs::conflict_branch(), - } - .execute_unboxed(&mut ws)?; - assert_matches!(result, MutationResult::UpdatedSelection { .. }); - - let head_rev = queries::query_revision(&ws, revs::working_copy())?; - let conflict_rev = queries::query_revision(&ws, revs::conflict_branch())?; - assert_matches!(head_rev, RevResult::NotFound { .. }); - assert_matches!(conflict_rev, RevResult::Detail { header, .. } if header.is_working_copy); - - Ok(()) -} - -#[test] -fn copy_changes() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let from_rev = queries::query_revision(&ws, revs::resolve_conflict())?; - let to_rev = queries::query_revision(&ws, revs::working_copy())?; - assert_matches!(from_rev, RevResult::Detail { changes, .. } if changes.len() == 1); - assert_matches!(to_rev, RevResult::Detail { changes, .. } if changes.len() == 0); - - let result = CopyChanges { - from_id: revs::resolve_conflict().commit, - to_id: revs::working_copy(), - paths: vec![TreePath { - repo_path: "b.txt".to_owned(), - relative_path: "".into(), - }], - } - .execute_unboxed(&mut ws)?; - assert_matches!(result, MutationResult::Updated { .. }); - - let from_rev = queries::query_revision(&ws, revs::resolve_conflict())?; - let to_rev = queries::query_revision(&ws, revs::working_copy())?; - assert_matches!(from_rev, RevResult::Detail { changes, .. } if changes.len() == 1); - assert_matches!(to_rev, RevResult::Detail { changes, .. } if changes.len() == 1); - - Ok(()) -} - -#[test] -fn create_revision_single_parent() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let parent_rev = queries::query_revision(&ws, revs::working_copy())?; - assert_matches!(parent_rev, RevResult::Detail { header, .. } if header.is_working_copy); - - let result = CreateRevision { - parent_ids: vec![revs::working_copy()], - } - .execute_unboxed(&mut ws)?; - - match result { - MutationResult::UpdatedSelection { new_selection, .. } => { - let parent_rev = queries::query_revision(&ws, revs::working_copy())?; - let child_rev = queries::query_revision(&ws, new_selection.id)?; - assert!( - matches!(parent_rev, RevResult::Detail { header, .. } if !header.is_working_copy) - ); - assert!( - matches!(child_rev, RevResult::Detail { header, .. } if header.is_working_copy) - ); - } - _ => assert!(false, "CreateRevision failed"), - } - - Ok(()) -} - -#[test] -fn create_revision_multi_parent() -> Result<()> { - let repo: tempfile::TempDir = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let parent_rev = queries::query_revision(&ws, revs::working_copy())?; - assert_matches!(parent_rev, RevResult::Detail { header, .. } if header.is_working_copy); - - let result = CreateRevision { - parent_ids: vec![revs::working_copy(), revs::conflict_branch()], - } - .execute_unboxed(&mut ws)?; - - match result { - MutationResult::UpdatedSelection { new_selection, .. } => { - let child_rev = queries::query_revision(&ws, new_selection.id)?; - assert_matches!(child_rev, RevResult::Detail { parents, .. } if parents.len() == 2); - } - _ => assert!(false, "CreateRevision failed"), - } - - Ok(()) -} - -#[test] -fn describe_revision() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let rev = queries::query_revision(&ws, revs::working_copy())?; - assert_matches!(rev, RevResult::Detail { header, .. } if header.description.lines[0] == ""); - - let result = DescribeRevision { - id: revs::working_copy(), - new_description: "wip".to_owned(), - reset_author: false, - } - .execute_unboxed(&mut ws)?; - assert_matches!(result, MutationResult::Updated { .. }); - - let rev = queries::query_revision(&ws, revs::working_copy())?; - assert!( - matches!(rev, RevResult::Detail { header, .. } if header.description.lines[0] == "wip") - ); - - Ok(()) -} - -#[test] -fn describe_revision_with_snapshot() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let rev = queries::query_revision(&ws, revs::working_copy())?; - assert!( - matches!(rev, RevResult::Detail { header, changes, .. } if header.description.lines[0] == "" && changes.len() == 0) - ); - - fs::write(repo.path().join("new.txt"), []).unwrap(); // changes the WC commit - - DescribeRevision { - id: revs::working_copy(), - new_description: "wip".to_owned(), - reset_author: false, - } - .execute_unboxed(&mut ws)?; - - let rev = queries::query_revision(&ws, revs::working_copy())?; - assert!( - matches!(rev, RevResult::Detail { header, changes, .. } if header.description.lines[0] == "wip" && changes.len() != 0) - ); - - Ok(()) -} - -#[test] -fn duplicate_revisions() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let rev = queries::query_revision(&ws, revs::working_copy())?; - assert_matches!(rev, RevResult::Detail { header, .. } if header.description.lines[0] == ""); - - let result = DuplicateRevisions { - ids: vec![revs::main_branch()], - } - .execute_unboxed(&mut ws)?; - assert_matches!(result, MutationResult::UpdatedSelection { .. }); - - let page = queries::query_log(&ws, "description(unsynced)", 3)?; - assert_eq!(2, page.rows.len()); - - Ok(()) -} - -#[test] -fn insert_revision() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let page = queries::query_log(&ws, "main::@", 4)?; - assert_eq!(2, page.rows.len()); - - InsertRevision { - after_id: revs::main_branch(), - before_id: revs::working_copy(), - id: revs::resolve_conflict(), - } - .execute_unboxed(&mut ws)?; - - let page = queries::query_log(&ws, "main::@", 4)?; - assert_eq!(3, page.rows.len()); - - Ok(()) -} - -#[test] -fn move_changes_all_paths() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let parent_rev = queries::query_revision(&ws, revs::conflict_branch())?; - assert_matches!(parent_rev, RevResult::Detail { header, .. } if header.has_conflict); - - let result = MoveChanges { - from_id: revs::resolve_conflict(), - to_id: revs::conflict_branch().commit, - paths: vec![], - } - .execute_unboxed(&mut ws)?; - assert_matches!(result, MutationResult::Updated { .. }); - - let parent_rev = queries::query_revision(&ws, revs::conflict_branch())?; - assert_matches!(parent_rev, RevResult::Detail { header, .. } if !header.has_conflict); - - Ok(()) -} - -#[test] -fn move_changes_single_path() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let from_rev = queries::query_revision(&ws, revs::main_branch())?; - let to_rev = queries::query_revision(&ws, revs::working_copy())?; - assert_matches!(from_rev, RevResult::Detail { changes, .. } if changes.len() == 2); - assert_matches!(to_rev, RevResult::Detail { changes, .. } if changes.len() == 0); - - let result = MoveChanges { - from_id: revs::main_branch(), - to_id: revs::working_copy().commit, - paths: vec![TreePath { - repo_path: "c.txt".to_owned(), - relative_path: "".into(), - }], - } - .execute_unboxed(&mut ws)?; - assert_matches!(result, MutationResult::Updated { .. }); - - let from_rev = queries::query_revision(&ws, revs::main_branch())?; - let to_rev = queries::query_revision(&ws, revs::working_copy())?; - assert_matches!(from_rev, RevResult::Detail { changes, .. } if changes.len() == 1); - assert_matches!(to_rev, RevResult::Detail { changes, .. } if changes.len() == 1); - - Ok(()) -} - -#[test] -fn move_source() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let mut ws = session.load_directory(repo.path())?; - - let page = queries::query_log(&ws, "@+", 1)?; - assert_eq!(0, page.rows.len()); - - MoveSource { - id: revs::resolve_conflict(), - parent_ids: vec![revs::working_copy().commit], - } - .execute_unboxed(&mut ws)?; - - let page = queries::query_log(&ws, "@+", 2)?; - assert_eq!(1, page.rows.len()); - - Ok(()) -} - -// XXX missing tests for: -// - branch/ref mutations -// - git interop +use super::{mkrepo, revs}; +use crate::{ + messages::{ + AbandonRevisions, CheckoutRevision, CopyChanges, CreateRevision, DescribeRevision, + DuplicateRevisions, InsertRevision, MoveChanges, MoveSource, MutationResult, RevResult, + TreePath, + }, + worker::{queries, Mutation, WorkerSession}, +}; +use anyhow::Result; +use assert_matches::assert_matches; +use std::fs; + +#[test] +fn abandon_revisions() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let page = queries::query_log(&ws, "all()", 100)?; + assert_eq!(12, page.rows.len()); + + AbandonRevisions { + ids: vec![revs::resolve_conflict().commit], + } + .execute_unboxed(&mut ws)?; + + let page = queries::query_log(&ws, "all()", 100)?; + assert_eq!(11, page.rows.len()); + + Ok(()) +} + +#[test] +fn checkout_revision() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let head_rev = queries::query_revision(&ws, revs::working_copy())?; + let conflict_rev = queries::query_revision(&ws, revs::conflict_branch())?; + assert_matches!(head_rev, RevResult::Detail { header, .. } if header.is_working_copy); + assert_matches!(conflict_rev, RevResult::Detail { header, .. } if !header.is_working_copy); + + let result = CheckoutRevision { + id: revs::conflict_branch(), + } + .execute_unboxed(&mut ws)?; + assert_matches!(result, MutationResult::UpdatedSelection { .. }); + + let head_rev = queries::query_revision(&ws, revs::working_copy())?; + let conflict_rev = queries::query_revision(&ws, revs::conflict_branch())?; + assert_matches!(head_rev, RevResult::NotFound { .. }); + assert_matches!(conflict_rev, RevResult::Detail { header, .. } if header.is_working_copy); + + Ok(()) +} + +#[test] +fn copy_changes() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let from_rev = queries::query_revision(&ws, revs::resolve_conflict())?; + let to_rev = queries::query_revision(&ws, revs::working_copy())?; + assert_matches!(from_rev, RevResult::Detail { changes, .. } if changes.len() == 1); + assert_matches!(to_rev, RevResult::Detail { changes, .. } if changes.len() == 0); + + let result = CopyChanges { + from_id: revs::resolve_conflict().commit, + to_id: revs::working_copy(), + paths: vec![TreePath { + repo_path: "b.txt".to_owned(), + relative_path: "".into(), + }], + } + .execute_unboxed(&mut ws)?; + assert_matches!(result, MutationResult::Updated { .. }); + + let from_rev = queries::query_revision(&ws, revs::resolve_conflict())?; + let to_rev = queries::query_revision(&ws, revs::working_copy())?; + assert_matches!(from_rev, RevResult::Detail { changes, .. } if changes.len() == 1); + assert_matches!(to_rev, RevResult::Detail { changes, .. } if changes.len() == 1); + + Ok(()) +} + +#[test] +fn create_revision_single_parent() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let parent_rev = queries::query_revision(&ws, revs::working_copy())?; + assert_matches!(parent_rev, RevResult::Detail { header, .. } if header.is_working_copy); + + let result = CreateRevision { + parent_ids: vec![revs::working_copy()], + } + .execute_unboxed(&mut ws)?; + + match result { + MutationResult::UpdatedSelection { new_selection, .. } => { + let parent_rev = queries::query_revision(&ws, revs::working_copy())?; + let child_rev = queries::query_revision(&ws, new_selection.id)?; + assert!( + matches!(parent_rev, RevResult::Detail { header, .. } if !header.is_working_copy) + ); + assert!( + matches!(child_rev, RevResult::Detail { header, .. } if header.is_working_copy) + ); + } + _ => assert!(false, "CreateRevision failed"), + } + + Ok(()) +} + +#[test] +fn create_revision_multi_parent() -> Result<()> { + let repo: tempfile::TempDir = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let parent_rev = queries::query_revision(&ws, revs::working_copy())?; + assert_matches!(parent_rev, RevResult::Detail { header, .. } if header.is_working_copy); + + let result = CreateRevision { + parent_ids: vec![revs::working_copy(), revs::conflict_branch()], + } + .execute_unboxed(&mut ws)?; + + match result { + MutationResult::UpdatedSelection { new_selection, .. } => { + let child_rev = queries::query_revision(&ws, new_selection.id)?; + assert_matches!(child_rev, RevResult::Detail { parents, .. } if parents.len() == 2); + } + _ => assert!(false, "CreateRevision failed"), + } + + Ok(()) +} + +#[test] +fn describe_revision() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let rev = queries::query_revision(&ws, revs::working_copy())?; + assert_matches!(rev, RevResult::Detail { header, .. } if header.description.lines[0] == ""); + + let result = DescribeRevision { + id: revs::working_copy(), + new_description: "wip".to_owned(), + reset_author: false, + } + .execute_unboxed(&mut ws)?; + assert_matches!(result, MutationResult::Updated { .. }); + + let rev = queries::query_revision(&ws, revs::working_copy())?; + assert!( + matches!(rev, RevResult::Detail { header, .. } if header.description.lines[0] == "wip") + ); + + Ok(()) +} + +#[test] +fn describe_revision_with_snapshot() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let rev = queries::query_revision(&ws, revs::working_copy())?; + assert!( + matches!(rev, RevResult::Detail { header, changes, .. } if header.description.lines[0] == "" && changes.len() == 0) + ); + + fs::write(repo.path().join("new.txt"), []).unwrap(); // changes the WC commit + + DescribeRevision { + id: revs::working_copy(), + new_description: "wip".to_owned(), + reset_author: false, + } + .execute_unboxed(&mut ws)?; + + let rev = queries::query_revision(&ws, revs::working_copy())?; + assert!( + matches!(rev, RevResult::Detail { header, changes, .. } if header.description.lines[0] == "wip" && changes.len() != 0) + ); + + Ok(()) +} + +#[test] +fn duplicate_revisions() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let rev = queries::query_revision(&ws, revs::working_copy())?; + assert_matches!(rev, RevResult::Detail { header, .. } if header.description.lines[0] == ""); + + let result = DuplicateRevisions { + ids: vec![revs::main_branch()], + } + .execute_unboxed(&mut ws)?; + assert_matches!(result, MutationResult::UpdatedSelection { .. }); + + let page = queries::query_log(&ws, "description(unsynced)", 3)?; + assert_eq!(2, page.rows.len()); + + Ok(()) +} + +#[test] +fn insert_revision() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let page = queries::query_log(&ws, "main::@", 4)?; + assert_eq!(2, page.rows.len()); + + InsertRevision { + after_id: revs::main_branch(), + before_id: revs::working_copy(), + id: revs::resolve_conflict(), + } + .execute_unboxed(&mut ws)?; + + let page = queries::query_log(&ws, "main::@", 4)?; + assert_eq!(3, page.rows.len()); + + Ok(()) +} + +#[test] +fn move_changes_all_paths() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let parent_rev = queries::query_revision(&ws, revs::conflict_branch())?; + assert_matches!(parent_rev, RevResult::Detail { header, .. } if header.has_conflict); + + let result = MoveChanges { + from_id: revs::resolve_conflict(), + to_id: revs::conflict_branch().commit, + paths: vec![], + } + .execute_unboxed(&mut ws)?; + assert_matches!(result, MutationResult::Updated { .. }); + + let parent_rev = queries::query_revision(&ws, revs::conflict_branch())?; + assert_matches!(parent_rev, RevResult::Detail { header, .. } if !header.has_conflict); + + Ok(()) +} + +#[test] +fn move_changes_single_path() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let from_rev = queries::query_revision(&ws, revs::main_branch())?; + let to_rev = queries::query_revision(&ws, revs::working_copy())?; + assert_matches!(from_rev, RevResult::Detail { changes, .. } if changes.len() == 2); + assert_matches!(to_rev, RevResult::Detail { changes, .. } if changes.len() == 0); + + let result = MoveChanges { + from_id: revs::main_branch(), + to_id: revs::working_copy().commit, + paths: vec![TreePath { + repo_path: "c.txt".to_owned(), + relative_path: "".into(), + }], + } + .execute_unboxed(&mut ws)?; + assert_matches!(result, MutationResult::Updated { .. }); + + let from_rev = queries::query_revision(&ws, revs::main_branch())?; + let to_rev = queries::query_revision(&ws, revs::working_copy())?; + assert_matches!(from_rev, RevResult::Detail { changes, .. } if changes.len() == 1); + assert_matches!(to_rev, RevResult::Detail { changes, .. } if changes.len() == 1); + + Ok(()) +} + +#[test] +fn move_source() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let mut ws = session.load_directory(repo.path())?; + + let page = queries::query_log(&ws, "@+", 1)?; + assert_eq!(0, page.rows.len()); + + MoveSource { + id: revs::resolve_conflict(), + parent_ids: vec![revs::working_copy().commit], + } + .execute_unboxed(&mut ws)?; + + let page = queries::query_log(&ws, "@+", 2)?; + assert_eq!(1, page.rows.len()); + + Ok(()) +} + +// XXX missing tests for: +// - branch/ref mutations +// - git interop diff --git a/src-tauri/src/worker/tests/queries.rs b/src-tauri/src/worker/tests/queries.rs index d59ab93..9f4e630 100644 --- a/src-tauri/src/worker/tests/queries.rs +++ b/src-tauri/src/worker/tests/queries.rs @@ -1,134 +1,134 @@ -use super::{mkrepo, revs}; -use crate::messages::{RevHeader, RevResult, StoreRef}; -use crate::worker::{queries, WorkerSession}; -use anyhow::Result; -use assert_matches::assert_matches; - -#[test] -fn log_all() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let ws = session.load_directory(repo.path())?; - - let all_rows = queries::query_log(&ws, "all()", 100)?; - - assert_eq!(12, all_rows.rows.len()); - assert!(!all_rows.has_more); - - Ok(()) -} - -#[test] -fn log_paged() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let ws = session.load_directory(repo.path())?; - - let page_rows = queries::query_log(&ws, "all()", 6)?; - - assert_eq!(6, page_rows.rows.len()); - assert!(page_rows.has_more); - - Ok(()) -} - -#[test] -fn log_subset() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let ws = session.load_directory(repo.path())?; - - let several_rows = queries::query_log(&ws, "branches()", 100)?; - - assert_eq!(3, several_rows.rows.len()); - - Ok(()) -} - -#[test] -fn log_mutable() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let ws = session.load_directory(repo.path())?; - - let single_row = queries::query_log(&ws, "mnkoropy", 100)? - .rows - .pop() - .unwrap(); - - assert!(!single_row.revision.is_immutable); - - Ok(()) -} - -#[test] -fn log_immutable() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let ws = session.load_directory(repo.path())?; - - let single_row = queries::query_log(&ws, "ummxkyyk", 100)? - .rows - .pop() - .unwrap(); - - assert!(single_row.revision.is_immutable); - - Ok(()) -} - -#[test] -fn revision() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let ws = session.load_directory(repo.path())?; - - let rev = queries::query_revision(&ws, revs::main_branch())?; - - assert_matches!( - rev, - RevResult::Detail { - header: RevHeader { refs, .. }, - .. - } if matches!(refs.as_slice(), [StoreRef::LocalBranch { branch_name, .. }] if branch_name == "main") - ); - - Ok(()) -} - -#[test] -fn remotes_all() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let ws = session.load_directory(repo.path())?; - - let remotes = queries::query_remotes(&ws, None)?; - - assert_eq!(2, remotes.len()); - assert!(remotes.contains(&String::from("origin"))); - assert!(remotes.contains(&String::from("second"))); - - Ok(()) -} - -#[test] -fn remotes_tracking_branch() -> Result<()> { - let repo = mkrepo(); - - let mut session = WorkerSession::default(); - let ws = session.load_directory(repo.path())?; - - let remotes = queries::query_remotes(&ws, Some(String::from("main")))?; - - assert_eq!(1, remotes.len()); - assert!(remotes.contains(&String::from("origin"))); - - Ok(()) -} +use super::{mkrepo, revs}; +use crate::messages::{RevHeader, RevResult, StoreRef}; +use crate::worker::{queries, WorkerSession}; +use anyhow::Result; +use assert_matches::assert_matches; + +#[test] +fn log_all() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let ws = session.load_directory(repo.path())?; + + let all_rows = queries::query_log(&ws, "all()", 100)?; + + assert_eq!(12, all_rows.rows.len()); + assert!(!all_rows.has_more); + + Ok(()) +} + +#[test] +fn log_paged() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let ws = session.load_directory(repo.path())?; + + let page_rows = queries::query_log(&ws, "all()", 6)?; + + assert_eq!(6, page_rows.rows.len()); + assert!(page_rows.has_more); + + Ok(()) +} + +#[test] +fn log_subset() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let ws = session.load_directory(repo.path())?; + + let several_rows = queries::query_log(&ws, "branches()", 100)?; + + assert_eq!(3, several_rows.rows.len()); + + Ok(()) +} + +#[test] +fn log_mutable() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let ws = session.load_directory(repo.path())?; + + let single_row = queries::query_log(&ws, "mnkoropy", 100)? + .rows + .pop() + .unwrap(); + + assert!(!single_row.revision.is_immutable); + + Ok(()) +} + +#[test] +fn log_immutable() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let ws = session.load_directory(repo.path())?; + + let single_row = queries::query_log(&ws, "ummxkyyk", 100)? + .rows + .pop() + .unwrap(); + + assert!(single_row.revision.is_immutable); + + Ok(()) +} + +#[test] +fn revision() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let ws = session.load_directory(repo.path())?; + + let rev = queries::query_revision(&ws, revs::main_branch())?; + + assert_matches!( + rev, + RevResult::Detail { + header: RevHeader { refs, .. }, + .. + } if matches!(refs.as_slice(), [StoreRef::LocalBranch { branch_name, .. }] if branch_name == "main") + ); + + Ok(()) +} + +#[test] +fn remotes_all() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let ws = session.load_directory(repo.path())?; + + let remotes = queries::query_remotes(&ws, None)?; + + assert_eq!(2, remotes.len()); + assert!(remotes.contains(&String::from("origin"))); + assert!(remotes.contains(&String::from("second"))); + + Ok(()) +} + +#[test] +fn remotes_tracking_branch() -> Result<()> { + let repo = mkrepo(); + + let mut session = WorkerSession::default(); + let ws = session.load_directory(repo.path())?; + + let remotes = queries::query_remotes(&ws, Some(String::from("main")))?; + + assert_eq!(1, remotes.len()); + assert!(remotes.contains(&String::from("origin"))); + + Ok(()) +} diff --git a/src-tauri/src/worker/tests/session.rs b/src-tauri/src/worker/tests/session.rs index 0c429a9..f2f2a0b 100644 --- a/src-tauri/src/worker/tests/session.rs +++ b/src-tauri/src/worker/tests/session.rs @@ -1,393 +1,393 @@ -use super::{mkid, mkrepo, revs}; -use crate::{ - messages::{LogPage, RepoConfig, RevResult}, - worker::{Session, SessionEvent, WorkerSession}, -}; -use anyhow::Result; -use jj_cli::config::ConfigSource; -use std::{path::PathBuf, sync::mpsc::channel}; - -#[test] -fn start_and_stop() -> Result<()> { - let (tx, rx) = channel::(); - tx.send(SessionEvent::EndSession)?; - WorkerSession::default().handle_events(&rx)?; - Ok(()) -} - -#[test] -fn load_repo() -> Result<()> { - let repo = mkrepo(); - - let (tx, rx) = channel::(); - let (tx_good_repo, rx_good_repo) = channel::>(); - let (tx_bad_repo, rx_bad_repo) = channel::>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_good_repo, - wd: Some(repo.path().to_owned()), - })?; - tx.send(SessionEvent::OpenWorkspace { - tx: tx_bad_repo, - wd: Some(PathBuf::new()), - })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession::default().handle_events(&rx)?; - - let config = rx_good_repo.recv()??; - assert!(matches!(config, RepoConfig::Workspace { .. })); - - let config = rx_bad_repo.recv()??; - assert!(matches!(config, RepoConfig::LoadError { .. })); - - Ok(()) -} - -#[test] -fn reload_repo() -> Result<()> { - let repo1 = mkrepo(); - let repo2 = mkrepo(); - - let (tx, rx) = channel::(); - let (tx_first_repo, rx_first_repo) = channel::>(); - let (tx_second_repo, rx_second_repo) = channel::>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_first_repo, - wd: Some(repo1.path().to_owned()), - })?; - tx.send(SessionEvent::OpenWorkspace { - tx: tx_second_repo, - wd: Some(repo2.path().to_owned()), - })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession::default().handle_events(&rx)?; - - let config = rx_first_repo.recv()??; - assert!(matches!(config, RepoConfig::Workspace { .. })); - - let config = rx_second_repo.recv()??; - assert!(matches!(config, RepoConfig::Workspace { .. })); - - Ok(()) -} - -#[test] -fn reload_with_default_query() -> Result<()> { - let repo = mkrepo(); - - let (tx, rx) = channel::(); - let (tx_load, rx_load) = channel::>(); - let (tx_query, rx_query) = channel::>(); - let (tx_reload, rx_reload) = channel::>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_load, - wd: Some(repo.path().to_owned()), - })?; - tx.send(SessionEvent::QueryLog { - tx: tx_query, - query: "none()".to_owned(), - })?; - tx.send(SessionEvent::OpenWorkspace { - tx: tx_reload, - wd: None, - })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession::default().handle_events(&rx)?; - - _ = rx_load.recv()??; - _ = rx_query.recv()??; - let config = rx_reload.recv()??; - assert!( - matches!(config, RepoConfig::Workspace { latest_query, .. } if latest_query == "none()") - ); - - Ok(()) -} - -#[test] -fn query_log_single() -> Result<()> { - let repo = mkrepo(); - - let (tx, rx) = channel::(); - let (tx_load, rx_load) = channel::>(); - let (tx_query, rx_query) = channel::>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_load, - wd: Some(repo.path().to_owned()), - })?; - tx.send(SessionEvent::QueryLog { - tx: tx_query, - query: "@".to_owned(), - })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession::default().handle_events(&rx)?; - - _ = rx_load.recv()??; - let page = rx_query.recv()??; - assert_eq!(1, page.rows.len()); - assert_eq!(false, page.has_more); - - Ok(()) -} - -#[test] -fn query_log_multi() -> Result<()> { - let repo = mkrepo(); - let (tx, rx) = channel::(); - let (tx_load, rx_load) = channel::>(); - let (tx_page1, rx_page1) = channel::>(); - let (tx_page2, rx_page2) = channel::>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_load, - wd: Some(repo.path().to_owned()), - })?; - tx.send(SessionEvent::QueryLog { - tx: tx_page1, - query: "all()".to_owned(), - })?; - tx.send(SessionEvent::QueryLogNextPage { tx: tx_page2 })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession { - force_log_page_size: Some(7), - ..Default::default() - } - .handle_events(&rx)?; - - rx_load.recv()??; - - let page1 = rx_page1.recv()??; - assert_eq!(7, page1.rows.len()); - assert_eq!(true, page1.has_more); - - let page2 = rx_page2.recv()??; - assert_eq!(5, page2.rows.len()); - assert_eq!(false, page2.has_more); - - Ok(()) -} - -#[test] -fn query_log_multi_restart() -> Result<()> { - let repo = mkrepo(); - let (tx, rx) = channel::(); - let (tx_load, rx_load) = channel::>(); - let (tx_page1, rx_page1) = channel::>(); - let (tx_page1b, rx_page1b) = channel::>(); - let (tx_page2, rx_page2) = channel::>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_load, - wd: Some(repo.path().to_owned()), - })?; - tx.send(SessionEvent::QueryLog { - tx: tx_page1, - query: "all()".to_owned(), - })?; - tx.send(SessionEvent::QueryLog { - tx: tx_page1b, - query: "all()".to_owned(), - })?; - tx.send(SessionEvent::QueryLogNextPage { tx: tx_page2 })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession { - force_log_page_size: Some(7), - ..Default::default() - } - .handle_events(&rx)?; - - rx_load.recv()??; - - let page1 = rx_page1.recv()??; - assert_eq!(7, page1.rows.len()); - assert_eq!(true, page1.has_more); - - let page1b = rx_page1b.recv()??; - assert_eq!(7, page1b.rows.len()); - assert_eq!(true, page1b.has_more); - - let page2 = rx_page2.recv()??; - assert_eq!(5, page2.rows.len()); - assert_eq!(false, page2.has_more); - - Ok(()) -} - -#[test] -fn query_log_multi_interrupt() -> Result<()> { - let repo = mkrepo(); - let (tx, rx) = channel::(); - let (tx_load, rx_load) = channel::>(); - let (tx_page1, rx_page1) = channel::>(); - let (tx_rev, rx_rev) = channel::>(); - let (tx_page2, rx_page2) = channel::>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_load, - wd: Some(repo.path().to_owned()), - })?; - tx.send(SessionEvent::QueryLog { - tx: tx_page1, - query: "all()".to_owned(), - })?; - tx.send(SessionEvent::QueryRevision { - tx: tx_rev, - id: revs::working_copy(), - })?; - tx.send(SessionEvent::QueryLogNextPage { tx: tx_page2 })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession { - force_log_page_size: Some(7), - ..Default::default() - } - .handle_events(&rx)?; - - rx_load.recv()??; - - let page1 = rx_page1.recv()??; - assert_eq!(7, page1.rows.len()); - assert_eq!(true, page1.has_more); - - let rev = rx_rev.recv()??; - assert!(matches!(rev, RevResult::Detail { header, .. } if header.is_working_copy)); - - let page2 = rx_page2.recv()??; - assert_eq!(5, page2.rows.len()); - assert_eq!(false, page2.has_more); - - Ok(()) -} - -#[test] -fn query_check_immutable() -> Result<()> { - let repo = mkrepo(); - let (tx, rx) = channel::(); - let (tx_load, rx_load) = channel::>(); - let (tx_page, rx_page) = channel::>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_load, - wd: Some(repo.path().to_owned()), - })?; - tx.send(SessionEvent::QueryLog { - tx: tx_page, - query: "@|main@origin".to_owned(), - })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession { - force_log_page_size: Some(2), - ..Default::default() - } - .handle_events(&rx)?; - - rx_load.recv()??; - - let page = rx_page.recv()??; - assert_eq!(2, page.rows.len()); - assert!(!page.rows[0].revision.is_immutable); - assert!(page.rows[1].revision.is_immutable); - - Ok(()) -} - -#[test] -fn query_rev_not_found() -> Result<()> { - let repo = mkrepo(); - - let (tx, rx) = channel::(); - let (tx_load, rx_load) = channel::>(); - let (tx_query, rx_query) = channel::>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_load, - wd: Some(repo.path().to_owned()), - })?; - tx.send(SessionEvent::QueryRevision { - tx: tx_query, - id: mkid("abcdefghijklmnopqrstuvwxyz", "00000000"), - })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession::default().handle_events(&rx)?; - - _ = rx_load.recv()??; - let result = rx_query.recv()??; - - assert!( - matches!(result, RevResult::NotFound { id } if id.change.hex == "abcdefghijklmnopqrstuvwxyz") - ); - - Ok(()) -} - -#[test] -fn config_read() -> Result<()> { - let repo = mkrepo(); - - let (tx, rx) = channel::(); - let (tx_load, rx_load) = channel::>(); - let (tx_read, rx_read) = channel::>>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_load, - wd: Some(repo.path().to_owned()), - })?; - tx.send(SessionEvent::ReadConfigArray { - tx: tx_read, - key: vec!["gg".into(), "ui".into(), "recent-workspaces".into()], - })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession::default().handle_events(&rx)?; - - _ = rx_load.recv()??; - let result = rx_read.recv()?; - - assert!(result.is_ok()); // key may be empty, but should exist due to defaults - - Ok(()) -} - -#[test] -fn config_write() -> Result<()> { - let repo = mkrepo(); - - let (tx, rx) = channel::(); - let (tx_load, rx_load) = channel::>(); - let (tx_read, rx_read) = channel::>>(); - - tx.send(SessionEvent::OpenWorkspace { - tx: tx_load, - wd: Some(repo.path().to_owned()), - })?; - tx.send(SessionEvent::WriteConfigArray { - scope: ConfigSource::Repo, - key: vec!["gg".into(), "test".into()], - values: vec!["a".into(), "b".into()], - })?; - tx.send(SessionEvent::ReadConfigArray { - tx: tx_read, - key: vec!["gg".into(), "test".into()], - })?; - tx.send(SessionEvent::EndSession)?; - - WorkerSession::default().handle_events(&rx)?; - - _ = rx_load.recv()??; - let result = rx_read.recv()??; - - assert_eq!(vec!["a".to_string(), "b".to_string()], result); - - Ok(()) -} +use super::{mkid, mkrepo, revs}; +use crate::{ + messages::{LogPage, RepoConfig, RevResult}, + worker::{Session, SessionEvent, WorkerSession}, +}; +use anyhow::Result; +use jj_cli::config::ConfigSource; +use std::{path::PathBuf, sync::mpsc::channel}; + +#[test] +fn start_and_stop() -> Result<()> { + let (tx, rx) = channel::(); + tx.send(SessionEvent::EndSession)?; + WorkerSession::default().handle_events(&rx)?; + Ok(()) +} + +#[test] +fn load_repo() -> Result<()> { + let repo = mkrepo(); + + let (tx, rx) = channel::(); + let (tx_good_repo, rx_good_repo) = channel::>(); + let (tx_bad_repo, rx_bad_repo) = channel::>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_good_repo, + wd: Some(repo.path().to_owned()), + })?; + tx.send(SessionEvent::OpenWorkspace { + tx: tx_bad_repo, + wd: Some(PathBuf::new()), + })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession::default().handle_events(&rx)?; + + let config = rx_good_repo.recv()??; + assert!(matches!(config, RepoConfig::Workspace { .. })); + + let config = rx_bad_repo.recv()??; + assert!(matches!(config, RepoConfig::LoadError { .. })); + + Ok(()) +} + +#[test] +fn reload_repo() -> Result<()> { + let repo1 = mkrepo(); + let repo2 = mkrepo(); + + let (tx, rx) = channel::(); + let (tx_first_repo, rx_first_repo) = channel::>(); + let (tx_second_repo, rx_second_repo) = channel::>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_first_repo, + wd: Some(repo1.path().to_owned()), + })?; + tx.send(SessionEvent::OpenWorkspace { + tx: tx_second_repo, + wd: Some(repo2.path().to_owned()), + })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession::default().handle_events(&rx)?; + + let config = rx_first_repo.recv()??; + assert!(matches!(config, RepoConfig::Workspace { .. })); + + let config = rx_second_repo.recv()??; + assert!(matches!(config, RepoConfig::Workspace { .. })); + + Ok(()) +} + +#[test] +fn reload_with_default_query() -> Result<()> { + let repo = mkrepo(); + + let (tx, rx) = channel::(); + let (tx_load, rx_load) = channel::>(); + let (tx_query, rx_query) = channel::>(); + let (tx_reload, rx_reload) = channel::>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_load, + wd: Some(repo.path().to_owned()), + })?; + tx.send(SessionEvent::QueryLog { + tx: tx_query, + query: "none()".to_owned(), + })?; + tx.send(SessionEvent::OpenWorkspace { + tx: tx_reload, + wd: None, + })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession::default().handle_events(&rx)?; + + _ = rx_load.recv()??; + _ = rx_query.recv()??; + let config = rx_reload.recv()??; + assert!( + matches!(config, RepoConfig::Workspace { latest_query, .. } if latest_query == "none()") + ); + + Ok(()) +} + +#[test] +fn query_log_single() -> Result<()> { + let repo = mkrepo(); + + let (tx, rx) = channel::(); + let (tx_load, rx_load) = channel::>(); + let (tx_query, rx_query) = channel::>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_load, + wd: Some(repo.path().to_owned()), + })?; + tx.send(SessionEvent::QueryLog { + tx: tx_query, + query: "@".to_owned(), + })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession::default().handle_events(&rx)?; + + _ = rx_load.recv()??; + let page = rx_query.recv()??; + assert_eq!(1, page.rows.len()); + assert_eq!(false, page.has_more); + + Ok(()) +} + +#[test] +fn query_log_multi() -> Result<()> { + let repo = mkrepo(); + let (tx, rx) = channel::(); + let (tx_load, rx_load) = channel::>(); + let (tx_page1, rx_page1) = channel::>(); + let (tx_page2, rx_page2) = channel::>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_load, + wd: Some(repo.path().to_owned()), + })?; + tx.send(SessionEvent::QueryLog { + tx: tx_page1, + query: "all()".to_owned(), + })?; + tx.send(SessionEvent::QueryLogNextPage { tx: tx_page2 })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession { + force_log_page_size: Some(7), + ..Default::default() + } + .handle_events(&rx)?; + + rx_load.recv()??; + + let page1 = rx_page1.recv()??; + assert_eq!(7, page1.rows.len()); + assert_eq!(true, page1.has_more); + + let page2 = rx_page2.recv()??; + assert_eq!(5, page2.rows.len()); + assert_eq!(false, page2.has_more); + + Ok(()) +} + +#[test] +fn query_log_multi_restart() -> Result<()> { + let repo = mkrepo(); + let (tx, rx) = channel::(); + let (tx_load, rx_load) = channel::>(); + let (tx_page1, rx_page1) = channel::>(); + let (tx_page1b, rx_page1b) = channel::>(); + let (tx_page2, rx_page2) = channel::>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_load, + wd: Some(repo.path().to_owned()), + })?; + tx.send(SessionEvent::QueryLog { + tx: tx_page1, + query: "all()".to_owned(), + })?; + tx.send(SessionEvent::QueryLog { + tx: tx_page1b, + query: "all()".to_owned(), + })?; + tx.send(SessionEvent::QueryLogNextPage { tx: tx_page2 })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession { + force_log_page_size: Some(7), + ..Default::default() + } + .handle_events(&rx)?; + + rx_load.recv()??; + + let page1 = rx_page1.recv()??; + assert_eq!(7, page1.rows.len()); + assert_eq!(true, page1.has_more); + + let page1b = rx_page1b.recv()??; + assert_eq!(7, page1b.rows.len()); + assert_eq!(true, page1b.has_more); + + let page2 = rx_page2.recv()??; + assert_eq!(5, page2.rows.len()); + assert_eq!(false, page2.has_more); + + Ok(()) +} + +#[test] +fn query_log_multi_interrupt() -> Result<()> { + let repo = mkrepo(); + let (tx, rx) = channel::(); + let (tx_load, rx_load) = channel::>(); + let (tx_page1, rx_page1) = channel::>(); + let (tx_rev, rx_rev) = channel::>(); + let (tx_page2, rx_page2) = channel::>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_load, + wd: Some(repo.path().to_owned()), + })?; + tx.send(SessionEvent::QueryLog { + tx: tx_page1, + query: "all()".to_owned(), + })?; + tx.send(SessionEvent::QueryRevision { + tx: tx_rev, + id: revs::working_copy(), + })?; + tx.send(SessionEvent::QueryLogNextPage { tx: tx_page2 })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession { + force_log_page_size: Some(7), + ..Default::default() + } + .handle_events(&rx)?; + + rx_load.recv()??; + + let page1 = rx_page1.recv()??; + assert_eq!(7, page1.rows.len()); + assert_eq!(true, page1.has_more); + + let rev = rx_rev.recv()??; + assert!(matches!(rev, RevResult::Detail { header, .. } if header.is_working_copy)); + + let page2 = rx_page2.recv()??; + assert_eq!(5, page2.rows.len()); + assert_eq!(false, page2.has_more); + + Ok(()) +} + +#[test] +fn query_check_immutable() -> Result<()> { + let repo = mkrepo(); + let (tx, rx) = channel::(); + let (tx_load, rx_load) = channel::>(); + let (tx_page, rx_page) = channel::>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_load, + wd: Some(repo.path().to_owned()), + })?; + tx.send(SessionEvent::QueryLog { + tx: tx_page, + query: "@|main@origin".to_owned(), + })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession { + force_log_page_size: Some(2), + ..Default::default() + } + .handle_events(&rx)?; + + rx_load.recv()??; + + let page = rx_page.recv()??; + assert_eq!(2, page.rows.len()); + assert!(!page.rows[0].revision.is_immutable); + assert!(page.rows[1].revision.is_immutable); + + Ok(()) +} + +#[test] +fn query_rev_not_found() -> Result<()> { + let repo = mkrepo(); + + let (tx, rx) = channel::(); + let (tx_load, rx_load) = channel::>(); + let (tx_query, rx_query) = channel::>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_load, + wd: Some(repo.path().to_owned()), + })?; + tx.send(SessionEvent::QueryRevision { + tx: tx_query, + id: mkid("abcdefghijklmnopqrstuvwxyz", "00000000"), + })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession::default().handle_events(&rx)?; + + _ = rx_load.recv()??; + let result = rx_query.recv()??; + + assert!( + matches!(result, RevResult::NotFound { id } if id.change.hex == "abcdefghijklmnopqrstuvwxyz") + ); + + Ok(()) +} + +#[test] +fn config_read() -> Result<()> { + let repo = mkrepo(); + + let (tx, rx) = channel::(); + let (tx_load, rx_load) = channel::>(); + let (tx_read, rx_read) = channel::>>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_load, + wd: Some(repo.path().to_owned()), + })?; + tx.send(SessionEvent::ReadConfigArray { + tx: tx_read, + key: vec!["gg".into(), "ui".into(), "recent-workspaces".into()], + })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession::default().handle_events(&rx)?; + + _ = rx_load.recv()??; + let result = rx_read.recv()?; + + assert!(result.is_ok()); // key may be empty, but should exist due to defaults + + Ok(()) +} + +#[test] +fn config_write() -> Result<()> { + let repo = mkrepo(); + + let (tx, rx) = channel::(); + let (tx_load, rx_load) = channel::>(); + let (tx_read, rx_read) = channel::>>(); + + tx.send(SessionEvent::OpenWorkspace { + tx: tx_load, + wd: Some(repo.path().to_owned()), + })?; + tx.send(SessionEvent::WriteConfigArray { + scope: ConfigSource::Repo, + key: vec!["gg".into(), "test".into()], + values: vec!["a".into(), "b".into()], + })?; + tx.send(SessionEvent::ReadConfigArray { + tx: tx_read, + key: vec!["gg".into(), "test".into()], + })?; + tx.send(SessionEvent::EndSession)?; + + WorkerSession::default().handle_events(&rx)?; + + _ = rx_load.recv()??; + let result = rx_read.recv()??; + + assert_eq!(vec!["a".to_string(), "b".to_string()], result); + + Ok(()) +} diff --git a/src/GraphLine.svelte b/src/GraphLine.svelte index cdabf35..f173f7c 100644 --- a/src/GraphLine.svelte +++ b/src/GraphLine.svelte @@ -1,107 +1,107 @@ - - -{#if !line.indirect} - - -
- - -{/if} - - - - + + +{#if !line.indirect} + + +
+ + +{/if} + + + + diff --git a/src/GraphLog.svelte b/src/GraphLog.svelte index 774217e..db34c6f 100644 --- a/src/GraphLog.svelte +++ b/src/GraphLog.svelte @@ -1,131 +1,131 @@ - - - - - - - - {#each visibleSlice.rows as row} - {#key row} - - - - - - {#if row} - - {/if} - - {/key} - {/each} - - {#each visibleSlice.rows as row} - {#key row} - {#each distinctLines(visibleSlice.keys, row) as line} - - {/each} - {/key} - {/each} - - - + + + + + + + + {#each visibleSlice.rows as row} + {#key row} + + + + + + {#if row} + + {/if} + + {/key} + {/each} + + {#each visibleSlice.rows as row} + {#key row} + {#each distinctLines(visibleSlice.keys, row) as line} + + {/each} + {/key} + {/each} + + + diff --git a/src/GraphNode.svelte b/src/GraphNode.svelte index 81a253b..de8c7e2 100644 --- a/src/GraphNode.svelte +++ b/src/GraphNode.svelte @@ -1,40 +1,40 @@ - - -{#if header.is_immutable} - -{:else} - - {#if header.is_working_copy} - - {/if} -{/if} - - + + +{#if header.is_immutable} + +{:else} + + {#if header.is_working_copy} + + {/if} +{/if} + + diff --git a/src/LogPane.svelte b/src/LogPane.svelte index 6cb3ac5..8d50ba1 100644 --- a/src/LogPane.svelte +++ b/src/LogPane.svelte @@ -1,215 +1,215 @@ - - - -
- - {option.label} - - -
- - - {#if graphRows} - - {#if row} - - {/if} - - {:else} -
Loading changes...
- {/if} -
-
- - + + + +
+ + {option.label} + + +
+ + + {#if graphRows} + + {#if row} + + {/if} + + {:else} +
Loading changes...
+ {/if} +
+
+ + diff --git a/src/RevisionPane.svelte b/src/RevisionPane.svelte index 4c6fea0..4a9578c 100644 --- a/src/RevisionPane.svelte +++ b/src/RevisionPane.svelte @@ -1,346 +1,346 @@ - - - -

- - | - {#if rev.header.is_working_copy} - | Working copy - {/if} - {#if rev.header.is_immutable} - | Immutable - {/if} - - -
- - Edit - - - New - -
-

- -
-