Merge pull request #2359 from dead10ck/test-harness
Integration testing harness
This commit is contained in:
commit
19dccade7c
29 changed files with 1160 additions and 224 deletions
|
@ -1,2 +1,3 @@
|
|||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
integration-test = "test --features integration --workspace --test integration"
|
||||
|
|
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
|
@ -38,6 +38,9 @@ jobs:
|
|||
test:
|
||||
name: Test Suite
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
HELIX_LOG_LEVEL: info
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
@ -50,9 +53,6 @@ jobs:
|
|||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Copy minimal languages config
|
||||
run: cp .github/workflows/languages.toml ./languages.toml
|
||||
|
||||
- name: Cache test tree-sitter grammar
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
|
@ -66,6 +66,11 @@ jobs:
|
|||
command: test
|
||||
args: --workspace
|
||||
|
||||
- name: Run cargo integration-test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: integration-test
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,5 @@
|
|||
target
|
||||
.direnv
|
||||
helix-term/rustfmt.toml
|
||||
helix-syntax/languages/
|
||||
result
|
||||
runtime/grammars
|
||||
|
|
325
Cargo.lock
generated
325
Cargo.lock
generated
|
@ -25,9 +25,9 @@ checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f"
|
|||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
|
@ -66,9 +66,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -121,9 +121,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.8"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
|
||||
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
|
@ -131,15 +131,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.23.2"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17"
|
||||
checksum = "77b75a27dc8d220f1f8521ea69cd55a34d720a200ebb3a624d9aa19193d3b432"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"futures-core",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.7.14",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
|
@ -184,9 +184,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
|||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.31"
|
||||
version = "0.8.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
|
||||
checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
@ -221,6 +221,15 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fern"
|
||||
version = "0.6.1"
|
||||
|
@ -293,9 +302,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.6"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
||||
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
|
@ -452,6 +461,7 @@ dependencies = [
|
|||
"helix-tui",
|
||||
"helix-view",
|
||||
"ignore",
|
||||
"indoc",
|
||||
"log",
|
||||
"once_cell",
|
||||
"pulldown-cmark",
|
||||
|
@ -460,6 +470,8 @@ dependencies = [
|
|||
"serde_json",
|
||||
"signal-hook",
|
||||
"signal-hook-tokio",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"toml",
|
||||
|
@ -545,10 +557,28 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.2"
|
||||
name = "indoc"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
|
||||
checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136"
|
||||
dependencies = [
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
|
@ -558,9 +588,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
version = "0.2.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
|
@ -574,19 +604,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
||||
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
@ -612,9 +641,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
|
@ -625,6 +654,19 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"miow",
|
||||
"ntapi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.3"
|
||||
|
@ -634,14 +676,32 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys",
|
||||
"windows-sys 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
|
@ -649,9 +709,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
@ -684,15 +744,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.3"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||
checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
"windows-sys 0.32.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -703,9 +763,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
|||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
|
@ -715,11 +775,11 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.39"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -744,18 +804,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.18"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
@ -771,29 +831,28 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.6"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -808,15 +867,24 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.26"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "retain_mut"
|
||||
version = "0.1.9"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
|
||||
checksum = "8c31b5c4033f8fdde8700e4657be2c497e7288f01515be52168c631e2e4d4086"
|
||||
|
||||
[[package]]
|
||||
name = "ropey"
|
||||
|
@ -830,9 +898,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.10"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
|
@ -851,18 +919,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.137"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.137"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
|
||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -871,9 +939,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.81"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -882,9 +950,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.8"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed"
|
||||
checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -903,12 +971,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.3"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||
checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.7.14",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
|
@ -941,9 +1009,9 @@ checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3"
|
|||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.6"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
||||
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
|
@ -995,9 +1063,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|||
|
||||
[[package]]
|
||||
name = "str-buf"
|
||||
version = "1.0.6"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
|
||||
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
|
||||
|
||||
[[package]]
|
||||
name = "str_indices"
|
||||
|
@ -1007,13 +1075,27 @@ checksum = "9d9199fa80c817e074620be84374a520062ebac833f358d74b37060ce4a0f2c0"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.95"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1029,18 +1111,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.31"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.31"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1067,9 +1149,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
@ -1089,7 +1171,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"mio 0.8.3",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
|
@ -1133,9 +1215,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tree-sitter"
|
||||
version = "0.20.6"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09b3b781640108d29892e8b9684642d2cda5ea05951fd58f0fea1db9edeb9b71"
|
||||
checksum = "4e34327f8eac545e3f037382471b2b19367725a242bba7bc45edb9efb49fe39a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
|
@ -1152,9 +1234,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-general-category"
|
||||
|
@ -1162,12 +1244,6 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1218098468b8085b19a2824104c70d976491d247ce194bbd9dc77181150cdfd6"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.2"
|
||||
|
@ -1198,6 +1274,18 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
|
@ -1242,9 +1330,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.2.5"
|
||||
version = "4.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
|
||||
checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2"
|
||||
dependencies = [
|
||||
"either",
|
||||
"lazy_static",
|
||||
|
@ -1282,43 +1370,86 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc 0.32.0",
|
||||
"windows_i686_gnu 0.32.0",
|
||||
"windows_i686_msvc 0.32.0",
|
||||
"windows_x86_64_gnu 0.32.0",
|
||||
"windows_x86_64_msvc 0.32.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_msvc 0.36.1",
|
||||
"windows_i686_gnu 0.36.1",
|
||||
"windows_i686_msvc 0.36.1",
|
||||
"windows_x86_64_gnu 0.36.1",
|
||||
"windows_x86_64_msvc 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.36.1"
|
||||
|
|
|
@ -35,3 +35,10 @@ to `cargo install` anything either).
|
|||
[architecture.md]: ./architecture.md
|
||||
[docs]: https://docs.helix-editor.com/
|
||||
[xtask]: https://github.com/matklad/cargo-xtask
|
||||
|
||||
# Integration tests
|
||||
|
||||
Integration tests for helix-term can be run with `cargo integration-test`. Code
|
||||
contributors are strongly encouraged to write integration tests for their code.
|
||||
Existing tests can be used as examples. Helpers can be found in
|
||||
[helpers.rs][../helix-term/tests/test/helpers.rs].
|
||||
|
|
|
@ -12,6 +12,7 @@ include = ["src/**/*", "README.md"]
|
|||
|
||||
[features]
|
||||
unicode-lines = ["ropey/unicode_lines"]
|
||||
integration = []
|
||||
|
||||
[dependencies]
|
||||
helix-loader = { version = "0.6", path = "../helix-loader" }
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
//! When typing the opening character of one of the possible pairs defined below,
|
||||
//! this module provides the functionality to insert the paired closing character.
|
||||
|
||||
use crate::{
|
||||
graphemes, movement::Direction, Range, Rope, RopeGraphemes, Selection, Tendril, Transaction,
|
||||
};
|
||||
use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use log::debug;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
// Heavily based on https://github.com/codemirror/closebrackets/
|
||||
|
@ -125,7 +122,7 @@ impl Default for AutoPairs {
|
|||
|
||||
#[must_use]
|
||||
pub fn hook(doc: &Rope, selection: &Selection, ch: char, pairs: &AutoPairs) -> Option<Transaction> {
|
||||
debug!("autopairs hook selection: {:#?}", selection);
|
||||
log::trace!("autopairs hook selection: {:#?}", selection);
|
||||
|
||||
if let Some(pair) = pairs.get(ch) {
|
||||
if pair.same() {
|
||||
|
@ -149,14 +146,6 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
|
|||
doc.get_char(pos - 1)
|
||||
}
|
||||
|
||||
fn is_single_grapheme(doc: &Rope, range: &Range) -> bool {
|
||||
let mut graphemes = RopeGraphemes::new(doc.slice(range.from()..range.to()));
|
||||
let first = graphemes.next();
|
||||
let second = graphemes.next();
|
||||
debug!("first: {:#?}, second: {:#?}", first, second);
|
||||
first.is_some() && second.is_none()
|
||||
}
|
||||
|
||||
/// calculate what the resulting range should be for an auto pair insertion
|
||||
fn get_next_range(
|
||||
doc: &Rope,
|
||||
|
@ -189,8 +178,8 @@ fn get_next_range(
|
|||
);
|
||||
}
|
||||
|
||||
let single_grapheme = is_single_grapheme(doc, start_range);
|
||||
let doc_slice = doc.slice(..);
|
||||
let single_grapheme = start_range.is_single_grapheme(doc_slice);
|
||||
|
||||
// just skip over graphemes
|
||||
if len_inserted == 0 {
|
||||
|
@ -235,9 +224,11 @@ fn get_next_range(
|
|||
// other end of the grapheme to get to where the new characters
|
||||
// are inserted, then move the head to where it should be
|
||||
let prev_bound = graphemes::prev_grapheme_boundary(doc_slice, start_range.head);
|
||||
debug!(
|
||||
log::trace!(
|
||||
"prev_bound: {}, offset: {}, len_inserted: {}",
|
||||
prev_bound, offset, len_inserted
|
||||
prev_bound,
|
||||
offset,
|
||||
len_inserted
|
||||
);
|
||||
prev_bound + offset + len_inserted
|
||||
};
|
||||
|
@ -312,7 +303,7 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
|||
});
|
||||
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
log::debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
|
@ -344,7 +335,7 @@ fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
|||
});
|
||||
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
log::debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
|
@ -384,7 +375,7 @@ fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
|||
});
|
||||
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
log::debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
prev_grapheme_boundary,
|
||||
},
|
||||
movement::Direction,
|
||||
Assoc, ChangeSet, RopeSlice,
|
||||
Assoc, ChangeSet, RopeGraphemes, RopeSlice,
|
||||
};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::borrow::Cow;
|
||||
|
@ -339,6 +339,14 @@ impl Range {
|
|||
pub fn cursor_line(&self, text: RopeSlice) -> usize {
|
||||
text.char_to_line(self.cursor(text))
|
||||
}
|
||||
|
||||
/// Returns true if this Range covers a single grapheme in the given text
|
||||
pub fn is_single_grapheme(&self, doc: RopeSlice) -> bool {
|
||||
let mut graphemes = RopeGraphemes::new(doc.slice(self.from()..self.to()));
|
||||
let first = graphemes.next();
|
||||
let second = graphemes.next();
|
||||
first.is_some() && second.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(usize, usize)> for Range {
|
||||
|
|
|
@ -20,7 +20,6 @@ toml = "0.5"
|
|||
etcetera = "0.4"
|
||||
tree-sitter = "0.20"
|
||||
once_cell = "1.12"
|
||||
|
||||
log = "0.4"
|
||||
|
||||
# TODO: these two should be on !wasm32 only
|
||||
|
|
|
@ -11,17 +11,19 @@ pub fn runtime_dir() -> std::path::PathBuf {
|
|||
return dir.into();
|
||||
}
|
||||
|
||||
if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
|
||||
// this is the directory of the crate being run by cargo, we need the workspace path so we take the parent
|
||||
let path = std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR);
|
||||
log::debug!("runtime dir: {}", path.to_string_lossy());
|
||||
return path;
|
||||
}
|
||||
|
||||
const RT_DIR: &str = "runtime";
|
||||
let conf_dir = config_dir().join(RT_DIR);
|
||||
if conf_dir.exists() {
|
||||
return conf_dir;
|
||||
}
|
||||
|
||||
if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
|
||||
// this is the directory of the crate being run by cargo, we need the workspace path so we take the parent
|
||||
return std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR);
|
||||
}
|
||||
|
||||
// fallback to location of the executable being run
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
|
|
|
@ -17,6 +17,7 @@ app = true
|
|||
|
||||
[features]
|
||||
unicode-lines = ["helix-core/unicode-lines"]
|
||||
integration = []
|
||||
|
||||
[[bin]]
|
||||
name = "hx"
|
||||
|
@ -73,3 +74,8 @@ signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
|
|||
|
||||
[build-dependencies]
|
||||
helix-loader = { version = "0.6", path = "../helix-loader" }
|
||||
|
||||
[dev-dependencies]
|
||||
smallvec = "1.8"
|
||||
indoc = "1.0.3"
|
||||
tempfile = "3.3.0"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use arc_swap::{access::Map, ArcSwap};
|
||||
use futures_util::Stream;
|
||||
use helix_core::{
|
||||
config::{default_syntax_loader, user_syntax_loader},
|
||||
pos_at_coords, syntax, Selection,
|
||||
|
@ -24,10 +25,10 @@ use std::{
|
|||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::Error;
|
||||
use anyhow::{Context, Error};
|
||||
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream},
|
||||
event::{DisableMouseCapture, EnableMouseCapture, Event},
|
||||
execute, terminal,
|
||||
tty::IsTty,
|
||||
};
|
||||
|
@ -39,9 +40,11 @@ use {
|
|||
#[cfg(windows)]
|
||||
type Signals = futures_util::stream::Empty<()>;
|
||||
|
||||
const LSP_DEADLINE: Duration = Duration::from_millis(16);
|
||||
|
||||
pub struct Application {
|
||||
compositor: Compositor,
|
||||
editor: Editor,
|
||||
pub editor: Editor,
|
||||
|
||||
config: Arc<ArcSwap<Config>>,
|
||||
|
||||
|
@ -53,32 +56,39 @@ pub struct Application {
|
|||
signals: Signals,
|
||||
jobs: Jobs,
|
||||
lsp_progress: LspProgressMap,
|
||||
last_render: Instant,
|
||||
}
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
fn setup_integration_logging() {
|
||||
let level = std::env::var("HELIX_LOG_LEVEL")
|
||||
.map(|lvl| lvl.parse().unwrap())
|
||||
.unwrap_or(log::LevelFilter::Info);
|
||||
|
||||
// Separate file config so we can include year, month and day in file logs
|
||||
let _ = fern::Dispatch::new()
|
||||
.format(|out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{} {} [{}] {}",
|
||||
chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%.3f"),
|
||||
record.target(),
|
||||
record.level(),
|
||||
message
|
||||
))
|
||||
})
|
||||
.level(level)
|
||||
.chain(std::io::stdout())
|
||||
.apply();
|
||||
}
|
||||
|
||||
impl Application {
|
||||
pub fn new(args: Args) -> Result<Self, Error> {
|
||||
pub fn new(args: Args, config: Config) -> Result<Self, Error> {
|
||||
#[cfg(feature = "integration")]
|
||||
setup_integration_logging();
|
||||
|
||||
use helix_view::editor::Action;
|
||||
|
||||
let config_dir = helix_loader::config_dir();
|
||||
if !config_dir.exists() {
|
||||
std::fs::create_dir_all(&config_dir).ok();
|
||||
}
|
||||
|
||||
let config = match std::fs::read_to_string(config_dir.join("config.toml")) {
|
||||
Ok(config) => toml::from_str(&config)
|
||||
.map(crate::keymap::merge_keys)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Bad config: {}", err);
|
||||
eprintln!("Press <ENTER> to continue with default config");
|
||||
use std::io::Read;
|
||||
// This waits for an enter press.
|
||||
let _ = std::io::stdin().read(&mut []);
|
||||
Config::default()
|
||||
}),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(),
|
||||
Err(err) => return Err(Error::new(err)),
|
||||
};
|
||||
|
||||
let theme_loader = std::sync::Arc::new(theme::Loader::new(
|
||||
&config_dir,
|
||||
&helix_loader::runtime_dir(),
|
||||
|
@ -116,7 +126,7 @@ impl Application {
|
|||
});
|
||||
let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf));
|
||||
|
||||
let mut compositor = Compositor::new()?;
|
||||
let mut compositor = Compositor::new().context("build compositor")?;
|
||||
let config = Arc::new(ArcSwap::from_pointee(config));
|
||||
let mut editor = Editor::new(
|
||||
compositor.size(),
|
||||
|
@ -135,26 +145,28 @@ impl Application {
|
|||
|
||||
if args.load_tutor {
|
||||
let path = helix_loader::runtime_dir().join("tutor.txt");
|
||||
editor.open(path, Action::VerticalSplit)?;
|
||||
editor.open(&path, Action::VerticalSplit)?;
|
||||
// Unset path to prevent accidentally saving to the original tutor file.
|
||||
doc_mut!(editor).set_path(None)?;
|
||||
} else if !args.files.is_empty() {
|
||||
let first = &args.files[0].0; // we know it's not empty
|
||||
if first.is_dir() {
|
||||
std::env::set_current_dir(&first)?;
|
||||
std::env::set_current_dir(&first).context("set current dir")?;
|
||||
editor.new_file(Action::VerticalSplit);
|
||||
let picker = ui::file_picker(".".into(), &config.load().editor);
|
||||
compositor.push(Box::new(overlayed(picker)));
|
||||
} else {
|
||||
let nr_of_files = args.files.len();
|
||||
editor.open(first.to_path_buf(), Action::VerticalSplit)?;
|
||||
editor.open(first, Action::VerticalSplit)?;
|
||||
for (file, pos) in args.files {
|
||||
if file.is_dir() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"expected a path to file, found a directory. (to open a directory pass it as first argument)"
|
||||
));
|
||||
} else {
|
||||
let doc_id = editor.open(file, Action::Load)?;
|
||||
let doc_id = editor
|
||||
.open(&file, Action::Load)
|
||||
.context(format!("open '{}'", file.to_string_lossy()))?;
|
||||
// with Action::Load all documents have the same view
|
||||
let view_id = editor.tree.focus;
|
||||
let doc = editor.document_mut(doc_id).unwrap();
|
||||
|
@ -168,7 +180,7 @@ impl Application {
|
|||
let (view, doc) = current!(editor);
|
||||
align_view(doc, view, Align::Center);
|
||||
}
|
||||
} else if stdin().is_tty() {
|
||||
} else if stdin().is_tty() || cfg!(feature = "integration") {
|
||||
editor.new_file(Action::VerticalSplit);
|
||||
} else if cfg!(target_os = "macos") {
|
||||
// On Linux and Windows, we allow the output of a command to be piped into the new buffer.
|
||||
|
@ -186,7 +198,8 @@ impl Application {
|
|||
#[cfg(windows)]
|
||||
let signals = futures_util::stream::empty();
|
||||
#[cfg(not(windows))]
|
||||
let signals = Signals::new(&[signal::SIGTSTP, signal::SIGCONT])?;
|
||||
let signals =
|
||||
Signals::new(&[signal::SIGTSTP, signal::SIGCONT]).context("build signal handler")?;
|
||||
|
||||
let app = Self {
|
||||
compositor,
|
||||
|
@ -200,31 +213,48 @@ impl Application {
|
|||
signals,
|
||||
jobs: Jobs::new(),
|
||||
lsp_progress: LspProgressMap::new(),
|
||||
last_render: Instant::now(),
|
||||
};
|
||||
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
fn render(&mut self) {
|
||||
let compositor = &mut self.compositor;
|
||||
|
||||
let mut cx = crate::compositor::Context {
|
||||
editor: &mut self.editor,
|
||||
jobs: &mut self.jobs,
|
||||
scroll: None,
|
||||
};
|
||||
|
||||
self.compositor.render(&mut cx);
|
||||
compositor.render(&mut cx);
|
||||
}
|
||||
|
||||
pub async fn event_loop(&mut self) {
|
||||
let mut reader = EventStream::new();
|
||||
let mut last_render = Instant::now();
|
||||
let deadline = Duration::from_secs(1) / 60;
|
||||
|
||||
pub async fn event_loop<S>(&mut self, input_stream: &mut S)
|
||||
where
|
||||
S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
|
||||
{
|
||||
self.render();
|
||||
self.last_render = Instant::now();
|
||||
|
||||
loop {
|
||||
if !self.event_loop_until_idle(input_stream).await {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
|
||||
where
|
||||
S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
|
||||
{
|
||||
#[cfg(feature = "integration")]
|
||||
let mut idle_handled = false;
|
||||
|
||||
loop {
|
||||
if self.editor.should_close() {
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
|
||||
use futures_util::StreamExt;
|
||||
|
@ -232,8 +262,8 @@ impl Application {
|
|||
tokio::select! {
|
||||
biased;
|
||||
|
||||
event = reader.next() => {
|
||||
self.handle_terminal_events(event)
|
||||
Some(event) = input_stream.next() => {
|
||||
self.handle_terminal_events(event);
|
||||
}
|
||||
Some(signal) = self.signals.next() => {
|
||||
self.handle_signals(signal).await;
|
||||
|
@ -242,9 +272,10 @@ impl Application {
|
|||
self.handle_language_server_message(call, id).await;
|
||||
// limit render calls for fast language server messages
|
||||
let last = self.editor.language_servers.incoming.is_empty();
|
||||
if last || last_render.elapsed() > deadline {
|
||||
|
||||
if last || self.last_render.elapsed() > LSP_DEADLINE {
|
||||
self.render();
|
||||
last_render = Instant::now();
|
||||
self.last_render = Instant::now();
|
||||
}
|
||||
}
|
||||
Some(payload) = self.editor.debugger_events.next() => {
|
||||
|
@ -269,9 +300,25 @@ impl Application {
|
|||
// idle timeout
|
||||
self.editor.clear_idle_timer();
|
||||
self.handle_idle_timeout();
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
{
|
||||
idle_handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for integration tests only, reset the idle timer after every
|
||||
// event to make a signal when test events are done processing
|
||||
#[cfg(feature = "integration")]
|
||||
{
|
||||
if idle_handled {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.editor.reset_idle_timer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_config_events(&mut self, config_event: ConfigEvent) {
|
||||
|
@ -370,7 +417,7 @@ impl Application {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::ErrorKind>>) {
|
||||
pub fn handle_terminal_events(&mut self, event: Result<Event, crossterm::ErrorKind>) {
|
||||
let mut cx = crate::compositor::Context {
|
||||
editor: &mut self.editor,
|
||||
jobs: &mut self.jobs,
|
||||
|
@ -378,15 +425,14 @@ impl Application {
|
|||
};
|
||||
// Handle key events
|
||||
let should_redraw = match event {
|
||||
Some(Ok(Event::Resize(width, height))) => {
|
||||
Ok(Event::Resize(width, height)) => {
|
||||
self.compositor.resize(width, height);
|
||||
|
||||
self.compositor
|
||||
.handle_event(Event::Resize(width, height), &mut cx)
|
||||
}
|
||||
Some(Ok(event)) => self.compositor.handle_event(event, &mut cx),
|
||||
Some(Err(x)) => panic!("{}", x),
|
||||
None => panic!(),
|
||||
Ok(event) => self.compositor.handle_event(event, &mut cx),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
||||
if should_redraw && !self.editor.should_close() {
|
||||
|
@ -740,7 +786,10 @@ impl Application {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<i32, Error> {
|
||||
pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
|
||||
where
|
||||
S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
|
||||
{
|
||||
self.claim_term().await?;
|
||||
|
||||
// Exit the alternate screen and disable raw mode before panicking
|
||||
|
@ -755,16 +804,20 @@ impl Application {
|
|||
hook(info);
|
||||
}));
|
||||
|
||||
self.event_loop().await;
|
||||
self.event_loop(input_stream).await;
|
||||
self.close().await?;
|
||||
self.restore_term()?;
|
||||
|
||||
self.jobs.finish().await;
|
||||
Ok(self.editor.exit_code)
|
||||
}
|
||||
|
||||
pub async fn close(&mut self) -> anyhow::Result<()> {
|
||||
self.jobs.finish().await?;
|
||||
|
||||
if self.editor.close_language_servers(None).await.is_err() {
|
||||
log::error!("Timed out waiting for language servers to shutdown");
|
||||
};
|
||||
|
||||
self.restore_term()?;
|
||||
|
||||
Ok(self.editor.exit_code)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1026,7 +1026,7 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
|
|||
for sel in paths {
|
||||
let p = sel.trim();
|
||||
if !p.is_empty() {
|
||||
if let Err(e) = cx.editor.open(PathBuf::from(p), action) {
|
||||
if let Err(e) = cx.editor.open(&PathBuf::from(p), action) {
|
||||
cx.editor.set_error(format!("Open file failed: {:?}", e));
|
||||
}
|
||||
}
|
||||
|
@ -1855,7 +1855,7 @@ fn global_search(cx: &mut Context) {
|
|||
}
|
||||
},
|
||||
move |cx, (line_num, path), action| {
|
||||
match cx.editor.open(path.into(), action) {
|
||||
match cx.editor.open(path, action) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
cx.editor.set_error(format!(
|
||||
|
@ -2100,10 +2100,17 @@ fn insert_mode(cx: &mut Context) {
|
|||
let (view, doc) = current!(cx.editor);
|
||||
enter_insert_mode(doc);
|
||||
|
||||
log::trace!(
|
||||
"entering insert mode with sel: {:?}, text: {:?}",
|
||||
doc.selection(view.id),
|
||||
doc.text().to_string()
|
||||
);
|
||||
|
||||
let selection = doc
|
||||
.selection(view.id)
|
||||
.clone()
|
||||
.transform(|range| Range::new(range.to(), range.from()));
|
||||
|
||||
doc.set_selection(view.id, selection);
|
||||
}
|
||||
|
||||
|
@ -2449,8 +2456,8 @@ fn normal_mode(cx: &mut Context) {
|
|||
graphemes::prev_grapheme_boundary(text, range.to()),
|
||||
)
|
||||
});
|
||||
doc.set_selection(view.id, selection);
|
||||
|
||||
doc.set_selection(view.id, selection);
|
||||
doc.restore_cursor = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ fn jump_to_location(
|
|||
return;
|
||||
}
|
||||
};
|
||||
let _id = editor.open(path, action).expect("editor.open failed");
|
||||
let _id = editor.open(&path, action).expect("editor.open failed");
|
||||
let (view, doc) = current!(editor);
|
||||
let definition_pos = location.range.start;
|
||||
// TODO: convert inside server
|
||||
|
@ -114,7 +114,7 @@ fn sym_picker(
|
|||
return;
|
||||
}
|
||||
};
|
||||
if let Err(err) = cx.editor.open(path, action) {
|
||||
if let Err(err) = cx.editor.open(&path, action) {
|
||||
let err = format!("failed to open document: {}: {}", uri, err);
|
||||
log::error!("{}", err);
|
||||
cx.editor.set_error(err);
|
||||
|
@ -383,7 +383,7 @@ pub fn apply_workspace_edit(
|
|||
};
|
||||
|
||||
let current_view_id = view!(editor).id;
|
||||
let doc_id = match editor.open(path, Action::Load) {
|
||||
let doc_id = match editor.open(&path, Action::Load) {
|
||||
Ok(doc_id) => doc_id,
|
||||
Err(err) => {
|
||||
let err = format!("failed to open document: {}: {}", uri, err);
|
||||
|
|
|
@ -50,7 +50,7 @@ fn open(
|
|||
ensure!(!args.is_empty(), "wrong argument count");
|
||||
for arg in args {
|
||||
let (path, pos) = args::parse_file(arg);
|
||||
let _ = cx.editor.open(path, Action::Replace)?;
|
||||
let _ = cx.editor.open(&path, Action::Replace)?;
|
||||
let (view, doc) = current!(cx.editor);
|
||||
let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true));
|
||||
doc.set_selection(view.id, pos);
|
||||
|
@ -233,6 +233,7 @@ fn write_impl(
|
|||
doc.detect_language(cx.editor.syn_loader.clone());
|
||||
let _ = cx.editor.refresh_language_server(id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -422,6 +423,7 @@ fn write_quit(
|
|||
event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
write_impl(cx, args.first(), false)?;
|
||||
helix_lsp::block_on(cx.jobs.finish())?;
|
||||
quit(cx, &[], event)
|
||||
}
|
||||
|
||||
|
@ -819,7 +821,7 @@ fn vsplit(
|
|||
} else {
|
||||
for arg in args {
|
||||
cx.editor
|
||||
.open(PathBuf::from(arg.as_ref()), Action::VerticalSplit)?;
|
||||
.open(&PathBuf::from(arg.as_ref()), Action::VerticalSplit)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -838,7 +840,7 @@ fn hsplit(
|
|||
} else {
|
||||
for arg in args {
|
||||
cx.editor
|
||||
.open(PathBuf::from(arg.as_ref()), Action::HorizontalSplit)?;
|
||||
.open(&PathBuf::from(arg.as_ref()), Action::HorizontalSplit)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -923,7 +925,7 @@ fn tutor(
|
|||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
let path = helix_loader::runtime_dir().join("tutor.txt");
|
||||
cx.editor.open(path, Action::Replace)?;
|
||||
cx.editor.open(&path, Action::Replace)?;
|
||||
// Unset path to prevent accidentally saving to the original tutor file.
|
||||
doc_mut!(cx.editor).set_path(None)?;
|
||||
Ok(())
|
||||
|
@ -1150,7 +1152,7 @@ fn open_config(
|
|||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
cx.editor
|
||||
.open(helix_loader::config_file(), Action::Replace)?;
|
||||
.open(&helix_loader::config_file(), Action::Replace)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1159,7 +1161,7 @@ fn open_log(
|
|||
_args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
cx.editor.open(helix_loader::log_file(), Action::Replace)?;
|
||||
cx.editor.open(&helix_loader::log_file(), Action::Replace)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ use helix_core::Position;
|
|||
use helix_view::graphics::{CursorKind, Rect};
|
||||
|
||||
use crossterm::event::Event;
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
use tui::backend::TestBackend;
|
||||
use tui::buffer::Buffer as Surface;
|
||||
|
||||
pub type Callback = Box<dyn FnOnce(&mut Compositor, &mut Context)>;
|
||||
|
@ -63,11 +66,21 @@ pub trait Component: Any + AnyComponent {
|
|||
}
|
||||
}
|
||||
|
||||
use anyhow::Error;
|
||||
use anyhow::Context as AnyhowContext;
|
||||
use tui::backend::Backend;
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
use tui::backend::CrosstermBackend;
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
use std::io::stdout;
|
||||
use tui::backend::{Backend, CrosstermBackend};
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
type Terminal = tui::terminal::Terminal<CrosstermBackend<std::io::Stdout>>;
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
type Terminal = tui::terminal::Terminal<TestBackend>;
|
||||
|
||||
pub struct Compositor {
|
||||
layers: Vec<Box<dyn Component>>,
|
||||
terminal: Terminal,
|
||||
|
@ -76,9 +89,14 @@ pub struct Compositor {
|
|||
}
|
||||
|
||||
impl Compositor {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
#[cfg(not(feature = "integration"))]
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
let backend = TestBackend::new(120, 150);
|
||||
|
||||
let terminal = Terminal::new(backend).context("build terminal")?;
|
||||
Ok(Self {
|
||||
layers: Vec::new(),
|
||||
terminal,
|
||||
|
|
|
@ -2,7 +2,7 @@ use helix_view::Editor;
|
|||
|
||||
use crate::compositor::Compositor;
|
||||
|
||||
use futures_util::future::{self, BoxFuture, Future, FutureExt};
|
||||
use futures_util::future::{BoxFuture, Future, FutureExt};
|
||||
use futures_util::stream::{FuturesUnordered, StreamExt};
|
||||
|
||||
pub type Callback = Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>;
|
||||
|
@ -93,8 +93,21 @@ impl Jobs {
|
|||
}
|
||||
|
||||
/// Blocks until all the jobs that need to be waited on are done.
|
||||
pub async fn finish(&mut self) {
|
||||
let wait_futures = std::mem::take(&mut self.wait_futures);
|
||||
wait_futures.for_each(|_| future::ready(())).await
|
||||
pub async fn finish(&mut self) -> anyhow::Result<()> {
|
||||
log::debug!("waiting on jobs...");
|
||||
let mut wait_futures = std::mem::take(&mut self.wait_futures);
|
||||
while let (Some(job), tail) = wait_futures.into_future().await {
|
||||
match job {
|
||||
Ok(_) => {
|
||||
wait_futures = tail;
|
||||
}
|
||||
Err(e) => {
|
||||
self.wait_futures = tail;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use anyhow::{Context, Result};
|
||||
use anyhow::{Context, Error, Result};
|
||||
use crossterm::event::EventStream;
|
||||
use helix_term::application::Application;
|
||||
use helix_term::args::Args;
|
||||
use helix_term::config::Config;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
|
||||
|
@ -110,10 +112,30 @@ FLAGS:
|
|||
|
||||
setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
|
||||
|
||||
// TODO: use the thread local executor to spawn the application task separately from the work pool
|
||||
let mut app = Application::new(args).context("unable to create new application")?;
|
||||
let config_dir = helix_loader::config_dir();
|
||||
if !config_dir.exists() {
|
||||
std::fs::create_dir_all(&config_dir).ok();
|
||||
}
|
||||
|
||||
let exit_code = app.run().await?;
|
||||
let config = match std::fs::read_to_string(config_dir.join("config.toml")) {
|
||||
Ok(config) => toml::from_str(&config)
|
||||
.map(helix_term::keymap::merge_keys)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Bad config: {}", err);
|
||||
eprintln!("Press <ENTER> to continue with default config");
|
||||
use std::io::Read;
|
||||
// This waits for an enter press.
|
||||
let _ = std::io::stdin().read(&mut []);
|
||||
Config::default()
|
||||
}),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(),
|
||||
Err(err) => return Err(Error::new(err)),
|
||||
};
|
||||
|
||||
// TODO: use the thread local executor to spawn the application task separately from the work pool
|
||||
let mut app = Application::new(args, config).context("unable to create new application")?;
|
||||
|
||||
let exit_code = app.run(&mut EventStream::new()).await?;
|
||||
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
|
|
@ -175,7 +175,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
|
|||
path.strip_prefix(&root).unwrap_or(path).to_string_lossy()
|
||||
},
|
||||
move |cx, path: &PathBuf, action| {
|
||||
if let Err(e) = cx.editor.open(path.into(), action) {
|
||||
if let Err(e) = cx.editor.open(path, action) {
|
||||
let err = if let Some(err) = e.source() {
|
||||
format!("{}", err)
|
||||
} else {
|
||||
|
|
25
helix-term/tests/integration.rs
Normal file
25
helix-term/tests/integration.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
#[cfg(feature = "integration")]
|
||||
mod test {
|
||||
mod helpers;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use helix_core::{syntax::AutoPairConfig, Position, Selection};
|
||||
use helix_term::{args::Args, config::Config};
|
||||
|
||||
use indoc::indoc;
|
||||
|
||||
use self::helpers::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn hello_world() -> anyhow::Result<()> {
|
||||
test(("#[\n|]#", "ihello world<esc>", "hello world#[|\n]#")).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod auto_indent;
|
||||
mod auto_pairs;
|
||||
mod commands;
|
||||
mod movement;
|
||||
mod write;
|
||||
}
|
26
helix-term/tests/test/auto_indent.rs
Normal file
26
helix-term/tests/test/auto_indent.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn auto_indent_c() -> anyhow::Result<()> {
|
||||
test_with_config(
|
||||
Args {
|
||||
files: vec![(PathBuf::from("foo.c"), Position::default())],
|
||||
..Default::default()
|
||||
},
|
||||
Config::default(),
|
||||
// switches to append mode?
|
||||
(
|
||||
helpers::platform_line("void foo() {#[|}]#").as_ref(),
|
||||
"i<ret><esc>",
|
||||
helpers::platform_line(indoc! {"\
|
||||
void foo() {
|
||||
#[|\n]#\
|
||||
}
|
||||
"})
|
||||
.as_ref(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
21
helix-term/tests/test/auto_pairs.rs
Normal file
21
helix-term/tests/test/auto_pairs.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn auto_pairs_basic() -> anyhow::Result<()> {
|
||||
test(("#[\n|]#", "i(<esc>", "(#[|)]#\n")).await?;
|
||||
|
||||
test_with_config(
|
||||
Args::default(),
|
||||
Config {
|
||||
editor: helix_view::editor::Config {
|
||||
auto_pairs: AutoPairConfig::Enable(false),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
("#[\n|]#", "i(<esc>", "(#[|\n]#"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
93
helix-term/tests/test/commands.rs
Normal file
93
helix-term/tests/test/commands.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use std::{
|
||||
io::{Read, Write},
|
||||
ops::RangeInclusive,
|
||||
};
|
||||
|
||||
use helix_core::diagnostic::Severity;
|
||||
use helix_term::application::Application;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_write_quit_fail() -> anyhow::Result<()> {
|
||||
let file = helpers::new_readonly_tempfile()?;
|
||||
|
||||
test_key_sequence(
|
||||
&mut helpers::app_with_file(file.path())?,
|
||||
Some("ihello<esc>:wq<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1);
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_buffer_close_concurrent() -> anyhow::Result<()> {
|
||||
test_key_sequences(
|
||||
&mut Application::new(Args::default(), Config::default())?,
|
||||
vec![
|
||||
(
|
||||
None,
|
||||
Some(&|app| {
|
||||
assert_eq!(1, app.editor.documents().count());
|
||||
assert!(!app.editor.is_err());
|
||||
}),
|
||||
),
|
||||
(
|
||||
Some("ihello<esc>:new<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(2, app.editor.documents().count());
|
||||
assert!(!app.editor.is_err());
|
||||
}),
|
||||
),
|
||||
(
|
||||
Some(":buffer<minus>close<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(1, app.editor.documents().count());
|
||||
assert!(!app.editor.is_err());
|
||||
}),
|
||||
),
|
||||
],
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// verify if writes are queued up, it finishes them before closing the buffer
|
||||
let mut file = tempfile::NamedTempFile::new()?;
|
||||
let mut command = String::new();
|
||||
const RANGE: RangeInclusive<i32> = 1..=1000;
|
||||
|
||||
for i in RANGE {
|
||||
let cmd = format!("%c{}<esc>:w<ret>", i);
|
||||
command.push_str(&cmd);
|
||||
}
|
||||
|
||||
command.push_str(":buffer<minus>close<ret>");
|
||||
|
||||
test_key_sequence(
|
||||
&mut helpers::app_with_file(file.path())?,
|
||||
Some(&command),
|
||||
Some(&|app| {
|
||||
assert!(!app.editor.is_err(), "error: {:?}", app.editor.get_status());
|
||||
|
||||
let doc = app.editor.document_by_path(file.path());
|
||||
assert!(doc.is_none(), "found doc: {:?}", doc);
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
file.as_file_mut().flush()?;
|
||||
file.as_file_mut().sync_all()?;
|
||||
|
||||
let mut file_content = String::new();
|
||||
file.as_file_mut().read_to_string(&mut file_content)?;
|
||||
assert_eq!(RANGE.end().to_string(), file_content);
|
||||
|
||||
Ok(())
|
||||
}
|
213
helix-term/tests/test/helpers.rs
Normal file
213
helix-term/tests/test/helpers.rs
Normal file
|
@ -0,0 +1,213 @@
|
|||
use std::{io::Write, path::PathBuf, time::Duration};
|
||||
|
||||
use anyhow::bail;
|
||||
use crossterm::event::{Event, KeyEvent};
|
||||
use helix_core::{test, Selection, Transaction};
|
||||
use helix_term::{application::Application, args::Args, config::Config};
|
||||
use helix_view::{doc, input::parse_macro};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TestCase {
|
||||
pub in_text: String,
|
||||
pub in_selection: Selection,
|
||||
pub in_keys: String,
|
||||
pub out_text: String,
|
||||
pub out_selection: Selection,
|
||||
}
|
||||
|
||||
impl<S: Into<String>> From<(S, S, S)> for TestCase {
|
||||
fn from((input, keys, output): (S, S, S)) -> Self {
|
||||
let (in_text, in_selection) = test::print(&input.into());
|
||||
let (out_text, out_selection) = test::print(&output.into());
|
||||
|
||||
TestCase {
|
||||
in_text,
|
||||
in_selection,
|
||||
in_keys: keys.into(),
|
||||
out_text,
|
||||
out_selection,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn test_key_sequence(
|
||||
app: &mut Application,
|
||||
in_keys: Option<&str>,
|
||||
test_fn: Option<&dyn Fn(&Application)>,
|
||||
should_exit: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
test_key_sequences(app, vec![(in_keys, test_fn)], should_exit).await
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub async fn test_key_sequences(
|
||||
app: &mut Application,
|
||||
inputs: Vec<(Option<&str>, Option<&dyn Fn(&Application)>)>,
|
||||
should_exit: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
const TIMEOUT: Duration = Duration::from_millis(500);
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let mut rx_stream = UnboundedReceiverStream::new(rx);
|
||||
let num_inputs = inputs.len();
|
||||
|
||||
for (i, (in_keys, test_fn)) in inputs.into_iter().enumerate() {
|
||||
if let Some(in_keys) = in_keys {
|
||||
for key_event in parse_macro(in_keys)?.into_iter() {
|
||||
tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?;
|
||||
}
|
||||
}
|
||||
|
||||
let app_exited = !app.event_loop_until_idle(&mut rx_stream).await;
|
||||
|
||||
// the app should not exit from any test until the last one
|
||||
if i < num_inputs - 1 && app_exited {
|
||||
bail!("application exited before test function could run");
|
||||
}
|
||||
|
||||
// verify if it exited on the last iteration if it should have and
|
||||
// the inverse
|
||||
if i == num_inputs - 1 && app_exited != should_exit {
|
||||
bail!("expected app to exit: {} != {}", app_exited, should_exit);
|
||||
}
|
||||
|
||||
if let Some(test) = test_fn {
|
||||
test(app);
|
||||
};
|
||||
}
|
||||
|
||||
if !should_exit {
|
||||
for key_event in parse_macro("<esc>:q!<ret>")?.into_iter() {
|
||||
tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?;
|
||||
}
|
||||
|
||||
let event_loop = app.event_loop(&mut rx_stream);
|
||||
tokio::time::timeout(TIMEOUT, event_loop).await?;
|
||||
}
|
||||
|
||||
app.close().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
|
||||
app: Option<Application>,
|
||||
test_case: T,
|
||||
test_fn: &dyn Fn(&Application),
|
||||
should_exit: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let test_case = test_case.into();
|
||||
let mut app = match app {
|
||||
Some(app) => app,
|
||||
None => Application::new(Args::default(), Config::default())?,
|
||||
};
|
||||
|
||||
let (view, doc) = helix_view::current!(app.editor);
|
||||
let sel = doc.selection(view.id).clone();
|
||||
|
||||
// replace the initial text with the input text
|
||||
doc.apply(
|
||||
&Transaction::change_by_selection(doc.text(), &sel, |_| {
|
||||
(0, doc.text().len_chars(), Some((&test_case.in_text).into()))
|
||||
})
|
||||
.with_selection(test_case.in_selection.clone()),
|
||||
view.id,
|
||||
);
|
||||
|
||||
test_key_sequence(
|
||||
&mut app,
|
||||
Some(&test_case.in_keys),
|
||||
Some(test_fn),
|
||||
should_exit,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Use this for very simple test cases where there is one input
|
||||
/// document, selection, and sequence of key presses, and you just
|
||||
/// want to verify the resulting document and selection.
|
||||
pub async fn test_with_config<T: Into<TestCase>>(
|
||||
args: Args,
|
||||
config: Config,
|
||||
test_case: T,
|
||||
) -> anyhow::Result<()> {
|
||||
let test_case = test_case.into();
|
||||
let app = Application::new(args, config)?;
|
||||
|
||||
test_key_sequence_with_input_text(
|
||||
Some(app),
|
||||
test_case.clone(),
|
||||
&|app| {
|
||||
let doc = doc!(app.editor);
|
||||
assert_eq!(&test_case.out_text, doc.text());
|
||||
|
||||
let mut selections: Vec<_> = doc.selections().values().cloned().collect();
|
||||
assert_eq!(1, selections.len());
|
||||
|
||||
let sel = selections.pop().unwrap();
|
||||
assert_eq!(test_case.out_selection, sel);
|
||||
},
|
||||
false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn test<T: Into<TestCase>>(test_case: T) -> anyhow::Result<()> {
|
||||
test_with_config(Args::default(), Config::default(), test_case).await
|
||||
}
|
||||
|
||||
pub fn temp_file_with_contents<S: AsRef<str>>(
|
||||
content: S,
|
||||
) -> anyhow::Result<tempfile::NamedTempFile> {
|
||||
let mut temp_file = tempfile::NamedTempFile::new()?;
|
||||
|
||||
temp_file
|
||||
.as_file_mut()
|
||||
.write_all(content.as_ref().as_bytes())?;
|
||||
|
||||
temp_file.flush()?;
|
||||
temp_file.as_file_mut().sync_all()?;
|
||||
Ok(temp_file)
|
||||
}
|
||||
|
||||
/// Replaces all LF chars with the system's appropriate line feed
|
||||
/// character, and if one doesn't exist already, appends the system's
|
||||
/// appropriate line ending to the end of a string.
|
||||
pub fn platform_line(input: &str) -> String {
|
||||
let line_end = helix_core::DEFAULT_LINE_ENDING.as_str();
|
||||
|
||||
// we can assume that the source files in this code base will always
|
||||
// be LF, so indoc strings will always insert LF
|
||||
let mut output = input.replace('\n', line_end);
|
||||
|
||||
if !output.ends_with(line_end) {
|
||||
output.push_str(line_end);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Creates a new temporary file that is set to read only. Useful for
|
||||
/// testing write failures.
|
||||
pub fn new_readonly_tempfile() -> anyhow::Result<NamedTempFile> {
|
||||
let mut file = tempfile::NamedTempFile::new()?;
|
||||
let metadata = file.as_file().metadata()?;
|
||||
let mut perms = metadata.permissions();
|
||||
perms.set_readonly(true);
|
||||
file.as_file_mut().set_permissions(perms)?;
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
/// Creates a new Application with default config that opens the given file
|
||||
/// path
|
||||
pub fn app_with_file<P: Into<PathBuf>>(path: P) -> anyhow::Result<Application> {
|
||||
Application::new(
|
||||
Args {
|
||||
files: vec![(path.into(), helix_core::Position::default())],
|
||||
..Default::default()
|
||||
},
|
||||
Config::default(),
|
||||
)
|
||||
}
|
87
helix-term/tests/test/movement.rs
Normal file
87
helix-term/tests/test/movement.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn insert_mode_cursor_position() -> anyhow::Result<()> {
|
||||
test(TestCase {
|
||||
in_text: String::new(),
|
||||
in_selection: Selection::single(0, 0),
|
||||
in_keys: "i".into(),
|
||||
out_text: String::new(),
|
||||
out_selection: Selection::single(0, 0),
|
||||
})
|
||||
.await?;
|
||||
|
||||
test(("#[\n|]#", "i", "#[|\n]#")).await?;
|
||||
test(("#[\n|]#", "i<esc>", "#[|\n]#")).await?;
|
||||
test(("#[\n|]#", "i<esc>i", "#[|\n]#")).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Range direction is preserved when escaping insert mode to normal
|
||||
#[tokio::test]
|
||||
async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
|
||||
test(("#[f|]#oo\n", "vll<A-;><esc>", "#[|foo]#\n")).await?;
|
||||
test((
|
||||
indoc! {"\
|
||||
#[f|]#oo
|
||||
#(b|)#ar"
|
||||
},
|
||||
"vll<A-;><esc>",
|
||||
indoc! {"\
|
||||
#[|foo]#
|
||||
#(|bar)#"
|
||||
},
|
||||
))
|
||||
.await?;
|
||||
|
||||
test((
|
||||
indoc! {"\
|
||||
#[f|]#oo
|
||||
#(b|)#ar"
|
||||
},
|
||||
"a",
|
||||
indoc! {"\
|
||||
#[fo|]#o
|
||||
#(ba|)#r"
|
||||
},
|
||||
))
|
||||
.await?;
|
||||
|
||||
test((
|
||||
indoc! {"\
|
||||
#[f|]#oo
|
||||
#(b|)#ar"
|
||||
},
|
||||
"a<esc>",
|
||||
indoc! {"\
|
||||
#[f|]#oo
|
||||
#(b|)#ar"
|
||||
},
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure the very initial cursor in an opened file is the width of
|
||||
/// the first grapheme
|
||||
#[tokio::test]
|
||||
async fn cursor_position_newly_opened_file() -> anyhow::Result<()> {
|
||||
let test = |content: &str, expected_sel: Selection| -> anyhow::Result<()> {
|
||||
let file = helpers::temp_file_with_contents(content)?;
|
||||
let mut app = helpers::app_with_file(file.path())?;
|
||||
|
||||
let (view, doc) = helix_view::current!(app.editor);
|
||||
let sel = doc.selection(view.id).clone();
|
||||
assert_eq!(expected_sel, sel);
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
test("foo", Selection::single(0, 1))?;
|
||||
test("👨👩👧👦 foo", Selection::single(0, 7))?;
|
||||
test("", Selection::single(0, 0))?;
|
||||
|
||||
Ok(())
|
||||
}
|
169
helix-term/tests/test/write.rs
Normal file
169
helix-term/tests/test/write.rs
Normal file
|
@ -0,0 +1,169 @@
|
|||
use std::{
|
||||
io::{Read, Write},
|
||||
ops::RangeInclusive,
|
||||
};
|
||||
|
||||
use helix_core::diagnostic::Severity;
|
||||
use helix_term::application::Application;
|
||||
use helix_view::doc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_write() -> anyhow::Result<()> {
|
||||
let mut file = tempfile::NamedTempFile::new()?;
|
||||
|
||||
test_key_sequence(
|
||||
&mut helpers::app_with_file(file.path())?,
|
||||
Some("ithe gostak distims the doshes<ret><esc>:w<ret>"),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
file.as_file_mut().flush()?;
|
||||
file.as_file_mut().sync_all()?;
|
||||
|
||||
let mut file_content = String::new();
|
||||
file.as_file_mut().read_to_string(&mut file_content)?;
|
||||
|
||||
assert_eq!(
|
||||
helpers::platform_line("the gostak distims the doshes"),
|
||||
file_content
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_write_quit() -> anyhow::Result<()> {
|
||||
let mut file = tempfile::NamedTempFile::new()?;
|
||||
|
||||
test_key_sequence(
|
||||
&mut helpers::app_with_file(file.path())?,
|
||||
Some("ithe gostak distims the doshes<ret><esc>:wq<ret>"),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
file.as_file_mut().flush()?;
|
||||
file.as_file_mut().sync_all()?;
|
||||
|
||||
let mut file_content = String::new();
|
||||
file.as_file_mut().read_to_string(&mut file_content)?;
|
||||
|
||||
assert_eq!(
|
||||
helpers::platform_line("the gostak distims the doshes"),
|
||||
file_content
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_write_concurrent() -> anyhow::Result<()> {
|
||||
let mut file = tempfile::NamedTempFile::new()?;
|
||||
let mut command = String::new();
|
||||
const RANGE: RangeInclusive<i32> = 1..=5000;
|
||||
|
||||
for i in RANGE {
|
||||
let cmd = format!("%c{}<esc>:w<ret>", i);
|
||||
command.push_str(&cmd);
|
||||
}
|
||||
|
||||
test_key_sequence(
|
||||
&mut helpers::app_with_file(file.path())?,
|
||||
Some(&command),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
file.as_file_mut().flush()?;
|
||||
file.as_file_mut().sync_all()?;
|
||||
|
||||
let mut file_content = String::new();
|
||||
file.as_file_mut().read_to_string(&mut file_content)?;
|
||||
assert_eq!(RANGE.end().to_string(), file_content);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_write_fail_mod_flag() -> anyhow::Result<()> {
|
||||
let file = helpers::new_readonly_tempfile()?;
|
||||
|
||||
test_key_sequences(
|
||||
&mut helpers::app_with_file(file.path())?,
|
||||
vec![
|
||||
(
|
||||
None,
|
||||
Some(&|app| {
|
||||
let doc = doc!(app.editor);
|
||||
assert!(!doc.is_modified());
|
||||
}),
|
||||
),
|
||||
(
|
||||
Some("ihello<esc>"),
|
||||
Some(&|app| {
|
||||
let doc = doc!(app.editor);
|
||||
assert!(doc.is_modified());
|
||||
}),
|
||||
),
|
||||
(
|
||||
Some(":w<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1);
|
||||
|
||||
let doc = doc!(app.editor);
|
||||
assert!(doc.is_modified());
|
||||
}),
|
||||
),
|
||||
],
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_write_fail_new_path() -> anyhow::Result<()> {
|
||||
let file = helpers::new_readonly_tempfile()?;
|
||||
|
||||
test_key_sequences(
|
||||
&mut Application::new(Args::default(), Config::default())?,
|
||||
vec![
|
||||
(
|
||||
None,
|
||||
Some(&|app| {
|
||||
let doc = doc!(app.editor);
|
||||
assert_ne!(
|
||||
Some(&Severity::Error),
|
||||
app.editor.get_status().map(|status| status.1)
|
||||
);
|
||||
assert_eq!(None, doc.path());
|
||||
}),
|
||||
),
|
||||
(
|
||||
Some(&format!(":w {}<ret>", file.path().to_string_lossy())),
|
||||
Some(&|app| {
|
||||
let doc = doc!(app.editor);
|
||||
assert_eq!(
|
||||
Some(&Severity::Error),
|
||||
app.editor.get_status().map(|status| status.1)
|
||||
);
|
||||
assert_eq!(None, doc.path());
|
||||
}),
|
||||
),
|
||||
],
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use anyhow::{anyhow, bail, Context, Error};
|
||||
use helix_core::auto_pairs::AutoPairs;
|
||||
use helix_core::Range;
|
||||
use serde::de::{self, Deserialize, Deserializer};
|
||||
use serde::Serialize;
|
||||
use std::cell::Cell;
|
||||
|
@ -83,7 +84,7 @@ impl Serialize for Mode {
|
|||
pub struct Document {
|
||||
pub(crate) id: DocumentId,
|
||||
text: Rope,
|
||||
pub(crate) selections: HashMap<ViewId, Selection>,
|
||||
selections: HashMap<ViewId, Selection>,
|
||||
|
||||
path: Option<PathBuf>,
|
||||
encoding: &'static encoding::Encoding,
|
||||
|
@ -637,6 +638,37 @@ impl Document {
|
|||
.insert(view_id, selection.ensure_invariants(self.text().slice(..)));
|
||||
}
|
||||
|
||||
/// Find the origin selection of the text in a document, i.e. where
|
||||
/// a single cursor would go if it were on the first grapheme. If
|
||||
/// the text is empty, returns (0, 0).
|
||||
pub fn origin(&self) -> Range {
|
||||
if self.text().len_chars() == 0 {
|
||||
return Range::new(0, 0);
|
||||
}
|
||||
|
||||
Range::new(0, 1).grapheme_aligned(self.text().slice(..))
|
||||
}
|
||||
|
||||
/// Reset the view's selection on this document to the
|
||||
/// [origin](Document::origin) cursor.
|
||||
pub fn reset_selection(&mut self, view_id: ViewId) {
|
||||
let origin = self.origin();
|
||||
self.set_selection(view_id, Selection::single(origin.anchor, origin.head));
|
||||
}
|
||||
|
||||
/// Initializes a new selection for the given view if it does not
|
||||
/// already have one.
|
||||
pub fn ensure_view_init(&mut self, view_id: ViewId) {
|
||||
if self.selections.get(&view_id).is_none() {
|
||||
self.reset_selection(view_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a view's selection from this document.
|
||||
pub fn remove_view(&mut self, view_id: ViewId) {
|
||||
self.selections.remove(&view_id);
|
||||
}
|
||||
|
||||
/// Apply a [`Transaction`] to the [`Document`] to change its text.
|
||||
fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
|
||||
let old_doc = self.text().clone();
|
||||
|
|
|
@ -32,12 +32,12 @@ use anyhow::{bail, Error};
|
|||
|
||||
pub use helix_core::diagnostic::Severity;
|
||||
pub use helix_core::register::Registers;
|
||||
use helix_core::Position;
|
||||
use helix_core::{
|
||||
auto_pairs::AutoPairs,
|
||||
syntax::{self, AutoPairConfig},
|
||||
Change,
|
||||
};
|
||||
use helix_core::{Position, Selection};
|
||||
use helix_dap as dap;
|
||||
|
||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
@ -590,6 +590,20 @@ impl Editor {
|
|||
self.status_msg = Some((error.into(), Severity::Error));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_status(&self) -> Option<(&Cow<'static, str>, &Severity)> {
|
||||
self.status_msg.as_ref().map(|(status, sev)| (status, sev))
|
||||
}
|
||||
|
||||
/// Returns true if the current status is an error
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> bool {
|
||||
self.status_msg
|
||||
.as_ref()
|
||||
.map(|(_, sev)| *sev == Severity::Error)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn set_theme(&mut self, theme: Theme) {
|
||||
// `ui.selection` is the only scope required to be able to render a theme.
|
||||
if theme.find_scope_index("ui.selection").is_none() {
|
||||
|
@ -664,11 +678,8 @@ impl Editor {
|
|||
view.offset = Position::default();
|
||||
|
||||
let doc = self.documents.get_mut(&doc_id).unwrap();
|
||||
doc.ensure_view_init(view.id);
|
||||
|
||||
// initialize selection for view
|
||||
doc.selections
|
||||
.entry(view.id)
|
||||
.or_insert_with(|| Selection::point(0));
|
||||
// TODO: reuse align_view
|
||||
let pos = doc
|
||||
.selection(view.id)
|
||||
|
@ -738,9 +749,7 @@ impl Editor {
|
|||
Action::Load => {
|
||||
let view_id = view!(self).id;
|
||||
let doc = self.documents.get_mut(&id).unwrap();
|
||||
if doc.selections().is_empty() {
|
||||
doc.set_selection(view_id, Selection::point(0));
|
||||
}
|
||||
doc.ensure_view_init(view_id);
|
||||
return;
|
||||
}
|
||||
Action::HorizontalSplit | Action::VerticalSplit => {
|
||||
|
@ -755,7 +764,7 @@ impl Editor {
|
|||
);
|
||||
// initialize selection for view
|
||||
let doc = self.documents.get_mut(&id).unwrap();
|
||||
doc.set_selection(view_id, Selection::point(0));
|
||||
doc.ensure_view_init(view_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -788,8 +797,9 @@ impl Editor {
|
|||
Ok(self.new_file_from_document(action, Document::from(rope, Some(encoding))))
|
||||
}
|
||||
|
||||
pub fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Error> {
|
||||
let path = helix_core::path::get_canonicalized_path(&path)?;
|
||||
// ??? possible use for integration tests
|
||||
pub fn open(&mut self, path: &Path, action: Action) -> Result<DocumentId, Error> {
|
||||
let path = helix_core::path::get_canonicalized_path(path)?;
|
||||
let id = self.document_by_path(&path).map(|doc| doc.id);
|
||||
|
||||
let id = if let Some(id) = id {
|
||||
|
@ -809,12 +819,7 @@ impl Editor {
|
|||
pub fn close(&mut self, id: ViewId) {
|
||||
let view = self.tree.get(self.tree.focus);
|
||||
// remove selection
|
||||
self.documents
|
||||
.get_mut(&view.doc)
|
||||
.unwrap()
|
||||
.selections
|
||||
.remove(&id);
|
||||
|
||||
self.documents.get_mut(&view.doc).unwrap().remove_view(id);
|
||||
self.tree.remove(id);
|
||||
self._refresh();
|
||||
}
|
||||
|
@ -889,7 +894,7 @@ impl Editor {
|
|||
let view = View::new(doc_id, self.config().gutters.clone());
|
||||
let view_id = self.tree.insert(view);
|
||||
let doc = self.documents.get_mut(&doc_id).unwrap();
|
||||
doc.set_selection(view_id, Selection::point(0));
|
||||
doc.ensure_view_init(view_id);
|
||||
}
|
||||
|
||||
self._refresh();
|
||||
|
|
|
@ -62,7 +62,7 @@ pub fn jump_to_stack_frame(editor: &mut Editor, frame: &helix_dap::StackFrame) {
|
|||
return;
|
||||
};
|
||||
|
||||
if let Err(e) = editor.open(path, Action::Replace) {
|
||||
if let Err(e) = editor.open(&path, Action::Replace) {
|
||||
editor.set_error(format!("Unable to jump to stack frame: {}", e));
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue