diff options
author | Norbert Preining <norbert@preining.info> | 2022-07-04 03:03:43 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2022-07-04 03:03:43 +0000 |
commit | 1c10375ec46d7d83b2f1efc2a71b7ea114c889f0 (patch) | |
tree | 47b3469111105b7767111dcb89858fbc1e73377f /support/texlab | |
parent | 34d318af65decbdb242ae03b64bf3f53266067b6 (diff) |
CTAN sync 202207040303
Diffstat (limited to 'support/texlab')
30 files changed, 1156 insertions, 561 deletions
diff --git a/support/texlab/CHANGELOG.md b/support/texlab/CHANGELOG.md index 5400a1b538..85f8d0bdb3 100644 --- a/support/texlab/CHANGELOG.md +++ b/support/texlab/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.2.0] - 03.07.2022 + +### Added + +- Add support for escaping placeholders in forward search ([#649](https://github.com/latex-lsp/texlab/issues/649)) +- Add support for diagnostic filtering ([#323](https://github.com/latex-lsp/texlab/issues/323)) +- Add pre-built binaries for the following targets: + - `aarch64-unknown-linux-gnu` + - `armv7-unknown-linux-gnueabihf` + - `x86_64-unknown-linux-musl` + - `aarch64-pc-windows-msvc` + - `i686-pc-windows-msvc` + +### Fixed + +- Parse incomplete server options correctly ([#651](https://github.com/latex-lsp/texlab/issues/651)) + ## [4.1.0] - 12.06.2022 ### Added diff --git a/support/texlab/Cargo.lock b/support/texlab/Cargo.lock index 8b5e64f3d7..cd1348e8c7 100644 --- a/support/texlab/Cargo.lock +++ b/support/texlab/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "assert_unordered" @@ -60,9 +60,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "beef" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" [[package]] name = "bitflags" @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "byte-tools" @@ -159,23 +159,23 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.18" +version = "3.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" dependencies = [ "bitflags", "clap_derive", "clap_lex", "indexmap", - "lazy_static", + "once_cell", "textwrap 0.15.0", ] [[package]] name = "clap_derive" -version = "3.1.18" +version = "3.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" +checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" dependencies = [ "heck", "proc-macro-error", @@ -186,9 +186,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] @@ -265,9 +265,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -286,26 +286,26 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "lazy_static", "memoffset", + "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" dependencies = [ "cfg-if", - "lazy_static", + "once_cell", ] [[package]] @@ -365,9 +365,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "encode_unicode" @@ -469,9 +469,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", @@ -486,12 +486,6 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "hashbrown" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" @@ -542,19 +536,19 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.7.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown", ] [[package]] name = "insta" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc3e639bcba360d9237acabd22014c16f3df772db463b7446cd81b070714767" +checksum = "4126dd76ebfe2561486a1bd6738a33d2029ffb068a99ac446b7f8c77b2e58dbc" dependencies = [ "console", "once_cell", @@ -614,9 +608,9 @@ checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae" [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" dependencies = [ "wasm-bindgen", ] @@ -635,9 +629,9 @@ checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "lock_api" @@ -735,23 +729,14 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", ] [[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -dependencies = [ - "serde", -] - -[[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -800,15 +785,15 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "os_str_bytes" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d8d0b2f198229de29dca79676f2738ff952edf3fde542eb8bf94d8c21b435" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", @@ -996,18 +981,18 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] @@ -1114,7 +1099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce1f383129e417a6265b16ed78e6e9307748f0863b2ba75f78ff14717db5b017" dependencies = [ "countme", - "hashbrown 0.12.1", + "hashbrown", "memoffset", "rustc-hash", "text-size", @@ -1137,9 +1122,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" [[package]] name = "ryu" @@ -1164,15 +1149,15 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" +checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" dependencies = [ "serde_derive", ] @@ -1189,9 +1174,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" dependencies = [ "proc-macro2", "quote", @@ -1200,9 +1185,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ "itoa 1.0.2", "ryu", @@ -1210,6 +1195,16 @@ dependencies = [ ] [[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + +[[package]] name = "serde_repr" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1267,9 +1262,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smol_str" @@ -1282,18 +1277,18 @@ dependencies = [ [[package]] name = "strum" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" dependencies = [ "heck", "proc-macro2", @@ -1304,9 +1299,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", @@ -1339,13 +1334,13 @@ dependencies = [ [[package]] name = "texlab" -version = "4.1.0" +version = "4.2.0" dependencies = [ "anyhow", "assert_unordered", "byteorder", "chrono", - "clap 3.1.18", + "clap 3.2.8", "criterion", "crossbeam-channel", "dashmap", @@ -1364,7 +1359,6 @@ dependencies = [ "logos", "lsp-server", "lsp-types", - "multimap", "once_cell", "petgraph", "regex", @@ -1372,6 +1366,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", + "serde_regex", "serde_repr", "smol_str", "strum", @@ -1469,9 +1464,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "titlecase" -version = "1.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f565e410cfc24c2f2a89960b023ca192689d7f77d3f8d4f4af50c2d8affe1117" +checksum = "ea7a2f1a76e892e6c07c275e6e851400257304a6e34c0e4e32ebbd3871808802" dependencies = [ "lazy_static", "regex", @@ -1497,15 +1492,15 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] @@ -1569,15 +1564,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1585,9 +1580,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" dependencies = [ "bumpalo", "lazy_static", @@ -1600,9 +1595,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1610,9 +1605,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", @@ -1623,15 +1618,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/support/texlab/Cargo.toml b/support/texlab/Cargo.toml index f85c0f31ed..803f280e39 100644 --- a/support/texlab/Cargo.toml +++ b/support/texlab/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "texlab" description = "LaTeX Language Server" -version = "4.1.0" +version = "4.2.0" license = "GPL-3.0" readme = "README.md" authors = [ - "Eric Förster <eric.foerster@outlook.com>", - "Patrick Förster <patrick.foerster@outlook.de>", + "Eric Förster <eric.foerster@outlook.com>", + "Patrick Förster <patrick.foerster@outlook.de>", ] edition = "2021" rust-version = "1.58.1" @@ -16,14 +16,14 @@ documentation = "https://github.com/latex-lsp/texlab" keywords = ["lsp", "server", "latex", "bibtex"] categories = ["development-tools"] exclude = [ - ".gitattributes", - ".gitignore", - ".github/**", - "tests/it/**", - "*.snap", - "texlab.1", - "texlab.pdf", - "texlab.tex", + ".gitattributes", + ".gitignore", + ".github/**", + "tests/it/**", + "*.snap", + "texlab.1", + "texlab.pdf", + "texlab.tex", ] [[bin]] @@ -38,14 +38,14 @@ default = ["completion"] completion = ["fuzzy-matcher"] [dependencies] -anyhow = "1.0.57" +anyhow = "1.0.58" byteorder = "1.4.3" chrono = { version = "0.4.19", default-features = false, features = ["std"] } -clap = { version = "3.1.17", features = [ - "std", - "derive", +clap = { version = "3.2.8", features = [ + "std", + "derive", ], default-features = false } -crossbeam-channel = "0.5.4" +crossbeam-channel = "0.5.5" dashmap = "~5.2.0" derive_more = "0.99.17" encoding_rs = "0.8.31" @@ -60,28 +60,28 @@ log = "0.4.17" logos = "0.12.1" lsp-server = "0.6.0" lsp-types = "0.93.0" -multimap = "0.8.3" once_cell = "1.10.0" petgraph = "0.6.2" regex = "1.5.5" rowan = "0.15.5" rustc-hash = "1.1.0" serde = "1.0.137" -serde_json = "1.0.81" +serde_json = "1.0.82" +serde_regex = "1.1.0" serde_repr = "0.1.8" smol_str = { version = "0.1.23", features = ["serde"] } strum = { version = "0.24.0", features = ["derive"] } tempfile = "3.3.0" threadpool = "1.8.1" -titlecase = "1.1.0" -unicode-normalization = "0.1.19" +titlecase = "2.0.0" +unicode-normalization = "0.1.20" url = { version = "2.2.2", features = ["serde"] } uuid = { version = "1.1.2", features = ["v4"] } [dev-dependencies] assert_unordered = "0.3.5" criterion = { version = "0.3.5" } -insta = { version = "1.14.1", features = ["backtrace", "redactions"] } +insta = { version = "1.15.0", features = ["backtrace", "redactions"] } jod-thread = "0.1.2" [profile.release] diff --git a/support/texlab/docs/options.md b/support/texlab/docs/options.md index e1a69c4996..9b3599cce1 100644 --- a/support/texlab/docs/options.md +++ b/support/texlab/docs/options.md @@ -77,6 +77,8 @@ In this case, use the `-outdir` flag for `latexmk`. **Default value:** `.` (the same directory as the TeX file) +--- + ## texlab.forwardSearch.executable Defines the executable of the PDF previewer. @@ -135,6 +137,38 @@ Delay in milliseconds before reporting diagnostics. --- +## texlab.diagnostics.allowedPatterns + +A list of regular expressions used to filter the list of reported diagnostics. +If specified, only diagnostics that match _at least one_ of the specified patterns +are sent to the client. + +See also [`texlab.diagnostics.ignoredPatterns`](#texlabdiagnosticsignoredpatterns). + +_Hint_: +If both `allowedPatterns` and `ignoredPatterns` are set, +then allowed patterns are applied first. Afterwards, the results are filtered with the ignored patterns. + +**Type:** `RegExp[]` + +**Default value:** `[]` + +--- + +## texlab.diagnostics.ignoredPatterns + +A list of regular expressions used to filter the list of reported diagnostics. +If specified, only diagnostics that match _none_ of the specified patterns +are sent to the client. + +See also [`texlab.diagnostics.allowedPatterns`](#texlabdiagnosticsallowedpatterns). + +**Type:** `RegExp[]` + +**Default value:** `[]` + +--- + ## texlab.formatterLineLength Defines the maximum amount of characters per line (0 = disable) when formatting BibTeX files. diff --git a/support/texlab/docs/previewing.md b/support/texlab/docs/previewing.md index 6c667e0fd0..3e97390429 100644 --- a/support/texlab/docs/previewing.md +++ b/support/texlab/docs/previewing.md @@ -97,7 +97,7 @@ Add the following lines to your editor config: ```json { "texlab.forwardSearch.executable": "okular", - "texlab.forwardSearch.args": ["--unique", "file:%p#src:%l%f"] + "texlab.forwardSearch.args": ["--editor-cmd": "code -g \"%%f\":%%l", "--unique", "file:%p#src:%l%f"] } ``` diff --git a/support/texlab/docs/tectonic.md b/support/texlab/docs/tectonic.md index e75fd4a3ee..b08e64b946 100644 --- a/support/texlab/docs/tectonic.md +++ b/support/texlab/docs/tectonic.md @@ -24,7 +24,7 @@ Without the `--keep-logs` flag, `texlab` won't be able to report compilation war "texlab.build.executable": "tectonic", "texlab.build.args": [ "-X", - "compile" + "compile", "%f", "--synctex", "--keep-logs", diff --git a/support/texlab/src/debouncer.rs b/support/texlab/src/debouncer.rs new file mode 100644 index 0000000000..b123dfc433 --- /dev/null +++ b/support/texlab/src/debouncer.rs @@ -0,0 +1,55 @@ +use std::time::{Duration, Instant}; + +use anyhow::Result; + +pub struct Sender<T> { + tx: crossbeam_channel::Sender<(T, crossbeam_channel::Receiver<Instant>)>, +} + +impl<T> Clone for Sender<T> { + fn clone(&self) -> Self { + Self { + tx: self.tx.clone(), + } + } +} + +impl<T> Sender<T> +where + T: Send + Sync + 'static, +{ + pub fn send(&self, msg: T, delay: Duration) -> Result<()> { + self.tx.send((msg, crossbeam_channel::after(delay)))?; + Ok(()) + } +} + +pub struct Receiver<T> { + rx: crossbeam_channel::Receiver<(T, crossbeam_channel::Receiver<Instant>)>, +} + +impl<T> Clone for Receiver<T> { + fn clone(&self) -> Self { + Self { + rx: self.rx.clone(), + } + } +} + +impl<T> Receiver<T> { + pub fn recv(&self) -> Result<T> { + let (mut last_msg, delay) = self.rx.recv()?; + delay.recv()?; + while let Ok((msg, delay)) = self.rx.try_recv() { + delay.recv()?; + last_msg = msg; + } + + Ok(last_msg) + } +} + +pub fn unbounded<T>() -> (Sender<T>, Receiver<T>) { + let (tx, rx) = crossbeam_channel::unbounded(); + (Sender { tx }, Receiver { rx }) +} diff --git a/support/texlab/src/diagnostics.rs b/support/texlab/src/diagnostics.rs index 5ce177b015..bdd8faf106 100644 --- a/support/texlab/src/diagnostics.rs +++ b/support/texlab/src/diagnostics.rs @@ -1,56 +1,167 @@ mod bibtex; -mod build_log; +mod build; mod chktex; -mod debouncer; mod latex; use std::sync::Arc; -use lsp_types::{Diagnostic, Url}; -use multimap::MultiMap; -use rustc_hash::FxHashMap; +use dashmap::DashMap; +use lsp_types::{DiagnosticSeverity, NumberOrString, Range, Url}; +use regex::Regex; -use crate::{Options, Workspace}; - -pub use self::debouncer::{DiagnosticsDebouncer, DiagnosticsMessage}; +use crate::Workspace; use self::{ - bibtex::analyze_bibtex_static, build_log::analyze_build_log_static, - chktex::analyze_latex_chktex, latex::analyze_latex_static, + bibtex::collect_bibtex_diagnostics, build::collect_build_diagnostics, + chktex::collect_chktex_diagnostics, latex::collect_latex_diagnostics, }; -#[derive(Default)] -pub struct DiagnosticsManager { - static_diagnostics: FxHashMap<Arc<Url>, MultiMap<Arc<Url>, Diagnostic>>, - chktex_diagnostics: MultiMap<Arc<Url>, Diagnostic>, +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Diagnostic { + pub severity: DiagnosticSeverity, + pub range: Range, + pub code: DiagnosticCode, + pub message: String, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] +pub enum DiagnosticCode { + Latex(LatexCode), + Bibtex(BibtexCode), + Chktex(String), + Build(Arc<Url>), +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub enum LatexCode { + UnexpectedRCurly, + RCurlyInserted, + MismatchedEnvironment, +} + +impl From<LatexCode> for String { + fn from(code: LatexCode) -> Self { + match code { + LatexCode::UnexpectedRCurly => "Unexpected \"}\"".to_string(), + LatexCode::RCurlyInserted => "Missing \"}\" inserted".to_string(), + LatexCode::MismatchedEnvironment => "Mismatched environment".to_string(), + } + } } -impl DiagnosticsManager { - pub fn update_static(&mut self, workspace: &Workspace, uri: Arc<Url>) { - let mut diagnostics_by_uri = MultiMap::new(); - analyze_build_log_static(workspace, &mut diagnostics_by_uri, &uri); - analyze_bibtex_static(workspace, &mut diagnostics_by_uri, &uri); - analyze_latex_static(workspace, &mut diagnostics_by_uri, &uri); - self.static_diagnostics.insert(uri, diagnostics_by_uri); +impl From<LatexCode> for NumberOrString { + fn from(code: LatexCode) -> Self { + match code { + LatexCode::UnexpectedRCurly => NumberOrString::Number(1), + LatexCode::RCurlyInserted => NumberOrString::Number(2), + LatexCode::MismatchedEnvironment => NumberOrString::Number(3), + } } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +#[allow(clippy::enum_variant_names)] +pub enum BibtexCode { + ExpectingLCurly, + ExpectingKey, + ExpectingRCurly, + ExpectingEq, + ExpectingFieldValue, +} - pub fn update_chktex(&mut self, workspace: &Workspace, uri: &Url, options: &Options) { - analyze_latex_chktex(workspace, &mut self.chktex_diagnostics, uri, options); +impl From<BibtexCode> for String { + fn from(code: BibtexCode) -> Self { + match code { + BibtexCode::ExpectingLCurly => "Expecting a curly bracket: \"{\"".to_string(), + BibtexCode::ExpectingKey => "Expecting a key".to_string(), + BibtexCode::ExpectingRCurly => "Expecting a curly bracket: \"}\"".to_string(), + BibtexCode::ExpectingEq => "Expecting an equality sign: \"=\"".to_string(), + BibtexCode::ExpectingFieldValue => "Expecting a field value".to_string(), + } } +} - #[must_use] - pub fn publish(&self, uri: &Url) -> Vec<Diagnostic> { - let mut all_diagnostics = Vec::new(); - for diagnostics_by_uri in self.static_diagnostics.values() { - if let Some(diagnostics) = diagnostics_by_uri.get_vec(uri) { - all_diagnostics.append(&mut diagnostics.clone()); - } +impl From<BibtexCode> for NumberOrString { + fn from(code: BibtexCode) -> Self { + match code { + BibtexCode::ExpectingLCurly => NumberOrString::Number(4), + BibtexCode::ExpectingKey => NumberOrString::Number(5), + BibtexCode::ExpectingRCurly => NumberOrString::Number(6), + BibtexCode::ExpectingEq => NumberOrString::Number(7), + BibtexCode::ExpectingFieldValue => NumberOrString::Number(8), } + } +} + +#[derive(Default, Clone)] +pub struct DiagnosticManager { + all_diagnostics: Arc<DashMap<Arc<Url>, Vec<Diagnostic>>>, +} + +impl DiagnosticManager { + pub fn push_syntax(&self, workspace: &Workspace, uri: &Url) { + collect_bibtex_diagnostics(&self.all_diagnostics, workspace, uri) + .or_else(|| collect_latex_diagnostics(&self.all_diagnostics, workspace, uri)) + .or_else(|| collect_build_diagnostics(&self.all_diagnostics, workspace, uri)); + } + + pub fn push_chktex(&self, workspace: &Workspace, uri: &Url) { + collect_chktex_diagnostics(&self.all_diagnostics, workspace, uri); + } - if let Some(diagnostics) = self.chktex_diagnostics.get_vec(uri) { - all_diagnostics.append(&mut diagnostics.clone()); + pub fn publish(&self, workspace: &Workspace, uri: &Url) -> Vec<lsp_types::Diagnostic> { + let options = &workspace.environment.options.diagnostics; + + let mut results = Vec::new(); + if let Some(diagnostics) = self.all_diagnostics.get(uri) { + for diagnostic in diagnostics.iter() { + if !options.allowed_patterns.is_empty() + && !options + .allowed_patterns + .iter() + .any(|pattern| pattern.0.is_match(&diagnostic.message)) + { + continue; + } + + if options + .ignored_patterns + .iter() + .any(|pattern| pattern.0.is_match(&diagnostic.message)) + { + continue; + } + + let source = match diagnostic.code { + DiagnosticCode::Latex(_) | DiagnosticCode::Bibtex(_) => "texlab", + DiagnosticCode::Chktex(_) => "chktex", + DiagnosticCode::Build(_) => "latex-build", + }; + + let code = match diagnostic.code.clone() { + DiagnosticCode::Latex(code) => Some(code.into()), + DiagnosticCode::Bibtex(code) => Some(code.into()), + DiagnosticCode::Chktex(code) => Some(NumberOrString::String(code)), + DiagnosticCode::Build(_) => None, + }; + + results.push(lsp_types::Diagnostic { + range: diagnostic.range, + code, + severity: Some(diagnostic.severity), + message: diagnostic.message.clone(), + source: Some(source.to_string()), + ..Default::default() + }); + } } - all_diagnostics + results } } + +#[derive(Debug, Default)] +pub struct DiagnosticFilter { + pub allowed_patterns: Vec<Regex>, + pub ignored_patterns: Vec<Regex>, +} diff --git a/support/texlab/src/diagnostics/bibtex.rs b/support/texlab/src/diagnostics/bibtex.rs index 1beec52c5c..42e215a794 100644 --- a/support/texlab/src/diagnostics/bibtex.rs +++ b/support/texlab/src/diagnostics/bibtex.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Url}; -use multimap::MultiMap; +use dashmap::DashMap; +use lsp_types::{DiagnosticSeverity, Url}; use rowan::{ast::AstNode, TextRange}; use crate::{ @@ -9,85 +9,84 @@ use crate::{ Document, LineIndexExt, Workspace, }; -pub fn analyze_bibtex_static( +use super::{BibtexCode, Diagnostic, DiagnosticCode}; + +pub fn collect_bibtex_diagnostics( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, workspace: &Workspace, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, uri: &Url, ) -> Option<()> { let document = workspace.documents_by_uri.get(uri)?; let data = document.data.as_bibtex()?; - for node in bibtex::SyntaxNode::new_root(data.green.clone()).descendants() { - analyze_entry(document, diagnostics_by_uri, node.clone()) - .or_else(|| analyze_field(document, diagnostics_by_uri, node)); + all_diagnostics.alter(uri, |_, mut diagnostics| { + diagnostics.retain(|diag| !matches!(diag.code, DiagnosticCode::Bibtex(_))); + diagnostics + }); + + let root = bibtex::SyntaxNode::new_root(data.green.clone()); + for node in root.descendants() { + analyze_entry(all_diagnostics, document, node.clone()) + .or_else(|| analyze_field(all_diagnostics, document, node)); } Some(()) } fn analyze_entry( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, document: &Document, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, node: bibtex::SyntaxNode, ) -> Option<()> { let entry = bibtex::Entry::cast(node)?; if entry.left_delim_token().is_none() { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = BibtexCode::ExpectingLCurly; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index .line_col_lsp_range(entry.type_token()?.text_range()), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(4)), - code_description: None, - source: Some("texlab".to_string()), - message: "Expecting a curly bracket: \"{\"".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Bibtex(code), + message: String::from(code), + }); + return Some(()); } if entry.name_token().is_none() { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = BibtexCode::ExpectingKey; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index .line_col_lsp_range(entry.left_delim_token()?.text_range()), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(5)), - code_description: None, - source: Some("texlab".to_string()), - message: "Expecting a key".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Bibtex(code), + message: String::from(code), + }); + return Some(()); } if entry.right_delim_token().is_none() { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = BibtexCode::ExpectingRCurly; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index .line_col_lsp_range(TextRange::empty(entry.syntax().text_range().end())), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(6)), - code_description: None, - source: Some("texlab".to_string()), - message: "Expecting a curly bracket: \"}\"".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Bibtex(code), + message: String::from(code), + }); + return Some(()); } @@ -95,48 +94,44 @@ fn analyze_entry( } fn analyze_field( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, document: &Document, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, node: bibtex::SyntaxNode, ) -> Option<()> { let field = bibtex::Field::cast(node)?; if field.eq_token().is_none() { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = BibtexCode::ExpectingEq; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index - .line_col_lsp_range(TextRange::empty(field.name_token()?.text_range().end())), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(7)), - code_description: None, - source: Some("texlab".to_string()), - message: "Expecting an equality sign: \"=\"".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + .line_col_lsp_range(field.name_token()?.text_range()), + + code: DiagnosticCode::Bibtex(code), + message: String::from(code), + }); + return Some(()); } if field.value().is_none() { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = BibtexCode::ExpectingFieldValue; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index - .line_col_lsp_range(TextRange::empty(field.eq_token()?.text_range().end())), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(8)), - code_description: None, - source: Some("texlab".to_string()), - message: "Expecting a field value".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + .line_col_lsp_range(field.name_token()?.text_range()), + + code: DiagnosticCode::Bibtex(code), + message: String::from(code), + }); + return Some(()); } diff --git a/support/texlab/src/diagnostics/build_log.rs b/support/texlab/src/diagnostics/build.rs index e280d862bc..4b4ffcc8b4 100644 --- a/support/texlab/src/diagnostics/build_log.rs +++ b/support/texlab/src/diagnostics/build.rs @@ -1,20 +1,29 @@ use std::{path::PathBuf, sync::Arc}; -use lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url}; -use multimap::MultiMap; +use dashmap::DashMap; +use lsp_types::{DiagnosticSeverity, Position, Range, Url}; use crate::{syntax::build_log::BuildErrorLevel, Workspace}; -pub fn analyze_build_log_static( +use super::{Diagnostic, DiagnosticCode}; + +pub fn collect_build_diagnostics( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, workspace: &Workspace, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, build_log_uri: &Url, ) -> Option<()> { let build_log_document = workspace.documents_by_uri.get(build_log_uri)?; - let parse = build_log_document.data.as_build_log()?; + let build_log = build_log_document.data.as_build_log()?; + + all_diagnostics.alter_all(|_, mut diagnostics| { + diagnostics.retain( + |diag| !matches!(&diag.code, DiagnosticCode::Build(uri) if uri.as_ref() == build_log_uri), + ); + diagnostics + }); let root_document = workspace.documents_by_uri.values().find(|document| { - if let Some(data) = document.data.as_latex() { + document.data.as_latex().map_or(false, |data| { !document.uri.as_str().ends_with(".aux") && data .extras @@ -22,30 +31,22 @@ pub fn analyze_build_log_static( .log .iter() .any(|u| u.as_ref() == build_log_uri) - } else { - false - } + }) })?; let base_path = PathBuf::from(root_document.uri.path()); - - for error in &parse.errors { - let pos = Position::new(error.line.unwrap_or(0), 0); + for error in &build_log.errors { + let position = Position::new(error.line.unwrap_or(0), 0); let severity = match error.level { BuildErrorLevel::Error => DiagnosticSeverity::ERROR, BuildErrorLevel::Warning => DiagnosticSeverity::WARNING, }; - let range = Range::new(pos, pos); + let range = Range::new(position, position); let diagnostic = Diagnostic { + severity, range, - severity: Some(severity), - code: None, - code_description: None, - source: Some("latex".into()), + code: DiagnosticCode::Build(Arc::clone(&build_log_document.uri)), message: error.message.clone(), - related_information: None, - tags: None, - data: None, }; let full_path = base_path.join(&error.relative_path); @@ -60,7 +61,8 @@ pub fn analyze_build_log_static( Arc::clone(&root_document.uri) }; - diagnostics_by_uri.insert(uri, diagnostic); + all_diagnostics.entry(uri).or_default().push(diagnostic); } + Some(()) } diff --git a/support/texlab/src/diagnostics/chktex.rs b/support/texlab/src/diagnostics/chktex.rs index 6b6405d975..ad676bd70d 100644 --- a/support/texlab/src/diagnostics/chktex.rs +++ b/support/texlab/src/diagnostics/chktex.rs @@ -5,24 +5,32 @@ use std::{ sync::Arc, }; -use lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Range, Url}; -use multimap::MultiMap; +use dashmap::DashMap; +use lsp_types::{DiagnosticSeverity, Range, Url}; use once_cell::sync::Lazy; use regex::Regex; use tempfile::tempdir; -use crate::{Options, RangeExt, Workspace}; +use crate::{RangeExt, Workspace}; -pub fn analyze_latex_chktex( +use super::{Diagnostic, DiagnosticCode}; + +pub fn collect_chktex_diagnostics( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, workspace: &Workspace, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, uri: &Url, - options: &Options, ) -> Option<()> { let document = workspace.documents_by_uri.get(uri)?; document.data.as_latex()?; - let current_dir = options + all_diagnostics.alter(uri, |_, mut diagnostics| { + diagnostics.retain(|diag| !matches!(diag.code, DiagnosticCode::Chktex(_))); + diagnostics + }); + + let current_dir = workspace + .environment + .options .root_directory .as_ref() .cloned() @@ -40,15 +48,15 @@ pub fn analyze_latex_chktex( }) .unwrap_or_else(|| ".".into()); - diagnostics_by_uri.remove(uri); - diagnostics_by_uri.insert_many( - Arc::clone(&document.uri), - lint(&document.text, ¤t_dir).unwrap_or_default(), - ); + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .extend(lint(&document.text, ¤t_dir).unwrap_or_default()); + Some(()) } -pub static LINE_REGEX: Lazy<Regex> = +static LINE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("(\\d+):(\\d+):(\\d+):(\\w+):(\\w+):(.*)").unwrap()); fn lint(text: &str, current_dir: &Path) -> io::Result<Vec<Diagnostic>> { @@ -85,14 +93,9 @@ fn lint(text: &str, current_dir: &Path) -> io::Result<Vec<Diagnostic>> { diagnostics.push(Diagnostic { range, - severity: Some(severity), - code: Some(NumberOrString::String(code.into())), - code_description: None, - source: Some("chktex".into()), + severity, + code: DiagnosticCode::Chktex(code.into()), message, - related_information: None, - tags: None, - data: None, }); } diff --git a/support/texlab/src/diagnostics/debouncer.rs b/support/texlab/src/diagnostics/debouncer.rs deleted file mode 100644 index 4c83636856..0000000000 --- a/support/texlab/src/diagnostics/debouncer.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::{ - sync::Arc, - thread::{self, JoinHandle}, - time::{Duration, Instant}, -}; - -use crossbeam_channel::Sender; -use dashmap::DashMap; -use lsp_types::Url; - -use crate::{Document, Workspace}; - -pub enum DiagnosticsMessage { - Analyze { - workspace: Workspace, - document: Document, - }, - Shutdown, -} - -pub struct DiagnosticsDebouncer { - pub sender: Sender<DiagnosticsMessage>, - handle: Option<JoinHandle<()>>, -} - -impl DiagnosticsDebouncer { - pub fn launch<A>(action: A) -> Self - where - A: Fn(Workspace, Document) + Send + Clone + 'static, - { - let (sender, receiver) = crossbeam_channel::unbounded(); - - let handle = thread::spawn(move || { - let pool = threadpool::Builder::new().build(); - let last_task_time_by_uri: Arc<DashMap<Arc<Url>, Instant>> = Arc::default(); - while let Ok(DiagnosticsMessage::Analyze { - workspace, - document, - }) = receiver.recv() - { - let delay = workspace - .environment - .options - .diagnostics_delay - .unwrap_or(300); - - if let Some(time) = last_task_time_by_uri.get(&document.uri) { - if time.elapsed().as_millis() < u128::from(delay) { - continue; - } - } - - let last_task_time_by_uri = Arc::clone(&last_task_time_by_uri); - let action = action.clone(); - pool.execute(move || { - thread::sleep(Duration::from_millis(delay)); - last_task_time_by_uri.insert(Arc::clone(&document.uri), Instant::now()); - action(workspace, document); - }); - } - }); - - Self { - sender, - handle: Some(handle), - } - } -} - -impl Drop for DiagnosticsDebouncer { - fn drop(&mut self) { - self.sender.send(DiagnosticsMessage::Shutdown).unwrap(); - self.handle.take().unwrap().join().unwrap(); - } -} diff --git a/support/texlab/src/diagnostics/latex.rs b/support/texlab/src/diagnostics/latex.rs index 2c271ad54a..a17aab3f77 100644 --- a/support/texlab/src/diagnostics/latex.rs +++ b/support/texlab/src/diagnostics/latex.rs @@ -1,14 +1,16 @@ use std::sync::Arc; -use lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Url}; -use multimap::MultiMap; +use dashmap::DashMap; +use lsp_types::{DiagnosticSeverity, Url}; use rowan::{ast::AstNode, NodeOrToken, TextRange}; use crate::{syntax::latex, Document, LineIndexExt, Workspace}; -pub fn analyze_latex_static( +use super::{Diagnostic, DiagnosticCode, LatexCode}; + +pub fn collect_latex_diagnostics( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, workspace: &Workspace, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, uri: &Url, ) -> Option<()> { let document = workspace.documents_by_uri.get(uri)?; @@ -18,25 +20,27 @@ pub fn analyze_latex_static( let data = document.data.as_latex()?; + all_diagnostics.alter(uri, |_, mut diagnostics| { + diagnostics.retain(|diag| !matches!(diag.code, DiagnosticCode::Latex(_))); + diagnostics + }); + for node in latex::SyntaxNode::new_root(data.green.clone()).descendants() { - analyze_environment(document, diagnostics_by_uri, node.clone()) - .or_else(|| analyze_curly_group(document, diagnostics_by_uri, &node)) + analyze_environment(all_diagnostics, document, node.clone()) + .or_else(|| analyze_curly_group(all_diagnostics, document, &node)) .or_else(|| { if node.kind() == latex::ERROR && node.first_token()?.text() == "}" { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = LatexCode::UnexpectedRCurly; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document.line_index.line_col_lsp_range(node.text_range()), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(1)), - code_description: None, - source: Some("texlab".to_string()), - message: "Unexpected \"}\"".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Latex(code), + message: String::from(code), + }); + Some(()) } else { None @@ -48,37 +52,33 @@ pub fn analyze_latex_static( } fn analyze_environment( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, document: &Document, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, node: latex::SyntaxNode, ) -> Option<()> { let environment = latex::Environment::cast(node)?; let name1 = environment.begin()?.name()?.key()?; let name2 = environment.end()?.name()?.key()?; if name1 != name2 { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = LatexCode::MismatchedEnvironment; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index .line_col_lsp_range(latex::small_range(&name1)), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(3)), - code_description: None, - source: Some("texlab".to_string()), - message: "Mismatched environment".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Latex(code), + message: String::from(code), + }); } Some(()) } fn analyze_curly_group( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, document: &Document, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, node: &latex::SyntaxNode, ) -> Option<()> { if !matches!( @@ -108,22 +108,18 @@ fn analyze_curly_group( .filter_map(NodeOrToken::into_token) .any(|token| token.kind() == latex::R_CURLY) { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = LatexCode::RCurlyInserted; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index .line_col_lsp_range(TextRange::empty(node.text_range().end())), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(2)), - code_description: None, - source: Some("texlab".to_string()), - message: "Missing \"}\" inserted".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Latex(code), + message: String::from(code), + }); } Some(()) diff --git a/support/texlab/src/features/build.rs b/support/texlab/src/features/build.rs index ae465590fc..d961052061 100644 --- a/support/texlab/src/features/build.rs +++ b/support/texlab/src/features/build.rs @@ -164,12 +164,12 @@ impl BuildEngine { let args: Vec<_> = options .build - .args() - .into_iter() - .map(|arg| replace_placeholder(arg, &path)) + .args + .iter() + .map(|arg| replace_placeholder(arg.clone(), &path)) .collect(); - let mut process = Command::new(options.build.executable()) + let mut process = Command::new(&options.build.executable) .args(args) .stdin(Stdio::null()) .stdout(Stdio::piped()) diff --git a/support/texlab/src/features/forward_search.rs b/support/texlab/src/features/forward_search.rs index 521cc31a59..54629d0dc0 100644 --- a/support/texlab/src/features/forward_search.rs +++ b/support/texlab/src/features/forward_search.rs @@ -86,6 +86,73 @@ pub fn execute_forward_search( Some(ForwardSearchResult { status }) } +/// Iterate overs chunks of a string. Either returns a slice of the +/// original string, or the placeholder replacement. +pub struct PlaceHolderIterator<'a> { + remainder: &'a str, + tex_file: &'a str, + pdf_file: &'a str, + line_number: &'a str, +} + +impl<'a> PlaceHolderIterator<'a> { + pub fn new(s: &'a str, tex_file: &'a str, pdf_file: &'a str, line_number: &'a str) -> Self { + Self { + remainder: s, + tex_file, + pdf_file, + line_number, + } + } + + pub fn yield_remainder(&mut self) -> Option<&'a str> { + let chunk = self.remainder; + self.remainder = ""; + Some(chunk) + } + + pub fn yield_placeholder(&mut self) -> Option<&'a str> { + if self.remainder.len() >= 2 { + let placeholder = self.remainder; + self.remainder = &self.remainder[2..]; + match &placeholder[1..2] { + "f" => Some(self.tex_file), + "p" => Some(self.pdf_file), + "l" => Some(self.line_number), + "%" => Some("%"), // escape % + _ => Some(&placeholder[0..2]), + } + } else { + self.remainder = &self.remainder[1..]; + Some("%") + } + } + + pub fn yield_str(&mut self, end: usize) -> Option<&'a str> { + let chunk = &self.remainder[..end]; + self.remainder = &self.remainder[end..]; + Some(chunk) + } +} + +impl<'a> Iterator for PlaceHolderIterator<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option<Self::Item> { + return if self.remainder.is_empty() { + None + } else if self.remainder.starts_with('%') { + self.yield_placeholder() + } else { + // yield up to the next % or to the end + match self.remainder.find('%') { + None => self.yield_remainder(), + Some(end) => self.yield_str(end), + } + }; + } +} + fn replace_placeholder( tex_file: &Path, pdf_file: &Path, @@ -95,10 +162,9 @@ fn replace_placeholder( let result = if argument.starts_with('"') || argument.ends_with('"') { argument.to_string() } else { - argument - .replace("%f", tex_file.to_str()?) - .replace("%p", pdf_file.to_str()?) - .replace("%l", &(line_number + 1).to_string()) + let line = &(line_number + 1).to_string(); + let it = PlaceHolderIterator::new(argument, tex_file.to_str()?, pdf_file.to_str()?, line); + it.collect::<Vec<&str>>().join("") }; Some(result) } diff --git a/support/texlab/src/lib.rs b/support/texlab/src/lib.rs index ce52bee1da..1f3d22d7aa 100644 --- a/support/texlab/src/lib.rs +++ b/support/texlab/src/lib.rs @@ -2,7 +2,8 @@ mod capabilities; pub mod citation; mod client; pub mod component_db; -pub mod diagnostics; +mod debouncer; +mod diagnostics; mod dispatch; pub mod distro; mod document; diff --git a/support/texlab/src/main.rs b/support/texlab/src/main.rs index 71338f1bcd..06cbba3f5d 100644 --- a/support/texlab/src/main.rs +++ b/support/texlab/src/main.rs @@ -1,7 +1,7 @@ use std::{env, fs::OpenOptions, io, path::PathBuf}; use anyhow::Result; -use clap::Parser; +use clap::{ArgAction, Parser}; use log::LevelFilter; use lsp_server::Connection; use texlab::Server; @@ -11,7 +11,7 @@ use texlab::Server; #[clap(version)] struct Opts { /// Increase message verbosity (-vvvv for max verbosity) - #[clap(short, long, parse(from_occurrences))] + #[clap(short, long, action = ArgAction::Count)] verbosity: u8, /// No output printed to stderr @@ -19,7 +19,7 @@ struct Opts { quiet: bool, /// Write the logging output to FILE - #[clap(long, name = "FILE", parse(from_os_str))] + #[clap(long, name = "FILE", value_parser)] log_file: Option<PathBuf>, /// Print version information and exit diff --git a/support/texlab/src/options.rs b/support/texlab/src/options.rs index fe0d81c639..241126d908 100644 --- a/support/texlab/src/options.rs +++ b/support/texlab/src/options.rs @@ -1,12 +1,15 @@ use std::path::PathBuf; +use regex::Regex; use serde::{Deserialize, Serialize}; -#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Options { + #[serde(default)] pub root_directory: Option<PathBuf>, + #[serde(default)] pub aux_directory: Option<PathBuf>, #[serde(default)] @@ -15,9 +18,14 @@ pub struct Options { #[serde(default)] pub latex_formatter: LatexFormatter, + #[serde(default)] pub formatter_line_length: Option<i32>, - pub diagnostics_delay: Option<u64>, + #[serde(default)] + pub diagnostics: DiagnosticsOptions, + + #[serde(default = "default_diagnostics_delay")] + pub diagnostics_delay: u64, #[serde(default)] pub build: BuildOptions, @@ -32,6 +40,10 @@ pub struct Options { pub forward_search: ForwardSearchOptions, } +fn default_diagnostics_delay() -> u64 { + 300 +} + #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum BibtexFormatter { @@ -61,6 +73,7 @@ impl Default for LatexFormatter { #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LatexindentOptions { + #[serde(default)] pub local: Option<String>, #[serde(default)] @@ -70,9 +83,11 @@ pub struct LatexindentOptions { #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BuildOptions { - pub executable: Option<String>, + #[serde(default = "default_build_executable")] + pub executable: String, - pub args: Option<Vec<String>>, + #[serde(default = "default_build_args")] + pub args: Vec<String>, #[serde(default)] pub is_continuous: bool, @@ -84,39 +99,17 @@ pub struct BuildOptions { pub forward_search_after: bool, } -impl BuildOptions { - #[must_use] - pub fn executable(&self) -> String { - self.executable - .as_ref() - .map_or_else(|| "latexmk".to_string(), Clone::clone) - } - - #[must_use] - pub fn args(&self) -> Vec<String> { - self.args.as_ref().map_or_else( - || { - vec![ - "-pdf".to_string(), - "-interaction=nonstopmode".to_string(), - "-synctex=1".to_string(), - "%f".to_string(), - ] - }, - Clone::clone, - ) - } +fn default_build_executable() -> String { + "latexmk".to_string() } -#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ViewerOptions { - #[serde(default)] - pub enabled: bool, - - pub executable: Option<String>, - - pub args: Option<String>, +fn default_build_args() -> Vec<String> { + vec![ + "-pdf".to_string(), + "-interaction=nonstopmode".to_string(), + "-synctex=1".to_string(), + "%f".to_string(), + ] } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] @@ -130,7 +123,24 @@ pub struct ChktexOptions { } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ForwardSearchOptions { + #[serde(default)] pub executable: Option<String>, + + #[serde(default)] pub args: Option<Vec<String>>, } + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiagnosticsOptions { + #[serde(default)] + pub allowed_patterns: Vec<DiagnosticsPattern>, + + #[serde(default)] + pub ignored_patterns: Vec<DiagnosticsPattern>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DiagnosticsPattern(#[serde(with = "serde_regex")] pub Regex); diff --git a/support/texlab/src/server.rs b/support/texlab/src/server.rs index f3d2bf44f4..0d5d1f8fe1 100644 --- a/support/texlab/src/server.rs +++ b/support/texlab/src/server.rs @@ -1,6 +1,7 @@ use std::{ path::PathBuf, sync::{Arc, Mutex}, + time::Duration, }; use anyhow::Result; @@ -13,7 +14,8 @@ use threadpool::ThreadPool; use crate::{ client::{send_notification, send_request}, - diagnostics::{DiagnosticsDebouncer, DiagnosticsManager, DiagnosticsMessage}, + debouncer, + diagnostics::DiagnosticManager, dispatch::{NotificationDispatcher, RequestDispatcher}, distro::Distribution, features::{ @@ -24,14 +26,14 @@ use crate::{ ForwardSearchStatus, }, req_queue::{IncomingData, ReqQueue}, - ClientCapabilitiesExt, DocumentLanguage, Environment, LineIndex, LineIndexExt, Options, - Workspace, WorkspaceEvent, + ClientCapabilitiesExt, Document, DocumentData, DocumentLanguage, Environment, LineIndex, + LineIndexExt, Options, Workspace, WorkspaceEvent, }; #[derive(Debug)] enum InternalMessage { SetDistro(Distribution), - SetOptions(Options), + SetOptions(Arc<Options>), } #[derive(Clone)] @@ -41,8 +43,8 @@ pub struct Server { internal_rx: Receiver<InternalMessage>, req_queue: Arc<Mutex<ReqQueue>>, workspace: Workspace, - static_debouncer: Arc<DiagnosticsDebouncer>, - chktex_debouncer: Arc<DiagnosticsDebouncer>, + diagnostic_tx: debouncer::Sender<Workspace>, + diagnostic_manager: DiagnosticManager, pool: Arc<Mutex<ThreadPool>>, load_resolver: bool, build_engine: Arc<BuildEngine>, @@ -56,25 +58,17 @@ impl Server { ) -> Self { let req_queue = Arc::default(); let workspace = Workspace::new(Environment::new(Arc::new(current_dir))); - let diag_manager = Arc::new(Mutex::new(DiagnosticsManager::default())); - - let static_debouncer = Arc::new(create_static_debouncer( - Arc::clone(&diag_manager), - &connection, - )); - - let chktex_debouncer = Arc::new(create_chktex_debouncer(diag_manager, &connection)); - let (internal_tx, internal_rx) = crossbeam_channel::unbounded(); - + let diagnostic_manager = DiagnosticManager::default(); + let diagnostic_tx = create_debouncer(connection.sender.clone(), diagnostic_manager.clone()); Self { connection: Arc::new(connection), internal_tx, internal_rx, req_queue, workspace, - static_debouncer, - chktex_debouncer, + diagnostic_tx, + diagnostic_manager, pool: Arc::new(Mutex::new(threadpool::Builder::new().build())), load_resolver, build_engine: Arc::default(), @@ -173,7 +167,7 @@ impl Server { self.spawn(move |server| { server.register_config_capability(); server.register_file_watching(); - server.pull_config(); + let _ = server.pull_config(); }); Ok(()) @@ -246,19 +240,17 @@ impl Server { fn register_diagnostics_handler(&mut self) { let (event_sender, event_receiver) = crossbeam_channel::unbounded(); - let diag_sender = self.static_debouncer.sender.clone(); + let diagnostic_tx = self.diagnostic_tx.clone(); + let diagnostic_manager = self.diagnostic_manager.clone(); std::thread::spawn(move || { for event in event_receiver { match event { WorkspaceEvent::Changed(workspace, document) => { - let message = DiagnosticsMessage::Analyze { - workspace, - document, - }; - - if diag_sender.send(message).is_err() { - break; - } + diagnostic_manager.push_syntax(&workspace, &document.uri); + let delay = workspace.environment.options.diagnostics_delay; + diagnostic_tx + .send(workspace, Duration::from_millis(delay)) + .unwrap(); } }; } @@ -272,14 +264,14 @@ impl Server { req_queue.incoming.register(id, IncomingData); } - fn pull_config(&self) { + fn pull_config(&self) -> Result<()> { if !self .workspace .environment .client_capabilities .has_pull_configuration_support() { - return; + return Ok(()); } let params = ConfigurationParams { @@ -296,22 +288,38 @@ impl Server { ) { Ok(mut json) => { let value = json.pop().expect("invalid configuration request"); - let options = match serde_json::from_value(value) { - Ok(new_options) => new_options, - Err(why) => { - warn!("Invalid configuration section \"texlab\": {}", why); - Options::default() - } - }; - + let options = self.parse_options(value)?; self.internal_tx - .send(InternalMessage::SetOptions(options)) + .send(InternalMessage::SetOptions(Arc::new(options))) .unwrap(); } Err(why) => { error!("Retrieving configuration failed: {}", why); } }; + + Ok(()) + } + + fn parse_options(&self, value: serde_json::Value) -> Result<Options> { + let options = match serde_json::from_value(value) { + Ok(new_options) => new_options, + Err(why) => { + send_notification::<ShowMessage>( + &self.connection.sender, + ShowMessageParams { + message: format!( + "The texlab configuration is invalid; using the default settings instead.\nDetails: {why}" + ), + typ: MessageType::WARNING, + }, + )?; + + Options::default() + } + }; + + Ok(options) } fn cancel(&self, params: CancelParams) -> Result<()> { @@ -352,18 +360,11 @@ impl Server { .has_pull_configuration_support() { self.spawn(move |server| { - server.pull_config(); + let _ = server.pull_config(); }); } else { - match serde_json::from_value(params.settings) { - Ok(options) => { - self.workspace.environment.options = Arc::new(options); - } - Err(why) => { - error!("Invalid configuration: {}", why); - } - }; - + let options = self.parse_options(params.settings)?; + self.workspace.environment.options = Arc::new(options); self.reparse_all()?; } @@ -382,12 +383,7 @@ impl Server { self.workspace.viewport.insert(Arc::clone(&document.uri)); if self.workspace.environment.options.chktex.on_open_and_save { - self.chktex_debouncer - .sender - .send(DiagnosticsMessage::Analyze { - workspace: self.workspace.clone(), - document, - })?; + self.run_chktex(document); } Ok(()) @@ -421,12 +417,7 @@ impl Server { ); if self.workspace.environment.options.chktex.on_edit { - self.chktex_debouncer - .sender - .send(DiagnosticsMessage::Analyze { - workspace: self.workspace.clone(), - document: new_document, - })?; + self.run_chktex(new_document); }; } None => match uri.to_file_path() { @@ -440,7 +431,7 @@ impl Server { Ok(()) } - fn did_save(&self, params: DidSaveTextDocumentParams) -> Result<()> { + fn did_save(&mut self, params: DidSaveTextDocumentParams) -> Result<()> { let uri = params.text_document.uri; if let Some(request) = self @@ -477,13 +468,9 @@ impl Server { .filter(|_| self.workspace.environment.options.chktex.on_open_and_save) .cloned() { - self.chktex_debouncer - .sender - .send(DiagnosticsMessage::Analyze { - workspace: self.workspace.clone(), - document, - })?; - }; + self.run_chktex(document); + } + Ok(()) } @@ -492,6 +479,20 @@ impl Server { Ok(()) } + fn run_chktex(&mut self, document: Document) { + self.spawn(move |server| { + server + .diagnostic_manager + .push_chktex(&server.workspace, &document.uri); + + let delay = server.workspace.environment.options.diagnostics_delay; + server + .diagnostic_tx + .send(server.workspace.clone(), Duration::from_millis(delay)) + .unwrap(); + }); + } + fn feature_request<P>(&self, uri: Arc<Url>, params: P) -> FeatureRequest<P> { FeatureRequest { params, @@ -841,7 +842,7 @@ impl Server { self.reparse_all()?; } InternalMessage::SetOptions(options) => { - self.workspace.environment.options = Arc::new(options); + self.workspace.environment.options = options; self.reparse_all()?; } }; @@ -853,50 +854,40 @@ impl Server { pub fn run(mut self) -> Result<()> { self.initialize()?; self.process_messages()?; - drop(self.static_debouncer); - drop(self.chktex_debouncer); self.pool.lock().unwrap().join(); Ok(()) } } -fn create_static_debouncer( - manager: Arc<Mutex<DiagnosticsManager>>, - conn: &Connection, -) -> DiagnosticsDebouncer { - let sender = conn.sender.clone(); - DiagnosticsDebouncer::launch(move |workspace, document| { - let mut manager = manager.lock().unwrap(); - manager.update_static(&workspace, Arc::clone(&document.uri)); - if let Err(why) = publish_diagnostics(&sender, &workspace, &manager) { - warn!("Failed to publish diagnostics: {}", why); +fn create_debouncer( + lsp_sender: Sender<Message>, + diagnostic_manager: DiagnosticManager, +) -> debouncer::Sender<Workspace> { + let (tx, rx) = debouncer::unbounded(); + std::thread::spawn(move || { + while let Ok(workspace) = rx.recv() { + if let Err(why) = publish_diagnostics(&lsp_sender, &diagnostic_manager, &workspace) { + warn!("Failed to publish diagnostics: {}", why); + } } - }) -} + }); -fn create_chktex_debouncer( - manager: Arc<Mutex<DiagnosticsManager>>, - conn: &Connection, -) -> DiagnosticsDebouncer { - let sender = conn.sender.clone(); - DiagnosticsDebouncer::launch(move |workspace, document| { - let mut manager = manager.lock().unwrap(); - manager.update_chktex(&workspace, &document.uri, &workspace.environment.options); - if let Err(why) = publish_diagnostics(&sender, &workspace, &manager) { - warn!("Failed to publish diagnostics: {}", why); - } - }) + tx } fn publish_diagnostics( - sender: &Sender<lsp_server::Message>, + lsp_sender: &Sender<lsp_server::Message>, + diagnostic_manager: &DiagnosticManager, workspace: &Workspace, - diag_manager: &DiagnosticsManager, ) -> Result<()> { for document in workspace.documents_by_uri.values() { - let diagnostics = diag_manager.publish(&document.uri); + if matches!(document.data, DocumentData::BuildLog(_)) { + continue; + } + + let diagnostics = diagnostic_manager.publish(workspace, &document.uri); send_notification::<PublishDiagnostics>( - sender, + lsp_sender, PublishDiagnosticsParams { uri: document.uri.as_ref().clone(), version: None, @@ -904,6 +895,7 @@ fn publish_diagnostics( }, )?; } + Ok(()) } diff --git a/support/texlab/tests/integration/dev.rs b/support/texlab/tests/integration/dev.rs deleted file mode 100644 index 7ba1e93d0c..0000000000 --- a/support/texlab/tests/integration/dev.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::process::{Command, Stdio}; - -use anyhow::Result; - -#[test] -fn rustfmt() -> Result<()> { - let success = Command::new("cargo") - .args(&["fmt", "--check"]) - .current_dir(env!("CARGO_MANIFEST_DIR")) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()? - .success(); - - assert!(success); - Ok(()) -} diff --git a/support/texlab/tests/integration/lsp/client.rs b/support/texlab/tests/integration/lsp/client.rs index dc6b66795c..004422813a 100644 --- a/support/texlab/tests/integration/lsp/client.rs +++ b/support/texlab/tests/integration/lsp/client.rs @@ -142,6 +142,13 @@ impl Client { Ok(()) } + pub fn store_on_disk(&mut self, name: &str, text: &str) -> Result<()> { + let path = self.directory.path().join(name); + std::fs::create_dir_all(path.parent().unwrap())?; + std::fs::write(path, text)?; + Ok(()) + } + pub fn shutdown(mut self) -> Result<ClientResult> { self.request::<Shutdown>(())?; self.notify::<Exit>(())?; diff --git a/support/texlab/tests/integration/lsp/fixture.rs b/support/texlab/tests/integration/lsp/fixture.rs index 17eb659923..83d63cce60 100644 --- a/support/texlab/tests/integration/lsp/fixture.rs +++ b/support/texlab/tests/integration/lsp/fixture.rs @@ -20,6 +20,8 @@ fn parse_line(line: &str) -> Line { Line::File(name, "latex") } else if let Some(name) = line.strip_prefix("%BIB ") { Line::File(name, "bibtex") + } else if let Some(name) = line.strip_prefix("%LOG ") { + Line::File(name, "log") } else if let Some(text) = line.strip_prefix("%SRC ") { Line::Plain(text) } else if let Some(text) = line.strip_prefix("%CUR ") { diff --git a/support/texlab/tests/integration/lsp/text_document.rs b/support/texlab/tests/integration/lsp/text_document.rs index 56ad1ee09f..283132d3c4 100644 --- a/support/texlab/tests/integration/lsp/text_document.rs +++ b/support/texlab/tests/integration/lsp/text_document.rs @@ -1,11 +1,13 @@ #[cfg(feature = "completion")] mod completion; mod definition; +mod did_change_configuration; mod document_highlight; mod document_link; mod document_symbol; mod folding_range; mod formatting; mod hover; +mod publish_diagnostics; mod references; mod rename; diff --git a/support/texlab/tests/integration/lsp/text_document/did_change_configuration.rs b/support/texlab/tests/integration/lsp/text_document/did_change_configuration.rs new file mode 100644 index 0000000000..c45dca094b --- /dev/null +++ b/support/texlab/tests/integration/lsp/text_document/did_change_configuration.rs @@ -0,0 +1,39 @@ +use anyhow::Result; +use insta::assert_snapshot; +use lsp_types::{ + notification::{DidChangeConfiguration, Notification, ShowMessage}, + ClientCapabilities, DidChangeConfigurationParams, ShowMessageParams, +}; + +use crate::lsp::client::Client; + +#[test] +fn invalid_configuration() -> Result<()> { + let mut client = Client::spawn()?; + client.initialize(ClientCapabilities::default(), None)?; + + client.notify::<DidChangeConfiguration>(DidChangeConfigurationParams { + settings: serde_json::json!({ + "diagnostics": { + "allowedPatterns": ["\\"] + } + }), + })?; + + let result = client.shutdown()?; + let message = result + .incoming + .notifications + .into_iter() + .filter_map(|notification| { + notification + .extract::<ShowMessageParams>(ShowMessage::METHOD) + .ok() + }) + .find(|params| params.message.contains("configuration")) + .unwrap() + .message; + + assert_snapshot!(message); + Ok(()) +} diff --git a/support/texlab/tests/integration/lsp/text_document/publish_diagnostics.rs b/support/texlab/tests/integration/lsp/text_document/publish_diagnostics.rs new file mode 100644 index 0000000000..7e406b5319 --- /dev/null +++ b/support/texlab/tests/integration/lsp/text_document/publish_diagnostics.rs @@ -0,0 +1,264 @@ +use anyhow::Result; +use insta::{assert_json_snapshot, internals::Redaction}; +use lsp_types::{ + notification::{ + DidChangeConfiguration, DidChangeWatchedFiles, Notification, PublishDiagnostics, + }, + ClientCapabilities, Diagnostic, DidChangeConfigurationParams, DidChangeWatchedFilesParams, + FileChangeType, FileEvent, PublishDiagnosticsParams, Url, +}; +use rustc_hash::FxHashMap; + +use crate::lsp::{client::Client, fixture}; + +struct DiagnosticResult { + all_diagnostics: FxHashMap<Url, Vec<Diagnostic>>, + uri_redaction: Redaction, +} + +fn find_diagnostics(fixture: &str, settings: serde_json::Value) -> Result<DiagnosticResult> { + let mut client = Client::spawn()?; + client.initialize(ClientCapabilities::default(), None)?; + + let mut disk_files = Vec::new(); + let fixture = fixture::parse(fixture); + for file in fixture.files { + if file.lang == "log" { + client.store_on_disk(file.name, &file.text)?; + disk_files.push(client.uri(file.name)?); + } else { + client.open(file.name, file.lang, file.text)?; + } + } + + client.notify::<DidChangeWatchedFiles>(DidChangeWatchedFilesParams { + changes: disk_files + .into_iter() + .map(|uri| FileEvent::new(uri, FileChangeType::CHANGED)) + .collect(), + })?; + + client.notify::<DidChangeConfiguration>(DidChangeConfigurationParams { settings })?; + + let result = client.shutdown()?; + + let uri = Url::from_directory_path(result.directory.path()).unwrap(); + let uri_redaction = insta::dynamic_redaction(move |content, _path| { + content.as_str().unwrap().replace(uri.as_str(), "[tmp]/") + }); + + let all_diagnostics = result + .incoming + .notifications + .into_iter() + .filter_map(|notification| { + notification + .extract::<PublishDiagnosticsParams>(PublishDiagnostics::METHOD) + .ok() + }) + .map(|params| (params.uri, params.diagnostics)) + .collect(); + + Ok(DiagnosticResult { + all_diagnostics, + uri_redaction, + }) +} + +macro_rules! assert_symbols { + ($result:expr) => { + let result = $result; + assert_json_snapshot!(result.all_diagnostics, { + ".$key" => result.uri_redaction + }); + }; +} + +static BUILD_LOG_FIXTURE: &str = r#" +%TEX main.tex +%SRC \documentclass{article} +%SRC +%SRC \usepackage{amsmath} +%SRC +%SRC \begin{document} +%SRC +%SRC \foo{} +%SRC aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +%SRC +%SRC \end{document} + +%LOG main.log +%SRC This is pdfTeX, Version 3.141592653-2.6-1.40.22 (TeX Live 2021/W32TeX) (preloaded format=pdflatex 2022.1.23) 16 JUN 2022 11:04 +%SRC entering extended mode +%SRC restricted \write18 enabled. +%SRC %&-line parsing enabled. +%SRC **main.tex +%SRC (./main.tex +%SRC LaTeX2e <2020-10-01> patch level 4 +%SRC L3 programming layer <2021-02-18> +%SRC (c:/texlive/2021/texmf-dist/tex/latex/base/article.cls +%SRC Document Class: article 2020/04/10 v1.4m Standard LaTeX document class +%SRC (c:/texlive/2021/texmf-dist/tex/latex/base/size10.clo +%SRC File: size10.clo 2020/04/10 v1.4m Standard LaTeX file (size option) +%SRC ) +%SRC \c@part=\count179 +%SRC \c@section=\count180 +%SRC \c@subsection=\count181 +%SRC \c@subsubsection=\count182 +%SRC \c@paragraph=\count183 +%SRC \c@subparagraph=\count184 +%SRC \c@figure=\count185 +%SRC \c@table=\count186 +%SRC \abovecaptionskip=\skip47 +%SRC \belowcaptionskip=\skip48 +%SRC \bibindent=\dimen138 +%SRC ) +%SRC (c:/texlive/2021/texmf-dist/tex/latex/amsmath/amsmath.sty +%SRC Package: amsmath 2020/09/23 v2.17i AMS math features +%SRC \@mathmargin=\skip49 +%SRC +%SRC For additional information on amsmath, use the `?' option. +%SRC (c:/texlive/2021/texmf-dist/tex/latex/amsmath/amstext.sty +%SRC Package: amstext 2000/06/29 v2.01 AMS text +%SRC +%SRC (c:/texlive/2021/texmf-dist/tex/latex/amsmath/amsgen.sty +%SRC File: amsgen.sty 1999/11/30 v2.0 generic functions +%SRC \@emptytoks=\toks15 +%SRC \ex@=\dimen139 +%SRC )) +%SRC (c:/texlive/2021/texmf-dist/tex/latex/amsmath/amsbsy.sty +%SRC Package: amsbsy 1999/11/29 v1.2d Bold Symbols +%SRC \pmbraise@=\dimen140 +%SRC ) +%SRC (c:/texlive/2021/texmf-dist/tex/latex/amsmath/amsopn.sty +%SRC Package: amsopn 2016/03/08 v2.02 operator names +%SRC ) +%SRC \inf@bad=\count187 +%SRC LaTeX Info: Redefining \frac on input line 234. +%SRC \uproot@=\count188 +%SRC \leftroot@=\count189 +%SRC LaTeX Info: Redefining \overline on input line 399. +%SRC \classnum@=\count190 +%SRC \DOTSCASE@=\count191 +%SRC LaTeX Info: Redefining \ldots on input line 496. +%SRC LaTeX Info: Redefining \dots on input line 499. +%SRC LaTeX Info: Redefining \cdots on input line 620. +%SRC \Mathstrutbox@=\box47 +%SRC \strutbox@=\box48 +%SRC \big@size=\dimen141 +%SRC LaTeX Font Info: Redeclaring font encoding OML on input line 743. +%SRC LaTeX Font Info: Redeclaring font encoding OMS on input line 744. +%SRC \macc@depth=\count192 +%SRC \c@MaxMatrixCols=\count193 +%SRC \dotsspace@=\muskip16 +%SRC \c@parentequation=\count194 +%SRC \dspbrk@lvl=\count195 +%SRC \tag@help=\toks16 +%SRC \row@=\count196 +%SRC \column@=\count197 +%SRC \maxfields@=\count198 +%SRC \andhelp@=\toks17 +%SRC \eqnshift@=\dimen142 +%SRC \alignsep@=\dimen143 +%SRC \tagshift@=\dimen144 +%SRC \tagwidth@=\dimen145 +%SRC \totwidth@=\dimen146 +%SRC \lineht@=\dimen147 +%SRC \@envbody=\toks18 +%SRC \multlinegap=\skip50 +%SRC \multlinetaggap=\skip51 +%SRC \mathdisplay@stack=\toks19 +%SRC LaTeX Info: Redefining \[ on input line 2923. +%SRC LaTeX Info: Redefining \] on input line 2924. +%SRC ) +%SRC (c:/texlive/2021/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def +%SRC File: l3backend-pdftex.def 2021-03-18 L3 backend support: PDF output (pdfTeX) +%SRC \l__color_backend_stack_int=\count199 +%SRC \l__pdf_internal_box=\box49 +%SRC ) +%SRC (./main.aux) +%SRC \openout1 = `main.aux'. +%SRC +%SRC LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 5. +%SRC LaTeX Font Info: ... okay on input line 5. +%SRC LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 5. +%SRC LaTeX Font Info: ... okay on input line 5. +%SRC LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 5. +%SRC LaTeX Font Info: ... okay on input line 5. +%SRC LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 5. +%SRC LaTeX Font Info: ... okay on input line 5. +%SRC LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 5. +%SRC LaTeX Font Info: ... okay on input line 5. +%SRC LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 5. +%SRC LaTeX Font Info: ... okay on input line 5. +%SRC LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 5. +%SRC LaTeX Font Info: ... okay on input line 5. +%SRC +%SRC ! Undefined control sequence. +%SRC l.7 \foo +%SRC {} +%SRC The control sequence at the end of the top line +%SRC of your error message was never \def'ed. If you have +%SRC misspelled it (e.g., `\hobx'), type `I' and the correct +%SRC spelling (e.g., `I\hbox'). Otherwise just continue, +%SRC and I'll forget about whatever was undefined. +%SRC +%SRC +%SRC Overfull \hbox (80.00125pt too wide) in paragraph at lines 8--9 +%SRC []\OT1/cmr/m/n/10 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +%SRC aaaaaaaaaaaaaaaaaaaaa +%SRC [] +%SRC +%SRC [1 +%SRC +%SRC {c:/texlive/2021/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./main.aux) ) +%SRC Here is how much of TeX's memory you used: +%SRC 1014 strings out of 478994 +%SRC 13996 string characters out of 5862207 +%SRC 300924 words of memory out of 5000000 +%SRC 18565 multiletter control sequences out of 15000+600000 +%SRC 403430 words of font info for 27 fonts, out of 8000000 for 9000 +%SRC 1141 hyphenation exceptions out of 8191 +%SRC 67i,4n,74p,200b,104s stack positions out of 5000i,500n,10000p,200000b,80000s +%SRC < +%SRC c:/texlive/2021/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb> +%SRC Output written on main.pdf (1 page, 9741 bytes). +%SRC PDF statistics: +%SRC 12 PDF objects out of 1000 (max. 8388607) +%SRC 7 compressed objects within 1 object stream +%SRC 0 named destinations out of 1000 (max. 500000) +%SRC 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#; + +#[test] +fn build_log_filter_none() -> Result<()> { + assert_symbols!(find_diagnostics(BUILD_LOG_FIXTURE, serde_json::json!({}))?); + Ok(()) +} + +#[test] +fn build_log_filter_allowed() -> Result<()> { + assert_symbols!(find_diagnostics( + BUILD_LOG_FIXTURE, + serde_json::json!({ + "diagnostics": { + "allowedPatterns": ["Overfull \\\\[hv]box"] + } + }) + )?); + + Ok(()) +} + +#[test] +fn build_log_filter_ignored() -> Result<()> { + assert_symbols!(find_diagnostics( + BUILD_LOG_FIXTURE, + serde_json::json!({ + "diagnostics": { + "ignoredPatterns": ["Overfull \\\\[hv]box"] + } + }) + )?); + + Ok(()) +} diff --git a/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__did_change_configuration__invalid_configuration.snap b/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__did_change_configuration__invalid_configuration.snap new file mode 100644 index 0000000000..4f41cfbeb2 --- /dev/null +++ b/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__did_change_configuration__invalid_configuration.snap @@ -0,0 +1,10 @@ +--- +source: tests/integration/lsp/text_document/did_change_configuration.rs +assertion_line: 37 +expression: message +--- +The texlab configuration is invalid; using the default settings instead. +Details: regex parse error: + \ + ^ +error: incomplete escape sequence, reached end of pattern prematurely diff --git a/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__publish_diagnostics__build_log_filter_allowed.snap b/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__publish_diagnostics__build_log_filter_allowed.snap new file mode 100644 index 0000000000..9fc11d295b --- /dev/null +++ b/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__publish_diagnostics__build_log_filter_allowed.snap @@ -0,0 +1,24 @@ +--- +source: tests/integration/lsp/text_document/publish_diagnostics.rs +assertion_line: 240 +expression: result.all_diagnostics +--- +{ + "[tmp]/main.tex": [ + { + "range": { + "start": { + "line": 7, + "character": 0 + }, + "end": { + "line": 7, + "character": 0 + } + }, + "severity": 2, + "source": "latex-build", + "message": "Overfull \\hbox (80.00125pt too wide) in paragraph at lines 8--9" + } + ] +} diff --git a/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__publish_diagnostics__build_log_filter_ignored.snap b/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__publish_diagnostics__build_log_filter_ignored.snap new file mode 100644 index 0000000000..28e0308a72 --- /dev/null +++ b/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__publish_diagnostics__build_log_filter_ignored.snap @@ -0,0 +1,24 @@ +--- +source: tests/integration/lsp/text_document/publish_diagnostics.rs +assertion_line: 254 +expression: result.all_diagnostics +--- +{ + "[tmp]/main.tex": [ + { + "range": { + "start": { + "line": 6, + "character": 0 + }, + "end": { + "line": 6, + "character": 0 + } + }, + "severity": 1, + "source": "latex-build", + "message": "Undefined control sequence." + } + ] +} diff --git a/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__publish_diagnostics__build_log_filter_none.snap b/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__publish_diagnostics__build_log_filter_none.snap new file mode 100644 index 0000000000..b16238381b --- /dev/null +++ b/support/texlab/tests/integration/lsp/text_document/snapshots/integration__lsp__text_document__publish_diagnostics__build_log_filter_none.snap @@ -0,0 +1,39 @@ +--- +source: tests/integration/lsp/text_document/publish_diagnostics.rs +assertion_line: 234 +expression: result.all_diagnostics +--- +{ + "[tmp]/main.tex": [ + { + "range": { + "start": { + "line": 6, + "character": 0 + }, + "end": { + "line": 6, + "character": 0 + } + }, + "severity": 1, + "source": "latex-build", + "message": "Undefined control sequence." + }, + { + "range": { + "start": { + "line": 7, + "character": 0 + }, + "end": { + "line": 7, + "character": 0 + } + }, + "severity": 2, + "source": "latex-build", + "message": "Overfull \\hbox (80.00125pt too wide) in paragraph at lines 8--9" + } + ] +} diff --git a/support/texlab/tests/integration/main.rs b/support/texlab/tests/integration/main.rs index 508158af91..44e0d3635a 100644 --- a/support/texlab/tests/integration/main.rs +++ b/support/texlab/tests/integration/main.rs @@ -1,2 +1 @@ -mod dev; mod lsp; |