/
formatting.go
128 lines (120 loc) · 3.65 KB
/
formatting.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package langserver
import (
"log"
"strings"
"github.com/dbaumgarten/yodk/pkg/lsp"
"github.com/dbaumgarten/yodk/pkg/nolol"
"github.com/dbaumgarten/yodk/pkg/parser"
"github.com/dbaumgarten/yodk/pkg/util"
"github.com/pmezard/go-difflib/difflib"
)
// Format computes formatting instructions for the given document.
// Parser errors during formatting are silently discared, as reporting them to the user would just be annoying
// and showing errors is already done by the diagnostics
func (s *LangServer) Format(params *lsp.DocumentFormattingParams) ([]lsp.TextEdit, error) {
unformatted, err := s.cache.Get(params.TextDocument.URI)
file := string(params.TextDocument.URI)
if err != nil {
return nil, err
}
var formatted string
if strings.HasSuffix(file, ".yolol") {
p := parser.NewParser()
parsed, errs := p.Parse(unformatted)
if errs != nil {
return []lsp.TextEdit{}, nil
}
gen := parser.Printer{}
if strings.HasSuffix(file, ".opt.yolol") {
gen.Mode = parser.PrintermodeCompact
} else {
switch s.settings.Yolol.Formatting.Mode {
case FormatModeReadale:
gen.Mode = parser.PrintermodeReadable
case FormatModeCompact:
gen.Mode = parser.PrintermodeCompact
default:
gen.Mode = parser.PrintermodeCompact
}
}
formatted, err = gen.Print(parsed)
if err != nil {
return []lsp.TextEdit{}, nil
}
err = util.CheckForFormattingErrorYolol(parsed, formatted)
if err != nil {
return nil, err
}
} else if strings.HasSuffix(file, ".nolol") {
p := nolol.NewParser()
parsed, errs := p.Parse(unformatted)
if errs != nil {
return []lsp.TextEdit{}, nil
}
printer := nolol.NewPrinter()
formatted, err = printer.Print(parsed)
if err != nil {
return []lsp.TextEdit{}, nil
}
err = util.CheckForFormattingErrorNolol(parsed, formatted)
if err != nil {
return nil, err
}
} else {
log.Println("Unsupported file-type:", file)
}
return ComputeTextEdits(unformatted, formatted), nil
}
// ComputeTextEdits computes text edits that are required to
// change the `unformatted` to the `formatted` text.
// Blatantly stolen from https://github.com/sourcegraph/go-langserver/blob/master/langserver/format.go
func ComputeTextEdits(unformatted string, formatted string) []lsp.TextEdit {
// LSP wants a list of TextEdits. We use difflib to compute a
// non-naive TextEdit. Originally we returned an edit which deleted
// everything followed by inserting everything. This leads to a poor
// experience in vscode.
unformattedLines := strings.Split(unformatted, "\n")
formattedLines := strings.Split(formatted, "\n")
m := difflib.NewMatcher(unformattedLines, formattedLines)
var edits []lsp.TextEdit
for _, op := range m.GetOpCodes() {
switch op.Tag {
case 'r': // 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
edits = append(edits, lsp.TextEdit{
Range: lsp.Range{
Start: lsp.Position{
Line: float64(op.I1),
},
End: lsp.Position{
Line: float64(op.I2),
},
},
NewText: strings.Join(formattedLines[op.J1:op.J2], "\n") + "\n",
})
case 'd': // 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
edits = append(edits, lsp.TextEdit{
Range: lsp.Range{
Start: lsp.Position{
Line: float64(op.I1),
},
End: lsp.Position{
Line: float64(op.I2),
},
},
})
case 'i': // 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
edits = append(edits, lsp.TextEdit{
Range: lsp.Range{
Start: lsp.Position{
Line: float64(op.I1),
},
End: lsp.Position{
Line: float64(op.I1),
},
},
NewText: strings.Join(formattedLines[op.J1:op.J2], "\n") + "\n",
})
}
}
return edits
}