summaryrefslogtreecommitdiff
path: root/support
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2023-04-17 03:03:48 +0000
committerNorbert Preining <norbert@preining.info>2023-04-17 03:03:48 +0000
commit88aa9bb9a3222cf13820ae3b6f64ce48dcd003ea (patch)
tree9495d0cd2219bb309106f5330e0aeba36a675319 /support
parent421b47819f21160c3662c40f7da028f15b726577 (diff)
CTAN sync 202304170303
Diffstat (limited to 'support')
-rw-r--r--support/lyluatex/lyluatex.lua20
-rw-r--r--support/lyluatex/lyluatex.pdfbin2028732 -> 2028731 bytes
-rw-r--r--support/lyluatex/lyluatex.sty2
-rw-r--r--support/lyluatex/lyluatex.tex2
-rw-r--r--support/lyluatex/lyluatexbase.cls2
-rw-r--r--support/lyluatex/lyluatexmanual.cls2
-rw-r--r--support/pdfcrop/README.md4
-rw-r--r--support/pdfcrop/pdfcrop.pl25
-rw-r--r--support/texlab/CHANGELOG.md15
-rw-r--r--support/texlab/Cargo.lock117
-rw-r--r--support/texlab/crates/base-db/src/config.rs89
-rw-r--r--support/texlab/crates/base-db/src/document.rs4
-rw-r--r--support/texlab/crates/base-feature/Cargo.toml14
-rw-r--r--support/texlab/crates/base-feature/src/lib.rs4
-rw-r--r--support/texlab/crates/base-feature/src/normalize_uri.rs56
-rw-r--r--support/texlab/crates/base-feature/src/placeholders.rs50
-rw-r--r--support/texlab/crates/commands/Cargo.toml24
-rw-r--r--support/texlab/crates/commands/src/build.rs102
-rw-r--r--support/texlab/crates/commands/src/change_env.rs40
-rw-r--r--support/texlab/crates/commands/src/clean.rs57
-rw-r--r--support/texlab/crates/commands/src/dep_graph.rs (renamed from support/texlab/crates/texlab/src/features/workspace_command/dep_graph.rs)4
-rw-r--r--support/texlab/crates/commands/src/fwd_search.rs107
-rw-r--r--support/texlab/crates/commands/src/lib.rs13
-rw-r--r--support/texlab/crates/parser/Cargo.toml1
-rw-r--r--support/texlab/crates/parser/src/config.rs153
-rw-r--r--support/texlab/crates/parser/src/latex.rs14
-rw-r--r--support/texlab/crates/parser/src/latex/lexer.rs10
-rw-r--r--support/texlab/crates/parser/src/latex/lexer/commands.rs15
-rw-r--r--support/texlab/crates/parser/src/lib.rs3
-rw-r--r--support/texlab/crates/texlab/Cargo.toml15
-rw-r--r--support/texlab/crates/texlab/benches/bench_main.rs5
-rw-r--r--support/texlab/crates/texlab/src/client.rs12
-rw-r--r--support/texlab/crates/texlab/src/features.rs3
-rw-r--r--support/texlab/crates/texlab/src/features/build.rs171
-rw-r--r--support/texlab/crates/texlab/src/features/build/progress.rs54
-rw-r--r--support/texlab/crates/texlab/src/features/completion.rs1
-rw-r--r--support/texlab/crates/texlab/src/features/completion/builder.rs55
-rw-r--r--support/texlab/crates/texlab/src/features/completion/matcher.rs42
-rw-r--r--support/texlab/crates/texlab/src/features/forward_search.rs195
-rw-r--r--support/texlab/crates/texlab/src/features/workspace_command.rs3
-rw-r--r--support/texlab/crates/texlab/src/features/workspace_command/change_environment.rs108
-rw-r--r--support/texlab/crates/texlab/src/features/workspace_command/clean.rs93
-rw-r--r--support/texlab/crates/texlab/src/lib.rs27
-rw-r--r--support/texlab/crates/texlab/src/server.rs347
-rw-r--r--support/texlab/crates/texlab/src/server/extensions.rs75
-rw-r--r--support/texlab/crates/texlab/src/server/options.rs36
-rw-r--r--support/texlab/crates/texlab/src/server/progress.rs44
-rw-r--r--support/texlab/texlab.14
-rw-r--r--support/texlab/texlab.pdfbin26391 -> 26543 bytes
49 files changed, 1219 insertions, 1020 deletions
diff --git a/support/lyluatex/lyluatex.lua b/support/lyluatex/lyluatex.lua
index c67613b3b5..0b8e74b2a7 100644
--- a/support/lyluatex/lyluatex.lua
+++ b/support/lyluatex/lyluatex.lua
@@ -1,8 +1,8 @@
-- luacheck: ignore ly log self luatexbase internalversion font fonts tex token kpse status ly_opts
local err, warn, info, log = luatexbase.provides_module({
name = "lyluatex",
- version = '1.1.3', --LYLUATEX_VERSION
- date = "2023/03/01", --LYLUATEX_DATE
+ version = '1.1.4', --LYLUATEX_VERSION
+ date = "2023/04/15", --LYLUATEX_DATE
description = "Module lyluatex.",
author = "The Gregorio Project − (see Contributors.md)",
copyright = "2015-2023 - jperon and others",
@@ -1357,7 +1357,21 @@ end
function ly.get_font_family(font_id)
- return lib.fontinfo(font_id).fullname:match("[^-]*")
+ local ft = lib.fontinfo(font_id)
+ if ft.shared.rawdata then return ft.shared.rawdata.metadata.familyname
+ else
+ warn([[
+Some useful informations aren’t available:
+you probably loaded polyglossia
+before defining the main font, and we have
+to "guess" the font’s familyname.
+If the text of your scores looks weird,
+you should consider using babel instead,
+or at least loading polyglossia
+after defining the main font.
+]])
+ return ft.fullname:match("[^-]*")
+ end
end
diff --git a/support/lyluatex/lyluatex.pdf b/support/lyluatex/lyluatex.pdf
index 6831174df1..77449a3486 100644
--- a/support/lyluatex/lyluatex.pdf
+++ b/support/lyluatex/lyluatex.pdf
Binary files differ
diff --git a/support/lyluatex/lyluatex.sty b/support/lyluatex/lyluatex.sty
index 58aaeb6b5d..86fb8ca56c 100644
--- a/support/lyluatex/lyluatex.sty
+++ b/support/lyluatex/lyluatex.sty
@@ -5,7 +5,7 @@
% This file is part of lyluatex.
\NeedsTeXFormat{LaTeX2e}%
-\ProvidesPackage{lyluatex}[2023/03/01 v1.1.3] %%LYLUATEX_DATE LYLUATEX_VERSION
+\ProvidesPackage{lyluatex}[2023/04/15 v1.1.4] %%LYLUATEX_DATE LYLUATEX_VERSION
% Dependencies
\RequirePackage{graphicx}
diff --git a/support/lyluatex/lyluatex.tex b/support/lyluatex/lyluatex.tex
index 80e891e21c..3932556f69 100644
--- a/support/lyluatex/lyluatex.tex
+++ b/support/lyluatex/lyluatex.tex
@@ -93,7 +93,7 @@
\apptocmd{\@title}{\par {\large #1 \par}}{}{}
}
\makeatother
-\subtitle{1.1.3}
+\subtitle{1.1.4}
\author{Fr.~Jacques Peron \and Urs Liska \and Br. Samuel Springuel}
\date{\lyluatexmanualdate}
diff --git a/support/lyluatex/lyluatexbase.cls b/support/lyluatex/lyluatexbase.cls
index d60f554e3c..1fd12d1bf7 100644
--- a/support/lyluatex/lyluatexbase.cls
+++ b/support/lyluatex/lyluatexbase.cls
@@ -5,7 +5,7 @@
% This file is part of lyluatex.
\NeedsTeXFormat{LaTeX2e}
-\ProvidesClass{lyluatexbase}[2023/03/01 v1.1.3] %%LYLUATEX_DATE LYLUATEX_VERSION
+\ProvidesClass{lyluatexbase}[2023/04/15 v1.1.4] %%LYLUATEX_DATE LYLUATEX_VERSION
\LoadClass[DIV=11]{scrartcl}
\RequirePackage{lyluatex}
diff --git a/support/lyluatex/lyluatexmanual.cls b/support/lyluatex/lyluatexmanual.cls
index de49a2c11d..9e016454bd 100644
--- a/support/lyluatex/lyluatexmanual.cls
+++ b/support/lyluatex/lyluatexmanual.cls
@@ -5,7 +5,7 @@
% This file is part of lyluatex.
\NeedsTeXFormat{LaTeX2e}
-\ProvidesClass{lyluatexmanual}[2023/03/01 v1.1.3] %%LYLUATEX_DATE LYLUATEX_VERSION
+\ProvidesClass{lyluatexmanual}[2023/04/15 v1.1.4] %%LYLUATEX_DATE LYLUATEX_VERSION
\LoadClass{lyluatexbase}
diff --git a/support/pdfcrop/README.md b/support/pdfcrop/README.md
index 93df549ae9..bbb22dc03a 100644
--- a/support/pdfcrop/README.md
+++ b/support/pdfcrop/README.md
@@ -1,6 +1,6 @@
# pdfcrop
-Version: 2023/04/13 v1.41
+Version: 2023/04/15 v1.42
## TABLE OF CONTENTS
@@ -255,7 +255,7 @@ My environment for developing and testing:
|-- | \* add -q option, issue 7 |
|-- | \* don't print whole help msg for unknown options, issue 7.|
|-- | \* do not create two pages with xetex, issue 3|
-
+|2023/04/15 v1.42:| \* update help text issue 18|
## 13. TODO
diff --git a/support/pdfcrop/pdfcrop.pl b/support/pdfcrop/pdfcrop.pl
index fd0642a558..9af00a6208 100644
--- a/support/pdfcrop/pdfcrop.pl
+++ b/support/pdfcrop/pdfcrop.pl
@@ -18,8 +18,8 @@ $^W=1; # turn warning on
my $prj = 'pdfcrop';
my $file = "$prj.pl";
my $program = uc($&) if $file =~ /^\w+/;
-my $version = "1.41";
-my $date = "2023/04/13";
+my $version = "1.42";
+my $date = "2023/04/15";
my $author = "Heiko Oberdiek, Oberdiek Package Support Group";
my $copyright = "Copyright (c) 2002-2023 by $author.";
#
@@ -111,6 +111,7 @@ my $copyright = "Copyright (c) 2002-2023 by $author.";
# add -q option, issue 7;
# don't print whole help msg for unknown options, issue 7.
# do not create two pages with xetex, issue 3
+# 2023/04/15 v1.42: * update help text issue 18
### program identification
my $title = "$program $version, $date - $copyright\n";
@@ -381,8 +382,9 @@ Options: (defaults:)
--help print usage
--version print version number
--(no)verbose verbose printing ($bool[$::opt_verbose])
- --(no)debug debug informations ($bool[$::opt_debug])
- --gscmd <name> call of ghostscript ($::opt_gscmd)
+ --(no)quiet silence normal output ($bool[$::opt_quiet])
+ --(no)debug debug information ($bool[$::opt_debug])
+ --gscmd <name> call of Ghostscript ($::opt_gscmd)
--pdftex | --xetex | --luatex
use pdfTeX | use XeTeX | use LuaTeX ($::opt_tex)
--pdftexcmd <name> call of pdfTeX ($::opt_pdftexcmd)
@@ -390,11 +392,11 @@ Options: (defaults:)
--luatexcmd <name> call of LuaTeX ($::opt_luatexcmd)
--margins "<left> <top> <right> <bottom>" ($::opt_margins)
add extra margins, unit is bp. If only one number is
- given, then it is used for all margins, in the case
- of two numbers they are also used for right and bottom.
+ given, then it is used for all margins; in the case
+ of two numbers, they are also used for right and bottom.
--(no)clip clipping support, if margins are set ($bool[$::opt_clip])
(not available for --xetex)
- --(no)hires using `%%HiResBoundingBox' ($bool[$::opt_hires])
+ --(no)hires use `%%HiResBoundingBox' ($bool[$::opt_hires])
instead of `%%BoundingBox'
--(no)ini use iniTeX variant of the TeX compiler ($bool[$::opt_initex])
Expert options:
@@ -402,22 +404,21 @@ Expert options:
--papersize <foo> parameter for gs's -sPAPERSIZE=<foo>,
use only with older gs versions <7.32 ($::opt_papersize)
--resolution <xres>x<yres> ($::opt_resolution)
- --resolution <res> pass argument to ghostscript's option -r
+ --resolution <res> pass argument to Ghostscript's option -r
Example: --resolution 72
--bbox "<left> <bottom> <right> <top>" ($::opt_bbox)
- override bounding box found by ghostscript
+ override bounding box found by Ghostscript
with origin at the lower left corner
--bbox-odd Same as --bbox, but for odd pages only ($::opt_bbox_odd)
--bbox-even Same as --bbox, but for even pages only ($::opt_bbox_even)
--pdfversion <x.y> | auto | none
- Set the PDF version to x.y, x= 1 or 2, y=0-9.
+ Set the PDF version to x.y, x= 1 or 2, y= 0-9.
If `auto' is given as value, then the
PDF version is taken from the header
of the input PDF file.
An empty value or `none' uses the
default of the TeX engine. ($::opt_pdfversion)
- --uncompress creates an uncompressed pdf,
- useful for debugging ($bool[$::opt_uncompress])
+ --uncompress create uncompressed pdf, for debugging ($bool[$::opt_uncompress])
Input file: If the name is `-', then the standard input is used and
the output file name must be explicitly given.
diff --git a/support/texlab/CHANGELOG.md b/support/texlab/CHANGELOG.md
index 557b8ea682..8d09300582 100644
--- a/support/texlab/CHANGELOG.md
+++ b/support/texlab/CHANGELOG.md
@@ -5,6 +5,21 @@ 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).
+## [5.5.0] - 2023-04-16
+
+### Added
+
+- Allow optionally passing cursor position to `textDocument/build` request for use in forward search after building.
+ Previously, the server had to guess the cursor position ([#475](https://github.com/latex-lsp/texlab/issues/475))
+- Add experimental `texlab.experimental.citationCommands` setting to allow extending the list of citation commands
+ ([#832](https://github.com/latex-lsp/texlab/issues/832))
+- Add support for escaping placeholders in build arguments similar to forward search
+- Allow configuring completion matching algorithm ([#872](https://github.com/latex-lsp/texlab/issues/872))
+
+### Fixed
+
+- Fix regression introduced in `v5.4.2` involving `texlab.cleanArtifacts` command.
+
## [5.4.2] - 2023-04-11
### Fixed
diff --git a/support/texlab/Cargo.lock b/support/texlab/Cargo.lock
index 724266ddb8..bbe9fa977b 100644
--- a/support/texlab/Cargo.lock
+++ b/support/texlab/Cargo.lock
@@ -34,42 +34,51 @@ dependencies = [
[[package]]
name = "anstream"
-version = "0.2.6"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f"
+checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371"
dependencies = [
"anstyle",
"anstyle-parse",
+ "anstyle-query",
"anstyle-wincon",
- "concolor-override",
- "concolor-query",
+ "colorchoice",
"is-terminal",
"utf8parse",
]
[[package]]
name = "anstyle"
-version = "0.3.5"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2"
+checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
[[package]]
name = "anstyle-parse"
-version = "0.1.1"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116"
+checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
dependencies = [
"utf8parse",
]
[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
name = "anstyle-wincon"
-version = "0.2.0"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa"
+checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd"
dependencies = [
"anstyle",
- "windows-sys 0.45.0",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -124,6 +133,14 @@ dependencies = [
]
[[package]]
+name = "base-feature"
+version = "0.0.0"
+dependencies = [
+ "rustc-hash",
+ "url",
+]
+
+[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -151,6 +168,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
dependencies = [
"memchr",
+ "once_cell",
+ "regex-automata",
"serde",
]
@@ -247,9 +266,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.2.1"
+version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3"
+checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a"
dependencies = [
"clap_builder",
"clap_derive",
@@ -258,9 +277,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.2.1"
+version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f"
+checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6"
dependencies = [
"anstream",
"anstyle",
@@ -297,18 +316,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
[[package]]
-name = "concolor-override"
+name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
-name = "concolor-query"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf"
+name = "commands"
+version = "0.0.0"
dependencies = [
- "windows-sys 0.45.0",
+ "anyhow",
+ "base-db",
+ "base-feature",
+ "bstr",
+ "crossbeam-channel",
+ "itertools",
+ "log",
+ "rowan",
+ "rustc-hash",
+ "syntax",
+ "thiserror",
+ "url",
]
[[package]]
@@ -437,19 +465,6 @@ dependencies = [
]
[[package]]
-name = "dashmap"
-version = "5.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
-dependencies = [
- "cfg-if",
- "hashbrown",
- "lock_api",
- "once_cell",
- "parking_lot_core",
-]
-
-[[package]]
name = "digest"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1090,6 +1105,7 @@ dependencies = [
"once_cell",
"regex",
"rowan",
+ "rustc-hash",
"syntax",
]
@@ -1305,6 +1321,12 @@ dependencies = [
]
[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
+[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1366,18 +1388,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
-version = "1.0.159"
+version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
+checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.159"
+version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
+checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [
"proc-macro2",
"quote",
@@ -1386,9 +1408,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.95"
+version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
dependencies = [
"itoa",
"ryu",
@@ -1456,9 +1478,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "smol_str"
-version = "0.1.24"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9"
+checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c"
dependencies = [
"serde",
]
@@ -1514,16 +1536,17 @@ dependencies = [
[[package]]
name = "texlab"
-version = "5.4.2"
+version = "5.5.0"
dependencies = [
"anyhow",
"assert_unordered",
"base-db",
+ "base-feature",
"citeproc",
- "clap 4.2.1",
+ "clap 4.2.2",
+ "commands",
"criterion",
"crossbeam-channel",
- "dashmap",
"dirs",
"distro",
"encoding_rs",
@@ -1531,7 +1554,6 @@ dependencies = [
"fern",
"flate2",
"fuzzy-matcher",
- "human_name",
"insta",
"itertools",
"log",
@@ -1551,7 +1573,6 @@ dependencies = [
"smol_str",
"syntax",
"tempfile",
- "thiserror",
"threadpool",
"titlecase",
]
diff --git a/support/texlab/crates/base-db/src/config.rs b/support/texlab/crates/base-db/src/config.rs
index 56ee5ee237..cf747d4ecd 100644
--- a/support/texlab/crates/base-db/src/config.rs
+++ b/support/texlab/crates/base-db/src/config.rs
@@ -1,7 +1,7 @@
use std::time::Duration;
+use parser::SyntaxConfig;
use regex::Regex;
-use rustc_hash::FxHashSet;
#[derive(Debug)]
pub struct Config {
@@ -12,6 +12,7 @@ pub struct Config {
pub synctex: Option<SynctexConfig>,
pub symbols: SymbolConfig,
pub syntax: SyntaxConfig,
+ pub completion: CompletionConfig,
}
#[derive(Debug)]
@@ -72,10 +73,16 @@ pub struct SymbolConfig {
}
#[derive(Debug)]
-pub struct SyntaxConfig {
- pub math_environments: FxHashSet<String>,
- pub enum_environments: FxHashSet<String>,
- pub verbatim_environments: FxHashSet<String>,
+pub struct CompletionConfig {
+ pub matcher: MatchingAlgo,
+}
+
+#[derive(Debug)]
+pub enum MatchingAlgo {
+ Skim,
+ SkimIgnoreCase,
+ Prefix,
+ PrefixIgnoreCase,
}
impl Default for Config {
@@ -88,6 +95,7 @@ impl Default for Config {
synctex: None,
symbols: SymbolConfig::default(),
syntax: SyntaxConfig::default(),
+ completion: CompletionConfig::default(),
}
}
}
@@ -157,77 +165,10 @@ impl Default for SymbolConfig {
}
}
-impl Default for SyntaxConfig {
+impl Default for CompletionConfig {
fn default() -> Self {
- let math_environments = DEFAULT_MATH_ENVIRONMENTS
- .iter()
- .copied()
- .map(String::from)
- .collect();
-
- let enum_environments = ["enumerate", "itemize", "description"]
- .into_iter()
- .map(String::from)
- .collect();
-
- let verbatim_environments = ["pycode", "minted", "asy", "lstlisting", "verbatim"]
- .into_iter()
- .map(String::from)
- .collect();
-
Self {
- math_environments,
- enum_environments,
- verbatim_environments,
+ matcher: MatchingAlgo::SkimIgnoreCase,
}
}
}
-
-static DEFAULT_MATH_ENVIRONMENTS: &[&str] = &[
- "align",
- "align*",
- "alignat",
- "alignat*",
- "aligned",
- "aligned*",
- "alignedat",
- "alignedat*",
- "array",
- "array*",
- "Bmatrix",
- "Bmatrix*",
- "bmatrix",
- "bmatrix*",
- "cases",
- "cases*",
- "CD",
- "CD*",
- "eqnarray",
- "eqnarray*",
- "equation",
- "equation*",
- "IEEEeqnarray",
- "IEEEeqnarray*",
- "subequations",
- "subequations*",
- "gather",
- "gather*",
- "gathered",
- "gathered*",
- "matrix",
- "matrix*",
- "multline",
- "multline*",
- "pmatrix",
- "pmatrix*",
- "smallmatrix",
- "smallmatrix*",
- "split",
- "split*",
- "subarray",
- "subarray*",
- "Vmatrix",
- "Vmatrix*",
- "vmatrix",
- "vmatrix*",
-];
diff --git a/support/texlab/crates/base-db/src/document.rs b/support/texlab/crates/base-db/src/document.rs
index 39d9ec3cf7..c6148deaa8 100644
--- a/support/texlab/crates/base-db/src/document.rs
+++ b/support/texlab/crates/base-db/src/document.rs
@@ -53,7 +53,7 @@ impl Document {
let diagnostics = Vec::new();
let data = match language {
Language::Tex => {
- let green = parser::parse_latex(&text);
+ let green = parser::parse_latex(&text, &config.syntax);
let mut semantics = semantics::tex::Semantics::default();
semantics.process_root(&latex::SyntaxNode::new_root(green.clone()));
DocumentData::Tex(TexDocumentData { green, semantics })
@@ -63,7 +63,7 @@ impl Document {
DocumentData::Bib(BibDocumentData { green })
}
Language::Aux => {
- let green = parser::parse_latex(&text);
+ let green = parser::parse_latex(&text, &config.syntax);
let mut semantics = semantics::auxiliary::Semantics::default();
semantics.process_root(&latex::SyntaxNode::new_root(green.clone()));
DocumentData::Aux(AuxDocumentData { green, semantics })
diff --git a/support/texlab/crates/base-feature/Cargo.toml b/support/texlab/crates/base-feature/Cargo.toml
new file mode 100644
index 0000000000..efd9247f78
--- /dev/null
+++ b/support/texlab/crates/base-feature/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "base-feature"
+version = "0.0.0"
+license.workspace = true
+authors.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+rustc-hash = "1.1.0"
+url = "2.3.1"
+
+[lib]
+doctest = false
diff --git a/support/texlab/crates/base-feature/src/lib.rs b/support/texlab/crates/base-feature/src/lib.rs
new file mode 100644
index 0000000000..589ec20de4
--- /dev/null
+++ b/support/texlab/crates/base-feature/src/lib.rs
@@ -0,0 +1,4 @@
+mod normalize_uri;
+mod placeholders;
+
+pub use self::{normalize_uri::normalize_uri, placeholders::*};
diff --git a/support/texlab/crates/base-feature/src/normalize_uri.rs b/support/texlab/crates/base-feature/src/normalize_uri.rs
new file mode 100644
index 0000000000..041258e817
--- /dev/null
+++ b/support/texlab/crates/base-feature/src/normalize_uri.rs
@@ -0,0 +1,56 @@
+use url::Url;
+
+pub fn normalize_uri(uri: &mut Url) {
+ if let Some(mut segments) = uri.path_segments() {
+ if let Some(mut path) = segments.next().and_then(fix_drive_letter) {
+ for segment in segments {
+ path.push('/');
+ path.push_str(segment);
+ }
+
+ uri.set_path(&path);
+ }
+ }
+
+ uri.set_fragment(None);
+}
+
+fn fix_drive_letter(text: &str) -> Option<String> {
+ if !text.is_ascii() {
+ return None;
+ }
+
+ match &text[1..] {
+ ":" => Some(text.to_ascii_uppercase()),
+ "%3A" | "%3a" => Some(format!("{}:", text[0..1].to_ascii_uppercase())),
+ _ => None,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use url::Url;
+
+ use super::normalize_uri;
+
+ #[test]
+ fn test_lowercase_drive_letter() {
+ let mut uri = Url::parse("file://c:/foo/bar.txt").unwrap();
+ normalize_uri(&mut uri);
+ assert_eq!(uri.as_str(), "file:///C:/foo/bar.txt");
+ }
+
+ #[test]
+ fn test_uppercase_drive_letter() {
+ let mut uri = Url::parse("file://C:/foo/bar.txt").unwrap();
+ normalize_uri(&mut uri);
+ assert_eq!(uri.as_str(), "file:///C:/foo/bar.txt");
+ }
+
+ #[test]
+ fn test_fragment() {
+ let mut uri = Url::parse("foo:///bar/baz.txt#qux").unwrap();
+ normalize_uri(&mut uri);
+ assert_eq!(uri.as_str(), "foo:///bar/baz.txt");
+ }
+}
diff --git a/support/texlab/crates/base-feature/src/placeholders.rs b/support/texlab/crates/base-feature/src/placeholders.rs
new file mode 100644
index 0000000000..913af33edf
--- /dev/null
+++ b/support/texlab/crates/base-feature/src/placeholders.rs
@@ -0,0 +1,50 @@
+use rustc_hash::FxHashMap;
+
+pub fn replace_placeholders(args: &[String], pairs: &[(char, &str)]) -> Vec<String> {
+ let map = FxHashMap::from_iter(pairs.iter().copied());
+ args.iter()
+ .map(|input| {
+ let quoted = input
+ .strip_prefix('"')
+ .and_then(|input| input.strip_suffix('"'));
+
+ match quoted {
+ Some(output) => String::from(output),
+ None => {
+ let mut output = String::new();
+ let mut chars = input.chars();
+ while let Some(ch) = chars.next() {
+ if ch == '%' {
+ match chars.next() {
+ Some(key) => match map.get(&key) {
+ Some(value) => output.push_str(&value),
+ None => output.push(key),
+ },
+ None => output.push('%'),
+ };
+ } else {
+ output.push(ch);
+ }
+ }
+
+ output
+ }
+ }
+ })
+ .collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::replace_placeholders;
+
+ #[test]
+ fn test_quoted() {
+ let output = replace_placeholders(
+ &["foo".into(), "\"%f\"".into(), "%%f".into(), "%fbar".into()],
+ &[('f', "foo")],
+ );
+
+ assert_eq!(output, vec!["foo".into(), "%f", "%f".into(), "foobar"]);
+ }
+}
diff --git a/support/texlab/crates/commands/Cargo.toml b/support/texlab/crates/commands/Cargo.toml
new file mode 100644
index 0000000000..e5a6f81a95
--- /dev/null
+++ b/support/texlab/crates/commands/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "commands"
+version = "0.0.0"
+license.workspace = true
+authors.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+anyhow = "1.0.70"
+base-db = { path = "../base-db" }
+base-feature = { path = "../base-feature" }
+bstr = "1.4.0"
+crossbeam-channel = "0.5.8"
+itertools = "0.10.5"
+log = "0.4.17"
+rowan = "0.15.11"
+rustc-hash = "1.1.0"
+syntax = { path = "../syntax" }
+thiserror = "1.0.40"
+url = "2.3.1"
+
+[lib]
+doctest = false
diff --git a/support/texlab/crates/commands/src/build.rs b/support/texlab/crates/commands/src/build.rs
new file mode 100644
index 0000000000..9a115db888
--- /dev/null
+++ b/support/texlab/crates/commands/src/build.rs
@@ -0,0 +1,102 @@
+use std::{
+ io::{BufReader, Read},
+ path::{Path, PathBuf},
+ process::{ExitStatus, Stdio},
+ thread::{self, JoinHandle},
+};
+
+use anyhow::Result;
+use base_db::Workspace;
+use base_feature::replace_placeholders;
+use bstr::io::BufReadExt;
+use crossbeam_channel::Sender;
+use thiserror::Error;
+use url::Url;
+
+#[derive(Debug, Error)]
+pub enum BuildError {
+ #[error("Document \"{0}\" was not found")]
+ NotFound(Url),
+
+ #[error("Document \"{0}\" does not exist on the local file system")]
+ NotLocal(Url),
+
+ #[error("Unable to run compiler: {0}")]
+ Compile(#[from] std::io::Error),
+}
+
+#[derive(Debug)]
+pub struct BuildCommand {
+ program: String,
+ args: Vec<String>,
+ working_dir: PathBuf,
+}
+
+impl BuildCommand {
+ pub fn new(workspace: &Workspace, uri: &Url) -> Result<Self, BuildError> {
+ let Some(document) = workspace.lookup(uri) else {
+ return Err(BuildError::NotFound(uri.clone()));
+ };
+
+ let document = workspace
+ .parents(document)
+ .into_iter()
+ .next()
+ .unwrap_or(document);
+
+ let Some(path) = document.path.as_deref().and_then(Path::to_str) else {
+ return Err(BuildError::NotLocal(document.uri.clone()));
+ };
+
+ let config = &workspace.config().build;
+ let program = config.program.clone();
+ let args = replace_placeholders(&config.args, &[('f', path)]);
+
+ let Ok(working_dir) = workspace.current_dir(&document.dir).to_file_path() else {
+ return Err(BuildError::NotLocal(document.uri.clone()));
+ };
+
+ Ok(Self {
+ program,
+ args,
+ working_dir,
+ })
+ }
+
+ pub fn run(self, sender: Sender<String>) -> Result<ExitStatus, BuildError> {
+ log::debug!(
+ "Spawning compiler {} {:#?} in directory {}",
+ self.program,
+ self.args,
+ self.working_dir.display()
+ );
+
+ let mut process = std::process::Command::new(&self.program)
+ .args(self.args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .current_dir(&self.working_dir)
+ .spawn()?;
+
+ track_output(process.stderr.take().unwrap(), sender.clone());
+ track_output(process.stdout.take().unwrap(), sender);
+
+ let status = process.wait();
+ Ok(status?)
+ }
+}
+
+fn track_output(
+ output: impl Read + Send + 'static,
+ sender: Sender<String>,
+) -> JoinHandle<std::io::Result<()>> {
+ let mut reader = BufReader::new(output);
+ thread::spawn(move || {
+ reader.for_byte_line(|line| {
+ let text = String::from_utf8_lossy(line).into_owned();
+ let _ = sender.send(text);
+ Ok(true)
+ })
+ })
+}
diff --git a/support/texlab/crates/commands/src/change_env.rs b/support/texlab/crates/commands/src/change_env.rs
new file mode 100644
index 0000000000..2b1c1afc35
--- /dev/null
+++ b/support/texlab/crates/commands/src/change_env.rs
@@ -0,0 +1,40 @@
+use base_db::Document;
+use rowan::{ast::AstNode, TextRange, TextSize};
+use syntax::latex;
+
+#[derive(Debug)]
+pub struct ChangeEnvironmentResult<'a> {
+ pub begin: TextRange,
+ pub end: TextRange,
+ pub old_name: String,
+ pub new_name: &'a str,
+}
+
+pub fn change_environment<'a>(
+ document: &'a Document,
+ position: TextSize,
+ new_name: &'a str,
+) -> Option<ChangeEnvironmentResult<'a>> {
+ let root = document.data.as_tex()?.root_node();
+
+ let environment = root
+ .token_at_offset(position)
+ .right_biased()?
+ .parent_ancestors()
+ .find_map(latex::Environment::cast)?;
+
+ let begin = environment.begin()?.name()?.key()?;
+ let end = environment.end()?.name()?.key()?;
+
+ let old_name = begin.to_string();
+ if old_name != end.to_string() {
+ return None;
+ }
+
+ Some(ChangeEnvironmentResult {
+ begin: latex::small_range(&begin),
+ end: latex::small_range(&end),
+ old_name,
+ new_name,
+ })
+}
diff --git a/support/texlab/crates/commands/src/clean.rs b/support/texlab/crates/commands/src/clean.rs
new file mode 100644
index 0000000000..391be79c81
--- /dev/null
+++ b/support/texlab/crates/commands/src/clean.rs
@@ -0,0 +1,57 @@
+use std::process::Stdio;
+
+use anyhow::Result;
+use base_db::{Document, Workspace};
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+pub enum CleanTarget {
+ Auxiliary,
+ Artifacts,
+}
+
+#[derive(Debug)]
+pub struct CleanCommand {
+ executable: String,
+ args: Vec<String>,
+}
+
+impl CleanCommand {
+ pub fn new(workspace: &Workspace, document: &Document, target: CleanTarget) -> Result<Self> {
+ let Some(path) = document.path.as_deref() else {
+ anyhow::bail!("document '{}' is not a local file", document.uri)
+ };
+
+ let dir = workspace.current_dir(&document.dir);
+ let dir = workspace.output_dir(&dir).to_file_path().unwrap();
+
+ let flag = match target {
+ CleanTarget::Auxiliary => "-c",
+ CleanTarget::Artifacts => "-C",
+ };
+
+ let executable = String::from("latexmk");
+ let args = vec![
+ format!("-outdir={}", dir.display()),
+ String::from(flag),
+ path.display().to_string(),
+ ];
+
+ Ok(Self { executable, args })
+ }
+
+ pub fn run(self) -> Result<()> {
+ log::debug!("Cleaning output files: {} {:?}", self.executable, self.args);
+ let result = std::process::Command::new(self.executable)
+ .args(self.args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .status();
+
+ if let Err(why) = result {
+ anyhow::bail!("failed to spawn process: {why}")
+ }
+
+ Ok(())
+ }
+}
diff --git a/support/texlab/crates/texlab/src/features/workspace_command/dep_graph.rs b/support/texlab/crates/commands/src/dep_graph.rs
index faf20d301e..1388756ddf 100644
--- a/support/texlab/crates/texlab/src/features/workspace_command/dep_graph.rs
+++ b/support/texlab/crates/commands/src/dep_graph.rs
@@ -1,8 +1,8 @@
+use std::io::Write;
+
use anyhow::Result;
use base_db::{graph, Document, Workspace};
use itertools::Itertools;
-use std::io::Write;
-
use rustc_hash::FxHashMap;
pub fn show_dependency_graph(workspace: &Workspace) -> Result<String> {
diff --git a/support/texlab/crates/commands/src/fwd_search.rs b/support/texlab/crates/commands/src/fwd_search.rs
new file mode 100644
index 0000000000..eaa27e5013
--- /dev/null
+++ b/support/texlab/crates/commands/src/fwd_search.rs
@@ -0,0 +1,107 @@
+use std::{
+ ffi::OsStr,
+ path::{Path, PathBuf},
+ process::Stdio,
+};
+
+use anyhow::Result;
+use base_db::Workspace;
+use base_feature::replace_placeholders;
+use thiserror::Error;
+use url::Url;
+
+#[derive(Debug, Error)]
+pub enum ForwardSearchError {
+ #[error("Forward search is not configured")]
+ Unconfigured,
+
+ #[error("Document \"{0}\" does not exist on the local file system")]
+ NotLocal(Url),
+
+ #[error("Document \"{0}\" has an invalid file path")]
+ InvalidPath(Url),
+
+ #[error("TeX document \"{0}\" not found")]
+ TexNotFound(Url),
+
+ #[error("PDF document \"{0}\" not found")]
+ PdfNotFound(PathBuf),
+
+ #[error("Unable to launch PDF viewer: {0}")]
+ LaunchViewer(#[from] std::io::Error),
+}
+
+pub struct ForwardSearch {
+ program: String,
+ args: Vec<String>,
+}
+
+impl ForwardSearch {
+ pub fn new(
+ workspace: &Workspace,
+ uri: &Url,
+ line: Option<u32>,
+ ) -> Result<Self, ForwardSearchError> {
+ let Some(config) = &workspace.config().synctex else {
+ return Err(ForwardSearchError::Unconfigured);
+ };
+
+ let Some(child) = workspace.lookup(uri) else {
+ return Err(ForwardSearchError::TexNotFound(uri.clone()));
+ };
+
+ let parents = workspace.parents(child);
+ let parent = parents.into_iter().next().unwrap_or(child);
+ if parent.uri.scheme() != "file" {
+ return Err(ForwardSearchError::NotLocal(parent.uri.clone()));
+ }
+
+ let dir = workspace.current_dir(&parent.dir);
+ let dir = workspace.output_dir(&dir).to_file_path().unwrap();
+
+ let Some(tex_path) = &child.path else {
+ return Err(ForwardSearchError::InvalidPath(child.uri.clone()));
+ };
+
+ let Some(pdf_path) = parent.path
+ .as_deref()
+ .and_then(Path::file_stem)
+ .and_then(OsStr::to_str)
+ .map(|stem| dir.join(format!("{stem}.pdf"))) else
+ {
+ return Err(ForwardSearchError::InvalidPath(parent.uri.clone()));
+ };
+
+ if !pdf_path.exists() {
+ return Err(ForwardSearchError::PdfNotFound(pdf_path.clone()));
+ }
+
+ let tex_path = tex_path.to_string_lossy().into_owned();
+ let pdf_path = pdf_path.to_string_lossy().into_owned();
+ let line = line.unwrap_or_else(|| child.line_index.line_col(child.cursor).line);
+ let line = (line + 1).to_string();
+
+ let program = config.program.clone();
+ let args = replace_placeholders(
+ &config.args,
+ &[('f', &tex_path), ('p', &pdf_path), ('l', &line)],
+ );
+
+ Ok(Self { program, args })
+ }
+}
+
+impl ForwardSearch {
+ pub fn run(self) -> Result<(), ForwardSearchError> {
+ log::debug!("Executing forward search: {} {:?}", self.program, self.args);
+
+ std::process::Command::new(self.program)
+ .args(self.args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .status()?;
+
+ Ok(())
+ }
+}
diff --git a/support/texlab/crates/commands/src/lib.rs b/support/texlab/crates/commands/src/lib.rs
new file mode 100644
index 0000000000..6c8f1e8f4f
--- /dev/null
+++ b/support/texlab/crates/commands/src/lib.rs
@@ -0,0 +1,13 @@
+mod build;
+mod change_env;
+mod clean;
+mod dep_graph;
+mod fwd_search;
+
+pub use self::{
+ build::{BuildCommand, BuildError},
+ change_env::{change_environment, ChangeEnvironmentResult},
+ clean::{CleanCommand, CleanTarget},
+ dep_graph::show_dependency_graph,
+ fwd_search::{ForwardSearch, ForwardSearchError},
+};
diff --git a/support/texlab/crates/parser/Cargo.toml b/support/texlab/crates/parser/Cargo.toml
index 3f2efc0450..8e8b88841f 100644
--- a/support/texlab/crates/parser/Cargo.toml
+++ b/support/texlab/crates/parser/Cargo.toml
@@ -11,6 +11,7 @@ logos = "0.13.0"
once_cell = "1.17.1"
regex = "1.7.3"
rowan = "0.15.11"
+rustc-hash = "1.1.0"
syntax = { path = "../syntax" }
[dev-dependencies]
diff --git a/support/texlab/crates/parser/src/config.rs b/support/texlab/crates/parser/src/config.rs
new file mode 100644
index 0000000000..b6f3e8476c
--- /dev/null
+++ b/support/texlab/crates/parser/src/config.rs
@@ -0,0 +1,153 @@
+use rustc_hash::FxHashSet;
+
+#[derive(Debug)]
+pub struct SyntaxConfig {
+ pub math_environments: FxHashSet<String>,
+ pub enum_environments: FxHashSet<String>,
+ pub verbatim_environments: FxHashSet<String>,
+ pub citation_commands: FxHashSet<String>,
+}
+
+impl Default for SyntaxConfig {
+ fn default() -> Self {
+ let math_environments = DEFAULT_MATH_ENVIRONMENTS
+ .iter()
+ .map(ToString::to_string)
+ .collect();
+
+ let enum_environments = DEFAULT_ENUM_ENVIRONMENTS
+ .iter()
+ .map(ToString::to_string)
+ .collect();
+
+ let verbatim_environments = DEFAULT_VERBATIM_ENVIRONMENTS
+ .iter()
+ .map(ToString::to_string)
+ .collect();
+
+ let citation_commands = DEFAULT_CITATION_COMMANDS
+ .iter()
+ .map(ToString::to_string)
+ .collect();
+
+ Self {
+ math_environments,
+ enum_environments,
+ verbatim_environments,
+ citation_commands,
+ }
+ }
+}
+
+static DEFAULT_MATH_ENVIRONMENTS: &[&str] = &[
+ "align",
+ "align*",
+ "alignat",
+ "alignat*",
+ "aligned",
+ "aligned*",
+ "alignedat",
+ "alignedat*",
+ "array",
+ "array*",
+ "Bmatrix",
+ "Bmatrix*",
+ "bmatrix",
+ "bmatrix*",
+ "cases",
+ "cases*",
+ "CD",
+ "CD*",
+ "eqnarray",
+ "eqnarray*",
+ "equation",
+ "equation*",
+ "IEEEeqnarray",
+ "IEEEeqnarray*",
+ "subequations",
+ "subequations*",
+ "gather",
+ "gather*",
+ "gathered",
+ "gathered*",
+ "matrix",
+ "matrix*",
+ "multline",
+ "multline*",
+ "pmatrix",
+ "pmatrix*",
+ "smallmatrix",
+ "smallmatrix*",
+ "split",
+ "split*",
+ "subarray",
+ "subarray*",
+ "Vmatrix",
+ "Vmatrix*",
+ "vmatrix",
+ "vmatrix*",
+];
+
+static DEFAULT_ENUM_ENVIRONMENTS: &[&str] = &["enumerate", "itemize", "description"];
+
+static DEFAULT_VERBATIM_ENVIRONMENTS: &[&str] =
+ &["pycode", "minted", "asy", "lstlisting", "verbatim"];
+
+static DEFAULT_CITATION_COMMANDS: &[&str] = &[
+ "cite",
+ "cite*",
+ "Cite",
+ "nocite",
+ "citet",
+ "citet*",
+ "citep",
+ "citep*",
+ "citeauthor",
+ "citeauthor*",
+ "Citeauthor",
+ "Citeauthor*",
+ "citetitle",
+ "citetitle*",
+ "citeyear",
+ "citeyear*",
+ "citedate",
+ "citedate*",
+ "citeurl",
+ "fullcite",
+ "citeyearpar",
+ "citealt",
+ "citealp",
+ "citetext",
+ "parencite",
+ "parencite*",
+ "Parencite",
+ "footcite",
+ "footfullcite",
+ "footcitetext",
+ "textcite",
+ "Textcite",
+ "smartcite",
+ "supercite",
+ "autocite",
+ "autocite*",
+ "Autocite",
+ "Autocite*",
+ "volcite",
+ "Volcite",
+ "pvolcite",
+ "Pvolcite",
+ "fvolcite",
+ "ftvolcite",
+ "svolcite",
+ "Svolcite",
+ "tvolcite",
+ "Tvolcite",
+ "avolcite",
+ "Avolcite",
+ "notecite",
+ "pnotecite",
+ "Pnotecite",
+ "fnotecite",
+ "citeA",
+ "citeA*",
+];
diff --git a/support/texlab/crates/parser/src/latex.rs b/support/texlab/crates/parser/src/latex.rs
index 52e164a349..f3e978a93b 100644
--- a/support/texlab/crates/parser/src/latex.rs
+++ b/support/texlab/crates/parser/src/latex.rs
@@ -3,6 +3,8 @@ mod lexer;
use rowan::{GreenNode, GreenNodeBuilder};
use syntax::latex::SyntaxKind::{self, *};
+use crate::SyntaxConfig;
+
use self::lexer::{
types::{CommandName, SectionLevel, Token},
Lexer,
@@ -30,9 +32,9 @@ struct Parser<'a> {
}
impl<'a> Parser<'a> {
- pub fn new(text: &'a str) -> Self {
+ pub fn new(text: &'a str, config: &SyntaxConfig) -> Self {
Self {
- lexer: Lexer::new(text),
+ lexer: Lexer::new(text, config),
builder: GreenNodeBuilder::new(),
}
}
@@ -1116,21 +1118,23 @@ impl<'a> Parser<'a> {
}
}
-pub fn parse_latex(text: &str) -> GreenNode {
- Parser::new(text).parse()
+pub fn parse_latex(text: &str, config: &SyntaxConfig) -> GreenNode {
+ Parser::new(text, config).parse()
}
#[cfg(test)]
mod tests {
use syntax::latex;
+ use crate::SyntaxConfig;
+
use super::parse_latex;
#[test]
fn test_parse() {
insta::glob!("test_data/latex/{,**/}*.txt", |path| {
let text = std::fs::read_to_string(path).unwrap().replace("\r\n", "\n");
- let root = latex::SyntaxNode::new_root(parse_latex(&text));
+ let root = latex::SyntaxNode::new_root(parse_latex(&text, &SyntaxConfig::default()));
insta::assert_debug_snapshot!(root);
});
}
diff --git a/support/texlab/crates/parser/src/latex/lexer.rs b/support/texlab/crates/parser/src/latex/lexer.rs
index c0129ed3ba..e53a2ed042 100644
--- a/support/texlab/crates/parser/src/latex/lexer.rs
+++ b/support/texlab/crates/parser/src/latex/lexer.rs
@@ -4,6 +4,8 @@ pub(super) mod types;
use logos::Logos;
use syntax::latex::SyntaxKind;
+use crate::SyntaxConfig;
+
use self::types::{CommandName, Token};
#[derive(Debug, PartialEq, Eq, Clone)]
@@ -12,8 +14,8 @@ pub struct Lexer<'a> {
}
impl<'a> Lexer<'a> {
- pub fn new(input: &'a str) -> Self {
- let mut tokens = tokenize(input);
+ pub fn new(input: &'a str, config: &SyntaxConfig) -> Self {
+ let mut tokens = tokenize(input, config);
tokens.reverse();
Self { tokens }
}
@@ -45,7 +47,7 @@ impl<'a> Lexer<'a> {
}
}
-fn tokenize(input: &str) -> Vec<(Token, &str)> {
+fn tokenize<'a>(input: &'a str, config: &SyntaxConfig) -> Vec<(Token, &'a str)> {
let mut lexer = Token::lexer(input);
std::iter::from_fn(move || {
let kind = lexer.next()?.unwrap();
@@ -54,7 +56,7 @@ fn tokenize(input: &str) -> Vec<(Token, &str)> {
})
.map(|(kind, text)| {
if kind == Token::CommandName(CommandName::Generic) {
- let name = commands::classify(&text[1..]);
+ let name = commands::classify(&text[1..], config);
(Token::CommandName(name), text)
} else {
(kind, text)
diff --git a/support/texlab/crates/parser/src/latex/lexer/commands.rs b/support/texlab/crates/parser/src/latex/lexer/commands.rs
index ba609f5d7f..8354fa1bfc 100644
--- a/support/texlab/crates/parser/src/latex/lexer/commands.rs
+++ b/support/texlab/crates/parser/src/latex/lexer/commands.rs
@@ -1,6 +1,8 @@
+use crate::SyntaxConfig;
+
use super::types::{CommandName, SectionLevel};
-pub fn classify(name: &str) -> CommandName {
+pub fn classify(name: &str, config: &SyntaxConfig) -> CommandName {
match name {
"begin" => CommandName::BeginEnvironment,
"end" => CommandName::EndEnvironment,
@@ -15,16 +17,6 @@ pub fn classify(name: &str) -> CommandName {
"subparagraph" | "subparagraph*" => CommandName::Section(SectionLevel::Subparagraph),
"item" => CommandName::EnumItem,
"caption" => CommandName::Caption,
- "cite" | "cite*" | "Cite" | "nocite" | "citet" | "citet*" | "citep" | "citep*"
- | "citeauthor" | "citeauthor*" | "Citeauthor" | "Citeauthor*" | "citetitle"
- | "citetitle*" | "citeyear" | "citeyear*" | "citedate" | "citedate*" | "citeurl"
- | "fullcite" | "citeyearpar" | "citealt" | "citealp" | "citetext" | "parencite"
- | "parencite*" | "Parencite" | "footcite" | "footfullcite" | "footcitetext"
- | "textcite" | "Textcite" | "smartcite" | "supercite" | "autocite" | "autocite*"
- | "Autocite" | "Autocite*" | "volcite" | "Volcite" | "pvolcite" | "Pvolcite"
- | "fvolcite" | "ftvolcite" | "svolcite" | "Svolcite" | "tvolcite" | "Tvolcite"
- | "avolcite" | "Avolcite" | "notecite" | "pnotecite" | "Pnotecite" | "fnotecite"
- | "citeA" | "citeA*" => CommandName::Citation,
"usepackage" | "RequirePackage" => CommandName::PackageInclude,
"documentclass" => CommandName::ClassInclude,
"include" | "subfileinclude" | "input" | "subfile" => CommandName::LatexInclude,
@@ -88,6 +80,7 @@ pub fn classify(name: &str) -> CommandName {
"graphicspath" => CommandName::GraphicsPath,
"iffalse" => CommandName::BeginBlockComment,
"fi" => CommandName::EndBlockComment,
+ _ if config.citation_commands.contains(name) => CommandName::Citation,
_ => CommandName::Generic,
}
}
diff --git a/support/texlab/crates/parser/src/lib.rs b/support/texlab/crates/parser/src/lib.rs
index 51d56475cf..fab576a041 100644
--- a/support/texlab/crates/parser/src/lib.rs
+++ b/support/texlab/crates/parser/src/lib.rs
@@ -1,5 +1,6 @@
mod bibtex;
mod build_log;
+mod config;
mod latex;
-pub use self::{bibtex::parse_bibtex, build_log::parse_build_log, latex::parse_latex};
+pub use self::{bibtex::parse_bibtex, build_log::parse_build_log, config::*, latex::parse_latex};
diff --git a/support/texlab/crates/texlab/Cargo.toml b/support/texlab/crates/texlab/Cargo.toml
index 09fff5f6db..ebc7cd222d 100644
--- a/support/texlab/crates/texlab/Cargo.toml
+++ b/support/texlab/crates/texlab/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "texlab"
description = "LaTeX Language Server"
-version = "5.4.2"
+version = "5.5.0"
license.workspace = true
readme = "README.md"
authors.workspace = true
@@ -33,10 +33,11 @@ doctest = false
[dependencies]
anyhow = "1.0.70"
base-db = { path = "../base-db" }
+base-feature = { path = "../base-feature" }
citeproc = { path = "../citeproc" }
-clap = { version = "4.2.1", features = ["derive"] }
+clap = { version = "4.2.2", features = ["derive"] }
+commands = { path = "../commands" }
crossbeam-channel = "0.5.8"
-dashmap = "5.4.0"
dirs = "5.0.0"
distro = { path = "../distro" }
encoding_rs = "0.8.32"
@@ -44,7 +45,6 @@ encoding_rs_io = "0.1.7"
fern = "0.6.2"
flate2 = "1.0.25"
fuzzy-matcher = { version = "0.3.7", features = ["compact"] }
-human_name = { version = "2.0.1", default-features = false }
itertools = "0.10.5"
log = "0.4.17"
lsp-server = "0.7.0"
@@ -56,14 +56,13 @@ parser = { path = "../parser" }
regex = "1.7.3"
rowan = "0.15.11"
rustc-hash = "1.1.0"
-serde = "1.0.159"
-serde_json = "1.0.95"
+serde = "1.0.160"
+serde_json = "1.0.96"
serde_regex = "1.1.0"
serde_repr = "0.1.12"
-smol_str = { version = "0.1.24", features = ["serde"] }
+smol_str = { version = "0.2.0", features = ["serde"] }
syntax = { path = "../syntax" }
tempfile = "3.5.0"
-thiserror = "1.0.40"
threadpool = "1.8.1"
titlecase = "2.2.1"
diff --git a/support/texlab/crates/texlab/benches/bench_main.rs b/support/texlab/crates/texlab/benches/bench_main.rs
index d9e4937dbd..8e69e1e5a4 100644
--- a/support/texlab/crates/texlab/benches/bench_main.rs
+++ b/support/texlab/crates/texlab/benches/bench_main.rs
@@ -2,14 +2,15 @@ use base_db::{Owner, Workspace};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use distro::Language;
use lsp_types::{ClientCapabilities, Position, Url};
-use parser::parse_latex;
+use parser::{parse_latex, SyntaxConfig};
use rowan::TextSize;
const CODE: &str = include_str!("../../../texlab.tex");
fn criterion_benchmark(c: &mut Criterion) {
+ let config = SyntaxConfig::default();
c.bench_function("LaTeX/Parser", |b| {
- b.iter(|| parse_latex(black_box(CODE)));
+ b.iter(|| parse_latex(black_box(CODE), &config));
});
c.bench_function("LaTeX/Completion/Command", |b| {
diff --git a/support/texlab/crates/texlab/src/client.rs b/support/texlab/crates/texlab/src/client.rs
index 0439445092..9bbace9938 100644
--- a/support/texlab/crates/texlab/src/client.rs
+++ b/support/texlab/crates/texlab/src/client.rs
@@ -5,9 +5,10 @@ use std::sync::{
use anyhow::{bail, Result};
use crossbeam_channel::Sender;
-use dashmap::DashMap;
use lsp_server::{ErrorCode, Message, Request, RequestId, Response};
use lsp_types::{notification::ShowMessage, MessageType, ShowMessageParams};
+use parking_lot::Mutex;
+use rustc_hash::FxHashMap;
use serde::{de::DeserializeOwned, Serialize};
use crate::server::options::Options;
@@ -16,7 +17,7 @@ use crate::server::options::Options;
struct RawClient {
sender: Sender<Message>,
next_id: AtomicI32,
- pending: DashMap<RequestId, Sender<Response>>,
+ pending: Mutex<FxHashMap<RequestId, Sender<Response>>>,
}
#[derive(Debug, Clone)]
@@ -29,7 +30,7 @@ impl LspClient {
let raw = Arc::new(RawClient {
sender,
next_id: AtomicI32::new(1),
- pending: DashMap::default(),
+ pending: Default::default(),
});
Self { raw }
@@ -55,7 +56,7 @@ impl LspClient {
let id = RequestId::from(self.raw.next_id.fetch_add(1, Ordering::SeqCst));
let (tx, rx) = crossbeam_channel::bounded(1);
- self.raw.pending.insert(id.clone(), tx);
+ self.raw.pending.lock().insert(id.clone(), tx);
self.raw
.sender
@@ -81,9 +82,10 @@ impl LspClient {
}
pub fn recv_response(&self, response: lsp_server::Response) -> Result<()> {
- let (_, tx) = self
+ let tx = self
.raw
.pending
+ .lock()
.remove(&response.id)
.expect("response with known request id received");
diff --git a/support/texlab/crates/texlab/src/features.rs b/support/texlab/crates/texlab/src/features.rs
index 0d82bcb4f4..9115580ce0 100644
--- a/support/texlab/crates/texlab/src/features.rs
+++ b/support/texlab/crates/texlab/src/features.rs
@@ -1,9 +1,7 @@
-pub mod build;
pub mod completion;
pub mod definition;
pub mod folding;
pub mod formatting;
-pub mod forward_search;
pub mod highlight;
pub mod hover;
pub mod inlay_hint;
@@ -11,4 +9,3 @@ pub mod link;
pub mod reference;
pub mod rename;
pub mod symbol;
-pub mod workspace_command;
diff --git a/support/texlab/crates/texlab/src/features/build.rs b/support/texlab/crates/texlab/src/features/build.rs
deleted file mode 100644
index a37fe65bf8..0000000000
--- a/support/texlab/crates/texlab/src/features/build.rs
+++ /dev/null
@@ -1,171 +0,0 @@
-mod progress;
-
-use std::{
- io::{BufRead, BufReader, Read},
- path::{Path, PathBuf},
- process::Stdio,
- thread::{self, JoinHandle},
-};
-
-use base_db::Workspace;
-use encoding_rs_io::DecodeReaderBytesBuilder;
-use lsp_types::{
- notification::LogMessage, ClientCapabilities, LogMessageParams, TextDocumentIdentifier, Url,
-};
-use serde::{Deserialize, Serialize};
-use serde_repr::{Deserialize_repr, Serialize_repr};
-
-use crate::{client::LspClient, util::capabilities::ClientCapabilitiesExt};
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct BuildParams {
- pub text_document: TextDocumentIdentifier,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct BuildResult {
- pub status: BuildStatus,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
-#[repr(i32)]
-pub enum BuildStatus {
- SUCCESS = 0,
- ERROR = 1,
- FAILURE = 2,
- CANCELLED = 3,
-}
-
-#[derive(Debug)]
-pub struct Command {
- uri: Url,
- progress: bool,
- program: String,
- args: Vec<String>,
- working_dir: PathBuf,
- client: LspClient,
-}
-
-impl Command {
- pub fn new(
- workspace: &Workspace,
- uri: Url,
- client: LspClient,
- client_capabilities: &ClientCapabilities,
- ) -> Option<Self> {
- let Some(document) = workspace
- .lookup(&uri)
- .map(|child| workspace.parents(child).into_iter().next().unwrap_or(child)) else { return None };
-
- let Some(path) = document.path.as_deref() else {
- log::warn!("Document {uri} cannot be compiled; skipping...");
- return None;
- };
-
- let config = &workspace.config().build;
- let program = config.program.clone();
- let args = config
- .args
- .iter()
- .map(|arg| replace_placeholder(arg, path))
- .collect();
-
- let working_dir = workspace.current_dir(&document.dir).to_file_path().ok()?;
-
- Some(Self {
- uri: document.uri.clone(),
- progress: client_capabilities.has_work_done_progress_support(),
- program,
- args,
- working_dir,
- client,
- })
- }
-
- pub fn run(self) -> BuildStatus {
- let reporter = if self.progress {
- let inner = progress::Reporter::new(&self.client);
- inner.start(&self.uri).expect("report progress");
- Some(inner)
- } else {
- None
- };
-
- let mut process = match std::process::Command::new(&self.program)
- .args(self.args)
- .stdin(Stdio::null())
- .stdout(Stdio::piped())
- .stderr(Stdio::piped())
- .current_dir(&self.working_dir)
- .spawn()
- {
- Ok(process) => process,
- Err(why) => {
- log::error!(
- "Failed to spawn process {:?} in directory {}: {}",
- self.program,
- self.working_dir.display(),
- why
- );
- return BuildStatus::FAILURE;
- }
- };
-
- let (sender, receiver) = crossbeam_channel::unbounded();
- track_output(process.stderr.take().unwrap(), sender.clone());
- track_output(process.stdout.take().unwrap(), sender.clone());
- let client = self.client.clone();
- let handle = std::thread::spawn(move || {
- let typ = lsp_types::MessageType::LOG;
-
- while let Ok(Some(message)) = receiver.recv() {
- let params = LogMessageParams { message, typ };
- let _ = client.send_notification::<LogMessage>(params);
- }
- });
-
- let status = process.wait().map_or(BuildStatus::FAILURE, |result| {
- if result.success() {
- BuildStatus::SUCCESS
- } else {
- BuildStatus::ERROR
- }
- });
-
- let _ = sender.send(None);
- handle.join().unwrap();
-
- drop(reporter);
- status
- }
-}
-
-fn track_output(
- output: impl Read + Send + 'static,
- sender: crossbeam_channel::Sender<Option<String>>,
-) -> JoinHandle<()> {
- let reader = BufReader::new(
- DecodeReaderBytesBuilder::new()
- .encoding(Some(encoding_rs::UTF_8))
- .utf8_passthru(true)
- .strip_bom(true)
- .build(output),
- );
-
- thread::spawn(move || {
- let _ = reader
- .lines()
- .flatten()
- .try_for_each(|line| sender.send(Some(line)));
- })
-}
-
-fn replace_placeholder(arg: &str, file: &Path) -> String {
- if arg.starts_with('"') || arg.ends_with('"') {
- arg.to_string()
- } else {
- arg.replace("%f", &file.to_string_lossy())
- }
-}
diff --git a/support/texlab/crates/texlab/src/features/build/progress.rs b/support/texlab/crates/texlab/src/features/build/progress.rs
deleted file mode 100644
index 6f235bebd6..0000000000
--- a/support/texlab/crates/texlab/src/features/build/progress.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-use std::sync::atomic::{AtomicI32, Ordering};
-
-use anyhow::Result;
-use lsp_types::{
- notification::Progress, request::WorkDoneProgressCreate, NumberOrString, ProgressParams,
- ProgressParamsValue, Url, WorkDoneProgress, WorkDoneProgressBegin,
- WorkDoneProgressCreateParams, WorkDoneProgressEnd,
-};
-
-use crate::client::LspClient;
-
-static NEXT_TOKEN: AtomicI32 = AtomicI32::new(1);
-
-pub struct Reporter<'a> {
- client: &'a LspClient,
- token: i32,
-}
-
-impl<'a> Reporter<'a> {
- pub fn new(client: &'a LspClient) -> Self {
- let token = NEXT_TOKEN.fetch_add(1, Ordering::SeqCst);
- Self { client, token }
- }
-
- pub fn start(&self, uri: &Url) -> Result<()> {
- self.client
- .send_request::<WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
- token: NumberOrString::Number(self.token),
- })?;
-
- self.client.send_notification::<Progress>(ProgressParams {
- token: NumberOrString::Number(self.token),
- value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(WorkDoneProgressBegin {
- title: "Building".to_string(),
- message: Some(uri.as_str().to_string()),
- cancellable: Some(false),
- percentage: None,
- })),
- })?;
-
- Ok(())
- }
-}
-
-impl<'a> Drop for Reporter<'a> {
- fn drop(&mut self) {
- let _ = self.client.send_notification::<Progress>(ProgressParams {
- token: NumberOrString::Number(self.token),
- value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(WorkDoneProgressEnd {
- message: None,
- })),
- });
- }
-}
diff --git a/support/texlab/crates/texlab/src/features/completion.rs b/support/texlab/crates/texlab/src/features/completion.rs
index 61528057a5..ab7cc4a1b6 100644
--- a/support/texlab/crates/texlab/src/features/completion.rs
+++ b/support/texlab/crates/texlab/src/features/completion.rs
@@ -13,6 +13,7 @@ mod glossary_ref;
mod import;
mod include;
mod label;
+mod matcher;
mod theorem;
mod tikz_library;
mod user_command;
diff --git a/support/texlab/crates/texlab/src/features/completion/builder.rs b/support/texlab/crates/texlab/src/features/completion/builder.rs
index a503fc6e1c..f37718a4ba 100644
--- a/support/texlab/crates/texlab/src/features/completion/builder.rs
+++ b/support/texlab/crates/texlab/src/features/completion/builder.rs
@@ -1,5 +1,5 @@
-use base_db::Document;
-use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
+use base_db::{Document, MatchingAlgo};
+use fuzzy_matcher::skim::SkimMatcherV2;
use itertools::Itertools;
use lsp_types::{
ClientCapabilities, ClientInfo, CompletionItem, CompletionItemKind, CompletionList,
@@ -23,12 +23,15 @@ use crate::util::{
lsp_enums::Structure,
};
-use super::COMPLETION_LIMIT;
+use super::{
+ matcher::{self, Matcher},
+ COMPLETION_LIMIT,
+};
pub struct CompletionBuilder<'a> {
context: &'a CursorContext<'a>,
items: Vec<Item<'a>>,
- matcher: SkimMatcherV2,
+ matcher: Box<dyn Matcher>,
text_pattern: String,
file_pattern: String,
preselect: Option<String>,
@@ -45,7 +48,13 @@ impl<'a> CompletionBuilder<'a> {
client_info: Option<&'a ClientInfo>,
) -> Self {
let items = Vec::new();
- let matcher = SkimMatcherV2::default().ignore_case();
+ let matcher: Box<dyn Matcher> = match context.workspace.config().completion.matcher {
+ MatchingAlgo::Skim => Box::new(SkimMatcherV2::default()),
+ MatchingAlgo::SkimIgnoreCase => Box::new(SkimMatcherV2::default().ignore_case()),
+ MatchingAlgo::Prefix => Box::new(matcher::Prefix),
+ MatchingAlgo::PrefixIgnoreCase => Box::new(matcher::PrefixIgnoreCase),
+ };
+
let text_pattern = match &context.cursor {
Cursor::Tex(token) if token.kind() == latex::COMMAND_NAME => {
if token.text_range().start() + TextSize::from(1) == context.offset {
@@ -124,7 +133,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn glossary_entry(&mut self, range: TextRange, name: String) -> Option<()> {
- let score = self.matcher.fuzzy_match(&name, &self.text_pattern)?;
+ let score = self.matcher.score(&name, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::GlossaryEntry { name },
@@ -141,7 +150,7 @@ impl<'a> CompletionBuilder<'a> {
name: &'a str,
image: Option<&'a str>,
) -> Option<()> {
- let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ let score = self.matcher.score(name, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::Argument { name, image },
@@ -154,7 +163,7 @@ impl<'a> CompletionBuilder<'a> {
pub fn begin_snippet(&mut self, range: TextRange) -> Option<()> {
if self.snippets {
- let score = self.matcher.fuzzy_match("begin", &self.text_pattern[1..])?;
+ let score = self.matcher.score("begin", &self.text_pattern[1..])?;
self.items.push(Item {
range,
data: Data::BeginSnippet,
@@ -187,7 +196,7 @@ impl<'a> CompletionBuilder<'a> {
.trim(),
);
- let score = self.matcher.fuzzy_match(&filter_text, &self.text_pattern)?;
+ let score = self.matcher.score(&filter_text, &self.text_pattern)?;
let data = Data::Citation {
document,
@@ -207,7 +216,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn color_model(&mut self, range: TextRange, name: &'a str) -> Option<()> {
- let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ let score = self.matcher.score(name, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::ColorModel { name },
@@ -219,7 +228,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn color(&mut self, range: TextRange, name: &'a str) -> Option<()> {
- let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ let score = self.matcher.score(name, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::Color { name },
@@ -238,7 +247,7 @@ impl<'a> CompletionBuilder<'a> {
glyph: Option<&'a str>,
file_names: &'a [SmolStr],
) -> Option<()> {
- let score = self.matcher.fuzzy_match(name, &self.text_pattern[1..])?;
+ let score = self.matcher.score(name, &self.text_pattern[1..])?;
let data = Data::ComponentCommand {
name,
image,
@@ -262,7 +271,7 @@ impl<'a> CompletionBuilder<'a> {
name: &'a str,
file_names: &'a [SmolStr],
) -> Option<()> {
- let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ let score = self.matcher.score(name, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::ComponentEnvironment { name, file_names },
@@ -280,7 +289,7 @@ impl<'a> CompletionBuilder<'a> {
) -> Option<()> {
let score = self
.matcher
- .fuzzy_match(&entry_type.name, &self.text_pattern[1..])?;
+ .score(&entry_type.name, &self.text_pattern[1..])?;
self.items.push(Item {
range,
@@ -293,7 +302,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn field(&mut self, range: TextRange, field: &'a BibtexFieldDoc) -> Option<()> {
- let score = self.matcher.fuzzy_match(&field.name, &self.text_pattern)?;
+ let score = self.matcher.score(&field.name, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::Field { field },
@@ -305,7 +314,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn class(&mut self, range: TextRange, name: &'a str) -> Option<()> {
- let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ let score = self.matcher.score(name, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::Class { name },
@@ -317,7 +326,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn package(&mut self, range: TextRange, name: &'a str) -> Option<()> {
- let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ let score = self.matcher.score(name, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::Package { name },
@@ -329,7 +338,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn file(&mut self, range: TextRange, name: String) -> Option<()> {
- let score = self.matcher.fuzzy_match(&name, &self.file_pattern)?;
+ let score = self.matcher.score(&name, &self.file_pattern)?;
self.items.push(Item {
range,
data: Data::File { name },
@@ -341,7 +350,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn directory(&mut self, range: TextRange, name: String) -> Option<()> {
- let score = self.matcher.fuzzy_match(&name, &self.file_pattern)?;
+ let score = self.matcher.score(&name, &self.file_pattern)?;
self.items.push(Item {
range,
data: Data::Directory { name },
@@ -361,7 +370,7 @@ impl<'a> CompletionBuilder<'a> {
footer: Option<&'a str>,
text: String,
) -> Option<()> {
- let score = self.matcher.fuzzy_match(&text, &self.text_pattern)?;
+ let score = self.matcher.score(&text, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::Label {
@@ -379,7 +388,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn tikz_library(&mut self, range: TextRange, name: &'a str) -> Option<()> {
- let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ let score = self.matcher.score(name, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::TikzLibrary { name },
@@ -391,7 +400,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn user_command(&mut self, range: TextRange, name: &'a str) -> Option<()> {
- let score = self.matcher.fuzzy_match(name, &self.text_pattern[1..])?;
+ let score = self.matcher.score(name, &self.text_pattern[1..])?;
self.items.push(Item {
range,
data: Data::UserCommand { name },
@@ -403,7 +412,7 @@ impl<'a> CompletionBuilder<'a> {
}
pub fn user_environment(&mut self, range: TextRange, name: &'a str) -> Option<()> {
- let score = self.matcher.fuzzy_match(name, &self.text_pattern)?;
+ let score = self.matcher.score(name, &self.text_pattern)?;
self.items.push(Item {
range,
data: Data::UserEnvironment { name },
diff --git a/support/texlab/crates/texlab/src/features/completion/matcher.rs b/support/texlab/crates/texlab/src/features/completion/matcher.rs
new file mode 100644
index 0000000000..a2b1a45e6a
--- /dev/null
+++ b/support/texlab/crates/texlab/src/features/completion/matcher.rs
@@ -0,0 +1,42 @@
+pub trait Matcher {
+ fn score(&mut self, choice: &str, pattern: &str) -> Option<i32>;
+}
+
+impl<T: fuzzy_matcher::FuzzyMatcher> Matcher for T {
+ fn score(&mut self, choice: &str, pattern: &str) -> Option<i32> {
+ fuzzy_matcher::FuzzyMatcher::fuzzy_match(self, choice, pattern)
+ }
+}
+
+#[derive(Debug)]
+pub struct Prefix;
+
+impl Matcher for Prefix {
+ fn score(&mut self, choice: &str, pattern: &str) -> Option<i32> {
+ if choice.starts_with(pattern) {
+ Some(-(choice.len() as i32))
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct PrefixIgnoreCase;
+
+impl Matcher for PrefixIgnoreCase {
+ fn score(&mut self, choice: &str, pattern: &str) -> Option<i32> {
+ if pattern.len() > choice.len() {
+ return None;
+ }
+
+ let mut cs = choice.chars();
+ for p in pattern.chars() {
+ if !cs.next().unwrap().eq_ignore_ascii_case(&p) {
+ return None;
+ }
+ }
+
+ return Some(-(choice.len() as i32));
+ }
+}
diff --git a/support/texlab/crates/texlab/src/features/forward_search.rs b/support/texlab/crates/texlab/src/features/forward_search.rs
deleted file mode 100644
index 019c08022e..0000000000
--- a/support/texlab/crates/texlab/src/features/forward_search.rs
+++ /dev/null
@@ -1,195 +0,0 @@
-use std::{
- io,
- path::{Path, PathBuf},
- process::Stdio,
-};
-
-use base_db::Workspace;
-use log::error;
-use lsp_types::{Position, Url};
-use thiserror::Error;
-
-use crate::util::line_index_ext::LineIndexExt;
-
-#[derive(Debug, Error)]
-pub enum Error {
- #[error("TeX document '{0}' not found")]
- TexNotFound(Url),
-
- #[error("TeX document '{0}' is invalid")]
- InvalidTexFile(Url),
-
- #[error("PDF document '{0}' not found")]
- PdfNotFound(PathBuf),
-
- #[error("TeX document '{0}' is not a local file")]
- NoLocalFile(Url),
-
- #[error("PDF viewer is not configured")]
- Unconfigured,
-
- #[error("Failed to spawn process: {0}")]
- Spawn(io::Error),
-}
-
-pub struct Command {
- program: String,
- args: Vec<String>,
-}
-
-impl Command {
- pub fn configure(
- workspace: &Workspace,
- uri: &Url,
- position: Option<Position>,
- ) -> Result<Self, Error> {
- let child = workspace
- .lookup(uri)
- .ok_or_else(|| Error::TexNotFound(uri.clone()))?;
-
- let parent = *workspace.parents(child).iter().next().unwrap_or(&child);
- if parent.uri.scheme() != "file" {
- return Err(Error::NoLocalFile(parent.uri.clone()));
- }
-
- let output_dir = workspace
- .output_dir(&workspace.current_dir(&parent.dir))
- .to_file_path()
- .unwrap();
-
- let tex_path = child
- .path
- .as_deref()
- .ok_or_else(|| Error::NoLocalFile(uri.clone()))?;
-
- let pdf_path = match parent
- .path
- .as_deref()
- .unwrap()
- .file_stem()
- .and_then(|stem| stem.to_str())
- {
- Some(stem) => output_dir.join(format!("{}.pdf", stem)),
- None => return Err(Error::InvalidTexFile(uri.clone())),
- };
-
- if !pdf_path.exists() {
- return Err(Error::PdfNotFound(pdf_path));
- }
-
- let position = position.unwrap_or_else(|| child.line_index.line_col_lsp(child.cursor));
-
- let Some(config) = &workspace.config().synctex else {
- return Err(Error::Unconfigured);
- };
-
- let program = config.program.clone();
-
- let args: Vec<_> = config
- .args
- .iter()
- .flat_map(|arg| replace_placeholder(tex_path, &pdf_path, position.line, arg))
- .collect();
-
- Ok(Self { program, args })
- }
-}
-
-impl Command {
- pub fn run(self) -> Result<(), Error> {
- log::debug!("Executing forward search: {} {:?}", self.program, self.args);
-
- std::process::Command::new(self.program)
- .args(self.args)
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .stderr(Stdio::null())
- .status()
- .map_err(Error::Spawn)?;
-
- Ok(())
- }
-}
-
-/// Iterate overs chunks of a string. Either returns a slice of the
-/// original string, or the placeholder replacement.
-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,
- line_number: u32,
- argument: &str,
-) -> Option<String> {
- let result = if argument.starts_with('"') || argument.ends_with('"') {
- argument.to_string()
- } else {
- 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/crates/texlab/src/features/workspace_command.rs b/support/texlab/crates/texlab/src/features/workspace_command.rs
deleted file mode 100644
index cac998125e..0000000000
--- a/support/texlab/crates/texlab/src/features/workspace_command.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub mod change_environment;
-pub mod clean;
-pub mod dep_graph;
diff --git a/support/texlab/crates/texlab/src/features/workspace_command/change_environment.rs b/support/texlab/crates/texlab/src/features/workspace_command/change_environment.rs
deleted file mode 100644
index a7d2eba6cf..0000000000
--- a/support/texlab/crates/texlab/src/features/workspace_command/change_environment.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-use std::collections::hash_map::HashMap;
-
-use anyhow::Result;
-use base_db::Workspace;
-use lsp_types::{ApplyWorkspaceEditParams, TextDocumentPositionParams, TextEdit, WorkspaceEdit};
-use rowan::ast::AstNode;
-use serde::{Deserialize, Serialize};
-use thiserror::Error;
-
-use crate::{
- normalize_uri,
- util::{cursor::CursorContext, line_index_ext::LineIndexExt},
-};
-
-fn change_environment_context(
- workspace: &Workspace,
- args: Vec<serde_json::Value>,
-) -> Result<CursorContext<Params>> {
- let params: ChangeEnvironmentParams = serde_json::from_value(
- args.into_iter()
- .next()
- .ok_or(ChangeEnvironmentError::MissingArg)?,
- )
- .map_err(ChangeEnvironmentError::InvalidArg)?;
-
- let mut uri = params.text_document_position.text_document.uri;
- normalize_uri(&mut uri);
- let position = params.text_document_position.position;
-
- CursorContext::new(
- workspace,
- &uri,
- position,
- Params {
- new_name: params.new_name,
- },
- )
- .ok_or(ChangeEnvironmentError::FailedCreatingContext.into())
-}
-
-pub fn change_environment(
- workspace: &Workspace,
- args: Vec<serde_json::Value>,
-) -> Option<((), ApplyWorkspaceEditParams)> {
- let context = change_environment_context(workspace, args).ok()?;
- let (beg, end) = context.find_environment()?;
-
- let beg_name = beg.to_string();
- let end_name = end.to_string();
-
- if beg_name != end_name {
- return None;
- }
- let new_name = &context.params.new_name;
-
- let line_index = &context.document.line_index;
- let mut changes = HashMap::default();
- changes.insert(
- context.document.uri.clone(),
- vec![
- TextEdit::new(
- line_index.line_col_lsp_range(beg.syntax().text_range()),
- new_name.clone(),
- ),
- TextEdit::new(
- line_index.line_col_lsp_range(end.syntax().text_range()),
- new_name.clone(),
- ),
- ],
- );
-
- Some((
- (),
- ApplyWorkspaceEditParams {
- label: Some(format!("change environment: {} -> {}", beg_name, new_name)),
- edit: WorkspaceEdit::new(changes),
- },
- ))
-}
-
-#[derive(Debug, Error)]
-pub enum ChangeEnvironmentError {
- #[error("rename parameters was not provided as an argument")]
- MissingArg,
-
- #[error("invalid argument: {0}")]
- InvalidArg(serde_json::Error),
-
- #[error("failed creating context")]
- FailedCreatingContext,
-
- #[error("could not create workspaces edit")]
- CouldNotCreateWorkspaceEdit,
-}
-
-#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct ChangeEnvironmentParams {
- #[serde(flatten)]
- pub text_document_position: TextDocumentPositionParams,
-
- pub new_name: String,
-}
-
-#[derive(Debug)]
-pub struct Params {
- new_name: String,
-}
diff --git a/support/texlab/crates/texlab/src/features/workspace_command/clean.rs b/support/texlab/crates/texlab/src/features/workspace_command/clean.rs
deleted file mode 100644
index d11c278d8c..0000000000
--- a/support/texlab/crates/texlab/src/features/workspace_command/clean.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-use std::process::Stdio;
-
-use anyhow::Result;
-use base_db::Workspace;
-use lsp_types::{TextDocumentIdentifier, Url};
-use thiserror::Error;
-
-use crate::normalize_uri;
-
-#[derive(Debug, Error)]
-pub enum CleanError {
- #[error("document '{0}' not found")]
- DocumentNotFound(Url),
-
- #[error("document '{0}' is not a local file")]
- NoLocalFile(Url),
-
- #[error("document was not provided as an argument")]
- MissingArg,
-
- #[error("invalid argument: {0}")]
- InvalidArg(serde_json::Error),
-
- #[error("failed to spawn process: {0}")]
- Spawn(std::io::Error),
-}
-
-#[derive(Debug)]
-pub struct CleanCommand {
- executable: String,
- args: Vec<String>,
-}
-
-impl CleanCommand {
- pub fn new(
- workspace: &Workspace,
- options: CleanOptions,
- args: Vec<serde_json::Value>,
- ) -> Result<Self> {
- let params: TextDocumentIdentifier =
- serde_json::from_value(args.into_iter().next().ok_or(CleanError::MissingArg)?)
- .map_err(CleanError::InvalidArg)?;
-
- let mut uri = params.uri;
- normalize_uri(&mut uri);
-
- let document = workspace
- .lookup(&uri)
- .ok_or_else(|| CleanError::DocumentNotFound(uri.clone()))?;
-
- let path = document
- .path
- .as_deref()
- .ok_or_else(|| CleanError::NoLocalFile(uri.clone()))?;
-
- let current_dir = workspace.current_dir(&document.dir);
-
- let output_dir = workspace.output_dir(&current_dir).to_file_path().unwrap();
-
- let flag = match options {
- CleanOptions::Auxiliary => "-c",
- CleanOptions::Artifacts => "-C",
- };
-
- let executable = "latexmk".to_string();
- let args = vec![
- format!("-outdir={}", output_dir.display()),
- flag.to_string(),
- path.display().to_string(),
- ];
-
- Ok(Self { executable, args })
- }
-
- pub fn run(self) -> Result<()> {
- log::debug!("Cleaning output files: {} {:?}", self.executable, self.args);
- std::process::Command::new(self.executable)
- .args(self.args)
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .stderr(Stdio::null())
- .status()
- .map_err(move |msg| anyhow::Error::new(CleanError::Spawn(msg)))?;
-
- Ok(())
- }
-}
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
-pub enum CleanOptions {
- Auxiliary,
- Artifacts,
-}
diff --git a/support/texlab/crates/texlab/src/lib.rs b/support/texlab/crates/texlab/src/lib.rs
index e17aab362b..19ef5ba8a9 100644
--- a/support/texlab/crates/texlab/src/lib.rs
+++ b/support/texlab/crates/texlab/src/lib.rs
@@ -4,30 +4,3 @@ mod server;
pub mod util;
pub use self::{client::LspClient, server::Server};
-
-pub(crate) fn normalize_uri(uri: &mut lsp_types::Url) {
- fn fix_drive_letter(text: &str) -> Option<String> {
- if !text.is_ascii() {
- return None;
- }
-
- match &text[1..] {
- ":" => Some(text.to_ascii_uppercase()),
- "%3A" | "%3a" => Some(format!("{}:", text[0..1].to_ascii_uppercase())),
- _ => None,
- }
- }
-
- if let Some(mut segments) = uri.path_segments() {
- if let Some(mut path) = segments.next().and_then(fix_drive_letter) {
- for segment in segments {
- path.push('/');
- path.push_str(segment);
- }
-
- uri.set_path(&path);
- }
- }
-
- uri.set_fragment(None);
-}
diff --git a/support/texlab/crates/texlab/src/server.rs b/support/texlab/crates/texlab/src/server.rs
index 0d52c41463..9cc0575e28 100644
--- a/support/texlab/crates/texlab/src/server.rs
+++ b/support/texlab/crates/texlab/src/server.rs
@@ -1,52 +1,59 @@
mod dispatch;
+mod extensions;
pub mod options;
+mod progress;
use std::{
+ collections::HashMap,
path::PathBuf,
- sync::{Arc, Mutex},
+ sync::{atomic::AtomicI32, Arc},
};
use anyhow::Result;
use base_db::{Config, Owner, Workspace};
+use base_feature::normalize_uri;
+use commands::{BuildCommand, CleanCommand, CleanTarget, ForwardSearch};
use crossbeam_channel::{Receiver, Sender};
use distro::{Distro, Language};
use lsp_server::{Connection, ErrorCode, Message, RequestId};
use lsp_types::{notification::*, request::*, *};
-use once_cell::sync::Lazy;
-use parking_lot::RwLock;
+use parking_lot::{Mutex, RwLock};
use rowan::{ast::AstNode, TextSize};
use rustc_hash::{FxHashMap, FxHashSet};
-use serde::{Deserialize, Serialize};
-use serde_repr::{Deserialize_repr, Serialize_repr};
+use serde::{de::DeserializeOwned, Serialize};
use syntax::bibtex;
use threadpool::ThreadPool;
use crate::{
client::LspClient,
features::{
- build::{self, BuildParams, BuildResult, BuildStatus},
completion::{self, builder::CompletionItemData},
- definition, folding, formatting, forward_search, highlight, hover, inlay_hint, link,
- reference, rename, symbol,
- workspace_command::{change_environment, clean, dep_graph},
+ definition, folding, formatting, highlight, hover, inlay_hint, link, reference, rename,
+ symbol,
},
- normalize_uri,
util::{
self, capabilities::ClientCapabilitiesExt, components::COMPONENT_DATABASE,
line_index_ext::LineIndexExt,
},
};
-use self::options::{Options, StartupOptions};
+use self::{
+ extensions::{
+ BuildParams, BuildRequest, BuildResult, BuildStatus, ForwardSearchRequest,
+ ForwardSearchResult, ForwardSearchStatus,
+ },
+ options::{Options, StartupOptions},
+ progress::ProgressReporter,
+};
#[derive(Debug)]
enum InternalMessage {
SetDistro(Distro),
SetOptions(Options),
FileEvent(notify::Event),
- ForwardSearch(Url),
Diagnostics,
ChktexResult(Url, Vec<lsp_types::Diagnostic>),
+ ForwardSearch(Url, Option<Position>),
}
pub struct Server {
@@ -67,6 +74,7 @@ impl Server {
let client = LspClient::new(connection.sender.clone());
let (internal_tx, internal_rx) = crossbeam_channel::unbounded();
let watcher = FileWatcher::new(internal_tx.clone()).expect("init file watcher");
+
Self {
connection: Arc::new(connection),
internal_tx,
@@ -94,27 +102,6 @@ impl Server {
});
}
- fn run_query_and_request<R, S, Q>(&self, id: RequestId, query: Q)
- where
- R: Request,
- S: Serialize,
- Q: FnOnce(&Workspace) -> Option<(S, R::Params)> + Send + 'static,
- {
- let client = self.client.clone();
- let workspace = Arc::clone(&self.workspace);
- self.pool.execute(move || match query(&workspace.read()) {
- Some((result, request_params)) => {
- let response = lsp_server::Response::new_ok(id, result);
- client.send_response(response).unwrap();
- client.send_request::<R>(request_params).unwrap();
- }
- None => {
- let response = lsp_server::Response::new_ok(id, Option::<S>::None);
- client.send_response(response).unwrap();
- }
- });
- }
-
fn run_fallible<R, Q>(&self, id: RequestId, query: Q)
where
R: Serialize,
@@ -428,7 +415,13 @@ impl Server {
normalize_uri(&mut uri);
if self.workspace.read().config().build.on_save {
- self.build_internal(uri.clone(), |_| ())?;
+ let text_document = TextDocumentIdentifier::new(uri.clone());
+ let params = BuildParams {
+ text_document,
+ position: None,
+ };
+
+ self.build(None, params)?;
}
self.publish_diagnostics_with_delay();
@@ -487,7 +480,7 @@ impl Server {
Ok(())
}
- fn completion(&mut self, id: RequestId, params: CompletionParams) -> Result<()> {
+ fn completion(&self, id: RequestId, params: CompletionParams) -> Result<()> {
let mut uri = params.text_document_position.text_document.uri;
normalize_uri(&mut uri);
let position = params.text_document_position.position;
@@ -633,29 +626,28 @@ impl Server {
Ok(())
}
- fn execute_command(&mut self, id: RequestId, params: ExecuteCommandParams) -> Result<()> {
+ fn execute_command(&self, id: RequestId, params: ExecuteCommandParams) -> Result<()> {
match params.command.as_str() {
"texlab.cleanAuxiliary" => {
- let workspace = self.workspace.read();
- let opt = clean::CleanOptions::Auxiliary;
- let command = clean::CleanCommand::new(&workspace, opt, params.arguments);
+ let command = self.prepare_clean_command(params, CleanTarget::Auxiliary);
self.run_fallible(id, || command?.run());
}
"texlab.cleanArtifacts" => {
- let workspace = self.workspace.read();
- let opt = clean::CleanOptions::Auxiliary;
- let command = clean::CleanCommand::new(&workspace, opt, params.arguments);
+ let command = self.prepare_clean_command(params, CleanTarget::Artifacts);
self.run_fallible(id, || command?.run());
}
"texlab.changeEnvironment" => {
- self.run_query_and_request::<ApplyWorkspaceEdit, _, _>(id, move |workspace| {
- change_environment::change_environment(workspace, params.arguments)
+ let client = self.client.clone();
+ let params = self.change_environment(params);
+ self.run_fallible(id, move || {
+ client.send_request::<ApplyWorkspaceEdit>(params?)
});
}
"texlab.showDependencyGraph" => {
- self.run_query(id, move |workspace| {
- dep_graph::show_dependency_graph(workspace).unwrap()
- });
+ let workspace = self.workspace.read();
+ let dot = commands::show_dependency_graph(&workspace).unwrap();
+ self.client
+ .send_response(lsp_server::Response::new_ok(id, dot))?;
}
_ => {
self.client
@@ -682,7 +674,7 @@ impl Server {
fn inlay_hint_resolve(&self, id: RequestId, hint: InlayHint) -> Result<()> {
let response = lsp_server::Response::new_ok(id, hint);
- self.connection.sender.send(response.into()).unwrap();
+ self.client.send_response(response)?;
Ok(())
}
@@ -694,99 +686,114 @@ impl Server {
Ok(())
}
- fn build(&mut self, id: RequestId, params: BuildParams) -> Result<()> {
+ fn build(&self, id: Option<RequestId>, params: BuildParams) -> Result<()> {
+ static LOCK: Mutex<()> = Mutex::new(());
+ static NEXT_TOKEN: AtomicI32 = AtomicI32::new(1);
+
let mut uri = params.text_document.uri;
normalize_uri(&mut uri);
+ let workspace = self.workspace.read();
+
let client = self.client.clone();
- self.build_internal(uri, move |status| {
- let result = BuildResult { status };
- let _ = client.send_response(lsp_server::Response::new_ok(id, result));
- })?;
- Ok(())
- }
+ let fwd_search_after = workspace.config().build.forward_search_after;
- fn build_internal(
- &mut self,
- uri: Url,
- callback: impl FnOnce(BuildStatus) + Send + 'static,
- ) -> Result<()> {
- static LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
+ let (sender, receiver) = crossbeam_channel::unbounded();
+ self.redirect_build_log(receiver);
- let workspace = self.workspace.read();
- let client = self.client.clone();
- let Some(compiler) = build::Command::new(&workspace, uri.clone(), client, &self.client_capabilities) else {
- callback(BuildStatus::FAILURE);
- return Ok(());
- };
+ let command = BuildCommand::new(&workspace, &uri);
+ let internal = self.internal_tx.clone();
+ let progress = self.client_capabilities.has_work_done_progress_support();
+ self.pool.execute(move || {
+ let guard = LOCK.lock();
- let forward_search_after = workspace.config().build.forward_search_after;
+ let progress_reporter = if progress {
+ let token = NEXT_TOKEN.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
+ Some(ProgressReporter::new(client.clone(), token, &uri))
+ } else {
+ None
+ };
- let sender = self.internal_tx.clone();
- self.pool.execute(move || {
- let guard = LOCK.lock().unwrap();
+ let status = match command.and_then(|command| command.run(sender)) {
+ Ok(status) if status.success() => BuildStatus::SUCCESS,
+ Ok(_) => BuildStatus::ERROR,
+ Err(why) => {
+ log::error!("Failed to compile document \"{uri}\": {why}");
+ BuildStatus::FAILURE
+ }
+ };
+
+ drop(progress_reporter);
+ drop(guard);
- let status = compiler.run();
- if forward_search_after {
- let _ = sender.send(InternalMessage::ForwardSearch(uri));
+ if let Some(id) = id {
+ let result = BuildResult { status };
+ let _ = client.send_response(lsp_server::Response::new_ok(id, result));
}
- drop(guard);
- callback(status);
+ if fwd_search_after {
+ let _ = internal.send(InternalMessage::ForwardSearch(uri, params.position));
+ }
});
Ok(())
}
- fn forward_search(&mut self, id: RequestId, params: TextDocumentPositionParams) -> Result<()> {
- let mut uri = params.text_document.uri;
- normalize_uri(&mut uri);
-
+ fn redirect_build_log(&self, receiver: Receiver<String>) {
let client = self.client.clone();
- self.forward_search_internal(uri, Some(params.position), move |status| {
- let result = ForwardSearchResult { status };
- let _ = client.send_response(lsp_server::Response::new_ok(id, result));
- })?;
-
- Ok(())
+ self.pool.execute(move || {
+ let typ = MessageType::LOG;
+ for message in receiver {
+ client
+ .send_notification::<LogMessage>(LogMessageParams { message, typ })
+ .unwrap();
+ }
+ });
}
- fn forward_search_internal(
- &mut self,
- uri: Url,
+ fn forward_search(
+ &self,
+ id: Option<RequestId>,
+ mut uri: Url,
position: Option<Position>,
- callback: impl FnOnce(ForwardSearchStatus) + Send + 'static,
) -> Result<()> {
- let workspace = self.workspace.read();
+ normalize_uri(&mut uri);
- let command = match forward_search::Command::configure(&workspace, &uri, position) {
- Ok(command) => command,
- Err(why) => {
- log::error!("Forward search failed: {}", why);
- callback(why.into());
- return Ok(());
- }
- };
+ let client = self.client.clone();
+ let command = ForwardSearch::new(
+ &self.workspace.read(),
+ &uri,
+ position.map(|position| position.line),
+ );
self.pool.execute(move || {
- let status = command
- .run()
- .map_or_else(ForwardSearchStatus::from, |()| ForwardSearchStatus::SUCCESS);
+ let status = match command.and_then(ForwardSearch::run) {
+ Ok(()) => ForwardSearchStatus::SUCCESS,
+ Err(why) => {
+ log::error!("Failed to execute forward search: {why}");
+ ForwardSearchStatus::from(why)
+ }
+ };
- callback(status);
+ if let Some(id) = id {
+ let result = ForwardSearchResult { status };
+ client
+ .send_response(lsp_server::Response::new_ok(id, result))
+ .unwrap();
+ }
});
Ok(())
}
- fn code_actions(&mut self, id: RequestId, _params: CodeActionParams) -> Result<()> {
+ fn code_actions(&self, id: RequestId, _params: CodeActionParams) -> Result<()> {
self.client
.send_response(lsp_server::Response::new_ok(id, Vec::<CodeAction>::new()))?;
Ok(())
}
- fn code_action_resolve(&mut self, id: RequestId, action: CodeAction) -> Result<()> {
+ fn code_action_resolve(&self, id: RequestId, action: CodeAction) -> Result<()> {
self.client
.send_response(lsp_server::Response::new_ok(id, action))?;
Ok(())
@@ -830,6 +837,77 @@ impl Server {
}
}
+ fn prepare_clean_command(
+ &self,
+ params: ExecuteCommandParams,
+ target: CleanTarget,
+ ) -> Result<CleanCommand> {
+ let workspace = self.workspace.read();
+ let mut params = self.parse_command_params::<TextDocumentIdentifier>(params.arguments)?;
+ normalize_uri(&mut params.uri);
+ let Some(document) = workspace.lookup(&params.uri) else {
+ anyhow::bail!("Document {} is not opened!", params.uri)
+ };
+
+ CleanCommand::new(&workspace, document, target)
+ }
+
+ fn change_environment(&self, params: ExecuteCommandParams) -> Result<ApplyWorkspaceEditParams> {
+ let workspace = self.workspace.read();
+ let params = self.parse_command_params::<RenameParams>(params.arguments)?;
+ let mut uri = params.text_document_position.text_document.uri;
+ normalize_uri(&mut uri);
+
+ let Some(document) = workspace.lookup(&uri) else {
+ anyhow::bail!("Document {} is not opened!", uri)
+ };
+
+ let line_index = &document.line_index;
+ let position = line_index.offset_lsp(params.text_document_position.position);
+
+ let Some(result) = commands::change_environment(document, position, &params.new_name) else {
+ anyhow::bail!("No environment found at the current position");
+ };
+
+ let range1 = line_index.line_col_lsp_range(result.begin);
+ let range2 = line_index.line_col_lsp_range(result.end);
+
+ let mut changes = HashMap::new();
+ changes.insert(
+ document.uri.clone(),
+ vec![
+ TextEdit::new(range1, params.new_name.clone()),
+ TextEdit::new(range2, params.new_name.clone()),
+ ],
+ );
+
+ let label = Some(format!(
+ "change environment: {} -> {}",
+ result.old_name, result.new_name
+ ));
+
+ let edit = WorkspaceEdit {
+ changes: Some(changes),
+ document_changes: None,
+ change_annotations: None,
+ };
+
+ Ok(ApplyWorkspaceEditParams { label, edit })
+ }
+
+ fn parse_command_params<T: DeserializeOwned>(
+ &self,
+ params: Vec<serde_json::Value>,
+ ) -> Result<T> {
+ if params.is_empty() {
+ anyhow::bail!("No argument provided!");
+ }
+
+ let value = params.into_iter().next().unwrap();
+ let value = serde_json::from_value(value)?;
+ Ok(value)
+ }
+
fn process_messages(&mut self) -> Result<()> {
loop {
crossbeam_channel::select! {
@@ -866,9 +944,9 @@ impl Server {
self.document_highlight(id, params)
})?
.on::<Formatting, _>(|id, params| self.formatting(id, params))?
- .on::<BuildRequest, _>(|id, params| self.build(id, params))?
+ .on::<BuildRequest, _>(|id, params| self.build(Some(id), params))?
.on::<ForwardSearchRequest, _>(|id, params| {
- self.forward_search(id, params)
+ self.forward_search(Some(id), params.text_document.uri, Some(params.position))
})?
.on::<ExecuteCommand,_>(|id, params| self.execute_command(id, params))?
.on::<SemanticTokensRangeRequest, _>(|id, params| {
@@ -888,7 +966,7 @@ impl Server {
})?
.default()
{
- self.connection.sender.send(response.into())?;
+ self.client.send_response(response)?;
}
}
Message::Notification(notification) => {
@@ -922,9 +1000,6 @@ impl Server {
InternalMessage::FileEvent(event) => {
self.handle_file_event(event);
}
- InternalMessage::ForwardSearch(uri) => {
- self.forward_search_internal(uri, None, |_| ())?;
- }
InternalMessage::Diagnostics => {
self.publish_diagnostics()?;
}
@@ -932,6 +1007,9 @@ impl Server {
self.chktex_diagnostics.insert(uri, diagnostics);
self.publish_diagnostics()?;
}
+ InternalMessage::ForwardSearch(uri, position) => {
+ self.forward_search(None, uri, position)?;
+ }
};
}
};
@@ -969,50 +1047,3 @@ impl FileWatcher {
workspace.watch(&mut self.watcher, &mut self.watched_dirs);
}
}
-
-struct BuildRequest;
-
-impl lsp_types::request::Request for BuildRequest {
- type Params = BuildParams;
-
- type Result = BuildResult;
-
- const METHOD: &'static str = "textDocument/build";
-}
-
-struct ForwardSearchRequest;
-
-impl lsp_types::request::Request for ForwardSearchRequest {
- type Params = TextDocumentPositionParams;
-
- type Result = ForwardSearchResult;
-
- const METHOD: &'static str = "textDocument/forwardSearch";
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
-#[repr(i32)]
-pub enum ForwardSearchStatus {
- SUCCESS = 0,
- ERROR = 1,
- FAILURE = 2,
- UNCONFIGURED = 3,
-}
-
-impl From<forward_search::Error> for ForwardSearchStatus {
- fn from(err: forward_search::Error) -> Self {
- match err {
- forward_search::Error::TexNotFound(_) => ForwardSearchStatus::FAILURE,
- forward_search::Error::InvalidTexFile(_) => ForwardSearchStatus::ERROR,
- forward_search::Error::PdfNotFound(_) => ForwardSearchStatus::ERROR,
- forward_search::Error::NoLocalFile(_) => ForwardSearchStatus::FAILURE,
- forward_search::Error::Unconfigured => ForwardSearchStatus::UNCONFIGURED,
- forward_search::Error::Spawn(_) => ForwardSearchStatus::ERROR,
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-pub struct ForwardSearchResult {
- pub status: ForwardSearchStatus,
-}
diff --git a/support/texlab/crates/texlab/src/server/extensions.rs b/support/texlab/crates/texlab/src/server/extensions.rs
new file mode 100644
index 0000000000..e8a2050cff
--- /dev/null
+++ b/support/texlab/crates/texlab/src/server/extensions.rs
@@ -0,0 +1,75 @@
+use commands::ForwardSearchError;
+use lsp_types::{Position, TextDocumentIdentifier, TextDocumentPositionParams};
+use serde::{Deserialize, Serialize};
+use serde_repr::{Deserialize_repr, Serialize_repr};
+
+pub struct BuildRequest;
+
+impl lsp_types::request::Request for BuildRequest {
+ type Params = BuildParams;
+
+ type Result = BuildResult;
+
+ const METHOD: &'static str = "textDocument/build";
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildParams {
+ pub text_document: TextDocumentIdentifier,
+
+ #[serde(default)]
+ pub position: Option<Position>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildResult {
+ pub status: BuildStatus,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
+#[repr(i32)]
+pub enum BuildStatus {
+ SUCCESS = 0,
+ ERROR = 1,
+ FAILURE = 2,
+ CANCELLED = 3,
+}
+
+pub struct ForwardSearchRequest;
+
+impl lsp_types::request::Request for ForwardSearchRequest {
+ type Params = TextDocumentPositionParams;
+
+ type Result = ForwardSearchResult;
+
+ const METHOD: &'static str = "textDocument/forwardSearch";
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
+#[repr(i32)]
+pub enum ForwardSearchStatus {
+ SUCCESS = 0,
+ ERROR = 1,
+ FAILURE = 2,
+ UNCONFIGURED = 3,
+}
+
+impl From<ForwardSearchError> for ForwardSearchStatus {
+ fn from(why: ForwardSearchError) -> Self {
+ match why {
+ ForwardSearchError::Unconfigured => ForwardSearchStatus::UNCONFIGURED,
+ ForwardSearchError::NotLocal(_) => ForwardSearchStatus::FAILURE,
+ ForwardSearchError::InvalidPath(_) => ForwardSearchStatus::ERROR,
+ ForwardSearchError::TexNotFound(_) => ForwardSearchStatus::FAILURE,
+ ForwardSearchError::PdfNotFound(_) => ForwardSearchStatus::ERROR,
+ ForwardSearchError::LaunchViewer(_) => ForwardSearchStatus::ERROR,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct ForwardSearchResult {
+ pub status: ForwardSearchStatus,
+}
diff --git a/support/texlab/crates/texlab/src/server/options.rs b/support/texlab/crates/texlab/src/server/options.rs
index a955905d3c..ba5b645402 100644
--- a/support/texlab/crates/texlab/src/server/options.rs
+++ b/support/texlab/crates/texlab/src/server/options.rs
@@ -20,6 +20,7 @@ pub struct Options {
pub symbols: SymbolOptions,
pub latexindent: LatexindentOptions,
pub forward_search: ForwardSearchOptions,
+ pub completion: CompletionOptions,
pub experimental: ExperimentalOptions,
}
@@ -111,6 +112,7 @@ pub struct ExperimentalOptions {
pub math_environments: Vec<String>,
pub enum_environments: Vec<String>,
pub verbatim_environments: Vec<String>,
+ pub citation_commands: Vec<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
@@ -120,6 +122,28 @@ pub struct StartupOptions {
pub skip_distro: bool,
}
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+#[serde(default)]
+pub struct CompletionOptions {
+ pub matcher: CompletionMatcher,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum CompletionMatcher {
+ Fuzzy,
+ FuzzyIgnoreCase,
+ Prefix,
+ PrefixIgnoreCase,
+}
+
+impl Default for CompletionMatcher {
+ fn default() -> Self {
+ Self::FuzzyIgnoreCase
+ }
+}
+
impl From<Options> for Config {
fn from(value: Options) -> Self {
let mut config = Config::default();
@@ -193,6 +217,13 @@ impl From<Options> for Config {
.map(|pattern| pattern.0)
.collect();
+ config.completion.matcher = match value.completion.matcher {
+ CompletionMatcher::Fuzzy => base_db::MatchingAlgo::Skim,
+ CompletionMatcher::FuzzyIgnoreCase => base_db::MatchingAlgo::SkimIgnoreCase,
+ CompletionMatcher::Prefix => base_db::MatchingAlgo::Prefix,
+ CompletionMatcher::PrefixIgnoreCase => base_db::MatchingAlgo::PrefixIgnoreCase,
+ };
+
config
.syntax
.math_environments
@@ -209,5 +240,10 @@ impl From<Options> for Config {
.extend(value.experimental.verbatim_environments);
config
+ .syntax
+ .citation_commands
+ .extend(value.experimental.citation_commands);
+
+ config
}
}
diff --git a/support/texlab/crates/texlab/src/server/progress.rs b/support/texlab/crates/texlab/src/server/progress.rs
new file mode 100644
index 0000000000..9a5dca9ffe
--- /dev/null
+++ b/support/texlab/crates/texlab/src/server/progress.rs
@@ -0,0 +1,44 @@
+use lsp_types::{
+ notification::Progress, request::WorkDoneProgressCreate, NumberOrString, ProgressParams,
+ ProgressParamsValue, Url, WorkDoneProgress, WorkDoneProgressBegin,
+ WorkDoneProgressCreateParams, WorkDoneProgressEnd,
+};
+
+use crate::LspClient;
+
+#[derive(Debug)]
+pub struct ProgressReporter {
+ client: LspClient,
+ token: i32,
+}
+
+impl Drop for ProgressReporter {
+ fn drop(&mut self) {
+ let _ = self.client.send_notification::<Progress>(ProgressParams {
+ token: NumberOrString::Number(self.token),
+ value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(WorkDoneProgressEnd {
+ message: None,
+ })),
+ });
+ }
+}
+
+impl ProgressReporter {
+ pub fn new(client: LspClient, token: i32, uri: &Url) -> Self {
+ let _ = client.send_request::<WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
+ token: NumberOrString::Number(token),
+ });
+
+ let _ = client.send_notification::<Progress>(ProgressParams {
+ token: NumberOrString::Number(token),
+ value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(WorkDoneProgressBegin {
+ title: "Building".into(),
+ message: Some(String::from(uri.as_str())),
+ cancellable: Some(false),
+ percentage: None,
+ })),
+ });
+
+ Self { client, token }
+ }
+}
diff --git a/support/texlab/texlab.1 b/support/texlab/texlab.1
index a34e068d09..a4a563f3fe 100644
--- a/support/texlab/texlab.1
+++ b/support/texlab/texlab.1
@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
-.TH TEXLAB "1" "April 2023" "texlab 5.4.2" "User Commands"
+.TH TEXLAB "1" "April 2023" "texlab 5.5.0" "User Commands"
.SH NAME
-texlab \- manual page for texlab 5.4.2
+texlab \- manual page for texlab 5.5.0
.SH SYNOPSIS
.B texlab
[\fI\,OPTIONS\/\fR]
diff --git a/support/texlab/texlab.pdf b/support/texlab/texlab.pdf
index 90d24c7a04..64e29c3d65 100644
--- a/support/texlab/texlab.pdf
+++ b/support/texlab/texlab.pdf
Binary files differ