|
15 | 15 | "lexer.rkt" |
16 | 16 | (only-in "lexer/state.rkt" |
17 | 17 | lexer-state-body-forest) |
| 18 | + (only-in "lexer/token-tree.rkt" |
| 19 | + Token-Leaf? Token-Leaf-span |
| 20 | + Token-List? Token-List-children |
| 21 | + Token-Prefix-Tree? |
| 22 | + Token-Forest-nodes |
| 23 | + token-node-children |
| 24 | + token-node-start |
| 25 | + token-node-end |
| 26 | + non-skippable-node? |
| 27 | + token-leaf-type?) |
18 | 28 | "doc-lang.rkt" |
19 | 29 | racket/match |
20 | 30 | racket/contract |
|
795 | 805 | #:location (Location #:uri uri |
796 | 806 | #:range range)))) |
797 | 807 |
|
| 808 | +;; Hierarchical Document Symbols: returns a tree of DocumentSymbol. |
| 809 | +;; |
| 810 | +;; The tree is built by recursing over the lexer's token forest, which already |
| 811 | +;; encodes paren nesting. Definition-like forms (define, struct, module+, ...) |
| 812 | +;; become symbols whose `range` spans the whole form and whose `selectionRange` |
| 813 | +;; covers the defined name; any other form is transparent and passes nested |
| 814 | +;; definitions up to the enclosing symbol. The forest tolerates incomplete code |
| 815 | +;; (forms left open mid-edit still parse to the end of the document), so the |
| 816 | +;; symbol tree stays stable during editing. |
| 817 | + |
| 818 | +;; Maps the head symbol of a form to how its SymbolKind is chosen. 'fn-or-var |
| 819 | +;; picks Function when the defined name uses the function shorthand |
| 820 | +;; (define (f x) ...) and Variable otherwise. |
| 821 | +(define *definition-form-kinds* |
| 822 | + (hash "define" 'fn-or-var |
| 823 | + "define/contract" 'fn-or-var |
| 824 | + "define/public" 'fn-or-var |
| 825 | + "define/private" 'fn-or-var |
| 826 | + "define/override" 'fn-or-var |
| 827 | + "define/augment" 'fn-or-var |
| 828 | + "define-values" SymbolKind-Variable |
| 829 | + "define-syntax" 'fn-or-var |
| 830 | + "define-syntax-rule" SymbolKind-Function |
| 831 | + "define-syntaxes" SymbolKind-Variable |
| 832 | + "define-match-expander" SymbolKind-Function |
| 833 | + "define-struct" SymbolKind-Struct |
| 834 | + "struct" SymbolKind-Struct |
| 835 | + "module" SymbolKind-Module |
| 836 | + "module+" SymbolKind-Module |
| 837 | + "module*" SymbolKind-Module)) |
| 838 | + |
| 839 | +;; Container forms become a symbol named after their own head so that |
| 840 | +;; scrolling inside a long body keeps a sticky header even though the form |
| 841 | +;; defines no name itself. |
| 842 | +(define *container-form-kinds* |
| 843 | + (hash "provide" SymbolKind-Namespace |
| 844 | + "require" SymbolKind-Namespace |
| 845 | + "class" SymbolKind-Class |
| 846 | + "class*" SymbolKind-Class |
| 847 | + "match" SymbolKind-Object |
| 848 | + "match*" SymbolKind-Object |
| 849 | + "cond" SymbolKind-Object |
| 850 | + "for" SymbolKind-Object |
| 851 | + "for/list" SymbolKind-Object |
| 852 | + "for/vector" SymbolKind-Object |
| 853 | + "for/hash" SymbolKind-Object |
| 854 | + "for/hasheq" SymbolKind-Object |
| 855 | + "for/hasheqv" SymbolKind-Object |
| 856 | + "for/hashalw" SymbolKind-Object |
| 857 | + "for/and" SymbolKind-Object |
| 858 | + "for/or" SymbolKind-Object |
| 859 | + "for/sum" SymbolKind-Object |
| 860 | + "for/product" SymbolKind-Object |
| 861 | + "for/lists" SymbolKind-Object |
| 862 | + "for/first" SymbolKind-Object |
| 863 | + "for/last" SymbolKind-Object |
| 864 | + "for/fold" SymbolKind-Object |
| 865 | + "for/foldr" SymbolKind-Object)) |
| 866 | + |
| 867 | +;; Class member forms: every name they declare becomes a Field symbol, either |
| 868 | +;; a bare name as in (field z) or a binding group as in (field [y 0]). |
| 869 | +(define *field-form-heads* |
| 870 | + (set "field" "init-field" "init")) |
| 871 | + |
| 872 | +(define/contract (doc-symbols-hierarchical doc) |
| 873 | + (-> Doc? (listof DocumentSymbol?)) |
| 874 | + ;; Paren tracking means nothing in non-sexp languages (e.g. Rhombus, |
| 875 | + ;; Scribble): form extents are set by blocks or markup rather than parens, |
| 876 | + ;; so any symbol the walker produced would have a bogus range. Report no |
| 877 | + ;; symbols instead. Unknown languages keep the sexp treatment, matching the |
| 878 | + ;; lexer fallback used elsewhere. |
| 879 | + (if (eq? (Language-Info-body-mode (doc-language-info doc)) 'non-sexp) |
| 880 | + '() |
| 881 | + (doc-symbols-hierarchical/sexp doc))) |
| 882 | + |
| 883 | +;; Walk the lexer's token forest. Each `(...)` form is a Token-List whose head |
| 884 | +;; is its first meaningful child. A definition form becomes a symbol named after |
| 885 | +;; the bound name, a container form a symbol named after its own head, and a |
| 886 | +;; field form one Field symbol per declared name. Any other form is transparent: |
| 887 | +;; it contributes its nested definitions to the enclosing scope. The forest |
| 888 | +;; already tracks paren nesting and tolerates unclosed mid-edit forms, so this |
| 889 | +;; reuses that structure instead of re-deriving it from the flat token stream. |
| 890 | +(define (doc-symbols-hierarchical/sexp doc) |
| 891 | + (define text (LexerSnapshot-text (doc-lexer-snapshot doc))) |
| 892 | + (define (span-text span) |
| 893 | + (substring text (LexerTokenSpan-start span) (LexerTokenSpan-end span))) |
| 894 | + |
| 895 | + (define (symbol-leaf? node) |
| 896 | + (and (Token-Leaf? node) |
| 897 | + (token-leaf-type? node 'symbol))) |
| 898 | + |
| 899 | + (define (meaningful nodes) |
| 900 | + (filter non-skippable-node? nodes)) |
| 901 | + |
| 902 | + ;; The first symbol leaf in pre-order across `nodes`, descending into lists |
| 903 | + ;; and prefix forms. This is the bound name of a definition, e.g. `f` in |
| 904 | + ;; `(define (f x) ...)` or `a` in `(define-values (a b) ...)`. |
| 905 | + (define (first-symbol-span nodes) |
| 906 | + (for/or ([node (in-list (meaningful nodes))]) |
| 907 | + (cond |
| 908 | + [(symbol-leaf? node) (Token-Leaf-span node)] |
| 909 | + [else (first-symbol-span (token-node-children node))]))) |
| 910 | + |
| 911 | + (define (node->document-symbol node name-span kind children) |
| 912 | + (DocumentSymbol |
| 913 | + #:name (span-text name-span) |
| 914 | + #:kind kind |
| 915 | + #:range (abs-range->range doc |
| 916 | + (token-node-start node) |
| 917 | + (token-node-end node)) |
| 918 | + #:selectionRange (abs-range->range doc |
| 919 | + (LexerTokenSpan-start name-span) |
| 920 | + (LexerTokenSpan-end name-span)) |
| 921 | + #:children children)) |
| 922 | + |
| 923 | + (define (definition-symbol-kind head function?) |
| 924 | + (match (hash-ref *definition-form-kinds* head) |
| 925 | + ['fn-or-var (if function? SymbolKind-Function SymbolKind-Variable)] |
| 926 | + [kind kind])) |
| 927 | + |
| 928 | + (define (collect-symbols nodes) |
| 929 | + (append-map (lambda (node) |
| 930 | + ;; Symbols contributed by `node` to its enclosing scope. |
| 931 | + (cond |
| 932 | + [(Token-List? node) (list-symbols node)] |
| 933 | + ;; A quoted/prefixed datum is transparent to symbol collection. |
| 934 | + [(Token-Prefix-Tree? node) (collect-symbols (token-node-children node))] |
| 935 | + [else '()])) |
| 936 | + (meaningful nodes))) |
| 937 | + |
| 938 | + (define (list-symbols node) |
| 939 | + (define forms (meaningful (Token-List-children node))) |
| 940 | + (define head-node (and (pair? forms) (car forms))) |
| 941 | + (define head (and head-node |
| 942 | + (symbol-leaf? head-node) |
| 943 | + (span-text (Token-Leaf-span head-node)))) |
| 944 | + (define args (if (pair? forms) (cdr forms) '())) |
| 945 | + (cond |
| 946 | + [(and head (set-member? *field-form-heads* head) |
| 947 | + ;; A field form like `(field z)`, `(field [y 0])`, or a mix declares one Field |
| 948 | + ;; per argument: a bare name covers just the name, a binding group covers the |
| 949 | + ;; whole `[name v]`. |
| 950 | + (filter-map |
| 951 | + (lambda (node) |
| 952 | + (define name-span |
| 953 | + (cond |
| 954 | + [(symbol-leaf? node) (Token-Leaf-span node)] |
| 955 | + [(Token-List? node) (first-symbol-span (Token-List-children node))] |
| 956 | + [else #f])) |
| 957 | + (and name-span |
| 958 | + (node->document-symbol node name-span SymbolKind-Field '()))) |
| 959 | + args))] |
| 960 | + ;; A container form is its own name, so scrolling its body keeps a sticky |
| 961 | + ;; header even though the form binds no name itself. |
| 962 | + [(and head (hash-has-key? *container-form-kinds* head)) |
| 963 | + (list (node->document-symbol node |
| 964 | + (Token-Leaf-span head-node) |
| 965 | + (hash-ref *container-form-kinds* head) |
| 966 | + (collect-symbols args)))] |
| 967 | + [(and head (hash-has-key? *definition-form-kinds* head)) |
| 968 | + (define name-span (first-symbol-span args)) |
| 969 | + (cond |
| 970 | + [name-span |
| 971 | + ;; The function shorthand `(define (f x) ...)` puts the name inside a |
| 972 | + ;; nested list, so the first argument is a list rather than a symbol. |
| 973 | + (define function? (and (pair? args) (not (symbol-leaf? (car args))))) |
| 974 | + (list (node->document-symbol node |
| 975 | + name-span |
| 976 | + (definition-symbol-kind head function?) |
| 977 | + ;; The first argument is the name header; |
| 978 | + ;; nested definitions live in the body. |
| 979 | + (collect-symbols (cdr args))))] |
| 980 | + ;; A definition head with no name yet (mid-edit) is transparent. |
| 981 | + [else (collect-symbols args)])] |
| 982 | + [else (collect-symbols args)])) |
| 983 | + |
| 984 | + (collect-symbols (Token-Forest-nodes (doc-body-forest doc)))) |
| 985 | + |
798 | 986 | (provide Doc? |
799 | 987 | Doc-version |
800 | 988 | Doc-uri |
|
844 | 1032 | doc-rename |
845 | 1033 | doc-prepare-rename |
846 | 1034 | doc-symbols |
847 | | - ) |
| 1035 | + doc-symbols-hierarchical) |
| 1036 | + |
0 commit comments