]> git.lizzy.rs Git - micro.git/blob - cmd/micro/util.go
Code optimisation (#1117)
[micro.git] / cmd / micro / util.go
1 package main
2
3 import (
4         "os"
5         "os/user"
6         "path/filepath"
7         "reflect"
8         "runtime"
9         "strconv"
10         "strings"
11         "time"
12         "unicode/utf8"
13
14         "github.com/mattn/go-runewidth"
15 )
16
17 // Util.go is a collection of utility functions that are used throughout
18 // the program
19
20 // Count returns the length of a string in runes
21 // This is exactly equivalent to utf8.RuneCountInString(), just less characters
22 func Count(s string) int {
23         return utf8.RuneCountInString(s)
24 }
25
26 // Convert byte array to rune array
27 func toRunes(b []byte) []rune {
28         runes := make([]rune, 0, utf8.RuneCount(b))
29
30         for len(b) > 0 {
31                 r, size := utf8.DecodeRune(b)
32                 runes = append(runes, r)
33
34                 b = b[size:]
35         }
36
37         return runes
38 }
39
40 func sliceStart(slc []byte, index int) []byte {
41         len := len(slc)
42         i := 0
43         totalSize := 0
44         for totalSize < len {
45                 if i >= index {
46                         return slc[totalSize:]
47                 }
48
49                 _, size := utf8.DecodeRune(slc[totalSize:])
50                 totalSize += size
51                 i++
52         }
53
54         return slc[totalSize:]
55 }
56
57 func sliceEnd(slc []byte, index int) []byte {
58         len := len(slc)
59         i := 0
60         totalSize := 0
61         for totalSize < len {
62                 if i >= index {
63                         return slc[:totalSize]
64                 }
65
66                 _, size := utf8.DecodeRune(slc[totalSize:])
67                 totalSize += size
68                 i++
69         }
70
71         return slc[:totalSize]
72 }
73
74 // NumOccurrences counts the number of occurrences of a byte in a string
75 func NumOccurrences(s string, c byte) int {
76         var n int
77         for i := 0; i < len(s); i++ {
78                 if s[i] == c {
79                         n++
80                 }
81         }
82         return n
83 }
84
85 // Spaces returns a string with n spaces
86 func Spaces(n int) string {
87         return strings.Repeat(" ", n)
88 }
89
90 // Min takes the min of two ints
91 func Min(a, b int) int {
92         if a > b {
93                 return b
94         }
95         return a
96 }
97
98 // Max takes the max of two ints
99 func Max(a, b int) int {
100         if a > b {
101                 return a
102         }
103         return b
104 }
105
106 // FSize gets the size of a file
107 func FSize(f *os.File) int64 {
108         fi, _ := f.Stat()
109         // get the size
110         return fi.Size()
111 }
112
113 // IsWordChar returns whether or not the string is a 'word character'
114 // If it is a unicode character, then it does not match
115 // Word characters are defined as [A-Za-z0-9_]
116 func IsWordChar(str string) bool {
117         if len(str) > 1 {
118                 // Unicode
119                 return true
120         }
121         c := str[0]
122         return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
123 }
124
125 // IsWhitespace returns true if the given rune is a space, tab, or newline
126 func IsWhitespace(c rune) bool {
127         return c == ' ' || c == '\t' || c == '\n'
128 }
129
130 // IsStrWhitespace returns true if the given string is all whitespace
131 func IsStrWhitespace(str string) bool {
132         for _, c := range str {
133                 if !IsWhitespace(c) {
134                         return false
135                 }
136         }
137         return true
138 }
139
140 // Contains returns whether or not a string array contains a given string
141 func Contains(list []string, a string) bool {
142         for _, b := range list {
143                 if b == a {
144                         return true
145                 }
146         }
147         return false
148 }
149
150 // Insert makes a simple insert into a string at the given position
151 func Insert(str string, pos int, value string) string {
152         return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
153 }
154
155 // MakeRelative will attempt to make a relative path between path and base
156 func MakeRelative(path, base string) (string, error) {
157         if len(path) > 0 {
158                 rel, err := filepath.Rel(base, path)
159                 if err != nil {
160                         return path, err
161                 }
162                 return rel, nil
163         }
164         return path, nil
165 }
166
167 // GetLeadingWhitespace returns the leading whitespace of the given string
168 func GetLeadingWhitespace(str string) string {
169         ws := ""
170         for _, c := range str {
171                 if c == ' ' || c == '\t' {
172                         ws += string(c)
173                 } else {
174                         break
175                 }
176         }
177         return ws
178 }
179
180 // IsSpaces checks if a given string is only spaces
181 func IsSpaces(str []byte) bool {
182         for _, c := range str {
183                 if c != ' ' {
184                         return false
185                 }
186         }
187
188         return true
189 }
190
191 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
192 func IsSpacesOrTabs(str string) bool {
193         for _, c := range str {
194                 if c != ' ' && c != '\t' {
195                         return false
196                 }
197         }
198
199         return true
200 }
201
202 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
203 // as 'true' and 'false' respectively
204 func ParseBool(str string) (bool, error) {
205         if str == "on" {
206                 return true, nil
207         }
208         if str == "off" {
209                 return false, nil
210         }
211         return strconv.ParseBool(str)
212 }
213
214 // EscapePath replaces every path separator in a given path with a %
215 func EscapePath(path string) string {
216         path = filepath.ToSlash(path)
217         return strings.Replace(path, "/", "%", -1)
218 }
219
220 // GetModTime returns the last modification time for a given file
221 // It also returns a boolean if there was a problem accessing the file
222 func GetModTime(path string) (time.Time, bool) {
223         info, err := os.Stat(path)
224         if err != nil {
225                 return time.Now(), false
226         }
227         return info.ModTime(), true
228 }
229
230 // StringWidth returns the width of a string where tabs count as `tabsize` width
231 func StringWidth(str string, tabsize int) int {
232         sw := runewidth.StringWidth(str)
233         lineIdx := 0
234         for _, ch := range str {
235                 switch ch {
236                 case '\t':
237                         ts := tabsize - (lineIdx % tabsize)
238                         sw += ts
239                         lineIdx += ts
240                 case '\n':
241                         lineIdx = 0
242                 default:
243                         lineIdx++
244                 }
245         }
246         return sw
247 }
248
249 // WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
250 // that have a width larger than 1 (this also counts tabs as `tabsize` width)
251 func WidthOfLargeRunes(str string, tabsize int) int {
252         count := 0
253         lineIdx := 0
254         for _, ch := range str {
255                 var w int
256                 if ch == '\t' {
257                         w = tabsize - (lineIdx % tabsize)
258                 } else {
259                         w = runewidth.RuneWidth(ch)
260                 }
261                 if w > 1 {
262                         count += (w - 1)
263                 }
264                 if ch == '\n' {
265                         lineIdx = 0
266                 } else {
267                         lineIdx += w
268                 }
269         }
270         return count
271 }
272
273 // RunePos returns the rune index of a given byte index
274 // This could cause problems if the byte index is between code points
275 func runePos(p int, str string) int {
276         return utf8.RuneCountInString(str[:p])
277 }
278
279 func lcs(a, b string) string {
280         arunes := []rune(a)
281         brunes := []rune(b)
282
283         lcs := ""
284         for i, r := range arunes {
285                 if i >= len(brunes) {
286                         break
287                 }
288                 if r == brunes[i] {
289                         lcs += string(r)
290                 } else {
291                         break
292                 }
293         }
294         return lcs
295 }
296
297 // CommonSubstring gets a common substring among the inputs
298 func CommonSubstring(arr ...string) string {
299         commonStr := arr[0]
300
301         for _, str := range arr[1:] {
302                 commonStr = lcs(commonStr, str)
303         }
304
305         return commonStr
306 }
307
308 // Abs is a simple absolute value function for ints
309 func Abs(n int) int {
310         if n < 0 {
311                 return -n
312         }
313         return n
314 }
315
316 // FuncName returns the full name of a given function object
317 func FuncName(i interface{}) string {
318         return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
319 }
320
321 // ShortFuncName returns the name only of a given function object
322 func ShortFuncName(i interface{}) string {
323         return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
324 }
325
326 // ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
327 // home directory. Does nothing if the path does not start with '~'.
328 func ReplaceHome(path string) string {
329         if !strings.HasPrefix(path, "~") {
330                 return path
331         }
332
333         var userData *user.User
334         var err error
335
336         homeString := strings.Split(path, "/")[0]
337         if homeString == "~" {
338                 userData, err = user.Current()
339                 if err != nil {
340                         messenger.Error("Could not find user: ", err)
341                 }
342         } else {
343                 userData, err = user.Lookup(homeString[1:])
344                 if err != nil {
345                         if messenger != nil {
346                                 messenger.Error("Could not find user: ", err)
347                         } else {
348                                 TermMessage("Could not find user: ", err)
349                         }
350                         return ""
351                 }
352         }
353
354         home := userData.HomeDir
355
356         return strings.Replace(path, homeString, home, 1)
357 }
358
359 // GetPath returns a filename without everything following a `:`
360 // This is used for opening files like util.go:10:5 to specify a line and column
361 func GetPath(path string) string {
362         if strings.Contains(path, ":") {
363                 path = strings.Split(path, ":")[0]
364         }
365         return path
366 }