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