/
open.go
259 lines (229 loc) · 6.14 KB
/
open.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
package actions
import (
"bytes"
"fmt"
"image"
"io/ioutil"
"os"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/driusan/de/demodel"
"github.com/driusan/de/viewer"
)
// setOpenDot sets dot when opening a file to the spec defined by
// params. Understood specs are:
//
// filename:lineno -- selects entire line
// filename:lineno:column -- sets cursor to column at lineNo
// filename:/regex/ -- selects the first match of regex in the file
func setOpenDot(buff *demodel.CharBuffer, params string) {
if buff == nil {
return
}
if params == "" {
buff.Dot.Start = 0
buff.Dot.End = 0
return
}
pArray := strings.Split(params, ":")
if len(pArray) == 0 {
// this shouldn't happen, at least we should have had 1 element
// with the same content
panic("Split a non-empty string and got nothing back.")
}
if params[0] == '/' {
var re *regexp.Regexp
var err error
// make sure there's 2 slashes, otherwise it's not a valid regex as far
// as we're concerned.
if i := strings.LastIndex(params, "/"); i > 0 {
re, err = regexp.Compile(params[1:i])
if err != nil {
fmt.Fprintf(os.Stderr, "Invalid regex in file open semantics: %s\n", params)
return
}
} else {
fmt.Fprintf(os.Stderr, "Invalid regex in file open semantics: usage filename:/re/\n")
return
}
match := re.FindIndex(buff.Buffer)
if match == nil {
buff.Dot.Start = 0
buff.Dot.End = 0
return
}
buff.Dot.Start = uint(match[0])
buff.Dot.End = uint(match[1] - 1)
return
}
var lineNo, columnNo int
var err error
lineNo, err = strconv.Atoi(pArray[0])
if err != nil {
// wasn't a number, wasn't a regex. Don't know what's going on, so
// abort.
return
}
if lineNo >= 0 && len(pArray) >= 2 {
columnNo, err = strconv.Atoi(pArray[1])
if err != nil {
// a lineNo was set and the column was invalid,
// to just ignore and pretend it was 0
columnNo = 0
}
}
if lineNo >= 0 {
currentLine := 1
// if endNext is true, we've found the start of the line and should set dot.End to
// the next line found.
endNext := false
for i := 0; i < len(buff.Buffer); i++ {
if buff.Buffer[i] == '\n' {
currentLine++
if endNext {
buff.Dot.End = uint(i)
return
}
}
if currentLine >= lineNo && !endNext {
// a column was provided, to set the cursor to that point
if columnNo > 0 {
buff.Dot.Start = uint(i + columnNo)
buff.Dot.End = buff.Dot.Start
return
}
// no column provided, so set dot to the whole line
buff.Dot.Start = uint(i + 1)
endNext = true
}
}
// was the last line, so set the end to the end of the buffer.
if endNext {
buff.Dot.End = uint(len(buff.Buffer)) - 1
}
}
}
func OpenFile(filename string, buff *demodel.CharBuffer, v demodel.Viewport) error {
var pager bool
fstat, err := os.Stat(filename)
// when opening a file as file:lineno or file:lineno:column: or file:regex
// dot params is the part after the colon, denoting where to move dot after
// opening the file.
var dotparams string
if filename == "-" {
pager = true
goto consideredgood
}
if err != nil {
// check if it's a go style pathspec and try that instead.
if path := strings.Index(filename, ":"); path > 0 {
if path+1 < len(filename) {
dotparams = filename[path+1:]
}
filename = filename[:path]
var err2 error
if fstat, err2 = os.Stat(filename[:path]); err2 != nil {
// return the original error, this one was mostly just
// a stab in the dark.
return err
}
// You're not the boss of me, Dijkstra!
goto consideredgood
}
return err
}
// filename has been stat'ed and can generally be considered good to open.
consideredgood:
if !pager && fstat.IsDir() {
// it's a directory
files, err := ioutil.ReadDir(filename)
if err != nil {
return err
}
os.Chdir(filename)
var bBuff bytes.Buffer
if runtime.GOOS != "windows" {
fmt.Fprintf(&bBuff, "Shell\n\n./\n../\n")
}
for _, f := range files {
if f.IsDir() {
fmt.Fprintf(&bBuff, "%s/\n", f.Name())
} else {
fmt.Fprintf(&bBuff, "%s\n", f.Name())
}
}
// save a reference to the old filename so we can strip the prefix.
oldFilename := buff.Filename
buff.Buffer = bBuff.Bytes()
buff.Filename = filename
buff.Dot.Start = 0
buff.Dot.End = 0
buff.Tagline.Buffer = append(
[]byte(filename+" "),
bytes.TrimPrefix(buff.Tagline.Buffer, []byte(oldFilename))...,
)
} else {
// it's a file
var b []byte
var ferr error
if pager == true {
b, ferr = ioutil.ReadAll(os.Stdin)
} else {
b, ferr = ioutil.ReadFile(filename)
}
if ferr != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return ferr
}
oldFilename := buff.Filename
buff.Buffer = b
buff.Filename = filename
buff.Dot.Start = 0
buff.Dot.End = 0
buff.Dirty = false
if buff.Tagline.Buffer == nil {
buff.Tagline.Buffer = make([]byte, 0)
}
// if the tagline starts with the filename, update it, otherwise,
// add it as a prefix.
buff.Tagline.Buffer = append(
[]byte(filename+" "),
bytes.TrimPrefix(buff.Tagline.Buffer, []byte(oldFilename))...,
)
}
// new file, nothing to undo yet..
buff.Undo = nil
setOpenDot(buff, dotparams)
FocusViewport(buff.Dot.Start, buff, v)
return nil
}
// Focus the viewport v (which is assumed to be rendering buff), such that
// the character at buff.Buffer[idx] is visible somewhere on the screen.
func FocusViewport(idx uint, buff *demodel.CharBuffer, v demodel.Viewport) error {
if v == nil {
return fmt.Errorf("No viewport")
}
im := v.GetImageMap(buff, image.ZR)
if im == nil {
return fmt.Errorf("Could not get image map for buffer.")
}
rect, err := im.Get(idx)
if err != nil {
return fmt.Errorf("Could not find location of character %d from image map", idx)
}
// this should be a method in the demodel.Viewport
// interface, but it isn't..
if vp, ok := v.(*viewer.Viewport); ok {
vp.Location = rect.Min
// don't put the character *directly* at the top unless it's the very start
// of the file. If the screen is less than 100px tall, this probably isn't
//the only thing that will break.
if vp.Location.Y > 100 {
vp.Location.Y -= 100
}
return nil
}
return fmt.Errorf("Could not set viewport location.")
}