]> git.lizzy.rs Git - micro.git/blob - internal/util/util.go
Update runtime
[micro.git] / internal / util / util.go
1 package util
2
3 import (
4         "bytes"
5         "errors"
6         "fmt"
7         "os"
8         "os/user"
9         "path/filepath"
10         "regexp"
11         "runtime"
12         "strconv"
13         "strings"
14         "time"
15         "unicode"
16         "unicode/utf8"
17
18         "github.com/blang/semver"
19         runewidth "github.com/mattn/go-runewidth"
20 )
21
22 var (
23         // These variables should be set by the linker when compiling
24
25         // Version is the version number or commit hash
26         Version = "0.0.0-unknown"
27         // Semantic version
28         SemVersion semver.Version
29         // CommitHash is the commit this version was built on
30         CommitHash = "Unknown"
31         // CompileDate is the date this binary was compiled on
32         CompileDate = "Unknown"
33         // Debug logging
34         Debug = "OFF"
35         // FakeCursor is used to disable the terminal cursor and have micro
36         // draw its own (enabled for windows consoles where the cursor is slow)
37         FakeCursor = false
38
39         // Stdout is a buffer that is written to stdout when micro closes
40         Stdout *bytes.Buffer
41 )
42
43 func init() {
44         var err error
45         SemVersion, err = semver.Make(Version)
46         if err != nil {
47                 fmt.Println("Invalid version: ", Version, err)
48         }
49
50         if runtime.GOOS == "windows" {
51                 FakeCursor = true
52         }
53         Stdout = new(bytes.Buffer)
54 }
55
56 // SliceEnd returns a byte slice where the index is a rune index
57 // Slices off the start of the slice
58 func SliceEnd(slc []byte, index int) []byte {
59         len := len(slc)
60         i := 0
61         totalSize := 0
62         for totalSize < len {
63                 if i >= index {
64                         return slc[totalSize:]
65                 }
66
67                 _, size := utf8.DecodeRune(slc[totalSize:])
68                 totalSize += size
69                 i++
70         }
71
72         return slc[totalSize:]
73 }
74
75 // SliceEndStr is the same as SliceEnd but for strings
76 func SliceEndStr(str string, index int) string {
77         len := len(str)
78         i := 0
79         totalSize := 0
80         for totalSize < len {
81                 if i >= index {
82                         return str[totalSize:]
83                 }
84
85                 _, size := utf8.DecodeRuneInString(str[totalSize:])
86                 totalSize += size
87                 i++
88         }
89
90         return str[totalSize:]
91 }
92
93 // SliceStart returns a byte slice where the index is a rune index
94 // Slices off the end of the slice
95 func SliceStart(slc []byte, index int) []byte {
96         len := len(slc)
97         i := 0
98         totalSize := 0
99         for totalSize < len {
100                 if i >= index {
101                         return slc[:totalSize]
102                 }
103
104                 _, size := utf8.DecodeRune(slc[totalSize:])
105                 totalSize += size
106                 i++
107         }
108
109         return slc[:totalSize]
110 }
111
112 // SliceStartStr is the same as SliceStart but for strings
113 func SliceStartStr(str string, index int) string {
114         len := len(str)
115         i := 0
116         totalSize := 0
117         for totalSize < len {
118                 if i >= index {
119                         return str[:totalSize]
120                 }
121
122                 _, size := utf8.DecodeRuneInString(str[totalSize:])
123                 totalSize += size
124                 i++
125         }
126
127         return str[:totalSize]
128 }
129
130 // SliceVisualEnd will take a byte slice and slice off the start
131 // up to a given visual index. If the index is in the middle of a
132 // rune the number of visual columns into the rune will be returned
133 // It will also return the char pos of the first character of the slice
134 func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {
135         width := 0
136         i := 0
137         for len(b) > 0 {
138                 r, size := utf8.DecodeRune(b)
139
140                 w := 0
141                 switch r {
142                 case '\t':
143                         ts := tabsize - (width % tabsize)
144                         w = ts
145                 default:
146                         w = runewidth.RuneWidth(r)
147                 }
148                 if width+w > n {
149                         return b, n - width, i
150                 }
151                 width += w
152                 b = b[size:]
153                 i++
154         }
155         return b, n - width, i
156 }
157
158 // Abs is a simple absolute value function for ints
159 func Abs(n int) int {
160         if n < 0 {
161                 return -n
162         }
163         return n
164 }
165
166 // StringWidth returns the visual width of a byte array indexed from 0 to n (rune index)
167 // with a given tabsize
168 func StringWidth(b []byte, n, tabsize int) int {
169         if n <= 0 {
170                 return 0
171         }
172         i := 0
173         width := 0
174         for len(b) > 0 {
175                 r, size := utf8.DecodeRune(b)
176                 b = b[size:]
177
178                 switch r {
179                 case '\t':
180                         ts := tabsize - (width % tabsize)
181                         width += ts
182                 default:
183                         width += runewidth.RuneWidth(r)
184                 }
185
186                 i++
187
188                 if i == n {
189                         return width
190                 }
191         }
192         return width
193 }
194
195 // Min takes the min of two ints
196 func Min(a, b int) int {
197         if a > b {
198                 return b
199         }
200         return a
201 }
202
203 // Max takes the max of two ints
204 func Max(a, b int) int {
205         if a > b {
206                 return a
207         }
208         return b
209 }
210
211 // FSize gets the size of a file
212 func FSize(f *os.File) int64 {
213         fi, _ := f.Stat()
214         return fi.Size()
215 }
216
217 // IsWordChar returns whether or not the string is a 'word character'
218 // Word characters are defined as numbers, letters, or '_'
219 func IsWordChar(r rune) bool {
220         return unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_'
221 }
222
223 // Spaces returns a string with n spaces
224 func Spaces(n int) string {
225         return strings.Repeat(" ", n)
226 }
227
228 // IsSpaces checks if a given string is only spaces
229 func IsSpaces(str []byte) bool {
230         for _, c := range str {
231                 if c != ' ' {
232                         return false
233                 }
234         }
235
236         return true
237 }
238
239 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
240 func IsSpacesOrTabs(str []byte) bool {
241         for _, c := range str {
242                 if c != ' ' && c != '\t' {
243                         return false
244                 }
245         }
246
247         return true
248 }
249
250 // IsWhitespace returns true if the given rune is a space, tab, or newline
251 func IsWhitespace(c rune) bool {
252         return unicode.IsSpace(c)
253 }
254
255 // IsBytesWhitespace returns true if the given bytes are all whitespace
256 func IsBytesWhitespace(b []byte) bool {
257         for _, c := range b {
258                 if !IsWhitespace(rune(c)) {
259                         return false
260                 }
261         }
262         return true
263 }
264
265 // RunePos returns the rune index of a given byte index
266 // Make sure the byte index is not between code points
267 func RunePos(b []byte, i int) int {
268         return utf8.RuneCount(b[:i])
269 }
270
271 // MakeRelative will attempt to make a relative path between path and base
272 func MakeRelative(path, base string) (string, error) {
273         if len(path) > 0 {
274                 rel, err := filepath.Rel(base, path)
275                 if err != nil {
276                         return path, err
277                 }
278                 return rel, nil
279         }
280         return path, nil
281 }
282
283 // ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
284 // home directory. Does nothing if the path does not start with '~'.
285 func ReplaceHome(path string) (string, error) {
286         if !strings.HasPrefix(path, "~") {
287                 return path, nil
288         }
289
290         var userData *user.User
291         var err error
292
293         homeString := strings.Split(path, "/")[0]
294         if homeString == "~" {
295                 userData, err = user.Current()
296                 if err != nil {
297                         return "", errors.New("Could not find user: " + err.Error())
298                 }
299         } else {
300                 userData, err = user.Lookup(homeString[1:])
301                 if err != nil {
302                         return "", errors.New("Could not find user: " + err.Error())
303                 }
304         }
305
306         home := userData.HomeDir
307
308         return strings.Replace(path, homeString, home, 1), nil
309 }
310
311 // GetPathAndCursorPosition returns a filename without everything following a `:`
312 // This is used for opening files like util.go:10:5 to specify a line and column
313 // Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
314 func GetPathAndCursorPosition(path string) (string, []string) {
315         re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
316         match := re.FindStringSubmatch(path)
317         // no lines/columns were specified in the path, return just the path with no cursor location
318         if len(match) == 0 {
319                 return path, nil
320         } else if match[len(match)-1] != "" {
321                 // if the last capture group match isn't empty then both line and column were provided
322                 return match[1], match[2:]
323         }
324         // if it was empty, then only a line was provided, so default to column 0
325         return match[1], []string{match[2], "0"}
326 }
327
328 // GetModTime returns the last modification time for a given file
329 func GetModTime(path string) (time.Time, error) {
330         info, err := os.Stat(path)
331         if err != nil {
332                 return time.Now(), err
333         }
334         return info.ModTime(), nil
335 }
336
337 // EscapePath replaces every path separator in a given path with a %
338 func EscapePath(path string) string {
339         path = filepath.ToSlash(path)
340         return strings.Replace(path, "/", "%", -1)
341 }
342
343 // GetLeadingWhitespace returns the leading whitespace of the given byte array
344 func GetLeadingWhitespace(b []byte) []byte {
345         ws := []byte{}
346         for len(b) > 0 {
347                 r, size := utf8.DecodeRune(b)
348                 if r == ' ' || r == '\t' {
349                         ws = append(ws, byte(r))
350                 } else {
351                         break
352                 }
353
354                 b = b[size:]
355         }
356         return ws
357 }
358
359 // IntOpt turns a float64 setting to an int
360 func IntOpt(opt interface{}) int {
361         return int(opt.(float64))
362 }
363
364 // GetCharPosInLine gets the char position of a visual x y
365 // coordinate (this is necessary because tabs are 1 char but
366 // 4 visual spaces)
367 func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
368         // Scan rune by rune until we exceed the visual width that we are
369         // looking for. Then we can return the character position we have found
370         i := 0     // char pos
371         width := 0 // string visual width
372         for len(b) > 0 {
373                 r, size := utf8.DecodeRune(b)
374                 b = b[size:]
375
376                 switch r {
377                 case '\t':
378                         ts := tabsize - (width % tabsize)
379                         width += ts
380                 default:
381                         width += runewidth.RuneWidth(r)
382                 }
383
384                 if width >= visualPos {
385                         if width == visualPos {
386                                 i++
387                         }
388                         break
389                 }
390                 i++
391         }
392
393         return i
394 }
395
396 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
397 // as 'true' and 'false' respectively
398 func ParseBool(str string) (bool, error) {
399         if str == "on" {
400                 return true, nil
401         }
402         if str == "off" {
403                 return false, nil
404         }
405         return strconv.ParseBool(str)
406 }
407
408 // Clamp clamps a value between min and max
409 func Clamp(val, min, max int) int {
410         if val < min {
411                 val = min
412         } else if val > max {
413                 val = max
414         }
415         return val
416 }
417
418 func IsNonAlphaNumeric(c rune) bool {
419         return !unicode.IsLetter(c) && !unicode.IsNumber(c)
420 }
421
422 func ParseSpecial(s string) string {
423         return strings.Replace(s, "\\t", "\t", -1)
424 }
425
426 // String converts a byte array to a string (for lua plugins)
427 func String(s []byte) string {
428         return string(s)
429 }