on master help sign in
99 files changed+3730−921
since splitsnapshots
unifiedsplit
.envrc+1 .github/workflows/build.yml+3−3 .github/workflows/cachix.yml+2−2 .github/workflows/release.yml+5−5 Cargo.lock+304−301 Cargo.toml+11 README.md+1−1 VERSION−1 book/src/configuration.md+18−1 book/src/generated/lang-support.md+8−2 book/src/generated/typable-cmd.md+1−1 book/src/guides/adding_languages.md+2 book/src/guides/indent.md+9 book/src/keymap.md+1−1 book/src/languages.md+1 book/src/themes.md+4−1 docs/releases.md+12−5 flake.lock+12−35 flake.nix+12−14 helix-core/Cargo.toml+11−10 helix-core/src/diagnostic.rs+4 helix-core/src/indent.rs+310−101 helix-core/src/syntax.rs+19−4 helix-core/src/transaction.rs+73−14 helix-core/tests/indent.rs+2−2 helix-dap/Cargo.toml+11−9 helix-event/Cargo.toml+8−7 helix-loader/Cargo.toml+11−10 helix-loader/build.rs+14−4 helix-lsp/Cargo.toml+15−14 helix-lsp/src/client.rs+8−8 helix-parsec/Cargo.toml+8−7 helix-term/Cargo.toml+25−21 helix-term/src/application.rs+95−46 helix-term/src/args.rs−1 helix-term/src/commands.rs+95−20 helix-term/src/commands/dap.rs+7−2 helix-term/src/commands/lsp.rs+12−2 helix-term/src/commands/typed.rs+4−5 helix-term/src/main.rs+7−5 helix-term/src/ui/completion.rs+17−1 helix-term/src/ui/document.rs+60−24 helix-term/src/ui/editor.rs+76−43 helix-term/src/ui/lsp.rs+3−1 helix-term/src/ui/menu.rs+34−13 helix-term/src/ui/picker.rs+7−3 helix-term/src/ui/popup.rs+27−7 helix-term/tests/test/commands.rs+46 helix-tui/Cargo.toml+13−13 helix-tui/src/buffer.rs+2−2 helix-vcs/Cargo.toml+12−11 helix-vcs/src/git.rs+4−4 helix-view/Cargo.toml+19−17 helix-view/src/document.rs+57−20 helix-view/src/editor.rs+31−11 helix-view/src/gutter.rs+1−2 helix-view/src/handlers/dap.rs+1−3 languages.toml+100−8 logo.svg+1−1 runtime/queries/_typescript/highlights.scm+3−3 runtime/queries/agda/highlights.scm+124 runtime/queries/c/indents.scm+13 runtime/queries/dbml/highlights.scm+44 runtime/queries/gleam/highlights.scm+16−5 runtime/queries/gn/highlights.scm+42 runtime/queries/gn/injections.scm+15 runtime/queries/gn/locals.scm+16 runtime/queries/janet/highlights.scm+1 runtime/queries/log/highlights.scm+10 runtime/queries/python/highlights.scm+6 runtime/queries/smali/folds.scm+12 runtime/queries/smali/highlights.scm+216 runtime/queries/smali/indents.scm+31 runtime/queries/smali/injections.scm+2 runtime/queries/smali/locals.scm+42 runtime/queries/yuck/highlights.scm+1−1 runtime/themes/README.md+8 runtime/themes/darcula.toml+6−2 runtime/themes/horizon-dark.toml+80 runtime/themes/licenses/modus_vivendi.LICENSE+5 runtime/themes/licenses/modus_vivendi_deuteranopia.LICENSE+5 runtime/themes/licenses/modus_vivendi_tinted.LICENSE+5 runtime/themes/licenses/modus_vivendi_tritanopia.LICENSE+5 runtime/themes/licenses/poimandres.LICENSE+21 runtime/themes/licenses/starlight.LICENSE+24 runtime/themes/modus_operandi.toml+194 runtime/themes/modus_operandi_deuteranopia.toml+46 runtime/themes/modus_operandi_tinted.toml+45 runtime/themes/modus_operandi_tritanopia.toml+73 runtime/themes/modus_vivendi.toml+242 runtime/themes/modus_vivendi_deuteranopia.toml+158 runtime/themes/modus_vivendi_tinted.toml+144 runtime/themes/modus_vivendi_tritanopia.toml+186 runtime/themes/noctis.toml+25−25 runtime/themes/nord.toml+9 runtime/themes/rose_pine.toml+30−28 runtime/tutor+154 rust-toolchain.toml+2−2 xtask/Cargo.toml+12−6
.envrc+1
@@ -1,5 +1,6 @@
11watch_file shell.nix
22watch_file flake.lock
3+watch_file rust-toolchain.toml
34
45# try to use flakes, if it fails use normal nix (ie. shell.nix)
56use flake || use nix
.github/workflows/build.yml+3−3
@@ -39,7 +39,7 @@ jobs:
3939 uses: actions/checkout@v4
4040
4141 - name: Install stable toolchain
42− uses: dtolnay/rust-toolchain@1.65
42+ uses: dtolnay/rust-toolchain@1.70
4343
4444 - uses: Swatinem/rust-cache@v2
4545 with:
@@ -70,7 +70,7 @@ jobs:
7070 uses: actions/checkout@v4
7171
7272 - name: Install stable toolchain
73− uses: dtolnay/rust-toolchain@1.65
73+ uses: dtolnay/rust-toolchain@1.70
7474 with:
7575 components: rustfmt, clippy
7676
@@ -97,7 +97,7 @@ jobs:
9797 uses: actions/checkout@v4
9898
9999 - name: Install stable toolchain
100− uses: dtolnay/rust-toolchain@1.65
100+ uses: dtolnay/rust-toolchain@1.70
101101
102102 - uses: Swatinem/rust-cache@v2
103103 with:
.github/workflows/cachix.yml+2−2
@@ -14,10 +14,10 @@ jobs:
1414 uses: actions/checkout@v4
1515
1616 - name: Install nix
17− uses: cachix/install-nix-action@v23
17+ uses: cachix/install-nix-action@v24
1818
1919 - name: Authenticate with Cachix
20− uses: cachix/cachix-action@v12
20+ uses: cachix/cachix-action@v13
2121 with:
2222 name: helix
2323 authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
.github/workflows/release.yml+5−5
@@ -36,7 +36,7 @@ jobs:
3636 - name: Bundle grammars
3737 run: tar cJf grammars.tar.xz -C runtime/grammars/sources .
3838
39− - uses: actions/upload-artifact@v3
39+ - uses: actions/upload-artifact@v4
4040 with:
4141 name: grammars
4242 path: grammars.tar.xz
@@ -106,7 +106,7 @@ jobs:
106106 uses: actions/checkout@v4
107107
108108 - name: Download grammars
109− uses: actions/download-artifact@v3
109+ uses: actions/download-artifact@v4
110110
111111 - name: Move grammars under runtime
112112 if: "!startsWith(matrix.os, 'windows')"
@@ -220,7 +220,7 @@ jobs:
220220 fi
221221 cp -r runtime dist
222222
223− - uses: actions/upload-artifact@v3
223+ - uses: actions/upload-artifact@v4
224224 with:
225225 name: bins-${{ matrix.build }}
226226 path: dist
@@ -233,7 +233,7 @@ jobs:
233233 - name: Checkout sources
234234 uses: actions/checkout@v4
235235
236− - uses: actions/download-artifact@v3
236+ - uses: actions/download-artifact@v4
237237
238238 - name: Build archive
239239 shell: bash
@@ -288,7 +288,7 @@ jobs:
288288 overwrite: true
289289
290290 - name: Upload binaries as artifact
291− uses: actions/upload-artifact@v3
291+ uses: actions/upload-artifact@v4
292292 if: env.preview == 'true'
293293 with:
294294 name: release
Cargo.lock+304−301
@@ -32,18 +32,9 @@ dependencies = [
3232
3333[[package]]
3434name = "aho-corasick"
35−version = "0.7.20"
35+version = "1.1.2"
3636source = "registry+https://github.com/rust-lang/crates.io-index"
37−checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
38−dependencies = [
39− "memchr",
40−]
41−
42−[[package]]
43−name = "aho-corasick"
44−version = "1.0.2"
45−source = "registry+https://github.com/rust-lang/crates.io-index"
46−checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
37+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
4738dependencies = [
4839 "memchr",
4940]
@@ -71,9 +62,9 @@ dependencies = [
7162
7263[[package]]
7364name = "anyhow"
74−version = "1.0.75"
65+version = "1.0.76"
7566source = "registry+https://github.com/rust-lang/crates.io-index"
76−checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
67+checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
7768
7869[[package]]
7970name = "arc-swap"
@@ -116,12 +107,12 @@ checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
116107
117108[[package]]
118109name = "bstr"
119−version = "1.6.0"
110+version = "1.8.0"
120111source = "registry+https://github.com/rust-lang/crates.io-index"
121−checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
112+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
122113dependencies = [
123114 "memchr",
124− "regex-automata 0.3.9",
115+ "regex-automata",
125116 "serde",
126117]
127118
@@ -140,12 +131,6 @@ version = "3.12.0"
140131source = "registry+https://github.com/rust-lang/crates.io-index"
141132checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
142133
143−[[package]]
144−name = "bytecount"
145−version = "0.6.3"
146−source = "registry+https://github.com/rust-lang/crates.io-index"
147−checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
148−
149134[[package]]
150135name = "bytes"
151136version = "1.4.0"
@@ -198,13 +183,11 @@ dependencies = [
198183
199184[[package]]
200185name = "clipboard-win"
201−version = "4.5.0"
186+version = "5.0.0"
202187source = "registry+https://github.com/rust-lang/crates.io-index"
203−checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
188+checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
204189dependencies = [
205190 "error-code",
206− "str-buf",
207− "winapi",
208191]
209192
210193[[package]]
@@ -367,27 +350,6 @@ dependencies = [
367350 "syn 2.0.38",
368351]
369352
370−[[package]]
371−name = "dirs"
372−version = "5.0.1"
373−source = "registry+https://github.com/rust-lang/crates.io-index"
374−checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
375−dependencies = [
376− "dirs-sys",
377−]
378−
379−[[package]]
380−name = "dirs-sys"
381−version = "0.4.1"
382−source = "registry+https://github.com/rust-lang/crates.io-index"
383−checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
384−dependencies = [
385− "libc",
386− "option-ext",
387− "redox_users",
388− "windows-sys 0.48.0",
389−]
390−
391353[[package]]
392354name = "dunce"
393355version = "1.0.4"
@@ -426,34 +388,19 @@ checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
426388
427389[[package]]
428390name = "errno"
429−version = "0.3.1"
391+version = "0.3.8"
430392source = "registry+https://github.com/rust-lang/crates.io-index"
431−checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
393+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
432394dependencies = [
433− "errno-dragonfly",
434− "libc",
435− "windows-sys 0.48.0",
436−]
437−
438−[[package]]
439−name = "errno-dragonfly"
440−version = "0.1.2"
441−source = "registry+https://github.com/rust-lang/crates.io-index"
442−checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
443−dependencies = [
444− "cc",
445395 "libc",
396+ "windows-sys 0.52.0",
446397]
447398
448399[[package]]
449400name = "error-code"
450−version = "2.3.1"
401+version = "3.0.0"
451402source = "registry+https://github.com/rust-lang/crates.io-index"
452−checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
453−dependencies = [
454− "libc",
455− "str-buf",
456−]
403+checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
457404
458405[[package]]
459406name = "etcetera"
@@ -468,9 +415,9 @@ dependencies = [
468415
469416[[package]]
470417name = "faster-hex"
471−version = "0.8.1"
418+version = "0.9.0"
472419source = "registry+https://github.com/rust-lang/crates.io-index"
473−checksum = "239f7bfb930f820ab16a9cd95afc26f88264cf6905c960b340a615384aa3338a"
420+checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
474421dependencies = [
475422 "serde",
476423]
@@ -511,26 +458,20 @@ dependencies = [
511458 "miniz_oxide",
512459]
513460
514−[[package]]
515−name = "fnv"
516−version = "1.0.7"
517−source = "registry+https://github.com/rust-lang/crates.io-index"
518−checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
519−
520461[[package]]
521462name = "form_urlencoded"
522−version = "1.2.0"
463+version = "1.2.1"
523464source = "registry+https://github.com/rust-lang/crates.io-index"
524−checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
465+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
525466dependencies = [
526467 "percent-encoding",
527468]
528469
529470[[package]]
530471name = "futures-core"
531−version = "0.3.29"
472+version = "0.3.30"
532473source = "registry+https://github.com/rust-lang/crates.io-index"
533−checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
474+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
534475
535476[[package]]
536477name = "futures-executor"
@@ -545,15 +486,15 @@ dependencies = [
545486
546487[[package]]
547488name = "futures-task"
548−version = "0.3.29"
489+version = "0.3.30"
549490source = "registry+https://github.com/rust-lang/crates.io-index"
550−checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
491+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
551492
552493[[package]]
553494name = "futures-util"
554−version = "0.3.29"
495+version = "0.3.30"
555496source = "registry+https://github.com/rust-lang/crates.io-index"
556−checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
497+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
557498dependencies = [
558499 "futures-core",
559500 "futures-task",
@@ -581,9 +522,9 @@ checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
581522
582523[[package]]
583524name = "gix"
584−version = "0.55.2"
525+version = "0.57.0"
585526source = "registry+https://github.com/rust-lang/crates.io-index"
586−checksum = "002667cd1ebb789313d0d0afe3d23b2821cf3b0e91605095f0e6d8751f0ceeea"
527+checksum = "721c7497ab24b665ed5a1eb2b3526936aa60068e61ba260651f7e77c52feec69"
587528dependencies = [
588529 "gix-actor",
589530 "gix-commitgraph",
@@ -622,46 +563,46 @@ dependencies = [
622563
623564[[package]]
624565name = "gix-actor"
625−version = "0.28.0"
566+version = "0.29.0"
626567source = "registry+https://github.com/rust-lang/crates.io-index"
627−checksum = "948a5f9e43559d16faf583694f1c742eb401ce24ce8e6f2238caedea7486433c"
568+checksum = "886014c4865b93ce268f1d3eddd4fd3261242c3f3ee61eb36009f913016a9059"
628569dependencies = [
629570 "bstr",
630571 "btoi",
631572 "gix-date",
632573 "itoa",
633574 "thiserror",
634− "winnow 0.5.17",
575+ "winnow 0.5.28",
635576]
636577
637578[[package]]
638579name = "gix-chunk"
639−version = "0.4.4"
580+version = "0.4.6"
640581source = "registry+https://github.com/rust-lang/crates.io-index"
641−checksum = "5b42ea64420f7994000130328f3c7a2038f639120518870436d31b8bde704493"
582+checksum = "2ade37ef69870de0ed966b97c57a3a947a22ff3482a52c3b99b205f77bcb08fb"
642583dependencies = [
643584 "thiserror",
644585]
645586
646587[[package]]
647588name = "gix-commitgraph"
648−version = "0.22.0"
589+version = "0.23.0"
649590source = "registry+https://github.com/rust-lang/crates.io-index"
650−checksum = "7e8bc78b1a6328fa6d8b3a53b6c73997af37fd6bfc1d6c49f149e63bda5cbb36"
591+checksum = "e7559ea9cefee188cd88b05afcc8e3ef7a3cb4a5c647bccf06b981e591b02b77"
651592dependencies = [
652593 "bstr",
653594 "gix-chunk",
654595 "gix-features",
655596 "gix-hash",
656− "memmap2 0.7.1",
597+ "memmap2",
657598 "thiserror",
658599]
659600
660601[[package]]
661602name = "gix-config"
662−version = "0.31.0"
603+version = "0.33.0"
663604source = "registry+https://github.com/rust-lang/crates.io-index"
664−checksum = "5cae98c6b4c66c09379bc35274b172587d6b0ac369a416c39128ad8c6454f9bb"
605+checksum = "9c35dc7f9c00a42bbc9cfa1ca2ec0a78ad1b76ff0736d3d35dfd612962244467"
665606dependencies = [
666607 "bstr",
667608 "gix-config-value",
@@ -675,14 +616,14 @@ dependencies = [
675616 "smallvec",
676617 "thiserror",
677618 "unicode-bom",
678− "winnow 0.5.17",
619+ "winnow 0.5.28",
679620]
680621
681622[[package]]
682623name = "gix-config-value"
683−version = "0.14.0"
624+version = "0.14.2"
684625source = "registry+https://github.com/rust-lang/crates.io-index"
685−checksum = "ea7505b97f4d8e7933e29735a568ba2f86d8de466669d9f0e8321384f9972f47"
626+checksum = "39f388cb2396aee82d6f460a2e7770659bdf8854e9e5478f7d2b1324a9698284"
686627dependencies = [
687628 "bitflags 2.4.1",
688629 "bstr",
@@ -693,9 +634,9 @@ dependencies = [
693634
694635[[package]]
695636name = "gix-date"
696−version = "0.8.0"
637+version = "0.8.2"
697638source = "registry+https://github.com/rust-lang/crates.io-index"
698−checksum = "fc7df669639582dc7c02737642f76890b03b5544e141caba68a7d6b4eb551e0d"
639+checksum = "9d85f4a01e0d05c3de585e28bae514d4baf01655e3fc3f14ce6f30bf62405345"
699640dependencies = [
700641 "bstr",
701642 "itoa",
@@ -705,10 +646,11 @@ dependencies = [
705646
706647[[package]]
707648name = "gix-diff"
708−version = "0.37.0"
649+version = "0.39.0"
709650source = "registry+https://github.com/rust-lang/crates.io-index"
710−checksum = "931394f69fb8c9ed6afc0aae3487bd869e936339bcc13ed8884472af072e0554"
651+checksum = "ac0e75f5afd2f6c47c800b6b0a000a08045739d0450d20482e8faa42543f62d1"
711652dependencies = [
653+ "bstr",
712654 "gix-hash",
713655 "gix-object",
714656 "thiserror",
@@ -716,9 +658,9 @@ dependencies = [
716658
717659[[package]]
718660name = "gix-discover"
719−version = "0.26.0"
661+version = "0.28.0"
720662source = "registry+https://github.com/rust-lang/crates.io-index"
721−checksum = "a45d5cf0321178883e38705ab2b098f625d609a7d4c391b33ac952eff2c490f2"
663+checksum = "f0b63ce2ad81632ac1a84ac370f85a5e580c3261580bd85ea17ff35d3a0bba18"
722664dependencies = [
723665 "bstr",
724666 "dunce",
@@ -731,9 +673,9 @@ dependencies = [
731673
732674[[package]]
733675name = "gix-features"
734−version = "0.36.0"
676+version = "0.37.0"
735677source = "registry+https://github.com/rust-lang/crates.io-index"
736−checksum = "51f4365ba17c4f218d7fd9ec102b8d2d3cb0ca200a835e81151ace7778aec827"
678+checksum = "befe7edea299a824504b5acc96d7a3a538125b38c42f3a8379f6912a29c90c81"
737679dependencies = [
738680 "crc32fast",
739681 "flate2",
@@ -749,18 +691,18 @@ dependencies = [
749691
750692[[package]]
751693name = "gix-fs"
752−version = "0.8.0"
694+version = "0.9.0"
753695source = "registry+https://github.com/rust-lang/crates.io-index"
754−checksum = "8cd171c0cae97cd0dc57e7b4601cb1ebf596450e263ef3c02be9107272c877bd"
696+checksum = "007017ce93b819ea52c0ec68306f7d72212a2d307c3d70f1548c7141c015d0a1"
755697dependencies = [
756698 "gix-features",
757699]
758700
759701[[package]]
760702name = "gix-glob"
761−version = "0.14.0"
703+version = "0.15.0"
762704source = "registry+https://github.com/rust-lang/crates.io-index"
763−checksum = "8fac08925dbc14d414bd02eb45ffb4cecd912d1fce3883f867bd0103c192d3e4"
705+checksum = "61fb116c2516d3a1170e010118f639944a6389872c875a008960cdab2a44ac72"
764706dependencies = [
765707 "bitflags 2.4.1",
766708 "bstr",
@@ -770,9 +712,9 @@ dependencies = [
770712
771713[[package]]
772714name = "gix-hash"
773−version = "0.13.1"
715+version = "0.14.0"
774716source = "registry+https://github.com/rust-lang/crates.io-index"
775−checksum = "1884c7b41ea0875217c1be9ce91322f90bde433e91d374d0e1276073a51ccc60"
717+checksum = "c52c3170a17b6031833cd2eb4ad7bc23f2755d06e6e70f78dec21d42e8fe1b30"
776718dependencies = [
777719 "faster-hex",
778720 "thiserror",
@@ -780,20 +722,20 @@ dependencies = [
780722
781723[[package]]
782724name = "gix-hashtable"
783−version = "0.4.0"
725+version = "0.5.0"
784726source = "registry+https://github.com/rust-lang/crates.io-index"
785−checksum = "409268480841ad008e81c17ca5a293393fbf9f2b6c2f85b8ab9de1f0c5176a16"
727+checksum = "40a838e6366a5e5b84668b6997ce0981b833136468e8ba949f424c0ef2927eba"
786728dependencies = [
787729 "gix-hash",
788− "hashbrown 0.14.2",
730+ "hashbrown 0.14.3",
789731 "parking_lot",
790732]
791733
792734[[package]]
793735name = "gix-lock"
794−version = "11.0.0"
736+version = "12.0.0"
795737source = "registry+https://github.com/rust-lang/crates.io-index"
796−checksum = "f4feb1dcd304fe384ddc22edba9dd56a42b0800032de6537728cea2f033a4f37"
738+checksum = "6cf112ddee94223c119a8534dad027740dc3aba3365ac5edeef8a7f6660c74db"
797739dependencies = [
798740 "gix-tempfile",
799741 "gix-utils",
@@ -802,9 +744,9 @@ dependencies = [
802744
803745[[package]]
804746name = "gix-macros"
805−version = "0.1.0"
747+version = "0.1.2"
806748source = "registry+https://github.com/rust-lang/crates.io-index"
807−checksum = "9d8acb5ee668d55f0f2d19a320a3f9ef67a6999ad483e11135abcc2464ed18b6"
749+checksum = "fc207b64189cf71bcb17fc841ab99a5d63d0845546b611e2703ce467f659323a"
808750dependencies = [
809751 "proc-macro2",
810752 "quote",
@@ -813,9 +755,9 @@ dependencies = [
813755
814756[[package]]
815757name = "gix-object"
816−version = "0.38.0"
758+version = "0.40.0"
817759source = "registry+https://github.com/rust-lang/crates.io-index"
818−checksum = "740f2a44267f58770a1cb3a3d01d14e67b089c7136c48d4bddbb3cfd2bf86a51"
760+checksum = "e4c77e47ffba92127faf632a841fce23d19547e269bd8b88e68961a70eab4e93"
819761dependencies = [
820762 "bstr",
821763 "btoi",
@@ -827,14 +769,14 @@ dependencies = [
827769 "itoa",
828770 "smallvec",
829771 "thiserror",
830− "winnow 0.5.17",
772+ "winnow 0.5.28",
831773]
832774
833775[[package]]
834776name = "gix-odb"
835−version = "0.54.0"
777+version = "0.56.0"
836778source = "registry+https://github.com/rust-lang/crates.io-index"
837−checksum = "8630b56cb80d8fa684d383dad006a66401ee8314e12fbf0e566ddad8c115143b"
779+checksum = "a3254f2005cc7553ea78e85e816a09150c6f7a64e6b7627b8d1fdc56721bea73"
838780dependencies = [
839781 "arc-swap",
840782 "gix-date",
@@ -851,9 +793,9 @@ dependencies = [
851793
852794[[package]]
853795name = "gix-pack"
854−version = "0.44.0"
796+version = "0.46.0"
855797source = "registry+https://github.com/rust-lang/crates.io-index"
856−checksum = "1431ba2e30deff1405920693d54ab231c88d7c240dd6ccc936ee223d8f8697c3"
798+checksum = "02ebc8cd657eec207d82d8f876ca402361308ecd3c87a47935b0299506257b4f"
857799dependencies = [
858800 "clru",
859801 "gix-chunk",
@@ -863,7 +805,7 @@ dependencies = [
863805 "gix-object",
864806 "gix-path",
865807 "gix-tempfile",
866− "memmap2 0.7.1",
808+ "memmap2",
867809 "parking_lot",
868810 "smallvec",
869811 "thiserror",
@@ -871,9 +813,9 @@ dependencies = [
871813
872814[[package]]
873815name = "gix-path"
874−version = "0.10.0"
816+version = "0.10.2"
875817source = "registry+https://github.com/rust-lang/crates.io-index"
876−checksum = "6a1d370115171e3ae03c5c6d4f7d096f2981a40ddccb98dfd704c773530ba73b"
818+checksum = "10931652a3da126990ac93bce1b1c600cc99d7c268d712b6360ed52174ce1b68"
877819dependencies = [
878820 "bstr",
879821 "gix-trace",
@@ -884,9 +826,9 @@ dependencies = [
884826
885827[[package]]
886828name = "gix-quote"
887−version = "0.4.7"
829+version = "0.4.9"
888830source = "registry+https://github.com/rust-lang/crates.io-index"
889−checksum = "475c86a97dd0127ba4465fbb239abac9ea10e68301470c9791a6dd5351cdc905"
831+checksum = "6c43530cb94a7759807e6f8d180e17ac9c65673b891645c6c433831dc0cf4342"
890832dependencies = [
891833 "bstr",
892834 "btoi",
@@ -895,9 +837,9 @@ dependencies = [
895837
896838[[package]]
897839name = "gix-ref"
898−version = "0.38.0"
840+version = "0.40.0"
899841source = "registry+https://github.com/rust-lang/crates.io-index"
900−checksum = "0ec2f6d07ac88d2fb8007ee3fa3e801856fb9d82e7366ec0ca332eb2c9d74a52"
842+checksum = "baa951b3b850d6d1be4f900768e49af20b76bf9505beac22af723d57249a2f1d"
901843dependencies = [
902844 "gix-actor",
903845 "gix-date",
@@ -909,16 +851,16 @@ dependencies = [
909851 "gix-path",
910852 "gix-tempfile",
911853 "gix-validate",
912− "memmap2 0.7.1",
854+ "memmap2",
913855 "thiserror",
914− "winnow 0.5.17",
856+ "winnow 0.5.28",
915857]
916858
917859[[package]]
918860name = "gix-refspec"
919−version = "0.19.0"
861+version = "0.21.0"
920862source = "registry+https://github.com/rust-lang/crates.io-index"
921−checksum = "ccb0974cc41dbdb43a180c7f67aa481e1c1e160fcfa8f4a55291fd1126c1a6e7"
863+checksum = "88c61f849d58c06e3068a0b601cf10127d2a07cdad00a725ed66cf303f76f6b3"
922864dependencies = [
923865 "bstr",
924866 "gix-hash",
@@ -930,9 +872,9 @@ dependencies = [
930872
931873[[package]]
932874name = "gix-revision"
933−version = "0.23.0"
875+version = "0.25.0"
934876source = "registry+https://github.com/rust-lang/crates.io-index"
935−checksum = "2ca97ac73459a7f3766aa4a5638a6e37d56d4c7962bc1986fbaf4883d0772588"
877+checksum = "bcbbf91f4200c5c76802ef5f057b96f5a336827508881fe55a780be71a794d22"
936878dependencies = [
937879 "bstr",
938880 "gix-date",
@@ -946,9 +888,9 @@ dependencies = [
946888
947889[[package]]
948890name = "gix-revwalk"
949−version = "0.9.0"
891+version = "0.11.0"
950892source = "registry+https://github.com/rust-lang/crates.io-index"
951−checksum = "a16d8c892e4cd676d86f0265bf9d40cefd73d8d94f86b213b8b77d50e77efae0"
893+checksum = "33ab66354d83f70f2730391747d0c75f94ef3c3dd40ebde2e206db9faaf7a0a7"
952894dependencies = [
953895 "gix-commitgraph",
954896 "gix-date",
@@ -961,21 +903,21 @@ dependencies = [
961903
962904[[package]]
963905name = "gix-sec"
964−version = "0.10.0"
906+version = "0.10.2"
965907source = "registry+https://github.com/rust-lang/crates.io-index"
966−checksum = "92b9542ac025a8c02ed5d17b3fc031a111a384e859d0be3532ec4d58c40a0f28"
908+checksum = "a9963f38a42144253ed4d571882d7db5ef644c12e6726e4b75135597cc9d0e1a"
967909dependencies = [
968910 "bitflags 2.4.1",
969911 "gix-path",
970912 "libc",
971− "windows",
913+ "windows 0.52.0",
972914]
973915
974916[[package]]
975917name = "gix-tempfile"
976−version = "11.0.0"
918+version = "12.0.0"
977919source = "registry+https://github.com/rust-lang/crates.io-index"
978−checksum = "05cc2205cf10d99f70b96e04e16c55d4c7cf33efc151df1f793e29fd12a931f8"
920+checksum = "e76a494bd530e1a1309188ff971825a24f159c76c2db0bf71fa5dfb469a2c915"
979921dependencies = [
980922 "gix-fs",
981923 "libc",
@@ -986,15 +928,15 @@ dependencies = [
986928
987929[[package]]
988930name = "gix-trace"
989−version = "0.1.3"
931+version = "0.1.5"
990932source = "registry+https://github.com/rust-lang/crates.io-index"
991−checksum = "96b6d623a1152c3facb79067d6e2ecdae48130030cf27d6eb21109f13bd7b836"
933+checksum = "bda62acb44dd86a40c7c3762a5403cfc1ac789ea559df54085cedf79864f809e"
992934
993935[[package]]
994936name = "gix-traverse"
995−version = "0.34.0"
937+version = "0.36.0"
996938source = "registry+https://github.com/rust-lang/crates.io-index"
997−checksum = "14d050ec7d4e1bb76abf0636cf4104fb915b70e54e3ced9a4427c999100ff38a"
939+checksum = "8661fab39985c9214e56d81a63ceb5886ac948cec2fba76c39494d1e0e307ea8"
998940dependencies = [
999941 "gix-commitgraph",
1000942 "gix-date",
@@ -1008,9 +950,9 @@ dependencies = [
1008950
1009951[[package]]
1010952name = "gix-url"
1011−version = "0.25.1"
953+version = "0.26.0"
1012954source = "registry+https://github.com/rust-lang/crates.io-index"
1013−checksum = "b1b9ac8ed32ad45f9fc6c5f8c0be2ed911e544a5a19afd62d95d524ebaa95671"
955+checksum = "10a0129c1e8b52736d7c5128300a4485dbc85863001371e2771ac1754bd89fd7"
1014956dependencies = [
1015957 "bstr",
1016958 "gix-features",
@@ -1022,18 +964,18 @@ dependencies = [
1022964
1023965[[package]]
1024966name = "gix-utils"
1025−version = "0.1.5"
967+version = "0.1.7"
1026968source = "registry+https://github.com/rust-lang/crates.io-index"
1027−checksum = "b85d89dc728613e26e0ed952a19583744e7f5240fcd4aa30d6c824ffd8b52f0f"
969+checksum = "ab9277a5e32e85d53f738096d872a4a9b76067ad471894ad31bd99c8fa2da1dc"
1028970dependencies = [
1029971 "fastrand",
1030972]
1031973
1032974[[package]]
1033975name = "gix-validate"
1034−version = "0.8.0"
976+version = "0.8.2"
1035977source = "registry+https://github.com/rust-lang/crates.io-index"
1036−checksum = "e05cab2b03a45b866156e052aa38619f4ece4adcb2f79978bfc249bc3b21b8c5"
978+checksum = "f805ebbdbaa4bfd98e2ee43e6d14099b8a4d9141f5d7b8202fea4d48e44263e7"
1037979dependencies = [
1038980 "bstr",
1039981 "thiserror",
@@ -1041,54 +983,52 @@ dependencies = [
1041983
1042984[[package]]
1043985name = "globset"
1044−version = "0.4.13"
986+version = "0.4.14"
1045987source = "registry+https://github.com/rust-lang/crates.io-index"
1046−checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
988+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
1047989dependencies = [
1048− "aho-corasick 1.0.2",
990+ "aho-corasick",
1049991 "bstr",
1050− "fnv",
1051992 "log",
1052− "regex",
993+ "regex-automata",
994+ "regex-syntax",
1053995]
1054996
1055997[[package]]
1056998name = "grep-matcher"
1057−version = "0.1.6"
999+version = "0.1.7"
10581000source = "registry+https://github.com/rust-lang/crates.io-index"
1059−checksum = "3902ca28f26945fe35cad349d776f163981d777fee382ccd6ef451126f51b319"
1001+checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
10601002dependencies = [
10611003 "memchr",
10621004]
10631005
10641006[[package]]
10651007name = "grep-regex"
1066−version = "0.1.11"
1008+version = "0.1.12"
10671009source = "registry+https://github.com/rust-lang/crates.io-index"
1068−checksum = "997598b41d53a37a2e3fc5300d5c11d825368c054420a9c65125b8fe1078463f"
1010+checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d"
10691011dependencies = [
1070− "aho-corasick 0.7.20",
10711012 "bstr",
10721013 "grep-matcher",
10731014 "log",
1074− "regex",
1075− "regex-syntax 0.6.29",
1076− "thread_local",
1015+ "regex-automata",
1016+ "regex-syntax",
10771017]
10781018
10791019[[package]]
10801020name = "grep-searcher"
1081−version = "0.1.11"
1021+version = "0.1.13"
10821022source = "registry+https://github.com/rust-lang/crates.io-index"
1083−checksum = "5601c4b9f480f0c9ebb40b1f6cbf447b8a50c5369223937a6c5214368c58779f"
1023+checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
10841024dependencies = [
10851025 "bstr",
1086− "bytecount",
10871026 "encoding_rs",
10881027 "encoding_rs_io",
10891028 "grep-matcher",
10901029 "log",
1091− "memmap2 0.5.10",
1030+ "memchr",
1031+ "memmap2",
10921032]
10931033
10941034[[package]]
@@ -1099,9 +1039,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
10991039
11001040[[package]]
11011041name = "hashbrown"
1102−version = "0.14.2"
1042+version = "0.14.3"
11031043source = "registry+https://github.com/rust-lang/crates.io-index"
1104−checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
1044+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
11051045dependencies = [
11061046 "ahash",
11071047 "allocator-api2",
@@ -1109,7 +1049,7 @@ dependencies = [
11091049
11101050[[package]]
11111051name = "helix-core"
1112−version = "0.6.0"
1052+version = "23.10.0"
11131053dependencies = [
11141054 "ahash",
11151055 "arc-swap",
@@ -1118,7 +1058,7 @@ dependencies = [
11181058 "dunce",
11191059 "encoding_rs",
11201060 "etcetera",
1121− "hashbrown 0.14.2",
1061+ "hashbrown 0.14.3",
11221062 "helix-loader",
11231063 "imara-diff",
11241064 "indoc",
@@ -1144,7 +1084,7 @@ dependencies = [
11441084
11451085[[package]]
11461086name = "helix-dap"
1147−version = "0.6.0"
1087+version = "23.10.0"
11481088dependencies = [
11491089 "anyhow",
11501090 "fern",
@@ -1159,7 +1099,7 @@ dependencies = [
11591099
11601100[[package]]
11611101name = "helix-event"
1162−version = "0.6.0"
1102+version = "23.10.0"
11631103dependencies = [
11641104 "parking_lot",
11651105 "tokio",
@@ -1167,7 +1107,7 @@ dependencies = [
11671107
11681108[[package]]
11691109name = "helix-loader"
1170−version = "0.6.0"
1110+version = "23.10.0"
11711111dependencies = [
11721112 "anyhow",
11731113 "cc",
@@ -1186,7 +1126,7 @@ dependencies = [
11861126
11871127[[package]]
11881128name = "helix-lsp"
1189−version = "0.6.0"
1129+version = "23.10.0"
11901130dependencies = [
11911131 "anyhow",
11921132 "futures-executor",
@@ -1208,11 +1148,11 @@ dependencies = [
12081148
12091149[[package]]
12101150name = "helix-parsec"
1211−version = "0.6.0"
1151+version = "23.10.0"
12121152
12131153[[package]]
12141154name = "helix-term"
1215−version = "0.6.0"
1155+version = "23.10.0"
12161156dependencies = [
12171157 "anyhow",
12181158 "arc-swap",
@@ -1237,6 +1177,7 @@ dependencies = [
12371177 "log",
12381178 "nucleo",
12391179 "once_cell",
1180+ "open",
12401181 "pulldown-cmark",
12411182 "serde",
12421183 "serde_json",
@@ -1247,12 +1188,13 @@ dependencies = [
12471188 "tokio",
12481189 "tokio-stream",
12491190 "toml",
1191+ "url",
12501192 "which",
12511193]
12521194
12531195[[package]]
12541196name = "helix-tui"
1255−version = "0.6.0"
1197+version = "23.10.0"
12561198dependencies = [
12571199 "bitflags 2.4.1",
12581200 "cassowary",
@@ -1268,7 +1210,7 @@ dependencies = [
12681210
12691211[[package]]
12701212name = "helix-vcs"
1271−version = "0.6.0"
1213+version = "23.10.0"
12721214dependencies = [
12731215 "anyhow",
12741216 "arc-swap",
@@ -1284,7 +1226,7 @@ dependencies = [
12841226
12851227[[package]]
12861228name = "helix-view"
1287−version = "0.6.0"
1229+version = "23.10.0"
12881230dependencies = [
12891231 "anyhow",
12901232 "arc-swap",
@@ -1326,11 +1268,11 @@ dependencies = [
13261268
13271269[[package]]
13281270name = "home"
1329−version = "0.5.4"
1271+version = "0.5.5"
13301272source = "registry+https://github.com/rust-lang/crates.io-index"
1331−checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
1273+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
13321274dependencies = [
1333− "winapi",
1275+ "windows-sys 0.48.0",
13341276]
13351277
13361278[[package]]
@@ -1344,7 +1286,7 @@ dependencies = [
13441286 "iana-time-zone-haiku",
13451287 "js-sys",
13461288 "wasm-bindgen",
1347− "windows",
1289+ "windows 0.48.0",
13481290]
13491291
13501292[[package]]
@@ -1359,9 +1301,9 @@ dependencies = [
13591301
13601302[[package]]
13611303name = "idna"
1362−version = "0.4.0"
1304+version = "0.5.0"
13631305source = "registry+https://github.com/rust-lang/crates.io-index"
1364−checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
1306+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
13651307dependencies = [
13661308 "unicode-bidi",
13671309 "unicode-normalization",
@@ -1369,17 +1311,16 @@ dependencies = [
13691311
13701312[[package]]
13711313name = "ignore"
1372−version = "0.4.20"
1314+version = "0.4.21"
13731315source = "registry+https://github.com/rust-lang/crates.io-index"
1374−checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
1316+checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
13751317dependencies = [
1318+ "crossbeam-deque",
13761319 "globset",
1377− "lazy_static",
13781320 "log",
13791321 "memchr",
1380− "regex",
1322+ "regex-automata",
13811323 "same-file",
1382− "thread_local",
13831324 "walkdir",
13841325 "winapi-util",
13851326]
@@ -1401,7 +1342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
14011342checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
14021343dependencies = [
14031344 "equivalent",
1404− "hashbrown 0.14.2",
1345+ "hashbrown 0.14.3",
14051346]
14061347
14071348[[package]]
@@ -1410,6 +1351,25 @@ version = "2.0.4"
14101351source = "registry+https://github.com/rust-lang/crates.io-index"
14111352checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
14121353
1354+[[package]]
1355+name = "is-docker"
1356+version = "0.2.0"
1357+source = "registry+https://github.com/rust-lang/crates.io-index"
1358+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
1359+dependencies = [
1360+ "once_cell",
1361+]
1362+
1363+[[package]]
1364+name = "is-wsl"
1365+version = "0.4.0"
1366+source = "registry+https://github.com/rust-lang/crates.io-index"
1367+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
1368+dependencies = [
1369+ "is-docker",
1370+ "once_cell",
1371+]
1372+
14131373[[package]]
14141374name = "itoa"
14151375version = "1.0.6"
@@ -1425,17 +1385,11 @@ dependencies = [
14251385 "wasm-bindgen",
14261386]
14271387
1428−[[package]]
1429−name = "lazy_static"
1430−version = "1.4.0"
1431−source = "registry+https://github.com/rust-lang/crates.io-index"
1432−checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
1433−
14341388[[package]]
14351389name = "libc"
1436−version = "0.2.150"
1390+version = "0.2.151"
14371391source = "registry+https://github.com/rust-lang/crates.io-index"
1438−checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
1392+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
14391393
14401394[[package]]
14411395name = "libloading"
@@ -1480,9 +1434,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
14801434
14811435[[package]]
14821436name = "lsp-types"
1483−version = "0.94.1"
1437+version = "0.95.0"
14841438source = "registry+https://github.com/rust-lang/crates.io-index"
1485−checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1"
1439+checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
14861440dependencies = [
14871441 "bitflags 1.3.2",
14881442 "serde",
@@ -1499,18 +1453,9 @@ checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
14991453
15001454[[package]]
15011455name = "memmap2"
1502−version = "0.5.10"
1503−source = "registry+https://github.com/rust-lang/crates.io-index"
1504−checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
1505−dependencies = [
1506− "libc",
1507−]
1508−
1509−[[package]]
1510−name = "memmap2"
1511−version = "0.7.1"
1456+version = "0.9.0"
15121457source = "registry+https://github.com/rust-lang/crates.io-index"
1513−checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
1458+checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
15141459dependencies = [
15151460 "libc",
15161461]
@@ -1606,15 +1551,20 @@ dependencies = [
16061551
16071552[[package]]
16081553name = "once_cell"
1609−version = "1.18.0"
1554+version = "1.19.0"
16101555source = "registry+https://github.com/rust-lang/crates.io-index"
1611−checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
1556+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
16121557
16131558[[package]]
1614−name = "option-ext"
1615−version = "0.2.0"
1559+name = "open"
1560+version = "5.0.1"
16161561source = "registry+https://github.com/rust-lang/crates.io-index"
1617−checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
1562+checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
1563+dependencies = [
1564+ "is-wsl",
1565+ "libc",
1566+ "pathdiff",
1567+]
16181568
16191569[[package]]
16201570name = "parking_lot"
@@ -1639,11 +1589,17 @@ dependencies = [
16391589 "windows-sys 0.45.0",
16401590]
16411591
1592+[[package]]
1593+name = "pathdiff"
1594+version = "0.2.1"
1595+source = "registry+https://github.com/rust-lang/crates.io-index"
1596+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
1597+
16421598[[package]]
16431599name = "percent-encoding"
1644−version = "2.3.0"
1600+version = "2.3.1"
16451601source = "registry+https://github.com/rust-lang/crates.io-index"
1646−checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
1602+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
16471603
16481604[[package]]
16491605name = "pin-project-lite"
@@ -1668,9 +1624,9 @@ dependencies = [
16681624
16691625[[package]]
16701626name = "prodash"
1671−version = "26.2.2"
1627+version = "28.0.0"
16721628source = "registry+https://github.com/rust-lang/crates.io-index"
1673−checksum = "794b5bf8e2d19b53dcdcec3e4bba628e20f5b6062503ba89281fa7037dd7bbcf"
1629+checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
16741630
16751631[[package]]
16761632name = "pulldown-cmark"
@@ -1759,52 +1715,29 @@ dependencies = [
17591715 "bitflags 1.3.2",
17601716]
17611717
1762−[[package]]
1763−name = "redox_users"
1764−version = "0.4.3"
1765−source = "registry+https://github.com/rust-lang/crates.io-index"
1766−checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
1767−dependencies = [
1768− "getrandom",
1769− "redox_syscall 0.2.16",
1770− "thiserror",
1771−]
1772−
17731718[[package]]
17741719name = "regex"
17751720version = "1.10.2"
17761721source = "registry+https://github.com/rust-lang/crates.io-index"
17771722checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
17781723dependencies = [
1779− "aho-corasick 1.0.2",
1724+ "aho-corasick",
17801725 "memchr",
1781− "regex-automata 0.4.3",
1782− "regex-syntax 0.8.2",
1726+ "regex-automata",
1727+ "regex-syntax",
17831728]
17841729
1785−[[package]]
1786−name = "regex-automata"
1787−version = "0.3.9"
1788−source = "registry+https://github.com/rust-lang/crates.io-index"
1789−checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
1790−
17911730[[package]]
17921731name = "regex-automata"
17931732version = "0.4.3"
17941733source = "registry+https://github.com/rust-lang/crates.io-index"
17951734checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
17961735dependencies = [
1797− "aho-corasick 1.0.2",
1736+ "aho-corasick",
17981737 "memchr",
1799− "regex-syntax 0.8.2",
1738+ "regex-syntax",
18001739]
18011740
1802−[[package]]
1803−name = "regex-syntax"
1804−version = "0.6.29"
1805−source = "registry+https://github.com/rust-lang/crates.io-index"
1806−checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
1807−
18081741[[package]]
18091742name = "regex-syntax"
18101743version = "0.8.2"
@@ -1829,15 +1762,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
18291762
18301763[[package]]
18311764name = "rustix"
1832−version = "0.38.22"
1765+version = "0.38.28"
18331766source = "registry+https://github.com/rust-lang/crates.io-index"
1834−checksum = "80109a168d9bc0c7f483083244543a6eb0dba02295d33ca268145e6190d6df0c"
1767+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
18351768dependencies = [
18361769 "bitflags 2.4.1",
18371770 "errno",
18381771 "libc",
18391772 "linux-raw-sys",
1840− "windows-sys 0.48.0",
1773+ "windows-sys 0.52.0",
18411774]
18421775
18431776[[package]]
@@ -1869,18 +1802,18 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
18691802
18701803[[package]]
18711804name = "serde"
1872−version = "1.0.192"
1805+version = "1.0.193"
18731806source = "registry+https://github.com/rust-lang/crates.io-index"
1874−checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
1807+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
18751808dependencies = [
18761809 "serde_derive",
18771810]
18781811
18791812[[package]]
18801813name = "serde_derive"
1881−version = "1.0.192"
1814+version = "1.0.193"
18821815source = "registry+https://github.com/rust-lang/crates.io-index"
1883−checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
1816+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
18841817dependencies = [
18851818 "proc-macro2",
18861819 "quote",
@@ -1977,9 +1910,9 @@ dependencies = [
19771910
19781911[[package]]
19791912name = "slotmap"
1980−version = "1.0.6"
1913+version = "1.0.7"
19811914source = "registry+https://github.com/rust-lang/crates.io-index"
1982−checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
1915+checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
19831916dependencies = [
19841917 "version_check",
19851918]
@@ -2023,12 +1956,6 @@ version = "1.1.0"
20231956source = "registry+https://github.com/rust-lang/crates.io-index"
20241957checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
20251958
2026−[[package]]
2027−name = "str-buf"
2028−version = "1.0.6"
2029−source = "registry+https://github.com/rust-lang/crates.io-index"
2030−checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
2031−
20321959[[package]]
20331960name = "str_indices"
20341961version = "0.4.1"
@@ -2101,34 +2028,24 @@ dependencies = [
21012028
21022029[[package]]
21032030name = "thiserror"
2104−version = "1.0.50"
2031+version = "1.0.52"
21052032source = "registry+https://github.com/rust-lang/crates.io-index"
2106−checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
2033+checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
21072034dependencies = [
21082035 "thiserror-impl",
21092036]
21102037
21112038[[package]]
21122039name = "thiserror-impl"
2113−version = "1.0.50"
2040+version = "1.0.52"
21142041source = "registry+https://github.com/rust-lang/crates.io-index"
2115−checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
2042+checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
21162043dependencies = [
21172044 "proc-macro2",
21182045 "quote",
21192046 "syn 2.0.38",
21202047]
21212048
2122−[[package]]
2123−name = "thread_local"
2124−version = "1.1.7"
2125−source = "registry+https://github.com/rust-lang/crates.io-index"
2126−checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
2127−dependencies = [
2128− "cfg-if",
2129− "once_cell",
2130−]
2131−
21322049[[package]]
21332050name = "threadpool"
21342051version = "1.8.1"
@@ -2184,9 +2101,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
21842101
21852102[[package]]
21862103name = "tokio"
2187−version = "1.34.0"
2104+version = "1.35.1"
21882105source = "registry+https://github.com/rust-lang/crates.io-index"
2189−checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
2106+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
21902107dependencies = [
21912108 "backtrace",
21922109 "bytes",
@@ -2328,9 +2245,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
23282245
23292246[[package]]
23302247name = "url"
2331−version = "2.4.1"
2248+version = "2.5.0"
23322249source = "registry+https://github.com/rust-lang/crates.io-index"
2333−checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
2250+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
23342251dependencies = [
23352252 "form_urlencoded",
23362253 "idna",
@@ -2346,9 +2263,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
23462263
23472264[[package]]
23482265name = "walkdir"
2349−version = "2.3.3"
2266+version = "2.4.0"
23502267source = "registry+https://github.com/rust-lang/crates.io-index"
2351−checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
2268+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
23522269dependencies = [
23532270 "same-file",
23542271 "winapi-util",
@@ -2416,14 +2333,15 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
24162333
24172334[[package]]
24182335name = "which"
2419−version = "4.4.1"
2336+version = "5.0.0"
24202337source = "registry+https://github.com/rust-lang/crates.io-index"
2421−checksum = "8ad25fe5717e59ada8ea33511bbbf7420b11031730a24c65e82428766c307006"
2338+checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
24222339dependencies = [
2423− "dirs",
24242340 "either",
2341+ "home",
24252342 "once_cell",
24262343 "rustix",
2344+ "windows-sys 0.48.0",
24272345]
24282346
24292347[[package]]
@@ -2466,6 +2384,25 @@ dependencies = [
24662384 "windows-targets 0.48.0",
24672385]
24682386
2387+[[package]]
2388+name = "windows"
2389+version = "0.52.0"
2390+source = "registry+https://github.com/rust-lang/crates.io-index"
2391+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
2392+dependencies = [
2393+ "windows-core",
2394+ "windows-targets 0.52.0",
2395+]
2396+
2397+[[package]]
2398+name = "windows-core"
2399+version = "0.52.0"
2400+source = "registry+https://github.com/rust-lang/crates.io-index"
2401+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
2402+dependencies = [
2403+ "windows-targets 0.52.0",
2404+]
2405+
24692406[[package]]
24702407name = "windows-sys"
24712408version = "0.45.0"
@@ -2484,6 +2421,15 @@ dependencies = [
24842421 "windows-targets 0.48.0",
24852422]
24862423
2424+[[package]]
2425+name = "windows-sys"
2426+version = "0.52.0"
2427+source = "registry+https://github.com/rust-lang/crates.io-index"
2428+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2429+dependencies = [
2430+ "windows-targets 0.52.0",
2431+]
2432+
24872433[[package]]
24882434name = "windows-targets"
24892435version = "0.42.2"
@@ -2514,6 +2460,21 @@ dependencies = [
25142460 "windows_x86_64_msvc 0.48.0",
25152461]
25162462
2463+[[package]]
2464+name = "windows-targets"
2465+version = "0.52.0"
2466+source = "registry+https://github.com/rust-lang/crates.io-index"
2467+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
2468+dependencies = [
2469+ "windows_aarch64_gnullvm 0.52.0",
2470+ "windows_aarch64_msvc 0.52.0",
2471+ "windows_i686_gnu 0.52.0",
2472+ "windows_i686_msvc 0.52.0",
2473+ "windows_x86_64_gnu 0.52.0",
2474+ "windows_x86_64_gnullvm 0.52.0",
2475+ "windows_x86_64_msvc 0.52.0",
2476+]
2477+
25172478[[package]]
25182479name = "windows_aarch64_gnullvm"
25192480version = "0.42.2"
@@ -2526,6 +2487,12 @@ version = "0.48.0"
25262487source = "registry+https://github.com/rust-lang/crates.io-index"
25272488checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
25282489
2490+[[package]]
2491+name = "windows_aarch64_gnullvm"
2492+version = "0.52.0"
2493+source = "registry+https://github.com/rust-lang/crates.io-index"
2494+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
2495+
25292496[[package]]
25302497name = "windows_aarch64_msvc"
25312498version = "0.42.2"
@@ -2538,6 +2505,12 @@ version = "0.48.0"
25382505source = "registry+https://github.com/rust-lang/crates.io-index"
25392506checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
25402507
2508+[[package]]
2509+name = "windows_aarch64_msvc"
2510+version = "0.52.0"
2511+source = "registry+https://github.com/rust-lang/crates.io-index"
2512+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
2513+
25412514[[package]]
25422515name = "windows_i686_gnu"
25432516version = "0.42.2"
@@ -2550,6 +2523,12 @@ version = "0.48.0"
25502523source = "registry+https://github.com/rust-lang/crates.io-index"
25512524checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
25522525
2526+[[package]]
2527+name = "windows_i686_gnu"
2528+version = "0.52.0"
2529+source = "registry+https://github.com/rust-lang/crates.io-index"
2530+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
2531+
25532532[[package]]
25542533name = "windows_i686_msvc"
25552534version = "0.42.2"
@@ -2562,6 +2541,12 @@ version = "0.48.0"
25622541source = "registry+https://github.com/rust-lang/crates.io-index"
25632542checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
25642543
2544+[[package]]
2545+name = "windows_i686_msvc"
2546+version = "0.52.0"
2547+source = "registry+https://github.com/rust-lang/crates.io-index"
2548+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
2549+
25652550[[package]]
25662551name = "windows_x86_64_gnu"
25672552version = "0.42.2"
@@ -2574,6 +2559,12 @@ version = "0.48.0"
25742559source = "registry+https://github.com/rust-lang/crates.io-index"
25752560checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
25762561
2562+[[package]]
2563+name = "windows_x86_64_gnu"
2564+version = "0.52.0"
2565+source = "registry+https://github.com/rust-lang/crates.io-index"
2566+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
2567+
25772568[[package]]
25782569name = "windows_x86_64_gnullvm"
25792570version = "0.42.2"
@@ -2586,6 +2577,12 @@ version = "0.48.0"
25862577source = "registry+https://github.com/rust-lang/crates.io-index"
25872578checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
25882579
2580+[[package]]
2581+name = "windows_x86_64_gnullvm"
2582+version = "0.52.0"
2583+source = "registry+https://github.com/rust-lang/crates.io-index"
2584+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
2585+
25892586[[package]]
25902587name = "windows_x86_64_msvc"
25912588version = "0.42.2"
@@ -2598,6 +2595,12 @@ version = "0.48.0"
25982595source = "registry+https://github.com/rust-lang/crates.io-index"
25992596checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
26002597
2598+[[package]]
2599+name = "windows_x86_64_msvc"
2600+version = "0.52.0"
2601+source = "registry+https://github.com/rust-lang/crates.io-index"
2602+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
2603+
26012604[[package]]
26022605name = "winnow"
26032606version = "0.4.6"
@@ -2609,16 +2612,16 @@ dependencies = [
26092612
26102613[[package]]
26112614name = "winnow"
2612−version = "0.5.17"
2615+version = "0.5.28"
26132616source = "registry+https://github.com/rust-lang/crates.io-index"
2614−checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c"
2617+checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
26152618dependencies = [
26162619 "memchr",
26172620]
26182621
26192622[[package]]
26202623name = "xtask"
2621−version = "0.6.0"
2624+version = "23.10.0"
26222625dependencies = [
26232626 "helix-core",
26242627 "helix-loader",
@@ -2629,18 +2632,18 @@ dependencies = [
26292632
26302633[[package]]
26312634name = "zerocopy"
2632−version = "0.7.20"
2635+version = "0.7.31"
26332636source = "registry+https://github.com/rust-lang/crates.io-index"
2634−checksum = "dd66a62464e3ffd4e37bd09950c2b9dd6c4f8767380fabba0d523f9a775bc85a"
2637+checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d"
26352638dependencies = [
26362639 "zerocopy-derive",
26372640]
26382641
26392642[[package]]
26402643name = "zerocopy-derive"
2641−version = "0.7.20"
2644+version = "0.7.31"
26422645source = "registry+https://github.com/rust-lang/crates.io-index"
2643−checksum = "255c4596d41e6916ced49cfafea18727b24d67878fa180ddfd69b9df34fd1726"
2646+checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
26442647dependencies = [
26452648 "proc-macro2",
26462649 "quote",
Cargo.toml+11
@@ -1,4 +1,5 @@
11[workspace]
2+resolver = "2"
23members = [
34 "helix-core",
45 "helix-view",
@@ -37,3 +38,13 @@ package.helix-term.opt-level = 2
3738[workspace.dependencies]
3839tree-sitter = { version = "0.20", git = "https://github.com/tree-sitter/tree-sitter", rev = "ab09ae20d640711174b8da8a654f6b3dec93da1a" }
3940nucleo = "0.2.0"
41+
42+[workspace.package]
43+version = "23.10.0"
44+edition = "2021"
45+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
46+categories = ["editor"]
47+repository = "https://github.com/helix-editor/helix"
48+homepage = "https://helix-editor.com"
49+license = "MPL-2.0"
50+rust-version = "1.70"
README.md+1−1
@@ -18,7 +18,7 @@
1818
1919![Screenshot](./screenshot.png)
2020
21−A Kakoune / Neovim inspired editor, written in Rust.
21+A [Kakoune](https://github.com/mawww/kakoune) / [Neovim](https://github.com/neovim/neovim) inspired editor, written in Rust.
2222
2323The editing model is very heavily based on Kakoune; during development I found
2424myself agreeing with most of Kakoune's design decisions.
VERSIONdeleted−1
@@ -1 +0,0 @@
1−23.10
book/src/configuration.md+18−1
@@ -65,6 +65,8 @@ Its settings will be merged with the configuration directory `config.toml` and t
6565| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
6666| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
6767| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
68+| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` |
69+| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`
6870
6971### `[editor.statusline]` Section
7072
@@ -172,10 +174,25 @@ All git related options are only enabled in a git repository.
172174|`parents` | Enables reading ignore files from parent directories | `true`
173175|`ignore` | Enables reading `.ignore` files | `true`
174176|`git-ignore` | Enables reading `.gitignore` files | `true`
175−|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludefile` option | `true`
177+|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludesfile` option | `true`
176178|`git-exclude` | Enables reading `.git/info/exclude` files | `true`
177179|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`.
178180
181+Ignore files can be placed locally as `.ignore` or put in your home directory as `~/.ignore`. They support the usual ignore and negative ignore (unignore) rules used in `.gitignore` files.
182+
183+Additionally, you can use Helix-specific ignore files by creating a local `.helix/ignore` file in the current workspace or a global `ignore` file located in your Helix config directory:
184+- Linux and Mac: `~/.config/helix/ignore`
185+- Windows: `%AppData%\helix\ignore`
186+
187+Example:
188+
189+```ini
190+# unignore in file picker and global search
191+!.github/
192+!.gitignore
193+!.gitattributes
194+```
195+
179196### `[editor.auto-pairs]` Section
180197
181198Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
book/src/generated/lang-support.md+8−2
@@ -1,5 +1,6 @@
11| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
22| --- | --- | --- | --- | --- |
3+| agda | ✓ | | | |
34| astro | ✓ | | | |
45| awk | ✓ | ✓ | | `awk-language-server` |
56| bash | ✓ | ✓ | ✓ | `bash-language-server` |
@@ -10,7 +11,7 @@
1011| blueprint | ✓ | | | `blueprint-compiler` |
1112| c | ✓ | ✓ | ✓ | `clangd` |
1213| c-sharp | ✓ | ✓ | | `OmniSharp` |
13−| cabal | | | | |
14+| cabal | | | | `haskell-language-server-wrapper` |
1415| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
1516| capnp | ✓ | | ✓ | |
1617| clojure | ✓ | | | `clojure-lsp` |
@@ -24,6 +25,7 @@
2425| cue | ✓ | | | `cuelsp` |
2526| d | ✓ | ✓ | ✓ | `serve-d` |
2627| dart | ✓ | | ✓ | `dart` |
28+| dbml | ✓ | | | |
2729| devicetree | ✓ | | | |
2830| dhall | ✓ | ✓ | | `dhall-lsp-server` |
2931| diff | ✓ | | | |
@@ -54,6 +56,7 @@
5456| git-rebase | ✓ | | | |
5557| gleam | ✓ | ✓ | | `gleam` |
5658| glsl | ✓ | ✓ | ✓ | |
59+| gn | ✓ | | | |
5760| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
5861| godot-resource | ✓ | | | |
5962| gomod | ✓ | | | `gopls` |
@@ -71,6 +74,7 @@
7174| idris | | | | `idris2-lsp` |
7275| iex | ✓ | | | |
7376| ini | ✓ | | | |
77+| janet | ✓ | | | |
7478| java | ✓ | ✓ | ✓ | `jdtls` |
7579| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
7680| jinja | ✓ | | | |
@@ -89,6 +93,7 @@
8993| llvm | ✓ | ✓ | ✓ | |
9094| llvm-mir | ✓ | ✓ | ✓ | |
9195| llvm-mir-yaml | ✓ | | ✓ | |
96+| log | ✓ | | | |
9297| lpf | ✓ | | | |
9398| lua | ✓ | ✓ | ✓ | `lua-language-server` |
9499| make | ✓ | | | |
@@ -104,7 +109,7 @@
104109| nickel | ✓ | | ✓ | `nls` |
105110| nim | ✓ | ✓ | ✓ | `nimlangserver` |
106111| nix | ✓ | | | `nil` |
107−| nu | ✓ | | | |
112+| nu | ✓ | | | `nu` |
108113| nunjucks | ✓ | | | |
109114| ocaml | ✓ | | ✓ | `ocamllsp` |
110115| ocaml-interface | ✓ | | | `ocamllsp` |
@@ -143,6 +148,7 @@
143148| scheme | ✓ | | ✓ | |
144149| scss | ✓ | | | `vscode-css-language-server` |
145150| slint | ✓ | | ✓ | `slint-lsp` |
151+| smali | ✓ | | ✓ | |
146152| smithy | ✓ | | | `cs` |
147153| sml | ✓ | | | |
148154| solidity | ✓ | | | `solc` |
book/src/generated/typable-cmd.md+1−1
@@ -17,7 +17,7 @@
1717| `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) |
1818| `:new`, `:n` | Create a new scratch buffer. |
1919| `:format`, `:fmt` | Format the file using the LSP formatter. |
20−| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
20+| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) |
2121| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
2222| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
2323| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
book/src/guides/adding_languages.md+2
@@ -36,6 +36,7 @@ below.
36363. Refer to the
3737 [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
3838 for more information on writing queries.
39+4. A list of highlight captures can be found [on the themes page](https://docs.helix-editor.com/themes.html#scopes).
3940
4041> 💡 In Helix, the first matching query takes precedence when evaluating
4142> queries, which is different from other editors such as Neovim where the last
@@ -51,3 +52,4 @@ below.
5152 grammars.
5253- If a parser is causing a segfault, or you want to remove it, make sure to
5354 remove the compiled parser located at `runtime/grammars/<name>.so`.
55+- If you are attempting to add queries and Helix is unable to locate them, ensure that the environment variable `HELIX_RUNTIME` is set to the location of the `runtime` folder you're developing in.
book/src/guides/indent.md+9
@@ -12,6 +12,15 @@ Note that it matters where these added indents begin. For example,
1212multiple indent level increases that start on the same line only increase
1313the total indent level by 1. See [Capture types](#capture-types).
1414
15+By default, Helix uses the `hybrid` indentation heuristic. This means that
16+indent queries are not used to compute the expected absolute indentation of a
17+line but rather the expected difference in indentation between the new and an
18+already existing line. This difference is then added to the actual indentation
19+of the already existing line. Since this makes errors in the indent queries
20+harder to find, it is recommended to disable it when testing via
21+`:set indent-heuristic tree-sitter`. The rest of this guide assumes that
22+the `tree-sitter` heuristic is used.
23+
1524## Indent queries
1625
1726When Helix is inserting a new line through `o`, `O`, or `<ret>`, to determine
book/src/keymap.md+1−1
@@ -25,7 +25,7 @@
2525
2626## Normal mode
2727
28−Normal mode is the default mode when you launch helix. Return to it from other modes by typing `Escape`.
28+Normal mode is the default mode when you launch helix. You can return to it from other modes by pressing the `Escape` key.
2929
3030### Movement
3131
book/src/languages.md+1
@@ -69,6 +69,7 @@ These configuration keys are available:
6969| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
7070| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
7171| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
72+| `persistent-diagnostic-sources` | An array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Helix can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
7273
7374### File-type detection and the `file-types` key
7475
book/src/themes.md+4−1
@@ -296,11 +296,14 @@ These scopes are used for theming the editor interface:
296296| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
297297| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
298298| `ui.statusline.separator` | Separator character in statusline |
299+| `ui.bufferline` | Style for the buffer line |
300+| `ui.bufferline.active` | Style for the active buffer in buffer line |
301+| `ui.bufferline.background` | Style for bufferline background |
299302| `ui.popup` | Documentation popups (e.g. Space + k) |
300303| `ui.popup.info` | Prompt for multiple key options |
301304| `ui.window` | Borderlines separating splits |
302305| `ui.help` | Description box for commands |
303−| `ui.text` | Command prompts, popup text, etc. |
306+| `ui.text` | Default text style, command prompts, popup text, etc. |
304307| `ui.text.focus` | The currently selected line in the picker |
305308| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
306309| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
docs/releases.md+12−5
@@ -1,16 +1,23 @@
11## Checklist
22
33Helix releases are versioned in the Calendar Versioning scheme:
4−`YY.0M(.MICRO)`, for example, `22.05` for May of 2022. In these instructions
5−we'll use `<tag>` as a placeholder for the tag being published.
4+`YY.0M(.MICRO)`, for example, `22.05` for May of 2022, or in a patch release,
5+`22.05.1`. In these instructions we'll use `<tag>` as a placeholder for the tag
6+being published.
67
78* Merge the changelog PR
89* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
910* Tag and push
1011 * `git tag -s -m "<tag>" -a <tag> && git push`
1112 * Make sure to switch to master and pull first
12−* Edit the `VERSION` file and change the date to the next planned release
13− * Releases are planned to happen every two months, so `22.05` would change to `22.07`
13+* Edit the `Cargo.toml` file and change the date in the `version` field to the next planned release
14+ * Due to Cargo having a strict requirement on SemVer with 3 or more version
15+ numbers, a `0` is required in the micro version; however, unless we are
16+ publishing a patch release after a major release, the `.0` is dropped in
17+ the user facing version.
18+ * Releases are planned to happen every two months, so `22.05.0` would change to `22.07.0`
19+ * If we are pushing a patch/bugfix release in the same month as the previous
20+ release, bump the micro version, e.g. `22.07.0` to `22.07.1`
1421* Wait for the Release CI to finish
1522 * It will automatically turn the git tag into a GitHub release when it uploads artifacts
1623* Edit the new release
@@ -57,4 +64,4 @@ versions for convenience:
5764> release. For the full log, check out the git log.
5865
5966Typically, small changes like dependencies or documentation updates, refactors,
60−or meta changes like GitHub Actions work are left out.
67+or meta changes like GitHub Actions work are left out.
flake.lock+12−35
@@ -2,23 +2,16 @@
22 "nodes": {
33 "crane": {
44 "inputs": {
5− "flake-compat": "flake-compat",
6− "flake-utils": [
7− "flake-utils"
8− ],
95 "nixpkgs": [
106 "nixpkgs"
11− ],
12− "rust-overlay": [
13− "rust-overlay"
147 ]
158 },
169 "locked": {
17− "lastModified": 1688772518,
18− "narHash": "sha256-ol7gZxwvgLnxNSZwFTDJJ49xVY5teaSvF7lzlo3YQfM=",
10+ "lastModified": 1701025348,
11+ "narHash": "sha256-42GHmYH+GF7VjwGSt+fVT1CQuNpGanJbNgVHTAZppUM=",
1912 "owner": "ipetkov",
2013 "repo": "crane",
21− "rev": "8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e",
14+ "rev": "42afaeb1a0325194a7cdb526332d2cb92fddd07b",
2215 "type": "github"
2316 },
2417 "original": {
@@ -27,32 +20,16 @@
2720 "type": "github"
2821 }
2922 },
30− "flake-compat": {
31− "flake": false,
32− "locked": {
33− "lastModified": 1673956053,
34− "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
35− "owner": "edolstra",
36− "repo": "flake-compat",
37− "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
38− "type": "github"
39− },
40− "original": {
41− "owner": "edolstra",
42− "repo": "flake-compat",
43− "type": "github"
44− }
45− },
4623 "flake-utils": {
4724 "inputs": {
4825 "systems": "systems"
4926 },
5027 "locked": {
51− "lastModified": 1689068808,
52− "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
28+ "lastModified": 1694529238,
29+ "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
5330 "owner": "numtide",
5431 "repo": "flake-utils",
55− "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
32+ "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
5633 "type": "github"
5734 },
5835 "original": {
@@ -63,11 +40,11 @@
6340 },
6441 "nixpkgs": {
6542 "locked": {
66− "lastModified": 1690272529,
67− "narHash": "sha256-MakzcKXEdv/I4qJUtq/k/eG+rVmyOZLnYNC2w1mB59Y=",
43+ "lastModified": 1700794826,
44+ "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=",
6845 "owner": "nixos",
6946 "repo": "nixpkgs",
70− "rev": "ef99fa5c5ed624460217c31ac4271cfb5cb2502c",
47+ "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8",
7148 "type": "github"
7249 },
7350 "original": {
@@ -95,11 +72,11 @@
9572 ]
9673 },
9774 "locked": {
98− "lastModified": 1690424156,
99− "narHash": "sha256-Bpml+L280tHTQpwpC5/BJbU4HSvEzMvW8IZ4gAXimhE=",
75+ "lastModified": 1701137803,
76+ "narHash": "sha256-0LcPAdql5IhQSUXJx3Zna0dYTgdIoYO7zUrsKgiBd04=",
10077 "owner": "oxalica",
10178 "repo": "rust-overlay",
102− "rev": "f335a0213504c7e6481c359dc1009be9cf34432c",
79+ "rev": "9dd940c967502f844eacea52a61e9596268d4f70",
10380 "type": "github"
10481 },
10582 "original": {
flake.nix+12−14
@@ -13,8 +13,6 @@
1313 };
1414 crane = {
1515 url = "github:ipetkov/crane";
16− inputs.rust-overlay.follows = "rust-overlay";
17− inputs.flake-utils.follows = "flake-utils";
1816 inputs.nixpkgs.follows = "nixpkgs";
1917 };
2018 };
@@ -123,18 +121,18 @@
123121 rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
124122 craneLibMSRV = (crane.mkLib pkgs).overrideToolchain rustToolchain;
125123 craneLibStable = (crane.mkLib pkgs).overrideToolchain pkgs.pkgsBuildHost.rust-bin.stable.latest.default;
126− commonArgs =
127− {
128− inherit stdenv;
129− src = filteredSource;
130− # disable fetching and building of tree-sitter grammars in the helix-term build.rs
131− HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
132− buildInputs = [stdenv.cc.cc.lib];
133− # disable tests
134− doCheck = false;
135− meta.mainProgram = "hx";
136− }
137− // craneLibMSRV.crateNameFromCargoToml {cargoToml = ./helix-term/Cargo.toml;};
124+ commonArgs = {
125+ inherit stdenv;
126+ inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./helix-term/Cargo.toml;}) pname;
127+ inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./Cargo.toml;}) version;
128+ src = filteredSource;
129+ # disable fetching and building of tree-sitter grammars in the helix-term build.rs
130+ HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
131+ buildInputs = [stdenv.cc.cc.lib];
132+ # disable tests
133+ doCheck = false;
134+ meta.mainProgram = "hx";
135+ };
138136 cargoArtifacts = craneLibMSRV.buildDepsOnly commonArgs;
139137 in {
140138 packages = {
helix-core/Cargo.toml+11−10
@@ -1,21 +1,22 @@
11[package]
22name = "helix-core"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
73description = "Helix editor core editing primitives"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
114include = ["src/**/*", "README.md"]
5+version.workspace = true
6+authors.workspace = true
7+edition.workspace = true
8+license.workspace = true
9+rust-version.workspace = true
10+categories.workspace = true
11+repository.workspace = true
12+homepage.workspace = true
1213
1314[features]
1415unicode-lines = ["ropey/unicode_lines"]
1516integration = []
1617
1718[dependencies]
18−helix-loader = { version = "0.6", path = "../helix-loader" }
19+helix-loader = { path = "../helix-loader" }
1920
2021ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
2122smallvec = "1.11"
@@ -26,12 +27,12 @@ unicode-general-category = "0.6"
2627# slab = "0.4.2"
2728slotmap = "1.0"
2829tree-sitter.workspace = true
29−once_cell = "1.18"
30+once_cell = "1.19"
3031arc-swap = "1"
3132regex = "1"
3233bitflags = "2.4"
3334ahash = "0.8.6"
34−hashbrown = { version = "0.14.2", features = ["raw"] }
35+hashbrown = { version = "0.14.3", features = ["raw"] }
3536dunce = "1.0"
3637
3738log = "0.4"
helix-core/src/diagnostic.rs+4
@@ -39,6 +39,10 @@ pub enum DiagnosticTag {
3939#[derive(Debug, Clone)]
4040pub struct Diagnostic {
4141 pub range: Range,
42+ // whether this diagnostic ends at the end of(or inside) a word
43+ pub ends_at_word: bool,
44+ pub starts_at_word: bool,
45+ pub zero_width: bool,
4246 pub line: usize,
4347 pub message: String,
4448 pub severity: Option<Severity>,
helix-core/src/indent.rs+310−101
@@ -4,10 +4,11 @@ use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
44
55use crate::{
66 chars::{char_is_line_ending, char_is_whitespace},
7+ find_first_non_whitespace_char,
78 graphemes::{grapheme_width, tab_width_at},
8− syntax::{LanguageConfiguration, RopeProvider, Syntax},
9+ syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
910 tree_sitter::Node,
10− Rope, RopeGraphemes, RopeSlice,
11+ Position, Rope, RopeGraphemes, RopeSlice,
1112};
1213
1314/// Enum representing indentation style.
@@ -21,7 +22,7 @@ pub enum IndentStyle {
2122
2223// 16 spaces
2324const INDENTS: &str = " ";
24−const MAX_INDENT: u8 = 16;
25+pub const MAX_INDENT: u8 = 16;
2526
2627impl IndentStyle {
2728 /// Creates an `IndentStyle` from an indentation string.
@@ -196,6 +197,56 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
196197 len / indent_width
197198}
198199
200+/// Create a string of tabs & spaces that has the same visual width as the given RopeSlice (independent of the tab width).
201+fn whitespace_with_same_width(text: RopeSlice) -> String {
202+ let mut s = String::new();
203+ for grapheme in RopeGraphemes::new(text) {
204+ if grapheme == "\t" {
205+ s.push('\t');
206+ } else {
207+ s.extend(std::iter::repeat(' ').take(grapheme_width(&Cow::from(grapheme))));
208+ }
209+ }
210+ s
211+}
212+
213+fn add_indent_level(
214+ mut base_indent: String,
215+ added_indent_level: isize,
216+ indent_style: &IndentStyle,
217+ tab_width: usize,
218+) -> String {
219+ if added_indent_level >= 0 {
220+ // Adding a non-negative indent is easy, we can simply append the indent string
221+ base_indent.push_str(&indent_style.as_str().repeat(added_indent_level as usize));
222+ base_indent
223+ } else {
224+ // In this case, we want to return a prefix of `base_indent`.
225+ // Since the width of a tab depends on its offset, we cannot simply iterate over
226+ // the chars of `base_indent` in reverse until we have the desired indent reduction,
227+ // instead we iterate over them twice in forward direction.
228+ let base_indent_rope = RopeSlice::from(base_indent.as_str());
229+ #[allow(deprecated)]
230+ let base_indent_width =
231+ crate::visual_coords_at_pos(base_indent_rope, base_indent_rope.len_chars(), tab_width)
232+ .col;
233+ let target_indent_width = base_indent_width
234+ .saturating_sub((-added_indent_level) as usize * indent_style.indent_width(tab_width));
235+ #[allow(deprecated)]
236+ let char_end_idx = crate::pos_at_visual_coords(
237+ base_indent_rope,
238+ Position {
239+ row: 0,
240+ col: target_indent_width,
241+ },
242+ tab_width,
243+ );
244+ let byte_end_idx = base_indent_rope.char_to_byte(char_end_idx);
245+ base_indent.truncate(byte_end_idx);
246+ base_indent
247+ }
248+}
249+
199250/// Computes for node and all ancestors whether they are the first node on their line.
200251/// The first entry in the return value represents the root node, the last one the node itself
201252fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
@@ -241,21 +292,21 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
241292/// - max(0, indent - outdent) tabs, if tabs are used for indentation
242293/// - max(0, indent - outdent)*indent_width spaces, if spaces are used for indentation
243294#[derive(Default, Debug, PartialEq, Eq, Clone)]
244−pub struct Indentation {
295+pub struct Indentation<'a> {
245296 indent: usize,
246297 indent_always: usize,
247298 outdent: usize,
248299 outdent_always: usize,
249300 /// The alignment, as a string containing only tabs & spaces. Storing this as a string instead of e.g.
250301 /// the (visual) width ensures that the alignment is preserved even if the tab width changes.
251− align: Option<String>,
302+ align: Option<RopeSlice<'a>>,
252303}
253304
254−impl Indentation {
305+impl<'a> Indentation<'a> {
255306 /// Add some other [Indentation] to this.
256307 /// The added indent should be the total added indent from one line.
257308 /// Indent should always be added starting from the bottom (or equivalently, the innermost tree-sitter node).
258− fn add_line(&mut self, added: Indentation) {
309+ fn add_line(&mut self, added: Indentation<'a>) {
259310 // Align overrides the indent from outer scopes.
260311 if self.align.is_some() {
261312 return;
@@ -274,7 +325,7 @@ impl Indentation {
274325 /// Only captures that apply to the same line should be added together in this way (otherwise use `add_line`)
275326 /// and the captures should be added starting from the innermost tree-sitter node (currently this only matters
276327 /// if multiple `@align` patterns occur on the same line).
277− fn add_capture(&mut self, added: IndentCaptureType) {
328+ fn add_capture(&mut self, added: IndentCaptureType<'a>) {
278329 match added {
279330 IndentCaptureType::Indent => {
280331 if self.indent_always == 0 {
@@ -303,43 +354,62 @@ impl Indentation {
303354 }
304355 }
305356 }
306− fn into_string(self, indent_style: &IndentStyle) -> String {
307− let indent = self.indent_always + self.indent;
308− let outdent = self.outdent_always + self.outdent;
309−
310− let indent_level = if indent >= outdent {
311− indent - outdent
312− } else {
313− log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
314− 0
315− };
316− let mut indent_string = if let Some(align) = self.align {
317− align
357+ fn net_indent(&self) -> isize {
358+ (self.indent + self.indent_always) as isize
359+ - ((self.outdent + self.outdent_always) as isize)
360+ }
361+ /// Convert `self` into a string, taking into account the computed and actual indentation of some other line.
362+ fn relative_indent(
363+ &self,
364+ other_computed_indent: &Self,
365+ other_leading_whitespace: RopeSlice,
366+ indent_style: &IndentStyle,
367+ tab_width: usize,
368+ ) -> Option<String> {
369+ if self.align == other_computed_indent.align {
370+ // If self and baseline are either not aligned to anything or both aligned the same way,
371+ // we can simply take `other_leading_whitespace` and add some indent / outdent to it (in the second
372+ // case, the alignment should already be accounted for in `other_leading_whitespace`).
373+ let indent_diff = self.net_indent() - other_computed_indent.net_indent();
374+ Some(add_indent_level(
375+ String::from(other_leading_whitespace),
376+ indent_diff,
377+ indent_style,
378+ tab_width,
379+ ))
318380 } else {
319− String::new()
320− };
321− indent_string.push_str(&indent_style.as_str().repeat(indent_level));
322− indent_string
381+ // If the alignment of both lines is different, we cannot compare their indentation in any meaningful way
382+ None
383+ }
384+ }
385+ pub fn to_string(&self, indent_style: &IndentStyle, tab_width: usize) -> String {
386+ add_indent_level(
387+ self.align
388+ .map_or_else(String::new, whitespace_with_same_width),
389+ self.net_indent(),
390+ indent_style,
391+ tab_width,
392+ )
323393 }
324394}
325395
326396/// An indent definition which corresponds to a capture from the indent query
327397#[derive(Debug)]
328−struct IndentCapture {
329− capture_type: IndentCaptureType,
398+struct IndentCapture<'a> {
399+ capture_type: IndentCaptureType<'a>,
330400 scope: IndentScope,
331401}
332402#[derive(Debug, Clone, PartialEq)]
333−enum IndentCaptureType {
403+enum IndentCaptureType<'a> {
334404 Indent,
335405 IndentAlways,
336406 Outdent,
337407 OutdentAlways,
338408 /// Alignment given as a string of whitespace
339− Align(String),
409+ Align(RopeSlice<'a>),
340410}
341411
342−impl IndentCaptureType {
412+impl<'a> IndentCaptureType<'a> {
343413 fn default_scope(&self) -> IndentScope {
344414 match self {
345415 IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
@@ -371,8 +441,8 @@ enum ExtendCapture {
371441/// each node (identified by its ID) the relevant captures (already filtered
372442/// by predicates).
373443#[derive(Debug)]
374−struct IndentQueryResult {
375− indent_captures: HashMap<usize, Vec<IndentCapture>>,
444+struct IndentQueryResult<'a> {
445+ indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
376446 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
377447}
378448
@@ -393,14 +463,14 @@ fn get_node_end_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
393463 node_line
394464}
395465
396−fn query_indents(
466+fn query_indents<'a>(
397467 query: &Query,
398468 syntax: &Syntax,
399469 cursor: &mut QueryCursor,
400− text: RopeSlice,
470+ text: RopeSlice<'a>,
401471 range: std::ops::Range<usize>,
402472 new_line_byte_pos: Option<usize>,
403−) -> IndentQueryResult {
473+) -> IndentQueryResult<'a> {
404474 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
405475 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
406476 cursor.set_byte_range(range);
@@ -488,7 +558,7 @@ fn query_indents(
488558 "outdent" => IndentCaptureType::Outdent,
489559 "outdent.always" => IndentCaptureType::OutdentAlways,
490560 // The alignment will be updated to the correct value at the end, when the anchor is known.
491− "align" => IndentCaptureType::Align(String::from("")),
561+ "align" => IndentCaptureType::Align(RopeSlice::from("")),
492562 "anchor" => {
493563 if anchor.is_some() {
494564 log::error!("Invalid indent query: Encountered more than one @anchor in the same match.")
@@ -560,22 +630,10 @@ fn query_indents(
560630 }
561631 Some(anchor) => anchor,
562632 };
563− // Create a string of tabs & spaces that should have the same width
564− // as the string that precedes the anchor (independent of the tab width).
565− let mut align = String::new();
566− for grapheme in RopeGraphemes::new(
633+ capture.capture_type = IndentCaptureType::Align(
567634 text.line(anchor.start_position().row)
568635 .byte_slice(0..anchor.start_position().column),
569− ) {
570− if grapheme == "\t" {
571− align.push('\t');
572− } else {
573− align.extend(
574− std::iter::repeat(' ').take(grapheme_width(&Cow::from(grapheme))),
575− );
576− }
577− }
578− capture.capture_type = IndentCaptureType::Align(align);
636+ );
579637 }
580638 indent_captures
581639 .entry(node_id)
@@ -661,56 +719,20 @@ fn extend_nodes<'a>(
661719 }
662720}
663721
664−/// Use the syntax tree to determine the indentation for a given position.
665−/// This can be used in 2 ways:
666−///
667−/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
668−/// - In this case, pos should be inside the first tree-sitter node on that line.
669−/// In most cases, this can just be the first non-whitespace on that line.
670−/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
671−/// after pos were moved to a new line.
672−///
673−/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
674−/// Each of these nodes produces some [Indentation] for:
675−///
676−/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
677−/// - The line after the node. This is defined by:
678−/// - The scope `tail`.
679−/// - The scope `all` if this node is not the first node on its line.
680−/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
681−/// The indents from different nodes for the same line are then combined.
682−/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
683−///
684−/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
685−/// ```ignore
686−/// some_function(|| {
687−/// // Both the function parameters as well as the contained block should be indented.
688−/// // Because they are on the same line, this only yields one indent level
689−/// });
690−/// ```
691−///
692−/// ```ignore
693−/// some_function(
694−/// param1,
695−/// || {
696−/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
697−/// },
698−/// );
699−/// ```
722+/// Prepare an indent query by computing:
723+/// - The node from which to start the query (this is non-trivial due to `@extend` captures)
724+/// - The indent captures for all relevant nodes.
700725#[allow(clippy::too_many_arguments)]
701−pub fn treesitter_indent_for_pos(
726+fn init_indent_query<'a, 'b>(
702727 query: &Query,
703− syntax: &Syntax,
704− indent_style: &IndentStyle,
728+ syntax: &'a Syntax,
729+ text: RopeSlice<'b>,
705730 tab_width: usize,
706731 indent_width: usize,
707− text: RopeSlice,
708732 line: usize,
709− pos: usize,
710− new_line: bool,
711−) -> Option<String> {
712− let byte_pos = text.char_to_byte(pos);
713− let new_line_byte_pos = new_line.then_some(byte_pos);
733+ byte_pos: usize,
734+ new_line_byte_pos: Option<usize>,
735+) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
714736 // The innermost tree-sitter node which is considered for the indent
715737 // computation. It may change if some predeceding node is extended
716738 let mut node = syntax
@@ -754,7 +776,6 @@ pub fn treesitter_indent_for_pos(
754776 (query_result, deepest_preceding)
755777 })
756778 };
757− let mut indent_captures = query_result.indent_captures;
758779 let extend_captures = query_result.extend_captures;
759780
760781 // Check for extend captures, potentially changing the node that the indent calculation starts with
@@ -769,6 +790,68 @@ pub fn treesitter_indent_for_pos(
769790 indent_width,
770791 );
771792 }
793+ Some((node, query_result.indent_captures))
794+}
795+
796+/// Use the syntax tree to determine the indentation for a given position.
797+/// This can be used in 2 ways:
798+///
799+/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
800+/// - In this case, pos should be inside the first tree-sitter node on that line.
801+/// In most cases, this can just be the first non-whitespace on that line.
802+/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
803+/// after pos were moved to a new line.
804+///
805+/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
806+/// Each of these nodes produces some [Indentation] for:
807+///
808+/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
809+/// - The line after the node. This is defined by:
810+/// - The scope `tail`.
811+/// - The scope `all` if this node is not the first node on its line.
812+/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
813+/// The indents from different nodes for the same line are then combined.
814+/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
815+///
816+/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
817+/// ```ignore
818+/// some_function(|| {
819+/// // Both the function parameters as well as the contained block should be indented.
820+/// // Because they are on the same line, this only yields one indent level
821+/// });
822+/// ```
823+///
824+/// ```ignore
825+/// some_function(
826+/// param1,
827+/// || {
828+/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
829+/// },
830+/// );
831+/// ```
832+#[allow(clippy::too_many_arguments)]
833+pub fn treesitter_indent_for_pos<'a>(
834+ query: &Query,
835+ syntax: &Syntax,
836+ tab_width: usize,
837+ indent_width: usize,
838+ text: RopeSlice<'a>,
839+ line: usize,
840+ pos: usize,
841+ new_line: bool,
842+) -> Option<Indentation<'a>> {
843+ let byte_pos = text.char_to_byte(pos);
844+ let new_line_byte_pos = new_line.then_some(byte_pos);
845+ let (mut node, mut indent_captures) = init_indent_query(
846+ query,
847+ syntax,
848+ text,
849+ tab_width,
850+ indent_width,
851+ line,
852+ byte_pos,
853+ new_line_byte_pos,
854+ )?;
772855 let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
773856
774857 let mut result = Indentation::default();
@@ -836,7 +919,7 @@ pub fn treesitter_indent_for_pos(
836919 break;
837920 }
838921 }
839− Some(result.into_string(indent_style))
922+ Some(result)
840923}
841924
842925/// Returns the indentation for a new line.
@@ -845,6 +928,7 @@ pub fn treesitter_indent_for_pos(
845928pub fn indent_for_newline(
846929 language_config: Option<&LanguageConfiguration>,
847930 syntax: Option<&Syntax>,
931+ indent_heuristic: &IndentationHeuristic,
848932 indent_style: &IndentStyle,
849933 tab_width: usize,
850934 text: RopeSlice,
@@ -853,14 +937,18 @@ pub fn indent_for_newline(
853937 current_line: usize,
854938) -> String {
855939 let indent_width = indent_style.indent_width(tab_width);
856− if let (Some(query), Some(syntax)) = (
940+ if let (
941+ IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
942+ Some(query),
943+ Some(syntax),
944+ ) = (
945+ indent_heuristic,
857946 language_config.and_then(|config| config.indent_query()),
858947 syntax,
859948 ) {
860949 if let Some(indent) = treesitter_indent_for_pos(
861950 query,
862951 syntax,
863− indent_style,
864952 tab_width,
865953 indent_width,
866954 text,
@@ -868,9 +956,57 @@ pub fn indent_for_newline(
868956 line_before_end_pos,
869957 true,
870958 ) {
871− return indent;
959+ if *indent_heuristic == IndentationHeuristic::Hybrid {
960+ // We want to compute the indentation not only based on the
961+ // syntax tree but also on the actual indentation of a previous
962+ // line. This makes indentation computation more resilient to
963+ // incomplete queries, incomplete source code & differing indentation
964+ // styles for the same language.
965+ // However, using the indent of a previous line as a baseline may not
966+ // make sense, e.g. if it has a different alignment than the new line.
967+ // In order to prevent edge cases with long running times, we only try
968+ // a constant number of (non-empty) lines.
969+ const MAX_ATTEMPTS: usize = 4;
970+ let mut num_attempts = 0;
971+ for line_idx in (0..=line_before).rev() {
972+ let line = text.line(line_idx);
973+ let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
974+ Some(i) => i,
975+ None => {
976+ continue;
977+ }
978+ };
979+ if let Some(indent) = (|| {
980+ let computed_indent = treesitter_indent_for_pos(
981+ query,
982+ syntax,
983+ tab_width,
984+ indent_width,
985+ text,
986+ line_idx,
987+ text.line_to_char(line_idx) + first_non_whitespace_char,
988+ false,
989+ )?;
990+ let leading_whitespace = line.slice(0..first_non_whitespace_char);
991+ indent.relative_indent(
992+ &computed_indent,
993+ leading_whitespace,
994+ indent_style,
995+ tab_width,
996+ )
997+ })() {
998+ return indent;
999+ }
1000+ num_attempts += 1;
1001+ if num_attempts == MAX_ATTEMPTS {
1002+ break;
1003+ }
1004+ }
1005+ }
1006+ return indent.to_string(indent_style, tab_width);
8721007 };
8731008 }
1009+ // Fallback in case we either don't have indent queries or they failed for some reason
8741010 let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
8751011 indent_style.as_str().repeat(indent_level)
8761012}
@@ -962,10 +1098,13 @@ mod test {
9621098 ..Default::default()
9631099 };
9641100
965− let add_capture = |mut indent: Indentation, capture| {
1101+ fn add_capture<'a>(
1102+ mut indent: Indentation<'a>,
1103+ capture: IndentCaptureType<'a>,
1104+ ) -> Indentation<'a> {
9661105 indent.add_capture(capture);
9671106 indent
968− };
1107+ }
9691108
9701109 // adding an indent to no indent makes an indent
9711110 assert_eq!(
@@ -1060,4 +1199,74 @@ mod test {
10601199 )
10611200 );
10621201 }
1202+
1203+ #[test]
1204+ fn test_relative_indent() {
1205+ let indent_style = IndentStyle::Spaces(4);
1206+ let tab_width: usize = 4;
1207+ let no_align = [
1208+ Indentation::default(),
1209+ Indentation {
1210+ indent: 1,
1211+ ..Default::default()
1212+ },
1213+ Indentation {
1214+ indent: 5,
1215+ outdent: 1,
1216+ ..Default::default()
1217+ },
1218+ ];
1219+ let align = no_align.clone().map(|indent| Indentation {
1220+ align: Some(RopeSlice::from("12345")),
1221+ ..indent
1222+ });
1223+ let different_align = Indentation {
1224+ align: Some(RopeSlice::from("123456")),
1225+ ..Default::default()
1226+ };
1227+
1228+ // Check that relative and absolute indentation computation are the same when the line we compare to is
1229+ // indented as we expect.
1230+ let check_consistency = |indent: &Indentation, other: &Indentation| {
1231+ assert_eq!(
1232+ indent.relative_indent(
1233+ other,
1234+ RopeSlice::from(other.to_string(&indent_style, tab_width).as_str()),
1235+ &indent_style,
1236+ tab_width
1237+ ),
1238+ Some(indent.to_string(&indent_style, tab_width))
1239+ );
1240+ };
1241+ for a in &no_align {
1242+ for b in &no_align {
1243+ check_consistency(a, b);
1244+ }
1245+ }
1246+ for a in &align {
1247+ for b in &align {
1248+ check_consistency(a, b);
1249+ }
1250+ }
1251+
1252+ // Relative indent computation makes no sense if the alignment differs
1253+ assert_eq!(
1254+ align[0].relative_indent(
1255+ &no_align[0],
1256+ RopeSlice::from(" "),
1257+ &indent_style,
1258+ tab_width
1259+ ),
1260+ None
1261+ );
1262+ assert_eq!(
1263+ align[0].relative_indent(
1264+ &different_align,
1265+ RopeSlice::from(" "),
1266+ &indent_style,
1267+ tab_width
1268+ ),
1269+ None
1270+ );
1271+ }
10631272}
helix-core/src/syntax.rs+19−4
@@ -155,6 +155,8 @@ pub struct LanguageConfiguration {
155155 /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
156156 /// Falling back to the current working directory if none are configured.
157157 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
158+ #[serde(default)]
159+ pub persistent_diagnostic_sources: Vec<String>,
158160}
159161
160162#[derive(Debug, PartialEq, Eq, Hash)]
@@ -212,10 +214,7 @@ impl<'de> Deserialize<'de> for FileType {
212214 {
213215 match map.next_entry::<String, String>()? {
214216 Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
215− // FIXME: use `suffix.replace('/', std::path::MAIN_SEPARATOR_STR)`
216− // if MSRV is updated to 1.68
217− let mut separator = [0; 1];
218− suffix.replace('/', std::path::MAIN_SEPARATOR.encode_utf8(&mut separator))
217+ suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
219218 })),
220219 Some((key, _value)) => Err(serde::de::Error::custom(format!(
221220 "unknown key in `file-types` list: {}",
@@ -445,6 +444,22 @@ pub struct IndentationConfiguration {
445444 pub unit: String,
446445}
447446
447+/// How the indentation for a newly inserted line should be determined.
448+/// If the selected heuristic is not available (e.g. because the current
449+/// language has no tree-sitter indent queries), a simpler one will be used.
450+#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
451+#[serde(rename_all = "kebab-case")]
452+pub enum IndentationHeuristic {
453+ /// Just copy the indentation of the line that the cursor is currently on.
454+ Simple,
455+ /// Use tree-sitter indent queries to compute the expected absolute indentation level of the new line.
456+ TreeSitter,
457+ /// Use tree-sitter indent queries to compute the expected difference in indentation between the new line
458+ /// and the line before. Add this to the actual indentation level of the line before.
459+ #[default]
460+ Hybrid,
461+}
462+
448463/// Configuration for auto pairs
449464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
450465#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
helix-core/src/transaction.rs+73−14
@@ -1,7 +1,7 @@
11use ropey::RopeSlice;
22use smallvec::SmallVec;
33
4−use crate::{Range, Rope, Selection, Tendril};
4+use crate::{chars::char_is_word, Range, Rope, Selection, Tendril};
55use std::{borrow::Cow, iter::once};
66
77/// (from, to, replacement)
@@ -23,6 +23,30 @@ pub enum Operation {
2323pub enum Assoc {
2424 Before,
2525 After,
26+ /// Acts like `After` if a word character is inserted
27+ /// after the position, otherwise acts like `Before`
28+ AfterWord,
29+ /// Acts like `Before` if a word character is inserted
30+ /// before the position, otherwise acts like `After`
31+ BeforeWord,
32+}
33+
34+impl Assoc {
35+ /// Whether to stick to gaps
36+ fn stay_at_gaps(self) -> bool {
37+ !matches!(self, Self::BeforeWord | Self::AfterWord)
38+ }
39+
40+ fn insert_offset(self, s: &str) -> usize {
41+ let chars = s.chars().count();
42+ match self {
43+ Assoc::After => chars,
44+ Assoc::AfterWord => s.chars().take_while(|&c| char_is_word(c)).count(),
45+ // return position before inserted text
46+ Assoc::Before => 0,
47+ Assoc::BeforeWord => chars - s.chars().rev().take_while(|&c| char_is_word(c)).count(),
48+ }
49+ }
2650}
2751
2852#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -415,8 +439,6 @@ impl ChangeSet {
415439 map!(|pos, _| (old_end > pos).then_some(new_pos), i);
416440 }
417441 Insert(s) => {
418− let ins = s.chars().count();
419−
420442 // a subsequent delete means a replace, consume it
421443 if let Some((_, Delete(len))) = iter.peek() {
422444 iter.next();
@@ -424,13 +446,13 @@ impl ChangeSet {
424446 old_end = old_pos + len;
425447 // in range of replaced text
426448 map!(
427− |pos, assoc| (old_end > pos).then(|| {
449+ |pos, assoc: Assoc| (old_end > pos).then(|| {
428450 // at point or tracking before
429− if pos == old_pos || assoc == Assoc::Before {
451+ if pos == old_pos && assoc.stay_at_gaps() {
430452 new_pos
431453 } else {
432454 // place to end of insert
433− new_pos + ins
455+ new_pos + assoc.insert_offset(s)
434456 }
435457 }),
436458 i
@@ -438,20 +460,15 @@ impl ChangeSet {
438460 } else {
439461 // at insert point
440462 map!(
441− |pos, assoc| (old_pos == pos).then(|| {
463+ |pos, assoc: Assoc| (old_pos == pos).then(|| {
442464 // return position before inserted text
443− if assoc == Assoc::Before {
444− new_pos
445− } else {
446− // after text
447− new_pos + ins
448− }
465+ new_pos + assoc.insert_offset(s)
449466 }),
450467 i
451468 );
452469 }
453470
454− new_pos += ins;
471+ new_pos += s.chars().count();
455472 }
456473 }
457474 old_pos = old_end;
@@ -884,6 +901,48 @@ mod test {
884901 let mut positions = [4, 2];
885902 cs.update_positions(positions.iter_mut().map(|pos| (pos, Assoc::After)));
886903 assert_eq!(positions, [4, 2]);
904+ // stays at word boundary
905+ let cs = ChangeSet {
906+ changes: vec![
907+ Retain(2), // <space><space>
908+ Insert(" ab".into()),
909+ Retain(2), // cd
910+ Insert("de ".into()),
911+ ],
912+ len: 4,
913+ len_after: 10,
914+ };
915+ assert_eq!(cs.map_pos(2, Assoc::BeforeWord), 3);
916+ assert_eq!(cs.map_pos(4, Assoc::AfterWord), 9);
917+ let cs = ChangeSet {
918+ changes: vec![
919+ Retain(1), // <space>
920+ Insert(" b".into()),
921+ Delete(1), // c
922+ Retain(1), // d
923+ Insert("e ".into()),
924+ Delete(1), // <space>
925+ ],
926+ len: 5,
927+ len_after: 7,
928+ };
929+ assert_eq!(cs.map_pos(1, Assoc::BeforeWord), 2);
930+ assert_eq!(cs.map_pos(3, Assoc::AfterWord), 5);
931+ let cs = ChangeSet {
932+ changes: vec![
933+ Retain(1), // <space>
934+ Insert("a".into()),
935+ Delete(2), // <space>b
936+ Retain(1), // d
937+ Insert("e".into()),
938+ Delete(1), // f
939+ Retain(1), // <space>
940+ ],
941+ len: 5,
942+ len_after: 7,
943+ };
944+ assert_eq!(cs.map_pos(2, Assoc::BeforeWord), 1);
945+ assert_eq!(cs.map_pos(4, Assoc::AfterWord), 4);
887946 }
888947
889948 #[test]
helix-core/tests/indent.rs+2−2
@@ -210,7 +210,6 @@ fn test_treesitter_indent(
210210 let suggested_indent = treesitter_indent_for_pos(
211211 indent_query,
212212 &syntax,
213− &indent_style,
214213 tab_width,
215214 indent_style.indent_width(tab_width),
216215 text,
@@ -218,7 +217,8 @@ fn test_treesitter_indent(
218217 text.line_to_char(i) + pos,
219218 false,
220219 )
221− .unwrap();
220+ .unwrap()
221+ .to_string(&indent_style, tab_width);
222222 assert!(
223223 line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
224224 "Wrong indentation for file {:?} on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
helix-dap/Cargo.toml+11−9
@@ -1,25 +1,27 @@
11[package]
22name = "helix-dap"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2018"
6−license = "MPL-2.0"
73description = "DAP client implementation for Helix project"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
4+version.workspace = true
5+authors.workspace = true
6+edition.workspace = true
7+license.workspace = true
8+rust-version.workspace = true
9+categories.workspace = true
10+repository.workspace = true
11+homepage.workspace = true
1112
1213# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1314
1415[dependencies]
15−helix-core = { version = "0.6", path = "../helix-core" }
16+helix-core = { path = "../helix-core" }
17+
1618anyhow = "1.0"
1719log = "0.4"
1820serde = { version = "1.0", features = ["derive"] }
1921serde_json = "1.0"
2022thiserror = "1.0"
2123tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
22−which = "4.4"
24+which = "5.0.0"
2325
2426[dev-dependencies]
2527fern = "0.6"
helix-event/Cargo.toml+8−7
@@ -1,12 +1,13 @@
11[package]
22name = "helix-event"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
7−categories = ["editor"]
8−repository = "https://github.com/helix-editor/helix"
9−homepage = "https://helix-editor.com"
3+version.workspace = true
4+authors.workspace = true
5+edition.workspace = true
6+license.workspace = true
7+rust-version.workspace = true
8+categories.workspace = true
9+repository.workspace = true
10+homepage.workspace = true
1011
1112# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1213
helix-loader/Cargo.toml+11−10
@@ -1,13 +1,14 @@
11[package]
22name = "helix-loader"
3−version = "0.6.0"
4−description = "A post-modern text editor."
5−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
6−edition = "2021"
7−license = "MPL-2.0"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
3+description = "Build bootstrapping for Helix crates"
4+version.workspace = true
5+authors.workspace = true
6+edition.workspace = true
7+license.workspace = true
8+rust-version.workspace = true
9+categories.workspace = true
10+repository.workspace = true
11+homepage.workspace = true
1112
1213[[bin]]
1314name = "hx-loader"
@@ -19,9 +20,9 @@ serde = { version = "1.0", features = ["derive"] }
1920toml = "0.7"
2021etcetera = "0.8"
2122tree-sitter.workspace = true
22−once_cell = "1.18"
23+once_cell = "1.19"
2324log = "0.4"
24−which = "4.4"
25+which = "5.0.0"
2526
2627# TODO: these two should be on !wasm32 only
2728
helix-loader/build.rs+14−4
@@ -2,7 +2,17 @@ use std::borrow::Cow;
22use std::path::Path;
33use std::process::Command;
44
5−const VERSION: &str = include_str!("../VERSION");
5+const MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
6+const MINOR: &str = env!("CARGO_PKG_VERSION_MINOR");
7+const PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");
8+
9+fn get_calver() -> String {
10+ if PATCH == "0" {
11+ format!("{MAJOR}.{MINOR}")
12+ } else {
13+ format!("{MAJOR}.{MINOR}.{PATCH}")
14+ }
15+}
616
717fn main() {
818 let git_hash = Command::new("git")
@@ -12,9 +22,10 @@ fn main() {
1222 .filter(|output| output.status.success())
1323 .and_then(|x| String::from_utf8(x.stdout).ok());
1424
25+ let calver = get_calver();
1526 let version: Cow<_> = match &git_hash {
16− Some(git_hash) => format!("{} ({})", VERSION, &git_hash[..8]).into(),
17− None => VERSION.into(),
27+ Some(git_hash) => format!("{} ({})", calver, &git_hash[..8]).into(),
28+ None => calver.into(),
1829 };
1930
2031 println!(
@@ -22,7 +33,6 @@ fn main() {
2233 std::env::var("TARGET").unwrap()
2334 );
2435
25− println!("cargo:rerun-if-changed=../VERSION");
2636 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
2737
2838 if git_hash.is_none() {
helix-lsp/Cargo.toml+15−14
@@ -1,31 +1,32 @@
11[package]
22name = "helix-lsp"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
73description = "LSP client implementation for Helix project"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
4+version.workspace = true
5+authors.workspace = true
6+edition.workspace = true
7+license.workspace = true
8+rust-version.workspace = true
9+categories.workspace = true
10+repository.workspace = true
11+homepage.workspace = true
1112
1213# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1314
1415[dependencies]
15−helix-core = { version = "0.6", path = "../helix-core" }
16−helix-loader = { version = "0.6", path = "../helix-loader" }
17−helix-parsec = { version = "0.6", path = "../helix-parsec" }
16+helix-core = { path = "../helix-core" }
17+helix-loader = { path = "../helix-loader" }
18+helix-parsec = { path = "../helix-parsec" }
1819
1920anyhow = "1.0"
2021futures-executor = "0.3"
2122futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
22−globset = "0.4.13"
23+globset = "0.4.14"
2324log = "0.4"
24−lsp-types = { version = "0.94" }
25+lsp-types = { version = "0.95" }
2526serde = { version = "1.0", features = ["derive"] }
2627serde_json = "1.0"
2728thiserror = "1.0"
28−tokio = { version = "1.34", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
29+tokio = { version = "1.35", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
2930tokio-stream = "0.1.14"
30−which = "4.4"
31+which = "5.0.0"
3132parking_lot = "0.12.1"
helix-lsp/src/client.rs+8−8
@@ -667,6 +667,7 @@ impl Client {
667667 version: Some(String::from(VERSION_AND_GIT_HASH)),
668668 }),
669669 locale: None, // TODO
670+ work_done_progress_params: lsp::WorkDoneProgressParams::default(),
670671 };
671672
672673 self.request::<lsp::request::Initialize>(params).await
@@ -969,20 +970,19 @@ impl Client {
969970 ) -> Option<impl Future<Output = Result<()>>> {
970971 let capabilities = self.capabilities.get().unwrap();
971972
972− let include_text = match &capabilities.text_document_sync {
973− Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
974− save: Some(options),
973+ let include_text = match &capabilities.text_document_sync.as_ref()? {
974+ lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
975+ save: options,
975976 ..
976− })) => match options {
977+ }) => match options.as_ref()? {
977978 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
978979 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
979980 include_text,
980981 }) => include_text.unwrap_or(false),
981− // Supported(false)
982− _ => return None,
982+ lsp::TextDocumentSyncSaveOptions::Supported(false) => return None,
983983 },
984− // unsupported
985− _ => return None,
984+ // see: https://github.com/microsoft/language-server-protocol/issues/288
985+ lsp::TextDocumentSyncCapability::Kind(..) => false,
986986 };
987987
988988 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
helix-parsec/Cargo.toml+8−7
@@ -1,13 +1,14 @@
11[package]
22name = "helix-parsec"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
73description = "Parser combinators for Helix"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
114include = ["src/**/*", "README.md"]
5+version.workspace = true
6+authors.workspace = true
7+edition.workspace = true
8+license.workspace = true
9+rust-version.workspace = true
10+categories.workspace = true
11+repository.workspace = true
12+homepage.workspace = true
1213
1314[dependencies]
helix-term/Cargo.toml+25−21
@@ -1,16 +1,16 @@
11[package]
22name = "helix-term"
3−version = "0.6.0"
43description = "A post-modern text editor."
5−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
6−edition = "2021"
7−license = "MPL-2.0"
8−categories = ["editor", "command-line-utilities"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
114include = ["src/**/*", "README.md"]
125default-run = "hx"
13−rust-version = "1.65"
6+version.workspace = true
7+authors.workspace = true
8+edition.workspace = true
9+license.workspace = true
10+rust-version.workspace = true
11+categories.workspace = true
12+repository.workspace = true
13+homepage.workspace = true
1414
1515[features]
1616default = ["git"]
@@ -23,18 +23,18 @@ name = "hx"
2323path = "src/main.rs"
2424
2525[dependencies]
26−helix-core = { version = "0.6", path = "../helix-core" }
27−helix-event = { version = "0.6", path = "../helix-event" }
28−helix-view = { version = "0.6", path = "../helix-view" }
29−helix-lsp = { version = "0.6", path = "../helix-lsp" }
30−helix-dap = { version = "0.6", path = "../helix-dap" }
31−helix-vcs = { version = "0.6", path = "../helix-vcs" }
32−helix-loader = { version = "0.6", path = "../helix-loader" }
26+helix-core = { path = "../helix-core" }
27+helix-event = { path = "../helix-event" }
28+helix-view = { path = "../helix-view" }
29+helix-lsp = { path = "../helix-lsp" }
30+helix-dap = { path = "../helix-dap" }
31+helix-vcs = { path = "../helix-vcs" }
32+helix-loader = { path = "../helix-loader" }
3333
3434anyhow = "1"
35−once_cell = "1.18"
35+once_cell = "1.19"
3636
37−which = "4.4"
37+which = "5.0.0"
3838
3939tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
4040tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
@@ -57,6 +57,10 @@ pulldown-cmark = { version = "0.9", default-features = false }
5757# file type detection
5858content_inspector = "0.2.4"
5959
60+# opening URLs
61+open = "5.0.1"
62+url = "2.5.0"
63+
6064# config
6165toml = "0.7"
6266
@@ -64,18 +68,18 @@ serde_json = "1.0"
6468serde = { version = "1.0", features = ["derive"] }
6569
6670# ripgrep for global search
67−grep-regex = "0.1.11"
68−grep-searcher = "0.1.11"
71+grep-regex = "0.1.12"
72+grep-searcher = "0.1.13"
6973
7074[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
7175signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
72−libc = "0.2.150"
76+libc = "0.2.151"
7377
7478[target.'cfg(target_os = "macos")'.dependencies]
7579crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] }
7680
7781[build-dependencies]
78−helix-loader = { version = "0.6", path = "../helix-loader" }
82+helix-loader = { path = "../helix-loader" }
7983
8084[dev-dependencies]
8185smallvec = "1.11"
helix-term/src/application.rs+95−46
@@ -1,6 +1,7 @@
11use arc_swap::{access::Map, ArcSwap};
22use futures_util::Stream;
33use helix_core::{
4+ chars::char_is_word,
45 diagnostic::{DiagnosticTag, NumberOrString},
56 path::get_relative_path,
67 pos_at_coords, syntax, Selection,
@@ -162,14 +163,19 @@ impl Application {
162163 // Unset path to prevent accidentally saving to the original tutor file.
163164 doc_mut!(editor).set_path(None);
164165 } else if !args.files.is_empty() {
165− if args.open_cwd {
166− // NOTE: The working directory is already set to args.files[0] in main()
167− editor.new_file(Action::VerticalSplit);
168− let picker = ui::file_picker(".".into(), &config.load().editor);
166+ let mut files_it = args.files.into_iter().peekable();
167+
168+ // If the first file is a directory, skip it and open a picker
169+ if let Some((first, _)) = files_it.next_if(|(p, _)| p.is_dir()) {
170+ let picker = ui::file_picker(first, &config.load().editor);
169171 compositor.push(Box::new(overlaid(picker)));
170− } else {
171− let nr_of_files = args.files.len();
172− for (i, (file, pos)) in args.files.into_iter().enumerate() {
172+ }
173+
174+ // If there are any more files specified, open them
175+ if files_it.peek().is_some() {
176+ let mut nr_of_files = 0;
177+ for (file, pos) in files_it {
178+ nr_of_files += 1;
173179 if file.is_dir() {
174180 return Err(anyhow::anyhow!(
175181 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
@@ -181,7 +187,7 @@ impl Application {
181187 // option. If neither of those two arguments are passed
182188 // in, just load the files normally.
183189 let action = match args.split {
184− _ if i == 0 => Action::VerticalSplit,
190+ _ if nr_of_files == 1 => Action::VerticalSplit,
185191 Some(Layout::Vertical) => Action::VerticalSplit,
186192 Some(Layout::Horizontal) => Action::HorizontalSplit,
187193 None => Action::Load,
@@ -208,6 +214,8 @@ impl Application {
208214 // does not affect views without pos since it is at the top
209215 let (view, doc) = current!(editor);
210216 align_view(doc, view, Align::Center);
217+ } else {
218+ editor.new_file(Action::VerticalSplit);
211219 }
212220 } else if stdin().is_tty() || cfg!(feature = "integration") {
213221 editor.new_file(Action::VerticalSplit);
@@ -710,7 +718,7 @@ impl Application {
710718 ));
711719 }
712720 }
713− Notification::PublishDiagnostics(params) => {
721+ Notification::PublishDiagnostics(mut params) => {
714722 let path = match params.uri.to_file_path() {
715723 Ok(path) => path,
716724 Err(_) => {
@@ -724,31 +732,69 @@ impl Application {
724732 return;
725733 }
726734 let offset_encoding = language_server.offset_encoding();
727− let doc = self.editor.document_by_path_mut(&path).filter(|doc| {
728− if let Some(version) = params.version {
729− if version != doc.version() {
730− log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
731− return false;
735+ // have to inline the function because of borrow checking...
736+ let doc = self.editor.documents.values_mut()
737+ .find(|doc| doc.path().map(|p| p == &path).unwrap_or(false))
738+ .filter(|doc| {
739+ if let Some(version) = params.version {
740+ if version != doc.version() {
741+ log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
742+ return false;
743+ }
732744 }
733− }
734−
735− true
736− });
745+ true
746+ });
737747
738748 if let Some(doc) = doc {
739− let lang_conf = doc.language_config();
740− let text = doc.text();
749+ let lang_conf = doc.language.clone();
750+ let text = doc.text().clone();
751+
752+ let mut unchaged_diag_sources_ = Vec::new();
753+ if let Some(lang_conf) = &lang_conf {
754+ if let Some(old_diagnostics) =
755+ self.editor.diagnostics.get(&params.uri)
756+ {
757+ if !lang_conf.persistent_diagnostic_sources.is_empty() {
758+ // Sort diagnostics first by severity and then by line numbers.
759+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
760+ params
761+ .diagnostics
762+ .sort_unstable_by_key(|d| (d.severity, d.range.start));
763+ }
764+ for source in &lang_conf.persistent_diagnostic_sources {
765+ let new_diagnostics = params
766+ .diagnostics
767+ .iter()
768+ .filter(|d| d.source.as_ref() == Some(source));
769+ let old_diagnostics = old_diagnostics
770+ .iter()
771+ .filter(|(d, d_server)| {
772+ *d_server == server_id
773+ && d.source.as_ref() == Some(source)
774+ })
775+ .map(|(d, _)| d);
776+ if new_diagnostics.eq(old_diagnostics) {
777+ unchaged_diag_sources_.push(source.clone())
778+ }
779+ }
780+ }
781+ }
741782
742− let diagnostics = params
743− .diagnostics
744− .iter()
745− .filter_map(|diagnostic| {
783+ let unchaged_diag_sources = &unchaged_diag_sources_;
784+ let diagnostics =
785+ params.diagnostics.iter().filter_map(move |diagnostic| {
746786 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
747787 use lsp::DiagnosticSeverity;
748788
789+ if diagnostic.source.as_ref().map_or(false, |source| {
790+ unchaged_diag_sources.contains(source)
791+ }) {
792+ return None;
793+ }
794+
749795 // TODO: convert inside server
750796 let start = if let Some(start) = lsp_pos_to_pos(
751− text,
797+ &text,
752798 diagnostic.range.start,
753799 offset_encoding,
754800 ) {
@@ -759,14 +805,13 @@ impl Application {
759805 };
760806
761807 let end = if let Some(end) =
762− lsp_pos_to_pos(text, diagnostic.range.end, offset_encoding)
808+ lsp_pos_to_pos(&text, diagnostic.range.end, offset_encoding)
763809 {
764810 end
765811 } else {
766812 log::warn!("lsp position out of bounds - {:?}", diagnostic);
767813 return None;
768814 };
769−
770815 let severity =
771816 diagnostic.severity.map(|severity| match severity {
772817 DiagnosticSeverity::ERROR => Error,
@@ -779,7 +824,7 @@ impl Application {
779824 ),
780825 });
781826
782− if let Some(lang_conf) = lang_conf {
827+ if let Some(lang_conf) = &lang_conf {
783828 if let Some(severity) = severity {
784829 if severity < lang_conf.diagnostic_severity {
785830 return None;
@@ -818,8 +863,17 @@ impl Application {
818863 Vec::new()
819864 };
820865
866+ let ends_at_word = start != end
867+ && end != 0
868+ && text.get_char(end - 1).map_or(false, char_is_word);
869+ let starts_at_word = start != end
870+ && text.get_char(start).map_or(false, char_is_word);
871+
821872 Some(Diagnostic {
822873 range: Range { start, end },
874+ ends_at_word,
875+ starts_at_word,
876+ zero_width: start == end,
823877 line: diagnostic.range.start.line as usize,
824878 message: diagnostic.message.clone(),
825879 severity,
@@ -829,38 +883,33 @@ impl Application {
829883 data: diagnostic.data.clone(),
830884 language_server_id: server_id,
831885 })
832− })
833− .collect();
886+ });
834887
835− doc.replace_diagnostics(diagnostics, server_id);
888+ doc.replace_diagnostics(diagnostics, unchaged_diag_sources, server_id);
836889 }
837890
838− let mut diagnostics = params
839− .diagnostics
840− .into_iter()
841− .map(|d| (d, server_id))
842− .collect();
891+ let diagnostics = params.diagnostics.into_iter().map(|d| (d, server_id));
843892
844893 // Insert the original lsp::Diagnostics here because we may have no open document
845894 // for diagnosic message and so we can't calculate the exact position.
846895 // When using them later in the diagnostics picker, we calculate them on-demand.
847− match self.editor.diagnostics.entry(params.uri) {
896+ let diagnostics = match self.editor.diagnostics.entry(params.uri) {
848897 Entry::Occupied(o) => {
849898 let current_diagnostics = o.into_mut();
850899 // there may entries of other language servers, which is why we can't overwrite the whole entry
851900 current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
852− current_diagnostics.append(&mut diagnostics);
853− // Sort diagnostics first by severity and then by line numbers.
854− // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
901+ current_diagnostics.extend(diagnostics);
855902 current_diagnostics
856− .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
857− }
858− Entry::Vacant(v) => {
859− diagnostics
860− .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
861− v.insert(diagnostics);
903+ // Sort diagnostics first by severity and then by line numbers.
862904 }
905+ Entry::Vacant(v) => v.insert(diagnostics.collect()),
863906 };
907+
908+ // Sort diagnostics first by severity and then by line numbers.
909+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
910+ diagnostics.sort_unstable_by_key(|(d, server_id)| {
911+ (d.severity, d.range.start, *server_id)
912+ });
864913 }
865914 Notification::ShowMessage(params) => {
866915 log::warn!("unhandled window/showMessage: {:?}", params);
helix-term/src/args.rs−1
@@ -17,7 +17,6 @@ pub struct Args {
1717 pub log_file: Option<PathBuf>,
1818 pub config_file: Option<PathBuf>,
1919 pub files: Vec<(PathBuf, Position)>,
20− pub open_cwd: bool,
2120 pub working_directory: Option<PathBuf>,
2221}
2322
helix-term/src/commands.rs+95−20
@@ -60,8 +60,13 @@ use crate::{
6060
6161use crate::job::{self, Jobs};
6262use futures_util::{stream::FuturesUnordered, TryStreamExt};
63−use std::{collections::HashMap, fmt, future::Future};
64−use std::{collections::HashSet, num::NonZeroUsize};
63+use std::{
64+ collections::{HashMap, HashSet},
65+ fmt,
66+ future::Future,
67+ io::Read,
68+ num::NonZeroUsize,
69+};
6570
6671use std::{
6772 borrow::Cow,
@@ -70,6 +75,7 @@ use std::{
7075
7176use once_cell::sync::Lazy;
7277use serde::de::{self, Deserialize, Deserializer};
78+use url::Url;
7379
7480use grep_regex::RegexMatcherBuilder;
7581use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
@@ -331,7 +337,7 @@ impl MappableCommand {
331337 goto_implementation, "Goto implementation",
332338 goto_file_start, "Goto line number <n> else file start",
333339 goto_file_end, "Goto file end",
334− goto_file, "Goto files in selection",
340+ goto_file, "Goto files/URLs in selection",
335341 goto_file_hsplit, "Goto files in selection (hsplit)",
336342 goto_file_vsplit, "Goto files in selection (vsplit)",
337343 goto_reference, "Goto references",
@@ -1190,10 +1196,53 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11901196 .to_string(),
11911197 );
11921198 }
1199+
11931200 for sel in paths {
11941201 let p = sel.trim();
1195− if !p.is_empty() {
1196− let path = &rel_path.join(p);
1202+ if p.is_empty() {
1203+ continue;
1204+ }
1205+
1206+ if let Ok(url) = Url::parse(p) {
1207+ return open_url(cx, url, action);
1208+ }
1209+
1210+ let path = &rel_path.join(p);
1211+ if path.is_dir() {
1212+ let picker = ui::file_picker(path.into(), &cx.editor.config());
1213+ cx.push_layer(Box::new(overlaid(picker)));
1214+ } else if let Err(e) = cx.editor.open(path, action) {
1215+ cx.editor.set_error(format!("Open file failed: {:?}", e));
1216+ }
1217+ }
1218+}
1219+
1220+/// Opens the given url. If the URL points to a valid textual file it is open in helix.
1221+// Otherwise, the file is open using external program.
1222+fn open_url(cx: &mut Context, url: Url, action: Action) {
1223+ let doc = doc!(cx.editor);
1224+ let rel_path = doc
1225+ .relative_path()
1226+ .map(|path| path.parent().unwrap().to_path_buf())
1227+ .unwrap_or_default();
1228+
1229+ if url.scheme() != "file" {
1230+ return open_external_url(cx, url);
1231+ }
1232+
1233+ let content_type = std::fs::File::open(url.path()).and_then(|file| {
1234+ // Read up to 1kb to detect the content type
1235+ let mut read_buffer = Vec::new();
1236+ let n = file.take(1024).read_to_end(&mut read_buffer)?;
1237+ Ok(content_inspector::inspect(&read_buffer[..n]))
1238+ });
1239+
1240+ // we attempt to open binary files - files that can't be open in helix - using external
1241+ // program as well, e.g. pdf files or images
1242+ match content_type {
1243+ Ok(content_inspector::ContentType::BINARY) => open_external_url(cx, url),
1244+ Ok(_) | Err(_) => {
1245+ let path = &rel_path.join(url.path());
11971246 if path.is_dir() {
11981247 let picker = ui::file_picker(path.into(), &cx.editor.config());
11991248 cx.push_layer(Box::new(overlaid(picker)));
@@ -1204,6 +1253,23 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
12041253 }
12051254}
12061255
1256+/// Opens URL in external program.
1257+fn open_external_url(cx: &mut Context, url: Url) {
1258+ let commands = open::commands(url.as_str());
1259+ cx.jobs.callback(async {
1260+ for cmd in commands {
1261+ let mut command = tokio::process::Command::new(cmd.get_program());
1262+ command.args(cmd.get_args());
1263+ if command.output().await.is_ok() {
1264+ return Ok(job::Callback::Editor(Box::new(|_| {})));
1265+ }
1266+ }
1267+ Ok(job::Callback::Editor(Box::new(move |editor| {
1268+ editor.set_error("Opening URL in external program failed")
1269+ })))
1270+ });
1271+}
1272+
12071273fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
12081274where
12091275 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1252,7 +1318,7 @@ fn extend_next_long_word_end(cx: &mut Context) {
12521318 extend_word_impl(cx, movement::move_next_long_word_end)
12531319}
12541320
1255−/// Separate branch to find_char designed only for <ret> char.
1321+/// Separate branch to find_char designed only for `<ret>` char.
12561322//
12571323// This is necessary because the one document can have different line endings inside. And we
12581324// cannot predict what character to find when <ret> is pressed. On the current line it can be `lf`
@@ -2987,6 +3053,7 @@ fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
29873053 let indent = indent::indent_for_newline(
29883054 language_config,
29893055 syntax,
3056+ &doc.config.load().indent_heuristic,
29903057 &doc.indent_style,
29913058 tab_width,
29923059 text,
@@ -3115,6 +3182,7 @@ fn open(cx: &mut Context, open: Open) {
31153182 let indent = indent::indent_for_newline(
31163183 doc.language_config(),
31173184 doc.syntax(),
3185+ &doc.config.load().indent_heuristic,
31183186 &doc.indent_style,
31193187 doc.tab_width(),
31203188 text,
@@ -3654,6 +3722,7 @@ pub mod insert {
36543722 let indent = indent::indent_for_newline(
36553723 doc.language_config(),
36563724 doc.syntax(),
3725+ &doc.config.load().indent_heuristic,
36573726 &doc.indent_style,
36583727 doc.tab_width(),
36593728 text,
@@ -4325,10 +4394,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
43254394 use movement::skip_while;
43264395 let (view, doc) = current!(cx.editor);
43274396 let text = doc.text();
4328− let slice = doc.text().slice(..);
4397+ let slice = text.slice(..);
43294398
43304399 let mut changes = Vec::new();
4331− let fragment = Tendril::from(" ");
43324400
43334401 for selection in doc.selection(view.id) {
43344402 let (start, mut end) = selection.line_range(slice);
@@ -4344,9 +4412,13 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
43444412 let mut end = text.line_to_char(line + 1);
43454413 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
43464414
4347− // need to skip from start, not end
4348− let change = (start, end, Some(fragment.clone()));
4349− changes.push(change);
4415+ let separator = if end == line_end_char_index(&slice, line + 1) {
4416+ // the joining line contains only space-characters => don't include a whitespace when joining
4417+ None
4418+ } else {
4419+ Some(Tendril::from(" "))
4420+ };
4421+ changes.push((start, end, separator));
43504422 }
43514423 }
43524424
@@ -4358,9 +4430,6 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
43584430 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
43594431 changes.dedup();
43604432
4361− // TODO: joining multiple empty lines should be replaced by a single space.
4362− // need to merge change ranges that touch
4363−
43644433 // select inserted spaces
43654434 let transaction = if select_space {
43664435 let ranges: SmallVec<_> = changes
@@ -4372,9 +4441,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
43724441 })
43734442 .collect();
43744443 let selection = Selection::new(ranges, 0);
4375− Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
4444+ Transaction::change(text, changes.into_iter()).with_selection(selection)
43764445 } else {
4377− Transaction::change(doc.text(), changes.into_iter())
4446+ Transaction::change(text, changes.into_iter())
43784447 };
43794448
43804449 doc.apply(&transaction, view.id);
@@ -5540,12 +5609,18 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
55405609 };
55415610
55425611 // These `usize`s cannot underflow because selection ranges cannot overlap.
5543− // Once the MSRV is 1.66.0 (mixed_integer_ops is stabilized), we can use checked
5544− // arithmetic to assert this.
5545− let anchor = (to as isize + offset - deleted_len as isize) as usize;
5612+ let anchor = to
5613+ .checked_add_signed(offset)
5614+ .expect("Selection ranges cannot overlap")
5615+ .checked_sub(deleted_len)
5616+ .expect("Selection ranges cannot overlap");
55465617 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
55475618 ranges.push(new_range);
5548− offset = offset + output_len as isize - deleted_len as isize;
5619+ offset = offset
5620+ .checked_add_unsigned(output_len)
5621+ .expect("Selection ranges cannot overlap")
5622+ .checked_sub_unsigned(deleted_len)
5623+ .expect("Selection ranges cannot overlap");
55495624
55505625 changes.push((from, to, Some(output)));
55515626 }
helix-term/src/commands/dap.rs+7−2
@@ -8,7 +8,7 @@ use dap::{StackFrame, Thread, ThreadStates};
88use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate};
99use helix_dap::{self as dap, Client};
1010use helix_lsp::block_on;
11−use helix_view::editor::Breakpoint;
11+use helix_view::{editor::Breakpoint, graphics::Margin};
1212
1313use serde_json::{to_value, Value};
1414use tokio_stream::wrappers::UnboundedReceiverStream;
@@ -581,7 +581,12 @@ pub fn dap_variables(cx: &mut Context) {
581581 }
582582
583583 let contents = Text::from(tui::text::Text::from(variables));
584− let popup = Popup::new("dap-variables", contents);
584+ let margin = if cx.editor.popup_border() {
585+ Margin::all(1)
586+ } else {
587+ Margin::none()
588+ };
589+ let popup = Popup::new("dap-variables", contents).margin(margin);
585590 cx.replace_or_push_layer("dap-variables", popup);
586591}
587592
helix-term/src/commands/lsp.rs+12−2
@@ -23,6 +23,7 @@ use helix_core::{
2323use helix_view::{
2424 document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
2525 editor::Action,
26+ graphics::Margin,
2627 theme::Style,
2728 Document, View,
2829};
@@ -49,7 +50,7 @@ use std::{
4950/// If there is no configured language server that supports the feature, this displays a status message.
5051/// Using this macro in a context where the editor automatically queries the LSP
5152/// (instead of when the user explicitly does so via a keybind like `gd`)
52−/// will spam the "No configured language server supports <feature>" status message confusingly.
53+/// will spam the "No configured language server supports \<feature>" status message confusingly.
5354#[macro_export]
5455macro_rules! language_server_with_feature {
5556 ($editor:expr, $doc:expr, $feature:expr) => {{
@@ -744,7 +745,16 @@ pub fn code_action(cx: &mut Context) {
744745 });
745746 picker.move_down(); // pre-select the first item
746747
747− let popup = Popup::new("code-action", picker).with_scrollbar(false);
748+ let margin = if editor.menu_border() {
749+ Margin::vertical(1)
750+ } else {
751+ Margin::none()
752+ };
753+
754+ let popup = Popup::new("code-action", picker)
755+ .with_scrollbar(false)
756+ .margin(margin);
757+
748758 compositor.replace_or_push("code-action", popup);
749759 };
750760
helix-term/src/commands/typed.rs+4−5
@@ -6,6 +6,7 @@ use crate::job::Job;
66use super::*;
77
88use helix_core::fuzzy::fuzzy_match;
9+use helix_core::indent::MAX_INDENT;
910use helix_core::{encoding, line_ending, path::get_canonicalized_path, shellwords::Shellwords};
1011use helix_lsp::{OffsetEncoding, Url};
1112use helix_view::document::DEFAULT_LANGUAGE_NAME;
@@ -476,8 +477,7 @@ fn set_indent_style(
476477 cx.editor.set_status(match style {
477478 Tabs => "tabs".to_owned(),
478479 Spaces(1) => "1 space".to_owned(),
479− Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
480− _ => unreachable!(), // Shouldn't happen.
480+ Spaces(n) => format!("{} spaces", n),
481481 });
482482 return Ok(());
483483 }
@@ -489,7 +489,7 @@ fn set_indent_style(
489489 Some(arg) => arg
490490 .parse::<u8>()
491491 .ok()
492− .filter(|n| (1..=8).contains(n))
492+ .filter(|n| (1..=MAX_INDENT).contains(n))
493493 .map(Spaces),
494494 _ => None,
495495 };
@@ -574,7 +574,6 @@ fn set_line_ending(
574574
575575 Ok(())
576576}
577−
578577fn earlier(
579578 cx: &mut compositor::Context,
580579 args: &[Cow<str>],
@@ -2606,7 +2605,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
26062605 TypableCommand {
26072606 name: "indent-style",
26082607 aliases: &[],
2609− doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
2608+ doc: "Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.)",
26102609 fun: set_indent_style,
26112610 signature: CommandSignature::none(),
26122611 },
helix-term/src/main.rs+7−5
@@ -116,16 +116,18 @@ FLAGS:
116116
117117 setup_logging(args.verbosity).context("failed to initialize logging")?;
118118
119+ // Before setting the working directory, resolve all the paths in args.files
120+ for (path, _) in args.files.iter_mut() {
121+ *path = helix_core::path::get_canonicalized_path(path);
122+ }
123+
119124 // NOTE: Set the working directory early so the correct configuration is loaded. Be aware that
120125 // Application::new() depends on this logic so it must be updated if this changes.
121126 if let Some(path) = &args.working_directory {
122127 helix_loader::set_current_working_dir(path)?;
123− }
124−
125− // If the first file is a directory, it will be the working directory and a file picker will be opened
126− if let Some((path, _)) = args.files.first().filter(|p| p.0.is_dir()) {
128+ } else if let Some((path, _)) = args.files.first().filter(|p| p.0.is_dir()) {
129+ // If the first file is a directory, it will be the working directory unless -w was specified
127130 helix_loader::set_current_working_dir(path)?;
128− args.open_cwd = true; // Signal Application that we want to open the picker on "."
129131 }
130132
131133 let config = match Config::load_default() {
helix-term/src/ui/completion.rs+17−1
@@ -2,6 +2,7 @@ use crate::compositor::{Component, Context, Event, EventResult};
22use helix_view::{
33 document::SavePoint,
44 editor::CompleteAction,
5+ graphics::Margin,
56 theme::{Modifier, Style},
67 ViewId,
78};
@@ -326,9 +327,18 @@ impl Completion {
326327 }
327328 };
328329 });
330+
331+ let margin = if editor.menu_border() {
332+ Margin::vertical(1)
333+ } else {
334+ Margin::none()
335+ };
336+
329337 let popup = Popup::new(Self::ID, menu)
330338 .with_scrollbar(false)
331− .ignore_escape_key(true);
339+ .ignore_escape_key(true)
340+ .margin(margin);
341+
332342 let mut completion = Self {
333343 popup,
334344 start_offset,
@@ -569,6 +579,12 @@ impl Component for Completion {
569579 // clear area
570580 let background = cx.editor.theme.get("ui.popup");
571581 surface.clear_with(doc_area, background);
582+
583+ if cx.editor.popup_border() {
584+ use tui::widgets::{Block, Borders, Widget};
585+ Widget::render(Block::default().borders(Borders::ALL), doc_area, surface);
586+ }
587+
572588 markdown_doc.render(doc_area, surface, cx);
573589 }
574590}
helix-term/src/ui/document.rs+60−24
@@ -97,7 +97,8 @@ pub fn render_document(
9797 doc: &Document,
9898 offset: ViewPosition,
9999 doc_annotations: &TextAnnotations,
100− highlight_iter: impl Iterator<Item = HighlightEvent>,
100+ syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
101+ overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
101102 theme: &Theme,
102103 line_decoration: &mut [Box<dyn LineDecoration + '_>],
103104 translated_positions: &mut [TranslatedPosition],
@@ -109,7 +110,8 @@ pub fn render_document(
109110 offset,
110111 &doc.text_format(viewport.width, Some(theme)),
111112 doc_annotations,
112− highlight_iter,
113+ syntax_highlight_iter,
114+ overlay_highlight_iter,
113115 theme,
114116 line_decoration,
115117 translated_positions,
@@ -157,7 +159,8 @@ pub fn render_text<'t>(
157159 offset: ViewPosition,
158160 text_fmt: &TextFormat,
159161 text_annotations: &TextAnnotations,
160− highlight_iter: impl Iterator<Item = HighlightEvent>,
162+ syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
163+ overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
161164 theme: &Theme,
162165 line_decorations: &mut [Box<dyn LineDecoration + '_>],
163166 translated_positions: &mut [TranslatedPosition],
@@ -178,10 +181,16 @@ pub fn render_text<'t>(
178181
179182 let (mut formatter, mut first_visible_char_idx) =
180183 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor);
181− let mut styles = StyleIter {
184+ let mut syntax_styles = StyleIter {
182185 text_style: renderer.text_style,
183186 active_highlights: Vec::with_capacity(64),
184− highlight_iter,
187+ highlight_iter: syntax_highlight_iter,
188+ theme,
189+ };
190+ let mut overlay_styles = StyleIter {
191+ text_style: Style::default(),
192+ active_highlights: Vec::with_capacity(64),
193+ highlight_iter: overlay_highlight_iter,
185194 theme,
186195 };
187196
@@ -193,7 +202,10 @@ pub fn render_text<'t>(
193202 };
194203 let mut is_in_indent_area = true;
195204 let mut last_line_indent_level = 0;
196− let mut style_span = styles
205+ let mut syntax_style_span = syntax_styles
206+ .next()
207+ .unwrap_or_else(|| (Style::default(), usize::MAX));
208+ let mut overlay_style_span = overlay_styles
197209 .next()
198210 .unwrap_or_else(|| (Style::default(), usize::MAX));
199211
@@ -221,9 +233,16 @@ pub fn render_text<'t>(
221233
222234 // skip any graphemes on visual lines before the block start
223235 if pos.row < row_off {
224− if char_pos >= style_span.1 {
225− style_span = if let Some(style_span) = styles.next() {
226− style_span
236+ if char_pos >= syntax_style_span.1 {
237+ syntax_style_span = if let Some(syntax_style_span) = syntax_styles.next() {
238+ syntax_style_span
239+ } else {
240+ break;
241+ }
242+ }
243+ if char_pos >= overlay_style_span.1 {
244+ overlay_style_span = if let Some(overlay_style_span) = overlay_styles.next() {
245+ overlay_style_span
227246 } else {
228247 break;
229248 }
@@ -260,8 +279,15 @@ pub fn render_text<'t>(
260279 }
261280
262281 // acquire the correct grapheme style
263− if char_pos >= style_span.1 {
264− style_span = styles.next().unwrap_or((Style::default(), usize::MAX));
282+ if char_pos >= syntax_style_span.1 {
283+ syntax_style_span = syntax_styles
284+ .next()
285+ .unwrap_or((Style::default(), usize::MAX));
286+ }
287+ if char_pos >= overlay_style_span.1 {
288+ overlay_style_span = overlay_styles
289+ .next()
290+ .unwrap_or((Style::default(), usize::MAX));
265291 }
266292 char_pos += grapheme.doc_chars();
267293
@@ -275,22 +301,25 @@ pub fn render_text<'t>(
275301 pos,
276302 );
277303
278− let grapheme_style = if let GraphemeSource::VirtualText { highlight } = grapheme.source {
279− let style = renderer.text_style;
280− if let Some(highlight) = highlight {
281− style.patch(theme.highlight(highlight.0))
304+ let (syntax_style, overlay_style) =
305+ if let GraphemeSource::VirtualText { highlight } = grapheme.source {
306+ let mut style = renderer.text_style;
307+ if let Some(highlight) = highlight {
308+ style = style.patch(theme.highlight(highlight.0))
309+ }
310+ (style, Style::default())
282311 } else {
283− style
284− }
285− } else {
286− style_span.0
287− };
312+ (syntax_style_span.0, overlay_style_span.0)
313+ };
288314
289− let virt = grapheme.is_virtual();
315+ let is_virtual = grapheme.is_virtual();
290316 renderer.draw_grapheme(
291317 grapheme.grapheme,
292− grapheme_style,
293− virt,
318+ GraphemeStyle {
319+ syntax_style,
320+ overlay_style,
321+ },
322+ is_virtual,
294323 &mut last_line_indent_level,
295324 &mut is_in_indent_area,
296325 pos,
@@ -322,6 +351,11 @@ pub struct TextRenderer<'a> {
322351 pub viewport: Rect,
323352}
324353
354+pub struct GraphemeStyle {
355+ syntax_style: Style,
356+ overlay_style: Style,
357+}
358+
325359impl<'a> TextRenderer<'a> {
326360 pub fn new(
327361 surface: &'a mut Surface,
@@ -395,7 +429,7 @@ impl<'a> TextRenderer<'a> {
395429 pub fn draw_grapheme(
396430 &mut self,
397431 grapheme: Grapheme,
398− mut style: Style,
432+ grapheme_style: GraphemeStyle,
399433 is_virtual: bool,
400434 last_indent_level: &mut usize,
401435 is_in_indent_area: &mut bool,
@@ -405,9 +439,11 @@ impl<'a> TextRenderer<'a> {
405439 let is_whitespace = grapheme.is_whitespace();
406440
407441 // TODO is it correct to apply the whitespace style to all unicode white spaces?
442+ let mut style = grapheme_style.syntax_style;
408443 if is_whitespace {
409444 style = style.patch(self.whitespace_style);
410445 }
446+ style = style.patch(grapheme_style.overlay_style);
411447
412448 let width = grapheme.width();
413449 let space = if is_virtual { " " } else { &self.space };
helix-term/src/ui/editor.rs+76−43
@@ -124,16 +124,20 @@ impl EditorView {
124124 line_decorations.push(Box::new(line_decoration));
125125 }
126126
127− let mut highlights =
127+ let syntax_highlights =
128128 Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
129− let overlay_highlights = Self::overlay_syntax_highlights(
129+
130+ let mut overlay_highlights =
131+ Self::empty_highlight_iter(doc, view.offset.anchor, inner.height);
132+ let overlay_syntax_highlights = Self::overlay_syntax_highlights(
130133 doc,
131134 view.offset.anchor,
132135 inner.height,
133136 &text_annotations,
134137 );
135− if !overlay_highlights.is_empty() {
136− highlights = Box::new(syntax::merge(highlights, overlay_highlights));
138+ if !overlay_syntax_highlights.is_empty() {
139+ overlay_highlights =
140+ Box::new(syntax::merge(overlay_highlights, overlay_syntax_highlights));
137141 }
138142
139143 for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
@@ -142,29 +146,28 @@ impl EditorView {
142146 if diagnostic.is_empty() {
143147 continue;
144148 }
145− highlights = Box::new(syntax::merge(highlights, diagnostic));
149+ overlay_highlights = Box::new(syntax::merge(overlay_highlights, diagnostic));
146150 }
147151
148− let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
152+ if is_focused {
149153 let highlights = syntax::merge(
150− highlights,
154+ overlay_highlights,
151155 Self::doc_selection_highlights(
152156 editor.mode(),
153157 doc,
154158 view,
155159 theme,
156160 &config.cursor_shape,
161+ self.terminal_focused,
157162 ),
158163 );
159164 let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme);
160165 if focused_view_elements.is_empty() {
161− Box::new(highlights)
166+ overlay_highlights = Box::new(highlights)
162167 } else {
163− Box::new(syntax::merge(highlights, focused_view_elements))
168+ overlay_highlights = Box::new(syntax::merge(highlights, focused_view_elements))
164169 }
165− } else {
166− Box::new(highlights)
167− };
170+ }
168171
169172 let gutter_overflow = view.gutter_offset(doc) == 0;
170173 if !gutter_overflow {
@@ -197,7 +200,8 @@ impl EditorView {
197200 doc,
198201 view.offset,
199202 &text_annotations,
200− highlights,
203+ syntax_highlights,
204+ overlay_highlights,
201205 theme,
202206 &mut line_decorations,
203207 &mut translated_positions,
@@ -257,27 +261,39 @@ impl EditorView {
257261 .for_each(|area| surface.set_style(area, ruler_theme))
258262 }
259263
260− pub fn overlay_syntax_highlights(
264+ fn viewport_byte_range(
265+ text: helix_core::RopeSlice,
266+ row: usize,
267+ height: u16,
268+ ) -> std::ops::Range<usize> {
269+ // Calculate viewport byte ranges:
270+ // Saturating subs to make it inclusive zero indexing.
271+ let last_line = text.len_lines().saturating_sub(1);
272+ let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
273+ let start = text.line_to_byte(row.min(last_line));
274+ let end = text.line_to_byte(last_visible_line + 1);
275+
276+ start..end
277+ }
278+
279+ pub fn empty_highlight_iter(
261280 doc: &Document,
262281 anchor: usize,
263282 height: u16,
264− text_annotations: &TextAnnotations,
265− ) -> Vec<(usize, std::ops::Range<usize>)> {
283+ ) -> Box<dyn Iterator<Item = HighlightEvent>> {
266284 let text = doc.text().slice(..);
267285 let row = text.char_to_line(anchor.min(text.len_chars()));
268286
269− let range = {
270− // Calculate viewport byte ranges:
271− // Saturating subs to make it inclusive zero indexing.
272− let last_line = text.len_lines().saturating_sub(1);
273− let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
274− let start = text.line_to_byte(row.min(last_line));
275− let end = text.line_to_byte(last_visible_line + 1);
276−
277− start..end
278− };
279−
280− text_annotations.collect_overlay_highlights(range)
287+ // Calculate viewport byte ranges:
288+ // Saturating subs to make it inclusive zero indexing.
289+ let range = Self::viewport_byte_range(text, row, height);
290+ Box::new(
291+ [HighlightEvent::Source {
292+ start: text.byte_to_char(range.start),
293+ end: text.byte_to_char(range.end),
294+ }]
295+ .into_iter(),
296+ )
281297 }
282298
283299 /// Get syntax highlights for a document in a view represented by the first line
@@ -292,16 +308,7 @@ impl EditorView {
292308 let text = doc.text().slice(..);
293309 let row = text.char_to_line(anchor.min(text.len_chars()));
294310
295− let range = {
296− // Calculate viewport byte ranges:
297− // Saturating subs to make it inclusive zero indexing.
298− let last_line = text.len_lines().saturating_sub(1);
299− let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
300− let start = text.line_to_byte(row.min(last_line));
301− let end = text.line_to_byte(last_visible_line + 1);
302−
303− start..end
304− };
311+ let range = Self::viewport_byte_range(text, row, height);
305312
306313 match doc.syntax() {
307314 Some(syntax) => {
@@ -334,6 +341,20 @@ impl EditorView {
334341 }
335342 }
336343
344+ pub fn overlay_syntax_highlights(
345+ doc: &Document,
346+ anchor: usize,
347+ height: u16,
348+ text_annotations: &TextAnnotations,
349+ ) -> Vec<(usize, std::ops::Range<usize>)> {
350+ let text = doc.text().slice(..);
351+ let row = text.char_to_line(anchor.min(text.len_chars()));
352+
353+ let range = Self::viewport_byte_range(text, row, height);
354+
355+ text_annotations.collect_overlay_highlights(range)
356+ }
357+
337358 /// Get highlight spans for document diagnostics
338359 pub fn doc_diagnostics_highlights(
339360 doc: &Document,
@@ -400,6 +421,7 @@ impl EditorView {
400421 view: &View,
401422 theme: &Theme,
402423 cursor_shape_config: &CursorShapeConfig,
424+ is_terminal_focused: bool,
403425 ) -> Vec<(usize, std::ops::Range<usize>)> {
404426 let text = doc.text().slice(..);
405427 let selection = doc.selection(view.id);
@@ -447,7 +469,7 @@ impl EditorView {
447469
448470 // Special-case: cursor at end of the rope.
449471 if range.head == range.anchor && range.head == text.len_chars() {
450− if !selection_is_primary || cursor_is_block {
472+ if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
451473 // Bar and underline cursors are drawn by the terminal
452474 // BUG: If the editor area loses focus while having a bar or
453475 // underline cursor (eg. when a regex prompt has focus) then
@@ -470,13 +492,17 @@ impl EditorView {
470492 cursor_start
471493 };
472494 spans.push((selection_scope, range.anchor..selection_end));
473− if !selection_is_primary || cursor_is_block {
495+ // add block cursors
496+ // skip primary cursor if terminal is unfocused - crossterm cursor is used in that case
497+ if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
474498 spans.push((cursor_scope, cursor_start..range.head));
475499 }
476500 } else {
477501 // Reverse case.
478502 let cursor_end = next_grapheme_boundary(text, range.head);
479− if !selection_is_primary || cursor_is_block {
503+ // add block cursors
504+ // skip primary cursor if terminal is unfocused - crossterm cursor is used in that case
505+ if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
480506 spans.push((cursor_scope, range.head..cursor_end));
481507 }
482508 // non block cursors look like they exclude the cursor
@@ -1500,8 +1526,15 @@ impl Component for EditorView {
15001526
15011527 fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
15021528 match editor.cursor() {
1503− // All block cursors are drawn manually
1504− (pos, CursorKind::Block) => (pos, CursorKind::Hidden),
1529+ // all block cursors are drawn manually
1530+ (pos, CursorKind::Block) => {
1531+ if self.terminal_focused {
1532+ (pos, CursorKind::Hidden)
1533+ } else {
1534+ // use crossterm cursor when terminal loses focus
1535+ (pos, CursorKind::Underline)
1536+ }
1537+ }
15051538 cursor => cursor,
15061539 }
15071540 }
helix-term/src/ui/lsp.rs+3−1
@@ -92,7 +92,9 @@ impl Component for SignatureHelp {
9292 Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
9393 };
9494 let sig_doc = sig_doc.parse(Some(&cx.editor.theme));
95− let sig_doc_area = area.clip_top(sig_text_area.height + 2);
95+ let sig_doc_area = area
96+ .clip_top(sig_text_area.height + 2)
97+ .clip_bottom(u16::from(cx.editor.popup_border()));
9698 let sig_doc_para = Paragraph::new(sig_doc)
9799 .wrap(Wrap { trim: false })
98100 .scroll((cx.scroll.unwrap_or_default() as u16, 0));
helix-term/src/ui/menu.rs+34−13
@@ -7,11 +7,18 @@ use crate::{
77use helix_core::fuzzy::MATCHER;
88use nucleo::pattern::{Atom, AtomKind, CaseMatching};
99use nucleo::{Config, Utf32Str};
10−use tui::{buffer::Buffer as Surface, widgets::Table};
10+use tui::{
11+ buffer::Buffer as Surface,
12+ widgets::{Block, Borders, Table, Widget},
13+};
1114
1215pub use tui::widgets::{Cell, Row};
1316
14−use helix_view::{editor::SmartTabConfig, graphics::Rect, Editor};
17+use helix_view::{
18+ editor::SmartTabConfig,
19+ graphics::{Margin, Rect},
20+ Editor,
21+};
1522use tui::layout::Constraint;
1623
1724pub trait Item: Sync + Send + 'static {
@@ -322,6 +329,15 @@ impl<T: Item + 'static> Component for Menu<T> {
322329 let selected = theme.get("ui.menu.selected");
323330 surface.clear_with(area, style);
324331
332+ let render_borders = cx.editor.menu_border();
333+
334+ let area = if render_borders {
335+ Widget::render(Block::default().borders(Borders::ALL), area, surface);
336+ area.inner(&Margin::vertical(1))
337+ } else {
338+ area
339+ };
340+
325341 let scroll = self.scroll;
326342
327343 let options: Vec<_> = self
@@ -362,15 +378,19 @@ impl<T: Item + 'static> Component for Menu<T> {
362378 false,
363379 );
364380
365− if let Some(cursor) = self.cursor {
366− let offset_from_top = cursor - scroll;
367− let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
368− left.set_style(selected);
369− let right = &mut surface[(
370− area.right().saturating_sub(1),
371− area.y + offset_from_top as u16,
372− )];
373− right.set_style(selected);
381+ let render_borders = cx.editor.menu_border();
382+
383+ if !render_borders {
384+ if let Some(cursor) = self.cursor {
385+ let offset_from_top = cursor - scroll;
386+ let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
387+ left.set_style(selected);
388+ let right = &mut surface[(
389+ area.right().saturating_sub(1),
390+ area.y + offset_from_top as u16,
391+ )];
392+ right.set_style(selected);
393+ }
374394 }
375395
376396 let fits = len <= win_height;
@@ -385,12 +405,13 @@ impl<T: Item + 'static> Component for Menu<T> {
385405 for i in 0..win_height {
386406 cell = &mut surface[(area.right() - 1, area.top() + i as u16)];
387407
388− cell.set_symbol("▐"); // right half block
408+ let half_block = if render_borders { "▌" } else { "▐" };
389409
390410 if scroll_line <= i && i < scroll_line + scroll_height {
391411 // Draw scroll thumb
412+ cell.set_symbol(half_block);
392413 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
393− } else {
414+ } else if !render_borders {
394415 // Draw scroll track
395416 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
396417 }
helix-term/src/ui/picker.rs+7−3
@@ -736,17 +736,20 @@ impl<T: Item + 'static> Picker<T> {
736736 }
737737 }
738738
739− let mut highlights = EditorView::doc_syntax_highlights(
739+ let syntax_highlights = EditorView::doc_syntax_highlights(
740740 doc,
741741 offset.anchor,
742742 area.height,
743743 &cx.editor.theme,
744744 );
745+
746+ let mut overlay_highlights =
747+ EditorView::empty_highlight_iter(doc, offset.anchor, area.height);
745748 for spans in EditorView::doc_diagnostics_highlights(doc, &cx.editor.theme) {
746749 if spans.is_empty() {
747750 continue;
748751 }
749− highlights = Box::new(helix_core::syntax::merge(highlights, spans));
752+ overlay_highlights = Box::new(helix_core::syntax::merge(overlay_highlights, spans));
750753 }
751754 let mut decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
752755
@@ -777,7 +780,8 @@ impl<T: Item + 'static> Picker<T> {
777780 offset,
778781 // TODO: compute text annotations asynchronously here (like inlay hints)
779782 &TextAnnotations::default(),
780− highlights,
783+ syntax_highlights,
784+ overlay_highlights,
781785 &cx.editor.theme,
782786 &mut decorations,
783787 &mut [],
helix-term/src/ui/popup.rs+27−7
@@ -3,7 +3,10 @@ use crate::{
33 compositor::{Callback, Component, Context, Event, EventResult},
44 ctrl, key,
55};
6−use tui::buffer::Buffer as Surface;
6+use tui::{
7+ buffer::Buffer as Surface,
8+ widgets::{Block, Borders, Widget},
9+};
710
811use helix_core::Position;
912use helix_view::{
@@ -252,13 +255,29 @@ impl<T: Component> Component for Popup<T> {
252255 let background = cx.editor.theme.get("ui.popup");
253256 surface.clear_with(area, background);
254257
255− let inner = area.inner(&self.margin);
258+ let render_borders = cx.editor.popup_border();
259+
260+ let inner = if self
261+ .contents
262+ .type_name()
263+ .starts_with("helix_term::ui::menu::Menu")
264+ {
265+ area
266+ } else {
267+ area.inner(&self.margin)
268+ };
269+
270+ let border = usize::from(render_borders);
271+ if render_borders {
272+ Widget::render(Block::default().borders(Borders::ALL), area, surface);
273+ }
274+
256275 self.contents.render(inner, surface, cx);
257276
258277 // render scrollbar if contents do not fit
259278 if self.has_scrollbar {
260− let win_height = inner.height as usize;
261− let len = self.child_size.1 as usize;
279+ let win_height = (inner.height as usize).saturating_sub(2 * border);
280+ let len = (self.child_size.1 as usize).saturating_sub(2 * border);
262281 let fits = len <= win_height;
263282 let scroll = self.scroll;
264283 let scroll_style = cx.editor.theme.get("ui.menu.scroll");
@@ -274,14 +293,15 @@ impl<T: Component> Component for Popup<T> {
274293
275294 let mut cell;
276295 for i in 0..win_height {
277− cell = &mut surface[(inner.right() - 1, inner.top() + i as u16)];
296+ cell = &mut surface[(inner.right() - 1, inner.top() + (border + i) as u16)];
278297
279− cell.set_symbol("▐"); // right half block
298+ let half_block = if render_borders { "▌" } else { "▐" };
280299
281300 if scroll_line <= i && i < scroll_line + scroll_height {
282301 // Draw scroll thumb
302+ cell.set_symbol(half_block);
283303 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
284− } else {
304+ } else if !render_borders {
285305 // Draw scroll track
286306 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
287307 }
helix-term/tests/test/commands.rs+46
@@ -480,3 +480,49 @@ fn bar() {#(\n|)#\
480480
481481 Ok(())
482482}
483+
484+#[tokio::test(flavor = "multi_thread")]
485+async fn test_join_selections() -> anyhow::Result<()> {
486+ // normal join
487+ test((
488+ platform_line(indoc! {"\
489+ #[a|]#bc
490+ def
491+ "}),
492+ "J",
493+ platform_line(indoc! {"\
494+ #[a|]#bc def
495+ "}),
496+ ))
497+ .await?;
498+
499+ // join with empty line
500+ test((
501+ platform_line(indoc! {"\
502+ #[a|]#bc
503+
504+ def
505+ "}),
506+ "JJ",
507+ platform_line(indoc! {"\
508+ #[a|]#bc def
509+ "}),
510+ ))
511+ .await?;
512+
513+ // join with additional space in non-empty line
514+ test((
515+ platform_line(indoc! {"\
516+ #[a|]#bc
517+
518+ def
519+ "}),
520+ "JJ",
521+ platform_line(indoc! {"\
522+ #[a|]#bc def
523+ "}),
524+ ))
525+ .await?;
526+
527+ Ok(())
528+}
helix-tui/Cargo.toml+13−13
@@ -1,28 +1,28 @@
11[package]
22name = "helix-tui"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−description = """
6−A library to build rich terminal user interfaces or dashboards
7−"""
8−edition = "2021"
9−license = "MPL-2.0"
10−categories = ["editor"]
11−repository = "https://github.com/helix-editor/helix"
12−homepage = "https://helix-editor.com"
3+description = """A library to build rich terminal user interfaces or dashboards"""
134include = ["src/**/*", "README.md"]
5+version.workspace = true
6+authors.workspace = true
7+edition.workspace = true
8+license.workspace = true
9+rust-version.workspace = true
10+categories.workspace = true
11+repository.workspace = true
12+homepage.workspace = true
1413
1514[features]
1615default = ["crossterm"]
1716
1817[dependencies]
18+helix-view = { path = "../helix-view", features = ["term"] }
19+helix-core = { path = "../helix-core" }
20+
1921bitflags = "2.4"
2022cassowary = "0.3"
2123unicode-segmentation = "1.10"
2224crossterm = { version = "0.27", optional = true }
2325termini = "1.0"
2426serde = { version = "1", "optional" = true, features = ["derive"]}
25−once_cell = "1.18"
27+once_cell = "1.19"
2628log = "~0.4"
27−helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
28−helix-core = { version = "0.6", path = "../helix-core" }
helix-tui/src/buffer.rs+2−2
@@ -213,7 +213,7 @@ impl Buffer {
213213 && y < self.area.bottom()
214214 }
215215
216− /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
216+ /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates.
217217 ///
218218 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
219219 ///
@@ -242,7 +242,7 @@ impl Buffer {
242242 ((y - self.area.y) as usize) * (self.area.width as usize) + ((x - self.area.x) as usize)
243243 }
244244
245− /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates,
245+ /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates,
246246 /// or `None` if the coordinates are outside the buffer's area.
247247 fn index_of_opt(&self, x: u16, y: u16) -> Option<usize> {
248248 if self.in_bounds(x, y) {
helix-vcs/Cargo.toml+12−11
@@ -1,24 +1,25 @@
11[package]
22name = "helix-vcs"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
7−categories = ["editor"]
8−repository = "https://github.com/helix-editor/helix"
9−homepage = "https://helix-editor.com"
3+version.workspace = true
4+authors.workspace = true
5+edition.workspace = true
6+license.workspace = true
7+rust-version.workspace = true
8+categories.workspace = true
9+repository.workspace = true
10+homepage.workspace = true
1011
1112# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1213
1314[dependencies]
14−helix-core = { version = "0.6", path = "../helix-core" }
15−helix-event = { version = "0.6", path = "../helix-event" }
15+helix-core = { path = "../helix-core" }
16+helix-event = { path = "../helix-event" }
1617
1718tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
1819parking_lot = "0.12"
1920arc-swap = { version = "1.6.0" }
2021
21−gix = { version = "0.55.0", default-features = false , optional = true }
22+gix = { version = "0.57.0", default-features = false , optional = true }
2223imara-diff = "0.1.5"
2324anyhow = "1"
2425
@@ -28,4 +29,4 @@ log = "0.4"
2829git = ["gix"]
2930
3031[dev-dependencies]
31−tempfile = "3.8"
32+tempfile = "3.8"
helix-vcs/src/git.rs+4−4
@@ -3,7 +3,7 @@ use arc_swap::ArcSwap;
33use std::path::Path;
44use std::sync::Arc;
55
6−use gix::objs::tree::EntryMode;
6+use gix::objs::tree::EntryKind;
77use gix::sec::trust::DefaultForLevel;
88use gix::{Commit, ObjectId, Repository, ThreadSafeRepository};
99
@@ -128,12 +128,12 @@ fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Resul
128128 let tree_entry = tree
129129 .lookup_entry_by_path(rel_path, &mut Vec::new())?
130130 .context("file is untracked")?;
131− match tree_entry.mode() {
131+ match tree_entry.mode().kind() {
132132 // not a file, everything is new, do not show diff
133− mode @ (EntryMode::Tree | EntryMode::Commit | EntryMode::Link) => {
133+ mode @ (EntryKind::Tree | EntryKind::Commit | EntryKind::Link) => {
134134 bail!("entry at {} is not a file but a {mode:?}", file.display())
135135 }
136136 // found a file
137− EntryMode::Blob | EntryMode::BlobExecutable => Ok(tree_entry.object_id()),
137+ EntryKind::Blob | EntryKind::BlobExecutable => Ok(tree_entry.object_id()),
138138 }
139139}
helix-view/Cargo.toml+19−17
@@ -1,32 +1,34 @@
11[package]
22name = "helix-view"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
73description = "UI abstractions for use in backends"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
4+version.workspace = true
5+authors.workspace = true
6+edition.workspace = true
7+license.workspace = true
8+rust-version.workspace = true
9+categories.workspace = true
10+repository.workspace = true
11+homepage.workspace = true
1112
1213[features]
1314default = []
1415term = ["crossterm"]
1516
1617[dependencies]
18+helix-core = { path = "../helix-core" }
19+helix-event = { path = "../helix-event" }
20+helix-loader = { path = "../helix-loader" }
21+helix-lsp = { path = "../helix-lsp" }
22+helix-dap = { path = "../helix-dap" }
23+helix-vcs = { path = "../helix-vcs" }
24+
1725bitflags = "2.4"
1826anyhow = "1"
19−helix-core = { version = "0.6", path = "../helix-core" }
20−helix-event = { version = "0.6", path = "../helix-event" }
21−helix-loader = { version = "0.6", path = "../helix-loader" }
22−helix-lsp = { version = "0.6", path = "../helix-lsp" }
23−helix-dap = { version = "0.6", path = "../helix-dap" }
2427crossterm = { version = "0.27", optional = true }
25−helix-vcs = { version = "0.6", path = "../helix-vcs" }
2628
2729# Conversion traits
28−once_cell = "1.18"
29−url = "2"
30+once_cell = "1.19"
31+url = "2.5.0"
3032
3133arc-swap = { version = "1.6.0" }
3234
@@ -43,12 +45,12 @@ serde_json = "1.0"
4345toml = "0.7"
4446log = "~0.4"
4547
46−which = "4.4"
48+which = "5.0.0"
4749parking_lot = "0.12.1"
4850
4951
5052[target.'cfg(windows)'.dependencies]
51−clipboard-win = { version = "4.5", features = ["std"] }
53+clipboard-win = { version = "5.0", features = ["std"] }
5254
5355[target.'cfg(unix)'.dependencies]
5456libc = "0.2"
helix-view/src/document.rs+57−20
@@ -1213,23 +1213,40 @@ impl Document {
12131213
12141214 let changes = transaction.changes();
12151215
1216− changes.update_positions(
1217− self.diagnostics
1218− .iter_mut()
1219− .map(|diagnostic| (&mut diagnostic.range.start, Assoc::After)),
1220− );
1221− changes.update_positions(
1222− self.diagnostics
1223− .iter_mut()
1224− .map(|diagnostic| (&mut diagnostic.range.end, Assoc::After)),
1225− );
1226− // map state.diagnostics over changes::map_pos too
1227− for diagnostic in &mut self.diagnostics {
1216+ // map diagnostics over changes too
1217+ changes.update_positions(self.diagnostics.iter_mut().map(|diagnostic| {
1218+ let assoc = if diagnostic.starts_at_word {
1219+ Assoc::BeforeWord
1220+ } else {
1221+ Assoc::After
1222+ };
1223+ (&mut diagnostic.range.start, assoc)
1224+ }));
1225+ changes.update_positions(self.diagnostics.iter_mut().map(|diagnostic| {
1226+ let assoc = if diagnostic.ends_at_word {
1227+ Assoc::AfterWord
1228+ } else {
1229+ Assoc::Before
1230+ };
1231+ (&mut diagnostic.range.end, assoc)
1232+ }));
1233+ self.diagnostics.retain_mut(|diagnostic| {
1234+ if diagnostic.range.start > diagnostic.range.end
1235+ || (!diagnostic.zero_width && diagnostic.range.start == diagnostic.range.end)
1236+ {
1237+ return false;
1238+ }
12281239 diagnostic.line = self.text.char_to_line(diagnostic.range.start);
1229− }
1240+ true
1241+ });
12301242
1231− self.diagnostics
1232− .sort_unstable_by_key(|diagnostic| diagnostic.range);
1243+ self.diagnostics.sort_unstable_by_key(|diagnostic| {
1244+ (
1245+ diagnostic.range,
1246+ diagnostic.severity,
1247+ diagnostic.language_server_id,
1248+ )
1249+ });
12331250
12341251 // Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place
12351252 let apply_inlay_hint_changes = |annotations: &mut Rc<[InlineAnnotation]>| {
@@ -1706,13 +1723,33 @@ impl Document {
17061723
17071724 pub fn replace_diagnostics(
17081725 &mut self,
1709− mut diagnostics: Vec<Diagnostic>,
1726+ diagnostics: impl IntoIterator<Item = Diagnostic>,
1727+ unchanged_sources: &[String],
17101728 language_server_id: usize,
17111729 ) {
1712− self.clear_diagnostics(language_server_id);
1713− self.diagnostics.append(&mut diagnostics);
1714− self.diagnostics
1715− .sort_unstable_by_key(|diagnostic| diagnostic.range);
1730+ if unchanged_sources.is_empty() {
1731+ self.clear_diagnostics(language_server_id);
1732+ } else {
1733+ self.diagnostics.retain(|d| {
1734+ if d.language_server_id != language_server_id {
1735+ return true;
1736+ }
1737+
1738+ if let Some(source) = &d.source {
1739+ unchanged_sources.contains(source)
1740+ } else {
1741+ false
1742+ }
1743+ });
1744+ }
1745+ self.diagnostics.extend(diagnostics);
1746+ self.diagnostics.sort_unstable_by_key(|diagnostic| {
1747+ (
1748+ diagnostic.range,
1749+ diagnostic.severity,
1750+ diagnostic.language_server_id,
1751+ )
1752+ });
17161753 }
17171754
17181755 pub fn clear_diagnostics(&mut self, language_server_id: usize) {
helix-view/src/editor.rs+31−11
@@ -42,10 +42,9 @@ use anyhow::{anyhow, bail, Error};
4242pub use helix_core::diagnostic::Severity;
4343use helix_core::{
4444 auto_pairs::AutoPairs,
45− syntax::{self, AutoPairConfig, SoftWrap},
46− Change, LineEnding, NATIVE_LINE_ENDING,
45+ syntax::{self, AutoPairConfig, IndentationHeuristic, SoftWrap},
46+ Change, LineEnding, Position, Selection, NATIVE_LINE_ENDING,
4747};
48−use helix_core::{Position, Selection};
4948use helix_dap as dap;
5049use helix_lsp::lsp;
5150
@@ -291,6 +290,11 @@ pub struct Config {
291290 pub insert_final_newline: bool,
292291 /// Enables smart tab
293292 pub smart_tab: Option<SmartTabConfig>,
293+ /// Draw border around popups.
294+ pub popup_border: PopupBorderConfig,
295+ /// Which indent heuristic to use when a new line is inserted
296+ #[serde(default)]
297+ pub indent_heuristic: IndentationHeuristic,
294298}
295299
296300#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
@@ -727,7 +731,7 @@ pub struct WhitespaceCharacters {
727731impl Default for WhitespaceCharacters {
728732 fn default() -> Self {
729733 Self {
730− space: '·', // U+00B7
734+ space: '·', // U+00B7
731735 nbsp: '⍽', // U+237D
732736 tab: '→', // U+2192
733737 newline: '⏎', // U+23CE
@@ -755,12 +759,13 @@ impl Default for IndentGuidesConfig {
755759}
756760
757761/// Line ending configuration.
758−#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
762+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
759763#[serde(rename_all = "lowercase")]
760764pub enum LineEndingConfig {
761765 /// The platform's native line ending.
762766 ///
763767 /// `crlf` on Windows, otherwise `lf`.
768+ #[default]
764769 Native,
765770 /// Line feed.
766771 LF,
@@ -777,12 +782,6 @@ pub enum LineEndingConfig {
777782 Nel,
778783}
779784
780−impl Default for LineEndingConfig {
781− fn default() -> Self {
782− LineEndingConfig::Native
783− }
784−}
785−
786785impl From<LineEndingConfig> for LineEnding {
787786 fn from(line_ending: LineEndingConfig) -> Self {
788787 match line_ending {
@@ -799,6 +798,15 @@ impl From<LineEndingConfig> for LineEnding {
799798 }
800799}
801800
801+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
802+#[serde(rename_all = "kebab-case")]
803+pub enum PopupBorderConfig {
804+ None,
805+ All,
806+ Popup,
807+ Menu,
808+}
809+
802810impl Default for Config {
803811 fn default() -> Self {
804812 Self {
@@ -846,6 +854,8 @@ impl Default for Config {
846854 default_line_ending: LineEndingConfig::default(),
847855 insert_final_newline: true,
848856 smart_tab: Some(SmartTabConfig::default()),
857+ popup_border: PopupBorderConfig::None,
858+ indent_heuristic: IndentationHeuristic::default(),
849859 }
850860 }
851861}
@@ -1065,6 +1075,16 @@ impl Editor {
10651075 }
10661076 }
10671077
1078+ pub fn popup_border(&self) -> bool {
1079+ self.config().popup_border == PopupBorderConfig::All
1080+ || self.config().popup_border == PopupBorderConfig::Popup
1081+ }
1082+
1083+ pub fn menu_border(&self) -> bool {
1084+ self.config().popup_border == PopupBorderConfig::All
1085+ || self.config().popup_border == PopupBorderConfig::Menu
1086+ }
1087+
10681088 pub fn apply_motion<F: Fn(&mut Self) + 'static>(&mut self, motion: F) {
10691089 motion(self);
10701090 self.last_motion = Some(Box::new(motion));
helix-view/src/gutter.rs+1−2
@@ -9,8 +9,7 @@ use crate::{
99};
1010
1111fn count_digits(n: usize) -> usize {
12− // TODO: use checked_log10 when MSRV reaches 1.67
13− std::iter::successors(Some(n), |&n| (n >= 10).then_some(n / 10)).count()
12+ (usize::checked_ilog10(n).unwrap_or(0) + 1) as usize
1413}
1514
1615pub type GutterFn<'doc> = Box<dyn FnMut(usize, bool, bool, &mut String) -> Option<Style> + 'doc>;
helix-view/src/handlers/dap.rs+1−3
@@ -369,9 +369,7 @@ impl Editor {
369369 {
370370 Ok(process) => process,
371371 Err(err) => {
372− // TODO replace the pretty print {:?} with a regular format {}
373− // when the MSRV is raised to 1.60.0
374− self.set_error(format!("Error starting external terminal: {:?}", err));
372+ self.set_error(format!("Error starting external terminal: {}", err));
375373 return true;
376374 }
377375 };
languages.toml+100−8
@@ -3,6 +3,7 @@
33
44[language-server]
55
6+als = { command = "als" }
67awk-language-server = { command = "awk-language-server" }
78bash-language-server = { command = "bash-language-server", args = ["start"] }
89bass = { command = "bass", args = ["--lsp"] }
@@ -47,6 +48,7 @@ mint = { command = "mint", args = ["ls"] }
4748nil = { command = "nil" }
4849nimlangserver = { command = "nimlangserver" }
4950nls = { command = "nls" }
51+nu-lsp = { command = "nu", args = [ "--lsp" ] }
5052ocamllsp = { command = "ocamllsp" }
5153ols = { command = "ols", args = [] }
5254omnisharp = { command = "OmniSharp", args = [ "--languageserver" ] }
@@ -189,6 +191,7 @@ auto-format = true
189191comment-token = "//"
190192language-servers = [ "rust-analyzer" ]
191193indent = { tab-width = 4, unit = " " }
194+persistent-diagnostic-sources = ["rustc", "clippy"]
192195
193196[language.auto-pairs]
194197'(' = ')'
@@ -344,7 +347,8 @@ file-types = [
344347 "jsonld",
345348 ".vuerc",
346349 "composer.lock",
347− ".watchmanconfig"
350+ ".watchmanconfig",
351+ "avsc"
348352]
349353language-servers = [ "vscode-json-language-server" ]
350354auto-format = true
@@ -696,7 +700,7 @@ indent = { tab-width = 4, unit = " " }
696700
697701[[grammar]]
698702name = "python"
699−source = { git = "https://github.com/tree-sitter/tree-sitter-python", rev = "de221eccf9a221f5b85474a553474a69b4b5784d" }
703+source = { git = "https://github.com/tree-sitter/tree-sitter-python", rev = "4bfdd9033a2225cc95032ce77066b7aeca9e2efc" }
700704
701705[[language]]
702706name = "nickel"
@@ -953,6 +957,19 @@ indent = { tab-width = 2, unit = " " }
953957name = "java"
954958source = { git = "https://github.com/tree-sitter/tree-sitter-java", rev = "09d650def6cdf7f479f4b78f595e9ef5b58ce31e" }
955959
960+[[language]]
961+name = "smali"
962+scope = "source.smali"
963+injection-regex = "smali"
964+file-types = ["smali"]
965+comment-token = "#"
966+roots = []
967+indent = { tab-width = 4, unit = " " }
968+
969+[[grammar]]
970+name = "smali"
971+source = { git = "https://github.com/amaanq/tree-sitter-smali", rev = "5ae51e15c4d1ac93cba6127caf3d1f0a072c140c" }
972+
956973[[language]]
957974name = "ledger"
958975scope = "source.ledger"
@@ -1522,7 +1539,7 @@ indent = { tab-width = 2, unit = " " }
15221539
15231540[[grammar]]
15241541name = "rescript"
1525−source = { git = "https://github.com/jaredramirez/tree-sitter-rescript", rev = "65609807c628477f3b94052e7ef895885ac51c3c" }
1542+source = { git = "https://github.com/jaredramirez/tree-sitter-rescript", rev = "467dcf99f68c47823d7b378779a6b282d7ef9782" }
15261543
15271544[[language]]
15281545name = "erlang"
@@ -1623,7 +1640,7 @@ auto-format = true
16231640
16241641[[grammar]]
16251642name = "gleam"
1626−source = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "a59aadf3d7c11702cad244e7cd6b67b34ca9c16a" }
1643+source = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "b2afa4fd6bb41a7bf912b034c653c90af7ae5122" }
16271644
16281645[[language]]
16291646name = "ron"
@@ -1667,6 +1684,7 @@ source = { git = "https://github.com/r-lib/tree-sitter-r", rev = "cc04302e1bff76
16671684[[language]]
16681685name = "rmarkdown"
16691686scope = "source.rmd"
1687+language-id = "rmd"
16701688injection-regex = "(r|R)md"
16711689file-types = ["rmd", "Rmd"]
16721690indent = { tab-width = 2, unit = " " }
@@ -1783,6 +1801,7 @@ file-types = ["nu"]
17831801shebangs = ["nu"]
17841802comment-token = "#"
17851803indent = { tab-width = 2, unit = " " }
1804+language-servers = [ "nu-lsp" ]
17861805
17871806[[grammar]]
17881807name = "nu"
@@ -2218,7 +2237,7 @@ formatter = { command = "dfmt" }
22182237
22192238[[grammar]]
22202239name = "d"
2221−source = { git = "https://github.com/gdamore/tree-sitter-d", rev="601c4a1e8310fb2f3c43fa8a923d0d27497f3c04" }
2240+source = { git = "https://github.com/gdamore/tree-sitter-d", rev = "5566f8ce8fc24186fad06170bbb3c8d97c935d74" }
22222241
22232242[[language]]
22242243name = "vhs"
@@ -2301,7 +2320,9 @@ file-types = [
23012320 "xliff",
23022321 "xpdl",
23032322 "xul",
2304− "xoml"
2323+ "xoml",
2324+ "musicxml",
2325+ "glif"
23052326]
23062327indent = { tab-width = 2, unit = " " }
23072328
@@ -2359,7 +2380,7 @@ source = { git = "https://github.com/hh9527/tree-sitter-wit", rev = "c917790ab9a
23592380[[language]]
23602381name = "env"
23612382scope = "source.env"
2362−file-types = [".env", ".env.local", ".env.development", ".env.production", ".env.dist", ".envrc"]
2383+file-types = [".env", ".env.local", ".env.development", ".env.production", ".env.dist", ".envrc", ".envrc.local", ".envrc.private"]
23632384injection-regex = "env"
23642385comment-token = "#"
23652386indent = { tab-width = 4, unit = "\t" }
@@ -2693,6 +2714,7 @@ file-types = [ "cabal" ]
26932714roots = ["cabal.project", "Setup.hs"]
26942715indent = { tab-width = 2, unit = " " }
26952716comment-token = "--"
2717+language-servers = [ "haskell-language-server" ]
26962718
26972719[[language]]
26982720name = "hurl"
@@ -2740,6 +2762,20 @@ indent = { tab-width = 4, unit = "\t" }
27402762name = "just"
27412763source = { git = "https://github.com/IndianBoy42/tree-sitter-just", rev = "8af0aab79854aaf25b620a52c39485849922f766" }
27422764
2765+[[language]]
2766+name = "gn"
2767+scope = "source.gn"
2768+injection-regex = "gn"
2769+file-types = ["gn", "gni"]
2770+roots = []
2771+comment-token = "#"
2772+indent = { tab-width = 2, unit = " " }
2773+formatter = { command = "gn", args = ["format", "--stdin"] }
2774+
2775+[[grammar]]
2776+name = "gn"
2777+source = { git = "https://github.com/willcassella/tree-sitter-gn", rev = "e18d6e36a79b20dafb58f19d407bd38b0e60260e" }
2778+
27432779[[language]]
27442780name = "blueprint"
27452781scope = "source.blueprint"
@@ -2900,7 +2936,33 @@ file-types = ["gmi"]
29002936
29012937[[grammar]]
29022938name = "gemini"
2903−source = { git = "https://git.sr.ht/~sfr/tree-sitter-gemini", rev = "3cc5e4bdf572d5df4277fc2e54d6299bd59a54b3" }
2939+source = { git = "https://git.sr.ht/~nbsp/tree-sitter-gemini", rev = "3cc5e4bdf572d5df4277fc2e54d6299bd59a54b3" }
2940+
2941+[[language]]
2942+name = "agda"
2943+scope = "source.agda"
2944+injection-regex = "agda"
2945+file-types = ["agda"]
2946+roots = []
2947+comment-token = "--"
2948+# language-servers = [ "als" ]
2949+# the agda language server is of questionable functionality.
2950+auto-format = false
2951+indent = { tab-width = 2, unit = " " }
2952+
2953+[language.auto-pairs]
2954+'"' = '"'
2955+"'" = "'"
2956+'{' = '}'
2957+'(' = ')'
2958+'[' = ']'
2959+
2960+# [language.debugger]
2961+# ?? can this be used for proof assistant support? explore
2962+
2963+[[grammar]]
2964+name = "agda"
2965+source = { git = "https://github.com/tree-sitter/tree-sitter-agda", rev = "c21c3a0f996363ed17b8ac99d827fe5a4821f217" }
29042966
29052967[[language]]
29062968name = "templ"
@@ -2914,3 +2976,33 @@ language-servers = [ "templ" ]
29142976[[grammar]]
29152977name = "templ"
29162978source = { git = "https://github.com/vrischmann/tree-sitter-templ", rev = "ea56ac0655243490a4929a988f4eaa91dfccc995" }
2979+
2980+[[language]]
2981+name = "dbml"
2982+scope = "source.dbml"
2983+injection-regex = "dbml"
2984+file-types = ["dbml"]
2985+comment-token = ";"
2986+indent = { tab-width = 2, unit = " " }
2987+
2988+[[grammar]]
2989+name = "dbml"
2990+source = { git = "https://github.com/dynamotn/tree-sitter-dbml", rev = "2e2fa5640268c33c3d3f27f7e676f631a9c68fd9" }
2991+
2992+[[language]]
2993+name = "log"
2994+scope = "source.log"
2995+file-types = ["log"]
2996+
2997+[[grammar]]
2998+name = "log"
2999+source = { git = "https://github.com/Tudyx/tree-sitter-log", rev = "62cfe307e942af3417171243b599cc7deac5eab9" }
3000+
3001+[[language]]
3002+name = "janet"
3003+scope = "source.janet"
3004+injection-regex = "janet"
3005+file-types = ["janet"]
3006+comment-token = "#"
3007+indent = { tab-width = 2, unit = " " }
3008+grammar = "clojure"
logo.svg+1−1
@@ -1 +1 @@
1−<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" viewBox="663.38 37.57 575.35 903.75"> <g transform="matrix(1,0,0,1,-31352.7,-1817.25)"> <g transform="matrix(1,0,0,1,31062.7,-20.8972)"> <g transform="matrix(1,0,0,1,-130.173,0.00185558)"> <path d="M1083.58,1875.72L1635.06,2194.12C1649.8,2202.63 1658.88,2218.37 1658.88,2235.39C1658.88,2264.98 1658.88,2311.74 1658.88,2341.33C1658.88,2349.84 1656.61,2358.03 1652.5,2365.16C1652.5,2365.16 1214.7,2112.4 1107.2,2050.33C1092.58,2041.89 1083.58,2026.29 1083.58,2009.41C1083.58,1963.5 1083.58,1875.72 1083.58,1875.72Z" style="fill:#706bc8;"></path> </g> <g transform="matrix(1,0,0,1,-130.173,0.00185558)"> <path d="M1635.26,2604.84C1649.88,2613.28 1658.88,2628.87 1658.88,2645.75C1658.88,2691.67 1658.88,2779.44 1658.88,2779.44L1107.41,2461.05C1092.66,2452.53 1083.58,2436.8 1083.58,2419.78C1083.58,2390.19 1083.58,2343.42 1083.58,2313.84C1083.58,2305.32 1085.85,2297.13 1089.96,2290.01C1089.96,2290.01 1527.76,2542.77 1635.26,2604.84Z" style="fill:#55c5e4;"></path> </g> <g transform="matrix(1,0,0,1,216.062,984.098)"> <path d="M790.407,1432.56C785.214,1435.55 780.717,1439.9 777.509,1445.46C767.862,1462.16 773.473,1483.76 790.004,1493.59L789.998,1493.59L761.173,1476.95C746.427,1468.44 737.344,1452.71 737.344,1435.68C737.344,1406.09 737.344,1359.33 737.344,1329.74C737.344,1312.71 746.427,1296.98 761.173,1288.47L1259.59,1000.74L1259.83,1000.6C1264.92,997.617 1269.33,993.314 1272.48,987.844C1282.13,971.136 1276.52,949.544 1259.99,939.707L1260,939.707L1288.82,956.349C1303.57,964.862 1312.65,980.595 1312.65,997.622C1312.65,1027.21 1312.65,1073.97 1312.65,1103.56C1312.65,1120.59 1303.57,1136.32 1288.82,1144.83L1259.19,1161.94L1259.59,1161.68L790.407,1432.56Z" style="fill:#84ddea;"></path> </g> <g transform="matrix(1,0,0,1,216.062,984.098)"> <path d="M790.407,1686.24C785.214,1689.23 780.717,1693.58 777.509,1699.13C767.862,1715.84 773.473,1737.43 790.004,1747.27L789.998,1747.27L761.173,1730.63C746.427,1722.12 737.344,1706.38 737.344,1689.36C737.344,1659.77 737.344,1613.01 737.344,1583.42C737.344,1566.39 746.427,1550.66 761.173,1542.15L1259.59,1254.42L1259.83,1254.28C1264.92,1251.29 1269.33,1246.99 1272.48,1241.52C1282.13,1224.81 1276.52,1203.22 1259.99,1193.38L1260,1193.38L1288.82,1210.03C1303.57,1218.54 1312.65,1234.27 1312.65,1251.3C1312.65,1280.89 1312.65,1327.65 1312.65,1357.24C1312.65,1374.26 1303.57,1390 1288.82,1398.51L1259.19,1415.61L1259.59,1415.36L790.407,1686.24Z" style="fill:#997bc8;"></path></g></g></g> </svg>
1+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" viewBox="663.38 37.57 575.35 903.75"><g transform="matrix(1,0,0,1,-31352.7,-1817.25)"><g transform="matrix(1,0,0,1,31062.7,-20.8972)"><g transform="matrix(1,0,0,1,-130.173,0.00185558)"><path style="fill:#706bc8;" d="M1083.58,1875.72L1635.06,2194.12C1649.8,2202.63 1658.88,2218.37 1658.88,2235.39C1658.88,2264.98 1658.88,2311.74 1658.88,2341.33C1658.88,2349.84 1656.61,2358.03 1652.5,2365.16C1652.5,2365.16 1214.7,2112.4 1107.2,2050.33C1092.58,2041.89 1083.58,2026.29 1083.58,2009.41C1083.58,1963.5 1083.58,1875.72 1083.58,1875.72Z"></path></g><g transform="matrix(1,0,0,1,-130.173,0.00185558)"><path style="fill:#55c5e4;" d="M1635.26,2604.84C1649.88,2613.28 1658.88,2628.87 1658.88,2645.75C1658.88,2691.67 1658.88,2779.44 1658.88,2779.44L1107.41,2461.05C1092.66,2452.53 1083.58,2436.8 1083.58,2419.78C1083.58,2390.19 1083.58,2343.42 1083.58,2313.84C1083.58,2305.32 1085.85,2297.13 1089.96,2290.01C1089.96,2290.01 1527.76,2542.77 1635.26,2604.84Z"></path></g><g transform="matrix(1,0,0,1,216.062,984.098)"><path style="fill:#84ddea;" d="M790.407,1432.56C785.214,1435.55 780.717,1439.9 777.509,1445.46C767.862,1462.16 773.473,1483.76 790.004,1493.59L789.998,1493.59L761.173,1476.95C746.427,1468.44 737.344,1452.71 737.344,1435.68C737.344,1406.09 737.344,1359.33 737.344,1329.74C737.344,1312.71 746.427,1296.98 761.173,1288.47L1259.59,1000.74L1259.83,1000.6C1264.92,997.617 1269.33,993.314 1272.48,987.844C1282.13,971.136 1276.52,949.544 1259.99,939.707L1260,939.707L1288.82,956.349C1303.57,964.862 1312.65,980.595 1312.65,997.622C1312.65,1027.21 1312.65,1073.97 1312.65,1103.56C1312.65,1120.59 1303.57,1136.32 1288.82,1144.83L1259.19,1161.94L1259.59,1161.68L790.407,1432.56Z"></path></g><g transform="matrix(1,0,0,1,216.062,984.098)"><path style="fill:#997bc8;" d="M790.407,1686.24C785.214,1689.23 780.717,1693.58 777.509,1699.13C767.862,1715.84 773.473,1737.43 790.004,1747.27L789.998,1747.27L761.173,1730.63C746.427,1722.12 737.344,1706.38 737.344,1689.36C737.344,1659.77 737.344,1613.01 737.344,1583.42C737.344,1566.39 746.427,1550.66 761.173,1542.15L1259.59,1254.42L1259.83,1254.28C1264.92,1251.29 1269.33,1246.99 1272.48,1241.52C1282.13,1224.81 1276.52,1203.22 1259.99,1193.38L1260,1193.38L1288.82,1210.03C1303.57,1218.54 1312.65,1234.27 1312.65,1251.3C1312.65,1280.89 1312.65,1327.65 1312.65,1357.24C1312.65,1374.26 1303.57,1390 1288.82,1398.51L1259.19,1415.61L1259.59,1415.36L790.407,1686.24Z"></path></g></g></g></svg>
runtime/queries/_typescript/highlights.scm+3−3
@@ -39,11 +39,11 @@
3939 (identifier) @variable.parameter))
4040
4141; (p?: t)
42−; (p?: t = 1) // Invalid but still posible to hihglight.
42+; (p?: t = 1) // Invalid but still possible to highlight.
4343(optional_parameter
4444 (identifier) @variable.parameter)
4545
46−; (...p?: t) // Invalid but still posible to hihglight.
46+; (...p?: t) // Invalid but still possible to highlight.
4747(optional_parameter
4848 (rest_pattern
4949 (identifier) @variable.parameter))
@@ -59,7 +59,7 @@
5959 (pair_pattern
6060 value: (identifier) @variable.parameter)))
6161
62−; ([ p ]?: t[]) // Invalid but still posible to hihglight.
62+; ([ p ]?: t[]) // Invalid but still possible to highlight.
6363(optional_parameter
6464 (array_pattern
6565 (identifier) @variable.parameter))
runtime/queries/agda/highlights.scmnew file+124
@@ -0,0 +1,124 @@
1+;; Punctuation
2+[ "." ";" ":"] @punctuation.delimiter
3+[ "(" ")" "{" "}" ] @punctuation.bracket
4+
5+;; Constants
6+(integer) @constant.numeric.integer
7+; (float) @constant.numeric.float
8+(literal) @string
9+
10+;; Pragmas and comments
11+(comment) @comment
12+(pragma) @attribute
13+(macro) @function.macro
14+
15+;; Imports
16+(module_name) @namespace
17+(import_directive (id) @namespace)
18+[(module) (import) (open)] @keyword.control.import
19+
20+;; Types
21+(typed_binding (expr) @type)
22+(record (expr) @type)
23+(data (expr) @type)
24+(signature (expr) @type)
25+(function (rhs (expr) @type))
26+; todo: these are too general. ideally, any nested (atom)
27+; https://github.com/tree-sitter/tree-sitter/issues/880
28+
29+;; Variables
30+(untyped_binding (atom) @variable)
31+(typed_binding (atom) @variable)
32+(field_name) @variable.other.member
33+
34+;; Functions
35+(function_name) @function
36+;(function (lhs
37+; . (atom) @function
38+; (atom) @variable.parameter))
39+; todo: currently fails to parse, upstream tree-sitter bug
40+
41+;; Data
42+[(data_name) (record_name)] @constructor
43+((atom) @constant.builtin.boolean
44+ (#any-of? @constant.builtin.boolean "true" "false" "True" "False"))
45+
46+"Set" @type.builtin
47+
48+; postulate
49+; type_signature
50+; pattern
51+; id
52+; bid
53+; typed_binding
54+; primitive
55+; private
56+; record_signature
57+; record_assignments
58+; field_assignment
59+; module_assignment
60+; renaming
61+; import_directive
62+; lambda
63+; let
64+; instance
65+; generalize
66+; record
67+; fields
68+; syntax
69+; hole_name
70+; data_signature
71+
72+;; Keywords
73+[
74+ "where"
75+ "data"
76+ "rewrite"
77+ "postulate"
78+ "public"
79+ "private"
80+ "tactic"
81+ "Prop"
82+ "quote"
83+ "renaming"
84+ "in"
85+ "hiding"
86+ "constructor"
87+ "abstract"
88+ "let"
89+ "field"
90+ "mutual"
91+ "infix"
92+ "infixl"
93+ "infixr"
94+ "record"
95+ "overlap"
96+ "instance"
97+ "do"
98+] @keyword
99+
100+[
101+ "="
102+] @operator
103+
104+; = | -> : ? \ .. ... λ ∀ →
105+; (_LAMBDA) (_FORALL) (_ARROW)
106+; "coinductive"
107+; "eta-equality"
108+; "field"
109+; "inductive"
110+; "interleaved"
111+; "macro"
112+; "no-eta-equality"
113+; "pattern"
114+; "primitive"
115+; "quoteTerm"
116+; "rewrite"
117+; "syntax"
118+; "unquote"
119+; "unquoteDecl"
120+; "unquoteDef"
121+; "using"
122+; "variable"
123+; "with"
124+
runtime/queries/c/indents.scm+13
@@ -36,3 +36,16 @@
3636(parameter_list
3737 . (parameter_declaration) @anchor
3838 (#set! "scope" "tail")) @align
39+(argument_list
40+ . (_) @anchor
41+ (#set! "scope" "tail")) @align
42+; These are a bit opinionated since some people just indent binary/ternary expressions spanning multiple lines.
43+; Since they are only triggered when a newline is inserted into an already complete binary/ternary expression,
44+; this should happen rarely, so it is not a big deal either way.
45+; Additionally, adding these queries has the advantage of preventing such continuation lines from being used
46+; as the baseline when the `hybrid` indent heuristic is used (which is desirable since their indentation is so inconsistent).
47+(binary_expression
48+ (#set! "scope" "tail")) @anchor @align
49+(conditional_expression
50+ "?" @anchor
51+ (#set! "scope" "tail")) @align
runtime/queries/dbml/highlights.scmnew file+44
@@ -0,0 +1,44 @@
1+
2+; comments highlighting
3+(comment) @comment
4+
5+; keyword highlighting
6+(keyword_def) @keyword
7+(keyword_enum) @keyword
8+(keyword_ref) @keyword
9+
10+; identify blocks and definitions
11+(definition) @function
12+
13+; for identifiers
14+(identifier) @variable
15+(type) @keyword
16+
17+; Highlight special types for database/data types
18+("Project" ) @type
19+("Table" ) @type
20+("TableGroup" ) @type
21+("database_type" ) @variable
22+
23+; string and number constants
24+("'''") @constant.character.escape
25+(string) @string
26+(number) @constant.numeric
27+
28+; brackets
29+[
30+ "("
31+ ")"
32+ "{"
33+ "}"
34+ "["
35+ "]"
36+] @punctuation.bracket
37+
38+; brackets
39+[
40+ ":"
41+ "."
42+ ","
43+] @punctuation.delimiter
44+
runtime/queries/gleam/highlights.scm+16−5
@@ -21,6 +21,8 @@
2121
2222; Functions
2323(unqualified_import (identifier) @function)
24+(unqualified_import "type" (type_identifier) @type)
25+(unqualified_import (type_identifier) @constructor)
2426(function
2527 name: (identifier) @function)
2628(external_function
@@ -43,6 +45,13 @@
4345(tuple_access
4446 index: (integer) @variable.other.member)
4547
48+; Attributes
49+(attribute
50+ "@" @attribute
51+ name: (identifier) @attribute)
52+
53+(attribute_value (identifier) @constant)
54+
4655; Type names
4756(remote_type_identifier) @type
4857(type_identifier) @type
@@ -60,10 +69,6 @@
6069(identifier) @variable
6170(discard) @comment.unused
6271
63−; Operators
64−(binary_expression
65− operator: _ @operator)
66−
6772; Keywords
6873[
6974 (visibility_modifier) ; "pub"
@@ -72,6 +77,7 @@
7277 "assert"
7378 "case"
7479 "const"
80+ ; DEPRECATED: 'external' was removed in v0.30.
7581 "external"
7682 "fn"
7783 "if"
@@ -79,11 +85,16 @@
7985 "let"
8086 "panic"
8187 "todo"
82− "try"
8388 "type"
8489 "use"
8590] @keyword
8691
92+; Operators
93+(binary_expression
94+ operator: _ @operator)
95+(boolean_negation "!" @operator)
96+(integer_negation "-" @operator)
97+
8798; Punctuation
8899[
89100 "("
runtime/queries/gn/highlights.scmnew file+42
@@ -0,0 +1,42 @@
1+; Copyright (C) 2021 Will Cassella (github@willcassella.com)
2+;
3+; Licensed under the Apache License, Version 2.0 (the "License");
4+; you may not use this file except in compliance with the License.
5+; You may obtain a copy of the License at
6+;
7+; http://www.apache.org/licenses/LICENSE-2.0
8+;
9+; Unless required by applicable law or agreed to in writing, software
10+; distributed under the License is distributed on an "AS IS" BASIS,
11+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+; See the License for the specific language governing permissions and
13+; limitations under the License.
14+
15+(identifier) @variable.builtin
16+
17+(scope_access field: (_) @variable.other.member)
18+
19+(call target: (_) @function)
20+
21+[ "if" "else" ] @keyword.control.conditional
22+
23+[
24+ (assign_op)
25+ (arithmetic_binary_op)
26+ (comparison_binary_op)
27+ (equivalence_binary_op)
28+ (logical_and_binary_op)
29+ (logical_or_binary_op)
30+ (negation_unary_op)
31+] @operator
32+
33+[ "(" ")" "[" "]" "{" "}" ] @punctuation.bracket
34+[ "." "," ] @punctuation.delimiter
35+
36+(string) @string
37+(string_escape) @constant.character.escape
38+(string_expansion [ "$" "${" "}" ] @constant.character.escape)
39+[ (integer) (hex) ] @constant.numeric
40+(boolean) @constant.builtin.boolean
41+
42+(comment) @comment
runtime/queries/gn/injections.scmnew file+15
@@ -0,0 +1,15 @@
1+; Copyright (C) 2021 Will Cassella (github@willcassella.com)
2+;
3+; Licensed under the Apache License, Version 2.0 (the "License");
4+; you may not use this file except in compliance with the License.
5+; You may obtain a copy of the License at
6+;
7+; http://www.apache.org/licenses/LICENSE-2.0
8+;
9+; Unless required by applicable law or agreed to in writing, software
10+; distributed under the License is distributed on an "AS IS" BASIS,
11+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+; See the License for the specific language governing permissions and
13+; limitations under the License.
14+
15+((comment) @injection.content (#set! injection.language "comment"))
runtime/queries/gn/locals.scmnew file+16
@@ -0,0 +1,16 @@
1+; Copyright (C) 2021 Will Cassella (github@willcassella.com)
2+;
3+; Licensed under the Apache License, Version 2.0 (the "License");
4+; you may not use this file except in compliance with the License.
5+; You may obtain a copy of the License at
6+;
7+; http://www.apache.org/licenses/LICENSE-2.0
8+;
9+; Unless required by applicable law or agreed to in writing, software
10+; distributed under the License is distributed on an "AS IS" BASIS,
11+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+; See the License for the specific language governing permissions and
13+; limitations under the License.
14+
15+(source_file) @local.scope
16+(assignment target: (identifier) @local.definition)
runtime/queries/janet/highlights.scmnew file+1
@@ -0,0 +1 @@
1+; inherits: clojure
runtime/queries/log/highlights.scmnew file+10
@@ -0,0 +1,10 @@
1+(trace) @comment
2+(debug) @hint
3+(info) @info
4+(warn) @warning
5+(error) @error
6+(year_month_day) @keyword
7+(time) @constant
8+(string_literal) @string
9+(number) @constant.numeric
10+(constant) @constant.builtin
runtime/queries/python/highlights.scm+6
@@ -134,14 +134,17 @@
134134 "//="
135135 "/="
136136 "&"
137+ "&="
137138 "%"
138139 "%="
139140 "^"
141+ "^="
140142 "+"
141143 "->"
142144 "+="
143145 "<"
144146 "<<"
147+ "<<="
145148 "<="
146149 "<>"
147150 "="
@@ -150,8 +153,11 @@
150153 ">"
151154 ">="
152155 ">>"
156+ ">>="
153157 "|"
158+ "|="
154159 "~"
160+ "@="
155161] @operator
156162
157163[
runtime/queries/smali/folds.scmnew file+12
@@ -0,0 +1,12 @@
1+[
2+ (annotation_directive)
3+ (array_data_directive)
4+ (field_definition)
5+ (method_definition)
6+ (packed_switch_directive)
7+ (param_directive)
8+ (parameter_directive)
9+ (sparse_switch_directive)
10+ (subannotation_directive)
11+ (list)
12+] @fold
runtime/queries/smali/highlights.scmnew file+216
@@ -0,0 +1,216 @@
1+; Types
2+
3+(class_identifier
4+ (identifier) @type)
5+
6+(primitive_type) @type.builtin
7+
8+((class_identifier
9+ . (identifier) @_first @type.builtin
10+ (identifier) @type.builtin)
11+ (#match? @_first "^(android|dalvik|java|kotlinx)$"))
12+
13+((class_identifier
14+ . (identifier) @_first @type.builtin
15+ . (identifier) @_second @type.builtin
16+ (identifier) @type.builtin)
17+ (#eq? @_first "com")
18+ (#match? @_second "^(android|google)$"))
19+
20+; Methods
21+
22+(method_definition
23+ (method_signature (method_identifier) @function.method))
24+
25+(expression
26+ (opcode) @_invoke
27+ (body
28+ (full_method_signature
29+ (method_signature (method_identifier) @function.method)))
30+ (#match? @_invoke "^invoke"))
31+
32+(expression
33+ (opcode) @_field_access
34+ (body
35+ (field_identifier) @variable.other.member)
36+ (#match? @_field_access "^[is](get|put)-"))
37+
38+(method_handle
39+ (full_method_signature
40+ (method_signature (method_identifier) @function.method)))
41+
42+(custom_invoke
43+ . (identifier) @function.method
44+ (method_signature (method_identifier) @function.method))
45+
46+(annotation_value
47+ (body
48+ (method_signature (method_identifier) @function.method)))
49+
50+(annotation_value
51+ (body
52+ (full_method_signature
53+ (method_signature (method_identifier) @function.method))))
54+
55+(field_definition
56+ (body
57+ (method_signature (method_identifier) @function.method)))
58+
59+(field_definition
60+ (body
61+ (full_method_signature
62+ (method_signature (method_identifier) @function.method))))
63+
64+((method_identifier) @constructor
65+ (#match? @constructor "^(<init>|<clinit>)$"))
66+
67+"constructor" @constructor
68+
69+; Fields
70+
71+[
72+ (field_identifier)
73+ (annotation_key)
74+] @variable.other.member
75+
76+((field_identifier) @constant
77+ (#match? @constant "^[%u_]*$"))
78+
79+; Variables
80+
81+(variable) @variable.builtin
82+
83+(local_directive
84+ (identifier) @variable)
85+
86+; Parameters
87+
88+(parameter) @variable.parameter
89+(param_identifier) @variable.parameter
90+
91+; Labels
92+
93+[
94+ (label)
95+ (jmp_label)
96+] @label
97+
98+; Operators
99+
100+; (opcode) @keyword.operator
101+
102+((opcode) @keyword.control.return
103+ (#match? @keyword.control.return "^return"))
104+
105+((opcode) @keyword.control.conditional
106+ (#match? @keyword.control.conditional "^if"))
107+
108+((opcode) @keyword.control.conditional
109+ (#match? @keyword.control.conditional "^cmp"))
110+
111+((opcode) @keyword.control.exception
112+ (#match? @keyword.control.exception "^throw"))
113+
114+[
115+ "="
116+ ".."
117+] @operator
118+
119+; Keywords
120+
121+[
122+ ".class"
123+ ".super"
124+ ".implements"
125+ ".field"
126+ ".end field"
127+ ".annotation"
128+ ".end annotation"
129+ ".subannotation"
130+ ".end subannotation"
131+ ".param"
132+ ".end param"
133+ ".parameter"
134+ ".end parameter"
135+ ".local"
136+ ".end local"
137+ ".restart local"
138+ ".registers"
139+ ".packed-switch"
140+ ".end packed-switch"
141+ ".sparse-switch"
142+ ".end sparse-switch"
143+ ".array-data"
144+ ".end array-data"
145+ ".enum"
146+ (prologue_directive)
147+ (epilogue_directive)
148+] @keyword
149+
150+[
151+ ".source"
152+] @keyword.directive
153+
154+[
155+ ".method"
156+ ".end method"
157+] @keyword.function
158+
159+[
160+ ".catch"
161+ ".catchall"
162+] @keyword.control.exception
163+
164+; Literals
165+
166+(string) @string
167+(source_directive (string "\"" _ @string.special.url "\""))
168+(escape_sequence) @constant.character.escape
169+
170+(character) @constant.character
171+
172+"L" @punctuation
173+
174+(line_directive (number) @comment) @comment
175+(".locals" (number) @comment) @comment
176+
177+(number) @constant.numeric.integer
178+
179+[
180+ (float)
181+ (NaN)
182+ (Infinity)
183+] @constant.numeric.float
184+
185+(boolean) @constant.builtin.boolean
186+
187+(null) @constant.builtin
188+
189+; Misc
190+
191+(annotation_visibility) @keyword.storage.modifier
192+
193+(access_modifier) @keyword.storage.type
194+
195+(array_type
196+ "[" @punctuation.special)
197+
198+["{" "}"] @punctuation.bracket
199+
200+["(" ")"] @punctuation.bracket
201+
202+[
203+ "->"
204+ ","
205+ ":"
206+ ";"
207+ "@"
208+ "/"
209+] @punctuation.delimiter
210+
211+; Comments
212+
213+(comment) @comment
214+
215+(class_definition
216+ (comment) @comment.block.documentation)
runtime/queries/smali/indents.scmnew file+31
@@ -0,0 +1,31 @@
1+[
2+ (annotation_directive)
3+ (array_data_directive)
4+ (field_definition)
5+ (method_definition)
6+ (packed_switch_directive)
7+ (param_directive)
8+ (parameter_directive)
9+ (sparse_switch_directive)
10+ (subannotation_directive)
11+ (list)
12+ "\{"
13+] @indent
14+
15+[
16+ ".end annotation"
17+ ".end array-data"
18+ ".end field"
19+ ".end method"
20+ ".end packed-switch"
21+ ".end param"
22+ ".end parameter"
23+ ".end sparse-switch"
24+ ".end subannotation"
25+ "\}"
26+] @outdent
27+
28+[
29+ (ERROR)
30+ (comment)
31+] @indent.always
runtime/queries/smali/injections.scmnew file+2
@@ -0,0 +1,2 @@
1+((comment) @injection.content
2+ (#set! injection.language "comment"))
runtime/queries/smali/locals.scmnew file+42
@@ -0,0 +1,42 @@
1+[
2+ (class_directive)
3+ (expression)
4+ (annotation_directive)
5+ (array_data_directive)
6+ (method_definition)
7+ (packed_switch_directive)
8+ (sparse_switch_directive)
9+ (subannotation_directive)
10+] @local.scope
11+
12+[
13+ (identifier)
14+ (class_identifier)
15+ (label)
16+ (jmp_label)
17+] @local.reference
18+
19+(enum_reference
20+ (field_identifier) @local.definition)
21+
22+((field_definition
23+ (access_modifiers) @_mod
24+ (field_identifier) @local.definition)
25+ (#eq? @_mod "enum"))
26+
27+(field_definition
28+ (field_identifier) @local.definition
29+ (field_type) @local.definition)
30+
31+(annotation_key) @local.definition
32+
33+(method_definition
34+ (method_signature (method_identifier) @local.definition))
35+
36+(param_identifier) @local.definition
37+
38+(annotation_directive
39+ (class_identifier) @local.definition)
40+
41+(class_directive
42+ (class_identifier) @local.definition)
runtime/queries/yuck/highlights.scm+1−1
@@ -101,7 +101,7 @@
101101(list
102102 (symbol) @tag)
103103
104−; Other stuff that has not been catched by the previous queries yet
104+; Other stuff that has not been caught by the previous queries yet
105105
106106(ident) @variable
107107(index) @variable
runtime/themes/README.md+8
@@ -6,4 +6,12 @@ If you submit a theme, please include a comment at the top with your name and em
66# Author : Name <email@my.domain>
77```
88
9+If you are submitting a theme that is already published somewhere, then please
10+add the corresponding license file for it under `licenses/` with the following
11+name `theme_name.license` and add a comment at the top of the theme file:
12+
13+```toml
14+# License: <License specification>
15+```
16+
917We have a preview page for themes on our [wiki](https://github.com/helix-editor/helix/wiki/Themes)!
runtime/themes/darcula.toml+6−2
@@ -15,7 +15,7 @@
1515"ui.statusline.select" = { bg = "orange", fg = "grey01" }
1616"ui.help" = { fg = "grey04", bg = "grey01" }
1717"ui.cursor" = { fg = "grey04", modifiers = ["reversed"] }
18−"ui.cursor.primary" = { fg = "grey05", modifiers = ["reversed"] }
18+"ui.cursor.primary" = { fg = "grey02", bg = "grey05" }
1919"ui.cursor.match" = { fg = "white", modifiers = ["underlined"] }
2020"ui.cursorline.primary" = { bg = "grey02" }
2121"ui.cursorline.secondary" = { bg = "grey02" }
@@ -35,7 +35,11 @@
3535"constant.numeric" = "lightblue"
3636"constant.character.escape" = "white"
3737"attribute" = "yellow"
38−"type" = "orange"
38+"type" = "white"
39+"type.builtin" = "orange"
40+"type.parameter" = { fg = "white", modifiers = ["bold"] }
41+"type.enum" = "white"
42+"type.enum.variant" = "orange"
3943"string" = "darkgreen"
4044"function" = "yellow"
4145"function.macro" = "green"
runtime/themes/horizon-dark.tomlnew file+80
@@ -0,0 +1,80 @@
1+# Syntax
2+string = "orange"
3+constant = "purple"
4+"constant.numeric" = "orange"
5+"constant.builtin" = "orange"
6+variable = "red"
7+comment = "light-gray"
8+special = "purple"
9+"punctuation.bracket" = "purple"
10+keyword = "purple"
11+function = "blue"
12+label = "orange"
13+type = "orange"
14+namespace = "orange"
15+
16+# User Interface
17+"ui.background" = { bg = "bg", fg = "gray" }
18+"ui.background.separator" = "light-gray"
19+"ui.text" = "white"
20+"ui.text.focus" = "green"
21+"ui.text.inactive" = "selection"
22+"ui.text.info" = "orange"
23+"ui.cursor" = { modifiers = ["reversed"] }
24+"ui.cursor.primary" = { modifiers = ["reversed"] }
25+"ui.cursor.match" = { bg = "selection" }
26+"ui.gutter" = "gray"
27+"ui.gutter.selected" = "light-gray"
28+"ui.selection" = { bg = "selection" }
29+"ui.virtual.indent-guide" = { fg = "gray"}
30+"ui.virtual.whitespace" = { fg = "light-gray"}
31+"ui.statusline" = { bg = "dark-bg", fg = "light-gray" }
32+"ui.popup" = { bg = "dark-bg", fg = "orange" }
33+"ui.help" = { bg = "dark-bg", fg = "orange" }
34+"ui.menu" = { bg = "dark-bg", fg = "light-gray" }
35+"ui.menu.selected" = { bg = "selection", fg = "orange" }
36+"ui.window" = "selection"
37+"ui.bufferline" = { bg = "dark-bg", fg = "light-gray" }
38+"ui.bufferline.active" = { bg = "dark-bg", fg = "orange" }
39+
40+# Diagnostics
41+"diagnostic" = { underline = { style = "curl" } }
42+"diagnostic.hint" = { underline = { color = "green", style = "curl" } }
43+"diagnostic.info" = { underline = { color = "blue", style = "curl" } }
44+"diagnostics.error" = { underline = { color = "red", style = "curl"} }
45+"diagnostics.warning" = { underline = { color = "orange", style = "curl"} }
46+
47+warning = { fg = "orange", modifiers = ["bold"] }
48+error = { fg = "red", modifiers = ["bold"] }
49+info = { fg = "blue", modifiers = ["bold"] }
50+hint = { fg = "green", modifiers = ["bold"] }
51+
52+# Markup
53+"markup.heading" = "pink"
54+"markup.list" = "light-gray"
55+"markup.list.checked" = "green"
56+"markup.list.unchecked" = "light-gray"
57+"markup.bold" = { modifiers = ["bold"] }
58+"markup.italic" = { modifiers = ["italic"] }
59+"markup.strikethrough" = { modifiers = ["crossed_out"] }
60+"markup.link" = "blue"
61+"markup.quote" = "orange"
62+"markup.raw" = "orange"
63+
64+"diff.plus" = "green"
65+"diff.minus" = "red"
66+"diff.delta" = "orange"
67+
68+[palette]
69+white = "#D5D8DA"
70+gray = "#2E303E"
71+light-gray = "#6C6F93"
72+bg = "#1C1E26"
73+dark-bg = "#16161C"
74+pink = "#EE64AE"
75+selection = "#353747"
76+green = "#27D796"
77+orange = "#FAB795"
78+purple = "#B877DB"
79+red = "#E95678"
80+blue = "#25B2BC"
runtime/themes/licenses/modus_vivendi.LICENSEnew file+5
@@ -0,0 +1,5 @@
1+Copyright (C) 2020-2023 Free Software Foundation, Inc.
2+
3+Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover Texts being “A GNU Manual,” and with the Back-Cover Texts as in (a) below. A copy of the license is included in the section entitled “GNU Free Documentation License.”
4+
5+(a) The FSF’s Back-Cover Text is: “You have the freedom to copy and modify this GNU manual.”
runtime/themes/licenses/modus_vivendi_deuteranopia.LICENSEnew file+5
@@ -0,0 +1,5 @@
1+Copyright (C) 2020-2023 Free Software Foundation, Inc.
2+
3+Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover Texts being “A GNU Manual,” and with the Back-Cover Texts as in (a) below. A copy of the license is included in the section entitled “GNU Free Documentation License.”
4+
5+(a) The FSF’s Back-Cover Text is: “You have the freedom to copy and modify this GNU manual.”
runtime/themes/licenses/modus_vivendi_tinted.LICENSEnew file+5
@@ -0,0 +1,5 @@
1+Copyright (C) 2020-2023 Free Software Foundation, Inc.
2+
3+Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover Texts being “A GNU Manual,” and with the Back-Cover Texts as in (a) below. A copy of the license is included in the section entitled “GNU Free Documentation License.”
4+
5+(a) The FSF’s Back-Cover Text is: “You have the freedom to copy and modify this GNU manual.”
runtime/themes/licenses/modus_vivendi_tritanopia.LICENSEnew file+5
@@ -0,0 +1,5 @@
1+Copyright (C) 2020-2023 Free Software Foundation, Inc.
2+
3+Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover Texts being “A GNU Manual,” and with the Back-Cover Texts as in (a) below. A copy of the license is included in the section entitled “GNU Free Documentation License.”
4+
5+(a) The FSF’s Back-Cover Text is: “You have the freedom to copy and modify this GNU manual.”
runtime/themes/licenses/poimandres.LICENSEnew file+21
@@ -0,0 +1,21 @@
1+MIT License
2+
3+Copyright (c) 2023 drcmda
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
runtime/themes/licenses/starlight.LICENSEnew file+24
@@ -0,0 +1,24 @@
1+This is free and unencumbered software released into the public domain.
2+
3+Anyone is free to copy, modify, publish, use, compile, sell, or
4+distribute this software, either in source code form or as a compiled
5+binary, for any purpose, commercial or non-commercial, and by any
6+means.
7+
8+In jurisdictions that recognize copyright laws, the author or authors
9+of this software dedicate any and all copyright interest in the
10+software to the public domain. We make this dedication for the benefit
11+of the public at large and to the detriment of our heirs and
12+successors. We intend this dedication to be an overt act of
13+relinquishment in perpetuity of all present and future rights to this
14+software under copyright law.
15+
16+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+OTHER DEALINGS IN THE SOFTWARE.
23+
24+For more information, please refer to <https://unlicense.org>
runtime/themes/modus_operandi.tomlnew file+194
@@ -0,0 +1,194 @@
1+# Author: Alexis Mousset <contact@amousset.me>
2+# Adapted from https://protesilaos.com/emacs/modus-themes, by Protesilaos Stavrou
3+# Version 4.3.0
4+#
5+
6+# Syntax highlighting
7+# -------------------
8+"type" = "cyan-cooler"
9+"constructor" = "cyan-cooler"
10+
11+"constant" = "blue-cooler"
12+"constant.character.escape" = "magenta"
13+
14+"string" = "blue-warmer"
15+"string.regexp" = "magenta-faint"
16+"string.special" = "blue-faint" # used for colors in CSS
17+
18+"comment" = "fg-dim"
19+
20+"variable.parameter" = "cyan"
21+"variable.builtin" = "magenta-cooler"
22+"label" = "fg-dim" # used for language in markdown code blocks
23+"keyword" = "magenta-cooler"
24+"keyword.directive" = "red-cooler"
25+"function" = "magenta"
26+"function.macro" = "magenta-warmer"
27+
28+punctuation = "fg-dim"
29+"tag" = "magenta"
30+"attribute" = "cyan-cooler"
31+"namespace" = "blue-cooler"
32+"special" = "red-cooler"
33+
34+"markup.heading.marker" = "fg-dim"
35+"markup.heading.1" = { fg = "fg-main", modifiers = ["bold"] }
36+"markup.heading.2" = { fg = "yellow-faint", modifiers = ["bold"] }
37+"markup.heading.3" = { fg = "fg-alt", modifiers = ["bold"] }
38+"markup.heading.4" = { fg = "magenta", modifiers = ["bold"] }
39+"markup.heading.5" = { fg = "green-faint", modifiers = ["bold"] }
40+"markup.heading.6" = { fg = "red-faint", modifiers = ["bold"] }
41+"markup.list" = "fg-dim"
42+"markup.list.checked" = { fg = "yellow-warmer" }
43+"markup.list.unchecked" = { fg = "yellow-warmer" }
44+"markup.bold" = { modifiers = ["bold"] }
45+"markup.italic" = { modifiers = ["italic"] }
46+"markup.strikethrough" = { modifiers = ["crossed_out"] }
47+"markup.link.url" = { fg = "cyan" }
48+"markup.link.text" = { fg = "blue-warmer", modifiers = ["underlined"] }
49+"markup.raw.block" = { bg = "bg-dim" }
50+"markup.raw.inline" = { fg = "green-cooler" }
51+
52+"diff.plus" = { fg = "fg-added", bg = "bg-added" }
53+"diff.plus.gutter" = "green-intense"
54+"diff.minus" = { fg = "fg-removed", bg = "bg-removed" }
55+"diff.minus.gutter" = "red-intense"
56+"diff.delta" = { fg = "fg-changed", bg = "bg-changed" }
57+"diff.delta.gutter" = "yellow-intense"
58+
59+# User Interface
60+# --------------
61+
62+"ui.background" = { bg = "bg-main" }
63+
64+"ui.linenr" = { fg = "fg-dim", bg = "bg-dim" }
65+"ui.linenr.selected" = { fg = "fg-main", bg = "bg-active" }
66+
67+"ui.statusline" = { fg = "fg-mode-line-active", bg = "bg-mode-line-active" }
68+"ui.statusline.inactive" = { fg = "fg-mode-line-inactive", bg = "bg-mode-line-inactive" }
69+"ui.statusline.normal" = { fg = "blue-warmer" }
70+"ui.statusline.insert" = { fg = "green-warmer" }
71+"ui.statusline.select" = { fg = "magenta-warmer" }
72+
73+"ui.popup" = { fg = "fg-main", bg = "bg-dim" }
74+"ui.window" = { fg = "fg-dim" }
75+"ui.help" = { fg = "fg-main", bg = "bg-dim" }
76+"ui.gutter" = { bg = "bg-dim" }
77+"ui.text" = "fg-main"
78+"ui.text.focus" = { fg = "fg-main", bg = "bg-completion", modifiers = ["bold"] }
79+"ui.text.inactive" = { fg = "fg-dim" }
80+"ui.virtual" = "bg-active"
81+"ui.virtual.ruler" = { bg = "bg-dim" }
82+"ui.virtual.inlay-hint" = { fg = "fg-dim", modifiers = ["italic"] }
83+
84+"ui.selection" = { fg = "fg-main", bg = "bg-inactive" }
85+"ui.selection.primary" = { fg = "fg-main", bg = "bg-active" }
86+
87+"ui.cursor" = { fg = "bg-main", bg = "fg-main" }
88+"ui.cursor.primary" = { fg = "bg-main", bg = "fg-dim" }
89+"ui.cursor.match" = { bg = "bg-paren-match" }
90+"ui.cursorline.primary" = { bg = "bg-hl-line" }
91+
92+"ui.highlight" = { bg = "bg-hl-line" }
93+
94+"ui.menu" = { fg = "fg-main", bg = "bg-dim" }
95+"ui.menu.selected" = { fg = "fg-main", bg = "bg-completion", modifiers = ["bold"] }
96+"ui.menu.scroll" = { fg = "fg-dim", bg = "bg-cyan-intense" }
97+
98+"diagnostic.error" = { underline = { color = "red-intense", style = "curl" } }
99+"diagnostic.warning" = { underline = { color = "yellow-intense", style = "curl" } }
100+"diagnostic.info" = { underline = { color = "cyan-intense", style = "curl" } }
101+"diagnostic.hint" = { underline = { color = "blue-intense", style = "curl" } }
102+
103+error = "red"
104+warning = "yellow-warmer"
105+info = "cyan-cooler"
106+hint = "blue-cooler"
107+
108+[palette]
109+# Basic values
110+bg-main = "#ffffff"
111+bg-dim = "#f2f2f2"
112+fg-main = "#000000"
113+fg-dim = "#595959"
114+fg-alt = "#193668"
115+bg-active = "#c4c4c4"
116+bg-inactive = "#e0e0e0"
117+
118+# Common accent foregrounds
119+red = "#a60000"
120+red-warmer = "#972500"
121+red-cooler = "#a0132f"
122+red-faint = "#7f0000"
123+red-intense = "#d00000"
124+green = "#006800"
125+green-warmer = "#316500"
126+green-cooler = "#00663f"
127+green-faint = "#2a5045"
128+green-intense = "#008900"
129+yellow = "#6f5500"
130+yellow-warmer = "#884900"
131+yellow-cooler = "#7a4f2f"
132+yellow-faint = "#624416"
133+yellow-intense = "#808000"
134+blue = "#0031a9"
135+blue-warmer = "#354fcf"
136+blue-cooler = "#0000b0"
137+blue-faint = "#003497"
138+blue-intense = "#0000ff"
139+magenta = "#721045"
140+magenta-warmer = "#8f0075"
141+magenta-cooler = "#531ab6"
142+magenta-faint = "#7c318f"
143+magenta-intense = "#dd22dd"
144+cyan = "#005e8b"
145+cyan-warmer = "#3f578f"
146+cyan-cooler = "#005f5f"
147+cyan-faint = "#005077"
148+cyan-intense = "#008899"
149+
150+# Common accent backgrounds
151+bg-red-intense = "#ff8f88"
152+bg-green-intense = "#8adf80"
153+bg-yellow-intense = "#f3d000"
154+bg-blue-intense = "#bfc9ff"
155+bg-magenta-intense = "#dfa0f0"
156+bg-cyan-intense = "#a4d5f9"
157+
158+bg-red-subtle = "#ffcfbf"
159+bg-green-subtle = "#b3fabf"
160+bg-yellow-subtle = "#fff576"
161+bg-blue-subtle = "#ccdfff"
162+bg-magenta-subtle = "#ffddff"
163+bg-cyan-subtle = "#bfefff"
164+
165+bg-red-nuanced = "#fff1f0"
166+bg-green-nuanced = "#ecf7ed"
167+bg-yellow-nuanced = "#fff3da"
168+bg-blue-nuanced = "#f3f3ff"
169+bg-magenta-nuanced = "#fdf0ff"
170+bg-cyan-nuanced = "#ebf6fa"
171+
172+# Special purpose
173+bg-completion = "#c0deff"
174+bg-hl-line = "#dae5ec"
175+
176+bg-mode-line-active = "#c8c8c8"
177+fg-mode-line-active = "#000000"
178+bg-mode-line-inactive = "#e6e6e6"
179+fg-mode-line-inactive = "#585858"
180+
181+modeline-err = "#7f0000"
182+modeline-warning = "#5f0070"
183+modeline-info = "#002580"
184+
185+# Diffs
186+bg-added = "#c1f2d1"
187+fg-added = "#005000"
188+bg-changed = "#ffdfa9"
189+fg-changed = "#553d00"
190+bg-removed = "#ffd8d5"
191+fg-removed = "#8f1313"
192+
193+# Paren match
194+bg-paren-match = "#5fcfff"
runtime/themes/modus_operandi_deuteranopia.tomlnew file+46
@@ -0,0 +1,46 @@
1+# Author: Alexis Mousset <contact@amousset.me>
2+# modus_operandi.toml variant
3+#
4+# This variant is optimized for users with red-green color deficiency (deuteranopia)
5+
6+inherits = "modus_operandi"
7+
8+"constant.character.escape" = "magenta"
9+"comment" = "yellow-cooler"
10+"function" = "magenta"
11+"tag" = "magenta"
12+
13+"ui.cursor" = { fg = "bg-main", bg = "blue-intense" }
14+"ui.cursor.primary.normal" = { fg = "bg-main", bg = "blue-intense" }
15+
16+"diagnostic.error" = { underline = { color = "yellow-intense", style = "curl" } }
17+"diagnostic.warning" = { underline = { color = "magenta-faint", style = "curl" } }
18+"diagnostic.info" = { underline = { color = "cyan", style = "curl" } }
19+"diagnostic.hint" = { underline = { color = "blue", style = "curl" } }
20+
21+error = "yellow-intense"
22+warning = "magenta-faint"
23+info = "cyan"
24+hint = "blue"
25+
26+[palette]
27+
28+yellow = "#695500"
29+yellow-warmer = "#973300"
30+yellow-cooler = "#77492f"
31+
32+bg-mode-line-active = "#d0d6ff"
33+fg-mode-line-active = "#0f0f0f"
34+border-mode-line-active = "#4f4f74"
35+
36+modeline-err = "#603a00"
37+modeline-warning = "#454500"
38+modeline-info = "#023d92"
39+
40+# Diffs
41+bg-added = "#d5d7ff"
42+fg-added = "#303099"
43+bg-changed = "#eecfdf"
44+fg-changed = "#6f1343"
45+bg-removed = "#f4f099"
46+fg-removed = "#553d00"
runtime/themes/modus_operandi_tinted.tomlnew file+45
@@ -0,0 +1,45 @@
1+# Author: Alexis Mousset <contact@amousset.me>
2+# modus_operandi.toml variant
3+
4+inherits = "modus_operandi"
5+
6+"comment" = "red-faint"
7+
8+"ui.cursor.primary" = { fg = "bg-main", bg = "red" }
9+
10+[palette]
11+# Basic values
12+bg-main = "#fbf7f0"
13+bg-dim = "#efe9dd"
14+bg-active = "#c9b9b0"
15+bg-inactive = "#dfd5cf"
16+border = "#9f9690"
17+
18+# Common accent backgrounds
19+bg-red-nuanced = "#ffe8f0"
20+bg-green-nuanced = "#e0f5e0"
21+bg-yellow-nuanced = "#f9ead0"
22+bg-blue-nuanced = "#ebebff"
23+bg-magenta-nuanced = "#f6e7ff"
24+bg-cyan-nuanced = "#e1f3fc"
25+
26+# Special purpose
27+bg-completion = "#f0c1cf"
28+bg-hl-line = "#f1d5d0"
29+bg-region = "#c2bcb5"
30+
31+bg-mode-line-active = "#cab9b2"
32+border-mode-line-active = "#545454"
33+bg-mode-line-inactive = "#dfd9cf"
34+border-mode-line-inactive = "#a59a94"
35+
36+bg-tab-bar = "#e0d4ce"
37+bg-tab-current = "#fbf7f0"
38+bg-tab-other = "#c8b8b2"
39+
40+# Diffs
41+bg-added = "#c3ebc1"
42+bg-removed = "#f4d0cf"
43+
44+# Paren match
45+bg-paren-match = "#7fdfcf"
runtime/themes/modus_operandi_tritanopia.tomlnew file+73
@@ -0,0 +1,73 @@
1+# Author: Alexis Mousset <contact@amousset.me>
2+# modus_operandi.toml variant
3+#
4+# This variant is optimized for users with blue-yellow color deficiency (tritanopia)
5+
6+inherits = "modus_operandi"
7+
8+"type" = "blue-warmer"
9+"constructor" = "blue-warmer"
10+"constant" = "green-cooler"
11+"constant.character.escape" = "red-cooler"
12+"string" = "cyan"
13+"comment" = "red-faint"
14+"variable.parameter" = "cyan-cooler"
15+"keyword" = "red-cooler"
16+"keyword.directive" = "red-warmer"
17+"function" = "cyan-warmer"
18+"function.macro" = "magenta"
19+"tag" = "red-cooler"
20+
21+"markup.heading.2" = { fg = "red-faint", modifiers = ["bold"] }
22+"markup.heading.3" = { fg = "cyan-faint", modifiers = ["bold"] }
23+"markup.heading.6" = { fg = "magenta-faint", modifiers = ["bold"] }
24+"markup.link.url" = { fg = "cyan-cooler" }
25+"markup.link.text" = { fg = "cyan", modifiers = ["underlined"] }
26+
27+"ui.cursor" = { fg = "bg-main", bg = "red-intense" }
28+"ui.cursor.primary.normal" = { fg = "bg-main", bg = "red-intense" }
29+
30+"diagnostic.error" = { underline = { color = "red-warmer", style = "curl" } }
31+"diagnostic.warning" = { underline = { color = "magenta", style = "curl" } }
32+"diagnostic.info" = { underline = { color = "cyan", style = "curl" } }
33+"diagnostic.hint" = { underline = { color = "blue", style = "curl" } }
34+
35+error = "red-warmer"
36+warning = "magenta"
37+info = "cyan"
38+hint = "blue"
39+
40+[palette]
41+
42+# Common accent foregrounds
43+red-warmer = "#b21100"
44+red-faint = "#702000"
45+yellow = "#695500"
46+yellow-warmer = "#973300"
47+yellow-cooler = "#77492f"
48+magenta-intense = "#cd22bd"
49+cyan-faint = "#004f5f"
50+
51+# Special purpose
52+bg-completion = "#afdfef"
53+bg-hover = "#ffafbc"
54+bg-hover-secondary = "#9fdfff"
55+bg-hl-line = "#dfeaec"
56+
57+bg-char-0 = "#ff908f"
58+bg-char-1 = "#bfbfff"
59+bg-char-2 = "#5fcfdf"
60+
61+bg-mode-line-active = "#afe0f2"
62+fg-mode-line-active = "#0f0f0f"
63+border-mode-line-active = "#2f4f44"
64+
65+modeline-err = "#8f0000"
66+modeline-warning = "#6f306f"
67+modeline-info = "#00445f"
68+
69+# Diffs
70+bg-added = "#b5e7ff"
71+fg-added = "#005079"
72+bg-changed = "#eecfdf"
73+fg-changed = "#6f1343"
runtime/themes/modus_vivendi.tomlnew file+242
@@ -0,0 +1,242 @@
1+# Author: Matous Dzivjak <matousdzivjak@gmail.com>
2+# Adapted from https://protesilaos.com/emacs/modus-themes, by Protesilaos Stavrou
3+# Source: https://github.com/protesilaos/modus-themes/blob/main/modus-vivendi-theme.el
4+# Version 4.3.0
5+
6+# Syntax highlighting
7+# -------------------
8+"type" = "cyan-cooler"
9+"constructor" = "cyan-cooler"
10+
11+"constant" = "blue-cooler"
12+"constant.character.escape" = "magenta"
13+
14+"string" = "blue-warmer"
15+"string.regexp" = "magenta-faint"
16+"string.special" = "blue-faint" # used for colors in CSS
17+
18+"comment" = "fg-dim"
19+
20+"variable.parameter" = "cyan"
21+"variable.builtin" = "magenta-cooler"
22+"label" = "fg-dim" # used for language in markdown code blocks
23+"keyword" = "magenta-cooler"
24+"keyword.directive" = "red-cooler"
25+"function" = "magenta"
26+"function.macro" = "magenta-warmer"
27+
28+punctuation = "fg-dim"
29+"tag" = "magenta"
30+"attribute" = "cyan-cooler"
31+"namespace" = "blue-cooler"
32+"special" = "red-cooler"
33+
34+"markup.heading.marker" = "fg-dim"
35+"markup.heading.1" = { fg = "fg-main", modifiers = ["bold"] }
36+"markup.heading.2" = { fg = "yellow-faint", modifiers = ["bold"] }
37+"markup.heading.3" = { fg = "fg-alt", modifiers = ["bold"] }
38+"markup.heading.4" = { fg = "magenta", modifiers = ["bold"] }
39+"markup.heading.5" = { fg = "green-faint", modifiers = ["bold"] }
40+"markup.heading.6" = { fg = "red-faint", modifiers = ["bold"] }
41+"markup.list" = "fg-dim"
42+"markup.list.checked" = { fg = "yellow-warmer" }
43+"markup.list.unchecked" = { fg = "yellow-warmer" }
44+"markup.bold" = { modifiers = ["bold"] }
45+"markup.italic" = { modifiers = ["italic"] }
46+"markup.strikethrough" = { modifiers = ["crossed_out"] }
47+"markup.link.url" = { fg = "cyan" }
48+"markup.link.text" = { fg = "blue-warmer", modifiers = ["underlined"] }
49+"markup.raw.block" = { bg = "bg-dim" }
50+"markup.raw.inline" = { fg = "green-cooler" }
51+
52+"diff.plus" = { fg = "fg-added", bg = "bg-added" }
53+"diff.plus.gutter" = "green-intense"
54+"diff.minus" = { fg = "fg-removed", bg = "bg-removed" }
55+"diff.minus.gutter" = "red-intense"
56+"diff.delta" = { fg = "fg-changed", bg = "bg-changed" }
57+"diff.delta.gutter" = "yellow-intense"
58+
59+# User Interface
60+# --------------
61+
62+"ui.background" = { bg = "bg-main" }
63+
64+"ui.linenr" = { fg = "fg-dim", bg = "bg-dim" }
65+"ui.linenr.selected" = { fg = "fg-main", bg = "bg-active" }
66+
67+"ui.statusline" = { fg = "fg-mode-line-active", bg = "bg-mode-line-active" }
68+"ui.statusline.inactive" = { fg = "fg-mode-line-inactive", bg = "bg-mode-line-inactive" }
69+"ui.statusline.normal" = { fg = "blue-warmer" }
70+"ui.statusline.insert" = { fg = "green-warmer" }
71+"ui.statusline.select" = { fg = "magenta-warmer" }
72+
73+"ui.bufferline" = { bg = "bg-tab-bar" }
74+"ui.bufferline.active" = { bg = "bg-tab-current", fg = "fg-mode-line-active" }
75+
76+"ui.popup" = { fg = "fg-main", bg = "bg-dim" }
77+"ui.window" = { fg = "fg-dim" }
78+"ui.help" = { fg = "fg-main", bg = "bg-dim" }
79+"ui.gutter" = { bg = "bg-dim" }
80+"ui.text" = "fg-main"
81+"ui.text.focus" = { fg = "fg-main", bg = "bg-completion", modifiers = ["bold"] }
82+"ui.text.inactive" = { fg = "fg-dim" }
83+"ui.virtual" = "bg-active"
84+"ui.virtual.ruler" = { bg = "bg-dim" }
85+"ui.virtual.inlay-hint" = { fg = "fg-dim", modifiers = ["italic"] }
86+
87+"ui.selection" = { fg = "fg-main", bg = "bg-inactive" }
88+"ui.selection.primary" = { fg = "fg-main", bg = "bg-active" }
89+
90+"ui.cursor" = { fg = "bg-main", bg = "fg-main" }
91+"ui.cursor.primary" = { fg = "bg-main", bg = "fg-dim" }
92+"ui.cursor.match" = { bg = "bg-paren-match" }
93+"ui.cursorline.primary" = { bg = "bg-hl-line" }
94+
95+"ui.highlight" = { bg = "bg-hl-line" }
96+
97+"ui.menu" = { fg = "fg-main", bg = "bg-dim" }
98+"ui.menu.selected" = { fg = "fg-main", bg = "bg-completion", modifiers = ["bold"] }
99+"ui.menu.scroll" = { fg = "fg-dim", bg = "bg-cyan-intense" }
100+
101+"diagnostic.error" = { underline = { color = "red-intense", style = "curl" } }
102+"diagnostic.warning" = { underline = { color = "yellow-intense", style = "curl" } }
103+"diagnostic.info" = { underline = { color = "cyan-intense", style = "curl" } }
104+"diagnostic.hint" = { underline = { color = "blue-intense", style = "curl" } }
105+
106+error = "red"
107+warning = "yellow-warmer"
108+info = "cyan-cooler"
109+hint = "blue-cooler"
110+
111+[palette]
112+# Basic values
113+bg-main = "#000000"
114+bg-dim = "#1e1e1e"
115+fg-main = "#ffffff"
116+fg-dim = "#989898"
117+fg-alt = "#c6daff"
118+bg-active = "#535353"
119+bg-inactive = "#303030"
120+
121+# Common accent foregrounds
122+red = "#ff5f59"
123+red-warmer = "#ff6b55"
124+red-cooler = "#ff7f9f"
125+red-faint = "#ff9580"
126+red-intense = "#ff5f5f"
127+green = "#44bc44"
128+green-warmer = "#70b900"
129+green-cooler = "#00c06f"
130+green-faint = "#88ca9f"
131+green-intense = "#44df44"
132+yellow = "#d0bc00"
133+yellow-warmer = "#fec43f"
134+yellow-cooler = "#dfaf7a"
135+yellow-faint = "#d2b580"
136+yellow-intense = "#efef00"
137+blue = "#2fafff"
138+blue-warmer = "#79a8ff"
139+blue-cooler = "#00bcff"
140+blue-faint = "#82b0ec"
141+blue-intense = "#338fff"
142+magenta = "#feacd0"
143+magenta-warmer = "#f78fe7"
144+magenta-cooler = "#b6a0ff"
145+magenta-faint = "#caa6df"
146+magenta-intense = "#ff66ff"
147+cyan = "#00d3d0"
148+cyan-warmer = "#4ae2f0"
149+cyan-cooler = "#6ae4b9"
150+cyan-faint = "#9ac8e0"
151+cyan-intense = "#00eff0"
152+
153+# Uncommon accent foregrounds
154+rust = "#db7b5f"
155+gold = "#c0965b"
156+olive = "#9cbd6f"
157+slate = "#76afbf"
158+indigo = "#9099d9"
159+maroon = "#cf7fa7"
160+pink = "#d09dc0"
161+
162+# Common accent backgrounds
163+bg-red-intense = "#9d1f1f"
164+bg-green-intense = "#2f822f"
165+bg-yellow-intense = "#7a6100"
166+bg-blue-intense = "#1640b0"
167+bg-magenta-intense = "#7030af"
168+bg-cyan-intense = "#2266ae"
169+
170+bg-red-subtle = "#620f2a"
171+bg-green-subtle = "#00422a"
172+bg-yellow-subtle = "#4a4000"
173+bg-blue-subtle = "#242679"
174+bg-magenta-subtle = "#552f5f"
175+bg-cyan-subtle = "#004065"
176+
177+bg-red-nuanced = "#2c0614"
178+bg-green-nuanced = "#001904"
179+bg-yellow-nuanced = "#221000"
180+bg-blue-nuanced = "#0f0e39"
181+bg-magenta-nuanced = "#230631"
182+bg-cyan-nuanced = "#041529"
183+
184+# Uncommon accent backgrounds
185+bg-ochre = "#442c2f"
186+bg-lavender = "#38325c"
187+bg-sage = "#0f3d30"
188+
189+# Special purpose
190+bg-completion = "#2f447f"
191+bg-hover = "#45605e"
192+bg-hover-secondary = "#654a39"
193+bg-hl-line = "#2f3849"
194+bg-region = "#5a5a5a"
195+fg-region = "#ffffff"
196+
197+bg-char-0 = "#0050af"
198+bg-char-1 = "#7f1f7f"
199+bg-char-2 = "#625a00"
200+
201+bg-mode-line-active = "#505050"
202+fg-mode-line-active = "#ffffff"
203+border-mode-line-active = "#959595"
204+bg-mode-line-inactive = "#2d2d2d"
205+fg-mode-line-inactive = "#969696"
206+border-mode-line-inactive = "#606060"
207+
208+modeline-err = "#ffa9bf"
209+modeline-warning = "#dfcf43"
210+modeline-info = "#9fefff"
211+
212+bg-tab-bar = "#313131"
213+bg-tab-current = "#000000"
214+bg-tab-other = "#545454"
215+
216+# Diffs
217+bg-added = "#00381f"
218+bg-added-faint = "#002910"
219+bg-added-refine = "#034f2f"
220+bg-added-fringe = "#237f3f"
221+fg-added = "#a0e0a0"
222+fg-added-intense = "#80e080"
223+
224+bg-changed = "#363300"
225+bg-changed-faint = "#2a1f00"
226+bg-changed-refine = "#4a4a00"
227+bg-changed-fringe = "#8a7a00"
228+fg-changed = "#efef80"
229+fg-changed-intense = "#c0b05f"
230+
231+bg-removed = "#4f1119"
232+bg-removed-faint = "#380a0f"
233+bg-removed-refine = "#781a1f"
234+bg-removed-fringe = "#b81a1f"
235+fg-removed = "#ffbfbf"
236+fg-removed-intense = "#ff9095"
237+
238+bg-diff-context = "#1a1a1a"
239+
240+# Paren match
241+bg-paren-match = "#2f7f9f"
242+tg-paren-expression = "#453040"
runtime/themes/modus_vivendi_deuteranopia.tomlnew file+158
@@ -0,0 +1,158 @@
1+# Author: Matous Dzivjak <matousdzivjak@gmail.com>
2+# Adapted from https://protesilaos.com/emacs/modus-themes, by Protesilaos Stavrou
3+# Source: https://github.com/protesilaos/modus-themes/blob/main/modus-vivendi-deuteranopia-theme.el
4+# Version 4.3.0
5+
6+inherits = "modus_vivendi"
7+
8+"constant.character.escape" = "magenta"
9+"comment" = "yellow-cooler"
10+"function" = "magenta"
11+"tag" = "magenta"
12+
13+"ui.cursor" = { fg = "bg-main", bg = "blue-intense" }
14+"ui.cursor.primary.normal" = { fg = "bg-main", bg = "blue-intense" }
15+
16+"diagnostic.error" = { underline = { color = "yellow-intense", style = "curl" } }
17+"diagnostic.warning" = { underline = { color = "magenta-faint", style = "curl" } }
18+"diagnostic.info" = { underline = { color = "cyan", style = "curl" } }
19+"diagnostic.hint" = { underline = { color = "blue", style = "curl" } }
20+
21+error = "yellow-intense"
22+warning = "magenta-faint"
23+info = "cyan"
24+hint = "blue"
25+
26+[palette]
27+# Basic values
28+bg-main = "#000000"
29+bg-dim = "#1e1e1e"
30+fg-main = "#ffffff"
31+fg-dim = "#989898"
32+fg-alt = "#c6daff"
33+bg-active = "#535353"
34+bg-inactive = "#303030"
35+border = "#646464"
36+
37+# Common accent foregrounds
38+red = "#ff5f59"
39+red-warmer = "#ff6b55"
40+red-cooler = "#ff7f9f"
41+red-faint = "#ff9580"
42+red-intense = "#ff5f5f"
43+green = "#44bc44"
44+green-warmer = "#70b900"
45+green-cooler = "#00c06f"
46+green-faint = "#88ca9f"
47+green-intense = "#44df44"
48+yellow = "#cabf00"
49+yellow-warmer = "#ffa00f"
50+yellow-cooler = "#d8af7a"
51+yellow-faint = "#d2b580"
52+yellow-intense = "#efef00"
53+blue = "#2fafff"
54+blue-warmer = "#79a8ff"
55+blue-cooler = "#00bcff"
56+blue-faint = "#82b0ec"
57+blue-intense = "#338fff"
58+magenta = "#feacd0"
59+magenta-warmer = "#f78fe7"
60+magenta-cooler = "#b6a0ff"
61+magenta-faint = "#caa6df"
62+magenta-intense = "#ff66ff"
63+cyan = "#00d3d0"
64+cyan-warmer = "#4ae2f0"
65+cyan-cooler = "#6ae4b9"
66+cyan-faint = "#9ac8e0"
67+cyan-intense = "#00eff0"
68+
69+# Uncommon accent foregrounds
70+rust = "#db7b5f"
71+gold = "#c0965b"
72+olive = "#9cbd6f"
73+slate = "#76afbf"
74+indigo = "#9099d9"
75+maroon = "#cf7fa7"
76+pink = "#d09dc0"
77+
78+# Common accent backgrounds
79+bg-red-intense = "#9d1f1f"
80+bg-green-intense = "#2f822f"
81+bg-yellow-intense = "#7a6100"
82+bg-blue-intense = "#1640b0"
83+bg-magenta-intense = "#7030af"
84+bg-cyan-intense = "#2266ae"
85+
86+bg-red-subtle = "#620f2a"
87+bg-green-subtle = "#00422a"
88+bg-yellow-subtle = "#4a4000"
89+bg-blue-subtle = "#242679"
90+bg-magenta-subtle = "#552f5f"
91+bg-cyan-subtle = "#004065"
92+
93+bg-red-nuanced = "#2c0614"
94+bg-green-nuanced = "#001904"
95+bg-yellow-nuanced = "#221000"
96+bg-blue-nuanced = "#0f0e39"
97+bg-magenta-nuanced = "#230631"
98+bg-cyan-nuanced = "#041529"
99+
100+# Uncommon accent backgrounds
101+bg-ochre = "#442c2f"
102+bg-lavender = "#38325c"
103+bg-sage = "#0f3d30"
104+
105+# Special purpose
106+bg-completion = "#2f447f"
107+bg-hover = "#45605e"
108+bg-hover-secondary = "#654a39"
109+bg-hl-line = "#2f3849"
110+bg-region = "#5a5a5a"
111+fg-region = "#ffffff"
112+
113+bg-char-0 = "#0050af"
114+bg-char-1 = "#7f1f7f"
115+bg-char-2 = "#625a00"
116+
117+bg-mode-line-active = "#2a2a6a"
118+fg-mode-line-active = "#f0f0f0"
119+border-mode-line-active = "#8080a7"
120+bg-mode-line-inactive = "#2d2d2d"
121+fg-mode-line-inactive = "#969696"
122+border-mode-line-inactive = "#606060"
123+
124+modeline-err = "#e5bf00"
125+modeline-warning = "#c0cf35"
126+modeline-info = "#abeadf"
127+
128+bg-tab-bar = "#313131"
129+bg-tab-current = "#000000"
130+bg-tab-other = "#545454"
131+
132+# Diffs
133+bg-added = "#003066"
134+bg-added-faint = "#001a4f"
135+bg-added-refine = "#0f4a77"
136+bg-added-fringe = "#006fff"
137+fg-added = "#c4d5ff"
138+fg-added-intense = "#8080ff"
139+
140+bg-changed = "#2f123f"
141+bg-changed-faint = "#1f022f"
142+bg-changed-refine = "#3f325f"
143+bg-changed-fringe = "#7f55a0"
144+fg-changed = "#e3cfff"
145+fg-changed-intense = "#cf9fe2"
146+
147+bg-removed = "#3d3d00"
148+bg-removed-faint = "#281f00"
149+bg-removed-refine = "#555500"
150+bg-removed-fringe = "#d0c03f"
151+fg-removed = "#d4d48f"
152+fg-removed-intense = "#d0b05f"
153+
154+bg-diff-context = "#1a1a1a"
155+
156+# Paren match
157+bg-paren-match = "#2f7f9f"
158+bg-paren-expression = "#453040"
runtime/themes/modus_vivendi_tinted.tomlnew file+144
@@ -0,0 +1,144 @@
1+# Author: Matous Dzivjak <matousdzivjak@gmail.com>
2+# Adapted from: https://protesilaos.com/emacs/modus-themes, by Protesilaos Stavrou
3+# Source: https://github.com/protesilaos/modus-themes/blob/main/modus-vivendi-tinted-theme.el
4+# Version 4.3.0
5+
6+inherits = "modus_vivendi"
7+
8+"comment" = "red-faint"
9+
10+"ui.cursor.primary" = { fg = "bg-main", bg = "red" }
11+
12+[palette]
13+# Basic values
14+bg-main = "#0d0e1c"
15+bg-dim = "#1d2235"
16+fg-main = "#ffffff"
17+fg-dim = "#989898"
18+fg-alt = "#c6daff"
19+bg-active = "#4a4f69"
20+bg-inactive = "#2b3045"
21+border = "#61647a"
22+
23+# Common accent foregrounds
24+red = "#ff5f59"
25+red-warmer = "#ff6b55"
26+red-cooler = "#ff7f9f"
27+red-faint = "#ff9f80"
28+red-intense = "#ff5f5f"
29+green = "#44bc44"
30+green-warmer = "#70b900"
31+green-cooler = "#00c06f"
32+green-faint = "#88ca9f"
33+green-intense = "#44df44"
34+yellow = "#d0bc00"
35+yellow-warmer = "#fec43f"
36+yellow-cooler = "#dfaf7a"
37+yellow-faint = "#d2b580"
38+yellow-intense = "#efef00"
39+blue = "#2fafff"
40+blue-warmer = "#79a8ff"
41+blue-cooler = "#00bcff"
42+blue-faint = "#82b0ec"
43+blue-intense = "#338fff"
44+magenta = "#feacd0"
45+magenta-warmer = "#f78fe7"
46+magenta-cooler = "#b6a0ff"
47+magenta-faint = "#caa6df"
48+magenta-intense = "#ff66ff"
49+cyan = "#00d3d0"
50+cyan-warmer = "#4ae2f0"
51+cyan-cooler = "#6ae4b9"
52+cyan-faint = "#9ac8e0"
53+cyan-intense = "#00eff0"
54+
55+# Uncommon accent foregrounds
56+rust = "#db7b5f"
57+gold = "#c0965b"
58+olive = "#9cbd6f"
59+slate = "#76afbf"
60+indigo = "#9099d9"
61+maroon = "#cf7fa7"
62+pink = "#d09dc0"
63+
64+# Common accent backgrounds
65+bg-red-intense = "#9d1f1f"
66+bg-green-intense = "#2f822f"
67+bg-yellow-intense = "#7a6100"
68+bg-blue-intense = "#1640b0"
69+bg-magenta-intense = "#7030af"
70+bg-cyan-intense = "#2266ae"
71+
72+bg-red-subtle = "#620f2a"
73+bg-green-subtle = "#00422a"
74+bg-yellow-subtle = "#4a4000"
75+bg-blue-subtle = "#242679"
76+bg-magenta-subtle = "#552f5f"
77+bg-cyan-subtle = "#004065"
78+
79+bg-red-nuanced = "#350f14"
80+bg-green-nuanced = "#002718"
81+bg-yellow-nuanced = "#2c1f00"
82+bg-blue-nuanced = "#131c4d"
83+bg-magenta-nuanced = "#2f133f"
84+bg-cyan-nuanced = "#04253f"
85+
86+# Special purpose
87+bg-completion = "#483d8a"
88+bg-hover = "#45605e"
89+bg-hover-secondary = "#654a39"
90+bg-hl-line = "#303a6f"
91+bg-region = "#555a66"
92+fg-region = "#ffffff"
93+
94+bg-char-0 = "#0050af"
95+bg-char-1 = "#7f1f7f"
96+bg-char-2 = "#625a00"
97+
98+bg-mode-line-active = "#484d67"
99+fg-mode-line-active = "#ffffff"
100+border-mode-line-active = "#979797"
101+bg-mode-line-inactive = "#292d48"
102+fg-mode-line-inactive = "#969696"
103+border-mode-line-inactive = "#606270"
104+
105+modeline-err = "#ffa9bf"
106+modeline-warning = "#dfcf43"
107+modeline-info = "#9fefff"
108+
109+bg-tab-bar = "#2c3045"
110+bg-tab-current = "#0d0e1c"
111+bg-tab-other = "#4a4f6a"
112+
113+# Diffs
114+bg-added = "#003a2f"
115+bg-added-faint = "#002922"
116+bg-added-refine = "#035542"
117+bg-added-fringe = "#23884f"
118+fg-added = "#a0e0a0"
119+fg-added-intense = "#80e080"
120+
121+bg-changed = "#363300"
122+bg-changed-faint = "#2a1f00"
123+bg-changed-refine = "#4a4a00"
124+bg-changed-fringe = "#8f7a30"
125+fg-changed = "#efef80"
126+fg-changed-intense = "#c0b05f"
127+
128+bg-removed = "#4f1127"
129+bg-removed-faint = "#380a19"
130+bg-removed-refine = "#781a3a"
131+bg-removed-fringe = "#b81a26"
132+fg-removed = "#ffbfbf"
133+fg-removed-intense = "#ff9095"
134+
135+bg-diff-context = "#1a1f30"
136+
137+# Uncommon accent backgrounds
138+bg-ochre = "#442c2f"
139+bg-lavender = "#38325c"
140+bg-sage = "#0f3d30"
141+
142+# Paren match
143+bg-paren-match = "#2f7f9f"
144+bg-paren-expression = "#453040"
runtime/themes/modus_vivendi_tritanopia.tomlnew file+186
@@ -0,0 +1,186 @@
1+# Author: Matous Dzivjak <matousdzivjak@gmail.com>
2+# Adapted from https://protesilaos.com/emacs/modus-themes, by Protesilaos Stavrou
3+# Source: https://github.com/protesilaos/modus-themes/blob/main/modus-vivendi-tritanopia-theme.el
4+# Version 4.3.0
5+
6+inherits = "modus_vivendi"
7+
8+"type" = "blue-warmer"
9+"constructor" = "blue-warmer"
10+"constant" = "green-cooler"
11+"constant.character.escape" = "red-cooler"
12+"string" = "cyan"
13+"comment" = "red-faint"
14+"variable.parameter" = "cyan-cooler"
15+"keyword" = "red-cooler"
16+"keyword.directive" = "red-warmer"
17+"function" = "cyan-warmer"
18+"function.macro" = "magenta"
19+"tag" = "red-cooler"
20+
21+"markup.heading.2" = { fg = "red-faint", modifiers = ["bold"] }
22+"markup.heading.3" = { fg = "cyan-faint", modifiers = ["bold"] }
23+"markup.heading.6" = { fg = "magenta-faint", modifiers = ["bold"] }
24+"markup.link.url" = { fg = "cyan-cooler" }
25+"markup.link.text" = { fg = "cyan", modifiers = ["underlined"] }
26+
27+"ui.cursor" = { fg = "bg-main", bg = "red-intense" }
28+"ui.cursor.primary.normal" = { fg = "bg-main", bg = "red-intense" }
29+
30+"diagnostic.error" = { underline = { color = "red-warmer", style = "curl" } }
31+"diagnostic.warning" = { underline = { color = "magenta", style = "curl" } }
32+"diagnostic.info" = { underline = { color = "cyan", style = "curl" } }
33+"diagnostic.hint" = { underline = { color = "blue", style = "curl" } }
34+
35+error = "red-warmer"
36+warning = "magenta"
37+info = "cyan"
38+hint = "blue"
39+
40+[palette]
41+# Basic values
42+bg-main = "#000000"
43+bg-dim = "#1e1e1e"
44+fg-main = "#ffffff"
45+fg-dim = "#989898"
46+fg-alt = "#c6daff"
47+bg-active = "#535353"
48+bg-inactive = "#303030"
49+border = "#646464"
50+
51+# Common accent foregrounds
52+red = "#ff5f59"
53+red-warmer = "#ff6740"
54+red-cooler = "#ff6f9f"
55+red-faint = "#ff9070"
56+red-intense = "#ff5f5f"
57+green = "#44bc44"
58+green-warmer = "#70b900"
59+green-cooler = "#00c06f"
60+green-faint = "#88ca9f"
61+green-intense = "#44df44"
62+yellow = "#cabf00"
63+yellow-warmer = "#ffa00f"
64+yellow-cooler = "#d8af7a"
65+yellow-faint = "#d2b580"
66+yellow-intense = "#efef00"
67+blue = "#2fafff"
68+blue-warmer = "#79a8ff"
69+blue-cooler = "#00bcff"
70+blue-faint = "#82b0ec"
71+blue-intense = "#338fff"
72+magenta = "#feacd0"
73+magenta-warmer = "#f78fe7"
74+magenta-cooler = "#b6a0ff"
75+magenta-faint = "#caa6df"
76+magenta-intense = "#ef7fff"
77+cyan = "#00d3d0"
78+cyan-warmer = "#4ae2ff"
79+cyan-cooler = "#6ae4b9"
80+cyan-faint = "#7fdbdf"
81+cyan-intense = "#00eff0"
82+
83+# Uncommon accent foregrounds
84+rust = "#db7b5f"
85+gold = "#c0965b"
86+olive = "#9cbd6f"
87+slate = "#76afbf"
88+indigo = "#9099d9"
89+maroon = "#cf7fa7"
90+pink = "#d09dc0"
91+
92+# Common accent backgrounds
93+bg-red-intense = "#9d1f1f"
94+bg-green-intense = "#2f822f"
95+bg-yellow-intense = "#7a6100"
96+bg-blue-intense = "#1640b0"
97+bg-magenta-intense = "#7030af"
98+bg-cyan-intense = "#2266ae"
99+
100+bg-red-subtle = "#620f2a"
101+bg-green-subtle = "#00422a"
102+bg-yellow-subtle = "#4a4000"
103+bg-blue-subtle = "#242679"
104+bg-magenta-subtle = "#552f5f"
105+bg-cyan-subtle = "#004065"
106+
107+bg-red-nuanced = "#2c0614"
108+bg-green-nuanced = "#001904"
109+bg-yellow-nuanced = "#221000"
110+bg-blue-nuanced = "#0f0e39"
111+bg-magenta-nuanced = "#230631"
112+bg-cyan-nuanced = "#041529"
113+
114+# Uncommon accent backgrounds
115+bg-ochre = "#442c2f"
116+bg-lavender = "#38325c"
117+bg-sage = "#0f3d30"
118+
119+# Graphs
120+bg-graph-red-0 = "#b52c2c"
121+bg-graph-red-1 = "#702020"
122+bg-graph-green-0 = "#afd1c0"
123+bg-graph-green-1 = "#607a8f"
124+bg-graph-yellow-0 = "#facfd6"
125+bg-graph-yellow-1 = "#b57b85"
126+bg-graph-blue-0 = "#4f9fdf"
127+bg-graph-blue-1 = "#004559"
128+bg-graph-magenta-0 = "#b6427f"
129+bg-graph-magenta-1 = "#7f506f"
130+bg-graph-cyan-0 = "#57dfea"
131+bg-graph-cyan-1 = "#00808f"
132+
133+# Special purpose
134+bg-completion = "#004253"
135+bg-hover = "#8e3e3b"
136+bg-hover-secondary = "#00405f"
137+bg-hl-line = "#2f3849"
138+bg-region = "#5a5a5a"
139+fg-region = "#ffffff"
140+
141+bg-char-0 = "#922a00"
142+bg-char-1 = "#00709f"
143+bg-char-2 = "#5f3faf"
144+
145+bg-mode-line-active = "#003c52"
146+fg-mode-line-active = "#f0f0f0"
147+border-mode-line-active = "#5f8fb4"
148+bg-mode-line-inactive = "#2d2d2d"
149+fg-mode-line-inactive = "#969696"
150+border-mode-line-inactive = "#606060"
151+
152+modeline-err = "#ff7fbf"
153+modeline-warning = "#df9f93"
154+modeline-info = "#4fcfef"
155+
156+bg-tab-bar = "#313131"
157+bg-tab-current = "#000000"
158+bg-tab-other = "#545454"
159+
160+# Diffs
161+bg-added = "#004254"
162+bg-added-faint = "#003042"
163+bg-added-refine = "#004f7f"
164+bg-added-fringe = "#008fcf"
165+fg-added = "#9fdfdf"
166+fg-added-intense = "#50c0ef"
167+
168+bg-changed = "#2f123f"
169+bg-changed-faint = "#1f022f"
170+bg-changed-refine = "#3f325f"
171+bg-changed-fringe = "#7f55a0"
172+fg-changed = "#e3cfff"
173+fg-changed-intense = "#cf9fe2"
174+
175+bg-removed = "#4f1119"
176+bg-removed-faint = "#380a0f"
177+bg-removed-refine = "#781a1f"
178+bg-removed-fringe = "#b81a1f"
179+fg-removed = "#ffbfbf"
180+fg-removed-intense = "#ff9095"
181+
182+bg-diff-context = "#1a1a1a"
183+
184+# Paren match
185+bg-paren-match = "#2f7f9f"
186+bg-paren-expression = "#453040"
runtime/themes/noctis.toml+25−25
@@ -7,8 +7,8 @@
77
88'property' = { fg = "red" } # Regex group names.
99"warning" = { fg ="yellow", modifiers = ["bold"] } # Editor warnings.
10−"error" = { bg = "mid-green", fg = "red", modifiers = ["bold"] } # Editor errors, like mis-typing a command.
11−"info" = { fg = "mid-blue", bg = "mid-green" } # Code diagnostic info in gutter (LSP).
10+"error" = { fg = "red", modifiers = ["bold"] } # Editor errors, like mis-typing a command.
11+"info" = { fg = "mid-blue" } # Code diagnostic info in gutter (LSP).
1212# ? Difference between info and hint ?
1313"hint" = { fg = "light-green", modifiers = ["bold"] } # Code diagnostics hint in gutter (LSP).
1414
@@ -21,14 +21,14 @@
2121# UI ==============================
2222# For styling helix itself.
2323
24−'ui.background' = { bg = "dark-green"} # Default background color.
24+'ui.background' = { fg = "autocomp-green", bg = "dark-green" } # Default background color.
2525'ui.window' = { fg = "mid-green" } # Window border between splits.
2626
2727'ui.gutter' = { } # Left gutter for diagnostics and breakpoints.
2828
2929'ui.text' = { fg = "white" } # Default text color.
3030'ui.text.focus' = { fg = "white", bg = "mid-green", modifiers = ["bold"] } # Selection highlight in buffer-picker or file-picker.
31−'ui.text.info' = { } # Info popup contents (space mode menu).
31+'ui.text.info' = { fg = "white" } # Info popup contents (space mode menu).
3232
3333'ui.cursor' = { fg = "dark-green", bg = "white" } # Fallback cursor colour, non-primary cursors when there are multiple (shift-c).
3434'ui.cursor.primary' = { fg = "dark-green", bg = "light-blue" } # The primary cursor when there are multiple (shift-c).
@@ -40,8 +40,8 @@
4040'ui.selection.primary' = { bg = "autocomp-green" } # The primary selection when there are multiple.
4141'ui.cursorline.primary' = { bg = 'mid-green' }
4242
43−'ui.linenr' = { bg = "dark-green", fg = "gray" } # Line numbers.
44−'ui.linenr.selected' = { bg = "mid-green", fg = "light-green", modifiers = [ "bold" ] } # Current line number.
43+'ui.linenr' = { fg = "gray" } # Line numbers.
44+'ui.linenr.selected' = { fg = "light-green", modifiers = [ "bold" ] } # Current line number.
4545
4646'ui.virtual' = { fg = "mid-green" } # Namespace for additions to the editing area.
4747'ui.virtual.ruler' = { bg = "mid-green" } # Vertical rulers (colored columns in editing area).
@@ -75,9 +75,9 @@
7575#'type.builtin' = { } # Primitive types of the language (string, int, float).
7676#'type.enum.variant' = { } # A variant of an enum.
7777
78−'constructor' = { fg = "mid-blue", modifiers = ["bold"]} # Constructor method for a class or struct. And in some cases applies to module names
78+'constructor' = { fg = "light-blue", modifiers = ["bold"]} # Constructor method for a class or struct. And in some cases applies to module names
7979
80−'constant' = { fg = "light-blue" } # Constant value
80+'constant' = { fg = "light-blue", modifiers = ["bold"] } # Constant value
8181'constant.builtin' = { fg = "mid-blue", modifiers = ["bold"] } # Special constants like `true`, `false`, `none`, etc.
8282'constant.builtin.boolean' = { } # True or False.
8383'constant.character' = { fg = "light-green"} # Constant of character type.
@@ -88,21 +88,20 @@
8888
8989'string' = { fg = "light-green" } # String literal.
9090'string.regexp' = { } # Regular expression literal.
91−'string.special' = { fg = "light-blue" } # Strings containing a path, URL, etc.
92−'string.special.path' = { } # String containing a file path.
93−'string.special.url' = { } # String containing a web URL.
94−'string.special.symbol' = { } # Erlang/Elixir atoms, Ruby symbols, Clojure keywords.
91+'string.special' = { fg = "red" } # Strings containing a path, URL, etc.
92+# 'string.special.path' = { } # String containing a file path.
93+# 'string.special.url' = { } # String containing a web URL.
94+# 'string.special.symbol' = { } # Erlang/Elixir atoms, Ruby symbols, Clojure keywords.
9595
9696'comment' = { fg = "gray", modifiers = ["italic"] } # This is a comment.
9797'comment.line' = { } # Line comments, like this.
9898'comment.block' = { } # Block comments, like /* this */ in some languages.
9999'comment.block.documentation' = { } # Doc comments, e.g '///' in rust.
100100
101−'variable' = { fg = "white" } # Variable names.
102−'variable.builtin' = { } # Language reserved variables: `this`, `self`, `super`, etc.
103−'variable.parameter' = { } # Function parameters.
104−'variable.other.member' = { } # Fields of composite data types (e.g. structs, unions).
105−'variable.function' = { } # ?
101+'variable' = { fg = "light-orange" } # Variable names.
102+# 'variable.builtin' = { } # Language reserved variables: `this`, `self`, `super`, etc.
103+# 'variable.parameter' = { } # Function parameters.
104+'variable.other.member' = { fg = "orange" } # Fields of composite data types (e.g. structs, unions).
106105
107106'label' = { fg = "purple" } # Loop labels in rust.
108107
@@ -113,17 +112,17 @@
113112'keyword' = { fg = "pink", modifiers = ["bold"] } # Language reserved keywords.
114113'keyword.control' = { fg = "pink", modifiers = ["bold"] } # Control keywords.
115114'keyword.control.conditional' = { fg = "pink", modifiers = ["bold"] } # 'if', 'else', 'elif'.
116−'keyword.control.repeat' = { } # 'for', 'while', 'loop'.
115+# 'keyword.control.repeat' = { } # 'for', 'while', 'loop'.
117116'keyword.control.import' = { fg = "pink", modifiers = ["italic"] } # 'import', 'export' ('use'?).
118−'keyword.control.return' = { } # 'return' in most languages.
117+# 'keyword.control.return' = { } # 'return' in most languages.
119118'keyword.control.exception' = {fg = "pink", modifiers = ["bold"] } # 'raise' in python.
120−'keyword.operator' = { } # 'or', 'and', 'in'.
119+'keyword.operator' = { fg = "pink" } # 'or', 'and', 'in'.
121120'keyword.directive' = { fg = "purple" } # Preprocessor directives (#if in C).
122−'keyword.function' = { } # The keyword to define a function: 'def', 'fun', 'fn'.
121+'keyword.function' = { fg= "red" } # The keyword to define a function: 'def', 'fun', 'fn'.
123122
124123'operator' = { fg = "pink", modifiers = ["bold"] } # Logical (&&, ||) and - I assume - Mathematical (+, %) operators
125124
126−'function' = { fg = "dark-blue"}
125+'function' = { fg = "mid-blue"}
127126'function.builtin' = { fg = "dark-blue" }
128127'function.method' = { fg = "dark-blue" } # Class / Struct methods.
129128'function.macro' = { fg = "purple" } # Like macros in rust.
@@ -181,8 +180,8 @@
181180# Version control changes.
182181
183182'diff.plus' = "light-green" # { } # Additions.
184−'diff.minus' = "yellow" # { } # Deletions.
185−'diff.delta' = "red" # { } # Modifications.
183+'diff.minus' = "red" # { } # Deletions.
184+'diff.delta' = "yellow" # { } # Modifications.
186185# 'diff.delta.moved' = { } # Renamed or moved files / changes.
187186
188187
@@ -197,8 +196,9 @@ yellow = "#ffd800"
197196purple = "#918cff"
198197white = "#b1cace"
199198orange = "#ffa864"
199+light-orange = "#fff2c5"
200200gray = "#5b858b" # mainly for comments/background text
201−light-gray = "#354e51" # used when whitespace rendering is enabled and for indent-guides
201+light-gray = "#2a3c41" # used when whitespace rendering is enabled and for indent-guides
202202red = "#e34e1b"
203203
204204dark-blue = "#19a2b7"
runtime/themes/nord.toml+9
@@ -134,6 +134,15 @@
134134"ui.bufferline" = { fg = "nord5", bg = "nord1" }
135135"ui.bufferline.active" = { fg = "nord6", bg = "nord2", underline = { color = "nord8", style = "line" }, modifiers = [ "italic" ] }
136136
137+# Markup
138+"markup.heading" = "nord8"
139+"markup.list" = "nord9"
140+"markup.bold" = { modifiers = ["bold"] }
141+"markup.italic" = { modifiers = ["italic"] }
142+"markup.strikethrough" = { modifiers = ["crossed_out"] }
143+"markup.link.text" = "nord8"
144+"markup.raw" = "nord7"
145+
137146[palette]
138147# Polar Night is made up of four darker colors that are commonly used for base elements like backgrounds or text color in bright ambiance designs.
139148#
runtime/themes/rose_pine.toml+30−28
@@ -10,7 +10,7 @@
1010"ui.cursor" = { fg = "text", bg = "highlight_high" }
1111# "ui.cursor.select" = {}
1212"ui.cursor.match" = { fg = "text", bg = "highlight_med" }
13−"ui.cursor.primary" = { fg = "text", bg = "muted"}
13+"ui.cursor.primary" = { fg = "text", bg = "muted" }
1414
1515# "ui.gutter" = {}
1616# "ui.gutter.selected" = {}
@@ -18,6 +18,8 @@
1818"ui.linenr" = { fg = "muted" }
1919"ui.linenr.selected" = { fg = "text" }
2020
21+"ui.bufferline" = { fg = "muted", bg = "base" }
22+"ui.bufferline.active" = { fg = "text", bg = "overlay" }
2123"ui.statusline" = { fg = "subtle", bg = "surface" }
2224"ui.statusline.inactive" = { fg = "muted", bg = "surface" }
2325"ui.statusline.normal" = { fg = "rose", bg = "rose_10" }
@@ -71,8 +73,8 @@
7173"constructor" = "foam"
7274
7375"constant" = "foam"
74−"constant.builtin" = "rose"
75−# "constant.builtin.boolean" = ""
76+"constant.builtin" = "love"
77+"constant.builtin.boolean" = "rose"
7678"constant.character" = "gold"
7779"constant.character.escape" = "pine"
7880"constant.numeric" = "gold"
@@ -86,7 +88,7 @@
8688# "string.special.url" = ""
8789# "string.special.symbol" = ""
8890
89−"comment" = { fg = "muted", modifiers = ["italic"]}
91+"comment" = { fg = "muted", modifiers = ["italic"] }
9092# "comment.line" = ""
9193# "comment.block" = ""
9294# "comment.block.documentation" = ""
@@ -95,7 +97,7 @@
9597"variable.builtin" = "love"
9698"variable.parameter" = "iris"
9799# "variable.other" = ""
98−# "variable.other.member" = ""
100+"variable.other.member" = "foam"
99101
100102"label" = "foam"
101103
@@ -120,7 +122,7 @@
120122
121123"operator" = "subtle"
122124
123−"function" = "rose" # maybe pine
125+"function" = "rose" # maybe pine
124126"function.builtin" = "love"
125127# "function.method" = ""
126128# "function.macro" = ""
@@ -128,7 +130,7 @@
128130
129131"tag" = "foam"
130132
131−"namespace" = "iris"
133+"namespace" = "text"
132134
133135"markup.heading.marker" = "muted"
134136"markup.heading" = { fg = "iris", modifiers = ["bold"] }
@@ -147,7 +149,7 @@
147149"markup.italic" = { modifiers = ["italic"] }
148150"markup.strikethrough" = { modifiers = ["crossed_out"] }
149151"markup.link" = "iris"
150−"markup.link.url" = { fg = "iris", underline = { color = "iris", style = "line" } }
152+"markup.link.url" = { fg = "iris", underline = { color = "iris", style = "line" } }
151153"markup.link.label" = "subtle"
152154"markup.link.text" = "text"
153155"markup.quote" = "subtle"
@@ -167,24 +169,24 @@
167169# "diff.delta.moved" = ""
168170
169171[palette]
170−base = "#191724"
171−surface = "#1f1d2e"
172−overlay = "#26233a"
173−muted = "#6e6a86"
174−subtle = "#908caa"
175−text = "#e0def4"
176−love = "#eb6f92"
177−love_10 = "#311f30"
178−gold = "#f6c177"
179−gold_10 = "#30282c"
180−rose = "#ebbcba"
181−rose_10 = "#2f2834"
182−pine = "#31748f"
183−pine_10 = "#1a2030"
184−foam = "#9ccfd8"
185−foam_10 = "#252937"
186−iris = "#c4a7e7"
187−iris_10 = "#2b2539"
188−highlight_low = "#21202e"
189−highlight_med = "#403d52"
172+base = "#191724"
173+surface = "#1f1d2e"
174+overlay = "#26233a"
175+muted = "#6e6a86"
176+subtle = "#908caa"
177+text = "#e0def4"
178+love = "#eb6f92"
179+love_10 = "#311f30"
180+gold = "#f6c177"
181+gold_10 = "#30282c"
182+rose = "#ebbcba"
183+rose_10 = "#2f2834"
184+pine = "#31748f"
185+pine_10 = "#1a2030"
186+foam = "#9ccfd8"
187+foam_10 = "#252937"
188+iris = "#c4a7e7"
189+iris_10 = "#2b2539"
190+highlight_low = "#21202e"
191+highlight_med = "#403d52"
190192highlight_high = "#524f67"
runtime/tutor+154
@@ -1186,6 +1186,160 @@ multiple cursors, they won't be uncommented but commented again.
11861186
11871187
11881188
1189+=================================================================
1190+= 12.1 USING MATCH MODE JUMP =
1191+=================================================================
1192+
1193+To switch to match mode from normal mode, type m. This feature
1194+is particularly useful for handling bracket pairs and their
1195+contents.
1196+
1197+There are several actions that can be performed in match mode,
1198+as indicated by the help pop-up. To jump to a matching bracket pair,
1199+simply press mm. For example on the lines below (starting with
1200+-->), move the cursor in normal mode to (, and then press mm to jump
1201+to the matching ). You can do the same on the line below: for example
1202+move to ], and press mm to jump to [ .
1203+
1204+--> you can (jump between matching parenthesis)
1205+--> or between matching [ square brackets ]
1206+--> now { you know the drill: this works with brackets too }
1207+
1208+
1209+
1210+
1211+=================================================================
1212+= 12.2 USING MATCH MODE SELECT INSIDE =
1213+=================================================================
1214+
1215+Match mode also lets you select the "inside" content between a
1216+pair of brackets or other delimiters. In the lines below:
1217+
1218+- move to the --> line, put your cursor in normal mode at any
1219+location between the parenthesis, for example at 'x', and press
1220+mi( or mi) to select the whole content inside the parenthesis
1221+(parenthesis excluded). As usual, you can then do anything you want
1222+with the selection (for example, press c to change it)
1223+
1224+--> outside and (inside x parenthesis) - and outside again
1225+
1226+Test below that you can do the same with [], or {}, or with
1227+nested combinations of these (this will act on the immediately
1228+surrounding matching pair). This also works with "" and similar
1229+
1230+--> test [ with square brackets ] !
1231+--> try ( with nested [ pairs of ( parenthesis) and "brackets" ])
1232+
1233+=================================================================
1234+= 12.3 USING MATCH MODE SELECT AROUND =
1235+=================================================================
1236+
1237+You can also select the "around" content, i.e. both the inside
1238+content and the delimiters themselves, by using the ma select.
1239+For example, move to the line under, move your cursor in normal
1240+mode to any position between the (), and select the content of
1241+the (), including the surrounding (), by typing ma( or ma). As
1242+usual, you can do anything you want with the selection, for
1243+example delete it all with ma(d .
1244+
1245+--> you ( select x around ) to include delimiters in the select
1246+
1247+This naturally works with other delimiters too:
1248+
1249+--> try [ with 'square' brackets ] too!
1250+
1251+
1252+
1253+
1254+
1255+=================================================================
1256+= 12.4 USING MATCH MODE SURROUND =
1257+=================================================================
1258+
1259+The match mode can also be used to add surrounding around the
1260+current selection. For example, move to the line below, then:
1261+ * i) select the "select all of this" line segment (for example,
1262+move in normal mode the cursor to the start of select, then enter
1263+selection mode with v , then select the 4 next words with 4e ),
1264+ * ii) press ms( or ms) to surround the selection with a pair of
1265+parenthesis.
1266+
1267+--> so, select all of this, and surround it with ()
1268+
1269+You can do the same with other delimiters: for example, ms' on
1270+WORD below to surround it with a pair of ''. You can try also
1271+with adding a surrounding pair of "", or {}, or [].
1272+
1273+--> surround this WORD !
1274+
1275+
1276+
1277+=================================================================
1278+= 12.5 USING MATCH MODE DELETE SURROUND =
1279+=================================================================
1280+
1281+You can delete surrounding pair of delimiters with the md
1282+command. On the line below, move the cursor anywhere
1283+within the pair of (), for example to the 'x', then from there,
1284+in normal mode, press md( or md) to delete the surrounding
1285+pair of parenthesis.
1286+
1287+--> delete (the x pair of parenthesis) from within!
1288+
1289+You can naturally delete other kinds of surroundings:
1290+
1291+--> delete (nested [delimiters]): "this" will delete the nearest
1292+matching surrounding pair.
1293+--> delete "layers "of" quote marks" too: this will delete the
1294+nearest previous and following quote marks
1295+
1296+Trying to delete unexisting surrounding delimiters print an error
1297+at the bottom bar and does nothing.
1298+
1299+=================================================================
1300+= 12.6 USING MATCH MODE REPLACE SURROUND =
1301+=================================================================
1302+
1303+You can replace surrounding pairs of delimiters with the mr
1304+command. On the line below, move the cursor to
1305+anywhere within the pair of (), for example on the 'x', then in
1306+normal mode, press mr([ to replace the pair of () with a pair
1307+of [].
1308+
1309+--> replace the (pair from x within), with something else
1310+
1311+This command will act on the closest enclosing pair, so you
1312+can try replacing different surrounding in the following:
1313+
1314+--> some (nested surroundings [can be replaced])
1315+--> this "works with 'other surroundings' too"
1316+
1317+You can try to replace a non existing pair: this will show
1318+an error warning at the bottom bar and do nothing.
1319+
1320+
1321+=================================================================
1322+= CHAPTER 12 RECAP =
1323+=================================================================
1324+
1325+You can enter the match mode with the m key; this will show the
1326+actions available in a popup. This will allow you to:
1327+ * jump to matching pair of delimiters with mm (you must have a
1328+ delimiter belonging to a pair under your cursor)
1329+ * select inside a pair of delimiters surrounding your cursor
1330+ (i.e. select the content but not the delimiters) with mi(
1331+ and similar
1332+ * select around a pair of delimiters surrounding your cursor
1333+ (i.e. select the content and the delimiters) with ma( and
1334+ similar
1335+ * delete surrounding delimiters with md( and similar
1336+ * add surrounding delimiters around the selection with ms(
1337+ * replace a pair of delimiters surrounding your selection with
1338+ mr([ to replace for example surrounding () with []
1339+
1340+
1341+
1342+
11891343=================================================================
11901344 This tutorial is still a work-in-progress.
11911345 More sections are planned.
rust-toolchain.toml+2−2
@@ -1,3 +1,3 @@
11[toolchain]
2−channel = "1.65.0"
3−components = ["rustfmt", "rust-src"]
2+channel = "1.70.0"
3+components = ["rustfmt", "rust-src", "clippy"]
xtask/Cargo.toml+12−6
@@ -1,13 +1,19 @@
11[package]
22name = "xtask"
3−version = "0.6.0"
4−edition = "2021"
3+version.workspace = true
4+authors.workspace = true
5+edition.workspace = true
6+license.workspace = true
7+rust-version.workspace = true
8+categories.workspace = true
9+repository.workspace = true
10+homepage.workspace = true
511
612# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
713
814[dependencies]
9−helix-term = { version = "0.6", path = "../helix-term" }
10−helix-core = { version = "0.6", path = "../helix-core" }
11−helix-view = { version = "0.6", path = "../helix-view" }
12−helix-loader = { version = "0.6", path = "../helix-loader" }
15+helix-term = { path = "../helix-term" }
16+helix-core = { path = "../helix-core" }
17+helix-view = { path = "../helix-view" }
18+helix-loader = { path = "../helix-loader" }
1319toml = "0.7"