Compare commits

..

136 Commits

Author SHA1 Message Date
Johannes Altmanninger
56bbdb3f39 Release 4.0.9
Created by ./build_tools/release.sh 4.0.9
2025-09-27 20:24:24 +02:00
Johannes Altmanninger
ce4aa7669d Release workflow fixups 2025-09-27 20:20:44 +02:00
Johannes Altmanninger
af8d8d3d1b release-notes.sh: add stats, round off committer list
Instead of adding these to the Markdown directly, add it to the
fake CHANGELOG.rst source, which makes escaping easier, and allows
generating other formats than Markdown in future.

(cherry picked from commit 06bbac8ed6)
2025-09-27 14:23:25 +02:00
Johannes Altmanninger
fecd0b4bf1 Revert "Only load sphinx_markdown_builder extension if it's used"
sphinx-build fails with

	sphinx.errors.SphinxError: Builder name markdown not registered or available through entry point

Apparently this issue was hidden locally by caching, and not checked
in CI because of this error causing
tests/checks/sphinx-markdown-changelog.fish to be skipped.

	sphinx-build 7.2.6
	runner@runnervm3ublj:~/work/fish-shell/fish-shell$ python -c 'import sphinx_markdown_builder'
	Traceback (most recent call last):
	  File "<string>", line 1, in <module>
	  File "/home/runner/.local/lib/python3.12/site-packages/sphinx_markdown_builder/__init__.py", line 6, in <module>
	    from sphinx.util.typing import ExtensionMetadata
	ImportError: cannot import name 'ExtensionMetadata' from 'sphinx.util.typing' (/usr/lib/python3/dist-packages/sphinx/util/typing.py)

This reverts commit 7b495497d7.

While at it, fail the test earlier if something went wrong, because the
remaining check will likely also fail and confuse.

(cherry picked from commit aab22a453b)
2025-09-27 14:23:25 +02:00
Johannes Altmanninger
2d4a43302a release-notes.sh: remove line breaks from generated Markdown, for GitHub
GitHub-flavored Markdown translates line breaks to <br/>, which does
not match our intent. Work around that by joining lines when producing
Markdown output.

(cherry picked from commit 4cc2d2ec30)
2025-09-27 14:23:25 +02:00
Johannes Altmanninger
5a4a913220 Enable sphinx parallelism also when building markdown release notes
We get a warning about sphinx_markdown_builder not being
parallelizable. Fix that.

Ref: https://github.com/liran-funaro/sphinx-markdown-builder/pull/38
(cherry picked from commit e6541c5c93)
2025-09-27 06:03:16 +02:00
Johannes Altmanninger
904ceba858 Only load sphinx_markdown_builder extension if it's used
As is, building man pages or HTML docs while sphinx_markdown_builder
is installed, will result in unrelated warnings.  Remove those by
removing it from the extensions config.  Markdown building (only used
for changelog) seems to work without this just fine.

(cherry picked from commit 7b495497d7)
2025-09-27 06:03:16 +02:00
Johannes Altmanninger
3c2adfbd4b Fix markdown changelog generation test
System tests typically run outside the workspace directory, but they
still have read-only access to the workspace; fix it accordingly.
This test only works on git checkouts, not in tarballs, so skip it
if .git doesn't exist.

(cherry picked from commit 6e90d9bd6f)
2025-09-27 06:03:16 +02:00
Johannes Altmanninger
9e4850b40a Disable sphinx-markdown-builder in tests again for now
Somehow I didn't realize this breaks the tests/checks/sphinx-* tests.

(cherry picked from commit b37b57781b)
2025-09-27 06:03:16 +02:00
Johannes Altmanninger
211b3f6670 release workflow: credit contributors in release notes
While at it, do a 's/^--$/^---/' to fix Markdown syntax for horizontal
line for CommonMark-based parsers.

(cherry picked from commit 22ffc31b71)
2025-09-24 15:58:42 +02:00
Johannes Altmanninger
d530e127f5 Test markdown changelog creation in CI
Extract a github action to install the same version used in the release
workflow.  In future we should probably migrate to requirements.txt
or similar.

(cherry picked from commit 127c02992d)
2025-09-24 15:58:42 +02:00
Johannes Altmanninger
a6698098db release workflow: resolve relative references in changelog properly
Instead of having sphinx-build only build CHANGELOG.rst, build the
entire thing, so relative references (":doc:", ":ref:") can be resolved
properly.  Replace our sed-based hacks with 'markdown_http_base'.

(cherry picked from commit 765ca54d59)
2025-09-24 15:58:42 +02:00
Johannes Altmanninger
f73b260a3a release workflow: extract script for generating markdown release notes
(cherry picked from commit 1519ea74be)
2025-09-24 15:58:42 +02:00
Daniel Rainer
ffa7abd6ff Fix release workflow syntax
The previous version results in an immediate workflow failure due to a
syntax error in the YAML. `workflow_dispatch` should be a dictionary
key, with its value being another dictionary (whose only key is `inputs`
at the moment).

(cherry picked from commit c8f31ceedb)
2025-09-24 15:58:42 +02:00
Johannes Altmanninger
b87ef689fa github release workflow: fix structure
(cherry picked from commit b1d1ef1b6e)
2025-09-24 15:58:42 +02:00
Johannes Altmanninger
4fe70f6965 github release workflow: only run on explicit dispatch
Release automation can be tested on any GitHub fork, using

	build_tools/release.sh $version $repository_owner $git_remote

which should work perfectly except for macOS packages (which fail
unless provided GitHub secrets).

People might push tags to their forks, both non-release tags (which
would trigger an early failure in "is-release-tag") or replicas of
our actual release tags (which would create a draft release etc. and
only fail when building macOS packages).

Run on explicit workflow dispatch to make sure it's not triggered by
accident like that.

This means that we'll use the .github/workflows/release.yml from
the default branch (i.e. master), so try to make sure it matches the
version in the release, to prevent accidents.

Closes #11816

(cherry picked from commit 01361b9217)
2025-09-24 15:58:42 +02:00
Johannes Altmanninger
7001abca9f github release workflow: use github context only if needed
(cherry picked from commit dab8df1a18)
2025-09-24 15:58:42 +02:00
Johannes Altmanninger
199316f1a3 build_tools/make_macos_pkg.sh: fix inconsistent version computation
make_tarball.sh and others do it differently.

(cherry picked from commit c771ff06d4)
2025-09-24 15:58:42 +02:00
David Adam
78c9ab29cd debian packaging: don't remove Cargo.toml.orig files
Newer versions of cargo include the Cargo.toml.orig file when vendoring,
but dh_clean removes those by default. Try to disable this to fix the
package builds again.

(cherry picked from commit 67e8657109)
2025-09-21 10:41:44 +08:00
David Adam
91c9dbdd89 debian packaging: use the correct test target
(cherry picked from commit 5e2ddaace9)
2025-09-21 10:41:44 +08:00
David Adam
e737ad1f0f debian packaging: fix typo 2025-09-20 22:11:10 +08:00
Johannes Altmanninger
6f536c6304 Fix new job control test; changelog 2025-09-20 14:57:49 +02:00
Piotr Kubaj
a1b4b391b2 path.rs: fix build on ARM / POWER
error[E0308]: mismatched types
   --> src/path.rs:749:13
    |
748 |         let remoteness = remoteness_via_statfs(
    |                          --------------------- arguments to this function are incorrect
749 |             libc::statfs,
    |             ^^^^^^^^^^^^ expected fn pointer, found fn item
    |
    = note: expected fn pointer `unsafe extern "C" fn(*const i8, _) -> _`
                  found fn item `unsafe extern "C" fn(*const u8, _) -> _ {libc::statfs}`
note: function defined here
   --> src/path.rs:712:12
    |
712 |         fn remoteness_via_statfs<StatFS, Flags>(
    |            ^^^^^^^^^^^^^^^^^^^^^
713 |             statfn: unsafe extern "C" fn(*const i8, *mut StatFS) -> libc::c_int,
    |             -------------------------------------------------------------------

error[E0308]: mismatched types
   --> src/path.rs:725:34
    |
725 |             if unsafe { (statfn)(path.as_ptr(), buf.as_mut_ptr()) } < 0 {
    |                         -------- ^^^^^^^^^^^^^ expected `*const i8`, found `*const u8`
    |                         |
    |                         arguments to this function are incorrect
    |
    = note: expected raw pointer `*const i8`
               found raw pointer `*const u8`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `fish` (lib) due to 2 previous errors

(cherry picked from commit 91ee45b0e1)
2025-09-20 14:32:55 +02:00
Johannes Altmanninger
a27f615350 Fix crash on "bg" of non-job-controlled job
fish -c 'sleep 1 & bg %1' is supposed to fail because the job is not
under job control.

When we try to print the failure, we accidentally still
hold a borrow of the job list.  This blows up because we use
"builtin_print_help_error()" to print the failure message; that
function runs "job_reap()" which wants an exclusive borrow of the
job list. Let's drop our job list earlier.

(cherry picked from commit 26116b477e)
2025-09-20 14:03:49 +02:00
Johannes Altmanninger
46ce8a1d2f Fix regression breaking self-insert of kitty shifted codepoint
Commit 50a6e486a5 (Allow explicit shift modifier for non-ASCII
letters, fix capslock behavior, 2025-03-30) delayed handling of kitty
keyboard protocol's shifted codepoints.  It does handle shifted
codepoints when matching keys to mappings; but it fails to handle
them in the self-insert code paths where we want to insert the text
represented by CharEvent::Key.
Fix it by resolving the shifted key.

Fixes #11813

(cherry picked from commit bb916f8d73)
2025-09-20 14:03:38 +02:00
Johannes Altmanninger
03ccc88868 Move key codepoint computation to key event
For the next commit.

(cherry picked from commit 8d12dfe065)
2025-09-20 14:02:30 +02:00
Johannes Altmanninger
79cf4c3e0b build_tools/release.sh: approve macos-codesign 2025-09-19 09:53:02 +02:00
Johannes Altmanninger
701e1a3b02 github release workflow: make sure that last changelog entry isn't given spurious markup
(cherry picked from commit ff633bd744)
2025-09-18 10:46:40 +02:00
Johannes Altmanninger
b1ec703ceb Release 4.0.8
Created by ./build_tools/release.sh 4.0.8
2025-09-18 10:00:03 +02:00
Johannes Altmanninger
553612f74a github release workflow: work around trailing "---"-line in changelog
The extracted release notes trigger a sphinx warning

	/tmp/tmp.V6RGP92nc2/src/index.rst:6:Document may not end with a transition.

which we don't seem to get on the full CHANGELOG.rst.
Let's work around that for now.

(cherry picked from commit 0d46c26988)
2025-09-18 09:58:55 +02:00
Johannes Altmanninger
861decb003 build_tools/release.sh: relax assertion about changelog title
I'd like to move to a process where everything goes into master first,
and then flows downstream to any release branches (i.e. no merging
of Integration_* branches back into master).

The only thing we need to change for that is to add release notes for
patch releases eagerly on master.  That implies that we want to use
the actual version instead of ???.  (Only if something goes wrong
in the release process, we need to change this on both branches,
but that should happen too often.)

(cherry picked from commit 1840df96a2)
2025-09-18 09:38:30 +02:00
Johannes Altmanninger
3b8780aa6c build_tools/release.sh: push to the integration branch when all goes well
Also, ignore any "sendemail.to" Git config, and remove a temporary
statement in release notes.

(cherry picked from commit 3456b33050)
2025-09-18 09:32:37 +02:00
Johannes Altmanninger
7783505bee Fix new-style bindings shadowing raw escape sequence bindings
Given

	bind up "echo user up, new notation"
	bind \e\[A "echo user up, legacy notation"

prior to b9d9e7edc6 (Match bindings with explicit shift
first, 2025-05-24), we prioritized the legacy notation because
input_mapping_insert_sorted() makes us try longer sequences first --
and "up" is only one key compared to the three-key legacy sequence.

This prioritization was broken in b9d9e7edc6, causing plugins that
update to the "bind up" notation to break users who haven't (#11803).

Even worse, it caused preset bindings to shadow user ones:

	bind --preset up "echo preset up, new notation"
	bind \e\[A "echo user up, legacy notation"

Restore backwards compatibility by treating matches against legacy
notation like exact matches again.

(cherry picked from commit 9a04c15894)
2025-09-18 09:29:45 +02:00
Johannes Altmanninger
23c25ffe43 build_tools/release.sh: fixes for updating fish-site
Also check that "cd fish-site && make && make new-release" doesn't
leave behind untracked files we're not aware of.  This implies that
this script ought to refuse to run if there are untracked files,
at least in fish-site.

(cherry picked from commit 529f722d2f)
2025-09-13 11:13:17 +02:00
Johannes Altmanninger
7619fa316c Release 4.0.6
Created by ./build_tools/release.sh 4.0.6
2025-09-12 11:47:41 +02:00
Johannes Altmanninger
e2005c64b3 Backport release-script related changes from master
Will commit these to master momentarily (#10449).
2025-09-12 11:47:01 +02:00
Johannes Altmanninger
9ada3e6c16 Group changelog entries 2025-09-12 11:14:52 +02:00
Johannes Altmanninger
bdba2c227d CHANGELOG: update 2025-09-11 13:55:30 +02:00
Johannes Altmanninger
201882e72a CHANGELOG: fix inline literal RST syntax 2025-09-09 07:46:31 +02:00
Johannes Altmanninger
1db0ff9f77 Allow overriding __fish_update_cwd_osc to work around terminal bugs
See #11777

While at it, pull in the TERM=dumb check from master.

(cherry picked from commit 898cc3242b)
2025-09-05 09:39:57 +02:00
Johannes Altmanninger
9258275fe6 config_paths: fix compiled-in locale dir for installed, non-embed builds
Commit bf65b9e3a7 (Change `gettext` paths to be relocatable (#11195),
2025-03-30) broke the locale path.

Commit c3740b85be (config_paths: fix compiled-in locale dir,
2025-06-12) fixed what it calls "case 4", but "case 2" is also
affected; fix that. Before/after:

	$ ~/.local/opt/fish/bin/fish -d config
	paths.locale: /home/johannes/.local/opt/fish/share/fish/locale
	$ ~/.local/opt/fish/bin/fish -d config
	paths.locale: /home/johannes/.local/opt/fish/share/locale

See https://github.com/fish-shell/fish-shell/issues/11683#issuecomment-3218190662

(cherry picked from commit 21a07f08a3)
2025-08-29 19:29:03 +02:00
Johannes Altmanninger
6900b89c82 Add mark-prompt feature flag
So far, terminals that fail to parse OSC sequences are the only reason
for wanting to turn off OSC 133.  Let's allow to work around it by
adding a feature flag (which is implied to be temporary).

To use it, run this once, and restart fish:

    set -Ua fish_features no-mark-prompt

Tested with

    fish -i | string escape | grep 133 &&
    ! fish_features=no-mark-prompt fish -i | string escape | grep 133

See #11749
Also #11609
2025-08-27 09:17:35 +02:00
Johannes Altmanninger
9c0086b7af Backport default alt-delete binding
This is standard on macOS and in chrome/firefox.

On master, this was sneakily added in
2bb5cbc959 (Default bindings for token movements v2, 2025-03-04)
and before that in
6af96a81a8 (Default bindings for token movement commands, 2024-10-05)

Ref: https://lobste.rs/s/ndlwoh/wizard_his_shell#c_qvhnvd
2025-08-02 09:53:22 +02:00
Johannes Altmanninger
e200abe39c __fish_seen_subcommand_from: fix regression causing false negatives given multiple arguments
Fixes 2bfa7db7bc (Restructure __fish_seen_subcommand_from, 2024-07-07)
Fixes #11685

(cherry picked from commit 4412164fd4)
2025-07-26 20:36:53 +02:00
Johannes Altmanninger
0c8f1f4220 Fix async-signal safety in SIGTERM handler
Cherry-picked from
- 941701da3d (Restore some async-signal discipline to SIGTERM, 2025-06-15)
- 81d45caa76e (Restore terminal state on SIGTERM again, 2025-06-21)

Also, be more careful in terminal_protocols_disable_ifn about accessing
reader_current_data(), as pointed out in 65a4cb5245 (Revert "Restore terminal
state on SIGTERM again", 2025-07-19).

See #11597
2025-07-26 13:09:18 +02:00
Johannes Altmanninger
e593da1c2e Increase timeout when reading escape sequences inside paste/kitty kbd
Historically, fish has treated input bytes [0x1b, 'b'] as alt-b (rather than
"escape,b") if the second byte arrives within 30ms of the first.

Since we made builtin bind match key events instead of raw byte sequences,
we have another place where we do similar disambiguation: when we read keys
such as alt-left ("\e[1;3D"), we only consider bytes to be part of this
sequence if stdin is immediately readable (actually "readable after a 1ms
timeout" since e1be842 (Work around torn byte sequences in qemu kbd input
with 1ms timeout, 2025-03-04)).

This is technically wrong but has worked in practice (for Kakoune etc.).

Issue #11668 reports two issues on some Windows terminals feeding a remote
fish shell:
- the "bracketed paste finished" sequence may be split into multiple packets,
  which causes a delay of > 1ms between individual bytes being readable.
- AutoHotKey scripts simulating seven "left" keys result in sequence tearing
  as well.

Try to fix the paste case by increasing the timeout when parsing escape
sequences.

Also increase the timeout for terminals that support the kitty keyboard
protocol.  The user should only notice this new delay after pressing one of
escape,O, escape,P, escape,[, or escape,] **while the kitty keyboard protocol
is disabled** (e.g. while an external command is running).  In this case,
the fish_escape_delay_ms is also virtually increased; hopefully this edge
case is not ever relevant.

Part of #11668

(cherry picked from commit 30ff3710a0)
2025-07-25 18:29:19 +02:00
Johannes Altmanninger
6666c8f1cd Block interrupts and uvar events while decoding key
readb() has only one caller that passes blocking=false: try_readb().
This function is used while decoding keys; anything but a successful read
is treated as "end of input sequence".

This means that key input sequences such as \e[1;3D
can be torn apart by
- signals (EINTR) which is more likely since e1be842 (Work around torn byte
  sequences in qemu kbd input with 1ms timeout, 2025-03-04).
- universal variable notifications (from other fish processes)

Fix this by blocking signals and not listening on the uvar fd.  We do something
similar when matching key sequences against bindings, so extract a function
and use it for key decoding too.

Ref: https://github.com/fish-shell/fish-shell/issues/11668#issuecomment-3101341081
(cherry picked from commit da96172739)
2025-07-25 18:21:27 +02:00
Johannes Altmanninger
3e61036911 Revert "Change readch() into try_readch()"
try_readch() was added to help a fuzzing harness, specifically to avoid a
call to `unreachable!()` in the NothingToRead case.  I don't know much about
that but it seems like we should find a better way to tell the fuzzer that
this can't happen.

Fortunately the next commit will get rid of readb()'s "blocking" argument,
along the NothingToRead enum variant. So we'll no longer need this.

This reverts commit b92830cb17.

(cherry picked from commit fb7ee0db74)
2025-07-25 18:21:27 +02:00
Johannes Altmanninger
f23a479b81 Reduce MaybeUninit lifetime
(cherry picked from commit 137f220225)
2025-07-25 18:21:27 +02:00
王宇逸
e274ff41d0 Use uninit instead of zeroed (src/input_common.rs)
(cherry picked from commit 7c2c7f5874)
2025-07-25 18:21:27 +02:00
Johannes Altmanninger
d2af306f3d Fix some unused-ControlFlow warnings 2025-07-25 18:11:04 +02:00
Johannes Altmanninger
0e7c7f1745 Remove unused import
(cherry picked from commit 07ff4e7df0)
2025-07-25 11:57:21 +02:00
Johannes Altmanninger
3fada80553 Fix regression causing \e[ to be interpreted as ctrl-[
Fixes 3201cb9f01 (Stop parsing invalid CSI/SS3 sequences as alt-[/alt-o,
2024-12-30).

(cherry picked from commit 43d583d991)
2025-07-25 11:49:07 +02:00
Johannes Altmanninger
c7d4acbef8 Update changelog 2025-06-29 16:01:44 +02:00
Johannes Altmanninger
e204a4c126 Add ctrl-alt-h compatibility binding
Historically, ctrl-i sends the same code as tab, ctrl-h sends backspace and
ctrl-j and ctrl-m behave like enter.

Even for terminals that send unambiguous encodings (via the kitty keyboard
protocol), we have kept bindings like ctrl-h, to support existing habits.

We forgot that pressing alt-ctrl-h would behave like alt-backspace (and can
be easier to reach) so maybe we should add that as well.

Don't add ctrl-shift-i because at least on Linux, that's usually intercepted
by the terminal emulator.

Technically there are some more such as "ctrl-2" (which used to do the same as
"ctrl-space") but I don't think anyone uses that over "ctrl-space".

Closes #https://github.com/fish-shell/fish-shell/discussions/11548

(cherry picked from commit 4d67ca7c58)
2025-06-28 14:20:03 +02:00
Johannes Altmanninger
9af33802ec Use statvfs on NetBSD again to fix build
From commit ba00d721f4 (Correct statvfs call to statfs, 2025-06-19):

> This was missed in the Rust port

To elaborate:

- ec176dc07e (Port path.h, 2023-04-09) didn't change this (as before,
 `statvfs` used `ST_LOCAL` and `statfs` used `MNT_LOCAL`)
- 6877773fdd (Fix build on NetBSD (#10270), 2024-01-28) changed the `statvfs`
  call to `statfs`, presumably due to the libc-wrapper for
  `statvfs` being missing on NetBSD.  This change happens
  to work fine on NetBSD because they do [`#define ST_LOCAL
  MNT_LOCAL`](https://github.com/fish-shell/fish-shell/pull/11486#discussion_r2092408952)
  But it was wrong on others like macOS and FreeBSD, which was fixed by
  ba00d721f4 (but that broke the build on NetBSD).
- 7228cb15bf (Include sys/statvfs.h for the definition of ST_LOCAL (Rust
  port regression), 2025-05-16)
  fixed a code clone left behind by the above commit (incorrectly assuming
  that the clone had always existed.)

Fix the NetBSD build specifically by using statfs on that platform.

Note that this still doesn't make the behavior equivalent to commit LastC++11.
That one used ST_LOCAL if defined, and otherwise MNT_LOCAL if defined.

If we want perfect equivalence, we could detect both flags in `src/build.rs`.
Then we would also build on operating systems that define neither. Not sure.

Closes #11596

(cherry picked from commit 6644cc9b0e)
2025-06-28 11:46:25 +02:00
王宇逸
eecf0814a1 Use uninit instead of zeroed (cherry-pikcked only the change to src/path.rs)
(cherry picked from commit 7c2c7f5874)
2025-06-28 11:46:25 +02:00
Johannes Altmanninger
1ceebdf580 Fix some CSI commands being sent to old midnight commander
Commit 97581ed20f (Do send bracketed paste inside midnight commander,
2024-10-12) accidentally started sending CSI commands such as "CSI >5;0m",
which we intentionally didn't do for some old versions of Midnight Commander,
which fail to parse them. Fix that.

Fixes #11617
2025-06-25 13:36:03 +02:00
Johannes Altmanninger
335f91babd completions/git: fix spurious error when no subcommand is in $PATH
Systems like NixOS might not have "git-receive-pack" or any other "git-*"
executable in in $PATH -- instead they patch git to use absolute paths.
This is weird. But no reason for us to fail. Silence the error.

Fixes #11590

(cherry picked from commit 4f46d369c4)
2025-06-24 11:59:57 +08:00
Johannes Altmanninger
ec66749369 __fish_complete_list: only unescape "$(commandline -t)"
Commit cd3da62d24 (fix(completion): unescape strings for __fish_complete_list,
2024-09-17) bravely addressed an issue that exists in a lot of completions.
It did so only for __fish_complete_list. Fair enough.

Unfortunately it unescaped more than just "$(commandline -t)".

This causes the problem described at
https://github.com/fish-shell/fish-shell/issues/11508#issuecomment-2889088934
where completion descriptions containing a backslash followed by "n" are
interpreted as newlines, breaking the completion parser.  Fix that.

(cherry picked from commit 60881f1195)
2025-06-21 18:58:33 +02:00
Johannes Altmanninger
6e9e33d81d __fish_complete_list: strip "--foo=" prefix from replacing completions
Given a command line like

	foo --foo=bar=baz=qux\=argument

(the behavior is the same if '=' is substituted with ':').

fish completes arguments starting from the last unescaped separator, i.e.

	foo --foo=bar=baz=qux\=argument
			 ^
__fish_complete_list provides completions like

	printf %s\n (commandline -t)(printf %s\n choice1 choice2 ...)

This means that completions include the "--foo=bar=baz=" prefix.

This is wrong. This wasn't a problem until commit f9febba (Fix replacing
completions with a -foo prefix, 2024-12-14), because prior to that, replacing
completions would replace the entire token.
This made it too hard to writ ecompletions like

	complete -c foo -s s -l long -xa "hello-world goodbye-friend"

that would work with "foo --long fri" as well as "foo --long=frie".
Replacing the entire token would only work if the completion included that
prefix, but the above command is supposed to just work.
So f9febba made us replace only the part after the separator.

Unfortunately that caused the earlier problem.  Work around this.  The change
is not pretty, but it's a compromise until we have a better way of telling
which character fish considers to be the separator.

Fixes #11508

(cherry picked from commit 320ebb6859)
2025-06-21 18:58:17 +02:00
Peter Ammon
f3ebc68d5d Correct statvfs call to statfs
This was missed in the Rust port - C++ had statfs for MNT_LOCAL and not statvfs.
The effect of this is that fish never thought its filesystem was local on macOS
or BSDs (Linux was OK). This caused history race tests to fail, and also could
in rare cases result in history items being dropped with multiple concurrent
sessions.

This fixes the history race tests under macOS and FreeBSD - we weren't locking
because we thought the history was a remote file.

Cherry-picked from ba00d721f4
2025-06-19 15:36:19 -07:00
Johannes Altmanninger
4be17bfefb fixup! Extract config path module. NFC
Fix cargo (non-cmake) build.
2025-06-13 12:17:10 +02:00
Johannes Altmanninger
6fd0025f38 Make LOCALEDIR relocatable as well
As explained in c3740b85be (config_paths: fix compiled-in locale dir,
2025-06-12), fish is "relocatable", i.e. "mv /usr/ /usr2/" will leave
"/usr2/bin/fish" fully functional.

There is one exception: for LOCALEDIR we always use the path determined at
compile time.
This seems wrong; let's use the same relocatable-logic as for other paths.

Inspired by bf65b9e3a7 (Change `gettext` paths to be relocatable (#11195),
2025-03-30).
2025-06-13 11:52:46 +02:00
Johannes Altmanninger
052fc18db9 Extract config path module. NFC
This is the "code movement" part of bf65b9e3 ("Change `gettext` paths to be
relocatable (#11195)").

While at it, fix some warnings.
2025-06-13 11:52:46 +02:00
Integral
63a08e53e5 Replace some PathBuf with Path avoid unnecessary heap allocation (#10929)
(cherry picked from commit b19a467ea6)
2025-06-11 11:38:53 +02:00
Erick Howard
62ac23453e Code cleanup in src/bin/fish.rs to make it more idiomatic (#10975)
Code cleanup in `src/bin/fish.rs` to make it more idiomatic

(cherry picked from commit 967c4b2272)
2025-06-11 11:31:05 +02:00
Johannes Altmanninger
c052beb4dd Fix build on Rust 1.70 2025-06-10 17:56:21 +02:00
Johannes Altmanninger
e0cabacdaa kitty keyboard protocol: fall back to base layout key
On terminals that do not implement the kitty keyboard protocol "ctrl-ц" on
a Russian keyboard layout generally sends the same byte as "ctrl-w". This
is because historically there was no standard way to encode "ctrl-ц",
and the "ц" letter happens to be in the same position as "w" on the PC-101
keyboard layout.

Users have gotten used to this, probably because many of them are switching
between a Russian (or Greek etc.) and an English layout.

Vim/Emacs allow opting in to this behavior by setting the "input method"
(which probably means "keyboard layout").

Match key events that have the base layout key set against bindings for
that key.

Closes #11520

---

Alternatively, we could add the relevant preset bindings (for "ctrl-ц" etc.)
but
1. this will be wrong if there is a disagreement on the placement of "ц" between two layouts
2. there are a lot of them
3. it won't work for user bindings (for better or worse)

(cherry picked from commit 7a79728df3)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
59b9f57802 fish_key_reader: unopinionated description for bind notation variants
As explained in the parent commit, "alt-+" is usually preferred over
"alt-shift-=" but both have their moments. We communicate this via a comment
saying "# recommended notation". This is not always true and not super helpful,
especially as we add a third variant for #11520 (physical key), which is
the recommended one for users who switch between English and Cyrillic layouts.

Only explain what each variant does. Based on this the user may figure out
which one to use.

(cherry picked from commit 4cbd1b83f1)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
65fc2b539c Fix some invalid assertions parsing keys
For example the terminal sending « CSI 55296 ; 5 u » would crash fish.

(cherry picked from commit c7391d1026)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
3f1add9e21 Sanitize some inputs in CSI parser
This was copied from C++ code but we have overflow checks, which
forces us to actually handle errors.

While at it, add some basic error logging.

Fixes #11092

(cherry picked from commit 4c28a7771e)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
68d2cafa6e input: remove unnecessary check in bracketed paste code path
When "self.paste_is_buffering()" is true, "parse_escape_sequence()" explicitly
returns "None" instead of "Some(Escape)".  This is irrelevant because this
return value is never read, as long as "self.paste_is_buffering()" remains
true until "parse_escape_sequence()" returns, because the caller will return
early in that case. Paste buffering only ends if we actually read a complete
escape sequence (for ending bracketed paste).

Remove this extra branch.

(cherry picked from commit e5fdd77b09)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
b9d9e7edc6 Match bindings with explicit shift first
The new key notation canonicalizes aggressively, e.g.  these two bindings
clash:

	bind ctrl-shift-a something
	bind shift-ctrl-a something else

This means that key events generally match at most one active binding that
uses the new syntax.

The exception -- two coexisting new-syntax binds that match the same key
event -- was added by commit 50a6e486a5 (Allow explicit shift modifier for
non-ASCII letters, fix capslock behavior, 2025-03-30):

	bind ctrl-A 'echo A'
	bind ctrl-shift-a 'echo shift-a'

The precedence was determined by definition order.
This doesn't seem very useful.

A following patch wants to resolve #11520 by matching "ctrl-ц" events against
"ctrl-w" bindings. It would be surprising if a "ctrl-w" binding shadowed a
"ctrl-ц" one based on something as subtle as definition order.  Additionally,
definition order semantics (which is an unintended cause of the implementation)
is not really obvious.  Reverse definition order would make more sense.

Remove the ambiguity by always giving precedence to bindings that use
explicit shift.

Unrelated to this, as established in 50a6e486a5, explicit shift is still
recommended for bicameral letters but not typically for others -- e.g. alt-+
is typically preferred over alt-shift-= because the former also works on a
German keyboard.

See #11520

(cherry picked from commit 08c8afcb12)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
6b17ec7dae Allow explicit shift modifier for non-ASCII letters, fix capslock behavior
We canonicalize "ctrl-shift-i" to "ctrl-I".
Both when deciphering this notation (as given to builtin bind),
and when receiving it as a key event ("\e[105;73;6u")

This has problems:

A. Our bind notation canonicalization only works for 26 English letters.
   For example, "ctrl-shift-ä" is not supported -- only "ctrl-Ä" is.
   We could try to fix that but this depends on the keyboard layout.
   For example "bind alt-shift-=" and "bind alt-+" are equivalent on a "us"
   layout but not on a "de" layout.
B. While capslock is on, the key event won't include a shifted key ("73" here).
   This is due a quirk in the kitty keyboard protocol[^1].  This means that
   fish_key_reader's canonicalization doesn't work (unless we call toupper()
   ourselves).

I think we want to support both notations.

It's recommended to match all of these (in this order) when pressing
"ctrl-shift-i".

	1. bind ctrl-shift-i do-something
	2. bind ctrl-shift-I do-something
	3. bind ctrl-I do-something
	4. bind ctrl-i do-something

Support 1 and 3 for now, allowing both bindings to coexist. No priorities
for now. This solves problem A, and -- if we take care to use the explicit
shift notation -- problem B.

For keys that are not affected by capslock, problem B does not apply.  In this
case, recommend the shifted notation ("alt-+" instead of "alt-shift-=")
since that seems more intuitive.
Though if we prioritized "alt-shift-=" over "alt-+" as per the recommendation,
that's an argument against the shifted key.

Example output for some key events:

	$ fish_key_reader -cV
	# decoded from: \e\[61:43\;4u
	bind alt-+ 'do something' # recommended notation
	bind alt-shift-= 'do something'
	# decoded from: \e\[61:43\;68u
	bind alt-+ 'do something' # recommended notation
	bind alt-shift-= 'do something'

	# decoded from: \e\[105:73\;6u
	bind ctrl-I 'do something'
	bind ctrl-shift-i 'do something' # recommended notation
	# decoded from: \e\[105\;70u
	bind ctrl-shift-i 'do something'

Due to the capslock quirk, the last one has only one matching representation
since there is no shifted key.  We could decide to match ctrl-shift-i events
(that don't have a shifted key) to ctrl-I bindings (for ASCII letters), as
before this patch. But that case is very rare, it should only happen when
capslock is on, so it's probably not even a breaking change.

The other way round is supported -- we do match ctrl-I events (typically
with shifted key) to ctrl-shift-i bindings (but only for ASCII letters).
This is mainly for backwards compatibility.

Also note that, bindings without other modifiers currently need to use the
shifted key (like "Ä", not "shift-ä"), since we still get a legacy encoding,
until we request "Report all keys as escape codes".

[^1]: <https://github.com/kovidgoyal/kitty/issues/8493>

(cherry picked from commit 50a6e486a5)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
08d796890a Stop accepting "bind shift-A"
This notation doesn't make sense, use either A or shift-a.  We accept it
for ASCII letters only -- things like "bind shift-!" or "bind shift-Ä"
do not work as of today, we don't tolerate extra shift modifiers yet.
So let's remove it for consistency.

Note that the next commit will allow the shift-A notation again, but it will
not match shift-a events.

(cherry picked from commit 7f25d865a9)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
4f98ef36f6 Extract KeyEvent type
The be used in the grandchild commit.

(cherry picked from commit 855a1f702e)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
a09c78491f Extract function for creating key event with modifiers
(cherry picked from commit fabbbba037)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
02932d6b8c Extract constant for the number of function keys
Switch to fish_wcstoul because we want the constant to be unsigned.
It's u32 because most callers of function_key() want that.

(cherry picked from commit e9d1cdfe87)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
7cca98bda2 Fix regression decoding function keys
Commit 109ef88831 (Add menu and printscreen keys, 2025-01-01)
accidentally broke an assumption by inverting f1..f12.  Fix that.

Fixes #11098

(cherry picked from commit d2b2c5286a)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
b77fc28692 Add menu and printscreen keys
These aren't typically used in the terminal but they are present on
many keyboards.

Also reorganize the named key constants a bit.  Between F500 and
ENCODE_DIRECT_BASE (F600) we have space for 256 named keys.

(cherry picked from commit 109ef88831)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
3475531ef7 Also ignore invalid recursive escape sequences
We parse "\e\e[x" as alt-modified "Invalid" key.  Due to this extra
modifier, we accidentally add it to the input queue, instead of
dropping this invalid key.

We don't really want to try to extract some valid keys from this
invalid sequence, see also the parent commit.

This allows us to remove misplaced validation that was added by
e8e91c97a6 (fish_key_reader: ignore sentinel key, 2024-04-02) but
later obsoleted by 66c6e89f98 (Don't add collateral sentinel key to
input queue, 2024-04-03).

(cherry picked from commit 84f19a931d)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
8222ed891b Stop parsing invalid CSI/SS3 sequences as alt-[/alt-o
This situation can be triggered in practice inside a terminal like tmux
3.5 by running

	tmux new-session fish -C 'sleep 2' -d reader -o log-file

and typing "alt-escape x"

The log shows that we drop treat this as alt-[ and drop  the x on the floor.

	reader: Read char alt-\[ -- Key { modifiers: Modifiers { ctrl: false,
	alt: true, shift: false }, codepoint: '[' } -- [27, 91, 120]

This input ("\e[x") is ambiguous.

It looks like it could mean "alt-[,x".  However that conflicts with a
potential future CSI code, so it makes no sense to try to support this.

Returning "None" from parse_csi() causes this weird behavior of
returning "alt-[" and dropping the rest of the parsed sequence.
This is too easy; it has even crept into a bunch of places
where the input sequence is actually valid like "VT200 button released"
but where we definitely don't want to report any key.

Fix the default: report no key for all unknown sequences and
intentionally-suppressed sequences.  Treat it at "alt-[" only when
there is no input byte available, which is more or less unambiguous,
hence a strong enough signal that this is a actually "alt-[".

(cherry picked from commit 3201cb9f01)
2025-06-10 16:50:00 +02:00
Johannes Altmanninger
f787e6858c Fix tests/checks/autoload.fish
Apparently this test runs with "build/tests" as CWD when run with cmake.
2025-06-10 16:50:00 +02:00
Fabian Boehm
a014166795 completions/nmcli: Complete at runtime
This used to get all the interfaces and ssids when the completions
were loaded. That's obviously wrong, given that ssids especially can, you know, change

(cherry picked from commit 9116c61736)

cherry-picking since this easy to trigger
(seen again in https://github.com/fish-shell/fish-shell/pull/11549)
2025-06-06 11:52:17 +02:00
Johannes Altmanninger
f7e639504a completions/git: improve idempotency in case of double load
As mentioned in the previous few commits and in #11535, running
"set fish_complete_path ..."  and "complete -C 'git ...'"  may result in
"share/completions/git.fish" being loaded multiple times.

This is usually fine because fish internally erases all cached completions
whenever fish_complete_path changes.

Unfortunately there is at least global variable that grows each time git.fish
is sourced. This doesn't make a functional difference but it does slow
down completions.  Fix that by resetting the variable at load time.

(cherry picked from commit 4b5650ee4f)
2025-05-29 18:01:22 +02:00
Johannes Altmanninger
028b60cad6 Fix "set fish_complete_path" accidentally disabling autoloading
Commit 5918bca1eb (Make "complete -e" prevent completion autoloading,
2024-08-24) makes "complete -e foo" add a tombstone for "foo", meaning we
will never again load completions for "foo".

Due to an oversight, the same tombstone is added when we clear cached
completions after changing "fish_complete_path", preventing completions from
being loaded in that case.  Fix this by restoring the old behavior unless
the user actually used "complete -e".

(cherry picked from commit a7c04890c9)
2025-05-29 18:01:04 +02:00
Johannes Altmanninger
b11e22d905 Fix uvar file mtime force-update (Rust port regression)
When two fish processes rewrite the uvar file concurrent, they rely on the
uvar file's mtime (queried after taking a lock, if locking is supported) to
tell us whether their view of the uvar file is still up-to-date.  If it is,
they proceed to move it into place atomically via rename().

Since the observable mtime only updates on every OS clock tick, we call
futimens() manually to force-update that, to make sure that -- unless both
fish conincide on the same *nanosecond* -- other fish will notice that the
file changed.

Unfortunately, commit 77aeb6a2a8 (Port execution, 2023-10-08) accidentally
made us call futimens() only if clock_gettime() failed, instead of when
it succeeded. This means that we need to wait for the next clock tick to
observe a change in mtime.
Any resulting false negatives might have caused us to drop universal variable updates.

Reported in https://github.com/fish-shell/fish-shell/pull/11492#discussion_r2098948362

See #10300

(cherry picked from commit 8617964d4d)
2025-05-23 08:50:17 +02:00
Johannes Altmanninger
33f8415785 Fixup history file EINTR loop to actually loop
Fixes d84e68dd4f (Retry history file flock() on EINTR, 2025-05-20).

(cherry picked from commit 3867163193)
2025-05-20 17:19:12 +02:00
Johannes Altmanninger
5ccd155177 Retry history file flock() on EINTR
When locking the uvar file, we retry whenever flock() fails with EINTR
(e.g. due to ctrl-c).

But not when locking the history file.  This seems wrong; all other libc
functions in the "history_file" code path do retry.

Fix that. In future we should extract a function.

Note that there are other inconsistencies; flock_uvar_file() does not
shy away from remote file systems and does not respect ABANDONED_LOCKING.
This means that empirically probably neither are necessary; let's make things
consistent in future.

See https://github.com/fish-shell/fish-shell/pull/11492#discussion_r2095096200
Might help #10300

(cherry picked from commit 4d84e68dd4)
2025-05-20 15:32:56 +02:00
Johannes Altmanninger
0f8d3a5174 Revert "Temporarily enable history_file debug category by default"
Commit f906a949cf (Temporarily enable history_file debug category by default,
2024-10-09) enabled the "history_file" debug category by default to gather
more data.

Judging from
https://github.com/fish-shell/fish-shell/issues/10300#issuecomment-2876718382
the logs didn't help, or were at least not visible when logging to stderr
(due to reboot).

Let's disable "history_file" logs again to remove potential
noise if the file system is read-only, disk is full etc., see
https://github.com/fish-shell/fish-shell/pull/11492#discussion_r2094781120

See #10300

(cherry picked from commit 285a810814)
2025-05-20 15:31:57 +02:00
Johannes Altmanninger
c4a26cb2b1 builtin status: remove spurious newline from current-command (Rust port regression)
WHen "status current-command" is called outside a function it always returns
"fish". An extra newline crept in, fix that.

Fixes 77aeb6a2a8 (Port execution, 2023-10-08).
Fixes #11503

(cherry picked from commit e26b585ce5)
2025-05-20 12:33:01 +02:00
Johannes Altmanninger
7228cb15bf Include sys/statvfs.h for the definition of ST_LOCAL (Rust port regression)
See https://man.netbsd.org/statvfs.5.
According to https://github.com/NetBSD/src/blob/trunk/sys/sys/statvfs.h#L135,
NetBSD has "#define ST_LOCAL MNT_LOCAL".  So this commit likely makes no
difference on existing systems.

While at it
- comment include statements
- remove a code clone

See #11486

(cherry picked from commit d68f8bdd3b)
2025-05-16 08:21:56 +02:00
Alan Somers
d5b46d6535 Fix remote filesystem detection on FreeBSD
Need an extra include to get the definition of MNT_LOCAL

Fixes #11483

(cherry picked from commit 7f4998ad9b)
2025-05-16 08:21:25 +02:00
Johannes Altmanninger
d4b4d44f14 Fix Vi mode glitch when replacing at last character
Another regression from d51f669647 (Vi mode: avoid placing cursor beyond last
character, 2024-02-14) "Unfortunately Vi mode sometimes needs to temporarily
select past end". So do the replace_one mode bindings which were forgotten.

Fix this.

This surfaces a tricky problem: when we use something like

	bind '' self-insert some-command

When key event "x" matches this generic binding, we insert both "self-insert"
and "some-command" at the front of the queue, and do *not* consume "x",
since the binding is empty.

Since there is a command (that might call "exit"), we insert a check-exit
event too, after "self-insert some-command" but _before_ "x".

The check-exit event makes "self-insert" do nothing. I don't think there's a
good reason for this; self-insert can only be triggered by a key event that
maps to self-insert; so there must always be a real key available for it to
consume. A "commandline -f self-insert" is a nop. Skip check-exit here.

Fixes #11484

(cherry picked from commit 107e4d11de)
2025-05-13 00:31:22 +02:00
Johannes Altmanninger
b8cfd6d12b Fix typo causing wrong cursor position after Vi mode paste
Regressed in d51f669647 (Vi mode: avoid placing cursor beyond last character,
2024-02-14).

(cherry picked from commit 50500ec5b9)
2025-05-13 00:31:09 +02:00
Johannes Altmanninger
6fb22a4fd1 Fix regression causing crash indenting commandline with "$()"
Commit b00899179f (Don't indent multi-line quoted strings; do indent inside
(), 2024-04-28) changed how we compute indents for string tokens with command
substitutions:

	echo "begin
	not indented
	end $(
	begin
	    indented
	end)"(
	begin
	    indented
	end
	)

For the leading quoted part of the string, we compute indentation only for
the first character (the opening quote), see 4c43819d32 (Fix crash indenting
quoted suffix after command substitution, 2024-09-28).

The command substitutions, we do indent as usual.

To implement the above, we need to separate quoted from non-quoted
parts. This logic crashes when indent_string_part() is wrongly passed
is_double_quoted=true.

This is because, given the string "$()"$(), parse_util_locate_cmdsub calls
quote_end() at index 4 (the second quote). This is wrong because that function
should only be called at opening quotes; this is a closing quote. The opening
quote is virtual here. Hack around this.

Fixes #11444

(cherry picked from commit 48704dc612)
2025-05-12 21:41:10 +02:00
Johannes Altmanninger
35849c57dc Explicit type for "$()" hack in parse_util_locate_cmdsub
(cherry picked from commit 8abab0e2cc)
2025-05-12 21:41:10 +02:00
Johannes Altmanninger
27504658ce Remove code clone in parse_util_locate_cmdsub
(cherry picked from commit bd178c8ba8)
2025-05-12 21:41:10 +02:00
Johannes Altmanninger
db323348c7 Set transient command line in custom completions (Rust port regression)
Commit df3b0bd89f (Fix commandline state for custom completions with variable
overrides, 2022-01-26) made us push a transient command line for custom
completions based on a tautological null-pointer check ("var_assignments").

Commit 77aeb6a2a8 (Port execution, 2023-10-08) turned the null pointer into
a reference and replaced the check with "!ad.var_assignments.is_empty()".
This broke scenarios that relied on the transient commandline.  In particular
the attached test cases rely on the transient commandline implicitly placing
the cursor at the end, irrespective of the cursor in the actual commandline.

I'm not sure if there is an easy way to identify these scenarios.

Let's restore historical behavior by always pushing the transient command line.

Fixes #11423

(cherry picked from commit 97641c7bf6)
2025-05-12 21:39:42 +02:00
Johannes Altmanninger
edb1b5f333 Share alt-{b,f} with Vi mode, to work around Terminal.app/Ghostty more
Commit f4503af037 (Make alt-{b,f} move in directory history if commandline is
empty, 2025-01-06) had the intentional side effect of making alt-{left,right}
(move in directory history) work in Terminal.app and Ghostty without other,
less reliable workarounds.
That commit says "that [workaround] alone should not be the reason for
this change."; maybe this was wrong.

Extend the workaround to Vi mode.  The intention here is to provide
alt-{left,right} in Vi mode.  This also adds alt-{b,f} which is odd but
mostly harmless (?) because those don't do anything else in Vi mode.
It might be confusing when studying "bind" output but that one already has
almost 400 lines for Vi mode.

Closes #11479

(cherry picked from commit 3081d0157b)
2025-05-12 21:35:47 +02:00
Daniel Rainer
2d8d377ddc Make printf unicode-aware
Specifically, the width and precision format specifiers are interpreted as
referring to the width of the grapheme clusters rather than the byte count of
the string. Note that grapheme clusters can differ in width.

If a precision is specified for a string, meaning its "maximum number of
characters", we consider this to limit the width displayed.
If there is a grapheme cluster whose width is greater than 1,
it might not be possible to get precisely the desired width.
In such cases, this last grapheme cluster is excluded from the output.

Note that the definitions used here are not consistent with the `string length`
builtin at the moment, but this has already been the case.

(cherry picked from commit 09eae92888)
2025-05-12 21:33:40 +02:00
Peter Ammon
057dd930b4 Changelog fix for #11354 2025-05-08 18:42:57 -07:00
Cuichen Li
25b944e3e6 Revert "Work around $PATH issues under WSL (#10506)"
This reverts commit 3374692b91.
2025-05-08 18:41:02 -07:00
David Adam
f1456f9707 Release 4.0.2 2025-04-20 21:11:52 +08:00
David Adam
c88e6827b7 CHANGELOG: work on 4.0.2 2025-04-19 00:06:31 +08:00
Johannes Altmanninger
bc3e3ae029 builtin read: always handle out-of-range codepoints (Rust port regression)
As mentioned in
https://github.com/fish-shell/fish-shell/pull/9688#discussion_r1155089596,
commit b77d1d0e2b (Stop crashing on invalid Unicode input, 2024-02-27), Rust's
char type doesn't support arbitrary 32-bit values.  Out-of-range Unicode
codepoints would cause crashes.  That commit addressed this by converting
the encoded bytes (e.g. UTF-8) to special private-use-area characters that
fish knows about.  It didn't bother to update the code path in builtin read
that relies on mbrtowc as well.

Fix that. Move and rename parse_codepoint() and rename/reorder its input/output
parameters.

Fixes #11383

(cherry picked from commit d9ba27f58f)
2025-04-16 11:33:15 +02:00
Johannes Altmanninger
3191ac13e5 Reduce parse_codepoint responsibilities, fixing alt in single-byte locale?
This also changes the single-byte locale code path to treat keyboard input
like "\x1ba" as alt-a instead of "escape,a".  I can't off-hand reproduce
a problem with "LC_ALL=C fish_key_reader", I guess we always use a UTF-8
locale if available?

(cherry picked from commit b061178606)
2025-04-16 11:28:28 +02:00
Johannes Altmanninger
4f810809c8 Fix builtin test assigning wrong range to "! -d /" (Rust port regression)
Fixes #11387

(cherry picked from commit c740c656a8)
2025-04-16 11:25:35 +02:00
Johannes Altmanninger
d622949d26 Fix kill-selection crash when selection start is out-of-bounds
This part of the code could use some love; when we happen to clear the
selected text, we should end the selection.

But that's not how it works today. This is fine for Vi mode, because Vi
mode never deletes in visual mode.

Let's fix the crash for now.

Fixes #11367

(cherry picked from commit af3b49bf9c)
2025-04-11 19:31:09 +02:00
Fabian Boehm
d95b662542 docs: Fix string-match glob examples
`?` no longer is a wildcard.

See #11361

(cherry picked from commit eb4a0b2560)
2025-04-08 17:14:12 +02:00
Johannes Altmanninger
d88d3122c8 Fix crash when history pager is closed before search
With

	bind ctrl-r 'sleep 1' history-pager

typing ctrl-r,escape crashes fish in the history pager completion callback,
because the history pager has already been closed.

Prior to 55fd43d86c (Port reader, 2023-12-22), the completion callback
would not crash open a pager -- which causes weird races with the
user input.

Apparently this crash as been triggered by running "playwright",
and -- while that's running typing ctrl-r ligh escape.
Those key strokes were received while the kitty keyboard protocol
was active, possibly a race.

Fixes #11355

(cherry picked from commit c94e30293a)
2025-04-04 14:37:03 +02:00
Fabian Boehm
a7f717c59c CHANGELOG for 4.0.2 2025-04-02 17:06:55 +02:00
Fabian Boehm
ba49981f17 tests: Just check that the version starts with a digit
Our versions look like

4.0.0
4.0b1
4.0.1-535-abfef-dirty

But packagers may want to add more information here, and we don't
really care. Note that we no longer ever set the version to "unknown"
since 5abd0e46f5.

Supersedes #11173

(cherry picked from commit 411a396fa9)
2025-04-02 17:03:57 +02:00
Farhood Etaati
05ae55b172 Adds git subtree completion
Closes #11063

(cherry picked from commit 48306409ef)
2025-04-02 17:03:57 +02:00
Ilya Grigoriev
b5877ebe44 jj completions: use dynamic completions by default, also fix
This uses jj's dynamic completions when possible.

This avoids an annoying problem. After 04a4e5c4, jj's dynamic
completions (see the second paragraph of
<https://jj-vcs.github.io/jj/latest/install-and-setup/#command-line-completion>)
do not work very well in fish if the user puts `COMPLETE=fish jj |
source` in their `~/.config/fish/config.fish`. When the user types `jj
<TAB>`, they are instead overridden by fish's built-in non-dynamic
completions.

The difference is subtle. One problem I saw is that `jj new <TAB>` works
as expected (and shows revisions) while `jj new -A <TAB>` becomes broken
(and shows files).

If the user puts `COMPLETE=fish jj | source` in
`~/.config/fish/completions/jj.fish` there is no problem. However, users
might be confused if they run `COMPLETE=fish jj | source` or put it in
their config and it works in a broken fashion. I certainly was.

Meanwhile, I checked that if the user has `jj completion fish | source`
in their `config.fish`, executing `COMPLETE=fish jj
__this_command_does_not_exist | source` afterwards still works
correctly.

Let me know if there's a better approach to this problem.

(cherry picked from commit 932010cd04)
2025-04-02 17:03:57 +02:00
Clément Martinez
f9a03215b8 Add wlr-randr completions
(cherry picked from commit ea8e122fad)
2025-04-02 17:03:57 +02:00
memchr
ff0980c4c1 completions/systemd-analyze: add new options and subcommands
options:
- instance
- image
- image-policy
- tldr
- unit
- table
- no-legend
- detailed
- scale-svg
- malloc

subcommands:
- filesystems
- compare-versions
- inspect-elf
- fdstore
- has-tpm2
- pcrs
- srk
- architectures
- smbios11

fix a typo in timespan completion

Signed-off-by: memchr <memchr@proton.me>
(cherry picked from commit 3744c02a01)
2025-04-02 17:03:57 +02:00
memchr
1a58d3f08b completions/cryptsetup: complete device mapping names
The commands 'close', 'resize', and 'status' each take 'name' as their solo argument.

Signed-off-by: memchr <memchr@proton.me>
(cherry picked from commit 5012bcb976)
2025-04-02 17:03:57 +02:00
memchr
b71027f622 completions/git: add --filter option
supported subcommands:
- clone
- fetch
- submodule update
- rev-list

(cherry picked from commit 795d6b6c40)
2025-04-02 17:03:57 +02:00
Jonathan Palardy
84c03c6f26 completions/git: Added autostash option to git merge
(cherry picked from commit 269ed5ddf4)
2025-04-02 17:03:57 +02:00
memchr
bf455bc316 completions/btrfs: add new options and commands
Also add completion for balance filters

(cherry picked from commit 95b93c6bff)
2025-04-02 17:03:57 +02:00
Johannes Altmanninger
6af0378916 Don't insert text from keys like super-i
While at it, use declaration order for modifiers.

(cherry picked from commit 35ae0bf1f2)
2025-04-02 17:03:57 +02:00
Fabian Boehm
de154065fe fish_print_hg_root: Don't break if $PWD includes newlines
Fixes #11348

(cherry picked from commit 5e25cdaa6f)
2025-04-02 17:01:25 +02:00
Fabian Boehm
459e9b7847 completions/cargo: Speed up
This does two things:

- it stops completing cargo- tools because `cargo --list` already
includes them. This speeds up loading especially with a long $PATH
- it stops using `cargo search` for `cargo add` and install.
  this removes a network call, which may be unexpected and can take a
  long time

Fixes #11347

(cherry picked from commit 18371fbd4e)
2025-04-02 17:00:30 +02:00
Johannes Altmanninger
f127323c33 Prevent commandline modification inside abbreviation callbacks
Consider command line modifications triggered from fish script via abbreviation
expansion:

	function my-abbr-func
	    commandline -r ""
	    echo expanded
	end
	abbr -a foo --function my-abbr-func

Prior to commit 8386088b3d (Update commandline state changes eagerly as well,
2024-04-11), we'd silently ignore the command line modification.
This is because the abbreviation machinery runs something similar to

	if my-abbr-func
	    commandline -rt expanded
	end

except without running "apply_commandline_state_changes()" after
"my-abbr-func", so the «commandline -r ""» update is lost.

Commit 8386088b3d applies the commandline change immediately in the abbrevation
function callback, invalidating abbrevation-expansion state.

The abbreviation design does not tell us what should happen here.  Let's ignore
commandline modifications for now. This mostly matches historical behavior.

Unlike historical behavior we also ignore modifications if the callback fails:

	function my-abbr-func
	    commandline -r ""
	    false
	end

Remove the resulting dead code in editable_line.

See #11324

(cherry picked from commit 11c7310f17)
2025-03-28 13:05:22 +01:00
Fabian Boehm
3fc245d829 docs: Readd bind -k to the docs
Fixes #11329

(cherry picked from commit d88f5ddbaf)
2025-03-28 12:59:44 +01:00
Johannes Altmanninger
542793a534 completions/git: fix arg completion for third-party git commands, again
Commit 50e595503e (completions/git: fix completions for third-party git
commands, 2025-03-03) wasn't quite right, as we can see in the linked
reproduction:

	$ fish_trace=1 complete -C 'git machete add --onto '
	----> complete -C git-machete\ add\n--onto\

The recursive completion invocation contains a spurious newline, which means
that "--onto" is the command name.  The newline is produced by "string escape
-- add --onto" inside a command substitution.

Fix this by interpreting newlines as list separators, and then joining
by spaces.

Fixes #11319

(cherry picked from commit 360cfdb7ae)
2025-03-25 11:16:38 +01:00
Johannes Altmanninger
18c231de29 Fix concurrent setlocale() in string escape tests
In our C++ implementation, these tests were run serially.  As pointed out in
https://github.com/fish-shell/fish-shell/issues/11254#issuecomment-2735623229
we run them in parallel now, which means that one test could be changing
the global locale used by another.

In theory this could be fine because all tests are setting setting the
global locale to the same thing but the existence of a lock suggests that
setlocale() is not guaranteed to be atomic, so it's possible that another
thread uses a temporarily-invalid locale.

Fixes #11254

(cherry picked from commit 1d78c8bd42)
2025-03-19 09:46:12 +01:00
Johannes Altmanninger
8e78857836 Fix Vi mode delete key bindings while numlock is active
Commit 8bf8b10f68 (Extended & human-friendly keys, 2024-03-30)
add bindings that obsolete the  terminfo-based `bind -k` invocations.

The `bind -k` variants were still left around[^*]. Unfortunately it forgot to
add the new syntax for some special keys in Vi mode.  This leads to issues if
a terminal that supports the kitty keyboard protocol sends an encoding that
differs from the traditional one.  As far as I can tell, this happens when
capslock or numlock is active.  Let's add the new key names and consistently
mark `bind -k` invocations as deprecated.

Fixes #11303

[^*]: Support for `bind -k` will probably be removed in a future release -
it leads to issues like https://github.com/fish-shell/fish-shell/issues/11278
where it's better to fail early.

(cherry picked from commit 733f704267)
2025-03-19 09:27:29 +01:00
Fabian Boehm
c7efbf590e function: Also error for read-only var in positional arg
We have this hack where any positional arguments are taken as argument
names if "--argument-names" is given, and that didn't check for
read-only variables.

Fixes #11295

(cherry picked from commit d203ee4d53)
2025-03-17 19:55:25 +01:00
Fabian Boehm
5771085280 CHANGELOG 2025-03-16 19:15:28 +01:00
Fabian Boehm
76d2419228 Downgrade $TERM warnings to a term-support flog
The chances that xterm-256color breaks anything are miniscule.

In the features we use, there are basically no differences,
especially when you consider that we decode keys independently.

E.g. tmux-256color has differences, but they are either just taste
questions (xterm's clear_screen will also clear scrollback),
or they're just... not actually different?

Terminfo will claim that it uses a different cursor_up and
exit_attribute_mode, but it also understands the xterm ones,
and it sends a different key_home,
but we decode that even with TERM=xterm-256color.

In some cases, terminfo is also just outright *wrong* and will claim
something does not support italics when it does.

So, since the differences are very likely to simply not matter,
throwing a warning is more confusing than it is helpful.

(cherry picked from commit 642ec399ca)
2025-03-16 18:50:46 +01:00
Johannes Altmanninger
ffbf957fa3 Quote only unique completions, use backslashes for ambiguous ones
Commit 29dc307111 (Insert some completions with quotes instead of backslashes,
2024-04-13) breaks some workflows. Given

	touch '[test] file1'
	touch '[test] file2'
	ls tes<Tab>

we insert completions quoted, which is inconvenient when using globs.

This implicit quoting feature is somewhat minor. But quotes look nicer,
so let's try to keep them.  Either way, users can ask for it by starting a
token with «"».

Use quoting only when we insert unique completions.

Closes #11271

(cherry picked from commit 9f79fe17fc)
2025-03-16 12:12:03 +01:00
Fabian Boehm
ae3532e9ec screen: Fix crash if prompt contains backspace
fish_wcwidth_visible can return -1, so usize::try_from fails.

Fixes #11280

(cherry picked from commit c03de2086a)
2025-03-14 20:19:49 +01:00
Fabian Boehm
5cd2ef903a key: Add super modifier
Fixes #11217

(cherry picked from commit 9f5e1736a8)
2025-03-14 20:19:49 +01:00
116 changed files with 2815 additions and 1147 deletions

View File

@@ -13,16 +13,20 @@ max_line_length = 100
indent_style = tab
[*.{md,rst}]
max_line_length = unset
trim_trailing_whitespace = false
[*.{sh,ac}]
indent_size = 2
[*.sh]
indent_size = 4
[build_tools/release.sh]
max_line_length = 72
[Dockerfile]
indent_size = 2
[share/{completions,functions}/**.fish]
max_line_length = off
max_line_length = unset
[{COMMIT_EDITMSG,git-revise-todo}]
max_line_length = 80
[{COMMIT_EDITMSG,git-revise-todo,*.jjdescription}]
max_line_length = 72

View File

@@ -0,0 +1,14 @@
name: Install sphinx-markdown-builder
permissions:
contents: read
runs:
using: "composite"
steps:
- shell: bash
run: |
set -x
commit=b259de1dc97573a71470a1d71c3d83535934136b
pip install git+https://github.com/krobelus/sphinx-markdown-builder@"$commit"
python -c 'import sphinx_markdown_builder'

View File

@@ -0,0 +1,20 @@
name: Oldest Supported Rust Toolchain
inputs:
targets:
description: Comma-separated list of target triples to install for this toolchain
required: false
components:
description: Comma-separated list of components to be additionally installed
required: false
permissions:
contents: read
runs:
using: "composite"
steps:
- uses: dtolnay/rust-toolchain@1.70
with:
targets: ${{ inputs.targets }}
components: ${{ inputs.components}}

View File

@@ -0,0 +1,20 @@
name: Stable Rust Toolchain
inputs:
targets:
description: Comma-separated list of target triples to install for this toolchain
required: false
components:
description: Comma-separated list of components to be additionally installed
required: false
permissions:
contents: read
runs:
using: "composite"
steps:
- uses: dtolnay/rust-toolchain@1.89
with:
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}

View File

@@ -1,42 +0,0 @@
name: macOS build and codesign
on:
workflow_dispatch: # Enables manual trigger from GitHub UI
jobs:
build-and-code-sign:
runs-on: macos-latest
environment: macos-codesign
steps:
- uses: actions/checkout@v4
- name: Install Rust 1.73.0
uses: dtolnay/rust-toolchain@1.73.0
with:
targets: x86_64-apple-darwin
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
- name: build-and-codesign
run: |
cargo install apple-codesign
mkdir -p "$FISH_ARTEFACT_PATH"
echo "$MAC_CODESIGN_APP_P12_BASE64" | base64 --decode > /tmp/app.p12
echo "$MAC_CODESIGN_INSTALLER_P12_BASE64" | base64 --decode > /tmp/installer.p12
echo "$MACOS_NOTARIZE_JSON" > /tmp/notarize.json
./build_tools/make_pkg.sh -s -f /tmp/app.p12 -i /tmp/installer.p12 -p "$MAC_CODESIGN_PASSWORD" -n -j /tmp/notarize.json
rm /tmp/installer.p12 /tmp/app.p12 /tmp/notarize.json
env:
MAC_CODESIGN_APP_P12_BASE64: ${{ secrets.MAC_CODESIGN_APP_P12_BASE64 }}
MAC_CODESIGN_INSTALLER_P12_BASE64: ${{ secrets.MAC_CODESIGN_INSTALLER_P12_BASE64 }}
MAC_CODESIGN_PASSWORD: ${{ secrets.MAC_CODESIGN_PASSWORD }}
MACOS_NOTARIZE_JSON: ${{ secrets.MACOS_NOTARIZE_JSON }}
# macOS runners keep having issues loading Cargo.toml dependencies from git (GitHub) instead
# of crates.io, so give this a try. It's also sometimes significantly faster on all platforms.
CARGO_NET_GIT_FETCH_WITH_CLI: true
FISH_ARTEFACT_PATH: /tmp/fish-built
- uses: actions/upload-artifact@v4
with:
name: macOS Artefacts
path: /tmp/fish-built/*
if-no-files-found: error

View File

@@ -22,6 +22,7 @@ jobs:
sudo apt install gettext libpcre2-dev python3-pexpect tmux
# Generate a locale that uses a comma as decimal separator.
sudo locale-gen fr_FR.UTF-8
- uses: ./.github/actions/install-sphinx-markdown-builder
- name: cmake
run: |
mkdir build && cd build

190
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,190 @@
name: Create a new release
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (tag name)'
required: true
type: string
permissions:
contents: write
jobs:
is-release-tag:
name: Pre-release checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Check if the pushed tag looks like a release
run: |
set -x
commit_subject=$(git log -1 --format=%s)
tag=$(git describe)
[ "$commit_subject" = "Release $tag" ]
source-tarball:
needs: [is-release-tag]
name: Create the source tarball
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
tarball-name: ${{ steps.version.outputs.tarball-name }}
steps:
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Install dependencies
run: sudo apt install cmake gettext ninja-build python3-pip python3-sphinx
- uses: ./.github/actions/install-sphinx-markdown-builder
- name: Create tarball
run: |
set -x
mkdir /tmp/fish-built
FISH_ARTEFACT_PATH=/tmp/fish-built ./build_tools/make_tarball.sh
relnotes=/tmp/fish-built/release-notes.md
# Need history since the last release (i.e. tag) for stats.
git fetch --tags
git fetch --unshallow
sh -x ./build_tools/release-notes.sh >"$relnotes"
# Delete title
sed -n 1p "$relnotes" | grep -q "^## fish .*"
sed -n 2p "$relnotes" | grep -q '^$'
sed -i 1,2d "$relnotes"
- name: Upload tarball artifact
uses: actions/upload-artifact@v4
with:
name: source-tarball
path: |
/tmp/fish-built/fish-${{ inputs.version }}.tar.xz
/tmp/fish-built/release-notes.md
if-no-files-found: error
packages-for-linux:
needs: [is-release-tag]
name: Build single-file fish for Linux (experimental)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Install Rust Stable
uses: ./.github/actions/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl
- name: Install dependencies
run: sudo apt install crossbuild-essential-arm64 musl-tools python3-sphinx
- name: Build statically-linked executables
run: |
set -x
CFLAGS="-D_FORTIFY_SOURCE=2" \
CMAKE_WITH_GETTEXT=0 \
CC=aarch64-linux-gnu-gcc \
RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc -C link-arg=-lgcc -C link-arg=-D_FORTIFY_SOURCE=0" \
cargo build --release --target aarch64-unknown-linux-musl --bin fish
cargo build --release --target x86_64-unknown-linux-musl --bin fish
- name: Compress
run: |
set -x
for arch in x86_64 aarch64; do
tar -cazf fish-$(git describe)-linux-$arch.tar.xz \
-C target/$arch-unknown-linux-musl/release fish
done
- uses: actions/upload-artifact@v4
with:
name: Static builds for Linux
path: fish-${{ inputs.version }}-linux-*.tar.xz
if-no-files-found: error
create-draft-release:
needs:
- is-release-tag
- source-tarball
- packages-for-linux
name: Create release draft
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
merge-multiple: true
path: /tmp/artifacts
- name: List artifacts
run: find /tmp/artifacts -type f
- name: Create draft release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ inputs.version }}
name: fish ${{ inputs.version }}
body_path: /tmp/artifacts/release-notes.md
draft: true
files: |
/tmp/artifacts/fish-${{ inputs.version }}.tar.xz
/tmp/artifacts/fish-${{ inputs.version }}-linux-*.tar.xz
packages-for-macos:
needs: [is-release-tag, create-draft-release]
name: Build packages for macOS
runs-on: macos-latest
environment: macos-codesign
steps:
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Install Rust
uses: ./.github/actions/rust-toolchain@oldest-supported
with:
targets: x86_64-apple-darwin
- name: Install Rust Stable
uses: ./.github/actions/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
- name: Build and codesign
run: |
die() { echo >&2 "$*"; exit 1; }
[ -n "$MAC_CODESIGN_APP_P12_BASE64" ] || die "Missing MAC_CODESIGN_APP_P12_BASE64"
[ -n "$MAC_CODESIGN_INSTALLER_P12_BASE64" ] || die "Missing MAC_CODESIGN_INSTALLER_P12_BASE64"
[ -n "$MAC_CODESIGN_PASSWORD" ] || die "Missing MAC_CODESIGN_PASSWORD"
[ -n "$MACOS_NOTARIZE_JSON" ] || die "Missing MACOS_NOTARIZE_JSON"
set -x
export FISH_ARTEFACT_PATH=/tmp/fish-built
# macOS runners keep having issues loading Cargo.toml dependencies from git (GitHub) instead
# of crates.io, so give this a try. It's also sometimes significantly faster on all platforms.
export CARGO_NET_GIT_FETCH_WITH_CLI=true
cargo install apple-codesign
mkdir -p "$FISH_ARTEFACT_PATH"
echo "$MAC_CODESIGN_APP_P12_BASE64" | base64 --decode >/tmp/app.p12
echo "$MAC_CODESIGN_INSTALLER_P12_BASE64" | base64 --decode >/tmp/installer.p12
echo "$MACOS_NOTARIZE_JSON" >/tmp/notarize.json
./build_tools/make_macos_pkg.sh -s -f /tmp/app.p12 \
-i /tmp/installer.p12 -p "$MAC_CODESIGN_PASSWORD" \
-n -j /tmp/notarize.json
version=$(git describe)
[ -f "${FISH_ARTEFACT_PATH}/fish-$version.app.zip" ]
[ -f "${FISH_ARTEFACT_PATH}/fish-$version.pkg" ]
rm /tmp/installer.p12 /tmp/app.p12 /tmp/notarize.json
env:
MAC_CODESIGN_APP_P12_BASE64: ${{ secrets.MAC_CODESIGN_APP_P12_BASE64 }}
MAC_CODESIGN_INSTALLER_P12_BASE64: ${{ secrets.MAC_CODESIGN_INSTALLER_P12_BASE64 }}
MAC_CODESIGN_PASSWORD: ${{ secrets.MAC_CODESIGN_PASSWORD }}
MACOS_NOTARIZE_JSON: ${{ secrets.MACOS_NOTARIZE_JSON }}
- name: Add macOS packages to the release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version=$(git describe)
gh release upload $version \
/tmp/fish-built/fish-$version.app.zip \
/tmp/fish-built/fish-$version.pkg

View File

@@ -1,47 +0,0 @@
name: staticbuilds
on:
# release:
# types: [published]
# schedule:
# - cron: "14 13 * * *"
workflow_dispatch:
env:
CTEST_PARALLEL_LEVEL: "1"
CMAKE_BUILD_PARALLEL_LEVEL: "4"
jobs:
staticbuilds:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: dtolnay/rust-toolchain@1.70
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Prepare
run: |
sudo apt install python3-sphinx
rustup target add x86_64-unknown-linux-musl
rustup target add aarch64-unknown-linux-musl
sudo apt install musl-tools crossbuild-essential-arm64 -y
- name: Build
run: |
CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2" CMAKE_WITH_GETTEXT=0 CC=aarch64-linux-gnu-gcc RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc -C link-arg=-lgcc -C link-arg=-D_FORTIFY_SOURCE=0" cargo build --release --target aarch64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
- name: Compress
run: |
tar -cazf fish-amd64.tar.xz -C target/x86_64-unknown-linux-musl/release/ fish{,_indent,_key_reader}
tar -cazf fish-aarch64.tar.xz -C target/aarch64-unknown-linux-musl/release/ fish{,_indent,_key_reader}
- uses: actions/upload-artifact@v4
with:
name: fish
path: |
fish-amd64.tar.xz
fish-aarch64.tar.xz
retention-days: 14

View File

@@ -1,3 +1,77 @@
fish 4.0.9 (released September 27, 2025)
========================================
This release fixes:
- a regression in 4.0.6 causing shifted keys to not be inserted on some terminals (:issue:`11813`).
- a regression in 4.0.6 causing the build to fail on systems where ``char`` is unsigned (:issue:`11804`).
- a regression in 4.0.0 causing a crash on an invalid :doc:`bg <cmds/bg>` invocation.
--------------
fish 4.0.8 (released September 18, 2025)
========================================
This release fixes a regression in 4.0.6 that caused user bindings to be shadowed by either fish's or a plugin's bindings (:issue:`11803`).
--------------
fish 4.0.6 (released September 12, 2025)
========================================
This release of fish fixes a number of issues identified in fish 4.0.2:
- fish now properly inherits $PATH under Windows WSL2 (:issue:`11354`).
- Remote filesystems are detected properly again on non-Linux systems.
- the :doc:`printf <cmds/printf>` builtin no longer miscalculates width of multi-byte characters (:issue:`11412`).
- For many years, fish has been "relocatable" -- it was possible to move the entire ``CMAKE_INSTALL_PREFIX`` and fish would use paths relative to its binary.
Only gettext locale paths were still determined purely at compile time, which has been fixed.
- the :doc:`commandline <cmds/commandline>` builtin failed to print the commandline set by a ``commandline -C`` invocation, which broke some completion scripts.
This has been corrected (:issue:`11423`).
- To work around terminals that fail to parse Operating System Command (OSC) sequences, a temporary feature flag has been added.
It allows you to disable prompt marking (OSC 133) by running (once) ``set -Ua fish_features no-mark-prompt`` and restarting fish (:issue:`11749`).
- The routines to save history and universal variables have seen some robustness improvements.
- builtin :doc:`status current-command <cmds/status>` no longer prints a trailing blank line.
- A crash displaying multi-line quoted command substitutions has been fixed (:issue:`11444`).
- Commands like ``set fish_complete_path ...`` accidentally disabled completion autoloading, which has been corrected.
- ``nmcli`` completions have been fixed to query network information dynamically instead of only when completing the first time.
- Git completions no longer print an error when no `git-foo` executable is in :envvar:`PATH`.
- Custom completions like ``complete foo -l long -xa ...`` that use the output of ``commandline -t``.
on a command-line like ``foo --long=`` have been invalidated by a change in 4.0; the completion scripts have been adjusted accordingly (:issue:`11508`).
- Some completions were misinterpreted, which caused garbage to be displayed in the completion list. This has been fixed.
- fish no longer interprets invalid control sequences from the terminal as if they were :kbd:`alt-[` or :kbd:`alt-o` key strokes.
- :doc:`bind <cmds/bind>` has been taught about the :kbd:`printscreen` and :kbd:`menu` keys.
- :kbd:`alt-delete` now deletes the word right of the cursor.
- :kbd:`ctrl-alt-h` erases the last word again (:issue:`11548`).
- :kbd:`alt-left` :kbd:`alt-right` were misinterpreted because they send unexpected sequences on some terminals; a workaround has been added. (:issue:`11479`).
- Key bindings like ``bind shift-A`` are no longer accepted; use ``bind shift-a`` or ``bind A``.
- Key bindings like ``bind shift-a`` take precedence over ``bind A`` when the key event included the shift modifier.
- Bindings using shift with non-ASCII letters (such as :kbd:`ctrl-shift-ä`) are now supported.
- Bindings with modifiers such as ``bind ctrl-w`` work again on non-Latin keyboard layouts such as a Russian one.
This is implemented by allowing key events such as :kbd:`ctrl-ц` to match bindings of the corresponding Latin key, using the kitty keyboard protocol's base layout key (:issue:`11520`).
- Vi mode: The cursor position after pasting via :kbd:`p` has been corrected.
- Vi mode: Trying to replace the last character via :kbd:`r` no longer replaces the last-but-one character (:issue:`11484`),
fish 4.0.2 (released April 20, 2025)
====================================
This release of fish fixes a number of issues identified in fish 4.0.1:
- Completions are quoted, rather than backslash-escaped, only if the completion is unambiguous. Continuing to edit the token is therefore easier (:issue:`11271`). This changes the behavior introduced in 4.0.0 where all completions were quoted.
- The warning when the terminfo database can't be found has been downgraded to a log message. fish will act as if the terminal behaves like xterm-256color, which is correct for the vast majority of cases (:issue:`11277`, :issue:`11290`).
- Key combinations using the super (Windows/command) key can now (actually) be bound using the :kbd:`super-` prefix (:issue:`11217`). This was listed in the release notes for 4.0.1 but did not work correctly.
- :doc:`function <cmds/function>` is stricter about argument parsing, rather than allowing additional parameters to be silently ignored (:issue:`11295`).
- Using parentheses in the :doc:`test <cmds/test>` builtin works correctly, following a regression in 4.0.0 where they were not recognized (:issue:`11387`).
- :kbd:`delete` in Vi mode when Num Lock is active will work correctly (:issue:`11303`).
- Abbreviations cannot alter the command-line contents, preventing a crash (:issue:`11324`).
- Improvements to various completions, including new completions for ``wl-randr`` (:issue:`11301`), performance improvements for ``cargo`` completions by avoiding network requests (:issue:`11347`), and other improvements for ``btrfs`` (:issue:`11320`), ``cryptsetup`` (:issue:`11315`), ``git`` (:issue:`11319`, :issue:`11322`, :issue:`11323`), ``jj`` (:issue:`11046`), and ``systemd-analyze`` (:issue:`11314`).
- The Mercurial (``hg``) prompt can handle working directories that contain an embedded newline, rather than producing errors (:issue:`11348`).
- A number of crashes have been fixed. Triggers include prompts containing backspace characters (:issue:`11280`), history pager search (:issue:`11355`), invalid UTF-8 in :doc:`read <cmds/read>` (:issue:`11383`), and the ``kill-selection`` binding (:issue:`11367`).
- A race condition in the test suite has been fixed (:issue:`11254`), and a test for fish versioning relaxed to support downstream distributors' modifications (:issue:`11173`).
- Small improvements to the documentation (:issue:`11264`, :issue:`11329`, :issue:`11361`).
--------------
fish 4.0.1 (released March 12, 2025)
====================================

16
Cargo.lock generated
View File

@@ -112,7 +112,7 @@ dependencies = [
[[package]]
name = "fish"
version = "4.0.1"
version = "4.0.9"
dependencies = [
"bitflags",
"cc",
@@ -139,6 +139,8 @@ name = "fish-printf"
version = "0.2.1"
dependencies = [
"libc",
"unicode-segmentation",
"unicode-width",
"widestring",
]
@@ -567,6 +569,18 @@ version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "version_check"
version = "0.9.5"

View File

@@ -16,7 +16,7 @@ debug = true
[package]
name = "fish"
version = "4.0.1"
version = "4.0.9"
edition.workspace = true
rust-version.workspace = true
default-run = "fish"

View File

@@ -227,7 +227,7 @@ fn has_small_stack(_: &Target) -> Result<bool, Box<dyn Error>> {
}
fn setup_paths() {
fn get_path(name: &str, default: &str, onvar: PathBuf) -> PathBuf {
fn get_path(name: &str, default: &str, onvar: &Path) -> PathBuf {
let mut var = PathBuf::from(env::var(name).unwrap_or(default.to_string()));
if var.is_relative() {
var = onvar.join(var);
@@ -250,7 +250,7 @@ fn get_path(name: &str, default: &str, onvar: PathBuf) -> PathBuf {
rsconf::rebuild_if_env_changed("PREFIX");
rsconf::set_env_value("PREFIX", prefix.to_str().unwrap());
let datadir = get_path("DATADIR", "share/", prefix.clone());
let datadir = get_path("DATADIR", "share/", &prefix);
rsconf::set_env_value("DATADIR", datadir.to_str().unwrap());
rsconf::rebuild_if_env_changed("DATADIR");
@@ -261,7 +261,7 @@ fn get_path(name: &str, default: &str, onvar: PathBuf) -> PathBuf {
};
rsconf::set_env_value("DATADIR_SUBDIR", datadir_subdir);
let bindir = get_path("BINDIR", "bin/", prefix.clone());
let bindir = get_path("BINDIR", "bin/", &prefix);
rsconf::set_env_value("BINDIR", bindir.to_str().unwrap());
rsconf::rebuild_if_env_changed("BINDIR");
@@ -270,16 +270,16 @@ fn get_path(name: &str, default: &str, onvar: PathBuf) -> PathBuf {
// If we get our prefix from $HOME, we should use the system's /etc/
// ~/.local/share/etc/ makes no sense
if prefix_from_home { "/etc/" } else { "etc/" },
datadir.clone(),
&datadir,
);
rsconf::set_env_value("SYSCONFDIR", sysconfdir.to_str().unwrap());
rsconf::rebuild_if_env_changed("SYSCONFDIR");
let localedir = get_path("LOCALEDIR", "locale/", datadir.clone());
let localedir = get_path("LOCALEDIR", "locale/", &datadir);
rsconf::set_env_value("LOCALEDIR", localedir.to_str().unwrap());
rsconf::rebuild_if_env_changed("LOCALEDIR");
let docdir = get_path("DOCDIR", "doc/fish", datadir.clone());
let docdir = get_path("DOCDIR", "doc/fish", &datadir);
rsconf::set_env_value("DOCDIR", docdir.to_str().unwrap());
rsconf::rebuild_if_env_changed("DOCDIR");
}
@@ -292,7 +292,7 @@ fn get_version(src_dir: &Path) -> String {
return var;
}
let path = PathBuf::from(src_dir).join("version");
let path = src_dir.join("version");
if let Ok(strver) = read_to_string(path) {
return strver.to_string();
}
@@ -321,7 +321,7 @@ fn get_version(src_dir: &Path) -> String {
// or because it refused (safe.directory applies to `git describe`!)
// So we read the SHA ourselves.
fn get_git_hash() -> Result<String, Box<dyn std::error::Error>> {
let gitdir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".git");
let gitdir = Path::new(env!("CARGO_MANIFEST_DIR")).join(".git");
// .git/HEAD contains ref: refs/heads/branch
let headpath = gitdir.join("HEAD");

177
build_tools/make_macos_pkg.sh Executable file
View File

@@ -0,0 +1,177 @@
#!/usr/bin/env bash
# Script to produce an OS X installer .pkg and .app(.zip)
usage() {
echo "Build macOS packages, optionally signing and notarizing them."
echo "Usage: $0 options"
echo "Options:"
echo " -s Enables code signing"
echo " -f <APP_KEY.p12> Path to .p12 file for application signing"
echo " -i <INSTALLER_KEY.p12> Path to .p12 file for installer signing"
echo " -p <PASSWORD> Password for the .p12 files (necessary to access the certificates)"
echo " -e <entitlements file> (Optional) Path to an entitlements XML file"
echo " -n Enables notarization. This will fail if code signing is not also enabled."
echo " -j <API_KEY.JSON> Path to JSON file generated with \`rcodesign encode-app-store-connect-api-key\` (required for notarization)"
echo
exit 1
}
set -x
set -e
SIGN=
NOTARIZE=
ARM64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=11.0'
X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.9'
# As of this writing, the most recent Rust release supports macOS back to 10.12.
# The first supported version of macOS on arm64 is 10.15, so any Rust is fine for arm64.
# We wish to support back to 10.9 on x86-64; the last version of Rust to support that is
# version 1.73.0.
RUST_VERSION_X86_64=1.70.0
while getopts "sf:i:p:e:nj:" opt; do
case $opt in
s) SIGN=1;;
f) P12_APP_FILE=$(realpath "$OPTARG");;
i) P12_INSTALL_FILE=$(realpath "$OPTARG");;
p) P12_PASSWORD="$OPTARG";;
e) ENTITLEMENTS_FILE=$(realpath "$OPTARG");;
n) NOTARIZE=1;;
j) API_KEY_FILE=$(realpath "$OPTARG");;
\?) usage;;
esac
done
if [ -n "$SIGN" ] && { [ -z "$P12_APP_FILE" ] || [ -z "$P12_INSTALL_FILE" ] || [ -z "$P12_PASSWORD" ]; }; then
usage
fi
if [ -n "$NOTARIZE" ] && [ -z "$API_KEY_FILE" ]; then
usage
fi
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
echo "Version is $VERSION"
PKGDIR=$(mktemp -d)
echo "$PKGDIR"
SRC_DIR=$PWD
OUTPUT_PATH=${FISH_ARTEFACT_PATH:-~/fish_built}
mkdir -p "$PKGDIR/build_x86_64" "$PKGDIR/build_arm64" "$PKGDIR/root" "$PKGDIR/intermediates" "$PKGDIR/dst"
# Build and install for arm64.
# Pass FISH_USE_SYSTEM_PCRE2=OFF because a system PCRE2 on macOS will not be signed by fish,
# and will probably not be built universal, so the package will fail to validate/run on other systems.
# Note CMAKE_OSX_ARCHITECTURES is still relevant for the Mac app.
{ cd "$PKGDIR/build_arm64" \
&& cmake \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
-DWITH_GETTEXT=OFF \
-DRust_CARGO_TARGET=aarch64-apple-darwin \
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
-DFISH_USE_SYSTEM_PCRE2=OFF \
"$SRC_DIR" \
&& env $ARM64_DEPLOY_TARGET make VERBOSE=1 -j 12 \
&& env DESTDIR="$PKGDIR/root/" $ARM64_DEPLOY_TARGET make install;
}
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
# Set RUST_VERSION_X86_64 to the last version of Rust that supports macOS 10.9.
{ cd "$PKGDIR/build_x86_64" \
&& cmake \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
-DWITH_GETTEXT=OFF \
-DRust_TOOLCHAIN="$RUST_VERSION_X86_64" \
-DRust_CARGO_TARGET=x86_64-apple-darwin \
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
-DFISH_USE_SYSTEM_PCRE2=OFF "$SRC_DIR" \
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
# Fatten them up.
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
X86_FILE="$PKGDIR/build_x86_64/$(basename "$FILE")"
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
chmod 755 "$FILE"
done
if test -n "$SIGN"; then
echo "Signing executables"
ARGS=(
--p12-file "$P12_APP_FILE"
--p12-password "$P12_PASSWORD"
--code-signature-flags runtime
--for-notarization
)
if [ -n "$ENTITLEMENTS_FILE" ]; then
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
fi
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
(set +x; rcodesign sign "${ARGS[@]}" "$FILE")
done
fi
pkgbuild --scripts "$SRC_DIR/build_tools/osx_package_scripts" --root "$PKGDIR/root/" --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" "$PKGDIR/intermediates/fish.pkg"
productbuild --package-path "$PKGDIR/intermediates" --distribution "$SRC_DIR/build_tools/osx_distribution.xml" --resources "$SRC_DIR/build_tools/osx_package_resources/" "$OUTPUT_PATH/fish-$VERSION.pkg"
if test -n "$SIGN"; then
echo "Signing installer"
ARGS=(
--p12-file "$P12_INSTALL_FILE"
--p12-password "$P12_PASSWORD"
--code-signature-flags runtime
--for-notarization
)
(set +x; rcodesign sign "${ARGS[@]}" "$OUTPUT_PATH/fish-$VERSION.pkg")
fi
# Make the app
(cd "$PKGDIR/build_arm64" && env $ARM64_DEPLOY_TARGET make -j 12 fish_macapp)
(cd "$PKGDIR/build_x86_64" && env $X86_64_DEPLOY_TARGET make -j 12 fish_macapp)
# Make the app's /usr/local/bin binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
cd "$PKGDIR/build_arm64"
for FILE in fish.app/Contents/Resources/base/usr/local/bin/*; do
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")"
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
# macho-universal-create screws up the permissions.
chmod 755 "$FILE"
done
if test -n "$SIGN"; then
echo "Signing app"
ARGS=(
--p12-file "$P12_APP_FILE"
--p12-password "$P12_PASSWORD"
--code-signature-flags runtime
--for-notarization
)
if [ -n "$ENTITLEMENTS_FILE" ]; then
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
fi
(set +x; rcodesign sign "${ARGS[@]}" "fish.app")
fi
cp -R "fish.app" "$OUTPUT_PATH/fish-$VERSION.app"
cd "$OUTPUT_PATH"
# Maybe notarize.
if test -n "$NOTARIZE"; then
echo "Notarizing"
rcodesign notarize --staple --wait --max-wait-seconds 1800 --api-key-file "$API_KEY_FILE" "$OUTPUT_PATH/fish-$VERSION.pkg"
rcodesign notarize --staple --wait --max-wait-seconds 1800 --api-key-file "$API_KEY_FILE" "$OUTPUT_PATH/fish-$VERSION.app"
fi
# Zip it up.
zip -r "fish-$VERSION.app.zip" "fish-$VERSION.app" && rm -Rf "fish-$VERSION.app"
rm -rf "$PKGDIR"

View File

@@ -3,18 +3,18 @@
# Script to produce an OS X installer .pkg and .app(.zip)
usage() {
echo "Build macOS packages, optionally signing and notarizing them."
echo "Usage: $0 options"
echo "Options:"
echo " -s Enables code signing"
echo " -f <APP_KEY.p12> Path to .p12 file for application signing"
echo " -i <INSTALLER_KEY.p12> Path to .p12 file for installer signing"
echo " -p <PASSWORD> Password for the .p12 files (necessary to access the certificates)"
echo " -e <entitlements file> (Optional) Path to an entitlements XML file"
echo " -n Enables notarization. This will fail if code signing is not also enabled."
echo " -j <API_KEY.JSON> Path to JSON file generated with `rcodesign encode-app-store-connect-api-key` (required for notarization)"
echo
exit 1
echo "Build macOS packages, optionally signing and notarizing them."
echo "Usage: $0 options"
echo "Options:"
echo " -s Enables code signing"
echo " -f <APP_KEY.p12> Path to .p12 file for application signing"
echo " -i <INSTALLER_KEY.p12> Path to .p12 file for installer signing"
echo " -p <PASSWORD> Password for the .p12 files (necessary to access the certificates)"
echo " -e <entitlements file> (Optional) Path to an entitlements XML file"
echo " -n Enables notarization. This will fail if code signing is not also enabled."
echo " -j <API_KEY.JSON> Path to JSON file generated with \`rcodesign encode-app-store-connect-api-key\` (required for notarization)"
echo
exit 1
}
set -x
@@ -30,35 +30,35 @@ X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.9'
# The first supported version of macOS on arm64 is 10.15, so any Rust is fine for arm64.
# We wish to support back to 10.9 on x86-64; the last version of Rust to support that is
# version 1.73.0.
RUST_VERSION_X86_64=1.73.0
RUST_VERSION_X86_64=1.70.0
while getopts "sf:i:p:e:nj:" opt; do
case $opt in
s) SIGN=1;;
f) P12_APP_FILE=$(realpath "$OPTARG");;
i) P12_INSTALL_FILE=$(realpath "$OPTARG");;
p) P12_PASSWORD="$OPTARG";;
e) ENTITLEMENTS_FILE=$(realpath "$OPTARG");;
n) NOTARIZE=1;;
j) API_KEY_FILE=$(realpath "$OPTARG");;
\?) usage;;
esac
case $opt in
s) SIGN=1;;
f) P12_APP_FILE=$(realpath "$OPTARG");;
i) P12_INSTALL_FILE=$(realpath "$OPTARG");;
p) P12_PASSWORD="$OPTARG";;
e) ENTITLEMENTS_FILE=$(realpath "$OPTARG");;
n) NOTARIZE=1;;
j) API_KEY_FILE=$(realpath "$OPTARG");;
\?) usage;;
esac
done
if [ -n "$SIGN" ] && ([ -z "$P12_APP_FILE" ] || [-z "$P12_INSTALL_FILE"] || [ -z "$P12_PASSWORD" ]); then
usage
if [ -n "$SIGN" ] && { [ -z "$P12_APP_FILE" ] || [ -z "$P12_INSTALL_FILE" ] || [ -z "$P12_PASSWORD" ]; }; then
usage
fi
if [ -n "$NOTARIZE" ] && [ -z "$API_KEY_FILE" ]; then
usage
usage
fi
VERSION=$(git describe --always --dirty 2>/dev/null)
if test -z "$VERSION" ; then
echo "Could not get version from git"
if test -f version; then
VERSION=$(cat version)
fi
echo "Could not get version from git"
if test -f version; then
VERSION=$(cat version)
fi
fi
echo "Version is $VERSION"
@@ -76,7 +76,7 @@ mkdir -p "$PKGDIR/build_x86_64" "$PKGDIR/build_arm64" "$PKGDIR/root" "$PKGDIR/in
# and will probably not be built universal, so the package will fail to validate/run on other systems.
# Note CMAKE_OSX_ARCHITECTURES is still relevant for the Mac app.
{ cd "$PKGDIR/build_arm64" \
&& cmake \
&& cmake \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
-DWITH_GETTEXT=OFF \
@@ -91,7 +91,7 @@ mkdir -p "$PKGDIR/build_x86_64" "$PKGDIR/build_arm64" "$PKGDIR/root" "$PKGDIR/in
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
# Set RUST_VERSION_X86_64 to the last version of Rust that supports macOS 10.9.
{ cd "$PKGDIR/build_x86_64" \
&& cmake \
&& cmake \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
-DWITH_GETTEXT=OFF \
@@ -99,11 +99,11 @@ mkdir -p "$PKGDIR/build_x86_64" "$PKGDIR/build_arm64" "$PKGDIR/root" "$PKGDIR/in
-DRust_CARGO_TARGET=x86_64-apple-darwin \
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
-DFISH_USE_SYSTEM_PCRE2=OFF "$SRC_DIR" \
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
# Fatten them up.
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
X86_FILE="$PKGDIR/build_x86_64/$(basename $FILE)"
X86_FILE="$PKGDIR/build_x86_64/$(basename "$FILE")"
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
chmod 755 "$FILE"
done
@@ -145,7 +145,7 @@ fi
# Make the app's /usr/local/bin binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
cd "$PKGDIR/build_arm64"
for FILE in fish.app/Contents/Resources/base/usr/local/bin/*; do
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename $FILE)"
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")"
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
# macho-universal-create screws up the permissions.

104
build_tools/release-notes.sh Executable file
View File

@@ -0,0 +1,104 @@
#!/bin/sh
set -e
workspace_root=$(dirname "$0")/..
relnotes_tmp=$(mktemp -d)
mkdir -p "$relnotes_tmp/fake-workspace" "$relnotes_tmp/out"
(
cd "$workspace_root"
cp -r doc_src CONTRIBUTING.rst README.rst "$relnotes_tmp/fake-workspace"
)
version=$(sed 's,^fish \(\S*\) .*,\1,; 1q' "$workspace_root/CHANGELOG.rst")
previous_version=$(
cd "$workspace_root"
awk <CHANGELOG.rst '
( /^fish \S*\.\S*\.\S* \(released .*\)$/ &&
NR > 1 &&
# Skip tags that have not been created yet..
system("git rev-parse --verify >/dev/null --quiet refs/tags/"$2) == 0 \
) {
print $2; ok = 1; exit
}
END { exit !ok }
'
)
minor_version=${version%.*}
previous_minor_version=${previous_version%.*}
{
sed -n 1,2p <"$workspace_root/CHANGELOG.rst"
ListCommitters() {
comm "$@" "$relnotes_tmp/committers-then" "$relnotes_tmp/committers-now"
}
(
cd "$workspace_root"
git log "$previous_version" --format="%aN" | sort -u >"$relnotes_tmp/committers-then"
git log "$previous_version".. --format="%aN" | sort -u >"$relnotes_tmp/committers-now"
ListCommitters -13 >"$relnotes_tmp/committers-new"
ListCommitters -12 >"$relnotes_tmp/committers-returning"
)
if [ "$minor_version" != "$previous_minor_version" ]; then
(
cd "$workspace_root"
num_commits=$(git log --no-merges --format=%H "$previous_version".. | wc -l)
num_authors=$(wc -l <"$relnotes_tmp/committers-now")
num_new_authors=$(wc -l <"$relnotes_tmp/committers-new")
printf %s \
"This release comprises $num_commits commits since $previous_version," \
" contributed by $num_authors authors, $num_new_authors of which are new committers."
echo
echo
)
fi
printf %s "$(awk <"$workspace_root/CHANGELOG.rst" '
NR <= 2 || /^\.\. ignore / { next }
/^===/ { exit }
{ print }
' | sed '$d')" |
sed -e '$s/^----*$//' # Remove spurious transitions at the end of the document.
if [ "$minor_version" != "$previous_minor_version" ]; then
JoinEscaped() {
sed 's/\S/\\&/g' |
awk '
NR != 1 { printf ",\n" }
{ printf "%s", $0 }
END { printf "\n" }
'
}
echo ""
echo "---"
echo ""
echo "Thanks to everyone who contributed through issue discussions, code reviews, or code changes."
echo
printf "Welcome our new committers: "
JoinEscaped <"$relnotes_tmp/committers-new"
echo
printf "Welcome back our returning committers: "
JoinEscaped <"$relnotes_tmp/committers-returning"
fi
echo
echo "---"
echo
echo "*Download links: To download the source code for fish, we suggest the file named \"fish-$version.tar.xz\". The file downloaded from \"Source code (tar.gz)\" will not build correctly.*"
echo
echo "*The files called fish-$version-linux-\*.tar.xz are experimental packages containing a single standalone ``fish`` binary for any Linux with the given CPU architecture.*"
} >"$relnotes_tmp/fake-workspace"/CHANGELOG.rst
sphinx-build >&2 -j auto \
-W -E -b markdown -c "$workspace_root/doc_src" \
-d "$relnotes_tmp/doctree" "$relnotes_tmp/fake-workspace/doc_src" "$relnotes_tmp/out" \
-D markdown_http_base="https://fishshell.com/docs/$minor_version" \
-D markdown_uri_doc_suffix=".html" \
-D markdown_github_flavored=1 \
"$@"
# Skip changelog header
sed -n 1p "$relnotes_tmp/out/relnotes.md" | grep -Fxq "# Release notes"
sed -n 2p "$relnotes_tmp/out/relnotes.md" | grep -Fxq ''
sed 1,2d "$relnotes_tmp/out/relnotes.md"
rm -r "$relnotes_tmp"

229
build_tools/release.sh Executable file
View File

@@ -0,0 +1,229 @@
#!/bin/sh
{
set -ex
version=$1
repository_owner=fish-shell
remote=origin
if [ -n "$2" ]; then
set -u
repository_owner=$2
remote=$3
set +u
[ $# -eq 3 ]
fi
[ -n "$version" ]
for tool in \
bundle \
gh \
jq \
ruby \
timeout \
; do
if ! command -v "$tool" >/dev/null; then
echo >&2 "$0: missing command: $1"
exit 1
fi
done
repo_root="$(dirname "$0")/.."
fish_site=$repo_root/../fish-site
for path in . "$fish_site"
do
if ! git -C "$path" diff HEAD --quiet ||
git ls-files --others --exclude-standard | grep .; then
echo >&2 "$0: index and worktree must be clean"
exit 1
fi
done
if git tag | grep -qxF "$version"; then
echo >&2 "$0: tag $version already exists"
exit 1
fi
integration_branch=$(
git for-each-ref --points-at=HEAD 'refs/heads/Integration_*' \
--format='%(refname:strip=2)'
)
[ -n "$integration_branch" ] ||
git merge-base --is-ancestor $remote/master HEAD
sed -n 1p CHANGELOG.rst | grep -q '^fish .*(released .*)$'
sed -n 2p CHANGELOG.rst | grep -q '^===*$'
changelog_title="fish $version (released $(date +'%B %d, %Y'))"
sed -i \
-e "1c$changelog_title" \
-e "2c$(printf %s "$changelog_title" | sed s/./=/g)" \
CHANGELOG.rst
CommitVersion() {
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
cargo fetch --offline
git add CHANGELOG.rst Cargo.toml Cargo.lock
git commit -m "$2
Created by ./build_tools/release.sh $version"
}
CommitVersion "$version" "Release $version"
# N.B. this is not GPG-signed.
git tag --annotate --message="Release $version" $version
git push $remote $version
TIMEOUT=
gh() {
command ${TIMEOUT:+timeout $TIMEOUT} \
gh --repo "$repository_owner/fish-shell" "$@"
}
gh workflow run release.yml --ref="$version" \
--raw-field="version=$version"
run_id=
while [ -z "$run_id" ] && sleep 5
do
run_id=$(gh run list \
--json=databaseId --jq=.[].databaseId \
--workflow=release.yml --limit=1 \
--commit="$(git rev-parse "$version^{commit}")")
done
# Update fishshell.com
tag_oid=$(git rev-parse "$version")
tmpdir=$(mktemp -d)
# TODO This works on draft releases only if "gh" is configured to
# have write access to the fish-shell repository. Unless we are fine
# publishing the release at this point, we should at least fail if
# "gh" doesn't have write access.
while ! \
gh release download "$version" --dir="$tmpdir" \
--pattern="fish-$version.tar.xz"
do
TIMEOUT=30 gh run watch "$run_id" ||:
sleep 5
done
actual_tag_oid=$(git ls-remote "$remote" |
awk '$2 == "refs/tags/'"$version"'" { print $1 }')
[ "$tag_oid" = "$actual_tag_oid" ]
( cd "$tmpdir" && tar xf fish-$version.tar.xz )
CopyDocs() {
rm -rf "$fish_site/site/docs/$1"
cp -r "$tmpdir/fish-$version/user_doc/html" "$fish_site/site/docs/$1"
git -C $fish_site add "site/docs/$1"
}
minor_version=${version%.*}
CopyDocs "$minor_version"
latest_release=$(
releases=$(git tag | grep '^[0-9]*\.[0-9]*\.[0-9]*.*' |
sed $(: "De-prioritize release candidates (1.2.3-rc0)") \
's/-/~/g' | LC_ALL=C sort --version-sort)
printf %s\\n "$releases" | tail -1
)
if [ "$version" = "$latest_release" ]; then
CopyDocs current
fi
rm -rf "$tmpdir"
(
cd "$fish_site"
make
git add -u
! git ls-files --others --exclude-standard | grep .
git commit --message="$(printf %s "\
| Release $version (docs)
|
| Created by ../fish-shell/build_tools/release.sh
" | sed 's,^\s*| \?,,')"
)
# Approve macos-codesign
# TODO what if current user can't approve?
gh_pending_deployments() {
command gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/repos/$repository_owner/fish-shell/actions/runs/$run_id/pending_deployments" \
"$@"
}
while {
environment_id=$(gh_pending_deployments | jq .[].environment.id)
[ -z "$environment_id" ]
}
do
sleep 5
done
echo '
{
"environment_ids": ['"$environment_id"'],
"state": "approved",
"comment": "Approved via ./build_tools/release.sh"
}
' |
gh_pending_deployments -XPOST --input=-
# Await completion.
gh run watch "$run_id"
while {
! draft=$(gh release view "$version" --json=isDraft --jq=.isDraft) \
|| [ "$draft" = true ]
}
do
sleep 20
done
(
cd "$fish_site"
make new-release
git add -u
! git ls-files --others --exclude-standard | grep .
git commit --message="$(printf %s "\
| Release $version (release list update)
|
| Created by ../fish-shell/build_tools/release.sh
" | sed 's,^\s*| \?,,')"
# This takes care to support remote names that are different from
# fish-shell remote name. Also, support detached HEAD state.
git push git@github.com:$repository_owner/fish-site HEAD:master
)
if [ -n "$integration_branch" ]; then
git push $remote "$version^{commit}":refs/heads/$integration_branch
else
changelog=$(cat - CHANGELOG.rst <<EOF
fish ?.?.? (released ???)
=========================
EOF
)
printf %s\\n "$changelog" >CHANGELOG.rst
CommitVersion ${version}-snapshot "start new cycle"
git push $remote HEAD:master
fi
# TODO This can currently require a TTY for editing and password
# prompts.
if [ "$repository_owner" = fish-shell ]; then {
mail=$(mktemp)
cat >$mail <<EOF
From: $(git var GIT_AUTHOR_IDENT | sed 's/ [0-9]* +[0-9]*$//')
Subject: fish $version released
See https://github.com/fish-shell/fish-shell/releases/tag/$version
EOF
git send-email --suppress-cc=all --confirm=always $mail \
--to="fish-users Mailing List <fish-users@lists.sourceforge.net>"
rm $mail
} fi
exit
}

View File

@@ -24,7 +24,7 @@ add_executable(fish_macapp EXCLUDE_FROM_ALL
# Compute the version. Note this is done at generation time, not build time,
# so cmake must be re-run after version changes for the app to be updated. But
# generally this will be run by make_pkg.sh which always re-runs cmake.
# generally this will be run by make_macos_pkg.sh which always re-runs cmake.
execute_process(
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh --stdout
COMMAND cut -d- -f1
@@ -32,7 +32,7 @@ execute_process(
OUTPUT_STRIP_TRAILING_WHITESPACE)
# Note CMake appends .app, so the real output name will be fish.app.
# Note CMake appends .app, so the real output name will be fish.app.
# This target does not include the 'base' resource.
set_target_properties(fish_macapp PROPERTIES OUTPUT_NAME "fish")

1
debian/control vendored
View File

@@ -20,7 +20,6 @@ Vcs-Browser: https://github.com/fish-shell/fish-shell
Package: fish
Architecture: any
Depends: bsdextrautils,
Depends: bsdextrautils | bsdmainutils,
file,
gettext-base,

5
debian/rules vendored
View File

@@ -17,6 +17,9 @@ override_dh_auto_configure:
dh_auto_configure --buildsystem=cmake -- -DCMAKE_BUILD_TYPE=RelWithDebInfo
override_dh_clean:
dh_clean
dh_clean --exclude=Cargo.toml.orig
-unlink .cargo
-unlink vendor
override_dh_auto_test:
make fish_run_tests

View File

@@ -23,7 +23,7 @@ If both ``KEYS`` and ``COMMAND`` are given, ``bind`` adds (or replaces) a bindin
If only ``KEYS`` is given, any existing binding in the given ``MODE`` will be printed.
``KEYS`` is a comma-separated list of key names.
Modifier keys can be specified by prefixing a key name with a combination of ``ctrl-``, ``alt-`` and ``shift-``.
Modifier keys can be specified by prefixing a key name with a combination of ``ctrl-``, ``alt-``, ``shift-`` and ``super-`` (i.e. the "windows" or "command" key).
For example, pressing :kbd:`w` while holding the Alt modifier is written as ``alt-w``.
Key names are case-sensitive; for example ``alt-W`` is the same as ``alt-shift-w``.
``ctrl-x,ctrl-e`` would mean pressing :kbd:`ctrl-x` followed by :kbd:`ctrl-e`.
@@ -99,6 +99,12 @@ The following options are available:
**-s** or **--silent**
Silences some of the error messages, including for unknown key names and unbound sequences.
**-k KEY_NAME** or **--key KEY_NAME**
This looks up KEY_NAME in terminfo and binds that sequence instead of a key that fish would decode.
To view a list of the terminfo keys fish knows about, use ``bind --key-names`` or ``bind -K``.
This is deprecated and provided for compatibility with older fish versions. You should bind the keys directly.
Instead of ``bind -k sright`` use ``bind shift-right``, instead of ``bind -k nul`` use ``bind ctrl-space`` and so on.
**-h** or **--help**
Displays help about using this command.

View File

@@ -52,13 +52,13 @@ Match Glob Examples
::
>_ string match '?' a
>_ string match 'a' a
a
>_ string match 'a*b' axxb
axxb
>_ string match -i 'a??B' Axxb
>_ string match -i 'a*B' Axxb
Axxb
>_ string match -- '-*' -h foo --version bar
@@ -67,7 +67,7 @@ Match Glob Examples
-h
--version
>_ echo 'ok?' | string match '*\?'
>_ echo 'ok?' | string match '*?'
ok?
# Note that only the second STRING will match here.
@@ -79,7 +79,7 @@ Match Glob Examples
foo
foo2
>_ string match 'foo?' 'foo1' 'foo' 'foo2'
>_ string match 'foo*' 'foo1' 'foo' 'foo2'
foo1
foo2

View File

@@ -10,9 +10,19 @@ import glob
import os.path
import subprocess
import sys
from sphinx.highlighting import lexers
from sphinx.errors import SphinxWarning
from docutils import nodes
try:
import sphinx_markdown_builder
extensions = [
"sphinx_markdown_builder",
]
except ImportError:
pass
# -- Helper functions --------------------------------------------------------
@@ -35,11 +45,14 @@ def issue_role(name, rawtext, text, lineno, inliner, options=None, content=None)
return [link], []
def remove_fish_indent_lexer(app):
if app.builder.name in ("man", "markdown"):
del lexers["fish-docs-samples"]
# -- Load our extensions -------------------------------------------------
def setup(app):
# Our own pygments lexers
from sphinx.highlighting import lexers
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, this_dir)
from fish_indent_lexer import FishIndentLexer
@@ -52,6 +65,8 @@ def setup(app):
app.add_config_value("issue_url", default=None, rebuild="html")
app.add_role("issue", issue_role)
app.connect("builder-inited", remove_fish_indent_lexer)
# The default language to assume
highlight_language = "fish-docs-samples"
@@ -59,7 +74,7 @@ highlight_language = "fish-docs-samples"
# -- Project information -----------------------------------------------------
project = "fish-shell"
copyright = "2024, fish-shell developers"
copyright = "fish-shell developers"
author = "fish-shell developers"
issue_url = "https://github.com/fish-shell/fish-shell/issues"
@@ -72,7 +87,7 @@ elif "FISH_BUILD_VERSION" in os.environ:
ret = os.environ["FISH_BUILD_VERSION"]
else:
ret = subprocess.check_output(
("fish_indent", "--version"), stderr=subprocess.STDOUT
("../build_tools/git_version_gen.sh", "--stdout"), stderr=subprocess.STDOUT
).decode("utf-8")
# The full version, including alpha/beta/rc tags

View File

@@ -9,3 +9,5 @@ license = "MIT"
[dependencies]
libc = "0.2.155"
widestring = { version = "1.0.2", optional = true }
unicode-segmentation = "1.12.0"
unicode-width = "0.2.0"

View File

@@ -73,7 +73,7 @@ macro_rules! sprintf {
/// - `args`: Iterator over the arguments to format.
///
/// # Returns
/// A `Result` which is `Ok` containing the number of characters written on success, or an `Error`.
/// A `Result` which is `Ok` containing the width of the string written on success, or an `Error`.
///
/// # Example
///

View File

@@ -5,6 +5,8 @@
use std::fmt::{self, Write};
use std::mem;
use std::result::Result;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
#[cfg(feature = "widestring")]
use widestring::Utf32Str as wstr;
@@ -382,7 +384,7 @@ pub fn sprintf_locale(
}
// Read field width. We do not support $.
let width = if s.at(0) == Some('*') {
let desired_width = if s.at(0) == Some('*') {
let arg_width = args.next().ok_or(Error::MissingArg)?.as_sint()?;
s.advance_by(1);
if arg_width < 0 {
@@ -397,7 +399,7 @@ pub fn sprintf_locale(
};
// Optionally read precision. We do not support $.
let mut prec: Option<usize> = if s.at(0) == Some('.') && s.at(1) == Some('*') {
let mut desired_precision: Option<usize> = if s.at(0) == Some('.') && s.at(1) == Some('*') {
// "A negative precision is treated as though it were missing."
// Here we assume the precision is always signed.
s.advance_by(2);
@@ -410,7 +412,7 @@ pub fn sprintf_locale(
None
};
// Disallow precisions larger than i32::MAX, in keeping with C.
if prec.unwrap_or(0) > i32::MAX as usize {
if desired_precision.unwrap_or(0) > i32::MAX as usize {
return Err(Error::Overflow);
}
@@ -429,7 +431,7 @@ pub fn sprintf_locale(
// "If a precision is given with a numeric conversion (d, i, o, u, i, x, and X),
// the 0 flag is ignored." p is included here.
let spec_is_numeric = matches!(conv_spec, CS::d | CS::u | CS::o | CS::p | CS::x | CS::X);
if spec_is_numeric && prec.is_some() {
if spec_is_numeric && desired_precision.is_some() {
flags.zero_pad = false;
}
@@ -443,13 +445,22 @@ pub fn sprintf_locale(
CS::e | CS::f | CS::g | CS::a | CS::E | CS::F | CS::G | CS::A => {
// Floating point types handle output on their own.
let float = arg.as_float()?;
let len = format_float(f, float, width, prec, flags, locale, conv_spec, buf)?;
let len = format_float(
f,
float,
desired_width,
desired_precision,
flags,
locale,
conv_spec,
buf,
)?;
out_len = out_len.checked_add(len).ok_or(Error::Overflow)?;
continue 'main;
}
CS::p => {
const PTR_HEX_DIGITS: usize = 2 * mem::size_of::<*const u8>();
prec = prec.map(|p| p.max(PTR_HEX_DIGITS));
desired_precision = desired_precision.map(|p| p.max(PTR_HEX_DIGITS));
let uint = arg.as_uint()?;
if uint != 0 {
prefix = "0x";
@@ -479,8 +490,8 @@ pub fn sprintf_locale(
if uint != 0 {
write!(buf, "{:o}", uint)?;
}
if flags.alt_form && prec.unwrap_or(0) <= buf.len() + 1 {
prec = Some(buf.len() + 1);
if flags.alt_form && desired_precision.unwrap_or(0) <= buf.len() + 1 {
desired_precision = Some(buf.len() + 1);
}
buf
}
@@ -514,10 +525,38 @@ pub fn sprintf_locale(
CS::s => {
// also 'S'
let s = arg.as_str(buf)?;
let p = prec.unwrap_or(s.len()).min(s.len());
prec = Some(p);
flags.zero_pad = false;
&s[..p]
match desired_precision {
Some(precision) => {
// from man printf(3)
// "the maximum number of characters to be printed from a string"
// We interpret this to mean the maximum width when printed, as defined by
// Unicode grapheme cluster width.
let mut byte_len = 0;
let mut width = 0;
let mut graphemes = s.graphemes(true);
// Iteratively add single grapheme clusters as long as the fit within the
// width limited by precision.
while width < precision {
match graphemes.next() {
Some(grapheme) => {
let grapheme_width = grapheme.width();
if width + grapheme_width <= precision {
byte_len += grapheme.len();
width += grapheme_width;
} else {
break;
}
}
None => break,
}
}
let p = precision.min(width);
desired_precision = Some(p);
&s[..byte_len]
}
None => s,
}
}
};
// Numeric output should be empty iff the value is 0.
@@ -528,23 +567,26 @@ pub fn sprintf_locale(
// Decide if we want to apply thousands grouping to the body, and compute its size.
// Note we have already errored out if grouped is set and this is non-numeric.
let wants_grouping = flags.grouped && locale.thousands_sep.is_some();
let body_len = match wants_grouping {
let body_width = match wants_grouping {
// We assume that text representing numbers is ASCII, so len == width.
true => body.len() + locale.separator_count(body.len()),
false => body.len(),
false => body.width(),
};
// Resolve the precision.
// In the case of a non-numeric conversion, update the precision to at least the
// length of the string.
let prec = if !spec_is_numeric {
prec.unwrap_or(body_len)
let desired_precision = if !spec_is_numeric {
desired_precision.unwrap_or(body_width)
} else {
prec.unwrap_or(1).max(body_len)
desired_precision.unwrap_or(1).max(body_width)
};
let prefix_len = prefix.len();
let unpadded_width = prefix_len.checked_add(prec).ok_or(Error::Overflow)?;
let width = width.max(unpadded_width);
let prefix_width = prefix.width();
let unpadded_width = prefix_width
.checked_add(desired_precision)
.ok_or(Error::Overflow)?;
let width = desired_width.max(unpadded_width);
// Pad on the left with spaces to the desired width?
if !flags.left_adj && !flags.zero_pad {
@@ -560,7 +602,8 @@ pub fn sprintf_locale(
}
// Pad on the left to the given precision?
pad(f, '0', prec, body_len)?;
// TODO: why pad with 0 here?
pad(f, '0', desired_precision, body_width)?;
// Output the actual value, perhaps with grouping.
if wants_grouping {

View File

@@ -13,6 +13,7 @@ macro_rules! sprintf_check {
$(,)? // optional trailing comma
) => {
{
use unicode_width::UnicodeWidthStr;
let mut target = String::new();
let mut args = [$($arg.to_arg()),*];
let len = $crate::printf_c_locale(
@@ -20,7 +21,7 @@ macro_rules! sprintf_check {
$fmt.as_ref() as &str,
&mut args,
).expect("printf failed");
assert!(len == target.len(), "Wrong length returned: {} vs {}", len, target.len());
assert_eq!(len, target.width(), "Wrong length returned");
target
}
};
@@ -723,6 +724,18 @@ fn test_huge_precision_g() {
sprintf_err!("%.2147483648g", f => Error::Overflow);
}
#[test]
fn test_non_ascii() {
assert_fmt!("%3s", "ö" => " ö");
assert_fmt!("%3s", "🇺🇳" => " 🇺🇳");
assert_fmt!("%.3s", "🇺🇳🇺🇳" => "🇺🇳");
assert_fmt!("%.3s", "a🇺🇳" => "a🇺🇳");
assert_fmt!("%.3s", "aa🇺🇳" => "aa");
assert_fmt!("%3.3s", "aa🇺🇳" => " aa");
assert_fmt!("%.1s", "𒈙a" => "𒈙");
assert_fmt!("%3.3s", "👨‍👨‍👧‍👧" => " 👨‍👨‍👧‍👧");
}
#[test]
fn test_errors() {
use Error::*;

View File

@@ -72,7 +72,7 @@ function __fish_bind_complete
printf '%sshift-\tShift modifier…\n' $prefix
set -l key_names minus comma backspace delete escape \
enter up down left right pageup pagedown home end insert tab \
space f(seq 12)
space menu printscreen f(seq 12)
printf '%s\tNamed key\n' $prefix$key_names
end
end

View File

@@ -102,33 +102,36 @@ complete -f -c btrfs -n $restore -s S -l symlink -d 'Restore symbolic links'
complete -f -c btrfs -n $restore -s v -l verbose -d Verbose
complete -f -c btrfs -n $restore -s i -l ignore-errors -d 'Ignore errors'
complete -f -c btrfs -n $restore -s o -l overwrite -d Overwrite
complete -f -c btrfs -n $restore -s t -d 'Tree location'
complete -f -c btrfs -n $restore -s f -d 'Filesystem location'
complete -f -c btrfs -n $restore -s u -l super -d 'Super mirror'
complete -f -c btrfs -n $restore -s r -l root -d 'Root objectid'
complete -f -c btrfs -n $restore -s t -r -d 'Tree location'
complete -f -c btrfs -n $restore -s f -r -d 'Filesystem location'
complete -f -c btrfs -n $restore -s u -l super -r -d 'Super mirror'
complete -f -c btrfs -n $restore -s r -l root -r -d 'Root objectid'
complete -f -c btrfs -n $restore -s d -d 'Find dir'
complete -f -c btrfs -n $restore -s l -l list-roots -d 'List tree roots'
complete -f -c btrfs -n $restore -s D -l dry-run -d 'Only list files that would be recovered'
complete -f -c btrfs -n $restore -l path-regex -d 'Restore only filenames matching regex'
complete -f -c btrfs -n $restore -l path-regex -r -d 'Restore only filenames matching regex'
complete -f -c btrfs -n $restore -s c -d 'Ignore case (--path-regex only)'
# btrfs send
complete -f -c btrfs -n $send -s e -d ''
complete -f -c btrfs -n $send -s p -d 'Send an incremental stream from <parent> to <subvol>'
complete -f -c btrfs -n $send -s c -d 'Use this snapshot as a clone source for an incremental send'
complete -f -c btrfs -n $send -s f -d 'Output is normally written to stdout'
complete -f -c btrfs -n $send -s p -r -d 'Send an incremental stream from <parent> to <subvol>'
complete -f -c btrfs -n $send -s c -r -d 'Use this snapshot as a clone source for an incremental send'
complete -f -c btrfs -n $send -s f -r -d 'Output is normally written to stdout'
complete -f -c btrfs -n $send -l no-data -d 'send in NO_FILE_DATA mode'
complete -f -c btrfs -n $send -s v -l verbose -d 'Enable verbose output to stderr'
complete -f -c btrfs -n $send -s q -l quiet -d 'Suppress all messages, except errors'
complete -f -c btrfs -n $send -l proto -a '0 1 2' -r -d 'Use send protocol version'
complete -f -c btrfs -n $send -l proto -l compressed-data -d 'Send compressed data directly'
# btrfs receive
complete -f -c btrfs -n $receive -s v -d 'Increase verbosity about performed actions'
complete -f -c btrfs -n $receive -s q -l quiet -d 'Suppress all messages, except errors'
complete -f -c btrfs -n $receive -s f -d 'Read the stream from FILE instead of stdin'
complete -f -c btrfs -n $receive -s f -r -d 'Read the stream from FILE instead of stdin'
complete -f -c btrfs -n $receive -s e -d 'Terminate after receiving an <end cmd> marker in the stream'
complete -f -c btrfs -n $receive -s C -l chroot -d 'Confine the process to <mount> using chroot'
complete -f -c btrfs -n $receive -s E -l max-errors -d 'Terminate when NUMBER errors occur'
complete -f -c btrfs -n $receive -s m -d 'The root mount point of the destination filesystem'
complete -f -c btrfs -n $receive -s E -l max-errors -r -d 'Terminate when NUMBER errors occur'
complete -f -c btrfs -n $receive -s m -r -d 'The root mount point of the destination filesystem'
complete -f -c btrfs -n $receive -l force-decompress -r -d 'Always decompress data'
complete -f -c btrfs -n $receive -l dump -d 'Dump stream metadata'
# btrfs help
@@ -147,9 +150,11 @@ complete -f -c btrfs -n $subvolume -a show -d 'Show more information about the s
complete -f -c btrfs -n $subvolume -a sync -d 'Wait until given subvolume(s) are completely removed from the filesystem.'
# btrfs subvolume create
complete -f -c btrfs -n '__btrfs_command_groups subvolume create' -s i -d 'Add subvolume to a qgroup (can be given multiple times)'
complete -f -c btrfs -n '__btrfs_command_groups subvolume create' -s p -d 'Create any missing parent directories'
# btrfs subvolume delete
complete -f -c btrfs -n '__btrfs_command_groups subvolume delete' -s c -l commit-after -d 'Wait for transaction commit at the end of the operation'
complete -f -c btrfs -n '__btrfs_command_groups subvolume delete' -s C -l commit-each -d 'Wait for transaction commit after deleting each subvolume'
complete -f -c btrfs -n '__btrfs_command_groups subvolume delete' -s R -l recursive -d 'Delete subvolumes beneath each subvolume recursively'
complete -f -c btrfs -n '__btrfs_command_groups subvolume delete' -s v -l verbose -d 'Verbose output of operations'
# btrfs subvolume list
complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s o -d 'Print only subvolumes below specified path'
@@ -164,8 +169,8 @@ complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s s -d 'List on
complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s r -d 'List readonly subvolumes (including snapshots)'
complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s d -d 'List deleted subvolumes that are not yet cleaned'
complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s t -d 'Print the result as a table'
complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s G -d 'Filter the subvolumes by generation'
complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s C -d 'Filter the subvolumes by ogeneration'
complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s G -r -d 'Filter the subvolumes by generation'
complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s C -r -d 'Filter the subvolumes by ogeneration'
complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -l sort -d 'List the subvolume in order' -a '{gen,ogen,rootid,path}'
# btrfs subvolume snapshot
complete -f -c btrfs -n '__btrfs_command_groups subvolume snapshot' -s r -d 'Create a readonly snapshot'
@@ -194,6 +199,7 @@ complete -f -c btrfs -n $filesystem -a defragment -d 'Defragment a file or a dir
complete -f -c btrfs -n $filesystem -a resize -d 'Resize a filesystem'
complete -f -c btrfs -n $filesystem -a label -d 'Get or change the label of a filesystem'
complete -f -c btrfs -n $filesystem -a usage -d 'Show detailed information about internal filesystem usage.'
complete -f -c btrfs -n $filesystem -a mkswapfile -d 'Create a new swapfile'
# btrfs filesystem df
complete -f -c btrfs -n '__btrfs_command_groups filesystem df' -s b -l raw -d 'Show raw numbers in bytes'
complete -f -c btrfs -n '__btrfs_command_groups filesystem df' -s h -l human-readable -d 'Show human friendly numbers, base 1024'
@@ -230,9 +236,12 @@ complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s v -d '
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s r -d 'Defragment files recursively'
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s c -d 'Compress the file while defragmenting' -ra '{zlib,lzo,zstd}'
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s f -d 'Flush data to disk immediately after defragmenting'
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s s -d 'Defragment only from NUMBER byte onward'
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s l -d 'Defragment only up to LEN bytes'
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s t -d 'Target extent SIZE hint'
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s s -r -d 'Defragment only from NUMBER byte onward'
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s l -r -d 'Defragment only up to LEN bytes'
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s t -r -d 'Target extent SIZE hint'
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -l step -r -d 'Defragment in steps of SIZE'
complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s L -l level -r -d 'Specify compression levels'
# btrfs filesystem usage
complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s b -l raw -d 'Show raw numbers in bytes'
complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s h -l human-readable -d 'Show human friendly numbers, base 1024'
@@ -244,6 +253,11 @@ complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s m -l mbytes
complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s g -l gbytes -d 'Show sizes in GiB, or GB with --si'
complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s t -l tbytes -d 'Show sizes in TiB, or TB with --si'
complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s T -d 'Show data in tabular format'
# btrfs filesystem mkswapfile
complete -f -c btrfs -n '__btrfs_command_groups filesystem mkswapfile' -s s -l size -r -d 'Swapfile size'
complete -f -c btrfs -n '__btrfs_command_groups filesystem mkswapfile' -s U -l uuid -r -d 'UUID for the swapfile'
# btrfs filesystem resize
complete -f -c btrfs -n '__btrfs_command_groups filesystem resize' -l enqueue -d 'Wait for other exclusive operations'
# btrfs balance
complete -f -c btrfs -n $balance -a start -d 'Balance chunks across the devices'
@@ -252,13 +266,23 @@ complete -f -c btrfs -n $balance -a cancel -d 'Cancel running or paused balance'
complete -f -c btrfs -n $balance -a resume -d 'Resume interrupted balance'
complete -f -c btrfs -n $balance -a status -d 'Show status of running or paused balance'
# btrfs balance start
complete -f -c btrfs -n '__btrfs_command_groups balance start' -s d -d 'Act on data chunks with FILTERS'
complete -f -c btrfs -n '__btrfs_command_groups balance start' -s m -d 'Act on metadata chunks with FILTERS'
complete -f -c btrfs -n '__btrfs_command_groups balance start' -s s -d 'Act on system chunks with FILTERS (only under -f)'
function __btrfs_balance_filters
set -l profiles raid{0,1{,c3,c4},10,5,6} dup single
set -l btrfs_balance_filters \
profiles=$profiles\t"Balances only block groups with the given profiles" \
convert=$profiles\t"Convert selected block groups to given profile" \
usage= devid= vrange= limit= strips= soft
set -l prefix (commandline -tc | string replace -r '^-d' -- '' | string match -rg '^(.*?)?[^,]*$' -- $token)
printf "%s\n" "$prefix"$btrfs_balance_filters
end
complete -f -c btrfs -n '__btrfs_command_groups balance start' -s d -ra '(__btrfs_balance_filters)' -d 'Act on data chunks with FILTERS'
complete -f -c btrfs -n '__btrfs_command_groups balance start' -s m -ra '(__btrfs_balance_filters)' -d 'Act on metadata chunks with FILTERS'
complete -f -c btrfs -n '__btrfs_command_groups balance start' -s s -ra '(__btrfs_balance_filters)' -d 'Act on system chunks with FILTERS (only under -f)'
complete -f -c btrfs -n '__btrfs_command_groups balance start' -s v -d 'Be verbose'
complete -f -c btrfs -n '__btrfs_command_groups balance start' -s f -d 'Force a reduction of metadata integrity'
complete -f -c btrfs -n '__btrfs_command_groups balance start' -l full-balance -d 'Do not print warning and do not delay start'
complete -f -c btrfs -n '__btrfs_command_groups balance start' -l background -l bg -d 'Run the balance as a background process'
complete -f -c btrfs -n '__btrfs_command_groups balance start' -l enqueue -d 'Wait for other exclusive operations'
# btrfs balance status
complete -f -c btrfs -n '__btrfs_command_groups balance status' -s v -d 'Be verbose'
@@ -279,6 +303,10 @@ complete -f -c btrfs -n '__btrfs_command_groups device scan' -s u -l forget -d '
# btrfs device stats
complete -f -c btrfs -n '__btrfs_command_groups device stats' -s c -l check -d 'Return non-zero if any stat counter is not zero'
complete -f -c btrfs -n '__btrfs_command_groups device stats' -s z -l reset -d 'Show current stats and reset values to zero'
complete -f -c btrfs -n '__btrfs_command_groups device stats' -s T -d "Print stats in a tabular form"
# btrfs device remove
complete -f -c btrfs -n '__btrfs_command_groups device remove' -l enqueue -d 'Wait for other exclusive operations'
complete -f -c btrfs -n '__btrfs_command_groups device remove' -l force -d 'Skip the safety timeout for removing multiple devices'
# btrfs device usage
complete -f -c btrfs -n '__btrfs_command_groups device usage' -s b -l raw -d 'Show raw numbers in bytes'
complete -f -c btrfs -n '__btrfs_command_groups device usage' -s h -l human-readable -d 'Show human friendly numbers, base 1024'
@@ -295,12 +323,18 @@ complete -f -c btrfs -n $scrub -a start -d 'Start a new scrub. If a scrub is alr
complete -f -c btrfs -n $scrub -a cancel -d 'Cancel a running scrub'
complete -f -c btrfs -n $scrub -a resume -d 'Resume previously canceled or interrupted scrub'
complete -f -c btrfs -n $scrub -a status -d 'Show status of running or finished scrub'
complete -f -c btrfs -n $scrub -a limit -d 'Show or set scrub limits on devices of the given filesystem'
# btrfs scrub limit
complete -f -c btrfs -n '__btrfs_command_groups scrub limit' -s d -l devid -d 'Select the device by DEVID to apply the limit'
complete -f -c btrfs -n '__btrfs_command_groups scrub limit' -s l -l limit -d 'Set the limit of the device'
complete -f -c btrfs -n '__btrfs_command_groups scrub limit' -s a -l all -d 'Apply the limit to all devices'
# btrfs scrub start
complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s B -d 'Do not background'
complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s d -d 'Stats per device (-B only)'
complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s q -d 'Be quiet'
complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s r -d 'Read only mode'
complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s R -d 'Raw print mode, print full data instead of summary'
complete -f -c btrfs -n '__btrfs_command_groups scrub start' -l limit -d 'Set the scrub throughput limit'
complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s c -d 'Set ioprio class (see ionice(1) manpage)'
complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s n -d 'Set ioprio classdata (see ionice(1) manpage)'
complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s f -d 'Force starting new scrub'
@@ -321,6 +355,9 @@ complete -f -c btrfs -n $rescue -a chunk-recover -d 'Recover the chunk tree by s
complete -f -c btrfs -n $rescue -a super-recover -d 'Recover bad superblocks from good copies'
complete -f -c btrfs -n $rescue -a zero-log -d 'Clear the tree log. Usable if it\'s corrupted and prevents mount.'
complete -f -c btrfs -n $rescue -a fix-device-size -d 'Re-align device and super block sizes'
complete -f -c btrfs -n $rescue -a clear-ino-cache -d 'Remove leftover items pertaining to the deprecated inode cache feature'
complete -f -c btrfs -n $rescue -a clear-space-cache -d 'Completely remove the on-disk data of free space cache of given version'
complete -f -c btrfs -n $rescue -a clear-uuid-tree -d 'Clear the UUID tree'
# btrfs rescue chunk-recover
complete -f -c btrfs -n '__btrfs_command_groups rescue chunk-recover' -s y -d 'Assume an answer of YES to all questions'
complete -f -c btrfs -n '__btrfs_command_groups rescue chunk-recover' -s v -d 'Verbose mode'
@@ -337,6 +374,8 @@ complete -f -c btrfs -n $inspect_internal -a min-dev-size -d 'Get the minimum si
complete -f -c btrfs -n $inspect_internal -a dump-tree -d 'Dump tree structures from a given device'
complete -f -c btrfs -n $inspect_internal -a dump-super -d 'Dump superblock from a device in a textual form'
complete -f -c btrfs -n $inspect_internal -a tree-stats -d 'Print various stats for trees'
complete -f -c btrfs -n $inspect_internal -a list-chunks -d 'Enumerate chunks on all devices'
complete -f -c btrfs -n $inspect_internal -a map-swapfile -d 'Find device-specific physical offset of file that can be used for hibernation'
# btrfs inspect-internal inode-resolve
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal inode-resolve' -s v -d 'Verbose mode'
# btrfs inspect-internal logical-resolve
@@ -351,20 +390,50 @@ complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s d
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s r -l roots -d 'Print only short root node info'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s R -l backups -d 'Print short root node info and backup root info'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s u -l uuid -d 'Print only the uuid tree'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s b -l block -d 'Print info from the specified BLOCK only'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s t -l tree -d 'Print only tree with the given ID'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s b -l block -r -d 'Print info from the specified BLOCK only'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s t -l tree -r -d 'Print only tree with the given ID'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l follow -d 'Use with -b, to show all children tree blocks of <block_num>'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l noscan -d 'Do not scan the devices from the filesystem'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l bfs -d 'Breadth-first traversal of the trees, print nodes, then leaves'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l dfs -d 'Depth-first traversal of the trees'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l hide-names -d 'Print placeholder instead of names'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l csum-headers -d 'Print b-tree node checksums in headers'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l csum-items -d 'Print checksums stored in checksum items'
# btrfs inspect-internal dump-super
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-super' -s f -l full -d 'Print full superblock information, backup roots etc.'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-super' -s a -l all -d 'Print information about all superblocks'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-super' -s s -l super -d 'Specify which SUPER-BLOCK copy to print out' -ra '{0,1,2}'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-super' -s F -l force -d 'Attempt to dump superblocks with bad magic'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-super' -l bytenr -d 'Specify alternate superblock OFFSET'
# btrfs inspect-internal logical-resolve
complete -f -c btrfs -n '__btrfs_command_groups logical-resolve' -s P -d 'Print inodes instead of resolving paths'
complete -f -c btrfs -n '__btrfs_command_groups logical-resolve' -s o -d 'Ignore offsets, find all references to an extent'
complete -f -c btrfs -n '__btrfs_command_groups logical-resolve' -s s -r -d 'Set internal buffer size for storing file names'
# btrfs inspect-internal tree-stats
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -s b -d 'Show raw numbers in bytes'
# btrfs inspect-internal list-chunks
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l sort -ra 'devid pstart lstart usage length' -d 'Sort by a column (ascending)'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l raw -d 'Show raw numbers in bytes'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l human-readable -d 'Show human friendly numbers, base 1024'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l iec -d 'Use 1024 as a base (KiB, MiB, GiB, TiB)'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l si -d 'Use 1000 as a base (kB, MB, GB, TB)'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l kbytes -d 'Show sizes in KiB, or kB with --si'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l mbytes -d 'Show sizes in MiB, or MB with --si'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l gbytes -d 'Show sizes in GiB, or GB with --si'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l tbytes -d 'Show sizes in TiB, or TB with --si'
# btrfs inspect-internal map-swapfile
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal map-swapfile' -l resume-offset -s r -d 'Print the value suitable as resume offset for /sys/power/resume_offset.'
# btrfs inspect-internal tree-stats
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -s t -r -d 'Print stats only for the given treeid'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l raw -s b -d 'Show raw numbers in bytes'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l human-readable -d 'Show human friendly numbers, base 1024'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l iec -d 'Use 1024 as a base (KiB, MiB, GiB, TiB)'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l si -d 'Use 1000 as a base (kB, MB, GB, TB)'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l kbytes -d 'Show sizes in KiB, or kB with --si'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l mbytes -d 'Show sizes in MiB, or MB with --si'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l gbytes -d 'Show sizes in GiB, or GB with --si'
complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l tbytes -d 'Show sizes in TiB, or TB with --si'
# btrfs property
complete -f -c btrfs -n $property -a get -d 'Get a property value of a btrfs object'
@@ -381,9 +450,12 @@ complete -f -c btrfs -n '__btrfs_command_groups property list' -s t -d 'List pro
complete -f -c btrfs -n $quota -a enable -d 'Enable subvolume quota support for a filesystem.'
complete -f -c btrfs -n $quota -a disable -d 'Disable subvolume quota support for a filesystem.'
complete -f -c btrfs -n $quota -a rescan -d 'Trash all qgroup numbers and scan the metadata again with the current config.'
# btrfs quota enable
complete -f -c btrfs -n '__btrfs_command_groups quota enable' -s s -l simple -d 'Use simple quotas'
# btrfs quota rescan
complete -f -c btrfs -n '__btrfs_command_groups quota rescan' -s s -d 'Show status of a running rescan operation'
complete -f -c btrfs -n '__btrfs_command_groups quota rescan' -s w -d 'Wait for rescan operation to finish'
complete -f -c btrfs -n '__btrfs_command_groups quota rescan' -s s -l status -d 'Show status of a running rescan operation'
complete -f -c btrfs -n '__btrfs_command_groups quota rescan' -s w -l wait -d 'Wait for rescan operation to finish'
complete -f -c btrfs -n '__btrfs_command_groups quota rescan' -s W -l wait-norescan -d 'Wait for rescan to finish without starting it'
# btrfs qgroup
complete -f -c btrfs -n $qgroup -a assign -d 'Assign SRC as the child qgroup of DST'
@@ -391,6 +463,7 @@ complete -f -c btrfs -n $qgroup -a remove -d 'Remove a child qgroup SRC from DST
complete -f -c btrfs -n $qgroup -a create -d 'Create a subvolume quota group.'
complete -f -c btrfs -n $qgroup -a destroy -d 'Destroy a quota group.'
complete -f -c btrfs -n $qgroup -a show -d 'Show subvolume quota groups.'
complete -f -c btrfs -n $qgroup -a clear-stale -d 'Clear all stale qgroups whose subvolume does not exist'
complete -f -c btrfs -n $qgroup -a limit -d 'Set the limits a subvolume quota group.'
# btrfs qgroup assign
complete -f -c btrfs -n '__btrfs_command_groups qgroup assign' -l rescan -d 'Schedule qutoa rescan if needed'
@@ -424,5 +497,7 @@ complete -f -c btrfs -n $replace -a cancel -d 'Cancel a running device replace o
complete -f -c btrfs -n '__btrfs_command_groups replace start' -s r -d 'Only read from <srcdev> if no other zero-defect mirror exists'
complete -f -c btrfs -n '__btrfs_command_groups replace start' -s f -d 'Force using and overwriting <targetdev>'
complete -f -c btrfs -n '__btrfs_command_groups replace start' -s B -d 'Do not background'
complete -f -c btrfs -n '__btrfs_command_groups replace start' -l enqueue -d "Wait if there's another exclusive operation running"
complete -f -c btrfs -n '__btrfs_command_groups replace start' -s K -l nodiscard -d 'Do not perform TRIM on DEVICES'
# btrfs replace status
complete -f -c btrfs -n '__btrfs_command_groups replace status' -s 1 -d 'Only print once until the replace operation finishes'

View File

@@ -3,9 +3,6 @@
## --- WRITTEN MANUALLY ---
set -l __fish_cargo_subcommands (cargo --list 2>&1 | string replace -rf '^\s+([^\s]+)\s*(.*)' '$1\t$2' | string escape)
# Append user-installed extensions (e.g. cargo-foo, invokable as `cargo foo`) to the list of subcommands (à la git)
set -la __fish_cargo_subcommands (complete -C'cargo-' | string replace -rf '^cargo-(\w+).*' '$1')
complete -c cargo -f -c cargo -n __fish_use_subcommand -a "$__fish_cargo_subcommands"
complete -c cargo -x -c cargo -n '__fish_seen_subcommand_from help' -a "$__fish_cargo_subcommands"
@@ -53,27 +50,6 @@ end
complete -c cargo -n '__fish_seen_subcommand_from run test build debug check' -l package \
-xa "(__fish_cargo_packages)"
# Look up crates.io crates matching the single argument provided to this function
function __fish_cargo_search
if test (string length -- "$argv[1]") -le 2
# Don't waste time searching for strings with too many results to realistically
# provide a meaningful completion within our results limit.
return
end
# This doesn't do a prefix search, so bump up the limit a tiny bit to try and
# get enough results to show something.
cargo search --color never --quiet --limit 20 -- $argv[1] 2>/dev/null |
# Filter out placeholders and "... and xxx more crates"
string match -rvi '^\.\.\.|= "0.0.0"|# .*(reserved|yanked)' |
# Remove the version number and map the description
string replace -rf '^([^ ]+).*# (.*)' '$1\t$2'
end
# Complete possible crate names by search the crates.io index
complete -c cargo -n '__fish_seen_subcommand_from add install' -n '__fish_is_nth_token 2' \
-a "(__fish_cargo_search (commandline -ct))"
## --- AUTO-GENERATED WITH `cargo complete fish` ---
# Manually massaged to improve some descriptions
complete -c cargo -n __fish_use_subcommand -l explain -d 'Run `rustc --explain CODE`'

View File

@@ -49,10 +49,10 @@ function __fish_clj_tools -V bb_helper
bb -e "$bb_helper" tools
end
complete -c clj -s X -x -r -k -a "(__fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath or supply exec fn/args"
complete -c clj -s A -x -r -k -a "(__fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath"
complete -c clj -s M -x -r -k -a "(__fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath or supply main opts"
complete -c clj -s T -x -r -k -a "(__fish_complete_list : __fish_clj_tools)" -d "Invoke tool by name or via aliases ala -X"
complete -c clj -s X -x -r -k -a "(__fish_stripprefix='^-\w*X' __fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath or supply exec fn/args"
complete -c clj -s A -x -r -k -a "(__fish_stripprefix='^-\w*A' __fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath"
complete -c clj -s M -x -r -k -a "(__fish_stripprefix='^-\w*M' __fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath or supply main opts"
complete -c clj -s T -x -r -k -a "(__fish_stripprefix='^-\w*T' __fish_complete_list : __fish_clj_tools)" -d "Invoke tool by name or via aliases ala -X"
complete -c clj -f -o Sdeps -r -d "Deps data to use as the last deps file to be merged"
complete -c clj -f -o Spath -d "Compute classpath and echo to stdout only"

View File

@@ -94,3 +94,6 @@ complete -c cryptsetup -l veracrypt-query-pim -d "Query Personal Iteration Multi
complete -c cryptsetup -l verbose -s v -d "Shows more detailed error messages"
complete -c cryptsetup -l verify-passphrase -s y -d "Verifies the passphrase by asking for it twice"
complete -c cryptsetup -l version -s V -d "Print package version"
# subcommands
complete -c cryptsetup -n "__fish_seen_subcommand_from close status resize" -f -r -a "(path basename /dev/mapper/* | string match -v control)"

View File

@@ -85,7 +85,7 @@ complete -c equery -n '__fish_seen_subcommand_from f files' -s s -l timestamp -d
complete -c equery -n '__fish_seen_subcommand_from f files' -s t -l type -d "Include file type in output"
complete -c equery -n '__fish_seen_subcommand_from f files' -l tree -d "Display results in a tree"
complete -c equery -n '__fish_seen_subcommand_from f files' -s f -l filter -d "Filter output by file type" \
-xa "(__fish_complete_list , __fish_equery_files_filter_args)"
-xa "(__fish_stripprefix='^(--filter=|-\w*f)' __fish_complete_list , __fish_equery_files_filter_args)"
# has + hasuse
complete -c equery -n '__fish_seen_subcommand_from a has h hasuse' -s I -l exclude-installed -d "Exclude installed pkgs from search path"

View File

@@ -0,0 +1,27 @@
complete -f -c git -a subtree -d 'Manage git subtrees'
# Git subtree common completions
complete -f -c git -n '__fish_git_using_command subtree' -s q -l quiet -d 'Suppress output'
complete -f -c git -n '__fish_git_using_command subtree' -s d -l debug -d 'Debug output'
complete -f -c git -n '__fish_git_using_command subtree' -s P -l path -d 'Path to the subtree'
# Git subtree subcommands
complete -f -c git -n '__fish_git_using_command subtree' -a add -d "Add a new subtree to the repository"
complete -f -c git -n '__fish_git_using_command subtree' -a merge -d "Merge changes from a subtree into the repository"
complete -f -c git -n '__fish_git_using_command subtree' -a split -d "Extract a subtree from the repository"
complete -f -c git -n '__fish_git_using_command subtree' -a pull -d "Fetch and integrate changes from a remote subtree"
complete -f -c git -n '__fish_git_using_command subtree' -a push -d "Push changes to a remote subtree"
# Completions for push and split subcommands
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split' -l annotate -d 'Annotate the commit'
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split' -s b -l branch -d 'Branch to split'
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split' -l ignore-joins -d 'Ignore joins during history reconstruction'
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split' -l onto -d 'Specify the commit ID to start history reconstruction'
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split' -l rejoin -d 'Merge the synthetic history back into the main project'
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split; and __fish_contains_opt rejoin' -l squash -d 'Merge subtree changes as a single commit'
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split; and __fish_contains_opt rejoin' -l no-squash -d 'Do not merge subtree changes as a single commit'
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split; and __fish_contains_opt rejoin' -s m -l message -d 'Use the given message as the commit message for the merge commit'
# Completion for add and merge subcommands
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from add merge' -l squash -d 'Merge subtree changes as a single commit'
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from add merge' -l no-squash -d 'Do not merge subtree changes as a single commit'
complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from add merge' -s m -l message -d 'Use the given message as the commit message for the merge commit'

View File

@@ -653,6 +653,7 @@ function __fish_git_aliased_command
end
end
set -g __fish_git_aliases
git config -z --get-regexp 'alias\..*' | while read -lz alias cmdline
set -l command (__fish_git_aliased_command $cmdline)
string match -q --regex '\w+' -- $command; or continue
@@ -778,7 +779,8 @@ function __fish_git_custom_commands
# if any of these completion results match the name of the builtin git commands,
# but it's simpler just to blacklist these names. They're unlikely to change,
# and the failure mode is we accidentally complete a plumbing command.
for name in (string replace -r "^.*/git-([^/]*)" '$1' $PATH/git-*)
set -l git_subcommands $PATH/git-*
for name in (string replace -r "^.*/git-([^/]*)" '$1' $git_subcommands)
switch $name
case cvsserver receive-pack shell upload-archive upload-pack
# skip these
@@ -899,6 +901,15 @@ function __fish_git_is_rebasing
test -e (__fish_git rev-parse --absolute-git-dir)/rebase-merge
end
function __fish_git_filters
printf "%s\n" \
blob:none\t"omits all blobs" \
blob:limit=\t"omits blobs by size" \
object:type={tag,commit,tree,blob}\t"omit object which are not of the requested type" \
sparse:oid=\t"omit blobs not required for a sparse checkout" \
tree:\t"omits all blobs and trees"
end
# general options
complete git -f -l help -s h -d 'Display manual of a Git command'
complete git -f -n __fish_git_needs_command -l version -s v -d 'display git version'
@@ -1033,6 +1044,7 @@ complete -f -c git -n '__fish_git_using_command fetch' -l unshallow -d 'Convert
complete -f -c git -n '__fish_git_using_command fetch' -l refetch -d 'Re-fetch without negotiating common commits'
complete -f -c git -n '__fish_git_using_command fetch' -l negotiation-tip -d 'Only report commits reachable from these tips' -kxa '(__fish_git_commits; __fish_git_branches)'
complete -f -c git -n '__fish_git_using_command fetch' -l negotiate-only -d "Don't fetch, only show commits in common with the server"
complete -f -c git -n '__fish_git_using_command fetch' -l filter -ra '(__fish_git_filters)' -d 'Request a subset of objects from server'
# TODO other options
@@ -1347,6 +1359,7 @@ complete -f -c git -n '__fish_git_using_command clone' -s o -l origin -d 'Use a
complete -f -c git -n '__fish_git_using_command clone' -s b -l branch -d 'Use a specific branch instead of the one used by the cloned repository'
complete -f -c git -n '__fish_git_using_command clone' -l depth -d 'Truncate the history to a specified number of revisions'
complete -f -c git -n '__fish_git_using_command clone' -l recursive -d 'Initialize all submodules within the cloned repository'
complete -f -c git -n '__fish_git_using_command clone' -l filter -ra '(__fish_git_filters)' -d 'Partial clone by requesting a subset of objects from server'
### commit
complete -c git -n __fish_git_needs_command -a commit -d 'Record changes to the repository'
@@ -1595,6 +1608,7 @@ complete -c git -n '__fish_git_using_command log rev-list' -l bisect
complete -c git -n '__fish_git_using_command log rev-list' -l stdin -d 'Read commits from stdin'
complete -c git -n '__fish_git_using_command log rev-list' -l cherry-mark -d 'Mark equivalent commits with = and inequivalent with +'
complete -c git -n '__fish_git_using_command log rev-list' -l cherry-pick -d 'Omit equivalent commits'
complete -f -c git -n '__fish_git_using_command rev-list' -l filter -ra '(__fish_git_filters)' -d 'Omits objects from the list of printed objects'
complete -c git -n '__fish_git_using_command log' -l left-only
complete -c git -n '__fish_git_using_command log' -l right-only
complete -c git -n '__fish_git_using_command log' -l cherry
@@ -1798,6 +1812,8 @@ complete -f -c git -n '__fish_git_using_command merge' -l rerere-autoupdate -d '
complete -f -c git -n '__fish_git_using_command merge' -l no-rerere-autoupdate -d 'Do not use previous conflict resolutions'
complete -f -c git -n '__fish_git_using_command merge' -l abort -d 'Abort the current conflict resolution process'
complete -f -c git -n '__fish_git_using_command merge' -l continue -d 'Conclude current conflict resolution process'
complete -f -c git -n '__fish_git_using_command merge' -l autostash -d 'Before starting merge, stash local changes, and apply stash when done'
complete -f -c git -n '__fish_git_using_command merge' -l no-autostash -d 'Do not stash local changes before starting merge'
### merge-base
complete -f -c git -n __fish_git_needs_command -a merge-base -d 'Find a common ancestor for a merge'
@@ -2278,6 +2294,7 @@ complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subco
complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from update' -s N -l no-fetch -d "Don't fetch new objects from the remote"
complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from update' -l remote -d "Instead of using superproject's SHA-1, use the state of the submodule's remote-tracking branch"
complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from update' -l force -d "Discard local changes when switching to a different commit & always run checkout"
complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from update' -l filter -ra '(__fish_git_filters)' -d 'Request a subset of objects from server'
complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from add' -l force -d "Also add ignored submodule path"
complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from deinit' -l force -d "Remove even with local changes"
complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from deinit' -l all -d "Remove all submodules"
@@ -2571,14 +2588,15 @@ function __fish_git_complete_custom_command -a subcommand
set -e cmd[1] # Drop "git".
set -l subcommand_args
if argparse -s (__fish_git_global_optspecs) -- $cmd
set subcommand_args $argv[2..] # Drop the subcommand.
set subcommand_args (string escape -- $argv[2..]) # Drop the subcommand.
end
complete -C "git-$subcommand $(string escape -- $subcommand_args) "(commandline -ct)
complete -C "git-$subcommand $subcommand_args "(commandline -ct)
end
# source git-* commands' autocompletion file if exists
set -l __fish_git_custom_commands_completion
for file in (path filter -xZ $PATH/git-* | path basename)
set -l git_subcommands $PATH/git-*
for file in (path filter -xZ $git_subcommands | path basename)
# Already seen this command earlier in $PATH.
contains -- $file $__fish_git_custom_commands_completion
and continue

View File

@@ -4,5 +4,5 @@ complete -c gpasswd -s d -l delete -d 'Remove user from group' -xa '(__fish_comp
complete -c gpasswd -s h -l help -d 'Print help'
complete -c gpasswd -s r -l remove-password -d 'Remove the GROUP\'s password'
complete -c gpasswd -s R -l restrict -d 'Restrict access to GROUP to its members'
complete -c gpasswd -s M -l members -d 'Set the list of members of GROUP' -xa '(__fish_complete_list , __fish_complete_users)'
complete -c gpasswd -s A -l administrators -d 'set the list of administrators for GROUP' -xa '(__fish_complete_list , __fish_complete_users)'
complete -c gpasswd -s M -l members -d 'Set the list of members of GROUP' -xa "(__fish_stripprefix='^(--members=|-\w*M)' __fish_complete_list , __fish_complete_users)"
complete -c gpasswd -s A -l administrators -d 'set the list of administrators for GROUP' -xa "(__fish_stripprefix='^(--administrators=|-\w*A)' __fish_complete_list , __fish_complete_users)"

View File

@@ -36,7 +36,7 @@ complete -c $command -s x -x \
-n $compile_condition
complete -c $command -s W -l warning \
-a '(__fish_complete_list , __fish_guild__complete_warnings)' \
-a "(__fish_stripprefix='^(--warning=|-\w*W)' __fish_complete_list , __fish_guild__complete_warnings)" \
-d 'Specify the warning level for a compilation' \
-n $compile_condition

View File

@@ -85,7 +85,7 @@ complete -c $command -o ds \
-d 'Treat the last -s option as if it occurred at this point'
complete -c $command -l use-srfi \
-a '(__fish_complete_list , __fish_guile__complete_srfis)' \
-a "(__fish_stripprefix='^--use-srfi=' __fish_complete_list , __fish_guile__complete_srfis)" \
-d 'Specify the SRFI modules to load'
for standard in 6 7

View File

@@ -58,7 +58,7 @@ complete -c hashcat -l restore -d "Restore session from --session"
complete -c hashcat -l restore-disable -d "Do not write restore file"
complete -c hashcat -l restore-file-path -rF -d "Specific path to restore file"
complete -c hashcat -s o -l outfile -rF -d "Define outfile for recovered hash"
complete -c hashcat -l outfile-format -xa "(__fish_complete_list , __fish_hashcat_outfile_formats)" -d "Outfile formats to use"
complete -c hashcat -l outfile-format -xa "(__fish_stripprefix='^--outfile-format=' __fish_complete_list , __fish_hashcat_outfile_formats)" -d "Outfile formats to use"
complete -c hashcat -l outfile-autohex-disable -d "Disable the use of \$HEX[] in output plains"
complete -c hashcat -l outfile-check-timer -x -d "Sets seconds between outfile checks"
complete -c hashcat -l wordlist-autohex-disable -d "Disable the conversion of \$HEX[] from the wordlist"
@@ -106,7 +106,7 @@ complete -c hashcat -l backend-ignore-metal -d "Do not try to open Metal interfa
complete -c hashcat -l backend-ignore-opencl -d "Do not try to open OpenCL interface on startup"
complete -c hashcat -s I -l backend-info -d "Show info about detected backend API devices"
complete -c hashcat -s d -l backend-devices -x -d "Backend devices to use"
complete -c hashcat -s D -l opencl-device-types -xa "(__fish_complete_list , __fish_hashcat_device_types)" -d "OpenCL device-types to use"
complete -c hashcat -s D -l opencl-device-types -xa "(__fish_stripprefix='^(--opencl-device-types=|-\w*D)' __fish_complete_list , __fish_hashcat_device_types)" -d "OpenCL device-types to use"
complete -c hashcat -s O -l optimized-kernel-enable -d "Enable optimized kernels (limits password length)"
complete -c hashcat -s M -l multiply-accel-disable -d "Disable multiply kernel-accel with processor count"
complete -c hashcat -s w -l workload-profile -d "Enable a specific workload profile" -xa "

View File

@@ -1 +1,10 @@
jj util completion fish | source
# The reason for `__this-command-does-not-exist` is that, if dynamic completion
# is not implemented, we'd like to get an error reliably. However, the
# behavior of `jj` without arguments depends on the value of a config, see
# https://jj-vcs.github.io/jj/latest/config/#default-command
if set -l completion (COMPLETE=fish jj __this-command-does-not-exist 2>/dev/null)
# jj is new enough for dynamic completions to be implemented
printf %s\n $completion | source
else
jj util completion fish | source
end

View File

@@ -9,7 +9,7 @@ function __fish_john_formats --description "Print JohnTheRipper hash formats"
end
complete -c john -l help -d "print usage summary"
complete -c john -l single -fa "(__fish_complete_list , __fish_john_rules)" -d "single crack mode"
complete -c john -l single -fa "(__fish_stripprefix='^--single=' __fish_complete_list , __fish_john_rules)" -d "single crack mode"
complete -c john -l single-seed -rf -d "add static seed word(s) for all salts in single mode"
complete -c john -l single-wordlist -rF -d "short wordlist with static seed words/morphemes"
complete -c john -l single-user-seed -rF -d "wordlist with seeds per username"
@@ -35,8 +35,8 @@ complete -c john -l prince-case-permute -d "permute case of first letter"
complete -c john -l prince-mmap -d "memory-map infile"
complete -c john -l prince-keyspace -d "just show total keyspace that would be produced"
complete -c john -l encoding -l input-encoding -fa "$__fish_john_encodings" -d "input encoding"
complete -c john -l rules -fa "(__fish_complete_list , __fish_john_rules)" -d "enable word mangling rules"
complete -c john -l rules-stack -fa "(__fish_complete_list , __fish_john_rules)" -d "stacked rules"
complete -c john -l rules -fa "(__fish_stripprefix='^--rules=' __fish_complete_list , __fish_john_rules)" -d "enable word mangling rules"
complete -c john -l rules-stack -fa "(__fish_stripprefix='^--rules-stack=' __fish_complete_list , __fish_john_rules)" -d "stacked rules"
complete -c john -l rules-skip-nop -d "skip any NOP rules"
complete -c john -l incremental -fa "(john --list=inc-modes 2>/dev/null)" -d "incremental mode"
complete -c john -l incremental-charcount -rf -d "override CharCount for incremental mode"
@@ -97,4 +97,4 @@ complete -c john -l internal-codepage -fa "$__fish_john_encodings" -d "codepage
complete -c john -l target-encoding -fa "$__fish_john_encodings" -d "output encoding"
complete -c john -l tune -fa "auto report N" -d "tuning options"
complete -c john -l force-tty -d "set up terminal for reading keystrokes"
complete -c john -l format -fa "(__fish_complete_list , __fish_john_formats)" -d "force hash type"
complete -c john -l format -fa "(__fish_stripprefix='^--format=' __fish_complete_list , __fish_john_formats)" -d "force hash type"

View File

@@ -41,7 +41,7 @@ complete -c losetup -s v -l verbose -d "Verbose mode"
complete -c losetup -s J -l json -d "Use JSON --list output format"
complete -c losetup -s l -l list -d "List info about all or specified"
complete -c losetup -s n -l noheadings -d "Don't print headings for --list output"
complete -c losetup -s O -l output -x -a "(__fish_complete_list , __fish_print_losetup_list_output)" -d "Specify columns to output for --list"
complete -c losetup -s O -l output -x -a "(__fish_stripprefix='^(--output=|-\w*O)' __fish_complete_list , __fish_print_losetup_list_output)" -d "Specify columns to output for --list"
complete -c losetup -l output-all -d "Output all columns"
complete -c losetup -l raw -d "Use raw --list output format"
complete -c losetup -s h -l help -d "Display help"

View File

@@ -25,7 +25,7 @@ complete -c lpadmin -s o -xa printer-is-shared=true -d 'Sets dest to shared/publ
complete -c lpadmin -s o -xa printer-is-shared=false -d 'Sets dest to shared/published or unshared/unpublished'
complete -c lpadmin -s o -d 'Set IPP operation policy associated with dest' -xa "printer-policy=(test -r /etc/cups/cupsd.conf; and string replace -r --filter '<Policy (.*)>' '$1' < /etc/cups/cupsd.conf)"
complete -c lpadmin -s u -xa 'allow:all allow:none (__fish_complete_list , __fish_complete_users allow:)' -d 'Sets user-level access control on a destination'
complete -c lpadmin -s u -xa '(__fish_complete_list , __fish_complete_groups allow: @)' -d 'Sets user-level access control on a destination'
complete -c lpadmin -s u -xa 'deny:all deny:none (__fish_complete_list , __fish_complete_users deny:)' -d 'Sets user-level access control on a destination'
complete -c lpadmin -s u -xa '(__fish_complete_list , __fish_complete_groups deny: @)' -d 'Sets user-level access control on a destination'
complete -c lpadmin -s u -xa "allow:all allow:none (__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_users allow:)" -d 'Sets user-level access control on a destination'
complete -c lpadmin -s u -xa "(__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_groups allow: @)" -d 'Sets user-level access control on a destination'
complete -c lpadmin -s u -xa "deny:all deny:none (__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_users deny:)" -d 'Sets user-level access control on a destination'
complete -c lpadmin -s u -xa "(__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_groups deny: @)" -d 'Sets user-level access control on a destination'

View File

@@ -12,7 +12,7 @@ complete -c lsblk -s h -l help -d "usage information (this)"
complete -c lsblk -s i -l ascii -d "use ascii characters only"
complete -c lsblk -s m -l perms -d "output info about permissions"
complete -c lsblk -s n -l noheadings -d "don't print headings"
complete -c lsblk -s o -l output -d "output columns" -xa '( __fish_complete_list , __fish_print_lsblk_columns )'
complete -c lsblk -s o -l output -d "output columns" -xa "(__fish_stripprefix='^(--output=|-\w*o)' __fish_complete_list , __fish_print_lsblk_columns)"
complete -c lsblk -s P -l pairs -d "use key='value' output format"
complete -c lsblk -s r -l raw -d "use raw output format"
complete -c lsblk -s t -l topology -d "output info about topology"

View File

@@ -11,9 +11,9 @@ i\t"ignore the device cache file"
r\t"read the device cache file"
u\t"read and update the device cache file"'
complete -c lsof -s g -d 'select by group (^ - negates)' -xa '(__fish_complete_list , __fish_complete_groups)'
complete -c lsof -s g -d 'select by group (^ - negates)' -xa "(__fish_stripprefix='^-\w*g' __fish_complete_list , __fish_complete_groups)"
complete -c lsof -s l -d 'Convert UIDs to login names'
complete -c lsof -s p -d 'Select or exclude processes by pid' -xa '(__fish_complete_list , __fish_complete_pids)'
complete -c lsof -s p -d 'Select or exclude processes by pid' -xa "(__fish_stripprefix='^-\w*p' __fish_complete_list , __fish_complete_pids)"
complete -c lsof -s R -d 'Print PPID'
complete -c lsof -s t -d 'Produce terse output (pids only, no header)'
complete -c lsof -s u -d 'select by user (^ - negates)' -xa '(__fish_complete_list , __fish_complete_users)'
complete -c lsof -s u -d 'select by user (^ - negates)' -xa "(__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_users)"

View File

@@ -35,7 +35,7 @@ function __fish_complete_openssl_ciphers
printf "%s\tCipher String\n" $cs
end
end
complete -c ncat -l ssl-ciphers -x -a "(__fish_complete_list : __fish_complete_openssl_ciphers)" -d "Specify SSL ciphersuites"
complete -c ncat -l ssl-ciphers -x -a "(__fish_stripprefix='^--ssl-ciphers=' __fish_complete_list : __fish_complete_openssl_ciphers)" -d "Specify SSL ciphersuites"
complete -c ncat -l ssl-servername -x -a "(__fish_print_hostnames)" -d "Request distinct server name"
complete -c ncat -l ssl-alpn -x -d "Specify ALPN protocol list"

View File

@@ -92,11 +92,11 @@ function __fish_complete_nmap_script
end
echo -e $__fish_nmap_script_completion_cache
end
complete -c nmap -l script -r -a "(__fish_complete_list , __fish_complete_nmap_script)"
complete -c nmap -l script -r -a "(__fish_stripprefix='^--script=' __fish_complete_list , __fish_complete_nmap_script)"
complete -c nmap -l script -r -d 'Runs a script scan'
complete -c nmap -l script-args -d 'provide arguments to NSE scripts'
complete -c nmap -l script-args-file -r -d 'load arguments to NSE scripts from a file'
complete -c nmap -l script-help -r -a "(__fish_complete_list , __fish_complete_nmap_script)"
complete -c nmap -l script-help -r -a "(__fish_stripprefix='^--script-help=' __fish_complete_list , __fish_complete_nmap_script)"
complete -c nmap -l script-help -r -d "Shows help about scripts"
complete -c nmap -l script-trace
complete -c nmap -l script-updatedb

View File

@@ -1,10 +1,9 @@
set -l nmoutput (nmcli -g NAME connection show --active 2>/dev/null)
or exit # networkmanager isn't running, no point in completing
set -l cname (string escape -- $nmoutput\t"Active connection")
set -a cname (string escape -- (nmcli -g NAME connection show)\t"Connection")
set -l ifname (string escape -- (nmcli -g DEVICE device status)\t"Interface name")
set -l ssid (string escape -- (nmcli -g SSID device wifi list)\t"SSID")
set -l bssid (string escape -- (nmcli -g BSSID device wifi list | string replace --all \\ '')\t"BSSID")
set -l nmoutput '(nmcli -g NAME connection show --active 2>/dev/null)'
set -l cname "$nmoutput"\t"Active connection"
set -a cname '(nmcli -g NAME connection show 2>/dev/null)\t"Connection"'
set -l ifname '(nmcli -g DEVICE device status 2>/dev/null)\t"Interface name"'
set -l ssid '(nmcli -g SSID device wifi list 2>/dev/null)\t"SSID"'
set -l bssid '(nmcli -g BSSID device wifi list 2>/dev/null | string replace --all \\\ "")\t"BSSID"'
set -l nmcli_commands general networking radio connection device agent monitor help
set -l nmcli_general status hostname permissions logging help

View File

@@ -10,7 +10,7 @@ if test "$gnu_linux" -eq 1
# Some short options are GNU-only
complete -c ps -s a -d "Select all processes except session leaders and terminal-less"
complete -c ps -s A -d "Select all"
complete -c ps -s C -d "Select by command" -ra '(__fish_complete_list , __fish_complete_proc)'
complete -c ps -s C -d "Select by command" -ra "(__fish_stripprefix='^-\w*C' __fish_complete_list , __fish_complete_proc)"
complete -c ps -s c -d 'Show different scheduler information for the -l option'
complete -c ps -s d -d "Select all processes except session leaders"
complete -c ps -s e -d "Select all"
@@ -24,9 +24,9 @@ if test "$gnu_linux" -eq 1
complete -c ps -s m -d 'Show threads after processes'
complete -c ps -s N -d "Invert selection"
complete -c ps -s n -d "Set namelist file" -r
complete -c ps -s s -l sid -d "Select by session ID" -x -a "(__fish_complete_list , __fish_complete_pids)"
complete -c ps -s s -l sid -d "Select by session ID" -x -a "(__fish_stripprefix='^(--sid=|-\w*s)' __fish_complete_list , __fish_complete_pids)"
complete -c ps -s T -d "Show threads. With SPID"
complete -c ps -s u -l user -d "Select by user" -x -a "(__fish_complete_list , __fish_complete_users)"
complete -c ps -s u -l user -d "Select by user" -x -a "(__fish_stripprefix='^(--script=|-\w*u)' __fish_complete_list , __fish_complete_users)"
complete -c ps -s V -l version -d "Display version and exit"
complete -c ps -s y -d "Do not show flags"
@@ -39,7 +39,7 @@ if test "$gnu_linux" -eq 1
complete -c ps -l info -d "Display debug info"
complete -c ps -l lines -l rows -d "Set screen height" -r
complete -c ps -l no-headers -d 'Print no headers'
complete -c ps -l ppid -d "Select by parent PID" -x -a "(__fish_complete_list , __fish_complete_pids)"
complete -c ps -l ppid -d "Select by parent PID" -x -a "(__fish_stripprefix='^--ppid=' __fish_complete_list , __fish_complete_pids)"
complete -c ps -l sort -d 'Specify sort order' -r
else
# Assume BSD options otherwise
@@ -81,6 +81,6 @@ end
complete -c ps -s o -lformat$bsd_null -d "User defined format" -x
complete -c ps -s Z -lcontext$bsd_null -d "Include security info"
complete -c ps -s t -ltty$bsd_null -d "Select by tty" -r
complete -c ps -s G -lgroup$bsd_null -d "Select by group" -x -a "(__fish_complete_list , __fish_complete_groups)"
complete -c ps -s U -luser$bsd_null -d "Select by user" -x -a "(__fish_complete_list , __fish_complete_users)"
complete -c ps -s p -lpid$bsd_null -d "Select by PID" -x -a "(__fish_complete_list , __fish_complete_pids)"
complete -c ps -s G -lgroup$bsd_null -d "Select by group" -x -a "(__fish_stripprefix='^(--group=|-\w*G)' __fish_complete_list , __fish_complete_groups)"
complete -c ps -s U -luser$bsd_null -d "Select by user" -x -a "(__fish_stripprefix='^(--user=|-\w*U)' __fish_complete_list , __fish_complete_users)"
complete -c ps -s p -lpid$bsd_null -d "Select by PID" -x -a "(__fish_stripprefix='^(--pid=|-\w*p)' __fish_complete_list , __fish_complete_pids)"

View File

@@ -15,7 +15,7 @@ complete -c setxkbmap -o keycodes -d 'Specifies keycodes component name' -xa "(s
complete -c setxkbmap -o keymap -d 'Specifies name of keymap to load' -xa "(sed -r $filter /usr/share/X11/xkb/keymap.dir)"
complete -c setxkbmap -o layout -d 'Specifies layout used to choose component names' -xa "(__fish_complete_setxkbmap layout)"
complete -c setxkbmap -o model -d 'Specifies model used to choose component names' -xa "(__fish_complete_setxkbmap model)"
complete -c setxkbmap -o option -d 'Adds an option used to choose component names' -xa "(__fish_complete_list , '__fish_complete_setxkbmap option')"
complete -c setxkbmap -o option -d 'Adds an option used to choose component names' -xa "(__fish_stripprefix='^--option=' __fish_complete_list , '__fish_complete_setxkbmap option')"
complete -c setxkbmap -o print -d 'Print a complete xkb_keymap description and exit'
complete -c setxkbmap -o query -d 'Print the current layout settings and exit'
complete -c setxkbmap -o rules -d 'Name of rules file to use' -x

View File

@@ -25,7 +25,7 @@ complete -c ssh -s k -d "Disables forwarding of GSSAPI credentials"
complete -c ssh -s L -d "Specify local port forwarding" -x
complete -c ssh -s l -x -a "(__fish_complete_users)" -d User
complete -c ssh -s M -d "Places the ssh client into master mode"
complete -c ssh -s m -d "MAC algorithm" -xa "(__fish_complete_list , __fish_ssh_macs)"
complete -c ssh -s m -d "MAC algorithm" -xa "(__fish_stripprefix='^-\w*m' __fish_complete_list , __fish_ssh_macs)"
complete -c ssh -s N -d "Do not execute remote command"
complete -c ssh -s n -d "Prevent reading from stdin"
complete -c ssh -s O -d "Control an active connection multiplexing master process" -x

View File

@@ -12,6 +12,6 @@ complete -c su -s G -l supp-group -x -a "(__fish_complete_groups)" -d "Specify a
complete -c su -s m -s p -l preserve_environment -d "Preserve environment"
complete -c su -s P -l pty -d "Create pseudo-terminal for the session"
complete -c su -s s -l shell -x -a "(cat /etc/shells)" -d "Run the specified shell"
complete -c su -s w -l whitelist-environment -x -a "(__fish_complete_list , __fish_complete_su_env_whitelist)" -d "Don't reset these environment variables"
complete -c su -s w -l whitelist-environment -x -a "(__fish_stripprefix='^(--whitelist-environment=|-\w*w)' __fish_complete_list , __fish_complete_su_env_whitelist)" -d "Don't reset these environment variables"
complete -c su -s h -l help -d "Display help and exit"
complete -c su -s V -l version -d "Display version and exit"

View File

@@ -16,9 +16,18 @@ complete -c systemd-analyze -l to-pattern -d 'dot: show relationships matching r
complete -c systemd-analyze -l fuzz -x -d 'critical-chain: also show units which finished timespan earlier than last unit in same level'
complete -c systemd-analyze -l man -xa no -d 'Do not invoke man to verify the existence of man pages'
complete -c systemd-analyze -l generators -d 'Invoke unit generators'
complete -c systemd-analyze -l instance -r -d 'Fallback instance name for template units'
complete -c systemd-analyze -l root -xa "(__fish_complete_directories)" -d 'With cat-files, show config files underneath the specified root path'
complete -c systemd-analyze -l image -r -d 'With cat-files, show config files inside the specified image path'
complete -c systemd-analyze -l image-policy -d 'Disk image dissection policy'
complete -c systemd-analyze -l iterations -x -d 'calendar: show number of iterations the calendar expression will elapse next'
complete -c systemd-analyze -l base-time -x -d 'calendar: show next iterations relative to the specified point in time'
complete -c systemd-analyze -l tldr -d 'cat-config: skip comments, empty lines and section headers'
complete -c systemd-analyze -l unit -r -d "condition: evaluate Condition and Assert assignments in unit file"
complete -c systemd-analyze -l table -d 'plot: output raw time data in a table'
complete -c systemd-analyze -l no-legend -d 'plot: exclude legends/hints'
complete -c systemd-analyze -l detailed -d "plot: show activation timestamps details in SVG plot"
complete -c systemd-analyze -l scale-svg -r -d "plot: stretch the x-axis of the plot"
complete -c systemd-analyze -s H -l host -xa "(__fish_complete_user_at_hosts)" -d 'Execute the operation on a remote host'
complete -c systemd-analyze -s M -l machine -xa "(__fish_systemd_machines)" -d 'Execute operation on a VM or container'
complete -c systemd-analyze -s h -l help -d 'Print a short help and exit'
@@ -34,6 +43,7 @@ complete -c systemd-analyze -n __fish_use_subcommand -a critical-chain -d "Print
complete -c systemd-analyze -n "__fish_seen_subcommand_from critical-chain" -a "(__fish_systemd_units)"
complete -c systemd-analyze -n __fish_use_subcommand -a dump -d "Output serialization of server state"
complete -c systemd-analyze -n __fish_use_subcommand -a malloc -d "Output internal memory state of D-Bus service"
complete -c systemd-analyze -n __fish_use_subcommand -a plot -d "Output SVG graphic showing service initialization"
complete -c systemd-analyze -n __fish_use_subcommand -a dot -d "Output dependency graph in dot(1) format"
complete -c systemd-analyze -n __fish_use_subcommand -a unit-paths -d "List all directories from which unit files may be loaded"
@@ -41,15 +51,25 @@ complete -c systemd-analyze -n __fish_use_subcommand -a exit-status -d "List exi
complete -c systemd-analyze -n __fish_use_subcommand -a capability -d "List Linux capabilities along with their numeric IDs"
complete -c systemd-analyze -n __fish_use_subcommand -a condition -d "Evaluate Condition and Assert assignments"
complete -c systemd-analyze -n __fish_use_subcommand -a syscall-filter -d "List system calls contained in the specified system call set"
complete -c systemd-analyze -n __fish_use_subcommand -a filesystems -d "List filesystems"
complete -c systemd-analyze -n __fish_use_subcommand -a calendar -d "Normalize repetitive calendar events and calculate when they elapse next"
complete -c systemd-analyze -n __fish_use_subcommand -a timestamp -d "Parse timestamp and output the normalized form"
complete -c systemd-analyze -n __fish_use_subcommand -a timestamp -d "Parse time span and output the normalized form"
complete -c systemd-analyze -n __fish_use_subcommand -a timespan -d "Parse time span and output the normalized form"
complete -c systemd-analyze -n __fish_use_subcommand -a cat-config -d "Show contents of a config file"
complete -c systemd-analyze -n "__fish_seen_subcommand_from cat-config" -F
complete -c systemd-analyze -n __fish_use_subcommand -a compare-versions -d "Compare two version strings"
complete -c systemd-analyze -n __fish_use_subcommand -a verify -d "Check unit files for correctness"
complete -c systemd-analyze -n "__fish_seen_subcommand_from verify" -F
complete -c systemd-analyze -n __fish_use_subcommand -a security -d "Analyze security settings of specified service units"
complete -c systemd-analyze -n "__fish_seen_subcommand_from security" -a "(__fish_systemctl_services)"
complete -c systemd-analyze -n __fish_use_subcommand -a inspect-elf -d "Parse and print ELF object packaging metadata"
complete -c systemd-analyze -n __fish_use_subcommand -a fdstore -d "List contents of service unit's file descriptor store"
complete -c systemd-analyze -n __fish_use_subcommand -a image-policy -d "Analyze image policy string"
complete -c systemd-analyze -n __fish_use_subcommand -a has-tpm2 -d "Report TPM2 support"
complete -c systemd-analyze -n __fish_use_subcommand -a pcrs -d "Show known TPM2 PCRs"
complete -c systemd-analyze -n __fish_use_subcommand -a srk -d "Read Storage Root Key from TPM2 device"
complete -c systemd-analyze -n __fish_use_subcommand -a architectures -d "List known CPU architectures"
complete -c systemd-analyze -n __fish_use_subcommand -a smbios11 -d "Show SMBIOS Type #11 strings passed to the system"

View File

@@ -40,6 +40,6 @@ complete -c systemd-cryptenroll -l fido2-with-user-presence -xa "yes no" -d "Req
complete -c systemd-cryptenroll -l fido2-with-user-verification -xa "yes no" -d "Require user verification when unlocking the volume"
complete -c systemd-cryptenroll -l tpm2-device -kxa "(__fish_cryptenroll_tpm2_devices)" -d "Enroll a TPM2 security chip"
complete -c systemd-cryptenroll -l tpm2-pcrs -x -d "Bind the enrollment of TPM2 device to speficied PCRs"
complete -c systemd-cryptenroll -l wipe-slot -kxa "(__fish_complete_list , __fish_cryptenroll_complete_wipe)" -d "Wipes one or more LUKS2 key slots"
complete -c systemd-cryptenroll -l wipe-slot -kxa "(__fish_stripprefix='^--wipe-slot=' __fish_complete_list , __fish_cryptenroll_complete_wipe)" -d "Wipes one or more LUKS2 key slots"
complete -c systemd-cryptenroll -l help -s h -d "Print a short help"
complete -c systemd-cryptenroll -l version -d "Print a short version string"

View File

@@ -5,7 +5,7 @@ complete -c usermod -s d -l home -d "Change user's login directory" -r
complete -c usermod -s e -l expiredate -d "Date (YYYY-MM-DD) on which the user account will be disabled" -x
complete -c usermod -s f -l inactive -d "Number of days after a password expires until the account is locked" -xa "(seq 0 365)"
complete -c usermod -s g -l gid -d "Group name or number of the user's new initial login group" -xa "(__fish_complete_groups)"
complete -c usermod -s G -l groups -d "List of groups which the user is also a member of" -xa "(__fish_complete_list , __fish_complete_groups)"
complete -c usermod -s G -l groups -d "List of groups which the user is also a member of" -xa "(__fish_stripprefix='^(--groups=|-\w*G)' __fish_complete_list , __fish_complete_groups)"
complete -c usermod -s l -l login -d "Change user's name" -x
complete -c usermod -s L -l lock -d "Lock user's password" -f
complete -c usermod -s m -l move-home -d "Move the content of the user's home directory to the new location" -f

View File

@@ -0,0 +1,58 @@
# `wlr-randr` completions.
# See: https://gitlab.freedesktop.org/emersion/wlr-randr
function __fish_print_wlr-randr_outputs --argument-names exclude
if command -q jq
wlr-randr --json | jq \
--raw-output \
--arg exclude "$exclude" \
'.[] | select(.name != $exclude) | "\(.name)\t\(.make), \(.model)"'
end
end
function __fish_get_wlr-randr-current_output
set -l last_output
set -l tokens (commandline -xpc)
while set -q tokens[1]
if test "$tokens[1]" = --output
set last_output "$tokens[2]"
set -e tokens[1]
else if string match -qr -- '^--output=' "$tokens[1]"
set last_output (string replace -r -- '--output=(.*)' '$1' "$tokens[1]")
end
set -e tokens[1]
end
printf "%s" $last_output
end
function __fish_complete_wlr-randr_modes
if command -q jq
set -l output (__fish_get_wlr-randr-current_output)
wlr-randr --json | jq \
--raw-output \
--arg output "$output" \
--arg preferred_str Preferred \
--arg empty_str '' \
'.[] | select(.name == $output) | .modes[] | "\(.width)x\(.height)\t\(if .preferred then $preferred_str else $empty_str end)"'
end
end
complete -c wlr-randr -f
complete -c wlr-randr -s h -l help -d 'Show help'
complete -c wlr-randr -l json -d 'Print as JSON'
complete -c wlr-randr -l dryrun -d 'Dry run'
complete -c wlr-randr -l output -x -d Output -a '(__fish_print_wlr-randr_outputs)'
complete -c wlr-randr -l on -d 'Turn on'
complete -c wlr-randr -l off -d 'Turn off'
complete -c wlr-randr -l mode -x -d Mode -a '(__fish_complete_wlr-randr_modes)'
complete -c wlr-randr -l pos -r -d Position
complete -c wlr-randr -l left-of -x -d 'Relative left position' -a '(__fish_print_wlr-randr_outputs (__fish_get_wlr-randr-current_output))'
complete -c wlr-randr -l right-of -x -d 'Relative right position' -a '(__fish_print_wlr-randr_outputs (__fish_get_wlr-randr-current_output))'
complete -c wlr-randr -l above -x -d 'Relative top position' -a '(__fish_print_wlr-randr_outputs (__fish_get_wlr-randr-current_output))'
complete -c wlr-randr -l below -x -d 'Relative bottom position' -a '(__fish_print_wlr-randr_outputs (__fish_get_wlr-randr-current_output))'
complete -c wlr-randr -l transform -x -d Transformation -a 'normal\t 90\t 180\t 270\t flipped\t flipped-90\t flipped-180\t flipped-270\t'
complete -c wlr-randr -l scale -x -d Scale

View File

@@ -50,7 +50,7 @@ complete -c $progname -s d -d 'Enable extra debugging shown to stderr'
complete -c $progname -s h -d 'Show the help message'
complete -c $progname -s i -d 'Ignore repositories defined in configuration files'
complete -c $progname -s M -d 'For remote repositories, the data is fetched and stored in memory only'
complete -c $progname -s p -d 'Match one or more package properties' -xa "(__fish_complete_list , __fish_print_xbps_pkg_props)"
complete -c $progname -s p -d 'Match one or more package properties' -xa "(__fish_stripprefix='^-\w*p' __fish_complete_list , __fish_print_xbps_pkg_props)"
complete -c $progname -s R -d 'Enable repository mode'
complete -c $progname -l repository -d 'Append the specified repository to the top of the list'
complete -c $progname -l regex -d 'Use Extended Regular Expressions'

View File

@@ -14,15 +14,17 @@ where:
set -q prefix[1]
or set -l prefix ""
set -l pat "$(commandline -t)"
#set -l pat $argv[5]
if set -q __fish_stripprefix[1]
set pat "$(string replace -r -- "$__fish_stripprefix" "" $pat)"
end
switch $pat
case "*$div*"
for i in (echo $pat | sed "s/^\(.\+$div\)$iprefix.*\$/\1/")$iprefix(eval $cmd)
string unescape -- $i
for i in (string unescape -- $pat | sed "s/^\(.\+$div\)$iprefix.*\$/\1/")$iprefix(eval $cmd)
printf %s\n $i
end
case '*'
for i in $prefix$iprefix(eval $cmd)
string unescape -- $i
printf %s\n $i
end
end

View File

@@ -1,15 +1,15 @@
function __fish_complete_pgrep -d 'Complete pgrep/pkill' --argument-names cmd
complete -c $cmd -xa '(__fish_complete_proc)'
complete -c $cmd -s f -d 'Match pattern against full command line'
complete -c $cmd -s g -d 'Only match processes in the process group' -xa '(__fish_complete_list , __fish_complete_groups)'
complete -c $cmd -s G -d "Only match processes whose real group ID is listed. Group 0 is translated into $cmd\'s own process group" -xa '(__fish_complete_list , __fish_complete_groups)'
complete -c $cmd -s g -d 'Only match processes in the process group' -xa "(__fish_stripprefix='^-\w*g' __fish_complete_list , __fish_complete_groups)"
complete -c $cmd -s G -d "Only match processes whose real group ID is listed. Group 0 is translated into $cmd\'s own process group" -xa "(__fish_stripprefix='^-\w*G' __fish_complete_list , __fish_complete_groups)"
complete -c $cmd -s n -d 'Select only the newest process'
complete -c $cmd -s o -d 'Select only the oldest process'
complete -c $cmd -s P -d 'Only match processes whose parent process ID is listed' -xa '(__fish_complete_list , __fish_complete_pids)'
complete -c $cmd -s P -d 'Only match processes whose parent process ID is listed' -xa "(__fish_stripprefix='^-\w*P' __fish_complete_list , __fish_complete_pids)"
complete -c $cmd -s s -d "Only match processes whose process session ID is listed. Session ID 0 is translated into $cmd\'s own session ID."
complete -c $cmd -s t -d 'Only match processes whose controlling terminal is listed. The terminal name should be specified without the "/dev/" prefix' -r
complete -c $cmd -s u -d 'Only match processes whose effective user ID is listed' -xa '(__fish_complete_list , __fish_complete_users)'
complete -c $cmd -s U -d 'Only match processes whose real user ID is listed' -xa '(__fish_complete_list , __fish_complete_users)'
complete -c $cmd -s u -d 'Only match processes whose effective user ID is listed' -xa "(__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_users)"
complete -c $cmd -s U -d 'Only match processes whose real user ID is listed' -xa "(__fish_stripprefix='^-\w*U' __fish_complete_list , __fish_complete_users)"
complete -c $cmd -s v -d 'Negates the matching'
complete -c $cmd -s x -d ' Only match processes whose name (or command line if -f is specified) exactly match the pattern'
end

View File

@@ -3,7 +3,7 @@ function __fish_complete_ssh -d "common completions for ssh commands" --argument
complete -c $command -s 6 -d "IPv6 only"
complete -c $command -s A -d "Enables forwarding of the authentication agent"
complete -c $command -s C -d "Compress all data"
complete -c $command -s c -d "Encryption algorithm" -xa "(__fish_complete_list , __fish_ssh_ciphers)"
complete -c $command -s c -d "Encryption algorithm" -xa "(__fish_stripprefix='^-\w*c' __fish_complete_list , __fish_ssh_ciphers)"
complete -c $command -s F -d "Configuration file" -rF
complete -c $command -s i -d "Identity key file" -rF
complete -c $command -s J -d 'ProxyJump host' -xa "(__fish_complete_user_at_hosts)"

View File

@@ -219,12 +219,17 @@ end" >$__fish_config_dir/config.fish
end
# Notify terminals when $PWD changes via OSC 7 (issue #906).
function __fish_update_cwd_osc --on-variable PWD --description 'Notify terminals when $PWD changes'
set -l host $hostname
if set -q KONSOLE_VERSION
set host ''
if not functions --query __fish_update_cwd_osc
function __fish_update_cwd_osc --on-variable PWD --description 'Notify terminals when $PWD changes'
set -l host $hostname
if set -q KONSOLE_VERSION
set host ''
end
if [ "$TERM" = dumb ]
return
end
printf \e\]7\;file://%s%s\a $host (string escape --style=url -- $PWD)
end
printf \e\]7\;file://%s%s\a $host (string escape --style=url -- $PWD)
end
__fish_update_cwd_osc # Run once because we might have already inherited a PWD from an old tab

View File

@@ -6,5 +6,5 @@
function __fish_seen_subcommand_from
set -l regex (string escape --style=regex -- (commandline -pxc)[2..] | string join '|')
string match -rq -- "^$regex"'$' $argv
string match -rq -- "^($regex)\$" $argv
end

View File

@@ -59,6 +59,9 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod
$legacy_bind --preset $argv \e\[1\;9C nextd-or-forward-word # iTerm2 < 3.5.12
$legacy_bind --preset $argv \e\[1\;9D prevd-or-backward-word # iTerm2 < 3.5.12
bind --preset $argv alt-b prevd-or-backward-word
bind --preset $argv alt-f nextd-or-forward-word
bind --preset $argv alt-up history-token-search-backward
bind --preset $argv alt-down history-token-search-forward
$legacy_bind --preset $argv \e\[1\;9A history-token-search-backward # iTerm2 < 3.5.12
@@ -120,7 +123,7 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod
bind --preset $argv alt-enter "commandline -i \n" expand-abbr
bind --preset $argv ")" self-insert expand-abbr # Closing a command substitution.
bind --preset $argv ctrl-space 'test -n "$(commandline)" && commandline -i " "'
bind --preset $argv -k nul 'test -n "$(commandline)" && commandline -i " "'
$legacy_bind --preset $argv -k nul 'test -n "$(commandline)" && commandline -i " "'
# Shift-space behaves like space because it's easy to mistype.
bind --preset $argv shift-space 'commandline -i " "' expand-abbr

View File

@@ -51,17 +51,17 @@ function fish_default_key_bindings -d "emacs-like key binds"
bind --preset $argv ctrl-/ undo
bind --preset $argv ctrl-_ undo # XTerm idiosyncracy, can get rid of this once we go full CSI u
bind --preset $argv ctrl-z undo
bind --preset $argv ctrl-Z redo
bind --preset $argv ctrl-shift-z redo
bind --preset $argv alt-/ redo
bind --preset $argv alt-t transpose-words
bind --preset $argv alt-u upcase-word
bind --preset $argv alt-c capitalize-word
bind --preset $argv alt-backspace backward-kill-word
bind --preset $argv alt-delete kill-word
bind --preset $argv ctrl-alt-h backward-kill-word
bind --preset $argv ctrl-backspace backward-kill-word
bind --preset $argv ctrl-delete kill-word
bind --preset $argv alt-b prevd-or-backward-word
bind --preset $argv alt-f nextd-or-forward-word
bind --preset $argv alt-\< beginning-of-buffer
bind --preset $argv alt-\> end-of-buffer

View File

@@ -7,16 +7,16 @@ function fish_print_hg_root
# Find an hg directory above $PWD
# without calling `hg root` because that's too slow
set -l root
set -l dir (pwd -P 2>/dev/null)
set -l dir "$(pwd -P 2>/dev/null)"
or return 1
while test $dir != /
while not contains -- "$dir" "" / .
if test -f $dir'/.hg/dirstate'
echo $dir/.hg
return 0
end
# Go up one directory
set dir (string replace -r '[^/]*/?$' '' $dir)
set dir (path dirname -- $dir)
end
return 1

View File

@@ -109,18 +109,24 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
bind -s --preset -M insert ctrl-n accept-autosuggestion
# Vi/Vim doesn't support these keys in insert mode but that seems silly so we do so anyway.
bind -s --preset -M insert -k home beginning-of-line
bind -s --preset -M default -k home beginning-of-line
bind -s --preset -M insert -k end end-of-line
bind -s --preset -M default -k end end-of-line
bind -s --preset -M insert home beginning-of-line
$legacy_bind -s --preset -M insert -k home beginning-of-line
bind -s --preset -M default home beginning-of-line
$legacy_bind -s --preset -M default -k home beginning-of-line
bind -s --preset -M insert end end-of-line
$legacy_bind -s --preset -M insert -k end end-of-line
bind -s --preset -M default end end-of-line
$legacy_bind -s --preset -M default -k end end-of-line
# Vi moves the cursor back if, after deleting, it is at EOL.
# To emulate that, move forward, then backward, which will be a NOP
# if there is something to move forward to.
bind -s --preset -M default x delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive'
bind -s --preset -M default X backward-delete-char
bind -s --preset -M insert -k dc delete-char forward-single-char backward-char
bind -s --preset -M default -k dc delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive'
bind -s --preset -M insert delete delete-char forward-single-char backward-char
$legacy_bind -s --preset -M insert -k dc delete-char forward-single-char backward-char
bind -s --preset -M default delete delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive'
$legacy_bind -s --preset -M default -k dc delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive'
# Backspace deletes a char in insert mode, but not in normal/default mode.
bind -s --preset -M insert backspace backward-delete-char
@@ -241,7 +247,7 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
# in vim p means paste *after* current character, so go forward a char before pasting
# also in vim, P means paste *at* current position (like at '|' with cursor = line),
# \ so there's no need to go back a char, just paste it without moving
bind -s --preset p 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_modefish_cursor_end_modeinclusive' yank
bind -s --preset p 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_mode inclusive' yank
bind -s --preset P yank
bind -s --preset g,p yank-pop
@@ -255,10 +261,10 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
# Lowercase r, enters replace_one mode
#
bind -s --preset -m replace_one r repaint-mode
bind -s --preset -M replace_one -m default '' delete-char self-insert backward-char repaint-mode
bind -s --preset -M replace_one -m default enter 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
bind -s --preset -M replace_one -m default ctrl-j 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
bind -s --preset -M replace_one -m default ctrl-m 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
bind -s --preset -M replace_one -m default '' 'set -g fish_cursor_end_mode exclusive' delete-char self-insert backward-char repaint-mode 'set -g fish_cursor_end_mode inclusive'
bind -s --preset -M replace_one -m default enter 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
bind -s --preset -M replace_one -m default ctrl-j 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
bind -s --preset -M replace_one -m default ctrl-m 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
bind -s --preset -M replace_one -m default escape cancel repaint-mode
bind -s --preset -M replace_one -m default ctrl-\[ cancel repaint-mode

View File

@@ -656,7 +656,8 @@ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
impl Acceptor for $name {
#[allow(unused_variables)]
fn accept<'a>(&'a self, visitor: &mut dyn NodeVisitor<'a>, reversed: bool) {
accept_list_visitor!(Self, accept, visit, self, visitor, reversed, $contents);
let _ =
accept_list_visitor!(Self, accept, visit, self, visitor, reversed, $contents);
}
}
impl AcceptorMut for $name {
@@ -743,7 +744,7 @@ macro_rules! implement_acceptor_for_branch {
impl Acceptor for $name {
#[allow(unused_variables)]
fn accept<'a>(&'a self, visitor: &mut dyn NodeVisitor<'a>, reversed: bool){
visitor_accept_field!(
let _ = visitor_accept_field!(
Self,
accept,
visit,
@@ -3718,7 +3719,7 @@ fn allocate<T: NodeMut + Default>(&self) -> Box<T> {
// Return the resulting Node pointer. It is never null.
fn allocate_visit<T: NodeMut + Default>(&mut self) -> Box<T> {
let mut result = Box::<T>::default();
self.visit_mut(&mut *result);
let _ = self.visit_mut(&mut *result);
result
}

View File

@@ -21,6 +21,8 @@
#![allow(unstable_name_collisions)]
#![allow(clippy::uninlined_format_args)]
#[cfg(feature = "installable")]
use fish::common::{get_executable_path, wcs2osstring};
#[allow(unused_imports)]
use fish::future::IsSomeAnd;
use fish::{
@@ -29,12 +31,12 @@
BUILTIN_ERR_MISSING, BUILTIN_ERR_UNKNOWN, STATUS_CMD_OK, STATUS_CMD_UNKNOWN,
},
common::{
escape, get_executable_path, save_term_foreground_process_group, scoped_push_replacer,
str2wcstring, wcs2osstring, wcs2string, PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME,
escape, save_term_foreground_process_group, scoped_push_replacer, str2wcstring, wcs2string,
PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME,
},
env::{
environment::{env_init, EnvStack, Environment},
ConfigPaths, EnvMode, Statuses,
ConfigPaths, EnvMode, Statuses, CONFIG_PATHS,
},
eprintf,
event::{self, Event},
@@ -65,22 +67,16 @@
use std::fs::File;
use std::mem::MaybeUninit;
use std::os::unix::prelude::*;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::{env, ops::ControlFlow};
const DOC_DIR: &str = env!("DOCDIR");
const DATA_DIR: &str = env!("DATADIR");
const DATA_DIR_SUBDIR: &str = env!("DATADIR_SUBDIR");
const SYSCONF_DIR: &str = env!("SYSCONFDIR");
const BIN_DIR: &str = env!("BINDIR");
#[cfg(feature = "installable")]
// Disable for clippy because otherwise it would require sphinx
#[cfg(not(clippy))]
fn install(confirm: bool, dir: PathBuf) -> bool {
fn install(confirm: bool, dir: &PathBuf) -> bool {
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
@@ -192,7 +188,8 @@ fn install(confirm: bool, dir: PathBuf) -> bool {
}
#[cfg(any(clippy, not(feature = "installable")))]
fn install(_confirm: bool, _dir: PathBuf) -> bool {
#[allow(dead_code)]
fn install(_confirm: bool, _dir: &PathBuf) -> bool {
eprintln!("Fish was built without support for self-installation");
return false;
}
@@ -264,128 +261,6 @@ fn print_rusage_self() {
eprintln!(" signals: {signals}");
}
fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
// PORTING: why is this not just an associated method on ConfigPaths?
let mut paths = ConfigPaths::default();
let mut done = false;
let exec_path = get_executable_path(argv0.as_ref());
if let Ok(exec_path) = exec_path.canonicalize() {
FLOG!(
config,
format!("exec_path: {:?}, argv[0]: {:?}", exec_path, argv0.as_ref())
);
// TODO: we should determine program_name from argv0 somewhere in this file
// Detect if we're running right out of the CMAKE build directory
if exec_path.starts_with(env!("CARGO_MANIFEST_DIR")) {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
FLOG!(
config,
"Running out of target directory, using paths relative to CARGO_MANIFEST_DIR:\n",
manifest_dir.display()
);
done = true;
paths = ConfigPaths {
data: manifest_dir.join("share"),
sysconf: manifest_dir.join("etc"),
doc: manifest_dir.join("user_doc/html"),
bin: Some(exec_path.parent().unwrap().to_owned()),
}
}
if !done {
// The next check is that we are in a relocatable directory tree
if exec_path.ends_with("bin/fish") {
let base_path = exec_path.parent().unwrap().parent().unwrap();
paths = ConfigPaths {
// One obvious path is ~/.local (with fish in ~/.local/bin/).
// If we picked ~/.local/share/fish as our data path,
// we would install there and erase history.
// So let's isolate us a bit more.
#[cfg(feature = "installable")]
data: base_path.join("share/fish/install"),
#[cfg(not(feature = "installable"))]
data: base_path.join("share/fish"),
sysconf: base_path.join("etc/fish"),
doc: base_path.join("share/doc/fish"),
bin: Some(base_path.join("bin")),
}
} else if exec_path.ends_with("fish") {
FLOG!(
config,
"'fish' not in a 'bin/', trying paths relative to source tree"
);
let base_path = exec_path.parent().unwrap();
paths = ConfigPaths {
#[cfg(feature = "installable")]
data: base_path.join("share/install"),
#[cfg(not(feature = "installable"))]
data: base_path.join("share"),
sysconf: base_path.join("etc"),
doc: base_path.join("user_doc/html"),
bin: Some(base_path.to_path_buf()),
}
}
if paths.data.exists() && paths.sysconf.exists() {
// The docs dir may not exist; in that case fall back to the compiled in path.
if !paths.doc.exists() {
paths.doc = PathBuf::from(DOC_DIR);
}
done = true;
}
}
}
if !done {
// Fall back to what got compiled in.
let data = if cfg!(feature = "installable") {
let Some(home) = fish::env::get_home() else {
FLOG!(
error,
"Cannot find home directory and will refuse to read configuration.\n",
"Consider installing into a directory tree with `fish --install=PATH`."
);
return paths;
};
PathBuf::from(home).join(DATA_DIR).join(DATA_DIR_SUBDIR)
} else {
PathBuf::from(DATA_DIR).join(DATA_DIR_SUBDIR)
};
let bin = if cfg!(feature = "installable") {
exec_path.parent().map(|x| x.to_path_buf())
} else {
Some(PathBuf::from(BIN_DIR))
};
FLOG!(config, "Using compiled in paths:");
paths = ConfigPaths {
data,
sysconf: PathBuf::from(SYSCONF_DIR).join("fish"),
doc: DOC_DIR.into(),
bin,
}
}
FLOGF!(
config,
"determine_config_directory_paths() results:\npaths.data: %ls\npaths.sysconf: \
%ls\npaths.doc: %ls\npaths.bin: %ls",
paths.data.display().to_string(),
paths.sysconf.display().to_string(),
paths.doc.display().to_string(),
paths
.bin
.clone()
.map(|x| x.display().to_string())
.unwrap_or("|not found|".to_string()),
);
paths
}
// Source the file config.fish in the given directory.
// Returns true if successful, false if not.
fn source_config_in_directory(parser: &Parser, dir: &wstr) -> bool {
@@ -463,7 +338,7 @@ fn read_init(parser: &Parser, paths: &ConfigPaths) {
);
}
install(true, PathBuf::from(wcs2osstring(&datapath)));
install(true, &PathBuf::from(wcs2osstring(&datapath)));
// We try to go on if installation failed (or was rejected) here
// If the assets are missing, we will trigger a later error,
// if they are outdated, things will probably (tm) work somewhat.
@@ -592,8 +467,9 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
// path/share/fish/ is the data directory
// path/etc/fish is sysconf????
use std::fs;
use std::path::Path;
let dir = PathBuf::from(wcs2osstring(path));
if install(true, dir.join("share/fish/install")) {
if install(true, &dir.join("share/fish/install")) {
for sub in &["share/fish/install", "etc/fish", "bin"] {
let p = dir.join(sub);
let Ok(_) = fs::create_dir_all(p.clone()) else {
@@ -626,14 +502,12 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
}
}
} else {
let paths = Some(determine_config_directory_paths(OsString::from_vec(
wcs2string(&args[0]),
)));
let paths = Some(&*CONFIG_PATHS);
let Some(paths) = paths else {
FLOG!(error, "Cannot find config paths");
std::process::exit(1);
};
install(true, paths.data);
install(true, &paths.data);
}
}
'l' => opts.is_login = true,
@@ -817,18 +691,19 @@ fn throwing_main() -> i32 {
save_term_foreground_process_group();
}
let mut paths: Option<ConfigPaths> = None;
let mut paths: Option<&ConfigPaths> = None;
// If we're not executing, there's no need to find the config.
if !opts.no_exec {
paths = Some(determine_config_directory_paths(OsString::from_vec(
wcs2string(&args[0]),
)));
paths = Some(&*CONFIG_PATHS);
env_init(
paths.as_ref(),
paths,
/* do uvars */ !opts.no_config,
/* default paths */ opts.no_config,
);
}
paths
} else {
None
};
// Set features early in case other initialization depends on them.
// Start with the ones set in the environment, then those set on the command line (so the
@@ -845,7 +720,7 @@ fn throwing_main() -> i32 {
// Construct the root parser!
let env = Rc::new(EnvStack::globals().create_child(true /* dispatches_var_changes */));
let parser: &Parser = &Parser::new(env, CancelBehavior::Clear);
let parser = &Parser::new(env, CancelBehavior::Clear);
parser.set_syncs_uvars(!opts.no_config);
if !opts.no_exec && !opts.no_config {
@@ -1008,7 +883,6 @@ fn fish_xdm_login_hack_hack_hack_hack(cmds: &mut [OsString], args: &[WString]) -
return false;
}
let mut result = false;
let cmd = &cmds[0];
if cmd == "exec \"${@}\"" || cmd == "exec \"$@\"" {
// We're going to construct a new command that starts with exec, and then has the
@@ -1020,7 +894,8 @@ fn fish_xdm_login_hack_hack_hack_hack(cmds: &mut [OsString], args: &[WString]) -
}
cmds[0] = new_cmd;
result = true;
true
} else {
false
}
result
}

View File

@@ -20,10 +20,10 @@
eprintf, fprintf,
input::input_terminfo_get_name,
input_common::{
terminal_protocol_hacks, terminal_protocols_enable_ifn, CharEvent, InputEventQueue,
InputEventQueuer,
match_key_event_to_key, terminal_protocol_hacks, terminal_protocols_enable_ifn, CharEvent,
InputEventQueue, InputEventQueuer, KeyEvent,
},
key::{self, char_to_symbol, Key},
key::{char_to_symbol, Key},
panic::panic_handler,
print_help::print_help,
printf,
@@ -37,15 +37,21 @@
};
/// Return true if the recent sequence of characters indicates the user wants to exit the program.
fn should_exit(recent_keys: &mut Vec<Key>, key: Key) -> bool {
recent_keys.push(key);
fn should_exit(recent_keys: &mut Vec<KeyEvent>, key_evt: KeyEvent) -> bool {
recent_keys.push(key_evt);
for evt in [VINTR, VEOF] {
let modes = shell_modes();
let cc = Key::from_single_byte(modes.c_cc[evt]);
if key == cc {
if recent_keys.iter().rev().nth(1) == Some(&cc) {
if match_key_event_to_key(&key_evt, &cc).is_some() {
if recent_keys
.iter()
.rev()
.nth(1)
.and_then(|&prev| match_key_event_to_key(&prev, &cc))
.is_some()
{
return true;
}
eprintf!(
@@ -101,9 +107,6 @@ fn process_input(continuous_mode: bool, verbose: bool) -> i32 {
continue;
};
let c = kevt.key.codepoint;
if c == key::Invalid {
continue;
}
if verbose {
printf!("# decoded from: ");
for byte in kevt.seq.chars() {
@@ -111,7 +114,27 @@ fn process_input(continuous_mode: bool, verbose: bool) -> i32 {
}
printf!("\n");
}
printf!("bind %s 'do something'\n", kevt.key);
let have_shifted_key = kevt.key.shifted_codepoint != '\0';
let mut keys = vec![(kevt.key.key, "")];
if have_shifted_key {
let mut shifted_key = kevt.key.key;
shifted_key.modifiers.shift = false;
shifted_key.codepoint = kevt.key.shifted_codepoint;
keys.push((shifted_key, "shifted key"));
}
if kevt.key.base_layout_codepoint != '\0' {
let mut base_layout_key = kevt.key.key;
base_layout_key.codepoint = kevt.key.base_layout_codepoint;
keys.push((base_layout_key, "physical key"));
}
for (key, explanation) in keys {
printf!(
"bind %s 'do something'%s%s\n",
key,
if explanation.is_empty() { "" } else { " # " },
explanation,
);
}
if let Some(name) = sequence_name(&mut recent_chars1, c) {
printf!("bind -k %ls 'do something'\n", name);
}

View File

@@ -17,12 +17,13 @@ fn send_to_bg(
let err = {
let job = &jobs[job_pos];
wgettext_fmt!(
"%ls: Can't put job %s, '%ls' to background because it is not under job control\n",
cmd,
job.job_id().to_wstring(),
job.command()
)
"%ls: Can't put job %s, '%ls' to background because it is not under job control\n",
cmd,
job.job_id().to_wstring(),
job.command()
)
};
drop(jobs);
builtin_print_help_error(parser, streams, cmd, &err);
return STATUS_CMD_ERROR;
}

View File

@@ -55,6 +55,7 @@ enum TokenMode {
/// \param buff the original command line buffer
/// \param cursor_pos the position of the cursor in the command line
fn replace_part(
parser: &Parser,
range: Range<usize>,
insert: &wstr,
insert_mode: AppendMode,
@@ -86,9 +87,9 @@ fn replace_part(
out.push_utfstr(&buff[range.end..]);
if search_field_mode {
commandline_set_search_field(out, Some(out_pos));
commandline_set_search_field(parser, out, Some(out_pos));
} else {
commandline_set_buffer(Some(out), Some(out_pos));
commandline_set_buffer(parser, Some(out), Some(out_pos));
}
}
@@ -465,7 +466,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
}
line_offset + new_coord
};
commandline_set_buffer(None, Some(new_pos));
commandline_set_buffer(parser, None, Some(new_pos));
} else {
streams.out.append(sprintf!(
"%d\n",
@@ -631,7 +632,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
.saturating_add_signed(isize::try_from(new_pos).unwrap()),
current_buffer.len(),
);
commandline_set_buffer(None, Some(new_pos));
commandline_set_buffer(parser, None, Some(new_pos));
} else {
streams
.out
@@ -652,6 +653,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
);
} else if positional_args == 1 {
replace_part(
parser,
range,
args[w.wopt_index],
append_mode,
@@ -662,6 +664,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
} else {
let sb = join_strings(&w.argv[w.wopt_index..], '\n');
replace_part(
parser,
range,
&sb,
append_mode,

View File

@@ -176,7 +176,7 @@ fn builtin_complete_remove_cmd(
if !removed {
// This means that all loops were empty.
complete_remove_all(cmd.to_owned(), cmd_is_path);
complete_remove_all(cmd.to_owned(), cmd_is_path, /*explicit=*/ true);
}
}
@@ -517,7 +517,8 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
next.flags,
faux_cmdline,
&mut tmp_cursor,
false,
/*append_only=*/ false,
/*is_unique=*/ false,
);
// completion_apply_to_command_line will append a space unless COMPLETE_NO_SPACE

View File

@@ -148,7 +148,7 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Optio
let job_group = job.group();
job_group.set_is_foreground(true);
if job.entitled_to_terminal() {
crate::input_common::terminal_protocols_disable_ifn();
crate::input_common::terminal_protocols_disable_ifn(false);
}
let tmodes = job_group.tmodes.borrow();
if job_group.wants_terminal() && tmodes.is_some() {

View File

@@ -92,6 +92,14 @@ fn parse_cmd_opts(
// A positional argument we got because we use RETURN_IN_ORDER.
let woptarg = w.woptarg.unwrap().to_owned();
if handling_named_arguments {
if is_read_only(&woptarg) {
streams.err.append(wgettext_fmt!(
"%ls: variable '%ls' is read-only\n",
cmd,
woptarg
));
return STATUS_INVALID_ARGS;
}
opts.named_arguments.push(woptarg);
} else {
streams.err.append(wgettext_fmt!(

View File

@@ -12,8 +12,9 @@
use crate::env::Environment;
use crate::env::READ_BYTE_LIMIT;
use crate::env::{EnvVar, EnvVarFlags};
use crate::input_common::decode_input_byte;
use crate::input_common::terminal_protocols_disable_ifn;
use crate::libc::MB_CUR_MAX;
use crate::input_common::DecodeState;
use crate::nix::isatty;
use crate::reader::commandline_set_buffer;
use crate::reader::ReaderConfig;
@@ -23,7 +24,6 @@
use crate::wcstringutil::split_about;
use crate::wcstringutil::split_string_tok;
use crate::wutil;
use crate::wutil::encoding::mbrtowc;
use crate::wutil::encoding::zero_mbstate;
use crate::wutil::perror;
use libc::SEEK_CUR;
@@ -234,7 +234,11 @@ fn read_interactive(
// Keep in-memory history only.
reader_push(parser, L!(""), conf);
commandline_set_buffer(Some(commandline.to_owned()), None);
let _modifiable_commandline = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().readonly_commandline, new_value),
false,
);
commandline_set_buffer(parser, Some(commandline.to_owned()), None);
let mline = {
let _interactive = scoped_push_replacer(
@@ -244,7 +248,7 @@ fn read_interactive(
reader_readline(parser, nchars)
};
terminal_protocols_disable_ifn();
terminal_protocols_disable_ifn(false);
if let Some(line) = mline {
*buff = line;
if nchars > 0 && nchars < buff.len() {
@@ -337,70 +341,61 @@ fn read_one_char_at_a_time(
split_null: bool,
) -> Option<c_int> {
let mut exit_res = STATUS_CMD_OK;
let mut eof = false;
let mut nbytes = 0;
let mut unconsumed = vec![];
loop {
let mut finished = false;
let mut res = '\x00';
let mut state = zero_mbstate();
while !finished {
let chars_read = buff.len();
let res = loop {
let mut b = [0_u8; 1];
match read_blocked(fd, &mut b) {
Ok(0) | Err(_) => {
eof = true;
break;
break None;
}
_ => {}
}
let b = b[0];
unconsumed.push(b);
nbytes += 1;
if MB_CUR_MAX() == 1 {
res = char::from(b);
finished = true;
} else {
let sz = unsafe {
mbrtowc(
std::ptr::addr_of_mut!(res).cast(),
std::ptr::addr_of!(b).cast(),
1,
&mut state,
)
} as isize;
if sz == -1 {
let mut consumed = 0;
match decode_input_byte(buff, &mut state, &unconsumed, &mut consumed) {
DecodeState::Incomplete => continue,
DecodeState::Complete => {
unconsumed.clear();
break Some(buff.as_char_slice().last().unwrap());
}
DecodeState::Error => {
state = zero_mbstate();
} else if sz != -2 {
finished = true;
unconsumed.clear();
}
}
}
};
if nbytes > READ_BYTE_LIMIT.load(Ordering::Relaxed) {
// Historical behavior: do not include the codepoint that made us overflow.
buff.truncate(chars_read);
exit_res = STATUS_READ_TOO_MUCH;
break;
}
if eof {
let Some(&res) = res else {
// EOF
if buff.is_empty() {
exit_res = STATUS_CMD_ERROR;
}
break;
};
if res == if split_null { '\0' } else { '\n' } {
buff.pop();
break;
}
if !split_null && res == '\n' {
break;
}
if split_null && res == '\0' {
break;
}
buff.push(res);
if nchars > 0 && nchars <= buff.len() {
break;
}
}
if buff.is_empty() && eof {
exit_res = STATUS_CMD_ERROR;
}
exit_res
}

View File

@@ -582,11 +582,10 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> O
STATUS_CURRENT_CMD => {
let command = &parser.libdata().status_vars.command;
if !command.is_empty() {
streams.out.append(command);
streams.out.appendln(command);
} else {
streams.out.appendln(*PROGRAM_NAME.get().unwrap());
}
streams.out.append_char('\n');
}
STATUS_CURRENT_COMMANDLINE => {
let commandline = &parser.libdata().status_vars.commandline;

View File

@@ -684,10 +684,11 @@ fn parse_4_arg_expression(
if let Token::UnaryBoolean(token) = first_token {
let subject = self.parse_3_arg_expression(start + 1, end)?;
let range = start..subject.range().end;
UnaryOperator {
subject,
token,
range: start..end,
range,
}
.into_some_box()
} else if first_token == Token::ParenOpen {

View File

@@ -103,6 +103,12 @@ fn test_test() {
assert!(run_test_test(1, &["!", "15", "-ge", "10"]));
assert!(run_test_test(0, &["!", "!", "15", "-ge", "10"]));
assert!(run_test_test(0, &[
"(", "-d", "/", ")",
"-o",
"(", "!", "-d", "/", ")",
]));
assert!(run_test_test(0, &["0", "-ne", "1", "-a", "0", "-eq", "0"]));
assert!(run_test_test(0, &["0", "-ne", "1", "-a", "-n", "5"]));
assert!(run_test_test(0, &["-n", "5", "-a", "10", "-gt", "5"]));

View File

@@ -1935,19 +1935,16 @@ fn complete_custom(&mut self, cmd: &wstr, cmdline: &wstr, ad: &mut CustomArgData
// Perhaps set a transient commandline so that custom completions
// builtin_commandline will refer to the wrapped command. But not if
// we're doing autosuggestions.
let mut _remove_transient = None;
let wants_transient =
(ad.wrap_depth > 0 || !ad.var_assignments.is_empty()) && !is_autosuggest;
if wants_transient {
let _remove_transient = (!is_autosuggest).then(|| {
let parser = self.ctx.parser();
parser
.libdata_mut()
.transient_commandlines
.push(cmdline.to_owned());
_remove_transient = Some(ScopeGuard::new((), move |_| {
ScopeGuard::new((), move |_| {
parser.libdata_mut().transient_commandlines.pop();
}));
}
})
});
// Maybe apply variable assignments.
let _restore_vars = self.apply_var_assignments(ad.var_assignments);
@@ -2342,14 +2339,14 @@ pub fn complete_remove(cmd: WString, cmd_is_path: bool, option: &wstr, typ: Comp
}
/// Removes all completions for a given command.
pub fn complete_remove_all(cmd: WString, cmd_is_path: bool) {
pub fn complete_remove_all(cmd: WString, cmd_is_path: bool, explicit: bool) {
let mut completion_map = COMPLETION_MAP.lock().expect("mutex poisoned");
let idx = CompletionEntryIndex {
name: cmd,
is_path: cmd_is_path,
};
let removed = completion_map.remove(&idx).is_some();
if !removed && !idx.is_path {
if explicit && !removed && !idx.is_path {
COMPLETION_TOMBSTONES.lock().unwrap().insert(idx.name);
}
}
@@ -2522,7 +2519,7 @@ pub fn complete_invalidate_path() {
.expect("mutex poisoned")
.get_autoloaded_commands();
for cmd in cmds {
complete_remove_all(cmd, false /* not a path */);
complete_remove_all(cmd, /*cmd_is_path=*/ false, /*explicit=*/ false);
}
}

168
src/env/config_paths.rs vendored Normal file
View File

@@ -0,0 +1,168 @@
use super::ConfigPaths;
use crate::env;
use crate::{common::get_executable_path, FLOG, FLOGF};
use once_cell::sync::Lazy;
use std::path::{Path, PathBuf};
const DOC_DIR: &str = env!("DOCDIR");
const DATA_DIR: &str = env!("DATADIR");
const DATA_DIR_SUBDIR: &str = env!("DATADIR_SUBDIR");
const SYSCONF_DIR: &str = env!("SYSCONFDIR");
const BIN_DIR: &str = env!("BINDIR");
const LOCALE_DIR: &str = env!("LOCALEDIR");
pub static CONFIG_PATHS: Lazy<ConfigPaths> = Lazy::new(|| {
// Read the current executable and follow all symlinks to it.
// OpenBSD has issues with `std::env::current_exe`, see gh-9086 and
// https://github.com/rust-lang/rust/issues/60560
let argv0 = PathBuf::from(std::env::args().next().unwrap());
let argv0 = if argv0.exists() {
argv0
} else {
std::env::current_exe().unwrap_or(argv0)
};
let argv0 = argv0.canonicalize().unwrap_or(argv0);
determine_config_directory_paths(argv0)
});
fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
// PORTING: why is this not just an associated method on ConfigPaths?
let mut paths = ConfigPaths::default();
let mut done = false;
let exec_path = get_executable_path(argv0.as_ref());
if let Ok(exec_path) = exec_path.canonicalize() {
FLOG!(
config,
format!("exec_path: {:?}, argv[0]: {:?}", exec_path, argv0.as_ref())
);
// TODO: we should determine program_name from argv0 somewhere in this file
// Detect if we're running right out of the CMAKE build directory
if exec_path.starts_with(env!("CARGO_MANIFEST_DIR")) {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
FLOG!(
config,
"Running out of target directory, using paths relative to CARGO_MANIFEST_DIR:\n",
manifest_dir.display()
);
done = true;
paths = ConfigPaths {
data: manifest_dir.join("share"),
sysconf: manifest_dir.join("etc"),
doc: manifest_dir.join("user_doc/html"),
bin: Some(exec_path.parent().unwrap().to_owned()),
locale: Some(manifest_dir.join("share/locale")),
}
}
if !done {
// The next check is that we are in a relocatable directory tree
if exec_path.ends_with("bin/fish") {
let base_path = exec_path.parent().unwrap().parent().unwrap();
#[cfg(feature = "installable")]
let data = base_path.join("share/fish/install");
#[cfg(not(feature = "installable"))]
let data = base_path.join("share/fish");
let locale =
(!cfg!(feature = "installable")).then(|| base_path.join("share/locale"));
paths = ConfigPaths {
// One obvious path is ~/.local (with fish in ~/.local/bin/).
// If we picked ~/.local/share/fish as our data path,
// we would install there and erase history.
// So let's isolate us a bit more.
data,
sysconf: base_path.join("etc/fish"),
doc: base_path.join("share/doc/fish"),
bin: Some(base_path.join("bin")),
locale,
}
} else if exec_path.ends_with("fish") {
FLOG!(
config,
"'fish' not in a 'bin/', trying paths relative to source tree"
);
let base_path = exec_path.parent().unwrap();
#[cfg(feature = "installable")]
let data = base_path.join("share/install");
#[cfg(not(feature = "installable"))]
let data = base_path.join("share");
let locale = Some(data.join("locale"));
paths = ConfigPaths {
data,
sysconf: base_path.join("etc"),
doc: base_path.join("user_doc/html"),
bin: Some(base_path.to_path_buf()),
locale,
}
}
if paths.data.exists() && paths.sysconf.exists() {
// The docs dir may not exist; in that case fall back to the compiled in path.
if !paths.doc.exists() {
paths.doc = PathBuf::from(DOC_DIR);
}
done = true;
}
}
}
if !done {
// Fall back to what got compiled in.
let data = if cfg!(feature = "installable") {
let Some(home) = env::get_home() else {
FLOG!(
error,
"Cannot find home directory and will refuse to read configuration.\n",
"Consider installing into a directory tree with `fish --install=PATH`."
);
return paths;
};
PathBuf::from(home).join(DATA_DIR).join(DATA_DIR_SUBDIR)
} else {
Path::new(DATA_DIR).join(DATA_DIR_SUBDIR)
};
let bin = if cfg!(feature = "installable") {
exec_path.parent().map(|x| x.to_path_buf())
} else {
Some(PathBuf::from(BIN_DIR))
};
let locale = if cfg!(feature = "installable") {
None
} else {
Some(PathBuf::from(LOCALE_DIR))
};
FLOG!(config, "Using compiled in paths:");
paths = ConfigPaths {
data,
sysconf: Path::new(SYSCONF_DIR).join("fish"),
doc: DOC_DIR.into(),
bin,
locale,
}
}
FLOGF!(
config,
"determine_config_directory_paths() results:\npaths.data: %ls\npaths.sysconf: \
%ls\npaths.doc: %ls\npaths.bin: %ls\npaths.locale: %ls",
paths.data.display().to_string(),
paths.sysconf.display().to_string(),
paths.doc.display().to_string(),
paths
.bin
.clone()
.map(|x| x.display().to_string())
.unwrap_or("|not found|".to_string()),
paths
.locale
.clone()
.map(|x| x.display().to_string())
.unwrap_or("|not found|".to_string()),
);
paths
}

2
src/env/mod.rs vendored
View File

@@ -1,6 +1,8 @@
mod config_paths;
pub mod environment;
mod environment_impl;
pub mod var;
pub use config_paths::CONFIG_PATHS;
use crate::common::ToCString;
pub use environment::*;

9
src/env/var.rs vendored
View File

@@ -50,10 +50,11 @@ fn from(val: EnvMode) -> Self {
/// env_init.
#[derive(Default)]
pub struct ConfigPaths {
pub data: PathBuf, // e.g., /usr/local/share
pub sysconf: PathBuf, // e.g., /usr/local/etc
pub doc: PathBuf, // e.g., /usr/local/share/doc/fish
pub bin: Option<PathBuf>, // e.g., /usr/local/bin
pub data: PathBuf, // e.g., /usr/local/share
pub sysconf: PathBuf, // e.g., /usr/local/etc
pub doc: PathBuf, // e.g., /usr/local/share/doc/fish
pub bin: Option<PathBuf>, // e.g., /usr/local/bin
pub locale: Option<PathBuf>, // e.g., /usr/local/share/locale
}
/// A collection of status and pipestatus.

View File

@@ -554,19 +554,16 @@ fn init_curses(vars: &EnvStack) {
if curses::setup(None, |term| apply_term_hacks(vars, term)).is_none() {
if is_interactive_session() {
let term = vars.get_unless_empty(L!("TERM")).map(|v| v.as_string());
// We do not warn for xterm-256color at all, we know that one.
if term != Some("xterm-256color".into()) {
if let Some(term) = term {
FLOG!(
warning,
wgettext_fmt!("Could not set up terminal for $TERM '%ls'. Falling back to hardcoded xterm-256color values", term)
);
} else {
FLOG!(
warning,
wgettext!("Could not set up terminal because $TERM is unset. Falling back to hardcoded xterm-256color values")
);
}
if let Some(term) = term {
FLOG!(
term_support,
wgettext_fmt!("Could not set up terminal for $TERM '%ls'. Falling back to hardcoded xterm-256color values", term)
);
} else {
FLOG!(
term_support,
wgettext!("Could not set up terminal because $TERM is unset. Falling back to hardcoded xterm-256color values")
);
}
}

View File

@@ -801,7 +801,7 @@ fn save(&mut self, directory: &wstr) -> bool {
{
let mut times: [libc::timespec; 2] = unsafe { std::mem::zeroed() };
times[0].tv_nsec = libc::UTIME_OMIT; // don't change ctime
if unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut times[1]) } != 0 {
if unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut times[1]) } == 0 {
unsafe {
libc::futimens(private_fd.as_raw_fd(), &times[0]);
}

View File

@@ -39,7 +39,7 @@
print_exit_warning_for_jobs, InternalProc, Job, JobGroupRef, Pid, ProcStatus, Process,
ProcessType, TtyTransfer,
};
use crate::reader::{reader_run_count, restore_term_mode};
use crate::reader::{reader_run_count, safe_restore_term_mode};
use crate::redirection::{dup2_list_resolve_chain, Dup2List};
use crate::threads::{iothread_perform_cant_wait, is_forked_child};
use crate::trace::trace_if_enabled_with_args;
@@ -437,7 +437,7 @@ fn launch_process_nofork(vars: &EnvStack, p: &Process) -> ! {
let actual_cmd = wcs2zstring(&p.actual_cmd);
// Ensure the terminal modes are what they were before we changed them.
restore_term_mode(false);
safe_restore_term_mode(false);
// Bounce to launch_process. This never returns.
safe_launch_process(p, &actual_cmd, &argv, &*envp);
}

View File

@@ -120,7 +120,7 @@ pub fn all_categories() -> Vec<&'static category_t> {
(char_encoding, "char-encoding", "Character encoding issues");
(history, "history", "Command history events");
(history_file, "history-file", "Reading/Writing the history file", true);
(history_file, "history-file", "Reading/Writing the history file");
(profile_history, "profile-history", "History performance measurements");

View File

@@ -30,6 +30,9 @@ pub enum FeatureFlag {
/// Whether keyboard protocols (kitty's CSI x u, xterm's modifyOtherKeys) are used
keyboard_protocols,
/// Whether to write OSC 133 prompt markers
mark_prompt,
}
struct Features {
@@ -118,6 +121,14 @@ pub struct FeatureMetadata {
default_value: true,
read_only: false,
},
FeatureMetadata {
flag: FeatureFlag::mark_prompt,
name: L!("mark-prompt"),
groups: L!("4.0"),
description: L!("Write OSC 133 prompt markers to the terminal"),
default_value: true,
read_only: false,
},
];
thread_local!(
@@ -180,6 +191,7 @@ const fn new() -> Self {
AtomicBool::new(METADATA[4].default_value),
AtomicBool::new(METADATA[5].default_value),
AtomicBool::new(METADATA[6].default_value),
AtomicBool::new(METADATA[7].default_value),
],
}
}

View File

@@ -44,7 +44,7 @@
};
use bitflags::bitflags;
use libc::{fchmod, fchown, flock, LOCK_EX, LOCK_SH, LOCK_UN};
use libc::{fchmod, fchown, flock, EINTR, LOCK_EX, LOCK_SH, LOCK_UN};
use lru::LruCache;
use nix::{fcntl::OFlag, sys::stat::Mode};
use rand::Rng;
@@ -1353,8 +1353,15 @@ unsafe fn maybe_lock_file(file: &mut File, lock_type: libc::c_int) -> bool {
return false;
}
let start_time = SystemTime::now();
let retval = unsafe { flock(raw_fd, lock_type) };
let (ok, start_time) = loop {
let start_time = SystemTime::now();
if unsafe { flock(raw_fd, lock_type) } != -1 {
break (true, start_time);
}
if errno::errno().0 != EINTR {
break (false, start_time);
}
};
if let Ok(duration) = start_time.elapsed() {
if duration > Duration::from_millis(250) {
FLOG!(
@@ -1367,7 +1374,7 @@ unsafe fn maybe_lock_file(file: &mut File, lock_type: libc::c_int) -> bool {
ABANDONED_LOCKING.store(true);
}
}
retval != -1
ok
}
/// Unlock a history file.

View File

@@ -3,8 +3,11 @@
use crate::env::{Environment, CURSES_INITIALIZED};
use crate::event;
use crate::flog::FLOG;
#[allow(unused_imports)]
use crate::future::IsSomeAnd;
use crate::input_common::{
CharEvent, CharInputStyle, InputData, InputEventQueuer, ReadlineCmd, R_END_INPUT_FUNCTIONS,
match_key_event_to_key, CharEvent, CharInputStyle, InputData, InputEventQueuer, KeyEvent,
KeyMatchQuality, ReadlineCmd, R_END_INPUT_FUNCTIONS,
};
use crate::key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers};
use crate::proc::job_reap;
@@ -16,6 +19,7 @@
use crate::wchar::prelude::*;
use once_cell::sync::{Lazy, OnceCell};
use std::ffi::CString;
use std::mem;
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex, MutexGuard,
@@ -429,7 +433,7 @@ fn select_interrupted(&mut self) {
if reader_reading_interrupted(self) != 0 {
let vintr = shell_modes().c_cc[libc::VINTR];
if vintr != 0 {
self.push_front(CharEvent::from_key(Key::from_single_byte(vintr)));
self.push_front(CharEvent::from_key(KeyEvent::from_single_byte(vintr)));
}
return;
}
@@ -509,14 +513,19 @@ fn next(&mut self) -> CharEvent {
/// Check if the next event is the given character. This advances the index on success only.
/// If `escaped` is set, then return false if this (or any other) character had a timeout.
fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> bool {
fn next_is_char(
&mut self,
style: &KeyNameStyle,
key: Key,
escaped: bool,
) -> Option<KeyMatchQuality> {
assert!(
self.idx <= self.peeked.len(),
"Index must not be larger than dequeued event count"
);
// See if we had a timeout already.
if escaped && self.had_timeout {
return false;
return None;
}
// Grab a new event if we have exhausted what we have already peeked.
// Use either readch or readch_timed, per our param.
@@ -527,7 +536,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
Some(evt) => evt,
None => {
self.had_timeout = true;
return false;
return None;
}
}
} else {
@@ -536,7 +545,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
Some(evt) => evt,
None => {
self.had_timeout = true;
return false;
return None;
}
}
};
@@ -546,9 +555,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
// Now we have peeked far enough; check the event.
// If it matches the char, then increment the index.
let evt = &self.peeked[self.idx];
let Some(kevt) = evt.get_key() else {
return false;
};
let kevt = evt.get_key()?;
if kevt.seq == L!("\x1b") && key.modifiers == Modifiers::ALT {
self.idx += 1;
self.subidx = 0;
@@ -556,13 +563,13 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
return self.next_is_char(style, Key::from_raw(key.codepoint), true);
}
if *style == KeyNameStyle::Plain {
if kevt.key == key {
let result = match_key_event_to_key(&kevt.key, &key);
if let Some(key_match) = &result {
assert!(self.subidx == 0);
self.idx += 1;
FLOG!(reader, "matched full key", key);
return true;
FLOG!(reader, "matched full key", key, "kind", key_match);
}
return false;
return result;
}
let actual_seq = kevt.seq.as_char_slice();
if !actual_seq.is_empty() {
@@ -582,7 +589,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
actual_seq.len()
)
);
return true;
return Some(KeyMatchQuality::Exact);
}
if key.modifiers == Modifiers::ALT && seq_char == '\x1b' {
if self.subidx + 1 == actual_seq.len() {
@@ -602,11 +609,11 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
self.subidx = 0;
}
FLOG!(reader, format!("matched {key} against raw escape sequence"));
return true;
return Some(KeyMatchQuality::Exact);
}
}
}
false
None
}
/// Consume all events up to the current index.
@@ -633,7 +640,12 @@ pub fn restart(&mut self) {
}
/// Return true if this `peeker` matches a given sequence of char events given by `str`.
fn try_peek_sequence(&mut self, style: &KeyNameStyle, seq: &[Key]) -> bool {
fn try_peek_sequence(
&mut self,
style: &KeyNameStyle,
seq: &[Key],
quality: &mut Vec<KeyMatchQuality>,
) -> bool {
assert!(
!seq.is_empty(),
"Empty sequence passed to try_peek_sequence"
@@ -643,9 +655,10 @@ fn try_peek_sequence(&mut self, style: &KeyNameStyle, seq: &[Key]) -> bool {
// If we just read an escape, we need to add a timeout for the next char,
// to distinguish between the actual escape key and an "alt"-modifier.
let escaped = *style != KeyNameStyle::Plain && prev == Key::from_raw(key::Escape);
if !self.next_is_char(style, *key, escaped) {
let Some(spec) = self.next_is_char(style, *key, escaped) else {
return false;
}
};
quality.push(spec);
prev = *key;
}
if self.subidx != 0 {
@@ -662,16 +675,24 @@ fn try_peek_sequence(&mut self, style: &KeyNameStyle, seq: &[Key]) -> bool {
/// user's mapping list, then the preset list.
/// Return none if nothing matches, or if we may have matched a longer sequence but it was
/// interrupted by a readline event.
pub fn find_mapping(
pub fn find_mapping<'a>(
&mut self,
vars: &dyn Environment,
ip: &InputMappingSet,
ip: &'a InputMappingSet,
) -> Option<InputMapping> {
let mut generic: Option<&InputMapping> = None;
let bind_mode = input_get_bind_mode(vars);
let mut escape: Option<&InputMapping> = None;
struct MatchedMapping<'a> {
mapping: &'a InputMapping,
quality: Vec<KeyMatchQuality>,
idx: usize,
subidx: usize,
}
let mut deferred: Option<MatchedMapping<'a>> = None;
let ml = ip.mapping_list.iter().chain(ip.preset_mapping_list.iter());
let mut quality = vec![];
for m in ml {
if m.mode != bind_mode {
continue;
@@ -679,24 +700,41 @@ pub fn find_mapping(
// Defer generic mappings until the end.
if m.is_generic() {
if generic.is_none() {
generic = Some(m);
if deferred.is_none() {
deferred = Some(MatchedMapping {
mapping: m,
quality: vec![],
idx: self.idx,
subidx: self.subidx,
});
}
continue;
}
// FLOG!(reader, "trying mapping", format!("{:?}", m));
if self.try_peek_sequence(&m.key_name_style, &m.seq) {
// A binding for just escape should also be deferred
// so escape sequences take precedence.
if m.seq == vec![Key::from_raw(key::Escape)] {
if escape.is_none() {
escape = Some(m);
}
} else {
if self.try_peek_sequence(&m.key_name_style, &m.seq, &mut quality) {
// // A binding for just escape should also be deferred
// // so escape sequences take precedence.
let is_escape = m.seq == vec![Key::from_raw(key::Escape)];
let is_perfect_match = quality
.iter()
.all(|key_match| *key_match == KeyMatchQuality::Exact);
if !is_escape && is_perfect_match {
return Some(m.clone());
}
if deferred
.as_ref()
.is_none_or(|matched| !is_escape && quality >= matched.quality)
{
deferred = Some(MatchedMapping {
mapping: m,
quality: mem::take(&mut quality),
idx: self.idx,
subidx: self.subidx,
});
}
}
quality.clear();
self.restart();
}
if self.char_sequence_interrupted() {
@@ -705,17 +743,13 @@ pub fn find_mapping(
return None;
}
if escape.is_some() {
// We need to reconsume the escape.
self.next();
return escape.cloned();
}
if generic.is_some() {
generic.cloned()
} else {
None
}
deferred
.map(|matched| {
self.idx = matched.idx;
self.subidx = matched.subidx;
matched.mapping
})
.cloned()
}
}
@@ -841,7 +875,7 @@ fn read_characters_no_readline(&mut self) -> CharEvent {
let evt_to_return: CharEvent;
loop {
let evt = self.readch();
if evt.is_readline_or_command() {
if evt.is_readline_or_command() || evt.is_check_exit() {
saved_events.push(evt);
} else {
evt_to_return = evt;

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,9 @@
use crate::{
common::{escape_string, EscapeFlags, EscapeStringStyle},
fallback::fish_wcwidth,
reader::TERMINAL_MODE_ON_STARTUP,
reader::safe_get_terminal_mode_on_startup,
wchar::{decode_byte_from_char, prelude::*},
wutil::{fish_is_pua, fish_wcstoi},
wutil::fish_wcstoul,
};
pub(crate) const Backspace: char = '\u{F500}'; // below ENCODE_DIRECT_BASE
@@ -18,16 +18,19 @@
pub(crate) const Right: char = '\u{F507}';
pub(crate) const PageUp: char = '\u{F508}';
pub(crate) const PageDown: char = '\u{F509}';
pub(crate) const Home: char = '\u{F50a}';
pub(crate) const End: char = '\u{F50b}';
pub(crate) const Insert: char = '\u{F50c}';
pub(crate) const Tab: char = '\u{F50d}';
pub(crate) const Space: char = '\u{F50e}';
pub const Invalid: char = '\u{F50f}';
pub(crate) const Home: char = '\u{F50A}';
pub(crate) const End: char = '\u{F50B}';
pub(crate) const Insert: char = '\u{F50C}';
pub(crate) const Tab: char = '\u{F50D}';
pub(crate) const Space: char = '\u{F50E}';
pub(crate) const Menu: char = '\u{F50F}';
pub(crate) const PrintScreen: char = '\u{F510}';
pub(crate) const MAX_FUNCTION_KEY: u32 = 12;
pub(crate) fn function_key(n: u32) -> char {
assert!((1..=12).contains(&n));
char::from_u32(u32::from(Invalid) + n).unwrap()
assert!((1..=MAX_FUNCTION_KEY).contains(&n));
char::from_u32(u32::from('\u{F5FF}') - MAX_FUNCTION_KEY + (n - 1)).unwrap()
}
pub(crate) const Invalid: char = '\u{F5FF}';
const KEY_NAMES: &[(char, &wstr)] = &[
('-', L!("minus")),
@@ -47,6 +50,8 @@ pub(crate) fn function_key(n: u32) -> char {
(Insert, L!("insert")),
(Tab, L!("tab")),
(Space, L!("space")),
(Menu, L!("menu")),
(PrintScreen, L!("printscreen")),
];
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
@@ -54,6 +59,7 @@ pub struct Modifiers {
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
pub sup: bool,
}
impl Modifiers {
@@ -62,18 +68,30 @@ const fn new() -> Self {
ctrl: false,
alt: false,
shift: false,
sup: false,
}
}
#[cfg(test)]
pub(crate) const CTRL: Self = {
let mut m = Self::new();
m.ctrl = true;
m
};
pub(crate) const ALT: Self = {
let mut m = Self::new();
m.alt = true;
m
};
pub const SHIFT: Self = {
let mut m = Self::new();
m.shift = true;
m
};
pub(crate) fn is_some(&self) -> bool {
self.ctrl || self.alt || self.shift
*self != Self::new()
}
pub(crate) fn is_none(&self) -> bool {
!self.is_some()
*self == Self::new()
}
}
@@ -84,39 +102,33 @@ pub struct Key {
}
impl Key {
pub(crate) fn from_raw(codepoint: char) -> Self {
pub(crate) const fn new(modifiers: Modifiers, codepoint: char) -> Self {
Self {
modifiers: Modifiers::default(),
modifiers,
codepoint,
}
}
pub(crate) fn from_raw(codepoint: char) -> Self {
Self::new(Modifiers::default(), codepoint)
}
}
pub(crate) const fn ctrl(codepoint: char) -> Key {
let mut modifiers = Modifiers::new();
modifiers.ctrl = true;
Key {
modifiers,
codepoint,
}
Key::new(modifiers, codepoint)
}
pub(crate) const fn alt(codepoint: char) -> Key {
let mut modifiers = Modifiers::new();
modifiers.alt = true;
Key {
modifiers,
codepoint,
}
Key::new(modifiers, codepoint)
}
pub(crate) const fn shift(codepoint: char) -> Key {
let mut modifiers = Modifiers::new();
modifiers.shift = true;
Key {
modifiers,
codepoint,
}
Key::new(modifiers, codepoint)
}
impl Key {
@@ -133,10 +145,7 @@ pub fn from_single_byte(c: u8) -> Self {
pub fn canonicalize_control_char(c: u8) -> Option<Key> {
let codepoint = canonicalize_keyed_control_char(char::from(c));
if u32::from(codepoint) > 255 {
return Some(Key {
modifiers: Modifiers::default(),
codepoint,
});
return Some(Key::from_raw(codepoint));
}
if c < 32 {
@@ -160,8 +169,10 @@ pub(crate) fn canonicalize_keyed_control_char(c: char) -> char {
if c == ' ' {
return Space;
}
if c == char::from(TERMINAL_MODE_ON_STARTUP.lock().unwrap().c_cc[VERASE]) {
return Backspace;
if let Some(tm) = safe_get_terminal_mode_on_startup() {
if c == char::from(tm.c_cc[VERASE]) {
return Backspace;
}
}
if c == char::from(127) {
// when it's not backspace
@@ -205,19 +216,6 @@ pub(crate) fn canonicalize_key(mut key: Key) -> Result<Key, WString> {
key.modifiers.ctrl = true;
}
}
if key.modifiers.shift {
if key.codepoint.is_ascii_alphabetic() {
// Shift + ASCII letters is just the uppercase letter.
key.modifiers.shift = false;
key.codepoint = key.codepoint.to_ascii_uppercase();
} else if !fish_is_pua(key.codepoint) {
// Shift + any other printable character is not allowed.
return Err(wgettext_fmt!(
"Shift modifier is only supported on special keys and lowercase ASCII, not '%s'",
key,
));
}
}
Ok(key)
}
@@ -271,6 +269,7 @@ pub(crate) fn parse_keys(value: &wstr) -> Result<Vec<Key>, WString> {
_ if modifier == "ctrl" => modifiers.ctrl = true,
_ if modifier == "alt" => modifiers.alt = true,
_ if modifier == "shift" => modifiers.shift = true,
_ if modifier == "super" => modifiers.sup = true,
_ => {
return Err(wgettext_fmt!(
"unknown modifier '%s' in '%s'",
@@ -286,25 +285,22 @@ pub(crate) fn parse_keys(value: &wstr) -> Result<Vec<Key>, WString> {
.find_map(|(codepoint, name)| (name == key_name).then_some(*codepoint))
.or_else(|| (key_name.len() == 1).then(|| key_name.as_char_slice()[0]));
let key = if let Some(codepoint) = codepoint {
canonicalize_key(Key {
modifiers,
codepoint,
})?
canonicalize_key(Key::new(modifiers, codepoint))?
} else if codepoint.is_none() && key_name.starts_with('f') && key_name.len() <= 3 {
let num = key_name.strip_prefix('f').unwrap();
let codepoint = match fish_wcstoi(num) {
Ok(n) if (1..=12).contains(&n) => function_key(u32::try_from(n).unwrap()),
let codepoint = match fish_wcstoul(num) {
Ok(n) if (1..=u64::from(MAX_FUNCTION_KEY)).contains(&n) => {
function_key(u32::try_from(n).unwrap())
}
_ => {
return Err(wgettext_fmt!(
"only f1 through f12 are supported, not 'f%s'",
"only f1 through f%d are supported, not 'f%s'",
MAX_FUNCTION_KEY,
num,
));
}
};
Key {
modifiers,
codepoint,
}
Key::new(modifiers, codepoint)
} else {
return Err(wgettext_fmt!(
"cannot parse key '%s'",
@@ -347,28 +343,6 @@ pub(crate) fn canonicalize_raw_escapes(keys: Vec<Key>) -> Vec<Key> {
canonical
}
impl Key {
pub(crate) fn codepoint_text(&self) -> Option<char> {
if self.modifiers.is_some() {
return None;
}
let c = self.codepoint;
if c == Space {
return Some(' ');
}
if c == Enter {
return Some('\n');
}
if c == Tab {
return Some('\t');
}
if fish_is_pua(c) || u32::from(c) <= 27 {
return None;
}
Some(c)
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
WString::from(*self).fmt(f)
@@ -387,7 +361,7 @@ fn from(key: Key) -> Self {
.iter()
.find_map(|&(codepoint, name)| (codepoint == key.codepoint).then(|| name.to_owned()))
.or_else(|| {
(function_key(1)..=function_key(12))
(function_key(1)..=function_key(MAX_FUNCTION_KEY))
.contains(&key.codepoint)
.then(|| {
sprintf!(
@@ -407,6 +381,9 @@ fn from(key: Key) -> Self {
if key.modifiers.ctrl {
res.insert_utfstr(0, L!("ctrl-"));
}
if key.modifiers.sup {
res.insert_utfstr(0, L!("super-"));
}
res
}

View File

@@ -1,13 +1,13 @@
#include <locale.h>
#include <paths.h>
#include <paths.h> // _PATH_BSHELL
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h> // MB_CUR_MAX
#include <sys/mount.h> // MNT_LOCAL
#include <sys/resource.h>
#include <unistd.h>
#define UNUSED(x) (void)(x)
#include <sys/statvfs.h> // ST_LOCAL
#include <unistd.h> // _CS_PATH, _PC_CASE_SENSITIVE
size_t C_MB_CUR_MAX() { return MB_CUR_MAX; }

View File

@@ -218,17 +218,30 @@ fn parse_util_locate_cmdsub(
let mut last_dollar = None;
let mut paran_begin = None;
let mut paran_end = None;
enum Quote {
Real(char),
VirtualDouble,
}
fn process_opening_quote(
input: &[char],
inout_is_quoted: &mut Option<&mut bool>,
paran_count: i32,
quoted_cmdsubs: &mut Vec<i32>,
pos: usize,
mut pos: usize,
last_dollar: &mut Option<usize>,
quote: char,
quote: Quote,
) -> Option<usize> {
let quote = match quote {
Quote::Real(q) => q,
Quote::VirtualDouble => {
pos = pos.saturating_sub(1);
'"'
}
};
let q_end = quote_end(input.into(), pos, quote)?;
// Found a valid closing quote.
if input[q_end] == '$' {
// The closing quote is another quoted command substitution.
*last_dollar = Some(q_end);
quoted_cmdsubs.push(paran_count);
}
@@ -254,9 +267,9 @@ fn process_opening_quote(
&mut quoted_cmdsubs,
pos,
&mut last_dollar,
'"',
Quote::VirtualDouble,
)
.unwrap_or(input.len());
.map_or(input.len(), |pos| pos + 1);
}
while pos < input.len() {
@@ -270,7 +283,7 @@ fn process_opening_quote(
&mut quoted_cmdsubs,
pos,
&mut last_dollar,
c,
Quote::Real(c),
) {
Some(q_end) => pos = q_end,
None => break,
@@ -307,21 +320,19 @@ fn process_opening_quote(
if quoted_cmdsubs.last() == Some(&paran_count) {
quoted_cmdsubs.pop();
// Quoted command substitutions temporarily close double quotes.
// In "foo$(bar)baz$(qux)"
// We are here ^
// After the ) in a quoted command substitution, we need to act as if
// there was an invisible double quote.
match quote_end(input.into(), pos, '"') {
Some(q_end) => {
// Found a valid closing quote.
// Stop at $(qux), which is another quoted command substitution.
if input[q_end] == '$' {
quoted_cmdsubs.push(paran_count);
}
pos = q_end;
}
// In "foo$(bar)baz$(qux)", after the ), we need to act as if there was a double quote.
match process_opening_quote(
input,
&mut inout_is_quoted,
paran_count,
&mut quoted_cmdsubs,
pos,
&mut last_dollar,
Quote::VirtualDouble,
) {
Some(q_end) => pos = q_end,
None => break,
};
}
}
}
is_token_begin = is_token_delimiter(c, input.get(pos + 1).copied());

View File

@@ -276,6 +276,9 @@ pub struct LibraryData {
/// Whether we are currently interactive.
pub is_interactive: bool,
/// Whether the command line is closed for modification from fish script.
pub readonly_commandline: bool,
/// Whether to suppress fish_trace output. This occurs in the prompt, event handlers, and key
/// bindings.
pub suppress_fish_trace: bool,
@@ -611,7 +614,7 @@ pub fn eval_node<T: Node>(
let mut execution_context =
ExecutionContext::new(ps.clone(), block_io.clone(), Rc::clone(&line_counter));
terminal_protocols_disable_ifn();
terminal_protocols_disable_ifn(false);
// Check the exec count so we know if anything got executed.
let prev_exec_count = self.libdata().exec_count;

View File

@@ -2,12 +2,10 @@
//! for testing if a command with a given name can be found in the PATH, and various other
//! path-related issues.
use crate::common::{is_windows_subsystem_for_linux as is_wsl, wcs2osstring, wcs2zstring, WSL};
use crate::common::{wcs2osstring, wcs2zstring};
use crate::env::{EnvMode, EnvStack, Environment};
use crate::expand::{expand_tilde, HOME_DIRECTORY};
use crate::flog::{FLOG, FLOGF};
#[cfg(not(target_os = "linux"))]
use crate::libc::{MNT_LOCAL, ST_LOCAL};
use crate::wchar::prelude::*;
use crate::wutil::{normalize_path, path_normalize_for_cd, waccess, wdirname, wstat};
use errno::{errno, set_errno, Errno};
@@ -15,6 +13,7 @@
use once_cell::sync::Lazy;
use std::ffi::OsStr;
use std::io::ErrorKind;
use std::mem::MaybeUninit;
use std::os::unix::prelude::*;
/// Returns the user configuration directory for fish. If the directory or one of its parents
@@ -308,29 +307,6 @@ fn path_get_path_core<S: AsRef<wstr>>(cmd: &wstr, pathsv: &[S]) -> GetPathResult
return GetPathResult::new(test_path(cmd).err(), cmd.to_owned());
}
// WSLv1/WSLv2 tack on the entire Windows PATH to the end of the PATH environment variable, and
// accessing these paths from WSL binaries is pathalogically slow. We also don't expect to find
// any "normal" nix binaries under these paths, so we can skip them unless we are executing bins
// with Windows-ish names. We try to keep paths manually added to $fish_user_paths by only
// chopping off entries after the last "normal" PATH entry.
let pathsv = if is_wsl(WSL::Any) && !cmd.contains('.') {
let win_path_count = pathsv
.iter()
.rev()
.take_while(|p| {
let p = p.as_ref();
p.starts_with("/mnt/")
&& p.chars()
.nth("/mnt/x".len())
.map(|c| c == '/')
.unwrap_or(false)
})
.count();
&pathsv[..pathsv.len() - win_path_count]
} else {
pathsv
};
let mut best = noent_res;
for next_path in pathsv {
let next_path: &wstr = next_path.as_ref();
@@ -699,10 +675,11 @@ fn path_remoteness(path: &wstr) -> DirRemoteness {
let narrow = wcs2zstring(path);
#[cfg(target_os = "linux")]
{
let mut buf: libc::statfs = unsafe { std::mem::zeroed() };
if unsafe { libc::statfs(narrow.as_ptr(), &mut buf) } < 0 {
let mut buf = MaybeUninit::uninit();
if unsafe { libc::statfs(narrow.as_ptr(), buf.as_mut_ptr()) } < 0 {
return DirRemoteness::unknown;
}
let buf = unsafe { buf.assume_init() };
// Linux has constants for these like NFS_SUPER_MAGIC, SMB_SUPER_MAGIC, CIFS_MAGIC_NUMBER but
// these are in varying headers. Simply hard code them.
// Note that we treat FUSE filesystems as remote, which means we lock less on such filesystems.
@@ -732,38 +709,49 @@ fn path_remoteness(path: &wstr) -> DirRemoteness {
}
#[cfg(not(target_os = "linux"))]
{
let st_local = ST_LOCAL();
if st_local != 0 {
// ST_LOCAL is a flag to statvfs, which is itself standardized.
// In practice the only system to use this path is NetBSD.
let mut buf: libc::statvfs = unsafe { std::mem::zeroed() };
if unsafe { libc::statvfs(narrow.as_ptr(), &mut buf) } < 0 {
fn remoteness_via_statfs<StatFS, Flags>(
statfn: unsafe extern "C" fn(*const libc::c_char, *mut StatFS) -> libc::c_int,
flagsfn: fn(&StatFS) -> Flags,
is_local_flag: u64,
path: &std::ffi::CStr,
) -> DirRemoteness
where
u64: From<Flags>,
{
if is_local_flag == 0 {
return DirRemoteness::unknown;
}
// statvfs::f_flag is `unsigned long`, which is 4-bytes on most 32-bit targets.
#[cfg_attr(target_pointer_width = "64", allow(clippy::useless_conversion))]
return if u64::from(buf.f_flag) & st_local != 0 {
DirRemoteness::local
} else {
DirRemoteness::remote
};
}
let mnt_local = MNT_LOCAL();
if mnt_local != 0 {
let mut buf: libc::statvfs = unsafe { std::mem::zeroed() };
if unsafe { libc::statvfs(narrow.as_ptr(), &mut buf) } < 0 {
let mut buf = MaybeUninit::uninit();
if unsafe { (statfn)(path.as_ptr(), buf.as_mut_ptr()) } < 0 {
return DirRemoteness::unknown;
}
let buf = unsafe { buf.assume_init() };
// statfs::f_flag is hard-coded as 64-bits on 32/64-bit FreeBSD but it's a (4-byte)
// long on 32-bit NetBSD.. and always 4-bytes on macOS (even on 64-bit builds).
#[allow(clippy::useless_conversion)]
return if u64::from(buf.f_flag) & mnt_local != 0 {
if u64::from((flagsfn)(&buf)) & is_local_flag != 0 {
DirRemoteness::local
} else {
DirRemoteness::remote
};
}
}
DirRemoteness::unknown
// ST_LOCAL is a flag to statvfs, which is itself standardized.
// In practice the only system to define it is NetBSD.
#[cfg(target_os = "netbsd")]
let remoteness = remoteness_via_statfs(
libc::statvfs,
|stat: &libc::statvfs| stat.f_flag,
crate::libc::ST_LOCAL(),
&narrow,
);
#[cfg(not(target_os = "netbsd"))]
let remoteness = remoteness_via_statfs(
libc::statfs,
|stat: &libc::statfs| stat.f_flags,
crate::libc::MNT_LOCAL(),
&narrow,
);
remoteness
}
}

View File

@@ -34,8 +34,7 @@
use std::rc::Rc;
#[cfg(target_has_atomic = "64")]
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::atomic::{AtomicI32, AtomicU32, AtomicU8};
use std::sync::atomic::{AtomicI32, AtomicPtr, AtomicU32, AtomicU8, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::{Duration, Instant};
@@ -67,6 +66,7 @@
use crate::flog::{FLOG, FLOGF};
#[allow(unused_imports)]
use crate::future::IsSomeAnd;
use crate::future_feature_flags::{feature_test, FeatureFlag};
use crate::global_safety::RelaxedAtomicBool;
use crate::highlight::{
autosuggest_validate_from_history, highlight_shell, HighlightRole, HighlightSpec,
@@ -152,9 +152,9 @@ enum ExitState {
pub static SHELL_MODES: Lazy<Mutex<libc::termios>> =
Lazy::new(|| Mutex::new(unsafe { std::mem::zeroed() }));
/// Mode on startup, which we restore on exit.
pub static TERMINAL_MODE_ON_STARTUP: Lazy<Mutex<libc::termios>> =
Lazy::new(|| Mutex::new(unsafe { std::mem::zeroed() }));
/// The valid terminal modes on startup. This is set once and not modified after.
/// Warning: this is read from the SIGTERM handler! Hence the raw global.
static TERMINAL_MODE_ON_STARTUP: AtomicPtr<libc::termios> = AtomicPtr::new(std::ptr::null_mut());
/// Mode we use to execute programs.
static TTY_MODES_FOR_EXTERNAL_CMDS: Lazy<Mutex<libc::termios>> =
@@ -171,6 +171,12 @@ enum ExitState {
/// This is set from a signal handler.
static SIGHUP_RECEIVED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
// Get the terminal mode on startup. This is "safe" because it's async-signal safe.
pub fn safe_get_terminal_mode_on_startup() -> Option<&'static libc::termios> {
// Safety: set atomically and not modified after.
unsafe { TERMINAL_MODE_ON_STARTUP.load(Ordering::Acquire).as_ref() }
}
/// A singleton snapshot of the reader state. This is factored out for thread-safety reasons:
/// it may be fetched on a background thread.
fn commandline_state_snapshot() -> MutexGuard<'static, CommandlineState> {
@@ -659,13 +665,15 @@ fn read_i(parser: &Parser) -> i32 {
data.command_line.clear();
data.update_buff_pos(EditableLineTag::Commandline, None);
data.command_line_changed(EditableLineTag::Commandline);
// OSC 133 "Command start"
write!(
BufferedOuputter::new(&mut Outputter::stdoutput().borrow_mut()),
"\x1b]133;C;cmdline_url={}\x07",
escape_string(&command, EscapeStringStyle::Url),
)
.unwrap();
if feature_test(FeatureFlag::mark_prompt) {
// OSC 133 "Command start"
write!(
BufferedOuputter::new(&mut Outputter::stdoutput().borrow_mut()),
"\x1b]133;C;cmdline_url={}\x07",
escape_string(&command, EscapeStringStyle::Url),
)
.unwrap();
}
event::fire_generic(parser, L!("fish_preexec").to_owned(), vec![command.clone()]);
let eval_res = reader_run_command(parser, &command);
signal_clear_cancel();
@@ -678,12 +686,14 @@ fn read_i(parser: &Parser) -> i32 {
parser.libdata_mut().exit_current_script = false;
// OSC 133 "Command finished"
write!(
BufferedOuputter::new(&mut Outputter::stdoutput().borrow_mut()),
"\x1b]133;D;{}\x07",
parser.get_last_status()
)
.unwrap();
if feature_test(FeatureFlag::mark_prompt) {
write!(
BufferedOuputter::new(&mut Outputter::stdoutput().borrow_mut()),
"\x1b]133;D;{}\x07",
parser.get_last_status()
)
.unwrap();
}
event::fire_generic(parser, L!("fish_postexec").to_owned(), vec![command]);
// Allow any pending history items to be returned in the history array.
data.history.resolve_pending();
@@ -813,8 +823,15 @@ fn read_ni(parser: &Parser, fd: RawFd, io: &IoChain) -> i32 {
/// Initialize the reader.
pub fn reader_init(will_restore_foreground_pgroup: bool) {
// Save the initial terminal mode.
let mut terminal_mode_on_startup = TERMINAL_MODE_ON_STARTUP.lock().unwrap();
unsafe { libc::tcgetattr(STDIN_FILENO, &mut *terminal_mode_on_startup) };
// Note this field is read by a signal handler, so do it atomically, with a leaked mode.
let mut terminal_mode_on_startup = unsafe { std::mem::zeroed::<libc::termios>() };
let ret = unsafe { libc::tcgetattr(libc::STDIN_FILENO, &mut terminal_mode_on_startup) };
// TODO: rationalize behavior if initial tcgetattr() fails.
if ret == 0 {
// Must be mut because AtomicPtr doesn't have const variant.
let leaked: *mut libc::termios = Box::leak(Box::new(terminal_mode_on_startup));
TERMINAL_MODE_ON_STARTUP.store(leaked, Ordering::Release);
}
#[cfg(not(test))]
assert!(AT_EXIT.get().is_none());
@@ -826,7 +843,7 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
// Set the mode used for program execution, initialized to the current mode.
let mut tty_modes_for_external_cmds = TTY_MODES_FOR_EXTERNAL_CMDS.lock().unwrap();
*tty_modes_for_external_cmds = *terminal_mode_on_startup;
*tty_modes_for_external_cmds = terminal_mode_on_startup;
term_fix_external_modes(&mut tty_modes_for_external_cmds);
// Disable flow control by default.
@@ -838,7 +855,6 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
term_fix_modes(&mut shell_modes());
drop(terminal_mode_on_startup);
drop(tty_modes_for_external_cmds);
// Set up our fixed terminal modes once,
@@ -850,9 +866,11 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
}
}
// TODO(pca): this is run in our "AT_EXIT" handler from a SIGTERM handler.
// It must be made async-signal-safe (or not invoked).
pub fn reader_deinit(in_signal_handler: bool, restore_foreground_pgroup: bool) {
restore_term_mode(in_signal_handler);
crate::input_common::terminal_protocols_disable_ifn();
safe_restore_term_mode(in_signal_handler);
crate::input_common::terminal_protocols_disable_ifn(in_signal_handler);
if restore_foreground_pgroup {
restore_term_foreground_process_group_for_exit();
}
@@ -861,20 +879,15 @@ pub fn reader_deinit(in_signal_handler: bool, restore_foreground_pgroup: bool) {
/// Restore the term mode if we own the terminal and are interactive (#8705).
/// It's important we do this before restore_foreground_process_group,
/// otherwise we won't think we own the terminal.
pub fn restore_term_mode(in_signal_handler: bool) {
/// THIS FUNCTION IS CALLED FROM A SIGNAL HANDLER. IT MUST BE ASYNC-SIGNAL-SAFE.
pub fn safe_restore_term_mode(in_signal_handler: bool) {
if !is_interactive_session() || unsafe { libc::getpgrp() != libc::tcgetpgrp(STDIN_FILENO) } {
return;
}
if unsafe {
libc::tcsetattr(
STDIN_FILENO,
TCSANOW,
&*TERMINAL_MODE_ON_STARTUP.lock().unwrap(),
) == -1
} && errno().0 == EIO
{
redirect_tty_output(in_signal_handler);
if let Some(modes) = safe_get_terminal_mode_on_startup() {
if unsafe { libc::tcsetattr(STDIN_FILENO, TCSANOW, modes) == -1 } && errno().0 == EIO {
redirect_tty_output(in_signal_handler);
}
}
}
@@ -955,6 +968,9 @@ pub fn reader_schedule_prompt_repaint() {
}
pub fn reader_execute_readline_cmd(parser: &Parser, ch: CharEvent) {
if parser.libdata().readonly_commandline {
return;
}
if let Some(data) = current_data() {
let mut data = Reader { parser, data };
let CharEvent::Readline(readline_cmd_evt) = &ch else {
@@ -977,7 +993,7 @@ pub fn reader_execute_readline_cmd(parser: &Parser, ch: CharEvent) {
data.rls = Some(ReadlineLoopState::new());
}
data.save_screen_state();
data.handle_char_event(Some(ch));
let _ = data.handle_char_event(Some(ch));
}
}
@@ -1033,7 +1049,10 @@ pub fn commandline_get_state(sync: bool) -> CommandlineState {
/// Set the command line text and position. This may be called on a background thread; the reader
/// will pick it up when it is done executing.
pub fn commandline_set_buffer(text: Option<WString>, cursor_pos: Option<usize>) {
pub fn commandline_set_buffer(parser: &Parser, text: Option<WString>, cursor_pos: Option<usize>) {
if parser.libdata().readonly_commandline {
return;
}
{
let mut state = commandline_state_snapshot();
if let Some(text) = text {
@@ -1044,7 +1063,10 @@ pub fn commandline_set_buffer(text: Option<WString>, cursor_pos: Option<usize>)
current_data().map(|data| data.apply_commandline_state_changes());
}
pub fn commandline_set_search_field(text: WString, cursor_pos: Option<usize>) {
pub fn commandline_set_search_field(parser: &Parser, text: WString, cursor_pos: Option<usize>) {
if parser.libdata().readonly_commandline {
return;
}
{
let mut state = commandline_state_snapshot();
assert!(state.search_field.is_some());
@@ -3696,8 +3718,11 @@ fn clear_pager(&mut self) {
fn get_selection(&self) -> Option<Range<usize>> {
let selection = self.selection?;
let start = selection.start;
let start = std::cmp::min(selection.start, self.command_line.len());
let end = std::cmp::min(selection.stop, self.command_line.len());
if start == end {
return None;
}
Some(start..end)
}
@@ -3777,6 +3802,7 @@ fn pager_selection_changed(&mut self) {
&self.cycle_command_line,
&mut cursor_pos,
false,
/*is_unique=*/ false, // don't care
),
};
@@ -4414,6 +4440,7 @@ fn get_autosuggestion_performer(
&search_string,
&mut cursor,
/*append_only=*/ true,
/*is_unique=*/ false,
);
}
result
@@ -4820,7 +4847,9 @@ fn fill_history_pager(
if search_term != zelf.pager.search_field_line.text() {
return; // Stale request.
}
let history_pager = zelf.history_pager.as_mut().unwrap();
let Some(history_pager) = zelf.history_pager.as_mut() else {
return; // Pager has been closed.
};
history_pager.direction = direction;
match direction {
SearchDirection::Forward => {
@@ -4889,6 +4918,10 @@ fn expand_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value),
false,
);
let _readonly_commandline = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().readonly_commandline, new_value),
true,
);
let mut outputs = vec![];
let ret = exec_subshell(
@@ -5597,6 +5630,7 @@ pub fn completion_apply_to_command_line(
command_line: &wstr,
inout_cursor_pos: &mut usize,
append_only: bool,
is_unique: bool,
) -> WString {
let add_space = !flags.contains(CompleteFlags::NO_SPACE);
let do_replace_token = flags.contains(CompleteFlags::REPLACES_TOKEN);
@@ -5621,7 +5655,7 @@ pub fn completion_apply_to_command_line(
}
let mut escape_flags = EscapeFlags::empty();
if append_only || !add_space {
if append_only || !is_unique || !add_space {
escape_flags.insert(EscapeFlags::NO_QUOTED);
}
if no_tilde {
@@ -5791,7 +5825,7 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
token_range.end += cmdsub_range.start;
// Wildcard expansion and completion below check for cancellation.
terminal_protocols_disable_ifn();
terminal_protocols_disable_ifn(false);
// Check if we have a wildcard within this string; if so we first attempt to expand the
// wildcard; if that succeeds we don't then apply user completions (#8593).
@@ -5869,7 +5903,12 @@ fn try_insert(&mut self, c: Completion, tok: &wstr, token_range: Range<usize>) {
// If this is a replacement completion, check that we know how to replace it, e.g. that
// the token doesn't contain evil operators like {}.
if !c.flags.contains(CompleteFlags::REPLACES_TOKEN) || reader_can_replace(tok, c.flags) {
self.completion_insert(&c.completion, token_range.end, c.flags);
self.completion_insert(
&c.completion,
token_range.end,
c.flags,
/*is_unique=*/ true,
);
}
}
@@ -6005,7 +6044,12 @@ fn handle_completions(&mut self, token_range: Range<usize>) -> bool {
if prefix_is_partial_completion {
flags |= CompleteFlags::NO_SPACE;
}
self.completion_insert(&common_prefix, token_range.end, flags);
self.completion_insert(
&common_prefix,
token_range.end,
flags,
/*is_unique=*/ false,
);
self.cycle_command_line = self.command_line.text().to_owned();
self.cycle_cursor_pos = self.command_line.position();
}
@@ -6049,7 +6093,13 @@ fn handle_completions(&mut self, token_range: Range<usize>) -> bool {
/// \param token_end the position after the token to complete
/// \param flags A union of all flags describing the completion to insert. See the completion_t
/// struct for more information on possible values.
fn completion_insert(&mut self, val: &wstr, token_end: usize, flags: CompleteFlags) {
fn completion_insert(
&mut self,
val: &wstr,
token_end: usize,
flags: CompleteFlags,
is_unique: bool,
) {
let (elt, el) = self.active_edit_line();
// Move the cursor to the end of the token.
@@ -6065,6 +6115,7 @@ fn completion_insert(&mut self, val: &wstr, token_end: usize, flags: CompleteFla
el.text(),
&mut cursor,
/*append_only=*/ false,
is_unique,
);
self.set_buffer_maintaining_pager(&new_command_line, cursor, false);
}

View File

@@ -7,6 +7,7 @@
//! The current implementation is less smart than ncurses allows and can not for example move blocks
//! of text around to handle text insertion.
use crate::future_feature_flags::{feature_test, FeatureFlag};
use crate::pager::{PageRendering, Pager, PAGER_MIN_HEIGHT};
use std::cell::RefCell;
use std::collections::LinkedList;
@@ -931,8 +932,11 @@ fn update(
} else if left_prompt != zelf.actual_left_prompt || (zelf.scrolled && is_final_rendering) {
zelf.r#move(0, 0);
let mut start = 0;
let osc_133_prompt_start =
|zelf: &mut Screen| zelf.write_bytes(b"\x1b]133;A;special_key=1\x07");
let osc_133_prompt_start = |zelf: &mut Screen| {
if feature_test(FeatureFlag::mark_prompt) {
zelf.write_bytes(b"\x1b]133;A;special_key=1\x07");
}
};
if left_prompt_layout.line_breaks.is_empty() {
osc_133_prompt_start(&mut zelf);
}
@@ -1636,7 +1640,11 @@ fn measure_run_from(
width = next_tab_stop(width);
} else {
// Ordinary char. Add its width with care to ignore control chars which have width -1.
width += usize::try_from(fish_wcwidth_visible(input.char_at(idx))).unwrap();
if let Ok(ww) = usize::try_from(fish_wcwidth_visible(input.char_at(idx))) {
width += ww;
} else {
width = width.saturating_sub(1);
}
}
idx += 1;
}
@@ -1682,7 +1690,8 @@ fn truncate_run(
curr_width = measure_run_from(run, 0, None, cache);
idx = 0;
} else {
let char_width = usize::try_from(fish_wcwidth_visible(c)).unwrap();
// FIXME: In case of backspace, this would remove the last width.
let char_width = usize::try_from(fish_wcwidth_visible(c)).unwrap_or(0);
curr_width -= std::cmp::min(curr_width, char_width);
run.remove(idx);
}

View File

@@ -160,7 +160,8 @@ macro_rules! unique_completion_applies_as {
completions[0].flags,
cmdline,
&mut cursor,
false,
/*append_only=*/ false,
/*is_unique=*/ true,
);
assert_eq!(newcmdline, L!($applied), "apply result mismatch");
};
@@ -224,6 +225,7 @@ macro_rules! unique_completion_applies_as {
L!("mv debug debug"),
&mut cursor_pos,
true,
/*is_unique=*/ false,
);
assert_eq!(newcmdline, L!("mv debug Debug/"));

View File

@@ -1,6 +1,6 @@
use crate::env::EnvStack;
use crate::input::{EventQueuePeeker, InputMappingSet, KeyNameStyle, DEFAULT_BIND_MODE};
use crate::input_common::{CharEvent, InputData, InputEventQueuer};
use crate::input_common::{CharEvent, InputData, InputEventQueuer, KeyEvent};
use crate::key::Key;
use crate::wchar::prelude::*;
use std::rc::Rc;
@@ -52,8 +52,10 @@ fn test_input() {
);
// Push the desired binding to the queue.
for c in desired_binding {
input.input_data.queue_char(CharEvent::from_key(c));
for key in desired_binding {
input
.input_data
.queue_char(CharEvent::from_key(KeyEvent::from(key)));
}
let mut peeker = EventQueuePeeker::new(&mut input);

View File

@@ -439,5 +439,10 @@ macro_rules! validate {
0, r#"echo "$()"'"#,
0, "\n"
);
validate!(
0, r#"""#,
0, "\n",
0, r#"$()"$() ""#
);
})();
}

View File

@@ -57,6 +57,7 @@ macro_rules! validate {
&line,
&mut cursor_pos,
$append_only,
/*is_unique=*/ false,
);
assert_eq!(result, expected);
assert_eq!(cursor_pos, out_cursor_pos);

Some files were not shown because too many files have changed in this diff Show More