lexer.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. """
  2. `GrammarLexer` is compatible with other lexers and can be used to highlight
  3. the input using a regular grammar with annotations.
  4. """
  5. from __future__ import annotations
  6. from typing import Callable
  7. from prompt_toolkit.document import Document
  8. from prompt_toolkit.formatted_text.base import StyleAndTextTuples
  9. from prompt_toolkit.formatted_text.utils import split_lines
  10. from prompt_toolkit.lexers import Lexer
  11. from .compiler import _CompiledGrammar
  12. __all__ = [
  13. "GrammarLexer",
  14. ]
  15. class GrammarLexer(Lexer):
  16. """
  17. Lexer which can be used for highlighting of fragments according to variables in the grammar.
  18. (It does not actual lexing of the string, but it exposes an API, compatible
  19. with the Pygments lexer class.)
  20. :param compiled_grammar: Grammar as returned by the `compile()` function.
  21. :param lexers: Dictionary mapping variable names of the regular grammar to
  22. the lexers that should be used for this part. (This can
  23. call other lexers recursively.) If you wish a part of the
  24. grammar to just get one fragment, use a
  25. `prompt_toolkit.lexers.SimpleLexer`.
  26. """
  27. def __init__(
  28. self,
  29. compiled_grammar: _CompiledGrammar,
  30. default_style: str = "",
  31. lexers: dict[str, Lexer] | None = None,
  32. ) -> None:
  33. self.compiled_grammar = compiled_grammar
  34. self.default_style = default_style
  35. self.lexers = lexers or {}
  36. def _get_text_fragments(self, text: str) -> StyleAndTextTuples:
  37. m = self.compiled_grammar.match_prefix(text)
  38. if m:
  39. characters: StyleAndTextTuples = [(self.default_style, c) for c in text]
  40. for v in m.variables():
  41. # If we have a `Lexer` instance for this part of the input.
  42. # Tokenize recursively and apply tokens.
  43. lexer = self.lexers.get(v.varname)
  44. if lexer:
  45. document = Document(text[v.start : v.stop])
  46. lexer_tokens_for_line = lexer.lex_document(document)
  47. text_fragments: StyleAndTextTuples = []
  48. for i in range(len(document.lines)):
  49. text_fragments.extend(lexer_tokens_for_line(i))
  50. text_fragments.append(("", "\n"))
  51. if text_fragments:
  52. text_fragments.pop()
  53. i = v.start
  54. for t, s, *_ in text_fragments:
  55. for c in s:
  56. if characters[i][0] == self.default_style:
  57. characters[i] = (t, characters[i][1])
  58. i += 1
  59. # Highlight trailing input.
  60. trailing_input = m.trailing_input()
  61. if trailing_input:
  62. for i in range(trailing_input.start, trailing_input.stop):
  63. characters[i] = ("class:trailing-input", characters[i][1])
  64. return characters
  65. else:
  66. return [("", text)]
  67. def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
  68. lines = list(split_lines(self._get_text_fragments(document.text)))
  69. def get_line(lineno: int) -> StyleAndTextTuples:
  70. try:
  71. return lines[lineno]
  72. except IndexError:
  73. return []
  74. return get_line