Compare commits

..

39 Commits

Author SHA1 Message Date
Zachary Yedidia
660d345880 Don't apply cli options to settings.json 2020-06-08 22:19:15 -04:00
Zachary Yedidia
ae05ff1811 settings.json only contains modified settings
If a setting has a default value it will not be listed in settings.json.
2020-06-08 15:33:38 -04:00
Zachary Yedidia
43924646f6 Merge 2020-06-08 13:55:24 -04:00
Zachary Yedidia
79ee757757 Only start autocompletion for alphanumerics
Ref #1712
2020-06-08 13:54:31 -04:00
Ryan Westlund
006165230d python.yaml: add async as a keyword (#1713)
await is already a keyword, but async is not.
2020-06-08 13:45:05 -04:00
Zachary Yedidia
ead07e0b60 Expose ConfigDir and Tabs to plugins
Access with `micro.ConfigDir` (constant value) and `micro.Tabs()`.
2020-06-07 18:21:46 -04:00
Zachary Yedidia
140662f1ec Verify that all settings have correct type
This prevents crashes that occur when the user has put the wrong
type for a setting manually in the settings.json file.
2020-06-07 17:31:16 -04:00
Zachary Yedidia
44c1929f9d Fix mouse support in command bar 2020-06-07 15:46:12 -04:00
Zachary Yedidia
397fe634d7 Update tcell to fix escape sequence bug 2020-06-07 15:22:17 -04:00
Zachary Yedidia
2e3d08580e Merge 2020-06-06 15:56:36 -04:00
Zachary Yedidia
466889f540 Fix fileformat for newly created files
Fixes #1575
2020-06-06 15:56:13 -04:00
Dmitry Maluka
63900cb395 Fix highlighting at the end of line (#1705)
Fixes #1664
2020-06-04 23:32:31 -04:00
Chloe Kudryavtsev
07860b8973 Add mksh to the set of supported shells (#1703) 2020-06-03 13:30:42 -04:00
Zachary Yedidia
b473fe458d Merge 2020-06-03 00:27:51 -04:00
Zachary Yedidia
8cf56bfc56 Up arrow on first line brings to start
Fixes #1701
2020-06-03 00:27:24 -04:00
Sijmen J. Mulder
51050811eb Add pkgsrc instruction to readme (#1699) 2020-06-02 16:49:18 -04:00
Zachary Yedidia
14cd3cdbf8 Update readme 2020-06-01 00:17:52 -04:00
Zachary Yedidia
51ab8f9914 Unicode replacement char for non-displayable chars 2020-05-30 18:11:52 -04:00
Zachary Yedidia
afeb07a024 More fixes for parsecursor
Fixes #1695
Fixes #1696
2020-05-30 12:23:29 -04:00
Zachary Yedidia
3fc9a8ad9e Fix handling of +LINE:COL syntax
Fixes #1685
2020-05-29 22:48:23 -04:00
Zachary Yedidia
b05d3a5193 Slightly improve performance for very long lines 2020-05-29 15:31:13 -04:00
Zachary Yedidia
ffc922a7c5 Only perform save callback if save was successful
Fixes #1684
2020-05-29 15:02:38 -04:00
Zachary Yedidia
eeab114ed5 Add parsecursor option for file:line:col syntax
This option is disabled by default, and when enabled causes micro
to parse `:line:col` as a location for the cursor rather than
as part of the filename.

Closes #1650
Closes #1685
2020-05-29 14:55:24 -04:00
Zachary Yedidia
8bd7e5807c Always use current pane for keybinding actions
Fixes #1677
2020-05-29 14:38:29 -04:00
Andrew Clarke
9b59e07b47 Use "goto -1" to move cursor to end of document. (#1691) 2020-05-29 13:29:09 -04:00
Colin Hughes
00edf0207f Added hybrid line numbers (#1690)
* Added hybrid line numbers

* Changed rulerhybrid to relativeruler, modified documentation accordingly.

* Reverted go.mod and go.sum
I don't know how they got changed but they are good now.

Co-authored-by: Colin Hughes <semilin@pop-os.localdomain>
2020-05-28 22:24:09 -04:00
Zachary Yedidia
a95b17a4e7 Update readme 2020-05-28 18:39:17 -04:00
Zachary Yedidia
8956448fca UpdateRules after save is successful 2020-05-28 13:06:29 -04:00
Zachary Yedidia
a915cf9283 Fix '> save' command 2020-05-28 13:02:09 -04:00
Zachary Yedidia
dd10869eca Update readme 2020-05-28 11:59:31 -04:00
Peder B. Sundt
4356c7e434 Tweak railscast colorscheme to better reflect original (#1297) 2020-05-28 11:50:35 -04:00
axxx007xxxz
2e770ce9ce README: Add Fedora install instructions (#1671) 2020-05-28 11:49:18 -04:00
pyfisch
381e1b639d Rewrite TOML syntax file (#1681) 2020-05-26 14:10:27 -04:00
Shinichi TAMURA
cc09712d14 set bash-fc file's syntax as shell (#1679)
* set bash-fc file's syntax as shell

* remove dups from shell file matcher
2020-05-24 23:50:30 -04:00
Dmitry Maluka
c5b0c2d41f Fix dropped redraw events (#1675)
If screen.Redraw() is called very quickly after a key or mouse event,
it may send the redraw event while micro is not waiting for it but
still processing the key or mouse event. Since drawChan is non-buffered
and at the same time non-blocking, this redraw event will be simply lost,
so the screen content will not be up-to-date.
2020-05-23 14:59:23 -04:00
Zachary Yedidia
bd43a44194 Merge branch 'master' of https://github.com/zyedidia/micro 2020-05-21 14:36:34 -04:00
Zachary Yedidia
bfe68b1626 Allow divider customization with divchars option
Adds the `divchars` and `divreverse` options to customize divider
styles.
2020-05-21 14:35:54 -04:00
Zachary Yedidia
0064b8268f Improve unicode line array test 2020-05-20 19:53:54 -04:00
Zachary Yedidia
9a22d93ea2 Expose CharacterCount to plugins 2020-05-20 18:04:00 -04:00
26 changed files with 363 additions and 120 deletions

View File

@@ -56,10 +56,10 @@ You can also check out the website for Micro at https://micro-editor.github.io.
- Note that while Windows is supported Mingw/Cygwin is not (see below)
- Plugin system (plugins are written in Lua).
- micro has a built-in plugin manager to automatically install, remove, and update plugins.
- Built-in diff gutter
- Simple autocompletion
- Built-in diff gutter.
- Simple autocompletion.
- Persistent undo.
- Automatic linting and error notifications
- Automatic linting and error notifications.
- Syntax highlighting for over [130 languages](runtime/syntax).
- Color scheme support.
- By default, micro comes with 16, 256, and true color themes.
@@ -76,17 +76,15 @@ To install micro, you can download a [prebuilt binary](https://github.com/zyedid
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro).
Use `micro -version` to get the version information after installing. It is only guaranteed that you are installing the most recent
stable version if you install from the prebuilt binaries, Homebrew, or Snap.
### Prebuilt binaries
All you need to install micro is one file, the binary itself. It's as simple as that!
Download the binary from the [releases](https://github.com/zyedidia/micro/releases) page.
On that page you'll see the nightly release, which contains binaries for micro which are built every night,
and you'll see all the stable releases with the corresponding binaries.
Running `micro -version` will give you the version information.
### Installation script
There is a script which can install micro for you by downloading the latest prebuilt binary. You can find it at <https://getmic.ro>.
@@ -97,7 +95,7 @@ You can easily install micro by running
curl https://getmic.ro | bash
```
The script will install the micro binary to the current directory. See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
The script will place the micro binary in the current directory. See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
### Package managers
@@ -117,27 +115,25 @@ On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/in
snap install micro --classic
```
On Debian `unstable | testing | buster-backports` and Ubuntu `focal` (20.04), micro is available
via `apt`:
```
sudo apt install micro
```
**Note for Linux:** for interfacing with the local system clipboard, `xclip` or `xsel`
must be installed. Please see the section on [Linux clipboard support](https://github.com/zyedidia/micro#linux-clipboard-support)
further below.
Micro is also available through other package managers on Linux such as AUR, Nix, and package managers
for other operating systems:
Micro is also available through other package managers on Linux such as apt, dnf, AUR, Nix, and package managers
for other operating systems. These packages are not guaranteed to be up-to-date.
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop)
* `choco install micro`
* `scoop install micro`
* OpenBSD: Available in the ports tree and also available as a binary package
* `pkd_add -v micro`
* Arch Linux, CRUX, Termux for Android
* See details in the [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro)
* Linux: Available in distro-specific package managers.
* `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`).
* `dnf install micro` (Fedora).
* `yay -S micro` (Arch Linux).
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop).
* `choco install micro`.
* `scoop install micro`.
* OpenBSD: Available in the ports tree and also available as a binary package.
* `pkd_add -v micro`.
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](http://www.pkgsrc.org/)-current:
* `pkg_add micro`
### Building from source
@@ -253,3 +249,5 @@ You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues)
to report bugs, ask questions, or suggest new features.
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro).
Sometimes I am unresponsive, and I apologize! If that happens, please ping me.

View File

@@ -53,6 +53,9 @@ func luaImportMicro() *lua.LTable {
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, func() *action.Tab {
return action.MainTab()
}))
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
return action.Tabs
}))
return pkg
}
@@ -83,6 +86,7 @@ func luaImportMicroConfig() *lua.LTable {
ulua.L.SetField(pkg, "GetGlobalOption", luar.New(ulua.L, config.GetGlobalOption))
ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOption))
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNative))
ulua.L.SetField(pkg, "ConfigDir", luar.New(ulua.L, config.ConfigDir))
return pkg
}
@@ -141,6 +145,7 @@ func luaImportMicroUtil() *lua.LTable {
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
ulua.L.SetField(pkg, "String", luar.New(ulua.L, util.String))
ulua.L.SetField(pkg, "CharacterCountInString", luar.New(ulua.L, util.CharacterCountInString))
ulua.L.SetField(pkg, "RuneStr", luar.New(ulua.L, func(r rune) string {
return string(r)
}))

View File

@@ -8,6 +8,7 @@ import (
"regexp"
"runtime"
"sort"
"strconv"
"time"
"github.com/go-errors/errors"
@@ -44,7 +45,7 @@ func InitFlags() {
fmt.Println(" \tCleans the configuration directory")
fmt.Println("-config-dir dir")
fmt.Println(" \tSpecify a custom location for the configuration directory")
fmt.Println("[FILE]:LINE:COL")
fmt.Println("[FILE]:LINE:COL (if the `parsecursor` option is enabled)")
fmt.Println("+LINE:COL")
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
fmt.Println("-options")
@@ -155,18 +156,31 @@ func LoadInput() []*buffer.Buffer {
}
files := make([]string, 0, len(args))
flagStartPos := ""
flagr := regexp.MustCompile(`^\+\d+(:\d+)?$`)
flagStartPos := buffer.Loc{-1, -1}
flagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
for _, a := range args {
if flagr.MatchString(a) {
flagStartPos = a[1:]
} else {
if flagStartPos != "" {
files = append(files, a+":"+flagStartPos)
flagStartPos = ""
} else {
files = append(files, a)
match := flagr.FindStringSubmatch(a)
if len(match) == 3 && match[2] != "" {
line, err := strconv.Atoi(match[1])
if err != nil {
screen.TermMessage(err)
continue
}
col, err := strconv.Atoi(match[2])
if err != nil {
screen.TermMessage(err)
continue
}
flagStartPos = buffer.Loc{col - 1, line - 1}
} else if len(match) == 3 && match[2] == "" {
line, err := strconv.Atoi(match[1])
if err != nil {
screen.TermMessage(err)
continue
}
flagStartPos = buffer.Loc{0, line - 1}
} else {
files = append(files, a)
}
}
@@ -174,7 +188,7 @@ func LoadInput() []*buffer.Buffer {
// Option 1
// We go through each file and load it
for i := 0; i < len(files); i++ {
buf, err := buffer.NewBufferFromFile(files[i], btype)
buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos)
if err != nil {
screen.TermMessage(err)
continue
@@ -191,10 +205,10 @@ func LoadInput() []*buffer.Buffer {
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
}
return buffers
@@ -229,7 +243,10 @@ func main() {
if err != nil {
screen.TermMessage(err)
}
config.InitGlobalSettings()
err = config.InitGlobalSettings()
if err != nil {
screen.TermMessage(err)
}
// flag options
for k, v := range optionFlags {
@@ -363,6 +380,9 @@ func DoEvent() {
case <-shell.CloseTerms:
case event = <-events:
case <-screen.DrawChan():
for len(screen.DrawChan()) > 0 {
<-screen.DrawChan()
}
}
if action.InfoBar.HasPrompt {

2
go.mod
View File

@@ -17,7 +17,7 @@ require (
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/pty v2.0.0+incompatible // indirect
github.com/zyedidia/tcell v1.4.5
github.com/zyedidia/tcell v1.4.6
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
golang.org/x/text v0.3.2
gopkg.in/sourcemap.v1 v1.0.5 // indirect

2
go.sum
View File

@@ -58,6 +58,8 @@ github.com/zyedidia/tcell v1.4.4 h1:o34LXujNuSueuyTy+5eoQW+rQr8g0UbY8k1NczZyskQ=
github.com/zyedidia/tcell v1.4.4/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/tcell v1.4.5 h1:JFmOiWLxr3Fsk2vjRL3n8oRUoJeyrazGhkhZqW31kEY=
github.com/zyedidia/tcell v1.4.5/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/tcell v1.4.6 h1:8OYvZpUyqYQ3nigenBwOtnY3fXWEHekbm6QYchBeOxs=
github.com/zyedidia/tcell v1.4.6/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@@ -663,7 +663,12 @@ func (h *BufPane) Autocomplete() bool {
return false
}
if !util.IsNonAlphaNumeric(h.Cursor.RuneUnder(h.Cursor.X)) {
if h.Cursor.X == 0 {
return false
}
r := h.Cursor.RuneUnder(h.Cursor.X)
prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
// don't autocomplete if cursor is on alpha numeric character (middle of a word)
return false
}
@@ -727,6 +732,7 @@ func (h *BufPane) Save() bool {
}
// SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
// The callback is only called if the save was successful
func (h *BufPane) SaveAsCB(action string, callback func()) bool {
InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
if !canceled {
@@ -757,6 +763,7 @@ func (h *BufPane) SaveAs() bool {
// This function saves the buffer to `filename` and changes the buffer's path and name
// to `filename` if the save is successful
// The callback is only called if the save was successful
func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
err := h.Buf.SaveAs(filename)
if err != nil {
@@ -769,6 +776,9 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
h.Buf.Path = filename
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
if callback != nil {
callback()
}
}
}
if h.Buf.Settings["autosu"].(bool) {
@@ -779,9 +789,6 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
saveWithSudo()
h.completeAction(action)
}
if callback != nil {
callback()
}
})
return false
}
@@ -792,9 +799,9 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
h.Buf.Path = filename
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
}
if callback != nil {
callback()
if callback != nil {
callback()
}
}
return true
}

View File

@@ -124,6 +124,8 @@ func BufMapKey(k Event, action string) {
break
}
}
// if the action changed the current pane, update the reference
h = MainTab().CurPane()
}
return true
}
@@ -330,7 +332,7 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
switch e.Buttons() {
case tcell.Button1:
_, my := e.Position()
if h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
cancel = true
}
case tcell.ButtonNone:

View File

@@ -339,7 +339,10 @@ func ReloadConfig() {
if err != nil {
screen.TermMessage(err)
}
config.InitGlobalSettings()
err = config.InitGlobalSettings()
if err != nil {
screen.TermMessage(err)
}
InitBindings()
InitCommands()
@@ -477,6 +480,7 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
if !local {
config.GlobalSettings[option] = nativeValue
config.ModifiedSettings[option] = true
if option == "colorscheme" {
// LoadSyntaxFiles()
@@ -701,6 +705,9 @@ func (h *BufPane) GotoCmd(args []string) {
InfoBar.Error(err)
return
}
if line < 0 {
line = h.Buf.LinesNum() + 1 + line
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
h.Cursor.GotoLoc(buffer.Loc{col, line})
@@ -710,6 +717,9 @@ func (h *BufPane) GotoCmd(args []string) {
InfoBar.Error(err)
return
}
if line < 0 {
line = h.Buf.LinesNum() + 1 + line
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
h.Cursor.GotoLoc(buffer.Loc{0, line})
}

View File

@@ -191,13 +191,22 @@ type Buffer struct {
StartCursor Loc
}
// NewBufferFromFile opens a new buffer using the given path
// It will also automatically handle `~`, and line/column with filename:l:c
// It will return an empty buffer if the path does not exist
// and an error if the file is a directory
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
// If cursorLoc is {-1, -1} the location does not overwrite what the cursor location
// would otherwise be (start of file, or saved cursor position if `savecursor` is
// enabled)
func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer, error) {
var err error
filename, cursorPos := util.GetPathAndCursorPosition(path)
filename := path
if config.GetGlobalOption("parsecursor").(bool) && cursorLoc.X == -1 && cursorLoc.Y == -1 {
var cursorPos []string
filename, cursorPos = util.GetPathAndCursorPosition(filename)
cursorLoc, err = ParseCursorLocation(cursorPos)
if err != nil {
cursorLoc = Loc{-1, -1}
}
}
filename, err = util.ReplaceHome(filename)
if err != nil {
return nil, err
@@ -212,11 +221,6 @@ func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
defer file.Close()
cursorLoc, cursorerr := ParseCursorLocation(cursorPos)
if cursorerr != nil {
cursorLoc = Loc{-1, -1}
}
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
@@ -228,6 +232,19 @@ func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
return buf, nil
}
// NewBufferFromFile opens a new buffer using the given path
// It will also automatically handle `~`, and line/column with filename:l:c
// It will return an empty buffer if the path does not exist
// and an error if the file is a directory
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
return NewBufferFromFileAtLoc(path, btype, Loc{-1, -1})
}
// NewBufferFromStringAtLoc creates a new buffer containing the given string with a cursor loc
func NewBufferFromStringAtLoc(text, path string, btype BufType, cursorLoc Loc) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, cursorLoc, btype)
}
// NewBufferFromString creates a new buffer containing the given string
func NewBufferFromString(text, path string, btype BufType) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, Loc{-1, -1}, btype)
@@ -280,7 +297,21 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
if !hasBackup {
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
var ff FileFormat = FFAuto
if size == 0 {
// for empty files, use the fileformat setting instead of
// autodetection
switch b.Settings["fileformat"] {
case "unix":
ff = FFUnix
case "dos":
ff = FFDos
}
}
b.LineArray = NewLineArray(uint64(size), ff, reader)
}
b.EventHandler = NewEventHandler(b.SharedBuffer, b.cursors)

View File

@@ -74,9 +74,6 @@ func (c *Cursor) GetVisualX() int {
bytes := c.buf.LineBytes(c.Y)
tabsize := int(c.buf.Settings["tabsize"].(float64))
if c.X > util.CharacterCount(bytes) {
c.X = util.CharacterCount(bytes) - 1
}
return util.StringWidth(bytes, c.X, tabsize)
}
@@ -242,6 +239,12 @@ func (c *Cursor) UpN(amount int) {
if c.X > util.CharacterCount(bytes) || (amount < 0 && proposedY == c.Y) {
c.X = util.CharacterCount(bytes)
c.StoreVisualX()
}
if c.X < 0 || (amount > 0 && proposedY == c.Y) {
c.X = 0
c.StoreVisualX()
}
c.Y = proposedY

View File

@@ -87,6 +87,8 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
br := bufio.NewReader(reader)
var loaded int
la.Endings = endings
n := 0
for {
data, err := br.ReadBytes('\n')

View File

@@ -46,14 +46,15 @@ func TestInsert(t *testing.T) {
assert.Equal(t, []byte("Uppen Sevarne staþe, foobar sel þar him þuhte,"), sub1)
la.insert(Loc{25, 2}, []byte("ಮಣ್ಣಾಗಿ"))
la.insert(Loc{25, 2}, []byte("H̼̥̯͇͙̕͘͞e̸̦̞̠̣̰͙̼̥̦̼̖̬͕͕̰̯̫͇̕ĺ̜̠̩̯̯͙̼̭̠͕̮̞͜l̶͓̫̞̮͈͞ͅo̸͔͙̳̠͈̮̼̳͙̥̲͜͠"))
sub2 := la.Substr(Loc{0, 2}, Loc{60, 2})
assert.Equal(t, []byte("He wonede at Ernleȝe at æಮಣ್ಣಾಗಿðelen are chirechen,"), sub2)
assert.Equal(t, []byte("He wonede at Ernleȝe at æH̼̥̯͇͙̕͘͞e̸̦̞̠̣̰͙̼̥̦̼̖̬͕͕̰̯̫͇̕ĺ̜̠̩̯̯͙̼̭̠͕̮̞͜l̶͓̫̞̮͈͞ͅo̸͔͙̳̠͈̮̼̳͙̥̲͜͠ðelen are chirechen,"), sub2)
}
func TestRemove(t *testing.T) {
la.remove(Loc{20, 3}, Loc{27, 3})
la.remove(Loc{25, 2}, Loc{32, 2})
la.remove(Loc{25, 2}, Loc{30, 2})
bytes := la.Bytes()
assert.Equal(t, unicode_txt, string(bytes))

View File

@@ -96,7 +96,6 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
return errors.New("Save with sudo not supported on Windows")
}
b.UpdateRules()
if b.Settings["rmtrailingws"].(bool) {
for i, l := range b.lines {
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
@@ -195,5 +194,6 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
absPath, _ := filepath.Abs(filename)
b.AbsPath = absPath
b.isModified = false
b.UpdateRules()
return err
}

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
@@ -27,9 +28,14 @@ var (
// This is the raw parsed json
parsedSettings map[string]interface{}
// ModifiedSettings is a map of settings which should be written to disk
// because they have been modified by the user in this session
ModifiedSettings map[string]bool
)
func init() {
ModifiedSettings = make(map[string]bool)
parsedSettings = make(map[string]interface{})
}
@@ -75,16 +81,33 @@ func ReadSettings() error {
return nil
}
func verifySetting(option string, value reflect.Type, def reflect.Type) bool {
var interfaceArr []interface{}
switch option {
case "pluginrepos", "pluginchannels":
return value.AssignableTo(reflect.TypeOf(interfaceArr))
default:
return def.AssignableTo(value)
}
}
// InitGlobalSettings initializes the options map and sets all options to their default values
// Must be called after ReadSettings
func InitGlobalSettings() {
func InitGlobalSettings() error {
var err error
GlobalSettings = DefaultGlobalSettings()
for k, v := range parsedSettings {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if _, ok := GlobalSettings[k]; ok && !verifySetting(k, reflect.TypeOf(v), reflect.TypeOf(GlobalSettings[k])) {
err = errors.New(fmt.Sprintf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k])))
continue
}
GlobalSettings[k] = v
}
}
return err
}
// InitLocalSettings scans the json in settings.json and sets the options locally based
@@ -97,6 +120,10 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
if strings.HasPrefix(k, "ft:") {
if settings["filetype"].(string) == k[3:] {
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
continue
}
settings[k1] = v1
}
}
@@ -109,6 +136,10 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
if g.MatchString(path) {
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
continue
}
settings[k1] = v1
}
}
@@ -122,8 +153,25 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
func WriteSettings(filename string) error {
var err error
if _, e := os.Stat(ConfigDir); e == nil {
defaults := DefaultGlobalSettings()
// remove any options froms parsedSettings that have since been marked as default
for k, v := range parsedSettings {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
cur, okcur := GlobalSettings[k]
if def, ok := defaults[k]; ok && okcur && reflect.DeepEqual(cur, def) {
delete(parsedSettings, k)
}
}
}
// add any options to parsedSettings that have since been marked as non-default
for k, v := range GlobalSettings {
parsedSettings[k] = v
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
if _, wr := ModifiedSettings[k]; wr {
parsedSettings[k] = v
}
}
}
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
@@ -132,10 +180,23 @@ func WriteSettings(filename string) error {
return err
}
// OverwriteSettings writes the current settings to settings.json and
// resets any user configuration of local settings present in settings.json
func OverwriteSettings(filename string) error {
settings := make(map[string]interface{})
var err error
if _, e := os.Stat(ConfigDir); e == nil {
txt, _ := json.MarshalIndent(GlobalSettings, "", " ")
defaults := DefaultGlobalSettings()
for k, v := range GlobalSettings {
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
if _, wr := ModifiedSettings[k]; wr {
settings[k] = v
}
}
}
txt, _ := json.MarshalIndent(settings, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return err
@@ -203,6 +264,7 @@ var defaultCommonSettings = map[string]interface{}{
"readonly": false,
"rmtrailingws": false,
"ruler": true,
"relativeruler": false,
"savecursor": false,
"saveundo": false,
"scrollbar": false,
@@ -248,9 +310,12 @@ func DefaultCommonSettings() map[string]interface{} {
var DefaultGlobalOnlySettings = map[string]interface{}{
"autosave": float64(0),
"colorscheme": "default",
"divchars": "|-",
"divreverse": true,
"infobar": true,
"keymenu": false,
"mouse": true,
"parsecursor": false,
"paste": false,
"savehistory": true,
"sucmd": "sudo",

View File

@@ -335,7 +335,14 @@ func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool
}
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
lineNum := strconv.Itoa(bloc.Y + 1)
cursorLine := w.Buf.GetActiveCursor().Loc.Y
var lineInt int
if w.Buf.Settings["relativeruler"] == false || cursorLine == bloc.Y {
lineInt = bloc.Y + 1
} else {
lineInt = bloc.Y - cursorLine
}
lineNum := strconv.Itoa(util.Abs(lineInt))
// Write the spaces before the line number if necessary
for i := 0; i < maxLineNumLength-len(lineNum); i++ {
@@ -659,7 +666,8 @@ func (w *BufWindow) displayBuffer() {
}
if vloc.X != bufWidth {
draw(' ', nil, curStyle, true)
// Display newline within a selection
draw(' ', nil, config.DefStyle, true)
}
bloc.X = w.StartCol
@@ -682,8 +690,27 @@ func (w *BufWindow) displayStatusLine() {
w.sline.Display()
} else if w.Y+w.Height != infoY {
w.drawStatus = true
divchars := config.GetGlobalOption("divchars").(string)
if util.CharacterCountInString(divchars) != 2 {
divchars = "|-"
}
_, _, size := util.DecodeCharacterInString(divchars)
divchar, combc, _ := util.DecodeCharacterInString(divchars[size:])
dividerStyle := config.DefStyle
if style, ok := config.Colorscheme["divider"]; ok {
dividerStyle = style
}
divreverse := config.GetGlobalOption("divreverse").(bool)
if divreverse {
dividerStyle = dividerStyle.Reverse(true)
}
for x := w.X; x < w.X+w.Width; x++ {
screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
screen.SetContent(x, w.Y+w.Height-1, divchar, combc, dividerStyle)
}
} else {
w.drawStatus = false

View File

@@ -4,6 +4,7 @@ import (
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/views"
)
@@ -24,11 +25,23 @@ func (w *UIWindow) drawNode(n *views.Node) {
dividerStyle = style
}
divchars := config.GetGlobalOption("divchars").(string)
if util.CharacterCountInString(divchars) != 2 {
divchars = "|-"
}
divchar, combc, _ := util.DecodeCharacterInString(divchars)
divreverse := config.GetGlobalOption("divreverse").(bool)
if divreverse {
dividerStyle = dividerStyle.Reverse(true)
}
for i, c := range cs {
if c.IsLeaf() && c.Kind == views.STVert {
if i != len(cs)-1 {
for h := 0; h < c.H; h++ {
screen.SetContent(c.X+c.W, c.Y+h, '|', nil, dividerStyle.Reverse(true))
screen.SetContent(c.X+c.W, c.Y+h, divchar, combc, dividerStyle)
}
}
} else {

View File

@@ -146,7 +146,7 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
h := i.History[i.PromptType]
h[len(h)-1] = resp
}
i.PromptCallback = nil
// i.PromptCallback = nil
}
i.Replace(i.Start(), i.End(), "")
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"sync"
"unicode"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
@@ -97,6 +98,10 @@ func ShowCursor(x, y int) {
// SetContent sets a cell at a point on the screen and makes sure that it is
// synced with the last cursor location
func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
if !unicode.IsPrint(mainc) {
mainc = '<27>'
}
Screen.SetContent(x, y, mainc, combc, style)
if util.FakeCursor && lastCursor.x == x && lastCursor.y == y {
lastCursor.r = mainc
@@ -127,7 +132,7 @@ func TempStart(screenWasNil bool) {
// Init creates and initializes the tcell screen
func Init() {
drawChan = make(chan bool)
drawChan = make(chan bool, 8)
// Should we enable true color?
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"

View File

@@ -418,6 +418,10 @@ func IsNonAlphaNumeric(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
}
func IsAutocomplete(c rune) bool {
return c == '.' || !IsNonAlphaNumeric(c)
}
func ParseSpecial(s string) string {
return strings.Replace(s, "\\t", "\t", -1)
}

View File

@@ -11,7 +11,7 @@ color-link todo "bold #cc7833,#2b2b2b"
color-link error "bold #cc7833,#2b2b2b"
color-link gutter-error "#cc7833,#11151C"
color-link indent-char "#414141,#2b2b2b"
color-link line-number "#a1a1a1,#353535"
color-link line-number "#a1a1a1,#232323"
color-link current-line-number "#e6e1dc,#2b2b2b"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
@@ -19,8 +19,8 @@ color-link diff-deleted "#D70000"
color-link gutter-warning "#a5c261,#11151C"
color-link symbol "#edb753,#2b2b2b"
color-link identifier "#edb753,#2b2b2b"
color-link statusline "#a1a1a1,#414141"
color-link tabbar "bold #a1a1a1,#414141"
color-link statusline "#b1b1b1,#232323"
color-link tabbar "bold #b1b1b1,#232323"
color-link cursor-line "#353535"
color-link color-column "#353535"
color-link space "underline #e6e1dc,#2b2b2b"

View File

@@ -80,6 +80,21 @@ Here are the available options:
default value: `false`
* `divchars`: specifies the "divider" characters used for the dividing line
between vertical/horizontal splits. The first character is for vertical
dividers, and the second is for horizontal dividers. By default, for
horizontal splits the statusline serves as a divider, but if the statusline
is disabled the horizontal divider character will be used.
default value: `|-`
* `divreverse`: colorschemes provide the color (foreground and background) for
the characters displayed in split dividers. With this option enabled, the
colors specified by the colorscheme will be reversed (foreground and
background colors swapped).
default value: `true`
* `encoding`: the encoding to open and save files with. Supported encodings
are listed at https://www.w3.org/TR/encoding/.
@@ -102,12 +117,15 @@ Here are the available options:
default value: `false`
* `fileformat`: this determines what kind of line endings micro will use for
the file. UNIX line endings are just `\n` (linefeed) whereas dos line
the file. Unix line endings are just `\n` (linefeed) whereas dos line
endings are `\r\n` (carriage return + linefeed). The two possible values for
this option are `unix` and `dos`. The fileformat will be automatically
detected (when you open an existing file) and displayed on the statusline,
but this option is useful if you would like to change the line endings or if
you are starting a new file.
you are starting a new file. Changing this option while editing a file will
change its line endings. Opening a file with this option set will only have
an effect if the file is empty/newly created, because otherwise the fileformat
will be automatically detected from the existing line endings.
default value: `unix`
@@ -163,7 +181,7 @@ Here are the available options:
default value: `true`
* `paste`: Treat characters sent from the terminal in a single chunk as a paste
* `paste`: treat characters sent from the terminal in a single chunk as a paste
event rather than a series of manual key presses. If you are pasting using
the terminal keybinding (not Ctrl-v, which is micro's default paste
keybinding) then it is a good idea to enable this option during the paste
@@ -172,6 +190,16 @@ Here are the available options:
default value: `false`
* `parsecursor`: if enabled, this will cause micro to parse filenames such as
file.txt:10:5 as requesting to open `file.txt` with the cursor at line 10
and column 5. The column number can also be dropped to open the file at a
given line and column 0. Note that with this option enabled it is not possible
to open a file such as `file.txt:10:5`, where `:10:5` is part of the filename.
It is also possible to open a file with a certain cursor location by using the
`+LINE,COL` flag syntax. See `micro -help` for the command line options.
default value: `false`
* `pluginchannels`: list of URLs pointing to plugin channels for downloading and
installing plugins. A plugin channel consists of a json file with links to
plugin repos, which store information about plugin versions and download URLs.
@@ -199,6 +227,12 @@ Here are the available options:
default value: `true`
* `relativeruler`: make line numbers display relatively. If set to true, all lines except
for the line that the cursor is located will display the distance from the
cursor's line.
default value: `false`
* `savecursor`: remember where the cursor was last time the file was opened and
put it there when you open the file again. Information is saved to
`~/.config/micro/buffers/`

View File

@@ -28,7 +28,7 @@ function onRune(bp, r)
if r == charAt(autoclosePairs[i], 1) then
local curLine = bp.Buf:Line(bp.Cursor.Y)
if bp.Cursor.X == utf8.RuneCountInString(curLine) or not uutil.IsWordChar(charAt(curLine, bp.Cursor.X+1)) then
if bp.Cursor.X == uutil.CharacterCountInString(curLine) or not uutil.IsWordChar(charAt(curLine, bp.Cursor.X+1)) then
-- the '-' here is to derefence the pointer to bp.Cursor.Loc which is automatically made
-- when converting go structs to lua
-- It needs to be dereferenced because the function expects a non pointer struct

View File

@@ -18,7 +18,7 @@ rules:
# definitions
- identifier: "def [a-zA-Z_0-9]+"
# keywords
- statement: "\\b(and|as|assert|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\\b"
- statement: "\\b(and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\\b"
# decorators
- brightgreen: "@.*[(]"
# operators

View File

@@ -1,8 +1,8 @@
filetype: shell
detect:
filename: "(\\.sh$|\\.bash|\\.ash|\\.bashrc|bashrc|\\.bash_aliases|bash_aliases|\\.bash_functions|bash_functions|\\.bash_profile|bash_profile|\\.profile|profile|Pkgfile|pkgmk.conf|profile|rc.conf|PKGBUILD|.ebuild\\$|APKBUILD)"
header: "^#!.*/(env +)?(ba)?(a)?sh( |$)"
filename: "(\\.sh$|\\.bash|\\.ash|bashrc|bash_aliases|bash_functions|profile|bash-fc\\.|Pkgfile|pkgmk.conf|rc.conf|PKGBUILD|.ebuild\\$|APKBUILD)"
header: "^#!.*/(env +)?(ba)?(a)?(mk)?sh( |$)"
rules:
# Numbers

View File

@@ -4,39 +4,53 @@ detect:
filename: "\\.toml"
rules:
- statement: "(.*)[[:space:]]="
- special: "="
# Bracket thingies
- special: "(\\[|\\])"
# Numbers and strings
- constant.number: "\\b([0-9]+|0x[0-9a-fA-F]*)\\b|'.'"
- constant.number: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
# Punctuation
- symbol: '[=,\.]'
- symbol.brackets: '[{\[\]}]'
# Strings
- constant.string:
start: "\""
end: "\""
skip: "\\\\."
start: '"""'
end: '\"{3,5}'
skip: '\\.'
rules:
- constant.specialChar: "\\\\."
- constant.specialChar: '\\u[[:xdigit:]]{4}'
- constant.specialChar: '\\U[[:xdigit:]]{8}'
- constant.specialChar: '\\[btnfr"\\]'
- constant.string:
start: '"'
end: '"'
skip: '\\.'
rules:
- constant.specialChar: '\\u[[:xdigit:]]{4}'
- constant.specialChar: '\\U[[:xdigit:]]{8}'
- constant.specialChar: '\\[btnfr"\\]'
- constant.string:
start: "'''"
end: "'{3,5}"
rules: []
- constant.string:
start: "'"
end: "'"
skip: "\\\\."
rules:
- constant.specialChar: "\\\\."
- constant.string:
start: "`"
end: "`"
rules:
- constant.specialChar: "\\\\."
rules: []
# Integer
- constant.number: '[+-]?(\d+_)*\d+\b'
- constant.number: '(0x([[:xdigit:]]+_)*[[:xdigit:]]+|0o([0-7]_)*[0-7]+|0b([01]+_)*[01]+)'
# Float
- constant.number: '[+-]?(\d+_)*\d+\.(\d+_)*\d+'
- constant.number: '[+-]?(\d+_)*\d+(\.(\d+_)*\d+)?[Ee][+-]?(\d+_)*\d+'
- constant.number: '(\+|-)(inf|nan)'
# Bare key, keys starting with a digit or dash are ambiguous with numbers and are skipped
- identifier: '\b[A-Za-z_][A-Za-z0-9_-]*\b'
# Boolean and inf, nan without sign
- constant.bool.true: '\btrue\b'
- constant.bool.false: '\bfalse\b'
- constant.number: '\b(inf|nan)\b'
# Date and Time
- constant: '\d+-\d{2}-\d{2}([T ]\d{2}:\d{2}:\d{2}(\.\d+)?([+-]\d{2}:\d{2}|Z)?)?'
- constant: '\d{2}:\d{2}:\d{2}(\.\d+)?'
# Comments
- comment:
start: "#"
end: "$"
rules:
- todo: "(TODO|XXX|FIXME):?"