17 "github.com/blang/semver"
18 runewidth "github.com/mattn/go-runewidth"
22 // These variables should be set by the linker when compiling
24 // Version is the version number or commit hash
25 Version = "0.0.0-unknown"
27 SemVersion semver.Version
28 // CommitHash is the commit this version was built on
29 CommitHash = "Unknown"
30 // CompileDate is the date this binary was compiled on
31 CompileDate = "Unknown"
34 // FakeCursor is used to disable the terminal cursor and have micro
35 // draw its own (enabled for windows consoles where the cursor is slow)
38 // Stdout is a buffer that is written to stdout when micro closes
44 SemVersion, err = semver.Make(Version)
46 fmt.Println("Invalid version: ", Version, err)
49 if runtime.GOOS == "windows" {
52 Stdout = new(bytes.Buffer)
55 // SliceEnd returns a byte slice where the index is a rune index
56 // Slices off the start of the slice
57 func SliceEnd(slc []byte, index int) []byte {
63 return slc[totalSize:]
66 _, _, size := DecodeCharacter(slc[totalSize:])
71 return slc[totalSize:]
74 // SliceEndStr is the same as SliceEnd but for strings
75 func SliceEndStr(str string, index int) string {
81 return str[totalSize:]
84 _, _, size := DecodeCharacterInString(str[totalSize:])
89 return str[totalSize:]
92 // SliceStart returns a byte slice where the index is a rune index
93 // Slices off the end of the slice
94 func SliceStart(slc []byte, index int) []byte {
100 return slc[:totalSize]
103 _, _, size := DecodeCharacter(slc[totalSize:])
108 return slc[:totalSize]
111 // SliceStartStr is the same as SliceStart but for strings
112 func SliceStartStr(str string, index int) string {
116 for totalSize < len {
118 return str[:totalSize]
121 _, _, size := DecodeCharacterInString(str[totalSize:])
126 return str[:totalSize]
129 // SliceVisualEnd will take a byte slice and slice off the start
130 // up to a given visual index. If the index is in the middle of a
131 // rune the number of visual columns into the rune will be returned
132 // It will also return the char pos of the first character of the slice
133 func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {
137 r, _, size := DecodeCharacter(b)
142 ts := tabsize - (width % tabsize)
145 w = runewidth.RuneWidth(r)
148 return b, n - width, i
154 return b, n - width, i
157 // Abs is a simple absolute value function for ints
158 func Abs(n int) int {
165 // StringWidth returns the visual width of a byte array indexed from 0 to n (rune index)
166 // with a given tabsize
167 func StringWidth(b []byte, n, tabsize int) int {
174 r, _, size := DecodeCharacter(b)
179 ts := tabsize - (width % tabsize)
182 width += runewidth.RuneWidth(r)
194 // Min takes the min of two ints
195 func Min(a, b int) int {
202 // Max takes the max of two ints
203 func Max(a, b int) int {
210 // FSize gets the size of a file
211 func FSize(f *os.File) int64 {
216 // IsWordChar returns whether or not the string is a 'word character'
217 // Word characters are defined as numbers, letters, or '_'
218 func IsWordChar(r rune) bool {
219 return unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_'
222 // Spaces returns a string with n spaces
223 func Spaces(n int) string {
224 return strings.Repeat(" ", n)
227 // IsSpaces checks if a given string is only spaces
228 func IsSpaces(str []byte) bool {
229 for _, c := range str {
238 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
239 func IsSpacesOrTabs(str []byte) bool {
240 for _, c := range str {
241 if c != ' ' && c != '\t' {
249 // IsWhitespace returns true if the given rune is a space, tab, or newline
250 func IsWhitespace(c rune) bool {
251 return unicode.IsSpace(c)
254 // IsBytesWhitespace returns true if the given bytes are all whitespace
255 func IsBytesWhitespace(b []byte) bool {
256 for _, c := range b {
257 if !IsWhitespace(rune(c)) {
264 // RunePos returns the rune index of a given byte index
265 // Make sure the byte index is not between code points
266 func RunePos(b []byte, i int) int {
267 return CharacterCount(b[:i])
270 // MakeRelative will attempt to make a relative path between path and base
271 func MakeRelative(path, base string) (string, error) {
273 rel, err := filepath.Rel(base, path)
282 // ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
283 // home directory. Does nothing if the path does not start with '~'.
284 func ReplaceHome(path string) (string, error) {
285 if !strings.HasPrefix(path, "~") {
289 var userData *user.User
292 homeString := strings.Split(path, "/")[0]
293 if homeString == "~" {
294 userData, err = user.Current()
296 return "", errors.New("Could not find user: " + err.Error())
299 userData, err = user.Lookup(homeString[1:])
301 return "", errors.New("Could not find user: " + err.Error())
305 home := userData.HomeDir
307 return strings.Replace(path, homeString, home, 1), nil
310 // GetPathAndCursorPosition returns a filename without everything following a `:`
311 // This is used for opening files like util.go:10:5 to specify a line and column
312 // Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
313 func GetPathAndCursorPosition(path string) (string, []string) {
314 re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
315 match := re.FindStringSubmatch(path)
316 // no lines/columns were specified in the path, return just the path with no cursor location
319 } else if match[len(match)-1] != "" {
320 // if the last capture group match isn't empty then both line and column were provided
321 return match[1], match[2:]
323 // if it was empty, then only a line was provided, so default to column 0
324 return match[1], []string{match[2], "0"}
327 // GetModTime returns the last modification time for a given file
328 func GetModTime(path string) (time.Time, error) {
329 info, err := os.Stat(path)
331 return time.Now(), err
333 return info.ModTime(), nil
336 // EscapePath replaces every path separator in a given path with a %
337 func EscapePath(path string) string {
338 path = filepath.ToSlash(path)
339 if runtime.GOOS == "windows" {
340 // ':' is not valid in a path name on Windows but is ok on Unix
341 path = strings.Replace(path, ":", "%", -1)
343 return strings.Replace(path, "/", "%", -1)
346 // GetLeadingWhitespace returns the leading whitespace of the given byte array
347 func GetLeadingWhitespace(b []byte) []byte {
350 r, _, size := DecodeCharacter(b)
351 if r == ' ' || r == '\t' {
352 ws = append(ws, byte(r))
362 // IntOpt turns a float64 setting to an int
363 func IntOpt(opt interface{}) int {
364 return int(opt.(float64))
367 // GetCharPosInLine gets the char position of a visual x y
368 // coordinate (this is necessary because tabs are 1 char but
370 func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
371 // Scan rune by rune until we exceed the visual width that we are
372 // looking for. Then we can return the character position we have found
374 width := 0 // string visual width
376 r, _, size := DecodeCharacter(b)
381 ts := tabsize - (width % tabsize)
384 width += runewidth.RuneWidth(r)
387 if width >= visualPos {
388 if width == visualPos {
399 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
400 // as 'true' and 'false' respectively
401 func ParseBool(str string) (bool, error) {
408 return strconv.ParseBool(str)
411 // Clamp clamps a value between min and max
412 func Clamp(val, min, max int) int {
415 } else if val > max {
421 func IsNonAlphaNumeric(c rune) bool {
422 return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
425 func IsAutocomplete(c rune) bool {
426 return c == '.' || !IsNonAlphaNumeric(c)
429 func ParseSpecial(s string) string {
430 return strings.Replace(s, "\\t", "\t", -1)
433 // String converts a byte array to a string (for lua plugins)
434 func String(s []byte) string {