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