github.com/april1989/origin-go-tools@v0.0.32/internal/lsp/source/folding_range.go (about) 1 package source 2 3 import ( 4 "context" 5 "go/ast" 6 "go/token" 7 "sort" 8 9 "github.com/april1989/origin-go-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 }