Repository: latex-lsp/texlab (Rust)

Issue:
Numbering on textDocument/documentSymbol
I noticed, that when requesting for textDocument/documentSymbol the resulting sections (SymbolKind.Module = 2) only have a number if they are assigned with a label.

A small example would be a document with
```
\section{Section}\label{sec:outer} % 1 Section
\subsection{Subsection} % 1.1 Subsection

\section{Section'} % 2 Section'
\subsection{Subsection'}\label{sec:inner} % 2.1 Subsection'
``` 
The resulting document symbols would be:
```
1 Section
Subsection
Section'
2.1 Subsection'
```
So the numbers are correct. But when a section has no label, it is not assigned any number.

Similarly,
```
\section{Section}\label{sec:outer} % 1 Section
\subsection{Subsection} % 1.1 Subsection
\section{Section'} % 2 Section'
\subsection{Dummy} % 2.1 Dummy
\subsection*{Subsection'}\label{sec:inner} % Subsection'
``` 
would result in
```
1 Section
Subsection
Section'
Dummy
2.1 Subsection'
```
even though the last subsection does not have a number (it is assigned the number from the previous section, and when Dummy section is removed the number would be 2).
[This is not bad, since we can not use the label anyway, because there is no number.]

I guess my expectation would be for the document symbol to always include the number that appears in the actual document. So the names that are after the % in the code.

I have not looked at the code yet, so I don't know if this is feasible in the current way the document is parsed. I also noticed a similar thing for equations. Equations in the symbol list will be numbered only if they have a label.


Discussion:

Patch:
diff --git a/crates/base-db/src/semantics/auxiliary.rs b/crates/base-db/src/semantics/auxiliary.rs
index 8c9fe012e..bb9a2c065 100644
--- a/crates/base-db/src/semantics/auxiliary.rs
+++ b/crates/base-db/src/semantics/auxiliary.rs
@@ -5,6 +5,7 @@ use syntax::latex::{self, HasCurly};
 #[derive(Debug, Clone, Default)]
 pub struct Semantics {
     pub label_numbers: FxHashMap<String, String>,
+    pub section_numbers: FxHashMap<String, String>,
 }
 
 impl Semantics {
@@ -18,6 +19,9 @@ impl Semantics {
         if let Some(label_number) = latex::LabelNumber::cast(node.clone()) {
             self.process_label_number(&label_number);
         }
+        if let Some(toc_line) = latex::TocContentsLine::cast(node.clone()) {
+            self.process_toc_line(&toc_line);
+        }
     }
 
     fn process_label_number(&mut self, label_number: &latex::LabelNumber) -> Option<()> {
@@ -40,4 +44,36 @@ impl Semantics {
         self.label_numbers.insert(name, text);
         Some(())
     }
+
+    fn process_toc_line(&mut self, toc_line: &latex::TocContentsLine) -> Option<()> {
+        let unit = toc_line.unit().and_then(|u| u.content_text())?.to_string();
+
+        if [
+            "part",
+            "chapter",
+            "section",
+            "subsection",
+            "subsubsection",
+            "paragraph",
+            "subparagraph",
+        ]
+        .contains(&unit.as_str())
+        {
+            let line = toc_line.line()?;
+            let name = line.syntax().children().find_map(|child| {
+                latex::Text::cast(child.clone())?;
+                Some(child)
+            })?;
+            let name = name.to_string();
+
+            let num_line = line
+                .syntax()
+                .children()
+                .find_map(|child| latex::TocNumberLine::cast(child.clone()))?;
+            let number = num_line.number()?;
+            let number = number.content_text()?.replace(['{', '}'], "");
+            self.section_numbers.insert(name, number);
+        }
+        Some(())
+    }
 }
diff --git a/crates/parser/src/latex.rs b/crates/parser/src/latex.rs
index d6b60cf25..8379d9c5c 100644
--- a/crates/parser/src/latex.rs
+++ b/crates/parser/src/latex.rs
@@ -159,6 +159,8 @@ impl<'a> Parser<'a> {
                 CommandName::VerbatimBlock => self.verbatim_block(),
                 CommandName::GraphicsPath => self.graphics_path(),
                 CommandName::BibItem => self.bibitem(),
+                CommandName::TocContentsLine => self.toc_contents_line(),
+                CommandName::TocNumberLine => self.toc_number_line(),
             },
         }
     }
@@ -1261,6 +1263,38 @@ impl<'a> Parser<'a> {
 
         self.builder.finish_node();
     }
+
+    fn toc_contents_line(&mut self) {
+        self.builder.start_node(TOC_CONTENTS_LINE.into());
+        self.eat();
+        self.trivia();
+
+        if self.lexer.peek() == Some(Token::LCurly) {
+            self.curly_group();
+        }
+
+        if self.lexer.peek() == Some(Token::LCurly) {
+            self.curly_group();
+        }
+
+        if self.lexer.peek() == Some(Token::LCurly) {
+            self.curly_group();
+        }
+
+        self.builder.finish_node();
+    }
+
+    fn toc_number_line(&mut self) {
+        self.builder.start_node(TOC_NUMBER_LINE.into());
+        self.eat();
+        self.trivia();
+
+        if self.lexer.peek() == Some(Token::LCurly) {
+            self.curly_group();
+        }
+
+        self.builder.finish_node();
+    }
 }
 
 pub fn parse_latex(text: &str, config: &SyntaxConfig) -> GreenNode {
diff --git a/crates/parser/src/latex/lexer/commands.rs b/crates/parser/src/latex/lexer/commands.rs
index 31cbf1330..3f8b6e36e 100644
--- a/crates/parser/src/latex/lexer/commands.rs
+++ b/crates/parser/src/latex/lexer/commands.rs
@@ -97,6 +97,8 @@ pub fn classify(name: &str, config: &SyntaxConfig) -> CommandName {
         "fi" => CommandName::EndBlockComment,
         "verb" => CommandName::VerbatimBlock,
         "bibitem" => CommandName::BibItem,
+        "contentsline" => CommandName::TocContentsLine,
+        "numberline" => CommandName::TocNumberLine,
 
         _ if config.citation_commands.contains(name) => CommandName::Citation,
         _ if config.label_definition_commands.contains(name) => CommandName::LabelDefinition,
diff --git a/crates/parser/src/latex/lexer/types.rs b/crates/parser/src/latex/lexer/types.rs
index 3936742ae..796378f67 100644
--- a/crates/parser/src/latex/lexer/types.rs
+++ b/crates/parser/src/latex/lexer/types.rs
@@ -96,6 +96,8 @@ pub enum CommandName {
     EndBlockComment,
     VerbatimBlock,
     BibItem,
+    TocContentsLine,
+    TocNumberLine,
 }
 
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
diff --git a/crates/symbols/src/document/tex.rs b/crates/symbols/src/document/tex.rs
index e0df9c85c..734335ef0 100644
--- a/crates/symbols/src/document/tex.rs
+++ b/crates/symbols/src/document/tex.rs
@@ -67,16 +67,14 @@ impl<'a> SymbolBuilder<'a> {
         let group_text = group.content_text()?;
         let kind = SymbolKind::Section;
 
-        let symbol = match self.find_label(section.syntax()) {
-            Some(label) => {
-                let name = match self.find_label_number(&label.text) {
-                    Some(number) => format!("{number} {group_text}"),
-                    None => group_text,
-                };
+        let name = match self.find_section_number(&group_text) {
+            Some(number) => format!("{number} {group_text}"),
+            None => group_text,
+        };
 
-                Symbol::new_label(name, kind, range, label)
-            }
-            None => Symbol::new_simple(group_text, kind, range, range),
+        let symbol = match self.find_label(section.syntax()) {
+            Some(label) => Symbol::new_label(name, kind, range, label),
+            None => Symbol::new_simple(name, kind, range, range)
         };
 
         Some(symbol)
@@ -247,6 +245,15 @@ impl<'a> SymbolBuilder<'a> {
         Some(Span { text, range })
     }
 
+    fn find_section_number(&self, name: &str) -> Option<&str> {
+        self.project
+            .documents
+            .iter()
+            .filter_map(|document| document.data.as_aux())
+            .find_map(|data| data.semantics.section_numbers.get(name))
+            .map(String::as_str)
+    }
+
     fn find_label_number(&self, name: &str) -> Option<&str> {
         self.project
             .documents
diff --git a/crates/syntax/src/latex/cst.rs b/crates/syntax/src/latex/cst.rs
index 536648de6..580ef6af1 100644
--- a/crates/syntax/src/latex/cst.rs
+++ b/crates/syntax/src/latex/cst.rs
@@ -762,3 +762,31 @@ impl BibItem {
         self.syntax().children().find_map(CurlyGroupWord::cast)
     }
 }
+
+cst_node!(TocContentsLine, TOC_CONTENTS_LINE);
+
+impl TocContentsLine {
+    pub fn command(&self) -> Option<SyntaxToken> {
+        self.syntax().first_token()
+    }
+
+    pub fn unit(&self) -> Option<CurlyGroup> {
+        self.syntax().children().find_map(CurlyGroup::cast)
+    }
+
+    pub fn line(&self) -> Option<CurlyGroup> {
+        self.syntax().children().filter_map(CurlyGroup::cast).nth(1)
+    }
+}
+
+cst_node!(TocNumberLine, TOC_NUMBER_LINE);
+
+impl TocNumberLine {
+    pub fn command(&self) -> Option<SyntaxToken> {
+        self.syntax().first_token()
+    }
+
+    pub fn number(&self) -> Option<CurlyGroup> {
+        self.syntax().children().find_map(CurlyGroup::cast)
+    }
+}
diff --git a/crates/syntax/src/latex/kind.rs b/crates/syntax/src/latex/kind.rs
index 9fc2bac87..f67201e00 100644
--- a/crates/syntax/src/latex/kind.rs
+++ b/crates/syntax/src/latex/kind.rs
@@ -83,6 +83,8 @@ pub enum SyntaxKind {
     GRAPHICS_PATH,
     BLOCK_COMMENT,
     BIBITEM,
+    TOC_CONTENTS_LINE,
+    TOC_NUMBER_LINE,
     ROOT,
 }
 


Test patch:
diff --git a/crates/symbols/src/document/tests.rs b/crates/symbols/src/document/tests.rs
index 25450a6cc..cf599529f 100644
--- a/crates/symbols/src/document/tests.rs
+++ b/crates/symbols/src/document/tests.rs
@@ -281,44 +281,44 @@ fn test_section() {
     check(
         &fixture,
         expect![[r#"
-        [
-            Symbol {
-                name: "Foo",
-                kind: Section,
-                label: None,
-                full_range: 43..56,
-                selection_range: 43..56,
-                children: [],
-            },
-            Symbol {
-                name: "2 Bar",
-                kind: Section,
-                label: Some(
-                    Span(
-                        "sec:bar",
-                        71..86,
+            [
+                Symbol {
+                    name: "1 Foo",
+                    kind: Section,
+                    label: None,
+                    full_range: 43..56,
+                    selection_range: 43..56,
+                    children: [],
+                },
+                Symbol {
+                    name: "2 Bar",
+                    kind: Section,
+                    label: Some(
+                        Span(
+                            "sec:bar",
+                            71..86,
+                        ),
                     ),
-                ),
-                full_range: 58..119,
-                selection_range: 71..86,
-                children: [
-                    Symbol {
-                        name: "Baz",
-                        kind: Section,
-                        label: Some(
-                            Span(
-                                "sec:baz",
-                                104..119,
+                    full_range: 58..119,
+                    selection_range: 71..86,
+                    children: [
+                        Symbol {
+                            name: "Baz",
+                            kind: Section,
+                            label: Some(
+                                Span(
+                                    "sec:baz",
+                                    104..119,
+                                ),
                             ),
-                        ),
-                        full_range: 88..119,
-                        selection_range: 104..119,
-                        children: [],
-                    },
-                ],
-            },
-        ]
-    "#]],
+                            full_range: 88..119,
+                            selection_range: 104..119,
+                            children: [],
+                        },
+                    ],
+                },
+            ]
+        "#]],
     );
 }