]> git.lizzy.rs Git - micro.git/blob - cmd/micro/util.go
Large syntax highlighting memory optimization
[micro.git] / cmd / micro / util.go
1 package main
2
3 import (
4         "os"
5         "path/filepath"
6         "reflect"
7         "runtime"
8         "strconv"
9         "strings"
10         "time"
11         "unicode/utf8"
12
13         "github.com/mattn/go-runewidth"
14         homedir "github.com/mitchellh/go-homedir"
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 // NumOccurrences counts the number of occurrences of a byte in a string
41 func NumOccurrences(s string, c byte) int {
42         var n int
43         for i := 0; i < len(s); i++ {
44                 if s[i] == c {
45                         n++
46                 }
47         }
48         return n
49 }
50
51 // Spaces returns a string with n spaces
52 func Spaces(n int) string {
53         return strings.Repeat(" ", n)
54 }
55
56 // Min takes the min of two ints
57 func Min(a, b int) int {
58         if a > b {
59                 return b
60         }
61         return a
62 }
63
64 // Max takes the max of two ints
65 func Max(a, b int) int {
66         if a > b {
67                 return a
68         }
69         return b
70 }
71
72 // FSize gets the size of a file
73 func FSize(f *os.File) int64 {
74         fi, _ := f.Stat()
75         // get the size
76         return fi.Size()
77 }
78
79 // IsWordChar returns whether or not the string is a 'word character'
80 // If it is a unicode character, then it does not match
81 // Word characters are defined as [A-Za-z0-9_]
82 func IsWordChar(str string) bool {
83         if len(str) > 1 {
84                 // Unicode
85                 return true
86         }
87         c := str[0]
88         return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
89 }
90
91 // IsWhitespace returns true if the given rune is a space, tab, or newline
92 func IsWhitespace(c rune) bool {
93         return c == ' ' || c == '\t' || c == '\n'
94 }
95
96 // IsStrWhitespace returns true if the given string is all whitespace
97 func IsStrWhitespace(str string) bool {
98         for _, c := range str {
99                 if !IsWhitespace(c) {
100                         return false
101                 }
102         }
103         return true
104 }
105
106 // Contains returns whether or not a string array contains a given string
107 func Contains(list []string, a string) bool {
108         for _, b := range list {
109                 if b == a {
110                         return true
111                 }
112         }
113         return false
114 }
115
116 // Insert makes a simple insert into a string at the given position
117 func Insert(str string, pos int, value string) string {
118         return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
119 }
120
121 // MakeRelative will attempt to make a relative path between path and base
122 func MakeRelative(path, base string) (string, error) {
123         if len(path) > 0 {
124                 rel, err := filepath.Rel(base, path)
125                 if err != nil {
126                         return path, err
127                 }
128                 return rel, nil
129         }
130         return path, nil
131 }
132
133 // GetLeadingWhitespace returns the leading whitespace of the given string
134 func GetLeadingWhitespace(str string) string {
135         ws := ""
136         for _, c := range str {
137                 if c == ' ' || c == '\t' {
138                         ws += string(c)
139                 } else {
140                         break
141                 }
142         }
143         return ws
144 }
145
146 // IsSpaces checks if a given string is only spaces
147 func IsSpaces(str string) bool {
148         for _, c := range str {
149                 if c != ' ' {
150                         return false
151                 }
152         }
153
154         return true
155 }
156
157 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
158 func IsSpacesOrTabs(str string) bool {
159         for _, c := range str {
160                 if c != ' ' && c != '\t' {
161                         return false
162                 }
163         }
164
165         return true
166 }
167
168 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
169 // as 'true' and 'false' respectively
170 func ParseBool(str string) (bool, error) {
171         if str == "on" {
172                 return true, nil
173         }
174         if str == "off" {
175                 return false, nil
176         }
177         return strconv.ParseBool(str)
178 }
179
180 // EscapePath replaces every path separator in a given path with a %
181 func EscapePath(path string) string {
182         path = filepath.ToSlash(path)
183         return strings.Replace(path, "/", "%", -1)
184 }
185
186 // GetModTime returns the last modification time for a given file
187 // It also returns a boolean if there was a problem accessing the file
188 func GetModTime(path string) (time.Time, bool) {
189         info, err := os.Stat(path)
190         if err != nil {
191                 return time.Now(), false
192         }
193         return info.ModTime(), true
194 }
195
196 // StringWidth returns the width of a string where tabs count as `tabsize` width
197 func StringWidth(str string, tabsize int) int {
198         sw := runewidth.StringWidth(str)
199         lineIdx := 0
200         for _, ch := range str {
201                 switch ch {
202                 case '\t':
203                         ts := tabsize - (lineIdx % tabsize)
204                         sw += ts
205                         lineIdx += ts
206                 case '\n':
207                         lineIdx = 0
208                 default:
209                         lineIdx++
210                 }
211         }
212         return sw
213 }
214
215 // WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
216 // that have a width larger than 1 (this also counts tabs as `tabsize` width)
217 func WidthOfLargeRunes(str string, tabsize int) int {
218         count := 0
219         lineIdx := 0
220         for _, ch := range str {
221                 var w int
222                 if ch == '\t' {
223                         w = tabsize - (lineIdx % tabsize)
224                 } else {
225                         w = runewidth.RuneWidth(ch)
226                 }
227                 if w > 1 {
228                         count += (w - 1)
229                 }
230                 if ch == '\n' {
231                         lineIdx = 0
232                 } else {
233                         lineIdx += w
234                 }
235         }
236         return count
237 }
238
239 // RunePos returns the rune index of a given byte index
240 // This could cause problems if the byte index is between code points
241 func runePos(p int, str string) int {
242         return utf8.RuneCountInString(str[:p])
243 }
244
245 func lcs(a, b string) string {
246         arunes := []rune(a)
247         brunes := []rune(b)
248
249         lcs := ""
250         for i, r := range arunes {
251                 if i >= len(brunes) {
252                         break
253                 }
254                 if r == brunes[i] {
255                         lcs += string(r)
256                 } else {
257                         break
258                 }
259         }
260         return lcs
261 }
262
263 // CommonSubstring gets a common substring among the inputs
264 func CommonSubstring(arr ...string) string {
265         commonStr := arr[0]
266
267         for _, str := range arr[1:] {
268                 commonStr = lcs(commonStr, str)
269         }
270
271         return commonStr
272 }
273
274 // Abs is a simple absolute value function for ints
275 func Abs(n int) int {
276         if n < 0 {
277                 return -n
278         }
279         return n
280 }
281
282 // FuncName returns the full name of a given function object
283 func FuncName(i interface{}) string {
284         return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
285 }
286
287 // ShortFuncName returns the name only of a given function object
288 func ShortFuncName(i interface{}) string {
289         return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
290 }
291
292 // ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
293 // home directory. Does nothing if the path does not start with '~'.
294 func ReplaceHome(path string) string {
295         if !strings.HasPrefix(path, "~") {
296                 return path
297         }
298
299         home, err := homedir.Dir()
300         if err != nil {
301                 messenger.Error("Could not find home directory: ", err)
302                 return path
303         }
304         return strings.Replace(path, "~", home, 1)
305 }