micro/cmd/micro/util/util.go

218 lines
4.9 KiB
Go
Raw Normal View History

2018-08-27 22:53:10 +03:00
package util
2016-03-18 00:27:57 +03:00
import (
2018-08-26 06:06:44 +03:00
"errors"
"os"
"os/user"
2018-08-27 22:53:10 +03:00
"path/filepath"
2018-08-26 06:06:44 +03:00
"regexp"
"strings"
"time"
2016-03-18 00:27:57 +03:00
"unicode/utf8"
2018-03-30 23:21:39 +03:00
2018-08-26 06:06:44 +03:00
runewidth "github.com/mattn/go-runewidth"
2016-03-18 00:27:57 +03:00
)
2018-08-26 06:06:44 +03:00
// SliceEnd returns a byte slice where the index is a rune index
// Slices off the start of the slice
func SliceEnd(slc []byte, index int) []byte {
2018-01-30 00:02:15 +03:00
len := len(slc)
i := 0
totalSize := 0
for totalSize < len {
if i >= index {
return slc[totalSize:]
}
_, size := utf8.DecodeRune(slc[totalSize:])
totalSize += size
i++
}
return slc[totalSize:]
}
2018-08-26 06:06:44 +03:00
// SliceStart returns a byte slice where the index is a rune index
// Slices off the end of the slice
func SliceStart(slc []byte, index int) []byte {
2018-01-30 00:02:15 +03:00
len := len(slc)
i := 0
totalSize := 0
for totalSize < len {
if i >= index {
return slc[:totalSize]
}
_, size := utf8.DecodeRune(slc[totalSize:])
totalSize += size
i++
}
return slc[:totalSize]
}
2018-08-26 06:06:44 +03:00
// SliceVisualEnd will take a byte slice and slice off the start
// up to a given visual index. If the index is in the middle of a
// rune the number of visual columns into the rune will be returned
func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int) {
width := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
w := 0
switch r {
case '\t':
ts := tabsize - (width % tabsize)
w = ts
default:
w = runewidth.RuneWidth(r)
2016-03-18 00:27:57 +03:00
}
2018-08-26 06:06:44 +03:00
if width+w > n {
return b, n - width
}
width += w
b = b[size:]
}
return b, width
}
// Abs is a simple absolute value function for ints
func Abs(n int) int {
if n < 0 {
return -n
2016-03-18 00:27:57 +03:00
}
return n
}
2018-08-26 06:06:44 +03:00
// StringWidth returns the visual width of a byte array indexed from 0 to n (rune index)
// with a given tabsize
func StringWidth(b []byte, n, tabsize int) int {
i := 0
width := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
b = b[size:]
switch r {
case '\t':
ts := tabsize - (width % tabsize)
width += ts
default:
width += runewidth.RuneWidth(r)
}
i++
if i == n {
return width
}
}
return width
2016-03-18 00:27:57 +03:00
}
2016-03-25 19:14:22 +03:00
// Min takes the min of two ints
func Min(a, b int) int {
if a > b {
return b
}
return a
}
// Max takes the max of two ints
func Max(a, b int) int {
if a > b {
return a
}
return b
}
2016-03-28 15:43:08 +03:00
2017-10-01 19:42:23 +03:00
// FSize gets the size of a file
func FSize(f *os.File) int64 {
fi, _ := f.Stat()
return fi.Size()
}
2016-03-28 15:43:08 +03:00
// IsWordChar returns whether or not the string is a 'word character'
// If it is a unicode character, then it does not match
// Word characters are defined as [A-Za-z0-9_]
2018-08-29 01:44:52 +03:00
func IsWordChar(r rune) bool {
return (r >= '0' && r <= '9') || (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r == '_')
2016-03-28 15:43:08 +03:00
}
2016-04-27 17:44:36 +03:00
// IsWhitespace returns true if the given rune is a space, tab, or newline
func IsWhitespace(c rune) bool {
return c == ' ' || c == '\t' || c == '\n'
}
// IsStrWhitespace returns true if the given string is all whitespace
func IsStrWhitespace(str string) bool {
2018-08-26 06:06:44 +03:00
// Range loop for unicode correctness
for _, c := range str {
if !IsWhitespace(c) {
return false
}
}
return true
}
2018-08-26 06:06:44 +03:00
// TODO: consider changing because of snap segfault
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
// home directory. Does nothing if the path does not start with '~'.
2018-08-26 06:06:44 +03:00
func ReplaceHome(path string) (string, error) {
if !strings.HasPrefix(path, "~") {
2018-08-26 06:06:44 +03:00
return path, nil
}
var userData *user.User
var err error
homeString := strings.Split(path, "/")[0]
if homeString == "~" {
userData, err = user.Current()
if err != nil {
2018-08-26 06:06:44 +03:00
return "", errors.New("Could not find user: " + err.Error())
}
} else {
userData, err = user.Lookup(homeString[1:])
if err != nil {
2018-08-26 06:06:44 +03:00
return "", errors.New("Could not find user: " + err.Error())
}
}
home := userData.HomeDir
2018-08-26 06:06:44 +03:00
return strings.Replace(path, homeString, home, 1), nil
}
// GetPathAndCursorPosition returns a filename without everything following a `:`
// This is used for opening files like util.go:10:5 to specify a line and column
// Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
func GetPathAndCursorPosition(path string) (string, []string) {
re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
match := re.FindStringSubmatch(path)
// no lines/columns were specified in the path, return just the path with no cursor location
if len(match) == 0 {
return path, nil
} else if match[len(match)-1] != "" {
// if the last capture group match isn't empty then both line and column were provided
return match[1], match[2:]
}
// if it was empty, then only a line was provided, so default to column 0
return match[1], []string{match[2], "0"}
}
2018-08-26 06:06:44 +03:00
// GetModTime returns the last modification time for a given file
func GetModTime(path string) (time.Time, error) {
info, err := os.Stat(path)
if err != nil {
2018-08-26 06:06:44 +03:00
return time.Now(), err
}
2018-08-26 06:06:44 +03:00
return info.ModTime(), nil
}
2018-08-27 22:53:10 +03:00
// EscapePath replaces every path separator in a given path with a %
func EscapePath(path string) string {
path = filepath.ToSlash(path)
return strings.Replace(path, "/", "%", -1)
}