14 runewidth "github.com/mattn/go-runewidth"
17 // SliceEnd returns a byte slice where the index is a rune index
18 // Slices off the start of the slice
19 func SliceEnd(slc []byte, index int) []byte {
25 return slc[totalSize:]
28 _, size := utf8.DecodeRune(slc[totalSize:])
33 return slc[totalSize:]
36 // SliceEndStr is the same as SliceEnd but for strings
37 func SliceEndStr(str string, index int) string {
43 return str[totalSize:]
46 _, size := utf8.DecodeRuneInString(str[totalSize:])
51 return str[totalSize:]
54 // SliceStart returns a byte slice where the index is a rune index
55 // Slices off the end of the slice
56 func SliceStart(slc []byte, index int) []byte {
62 return slc[:totalSize]
65 _, size := utf8.DecodeRune(slc[totalSize:])
70 return slc[:totalSize]
73 // SliceStartStr is the same as SliceStart but for strings
74 func SliceStartStr(str string, index int) string {
80 return str[:totalSize]
83 _, size := utf8.DecodeRuneInString(str[totalSize:])
88 return str[:totalSize]
91 // SliceVisualEnd will take a byte slice and slice off the start
92 // up to a given visual index. If the index is in the middle of a
93 // rune the number of visual columns into the rune will be returned
94 // It will also return the char pos of the first character of the slice
95 func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {
99 r, size := utf8.DecodeRune(b)
104 ts := tabsize - (width % tabsize)
107 w = runewidth.RuneWidth(r)
110 return b, n - width, i
116 return b, n - width, i
119 // Abs is a simple absolute value function for ints
120 func Abs(n int) int {
127 // StringWidth returns the visual width of a byte array indexed from 0 to n (rune index)
128 // with a given tabsize
129 func StringWidth(b []byte, n, tabsize int) int {
136 r, size := utf8.DecodeRune(b)
141 ts := tabsize - (width % tabsize)
144 width += runewidth.RuneWidth(r)
156 // Min takes the min of two ints
157 func Min(a, b int) int {
164 // Max takes the max of two ints
165 func Max(a, b int) int {
172 // FSize gets the size of a file
173 func FSize(f *os.File) int64 {
178 // IsWordChar returns whether or not the string is a 'word character'
179 // If it is a unicode character, then it does not match
180 // Word characters are defined as [A-Za-z0-9_]
181 func IsWordChar(r rune) bool {
182 return (r >= '0' && r <= '9') || (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r == '_')
185 // Spaces returns a string with n spaces
186 func Spaces(n int) string {
187 return strings.Repeat(" ", n)
190 // IsSpaces checks if a given string is only spaces
191 func IsSpaces(str []byte) bool {
192 for _, c := range str {
201 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
202 func IsSpacesOrTabs(str []byte) bool {
203 for _, c := range str {
204 if c != ' ' && c != '\t' {
212 // IsWhitespace returns true if the given rune is a space, tab, or newline
213 func IsWhitespace(c rune) bool {
214 return c == ' ' || c == '\t' || c == '\n'
217 // IsStrWhitespace returns true if the given string is all whitespace
218 func IsStrWhitespace(str string) bool {
219 // Range loop for unicode correctness
220 for _, c := range str {
221 if !IsWhitespace(c) {
228 // RunePos returns the rune index of a given byte index
229 // Make sure the byte index is not between code points
230 func RunePos(b []byte, i int) int {
231 return utf8.RuneCount(b[:i])
234 // MakeRelative will attempt to make a relative path between path and base
235 func MakeRelative(path, base string) (string, error) {
237 rel, err := filepath.Rel(base, path)
246 // TODO: consider changing because of snap segfault
247 // ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
248 // home directory. Does nothing if the path does not start with '~'.
249 func ReplaceHome(path string) (string, error) {
250 if !strings.HasPrefix(path, "~") {
254 var userData *user.User
257 homeString := strings.Split(path, "/")[0]
258 if homeString == "~" {
259 userData, err = user.Current()
261 return "", errors.New("Could not find user: " + err.Error())
264 userData, err = user.Lookup(homeString[1:])
266 return "", errors.New("Could not find user: " + err.Error())
270 home := userData.HomeDir
272 return strings.Replace(path, homeString, home, 1), nil
275 // GetPathAndCursorPosition returns a filename without everything following a `:`
276 // This is used for opening files like util.go:10:5 to specify a line and column
277 // Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
278 func GetPathAndCursorPosition(path string) (string, []string) {
279 re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
280 match := re.FindStringSubmatch(path)
281 // no lines/columns were specified in the path, return just the path with no cursor location
284 } else if match[len(match)-1] != "" {
285 // if the last capture group match isn't empty then both line and column were provided
286 return match[1], match[2:]
288 // if it was empty, then only a line was provided, so default to column 0
289 return match[1], []string{match[2], "0"}
292 // GetModTime returns the last modification time for a given file
293 func GetModTime(path string) (time.Time, error) {
294 info, err := os.Stat(path)
296 return time.Now(), err
298 return info.ModTime(), nil
301 // EscapePath replaces every path separator in a given path with a %
302 func EscapePath(path string) string {
303 path = filepath.ToSlash(path)
304 return strings.Replace(path, "/", "%", -1)
307 // GetLeadingWhitespace returns the leading whitespace of the given byte array
308 func GetLeadingWhitespace(b []byte) []byte {
311 r, size := utf8.DecodeRune(b)
312 if r == ' ' || r == '\t' {
313 ws = append(ws, byte(r))
323 // IntOpt turns a float64 setting to an int
324 func IntOpt(opt interface{}) int {
325 return int(opt.(float64))
328 // GetCharPosInLine gets the char position of a visual x y
329 // coordinate (this is necessary because tabs are 1 char but
331 func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
333 // Scan rune by rune until we exceed the visual width that we are
334 // looking for. Then we can return the character position we have found
336 width := 0 // string visual width
338 r, size := utf8.DecodeRune(b)
343 ts := tabsize - (width % tabsize)
346 width += runewidth.RuneWidth(r)
349 if width >= visualPos {
350 if width == visualPos {
361 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
362 // as 'true' and 'false' respectively
363 func ParseBool(str string) (bool, error) {
370 return strconv.ParseBool(str)
373 // Clamp clamps a value between min and max
374 func Clamp(val, min, max int) int {
377 } else if val > max {