diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml
deleted file mode 100644
index 223f8450..00000000
--- a/.github/workflows/gh-pages.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: Github Pages
-
-on:
- push:
- branches:
- - master
- tags:
- - '*'
-
-jobs:
- deploy:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
-
- - name: Setup mdBook
- uses: peaceiris/actions-mdbook@v1
- with:
- mdbook-version: 'latest'
- # mdbook-version: '0.4.8'
-
- - run: mdbook build book
-
- - name: Set output directory
- run: |
- OUTDIR=$(basename ${{ github.ref }})
- echo "OUTDIR=$OUTDIR" >> $GITHUB_ENV
-
- - name: Deploy
- uses: peaceiris/actions-gh-pages@v3
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./book/book
- destination_dir: ./${{ env.OUTDIR }}
-
- - name: Deploy stable
- uses: peaceiris/actions-gh-pages@v3
- if: startswith(github.ref, 'refs/tags/')
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./book/book
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9518a537..547d2480 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -116,8 +116,10 @@ jobs:
- name: Install ${{ matrix.rust }} toolchain
uses: dtolnay/rust-toolchain@master
with:
+ profile: minimal
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
+ override: true
# Install a pre-release version of Cross
# TODO: We need to pre-install Cross because we need cross-rs/cross#591 to
@@ -137,8 +139,12 @@ jobs:
echo "target flag is: ${{ env.TARGET_FLAGS }}"
- name: Run cargo test
+ uses: actions-rs/cargo@v1
if: "!matrix.skip_tests"
- run: ${{ env.CARGO }} test --release --locked --target ${{ matrix.target }} --workspace
+ with:
+ use-cross: ${{ matrix.cross }}
+ command: test
+ args: --release --locked --target ${{ matrix.target }} --workspace
- name: Set profile.release.strip = true
shell: bash
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..13566b81
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/helix.iml b/.idea/helix.iml
new file mode 100644
index 00000000..bc2cd874
--- /dev/null
+++ b/.idea/helix.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..03d9549e
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..6c65ec58
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..94a25f7f
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 69ba8444..d834f3de 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -84,20 +84,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
-dependencies = [
- "lazy_static",
- "memchr",
- "regex-automata",
-]
-
-[[package]]
-name = "bstr"
-version = "1.0.1"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd"
+checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b"
dependencies = [
"memchr",
"once_cell",
@@ -250,15 +239,6 @@ dependencies = [
"cfg-if",
]
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
-dependencies = [
- "cfg-if",
-]
-
[[package]]
name = "crossterm"
version = "0.25.0"
@@ -287,9 +267,9 @@ dependencies = [
[[package]]
name = "cxx"
-version = "1.0.82"
+version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453"
+checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -299,9 +279,9 @@ dependencies = [
[[package]]
name = "cxx-build"
-version = "1.0.82"
+version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0"
+checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70"
dependencies = [
"cc",
"codespan-reporting",
@@ -314,15 +294,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
-version = "1.0.82"
+version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71"
+checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c"
[[package]]
name = "cxxbridge-macro"
-version = "1.0.82"
+version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470"
+checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5"
dependencies = [
"proc-macro2",
"quote",
@@ -339,7 +319,7 @@ dependencies = [
"hashbrown 0.12.3",
"lock_api",
"once_cell",
- "parking_lot_core 0.9.4",
+ "parking_lot_core 0.9.6",
]
[[package]]
@@ -448,9 +428,9 @@ dependencies = [
[[package]]
name = "filetime"
-version = "0.2.18"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3"
+checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
dependencies = [
"cfg-if",
"libc",
@@ -545,7 +525,7 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9e5fd7bc63ad527d64584f8d01f99b89c051f5fbb8144b58ae5f812775065cf"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"btoi",
"git-date",
"itoa",
@@ -555,11 +535,11 @@ dependencies = [
[[package]]
name = "git-attributes"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8013dfce47c1e29236d732308933e2c77af5355ec5105755d26faf7764d3f7b"
+checksum = "b2c9687a890892650e8574e123b4b633d277b99953cb877dc02aba852a0139fa"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"compact_str",
"git-features",
"git-glob",
@@ -589,20 +569,20 @@ dependencies = [
[[package]]
name = "git-command"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "215145cc1686a45bc6f9872b153a0d3f3c40a1b94173a928325e1b53dfa5e2af"
+checksum = "5a19fe1efc0b4969b2b2a14621f6cf6a007cf6cbabcf344e078271b65d1f7cef"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
]
[[package]]
name = "git-config"
-version = "0.15.0"
+version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9da662fd64ac69772158dcf04777da6266f0f36bc9a310b3eb2d805bb696315"
+checksum = "500cc0517781f9f573c4dc26feb3ae0cdc28ae7160a81ef104590943984f6a8e"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"git-config-value",
"git-features",
"git-glob",
@@ -624,7 +604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989a90c1c630513a153c685b4249b96fdf938afc75bf7ef2ae1ccbd3d799f5db"
dependencies = [
"bitflags",
- "bstr 1.0.1",
+ "bstr",
"git-path",
"libc",
"thiserror",
@@ -636,7 +616,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97cd6bbe001afd6356b35ef13f2a6b0f0abc0133d1b2ecaec1033bdd769616d6"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"git-command",
"git-config-value",
"git-path",
@@ -648,11 +628,11 @@ dependencies = [
[[package]]
name = "git-date"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "412c9b89026505bd24d5f8acafa578de6eea3b271ece307a73b8e646e671302a"
+checksum = "3777ed3a92334193bc5032d468dee2ddb7d1101263e58e0d2edcc308c06948b5"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"itoa",
"thiserror",
"time",
@@ -672,11 +652,11 @@ dependencies = [
[[package]]
name = "git-discover"
-version = "0.12.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e9e26e0bc434643228cd418185bd28ca5c7cf831bde1da434807391c27ac40e"
+checksum = "a2738a9941f1411cff31e6ea4399a6c7304cc3ea34fb8c1c6f1aef1f667d46cc"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"git-hash",
"git-path",
"git-ref",
@@ -686,9 +666,9 @@ dependencies = [
[[package]]
name = "git-features"
-version = "0.26.0"
+version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ff74064fa007c5beefa89a64bb72834f32b3c497750a56c79c6802bbdb311f9"
+checksum = "0019327672cb759f851d1b18fdcc36bb797dc62b925cb93c8c881b54735eb2c2"
dependencies = [
"crc32fast",
"flate2",
@@ -703,12 +683,12 @@ dependencies = [
[[package]]
name = "git-glob"
-version = "0.5.1"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3908404c9b76ac7b3f636a104142378d3eaa78623cbc6eb7c7f0651979d48e8a"
+checksum = "aa73cf9c9c1a66e28de1cf250fc1ebe323e7c7c59768c1a2331e3b3308e783a3"
dependencies = [
"bitflags",
- "bstr 1.0.1",
+ "bstr",
]
[[package]]
@@ -728,18 +708,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c52b625ad8cc360a0b7f426266f21fb07bd49b8f4ccf1b3ca7bc89424db1dec4"
dependencies = [
"git-hash",
- "hashbrown 0.13.1",
+ "hashbrown 0.13.2",
]
[[package]]
name = "git-index"
-version = "0.12.1"
+version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "485da97dd4f69c7d9a8dc238cd6f4a726387ffc34573489e8e0d2bee266e3454"
+checksum = "b82fd3d70ed6fbceb7573f145fbf79371e4d0c8dbdf7ad46f3a03328239ddda7"
dependencies = [
"atoi",
"bitflags",
- "bstr 1.0.1",
+ "bstr",
"filetime",
"git-bitmap",
"git-features",
@@ -755,9 +735,9 @@ dependencies = [
[[package]]
name = "git-lock"
-version = "3.0.0"
+version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89e4f05b8a68c3a5dd83a6651c76be384e910fe283072184fdab9d77f87ccec2"
+checksum = "e7cf6a3c9d1a9932bb9bcb7e0044e2e429f9d94711969a7d2a09e34ae21f6437"
dependencies = [
"fastrand",
"git-tempfile",
@@ -766,11 +746,11 @@ dependencies = [
[[package]]
name = "git-mailmap"
-version = "0.9.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0316b4346f3e162ade368209efb8a609b587793c74aa3b8de0ec01a4f3580120"
+checksum = "1957f2f550e345f70cb0615d390fb0446d41eeb5bc87824b7bae31efd8cfc2da"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"git-actor",
"quick-error",
]
@@ -781,7 +761,7 @@ version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f8563e2d6f524d7053f3106714f99ecdc3adbba2cb7108c09d71a02579f2e19"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"btoi",
"git-actor",
"git-features",
@@ -796,9 +776,9 @@ dependencies = [
[[package]]
name = "git-odb"
-version = "0.40.0"
+version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "616115a0e3daff6e08842758d24547b37a6eb6d0e2eedd95a740c3aaa2750333"
+checksum = "b43514e1d1062613352a10f96d48e69967f4c420776596e1af9a6f368df2eea2"
dependencies = [
"arc-swap",
"git-features",
@@ -814,9 +794,9 @@ dependencies = [
[[package]]
name = "git-pack"
-version = "0.30.0"
+version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cd16b88f4b66041f41ca510c28bd81c4ee7363c5a544b3d62b4170432965871"
+checksum = "9e124d13e4e4b53ca7544e9786f30dafe2e76b4a75ba62b2156ee0656b356c71"
dependencies = [
"bytesize",
"clru",
@@ -842,15 +822,15 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e40e68481a06da243d3f4dfd86a4be39c24eefb535017a862e845140dcdb878a"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"thiserror",
]
[[package]]
name = "git-prompt"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3612a486e507dd431ef0f7108eeaafc8fd1ed7bd0f205a88554f6f91fe5dccbf"
+checksum = "ad3f84ec28896f6a4b3f3174a1125117ac91788b1c64d96f25eabcd8d01cc7e3"
dependencies = [
"git-command",
"git-config-value",
@@ -865,16 +845,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd11f4e7f251ab297545faa4c5a4517f4985a43b9c16bf96fa49107f58e837f"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"btoi",
"quick-error",
]
[[package]]
name = "git-ref"
-version = "0.23.0"
+version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e6767925a6fc4af5c5a81e348d1d851c1b3ab2b512bd7f562ac11be37c14468"
+checksum = "2a2c29bab109acaf626d49a54f1f85ab7f0911268fbf62c2b39680ef4ef19069"
dependencies = [
"git-actor",
"git-features",
@@ -891,11 +871,11 @@ dependencies = [
[[package]]
name = "git-refspec"
-version = "0.7.0"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddf310ed5f2829ac0af96e7d4aebd4ae4b89f0718a7ae3666d09b02b2c5a1dfd"
+checksum = "419fba469ca7dca4746de8b2be6a21990b276f3974acaa94314f39d4c2bbfc0a"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"git-hash",
"git-revision",
"git-validate",
@@ -905,9 +885,9 @@ dependencies = [
[[package]]
name = "git-repository"
-version = "0.32.0"
+version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "993277960cb7e2d3991a11c1ec6951c1d142de052c26a18d2db64304e52d3741"
+checksum = "1a958d3fa83660d15c6535765477a8895156bdd6a922dbd0fc445afa42f4b534"
dependencies = [
"git-actor",
"git-attributes",
@@ -948,11 +928,11 @@ dependencies = [
[[package]]
name = "git-revision"
-version = "0.10.0"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f9a6bd28c9d1676bb96f428cd09614ae18a0087d7cea1cebfd177e25f99b2af"
+checksum = "bfc3f7c901777f8318f059dbdf73dbda05acdb36c631fe12465bd955e230a205"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"git-date",
"git-hash",
"git-hashtable",
@@ -962,9 +942,9 @@ dependencies = [
[[package]]
name = "git-sec"
-version = "0.6.0"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1802e8252fa223b0ad89a393aed461132174ced1e6842a41f56dc92a3fc14f"
+checksum = "6696a816445a51f76995d579a3122f98247377cc45cd681764f740f3a2666004"
dependencies = [
"bitflags",
"dirs",
@@ -975,9 +955,9 @@ dependencies = [
[[package]]
name = "git-tempfile"
-version = "3.0.0"
+version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6bb4dee86c8cae5a078cfaac3b004ef99c31548ed86218f23a7ff9b4b74f3be"
+checksum = "2d851911a2b043dc1ab6cd5432ce7a3ee3a2fd614ed87428cec1b15f5abb7e0c"
dependencies = [
"dashmap",
"libc",
@@ -1001,11 +981,11 @@ dependencies = [
[[package]]
name = "git-url"
-version = "0.13.0"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c85af407ed0dbb8d8da2a7241827d2fd5681186d9dab3570fc8dd8d6152ec48f"
+checksum = "cc9a3df0498c511cf34739eab2692352939b54075c2fc96e8f688d402f3f1250"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"git-features",
"git-path",
"home",
@@ -1019,17 +999,17 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0431cf9352c596dc7c8ec9066ee551ce54e63c86c3c767e5baf763f6019ff3c2"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"thiserror",
]
[[package]]
name = "git-worktree"
-version = "0.12.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3bc63878f134e08ed52dba5d82422798c01a3f2e48c38ae9a2f7ff9194f362"
+checksum = "0c28b292694c98bba8225c39d4e86605843882ba7117ca98491841761e710547"
dependencies = [
- "bstr 1.0.1",
+ "bstr",
"git-attributes",
"git-features",
"git-glob",
@@ -1043,12 +1023,12 @@ dependencies = [
[[package]]
name = "globset"
-version = "0.4.9"
+version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
+checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
"aho-corasick",
- "bstr 0.2.17",
+ "bstr",
"fnv",
"log",
"regex",
@@ -1056,21 +1036,21 @@ dependencies = [
[[package]]
name = "grep-matcher"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d27563c33062cd33003b166ade2bb4fd82db1fd6a86db764dfdad132d46c1cc"
+checksum = "3902ca28f26945fe35cad349d776f163981d777fee382ccd6ef451126f51b319"
dependencies = [
"memchr",
]
[[package]]
name = "grep-regex"
-version = "0.1.10"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1345f8d33c89f2d5b081f2f2a41175adef9fd0bed2fea6a26c96c2deb027e58e"
+checksum = "997598b41d53a37a2e3fc5300d5c11d825368c054420a9c65125b8fe1078463f"
dependencies = [
"aho-corasick",
- "bstr 0.2.17",
+ "bstr",
"grep-matcher",
"log",
"regex",
@@ -1080,11 +1060,11 @@ dependencies = [
[[package]]
name = "grep-searcher"
-version = "0.1.10"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48852bd08f9b4eb3040ecb6d2f4ade224afe880a9a0909c5563cc59fa67932cc"
+checksum = "5601c4b9f480f0c9ebb40b1f6cbf447b8a50c5369223937a6c5214368c58779f"
dependencies = [
- "bstr 0.2.17",
+ "bstr",
"bytecount",
"encoding_rs",
"encoding_rs_io",
@@ -1104,9 +1084,9 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.13.1"
+version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash 0.8.2",
]
@@ -1121,7 +1101,7 @@ dependencies = [
"chrono",
"encoding_rs",
"etcetera",
- "hashbrown 0.13.1",
+ "hashbrown 0.13.2",
"helix-loader",
"imara-diff",
"log",
@@ -1288,9 +1268,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
-version = "0.1.19"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
@@ -1352,11 +1332,10 @@ dependencies = [
[[package]]
name = "ignore"
-version = "0.4.18"
+version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
+checksum = "a05705bc64e0b66a806c3740bd6578ea66051b157ec42dc219c785cbf185aef3"
dependencies = [
- "crossbeam-utils",
"globset",
"lazy_static",
"log",
@@ -1405,9 +1384,9 @@ dependencies = [
[[package]]
name = "itoa"
-version = "1.0.4"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
+checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "js-sys"
@@ -1426,9 +1405,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.137"
+version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libloading"
@@ -1442,9 +1421,9 @@ dependencies = [
[[package]]
name = "link-cplusplus"
-version = "1.0.7"
+version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
@@ -1537,9 +1516,9 @@ dependencies = [
[[package]]
name = "nom"
-version = "7.1.1"
+version = "7.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c"
dependencies = [
"memchr",
"minimal-lexical",
@@ -1566,9 +1545,9 @@ dependencies = [
[[package]]
name = "num_cpus"
-version = "1.14.0"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
@@ -1607,7 +1586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
- "parking_lot_core 0.9.4",
+ "parking_lot_core 0.9.6",
]
[[package]]
@@ -1626,9 +1605,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.4"
+version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
+checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
dependencies = [
"cfg-if",
"libc",
@@ -1657,9 +1636,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
-version = "1.0.47"
+version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
dependencies = [
"unicode-ident",
]
@@ -1704,9 +1683,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.21"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
@@ -1793,15 +1772,15 @@ dependencies = [
[[package]]
name = "rustversion"
-version = "1.0.9"
+version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
+checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]]
name = "ryu"
-version = "1.0.11"
+version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "same-file"
@@ -1820,9 +1799,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
+checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]]
name = "serde"
@@ -1857,9 +1836,9 @@ dependencies = [
[[package]]
name = "serde_repr"
-version = "0.1.9"
+version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca"
+checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e"
dependencies = [
"proc-macro2",
"quote",
@@ -1979,15 +1958,15 @@ checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]]
name = "str_indices"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d9199fa80c817e074620be84374a520062ebac833f358d74b37060ce4a0f2c0"
+checksum = "5f026164926842ec52deb1938fae44f83dfdb82d0a5b0270c5bd5935ab74d6dd"
[[package]]
name = "syn"
-version = "1.0.104"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce"
+checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
@@ -2141,9 +2120,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
-version = "1.8.0"
+version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
+checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
@@ -2209,9 +2188,9 @@ checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7"
[[package]]
name = "unicode-ident"
-version = "1.0.5"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-linebreak"
@@ -2377,17 +2356,17 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
-version = "0.40.0"
+version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e30acc718a52fb130fec72b1cb5f55ffeeec9253e1b785e94db222178a6acaa1"
+checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244"
dependencies = [
- "windows_aarch64_gnullvm 0.40.0",
- "windows_aarch64_msvc 0.40.0",
- "windows_i686_gnu 0.40.0",
- "windows_i686_msvc 0.40.0",
- "windows_x86_64_gnu 0.40.0",
- "windows_x86_64_gnullvm 0.40.0",
- "windows_x86_64_msvc 0.40.0",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
]
[[package]]
@@ -2396,98 +2375,56 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
- "windows_aarch64_gnullvm 0.42.0",
- "windows_aarch64_msvc 0.42.0",
- "windows_i686_gnu 0.42.0",
- "windows_i686_msvc 0.42.0",
- "windows_x86_64_gnu 0.42.0",
- "windows_x86_64_gnullvm 0.42.0",
- "windows_x86_64_msvc 0.42.0",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.40.0"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3caa4a1a16561b714323ca6b0817403738583033a6a92e04c5d10d4ba37ca10"
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.40.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "328973c62dfcc50fb1aaa8e7100676e0b642fe56bac6bafff3327902db843ab4"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.40.0"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa5b09fad70f0df85dea2ac2a525537e415e2bf63ee31cf9b8e263645ee9f3c1"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.40.0"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a1ad4031c1a98491fa195d8d43d7489cb749f135f2e5c4eed58da094bd0d876"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
-version = "0.42.0"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.40.0"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "520ff37edd72da8064b49d2281182898e17f0688ae9f4070bca27e4b5c162ac7"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.40.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046e5b82215102c44fd75f488f1b9158973d02aa34d06ed85c23d6f5520a2853"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.42.0"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.40.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a0c9c6df55dd1bfa76e131cef44bdd8ec9c819ef3611f04dfe453fd5bfeda28"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.42.0"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "xtask"
diff --git a/README.md b/README.md
index 60125e11..5b7b54f4 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,6 @@
-
-
-
-
-
-
-
-
-
+# Helix
[![Build status](https://github.com/helix-editor/helix/actions/workflows/build.yml/badge.svg)](https://github.com/helix-editor/helix/actions)
-[![GitHub Release](https://img.shields.io/github/v/release/helix-editor/helix)](https://github.com/helix-editor/helix/releases/latest)
-[![Documentation](https://shields.io/badge/-documentation-452859)](https://docs.helix-editor.com/)
-[![GitHub contributors](https://img.shields.io/github/contributors/helix-editor/helix)](https://github.com/helix-editor/helix/graphs/contributors)
-[![Matrix Space](https://img.shields.io/matrix/helix-community:matrix.org)](https://matrix.to/#/#helix-community:matrix.org)
-
-
![Screenshot](./screenshot.png)
@@ -139,7 +125,3 @@ Contributing guidelines can be found [here](./docs/CONTRIBUTING.md).
Your question might already be answered on the [FAQ](https://github.com/helix-editor/helix/wiki/FAQ).
Discuss the project on the community [Matrix Space](https://matrix.to/#/#helix-community:matrix.org) (make sure to join `#helix-editor:matrix.org` if you're on a client that doesn't support Matrix Spaces yet).
-
-# Credits
-
-Thanks to [@JakeHL](https://github.com/JakeHL) for designing the logo!
diff --git a/book/src/configuration.md b/book/src/configuration.md
index a35482e6..af80b177 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -52,6 +52,7 @@ on unix operating systems.
| `auto-save` | Enable automatic saving on focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal. | `false` |
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
+| `completion-trigger-chars` | The chars that trigger completion (additional to all word chars) | `['.', ':']` |
| `auto-info` | Whether to display infoboxes | `true` |
| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` |
| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` |
@@ -172,6 +173,8 @@ auto-pairs = false # defaults to `true`
The default pairs are (){}[]''""``
, but these can be customized by
setting `auto-pairs` to a TOML table:
+Example
+
```toml
[editor.auto-pairs]
'(' = ')'
@@ -237,22 +240,85 @@ tab = "→"
newline = "⏎"
tabpad = "·" # Tabs will look like "→···" (depending on tab width)
```
+<<<<<<< HEAD
+
+### `[editor.explorer]` Section
+Sets explorer side width and style.
+
+ | Key | Description | Default |
+ | --- | ----------- | ------- |
+ | `column-width` | explorer side width | 30 |
+ | `style` | explorer item style, tree or list | tree |
+ | `position` | explorer widget position, embed or overlay | overlay |
+||||||| 43027d91
+=======
### `[editor.indent-guides]` Section
Options for rendering vertical indent guides.
+<<<<<<< HEAD
+| Key | Description | Default |
+| --- | --- | --- |
+| `render` | Whether to render indent guides. | `true` |
+| `character` | Literal character to use for rendering the indent guide | `│` |
+| `rainbow` | Whether or not the indent guides shall have changing colors. | `false` |
+| `skip-levels` | Number of indent levels to skip | `0` |
+||||||| merged common ancestors
+<<<<<<<<< Temporary merge branch 1
+| Key | Description | Default |
+| --- | --- | --- |
+| `render` | Whether to render indent guides. | `false` |
+| `character` | Literal character to use for rendering the indent guide | `│` |
+| `rainbow` | Whether or not the indent guides shall have changing colors. | `false` |
+||||||||| 60aa7d36
+| Key | Description | Default |
+| --- | --- | --- |
+| `render` | Whether to render indent guides. | `false` |
+| `character` | Literal character to use for rendering the indent guide | `│` |
+=========
| Key | Description | Default |
| --- | --- | --- |
| `render` | Whether to render indent guides. | `false` |
| `character` | Literal character to use for rendering the indent guide | `│` |
| `skip-levels` | Number of indent levels to skip | `0` |
+>>>>>>>>> Temporary merge branch 2
+=======
+| Key | Description | Default |
+| --- | --- | --- |
+| `render` | Whether to render indent guides. | `false` |
+| `character` | Literal character to use for rendering the indent guide | `│` |
+| `rainbow` | Whether or not the indent guides shall have changing colors. It can be `none`, `dim` or `normal`| `none` |
+| `skip-levels` | Number of indent levels to skip | `0` |
+>>>>>>> colored-indent-guides
Example:
```toml
[editor.indent-guides]
render = true
+character = "╎"
+<<<<<<< HEAD
+rainbow = true
+||||||| merged common ancestors
+rainbow = true
+||||||||| 60aa7d36
+character = "╎"
+=========
character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
+=======
+rainbow = "normal"
+character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
+>>>>>>> colored-indent-guides
skip-levels = 1
```
+
+### `[editor.explorer]` Section
+Sets explorer side width and style.
+
+ | Key | Description | Default |
+ | --- | ----------- | ------- |
+ | `column-width` | explorer side width | 30 |
+ | `style` | explorer item style, tree or list | tree |
+ | `position` | explorer widget position, embed or overlay | overlay |
+>>>>>>> 0e04c4c93caadb704c11a72bcf626b1f10ff2d98
diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md
index 66e6ac03..434a343b 100644
--- a/book/src/generated/typable-cmd.md
+++ b/book/src/generated/typable-cmd.md
@@ -73,3 +73,4 @@
| `:pipe` | Pipe each selection to the shell command. |
| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
| `:run-shell-command`, `:sh` | Run a shell command |
+| `:lsp-restart` | Restarts the LSP server of the current buffer |
diff --git a/book/src/keymap.md b/book/src/keymap.md
index b38a1f44..1eb48a6f 100644
--- a/book/src/keymap.md
+++ b/book/src/keymap.md
@@ -287,6 +287,8 @@ This layer is a kludge of mappings, mostly pickers.
| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
| `/` | Global search in workspace folder | `global_search` |
| `?` | Open command palette | `command_palette` |
+| `e` | Open or focus explorer | `toggle_or_focus_explorer` |
+| `E` | open explorer recursion | `open_explorer_recursion` |
> TIP: Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file.
@@ -437,3 +439,35 @@ Keys to use within prompt, Remapping currently not supported.
| `Tab` | Select next completion item |
| `BackTab` | Select previous completion item |
| `Enter` | Open selected |
+
+# File explorer
+Keys to use within explorer, Remapping currently not supported.
+
+| Key | Description |
+| ----- | ------------- |
+| `Escape` | Back to editor |
+| `Ctrl-c` | Close explorer |
+| `Enter` | Open file or toggle dir selected |
+| `b` | Back to current root's parent |
+| `f` | Filter items |
+| `z` | Fold currrent level |
+| `k`, `Shift-Tab`, `Up` | select previous item |
+| `j`, `Tab`, `Down` | select next item |
+| `h` | Scroll left |
+| `l` | Scroll right |
+| `G` | Move to last item |
+| `Ctrl-d` | Move down half page |
+| `Ctrl-u` | Move up half page |
+| `Shift-d` | Move down a page |
+| `Shift-u` | Move up a page |
+| `/` | Search item |
+| `?` | Search item reverse |
+| `n` | Repeat last search |
+| `Shift-n` | Repeat last search reverse |
+| `gg` | Move to first item |
+| `ge` | Move to last item |
+| `gc` | Make current dir as root dir |
+| `mf` | Create new file under current item's parent |
+| `md` | Create new dir under current item's parent |
+| `rf` | Remove file selected |
+| `rd` | Remove dir selected |
diff --git a/book/src/themes.md b/book/src/themes.md
index 015ec59b..37ee0924 100644
--- a/book/src/themes.md
+++ b/book/src/themes.md
@@ -107,6 +107,35 @@ Some styles might not be supported by your terminal emulator.
| `double_line` |
+<<<<<<< HEAD
+<<<<<<< HEAD
+### Rainbow
+
+The `rainbow` key is used for rainbow highlight for matching brackets.
+The key is a list of styles.
+
+```toml
+rainbow = ["#ff0000", "#ffa500", "#fff000", { fg = "#00ff00", modifiers = ["bold"] }]
+```
+
+Colors from the palette and modifiers may be used.
+
+||||||| 60aa7d36
+=======
+||||||| merged common ancestors
+=======
+### Rainbow
+
+The `rainbow` key is used for rainbow highlight for matching brackets.
+The key is a list of styles.
+
+```toml
+rainbow = ["#ff0000", "#ffa500", "#fff000", { fg = "#00ff00", modifiers = ["bold"] }]
+```
+
+Colors from the palette and modifiers may be used.
+
+>>>>>>> colored-indent-guides
### Inheritance
Extend upon other themes by setting the `inherits` property to an existing theme.
@@ -122,6 +151,22 @@ inherits = "boo_berry"
berry = "#2A2A4D"
```
+<<<<<<< HEAD
+>>>>>>> seperate_code_action
+||||||| merged common ancestors
+### Rainbow
+
+The `rainbow` key is used for rainbow highlight for matching brackets.
+The key is a list of styles.
+
+```toml
+rainbow = ["#ff0000", "#ffa500", "#fff000", { fg = "#00ff00", modifiers = ["bold"] }]
+```
+
+Colors from the palette and modifiers may be used.
+
+=======
+>>>>>>> colored-indent-guides
### Scopes
The following is a list of scopes available to use for styling.
diff --git a/contrib/themes b/contrib/themes
deleted file mode 120000
index d09bf827..00000000
--- a/contrib/themes
+++ /dev/null
@@ -1 +0,0 @@
-../runtime/themes
\ No newline at end of file
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index ca6cd51e..4404ea96 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -18,34 +18,34 @@ integration = []
helix-loader = { version = "0.6", path = "../helix-loader" }
ropey = { version = "1.5.1", default-features = false, features = ["simd"] }
-smallvec = "1.10"
+smallvec = "1.10.0"
smartstring = "1.0.1"
-unicode-segmentation = "1.10"
-unicode-width = "0.1"
-unicode-general-category = "0.6"
+unicode-segmentation = "1.10.0"
+unicode-width = "0.1.10"
+unicode-general-category = "0.6.0"
# slab = "0.4.2"
-slotmap = "1.0"
-tree-sitter = "0.20"
-once_cell = "1.17"
-arc-swap = "1"
-regex = "1"
-bitflags = "1.3"
+slotmap = "1.0.6"
+tree-sitter = "0.20.9"
+once_cell = "1.17.0"
+arc-swap = "1.6.0"
+regex = "1.7.1"
+bitflags = "1.3.2"
ahash = "0.8.2"
-hashbrown = { version = "0.13.1", features = ["raw"] }
+hashbrown = { version = "0.13.2", features = ["raw"] }
-log = "0.4"
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-toml = "0.5"
+log = "0.4.17"
+serde = { version = "1.0.152", features = ["derive"] }
+serde_json = "1.0.91"
+toml = "0.5.10"
-imara-diff = "0.1.0"
+imara-diff = "0.1.5"
-encoding_rs = "0.8"
+encoding_rs = "0.8.31"
-chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
+chrono = { version = "0.4.23", default-features = false, features = ["alloc", "std"] }
-etcetera = "0.4"
+etcetera = "0.4.0"
textwrap = "0.16.0"
[dev-dependencies]
-quickcheck = { version = "1", default-features = false }
+quickcheck = { version = "1.0.3", default-features = false }
diff --git a/helix-core/src/graphemes.rs b/helix-core/src/graphemes.rs
index 675f5750..98b9dac4 100644
--- a/helix-core/src/graphemes.rs
+++ b/helix-core/src/graphemes.rs
@@ -190,6 +190,8 @@ pub fn ensure_grapheme_boundary_next(slice: RopeSlice, char_idx: usize) -> usize
pub fn ensure_grapheme_boundary_prev(slice: RopeSlice, char_idx: usize) -> usize {
if char_idx == slice.len_chars() {
char_idx
+ } else if char_idx > slice.len_chars() {
+ slice.len_chars()
} else {
prev_grapheme_boundary(slice, char_idx + 1)
}
diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs
index 482fd6d9..3f695085 100644
--- a/helix-core/src/transaction.rs
+++ b/helix-core/src/transaction.rs
@@ -394,10 +394,12 @@ impl ChangeSet {
}
if pos > old_pos {
- panic!(
+ log::error!(
"Position {} is out of range for changeset len {}!",
- pos, old_pos
- )
+ pos,
+ old_pos
+ );
+ return old_pos;
}
new_pos
}
diff --git a/helix-core/tests/data/indent/indent.rs b/helix-core/tests/data/indent/indent.rs
deleted file mode 120000
index 2ac16cf9..00000000
--- a/helix-core/tests/data/indent/indent.rs
+++ /dev/null
@@ -1 +0,0 @@
-../../../src/indent.rs
\ No newline at end of file
diff --git a/helix-dap/Cargo.toml b/helix-dap/Cargo.toml
index 95a05905..aef3af20 100644
--- a/helix-dap/Cargo.toml
+++ b/helix-dap/Cargo.toml
@@ -13,13 +13,13 @@ homepage = "https://helix-editor.com"
[dependencies]
helix-core = { version = "0.6", path = "../helix-core" }
-anyhow = "1.0"
-log = "0.4"
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-thiserror = "1.0"
-tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
-which = "4.2"
+anyhow = "1.0.68"
+log = "0.4.17"
+serde = { version = "1.0.152", features = ["derive"] }
+serde_json = "1.0.91"
+thiserror = "1.0.38"
+tokio = { version = "1.24.1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
+which = "4.3.0"
[dev-dependencies]
-fern = "0.6"
+fern = "0.6.1"
diff --git a/helix-loader/Cargo.toml b/helix-loader/Cargo.toml
index a3d14584..299ae2fc 100644
--- a/helix-loader/Cargo.toml
+++ b/helix-loader/Cargo.toml
@@ -14,19 +14,19 @@ name = "hx-loader"
path = "src/main.rs"
[dependencies]
-anyhow = "1"
-serde = { version = "1.0", features = ["derive"] }
-toml = "0.5"
-etcetera = "0.4"
-tree-sitter = "0.20"
-once_cell = "1.17"
-log = "0.4"
+anyhow = "1.0.68"
+serde = { version = "1.0.152", features = ["derive"] }
+toml = "0.5.10"
+etcetera = "0.4.0"
+tree-sitter = "0.20.9"
+once_cell = "1.17.0"
+log = "0.4.17"
# TODO: these two should be on !wasm32 only
# cloning/compiling tree-sitter grammars
-cc = { version = "1" }
-threadpool = { version = "1.0" }
+cc = "1.0.78"
+threadpool = "1.8.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-libloading = "0.7"
+libloading = "0.7.4"
diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml
index d31731d2..6ecfa46c 100644
--- a/helix-lsp/Cargo.toml
+++ b/helix-lsp/Cargo.toml
@@ -15,14 +15,14 @@ homepage = "https://helix-editor.com"
helix-core = { version = "0.6", path = "../helix-core" }
helix-loader = { version = "0.6", path = "../helix-loader" }
-anyhow = "1.0"
-futures-executor = "0.3"
-futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
-log = "0.4"
-lsp-types = { version = "0.93", features = ["proposed"] }
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-thiserror = "1.0"
-tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
+anyhow = "1.0.68"
+futures-executor = "0.3.25"
+futures-util = { version = "0.3.25", features = ["std", "async-await"], default-features = false }
+log = "0.4.17"
+lsp-types = { version = "0.93.2", features = ["proposed"] }
+serde = { version = "1.0.152", features = ["derive"] }
+serde_json = "1.0.91"
+thiserror = "1.0.38"
+tokio = { version = "1.24.1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
tokio-stream = "0.1.11"
-which = "4.2"
+which = "4.3.0"
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index 8418896c..5456cffe 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -39,6 +39,8 @@ pub enum Error {
Timeout,
#[error("server closed the stream")]
StreamClosed,
+ #[error("LPS not defined")]
+ LspNotDefined,
#[error("Unhandled")]
Unhandled,
#[error(transparent)]
diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml
index 895a0882..3d5ebae5 100644
--- a/helix-term/Cargo.toml
+++ b/helix-term/Cargo.toml
@@ -34,49 +34,49 @@ helix-dap = { version = "0.6", path = "../helix-dap" }
helix-vcs = { version = "0.6", path = "../helix-vcs" }
helix-loader = { version = "0.6", path = "../helix-loader" }
-anyhow = "1"
-once_cell = "1.17"
+anyhow = "1.0.68"
+once_cell = "1.17.0"
-which = "4.2"
+which = "4.3.0"
-tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
+tokio = { version = "1.24.1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
-crossterm = { version = "0.25", features = ["event-stream"] }
-signal-hook = "0.3"
-tokio-stream = "0.1"
-futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
-arc-swap = { version = "1.6.0" }
+crossterm = { version = "0.25.0", features = ["event-stream"] }
+signal-hook = "0.3.14"
+tokio-stream = "0.1.11"
+futures-util = { version = "0.3.25", features = ["std", "async-await"], default-features = false }
+arc-swap = "1.6.0"
# Logging
-fern = "0.6"
-chrono = { version = "0.4", default-features = false, features = ["clock"] }
-log = "0.4"
+fern = "0.6.1"
+chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
+log = "0.4.17"
# File picker
-fuzzy-matcher = "0.3"
-ignore = "0.4"
+fuzzy-matcher = "0.3.7"
+ignore = "0.4.19"
# markdown doc rendering
-pulldown-cmark = { version = "0.9", default-features = false }
+pulldown-cmark = { version = "0.9.2", default-features = false }
# file type detection
content_inspector = "0.2.4"
# config
-toml = "0.5"
+toml = "0.5.10"
-serde_json = "1.0"
-serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0.91"
+serde = { version = "1.0.152", features = ["derive"] }
# ripgrep for global search
-grep-regex = "0.1.10"
-grep-searcher = "0.1.10"
+grep-regex = "0.1.11"
+grep-searcher = "0.1.11"
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
-signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
+signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"] }
[build-dependencies]
helix-loader = { version = "0.6", path = "../helix-loader" }
[dev-dependencies]
-smallvec = "1.10"
+smallvec = "1.10.0"
indoc = "1.0.8"
tempfile = "3.3.0"
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index c0cbc245..62f87a5d 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -21,11 +21,11 @@ use tui::backend::Backend;
use crate::{
args::Args,
commands::apply_workspace_edit,
- compositor::{Compositor, Event},
+ compositor::{self, Compositor, Event},
config::Config,
job::Jobs,
keymap::Keymaps,
- ui::{self, overlay::overlayed},
+ ui::{self, overlay::overlayed, Explorer},
};
use log::{debug, error, warn};
@@ -180,7 +180,19 @@ impl Application {
let keys = Box::new(Map::new(Arc::clone(&config), |config: &Config| {
&config.keys
}));
- let editor_view = Box::new(ui::EditorView::new(Keymaps::new(keys)));
+ let mut editor_view = Box::new(ui::EditorView::new(Keymaps::new(keys)));
+
+ if args.show_explorer {
+ let mut jobs = Jobs::new();
+ let mut context = compositor::Context {
+ editor: &mut editor,
+ scroll: None,
+ jobs: &mut jobs,
+ };
+ let mut explorer = Explorer::new(&mut context)?;
+ explorer.unfocus();
+ editor_view.explorer = Some(overlayed(explorer));
+ }
compositor.push(editor_view);
if args.load_tutor {
diff --git a/helix-term/src/args.rs b/helix-term/src/args.rs
index dd787f1f..597a4688 100644
--- a/helix-term/src/args.rs
+++ b/helix-term/src/args.rs
@@ -17,6 +17,7 @@ pub struct Args {
pub log_file: Option,
pub config_file: Option,
pub files: Vec<(PathBuf, Position)>,
+ pub show_explorer: bool,
}
impl Args {
@@ -32,6 +33,7 @@ impl Args {
"--version" => args.display_version = true,
"--help" => args.display_help = true,
"--tutor" => args.load_tutor = true,
+ "--show-explorer" => args.show_explorer = true,
"--vsplit" => match args.split {
Some(_) => anyhow::bail!("can only set a split once of a specific type"),
None => args.split = Some(Layout::Vertical),
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 09c2e5df..b4bfadbf 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -272,6 +272,7 @@ impl MappableCommand {
file_picker, "Open file picker",
file_picker_in_current_directory, "Open file picker at current working directory",
code_action, "Perform code action",
+ workspace_command_picker, "Open workspace command picker",
buffer_picker, "Open buffer picker",
jumplist_picker, "Open jumplist picker",
symbol_picker, "Open symbol picker",
@@ -444,7 +445,10 @@ impl MappableCommand {
decrement, "Decrement item under cursor",
record_macro, "Record macro",
replay_macro, "Replay macro",
- command_palette, "Open command palette",
+ command_palette, "Open command pallete",
+ toggle_or_focus_explorer, "toggle or focus explorer",
+ open_explorer_recursion, "open explorer recursion",
+ close_explorer, "close explorer",
);
}
@@ -2300,6 +2304,43 @@ fn file_picker_in_current_directory(cx: &mut Context) {
cx.push_layer(Box::new(overlayed(picker)));
}
+fn toggle_or_focus_explorer(cx: &mut Context) {
+ cx.callback = Some(Box::new(
+ |compositor: &mut Compositor, cx: &mut compositor::Context| {
+ if let Some(editor) = compositor.find::() {
+ match editor.explorer.as_mut() {
+ Some(explore) => explore.content.focus(),
+ None => match ui::Explorer::new(cx) {
+ Ok(explore) => editor.explorer = Some(overlayed(explore)),
+ Err(err) => cx.editor.set_error(format!("{}", err)),
+ },
+ }
+ }
+ },
+ ));
+}
+
+fn open_explorer_recursion(cx: &mut Context) {
+ cx.callback = Some(Box::new(
+ |compositor: &mut Compositor, cx: &mut compositor::Context| {
+ if let Some(editor) = compositor.find::() {
+ match ui::Explorer::new_explorer_recursion() {
+ Ok(explore) => editor.explorer = Some(overlayed(explore)),
+ Err(err) => cx.editor.set_error(format!("{}", err)),
+ }
+ }
+ },
+ ));
+}
+
+fn close_explorer(cx: &mut Context) {
+ cx.callback = Some(Box::new(|compositor: &mut Compositor, _| {
+ if let Some(editor) = compositor.find::() {
+ editor.explorer.take();
+ }
+ }));
+}
+
fn buffer_picker(cx: &mut Context) {
let current = view!(cx.editor).doc;
@@ -2999,18 +3040,11 @@ pub mod insert {
super::completion(cx);
}
- fn language_server_completion(cx: &mut Context, ch: char) {
- let config = cx.editor.config();
- if !config.auto_completion {
- return;
- }
-
+ fn is_server_trigger_char(doc: &Document, ch: char) -> bool {
use helix_lsp::lsp;
- // if ch matches completion char, trigger completion
- let doc = doc_mut!(cx.editor);
- let language_server = match doc.language_server() {
- Some(language_server) => language_server,
- None => return,
+
+ let Some(language_server) = doc.language_server() else {
+ return false;
};
let capabilities = language_server.capabilities();
@@ -3020,11 +3054,36 @@ pub mod insert {
..
}) = &capabilities.completion_provider
{
- // TODO: what if trigger is multiple chars long
- if triggers.iter().any(|trigger| trigger.contains(ch)) {
- cx.editor.clear_idle_timer();
- super::completion(cx);
+ triggers.iter().any(|t| t.contains(ch))
+ } else {
+ false
+ }
+ }
+
+ fn language_server_completion(cx: &mut Context, ch: char) {
+ use helix_core::chars::char_is_word;
+
+ let config = cx.editor.config();
+ if !config.auto_completion {
+ return;
+ }
+
+ let (view, doc) = current_ref!(cx.editor);
+
+ if char_is_word(ch) && doc.savepoint.is_none() {
+ let text = doc.text().slice(..);
+ let cursor = doc.selection(view.id).primary().cursor(text);
+ let mut chars = text.chars_at(cursor);
+ chars.reverse();
+
+ for _ in 0..config.completion_trigger_len {
+ if chars.next().map_or(true, |c| !char_is_word(c)) {
+ return;
+ }
}
+ cx.editor.reset_idle_timer();
+ } else if is_server_trigger_char(doc, ch) {
+ cx.editor.reset_idle_timer();
}
}
@@ -4029,9 +4088,15 @@ pub fn completion(cx: &mut Context) {
let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
- let future = match language_server.completion(doc.identifier(), pos, None) {
- Some(future) => future,
- None => return,
+ let Some(future) = language_server.completion(doc.identifier(), pos, None) else {
+ return;
+ };
+ let future = async move {
+ match future.await {
+ Ok(v) => Ok(v),
+ Err(helix_lsp::Error::Timeout) => Ok(serde_json::Value::Null),
+ Err(e) => Err(e),
+ }
};
let trigger_offset = cursor;
@@ -4044,29 +4109,54 @@ pub fn completion(cx: &mut Context) {
iter.reverse();
let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count();
let start_offset = cursor.saturating_sub(offset);
+ let prefix = text.slice(start_offset..cursor).to_string();
+
+ doc.savepoint();
+ let trigger_version = doc.version();
cx.callback(
future,
move |editor, compositor, response: Option| {
+ let doc = doc_mut!(editor);
+ let Some(savepoint) = doc.savepoint.take() else {
+ return;
+ };
if editor.mode != Mode::Insert {
// we're not in insert mode anymore
return;
}
+ if savepoint.0 != trigger_version {
+ doc.savepoint = Some(savepoint);
+ return;
+ }
- let items = match response {
+ let mut items = match response {
Some(lsp::CompletionResponse::Array(items)) => items,
// TODO: do something with is_incomplete
Some(lsp::CompletionResponse::List(lsp::CompletionList {
is_incomplete: _is_incomplete,
items,
})) => items,
- None => Vec::new(),
+ None => {
+ editor.set_status(
+ "The completion response is None. We will ask the server again",
+ );
+ editor.reset_idle_timer();
+ return;
+ }
};
+ if prefix.is_empty() {
+ items.retain(|item| match &item.filter_text {
+ Some(t) => t.starts_with(&prefix),
+ None => item.label.starts_with(&prefix),
+ })
+ }
if items.is_empty() {
// editor.set_error("No completion available");
return;
}
+ doc.savepoint = Some(savepoint);
let size = compositor.size();
let ui = compositor.find::().unwrap();
ui.set_completion(
diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs
index 86b0c5fa..c19c536e 100644
--- a/helix-term/src/commands/lsp.rs
+++ b/helix-term/src/commands/lsp.rs
@@ -590,7 +590,6 @@ pub fn code_action(cx: &mut Context) {
}
// Sort codeactions into a useful order. This behaviour is only partially described in the LSP spec.
- // Many details are modeled after vscode because langauge servers are usually tested against it.
// VScode sorts the codeaction two times:
//
// First the codeactions that fix some diagnostics are moved to the front.
@@ -624,7 +623,7 @@ pub fn code_action(cx: &mut Context) {
.reverse()
});
- let mut picker = ui::Menu::new(actions, (), move |editor, code_action, event| {
+ let mut picker = ui::Menu::new(actions, true, (), move |editor, code_action, event| {
if event != PromptEvent::Validate {
return;
}
@@ -654,7 +653,7 @@ pub fn code_action(cx: &mut Context) {
});
picker.move_down(); // pre-select the first item
- let popup = Popup::new("code-action", picker).with_scrollbar(false);
+ let popup = Popup::new("code-action", picker);
compositor.replace_or_push("code-action", popup);
},
)
@@ -667,6 +666,34 @@ impl ui::menu::Item for lsp::Command {
}
}
+pub fn workspace_command_picker(cx: &mut Context) {
+ let (_, doc) = current!(cx.editor);
+
+ let language_server = language_server!(cx.editor, doc);
+
+ let execute_command_provider = match &language_server.capabilities().execute_command_provider {
+ Some(p) => p,
+ None => return,
+ };
+ let commands = execute_command_provider
+ .commands
+ .iter()
+ .map(|command| lsp::Command {
+ title: command.clone(),
+ command: command.clone(),
+ arguments: None,
+ })
+ .collect::>();
+ cx.callback = Some(Box::new(
+ move |compositor: &mut Compositor, _cx: &mut compositor::Context| {
+ let picker = ui::Picker::new(commands, (), move |cx, command, _action| {
+ execute_lsp_command(cx.editor, command.clone());
+ });
+ compositor.push(Box::new(overlayed(picker)))
+ },
+ ));
+}
+
pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
let doc = doc!(editor);
let language_server = language_server!(editor, doc);
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index de24c4fb..4b0ec0e0 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -281,6 +281,31 @@ fn buffer_previous(
Ok(())
}
+fn delete(
+ cx: &mut compositor::Context,
+ _args: &[Cow],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
+ if event != PromptEvent::Validate {
+ return Ok(());
+ }
+
+ let doc = doc_mut!(cx.editor);
+
+ if doc.path().is_none() {
+ bail!("cannot delete a buffer with no associated file on the disk");
+ }
+
+ let future = doc.delete();
+ cx.jobs.add(Job::new(future));
+
+ cx.block_try_flush_writes()?;
+ let doc_id = view!(cx.editor).doc;
+ cx.editor.close_document(doc_id, true)?;
+
+ Ok(())
+}
+
fn write_impl(
cx: &mut compositor::Context,
path: Option<&Cow>,
@@ -1924,6 +1949,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: new_file,
completer: Some(completers::filename),
},
+ TypableCommand {
+ name: "delete",
+ aliases: &["remove", "rm", "del"],
+ doc: "Deletes the file associated with the current buffer",
+ fun: delete,
+ completer: None,
+ },
TypableCommand {
name: "format",
aliases: &["fmt"],
@@ -2340,6 +2372,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: run_shell_command,
completer: Some(completers::directory),
},
+ TypableCommand {
+ name: "lsp-restart",
+ aliases: &[],
+ doc: "Restarts the LSP server of the current buffer",
+ fun: lsp_restart,
+ completer: None,
+ },
];
pub static TYPABLE_COMMAND_MAP: Lazy> =
diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs
index ef93dee0..5a7bd034 100644
--- a/helix-term/src/keymap/default.rs
+++ b/helix-term/src/keymap/default.rs
@@ -10,6 +10,8 @@ pub fn default() -> HashMap {
"j" | "down" => move_line_down,
"k" | "up" => move_line_up,
"l" | "right" => move_char_right,
+ "C-j" => half_page_down,
+ "C-k" => half_page_up,
"t" => find_till_char,
"f" => find_next_char,
@@ -175,7 +177,7 @@ pub fn default() -> HashMap {
"C-u" => half_page_up,
"C-d" => half_page_down,
- "C-w" => { "Window"
+ "C-v" => { "View"
"C-w" | "w" => rotate_view,
"C-s" | "s" => hsplit,
"C-v" | "v" => vsplit,
@@ -238,7 +240,8 @@ pub fn default() -> HashMap {
"e" => dap_enable_exceptions,
"E" => dap_disable_exceptions,
},
- "w" => { "Window"
+ "w" => workspace_command_picker,
+ "v" => { "View"
"C-w" | "w" => rotate_view,
"C-s" | "s" => hsplit,
"C-v" | "v" => vsplit,
@@ -270,6 +273,8 @@ pub fn default() -> HashMap {
"r" => rename_symbol,
"h" => select_references_to_symbol_under_cursor,
"?" => command_palette,
+ "e" => toggle_or_focus_explorer,
+ "E" => close_explorer,
},
"z" => { "View"
"z" | "c" => align_view_center,
diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs
index aac5c537..a8c676b0 100644
--- a/helix-term/src/main.rs
+++ b/helix-term/src/main.rs
@@ -73,6 +73,7 @@ FLAGS:
-V, --version Prints version information
--vsplit Splits all given files vertically into different windows
--hsplit Splits all given files horizontally into different windows
+ --show-explorer Opens the explorer on startup
",
env!("CARGO_PKG_NAME"),
VERSION_AND_GIT_HASH,
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 11d7886a..e24c0395 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -104,7 +104,7 @@ impl Completion {
items.sort_by_key(|item| !item.preselect.unwrap_or(false));
// Then create the menu
- let menu = Menu::new(items, (), move |editor: &mut Editor, item, event| {
+ let menu = Menu::new(items, true, (), move |editor: &mut Editor, item, event| {
fn item_to_transaction(
doc: &Document,
view_id: ViewId,
@@ -462,6 +462,16 @@ impl Component for Completion {
height = rel_height.min(height);
}
Rect::new(x, y, width, height)
+ } else if popup_x > 30 {
+ let mut height = area.height.saturating_sub(popup_y);
+ let mut width = popup_x;
+ if let Some((rel_width, rel_height)) = markdown_doc.required_size((width, height)) {
+ width = rel_width.min(width);
+ height = rel_height.min(height);
+ }
+ let x = popup_x - width;
+ let y = popup_y;
+ Rect::new(x, y, width, height)
} else {
let half = area.height / 2;
let height = 15.min(half);
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 35cf77ab..1412b6cd 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -4,7 +4,7 @@ use crate::{
job::{self, Callback},
key,
keymap::{KeymapResult, Keymaps},
- ui::{Completion, ProgressSpinners},
+ ui::{overlay::Overlay, Completion, Explorer, ProgressSpinners},
};
use helix_core::{
@@ -19,7 +19,7 @@ use helix_core::{
use helix_view::{
apply_transaction,
document::{Mode, SCRATCH_BUFFER_NAME},
- editor::{CompleteAction, CursorShapeConfig},
+ editor::{CompleteAction, CursorShapeConfig, RainbowIndentOptions},
graphics::{Color, CursorKind, Modifier, Rect, Style},
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
keyboard::{KeyCode, KeyModifiers},
@@ -39,6 +39,7 @@ pub struct EditorView {
last_insert: (commands::MappableCommand, Vec),
pub(crate) completion: Option,
spinners: ProgressSpinners,
+ pub(crate) explorer: Option>,
}
#[derive(Debug, Clone)]
@@ -63,6 +64,7 @@ impl EditorView {
last_insert: (commands::MappableCommand::normal_mode, Vec::new()),
completion: None,
spinners: ProgressSpinners::default(),
+ explorer: None,
}
}
@@ -462,6 +464,27 @@ impl EditorView {
let starting_indent =
(offset.col / tab_width) + config.indent_guides.skip_levels as usize;
+ let modifier = if config.indent_guides.rainbow == RainbowIndentOptions::Dim {
+ Modifier::DIM
+ } else {
+ Modifier::empty()
+ };
+
+ for i in starting_indent..(indent_level / tab_width) {
+ let style = if config.indent_guides.rainbow != RainbowIndentOptions::None {
+ indent_guide_style
+ .patch(theme.get_rainbow(i as usize))
+ .add_modifier(modifier)
+ } else {
+ indent_guide_style
+ };
+ surface.set_string(
+ viewport.x + (i as u16 * tab_width as u16) - offset.col as u16,
+ viewport.y + line,
+ &indent_guide_char,
+ style,
+ );
+ }
// Don't draw indent guides outside of view
let end_indent = min(
indent_level,
@@ -934,7 +957,7 @@ impl EditorView {
}
(Mode::Insert, Mode::Normal) => {
// if exiting insert mode, remove completion
- self.completion = None;
+ self.clear_completion(cxt.editor);
// TODO: Use an on_mode_change hook to remove signature help
cxt.jobs.callback(async {
@@ -1072,9 +1095,6 @@ impl EditorView {
return;
}
- // Immediately initialize a savepoint
- doc_mut!(editor).savepoint();
-
editor.last_completion = None;
self.last_insert.1.push(InsertEvent::TriggerCompletion);
@@ -1105,7 +1125,15 @@ impl EditorView {
return EventResult::Ignored(None);
}
- crate::commands::insert::idle_completion(cx);
+ let mut cx = commands::Context {
+ register: None,
+ editor: cx.editor,
+ jobs: cx.jobs,
+ count: None,
+ callback: None,
+ on_next_key_callback: None,
+ };
+ crate::commands::insert::idle_completion(&mut cx);
EventResult::Consumed(None)
}
@@ -1299,6 +1327,11 @@ impl Component for EditorView {
event: &Event,
context: &mut crate::compositor::Context,
) -> EventResult {
+ if let Some(explore) = self.explorer.as_mut() {
+ if let EventResult::Consumed(callback) = explore.handle_event(event, context) {
+ return EventResult::Consumed(callback);
+ }
+ }
let mut cx = commands::Context {
editor: context.editor,
count: None,
@@ -1333,7 +1366,7 @@ impl Component for EditorView {
EventResult::Consumed(None)
}
Event::Key(mut key) => {
- cx.editor.reset_idle_timer();
+ cx.editor.clear_idle_timer();
canonicalize_key(&mut key);
// clear status
@@ -1385,7 +1418,8 @@ impl Component for EditorView {
if let Some(completion) = &mut self.completion {
completion.update(&mut cx);
if completion.is_empty() {
- self.clear_completion(cx.editor);
+ self.completion = None;
+ doc_mut!(cx.editor).savepoint = None;
}
}
}
@@ -1462,6 +1496,21 @@ impl Component for EditorView {
}
// if the terminal size suddenly changed, we need to trigger a resize
+ if self.explorer.is_some() && (config.explorer.is_embed()) {
+ editor_area = editor_area.clip_left(config.explorer.column_width as u16 + 2);
+ }
+ cx.editor.resize(editor_area); // -1 from bottom for commandline
+
+ if let Some(explore) = self.explorer.as_mut() {
+ if !explore.content.is_focus() && config.explorer.is_embed() {
+ let current_doc = view!(cx.editor).doc;
+ let current_doc = cx.editor.document(current_doc).unwrap();
+ if let Some(path) = current_doc.path() {
+ explore.content.set_selection(&path);
+ }
+ explore.content.render(area, surface, cx);
+ }
+ }
cx.editor.resize(editor_area);
if use_bufferline {
@@ -1542,9 +1591,30 @@ impl Component for EditorView {
if let Some(completion) = self.completion.as_mut() {
completion.render(area, surface, cx);
}
+
+ if let Some(explore) = self.explorer.as_mut() {
+ if explore.content.is_focus() {
+ if config.explorer.is_embed() {
+ explore.content.render(area, surface, cx);
+ } else {
+ explore.render(area, surface, cx);
+ }
+ }
+ }
}
fn cursor(&self, _area: Rect, editor: &Editor) -> (Option, CursorKind) {
+ if let Some(explore) = &self.explorer {
+ if explore.content.is_focus() {
+ if editor.config().explorer.is_overlay() {
+ return explore.cursor(_area, editor);
+ }
+ let cursor = explore.content.cursor(_area, editor);
+ if cursor.0.is_some() {
+ return cursor;
+ }
+ }
+ }
match editor.cursor() {
// All block cursors are drawn manually
(pos, CursorKind::Block) => (pos, CursorKind::Hidden),
diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs
new file mode 100644
index 00000000..83250460
--- /dev/null
+++ b/helix-term/src/ui/explore.rs
@@ -0,0 +1,914 @@
+use super::{Prompt, Tree, TreeItem, TreeOp};
+use crate::{
+ compositor::{Component, Compositor, Context, EventResult},
+ ctrl, key, shift, ui,
+};
+use anyhow::{bail, ensure, Result};
+use helix_core::Position;
+use helix_view::{
+ editor::Action,
+ graphics::{CursorKind, Modifier, Rect},
+ input::{Event, KeyEvent},
+ Editor,
+};
+use std::borrow::Cow;
+use std::cmp::Ordering;
+use std::path::{Path, PathBuf};
+use tui::{
+ buffer::Buffer as Surface,
+ text::{Span, Spans},
+ widgets::{Block, Borders, Widget},
+};
+
+macro_rules! get_theme {
+ ($theme: expr, $s1: expr, $s2: expr) => {
+ $theme.try_get($s1).unwrap_or_else(|| $theme.get($s2))
+ };
+}
+
+const ICONS: &'static [&'static str] =
+ &["", "", "", "", "", "ﰟ", "", "", "", "ﯤ", "", "ﬥ"];
+
+const ICONS_EXT: &'static [&'static str] = &[
+ ".rs", ".md", ".js", ".c", ".png", ".svg", ".css", ".html", ".lua", ".ts", ".py", ".json",
+];
+
+const ICONS_COLORS: &'static [helix_view::theme::Color] = &[
+ helix_view::theme::Color::Rgb(227, 134, 84),
+ helix_view::theme::Color::LightCyan,
+ helix_view::theme::Color::Yellow,
+ helix_view::theme::Color::Blue,
+ helix_view::theme::Color::Yellow,
+ helix_view::theme::Color::Yellow,
+ helix_view::theme::Color::Green,
+ helix_view::theme::Color::Blue,
+ helix_view::theme::Color::Red,
+ helix_view::theme::Color::Blue,
+ helix_view::theme::Color::Red,
+];
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+enum FileType {
+ File,
+ Dir,
+ Exe,
+ Placeholder,
+ Parent,
+ Root,
+}
+
+#[derive(Debug, Clone)]
+struct FileInfo {
+ file_type: FileType,
+ expanded: bool,
+ path: PathBuf,
+}
+
+impl FileInfo {
+ fn new(path: PathBuf, file_type: FileType) -> Self {
+ Self {
+ path,
+ file_type,
+ expanded: false,
+ }
+ }
+
+ fn root(path: PathBuf) -> Self {
+ Self {
+ file_type: FileType::Root,
+ path,
+ expanded: true,
+ }
+ }
+
+ fn parent(path: &Path) -> Self {
+ let p = path.parent().unwrap_or_else(|| Path::new(""));
+ Self {
+ file_type: FileType::Parent,
+ path: p.to_path_buf(),
+ expanded: false,
+ }
+ }
+
+ fn get_text(&self) -> Cow<'static, str> {
+ match self.file_type {
+ FileType::Parent => "..".into(),
+ FileType::Placeholder => "---".into(),
+ FileType::Root => {
+ if let Some(path) = self.path.iter().last() {
+ format!("- {} -", path.to_string_lossy()).into()
+ } else {
+ Cow::from("/")
+ }
+ }
+ FileType::File | FileType::Exe | FileType::Dir => self
+ .path
+ .file_name()
+ .map_or("/".into(), |p| p.to_string_lossy().into_owned().into()),
+ }
+ }
+}
+
+impl TreeItem for FileInfo {
+ type Params = State;
+ fn text(&self, cx: &mut Context, selected: bool, state: &mut State) -> Spans {
+ let text = self.get_text();
+ let theme = &cx.editor.theme;
+
+ let style = match self.file_type {
+ FileType::Parent | FileType::Dir | FileType::Root => "ui.explorer.dir",
+ FileType::File | FileType::Exe | FileType::Placeholder => "ui.explorer.file",
+ };
+ let mut style = theme.try_get(style).unwrap_or_else(|| theme.get("ui.text"));
+ if selected {
+ let patch = match state.focus {
+ true => "ui.explorer.focus",
+ false => "ui.explorer.unfocus",
+ };
+ if let Some(patch) = theme.try_get(patch) {
+ style = style.patch(patch);
+ } else {
+ style = style.add_modifier(Modifier::REVERSED);
+ }
+ }
+ Spans::from(Span::styled(text, style))
+ }
+
+ fn is_child(&self, other: &Self) -> bool {
+ if let FileType::Parent = other.file_type {
+ return false;
+ }
+ if let FileType::Placeholder = self.file_type {
+ self.path == other.path
+ } else {
+ self.path.parent().map_or(false, |p| p == other.path)
+ }
+ }
+
+ fn cmp(&self, other: &Self) -> Ordering {
+ use FileType::*;
+ match (self.file_type, other.file_type) {
+ (Parent, _) => return Ordering::Less,
+ (_, Parent) => return Ordering::Greater,
+ (Root, _) => return Ordering::Less,
+ (_, Root) => return Ordering::Greater,
+ _ => {}
+ };
+
+ if self.path == other.path {
+ match (self.file_type, other.file_type) {
+ (_, Placeholder) => return Ordering::Less,
+ (Placeholder, _) => return Ordering::Greater,
+ _ => {}
+ };
+ }
+
+ if let (Some(p1), Some(p2)) = (self.path.parent(), other.path.parent()) {
+ if p1 == p2 {
+ match (self.file_type, other.file_type) {
+ (Dir, File | Exe) => return Ordering::Less,
+ (File | Exe, Dir) => return Ordering::Greater,
+ _ => {}
+ };
+ }
+ }
+ self.path.cmp(&other.path)
+ }
+
+ fn get_childs(&self) -> Result> {
+ match self.file_type {
+ FileType::Root | FileType::Dir => {}
+ _ => return Ok(vec![]),
+ };
+ let mut ret: Vec<_> = std::fs::read_dir(&self.path)?
+ .filter_map(|entry| entry.ok())
+ .filter_map(|entry| {
+ entry.metadata().ok().map(|meta| {
+ let is_exe = false;
+ let file_type = match (meta.is_dir(), is_exe) {
+ (true, _) => FileType::Dir,
+ (_, false) => FileType::File,
+ (_, true) => FileType::Exe,
+ };
+ Self {
+ file_type,
+ path: self.path.join(entry.file_name()),
+ expanded: false,
+ }
+ })
+ })
+ .collect();
+ if ret.is_empty() {
+ ret.push(Self {
+ path: self.path.clone(),
+ file_type: FileType::Placeholder,
+ expanded: false,
+ })
+ }
+ Ok(ret)
+ }
+
+ fn filter(&self, _cx: &mut Context, s: &str, _params: &mut Self::Params) -> bool {
+ if s.is_empty() {
+ false
+ } else {
+ self.get_text().contains(s)
+ }
+ }
+
+ fn icon(&self) -> Option<(&'static str, &'static helix_view::theme::Color)> {
+ return match self.file_type {
+ FileType::Dir => {
+ if self.expanded {
+ Some(("", &helix_view::theme::Color::Yellow))
+ } else {
+ Some(("", &helix_view::theme::Color::Yellow))
+ }
+ }
+ FileType::File => {
+ for (i, ext) in ICONS_EXT.iter().enumerate() {
+ if self.get_text().ends_with(ext) {
+ let color = ICONS_COLORS
+ .iter()
+ .nth(i)
+ .unwrap_or(&helix_view::theme::Color::Blue);
+ return ICONS.iter().nth(i).map(|c| (*c, color));
+ }
+ }
+ return Some(("", &helix_view::theme::Color::LightBlue));
+ }
+ _ => None,
+ };
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+enum PromptAction {
+ Search(bool), // search next/search pre
+ Mkdir,
+ CreateFile,
+ RemoveDir,
+ RemoveFile,
+ Filter,
+}
+
+#[derive(Clone, Debug)]
+struct State {
+ focus: bool,
+ current_root: PathBuf,
+}
+
+impl State {
+ fn new(focus: bool, current_root: PathBuf) -> Self {
+ Self {
+ focus,
+ current_root,
+ }
+ }
+}
+
+pub struct Explorer {
+ tree: Tree,
+ state: State,
+ prompt: Option<(PromptAction, Prompt)>,
+ #[allow(clippy::type_complexity)]
+ on_next_key: Option EventResult>>,
+ #[allow(clippy::type_complexity)]
+ repeat_motion: Option>,
+}
+
+impl Explorer {
+ pub fn new(cx: &mut Context) -> Result {
+ let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into());
+ let items = Self::get_items(current_root.clone(), cx)?;
+ Ok(Self {
+ tree: Tree::build_tree(items)
+ .with_enter_fn(Self::toggle_current)
+ .with_folded_fn(Self::fold_current),
+ state: State::new(true, current_root),
+ repeat_motion: None,
+ prompt: None,
+ on_next_key: None,
+ })
+ }
+
+ pub fn set_selection(&mut self, path: &Path) {
+ let info = if path.is_file() {
+ FileInfo::new(path.into(), FileType::File)
+ } else {
+ FileInfo::new(path.into(), FileType::Dir)
+ };
+ self.tree.select(&info);
+ self.tree.save_view();
+ }
+
+ pub fn new_explorer_recursion() -> Result {
+ let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into());
+ let parent = FileInfo::parent(¤t_root);
+ let root = FileInfo::root(current_root.clone());
+ let mut tree = Tree::build_from_root(root, usize::MAX / 2)?
+ .with_enter_fn(Self::toggle_current)
+ .with_folded_fn(Self::fold_current);
+ tree.insert_current_level(parent);
+ Ok(Self {
+ tree,
+ state: State::new(true, current_root),
+ repeat_motion: None,
+ prompt: None,
+ on_next_key: None,
+ })
+ }
+
+ pub fn focus(&mut self) {
+ self.state.focus = true
+ }
+
+ pub fn unfocus(&mut self) {
+ self.state.focus = false;
+ }
+
+ pub fn is_focus(&self) -> bool {
+ self.state.focus
+ }
+
+ fn get_items(p: PathBuf, cx: &mut Context) -> Result> {
+ let mut items = Vec::new();
+ let root = FileInfo::root(p);
+ let childs = root.get_childs()?;
+ if cx.editor.config().explorer.is_tree() {
+ items.push(root)
+ }
+ items.extend(childs);
+ Ok(items)
+ }
+
+ fn render_preview(&mut self, area: Rect, surface: &mut Surface, editor: &Editor) {
+ if area.height <= 2 || area.width < 60 {
+ return;
+ }
+ let item = self.tree.current().item();
+ if item.file_type == FileType::Placeholder {
+ return;
+ }
+ let head_area = render_block(
+ area.clip_bottom(area.height - 2),
+ surface,
+ Borders::BOTTOM,
+ None,
+ );
+ let path_str = format!("{}", item.path.display());
+ surface.set_stringn(
+ head_area.x,
+ head_area.y,
+ path_str,
+ head_area.width as usize,
+ get_theme!(editor.theme, "ui.explorer.dir", "ui.text"),
+ );
+
+ let body_area = area.clip_top(2);
+ let style = editor.theme.get("ui.text");
+ if let Ok(preview_content) = get_preview(&item.path, body_area.height as usize) {
+ preview_content
+ .into_iter()
+ .enumerate()
+ .for_each(|(row, line)| {
+ surface.set_stringn(
+ body_area.x,
+ body_area.y + row as u16,
+ line,
+ body_area.width as usize,
+ style,
+ );
+ })
+ }
+ }
+
+ fn new_search_prompt(&mut self, search_next: bool) {
+ self.tree.save_view();
+ self.prompt = Some((
+ PromptAction::Search(search_next),
+ Prompt::new("search: ".into(), None, ui::completers::none, |_, _, _| {}),
+ ))
+ }
+
+ fn new_filter_prompt(&mut self) {
+ self.tree.save_view();
+ self.prompt = Some((
+ PromptAction::Filter,
+ Prompt::new("filter: ".into(), None, ui::completers::none, |_, _, _| {}),
+ ))
+ }
+
+ fn new_mkdir_prompt(&mut self) {
+ self.prompt = Some((
+ PromptAction::Mkdir,
+ Prompt::new("mkdir: ".into(), None, ui::completers::none, |_, _, _| {}),
+ ));
+ }
+
+ fn new_create_file_prompt(&mut self) {
+ self.prompt = Some((
+ PromptAction::CreateFile,
+ Prompt::new(
+ "create file: ".into(),
+ None,
+ ui::completers::none,
+ |_, _, _| {},
+ ),
+ ));
+ }
+
+ fn new_remove_file_prompt(&mut self, cx: &mut Context) {
+ let item = self.tree.current_item();
+ let check = || {
+ ensure!(item.file_type != FileType::Placeholder, "The path is empty");
+ ensure!(
+ item.file_type != FileType::Parent,
+ "can not remove parent dir"
+ );
+ ensure!(item.path.is_file(), "The path is not a file");
+ let doc = cx.editor.document_by_path(&item.path);
+ ensure!(doc.is_none(), "The file is opened");
+ Ok(())
+ };
+ if let Err(e) = check() {
+ cx.editor.set_error(format!("{e}"));
+ return;
+ }
+ let p = format!("remove file: {}, YES? ", item.path.display());
+ self.prompt = Some((
+ PromptAction::RemoveFile,
+ Prompt::new(p.into(), None, ui::completers::none, |_, _, _| {}),
+ ));
+ }
+
+ fn new_remove_dir_prompt(&mut self, cx: &mut Context) {
+ let item = self.tree.current_item();
+ let check = || {
+ ensure!(item.file_type != FileType::Placeholder, "The path is empty");
+ ensure!(
+ item.file_type != FileType::Parent,
+ "can not remove parent dir"
+ );
+ ensure!(item.path.is_dir(), "The path is not a dir");
+ let doc = cx.editor.documents().find(|doc| {
+ doc.path()
+ .map(|p| p.starts_with(&item.path))
+ .unwrap_or(false)
+ });
+ ensure!(doc.is_none(), "There are files opened under the dir");
+ Ok(())
+ };
+ if let Err(e) = check() {
+ cx.editor.set_error(format!("{e}"));
+ return;
+ }
+ let p = format!("remove dir: {}, YES? ", item.path.display());
+ self.prompt = Some((
+ PromptAction::RemoveDir,
+ Prompt::new(p.into(), None, ui::completers::none, |_, _, _| {}),
+ ));
+ }
+
+ fn fold_current(item: &mut FileInfo, _cx: &mut Context, _state: &mut State) {
+ if item.path.is_dir() && item.file_type != FileType::Root {
+ item.expanded = false;
+ }
+ }
+
+ fn toggle_current(
+ item: &mut FileInfo,
+ cx: &mut Context,
+ state: &mut State,
+ ) -> TreeOp {
+ if item.file_type == FileType::Placeholder {
+ return TreeOp::Noop;
+ }
+ if item.path == Path::new("") {
+ return TreeOp::Noop;
+ }
+ let meta = match std::fs::metadata(&item.path) {
+ Ok(meta) => meta,
+ Err(e) => {
+ cx.editor.set_error(format!("{e}"));
+ return TreeOp::Noop;
+ }
+ };
+ if meta.is_file() {
+ if let Err(e) = cx.editor.open(&item.path.clone(), Action::Replace) {
+ cx.editor.set_error(format!("{e}"));
+ }
+ state.focus = false;
+ return TreeOp::Noop;
+ }
+
+ if item.path.is_dir() {
+ item.expanded = true;
+ if cx.editor.config().explorer.is_list() || item.file_type == FileType::Parent {
+ match Self::get_items(item.path.clone(), cx) {
+ Ok(items) => {
+ state.current_root = item.path.clone();
+ return TreeOp::ReplaceTree(items);
+ }
+ Err(e) => cx.editor.set_error(format!("{e}")),
+ }
+ } else {
+ return TreeOp::GetChildsAndInsert;
+ }
+ }
+ cx.editor.set_error("unkonw file type");
+ TreeOp::Noop
+ }
+
+ fn render_float(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
+ let background = cx.editor.theme.get("ui.background");
+ let column_width = cx.editor.config().explorer.column_width as u16;
+ surface.clear_with(area, background);
+ let area = render_block(area, surface, Borders::ALL, None);
+
+ let mut preview_area = area.clip_left(column_width + 1);
+ if let Some((_, prompt)) = self.prompt.as_mut() {
+ let area = preview_area.clip_bottom(2);
+ let promp_area = render_block(
+ preview_area.clip_top(area.height),
+ surface,
+ Borders::TOP,
+ None,
+ );
+ prompt.render(promp_area, surface, cx);
+ preview_area = area;
+ }
+ self.render_preview(preview_area, surface, cx.editor);
+
+ let list_area = render_block(
+ area.clip_right(preview_area.width),
+ surface,
+ Borders::RIGHT,
+ None,
+ );
+ self.tree.render(list_area, surface, cx, &mut self.state);
+ }
+
+ fn render_embed(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
+ let config = &cx.editor.config().explorer;
+ let side_area = area
+ .with_width(area.width.min(config.column_width as u16 + 2))
+ .clip_bottom(1);
+
+ let background = cx.editor.theme.get("ui.statusline");
+ surface.clear_with(side_area, background);
+
+ let preview_area = area.clip_left(side_area.width).clip_bottom(2);
+ let prompt_area = area.clip_top(side_area.height);
+
+ let border_style = cx.editor.theme.get("ui.explorer.border");
+ let list_area = render_block(
+ side_area.clip_left(1),
+ surface,
+ Borders::RIGHT,
+ Some(border_style),
+ )
+ .clip_bottom(1);
+ self.tree.render(list_area, surface, cx, &mut self.state);
+
+ {
+ let statusline = if self.is_focus() {
+ cx.editor.theme.get("ui.statusline")
+ } else {
+ cx.editor.theme.get("ui.statusline.inactive")
+ };
+ let area = side_area.clip_top(list_area.height).clip_right(1);
+ surface.clear_with(area, statusline);
+ }
+
+ if self.is_focus() {
+ if preview_area.width < 30 || preview_area.height < 3 {
+ return;
+ }
+ let width = preview_area.width.min(90);
+ let mut y = self.tree.row().saturating_sub(1) as u16;
+ let height = (preview_area.height).min(25);
+ if (height + y) > preview_area.height {
+ y = preview_area.height - height;
+ }
+ let area = Rect::new(preview_area.x, y, width, height);
+ surface.clear_with(area, background);
+ let area = render_block(area, surface, Borders::all(), None);
+ self.render_preview(area, surface, cx.editor);
+ }
+
+ if let Some((_, prompt)) = self.prompt.as_mut() {
+ prompt.render_prompt(prompt_area, surface, cx)
+ }
+ }
+
+ fn handle_filter_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
+ let (action, mut prompt) = self.prompt.take().unwrap();
+ match event {
+ key!(Tab) | key!(Down) | ctrl!('j') => {
+ self.tree.clean_recycle();
+ return self
+ .tree
+ .handle_event(Event::Key(event.clone()), cx, &mut self.state);
+ }
+ key!(Enter) => {
+ self.tree.clean_recycle();
+ return self
+ .tree
+ .handle_event(Event::Key(event.clone()), cx, &mut self.state);
+ }
+ key!(Esc) | ctrl!('c') => self.tree.restore_recycle(),
+ _ => {
+ if let EventResult::Consumed(_) = prompt.handle_event(&Event::Key(*event), cx) {
+ self.tree.filter(prompt.line(), cx, &mut self.state);
+ }
+ self.prompt = Some((action, prompt));
+ }
+ };
+ EventResult::Consumed(None)
+ }
+
+ fn handle_search_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
+ let (action, mut prompt) = self.prompt.take().unwrap();
+ let search_next = match action {
+ PromptAction::Search(search_next) => search_next,
+ _ => return EventResult::Ignored(None),
+ };
+ match event {
+ key!(Tab) | key!(Down) | ctrl!('j') => {
+ return self
+ .tree
+ .handle_event(Event::Key(event.clone()), cx, &mut self.state)
+ }
+ key!(Enter) => {
+ let search_str = prompt.line().clone();
+ if !search_str.is_empty() {
+ self.repeat_motion = Some(Box::new(move |explorer, action, cx| {
+ if let PromptAction::Search(is_next) = action {
+ explorer.tree.save_view();
+ if is_next == search_next {
+ explorer
+ .tree
+ .search_next(cx, &search_str, &mut explorer.state);
+ } else {
+ explorer
+ .tree
+ .search_pre(cx, &search_str, &mut explorer.state);
+ }
+ }
+ }))
+ } else {
+ self.repeat_motion = None;
+ }
+ return self
+ .tree
+ .handle_event(Event::Key(event.clone()), cx, &mut self.state);
+ }
+ key!(Esc) | ctrl!('c') => self.tree.restore_view(),
+ _ => {
+ if let EventResult::Consumed(_) = prompt.handle_event(&Event::Key(*event), cx) {
+ if search_next {
+ self.tree.search_next(cx, prompt.line(), &mut self.state);
+ } else {
+ self.tree.search_pre(cx, prompt.line(), &mut self.state);
+ }
+ }
+ self.prompt = Some((action, prompt));
+ }
+ };
+ EventResult::Consumed(None)
+ }
+
+ fn handle_prompt_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
+ match &self.prompt {
+ Some((PromptAction::Search(_), _)) => return self.handle_search_event(event, cx),
+ Some((PromptAction::Filter, _)) => return self.handle_filter_event(event, cx),
+ _ => {}
+ };
+ let (action, mut prompt) = match self.prompt.take() {
+ Some((action, p)) => (action, p),
+ _ => return EventResult::Ignored(None),
+ };
+ let line = prompt.line();
+ match (action, event) {
+ (PromptAction::Mkdir, key!(Enter)) => {
+ if let Err(e) = self.new_path(line, true) {
+ cx.editor.set_error(format!("{e}"))
+ }
+ }
+ (PromptAction::CreateFile, key!(Enter)) => {
+ if let Err(e) = self.new_path(line, false) {
+ cx.editor.set_error(format!("{e}"))
+ }
+ }
+ (PromptAction::RemoveDir, key!(Enter)) => {
+ let item = self.tree.current_item();
+ if let Err(e) = std::fs::remove_dir_all(&item.path) {
+ cx.editor.set_error(format!("{e}"));
+ } else {
+ self.tree.fold_current_child();
+ self.tree.remove_current();
+ }
+ }
+ (PromptAction::RemoveFile, key!(Enter)) => {
+ if line == "YES" {
+ let item = self.tree.current_item();
+ if let Err(e) = std::fs::remove_file(&item.path) {
+ cx.editor.set_error(format!("{e}"));
+ } else {
+ self.tree.remove_current();
+ }
+ }
+ }
+ (_, key!(Esc) | ctrl!('c')) => {}
+ _ => {
+ prompt.handle_event(&Event::Key(*event), cx);
+ self.prompt = Some((action, prompt));
+ }
+ }
+ EventResult::Consumed(None)
+ }
+
+ fn new_path(&mut self, file_name: &str, is_dir: bool) -> Result<()> {
+ let current = self.tree.current_item();
+ let current_parent = if current.file_type == FileType::Placeholder {
+ ¤t.path
+ } else {
+ current
+ .path
+ .parent()
+ .ok_or_else(|| anyhow::anyhow!("can not get parent dir"))?
+ };
+ let p = helix_core::path::get_normalized_path(¤t_parent.join(file_name));
+ match p.parent() {
+ Some(p) if p == current_parent => {}
+ _ => bail!("The file name is not illegal"),
+ };
+
+ let f = if is_dir {
+ std::fs::create_dir(&p)?;
+ FileInfo::new(p, FileType::Dir)
+ } else {
+ let mut fd = std::fs::OpenOptions::new();
+ fd.create_new(true).write(true).open(&p)?;
+ FileInfo::new(p, FileType::File)
+ };
+ if current.file_type == FileType::Placeholder {
+ self.tree.replace_current(f);
+ } else {
+ self.tree.insert_current_level(f);
+ }
+ Ok(())
+ }
+}
+
+impl Component for Explorer {
+ /// Process input events, return true if handled.
+ fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
+ let key_event = match event {
+ Event::Key(event) => *event,
+ Event::Resize(..) => return EventResult::Consumed(None),
+ _ => return EventResult::Ignored(None),
+ };
+ if !self.is_focus() {
+ return EventResult::Ignored(None);
+ }
+ if let Some(mut on_next_key) = self.on_next_key.take() {
+ return on_next_key(cx, self, &key_event);
+ }
+
+ if let EventResult::Consumed(c) = self.handle_prompt_event(&key_event, cx) {
+ return EventResult::Consumed(c);
+ }
+
+ let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
+ if let Some(editor) = compositor.find::() {
+ editor.explorer = None;
+ }
+ })));
+
+ match key_event {
+ key!(Esc) => self.unfocus(),
+ ctrl!('c') => return close_fn,
+ key!('n') => {
+ if let Some(mut repeat_motion) = self.repeat_motion.take() {
+ repeat_motion(self, PromptAction::Search(true), cx);
+ self.repeat_motion = Some(repeat_motion);
+ }
+ }
+ shift!('N') => {
+ if let Some(mut repeat_motion) = self.repeat_motion.take() {
+ repeat_motion(self, PromptAction::Search(false), cx);
+ self.repeat_motion = Some(repeat_motion);
+ }
+ }
+ key!('f') => self.new_filter_prompt(),
+ key!('/') => self.new_search_prompt(true),
+ key!('?') => self.new_search_prompt(false),
+ key!('m') => {
+ self.on_next_key = Some(Box::new(|_, explorer, event| {
+ match event {
+ key!('d') => explorer.new_mkdir_prompt(),
+ key!('f') => explorer.new_create_file_prompt(),
+ _ => return EventResult::Ignored(None),
+ };
+ EventResult::Consumed(None)
+ }));
+ }
+ key!('r') => {
+ self.on_next_key = Some(Box::new(|cx, explorer, event| {
+ match event {
+ key!('d') => explorer.new_remove_dir_prompt(cx),
+ key!('f') => explorer.new_remove_file_prompt(cx),
+ _ => return EventResult::Ignored(None),
+ };
+ EventResult::Consumed(None)
+ }));
+ }
+ _ => {
+ self.tree
+ .handle_event(Event::Key(key_event.clone()), cx, &mut self.state);
+ }
+ }
+
+ EventResult::Consumed(None)
+ }
+
+ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
+ if area.width < 10 || area.height < 5 {
+ cx.editor.set_error("explorer render area is too small");
+ return;
+ }
+ let config = &cx.editor.config().explorer;
+ if config.is_embed() {
+ self.render_embed(area, surface, cx);
+ } else {
+ self.render_float(area, surface, cx);
+ }
+ }
+
+ fn cursor(&self, area: Rect, editor: &Editor) -> (Option, CursorKind) {
+ let prompt = match self.prompt.as_ref() {
+ Some((_, prompt)) => prompt,
+ None => return (None, CursorKind::Hidden),
+ };
+ let config = &editor.config().explorer;
+ let (x, y) = if config.is_overlay() {
+ let colw = config.column_width as u16;
+ if area.width > colw {
+ (area.x + colw + 2, area.y + area.height - 2)
+ } else {
+ return (None, CursorKind::Hidden);
+ }
+ } else {
+ (area.x, area.y + area.height - 1)
+ };
+ prompt.cursor(Rect::new(x, y, area.width, 1), editor)
+ }
+}
+
+fn get_preview(p: impl AsRef, max_line: usize) -> Result> {
+ let p = p.as_ref();
+ if p.is_dir() {
+ return Ok(p
+ .read_dir()?
+ .filter_map(|entry| entry.ok())
+ .take(max_line)
+ .map(|entry| {
+ if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
+ format!("{}/", entry.file_name().to_string_lossy())
+ } else {
+ format!("{}", entry.file_name().to_string_lossy())
+ }
+ })
+ .collect());
+ }
+
+ ensure!(p.is_file(), "path: {} is not file or dir", p.display());
+ use std::fs::OpenOptions;
+ use std::io::BufRead;
+ let mut fd = OpenOptions::new();
+ fd.read(true);
+ let fd = fd.open(p)?;
+ Ok(std::io::BufReader::new(fd)
+ .lines()
+ .take(max_line)
+ .filter_map(|line| line.ok())
+ .map(|line| line.replace('\t', " "))
+ .collect())
+}
+
+fn render_block(
+ area: Rect,
+ surface: &mut Surface,
+ borders: Borders,
+ border_style: Option,
+) -> Rect {
+ let mut block = Block::default().borders(borders);
+ if let Some(style) = border_style {
+ block = block.border_style(style);
+ }
+ let inner = block.inner(area);
+ block.render(area, surface);
+ inner
+}
diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs
index b9c1f9de..1e9b08e7 100644
--- a/helix-term/src/ui/menu.rs
+++ b/helix-term/src/ui/menu.rs
@@ -74,11 +74,12 @@ impl Menu {
// rendering)
pub fn new(
options: Vec,
+ sort: bool,
editor_data: ::Data,
callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
) -> Self {
let matches = (0..options.len()).map(|i| (i, 0)).collect();
- Self {
+ let mut menu = Self {
options,
editor_data,
matcher: Box::new(Matcher::default()),
@@ -90,7 +91,16 @@ impl Menu {
size: (0, 0),
viewport: (0, 0),
recalculate: true,
+ };
+
+ if sort {
+ // TODO: scoring on empty input should just use a fastpath
+ menu.score("");
+ } else {
+ menu.matches = (0..menu.options.len()).map(|i| (i, 0)).collect();
}
+
+ menu
}
pub fn score(&mut self, pattern: &str) {
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index eb480758..ea6d9b7e 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -1,5 +1,6 @@
mod completion;
pub(crate) mod editor;
+mod explore;
mod fuzzy_match;
mod info;
pub mod lsp;
@@ -12,11 +13,13 @@ mod prompt;
mod spinner;
mod statusline;
mod text;
+mod tree;
use crate::compositor::{Component, Compositor};
use crate::job::{self, Callback};
pub use completion::Completion;
pub use editor::EditorView;
+pub use explore::Explorer;
pub use markdown::Markdown;
pub use menu::Menu;
pub use picker::{DynamicPicker, FileLocation, FilePicker, Picker};
@@ -24,6 +27,7 @@ pub use popup::Popup;
pub use prompt::{Prompt, PromptEvent};
pub use spinner::{ProgressSpinners, Spinner};
pub use text::Text;
+pub use tree::{Tree, TreeItem, TreeOp};
use helix_core::regex::Regex;
use helix_core::regex::RegexBuilder;
diff --git a/helix-term/src/ui/tree.rs b/helix-term/src/ui/tree.rs
new file mode 100644
index 00000000..6c643b8f
--- /dev/null
+++ b/helix-term/src/ui/tree.rs
@@ -0,0 +1,707 @@
+use std::cmp::Ordering;
+use std::iter::Peekable;
+
+use anyhow::Result;
+
+use crate::{
+ compositor::{Context, EventResult},
+ ctrl, key, shift,
+};
+use helix_core::unicode::width::UnicodeWidthStr;
+use helix_view::{
+ graphics::Rect,
+ input::{Event, KeyEvent},
+};
+use tui::{buffer::Buffer as Surface, text::Spans};
+
+pub trait TreeItem: Sized {
+ type Params;
+
+ fn text(&self, cx: &mut Context, selected: bool, params: &mut Self::Params) -> Spans;
+ fn is_child(&self, other: &Self) -> bool;
+ fn cmp(&self, other: &Self) -> Ordering;
+ fn icon(&self) -> Option<(&'static str, &'static helix_view::theme::Color)>;
+
+ fn filter(&self, cx: &mut Context, s: &str, params: &mut Self::Params) -> bool {
+ self.text(cx, false, params)
+ .0
+ .into_iter()
+ .map(|s| s.content)
+ .collect::>()
+ .concat()
+ .contains(s)
+ }
+
+ fn get_childs(&self) -> Result> {
+ Ok(vec![])
+ }
+}
+
+fn tree_item_cmp(item1: &T, item2: &T) -> Ordering {
+ if item1.is_child(item2) {
+ return Ordering::Greater;
+ }
+ if item2.is_child(item1) {
+ return Ordering::Less;
+ }
+
+ T::cmp(item1, item2)
+}
+
+fn vec_to_tree(mut items: Vec, level: usize) -> Vec> {
+ fn get_childs(iter: &mut Peekable, elem: &mut Elem)
+ where
+ T: TreeItem,
+ Iter: Iterator- ,
+ {
+ let level = elem.level + 1;
+ loop {
+ if !iter.peek().map_or(false, |next| next.is_child(&elem.item)) {
+ break;
+ }
+ let mut child = Elem::new(iter.next().unwrap(), level);
+ if iter.peek().map_or(false, |nc| nc.is_child(&child.item)) {
+ get_childs(iter, &mut child);
+ }
+ elem.folded.push(child);
+ }
+ }
+
+ items.sort_by(tree_item_cmp);
+ let mut elems = Vec::with_capacity(items.len());
+ let mut iter = items.into_iter().peekable();
+ while let Some(item) = iter.next() {
+ let mut elem = Elem::new(item, level);
+ if iter.peek().map_or(false, |next| next.is_child(&elem.item)) {
+ get_childs(&mut iter, &mut elem);
+ }
+ expand_elems(&mut elems, elem);
+ }
+ elems
+}
+
+// return total elems's count contain self
+fn get_elems_recursion(t: &mut Elem, depth: usize) -> Result {
+ let mut childs = t.item.get_childs()?;
+ childs.sort_by(tree_item_cmp);
+ let mut elems = Vec::with_capacity(childs.len());
+ let level = t.level + 1;
+ let mut total = 1;
+ for child in childs {
+ let mut elem = Elem::new(child, level);
+ let count = if depth > 0 {
+ get_elems_recursion(&mut elem, depth - 1)?
+ } else {
+ 1
+ };
+ elems.push(elem);
+ total += count;
+ }
+ t.folded = elems;
+ Ok(total)
+}
+
+fn expand_elems(dist: &mut Vec>, mut t: Elem) {
+ let childs = std::mem::take(&mut t.folded);
+ dist.push(t);
+ for child in childs {
+ expand_elems(dist, child)
+ }
+}
+
+pub enum TreeOp {
+ Noop,
+ Restore,
+ InsertChild(Vec),
+ GetChildsAndInsert,
+ ReplaceTree(Vec),
+}
+
+pub struct Elem {
+ item: T,
+ level: usize,
+ folded: Vec,
+}
+
+impl Clone for Elem {
+ fn clone(&self) -> Self {
+ Self {
+ item: self.item.clone(),
+ level: self.level,
+ folded: self.folded.clone(),
+ }
+ }
+}
+
+impl Elem {
+ pub fn new(item: T, level: usize) -> Self {
+ Self {
+ item,
+ level,
+ folded: vec![],
+ }
+ }
+
+ pub fn item(&self) -> &T {
+ &self.item
+ }
+}
+
+pub struct Tree {
+ items: Vec>,
+ recycle: Option<(String, Vec>)>,
+ selected: usize,
+ save_view: (usize, usize), // (selected, row)
+ row: usize,
+ col: usize,
+ max_len: usize,
+ count: usize,
+ tree_symbol_style: String,
+ #[allow(clippy::type_complexity)]
+ pre_render: Option>,
+ #[allow(clippy::type_complexity)]
+ on_opened_fn:
+ Option TreeOp + 'static>>,
+ #[allow(clippy::type_complexity)]
+ on_folded_fn: Option>,
+ #[allow(clippy::type_complexity)]
+ on_next_key: Option>,
+}
+
+impl Tree {
+ pub fn new(items: Vec>) -> Self {
+ Self {
+ items,
+ recycle: None,
+ selected: 0,
+ save_view: (0, 0),
+ row: 0,
+ col: 0,
+ max_len: 0,
+ count: 0,
+ tree_symbol_style: "ui.explorer.guide".into(),
+ pre_render: None,
+ on_opened_fn: None,
+ on_folded_fn: None,
+ on_next_key: None,
+ }
+ }
+
+ pub fn replace_with_new_items(&mut self, items: Vec) {
+ let old = std::mem::replace(self, Self::new(vec_to_tree(items, 0)));
+ self.on_opened_fn = old.on_opened_fn;
+ self.on_folded_fn = old.on_folded_fn;
+ self.tree_symbol_style = old.tree_symbol_style;
+ }
+
+ pub fn build_tree(items: Vec) -> Self {
+ Self::new(vec_to_tree(items, 0))
+ }
+
+ pub fn build_from_root(t: T, depth: usize) -> Result {
+ let mut elem = Elem::new(t, 0);
+ let count = get_elems_recursion(&mut elem, depth)?;
+ let mut elems = Vec::with_capacity(count);
+ expand_elems(&mut elems, elem);
+ Ok(Self::new(elems))
+ }
+
+ pub fn with_enter_fn(mut self, f: F) -> Self
+ where
+ F: FnMut(&mut T, &mut Context, &mut T::Params) -> TreeOp + 'static,
+ {
+ self.on_opened_fn = Some(Box::new(f));
+ self
+ }
+
+ pub fn with_folded_fn(mut self, f: F) -> Self
+ where
+ F: FnMut(&mut T, &mut Context, &mut T::Params) + 'static,
+ {
+ self.on_folded_fn = Some(Box::new(f));
+ self
+ }
+
+ pub fn tree_symbol_style(mut self, style: String) -> Self {
+ self.tree_symbol_style = style;
+ self
+ }
+
+ fn next_item(&self) -> Option<&Elem> {
+ self.items.get(self.selected + 1)
+ }
+
+ fn next_not_descendant_pos(&self, index: usize) -> usize {
+ let item = &self.items[index];
+ self.find(index + 1, false, |n| n.level <= item.level)
+ .unwrap_or(self.items.len())
+ }
+
+ fn find_parent(&self, index: usize) -> Option {
+ let item = &self.items[index];
+ self.find(index, true, |p| p.level < item.level)
+ }
+
+ // rev start: start - 1
+ fn find(&self, start: usize, rev: bool, f: F) -> Option
+ where
+ F: FnMut(&Elem) -> bool,
+ {
+ let iter = self.items.iter();
+ if rev {
+ iter.take(start).rposition(f)
+ } else {
+ iter.skip(start).position(f).map(|p| p + start)
+ }
+ }
+}
+
+impl Tree {
+ pub fn on_enter(&mut self, cx: &mut Context, params: &mut T::Params) {
+ if self.items.is_empty() {
+ return;
+ }
+ if let Some(next_level) = self.next_item().map(|elem| elem.level) {
+ let current = &mut self.items[self.selected];
+ let current_level = current.level;
+ if next_level > current_level {
+ if let Some(mut on_folded_fn) = self.on_folded_fn.take() {
+ on_folded_fn(&mut current.item, cx, params);
+ self.on_folded_fn = Some(on_folded_fn);
+ }
+ self.fold_current_child();
+ return;
+ }
+ }
+
+ if let Some(mut on_open_fn) = self.on_opened_fn.take() {
+ let mut f = || {
+ let current = &mut self.items[self.selected];
+ let items = match on_open_fn(&mut current.item, cx, params) {
+ TreeOp::Restore => {
+ let inserts = std::mem::take(&mut current.folded);
+ let _: Vec<_> = self
+ .items
+ .splice(self.selected + 1..self.selected + 1, inserts)
+ .collect();
+ return;
+ }
+ TreeOp::InsertChild(items) => items,
+ TreeOp::GetChildsAndInsert => match current.item.get_childs() {
+ Ok(items) => items,
+ Err(e) => return cx.editor.set_error(format!("{e}")),
+ },
+ TreeOp::ReplaceTree(items) => return self.replace_with_new_items(items),
+ TreeOp::Noop => return,
+ };
+ current.folded = vec![];
+ let inserts = vec_to_tree(items, current.level + 1);
+ let _: Vec<_> = self
+ .items
+ .splice(self.selected + 1..self.selected + 1, inserts)
+ .collect();
+ };
+ f();
+ self.on_opened_fn = Some(on_open_fn)
+ } else {
+ let current = &mut self.items[self.selected];
+ let inserts = std::mem::take(&mut current.folded);
+ let _: Vec<_> = self
+ .items
+ .splice(self.selected + 1..self.selected + 1, inserts)
+ .collect();
+ }
+ }
+
+ pub fn fold_current_level(&mut self) {
+ let start = match self.find_parent(self.selected) {
+ Some(start) => start,
+ None => return,
+ };
+ self.selected = start;
+ self.fold_current_child();
+ }
+
+ pub fn fold_current_child(&mut self) {
+ if self.selected + 1 >= self.items.len() {
+ return;
+ }
+ let pos = self.next_not_descendant_pos(self.selected);
+ if self.selected < pos {
+ self.items[self.selected].folded = self.items.drain(self.selected + 1..pos).collect();
+ }
+ }
+
+ pub fn search_next(&mut self, cx: &mut Context, s: &str, params: &mut T::Params) {
+ let skip = self.save_view.0 + 1;
+ self.selected = self
+ .find(skip, false, |e| e.item.filter(cx, s, params))
+ .unwrap_or(self.save_view.0);
+
+ self.row = (self.save_view.1 + self.selected).saturating_sub(self.save_view.0);
+ }
+
+ pub fn search_pre(&mut self, cx: &mut Context, s: &str, params: &mut T::Params) {
+ let take = self.save_view.0;
+ self.selected = self
+ .find(take, true, |e| e.item.filter(cx, s, params))
+ .unwrap_or(self.save_view.0);
+
+ self.row = (self.save_view.1 + self.selected).saturating_sub(self.save_view.0);
+ }
+
+ pub fn move_down(&mut self, rows: usize) {
+ let len = self.items.len();
+ if len > 0 {
+ self.selected = std::cmp::min(self.selected + rows, len.saturating_sub(1));
+ self.row = std::cmp::min(self.selected, self.row + rows);
+ }
+ }
+
+ pub fn move_up(&mut self, rows: usize) {
+ let len = self.items.len();
+ if len > 0 {
+ self.selected = self.selected.saturating_sub(rows);
+ self.row = std::cmp::min(self.selected, self.row.saturating_sub(rows));
+ }
+ }
+
+ pub fn move_left(&mut self, cols: usize) {
+ self.col = self.col.saturating_sub(cols);
+ }
+
+ pub fn move_right(&mut self, cols: usize) {
+ self.pre_render = Some(Box::new(move |tree: &mut Self, area: Rect| {
+ let max_scroll = tree.max_len.saturating_sub(area.width as usize);
+ tree.col = max_scroll.min(tree.col + cols);
+ }));
+ }
+
+ pub fn move_down_half_page(&mut self) {
+ self.pre_render = Some(Box::new(|tree: &mut Self, area: Rect| {
+ tree.move_down((area.height / 2) as usize);
+ }));
+ }
+
+ pub fn move_up_half_page(&mut self) {
+ self.pre_render = Some(Box::new(|tree: &mut Self, area: Rect| {
+ tree.move_up((area.height / 2) as usize);
+ }));
+ }
+
+ pub fn move_down_page(&mut self) {
+ self.pre_render = Some(Box::new(|tree: &mut Self, area: Rect| {
+ tree.move_down((area.height) as usize);
+ }));
+ }
+
+ pub fn move_up_page(&mut self) {
+ self.pre_render = Some(Box::new(|tree: &mut Self, area: Rect| {
+ tree.move_up((area.height) as usize);
+ }));
+ }
+
+ pub fn save_view(&mut self) {
+ self.save_view = (self.selected, self.row);
+ }
+
+ pub fn restore_view(&mut self) {
+ (self.selected, self.row) = self.save_view;
+ }
+
+ pub fn current(&self) -> &Elem {
+ &self.items[self.selected]
+ }
+
+ pub fn current_item(&self) -> &T {
+ &self.items[self.selected].item
+ }
+
+ pub fn row(&self) -> usize {
+ self.row
+ }
+
+ pub fn remove_current(&mut self) -> T {
+ let elem = self.items.remove(self.selected);
+ self.selected = self.selected.saturating_sub(1);
+ elem.item
+ }
+
+ pub fn replace_current(&mut self, item: T) {
+ self.items[self.selected].item = item;
+ }
+
+ pub fn select(&mut self, select_item: &T) {
+ let selected = self
+ .items
+ .iter()
+ .enumerate()
+ .filter(|(_, i)| i.item.cmp(select_item) == Ordering::Equal)
+ .next();
+ if let Some((idx, _)) = selected {
+ self.selected = idx;
+ self.row = idx;
+ }
+ }
+
+ pub fn insert_current_level(&mut self, item: T) {
+ let current = self.current();
+ let level = current.level;
+ let pos = match current.item.cmp(&item) {
+ Ordering::Less => self
+ .find(self.selected + 1, false, |e| {
+ e.level < level || (e.level == level && e.item.cmp(&item) != Ordering::Less)
+ })
+ .unwrap_or(self.items.len()),
+
+ Ordering::Greater => {
+ match self.find(self.selected, true, |elem| {
+ elem.level < level
+ || (elem.level == level && elem.item.cmp(&item) != Ordering::Greater)
+ }) {
+ Some(p) if self.items[p].level == level => self.next_not_descendant_pos(p),
+ Some(p) => p + 1,
+ None => 0,
+ }
+ }
+ Ordering::Equal => self.selected + 1,
+ };
+ self.items.insert(pos, Elem::new(item, level));
+ }
+}
+
+impl Tree {
+ pub fn render(
+ &mut self,
+ area: Rect,
+ surface: &mut Surface,
+ cx: &mut Context,
+ params: &mut T::Params,
+ ) {
+ if let Some(pre_render) = self.pre_render.take() {
+ pre_render(self, area);
+ }
+
+ self.max_len = 0;
+ self.row = std::cmp::min(self.row, area.height.saturating_sub(1) as usize);
+ let style = cx.editor.theme.get(&self.tree_symbol_style);
+ let folder_style = cx.editor.theme.get("special");
+ let last_item_index = self.items.len().saturating_sub(1);
+ let skip = self.selected.saturating_sub(self.row);
+ let iter = self
+ .items
+ .iter()
+ .skip(skip)
+ .take(area.height as usize)
+ .enumerate();
+ for (index, elem) in iter {
+ let row = index as u16;
+ let mut area = Rect::new(area.x, area.y + row, area.width, 1);
+ let indent = if elem.level > 0 {
+ if index + skip != last_item_index {
+ format!("{}", "│ ".repeat(elem.level - 1))
+ } else {
+ format!("{}", "".repeat(elem.level - 1))
+ }
+ } else {
+ "".to_string()
+ };
+
+ let indent_len = indent.chars().count();
+ if indent_len > self.col {
+ let indent: String = indent.chars().skip(self.col).collect();
+ if !indent.is_empty() {
+ surface.set_stringn(area.x, area.y, &indent, area.width as usize, style);
+ area = area.clip_left(indent.width() as u16);
+ }
+ };
+ let mut start_index = self.col.saturating_sub(indent_len);
+ let mut text = elem.item.text(cx, skip + index == self.selected, params);
+ self.max_len = self.max_len.max(text.width() + indent.len() - 2);
+ for span in text.0.iter_mut() {
+ if area.width == 0 {
+ return;
+ }
+ if start_index == 0 {
+ let mut icon_offset = 0;
+ if let Some((icon, color)) = elem.item.icon() {
+ let style = folder_style.fg(*color);
+ surface.set_string(area.x, area.y, icon, style);
+ icon_offset = 2;
+ }
+ surface.set_span(area.x + icon_offset, area.y, span, area.width - icon_offset);
+ area = area.clip_left((span.width() - icon_offset as usize) as u16);
+ } else {
+ let span_width = span.width();
+ if start_index > span_width {
+ start_index -= span_width;
+ } else {
+ let content: String = span
+ .content
+ .chars()
+ .filter(|c| {
+ if start_index > 0 {
+ start_index = start_index.saturating_sub(c.to_string().width());
+ false
+ } else {
+ true
+ }
+ })
+ .collect();
+ let mut cont = String::new();
+ cont.push_str("");
+ cont.push_str(&content);
+ surface.set_string_truncated(
+ area.x,
+ area.y,
+ &cont,
+ area.width as usize,
+ |_| span.style,
+ false,
+ false,
+ );
+ start_index = 0
+ }
+ }
+ }
+ }
+ }
+
+ pub fn handle_event(
+ &mut self,
+ event: Event,
+ cx: &mut Context,
+ params: &mut T::Params,
+ ) -> EventResult {
+ let key_event = match event {
+ Event::Key(event) => event,
+ Event::Resize(..) => return EventResult::Consumed(None),
+ _ => return EventResult::Ignored(None),
+ };
+ if let Some(mut on_next_key) = self.on_next_key.take() {
+ on_next_key(cx, self, key_event);
+ return EventResult::Consumed(None);
+ }
+ let count = std::mem::replace(&mut self.count, 0);
+ match key_event.into() {
+ key!(i @ '0'..='9') => self.count = i.to_digit(10).unwrap() as usize + count * 10,
+ key!('k') | shift!(Tab) | key!(Up) => self.move_up(1.max(count)),
+ key!('j') | key!(Tab) | key!(Down) => self.move_down(1.max(count)),
+ key!('z') => self.fold_current_level(),
+ key!('h') => self.move_left(1.max(count)),
+ key!('l') => self.move_right(1.max(count)),
+ shift!('G') => self.move_down(usize::MAX / 2),
+ key!(Enter) => self.on_enter(cx, params),
+ key!(' ') => self.on_enter(cx, params),
+ ctrl!('d') | ctrl!('j') => self.move_down_half_page(),
+ ctrl!('u') | ctrl!('k') => self.move_up_half_page(),
+ shift!('D') => self.move_down_page(),
+ shift!('U') => self.move_up_page(),
+ key!('g') => {
+ self.on_next_key = Some(Box::new(|_, tree, event| match event.into() {
+ key!('g') => tree.move_up(usize::MAX / 2),
+ key!('e') => tree.move_down(usize::MAX / 2),
+ _ => {}
+ }));
+ }
+ _ => return EventResult::Ignored(None),
+ }
+
+ EventResult::Consumed(None)
+ }
+}
+
+impl Tree {
+ pub fn filter(&mut self, s: &str, cx: &mut Context, params: &mut T::Params) {
+ fn filter_recursion(
+ elems: &Vec>,
+ mut index: usize,
+ s: &str,
+ cx: &mut Context,
+ params: &mut T::Params,
+ ) -> (Vec>, usize)
+ where
+ T: TreeItem + Clone,
+ {
+ let mut retain = vec![];
+ let elem = &elems[index];
+ loop {
+ let child = match elems.get(index + 1) {
+ Some(child) if child.item.is_child(&elem.item) => child,
+ _ => break,
+ };
+ index += 1;
+ let next = elems.get(index + 1);
+ if next.map_or(false, |n| n.item.is_child(&child.item)) {
+ let (sub_retain, current_index) = filter_recursion(elems, index, s, cx, params);
+ retain.extend(sub_retain);
+ index = current_index;
+ } else if child.item.filter(cx, s, params) {
+ retain.push(child.clone());
+ }
+ }
+ if !retain.is_empty() || elem.item.filter(cx, s, params) {
+ retain.insert(0, elem.clone());
+ }
+ (retain, index)
+ }
+
+ if s.is_empty() {
+ if let Some((_, recycle)) = self.recycle.take() {
+ self.items = recycle;
+ self.restore_view();
+ return;
+ }
+ }
+
+ let mut retain = vec![];
+ let mut index = 0;
+ let items = match &self.recycle {
+ Some((pre, _)) if pre == s => return,
+ Some((pre, recycle)) if pre.contains(s) => recycle,
+ _ => &self.items,
+ };
+ while let Some(elem) = items.get(index) {
+ let next = items.get(index + 1);
+ if next.map_or(false, |n| n.item.is_child(&elem.item)) {
+ let (sub_items, current_index) = filter_recursion(items, index, s, cx, params);
+ index = current_index;
+ retain.extend(sub_items);
+ } else if elem.item.filter(cx, s, params) {
+ retain.push(elem.clone())
+ }
+ index += 1;
+ }
+
+ if retain.is_empty() {
+ if let Some((_, recycle)) = self.recycle.take() {
+ self.items = recycle;
+ self.restore_view();
+ }
+ return;
+ }
+
+ let recycle = std::mem::replace(&mut self.items, retain);
+ if let Some(r) = self.recycle.as_mut() {
+ r.0 = s.into()
+ } else {
+ self.recycle = Some((s.into(), recycle));
+ self.save_view();
+ }
+
+ self.selected = self
+ .find(0, false, |elem| elem.item.filter(cx, s, params))
+ .unwrap_or(0);
+ self.row = self.selected;
+ }
+
+ pub fn clean_recycle(&mut self) {
+ self.recycle = None;
+ }
+
+ pub fn restore_recycle(&mut self) {
+ if let Some((_, recycle)) = self.recycle.take() {
+ self.items = recycle;
+ }
+ }
+}
diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml
index a4a1c389..648af4c1 100644
--- a/helix-tui/Cargo.toml
+++ b/helix-tui/Cargo.toml
@@ -16,11 +16,11 @@ include = ["src/**/*", "README.md"]
default = ["crossterm"]
[dependencies]
-bitflags = "1.3"
-cassowary = "0.3"
-unicode-segmentation = "1.10"
-crossterm = { version = "0.25", optional = true }
-termini = "0.1"
-serde = { version = "1", "optional" = true, features = ["derive"]}
+bitflags = "1.3.2"
+cassowary = "0.3.0"
+unicode-segmentation = "1.10.0"
+crossterm = { version = "0.25.0", optional = true }
+termini = "0.1.4"
+serde = { version = "1.0.152", "optional" = true, features = ["derive"] }
helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
helix-core = { version = "0.6", path = "../helix-core" }
diff --git a/helix-vcs/Cargo.toml b/helix-vcs/Cargo.toml
index 19b660a6..d8c033cb 100644
--- a/helix-vcs/Cargo.toml
+++ b/helix-vcs/Cargo.toml
@@ -13,16 +13,16 @@ homepage = "https://helix-editor.com"
[dependencies]
helix-core = { version = "0.6", path = "../helix-core" }
-tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
-parking_lot = "0.12"
+tokio = { version = "1.24.1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
+parking_lot = "0.12.1"
-git-repository = { version = "0.32", default-features = false , optional = true }
+git-repository = { version = "0.33.0", default-features = false, optional = true }
imara-diff = "0.1.5"
-log = "0.4"
+log = "0.4.17"
[features]
git = ["git-repository"]
[dev-dependencies]
-tempfile = "3.3"
\ No newline at end of file
+tempfile = "3.3.0"
diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml
index e7a20496..82ea05df 100644
--- a/helix-view/Cargo.toml
+++ b/helix-view/Cargo.toml
@@ -14,39 +14,39 @@ default = []
term = ["crossterm"]
[dependencies]
-bitflags = "1.3"
-anyhow = "1"
+bitflags = "1.3.2"
+anyhow = "1.0.68"
helix-core = { version = "0.6", path = "../helix-core" }
helix-loader = { version = "0.6", path = "../helix-loader" }
helix-lsp = { version = "0.6", path = "../helix-lsp" }
helix-dap = { version = "0.6", path = "../helix-dap" }
-crossterm = { version = "0.25", optional = true }
+crossterm = { version = "0.25.0", optional = true }
helix-vcs = { version = "0.6", path = "../helix-vcs" }
# Conversion traits
-once_cell = "1.17"
-url = "2"
+once_cell = "1.17.0"
+url = "2.3.1"
-arc-swap = { version = "1.6.0" }
+arc-swap = "1.6.0"
-tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
-tokio-stream = "0.1"
-futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
+tokio = { version = "1.24.1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
+tokio-stream = "0.1.11"
+futures-util = { version = "0.3.25", features = ["std", "async-await"], default-features = false }
-slotmap = "1"
+slotmap = "1.0.6"
-chardetng = "0.1"
+chardetng = "0.1.17"
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-toml = "0.5"
-log = "~0.4"
+serde = { version = "1.0.152", features = ["derive"] }
+serde_json = "1.0.91"
+toml = "0.5.10"
+log = "0.4.17"
-which = "4.2"
+which = "4.3.0"
[target.'cfg(windows)'.dependencies]
-clipboard-win = { version = "4.5", features = ["std"] }
+clipboard-win = { version = "4.5.0", features = ["std"] }
[dev-dependencies]
helix-tui = { path = "../helix-tui" }
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 856e5628..af948ecd 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -128,7 +128,7 @@ pub struct Document {
// be more troublesome.
pub history: Cell,
- pub savepoint: Option,
+ pub savepoint: Option<(i32, Transaction)>,
last_saved_revision: usize,
version: i32, // should be usize?
@@ -510,6 +510,21 @@ impl Document {
Some(fut.boxed())
}
+ /// Deletes the file associated with this document
+ pub fn delete(&mut self) -> impl Future