fix(status-bar): reflect actual current keybindings (#1242)
* status-bar: first_line: Use more generic var names
Rename all `CtrlKey...` to the equivalent `Key...` to make the name less
specific. It implies that all key bindings use Ctrl as modifier key,
which needn't necessarily be the case.
* status-bar: first_line: Refactor `ctrl_keys`
Removes lots of code duplication by `Unselect`ing all keys by default
and only `Select`ing what is actually required for a given Input mode.
* utils: conditionally compile unix-specific code
In `zellij_utils`, the following modules each contained code that was
previously targeting only the unix platform:
- consts: Works with unix-specific filesystem attributes to set e.g.
special file permissions. Also relies on having a UID.
- shared: Uses unix-specific filesystem attributes to set file
permissions
These will never work when targeting wasm. Hence the concerning code
passages have been moved into private submodules that are only compiled
and re-exported when the target isn't `#[cfg(unix)]`. The re-export
makes sure that crates from the outside that use `zellij_utils` work as
before, since from their point of view nothing has changed.
* utils: Share more modules with wasm
that work on both wasm and unix natively. This requires factoring out
bits of code in the `setup` and `input` modules into a private submodule
that is re-exported when the compilation target is *not* "wasm". The
following modules are now available to the wasm target:
- cli
- consts
- data
- envs
- input (partial)
- actions
- command
- configs
- keybinds
- layout
- options
- plugins
- theme
- pane_size
- position
- setup (partial)
- shared
The remaining modules unavailable to wasm have dependencies on crates
that cannot compile against wasm, such as `async_std` or `termwiz`.
* utils/input/keybinds_test: Fix import
of the `CharOrArrow` struct which is now part of the `data` submodule.
* utils/layout: Use global serde crate
Previously the code was decorated with `#[serde(crate = "self::serde")]`
statements which cannot be shared with wasm. Use the regular serde
without specifying which serde is meant.
* utils/data: Implement `fmt::Display` for `Key`
so the Keybindings can be displayed via `format!` and friends in e.g.
the status bar.
* tile/prelude: Re-export `actions`
submodule of `zellij_utils` so the plugins can access the `ModeKeybinds`
struct with all of its members.
* utils/data: Fix `ModeInfo::keybinds` type
and transfer a vector of `(Key, Vec<Action>)` to the plugins so they can
parse it themselves, instead of passing strings around. Due to the
requirement of the `Eq` trait derive on `ModeInfo` this requires
deriving `Eq` on all the types included by `Key` and `Action` as well.
Note that `Action` includes the `layout::SplitSize` structure as a
member. We cannot derive `Eq` here since `SplitSize::Percent(f64)`
cannot satisfy `Eq` because `f64` doesn't implement this. So we add a
new type to hack around this limitation by storing the percentage as
`u64` internally, scaled by a factor of 10 000 and transforming it to
f64 when needed. Refer to the documentation of `layout::Percent` for
further information.
* utils/data: Make `Key` sortable
so the keybindings can be sorted after their keys.
* WIP: utils/input: Make keybinds accessible
when generating `ModeInfo` structs.
* utils/data: Handle unprintable chars in `Key`
when displaying via the `fmt::Display` trait. Handles `\t` and `\n` and
represents them as UTF-8 arrow glyphs.
* HACK: utils/layout: Use u64 for SplitSize::Percent
The previous workaround using a custom `Percent` type fails at the
absolute latest when confronted with user layouts, since these do not
know about the scaling factor and will thus break. It still breaks
currently because `Percent` now expects a u64 (i.e. `50`, not `50.0`)
but this is more easily explained and understood.
* status-bar: Add helper macros
that retrieve the key bound to execute a sequence of `Action` given a
specific Keybinding, and a shorthand that expands to
`Action::SwitchToMode(InputMode::Normal)` used for pattern matching with
the `matches!` macro.
* status-bar/first_line: Get shared superkey if any
from the `ModeKeybindings` in the current `ModeInfo` struct. If the
configured keybindings for switching the modes don't have a superkey in
common, do not print a common prefix.
* status-bar/first_line: Add key to KeyShortcut
which is the key that must be pressed in the current mode to execute the
given shortcut (i.e. switch to the given mode).
* status-bar/first_line: Dynamically set mode binds
Read the keybindings for switching the modes to print in the first line
from the actually configured keybindings for the current mode. Add some
logic to the code that:
- Prints only the "single letter" of the keybinding if all mode-switch
shortcuts *share the same modifier key*,
- Or prints the whole keybinding (with modified) into each segment if
there is no common modifier key.
* status-bar/second_line: Display configured binds
Instead of showing some hard-coded default values. For each mode, reads
the keybindings from the configured keybindings based on some sequence
of action. For example, the keybinding for `New` in the `Pane` menu is
now determined by looking into the configured keybindings and finding
what key is bound to the `Action::NewPane(None)` action.
If no keybinding is found for a given sequence of actions, it will not
show up in the segments either.
* WIP: utils/keybinds: Make key order deterministic
by using a BTreeMap which by default has all of its elements in sorted
order internally. As of currently this doesn't seem to impress the order
in which the keybindings are sent to the plugins, though.
* utils/data: Reorder `Key` variants
to have the Arrow keys sorted as "left", "down", "up", "right" in
accordance with the display in e.g. the status bar.
* status-bar/first_line: Fix inverted `matches!`
when trying to obtain the keybindings to switch between the input modes.
Its initial purpose was to filter out all ' ', '\n' and 'Esc'
keybindings for switching modes (As these are the default and not of
interest for the status bar display), but it was not negated and thus
only filtered out the aforementioned keys.
* status-bar: Don't get all modeswitch keybinds
but only those that are displayed in the status bar. This currently
excludes the keybindings for Entering the Pane/TabRename mode, Tmux mode
and Prompt mode. We must explicitly exclude these since they aren't
bound to the same Modifiers as the regular keys. Thus, if we e.g. enter
Pane or Tab mode, it will pick up the
`SwitchToMode(InputMode::TabRename)` action as being bound to `c`, hence
the `superkey` function cannot find a common modifier, etc. But we don't
display the `TabRename` input mode in the first line anyway, so we must
ignore it.
Therefore, we additionally add the keybinding to call the `Action::Quit`
action to terminate zellij to the vector we return. Also remove the
`(Key, InputMode)` tuple and convert the return type to a plain
`Vec<Key>`, since the never worked with the `InputMode` in the first
place.
* status-bar/first_line: Fix output for tight screen
Implement the "Squeezed" display variant where we do not display which
of the modes each keybinding switches to, but only the keybinding
itself.
* status-bar/second_line: Remove trailing " / "
* status-bar/second-line: Refactor key hints
Instead of determining the appropriate key hints for every case
separately (i.e. enough space to show all, show shortened, shot
best-effort), create a central function that returns for the current
`InputMode` a Vector with tuples of:
- A String to show in full-length mode
- A String to show in shortened/best-effort mode
- The vector of keys that goes with this key hint
This allows all functions that need the hints to iterate over the vector
and pick whatever hint suits them along with the Keys to display.
* status-bar/second-line: Implement shortened hints
* utils/data: Fix display for `Key::Alt`
which previously printed only the internal char but not the modifier.
* status-bar/first-line: Add hidden Tmux tile
that is only shown when in Tmux mode. Note that with the default config
this "breaks" the shared superkey display, because it correctly
identifies that one can switch to Scroll mode via `[`.
* status-bar: Print superkey as part of first line
Instead of first obtaining the superkey and then the rest of the first
line to display. This way we don't need to split up individual data
structures and carry a boolean flag around multiple functions.
It also has the advantage that when the available space is really tight,
the first line is entirely empty and doesn't display a stale superkey
without any other keybinding hints.
* status-bar: Rework keybinding theming
Previously there were individual functions to create the tiles in the
first line depending on whether:
- A tile was selected, unselected, unselected alternate (for theming) or disabled, and
- Tiles had full length or were displayed shortened
In the first case, the functions that previously handled the theming
only differed in what theme they apply to the otherwise identical
content. Since the theming information was drawn from a flat structure
that simulated hierarchy by giving hierarchical names to its theme
"members", this couldn't be handled in code. In the second case, some of
the theming information needed for the full-length shortcuts was
replicated for the shortened shortcuts.
Instead, rewrite the general Theming structure into a hierarchical one:
Adds a new structure `SegmentStyle` that contains the style for a single
segment depending on whether it is selected, unselected (alternate) or
disabled. Refactor the `first-line` module to use a single function to
generate either full-length or shortened tiles, that does functionally
the same but switches themes based on the selection status of the tile
it themes.
* status-bar/second-line: Return new `LinePart`s
from the `add_shortcut` function instead of modifying the input
parameters.
* status-bar/second-line: Implement adaptive behavior
and make the keyhints adapt when the screen runs out of space. The hints
first become shortened and when necessary partially disappear to display
a "..." hint instead.
* status-bar/second-line: Show float pane binding
based on the keycombination that's really bound to switching into the
"Pane" input mode.
* status-bar/get_keys_and_hints: Add more modes
for the keybindings in Tmux and the Pane/TabRename input modes.
* status-bar/second-line: Unify mode handling
and don't do extra shortcut handling for Tmux and the Pane/TabRename
modes any longer. Instead, assemble this like for all other modes from
the keybinding and hints vector.
* status-bar/first-line: Refactor common modifier
to a separate function so it can be used by other modules, too.
* status-bar/second-line: Display modifier in hints
when available. For example, for bindings to move between panes when in
PaneRename mode, now displays "Alt + <hjkl>" instead of
"<Alt+hAlt+j...>".
* utils/ipc: Remove `Copy` from `ClientAttributes`
as preparation to add `Keybinds` as a member to the `ClientAttributes`
struct. `Keybinds` contains a `HashMap`, for which the `std` doesn't
derive `Copy` but only `Clone`.
* utils/input/keybinds: Fix import path
Import `Key` and `InputMode` directly from `data`.
* utils/ipc: Add `Keybinds` to `ClientAttributes`
so we can keep track, pre-client, of the configured key bindings and
pass them around further in the code.
* server/lib: Store `ClientAttributes` over `Style`
in `SessionMetadata` to be able to pass Keybindings to other places in
the code, too. Since `Style` is also a member of `ClientAttributes`,
this works with minimal modifications.
* utils/input: Change `get_mode_info` parameters
to take a `ClientAttributes` struct instead of merely the `Style`
information. This way we can get the `Style` from the
`ClientAttributes`, and also have access to the `keybinds` member that
stores the keybinding configuration.
* utils/ipc: Use `rmp` for serde of IPC messages
instead of `bincode`, which seemingly has issues (de)serializing
`HashMap`s and `BTreeMap`s since `deserialize_any` isn't implemented for
these types.
* fix(nix): remove `assets` from `gitignore`
Remove `assets` from the gitignore of the plugins themselves,
since every single plugin now depends on the asset being accessible
in its source directory.
* tests/e2e: Fix status bar in snapshots
to reflect the current state of the dynamic keybindings.
* status_bar/first_line: Don't show unbound modes
If switching to a specific mode isn't bound to a key, don't show a
tile/ribbon for it either. E.g. in `LOCKED` mode, this will only show
the tile for the `LOCK` mode and ignore all others.
* utils/data: Make 'Key::Char(' ') visible as "␣"
so the user doesn't only see a blank char but has an idea that the space
key is meant.
* status_bar/second_line: Remove extra hints
generated by the `hint_producing_function` that would tell the user in
every input mode how to get back to normal mode. Instead, add this as
keybinding to the general keybindings vector.
This removes some lines of duplicated code but most of all now applies
the correct theming to this keybinding. Additionally, previously the
`RenameTab` and `RenamePane` input modes would show the keybinding to
get back to normal mode twice and both of them were hardcoded. This
binding is now dynamically displayed based on what the user configured
as keybinding.
* utils/data: format unprintable chars as words
instead of unicode symbols. E.g. write "SPACE" instead of "␣".
* utils/data: Fix display for `Ctrl`/`Alt` keys
previously their "inner" chars would be displayed with a regular
`fmt::Display` for the `&str` type they are. This doesn't match what we
want to output. So instead we wrap the inner chars into `Key::Char`
before printing them.
* utils/data: Change order of `Key`s
so that e.g. for the default bindings in `Scroll` mode we prefer to show
`PgDn|PgUp` rather than the arrow keys these actions are bound to as
well.
* status_bar/first_line: Don't ignore default char
bindings by default. These include the '\n', ' ' and 'Esc' bindings that
by default lead back to `Normal` input mode from all the modes.
Previously we would unconditionally ignore them and consequently not
print the tile when in fact the user may have bound this particular
action to either of the keys.
Instead now we first ignore the keys mentioned and if we turn up with an
undefined binding, we consider these default keys as well so we get
*something* to display in any case.
* status_bar/first_line: Add space when no modifier
is shared between the keybindings. This way there isn't a stray arrow at
the very border of the screen, but it is spaced just like the tab-bar
and the second line is.
* status_bar/second_line: Print separators
between consecutive keys bound to specific actions. This allows the user
to visually differ between different keys.
* status_bar/main: Don't return modifier if empty
* status_bar/first_line: Don't suppress Disabled tiles
Disabled is a special state that the keybindings only assume in locked
mode. It turns the respective tiles grey to signal to the user that
these are currently inactive. With respect to users new to zellij, it
may appear confusing that when entering locked mode all the other tiles
disappear (which they do because they have no valid keybinding
assigned). Since we have no keybinding for them, we still display them
but without any associated key (i.e. as `<>` for the binding).
* status_bar/first_line: Don't print leading triangle
on first tile, when there is no shared superkey.
* status_bar/second_line: Add exceptions
for inter-key separators. Keeps groups of `hjkl` and arrow keys intact
(doesn't add separators between the keys) but separates all others.
* status_bar/main: Refactor `action_key`
to a regular function instead of a macro. It turns out that while being
able to match patterns is a nice feature, we completely rely on the keys
that drop out of the pattern found this way to be sorted in a sensible
way. Since we sort the key vectors in the necessary places after the
keys, and not the actions, this of course doesn't apply when the user
changes "hjkl" to "zjkl", which would then become "jklz". Now this is of
course wrong, because "z" still means "Move focus left", and not "Move
focus right".
With the function we now assume a slice of Actions that we match the
action vectors from the keybindings against to obtain the necessary
keys. In order to avoid ugly `into_iter().chain(...)` constructs we had
before we also add a new function `action_key_group` that takes a sliced
array of slices to get a whole group of keys to display.
* status_bar/first_line: Fix "triangle" for short tiles
since we do not want to display a colored triangle at the start of the
line when in sortened mode (just as we do for the long tiles now).
Also fix a bug that would make the triangle reappear when the first
keybinding to be displayed didn't have a key assigned and thus wouldn't
be displayed at all.
* status_bar/second_line: Fix typo
that would cause single `Ctrl+?` bindings for actions in the second line
to be displayed as `Ctrl + <Ctrl+?>`.
* status_bar/second_line: Fix char count
when displaying groups of keys in a binding with or without a separator.
* status_bar: Use new `action_key` fn
instead of the previous macro to obtain the keys to display in the
status bar in a fixed given order. Also fix the display "bug" where tab
switching would be shows as "ArrowLeft/ArrowDown" instead of
"ArrowLeft/ArrowRight".
* status_bar/second_line: Fix floating pane hint
that tells the user what keybinding to press to suppress the currently
active floating panes. This was previously hardcoded.
* utils: Send full keybinds in `ModeInfo`
instead of the currently active `ModeKeybinds` for the active input
mode. Some of the UI issues cannot be solved without having access to
*all* keybindings.
* utils: Refactor keybinds vec into type
to make clippy happy.
* status_bar/first_line: Remove needless borrows
* status_bar: Factor out printing keybindings
into a separate function that takes a vector of keys and a palette and
returns the painted key groups, with correct inter-character separation
where necessary and factoring out common modifier keys.
* status_bar/tip: Use real keybindings
instead of printing hard-coded messages to the user.
* status_bar: abort early when keyvector is empty
in `style_key_with_modifier`.
* status_bar/tip: Fix all keybindings
and make them dynamic given the keybindings really active in the current
session. Also display **UNBOUND** is some keybinding is missing from the
users config.
* status_bar: Go clippy!
* status_bar: Add documentation
and add a new exception group to `action_key_group` that ensures that
`hl` and `jk` won't be separated with `|`.
* status_bar/tip: Detect when key aren't bound
correctly and show "UNBOUND" as keyhint instead, then. Previously we
would only check the length of the whole keybinding segment, but that
isn't a good indicator since most of the bindings require changing modes
first, which already adds a variable number of letters to the segment.
However, there is not point in showing how to get to a certain mode, if
the binding needed in that mode doesn't exist.
* status_bar/first_line: Show bindings when locked
if the user has any configured.
* status_bar: Don't consider 'hl', 'jk' groups
that don't need a separator in between the letters.
* status_bar/second_line: Add "search" keybindings
for the new Search functionality.
* tests/e2e: Fix snapshots
with what the status bar now really displays.
* status_bar: Remove old comments
* status_bar/first_line: Rename 'long_tile'
to the more descriptive name 'mode_shortcut', which better describes
what this function does.
* status_bar/first_line: Fix spacing in simple UI
where the modifier would be shows as `Ctrl +`, without a trailing space.
This isn't an issue in regular mode, where we have the spacing from the
arrow gaps (`>>`) that "simulates" this effect.
* status_bar: Refactor and rename `ctrl_keys`
so it doesn't rely on some "external" index for operation any more.
* status_bar: Add unit tests to shared functions
and fix a bug in the process where certain `Ctrl` keybindings would be
displayed wrong.
* status_bar/first_line: Rename functions
responsible for printing the long and short shortcut keyhint tiles. Also
add some documentation that explains their purpose and the arguments
they accept.
* status_bar/tips: Remove stray "/" in quicknav tip
* utils/layout: Remove old comments
introduced when rewriting `SplitSize::Percent` to not hold an `f64`
type.
* status_bar: Add "regex" as test dependency
We use regular expressions to strip all ANSI escape sequences in the
strings that are produced by the plugin functions during testing. We do
not test for the style information, but merely for the raw text.
* status_bar: Implement unit tests
* Makefile: Always run tests on host triple
This allows the unit tests for all plugins to be run on the host as well
(because their default compilation target is wasm32-wasi).
* tests/e2e: Add test for custom bindings
in the status bar. Makes sure that the modified bindings from a custom
configuration file are read and applied to the UI.
Co-authored-by: a-kenji <aks.kenji@protonmail.com>
This commit is contained in:
parent
408f520e4c
commit
f76b828209
71 changed files with 3274 additions and 1894 deletions
|
|
@ -68,6 +68,8 @@ flake-utils.lib.eachSystem [
|
|||
};
|
||||
cargo = rustToolchainToml;
|
||||
rustc = rustToolchainToml;
|
||||
cargoMSRV = msrvToolchain;
|
||||
rustcMSRV = msrvToolchain;
|
||||
|
||||
buildInputs = [
|
||||
# in order to run tests
|
||||
|
|
@ -163,6 +165,7 @@ flake-utils.lib.eachSystem [
|
|||
license = [licenses.mit];
|
||||
};
|
||||
in rec {
|
||||
packages.default = packages.zellij-native;
|
||||
# crate2nix - better incremental builds, but uses ifd
|
||||
packages.zellij = crate2nixPkgs.callPackage ./crate2nix.nix {
|
||||
inherit
|
||||
|
|
@ -177,7 +180,7 @@ in rec {
|
|||
nativeBuildInputs = nativeBuildInputs ++ defaultPlugins;
|
||||
};
|
||||
|
||||
packages.zellij-msrv = crate2nixMsrvPkgs.callPackage ./crate2nix.nix {
|
||||
packages.zellij-crate-msrv = crate2nixMsrvPkgs.callPackage ./crate2nix.nix {
|
||||
inherit
|
||||
name
|
||||
src
|
||||
|
|
@ -204,7 +207,25 @@ in rec {
|
|||
;
|
||||
nativeBuildInputs = nativeBuildInputs ++ defaultPlugins;
|
||||
};
|
||||
packages.default = packages.zellij;
|
||||
# native nixpkgs support - msrv
|
||||
packages.zellij-msrv =
|
||||
(pkgs.makeRustPlatform {
|
||||
cargo = cargoMSRV;
|
||||
rustc = rustcMSRV;
|
||||
})
|
||||
.buildRustPackage {
|
||||
inherit
|
||||
src
|
||||
name
|
||||
cargoLock
|
||||
buildInputs
|
||||
postInstall
|
||||
patchPhase
|
||||
desktopItems
|
||||
meta
|
||||
;
|
||||
nativeBuildInputs = nativeBuildInputs ++ defaultPlugins;
|
||||
};
|
||||
|
||||
packages.plugins-compact = plugins.compact-bar;
|
||||
packages.plugins-status-bar = plugins.status-bar;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
ignoreSource = [
|
||||
".git"
|
||||
".github"
|
||||
"assets"
|
||||
"docs"
|
||||
"example"
|
||||
"target"
|
||||
|
|
|
|||
31
Cargo.lock
generated
31
Cargo.lock
generated
|
|
@ -1677,6 +1677,12 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
|
|
@ -2049,6 +2055,28 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25786b0d276110195fa3d6f3f31299900cf71dfbd6c28450f3f58a0e7f7a347e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
|
|
@ -2301,6 +2329,7 @@ dependencies = [
|
|||
"colored",
|
||||
"lazy_static",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
|
|
@ -3348,7 +3377,6 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-std",
|
||||
"backtrace",
|
||||
"bincode",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"colored",
|
||||
|
|
@ -3364,6 +3392,7 @@ dependencies = [
|
|||
"nix",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
|
|
|
|||
|
|
@ -18,18 +18,9 @@ dependencies = [
|
|||
"clippy",
|
||||
]
|
||||
|
||||
# Patching the default flows to skip testing of wasm32-wasi targets
|
||||
[tasks.pre-test]
|
||||
condition = { env = { "CARGO_MAKE_CRATE_TARGET_TRIPLE" = "wasm32-wasi" } }
|
||||
env = { "SKIP_TEST" = true }
|
||||
|
||||
[tasks.test]
|
||||
condition = { env_false = ["SKIP_TEST"] }
|
||||
dependencies = ["pre-test"]
|
||||
args = ["test", "--", "@@split(CARGO_MAKE_TASK_ARGS,;)"]
|
||||
|
||||
[tasks.post-test]
|
||||
env = { "SKIP_TEST" = false }
|
||||
dependencies = ["get-host-triple"]
|
||||
args = ["test", "--target", "${CARGO_HOST_TRIPLE}", "--", "@@split(CARGO_MAKE_TASK_ARGS,;)"]
|
||||
|
||||
# Running Zellij using the development data directory
|
||||
[tasks.run]
|
||||
|
|
@ -125,6 +116,25 @@ env = { "CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS" = [
|
|||
] }
|
||||
run_task = { name = "build", fork = true }
|
||||
|
||||
[tasks.get-host-triple]
|
||||
script_runner = "@duckscript"
|
||||
script = '''
|
||||
output = exec rustc -v -V
|
||||
lines = split ${output.stdout} \n
|
||||
triple = set ""
|
||||
for line in ${lines}
|
||||
if starts_with ${line} "host:" && not is_empty ${line}
|
||||
bits = split ${line} " "
|
||||
triple = array_get ${bits} 1
|
||||
triple = set ${triple}
|
||||
end
|
||||
end
|
||||
|
||||
if not is_empty ${triple}
|
||||
set_env CARGO_HOST_TRIPLE "${triple}"
|
||||
end
|
||||
'''
|
||||
|
||||
[tasks.wasm-opt-plugins]
|
||||
dependencies = ["build-plugins-release"]
|
||||
script_runner = "@duckscript"
|
||||
|
|
|
|||
|
|
@ -15,3 +15,6 @@ serde_json = "1.0"
|
|||
thiserror = "1.0.30"
|
||||
zellij-tile = { path = "../../zellij-tile" }
|
||||
zellij-tile-utils = { path = "../../zellij-tile-utils" }
|
||||
|
||||
[dev-dependencies]
|
||||
regex = "1"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,13 +2,18 @@ mod first_line;
|
|||
mod second_line;
|
||||
mod tip;
|
||||
|
||||
use ansi_term::Style;
|
||||
use ansi_term::{
|
||||
ANSIString,
|
||||
Colour::{Fixed, RGB},
|
||||
Style,
|
||||
};
|
||||
|
||||
use std::fmt::{Display, Error, Formatter};
|
||||
use zellij_tile::prelude::actions::Action;
|
||||
use zellij_tile::prelude::*;
|
||||
use zellij_tile_utils::style;
|
||||
use zellij_tile_utils::{palette_match, style};
|
||||
|
||||
use first_line::{ctrl_keys, superkey};
|
||||
use first_line::first_line;
|
||||
use second_line::{
|
||||
floating_panes_are_visible, fullscreen_panes_to_hide, keybinds,
|
||||
locked_floating_panes_are_visible, locked_fullscreen_panes_to_hide, system_clipboard_error,
|
||||
|
|
@ -19,6 +24,8 @@ use tip::utils::get_cached_tip_name;
|
|||
// for more of these, copy paste from: https://en.wikipedia.org/wiki/Box-drawing_character
|
||||
static ARROW_SEPARATOR: &str = "";
|
||||
static MORE_MSG: &str = " ... ";
|
||||
/// Shorthand for `Action::SwitchToMode(InputMode::Normal)`.
|
||||
const TO_NORMAL: Action = Action::SwitchToMode(InputMode::Normal);
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
|
|
@ -45,48 +52,25 @@ impl Display for LinePart {
|
|||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ColoredElements {
|
||||
// selected mode
|
||||
pub selected_prefix_separator: Style,
|
||||
pub selected_char_left_separator: Style,
|
||||
pub selected_char_shortcut: Style,
|
||||
pub selected_char_right_separator: Style,
|
||||
pub selected_styled_text: Style,
|
||||
pub selected_suffix_separator: Style,
|
||||
// unselected mode
|
||||
pub unselected_prefix_separator: Style,
|
||||
pub unselected_char_left_separator: Style,
|
||||
pub unselected_char_shortcut: Style,
|
||||
pub unselected_char_right_separator: Style,
|
||||
pub unselected_styled_text: Style,
|
||||
pub unselected_suffix_separator: Style,
|
||||
// unselected mode alternate color
|
||||
pub unselected_alternate_prefix_separator: Style,
|
||||
pub unselected_alternate_char_left_separator: Style,
|
||||
pub unselected_alternate_char_shortcut: Style,
|
||||
pub unselected_alternate_char_right_separator: Style,
|
||||
pub unselected_alternate_styled_text: Style,
|
||||
pub unselected_alternate_suffix_separator: Style,
|
||||
// disabled mode
|
||||
pub disabled_prefix_separator: Style,
|
||||
pub disabled_styled_text: Style,
|
||||
pub disabled_suffix_separator: Style,
|
||||
// selected single letter
|
||||
pub selected_single_letter_prefix_separator: Style,
|
||||
pub selected_single_letter_char_shortcut: Style,
|
||||
pub selected_single_letter_suffix_separator: Style,
|
||||
// unselected single letter
|
||||
pub unselected_single_letter_prefix_separator: Style,
|
||||
pub unselected_single_letter_char_shortcut: Style,
|
||||
pub unselected_single_letter_suffix_separator: Style,
|
||||
// unselected alternate single letter
|
||||
pub unselected_alternate_single_letter_prefix_separator: Style,
|
||||
pub unselected_alternate_single_letter_char_shortcut: Style,
|
||||
pub unselected_alternate_single_letter_suffix_separator: Style,
|
||||
pub selected: SegmentStyle,
|
||||
pub unselected: SegmentStyle,
|
||||
pub unselected_alternate: SegmentStyle,
|
||||
pub disabled: SegmentStyle,
|
||||
// superkey
|
||||
pub superkey_prefix: Style,
|
||||
pub superkey_suffix_separator: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SegmentStyle {
|
||||
pub prefix_separator: Style,
|
||||
pub char_left_separator: Style,
|
||||
pub char_shortcut: Style,
|
||||
pub char_right_separator: Style,
|
||||
pub styled_text: Style,
|
||||
pub suffix_separator: Style,
|
||||
}
|
||||
|
||||
// I really hate this, but I can't come up with a good solution for this,
|
||||
// we need different colors from palette for the default theme
|
||||
// plus here we can add new sources in the future, like Theme
|
||||
|
|
@ -110,109 +94,74 @@ fn color_elements(palette: Palette, different_color_alternates: bool) -> Colored
|
|||
};
|
||||
match palette.source {
|
||||
PaletteSource::Default => ColoredElements {
|
||||
selected_prefix_separator: style!(background, palette.green),
|
||||
selected_char_left_separator: style!(background, palette.green).bold(),
|
||||
selected_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||
selected_char_right_separator: style!(background, palette.green).bold(),
|
||||
selected_styled_text: style!(background, palette.green).bold(),
|
||||
selected_suffix_separator: style!(palette.green, background).bold(),
|
||||
|
||||
unselected_prefix_separator: style!(background, palette.fg),
|
||||
unselected_char_left_separator: style!(background, palette.fg).bold(),
|
||||
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||
unselected_char_right_separator: style!(background, palette.fg).bold(),
|
||||
unselected_styled_text: style!(background, palette.fg).bold(),
|
||||
unselected_suffix_separator: style!(palette.fg, background),
|
||||
|
||||
unselected_alternate_prefix_separator: style!(background, alternate_background_color),
|
||||
unselected_alternate_char_left_separator: style!(
|
||||
background,
|
||||
alternate_background_color
|
||||
)
|
||||
.bold(),
|
||||
unselected_alternate_char_shortcut: style!(palette.red, alternate_background_color)
|
||||
.bold(),
|
||||
unselected_alternate_char_right_separator: style!(
|
||||
background,
|
||||
alternate_background_color
|
||||
)
|
||||
.bold(),
|
||||
unselected_alternate_styled_text: style!(background, alternate_background_color).bold(),
|
||||
unselected_alternate_suffix_separator: style!(alternate_background_color, background),
|
||||
|
||||
disabled_prefix_separator: style!(background, palette.fg),
|
||||
disabled_styled_text: style!(background, palette.fg).dimmed().italic(),
|
||||
disabled_suffix_separator: style!(palette.fg, background),
|
||||
selected_single_letter_prefix_separator: style!(background, palette.green),
|
||||
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||
selected_single_letter_suffix_separator: style!(palette.green, background),
|
||||
|
||||
unselected_single_letter_prefix_separator: style!(background, palette.fg),
|
||||
unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold().dimmed(),
|
||||
unselected_single_letter_suffix_separator: style!(palette.fg, background),
|
||||
|
||||
unselected_alternate_single_letter_prefix_separator: style!(background, palette.fg),
|
||||
unselected_alternate_single_letter_char_shortcut: style!(
|
||||
palette.red,
|
||||
alternate_background_color
|
||||
)
|
||||
.bold()
|
||||
.dimmed(),
|
||||
unselected_alternate_single_letter_suffix_separator: style!(palette.fg, background),
|
||||
|
||||
selected: SegmentStyle {
|
||||
prefix_separator: style!(background, palette.green),
|
||||
char_left_separator: style!(background, palette.green).bold(),
|
||||
char_shortcut: style!(palette.red, palette.green).bold(),
|
||||
char_right_separator: style!(background, palette.green).bold(),
|
||||
styled_text: style!(background, palette.green).bold(),
|
||||
suffix_separator: style!(palette.green, background).bold(),
|
||||
},
|
||||
unselected: SegmentStyle {
|
||||
prefix_separator: style!(background, palette.fg),
|
||||
char_left_separator: style!(background, palette.fg).bold(),
|
||||
char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||
char_right_separator: style!(background, palette.fg).bold(),
|
||||
styled_text: style!(background, palette.fg).bold(),
|
||||
suffix_separator: style!(palette.fg, background),
|
||||
},
|
||||
unselected_alternate: SegmentStyle {
|
||||
prefix_separator: style!(background, alternate_background_color),
|
||||
char_left_separator: style!(background, alternate_background_color).bold(),
|
||||
char_shortcut: style!(palette.red, alternate_background_color).bold(),
|
||||
char_right_separator: style!(background, alternate_background_color).bold(),
|
||||
styled_text: style!(background, alternate_background_color).bold(),
|
||||
suffix_separator: style!(alternate_background_color, background),
|
||||
},
|
||||
disabled: SegmentStyle {
|
||||
prefix_separator: style!(background, palette.fg),
|
||||
char_left_separator: style!(background, palette.fg).dimmed().italic(),
|
||||
char_shortcut: style!(background, palette.fg).dimmed().italic(),
|
||||
char_right_separator: style!(background, palette.fg).dimmed().italic(),
|
||||
styled_text: style!(background, palette.fg).dimmed().italic(),
|
||||
suffix_separator: style!(palette.fg, background),
|
||||
},
|
||||
superkey_prefix: style!(foreground, background).bold(),
|
||||
superkey_suffix_separator: style!(background, background),
|
||||
},
|
||||
PaletteSource::Xresources => ColoredElements {
|
||||
selected_prefix_separator: style!(background, palette.green),
|
||||
selected_char_left_separator: style!(palette.fg, palette.green).bold(),
|
||||
selected_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||
selected_char_right_separator: style!(palette.fg, palette.green).bold(),
|
||||
selected_styled_text: style!(background, palette.green).bold(),
|
||||
selected_suffix_separator: style!(palette.green, background).bold(),
|
||||
unselected_prefix_separator: style!(background, palette.fg),
|
||||
unselected_char_left_separator: style!(background, palette.fg).bold(),
|
||||
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||
unselected_char_right_separator: style!(background, palette.fg).bold(),
|
||||
unselected_styled_text: style!(background, palette.fg).bold(),
|
||||
unselected_suffix_separator: style!(palette.fg, background),
|
||||
|
||||
unselected_alternate_prefix_separator: style!(background, alternate_background_color),
|
||||
unselected_alternate_char_left_separator: style!(
|
||||
background,
|
||||
alternate_background_color
|
||||
)
|
||||
.bold(),
|
||||
unselected_alternate_char_shortcut: style!(palette.red, alternate_background_color)
|
||||
.bold(),
|
||||
unselected_alternate_char_right_separator: style!(
|
||||
background,
|
||||
alternate_background_color
|
||||
)
|
||||
.bold(),
|
||||
unselected_alternate_styled_text: style!(background, alternate_background_color).bold(),
|
||||
unselected_alternate_suffix_separator: style!(alternate_background_color, background),
|
||||
|
||||
disabled_prefix_separator: style!(background, palette.fg),
|
||||
disabled_styled_text: style!(background, palette.fg).dimmed(),
|
||||
disabled_suffix_separator: style!(palette.fg, background),
|
||||
selected_single_letter_prefix_separator: style!(palette.fg, palette.green),
|
||||
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||
selected_single_letter_suffix_separator: style!(palette.green, palette.fg),
|
||||
|
||||
unselected_single_letter_prefix_separator: style!(palette.fg, background),
|
||||
unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||
unselected_single_letter_suffix_separator: style!(palette.fg, background),
|
||||
|
||||
unselected_alternate_single_letter_prefix_separator: style!(background, palette.fg),
|
||||
unselected_alternate_single_letter_char_shortcut: style!(
|
||||
palette.red,
|
||||
alternate_background_color
|
||||
)
|
||||
.bold()
|
||||
.dimmed(),
|
||||
unselected_alternate_single_letter_suffix_separator: style!(palette.fg, background),
|
||||
|
||||
selected: SegmentStyle {
|
||||
prefix_separator: style!(background, palette.green),
|
||||
char_left_separator: style!(palette.fg, palette.green).bold(),
|
||||
char_shortcut: style!(palette.red, palette.green).bold(),
|
||||
char_right_separator: style!(palette.fg, palette.green).bold(),
|
||||
styled_text: style!(background, palette.green).bold(),
|
||||
suffix_separator: style!(palette.green, background).bold(),
|
||||
},
|
||||
unselected: SegmentStyle {
|
||||
prefix_separator: style!(background, palette.fg),
|
||||
char_left_separator: style!(background, palette.fg).bold(),
|
||||
char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||
char_right_separator: style!(background, palette.fg).bold(),
|
||||
styled_text: style!(background, palette.fg).bold(),
|
||||
suffix_separator: style!(palette.fg, background),
|
||||
},
|
||||
unselected_alternate: SegmentStyle {
|
||||
prefix_separator: style!(background, alternate_background_color),
|
||||
char_left_separator: style!(background, alternate_background_color).bold(),
|
||||
char_shortcut: style!(palette.red, alternate_background_color).bold(),
|
||||
char_right_separator: style!(background, alternate_background_color).bold(),
|
||||
styled_text: style!(background, alternate_background_color).bold(),
|
||||
suffix_separator: style!(alternate_background_color, background),
|
||||
},
|
||||
disabled: SegmentStyle {
|
||||
prefix_separator: style!(background, palette.fg),
|
||||
char_left_separator: style!(background, palette.fg).dimmed(),
|
||||
char_shortcut: style!(background, palette.fg).dimmed(),
|
||||
char_right_separator: style!(background, palette.fg).dimmed(),
|
||||
styled_text: style!(background, palette.fg).dimmed(),
|
||||
suffix_separator: style!(palette.fg, background),
|
||||
},
|
||||
superkey_prefix: style!(background, palette.fg).bold(),
|
||||
superkey_suffix_separator: style!(palette.fg, background),
|
||||
},
|
||||
|
|
@ -263,15 +212,7 @@ impl ZellijPlugin for State {
|
|||
""
|
||||
};
|
||||
|
||||
let colored_elements = color_elements(self.mode_info.style.colors, !supports_arrow_fonts);
|
||||
let superkey = superkey(colored_elements, separator);
|
||||
let ctrl_keys = ctrl_keys(
|
||||
&self.mode_info,
|
||||
cols.saturating_sub(superkey.len),
|
||||
separator,
|
||||
);
|
||||
|
||||
let first_line = format!("{}{}", superkey, ctrl_keys);
|
||||
let first_line = first_line(&self.mode_info, cols, separator);
|
||||
let second_line = self.second_line(cols);
|
||||
|
||||
let background = match self.mode_info.style.colors.theme_hue {
|
||||
|
|
@ -316,7 +257,7 @@ impl State {
|
|||
}
|
||||
} else if active_tab.are_floating_panes_visible {
|
||||
match self.mode_info.mode {
|
||||
InputMode::Normal => floating_panes_are_visible(&self.mode_info.style.colors),
|
||||
InputMode::Normal => floating_panes_are_visible(&self.mode_info),
|
||||
InputMode::Locked => {
|
||||
locked_floating_panes_are_visible(&self.mode_info.style.colors)
|
||||
},
|
||||
|
|
@ -330,3 +271,496 @@ impl State {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a common modifier key from a key vector.
|
||||
///
|
||||
/// Iterates over all keys and returns any found common modifier key. Possible modifiers that will
|
||||
/// be detected are "Ctrl" and "Alt".
|
||||
pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option<String> {
|
||||
let mut modifier = "";
|
||||
let mut new_modifier;
|
||||
for key in keyvec.iter() {
|
||||
match key {
|
||||
Key::Ctrl(_) => new_modifier = "Ctrl",
|
||||
Key::Alt(_) => new_modifier = "Alt",
|
||||
_ => return None,
|
||||
}
|
||||
if modifier.is_empty() {
|
||||
modifier = new_modifier;
|
||||
} else if modifier != new_modifier {
|
||||
// Prefix changed!
|
||||
return None;
|
||||
}
|
||||
}
|
||||
match modifier.is_empty() {
|
||||
true => None,
|
||||
false => Some(modifier.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get key from action pattern(s).
|
||||
///
|
||||
/// This function takes as arguments a `keymap` that is a `Vec<(Key, Vec<Action>)>` and contains
|
||||
/// all keybindings for the current mode and one or more `p` patterns which match a sequence of
|
||||
/// actions to search for. If within the keymap a sequence of actions matching `p` is found, all
|
||||
/// keys that trigger the action pattern are returned as vector of `Vec<Key>`.
|
||||
pub fn action_key(keymap: &[(Key, Vec<Action>)], action: &[Action]) -> Vec<Key> {
|
||||
keymap
|
||||
.iter()
|
||||
.filter_map(|(key, acvec)| {
|
||||
if acvec.as_slice() == action {
|
||||
Some(*key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Key>>()
|
||||
}
|
||||
|
||||
/// Get multiple keys for multiple actions.
|
||||
///
|
||||
/// An extension of [`action_key`] that iterates over all action tuples and collects the results.
|
||||
pub fn action_key_group(keymap: &[(Key, Vec<Action>)], actions: &[&[Action]]) -> Vec<Key> {
|
||||
let mut ret = vec![];
|
||||
for action in actions {
|
||||
ret.extend(action_key(keymap, action));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/// Style a vector of [`Key`]s with the given [`Palette`].
|
||||
///
|
||||
/// Creates a line segment of style `<KEYS>`, with correct theming applied: The brackets have the
|
||||
/// regular text color, the enclosed keys are painted green and bold. If the keys share a common
|
||||
/// modifier (See [`get_common_modifier`]), it is printed in front of the keys, painted green and
|
||||
/// bold, separated with a `+`: `MOD + <KEYS>`.
|
||||
///
|
||||
/// If multiple [`Key`]s are given, the individual keys are separated with a `|` char. This does
|
||||
/// not apply to the following groups of keys which are treated specially and don't have a
|
||||
/// separator between them:
|
||||
///
|
||||
/// - "hjkl"
|
||||
/// - "←↓↑→"
|
||||
/// - "←→"
|
||||
/// - "↓↑"
|
||||
///
|
||||
/// The returned Vector of [`ANSIString`] is suitable for transformation into an [`ANSIStrings`]
|
||||
/// type.
|
||||
pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec<ANSIString<'static>> {
|
||||
// Nothing to do, quit...
|
||||
if keyvec.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let text_color = palette_match!(match palette.theme_hue {
|
||||
ThemeHue::Dark => palette.white,
|
||||
ThemeHue::Light => palette.black,
|
||||
});
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let mut ret = vec![];
|
||||
|
||||
// Prints modifier key
|
||||
let modifier_str = match get_common_modifier(keyvec.iter().collect()) {
|
||||
Some(modifier) => modifier,
|
||||
None => "".to_string(),
|
||||
};
|
||||
let no_modifier = modifier_str.is_empty();
|
||||
let painted_modifier = if modifier_str.is_empty() {
|
||||
Style::new().paint("")
|
||||
} else {
|
||||
Style::new().fg(orange_color).bold().paint(modifier_str)
|
||||
};
|
||||
ret.push(painted_modifier);
|
||||
|
||||
// Prints key group start
|
||||
let group_start_str = if no_modifier { "<" } else { " + <" };
|
||||
ret.push(Style::new().fg(text_color).paint(group_start_str));
|
||||
|
||||
// Prints the keys
|
||||
let key = keyvec
|
||||
.iter()
|
||||
.map(|key| {
|
||||
if no_modifier {
|
||||
format!("{}", key)
|
||||
} else {
|
||||
match key {
|
||||
Key::Ctrl(c) => format!("{}", Key::Char(*c)),
|
||||
Key::Alt(c) => format!("{}", c),
|
||||
_ => format!("{}", key),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// Special handling of some pre-defined keygroups
|
||||
let key_string = key.join("");
|
||||
let key_separator = match &key_string[..] {
|
||||
"hjkl" => "",
|
||||
"←↓↑→" => "",
|
||||
"←→" => "",
|
||||
"↓↑" => "",
|
||||
_ => "|",
|
||||
};
|
||||
|
||||
for (idx, key) in key.iter().enumerate() {
|
||||
if idx > 0 && !key_separator.is_empty() {
|
||||
ret.push(Style::new().fg(text_color).paint(key_separator));
|
||||
}
|
||||
ret.push(Style::new().fg(green_color).bold().paint(key.clone()));
|
||||
}
|
||||
|
||||
let group_end_str = ">";
|
||||
ret.push(Style::new().fg(text_color).paint(group_end_str));
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ansi_term::unstyle;
|
||||
use ansi_term::ANSIStrings;
|
||||
use zellij_tile::prelude::CharOrArrow;
|
||||
use zellij_tile::prelude::Direction;
|
||||
|
||||
fn big_keymap() -> Vec<(Key, Vec<Action>)> {
|
||||
vec![
|
||||
(Key::Char('a'), vec![Action::Quit]),
|
||||
(Key::Ctrl('b'), vec![Action::ScrollUp]),
|
||||
(Key::Ctrl('d'), vec![Action::ScrollDown]),
|
||||
(
|
||||
Key::Alt(CharOrArrow::Char('c')),
|
||||
vec![Action::ScrollDown, Action::SwitchToMode(InputMode::Normal)],
|
||||
),
|
||||
(
|
||||
Key::Char('1'),
|
||||
vec![TO_NORMAL, Action::SwitchToMode(InputMode::Locked)],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_modifier_with_ctrl_keys() {
|
||||
let keyvec = vec![Key::Ctrl('a'), Key::Ctrl('b'), Key::Ctrl('c')];
|
||||
let ret = get_common_modifier(keyvec.iter().collect());
|
||||
assert_eq!(ret, Some("Ctrl".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_modifier_with_alt_keys_chars() {
|
||||
let keyvec = vec![
|
||||
Key::Alt(CharOrArrow::Char('1')),
|
||||
Key::Alt(CharOrArrow::Char('t')),
|
||||
Key::Alt(CharOrArrow::Char('z')),
|
||||
];
|
||||
let ret = get_common_modifier(keyvec.iter().collect());
|
||||
assert_eq!(ret, Some("Alt".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_modifier_with_alt_keys_arrows() {
|
||||
let keyvec = vec![
|
||||
Key::Alt(CharOrArrow::Direction(Direction::Left)),
|
||||
Key::Alt(CharOrArrow::Direction(Direction::Right)),
|
||||
];
|
||||
let ret = get_common_modifier(keyvec.iter().collect());
|
||||
assert_eq!(ret, Some("Alt".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_modifier_with_alt_keys_arrows_and_chars() {
|
||||
let keyvec = vec![
|
||||
Key::Alt(CharOrArrow::Direction(Direction::Left)),
|
||||
Key::Alt(CharOrArrow::Direction(Direction::Right)),
|
||||
Key::Alt(CharOrArrow::Char('t')),
|
||||
Key::Alt(CharOrArrow::Char('z')),
|
||||
];
|
||||
let ret = get_common_modifier(keyvec.iter().collect());
|
||||
assert_eq!(ret, Some("Alt".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_modifier_with_mixed_alt_ctrl_keys() {
|
||||
let keyvec = vec![
|
||||
Key::Alt(CharOrArrow::Direction(Direction::Left)),
|
||||
Key::Alt(CharOrArrow::Char('z')),
|
||||
Key::Ctrl('a'),
|
||||
Key::Ctrl('1'),
|
||||
];
|
||||
let ret = get_common_modifier(keyvec.iter().collect());
|
||||
assert_eq!(ret, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_modifier_with_any_keys() {
|
||||
let keyvec = vec![Key::Backspace, Key::Char('f'), Key::Down];
|
||||
let ret = get_common_modifier(keyvec.iter().collect());
|
||||
assert_eq!(ret, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_modifier_with_ctrl_and_normal_keys() {
|
||||
let keyvec = vec![Key::Ctrl('a'), Key::Char('f'), Key::Down];
|
||||
let ret = get_common_modifier(keyvec.iter().collect());
|
||||
assert_eq!(ret, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_modifier_with_alt_and_normal_keys() {
|
||||
let keyvec = vec![Key::Alt(CharOrArrow::Char('a')), Key::Char('f'), Key::Down];
|
||||
let ret = get_common_modifier(keyvec.iter().collect());
|
||||
assert_eq!(ret, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn action_key_simple_pattern_match_exact() {
|
||||
let keymap = &[(Key::Char('f'), vec![Action::Quit])];
|
||||
let ret = action_key(keymap, &[Action::Quit]);
|
||||
assert_eq!(ret, vec![Key::Char('f')]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn action_key_simple_pattern_match_pattern_too_long() {
|
||||
let keymap = &[(Key::Char('f'), vec![Action::Quit])];
|
||||
let ret = action_key(keymap, &[Action::Quit, Action::ScrollUp]);
|
||||
assert_eq!(ret, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn action_key_simple_pattern_match_pattern_empty() {
|
||||
let keymap = &[(Key::Char('f'), vec![Action::Quit])];
|
||||
let ret = action_key(keymap, &[]);
|
||||
assert_eq!(ret, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn action_key_long_pattern_match_exact() {
|
||||
let keymap = big_keymap();
|
||||
let ret = action_key(&keymap, &[Action::ScrollDown, TO_NORMAL]);
|
||||
assert_eq!(ret, vec![Key::Alt(CharOrArrow::Char('c'))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn action_key_long_pattern_match_too_short() {
|
||||
let keymap = big_keymap();
|
||||
let ret = action_key(&keymap, &[TO_NORMAL]);
|
||||
assert_eq!(ret, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn action_key_group_single_pattern() {
|
||||
let keymap = big_keymap();
|
||||
let ret = action_key_group(&keymap, &[&[Action::Quit]]);
|
||||
assert_eq!(ret, vec![Key::Char('a')]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn action_key_group_two_patterns() {
|
||||
let keymap = big_keymap();
|
||||
let ret = action_key_group(&keymap, &[&[Action::ScrollDown], &[Action::ScrollUp]]);
|
||||
// Mind the order!
|
||||
assert_eq!(ret, vec![Key::Ctrl('d'), Key::Ctrl('b')]);
|
||||
}
|
||||
|
||||
fn get_palette() -> Palette {
|
||||
Palette::default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_only_chars() {
|
||||
let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "<a|b|c>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_special_group_hjkl() {
|
||||
let keyvec = vec![
|
||||
Key::Char('h'),
|
||||
Key::Char('j'),
|
||||
Key::Char('k'),
|
||||
Key::Char('l'),
|
||||
];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "<hjkl>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_special_group_hjkl_broken() {
|
||||
// Sorted the wrong way
|
||||
let keyvec = vec![
|
||||
Key::Char('h'),
|
||||
Key::Char('k'),
|
||||
Key::Char('j'),
|
||||
Key::Char('l'),
|
||||
];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "<h|k|j|l>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_special_group_all_arrows() {
|
||||
let keyvec = vec![
|
||||
Key::Char('←'),
|
||||
Key::Char('↓'),
|
||||
Key::Char('↑'),
|
||||
Key::Char('→'),
|
||||
];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "<←↓↑→>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_special_group_left_right_arrows() {
|
||||
let keyvec = vec![Key::Char('←'), Key::Char('→')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "<←→>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_special_group_down_up_arrows() {
|
||||
let keyvec = vec![Key::Char('↓'), Key::Char('↑')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "<↓↑>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_common_ctrl_modifier_chars() {
|
||||
let keyvec = vec![
|
||||
Key::Ctrl('a'),
|
||||
Key::Ctrl('b'),
|
||||
Key::Ctrl('c'),
|
||||
Key::Ctrl('d'),
|
||||
];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "Ctrl + <a|b|c|d>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_common_alt_modifier_chars() {
|
||||
let keyvec = vec![
|
||||
Key::Alt(CharOrArrow::Char('a')),
|
||||
Key::Alt(CharOrArrow::Char('b')),
|
||||
Key::Alt(CharOrArrow::Char('c')),
|
||||
Key::Alt(CharOrArrow::Char('d')),
|
||||
];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "Alt + <a|b|c|d>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_common_alt_modifier_with_special_group_all_arrows() {
|
||||
let keyvec = vec![
|
||||
Key::Alt(CharOrArrow::Direction(Direction::Left)),
|
||||
Key::Alt(CharOrArrow::Direction(Direction::Down)),
|
||||
Key::Alt(CharOrArrow::Direction(Direction::Up)),
|
||||
Key::Alt(CharOrArrow::Direction(Direction::Right)),
|
||||
];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "Alt + <←↓↑→>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_ctrl_alt_char_mixed() {
|
||||
let keyvec = vec![
|
||||
Key::Alt(CharOrArrow::Char('a')),
|
||||
Key::Ctrl('b'),
|
||||
Key::Char('c'),
|
||||
];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "<Alt+a|Ctrl+b|c>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_unprintables() {
|
||||
let keyvec = vec![
|
||||
Key::Backspace,
|
||||
Key::Char('\n'),
|
||||
Key::Char(' '),
|
||||
Key::Char('\t'),
|
||||
Key::PageDown,
|
||||
Key::Delete,
|
||||
Key::Home,
|
||||
Key::End,
|
||||
Key::Insert,
|
||||
Key::BackTab,
|
||||
Key::Esc,
|
||||
];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(
|
||||
ret,
|
||||
"<BACKSPACE|ENTER|SPACE|TAB|PgDn|DEL|HOME|END|INS|TAB|ESC>".to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_unprintables_with_common_ctrl_modifier() {
|
||||
let keyvec = vec![Key::Ctrl('\n'), Key::Ctrl(' '), Key::Ctrl('\t')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "Ctrl + <ENTER|SPACE|TAB>".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_key_with_modifier_unprintables_with_common_alt_modifier() {
|
||||
let keyvec = vec![
|
||||
Key::Alt(CharOrArrow::Char('\n')),
|
||||
Key::Alt(CharOrArrow::Char(' ')),
|
||||
Key::Alt(CharOrArrow::Char('\t')),
|
||||
];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = style_key_with_modifier(&keyvec, &palette);
|
||||
let ret = unstyle(&ANSIStrings(&ret));
|
||||
|
||||
assert_eq!(ret, "Alt + <ENTER|SPACE|TAB>".to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +1,47 @@
|
|||
use ansi_term::{
|
||||
ANSIStrings,
|
||||
unstyled_len, ANSIString, ANSIStrings,
|
||||
Color::{Fixed, RGB},
|
||||
Style,
|
||||
};
|
||||
use zellij_tile::prelude::actions::Action;
|
||||
use zellij_tile::prelude::*;
|
||||
use zellij_tile_utils::palette_match;
|
||||
|
||||
use crate::{
|
||||
action_key, action_key_group, style_key_with_modifier,
|
||||
tip::{data::TIPS, TipFn},
|
||||
LinePart, MORE_MSG,
|
||||
LinePart, MORE_MSG, TO_NORMAL,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum StatusBarTextColor {
|
||||
White,
|
||||
Green,
|
||||
Orange,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum StatusBarTextBoldness {
|
||||
Bold,
|
||||
NotBold,
|
||||
}
|
||||
|
||||
fn full_length_shortcut(
|
||||
is_first_shortcut: bool,
|
||||
letter: &str,
|
||||
description: &str,
|
||||
key: Vec<Key>,
|
||||
action: &str,
|
||||
palette: Palette,
|
||||
) -> LinePart {
|
||||
let text_color = palette_match!(match palette.theme_hue {
|
||||
ThemeHue::Dark => palette.white,
|
||||
ThemeHue::Light => palette.black,
|
||||
});
|
||||
let green_color = palette_match!(palette.green);
|
||||
let separator = if is_first_shortcut { " " } else { " / " };
|
||||
let separator = Style::new().fg(text_color).paint(separator);
|
||||
let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
|
||||
let shortcut_left_separator = Style::new().fg(text_color).paint("<");
|
||||
let shortcut = Style::new().fg(green_color).bold().paint(letter);
|
||||
let shortcut_right_separator = Style::new().fg(text_color).paint("> ");
|
||||
let description_len = description.chars().count();
|
||||
let description = Style::new().fg(text_color).bold().paint(description);
|
||||
let len = shortcut_len + description_len + separator.chars().count();
|
||||
LinePart {
|
||||
part: ANSIStrings(&[
|
||||
separator,
|
||||
shortcut_left_separator,
|
||||
shortcut,
|
||||
shortcut_right_separator,
|
||||
description,
|
||||
])
|
||||
.to_string(),
|
||||
len,
|
||||
}
|
||||
if key.is_empty() {
|
||||
return LinePart::default();
|
||||
}
|
||||
|
||||
fn first_word_shortcut(
|
||||
is_first_shortcut: bool,
|
||||
letter: &str,
|
||||
description: &str,
|
||||
palette: Palette,
|
||||
) -> LinePart {
|
||||
let text_color = palette_match!(match palette.theme_hue {
|
||||
ThemeHue::Dark => palette.white,
|
||||
ThemeHue::Light => palette.black,
|
||||
});
|
||||
let green_color = palette_match!(palette.green);
|
||||
|
||||
let separator = if is_first_shortcut { " " } else { " / " };
|
||||
let separator = Style::new().fg(text_color).paint(separator);
|
||||
let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
|
||||
let shortcut_left_separator = Style::new().fg(text_color).paint("<");
|
||||
let shortcut = Style::new().fg(green_color).bold().paint(letter);
|
||||
let shortcut_right_separator = Style::new().fg(text_color).paint("> ");
|
||||
let description_first_word = description.split(' ').next().unwrap_or("");
|
||||
let description_first_word_length = description_first_word.chars().count();
|
||||
let description_first_word = Style::new()
|
||||
let mut bits: Vec<ANSIString> = vec![Style::new().fg(text_color).paint(separator)];
|
||||
bits.extend(style_key_with_modifier(&key, &palette));
|
||||
bits.push(
|
||||
Style::new()
|
||||
.fg(text_color)
|
||||
.bold()
|
||||
.paint(description_first_word);
|
||||
let len = shortcut_len + description_first_word_length + separator.chars().count();
|
||||
.paint(format!(" {}", action)),
|
||||
);
|
||||
let part = ANSIStrings(&bits);
|
||||
|
||||
LinePart {
|
||||
part: ANSIStrings(&[
|
||||
separator,
|
||||
shortcut_left_separator,
|
||||
shortcut,
|
||||
shortcut_right_separator,
|
||||
description_first_word,
|
||||
])
|
||||
.to_string(),
|
||||
len,
|
||||
part: part.to_string(),
|
||||
len: unstyled_len(&part),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,168 +59,241 @@ fn locked_interface_indication(palette: Palette) -> LinePart {
|
|||
}
|
||||
}
|
||||
|
||||
fn show_extra_hints(
|
||||
palette: Palette,
|
||||
text_with_style: Vec<(&str, StatusBarTextColor, StatusBarTextBoldness)>,
|
||||
) -> LinePart {
|
||||
use StatusBarTextBoldness::*;
|
||||
use StatusBarTextColor::*;
|
||||
// get the colors
|
||||
let white_color = palette_match!(palette.white);
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
// calculate length of tipp
|
||||
let len = text_with_style
|
||||
.iter()
|
||||
.fold(0, |len_sum, (text, _, _)| len_sum + text.chars().count());
|
||||
// apply the styles defined above
|
||||
let styled_text = text_with_style
|
||||
.into_iter()
|
||||
.map(|(text, color, is_bold)| {
|
||||
let color = match color {
|
||||
White => white_color,
|
||||
Green => green_color,
|
||||
Orange => orange_color,
|
||||
fn add_shortcut(help: &ModeInfo, linepart: &LinePart, text: &str, keys: Vec<Key>) -> LinePart {
|
||||
let shortcut = if linepart.len == 0 {
|
||||
full_length_shortcut(true, keys, text, help.style.colors)
|
||||
} else {
|
||||
full_length_shortcut(false, keys, text, help.style.colors)
|
||||
};
|
||||
match is_bold {
|
||||
Bold => Style::new().fg(color).bold().paint(text),
|
||||
NotBold => Style::new().fg(color).paint(text),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
LinePart {
|
||||
part: ANSIStrings(&styled_text[..]).to_string(),
|
||||
len,
|
||||
}
|
||||
|
||||
let mut new_linepart = LinePart::default();
|
||||
new_linepart.len += linepart.len + shortcut.len;
|
||||
new_linepart.part = format!("{}{}", linepart.part, shortcut);
|
||||
new_linepart
|
||||
}
|
||||
|
||||
/// Creates hints for usage of Pane Mode
|
||||
fn confirm_pane_selection(palette: Palette) -> LinePart {
|
||||
use StatusBarTextBoldness::*;
|
||||
use StatusBarTextColor::*;
|
||||
let text_with_style = [
|
||||
(" / ", White, NotBold),
|
||||
("<ENTER>", Green, Bold),
|
||||
(" Select pane", White, Bold),
|
||||
];
|
||||
show_extra_hints(palette, text_with_style.to_vec())
|
||||
}
|
||||
|
||||
/// Creates hints for usage of Rename Mode in Pane Mode
|
||||
fn select_pane_shortcut(palette: Palette) -> LinePart {
|
||||
use StatusBarTextBoldness::*;
|
||||
use StatusBarTextColor::*;
|
||||
let text_with_style = [
|
||||
(" / ", White, NotBold),
|
||||
("Alt", Orange, Bold),
|
||||
(" + ", White, NotBold),
|
||||
("<", Green, Bold),
|
||||
("[]", Green, Bold),
|
||||
(" or ", White, NotBold),
|
||||
("hjkl", Green, Bold),
|
||||
(">", Green, Bold),
|
||||
(" Select pane", White, Bold),
|
||||
];
|
||||
show_extra_hints(palette, text_with_style.to_vec())
|
||||
}
|
||||
|
||||
fn full_shortcut_list_nonstandard_mode(
|
||||
extra_hint_producing_function: fn(Palette) -> LinePart,
|
||||
) -> impl FnOnce(&ModeInfo) -> LinePart {
|
||||
move |help| {
|
||||
fn full_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart {
|
||||
let mut line_part = LinePart::default();
|
||||
for (i, (letter, description)) in help.keybinds.iter().enumerate() {
|
||||
let shortcut = full_length_shortcut(i == 0, letter, description, help.style.colors);
|
||||
line_part.len += shortcut.len;
|
||||
line_part.part = format!("{}{}", line_part.part, shortcut,);
|
||||
let keys_and_hints = get_keys_and_hints(help);
|
||||
|
||||
for (long, _short, keys) in keys_and_hints.into_iter() {
|
||||
line_part = add_shortcut(help, &line_part, &long, keys.to_vec());
|
||||
}
|
||||
let select_pane_shortcut = extra_hint_producing_function(help.style.colors);
|
||||
line_part.len += select_pane_shortcut.len;
|
||||
line_part.part = format!("{}{}", line_part.part, select_pane_shortcut,);
|
||||
line_part
|
||||
}
|
||||
|
||||
/// Collect all relevant keybindings and hints to display.
|
||||
///
|
||||
/// Creates a vector with tuples containing the following entries:
|
||||
///
|
||||
/// - A String to display for this keybinding when there are no size restrictions,
|
||||
/// - A shortened String (where sensible) to display if the whole second line becomes too long,
|
||||
/// - A `Vec<Key>` of the keys that map to this keyhint
|
||||
///
|
||||
/// This vector is created by iterating over the keybindings for the current [`InputMode`] and
|
||||
/// storing all Keybindings that match pre-defined patterns of `Action`s. For example, the
|
||||
/// `InputMode::Pane` input mode determines which keys to display for the "Move focus" hint by
|
||||
/// searching the keybindings for anything that matches the `Action::MoveFocus(_)` action. Since by
|
||||
/// default multiple keybindings map to some action patterns (e.g. `Action::MoveFocus(_)` is bound
|
||||
/// to "hjkl", the arrow keys and "Alt + <hjkl>"), we deduplicate the vector of all keybindings
|
||||
/// before processing it.
|
||||
///
|
||||
/// Therefore we sort it by the [`Key`]s of the current keymap and deduplicate the resulting sorted
|
||||
/// vector by the `Vec<Action>` action vectors bound to the keys. As such, when multiple keys map
|
||||
/// to the same sequence of actions, the keys that appear first in the [`Key`] structure will be
|
||||
/// displayed.
|
||||
// Please don't let rustfmt play with the formatting. It will stretch out the function to about
|
||||
// three times the length and all the keybinding vectors we generate become virtually unreadable
|
||||
// for humans.
|
||||
#[rustfmt::skip]
|
||||
fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> {
|
||||
use Action as A;
|
||||
use InputMode as IM;
|
||||
use actions::Direction as Dir;
|
||||
use actions::ResizeDirection as RDir;
|
||||
use actions::SearchDirection as SDir;
|
||||
use actions::SearchOption as SOpt;
|
||||
|
||||
let mut old_keymap = mi.get_mode_keybinds();
|
||||
let s = |string: &str| string.to_string();
|
||||
|
||||
// Find a keybinding to get back to "Normal" input mode. In this case we prefer '\n' over other
|
||||
// choices. Do it here before we dedupe the keymap below!
|
||||
let to_normal_keys = action_key(&old_keymap, &[TO_NORMAL]);
|
||||
let to_normal_key = if to_normal_keys.contains(&Key::Char('\n')) {
|
||||
vec![Key::Char('\n')]
|
||||
} else {
|
||||
// Yield `vec![key]` if `to_normal_keys` has at least one key, or an empty vec otherwise.
|
||||
to_normal_keys.into_iter().take(1).collect()
|
||||
};
|
||||
|
||||
// Sort and deduplicate the keybindings first. We sort after the `Key`s, and deduplicate by
|
||||
// their `Action` vectors. An unstable sort is fine here because if the user maps anything to
|
||||
// the same key again, anything will happen...
|
||||
old_keymap.sort_unstable_by(|(keya, _), (keyb, _)| keya.partial_cmp(keyb).unwrap());
|
||||
|
||||
let mut known_actions: Vec<Vec<Action>> = vec![];
|
||||
let mut km = vec![];
|
||||
for (key, acvec) in old_keymap {
|
||||
if known_actions.contains(&acvec) {
|
||||
// This action is known already
|
||||
continue;
|
||||
} else {
|
||||
known_actions.push(acvec.to_vec());
|
||||
km.push((key, acvec));
|
||||
}
|
||||
}
|
||||
|
||||
if mi.mode == IM::Pane { vec![
|
||||
(s("Move focus"), s("Move"),
|
||||
action_key_group(&km, &[&[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)],
|
||||
&[A::MoveFocus(Dir::Up)], &[A::MoveFocus(Dir::Right)]])),
|
||||
(s("New"), s("New"), action_key(&km, &[A::NewPane(None), TO_NORMAL])),
|
||||
(s("Close"), s("Close"), action_key(&km, &[A::CloseFocus, TO_NORMAL])),
|
||||
(s("Rename"), s("Rename"),
|
||||
action_key(&km, &[A::SwitchToMode(IM::RenamePane), A::PaneNameInput(vec![0])])),
|
||||
(s("Split down"), s("Down"), action_key(&km, &[A::NewPane(Some(Dir::Down)), TO_NORMAL])),
|
||||
(s("Split right"), s("Right"), action_key(&km, &[A::NewPane(Some(Dir::Right)), TO_NORMAL])),
|
||||
(s("Fullscreen"), s("Fullscreen"), action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])),
|
||||
(s("Frames"), s("Frames"), action_key(&km, &[A::TogglePaneFrames, TO_NORMAL])),
|
||||
(s("Floating toggle"), s("Floating"),
|
||||
action_key(&km, &[A::ToggleFloatingPanes, TO_NORMAL])),
|
||||
(s("Embed pane"), s("Embed"), action_key(&km, &[A::TogglePaneEmbedOrFloating, TO_NORMAL])),
|
||||
(s("Next"), s("Next"), action_key(&km, &[A::SwitchFocus])),
|
||||
(s("Select pane"), s("Select"), to_normal_key),
|
||||
]} else if mi.mode == IM::Tab {
|
||||
// With the default bindings, "Move focus" for tabs is tricky: It binds all the arrow keys
|
||||
// to moving tabs focus (left/up go left, right/down go right). Since we sort the keys
|
||||
// above and then dedpulicate based on the actions, we will end up with LeftArrow for
|
||||
// "left" and DownArrow for "right". What we really expect is to see LeftArrow and
|
||||
// RightArrow.
|
||||
// FIXME: So for lack of a better idea we just check this case manually here.
|
||||
let old_keymap = mi.get_mode_keybinds();
|
||||
let focus_keys_full: Vec<Key> = action_key_group(&old_keymap,
|
||||
&[&[A::GoToPreviousTab], &[A::GoToNextTab]]);
|
||||
let focus_keys = if focus_keys_full.contains(&Key::Left)
|
||||
&& focus_keys_full.contains(&Key::Right) {
|
||||
vec![Key::Left, Key::Right]
|
||||
} else {
|
||||
action_key_group(&km, &[&[A::GoToPreviousTab], &[A::GoToNextTab]])
|
||||
};
|
||||
|
||||
vec![
|
||||
(s("Move focus"), s("Move"), focus_keys),
|
||||
(s("New"), s("New"), action_key(&km, &[A::NewTab(None), TO_NORMAL])),
|
||||
(s("Close"), s("Close"), action_key(&km, &[A::CloseTab, TO_NORMAL])),
|
||||
(s("Rename"), s("Rename"),
|
||||
action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])),
|
||||
(s("Sync"), s("Sync"), action_key(&km, &[A::ToggleActiveSyncTab, TO_NORMAL])),
|
||||
(s("Toggle"), s("Toggle"), action_key(&km, &[A::ToggleTab])),
|
||||
(s("Select pane"), s("Select"), to_normal_key),
|
||||
]} else if mi.mode == IM::Resize { vec![
|
||||
(s("Resize"), s("Resize"), action_key_group(&km, &[
|
||||
&[A::Resize(RDir::Left)], &[A::Resize(RDir::Down)],
|
||||
&[A::Resize(RDir::Up)], &[A::Resize(RDir::Right)]])),
|
||||
(s("Increase/Decrease size"), s("Increase/Decrease"),
|
||||
action_key_group(&km, &[&[A::Resize(RDir::Increase)], &[A::Resize(RDir::Decrease)]])),
|
||||
(s("Select pane"), s("Select"), to_normal_key),
|
||||
]} else if mi.mode == IM::Move { vec![
|
||||
(s("Move"), s("Move"), action_key_group(&km, &[
|
||||
&[Action::MovePane(Some(Dir::Left))], &[Action::MovePane(Some(Dir::Down))],
|
||||
&[Action::MovePane(Some(Dir::Up))], &[Action::MovePane(Some(Dir::Right))]])),
|
||||
(s("Next pane"), s("Next"), action_key(&km, &[Action::MovePane(None)])),
|
||||
]} else if mi.mode == IM::Scroll { vec![
|
||||
(s("Scroll"), s("Scroll"),
|
||||
action_key_group(&km, &[&[Action::ScrollDown], &[Action::ScrollUp]])),
|
||||
(s("Scroll page"), s("Scroll"),
|
||||
action_key_group(&km, &[&[Action::PageScrollDown], &[Action::PageScrollUp]])),
|
||||
(s("Scroll half page"), s("Scroll"),
|
||||
action_key_group(&km, &[&[Action::HalfPageScrollDown], &[Action::HalfPageScrollUp]])),
|
||||
(s("Edit scrollback in default editor"), s("Edit"),
|
||||
action_key(&km, &[Action::EditScrollback, TO_NORMAL])),
|
||||
(s("Enter search term"), s("Search"),
|
||||
action_key(&km, &[A::SwitchToMode(IM::EnterSearch), A::SearchInput(vec![0])])),
|
||||
(s("Select pane"), s("Select"), to_normal_key),
|
||||
]} else if mi.mode == IM::EnterSearch { vec![
|
||||
(s("When done"), s("Done"), action_key(&km, &[A::SwitchToMode(IM::Search)])),
|
||||
(s("Cancel"), s("Cancel"),
|
||||
action_key(&km, &[A::SearchInput(vec![27]), A::SwitchToMode(IM::Scroll)])),
|
||||
]} else if mi.mode == IM::Search { vec![
|
||||
(s("Scroll"), s("Scroll"),
|
||||
action_key_group(&km, &[&[Action::ScrollDown], &[Action::ScrollUp]])),
|
||||
(s("Scroll page"), s("Scroll"),
|
||||
action_key_group(&km, &[&[Action::PageScrollDown], &[Action::PageScrollUp]])),
|
||||
(s("Scroll half page"), s("Scroll"),
|
||||
action_key_group(&km, &[&[Action::HalfPageScrollDown], &[Action::HalfPageScrollUp]])),
|
||||
(s("Enter term"), s("Search"),
|
||||
action_key(&km, &[A::SwitchToMode(IM::EnterSearch), A::SearchInput(vec![0])])),
|
||||
(s("Search down"), s("Down"), action_key(&km, &[A::Search(SDir::Down)])),
|
||||
(s("Search up"), s("Up"), action_key(&km, &[A::Search(SDir::Up)])),
|
||||
(s("Case sensitive"), s("Case"),
|
||||
action_key(&km, &[A::SearchToggleOption(SOpt::CaseSensitivity)])),
|
||||
(s("Wrap"), s("Wrap"),
|
||||
action_key(&km, &[A::SearchToggleOption(SOpt::Wrap)])),
|
||||
(s("Whole words"), s("Whole"),
|
||||
action_key(&km, &[A::SearchToggleOption(SOpt::WholeWord)])),
|
||||
]} else if mi.mode == IM::Session { vec![
|
||||
(s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])),
|
||||
(s("Select pane"), s("Select"), to_normal_key),
|
||||
]} else if mi.mode == IM::Tmux { vec![
|
||||
(s("Move focus"), s("Move"), action_key_group(&km, &[
|
||||
&[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)],
|
||||
&[A::MoveFocus(Dir::Up)], &[A::MoveFocus(Dir::Right)]])),
|
||||
(s("Split down"), s("Down"), action_key(&km, &[A::NewPane(Some(Dir::Down)), TO_NORMAL])),
|
||||
(s("Split right"), s("Right"), action_key(&km, &[A::NewPane(Some(Dir::Right)), TO_NORMAL])),
|
||||
(s("Fullscreen"), s("Fullscreen"), action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])),
|
||||
(s("New tab"), s("New"), action_key(&km, &[A::NewTab(None), TO_NORMAL])),
|
||||
(s("Rename tab"), s("Rename"),
|
||||
action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])),
|
||||
(s("Previous Tab"), s("Previous"), action_key(&km, &[A::GoToPreviousTab, TO_NORMAL])),
|
||||
(s("Next Tab"), s("Next"), action_key(&km, &[A::GoToNextTab, TO_NORMAL])),
|
||||
(s("Select pane"), s("Select"), to_normal_key),
|
||||
]} else if matches!(mi.mode, IM::RenamePane | IM::RenameTab) { vec![
|
||||
(s("When done"), s("Done"), to_normal_key),
|
||||
(s("Select pane"), s("Select"), action_key_group(&km, &[
|
||||
&[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)],
|
||||
&[A::MoveFocus(Dir::Up)], &[A::MoveFocus(Dir::Right)]])),
|
||||
]} else { vec![] }
|
||||
}
|
||||
|
||||
fn full_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart {
|
||||
match help.mode {
|
||||
InputMode::Normal => tip(help.style.colors),
|
||||
InputMode::Normal => tip(help),
|
||||
InputMode::Locked => locked_interface_indication(help.style.colors),
|
||||
InputMode::Tmux => full_tmux_mode_indication(help),
|
||||
InputMode::RenamePane => full_shortcut_list_nonstandard_mode(select_pane_shortcut)(help),
|
||||
InputMode::EnterSearch => full_shortcut_list_nonstandard_mode(select_pane_shortcut)(help),
|
||||
_ => full_shortcut_list_nonstandard_mode(confirm_pane_selection)(help),
|
||||
_ => full_shortcut_list_nonstandard_mode(help),
|
||||
}
|
||||
}
|
||||
|
||||
fn shortened_shortcut_list_nonstandard_mode(
|
||||
extra_hint_producing_function: fn(Palette) -> LinePart,
|
||||
) -> impl FnOnce(&ModeInfo) -> LinePart {
|
||||
move |help| {
|
||||
fn shortened_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart {
|
||||
let mut line_part = LinePart::default();
|
||||
for (i, (letter, description)) in help.keybinds.iter().enumerate() {
|
||||
let shortcut = first_word_shortcut(i == 0, letter, description, help.style.colors);
|
||||
line_part.len += shortcut.len;
|
||||
line_part.part = format!("{}{}", line_part.part, shortcut,);
|
||||
let keys_and_hints = get_keys_and_hints(help);
|
||||
|
||||
for (_, short, keys) in keys_and_hints.into_iter() {
|
||||
line_part = add_shortcut(help, &line_part, &short, keys.to_vec());
|
||||
}
|
||||
let select_pane_shortcut = extra_hint_producing_function(help.style.colors);
|
||||
line_part.len += select_pane_shortcut.len;
|
||||
line_part.part = format!("{}{}", line_part.part, select_pane_shortcut,);
|
||||
line_part
|
||||
}
|
||||
}
|
||||
|
||||
fn shortened_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart {
|
||||
match help.mode {
|
||||
InputMode::Normal => tip(help.style.colors),
|
||||
InputMode::Normal => tip(help),
|
||||
InputMode::Locked => locked_interface_indication(help.style.colors),
|
||||
InputMode::Tmux => short_tmux_mode_indication(help),
|
||||
InputMode::RenamePane => {
|
||||
shortened_shortcut_list_nonstandard_mode(select_pane_shortcut)(help)
|
||||
},
|
||||
InputMode::EnterSearch => {
|
||||
shortened_shortcut_list_nonstandard_mode(select_pane_shortcut)(help)
|
||||
},
|
||||
_ => shortened_shortcut_list_nonstandard_mode(confirm_pane_selection)(help),
|
||||
_ => shortened_shortcut_list_nonstandard_mode(help),
|
||||
}
|
||||
}
|
||||
|
||||
fn best_effort_shortcut_list_nonstandard_mode(
|
||||
extra_hint_producing_function: fn(Palette) -> LinePart,
|
||||
) -> impl FnOnce(&ModeInfo, usize) -> LinePart {
|
||||
move |help, max_len| {
|
||||
fn best_effort_shortcut_list_nonstandard_mode(help: &ModeInfo, max_len: usize) -> LinePart {
|
||||
let mut line_part = LinePart::default();
|
||||
for (i, (letter, description)) in help.keybinds.iter().enumerate() {
|
||||
let shortcut = first_word_shortcut(i == 0, letter, description, help.style.colors);
|
||||
if line_part.len + shortcut.len + MORE_MSG.chars().count() > max_len {
|
||||
// TODO: better
|
||||
line_part.part = format!("{}{}", line_part.part, MORE_MSG);
|
||||
line_part.len += MORE_MSG.chars().count();
|
||||
break;
|
||||
}
|
||||
line_part.len += shortcut.len;
|
||||
line_part.part = format!("{}{}", line_part.part, shortcut);
|
||||
}
|
||||
let select_pane_shortcut = extra_hint_producing_function(help.style.colors);
|
||||
if line_part.len + select_pane_shortcut.len <= max_len {
|
||||
line_part.len += select_pane_shortcut.len;
|
||||
line_part.part = format!("{}{}", line_part.part, select_pane_shortcut,);
|
||||
}
|
||||
line_part
|
||||
}
|
||||
}
|
||||
let keys_and_hints = get_keys_and_hints(help);
|
||||
|
||||
fn best_effort_tmux_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart {
|
||||
let mut line_part = tmux_mode_indication(help);
|
||||
for (i, (letter, description)) in help.keybinds.iter().enumerate() {
|
||||
let shortcut = first_word_shortcut(i == 0, letter, description, help.style.colors);
|
||||
if line_part.len + shortcut.len + MORE_MSG.chars().count() > max_len {
|
||||
// TODO: better
|
||||
for (_, short, keys) in keys_and_hints.into_iter() {
|
||||
let new_line_part = add_shortcut(help, &line_part, &short, keys.to_vec());
|
||||
if new_line_part.len + MORE_MSG.chars().count() > max_len {
|
||||
line_part.part = format!("{}{}", line_part.part, MORE_MSG);
|
||||
line_part.len += MORE_MSG.chars().count();
|
||||
break;
|
||||
}
|
||||
line_part.len += shortcut.len;
|
||||
line_part.part = format!("{}{}", line_part.part, shortcut);
|
||||
line_part = new_line_part;
|
||||
}
|
||||
line_part
|
||||
}
|
||||
|
|
@ -277,7 +301,7 @@ fn best_effort_tmux_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart {
|
|||
fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> LinePart {
|
||||
match help.mode {
|
||||
InputMode::Normal => {
|
||||
let line_part = tip(help.style.colors);
|
||||
let line_part = tip(help);
|
||||
if line_part.len <= max_len {
|
||||
line_part
|
||||
} else {
|
||||
|
|
@ -292,11 +316,7 @@ fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> Lin
|
|||
LinePart::default()
|
||||
}
|
||||
},
|
||||
InputMode::Tmux => best_effort_tmux_shortcut_list(help, max_len),
|
||||
InputMode::RenamePane => {
|
||||
best_effort_shortcut_list_nonstandard_mode(select_pane_shortcut)(help, max_len)
|
||||
},
|
||||
_ => best_effort_shortcut_list_nonstandard_mode(confirm_pane_selection)(help, max_len),
|
||||
_ => best_effort_shortcut_list_nonstandard_mode(help, max_len),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -374,7 +394,9 @@ pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> Line
|
|||
}
|
||||
}
|
||||
|
||||
pub fn floating_panes_are_visible(palette: &Palette) -> LinePart {
|
||||
pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart {
|
||||
let palette = mode_info.style.colors;
|
||||
let km = &mode_info.get_mode_keybinds();
|
||||
let white_color = match palette.white {
|
||||
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
||||
PaletteColor::EightBit(color) => Fixed(color),
|
||||
|
|
@ -391,16 +413,29 @@ pub fn floating_panes_are_visible(palette: &Palette) -> LinePart {
|
|||
let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): ");
|
||||
let floating_panes = "FLOATING PANES VISIBLE";
|
||||
let press = "Press ";
|
||||
let ctrl = "Ctrl-p ";
|
||||
let plus = "+ ";
|
||||
let pane_mode = format!(
|
||||
"{}",
|
||||
action_key(km, &[Action::SwitchToMode(InputMode::Pane)])
|
||||
.first()
|
||||
.unwrap_or(&Key::Char('?'))
|
||||
);
|
||||
let plus = ", ";
|
||||
let p_left_separator = "<";
|
||||
let p = "w";
|
||||
let p = format!(
|
||||
"{}",
|
||||
action_key(
|
||||
&mode_info.get_keybinds_for_mode(InputMode::Pane),
|
||||
&[Action::ToggleFloatingPanes, TO_NORMAL]
|
||||
)
|
||||
.first()
|
||||
.unwrap_or(&Key::Char('?'))
|
||||
);
|
||||
let p_right_separator = "> ";
|
||||
let to_hide = "to hide.";
|
||||
|
||||
let len = floating_panes.chars().count()
|
||||
+ press.chars().count()
|
||||
+ ctrl.chars().count()
|
||||
+ pane_mode.chars().count()
|
||||
+ plus.chars().count()
|
||||
+ p_left_separator.chars().count()
|
||||
+ p.chars().count()
|
||||
|
|
@ -414,7 +449,7 @@ pub fn floating_panes_are_visible(palette: &Palette) -> LinePart {
|
|||
Style::new().fg(orange_color).bold().paint(floating_panes),
|
||||
shortcut_right_separator,
|
||||
Style::new().fg(white_color).bold().paint(press),
|
||||
Style::new().fg(green_color).bold().paint(ctrl),
|
||||
Style::new().fg(green_color).bold().paint(pane_mode),
|
||||
Style::new().fg(white_color).bold().paint(plus),
|
||||
Style::new().fg(white_color).bold().paint(p_left_separator),
|
||||
Style::new().fg(green_color).bold().paint(p),
|
||||
|
|
@ -425,90 +460,6 @@ pub fn floating_panes_are_visible(palette: &Palette) -> LinePart {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tmux_mode_indication(help: &ModeInfo) -> LinePart {
|
||||
let white_color = match help.style.colors.white {
|
||||
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
||||
PaletteColor::EightBit(color) => Fixed(color),
|
||||
};
|
||||
let orange_color = match help.style.colors.orange {
|
||||
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
||||
PaletteColor::EightBit(color) => Fixed(color),
|
||||
};
|
||||
|
||||
let shortcut_left_separator = Style::new().fg(white_color).bold().paint(" (");
|
||||
let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): ");
|
||||
let tmux_mode_text = "TMUX MODE";
|
||||
let tmux_mode_indicator = Style::new().fg(orange_color).bold().paint(tmux_mode_text);
|
||||
let line_part = LinePart {
|
||||
part: format!(
|
||||
"{}{}{}",
|
||||
shortcut_left_separator, tmux_mode_indicator, shortcut_right_separator
|
||||
),
|
||||
len: tmux_mode_text.chars().count() + 5, // 2 for the separators, 3 for the colon and following space
|
||||
};
|
||||
line_part
|
||||
}
|
||||
|
||||
pub fn full_tmux_mode_indication(help: &ModeInfo) -> LinePart {
|
||||
let white_color = match help.style.colors.white {
|
||||
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
||||
PaletteColor::EightBit(color) => Fixed(color),
|
||||
};
|
||||
let orange_color = match help.style.colors.orange {
|
||||
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
||||
PaletteColor::EightBit(color) => Fixed(color),
|
||||
};
|
||||
|
||||
let shortcut_left_separator = Style::new().fg(white_color).bold().paint(" (");
|
||||
let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): ");
|
||||
let tmux_mode_text = "TMUX MODE";
|
||||
let tmux_mode_indicator = Style::new().fg(orange_color).bold().paint(tmux_mode_text);
|
||||
let mut line_part = LinePart {
|
||||
part: format!(
|
||||
"{}{}{}",
|
||||
shortcut_left_separator, tmux_mode_indicator, shortcut_right_separator
|
||||
),
|
||||
len: tmux_mode_text.chars().count() + 5, // 2 for the separators, 3 for the colon and following space
|
||||
};
|
||||
|
||||
for (i, (letter, description)) in help.keybinds.iter().enumerate() {
|
||||
let shortcut = full_length_shortcut(i == 0, letter, description, help.style.colors);
|
||||
line_part.len += shortcut.len;
|
||||
line_part.part = format!("{}{}", line_part.part, shortcut,);
|
||||
}
|
||||
line_part
|
||||
}
|
||||
|
||||
pub fn short_tmux_mode_indication(help: &ModeInfo) -> LinePart {
|
||||
let white_color = match help.style.colors.white {
|
||||
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
||||
PaletteColor::EightBit(color) => Fixed(color),
|
||||
};
|
||||
let orange_color = match help.style.colors.orange {
|
||||
PaletteColor::Rgb((r, g, b)) => RGB(r, g, b),
|
||||
PaletteColor::EightBit(color) => Fixed(color),
|
||||
};
|
||||
|
||||
let shortcut_left_separator = Style::new().fg(white_color).bold().paint(" (");
|
||||
let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): ");
|
||||
let tmux_mode_text = "TMUX MODE";
|
||||
let tmux_mode_indicator = Style::new().fg(orange_color).bold().paint(tmux_mode_text);
|
||||
let mut line_part = LinePart {
|
||||
part: format!(
|
||||
"{}{}{}",
|
||||
shortcut_left_separator, tmux_mode_indicator, shortcut_right_separator
|
||||
),
|
||||
len: tmux_mode_text.chars().count() + 5, // 2 for the separators, 3 for the colon and following space
|
||||
};
|
||||
|
||||
for (i, (letter, description)) in help.keybinds.iter().enumerate() {
|
||||
let shortcut = first_word_shortcut(i == 0, letter, description, help.style.colors);
|
||||
line_part.len += shortcut.len;
|
||||
line_part.part = format!("{}{}", line_part.part, shortcut);
|
||||
}
|
||||
line_part
|
||||
}
|
||||
|
||||
pub fn locked_fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> LinePart {
|
||||
let text_color = palette_match!(match palette.theme_hue {
|
||||
ThemeHue::Dark => palette.white,
|
||||
|
|
@ -570,3 +521,244 @@ pub fn locked_floating_panes_are_visible(palette: &Palette) -> LinePart {
|
|||
len,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Unit tests.
|
||||
///
|
||||
/// Note that we cheat a little here, because the number of things one may want to test is endless,
|
||||
/// and creating a Mockup of [`ModeInfo`] by hand for all these testcases is nothing less than
|
||||
/// torture. Hence, we test the most atomic unit thoroughly ([`full_length_shortcut`] and then test
|
||||
/// the public API ([`keybinds`]) to ensure correct operation.
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Strip style information from `LinePart` and return a raw String instead
|
||||
fn unstyle(line_part: LinePart) -> String {
|
||||
let string = line_part.to_string();
|
||||
|
||||
let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap();
|
||||
let string = re.replace_all(&string, "".to_string());
|
||||
|
||||
string.to_string()
|
||||
}
|
||||
|
||||
fn get_palette() -> Palette {
|
||||
Palette::default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_length_shortcut_with_key() {
|
||||
let keyvec = vec![Key::Char('a')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " / <a> Foobar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_length_shortcut_with_key_first_element() {
|
||||
let keyvec = vec![Key::Char('a')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = full_length_shortcut(true, keyvec, "Foobar", palette);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " <a> Foobar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// When there is no binding, we print no shortcut either
|
||||
fn full_length_shortcut_without_key() {
|
||||
let keyvec = vec![];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_length_shortcut_with_key_unprintable_1() {
|
||||
let keyvec = vec![Key::Char('\n')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " / <ENTER> Foobar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_length_shortcut_with_key_unprintable_2() {
|
||||
let keyvec = vec![Key::Backspace];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " / <BACKSPACE> Foobar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_length_shortcut_with_ctrl_key() {
|
||||
let keyvec = vec![Key::Ctrl('a')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " / Ctrl + <a> Foobar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_length_shortcut_with_alt_key() {
|
||||
let keyvec = vec![Key::Alt(CharOrArrow::Char('a'))];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " / Alt + <a> Foobar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_length_shortcut_with_homogenous_key_group() {
|
||||
let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " / <a|b|c> Foobar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_length_shortcut_with_heterogenous_key_group() {
|
||||
let keyvec = vec![Key::Char('a'), Key::Ctrl('b'), Key::Char('\n')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " / <a|Ctrl+b|ENTER> Foobar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_length_shortcut_with_key_group_shared_ctrl_modifier() {
|
||||
let keyvec = vec![Key::Ctrl('a'), Key::Ctrl('b'), Key::Ctrl('c')];
|
||||
let palette = get_palette();
|
||||
|
||||
let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " / Ctrl + <a|b|c> Foobar");
|
||||
}
|
||||
//pub fn keybinds(help: &ModeInfo, tip_name: &str, max_width: usize) -> LinePart {
|
||||
|
||||
#[test]
|
||||
// Note how it leaves out elements that don't exist!
|
||||
fn keybinds_wide() {
|
||||
let mode_info = ModeInfo {
|
||||
mode: InputMode::Pane,
|
||||
keybinds: vec![(
|
||||
InputMode::Pane,
|
||||
vec![
|
||||
(Key::Left, vec![Action::MoveFocus(actions::Direction::Left)]),
|
||||
(Key::Down, vec![Action::MoveFocus(actions::Direction::Down)]),
|
||||
(Key::Up, vec![Action::MoveFocus(actions::Direction::Up)]),
|
||||
(
|
||||
Key::Right,
|
||||
vec![Action::MoveFocus(actions::Direction::Right)],
|
||||
),
|
||||
(Key::Char('n'), vec![Action::NewPane(None), TO_NORMAL]),
|
||||
(Key::Char('x'), vec![Action::CloseFocus, TO_NORMAL]),
|
||||
(
|
||||
Key::Char('f'),
|
||||
vec![Action::ToggleFocusFullscreen, TO_NORMAL],
|
||||
),
|
||||
],
|
||||
)],
|
||||
..ModeInfo::default()
|
||||
};
|
||||
|
||||
let ret = keybinds(&mode_info, "quicknav", 500);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(
|
||||
ret,
|
||||
" <←↓↑→> Move focus / <n> New / <x> Close / <f> Fullscreen"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Note how "Move focus" becomes "Move"
|
||||
fn keybinds_tight_width() {
|
||||
let mode_info = ModeInfo {
|
||||
mode: InputMode::Pane,
|
||||
keybinds: vec![(
|
||||
InputMode::Pane,
|
||||
vec![
|
||||
(Key::Left, vec![Action::MoveFocus(actions::Direction::Left)]),
|
||||
(Key::Down, vec![Action::MoveFocus(actions::Direction::Down)]),
|
||||
(Key::Up, vec![Action::MoveFocus(actions::Direction::Up)]),
|
||||
(
|
||||
Key::Right,
|
||||
vec![Action::MoveFocus(actions::Direction::Right)],
|
||||
),
|
||||
(Key::Char('n'), vec![Action::NewPane(None), TO_NORMAL]),
|
||||
(Key::Char('x'), vec![Action::CloseFocus, TO_NORMAL]),
|
||||
(
|
||||
Key::Char('f'),
|
||||
vec![Action::ToggleFocusFullscreen, TO_NORMAL],
|
||||
),
|
||||
],
|
||||
)],
|
||||
..ModeInfo::default()
|
||||
};
|
||||
|
||||
let ret = keybinds(&mode_info, "quicknav", 35);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " <←↓↑→> Move / <n> New ... ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keybinds_wide_weird_keys() {
|
||||
let mode_info = ModeInfo {
|
||||
mode: InputMode::Pane,
|
||||
keybinds: vec![(
|
||||
InputMode::Pane,
|
||||
vec![
|
||||
(
|
||||
Key::Ctrl('a'),
|
||||
vec![Action::MoveFocus(actions::Direction::Left)],
|
||||
),
|
||||
(
|
||||
Key::Ctrl('\n'),
|
||||
vec![Action::MoveFocus(actions::Direction::Down)],
|
||||
),
|
||||
(
|
||||
Key::Ctrl('1'),
|
||||
vec![Action::MoveFocus(actions::Direction::Up)],
|
||||
),
|
||||
(
|
||||
Key::Ctrl(' '),
|
||||
vec![Action::MoveFocus(actions::Direction::Right)],
|
||||
),
|
||||
(Key::Backspace, vec![Action::NewPane(None), TO_NORMAL]),
|
||||
(Key::Esc, vec![Action::CloseFocus, TO_NORMAL]),
|
||||
(Key::End, vec![Action::ToggleFocusFullscreen, TO_NORMAL]),
|
||||
],
|
||||
)],
|
||||
..ModeInfo::default()
|
||||
};
|
||||
|
||||
let ret = keybinds(&mode_info, "quicknav", 500);
|
||||
let ret = unstyle(ret);
|
||||
|
||||
assert_eq!(ret, " Ctrl + <a|ENTER|1|SPACE> Move focus / <BACKSPACE> New / <ESC> Close / <END> Fullscreen");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ use ansi_term::{
|
|||
};
|
||||
|
||||
use crate::LinePart;
|
||||
use zellij_tile::prelude::*;
|
||||
use crate::{action_key, style_key_with_modifier};
|
||||
use zellij_tile::prelude::{actions::Action, *};
|
||||
use zellij_tile_utils::palette_match;
|
||||
|
||||
macro_rules! strings {
|
||||
($ANSIStrings:expr) => {{
|
||||
let strings: &[ANSIString<'static>] = $ANSIStrings;
|
||||
let strings: &[ANSIString] = $ANSIStrings;
|
||||
|
||||
let ansi_strings = ANSIStrings(strings);
|
||||
|
||||
|
|
@ -21,13 +22,12 @@ macro_rules! strings {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn compact_layout_full(palette: Palette) -> LinePart {
|
||||
pub fn compact_layout_full(help: &ModeInfo) -> LinePart {
|
||||
// Tip: UI taking up too much space? Start Zellij with
|
||||
// zellij -l compact or remove pane frames with Ctrl + <p> + <z>
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().paint("UI taking up too much space? Start Zellij with "),
|
||||
Style::new()
|
||||
|
|
@ -35,21 +35,17 @@ pub fn compact_layout_full(palette: Palette) -> LinePart {
|
|||
.bold()
|
||||
.paint("zellij -l compact"),
|
||||
Style::new().paint(" or remove pane frames with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<p>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<z>"),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn compact_layout_medium(palette: Palette) -> LinePart {
|
||||
pub fn compact_layout_medium(help: &ModeInfo) -> LinePart {
|
||||
// Tip: To save screen space, start Zellij with
|
||||
// zellij -l compact or remove pane frames with Ctrl + <p> + <z>
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().paint("To save screen space, start Zellij with "),
|
||||
Style::new()
|
||||
|
|
@ -57,31 +53,48 @@ pub fn compact_layout_medium(palette: Palette) -> LinePart {
|
|||
.bold()
|
||||
.paint("zellij -l compact"),
|
||||
Style::new().paint(" or remove frames with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<p>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<z>"),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn compact_layout_short(palette: Palette) -> LinePart {
|
||||
pub fn compact_layout_short(help: &ModeInfo) -> LinePart {
|
||||
// Save screen space, start Zellij with
|
||||
// zellij -l compact or remove pane frames with Ctrl + <p> + <z>
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Save screen space, start with: "),
|
||||
Style::new()
|
||||
.fg(green_color)
|
||||
.bold()
|
||||
.paint("zellij -l compact"),
|
||||
Style::new().paint(" or remove frames with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<p>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<z>"),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
fn add_keybinds(help: &ModeInfo) -> Vec<ANSIString> {
|
||||
let to_pane = action_key(
|
||||
&help.get_mode_keybinds(),
|
||||
&[Action::SwitchToMode(InputMode::Pane)],
|
||||
);
|
||||
let pane_frames = action_key(
|
||||
&help.get_keybinds_for_mode(InputMode::Pane),
|
||||
&[
|
||||
Action::TogglePaneFrames,
|
||||
Action::SwitchToMode(InputMode::Normal),
|
||||
],
|
||||
);
|
||||
|
||||
if pane_frames.is_empty() {
|
||||
return vec![Style::new().bold().paint("UNBOUND")];
|
||||
}
|
||||
|
||||
let mut bits = vec![];
|
||||
bits.extend(style_key_with_modifier(&to_pane, &help.style.colors));
|
||||
bits.push(Style::new().paint(", "));
|
||||
bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors));
|
||||
bits
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ use ansi_term::{
|
|||
Style,
|
||||
};
|
||||
|
||||
use crate::LinePart;
|
||||
use zellij_tile::prelude::*;
|
||||
use crate::{action_key, style_key_with_modifier, LinePart};
|
||||
use zellij_tile::prelude::{actions::Action, *};
|
||||
use zellij_tile_utils::palette_match;
|
||||
|
||||
macro_rules! strings {
|
||||
($ANSIStrings:expr) => {{
|
||||
let strings: &[ANSIString<'static>] = $ANSIStrings;
|
||||
let strings: &[ANSIString] = $ANSIStrings;
|
||||
|
||||
let ansi_strings = ANSIStrings(strings);
|
||||
|
||||
|
|
@ -21,58 +21,70 @@ macro_rules! strings {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn edit_scrollbuffer_full(palette: Palette) -> LinePart {
|
||||
pub fn edit_scrollbuffer_full(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Search through the scrollbuffer using your default $EDITOR with
|
||||
// Ctrl + <s> + <e>
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().paint("Search through the scrollbuffer using your default "),
|
||||
Style::new().fg(green_color).bold().paint("$EDITOR"),
|
||||
Style::new().paint(" with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<s>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<e>"),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn edit_scrollbuffer_medium(palette: Palette) -> LinePart {
|
||||
pub fn edit_scrollbuffer_medium(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Search the scrollbuffer using your $EDITOR with
|
||||
// Ctrl + <s> + <e>
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().paint("Search the scrollbuffer using your "),
|
||||
Style::new().fg(green_color).bold().paint("$EDITOR"),
|
||||
Style::new().paint(" with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<s>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<e>"),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn edit_scrollbuffer_short(palette: Palette) -> LinePart {
|
||||
pub fn edit_scrollbuffer_short(help: &ModeInfo) -> LinePart {
|
||||
// Search using $EDITOR with
|
||||
// Ctrl + <s> + <e>
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Search using "),
|
||||
Style::new().fg(green_color).bold().paint("$EDITOR"),
|
||||
Style::new().paint(" with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<s>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<e>"),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
fn add_keybinds(help: &ModeInfo) -> Vec<ANSIString> {
|
||||
let to_pane = action_key(
|
||||
&help.get_mode_keybinds(),
|
||||
&[Action::SwitchToMode(InputMode::Scroll)],
|
||||
);
|
||||
let edit_buffer = action_key(
|
||||
&help.get_keybinds_for_mode(InputMode::Scroll),
|
||||
&[
|
||||
Action::EditScrollback,
|
||||
Action::SwitchToMode(InputMode::Normal),
|
||||
],
|
||||
);
|
||||
|
||||
if edit_buffer.is_empty() {
|
||||
return vec![Style::new().bold().paint("UNBOUND")];
|
||||
}
|
||||
|
||||
let mut bits = vec![];
|
||||
bits.extend(style_key_with_modifier(&to_pane, &help.style.colors));
|
||||
bits.push(Style::new().paint(", "));
|
||||
bits.extend(style_key_with_modifier(&edit_buffer, &help.style.colors));
|
||||
bits
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
use ansi_term::{
|
||||
unstyled_len, ANSIString, ANSIStrings,
|
||||
Color::{Fixed, RGB},
|
||||
Style,
|
||||
};
|
||||
use ansi_term::{unstyled_len, ANSIString, ANSIStrings, Style};
|
||||
|
||||
use crate::LinePart;
|
||||
use zellij_tile::prelude::*;
|
||||
use zellij_tile_utils::palette_match;
|
||||
use crate::{action_key, style_key_with_modifier, LinePart};
|
||||
use zellij_tile::prelude::{actions::Action, *};
|
||||
|
||||
macro_rules! strings {
|
||||
($ANSIStrings:expr) => {{
|
||||
let strings: &[ANSIString<'static>] = $ANSIStrings;
|
||||
let strings: &[ANSIString] = $ANSIStrings;
|
||||
|
||||
let ansi_strings = ANSIStrings(strings);
|
||||
|
||||
|
|
@ -21,49 +16,57 @@ macro_rules! strings {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn floating_panes_mouse_full(palette: Palette) -> LinePart {
|
||||
pub fn floating_panes_mouse_full(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Toggle floating panes with Ctrl + <p> + <w> and move them with keyboard or mouse
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().paint("Toggle floating panes with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<p>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<w>"),
|
||||
Style::new().paint(" and move them with keyboard or mouse"),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
bits.push(Style::new().paint(" and move them with keyboard or mouse"));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn floating_panes_mouse_medium(palette: Palette) -> LinePart {
|
||||
pub fn floating_panes_mouse_medium(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Toggle floating panes with Ctrl + <p> + <w>
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().paint("Toggle floating panes with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<p>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<w>"),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn floating_panes_mouse_short(palette: Palette) -> LinePart {
|
||||
pub fn floating_panes_mouse_short(help: &ModeInfo) -> LinePart {
|
||||
// Ctrl + <p> + <w> => floating panes
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
|
||||
strings!(&[
|
||||
Style::new().fg(orange_color).bold().paint(" Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<p>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<w>"),
|
||||
Style::new().paint(" => floating panes"),
|
||||
])
|
||||
let mut bits = add_keybinds(help);
|
||||
bits.push(Style::new().paint(" => floating panes"));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
fn add_keybinds(help: &ModeInfo) -> Vec<ANSIString> {
|
||||
let to_pane = action_key(
|
||||
&help.get_mode_keybinds(),
|
||||
&[Action::SwitchToMode(InputMode::Pane)],
|
||||
);
|
||||
let floating_toggle = action_key(
|
||||
&help.get_keybinds_for_mode(InputMode::Pane),
|
||||
&[
|
||||
Action::ToggleFloatingPanes,
|
||||
Action::SwitchToMode(InputMode::Normal),
|
||||
],
|
||||
);
|
||||
|
||||
if floating_toggle.is_empty() {
|
||||
return vec![Style::new().bold().paint("UNBOUND")];
|
||||
}
|
||||
|
||||
let mut bits = vec![];
|
||||
bits.extend(style_key_with_modifier(&to_pane, &help.style.colors));
|
||||
bits.push(Style::new().paint(", "));
|
||||
bits.extend(style_key_with_modifier(
|
||||
&floating_toggle,
|
||||
&help.style.colors,
|
||||
));
|
||||
bits
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
use ansi_term::{
|
||||
unstyled_len, ANSIString, ANSIStrings,
|
||||
Color::{Fixed, RGB},
|
||||
Style,
|
||||
};
|
||||
use ansi_term::{unstyled_len, ANSIString, ANSIStrings, Style};
|
||||
|
||||
use crate::LinePart;
|
||||
use zellij_tile::prelude::*;
|
||||
use zellij_tile_utils::palette_match;
|
||||
use crate::{action_key_group, style_key_with_modifier, LinePart};
|
||||
use zellij_tile::prelude::{
|
||||
actions::{Action, Direction},
|
||||
*,
|
||||
};
|
||||
|
||||
macro_rules! strings {
|
||||
($ANSIStrings:expr) => {{
|
||||
let strings: &[ANSIString<'static>] = $ANSIStrings;
|
||||
let strings: &[ANSIString] = $ANSIStrings;
|
||||
|
||||
let ansi_strings = ANSIStrings(strings);
|
||||
|
||||
|
|
@ -21,44 +19,71 @@ macro_rules! strings {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn move_focus_hjkl_tab_switch_full(palette: Palette) -> LinePart {
|
||||
pub fn move_focus_hjkl_tab_switch_full(help: &ModeInfo) -> LinePart {
|
||||
// Tip: When changing focus with Alt + <←↓↑→> moving off screen left/right focuses the next tab.
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().paint("When changing focus with "),
|
||||
Style::new().fg(orange_color).bold().paint("Alt"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<←↓↑→>"),
|
||||
Style::new().paint(" moving off screen left/right focuses the next tab."),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
bits.push(Style::new().paint(" moving off screen left/right focuses the next tab."));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn move_focus_hjkl_tab_switch_medium(palette: Palette) -> LinePart {
|
||||
pub fn move_focus_hjkl_tab_switch_medium(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Changing focus with Alt + <←↓↑→> off screen focuses the next tab.
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().paint("Changing focus with "),
|
||||
Style::new().fg(orange_color).bold().paint("Alt"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<←↓↑→>"),
|
||||
Style::new().paint(" off screen focuses the next tab."),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
bits.push(Style::new().paint(" off screen focuses the next tab."));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn move_focus_hjkl_tab_switch_short(palette: Palette) -> LinePart {
|
||||
pub fn move_focus_hjkl_tab_switch_short(help: &ModeInfo) -> LinePart {
|
||||
// Alt + <←↓↑→> off screen edge focuses next tab.
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
|
||||
strings!(&[
|
||||
Style::new().fg(orange_color).bold().paint(" Alt"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<←↓↑→>"),
|
||||
Style::new().paint(" off screen edge focuses next tab."),
|
||||
])
|
||||
let mut bits = add_keybinds(help);
|
||||
bits.push(Style::new().paint(" off screen edge focuses next tab."));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
fn add_keybinds(help: &ModeInfo) -> Vec<ANSIString> {
|
||||
let pane_keymap = help.get_keybinds_for_mode(InputMode::Pane);
|
||||
let move_focus_keys = action_key_group(
|
||||
&pane_keymap,
|
||||
&[
|
||||
&[Action::MoveFocusOrTab(Direction::Left)],
|
||||
&[Action::MoveFocusOrTab(Direction::Right)],
|
||||
],
|
||||
);
|
||||
|
||||
// Let's see if we have some pretty groups in common here
|
||||
let mut arrows = vec![];
|
||||
let mut letters = vec![];
|
||||
for key in move_focus_keys.into_iter() {
|
||||
let key_str = key.to_string();
|
||||
if key_str.contains('←')
|
||||
|| key_str.contains('↓')
|
||||
|| key_str.contains('↑')
|
||||
|| key_str.contains('→')
|
||||
{
|
||||
arrows.push(key);
|
||||
} else {
|
||||
letters.push(key);
|
||||
}
|
||||
}
|
||||
let arrows = style_key_with_modifier(&arrows, &help.style.colors);
|
||||
let letters = style_key_with_modifier(&letters, &help.style.colors);
|
||||
if arrows.is_empty() && letters.is_empty() {
|
||||
vec![Style::new().bold().paint("UNBOUND")]
|
||||
} else if arrows.is_empty() || letters.is_empty() {
|
||||
arrows.into_iter().chain(letters.into_iter()).collect()
|
||||
} else {
|
||||
arrows
|
||||
.into_iter()
|
||||
.chain(vec![Style::new().paint(" or ")].into_iter())
|
||||
.chain(letters.into_iter())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
use ansi_term::{
|
||||
unstyled_len, ANSIString, ANSIStrings,
|
||||
Color::{Fixed, RGB},
|
||||
Style,
|
||||
};
|
||||
use ansi_term::{unstyled_len, ANSIString, ANSIStrings, Style};
|
||||
|
||||
use crate::LinePart;
|
||||
use zellij_tile::prelude::*;
|
||||
use zellij_tile_utils::palette_match;
|
||||
use crate::{action_key, action_key_group, style_key_with_modifier, LinePart};
|
||||
use zellij_tile::prelude::{
|
||||
actions::{Action, Direction, ResizeDirection},
|
||||
*,
|
||||
};
|
||||
|
||||
macro_rules! strings {
|
||||
($ANSIStrings:expr) => {{
|
||||
let strings: &[ANSIString<'static>] = $ANSIStrings;
|
||||
let strings: &[ANSIString] = $ANSIStrings;
|
||||
|
||||
let ansi_strings = ANSIStrings(strings);
|
||||
|
||||
|
|
@ -21,66 +19,115 @@ macro_rules! strings {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn quicknav_full(palette: Palette) -> LinePart {
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
pub fn quicknav_full(help: &ModeInfo) -> LinePart {
|
||||
let groups = add_keybinds(help);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().fg(orange_color).bold().paint("Alt"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<n>"),
|
||||
Style::new().paint(" => open new pane. "),
|
||||
Style::new().fg(orange_color).bold().paint("Alt"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<←↓↑→"),
|
||||
Style::new().paint(" or "),
|
||||
Style::new().fg(green_color).bold().paint("hjkl>"),
|
||||
Style::new().paint(" => navigate between panes. "),
|
||||
Style::new().fg(orange_color).bold().paint("Alt"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<+->"),
|
||||
Style::new().paint(" => increase/decrease pane size."),
|
||||
])
|
||||
let mut bits = vec![Style::new().paint(" Tip: ")];
|
||||
bits.extend(groups.new_pane);
|
||||
bits.push(Style::new().paint(" => open new pane. "));
|
||||
bits.extend(groups.move_focus);
|
||||
bits.push(Style::new().paint(" => navigate between panes. "));
|
||||
bits.extend(groups.resize);
|
||||
bits.push(Style::new().paint(" => increase/decrease pane size."));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn quicknav_medium(palette: Palette) -> LinePart {
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
pub fn quicknav_medium(help: &ModeInfo) -> LinePart {
|
||||
let groups = add_keybinds(help);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().fg(orange_color).bold().paint("Alt"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<n>"),
|
||||
Style::new().paint(" => new pane. "),
|
||||
Style::new().fg(orange_color).bold().paint("Alt"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<←↓↑→"),
|
||||
Style::new().paint(" or "),
|
||||
Style::new().fg(green_color).bold().paint("hjkl>"),
|
||||
Style::new().paint(" => navigate. "),
|
||||
Style::new().fg(orange_color).bold().paint("Alt"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<+->"),
|
||||
Style::new().paint(" => resize pane."),
|
||||
])
|
||||
let mut bits = vec![Style::new().paint(" Tip: ")];
|
||||
bits.extend(groups.new_pane);
|
||||
bits.push(Style::new().paint(" => new pane. "));
|
||||
bits.extend(groups.move_focus);
|
||||
bits.push(Style::new().paint(" => navigate. "));
|
||||
bits.extend(groups.resize);
|
||||
bits.push(Style::new().paint(" => resize pane."));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn quicknav_short(palette: Palette) -> LinePart {
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
pub fn quicknav_short(help: &ModeInfo) -> LinePart {
|
||||
let groups = add_keybinds(help);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" QuickNav: "),
|
||||
Style::new().fg(orange_color).bold().paint("Alt"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("n"),
|
||||
Style::new().paint("/"),
|
||||
Style::new().fg(green_color).bold().paint("<←↓↑→"),
|
||||
Style::new().paint("/"),
|
||||
Style::new().fg(green_color).bold().paint("hjkl"),
|
||||
Style::new().paint("/"),
|
||||
Style::new().fg(green_color).bold().paint("+->"),
|
||||
])
|
||||
let mut bits = vec![Style::new().paint(" QuickNav: ")];
|
||||
bits.extend(groups.new_pane);
|
||||
bits.push(Style::new().paint(" / "));
|
||||
bits.extend(groups.move_focus);
|
||||
bits.push(Style::new().paint(" / "));
|
||||
bits.extend(groups.resize);
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
struct Keygroups<'a> {
|
||||
new_pane: Vec<ANSIString<'a>>,
|
||||
move_focus: Vec<ANSIString<'a>>,
|
||||
resize: Vec<ANSIString<'a>>,
|
||||
}
|
||||
|
||||
fn add_keybinds(help: &ModeInfo) -> Keygroups {
|
||||
let normal_keymap = help.get_mode_keybinds();
|
||||
let new_pane_keys = action_key(&normal_keymap, &[Action::NewPane(None)]);
|
||||
let new_pane = if new_pane_keys.is_empty() {
|
||||
vec![Style::new().bold().paint("UNBOUND")]
|
||||
} else {
|
||||
style_key_with_modifier(&new_pane_keys, &help.style.colors)
|
||||
};
|
||||
|
||||
let resize_keys = action_key_group(
|
||||
&normal_keymap,
|
||||
&[
|
||||
&[Action::Resize(ResizeDirection::Increase)],
|
||||
&[Action::Resize(ResizeDirection::Decrease)],
|
||||
],
|
||||
);
|
||||
let resize = if resize_keys.is_empty() {
|
||||
vec![Style::new().bold().paint("UNBOUND")]
|
||||
} else {
|
||||
style_key_with_modifier(&resize_keys, &help.style.colors)
|
||||
};
|
||||
|
||||
let move_focus_keys = action_key_group(
|
||||
&normal_keymap,
|
||||
&[
|
||||
&[Action::MoveFocus(Direction::Left)],
|
||||
&[Action::MoveFocusOrTab(Direction::Left)],
|
||||
&[Action::MoveFocus(Direction::Down)],
|
||||
&[Action::MoveFocus(Direction::Up)],
|
||||
&[Action::MoveFocus(Direction::Right)],
|
||||
&[Action::MoveFocusOrTab(Direction::Right)],
|
||||
],
|
||||
);
|
||||
// Let's see if we have some pretty groups in common here
|
||||
let mut arrows = vec![];
|
||||
let mut letters = vec![];
|
||||
for key in move_focus_keys.into_iter() {
|
||||
let key_str = key.to_string();
|
||||
if key_str.contains('←')
|
||||
|| key_str.contains('↓')
|
||||
|| key_str.contains('↑')
|
||||
|| key_str.contains('→')
|
||||
{
|
||||
arrows.push(key);
|
||||
} else {
|
||||
letters.push(key);
|
||||
}
|
||||
}
|
||||
let arrows = style_key_with_modifier(&arrows, &help.style.colors);
|
||||
let letters = style_key_with_modifier(&letters, &help.style.colors);
|
||||
let move_focus = if arrows.is_empty() && letters.is_empty() {
|
||||
vec![Style::new().bold().paint("UNBOUND")]
|
||||
} else if arrows.is_empty() || letters.is_empty() {
|
||||
arrows.into_iter().chain(letters.into_iter()).collect()
|
||||
} else {
|
||||
arrows
|
||||
.into_iter()
|
||||
.chain(vec![Style::new().paint(" or ")].into_iter())
|
||||
.chain(letters.into_iter())
|
||||
.collect()
|
||||
};
|
||||
|
||||
Keygroups {
|
||||
new_pane,
|
||||
move_focus,
|
||||
resize,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use zellij_tile_utils::palette_match;
|
|||
|
||||
macro_rules! strings {
|
||||
($ANSIStrings:expr) => {{
|
||||
let strings: &[ANSIString<'static>] = $ANSIStrings;
|
||||
let strings: &[ANSIString] = $ANSIStrings;
|
||||
|
||||
let ansi_strings = ANSIStrings(strings);
|
||||
|
||||
|
|
@ -21,43 +21,43 @@ macro_rules! strings {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn mouse_click_to_terminal_full(palette: Palette) -> LinePart {
|
||||
pub fn mouse_click_to_terminal_full(help: &ModeInfo) -> LinePart {
|
||||
// Tip: SHIFT + <mouse-click> bypasses Zellij and sends the mouse click directly to the terminal
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
let orange_color = palette_match!(help.style.colors.orange);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().fg(orange_color).bold().paint("SHIFT"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<mouse-click>"),
|
||||
Style::new().paint(" bypasses Zellij and sends the mouse click directly to the terminal."),
|
||||
Style::new().fg(orange_color).bold().paint("Shift"),
|
||||
Style::new().paint(" + <"),
|
||||
Style::new().fg(green_color).bold().paint("mouse-click"),
|
||||
Style::new().paint("> bypasses Zellij and sends the mouse click directly to the terminal."),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn mouse_click_to_terminal_medium(palette: Palette) -> LinePart {
|
||||
pub fn mouse_click_to_terminal_medium(help: &ModeInfo) -> LinePart {
|
||||
// Tip: SHIFT + <mouse-click> sends the click directly to the terminal
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
let orange_color = palette_match!(help.style.colors.orange);
|
||||
strings!(&[
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().fg(orange_color).bold().paint("SHIFT"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<mouse-click>"),
|
||||
Style::new().paint(" sends the click directly to the terminal."),
|
||||
Style::new().fg(orange_color).bold().paint("Shift"),
|
||||
Style::new().paint(" + <"),
|
||||
Style::new().fg(green_color).bold().paint("mouse-click"),
|
||||
Style::new().paint("> sends the click directly to the terminal."),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn mouse_click_to_terminal_short(palette: Palette) -> LinePart {
|
||||
pub fn mouse_click_to_terminal_short(help: &ModeInfo) -> LinePart {
|
||||
// Tip: SHIFT + <mouse-click> => sends click to terminal.
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
let orange_color = palette_match!(help.style.colors.orange);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().fg(orange_color).bold().paint("SHIFT"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<mouse-click>"),
|
||||
Style::new().paint(" => sends click to terminal."),
|
||||
Style::new().fg(orange_color).bold().paint("Shift"),
|
||||
Style::new().paint(" + <"),
|
||||
Style::new().fg(green_color).bold().paint("mouse-click"),
|
||||
Style::new().paint("> => sends click to terminal."),
|
||||
])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
use ansi_term::{
|
||||
unstyled_len, ANSIString, ANSIStrings,
|
||||
Color::{Fixed, RGB},
|
||||
Style,
|
||||
};
|
||||
use ansi_term::{unstyled_len, ANSIString, ANSIStrings, Style};
|
||||
|
||||
use crate::LinePart;
|
||||
use zellij_tile::prelude::*;
|
||||
use zellij_tile_utils::palette_match;
|
||||
use crate::{action_key, style_key_with_modifier, LinePart};
|
||||
use zellij_tile::prelude::{actions::Action, *};
|
||||
|
||||
macro_rules! strings {
|
||||
($ANSIStrings:expr) => {{
|
||||
let strings: &[ANSIString<'static>] = $ANSIStrings;
|
||||
let strings: &[ANSIString] = $ANSIStrings;
|
||||
|
||||
let ansi_strings = ANSIStrings(strings);
|
||||
|
||||
|
|
@ -21,49 +16,53 @@ macro_rules! strings {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn sync_tab_full(palette: Palette) -> LinePart {
|
||||
pub fn sync_tab_full(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Sync a tab and write keyboard input to all panes with Ctrl + <t> + <s>
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().paint("Sync a tab and write keyboard input to all its panes with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<t>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<s>"),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn sync_tab_medium(palette: Palette) -> LinePart {
|
||||
pub fn sync_tab_medium(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Sync input to panes in a tab with Ctrl + <t> + <s>
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
|
||||
strings!(&[
|
||||
let mut bits = vec![
|
||||
Style::new().paint(" Tip: "),
|
||||
Style::new().paint("Sync input to panes in a tab with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<t>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<s>"),
|
||||
])
|
||||
];
|
||||
bits.extend(add_keybinds(help));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
pub fn sync_tab_short(palette: Palette) -> LinePart {
|
||||
pub fn sync_tab_short(help: &ModeInfo) -> LinePart {
|
||||
// Sync input in a tab with Ctrl + <t> + <s>
|
||||
let green_color = palette_match!(palette.green);
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" Sync input in a tab with "),
|
||||
Style::new().fg(orange_color).bold().paint("Ctrl"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<t>"),
|
||||
Style::new().paint(" + "),
|
||||
Style::new().fg(green_color).bold().paint("<s>"),
|
||||
])
|
||||
let mut bits = vec![Style::new().paint(" Sync input in a tab with ")];
|
||||
bits.extend(add_keybinds(help));
|
||||
strings!(&bits)
|
||||
}
|
||||
|
||||
fn add_keybinds(help: &ModeInfo) -> Vec<ANSIString> {
|
||||
let to_tab = action_key(
|
||||
&help.get_mode_keybinds(),
|
||||
&[Action::SwitchToMode(InputMode::Tab)],
|
||||
);
|
||||
let sync_tabs = action_key(
|
||||
&help.get_keybinds_for_mode(InputMode::Tab),
|
||||
&[
|
||||
Action::ToggleActiveSyncTab,
|
||||
Action::SwitchToMode(InputMode::Normal),
|
||||
],
|
||||
);
|
||||
|
||||
if sync_tabs.is_empty() {
|
||||
return vec![Style::new().bold().paint("UNBOUND")];
|
||||
}
|
||||
|
||||
let mut bits = vec![];
|
||||
bits.extend(style_key_with_modifier(&to_tab, &help.style.colors));
|
||||
bits.push(Style::new().paint(", "));
|
||||
bits.extend(style_key_with_modifier(&sync_tabs, &help.style.colors));
|
||||
bits
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use zellij_tile_utils::palette_match;
|
|||
|
||||
macro_rules! strings {
|
||||
($ANSIStrings:expr) => {{
|
||||
let strings: &[ANSIString<'static>] = $ANSIStrings;
|
||||
let strings: &[ANSIString] = $ANSIStrings;
|
||||
|
||||
let ansi_strings = ANSIStrings(strings);
|
||||
|
||||
|
|
@ -21,10 +21,10 @@ macro_rules! strings {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn use_mouse_full(palette: Palette) -> LinePart {
|
||||
pub fn use_mouse_full(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Use the mouse to switch pane focus, scroll through the pane
|
||||
// scrollbuffer, switch or scroll through tabs
|
||||
let green_color = palette_match!(palette.green);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" Tip: "),
|
||||
|
|
@ -33,10 +33,10 @@ pub fn use_mouse_full(palette: Palette) -> LinePart {
|
|||
])
|
||||
}
|
||||
|
||||
pub fn use_mouse_medium(palette: Palette) -> LinePart {
|
||||
pub fn use_mouse_medium(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Use the mouse to switch panes/tabs or scroll through the pane
|
||||
// scrollbuffer
|
||||
let green_color = palette_match!(palette.green);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" Tip: "),
|
||||
|
|
@ -45,9 +45,9 @@ pub fn use_mouse_medium(palette: Palette) -> LinePart {
|
|||
])
|
||||
}
|
||||
|
||||
pub fn use_mouse_short(palette: Palette) -> LinePart {
|
||||
pub fn use_mouse_short(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Use the mouse to switch panes/tabs or scroll
|
||||
let green_color = palette_match!(palette.green);
|
||||
let green_color = palette_match!(help.style.colors.green);
|
||||
|
||||
strings!(&[
|
||||
Style::new().fg(green_color).bold().paint(" Use the mouse"),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use zellij_tile_utils::palette_match;
|
|||
|
||||
macro_rules! strings {
|
||||
($ANSIStrings:expr) => {{
|
||||
let strings: &[ANSIString<'static>] = $ANSIStrings;
|
||||
let strings: &[ANSIString] = $ANSIStrings;
|
||||
|
||||
let ansi_strings = ANSIStrings(strings);
|
||||
|
||||
|
|
@ -21,9 +21,9 @@ macro_rules! strings {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn zellij_setup_check_full(palette: Palette) -> LinePart {
|
||||
pub fn zellij_setup_check_full(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Having issues with Zellij? Try running "zellij setup --check"
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let orange_color = palette_match!(help.style.colors.orange);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" Tip: "),
|
||||
|
|
@ -35,9 +35,9 @@ pub fn zellij_setup_check_full(palette: Palette) -> LinePart {
|
|||
])
|
||||
}
|
||||
|
||||
pub fn zellij_setup_check_medium(palette: Palette) -> LinePart {
|
||||
pub fn zellij_setup_check_medium(help: &ModeInfo) -> LinePart {
|
||||
// Tip: Run "zellij setup --check" to find issues
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let orange_color = palette_match!(help.style.colors.orange);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" Tip: "),
|
||||
|
|
@ -50,9 +50,9 @@ pub fn zellij_setup_check_medium(palette: Palette) -> LinePart {
|
|||
])
|
||||
}
|
||||
|
||||
pub fn zellij_setup_check_short(palette: Palette) -> LinePart {
|
||||
pub fn zellij_setup_check_short(help: &ModeInfo) -> LinePart {
|
||||
// Run "zellij setup --check" to find issues
|
||||
let orange_color = palette_match!(palette.orange);
|
||||
let orange_color = palette_match!(help.style.colors.orange);
|
||||
|
||||
strings!(&[
|
||||
Style::new().paint(" Run "),
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ pub mod utils;
|
|||
use crate::LinePart;
|
||||
use zellij_tile::prelude::*;
|
||||
|
||||
pub type TipFn = fn(Palette) -> LinePart;
|
||||
pub type TipFn = fn(&ModeInfo) -> LinePart;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TipBody {
|
||||
pub short: TipFn,
|
||||
pub medium: TipFn,
|
||||
|
|
|
|||
|
|
@ -983,6 +983,41 @@ pub fn accepts_basic_layout() {
|
|||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn status_bar_loads_custom_keybindings() {
|
||||
let fake_win_size = Size {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
};
|
||||
let config_file_name = "changed_keys.yaml";
|
||||
let mut test_attempts = 10;
|
||||
let last_snapshot = loop {
|
||||
RemoteRunner::kill_running_sessions(fake_win_size);
|
||||
let mut runner = RemoteRunner::new_with_config(fake_win_size, config_file_name);
|
||||
runner.run_all_steps();
|
||||
let last_snapshot = runner.take_snapshot_after(Step {
|
||||
name: "Wait for app to load",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(3, 1)
|
||||
&& remote_terminal.snapshot_contains("$ █ ││$")
|
||||
&& remote_terminal.snapshot_contains("$ ") {
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
});
|
||||
if runner.test_timed_out && test_attempts > 0 {
|
||||
test_attempts -= 1;
|
||||
continue;
|
||||
} else {
|
||||
break last_snapshot;
|
||||
}
|
||||
};
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn focus_pane_with_mouse() {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ use std::rc::Rc;
|
|||
const ZELLIJ_EXECUTABLE_LOCATION: &str = "/usr/src/zellij/x86_64-unknown-linux-musl/release/zellij";
|
||||
const SET_ENV_VARIABLES: &str = "EDITOR=/usr/bin/vi";
|
||||
const ZELLIJ_LAYOUT_PATH: &str = "/usr/src/zellij/fixtures/layouts";
|
||||
const ZELLIJ_CONFIG_PATH: &str = "/usr/src/zellij/fixtures/configs";
|
||||
const ZELLIJ_DATA_DIR: &str = "/usr/src/zellij/e2e-data";
|
||||
const ZELLIJ_FIXTURE_PATH: &str = "/usr/src/zellij/fixtures";
|
||||
const CONNECTION_STRING: &str = "127.0.0.1:2222";
|
||||
|
|
@ -163,6 +164,25 @@ fn start_zellij_with_layout(channel: &mut ssh2::Channel, layout_path: &str) {
|
|||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN
|
||||
}
|
||||
|
||||
fn start_zellij_with_config(channel: &mut ssh2::Channel, config_path: &str) {
|
||||
stop_zellij(channel);
|
||||
channel
|
||||
.write_all(
|
||||
format!(
|
||||
"{} {} --config {} --session {} --data-dir {}\n",
|
||||
SET_ENV_VARIABLES,
|
||||
ZELLIJ_EXECUTABLE_LOCATION,
|
||||
config_path,
|
||||
SESSION_NAME,
|
||||
ZELLIJ_DATA_DIR
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN
|
||||
}
|
||||
|
||||
fn read_from_channel(
|
||||
channel: &Arc<Mutex<ssh2::Channel>>,
|
||||
last_snapshot: &Arc<Mutex<String>>,
|
||||
|
|
@ -587,6 +607,42 @@ impl RemoteRunner {
|
|||
reader_thread,
|
||||
}
|
||||
}
|
||||
pub fn new_with_config(win_size: Size, config_file_name: &'static str) -> Self {
|
||||
let remote_path = Path::new(ZELLIJ_CONFIG_PATH).join(config_file_name);
|
||||
let sess = ssh_connect();
|
||||
let mut channel = sess.channel_session().unwrap();
|
||||
let mut rows = Dimension::fixed(win_size.rows);
|
||||
let mut cols = Dimension::fixed(win_size.cols);
|
||||
rows.set_inner(win_size.rows);
|
||||
cols.set_inner(win_size.cols);
|
||||
let pane_geom = PaneGeom {
|
||||
x: 0,
|
||||
y: 0,
|
||||
rows,
|
||||
cols,
|
||||
};
|
||||
setup_remote_environment(&mut channel, win_size);
|
||||
start_zellij_with_config(&mut channel, &remote_path.to_string_lossy());
|
||||
let channel = Arc::new(Mutex::new(channel));
|
||||
let last_snapshot = Arc::new(Mutex::new(String::new()));
|
||||
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
|
||||
sess.set_blocking(false);
|
||||
let reader_thread =
|
||||
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
|
||||
RemoteRunner {
|
||||
steps: vec![],
|
||||
channel,
|
||||
currently_running_step: None,
|
||||
current_step_index: 0,
|
||||
retries_left: RETRIES,
|
||||
retry_pause_ms: 100,
|
||||
test_timed_out: false,
|
||||
panic_on_no_retries_left: true,
|
||||
last_snapshot,
|
||||
cursor_coordinates,
|
||||
reader_thread,
|
||||
}
|
||||
}
|
||||
pub fn dont_panic(mut self) -> Self {
|
||||
self.panic_on_no_retries_left = false;
|
||||
self
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -21,5 +21,5 @@ expression: last_snapshot
|
|||
│ │
|
||||
│ │
|
||||
└──────┘
|
||||
Ctrl +
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -25,5 +25,5 @@ expression: last_snapshot
|
|||
│ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Ctrl + <g> LOCK <> PANE <> TAB <> RESIZE <> MOVE <> SEARCH <> SESSION <> QUIT
|
||||
-- INTERFACE LOCKED --
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: second_runner_snapshot
|
|||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
<←↓↑→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <Tab> Toggle / <ENTER> Select pane
|
||||
<←→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <TAB> Toggle / <ENTER> Select pane
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: first_runner_snapshot
|
|||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: second_runner_snapshot
|
|||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: first_runner_snapshot
|
|||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: second_runner_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: first_runner_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: second_runner_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: first_runner_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ ││ │
|
||||
└────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ ││ │
|
||||
└────────────────────────────────────────────────┘└────────────────────────────────────────────────┘
|
||||
Ctrl + g p t n h s o q
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
QuickNav: Alt + <n> / Alt + <←↓↑→> or Alt + <hjkl> / Alt + <+|=|->
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ ││li█e21 │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
<↓↑> Scroll / <PgDn/PgUp> Scroll / <d/u> Scroll / <e> Edit / <s> Enter / <ENTER> Select pane
|
||||
<↓↑> Scroll / <PgDn|PgUp> Scroll / <d|u> Scroll / <e> Edit / <s> Search / <ENTER> Select
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ ││li█e19 │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ $ │$ █
|
|||
│
|
||||
│
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
source: src/tests/e2e/cases.rs
|
||||
assertion_line: 398
|
||||
expression: last_snapshot
|
||||
---
|
||||
Zellij (e2e-test) Tab #1
|
||||
┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│$ █ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
<F1> LOCK <F2> PANE <F3> TAB <F4> RESIZE <F5> MOVE <F6> SEARCH <F7> SESSION <F8> QUIT
|
||||
Tip: UNBOUND => open new pane. UNBOUND => navigate between panes. UNBOUND => increase/decrease pane size.
|
||||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
(FLOATING PANES VISIBLE): Press Ctrl-p + <w> to hide.
|
||||
(FLOATING PANES VISIBLE): Press Ctrl+p, <w> to hide.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ expression: last_snapshot
|
|||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|=|-> => resize pane.
|
||||
|
|
|
|||
26
src/tests/fixtures/configs/changed_keys.yaml
vendored
Normal file
26
src/tests/fixtures/configs/changed_keys.yaml
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
# Configuration for zellij.
|
||||
|
||||
# In order to troubleshoot your configuration try using the following command:
|
||||
# `zellij setup --check`
|
||||
# It should show current config locations and features that are enabled.
|
||||
|
||||
keybinds:
|
||||
unbind: true
|
||||
normal:
|
||||
- action: [SwitchToMode: Locked,]
|
||||
key: [F: 1]
|
||||
- action: [SwitchToMode: Pane,]
|
||||
key: [F: 2]
|
||||
- action: [SwitchToMode: Tab,]
|
||||
key: [F: 3]
|
||||
- action: [SwitchToMode: Resize,]
|
||||
key: [F: 4]
|
||||
- action: [SwitchToMode: Move,]
|
||||
key: [F: 5]
|
||||
- action: [SwitchToMode: Scroll,]
|
||||
key: [F: 6]
|
||||
- action: [SwitchToMode: Session,]
|
||||
key: [F: 7]
|
||||
- action: [Quit,]
|
||||
key: [F: 8]
|
||||
|
|
@ -49,6 +49,7 @@ pub fn start_fake_client(
|
|||
colors: palette,
|
||||
rounded_corners: config.ui.unwrap_or_default().pane_frames.rounded_corners,
|
||||
},
|
||||
keybinds: config.keybinds.clone(),
|
||||
};
|
||||
|
||||
let first_msg = ClientToServerMsg::AttachClient(client_attributes, config_options.clone());
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ pub fn start_client(
|
|||
colors: palette,
|
||||
rounded_corners: config.ui.unwrap_or_default().pane_frames.rounded_corners,
|
||||
},
|
||||
keybinds: config.keybinds.clone(),
|
||||
};
|
||||
|
||||
let first_msg = match info {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ use zellij_utils::{
|
|||
channels::{self, ChannelWithContext, SenderWithContext},
|
||||
cli::CliArgs,
|
||||
consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE},
|
||||
data::{Event, PluginCapabilities, Style},
|
||||
data::{Event, PluginCapabilities},
|
||||
errors::{ContextType, ErrorInstruction, ServerContext},
|
||||
input::{
|
||||
command::{RunCommand, TerminalAction},
|
||||
|
|
@ -104,7 +104,7 @@ impl ErrorInstruction for ServerInstruction {
|
|||
pub(crate) struct SessionMetaData {
|
||||
pub senders: ThreadSenders,
|
||||
pub capabilities: PluginCapabilities,
|
||||
pub style: Style,
|
||||
pub client_attributes: ClientAttributes,
|
||||
pub default_shell: Option<TerminalAction>,
|
||||
screen_thread: Option<thread::JoinHandle<()>>,
|
||||
pty_thread: Option<thread::JoinHandle<()>>,
|
||||
|
|
@ -285,7 +285,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
|||
let session = init_session(
|
||||
os_input.clone(),
|
||||
to_server.clone(),
|
||||
client_attributes,
|
||||
client_attributes.clone(),
|
||||
SessionOptions {
|
||||
opts,
|
||||
layout: layout.clone(),
|
||||
|
|
@ -378,7 +378,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
|||
.send_to_plugin(PluginInstruction::AddClient(client_id))
|
||||
.unwrap();
|
||||
let default_mode = options.default_mode.unwrap_or_default();
|
||||
let mode_info = get_mode_info(default_mode, attrs.style, session_data.capabilities);
|
||||
let mode_info = get_mode_info(default_mode, &attrs, session_data.capabilities);
|
||||
let mode = mode_info.mode;
|
||||
session_data
|
||||
.senders
|
||||
|
|
@ -654,8 +654,14 @@ fn init_session(
|
|||
);
|
||||
let max_panes = opts.max_panes;
|
||||
|
||||
let client_attributes_clone = client_attributes.clone();
|
||||
move || {
|
||||
screen_thread_main(screen_bus, max_panes, client_attributes, config_options);
|
||||
screen_thread_main(
|
||||
screen_bus,
|
||||
max_panes,
|
||||
client_attributes_clone,
|
||||
config_options,
|
||||
);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
|
@ -705,7 +711,7 @@ fn init_session(
|
|||
},
|
||||
capabilities,
|
||||
default_shell,
|
||||
style: client_attributes.style,
|
||||
client_attributes,
|
||||
screen_thread: Some(screen_thread),
|
||||
pty_thread: Some(pty_thread),
|
||||
wasm_thread: Some(wasm_thread),
|
||||
|
|
|
|||
|
|
@ -75,22 +75,23 @@ fn route_action(
|
|||
.unwrap();
|
||||
},
|
||||
Action::SwitchToMode(mode) => {
|
||||
let style = session.style;
|
||||
let attrs = &session.client_attributes;
|
||||
// TODO: use the palette from the client and remove it from the server os api
|
||||
// this is left here as a stop gap measure until we shift some code around
|
||||
// to allow for this
|
||||
// TODO: Need access to `ClientAttributes` here
|
||||
session
|
||||
.senders
|
||||
.send_to_plugin(PluginInstruction::Update(
|
||||
None,
|
||||
Some(client_id),
|
||||
Event::ModeUpdate(get_mode_info(mode, style, session.capabilities)),
|
||||
Event::ModeUpdate(get_mode_info(mode, attrs, session.capabilities)),
|
||||
))
|
||||
.unwrap();
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ChangeMode(
|
||||
get_mode_info(mode, style, session.capabilities),
|
||||
get_mode_info(mode, attrs, session.capabilities),
|
||||
client_id,
|
||||
))
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -915,7 +915,7 @@ pub(crate) fn screen_thread_main(
|
|||
max_panes,
|
||||
get_mode_info(
|
||||
config_options.default_mode.unwrap_or_default(),
|
||||
client_attributes.style,
|
||||
&client_attributes,
|
||||
PluginCapabilities {
|
||||
arrow_fonts: capabilities.unwrap_or_default(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub use crate::shim::*;
|
||||
pub use crate::*;
|
||||
pub use zellij_utils::data::*;
|
||||
pub use zellij_utils::input::actions;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ license = "MIT"
|
|||
[dependencies]
|
||||
anyhow = "1.0.45"
|
||||
backtrace = "0.3.55"
|
||||
bincode = "1.3.1"
|
||||
rmp-serde = "1.1.0"
|
||||
clap = { version = "3.2.2", features = ["derive", "env"] }
|
||||
clap_complete = "3.2.1"
|
||||
colored = "2.0.0"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
//! Zellij program-wide constants.
|
||||
|
||||
use crate::envs;
|
||||
use crate::shared::set_permissions;
|
||||
use directories_next::ProjectDirs;
|
||||
use lazy_static::lazy_static;
|
||||
use nix::unistd::Uid;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
pub const ZELLIJ_CONFIG_FILE_ENV: &str = "ZELLIJ_CONFIG_FILE";
|
||||
pub const ZELLIJ_CONFIG_DIR_ENV: &str = "ZELLIJ_CONFIG_DIR";
|
||||
|
|
@ -28,9 +24,40 @@ const fn system_default_data_dir() -> &'static str {
|
|||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref UID: Uid = Uid::current();
|
||||
pub static ref ZELLIJ_PROJ_DIR: ProjectDirs =
|
||||
ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
|
||||
pub static ref ZELLIJ_CACHE_DIR: PathBuf = ZELLIJ_PROJ_DIR.cache_dir().to_path_buf();
|
||||
}
|
||||
|
||||
pub const FEATURES: &[&str] = &[
|
||||
#[cfg(feature = "disable_automatic_asset_installation")]
|
||||
"disable_automatic_asset_installation",
|
||||
];
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use unix_only::*;
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix_only {
|
||||
use super::*;
|
||||
use crate::envs;
|
||||
use crate::shared::set_permissions;
|
||||
use lazy_static::lazy_static;
|
||||
use nix::unistd::Uid;
|
||||
use std::fs;
|
||||
|
||||
lazy_static! {
|
||||
static ref UID: Uid = Uid::current();
|
||||
pub static ref ZELLIJ_IPC_PIPE: PathBuf = {
|
||||
let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
|
||||
fs::create_dir_all(&sock_dir).unwrap();
|
||||
set_permissions(&sock_dir, 0o700).unwrap();
|
||||
sock_dir.push(envs::get_session_name().unwrap());
|
||||
sock_dir
|
||||
};
|
||||
pub static ref ZELLIJ_TMP_DIR: PathBuf = PathBuf::from(format!("/tmp/zellij-{}", *UID));
|
||||
pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log");
|
||||
pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("zellij.log");
|
||||
pub static ref ZELLIJ_SOCK_DIR: PathBuf = {
|
||||
let mut ipc_dir = envs::get_socket_dir().map_or_else(
|
||||
|_| {
|
||||
|
|
@ -43,20 +70,5 @@ lazy_static! {
|
|||
ipc_dir.push(VERSION);
|
||||
ipc_dir
|
||||
};
|
||||
pub static ref ZELLIJ_IPC_PIPE: PathBuf = {
|
||||
let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
|
||||
fs::create_dir_all(&sock_dir).unwrap();
|
||||
set_permissions(&sock_dir, 0o700).unwrap();
|
||||
sock_dir.push(envs::get_session_name().unwrap());
|
||||
sock_dir
|
||||
};
|
||||
pub static ref ZELLIJ_TMP_DIR: PathBuf = PathBuf::from(format!("/tmp/zellij-{}", *UID));
|
||||
pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log");
|
||||
pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("zellij.log");
|
||||
pub static ref ZELLIJ_CACHE_DIR: PathBuf = ZELLIJ_PROJ_DIR.cache_dir().to_path_buf();
|
||||
}
|
||||
|
||||
pub const FEATURES: &[&str] = &[
|
||||
#[cfg(feature = "disable_automatic_asset_installation")]
|
||||
"disable_automatic_asset_installation",
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::input::actions::Action;
|
||||
use clap::ArgEnum;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
|
@ -30,37 +31,81 @@ pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) {
|
|||
(colors.green, colors.black)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
// TODO: Add a shortened string representation (beyond `Display::fmt` below) that can be used when
|
||||
// screen space is scarce. Useful for e.g. "ENTER", "SPACE", "TAB" to display as Unicode
|
||||
// representations instead.
|
||||
// NOTE: Do not reorder the key variants since that influences what the `status_bar` plugin
|
||||
// displays!
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
|
||||
pub enum Key {
|
||||
Backspace,
|
||||
PageDown,
|
||||
PageUp,
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
Up,
|
||||
Right,
|
||||
Home,
|
||||
End,
|
||||
PageUp,
|
||||
PageDown,
|
||||
BackTab,
|
||||
Backspace,
|
||||
Delete,
|
||||
Insert,
|
||||
F(u8),
|
||||
Char(char),
|
||||
Alt(CharOrArrow),
|
||||
Ctrl(char),
|
||||
BackTab,
|
||||
Null,
|
||||
Esc,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
impl fmt::Display for Key {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Key::Backspace => write!(f, "BACKSPACE"),
|
||||
Key::Left => write!(f, "{}", Direction::Left),
|
||||
Key::Right => write!(f, "{}", Direction::Right),
|
||||
Key::Up => write!(f, "{}", Direction::Up),
|
||||
Key::Down => write!(f, "{}", Direction::Down),
|
||||
Key::Home => write!(f, "HOME"),
|
||||
Key::End => write!(f, "END"),
|
||||
Key::PageUp => write!(f, "PgUp"),
|
||||
Key::PageDown => write!(f, "PgDn"),
|
||||
Key::BackTab => write!(f, "TAB"),
|
||||
Key::Delete => write!(f, "DEL"),
|
||||
Key::Insert => write!(f, "INS"),
|
||||
Key::F(n) => write!(f, "F{}", n),
|
||||
Key::Char(c) => match c {
|
||||
'\n' => write!(f, "ENTER"),
|
||||
'\t' => write!(f, "TAB"),
|
||||
' ' => write!(f, "SPACE"),
|
||||
_ => write!(f, "{}", c),
|
||||
},
|
||||
Key::Alt(c) => write!(f, "Alt+{}", c),
|
||||
Key::Ctrl(c) => write!(f, "Ctrl+{}", Key::Char(*c)),
|
||||
Key::Null => write!(f, "NULL"),
|
||||
Key::Esc => write!(f, "ESC"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
|
||||
#[serde(untagged)]
|
||||
pub enum CharOrArrow {
|
||||
Char(char),
|
||||
Direction(Direction),
|
||||
}
|
||||
|
||||
impl fmt::Display for CharOrArrow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
CharOrArrow::Char(c) => write!(f, "{}", Key::Char(*c)),
|
||||
CharOrArrow::Direction(d) => write!(f, "{}", d),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The four directions (left, right, up, down).
|
||||
#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize)]
|
||||
#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
|
||||
pub enum Direction {
|
||||
Left,
|
||||
Right,
|
||||
|
|
@ -68,6 +113,17 @@ pub enum Direction {
|
|||
Down,
|
||||
}
|
||||
|
||||
impl fmt::Display for Direction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Direction::Left => write!(f, "←"),
|
||||
Direction::Right => write!(f, "→"),
|
||||
Direction::Up => write!(f, "↑"),
|
||||
Direction::Down => write!(f, "↓"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
// FIXME: This should be extended to handle different button clicks (not just
|
||||
// left click) and the `ScrollUp` and `ScrollDown` events could probably be
|
||||
|
|
@ -237,19 +293,36 @@ pub struct Style {
|
|||
pub rounded_corners: bool,
|
||||
}
|
||||
|
||||
// FIXME: Poor devs hashtable since HashTable can't derive `Default`...
|
||||
pub type KeybindsVec = Vec<(InputMode, Vec<(Key, Vec<Action>)>)>;
|
||||
|
||||
/// Represents the contents of the help message that is printed in the status bar,
|
||||
/// which indicates the current [`InputMode`] and what the keybinds for that mode
|
||||
/// are. Related to the default `status-bar` plugin.
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ModeInfo {
|
||||
pub mode: InputMode,
|
||||
// FIXME: This should probably return Keys and Actions, then sort out strings plugin-side
|
||||
pub keybinds: Vec<(String, String)>, // <shortcut> => <shortcut description>
|
||||
pub keybinds: KeybindsVec,
|
||||
pub style: Style,
|
||||
pub capabilities: PluginCapabilities,
|
||||
pub session_name: Option<String>,
|
||||
}
|
||||
|
||||
impl ModeInfo {
|
||||
pub fn get_mode_keybinds(&self) -> Vec<(Key, Vec<Action>)> {
|
||||
self.get_keybinds_for_mode(self.mode)
|
||||
}
|
||||
|
||||
pub fn get_keybinds_for_mode(&self, mode: InputMode) -> Vec<(Key, Vec<Action>)> {
|
||||
for (vec_mode, map) in &self.keybinds {
|
||||
if mode == *vec_mode {
|
||||
return map.to_vec();
|
||||
}
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||
pub struct TabInfo {
|
||||
/* subset of fields to publish to plugins */
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ pub enum Direction {
|
|||
Down,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum ResizeDirection {
|
||||
Left,
|
||||
Right,
|
||||
|
|
@ -27,13 +27,13 @@ pub enum ResizeDirection {
|
|||
Decrease,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum SearchDirection {
|
||||
Down,
|
||||
Up,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum SearchOption {
|
||||
CaseSensitivity,
|
||||
WholeWord,
|
||||
|
|
@ -45,7 +45,7 @@ pub enum SearchOption {
|
|||
// They might need to be adjusted in the default config
|
||||
// as well `../../assets/config/default.yaml`
|
||||
/// Actions that can be bound to keys.
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum Action {
|
||||
/// Quit Zellij.
|
||||
Quit,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Mapping of inputs to sequences of actions.
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use super::actions::Action;
|
||||
use super::config;
|
||||
use crate::input::{InputMode, Key};
|
||||
use crate::data::{InputMode, Key, KeybindsVec};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
|
@ -12,7 +12,7 @@ use strum::IntoEnumIterator;
|
|||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
|
||||
pub struct ModeKeybinds(BTreeMap<Key, Vec<Action>>);
|
||||
|
||||
/// Intermediate struct used for deserialisation
|
||||
/// Used in the config file.
|
||||
|
|
@ -84,6 +84,20 @@ impl Keybinds {
|
|||
.keybinds
|
||||
}
|
||||
|
||||
pub fn to_keybinds_vec(&self) -> KeybindsVec {
|
||||
let mut ret = vec![];
|
||||
for (mode, mode_binds) in &self.0 {
|
||||
ret.push((*mode, mode_binds.to_cloned_vec()));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn get_mode_keybinds(&self, mode: &InputMode) -> &ModeKeybinds {
|
||||
self.0
|
||||
.get(mode)
|
||||
.expect("Failed to get Keybinds for current mode")
|
||||
}
|
||||
|
||||
/// Entrypoint from the config module
|
||||
pub fn get_default_keybinds_with_config(from_yaml: Option<KeybindsFromYaml>) -> Keybinds {
|
||||
let default_keybinds = match from_yaml.clone() {
|
||||
|
|
@ -221,7 +235,7 @@ impl Keybinds {
|
|||
|
||||
impl ModeKeybinds {
|
||||
fn new() -> ModeKeybinds {
|
||||
ModeKeybinds(HashMap::<Key, Vec<Action>>::new())
|
||||
ModeKeybinds(BTreeMap::<Key, Vec<Action>>::new())
|
||||
}
|
||||
|
||||
/// Merges `self` with `other`, if keys are the same, `other` overwrites.
|
||||
|
|
@ -239,6 +253,13 @@ impl ModeKeybinds {
|
|||
}
|
||||
keymap
|
||||
}
|
||||
|
||||
pub fn to_cloned_vec(&self) -> Vec<(Key, Vec<Action>)> {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|(key, vac)| (*key, vac.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeybindsFromYaml> for Keybinds {
|
||||
|
|
@ -269,7 +290,7 @@ impl From<KeyActionFromYaml> for ModeKeybinds {
|
|||
.key
|
||||
.into_iter()
|
||||
.map(|k| (k, actions.clone()))
|
||||
.collect::<HashMap<Key, Vec<Action>>>(),
|
||||
.collect::<BTreeMap<Key, Vec<Action>>>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ use crate::{
|
|||
pane_size::{Dimension, PaneGeom},
|
||||
setup,
|
||||
};
|
||||
use crate::{serde, serde_yaml};
|
||||
|
||||
use super::{
|
||||
config::ConfigFromYaml,
|
||||
|
|
@ -35,7 +34,6 @@ use std::{fs::File, io::prelude::*};
|
|||
use url::Url;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub enum Direction {
|
||||
#[serde(alias = "horizontal")]
|
||||
Horizontal,
|
||||
|
|
@ -54,17 +52,15 @@ impl Not for Direction {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SplitSize {
|
||||
#[serde(alias = "percent")]
|
||||
Percent(f64), // 1 to 100
|
||||
Percent(u64), // 1 to 100
|
||||
#[serde(alias = "fixed")]
|
||||
Fixed(usize), // An absolute number of columns or rows
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub enum Run {
|
||||
#[serde(rename = "plugin")]
|
||||
Plugin(RunPlugin),
|
||||
|
|
@ -73,7 +69,6 @@ pub enum Run {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub enum RunFromYaml {
|
||||
#[serde(rename = "plugin")]
|
||||
Plugin(RunPluginFromYaml),
|
||||
|
|
@ -82,7 +77,6 @@ pub enum RunFromYaml {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub struct RunPluginFromYaml {
|
||||
#[serde(default)]
|
||||
pub _allow_exec_host_cmd: bool,
|
||||
|
|
@ -90,7 +84,6 @@ pub struct RunPluginFromYaml {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub struct RunPlugin {
|
||||
#[serde(default)]
|
||||
pub _allow_exec_host_cmd: bool,
|
||||
|
|
@ -98,7 +91,6 @@ pub struct RunPlugin {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub enum RunPluginLocation {
|
||||
File(PathBuf),
|
||||
Zellij(PluginTag),
|
||||
|
|
@ -133,7 +125,6 @@ impl fmt::Display for RunPluginLocation {
|
|||
|
||||
// The layout struct ultimately used to build the layouts.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub struct Layout {
|
||||
pub direction: Direction,
|
||||
#[serde(default)]
|
||||
|
|
@ -152,7 +143,6 @@ pub struct Layout {
|
|||
// https://github.com/bincode-org/bincode/issues/245
|
||||
// flattened fields don't retain size information.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
#[serde(default)]
|
||||
pub struct LayoutFromYamlIntermediate {
|
||||
#[serde(default)]
|
||||
|
|
@ -170,7 +160,6 @@ pub struct LayoutFromYamlIntermediate {
|
|||
// The struct that is used to deserialize the layout from
|
||||
// a yaml configuration file
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(crate = "self::serde")]
|
||||
#[serde(default)]
|
||||
pub struct LayoutFromYaml {
|
||||
#[serde(default)]
|
||||
|
|
@ -422,7 +411,6 @@ impl LayoutFromYaml {
|
|||
// The struct that is used to deserialize the session from
|
||||
// a yaml configuration file
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub struct SessionFromYaml {
|
||||
pub name: Option<String>,
|
||||
#[serde(default = "default_as_some_true")]
|
||||
|
|
@ -436,7 +424,6 @@ fn default_as_some_true() -> Option<bool> {
|
|||
// The struct that carries the information template that is used to
|
||||
// construct the layout
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
pub struct LayoutTemplate {
|
||||
pub direction: Direction,
|
||||
#[serde(default)]
|
||||
|
|
@ -481,8 +468,7 @@ impl LayoutTemplate {
|
|||
}
|
||||
|
||||
// The tab-layout struct used to specify each individual tab.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(crate = "self::serde")]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct TabLayout {
|
||||
#[serde(default)]
|
||||
pub direction: Direction,
|
||||
|
|
@ -606,7 +592,7 @@ fn split_space(space_to_split: &PaneGeom, layout: &Layout) -> Vec<(Layout, PaneG
|
|||
|
||||
for (&size, part) in sizes.iter().zip(&layout.parts) {
|
||||
let split_dimension = match size {
|
||||
Some(SplitSize::Percent(percent)) => Dimension::percent(percent),
|
||||
Some(SplitSize::Percent(percent)) => Dimension::percent(percent as f64),
|
||||
Some(SplitSize::Fixed(size)) => Dimension::fixed(size),
|
||||
None => {
|
||||
let free_percent = if let Some(p) = split_dimension_space.as_percent() {
|
||||
|
|
@ -614,7 +600,7 @@ fn split_space(space_to_split: &PaneGeom, layout: &Layout) -> Vec<(Layout, PaneG
|
|||
.iter()
|
||||
.map(|&s| {
|
||||
if let Some(SplitSize::Percent(ip)) = s {
|
||||
ip
|
||||
ip as f64
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,98 +1,43 @@
|
|||
//! The way terminal input is handled.
|
||||
|
||||
pub mod actions;
|
||||
pub mod command;
|
||||
pub mod config;
|
||||
pub mod keybinds;
|
||||
pub mod layout;
|
||||
pub mod mouse;
|
||||
pub mod options;
|
||||
pub mod plugins;
|
||||
pub mod theme;
|
||||
|
||||
use super::{
|
||||
data::{CharOrArrow, Direction, Style},
|
||||
data::{InputMode, Key, ModeInfo, PluginCapabilities},
|
||||
// Can't use this in wasm due to dependency on the `termwiz` crate.
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod mouse;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub use not_wasm::*;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
mod not_wasm {
|
||||
use crate::{
|
||||
data::{CharOrArrow, Direction, InputMode, Key, ModeInfo, PluginCapabilities},
|
||||
envs,
|
||||
ipc::ClientAttributes,
|
||||
};
|
||||
use crate::envs;
|
||||
use termwiz::input::{InputEvent, InputParser, KeyCode, KeyEvent, Modifiers};
|
||||
|
||||
/// Creates a [`ModeInfo`] struct indicating the current [`InputMode`] and its keybinds
|
||||
/// (as pairs of [`String`]s).
|
||||
pub fn get_mode_info(mode: InputMode, style: Style, capabilities: PluginCapabilities) -> ModeInfo {
|
||||
let keybinds = match mode {
|
||||
InputMode::Normal | InputMode::Locked | InputMode::Prompt => Vec::new(),
|
||||
InputMode::Resize => vec![
|
||||
("←↓↑→".to_string(), "Resize".to_string()),
|
||||
("+-".to_string(), "Increase/Decrease size".to_string()),
|
||||
],
|
||||
InputMode::Move => vec![
|
||||
("←↓↑→".to_string(), "Move".to_string()),
|
||||
("n/Tab".to_string(), "Next Pane".to_string()),
|
||||
],
|
||||
InputMode::Pane => vec![
|
||||
("←↓↑→".to_string(), "Move focus".to_string()),
|
||||
("n".to_string(), "New".to_string()),
|
||||
("d".to_string(), "Down split".to_string()),
|
||||
("r".to_string(), "Right split".to_string()),
|
||||
("x".to_string(), "Close".to_string()),
|
||||
("f".to_string(), "Fullscreen".to_string()),
|
||||
("z".to_string(), "Frames".to_string()),
|
||||
("c".to_string(), "Rename".to_string()),
|
||||
("w".to_string(), "Floating Toggle".to_string()),
|
||||
("e".to_string(), "Embed Pane".to_string()),
|
||||
("p".to_string(), "Next".to_string()),
|
||||
],
|
||||
InputMode::Tab => vec![
|
||||
("←↓↑→".to_string(), "Move focus".to_string()),
|
||||
("n".to_string(), "New".to_string()),
|
||||
("x".to_string(), "Close".to_string()),
|
||||
("r".to_string(), "Rename".to_string()),
|
||||
("s".to_string(), "Sync".to_string()),
|
||||
("Tab".to_string(), "Toggle".to_string()),
|
||||
],
|
||||
InputMode::Scroll => vec![
|
||||
("↓↑".to_string(), "Scroll".to_string()),
|
||||
("PgDn/PgUp".to_string(), "Scroll Page".to_string()),
|
||||
("d/u".to_string(), "Scroll Half Page".to_string()),
|
||||
(
|
||||
"e".to_string(),
|
||||
"Edit Scrollback in Default Editor".to_string(),
|
||||
),
|
||||
("s".to_string(), "Enter search term".to_string()),
|
||||
],
|
||||
InputMode::EnterSearch => vec![("Enter".to_string(), "when done".to_string())],
|
||||
InputMode::Search => vec![
|
||||
("↓↑".to_string(), "Scroll".to_string()),
|
||||
("PgUp/PgDn".to_string(), "Scroll Page".to_string()),
|
||||
("u/d".to_string(), "Scroll Half Page".to_string()),
|
||||
("n".to_string(), "Search down".to_string()),
|
||||
("p".to_string(), "Search up".to_string()),
|
||||
("c".to_string(), "Case sensitivity".to_string()),
|
||||
("w".to_string(), "Wrap".to_string()),
|
||||
("o".to_string(), "Whole words".to_string()),
|
||||
],
|
||||
InputMode::RenameTab => vec![("Enter".to_string(), "when done".to_string())],
|
||||
InputMode::RenamePane => vec![("Enter".to_string(), "when done".to_string())],
|
||||
InputMode::Session => vec![("d".to_string(), "Detach".to_string())],
|
||||
InputMode::Tmux => vec![
|
||||
("←↓↑→".to_string(), "Move focus".to_string()),
|
||||
("\"".to_string(), "Split Down".to_string()),
|
||||
("%".to_string(), "Split Right".to_string()),
|
||||
("z".to_string(), "Fullscreen".to_string()),
|
||||
("c".to_string(), "New Tab".to_string()),
|
||||
(",".to_string(), "Rename Tab".to_string()),
|
||||
("p".to_string(), "Previous Tab".to_string()),
|
||||
("n".to_string(), "Next Tab".to_string()),
|
||||
],
|
||||
};
|
||||
|
||||
pub fn get_mode_info(
|
||||
mode: InputMode,
|
||||
attributes: &ClientAttributes,
|
||||
capabilities: PluginCapabilities,
|
||||
) -> ModeInfo {
|
||||
let keybinds = attributes.keybinds.to_keybinds_vec();
|
||||
let session_name = envs::get_session_name().ok();
|
||||
|
||||
ModeInfo {
|
||||
mode,
|
||||
keybinds,
|
||||
style,
|
||||
style: attributes.style,
|
||||
capabilities,
|
||||
session_name,
|
||||
}
|
||||
|
|
@ -174,3 +119,4 @@ pub fn cast_termwiz_key(event: KeyEvent, raw_bytes: &[u8]) -> Key {
|
|||
_ => Key::Esc, // there are other keys we can implement here, but we might need additional terminal support to implement them, not just exhausting this enum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use super::super::actions::*;
|
||||
use super::super::keybinds::*;
|
||||
use crate::data::Key;
|
||||
use crate::input::CharOrArrow;
|
||||
use crate::data::{CharOrArrow, Key};
|
||||
|
||||
#[test]
|
||||
fn merge_keybinds_merges_different_keys() {
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ fn three_panes_with_tab_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -213,7 +213,7 @@ fn three_panes_with_tab_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -222,7 +222,7 @@ fn three_panes_with_tab_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
],
|
||||
|
|
@ -319,7 +319,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -334,7 +334,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -343,7 +343,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
],
|
||||
|
|
@ -467,7 +467,7 @@ fn deeply_nested_tab_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(21.0)),
|
||||
split_size: Some(SplitSize::Percent(21)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -482,7 +482,7 @@ fn deeply_nested_tab_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(22.0)),
|
||||
split_size: Some(SplitSize::Percent(22)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -497,7 +497,7 @@ fn deeply_nested_tab_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(23.0)),
|
||||
split_size: Some(SplitSize::Percent(23)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -506,19 +506,19 @@ fn deeply_nested_tab_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(24.0)),
|
||||
split_size: Some(SplitSize::Percent(24)),
|
||||
run: None,
|
||||
},
|
||||
],
|
||||
split_size: Some(SplitSize::Percent(78.0)),
|
||||
split_size: Some(SplitSize::Percent(78)),
|
||||
run: None,
|
||||
},
|
||||
],
|
||||
split_size: Some(SplitSize::Percent(79.0)),
|
||||
split_size: Some(SplitSize::Percent(79)),
|
||||
run: None,
|
||||
},
|
||||
],
|
||||
split_size: Some(SplitSize::Percent(90.0)),
|
||||
split_size: Some(SplitSize::Percent(90)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -527,7 +527,7 @@ fn deeply_nested_tab_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(15.0)),
|
||||
split_size: Some(SplitSize::Percent(15)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -536,7 +536,7 @@ fn deeply_nested_tab_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(15.0)),
|
||||
split_size: Some(SplitSize::Percent(15)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -545,7 +545,7 @@ fn deeply_nested_tab_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(15.0)),
|
||||
split_size: Some(SplitSize::Percent(15)),
|
||||
run: None,
|
||||
},
|
||||
],
|
||||
|
|
@ -591,7 +591,7 @@ fn three_tabs_tab_one_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -638,7 +638,7 @@ fn three_tabs_tab_two_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -651,7 +651,7 @@ fn three_tabs_tab_two_merged_correctly() {
|
|||
run: None,
|
||||
},
|
||||
],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -698,7 +698,7 @@ fn three_tabs_tab_three_merged_correctly() {
|
|||
pane_name: None,
|
||||
focus: None,
|
||||
parts: vec![],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
@ -711,7 +711,7 @@ fn three_tabs_tab_three_merged_correctly() {
|
|||
run: None,
|
||||
},
|
||||
],
|
||||
split_size: Some(SplitSize::Percent(50.0)),
|
||||
split_size: Some(SplitSize::Percent(50)),
|
||||
run: None,
|
||||
},
|
||||
Layout {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
//! IPC stuff for starting to split things into a client and server model.
|
||||
|
||||
use crate::{
|
||||
cli::CliArgs,
|
||||
data::{ClientId, InputMode, Style},
|
||||
errors::{get_current_ctx, ErrorContext},
|
||||
input::{actions::Action, layout::LayoutFromYaml, options::Options, plugins::PluginsConfig},
|
||||
input::{
|
||||
actions::Action, keybinds::Keybinds, layout::LayoutFromYaml, options::Options,
|
||||
plugins::PluginsConfig,
|
||||
},
|
||||
pane_size::{Size, SizeInPixels},
|
||||
};
|
||||
use interprocess::local_socket::LocalSocketStream;
|
||||
|
|
@ -37,10 +39,11 @@ pub enum ClientType {
|
|||
Writer,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ClientAttributes {
|
||||
pub size: Size,
|
||||
pub style: Style,
|
||||
pub keybinds: Keybinds,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -155,7 +158,7 @@ impl<T: Serialize> IpcSenderWithContext<T> {
|
|||
/// Sends an event, along with the current [`ErrorContext`], on this [`IpcSenderWithContext`]'s socket.
|
||||
pub fn send(&mut self, msg: T) {
|
||||
let err_ctx = get_current_ctx();
|
||||
bincode::serialize_into(&mut self.sender, &(msg, err_ctx)).unwrap();
|
||||
rmp_serde::encode::write(&mut self.sender, &(msg, err_ctx)).unwrap();
|
||||
// TODO: unwrapping here can cause issues when the server disconnects which we don't mind
|
||||
// do we need to handle errors here in other cases?
|
||||
let _ = self.sender.flush();
|
||||
|
|
@ -193,7 +196,7 @@ where
|
|||
|
||||
/// Receives an event, along with the current [`ErrorContext`], on this [`IpcReceiverWithContext`]'s socket.
|
||||
pub fn recv(&mut self) -> Option<(T, ErrorContext)> {
|
||||
match bincode::deserialize_from(&mut self.receiver) {
|
||||
match rmp_serde::decode::from_read(&mut self.receiver) {
|
||||
Ok(msg) => Some(msg),
|
||||
Err(e) => {
|
||||
warn!("Error in IpcReceiver.recv(): {:?}", e);
|
||||
|
|
|
|||
|
|
@ -1,30 +1,23 @@
|
|||
pub mod data;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod channels;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod cli;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod consts;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod data;
|
||||
pub mod envs;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod errors;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod input;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod ipc;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod logging;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod pane_size;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod position;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod setup;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod shared;
|
||||
|
||||
// The following modules can't be used when targeting wasm
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod channels; // Requires async_std
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod errors; // Requires async_std (via channels)
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod ipc; // Requires interprocess
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod logging; // Requires log4rs
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub use ::{
|
||||
anyhow, async_std, clap, interprocess, lazy_static, libc, nix, regex, serde, serde_yaml,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Deserialize, Serialize)]
|
||||
#[derive(Debug, Hash, Copy, Clone, PartialEq, Eq, PartialOrd, Deserialize, Serialize)]
|
||||
pub struct Position {
|
||||
pub line: Line,
|
||||
pub column: Column,
|
||||
|
|
@ -30,7 +30,7 @@ impl Position {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, PartialOrd)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd)]
|
||||
pub struct Line(pub isize);
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, PartialOrd)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd)]
|
||||
pub struct Column(pub usize);
|
||||
|
|
|
|||
|
|
@ -1,94 +1,13 @@
|
|||
use crate::{
|
||||
cli::{CliArgs, Command},
|
||||
consts::{
|
||||
FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION,
|
||||
ZELLIJ_PROJ_DIR,
|
||||
},
|
||||
input::{
|
||||
config::{Config, ConfigError},
|
||||
layout::{LayoutFromYaml, LayoutFromYamlIntermediate},
|
||||
options::Options,
|
||||
theme::ThemesFromYaml,
|
||||
},
|
||||
};
|
||||
use clap::{Args, IntoApp};
|
||||
use clap_complete::Shell;
|
||||
use crate::consts::{SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, ZELLIJ_PROJ_DIR};
|
||||
use clap::Args;
|
||||
use directories_next::BaseDirs;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
convert::TryFrom, fmt::Write as FmtWrite, io::Write, path::Path, path::PathBuf, process,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
const CONFIG_LOCATION: &str = ".config/zellij";
|
||||
const CONFIG_NAME: &str = "config.yaml";
|
||||
static ARROW_SEPARATOR: &str = "";
|
||||
|
||||
#[cfg(not(test))]
|
||||
/// Goes through a predefined list and checks for an already
|
||||
/// existing config directory, returns the first match
|
||||
pub fn find_default_config_dir() -> Option<PathBuf> {
|
||||
default_config_dirs()
|
||||
.into_iter()
|
||||
.filter(|p| p.is_some())
|
||||
.find(|p| p.clone().unwrap().exists())
|
||||
.flatten()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn find_default_config_dir() -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Order in which config directories are checked
|
||||
fn default_config_dirs() -> Vec<Option<PathBuf>> {
|
||||
vec![
|
||||
home_config_dir(),
|
||||
Some(xdg_config_dir()),
|
||||
Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()),
|
||||
]
|
||||
}
|
||||
|
||||
/// Looks for an existing dir, uses that, else returns a
|
||||
/// dir matching the config spec.
|
||||
pub fn get_default_data_dir() -> PathBuf {
|
||||
[
|
||||
xdg_data_dir(),
|
||||
Path::new(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"),
|
||||
]
|
||||
.into_iter()
|
||||
.find(|p| p.exists())
|
||||
.unwrap_or_else(xdg_data_dir)
|
||||
}
|
||||
|
||||
pub fn xdg_config_dir() -> PathBuf {
|
||||
ZELLIJ_PROJ_DIR.config_dir().to_owned()
|
||||
}
|
||||
|
||||
pub fn xdg_data_dir() -> PathBuf {
|
||||
ZELLIJ_PROJ_DIR.data_dir().to_owned()
|
||||
}
|
||||
|
||||
pub fn home_config_dir() -> Option<PathBuf> {
|
||||
if let Some(user_dirs) = BaseDirs::new() {
|
||||
let config_dir = user_dirs.home_dir().join(CONFIG_LOCATION);
|
||||
Some(config_dir)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_layout_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> {
|
||||
config_dir.map(|dir| dir.join("layouts"))
|
||||
}
|
||||
|
||||
pub fn get_theme_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> {
|
||||
config_dir.map(|dir| dir.join("themes"))
|
||||
}
|
||||
|
||||
pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> {
|
||||
std::io::stdout().write_all(asset)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub const DEFAULT_CONFIG: &[u8] = include_bytes!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
|
|
@ -144,6 +63,15 @@ pub const ZSH_AUTO_START_SCRIPT: &[u8] = include_bytes!(concat!(
|
|||
"assets/shell/auto-start.zsh"
|
||||
));
|
||||
|
||||
pub fn get_theme_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> {
|
||||
config_dir.map(|dir| dir.join("themes"))
|
||||
}
|
||||
|
||||
pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> {
|
||||
std::io::stdout().write_all(asset)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn dump_default_config() -> std::io::Result<()> {
|
||||
dump_asset(DEFAULT_CONFIG)
|
||||
}
|
||||
|
|
@ -190,6 +118,88 @@ pub struct Setup {
|
|||
pub generate_auto_start: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn find_default_config_dir() -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
/// Goes through a predefined list and checks for an already
|
||||
/// existing config directory, returns the first match
|
||||
pub fn find_default_config_dir() -> Option<PathBuf> {
|
||||
default_config_dirs()
|
||||
.into_iter()
|
||||
.filter(|p| p.is_some())
|
||||
.find(|p| p.clone().unwrap().exists())
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Order in which config directories are checked
|
||||
#[allow(dead_code)]
|
||||
fn default_config_dirs() -> Vec<Option<PathBuf>> {
|
||||
vec![
|
||||
home_config_dir(),
|
||||
Some(xdg_config_dir()),
|
||||
Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()),
|
||||
]
|
||||
}
|
||||
|
||||
/// Looks for an existing dir, uses that, else returns a
|
||||
/// dir matching the config spec.
|
||||
pub fn get_default_data_dir() -> PathBuf {
|
||||
[
|
||||
xdg_data_dir(),
|
||||
Path::new(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"),
|
||||
]
|
||||
.into_iter()
|
||||
.find(|p| p.exists())
|
||||
.unwrap_or_else(xdg_data_dir)
|
||||
}
|
||||
|
||||
pub fn xdg_config_dir() -> PathBuf {
|
||||
ZELLIJ_PROJ_DIR.config_dir().to_owned()
|
||||
}
|
||||
|
||||
pub fn xdg_data_dir() -> PathBuf {
|
||||
ZELLIJ_PROJ_DIR.data_dir().to_owned()
|
||||
}
|
||||
|
||||
pub fn home_config_dir() -> Option<PathBuf> {
|
||||
if let Some(user_dirs) = BaseDirs::new() {
|
||||
let config_dir = user_dirs.home_dir().join(CONFIG_LOCATION);
|
||||
Some(config_dir)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_layout_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> {
|
||||
config_dir.map(|dir| dir.join("layouts"))
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub use not_wasm::*;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
mod not_wasm {
|
||||
use super::*;
|
||||
use crate::{
|
||||
cli::{CliArgs, Command},
|
||||
consts::{FEATURES, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION},
|
||||
input::{
|
||||
config::{Config, ConfigError},
|
||||
layout::{LayoutFromYaml, LayoutFromYamlIntermediate},
|
||||
options::Options,
|
||||
theme::ThemesFromYaml,
|
||||
},
|
||||
};
|
||||
use clap::IntoApp;
|
||||
use clap_complete::Shell;
|
||||
use std::{convert::TryFrom, fmt::Write as FmtWrite, io::Write, path::PathBuf, process};
|
||||
|
||||
const CONFIG_NAME: &str = "config.yaml";
|
||||
static ARROW_SEPARATOR: &str = "";
|
||||
|
||||
impl Setup {
|
||||
/// Entrypoint from main
|
||||
/// Merges options from the config file and the command line options
|
||||
|
|
@ -231,16 +241,17 @@ impl Setup {
|
|||
|
||||
let config_options = Options::from_cli(&config.options, opts.command.clone());
|
||||
|
||||
let layout_dir = config_options
|
||||
.layout_dir
|
||||
.clone()
|
||||
.or_else(|| get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir)));
|
||||
let layout_dir = config_options.layout_dir.clone().or_else(|| {
|
||||
get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir))
|
||||
});
|
||||
let chosen_layout = opts
|
||||
.layout
|
||||
.clone()
|
||||
.or_else(|| config_options.default_layout.clone());
|
||||
let layout_result =
|
||||
LayoutFromYamlIntermediate::from_path_or_default(chosen_layout.as_ref(), layout_dir);
|
||||
let layout_result = LayoutFromYamlIntermediate::from_path_or_default(
|
||||
chosen_layout.as_ref(),
|
||||
layout_dir,
|
||||
);
|
||||
let layout = match layout_result {
|
||||
None => None,
|
||||
Some(Ok(layout)) => Some(layout),
|
||||
|
|
@ -348,7 +359,10 @@ impl Setup {
|
|||
Ok((config, layout, config_options))
|
||||
}
|
||||
|
||||
pub fn check_defaults_config(opts: &CliArgs, config_options: &Options) -> std::io::Result<()> {
|
||||
pub fn check_defaults_config(
|
||||
opts: &CliArgs,
|
||||
config_options: &Options,
|
||||
) -> std::io::Result<()> {
|
||||
let data_dir = opts.data_dir.clone().unwrap_or_else(get_default_data_dir);
|
||||
let config_dir = opts.config_dir.clone().or_else(find_default_config_dir);
|
||||
let plugin_dir = data_dir.join("plugins");
|
||||
|
|
@ -360,7 +374,8 @@ impl Setup {
|
|||
.theme_dir
|
||||
.clone()
|
||||
.or_else(|| get_theme_dir(config_dir.clone()));
|
||||
let system_data_dir = PathBuf::from(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij");
|
||||
let system_data_dir =
|
||||
PathBuf::from(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij");
|
||||
let config_file = opts
|
||||
.config
|
||||
.clone()
|
||||
|
|
@ -600,3 +615,4 @@ mod setup_test {
|
|||
assert_eq!(config, goal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,24 @@ use std::{iter, str::from_utf8};
|
|||
use crate::data::{Palette, PaletteColor, PaletteSource, ThemeHue};
|
||||
use crate::envs::get_session_name;
|
||||
use colorsys::Rgb;
|
||||
use strip_ansi_escapes::strip;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use unix_only::*;
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix_only {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
use strip_ansi_escapes::strip;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
pub fn set_permissions(path: &Path, mode: u32) -> io::Result<()> {
|
||||
let mut permissions = fs::metadata(path)?.permissions();
|
||||
permissions.set_mode(mode);
|
||||
fs::set_permissions(path, permissions)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ansi_len(s: &str) -> usize {
|
||||
from_utf8(&strip(s).unwrap()).unwrap().width()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue