|
| 1 | +package compose |
| 2 | + |
| 3 | +import ( |
| 4 | + "strings" |
| 5 | + |
| 6 | + "github.com/docker/docker-language-server/internal/pkg/document" |
| 7 | + "github.com/docker/docker-language-server/internal/tliron/glsp/protocol" |
| 8 | + "github.com/sourcegraph/jsonrpc2" |
| 9 | +) |
| 10 | + |
| 11 | +type indentation struct { |
| 12 | + original int |
| 13 | + desired int |
| 14 | +} |
| 15 | + |
| 16 | +type comment struct { |
| 17 | + line int |
| 18 | + whitespace int |
| 19 | +} |
| 20 | + |
| 21 | +func formattingOptionTabSize(options protocol.FormattingOptions) (int, error) { |
| 22 | + if tabSize, ok := options[protocol.FormattingOptionTabSize].(float64); ok && tabSize > 0 { |
| 23 | + return int(tabSize), nil |
| 24 | + } |
| 25 | + return -1, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams, Message: "tabSize is not a positive integer"} |
| 26 | +} |
| 27 | + |
| 28 | +func indent(indentation int) string { |
| 29 | + sb := strings.Builder{} |
| 30 | + for range indentation { |
| 31 | + sb.WriteString(" ") |
| 32 | + } |
| 33 | + return sb.String() |
| 34 | +} |
| 35 | + |
| 36 | +func Formatting(doc document.ComposeDocument, options protocol.FormattingOptions) ([]protocol.TextEdit, error) { |
| 37 | + file := doc.File() |
| 38 | + if file == nil || len(file.Docs) == 0 { |
| 39 | + return nil, nil |
| 40 | + } |
| 41 | + tabSize, err := formattingOptionTabSize(options) |
| 42 | + if err != nil { |
| 43 | + return nil, err |
| 44 | + } |
| 45 | + |
| 46 | + edits := []protocol.TextEdit{} |
| 47 | + indentations := []indentation{} |
| 48 | + comments := []comment{} |
| 49 | + topLevelNodeDetected := false |
| 50 | + lines := strings.Split(string(doc.Input()), "\n") |
| 51 | +lineCheck: |
| 52 | + for lineNumber, line := range lines { |
| 53 | + lineIndentation := 0 |
| 54 | + stop := 0 |
| 55 | + isComment := false |
| 56 | + empty := true |
| 57 | + for i := range len(line) { |
| 58 | + if line[i] == 32 { |
| 59 | + lineIndentation++ |
| 60 | + } else if line[i] == '#' { |
| 61 | + empty = false |
| 62 | + isComment = true |
| 63 | + comments = append(comments, comment{line: lineNumber, whitespace: i}) |
| 64 | + break |
| 65 | + } else { |
| 66 | + empty = false |
| 67 | + if strings.HasPrefix(lines[lineNumber], "---") { |
| 68 | + edits = append(edits, formatComments(comments, 0)...) |
| 69 | + comments = nil |
| 70 | + indentations = nil |
| 71 | + topLevelNodeDetected = false |
| 72 | + continue lineCheck |
| 73 | + } |
| 74 | + |
| 75 | + if !topLevelNodeDetected { |
| 76 | + topLevelNodeDetected = true |
| 77 | + if lineIndentation > 0 { |
| 78 | + newIndentation, _ := updateIndentation(indentations, lineIndentation, 0) |
| 79 | + indentations = append(indentations, newIndentation) |
| 80 | + } |
| 81 | + } |
| 82 | + break |
| 83 | + } |
| 84 | + stop++ |
| 85 | + } |
| 86 | + |
| 87 | + if isComment { |
| 88 | + continue |
| 89 | + } |
| 90 | + |
| 91 | + if lineIndentation != 0 { |
| 92 | + newIndentation, resetIndex := updateIndentation(indentations, lineIndentation, tabSize) |
| 93 | + if resetIndex == -1 { |
| 94 | + indentations = append(indentations, newIndentation) |
| 95 | + } else { |
| 96 | + indentations = indentations[:resetIndex+1] |
| 97 | + } |
| 98 | + edits = append(edits, formatComments(comments, newIndentation.desired)...) |
| 99 | + comments = nil |
| 100 | + if lineIndentation != newIndentation.desired { |
| 101 | + edits = append(edits, protocol.TextEdit{ |
| 102 | + NewText: indent(newIndentation.desired), |
| 103 | + Range: protocol.Range{ |
| 104 | + Start: protocol.Position{Line: protocol.UInteger(lineNumber), Character: 0}, |
| 105 | + End: protocol.Position{Line: protocol.UInteger(lineNumber), Character: protocol.UInteger(stop)}, |
| 106 | + }, |
| 107 | + }) |
| 108 | + } |
| 109 | + } else if !empty { |
| 110 | + edits = append(edits, formatComments(comments, 0)...) |
| 111 | + comments = nil |
| 112 | + indentations = nil |
| 113 | + } |
| 114 | + } |
| 115 | + return edits, nil |
| 116 | +} |
| 117 | + |
| 118 | +// formatComments goes over the list of comments and corrects its |
| 119 | +// indentation to the desired indentation only if it differs. Any |
| 120 | +// comment that needs to have its indentation changed will have a |
| 121 | +// TextEdit created for it and included in the returned result. |
| 122 | +func formatComments(comments []comment, desired int) []protocol.TextEdit { |
| 123 | + edits := []protocol.TextEdit{} |
| 124 | + for _, c := range comments { |
| 125 | + if desired != c.whitespace { |
| 126 | + edits = append(edits, protocol.TextEdit{ |
| 127 | + NewText: indent(desired), |
| 128 | + Range: protocol.Range{ |
| 129 | + Start: protocol.Position{Line: protocol.UInteger(c.line), Character: 0}, |
| 130 | + End: protocol.Position{Line: protocol.UInteger(c.line), Character: protocol.UInteger(c.whitespace)}, |
| 131 | + }, |
| 132 | + }) |
| 133 | + } |
| 134 | + } |
| 135 | + return edits |
| 136 | +} |
| 137 | + |
| 138 | +func updateIndentation(indentations []indentation, original, tabSpacing int) (indentation, int) { |
| 139 | + last := tabSpacing |
| 140 | + for i := range indentations { |
| 141 | + if indentations[i].original == original { |
| 142 | + return indentations[i], i |
| 143 | + } |
| 144 | + last = indentations[i].desired + tabSpacing |
| 145 | + } |
| 146 | + return indentation{ |
| 147 | + original: original, |
| 148 | + desired: last, |
| 149 | + }, -1 |
| 150 | +} |
0 commit comments