Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / internal / lsp / source / folding_range.go
1 package source
2
3 import (
4         "context"
5         "go/ast"
6         "go/token"
7         "sort"
8
9         "golang.org/x/tools/internal/lsp/protocol"
10 )
11
12 // FoldingRangeInfo holds range and kind info of folding for an ast.Node
13 type FoldingRangeInfo struct {
14         MappedRange
15         Kind protocol.FoldingRangeKind
16 }
17
18 // FoldingRange gets all of the folding range for f.
19 func FoldingRange(ctx context.Context, snapshot Snapshot, fh FileHandle, lineFoldingOnly bool) (ranges []*FoldingRangeInfo, err error) {
20         // TODO(suzmue): consider limiting the number of folding ranges returned, and
21         // implement a way to prioritize folding ranges in that case.
22         pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
23         if err != nil {
24                 return nil, err
25         }
26         fset := snapshot.FileSet()
27
28         // Get folding ranges for comments separately as they are not walked by ast.Inspect.
29         ranges = append(ranges, commentsFoldingRange(fset, pgf.Mapper, pgf.File)...)
30
31         visit := func(n ast.Node) bool {
32                 rng := foldingRangeFunc(fset, pgf.Mapper, n, lineFoldingOnly)
33                 if rng != nil {
34                         ranges = append(ranges, rng)
35                 }
36                 return true
37         }
38         // Walk the ast and collect folding ranges.
39         ast.Inspect(pgf.File, visit)
40
41         sort.Slice(ranges, func(i, j int) bool {
42                 irng, _ := ranges[i].Range()
43                 jrng, _ := ranges[j].Range()
44                 return protocol.CompareRange(irng, jrng) < 0
45         })
46
47         return ranges, nil
48 }
49
50 // foldingRangeFunc calculates the line folding range for ast.Node n
51 func foldingRangeFunc(fset *token.FileSet, m *protocol.ColumnMapper, n ast.Node, lineFoldingOnly bool) *FoldingRangeInfo {
52         // TODO(suzmue): include trailing empty lines before the closing
53         // parenthesis/brace.
54         var kind protocol.FoldingRangeKind
55         var start, end token.Pos
56         switch n := n.(type) {
57         case *ast.BlockStmt:
58                 // Fold between positions of or lines between "{" and "}".
59                 var startList, endList token.Pos
60                 if num := len(n.List); num != 0 {
61                         startList, endList = n.List[0].Pos(), n.List[num-1].End()
62                 }
63                 start, end = validLineFoldingRange(fset, n.Lbrace, n.Rbrace, startList, endList, lineFoldingOnly)
64         case *ast.CaseClause:
65                 // Fold from position of ":" to end.
66                 start, end = n.Colon+1, n.End()
67         case *ast.CommClause:
68                 // Fold from position of ":" to end.
69                 start, end = n.Colon+1, n.End()
70         case *ast.CallExpr:
71                 // Fold from position of "(" to position of ")".
72                 start, end = n.Lparen+1, n.Rparen
73         case *ast.FieldList:
74                 // Fold between positions of or lines between opening parenthesis/brace and closing parenthesis/brace.
75                 var startList, endList token.Pos
76                 if num := len(n.List); num != 0 {
77                         startList, endList = n.List[0].Pos(), n.List[num-1].End()
78                 }
79                 start, end = validLineFoldingRange(fset, n.Opening, n.Closing, startList, endList, lineFoldingOnly)
80         case *ast.GenDecl:
81                 // If this is an import declaration, set the kind to be protocol.Imports.
82                 if n.Tok == token.IMPORT {
83                         kind = protocol.Imports
84                 }
85                 // Fold between positions of or lines between "(" and ")".
86                 var startSpecs, endSpecs token.Pos
87                 if num := len(n.Specs); num != 0 {
88                         startSpecs, endSpecs = n.Specs[0].Pos(), n.Specs[num-1].End()
89                 }
90                 start, end = validLineFoldingRange(fset, n.Lparen, n.Rparen, startSpecs, endSpecs, lineFoldingOnly)
91         case *ast.CompositeLit:
92                 // Fold between positions of or lines between "{" and "}".
93                 var startElts, endElts token.Pos
94                 if num := len(n.Elts); num != 0 {
95                         startElts, endElts = n.Elts[0].Pos(), n.Elts[num-1].End()
96                 }
97                 start, end = validLineFoldingRange(fset, n.Lbrace, n.Rbrace, startElts, endElts, lineFoldingOnly)
98         }
99
100         // Check that folding positions are valid.
101         if !start.IsValid() || !end.IsValid() {
102                 return nil
103         }
104         // in line folding mode, do not fold if the start and end lines are the same.
105         if lineFoldingOnly && fset.Position(start).Line == fset.Position(end).Line {
106                 return nil
107         }
108         return &FoldingRangeInfo{
109                 MappedRange: NewMappedRange(fset, m, start, end),
110                 Kind:        kind,
111         }
112 }
113
114 // validLineFoldingRange returns start and end token.Pos for folding range if the range is valid.
115 // returns token.NoPos otherwise, which fails token.IsValid check
116 func validLineFoldingRange(fset *token.FileSet, open, close, start, end token.Pos, lineFoldingOnly bool) (token.Pos, token.Pos) {
117         if lineFoldingOnly {
118                 if !open.IsValid() || !close.IsValid() {
119                         return token.NoPos, token.NoPos
120                 }
121
122                 // Don't want to fold if the start/end is on the same line as the open/close
123                 // as an example, the example below should *not* fold:
124                 // var x = [2]string{"d",
125                 // "e" }
126                 if fset.Position(open).Line == fset.Position(start).Line ||
127                         fset.Position(close).Line == fset.Position(end).Line {
128                         return token.NoPos, token.NoPos
129                 }
130
131                 return open + 1, end
132         }
133         return open + 1, close
134 }
135
136 // commentsFoldingRange returns the folding ranges for all comment blocks in file.
137 // The folding range starts at the end of the first comment, and ends at the end of the
138 // comment block and has kind protocol.Comment.
139 func commentsFoldingRange(fset *token.FileSet, m *protocol.ColumnMapper, file *ast.File) (comments []*FoldingRangeInfo) {
140         for _, commentGrp := range file.Comments {
141                 // Don't fold single comments.
142                 if len(commentGrp.List) <= 1 {
143                         continue
144                 }
145                 comments = append(comments, &FoldingRangeInfo{
146                         // Fold from the end of the first line comment to the end of the comment block.
147                         MappedRange: NewMappedRange(fset, m, commentGrp.List[0].End(), commentGrp.End()),
148                         Kind:        protocol.Comment,
149                 })
150         }
151         return comments
152 }