/
render.go
160 lines (145 loc) · 2.93 KB
/
render.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package prettier
// fits determines if the given document
// fits into the remaining line width
//
func fits(remainingWidth int, doc simpleDoc) bool {
if remainingWidth < 0 {
return false
}
if doc, ok := doc.(simpleText); ok {
return fits(
remainingWidth-len(doc.text),
doc.next.get(),
)
}
return true
}
// best returns the best of all possible layouts for the given documents,
// based on the maximum width for each line (the available width),
// and the line width, the number of characters already placed
// on the current line (including indentation)
//
func best(maxLineWidth, lineWidth, indentWidth int, docs *layoutDocs) simpleDoc {
if docs == nil {
return nil
}
// Ignore the empty document (nil)
if docs.doc == nil {
return best(
maxLineWidth,
lineWidth,
indentWidth,
docs.next,
)
}
switch doc := docs.doc.(type) {
case Concat:
newDocs := docs.next
// Prepend the documents to the linked list,
// i.e iterate in reverse order
for i := len(doc) - 1; i >= 0; i-- {
newDocs = &layoutDocs{
indent: docs.indent,
doc: doc[i],
next: newDocs,
}
}
return best(
maxLineWidth,
lineWidth,
indentWidth,
newDocs,
)
case Indent:
return best(
maxLineWidth,
lineWidth,
indentWidth,
&layoutDocs{
indent: docs.indent + 1,
doc: doc.Doc,
next: docs.next,
},
)
case Dedent:
return best(
maxLineWidth,
lineWidth,
indentWidth,
&layoutDocs{
indent: docs.indent - 1,
doc: doc.Doc,
next: docs.next,
},
)
case Group:
newDocs := &layoutDocs{
indent: docs.indent,
doc: doc.Doc.Flatten(),
next: docs.next,
}
flattenedDoc := best(
maxLineWidth,
lineWidth,
indentWidth,
newDocs,
)
if fits(maxLineWidth-lineWidth, flattenedDoc) {
return flattenedDoc
}
newDocs.doc = doc.Doc
return best(maxLineWidth, lineWidth, indentWidth, newDocs)
case Line, SoftLine, HardLine:
return simpleLine{
indent: docs.indent,
next: &simpleDocCache{
getter: func() simpleDoc {
return best(
maxLineWidth,
docs.indent*indentWidth,
indentWidth,
docs.next,
)
},
},
}
case Text:
return simpleText{
text: string(doc),
next: &simpleDocCache{
getter: func() simpleDoc {
return best(
maxLineWidth,
lineWidth+len(doc),
indentWidth,
docs.next,
)
},
},
}
default:
return nil
}
}
// layoutDocs is a linked list of possible document layouts
//
type layoutDocs struct {
indent int
doc Doc
next *layoutDocs
}
// pretty returns the best of all possible layouts for the given document,
// based on the maximum width for each line
//
func pretty(maxLineWidth, indentWidth int, doc Doc) simpleDoc {
// Determine the best layout for the document,
// by starting with just this document,
// and no indentation
docs := &layoutDocs{indent: 0, doc: doc}
return best(
maxLineWidth,
0,
indentWidth,
docs,
)
}