summaryrefslogtreecommitdiff
path: root/support/texlab/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/features')
-rw-r--r--support/texlab/src/features/completion.rs36
-rw-r--r--support/texlab/src/features/completion/acronym_ref.rs16
-rw-r--r--support/texlab/src/features/completion/citation.rs74
-rw-r--r--support/texlab/src/features/completion/color.rs11
-rw-r--r--support/texlab/src/features/completion/color_model.rs31
-rw-r--r--support/texlab/src/features/completion/component_environment.rs20
-rw-r--r--support/texlab/src/features/completion/field.rs2
-rw-r--r--support/texlab/src/features/completion/glossary_ref.rs18
-rw-r--r--support/texlab/src/features/completion/import.rs13
-rw-r--r--support/texlab/src/features/completion/include.rs11
-rw-r--r--support/texlab/src/features/completion/label.rs60
-rw-r--r--support/texlab/src/features/completion/theorem.rs21
-rw-r--r--support/texlab/src/features/completion/tikz_library.rs12
-rw-r--r--support/texlab/src/features/completion/types.rs8
-rw-r--r--support/texlab/src/features/completion/user_environment.rs21
-rw-r--r--support/texlab/src/features/cursor.rs110
-rw-r--r--support/texlab/src/features/definition/entry.rs12
-rw-r--r--support/texlab/src/features/definition/label.rs13
-rw-r--r--support/texlab/src/features/formatting.rs6
-rw-r--r--support/texlab/src/features/formatting/bibtex_internal.rs2
-rw-r--r--support/texlab/src/features/formatting/latexindent.rs96
-rw-r--r--support/texlab/src/features/highlight.rs7
-rw-r--r--support/texlab/src/features/highlight/label.rs61
-rw-r--r--support/texlab/src/features/hover/label.rs4
-rw-r--r--support/texlab/src/features/reference/entry.rs10
-rw-r--r--support/texlab/src/features/reference/label.rs2
-rw-r--r--support/texlab/src/features/rename/entry.rs35
-rw-r--r--support/texlab/src/features/rename/label.rs37
-rw-r--r--support/texlab/src/features/symbol/bibtex.rs4
-rw-r--r--support/texlab/src/features/symbol/latex.rs22
30 files changed, 420 insertions, 355 deletions
diff --git a/support/texlab/src/features/completion.rs b/support/texlab/src/features/completion.rs
index 13c7aad22b..29559bd808 100644
--- a/support/texlab/src/features/completion.rs
+++ b/support/texlab/src/features/completion.rs
@@ -19,6 +19,8 @@ mod user_command;
mod user_environment;
mod util;
+use std::borrow::Cow;
+
use cancellation::CancellationToken;
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use lsp_types::{
@@ -144,15 +146,29 @@ fn dedup(items: Vec<InternalCompletionItem>) -> Vec<InternalCompletionItem> {
}
fn score(context: &CursorContext<CompletionParams>, items: &mut Vec<InternalCompletionItem>) {
- let pattern = match &context.cursor {
- Cursor::Latex(token) if token.kind().is_command_name() => token.text().trim_end(),
- Cursor::Latex(token) if token.kind() == latex::WORD => token.text(),
- Cursor::Latex(_) => "",
- Cursor::Bibtex(token) if token.kind().is_type() => token.text(),
- Cursor::Bibtex(token) if token.kind() == bibtex::WORD => token.text(),
- Cursor::Bibtex(token) if token.kind() == bibtex::COMMAND_NAME => token.text().trim_end(),
- Cursor::Bibtex(_) => "",
- Cursor::Nothing => "",
+ let pattern: Cow<str> = match &context.cursor {
+ Cursor::Latex(token) if token.kind().is_command_name() => token.text().trim_end().into(),
+ Cursor::Latex(token) if token.kind() == latex::WORD => {
+ if let Some(key) = latex::Key::cast(token.parent()) {
+ key.to_string().into()
+ } else {
+ token.text().into()
+ }
+ }
+ Cursor::Latex(_) => "".into(),
+ Cursor::Bibtex(token) if token.kind().is_type() => token.text().into(),
+ Cursor::Bibtex(token) if token.kind() == bibtex::WORD => {
+ if let Some(key) = bibtex::Key::cast(token.parent()) {
+ key.to_string().into()
+ } else {
+ token.text().into()
+ }
+ }
+ Cursor::Bibtex(token) if token.kind() == bibtex::COMMAND_NAME => {
+ token.text().trim_end().into()
+ }
+ Cursor::Bibtex(_) => "".into(),
+ Cursor::Nothing => "".into(),
};
let file_pattern = pattern.split('/').last().unwrap();
@@ -211,7 +227,7 @@ fn preselect(
let group = latex::CurlyGroupWord::cast(name.parent())?;
let end = latex::End::cast(group.syntax().parent()?)?;
let environment = latex::Environment::cast(end.syntax().parent()?)?;
- let name = environment.begin()?.name()?.word()?.text();
+ let name = environment.begin()?.name()?.key()?.to_string();
for item in items {
if item.data.label() == name {
diff --git a/support/texlab/src/features/completion/acronym_ref.rs b/support/texlab/src/features/completion/acronym_ref.rs
index 9d8bcc6c5f..db3e34f9eb 100644
--- a/support/texlab/src/features/completion/acronym_ref.rs
+++ b/support/texlab/src/features/completion/acronym_ref.rs
@@ -1,5 +1,4 @@
use cancellation::CancellationToken;
-use cstree::TextRange;
use lsp_types::CompletionParams;
use crate::{
@@ -16,18 +15,9 @@ pub fn complete_acronyms<'a>(
) -> Option<()> {
cancellation_token.result().ok()?;
- let token = context.cursor.as_latex()?;
- let group = latex::CurlyGroupWord::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
-
+ let (_, range, group) = context.find_curly_group_word()?;
latex::AcronymReference::cast(group.syntax().parent()?)?;
- let range = if token.kind() == latex::WORD {
- token.text_range()
- } else {
- TextRange::empty(context.offset)
- };
-
for document in &context.request.subset.documents {
if let Some(data) = document.data.as_latex() {
for name in data
@@ -35,8 +25,8 @@ pub fn complete_acronyms<'a>(
.descendants()
.filter_map(latex::AcronymDefinition::cast)
.filter_map(|node| node.name())
- .filter_map(|name| name.word())
- .map(|name| name.text())
+ .filter_map(|name| name.key())
+ .map(|name| name.to_string())
{
items.push(InternalCompletionItem::new(
range,
diff --git a/support/texlab/src/features/completion/citation.rs b/support/texlab/src/features/completion/citation.rs
index 6df3364099..0a7cc26b10 100644
--- a/support/texlab/src/features/completion/citation.rs
+++ b/support/texlab/src/features/completion/citation.rs
@@ -26,12 +26,14 @@ pub fn complete_citations<'a>(
let token = context.cursor.as_latex()?;
let range = if token.kind() == latex::WORD {
- token.text_range()
+ latex::Key::cast(token.parent())
+ .map(|key| key.small_range())
+ .or_else(|| latex::Text::cast(token.parent()).map(|text| text.small_range()))?
} else {
TextRange::empty(context.offset)
};
- check_citation(context, token).or_else(|| check_acronym(token))?;
+ check_citation(context).or_else(|| check_acronym(context))?;
for document in &context.request.subset.documents {
if let Some(data) = document.data.as_bibtex() {
for entry in data.root.children().filter_map(bibtex::Entry::cast) {
@@ -46,20 +48,17 @@ pub fn complete_citations<'a>(
Some(())
}
-fn check_citation(
- context: &CursorContext<CompletionParams>,
- token: &latex::SyntaxToken,
-) -> Option<()> {
- let group = latex::CurlyGroupWordList::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
+fn check_citation(context: &CursorContext<CompletionParams>) -> Option<()> {
+ let (_, _, group) = context.find_curly_group_word_list()?;
latex::Citation::cast(group.syntax().parent()?)?;
Some(())
}
-fn check_acronym(token: &latex::SyntaxToken) -> Option<()> {
+fn check_acronym(context: &CursorContext<CompletionParams>) -> Option<()> {
+ let token = context.cursor.as_latex()?;
+
let pair = token.ancestors().find_map(latex::KeyValuePair::cast)?;
- let mut key_words = pair.key()?.words();
- if key_words.next()?.text() != "cite" || key_words.next().is_some() {
+ if pair.key()?.to_string() != "cite" {
return None;
}
@@ -72,7 +71,7 @@ fn make_item<'a>(
entry: bibtex::Entry<'a>,
range: TextRange,
) -> Option<InternalCompletionItem<'a>> {
- let key = entry.key()?.text();
+ let key = entry.key()?.to_string();
let ty = LANGUAGE_DATA
.find_entry_type(&entry.ty()?.text()[1..])
.map(|ty| Structure::Entry(ty.category))
@@ -98,7 +97,7 @@ fn make_item<'a>(
range,
InternalCompletionItemData::Citation {
uri: Arc::clone(&document.uri),
- key: key.into(),
+ key,
text,
ty,
},
@@ -171,6 +170,29 @@ mod tests {
}
#[test]
+ fn test_latex_two_words() {
+ let request = FeatureTester::builder()
+ .files(vec![
+ ("main.tex", "\\addbibresource{main.bib}\n\\cite{foo}"),
+ ("main.bib", "@article{foo bar,}"),
+ ])
+ .main("main.tex")
+ .line(1)
+ .character(6)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_citations(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(!actual_items.is_empty());
+ for item in actual_items {
+ assert_eq!(item.range, TextRange::new(32.into(), 35.into()));
+ }
+ }
+
+ #[test]
fn test_latex_open_brace() {
let request = FeatureTester::builder()
.files(vec![
@@ -241,4 +263,30 @@ mod tests {
assert_eq!(item.range, TextRange::new(54.into(), 54.into()));
}
}
+
+ #[test]
+ fn test_latex_acronym_two_words() {
+ let request = FeatureTester::builder()
+ .files(vec![
+ (
+ "main.tex",
+ "\\addbibresource{main.bib}\n\\DeclareAcronym{foo}{cite={\na b}}",
+ ),
+ ("main.bib", "@article{foo,}"),
+ ])
+ .main("main.tex")
+ .line(2)
+ .character(0)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_citations(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(!actual_items.is_empty());
+ for item in actual_items {
+ assert_eq!(item.range, TextRange::new(54.into(), 57.into()));
+ }
+ }
}
diff --git a/support/texlab/src/features/completion/color.rs b/support/texlab/src/features/completion/color.rs
index 0e94af15fe..15a7c341f5 100644
--- a/support/texlab/src/features/completion/color.rs
+++ b/support/texlab/src/features/completion/color.rs
@@ -1,5 +1,4 @@
use cancellation::CancellationToken;
-use cstree::TextRange;
use lsp_types::CompletionParams;
use crate::{
@@ -17,15 +16,7 @@ pub fn complete_colors<'a>(
) -> Option<()> {
cancellation_token.result().ok()?;
- let token = context.cursor.as_latex()?;
- let range = if token.kind() == latex::WORD {
- token.text_range()
- } else {
- TextRange::empty(context.offset)
- };
-
- let group = latex::CurlyGroupWord::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
+ let (_, range, group) = context.find_curly_group_word()?;
latex::ColorReference::cast(group.syntax().parent()?)?;
for name in &LANGUAGE_DATA.colors {
diff --git a/support/texlab/src/features/completion/color_model.rs b/support/texlab/src/features/completion/color_model.rs
index f7f09b6367..a24953caa5 100644
--- a/support/texlab/src/features/completion/color_model.rs
+++ b/support/texlab/src/features/completion/color_model.rs
@@ -18,15 +18,7 @@ pub fn complete_color_models<'a>(
) -> Option<()> {
cancellation_token.result().ok()?;
- let token = context.cursor.as_latex()?;
- let range = if token.kind() == latex::WORD {
- token.text_range()
- } else {
- TextRange::empty(context.offset)
- };
-
- check_color_definition(context, token)
- .or_else(|| check_color_definition_set(context, token))?;
+ let range = check_color_definition(context).or_else(|| check_color_definition_set(context))?;
for name in MODEL_NAMES {
items.push(InternalCompletionItem::new(
@@ -38,30 +30,23 @@ pub fn complete_color_models<'a>(
Some(())
}
-fn check_color_definition(
- context: &CursorContext<CompletionParams>,
- token: &latex::SyntaxToken,
-) -> Option<()> {
- let group = latex::CurlyGroupWord::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
+fn check_color_definition(context: &CursorContext<CompletionParams>) -> Option<TextRange> {
+ let (_, range, group) = context.find_curly_group_word()?;
+
let definition = latex::ColorDefinition::cast(group.syntax().parent()?)?;
definition
.model()
.filter(|model| model.syntax().text_range() == group.syntax().text_range())?;
- Some(())
+ Some(range)
}
-fn check_color_definition_set(
- context: &CursorContext<CompletionParams>,
- token: &latex::SyntaxToken,
-) -> Option<()> {
- let group = latex::CurlyGroupWordList::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
+fn check_color_definition_set(context: &CursorContext<CompletionParams>) -> Option<TextRange> {
+ let (_, range, group) = context.find_curly_group_word_list()?;
let definition = latex::ColorSetDefinition::cast(group.syntax().parent()?)?;
definition
.model_list()
.filter(|model| model.syntax().text_range() == group.syntax().text_range())?;
- Some(())
+ Some(range)
}
#[cfg(test)]
diff --git a/support/texlab/src/features/completion/component_environment.rs b/support/texlab/src/features/completion/component_environment.rs
index fca9d03780..d4340a160a 100644
--- a/support/texlab/src/features/completion/component_environment.rs
+++ b/support/texlab/src/features/completion/component_environment.rs
@@ -1,12 +1,7 @@
use cancellation::CancellationToken;
-use cstree::TextRange;
use lsp_types::CompletionParams;
-use crate::{
- component_db::COMPONENT_DATABASE,
- features::cursor::CursorContext,
- syntax::{latex, CstNode},
-};
+use crate::{component_db::COMPONENT_DATABASE, features::cursor::CursorContext};
use super::types::{InternalCompletionItem, InternalCompletionItemData};
@@ -17,18 +12,7 @@ pub fn complete_component_environments<'a>(
) -> Option<()> {
cancellation_token.result().ok()?;
- let token = context.cursor.as_latex()?;
- let group = latex::CurlyGroupWord::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
- if !matches!(group.syntax().parent()?.kind(), latex::BEGIN | latex::END) {
- return None;
- }
-
- let range = if token.kind() == latex::WORD {
- token.text_range()
- } else {
- TextRange::empty(context.offset)
- };
+ let (_, range) = context.find_environment_name()?;
for component in COMPONENT_DATABASE.linked_components(&context.request.subset) {
cancellation_token.result().ok()?;
diff --git a/support/texlab/src/features/completion/field.rs b/support/texlab/src/features/completion/field.rs
index d7297b481b..ad1042b04d 100644
--- a/support/texlab/src/features/completion/field.rs
+++ b/support/texlab/src/features/completion/field.rs
@@ -26,7 +26,7 @@ pub fn complete_fields<'a>(
let parent = token.parent();
if let Some(entry) = bibtex::Entry::cast(parent) {
- if entry.key()?.text_range() == token.text_range() {
+ if entry.key()?.small_range() == token.text_range() {
return None;
}
} else {
diff --git a/support/texlab/src/features/completion/glossary_ref.rs b/support/texlab/src/features/completion/glossary_ref.rs
index 4529129fd7..66d20e46ff 100644
--- a/support/texlab/src/features/completion/glossary_ref.rs
+++ b/support/texlab/src/features/completion/glossary_ref.rs
@@ -1,5 +1,4 @@
use cancellation::CancellationToken;
-use cstree::TextRange;
use lsp_types::CompletionParams;
use crate::{
@@ -16,15 +15,8 @@ pub fn complete_glossary_entries<'a>(
) -> Option<()> {
cancellation_token.result().ok()?;
- let token = context.cursor.as_latex()?;
- let group = latex::CurlyGroupWord::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
+ let (_, range, group) = context.find_curly_group_word()?;
latex::GlossaryEntryReference::cast(group.syntax().parent()?)?;
- let range = if token.kind() == latex::WORD {
- token.text_range()
- } else {
- TextRange::empty(context.offset)
- };
for document in &context.request.subset.documents {
if let Some(data) = document.data.as_latex() {
@@ -33,8 +25,8 @@ pub fn complete_glossary_entries<'a>(
if let Some(name) = latex::GlossaryEntryDefinition::cast(node)
.and_then(|entry| entry.name())
- .and_then(|name| name.word())
- .map(|name| name.text())
+ .and_then(|name| name.key())
+ .map(|name| name.to_string())
{
items.push(InternalCompletionItem::new(
range,
@@ -42,8 +34,8 @@ pub fn complete_glossary_entries<'a>(
));
} else if let Some(name) = latex::AcronymDefinition::cast(node)
.and_then(|entry| entry.name())
- .and_then(|name| name.word())
- .map(|name| name.text())
+ .and_then(|name| name.key())
+ .map(|name| name.to_string())
{
items.push(InternalCompletionItem::new(
range,
diff --git a/support/texlab/src/features/completion/import.rs b/support/texlab/src/features/completion/import.rs
index ac5f2e6e40..da4c12c02b 100644
--- a/support/texlab/src/features/completion/import.rs
+++ b/support/texlab/src/features/completion/import.rs
@@ -1,5 +1,4 @@
use cancellation::CancellationToken;
-use cstree::TextRange;
use lsp_types::CompletionParams;
use rustc_hash::FxHashSet;
use smol_str::SmolStr;
@@ -17,15 +16,7 @@ pub fn complete_imports<'a>(
items: &mut Vec<InternalCompletionItem<'a>>,
cancellation_token: &CancellationToken,
) -> Option<()> {
- let token = context.cursor.as_latex()?;
- let range = if token.kind() == latex::WORD {
- token.text_range()
- } else {
- TextRange::empty(context.offset)
- };
-
- let group = latex::CurlyGroupWordList::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
+ let (_, range, group) = context.find_curly_group_word_list()?;
let (extension, mut factory): (
&str,
@@ -76,6 +67,8 @@ pub fn complete_imports<'a>(
#[cfg(test)]
mod tests {
+ use cstree::TextRange;
+
use crate::features::testing::FeatureTester;
use super::*;
diff --git a/support/texlab/src/features/completion/include.rs b/support/texlab/src/features/completion/include.rs
index c711ea5ce7..4a8e561866 100644
--- a/support/texlab/src/features/completion/include.rs
+++ b/support/texlab/src/features/completion/include.rs
@@ -25,15 +25,8 @@ pub fn complete_includes<'a>(
return None;
}
- let token = context.cursor.as_latex()?;
- let (path_text, path_range) = if token.kind() == latex::WORD {
- (token.text(), token.text_range())
- } else {
- ("", TextRange::empty(context.offset))
- };
+ let (path_text, path_range, group) = context.find_curly_group_word_list()?;
- let group = latex::CurlyGroupWordList::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
let include = group.syntax().parent()?;
let (include_extension, extensions): (bool, &[&str]) = match include.kind() {
latex::PACKAGE_INCLUDE => (false, &["sty"]),
@@ -62,7 +55,7 @@ pub fn complete_includes<'a>(
TextRange::new(start, path_range.end())
};
- let current_dir = current_dir(context, path_text)?;
+ let current_dir = current_dir(context, &path_text)?;
for entry in fs::read_dir(current_dir).ok()?.filter_map(Result::ok) {
let mut path = entry.path();
diff --git a/support/texlab/src/features/completion/label.rs b/support/texlab/src/features/completion/label.rs
index 2f7e2d8f77..3efb168d0d 100644
--- a/support/texlab/src/features/completion/label.rs
+++ b/support/texlab/src/features/completion/label.rs
@@ -18,26 +18,7 @@ pub fn complete_labels<'a>(
) -> Option<()> {
cancellation_token.result().ok()?;
- let token = context.cursor.as_latex()?;
- let is_math = latex::CurlyGroupWordList::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))
- .and_then(|group| group.syntax().parent())
- .and_then(|reference| latex::LabelReference::cast(reference))
- .and_then(|refernce| refernce.command())
- .map(|reference| reference.text() == "\\eqref")
- .or_else(|| {
- latex::CurlyGroupWord::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))
- .and_then(|group| group.syntax().parent())
- .and_then(|reference| latex::LabelReferenceRange::cast(reference))
- .map(|_| false)
- })?;
-
- let range = if token.kind() == latex::WORD {
- token.text_range()
- } else {
- TextRange::empty(context.offset)
- };
+ let (range, is_math) = find_reference(context).or_else(|| find_reference_range(context))?;
for document in &context.request.subset.documents {
if let Some(data) = document.data.as_latex() {
@@ -49,10 +30,10 @@ pub fn complete_labels<'a>(
{
if let Some(name) = label
.name()
- .and_then(|name| name.word())
- .map(|name| name.text())
+ .and_then(|name| name.key())
+ .map(|name| name.to_string())
{
- match render_label(&context.request.subset, name, Some(label)) {
+ match render_label(&context.request.subset, &name, Some(label)) {
Some(rendered_label) => {
let kind = match &rendered_label.object {
LabelledObject::Section { .. } => Structure::Section,
@@ -112,6 +93,19 @@ pub fn complete_labels<'a>(
Some(())
}
+fn find_reference(context: &CursorContext<CompletionParams>) -> Option<(TextRange, bool)> {
+ let (_, range, group) = context.find_curly_group_word_list()?;
+ let reference = latex::LabelReference::cast(group.syntax().parent()?)?;
+ let is_math = reference.command()?.text() == "\\eqref";
+ Some((range, is_math))
+}
+
+fn find_reference_range(context: &CursorContext<CompletionParams>) -> Option<(TextRange, bool)> {
+ let (_, range, group) = context.find_curly_group_word()?;
+ latex::LabelReferenceRange::cast(group.syntax().parent()?)?;
+ Some((range, false))
+}
+
#[cfg(test)]
mod tests {
use cstree::TextRange;
@@ -193,4 +187,24 @@ mod tests {
assert_eq!(item.range, TextRange::new(11.into(), 11.into()));
}
}
+
+ #[test]
+ fn test_multi_word() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.tex", "\\ref{foo}\\label{foo bar}")])
+ .main("main.tex")
+ .line(0)
+ .character(8)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_labels(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(!actual_items.is_empty());
+ for item in actual_items {
+ assert_eq!(item.range, TextRange::new(5.into(), 8.into()));
+ }
+ }
}
diff --git a/support/texlab/src/features/completion/theorem.rs b/support/texlab/src/features/completion/theorem.rs
index 54db031a4b..25eea87ab7 100644
--- a/support/texlab/src/features/completion/theorem.rs
+++ b/support/texlab/src/features/completion/theorem.rs
@@ -1,11 +1,7 @@
use cancellation::CancellationToken;
-use cstree::TextRange;
use lsp_types::CompletionParams;
-use crate::{
- features::cursor::CursorContext,
- syntax::{latex, CstNode},
-};
+use crate::features::cursor::CursorContext;
use super::types::{InternalCompletionItem, InternalCompletionItemData};
@@ -18,18 +14,7 @@ pub fn complete_theorem_environments<'a>(
return None;
}
- let token = context.cursor.as_latex()?;
- let group = latex::CurlyGroupWord::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
- if !matches!(group.syntax().parent()?.kind(), latex::BEGIN | latex::END) {
- return None;
- }
-
- let range = if token.kind() == latex::WORD {
- token.text_range()
- } else {
- TextRange::empty(context.offset)
- };
+ let (_, range) = context.find_environment_name()?;
for document in &context.request.subset.documents {
if let Some(data) = document.data.as_latex() {
@@ -49,6 +34,8 @@ pub fn complete_theorem_environments<'a>(
#[cfg(test)]
mod tests {
+ use cstree::TextRange;
+
use crate::features::testing::FeatureTester;
use super::*;
diff --git a/support/texlab/src/features/completion/tikz_library.rs b/support/texlab/src/features/completion/tikz_library.rs
index da2a26cc9b..b9c5e3ec7a 100644
--- a/support/texlab/src/features/completion/tikz_library.rs
+++ b/support/texlab/src/features/completion/tikz_library.rs
@@ -1,5 +1,4 @@
use cancellation::CancellationToken;
-use cstree::TextRange;
use lsp_types::CompletionParams;
use crate::{
@@ -17,15 +16,8 @@ pub fn complete_tikz_libraries<'a>(
) -> Option<()> {
cancellation_token.result().ok()?;
- let token = context.cursor.as_latex()?;
- let range = if token.kind() == latex::WORD {
- token.text_range()
- } else {
- TextRange::empty(context.offset)
- };
+ let (_, range, group) = context.find_curly_group_word_list()?;
- let group = latex::CurlyGroupWordList::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
let import = latex::TikzLibraryImport::cast(group.syntax().parent()?)?;
if import.command()?.text() == "\\usepgflibrary" {
@@ -49,6 +41,8 @@ pub fn complete_tikz_libraries<'a>(
#[cfg(test)]
mod tests {
+ use cstree::TextRange;
+
use crate::features::testing::FeatureTester;
use super::*;
diff --git a/support/texlab/src/features/completion/types.rs b/support/texlab/src/features/completion/types.rs
index b58a155fc4..b7e5c09592 100644
--- a/support/texlab/src/features/completion/types.rs
+++ b/support/texlab/src/features/completion/types.rs
@@ -40,7 +40,7 @@ pub enum InternalCompletionItemData<'a> {
BeginCommand,
Citation {
uri: Arc<Uri>,
- key: &'a str,
+ key: String,
text: String,
ty: Structure,
},
@@ -67,10 +67,10 @@ pub enum InternalCompletionItemData<'a> {
name: &'a str,
},
Acronym {
- name: &'a str,
+ name: String,
},
GlossaryEntry {
- name: &'a str,
+ name: String,
},
File {
name: SmolStr,
@@ -79,7 +79,7 @@ pub enum InternalCompletionItemData<'a> {
name: SmolStr,
},
Label {
- name: &'a str,
+ name: String,
kind: Structure,
header: Option<String>,
footer: Option<String>,
diff --git a/support/texlab/src/features/completion/user_environment.rs b/support/texlab/src/features/completion/user_environment.rs
index a1a5701fac..5529143adc 100644
--- a/support/texlab/src/features/completion/user_environment.rs
+++ b/support/texlab/src/features/completion/user_environment.rs
@@ -1,11 +1,7 @@
use cancellation::CancellationToken;
-use cstree::TextRange;
use lsp_types::CompletionParams;
-use crate::{
- features::cursor::CursorContext,
- syntax::{latex, CstNode},
-};
+use crate::features::cursor::CursorContext;
use super::types::{InternalCompletionItem, InternalCompletionItemData};
@@ -16,18 +12,7 @@ pub fn complete_user_environments<'a>(
) -> Option<()> {
cancellation_token.result().ok()?;
- let token = context.cursor.as_latex()?;
- let group = latex::CurlyGroupWord::cast(token.parent())
- .filter(|group| context.is_inside_latex_curly(group))?;
- if !matches!(group.syntax().parent()?.kind(), latex::BEGIN | latex::END) {
- return None;
- }
-
- let range = if token.kind() == latex::WORD {
- token.text_range()
- } else {
- TextRange::empty(context.offset)
- };
+ let (name, range) = context.find_environment_name()?;
for document in &context.request.subset.documents {
if let Some(data) = document.data.as_latex() {
@@ -35,7 +20,7 @@ pub fn complete_user_environments<'a>(
.extras
.environment_names
.iter()
- .filter(|name| name.as_str() != token.text())
+ .filter(|n| n.as_str() != name)
{
cancellation_token.result().ok()?;
items.push(InternalCompletionItem::new(
diff --git a/support/texlab/src/features/cursor.rs b/support/texlab/src/features/cursor.rs
index 19079b841b..6e8719987a 100644
--- a/support/texlab/src/features/cursor.rs
+++ b/support/texlab/src/features/cursor.rs
@@ -1,7 +1,7 @@
use cstree::{TextRange, TextSize};
use lsp_types::{
- CompletionParams, GotoDefinitionParams, HoverParams, Position, ReferenceParams, RenameParams,
- TextDocumentPositionParams,
+ CompletionParams, DocumentHighlightParams, GotoDefinitionParams, HoverParams, Position,
+ ReferenceParams, RenameParams, TextDocumentPositionParams,
};
use crate::{
@@ -42,6 +42,14 @@ impl Cursor {
return Some(Self::Latex(right));
}
+ if left.kind() == latex::WHITESPACE && left.parent().kind() == latex::KEY {
+ return Some(Self::Latex(left));
+ }
+
+ if right.kind() == latex::WHITESPACE && right.parent().kind() == latex::KEY {
+ return Some(Self::Latex(right));
+ }
+
Some(Self::Latex(right))
}
@@ -149,66 +157,116 @@ impl<P: HasPosition> CursorContext<P> {
group.small_range().contains(self.offset) || group.right_curly().is_none()
}
- pub fn find_citation_key_word(&self) -> Option<(&str, TextRange)> {
- let key = self
+ pub fn find_citation_key_word(&self) -> Option<(String, TextRange)> {
+ let word = self
.cursor
.as_latex()
.filter(|token| token.kind() == latex::WORD)?;
- let group = latex::CurlyGroupWordList::cast(key.parent())?;
+ let key = latex::Key::cast(word.parent())?;
+
+ let group = latex::CurlyGroupWordList::cast(key.syntax().parent()?)?;
latex::Citation::cast(group.syntax().parent()?)?;
- Some((key.text(), key.text_range()))
+ Some((key.to_string(), key.small_range()))
}
- pub fn find_citation_key_command(&self) -> Option<(&str, TextRange)> {
+ pub fn find_citation_key_command(&self) -> Option<(String, TextRange)> {
let command = self.cursor.as_latex()?;
let citation = latex::Citation::cast(command.parent())?;
- let key = citation.key_list()?.words().next()?;
- Some((key.text(), key.text_range()))
+ let key = citation.key_list()?.keys().next()?;
+ Some((key.to_string(), key.small_range()))
}
- pub fn find_entry_key(&self) -> Option<(&str, TextRange)> {
- let key = self
+ pub fn find_entry_key(&self) -> Option<(String, TextRange)> {
+ let word = self
.cursor
.as_bibtex()
.filter(|token| token.kind() == bibtex::WORD)?;
- bibtex::Entry::cast(key.parent())?;
- Some((key.text(), key.text_range()))
+ let key = bibtex::Key::cast(word.parent())?;
+
+ bibtex::Entry::cast(key.syntax().parent()?)?;
+ Some((key.to_string(), key.small_range()))
}
- pub fn find_label_name_word(&self) -> Option<(&str, TextRange)> {
+ pub fn find_label_name_key(&self) -> Option<(String, TextRange)> {
let name = self
.cursor
.as_latex()
.filter(|token| token.kind() == latex::WORD)?;
+ let key = latex::Key::cast(name.parent())?;
+
if matches!(
- name.parent().parent()?.kind(),
+ key.syntax().parent()?.parent()?.kind(),
latex::LABEL_DEFINITION | latex::LABEL_REFERENCE | latex::LABEL_REFERENCE_RANGE
) {
- Some((name.text(), name.text_range()))
+ Some((key.to_string(), key.small_range()))
} else {
None
}
}
- pub fn find_label_name_command(&self) -> Option<(&str, TextRange)> {
+ pub fn find_label_name_command(&self) -> Option<(String, TextRange)> {
let node = self.cursor.as_latex()?.parent();
if let Some(label) = latex::LabelDefinition::cast(node) {
- let name = label.name()?.word()?;
- Some((name.text(), name.text_range()))
+ let name = label.name()?.key()?;
+ Some((name.to_string(), name.small_range()))
} else if let Some(label) = latex::LabelReference::cast(node) {
- let name = label.name_list()?.words().next()?;
- Some((name.text(), name.text_range()))
+ let name = label.name_list()?.keys().next()?;
+ Some((name.to_string(), name.small_range()))
} else if let Some(label) = latex::LabelReferenceRange::cast(node) {
- let name = label.from()?.word()?;
- Some((name.text(), name.text_range()))
+ let name = label.from()?.key()?;
+ Some((name.to_string(), name.small_range()))
} else {
None
}
}
+
+ pub fn find_environment_name(&self) -> Option<(String, TextRange)> {
+ let (name, range, group) = self.find_curly_group_word()?;
+
+ if !matches!(group.syntax().parent()?.kind(), latex::BEGIN | latex::END) {
+ return None;
+ }
+
+ Some((name, range))
+ }
+
+ pub fn find_curly_group_word(&self) -> Option<(String, TextRange, latex::CurlyGroupWord)> {
+ let token = self.cursor.as_latex()?;
+ let key = latex::Key::cast(token.parent());
+
+ let group = key
+ .as_ref()
+ .and_then(|key| key.syntax().parent())
+ .unwrap_or(token.parent());
+
+ let group =
+ latex::CurlyGroupWord::cast(group).filter(|group| self.is_inside_latex_curly(group))?;
+
+ key.map(|key| (key.to_string(), key.small_range(), group))
+ .or_else(|| Some((String::new(), TextRange::empty(self.offset), group)))
+ }
+
+ pub fn find_curly_group_word_list(
+ &self,
+ ) -> Option<(String, TextRange, latex::CurlyGroupWordList)> {
+ let token = self.cursor.as_latex()?;
+ let key = latex::Key::cast(token.parent());
+
+ let group = key
+ .as_ref()
+ .and_then(|key| key.syntax().parent())
+ .unwrap_or(token.parent());
+
+ let group = latex::CurlyGroupWordList::cast(group)
+ .filter(|group| self.is_inside_latex_curly(group))?;
+
+ key.map(|key| (key.to_string(), key.small_range(), group))
+ .or_else(|| Some((String::new(), TextRange::empty(self.offset), group)))
+ }
}
pub trait HasPosition {
@@ -250,3 +308,9 @@ impl HasPosition for GotoDefinitionParams {
self.text_document_position_params.position
}
}
+
+impl HasPosition for DocumentHighlightParams {
+ fn position(&self) -> Position {
+ self.text_document_position_params.position
+ }
+}
diff --git a/support/texlab/src/features/definition/entry.rs b/support/texlab/src/features/definition/entry.rs
index 7f489ec848..b04490dc32 100644
--- a/support/texlab/src/features/definition/entry.rs
+++ b/support/texlab/src/features/definition/entry.rs
@@ -13,29 +13,31 @@ pub fn goto_entry_definition(
) -> Option<Vec<LocationLink>> {
let main_document = context.request.main_document();
- let key = context
+ let word = context
.cursor
.as_latex()
.filter(|token| token.kind() == latex::WORD)?;
- latex::Citation::cast(key.parent().parent()?)?;
+ let key = latex::Key::cast(word.parent())?;
+
+ latex::Citation::cast(key.syntax().parent()?.parent()?)?;
let origin_selection_range = main_document
.line_index
- .line_col_lsp_range(key.text_range());
+ .line_col_lsp_range(key.small_range());
for document in &context.request.subset.documents {
if let Some(data) = document.data.as_bibtex() {
for entry in data.root.children().filter_map(bibtex::Entry::cast) {
cancellation_token.result().ok()?;
- if let Some(key) = entry.key().filter(|k| k.text() == key.text()) {
+ if let Some(key) = entry.key().filter(|k| k.to_string() == key.to_string()) {
return Some(vec![LocationLink {
origin_selection_range: Some(origin_selection_range),
target_uri: document.uri.as_ref().clone().into(),
target_selection_range: document
.line_index
- .line_col_lsp_range(key.text_range()),
+ .line_col_lsp_range(key.small_range()),
target_range: document.line_index.line_col_lsp_range(entry.small_range()),
}]);
}
diff --git a/support/texlab/src/features/definition/label.rs b/support/texlab/src/features/definition/label.rs
index 65f0981de0..fd30b1fbc5 100644
--- a/support/texlab/src/features/definition/label.rs
+++ b/support/texlab/src/features/definition/label.rs
@@ -1,7 +1,10 @@
use cancellation::CancellationToken;
use lsp_types::{GotoDefinitionParams, LocationLink};
-use crate::{features::cursor::CursorContext, find_label_definition, render_label, LineIndexExt};
+use crate::{
+ features::cursor::CursorContext, find_label_definition, render_label, syntax::CstNode,
+ LineIndexExt,
+};
pub fn goto_label_definition(
context: &CursorContext<GotoDefinitionParams>,
@@ -10,7 +13,7 @@ pub fn goto_label_definition(
let main_document = context.request.main_document();
let (name_text, name_range) = context
- .find_label_name_word()
+ .find_label_name_key()
.or_else(|| context.find_label_name_command())?;
let origin_selection_range = main_document.line_index.line_col_lsp_range(name_range);
@@ -18,10 +21,10 @@ pub fn goto_label_definition(
for document in &context.request.subset.documents {
cancellation_token.result().ok()?;
if let Some(data) = document.data.as_latex() {
- if let Some(definition) = find_label_definition(&data.root, name_text) {
- let target_selection_range = definition.name()?.word()?.text_range();
+ if let Some(definition) = find_label_definition(&data.root, &name_text) {
+ let target_selection_range = definition.name()?.key()?.small_range();
let target_range =
- render_label(&context.request.subset, name_text, Some(definition))
+ render_label(&context.request.subset, &name_text, Some(definition))
.map(|label| label.range)
.unwrap_or(target_selection_range);
diff --git a/support/texlab/src/features/formatting.rs b/support/texlab/src/features/formatting.rs
index 4492a0532d..c7c2dc410a 100644
--- a/support/texlab/src/features/formatting.rs
+++ b/support/texlab/src/features/formatting.rs
@@ -4,7 +4,7 @@ mod latexindent;
use cancellation::CancellationToken;
use lsp_types::{DocumentFormattingParams, TextEdit};
-use crate::BibtexFormatter;
+use crate::{BibtexFormatter, LatexFormatter};
use self::{bibtex_internal::format_bibtex_internal, latexindent::format_with_latexindent};
@@ -19,6 +19,10 @@ pub fn format_source_code(
edits = edits.or_else(|| format_bibtex_internal(&request, cancellation_token));
}
+ if request.context.options.read().unwrap().latex_formatter == LatexFormatter::Texlab {
+ edits = edits.or_else(|| Some(vec![]));
+ }
+
edits = edits.or_else(|| format_with_latexindent(&request, cancellation_token));
edits
}
diff --git a/support/texlab/src/features/formatting/bibtex_internal.rs b/support/texlab/src/features/formatting/bibtex_internal.rs
index 8481f561e7..641bee0cfe 100644
--- a/support/texlab/src/features/formatting/bibtex_internal.rs
+++ b/support/texlab/src/features/formatting/bibtex_internal.rs
@@ -148,7 +148,7 @@ impl<'a> Formatter<'a> {
self.visit_token_lowercase(entry.ty().unwrap());
self.output.push('{');
if let Some(key) = entry.key() {
- self.output.push_str(key.text());
+ self.output.push_str(&key.to_string());
self.output.push(',');
self.output.push('\n');
for field in entry.fields() {
diff --git a/support/texlab/src/features/formatting/latexindent.rs b/support/texlab/src/features/formatting/latexindent.rs
index f603728a5d..300f512578 100644
--- a/support/texlab/src/features/formatting/latexindent.rs
+++ b/support/texlab/src/features/formatting/latexindent.rs
@@ -1,50 +1,98 @@
use std::{
- io::{BufWriter, Write},
+ fs,
process::{Command, Stdio},
};
use cancellation::CancellationToken;
use cstree::{TextLen, TextRange};
use lsp_types::{DocumentFormattingParams, TextEdit};
+use tempfile::tempdir;
-use crate::{features::FeatureRequest, LineIndexExt};
+use crate::{features::FeatureRequest, DocumentLanguage, LineIndexExt};
pub fn format_with_latexindent(
request: &FeatureRequest<DocumentFormattingParams>,
_cancellation_token: &CancellationToken,
) -> Option<Vec<TextEdit>> {
+ let directory = tempdir().ok()?;
let document = request.main_document();
- let current_dir = &request.context.current_directory;
let options = request.context.options.read().unwrap();
- let current_dir = match &options.root_directory {
- Some(root_directory) => current_dir.join(root_directory),
- None => current_dir.clone(),
+ let current_dir = options
+ .root_directory
+ .as_ref()
+ .cloned()
+ .or_else(|| {
+ if document.uri.scheme() == "file" {
+ document
+ .uri
+ .to_file_path()
+ .unwrap()
+ .parent()
+ .map(ToOwned::to_owned)
+ } else {
+ None
+ }
+ })
+ .unwrap_or_else(|| ".".into());
+
+ let local = match &options.latexindent.local {
+ Some(local) => format!("--local={}", local),
+ None => "-l".to_string(),
};
+
+ let modify_line_breaks = options.latexindent.modify_line_breaks;
+
drop(options);
- let mut process = Command::new("latexindent")
- .arg("-l")
+ let path = directory.path();
+ let _ = fs::copy(
+ current_dir.join("localSettings.yaml"),
+ path.join("localSettings.yaml"),
+ );
+ let _ = fs::copy(
+ current_dir.join(".localSettings.yaml"),
+ path.join(".localSettings.yaml"),
+ );
+ let _ = fs::copy(
+ current_dir.join("latexindent.yaml"),
+ path.join("latexindent.yaml"),
+ );
+
+ let name = if document.language() == DocumentLanguage::Bibtex {
+ "file.bib"
+ } else {
+ "file.tex"
+ };
+
+ fs::write(directory.path().join(name), &document.text).ok()?;
+
+ let mut args = Vec::new();
+ if modify_line_breaks {
+ args.push("--modifylinebreaks");
+ }
+ args.push(&local);
+ args.push(name);
+
+ let output = Command::new("latexindent")
+ .args(&args)
.current_dir(current_dir)
- .stdin(Stdio::piped())
+ .stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
- .spawn()
+ .current_dir(directory.path())
+ .output()
.ok()?;
- let stdin = process.stdin.take()?;
- let mut stdin = BufWriter::new(stdin);
- stdin.write_all(document.text.as_bytes()).ok()?;
- drop(stdin);
-
- let output = process.wait_with_output().ok()?;
-
let new_text = String::from_utf8_lossy(&output.stdout).into_owned();
-
- Some(vec![TextEdit {
- range: document
- .line_index
- .line_col_lsp_range(TextRange::new(0.into(), document.text.text_len())),
- new_text,
- }])
+ if new_text.is_empty() {
+ None
+ } else {
+ Some(vec![TextEdit {
+ range: document
+ .line_index
+ .line_col_lsp_range(TextRange::new(0.into(), document.text.text_len())),
+ new_text,
+ }])
+ }
}
diff --git a/support/texlab/src/features/highlight.rs b/support/texlab/src/features/highlight.rs
index e1aea6e2ad..386018f353 100644
--- a/support/texlab/src/features/highlight.rs
+++ b/support/texlab/src/features/highlight.rs
@@ -5,11 +5,12 @@ use lsp_types::{DocumentHighlight, DocumentHighlightParams};
use self::label::find_label_highlights;
-use super::FeatureRequest;
+use super::{cursor::CursorContext, FeatureRequest};
pub fn find_document_highlights(
request: FeatureRequest<DocumentHighlightParams>,
- token: &CancellationToken,
+ cancellation_token: &CancellationToken,
) -> Option<Vec<DocumentHighlight>> {
- find_label_highlights(&request, token)
+ let context = CursorContext::new(request);
+ find_label_highlights(&context, cancellation_token)
}
diff --git a/support/texlab/src/features/highlight/label.rs b/support/texlab/src/features/highlight/label.rs
index 4c94180f2a..9694993033 100644
--- a/support/texlab/src/features/highlight/label.rs
+++ b/support/texlab/src/features/highlight/label.rs
@@ -2,48 +2,32 @@ use cancellation::CancellationToken;
use lsp_types::{DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams};
use crate::{
- features::FeatureRequest,
+ features::cursor::CursorContext,
syntax::{latex, CstNode},
LineIndexExt,
};
pub fn find_label_highlights(
- request: &FeatureRequest<DocumentHighlightParams>,
- token: &CancellationToken,
+ context: &CursorContext<DocumentHighlightParams>,
+ cancellation_token: &CancellationToken,
) -> Option<Vec<DocumentHighlight>> {
- let main_document = request.main_document();
-
- let offset = main_document
- .line_index
- .offset_lsp(request.params.text_document_position_params.position);
+ let (name_text, _) = context.find_label_name_key()?;
+ let main_document = context.request.main_document();
let data = main_document.data.as_latex()?;
- let name = data.root.token_at_offset(offset).right_biased()?;
- if name.kind() != latex::WORD {
- return None;
- }
-
- if !matches!(
- name.parent().parent()?.kind(),
- latex::LABEL_DEFINITION | latex::LABEL_REFERENCE | latex::LABEL_REFERENCE_RANGE
- ) {
- return None;
- }
let mut highlights = Vec::new();
for node in data.root.descendants() {
- if token.is_canceled() {
- return None;
- }
+ cancellation_token.result().ok()?;
if let Some(label_name) = latex::LabelDefinition::cast(node)
.and_then(|label| label.name())
- .and_then(|label_name| label_name.word())
- .filter(|label_name| label_name.text() == name.text())
+ .and_then(|label_name| label_name.key())
+ .filter(|label_name| label_name.to_string() == name_text)
{
let range = main_document
.line_index
- .line_col_lsp_range(label_name.text_range());
+ .line_col_lsp_range(label_name.small_range());
highlights.push(DocumentHighlight {
range,
@@ -53,12 +37,12 @@ pub fn find_label_highlights(
for label_name in label
.name_list()
.into_iter()
- .flat_map(|name| name.words())
- .filter(|label_name| label_name.text() == name.text())
+ .flat_map(|name| name.keys())
+ .filter(|label_name| label_name.to_string() == name_text)
{
let range = main_document
.line_index
- .line_col_lsp_range(label_name.text_range());
+ .line_col_lsp_range(label_name.small_range());
highlights.push(DocumentHighlight {
range,
@@ -68,12 +52,12 @@ pub fn find_label_highlights(
} else if let Some(label) = latex::LabelReferenceRange::cast(node) {
if let Some(label_name) = label
.from()
- .and_then(|label_name| label_name.word())
- .filter(|label_name| label_name.text() == name.text())
+ .and_then(|label_name| label_name.key())
+ .filter(|label_name| label_name.to_string() == name_text)
{
let range = main_document
.line_index
- .line_col_lsp_range(label_name.text_range());
+ .line_col_lsp_range(label_name.small_range());
highlights.push(DocumentHighlight {
range,
@@ -83,12 +67,12 @@ pub fn find_label_highlights(
if let Some(label_name) = label
.to()
- .and_then(|label_name| label_name.word())
- .filter(|label_name| label_name.text() == name.text())
+ .and_then(|label_name| label_name.key())
+ .filter(|label_name| label_name.to_string() == name_text)
{
let range = main_document
.line_index
- .line_col_lsp_range(label_name.text_range());
+ .line_col_lsp_range(label_name.small_range());
highlights.push(DocumentHighlight {
range,
@@ -118,8 +102,9 @@ mod tests {
.character(0)
.build()
.highlight();
+ let context = CursorContext::new(request);
- let actual_links = find_label_highlights(&request, CancellationToken::none());
+ let actual_links = find_label_highlights(&context, CancellationToken::none());
assert!(actual_links.is_none());
}
@@ -133,8 +118,9 @@ mod tests {
.character(0)
.build()
.highlight();
+ let context = CursorContext::new(request);
- let actual_links = find_label_highlights(&request, CancellationToken::none());
+ let actual_links = find_label_highlights(&context, CancellationToken::none());
assert!(actual_links.is_none());
}
@@ -148,8 +134,9 @@ mod tests {
.character(7)
.build();
let request = tester.highlight();
+ let context = CursorContext::new(request);
- let actual_highlights = find_label_highlights(&request, CancellationToken::none()).unwrap();
+ let actual_highlights = find_label_highlights(&context, CancellationToken::none()).unwrap();
let expected_highlights = vec![
DocumentHighlight {
diff --git a/support/texlab/src/features/hover/label.rs b/support/texlab/src/features/hover/label.rs
index 79afe7d250..26477feb7c 100644
--- a/support/texlab/src/features/hover/label.rs
+++ b/support/texlab/src/features/hover/label.rs
@@ -10,10 +10,10 @@ pub fn find_label_hover(
let main_document = context.request.main_document();
let (name_text, name_range) = context
- .find_label_name_word()
+ .find_label_name_key()
.or_else(|| context.find_label_name_command())?;
- let label = render_label(&context.request.subset, name_text, None)?;
+ let label = render_label(&context.request.subset, &name_text, None)?;
Some(Hover {
range: Some(main_document.line_index.line_col_lsp_range(name_range)),
diff --git a/support/texlab/src/features/reference/entry.rs b/support/texlab/src/features/reference/entry.rs
index fbbcbdb4cc..a9e15ba5b6 100644
--- a/support/texlab/src/features/reference/entry.rs
+++ b/support/texlab/src/features/reference/entry.rs
@@ -26,9 +26,9 @@ pub fn find_entry_references(
.descendants()
.filter_map(latex::Citation::cast)
.filter_map(|citation| citation.key_list())
- .flat_map(|keys| keys.words())
- .filter(|key| key.text() == key_text)
- .map(|key| document.line_index.line_col_lsp_range(key.text_range()))
+ .flat_map(|keys| keys.keys())
+ .filter(|key| key.to_string() == key_text)
+ .map(|key| document.line_index.line_col_lsp_range(key.small_range()))
.for_each(|range| {
references.push(Location::new(document.uri.as_ref().clone().into(), range));
});
@@ -38,8 +38,8 @@ pub fn find_entry_references(
.children()
.filter_map(bibtex::Entry::cast)
.filter_map(|entry| entry.key())
- .filter(|key| key.text() == key_text)
- .map(|key| document.line_index.line_col_lsp_range(key.text_range()))
+ .filter(|key| key.to_string() == key_text)
+ .map(|key| document.line_index.line_col_lsp_range(key.small_range()))
.for_each(|range| {
references.push(Location::new(document.uri.as_ref().clone().into(), range));
});
diff --git a/support/texlab/src/features/reference/label.rs b/support/texlab/src/features/reference/label.rs
index e9aa3a8510..45c6b90137 100644
--- a/support/texlab/src/features/reference/label.rs
+++ b/support/texlab/src/features/reference/label.rs
@@ -11,7 +11,7 @@ pub fn find_label_references(
cancellation_token.result().ok()?;
let (name_text, _) = context
- .find_label_name_word()
+ .find_label_name_key()
.or_else(|| context.find_label_name_command())?;
for document in &context.request.subset.documents {
diff --git a/support/texlab/src/features/rename/entry.rs b/support/texlab/src/features/rename/entry.rs
index 14fbfd42d7..35a77d926e 100644
--- a/support/texlab/src/features/rename/entry.rs
+++ b/support/texlab/src/features/rename/entry.rs
@@ -4,7 +4,7 @@ use cancellation::CancellationToken;
use lsp_types::{Range, RenameParams, TextEdit, WorkspaceEdit};
use crate::{
- features::cursor::{Cursor, CursorContext, HasPosition},
+ features::cursor::{CursorContext, HasPosition},
syntax::{bibtex, latex, CstNode},
DocumentData, LineIndexExt,
};
@@ -13,18 +13,9 @@ pub fn prepare_entry_rename<P: HasPosition>(
context: &CursorContext<P>,
_cancellation_token: &CancellationToken,
) -> Option<Range> {
- let range = match &context.cursor {
- Cursor::Latex(token) if token.kind() == latex::WORD => {
- let group = latex::CurlyGroupWordList::cast(token.parent())?;
- latex::Citation::cast(group.syntax().parent()?)?;
- token.text_range()
- }
- Cursor::Bibtex(token) if token.kind() == bibtex::WORD => {
- bibtex::Entry::cast(token.parent())?;
- token.text_range()
- }
- _ => return None,
- };
+ let (_, range) = context
+ .find_citation_key_word()
+ .or_else(|| context.find_entry_key())?;
Some(
context
@@ -41,11 +32,9 @@ pub fn rename_entry(
) -> Option<WorkspaceEdit> {
cancellation_token.result().ok()?;
prepare_entry_rename(context, cancellation_token)?;
- let key_text = context
- .cursor
- .as_latex()
- .map(|token| token.text())
- .or_else(|| context.cursor.as_bibtex().map(|token| token.text()))?;
+ let (key_text, _) = context
+ .find_citation_key_word()
+ .or_else(|| context.find_entry_key())?;
let mut changes = HashMap::new();
for document in &context.request.subset.documents {
@@ -57,9 +46,9 @@ pub fn rename_entry(
.descendants()
.filter_map(latex::Citation::cast)
.filter_map(|citation| citation.key_list())
- .flat_map(|keys| keys.words())
- .filter(|key| key.text() == key_text)
- .map(|key| document.line_index.line_col_lsp_range(key.text_range()))
+ .flat_map(|keys| keys.keys())
+ .filter(|key| key.to_string() == key_text)
+ .map(|key| document.line_index.line_col_lsp_range(key.small_range()))
.map(|range| TextEdit::new(range, context.request.params.new_name.clone()))
.collect();
changes.insert(document.uri.as_ref().clone().into(), edits);
@@ -70,8 +59,8 @@ pub fn rename_entry(
.descendants()
.filter_map(bibtex::Entry::cast)
.filter_map(|entry| entry.key())
- .filter(|key| key.text() == key_text)
- .map(|key| document.line_index.line_col_lsp_range(key.text_range()))
+ .filter(|key| key.to_string() == key_text)
+ .map(|key| document.line_index.line_col_lsp_range(key.small_range()))
.map(|range| TextEdit::new(range, context.request.params.new_name.clone()))
.collect();
changes.insert(document.uri.as_ref().clone().into(), edits);
diff --git a/support/texlab/src/features/rename/label.rs b/support/texlab/src/features/rename/label.rs
index 11b414ccfa..a73a0064bf 100644
--- a/support/texlab/src/features/rename/label.rs
+++ b/support/texlab/src/features/rename/label.rs
@@ -13,20 +13,14 @@ pub fn prepare_label_rename<P: HasPosition>(
context: &CursorContext<P>,
_cancellation_token: &CancellationToken,
) -> Option<Range> {
- let name = context.cursor.as_latex()?;
- name.parent().parent().filter(|node| {
- matches!(
- node.kind(),
- latex::LABEL_DEFINITION | latex::LABEL_REFERENCE | latex::LABEL_REFERENCE_RANGE
- )
- })?;
+ let (_, range) = context.find_label_name_key()?;
Some(
context
.request
.main_document()
.line_index
- .line_col_lsp_range(name.text_range()),
+ .line_col_lsp_range(range),
)
}
@@ -35,7 +29,8 @@ pub fn rename_label(
cancellation_token: &CancellationToken,
) -> Option<WorkspaceEdit> {
prepare_label_rename(context, cancellation_token)?;
- let name_text = context.cursor.as_latex()?.text();
+ let (name_text, _) = context.find_label_name_key()?;
+
let mut changes = HashMap::new();
for document in &context.request.subset.documents {
cancellation_token.result().ok()?;
@@ -44,9 +39,9 @@ pub fn rename_label(
for node in data.root.descendants() {
if let Some(range) = latex::LabelDefinition::cast(node)
.and_then(|label| label.name())
- .and_then(|name| name.word())
- .filter(|name| name.text() == name_text)
- .map(|name| document.line_index.line_col_lsp_range(name.text_range()))
+ .and_then(|name| name.key())
+ .filter(|name| name.to_string() == name_text)
+ .map(|name| document.line_index.line_col_lsp_range(name.small_range()))
{
edits.push(TextEdit::new(
range,
@@ -57,9 +52,9 @@ pub fn rename_label(
latex::LabelReference::cast(node)
.and_then(|label| label.name_list())
.into_iter()
- .flat_map(|label| label.words())
- .filter(|name| name.text() == name_text)
- .map(|name| document.line_index.line_col_lsp_range(name.text_range()))
+ .flat_map(|label| label.keys())
+ .filter(|name| name.to_string() == name_text)
+ .map(|name| document.line_index.line_col_lsp_range(name.small_range()))
.for_each(|range| {
edits.push(TextEdit::new(
range,
@@ -70,22 +65,22 @@ pub fn rename_label(
if let Some(label) = latex::LabelReferenceRange::cast(node) {
if let Some(name1) = label
.from()
- .and_then(|name| name.word())
- .filter(|name| name.text() == name_text)
+ .and_then(|name| name.key())
+ .filter(|name| name.to_string() == name_text)
{
edits.push(TextEdit::new(
- document.line_index.line_col_lsp_range(name1.text_range()),
+ document.line_index.line_col_lsp_range(name1.small_range()),
context.request.params.new_name.clone(),
));
}
if let Some(name2) = label
.from()
- .and_then(|name| name.word())
- .filter(|name| name.text() == name_text)
+ .and_then(|name| name.key())
+ .filter(|name| name.to_string() == name_text)
{
edits.push(TextEdit::new(
- document.line_index.line_col_lsp_range(name2.text_range()),
+ document.line_index.line_col_lsp_range(name2.small_range()),
context.request.params.new_name.clone(),
));
}
diff --git a/support/texlab/src/features/symbol/bibtex.rs b/support/texlab/src/features/symbol/bibtex.rs
index e030195880..db555a7176 100644
--- a/support/texlab/src/features/symbol/bibtex.rs
+++ b/support/texlab/src/features/symbol/bibtex.rs
@@ -68,7 +68,7 @@ pub fn find_bibtex_symbols(
.unwrap_or(BibtexEntryTypeCategory::Misc);
buf.push(InternalSymbol {
- name: key.text().to_string(),
+ name: key.to_string(),
label: None,
kind: InternalSymbolKind::Entry(category),
deprecated: false,
@@ -77,7 +77,7 @@ pub fn find_bibtex_symbols(
.line_col_lsp_range(entry.small_range()),
selection_range: main_document
.line_index
- .line_col_lsp_range(key.text_range()),
+ .line_col_lsp_range(key.small_range()),
children,
});
}
diff --git a/support/texlab/src/features/symbol/latex.rs b/support/texlab/src/features/symbol/latex.rs
index ce584c0c76..ec549c87fe 100644
--- a/support/texlab/src/features/symbol/latex.rs
+++ b/support/texlab/src/features/symbol/latex.rs
@@ -59,25 +59,25 @@ fn visit(context: &mut Context, node: &latex::SyntaxNode) -> Vec<InternalSymbol>
latex::ENVIRONMENT => latex::Environment::cast(node)
.and_then(|env| env.begin())
.and_then(|begin| begin.name())
- .and_then(|name| name.word())
- .map(|name| name.text())
+ .and_then(|name| name.key())
+ .map(|name| name.to_string())
.and_then(|name| {
if LANGUAGE_DATA
.math_environments
.iter()
- .any(|env| env == name)
+ .any(|env| env == &name)
{
visit_equation_environment(context, node)
} else if LANGUAGE_DATA
.enum_environments
.iter()
- .any(|env| env == name)
+ .any(|env| env == &name)
{
- visit_enumeration(context, node, name)
- } else if let Ok(float_kind) = LabelledFloatKind::from_str(name) {
+ visit_enumeration(context, node, &name)
+ } else if let Ok(float_kind) = LabelledFloatKind::from_str(&name) {
visit_float(context, node, float_kind)
} else {
- visit_theorem(context, node, name)
+ visit_theorem(context, node, &name)
}
}),
_ => None,
@@ -161,12 +161,12 @@ fn visit_enum_item(context: &mut Context, node: &latex::SyntaxNode) -> Option<In
.filter_map(latex::Environment::cast)
.filter_map(|environment| environment.begin())
.filter_map(|begin| begin.name())
- .filter_map(|name| name.word())
+ .filter_map(|name| name.key())
.any(|name| {
LANGUAGE_DATA
.enum_environments
.iter()
- .any(|e| e == name.text())
+ .any(|e| e == &name.to_string())
})
{
return None;
@@ -471,7 +471,7 @@ fn find_label_by_parent(
) -> Option<NumberedLabel> {
let node = parent.children().find_map(latex::LabelDefinition::cast)?;
- let name = node.name()?.word()?.text();
+ let name = node.name()?.key()?.to_string();
let range = context
.subset
.documents
@@ -479,7 +479,7 @@ fn find_label_by_parent(
.line_index
.line_col_lsp_range(node.small_range());
- let number = find_label_number(&context.subset, name);
+ let number = find_label_number(&context.subset, &name);
Some(NumberedLabel {
name: name.to_string(),
range,