Compare commits

..

1 Commits
v1.4.0 ... dep

Author SHA1 Message Date
Zachary Yedidia
bd8776cdd0 Use dep for dependency management 2017-06-10 11:49:41 -04:00
152 changed files with 3944 additions and 7862 deletions

View File

@@ -1,7 +0,0 @@
# See http://editorconfig.org
# In Go files we indent with tabs but still
# set indent_size to control the GitHub web viewer.
[*.go]
indent_size=4

3
.gitignore vendored
View File

@@ -1,5 +1,3 @@
.DS_Store
micro
!cmd/micro
binaries/
@@ -7,3 +5,4 @@ tmp.sh
test/
.idea/
packages/
cmd/micro/vendor

63
.gitmodules vendored
View File

@@ -1,63 +0,0 @@
[submodule "cmd/micro/vendor/github.com/blang/semver"]
path = cmd/micro/vendor/github.com/blang/semver
url = https://github.com/blang/semver
[submodule "cmd/micro/vendor/github.com/dustin/go-humanize"]
path = cmd/micro/vendor/github.com/dustin/go-humanize
url = https://github.com/dustin/go-humanize
[submodule "cmd/micro/vendor/github.com/go-errors/errors"]
path = cmd/micro/vendor/github.com/go-errors/errors
url = https://github.com/go-errors/errors
[submodule "cmd/micro/vendor/github.com/mattn/go-isatty"]
path = cmd/micro/vendor/github.com/mattn/go-isatty
url = https://github.com/mattn/go-isatty
[submodule "cmd/micro/vendor/github.com/mattn/go-runewidth"]
path = cmd/micro/vendor/github.com/mattn/go-runewidth
url = https://github.com/mattn/go-runewidth
[submodule "cmd/micro/vendor/github.com/mitchellh/go-homedir"]
path = cmd/micro/vendor/github.com/mitchellh/go-homedir
url = https://github.com/mitchellh/go-homedir
[submodule "cmd/micro/vendor/github.com/sergi/go-diff"]
path = cmd/micro/vendor/github.com/sergi/go-diff
url = https://github.com/sergi/go-diff
[submodule "cmd/micro/vendor/github.com/yuin/gopher-lua"]
path = cmd/micro/vendor/github.com/yuin/gopher-lua
url = https://github.com/yuin/gopher-lua
[submodule "cmd/micro/vendor/golang.org/x/net"]
path = cmd/micro/vendor/golang.org/x/net
url = https://go.googlesource.com/net
[submodule "cmd/micro/vendor/github.com/zyedidia/clipboard"]
path = cmd/micro/vendor/github.com/zyedidia/clipboard
url = https://github.com/zyedidia/clipboard
[submodule "cmd/micro/vendor/github.com/zyedidia/glob"]
path = cmd/micro/vendor/github.com/zyedidia/glob
url = https://github.com/zyedidia/glob
[submodule "cmd/micro/vendor/github.com/zyedidia/tcell"]
path = cmd/micro/vendor/github.com/zyedidia/tcell
url = https://github.com/zyedidia/tcell
[submodule "cmd/micro/vendor/github.com/gdamore/encoding"]
path = cmd/micro/vendor/github.com/gdamore/encoding
url = https://github.com/gdamore/encoding
[submodule "cmd/micro/vendor/golang.org/x/text"]
path = cmd/micro/vendor/golang.org/x/text
url = https://go.googlesource.com/text
[submodule "cmd/micro/vendor/github.com/lucasb-eyer/go-colorful"]
path = cmd/micro/vendor/github.com/lucasb-eyer/go-colorful
url = https://github.com/lucasb-eyer/go-colorful
[submodule "cmd/micro/vendor/layeh.com/gopher-luar"]
path = cmd/micro/vendor/layeh.com/gopher-luar
url = https://github.com/layeh/gopher-luar
[submodule "cmd/micro/vendor/gopkg.in/yaml.v2"]
path = cmd/micro/vendor/gopkg.in/yaml.v2
url = https://gopkg.in/yaml.v2
[submodule "cmd/micro/vendor/github.com/zyedidia/poller"]
path = cmd/micro/vendor/github.com/zyedidia/poller
url = https://github.com/zyedidia/poller
[submodule "cmd/micro/vendor/github.com/flynn/json5"]
path = cmd/micro/vendor/github.com/flynn/json5
url = https://github.com/flynn/json5
[submodule "cmd/micro/vendor/github.com/zyedidia/terminal"]
path = cmd/micro/vendor/github.com/zyedidia/terminal
url = https://github.com/zyedidia/terminal
[submodule "cmd/micro/vendor/github.com/zyedidia/pty"]
path = cmd/micro/vendor/github.com/zyedidia/pty
url = https://github.com/zyedidia/pty

View File

@@ -1,6 +1,6 @@
MIT License
Micro is licensed under the MIT "Expat" License:
Copyright (c) 2016-2017: Zachary Yedidia, et al.
Copyright (c) 2016: Zachary Yedidia, et al.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -1106,111 +1106,3 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
github.com/flynn/json5/LICENSE
================
Decoder code based on package encoding/json from the Go language.
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Test data based on the parse cases from https://github.com/json5/json5
Copyright (c) 2012-2016 Aseem Kishore, and others.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
github.com/james4k/terminal/LICENSE
================
Copyright (C) 2013 James Gray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without liitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and thismssion notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
github.com/kr/pty/License
================
Copyright (c) 2011 Keith Rarick
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall
be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -11,7 +11,7 @@ ADDITIONAL_GO_LINKER_FLAGS := $(shell GOOS=$(shell go env GOHOSTOS) \
GOBIN ?= $(shell go env GOPATH)/bin
# Builds micro after checking dependencies but without updating the runtime
build: update
build: deps
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Builds micro after building the runtime and checking dependencies
@@ -22,7 +22,7 @@ build-quick:
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Same as 'build' but installs to $GOBIN afterward
install: update
install: deps
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Same as 'build-all' but installs to $GOBIN afterward
@@ -32,9 +32,13 @@ install-all: runtime install
install-quick:
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Checks for dependencies
deps:
go get -d ./cmd/micro
update:
git pull
git submodule update --init
go get -u -d ./cmd/micro
# Builds the runtime
runtime:
@@ -43,6 +47,7 @@ runtime:
mv runtime.go cmd/micro
test:
go get -d ./cmd/micro
go test ./cmd/micro
clean:

View File

@@ -4,7 +4,6 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/zyedidia/micro)](https://goreportcard.com/report/github.com/zyedidia/micro)
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
[![Snap Status](https://build.snapcraft.io/badge/zyedidia/micro.svg)](https://build.snapcraft.io/user/zyedidia/micro)
Micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities
of modern terminals. It comes as one single, batteries-included, static binary with no dependencies, and you can download and use it right now.
@@ -20,33 +19,15 @@ To see more screenshots of micro, showcasing all of the default colorschemes, se
You can also check out the website for Micro at https://micro-editor.github.io.
# Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Prebuilt binaries](#prebuilt-binaries)
- [Package Managers](#package-managers)
- [Building from source](#building-from-source)
- [MacOS terminal](#macos-terminal)
- [Linux clipboard support](#linux-clipboard-support)
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
- [Plan9, Cygwin](#plan9-cygwin)
- [Usage](#usage)
- [Documentation and Help](#documentation-and-help)
- [Contributing](#contributing)
- - -
# Features
* Easy to use and to install
* No dependencies or external files are needed -- just the binary you can download further down the page
* Multiple cursors
* Common keybindings (ctrl-s, ctrl-c, ctrl-v, ctrl-z...)
* Keybindings can be rebound to your liking
* Sane defaults
* You shouldn't have to configure much out of the box (and it is extremely easy to configure)
* Splits and tabs
* Nano-like menu to help you remember the keybindings
* Extremely good mouse support
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
* Cross platform (It should work on all the platforms Go runs on)
@@ -67,7 +48,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
* Macros
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)) or a tree view ([#249](https://github.com/zyedidia/micro/issues/249)) in the future.
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)), and multiple cursors ([#5](https://github.com/zyedidia/micro/issues/5)) in the future.
# Installation
@@ -86,19 +67,7 @@ and you'll see all the stable releases with the corresponding binaries.
If you'd like to see more information after installing micro, run `micro -version`.
### Installation script
There is a great script which can install micro for you by downloading the latest prebuilt binary. You can find it at https://getmic.ro (the github repo for it is [here](https://github.com/benweissmann/getmic.ro)).
Then you can easily install micro:
$ curl https://getmic.ro | bash
The script will install the micro binary to the current directory.
See the [Github page](https://github.com/benweissmann/getmic.ro) for more information.
### Package managers
### Package Managers
You can install micro using Homebrew on Mac:
@@ -106,51 +75,39 @@ You can install micro using Homebrew on Mac:
brew install micro
```
On Windows, you can install micro through [Chocolatey](https://chocolatey.org/) or [Scoop](https://github.com/lukesampson/scoop):
On Windows, you can install micro through Chocolatey:
```
choco install micro
```
or
```
scoop install micro
```
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
```
snap install micro --classic
```
On OpenBSD, micro is available in the ports tree. It is also available as a binary package.
```
pkg_add -v micro
snap install micro --beta
```
### Building from source
If your operating system does not have a binary release, but does run Go, you can build from source.
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommend setting it to `~/go` if you don't have one).
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommand setting it to `~/go` if you don't have one).
```
go get -d github.com/zyedidia/micro/cmd/micro
go get -d github.com/zyedidia/micro/...
cd $GOPATH/src/github.com/zyedidia/micro
make install
```
The binary will then be installed to `$GOPATH/bin` (or your `$GOBIN`).
Please make sure that when you are working with micro's code, you are working on your `GOPATH`.
The binary will then be installed to `$GOPATH/bin` (or your `$GOBIN`).
You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd/micro`) but this isn't recommended because it doesn't build micro with version information which is useful for the plugin manager.
### MacOS terminal
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Load Preset`. The newest versions also support true color.
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. The newest versions also support true color.
### Linux clipboard support
@@ -178,11 +135,11 @@ that micro's default colorscheme won't look very good. You can either set
the colorscheme to `simple`, or download a better terminal emulator, like
mintty.
### Plan9, Cygwin
### Plan9, NaCl, Cygwin
Please note that micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).
Plan9, NaCl, and Cygwin (although this may change in the future).
# Usage

View File

@@ -1,63 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg3336"
version="1.1"
inkscape:version="0.91 r13725"
width="128"
height="128"
viewBox="0 0 128 128"
sodipodi:docname="logo.svg">
<metadata
id="metadata3342">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3340" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1355"
inkscape:window-height="717"
id="namedview3338"
showgrid="false"
inkscape:zoom="1.6243169"
inkscape:cx="111.32302"
inkscape:cy="30.538264"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg3336" />
<path
style="fill:#2e3192;fill-opacity:1"
d="m 56.1,127.32358 c -13.68932,-1.70993 -27.156628,-8.3544 -37.112903,-18.31068 -25.0687936,-25.068788 -25.0687936,-65.95701 0,-91.025803 25.068793,-25.0687936 65.957015,-25.0687936 91.025803,0 25.0688,25.068793 25.0688,65.957015 0,91.025803 C 95.87457,123.15123 76.198116,129.83404 56.1,127.32358 Z"
id="path3364"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff"
d="m 40.756452,106.01908 c 1.442831,-1.83426 1.55476,-4.09687 0.414499,-8.37899 -0.678184,-2.546844 -0.684604,-4.05591 -0.03829,-9 1.276867,-9.767604 4.483143,-23.040636 5.565559,-23.039766 0.220979,1.74e-4 0.417725,2.092674 0.437213,4.65 0.04167,5.468298 1.558564,9.06891 4.638769,11.010942 2.551646,1.608774 9.15365,1.329324 12.80399,-0.541974 3.245124,-1.663572 7.649064,-6.112434 9.850956,-9.951438 L 76.188736,67.7 l 0.0054,3.922866 c 0.0042,2.867148 0.36894,4.642788 1.355628,6.59796 1.532058,3.035856 3.323226,4.15755 6.659322,4.17033 5.192928,0.01986 9.07014,-3.668676 10.866768,-10.338036 0.98277,-3.64821 1.064448,-11.21265 0.09235,-8.55312 -3.025218,8.276592 -4.468212,9.893562 -9.238056,10.351884 -2.629152,0.25263 -3.177804,0.08883 -4.921776,-1.469412 -1.609044,-1.437678 -2.016072,-2.308416 -2.258508,-4.8315 -0.262884,-2.73585 0.105942,-4.06497 3.32007,-11.964365 C 88.28388,40.315087 89.33625,35.536248 87,33.2 c -1.559352,-1.559353 -3.62787,-1.522741 -5.691792,0.10074 -2.295762,1.805846 -3.105984,4.070756 -5.14293,14.376662 -2.464164,12.46744 -6.525822,20.297092 -12.62193,24.331306 C 59.052142,74.98085 52.704914,73.6403 50.637191,69.282896 49.19967,66.253544 49.857706,62.552972 53.387813,53.814319 56.613526,45.829186 58.8,38.711369 58.8,36.195564 c 0,-4.161283 -4.366993,-5.665719 -7.364438,-2.537061 -2.183558,2.279144 -3.117251,5.256959 -4.280897,13.653016 -0.547956,3.953665 -1.259292,9.010489 -1.580746,11.237387 -0.321454,2.226896 -2.083918,8.706896 -3.916587,14.400002 -4.33165,13.456074 -6.85029,23.184822 -7.273674,28.096022 -0.325586,3.77675 -0.269352,4.00056 1.319044,5.25 2.187498,1.72068 3.541408,1.64679 5.05375,-0.27585 z"
id="path3362"
inkscape:connector-curvature="0" />
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,56 +0,0 @@
.\" micro manual page - micro(1)
.\"
.\" Copyright © 2017 Zachary Yedidia <zyedidia@gmail.com>
.\" Copyright © 2017 Collin Warren <anatoly@somethinghub.com>
.\"
.\" This document is provided under the same licensing as micro.
.\" See \usr\share\doc\micro\LICENSE for more information.
.TH micro 1 "2017-03-28"
.SH NAME
micro \- An intuitive and modern terminal text editor
.
.SH SYNOPSIS
.B micro
.RB []
[
.I "filename \&..."
]
.SH DESCRIPTION
( Copied from the README file. )
Micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities
of modern terminals. It comes as one single, batteries-included, static binary with no dependencies.
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
.SH OPTIONS
.B \-v --version
Displays the current version of micro and the git commit hash.
.TP
.SH ENVIRONMENT
Micro's behaviour can be changed by setting environment variables, of which
there is currently only one:
.I MICRO_TRUE_COLOR
When MICRO_TRUE_COLOR is set to 1, micro will attempt to treat your terminal as
a true-color terminal and will be able to make full use of the true-color colorschemes
that are included with micro. If MICRO_TRUE_COLOR is not set or is set to 0, then
micro will only make use of 256 color features and will internally map true-color
colorschemes to the nearest colors available. For more information see micro's documentation.
.SH NOTICE
This manpage is intended only to serve as a quick guide to the invocation of
micro and is not intended to replace the full documentation included with micro
which can be accessed from within micro. Micro tells you what key combination to
press to get help in the lower right.
.SH BUGS
A comprehensive list of bugs will not be listed in this manpage. See the Github
page at \fBhttps://github.com/zyedidia/micro/issues\fP for a list of known bugs
and to report any newly encountered bugs you may find. We strive to correct
bugs as swiftly as possible.
.SH COPYRIGHT
Copyright \(co 2017 Zachary Yedidia, Collin Warren, et al.
See /usr/share/doc/micro/LICENSE and /usr/share/doc/micro/AUTHORS for more information.

View File

@@ -1,15 +0,0 @@
[Desktop Entry]
Name=Micro
GenericName=Text Editor
Comment=Edit text files in a terminal
Icon=micro
Type=Application
Categories=terminal;TextEditor;
Keywords=text;editor;syntax;terminal;
Exec=micro %U
StartupNotify=false
Terminal=true
MimeType=text/plain;text/x-chdr;text/x-csrc;text/x-c++hdr;text/x-c++src;text/x-java;text/x-dsrc;text/x-pascal;text/x-perl;text/x-python;application/x-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/xml;text/html;text/css;text/x-sql;text/x-diff;

123
cmd/micro/Gopkg.lock generated Normal file
View File

@@ -0,0 +1,123 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/blang/semver"
packages = ["."]
revision = "4a1e882c79dcf4ec00d2e29fac74b9c8938d5052"
[[projects]]
branch = "master"
name = "github.com/dustin/go-humanize"
packages = ["."]
revision = "259d2a102b871d17f30e3cd9881a642961a1e486"
[[projects]]
branch = "master"
name = "github.com/gdamore/encoding"
packages = ["."]
revision = "b23993cbb6353f0e6aa98d0ee318a34728f628b9"
[[projects]]
branch = "master"
name = "github.com/go-errors/errors"
packages = ["."]
revision = "8fa88b06e5974e97fbf9899a7f86a344bfd1f105"
[[projects]]
branch = "master"
name = "github.com/lucasb-eyer/go-colorful"
packages = ["."]
revision = "9c2852a141bf4711e4276f8f119c90d0f20a556c"
[[projects]]
branch = "master"
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe"
[[projects]]
branch = "master"
name = "github.com/mattn/go-runewidth"
packages = ["."]
revision = "97311d9f7767e3d6f422ea06661bc2c7a19e8a5d"
[[projects]]
branch = "master"
name = "github.com/mitchellh/go-homedir"
packages = ["."]
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
[[projects]]
branch = "master"
name = "github.com/sergi/go-diff"
packages = ["diffmatchpatch"]
revision = "feef008d51ad2b3778f85d387ccf91735543008d"
[[projects]]
branch = "master"
name = "github.com/yuin/gopher-lua"
packages = [".","ast","parse","pm"]
revision = "b402f3114ec730d8bddb074a6c137309f561aa78"
[[projects]]
branch = "master"
name = "github.com/zyedidia/clipboard"
packages = ["."]
revision = "adacf416cec40266b051e7bc096c52951f2725e9"
[[projects]]
branch = "master"
name = "github.com/zyedidia/glob"
packages = ["."]
revision = "72567a468b2481490f359cdfb015231389e0bf9d"
[[projects]]
branch = "master"
name = "github.com/zyedidia/json5"
packages = ["encoding/json5"]
revision = "2518f8beebde6814f2d30d566260480d2ded2f76"
[[projects]]
branch = "master"
name = "github.com/zyedidia/tcell"
packages = [".","encoding"]
revision = "7095cc1c7f4173ae48314d80878e9985a0658889"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context"]
revision = "007e530097ad7f954752df63046b4036f98ba6a6"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "d4feaf1a7e61e1d9e79e6c4e76c6349e9cab0a03"
[[projects]]
branch = "master"
name = "golang.org/x/text"
packages = ["encoding","encoding/charmap","encoding/internal","encoding/internal/identifier","encoding/japanese","encoding/korean","encoding/simplifiedchinese","encoding/traditionalchinese","internal/gen","transform","unicode/cldr"]
revision = "506f9d5c962f284575e88337e7d9296d27e729d3"
[[projects]]
branch = "v2"
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "a83829b6f1293c91addabc89d0571c246397bbf4"
[[projects]]
branch = "master"
name = "layeh.com/gopher-luar"
packages = ["."]
revision = "80196fe2abc5682963fc7a5261f5a5d77509938b"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "aebc6aa50d78830f86e8964c1ebc1804c1f9603477ddba606717a972ca70d261"
solver-name = "gps-cdcl"
solver-version = 1

123
cmd/micro/Gopkg.toml Normal file
View File

@@ -0,0 +1,123 @@
## Gopkg.toml example (these lines may be deleted)
## "metadata" defines metadata about the project that could be used by other independent
## systems. The metadata defined here will be ignored by dep.
# [metadata]
# key1 = "value that convey data to other systems"
# system1-data = "value that is used by a system"
# system2-data = "value that is used by another system"
## "required" lists a set of packages (not projects) that must be included in
## Gopkg.lock. This list is merged with the set of packages imported by the current
## project. Use it when your project needs a package it doesn't explicitly import -
## including "main" packages.
# required = ["github.com/user/thing/cmd/thing"]
## "ignored" lists a set of packages (not projects) that are ignored when
## dep statically analyzes source code. Ignored packages can be in this project,
## or in a dependency.
# ignored = ["github.com/user/project/badpkg"]
## Constraints are rules for how directly imported projects
## may be incorporated into the depgraph. They are respected by
## dep whether coming from the Gopkg.toml of the current project or a dependency.
# [[constraint]]
## Required: the root import path of the project being constrained.
# name = "github.com/user/project"
#
## Recommended: the version constraint to enforce for the project.
## Only one of "branch", "version" or "revision" can be specified.
# version = "1.0.0"
# branch = "master"
# revision = "abc123"
#
## Optional: an alternate location (URL or import path) for the project's source.
# source = "https://github.com/myfork/package.git"
#
## "metadata" defines metadata about the dependency or override that could be used
## by other independent systems. The metadata defined here will be ignored by dep.
# [metadata]
# key1 = "value that convey data to other systems"
# system1-data = "value that is used by a system"
# system2-data = "value that is used by another system"
## Overrides have the same structure as [[constraint]], but supersede all
## [[constraint]] declarations from all projects. Only [[override]] from
## the current project's are applied.
##
## Overrides are a sledgehammer. Use them only as a last resort.
# [[override]]
## Required: the root import path of the project being constrained.
# name = "github.com/user/project"
#
## Optional: specifying a version constraint override will cause all other
## constraints on this project to be ignored; only the overridden constraint
## need be satisfied.
## Again, only one of "branch", "version" or "revision" can be specified.
# version = "1.0.0"
# branch = "master"
# revision = "abc123"
#
## Optional: specifying an alternate source location as an override will
## enforce that the alternate location is used for that project, regardless of
## what source location any dependent projects specify.
# source = "https://github.com/myfork/package.git"
[[constraint]]
branch = "master"
name = "github.com/blang/semver"
[[constraint]]
branch = "master"
name = "github.com/dustin/go-humanize"
[[constraint]]
branch = "master"
name = "github.com/go-errors/errors"
[[constraint]]
branch = "master"
name = "github.com/mattn/go-isatty"
[[constraint]]
branch = "master"
name = "github.com/mattn/go-runewidth"
[[constraint]]
branch = "master"
name = "github.com/mitchellh/go-homedir"
[[constraint]]
branch = "master"
name = "github.com/sergi/go-diff"
[[constraint]]
branch = "master"
name = "github.com/yuin/gopher-lua"
[[constraint]]
branch = "master"
name = "github.com/zyedidia/clipboard"
[[constraint]]
branch = "master"
name = "github.com/zyedidia/glob"
[[constraint]]
branch = "master"
name = "github.com/zyedidia/json5"
[[constraint]]
branch = "master"
name = "github.com/zyedidia/tcell"
[[constraint]]
branch = "v2"
name = "gopkg.in/yaml.v2"
[[constraint]]
branch = "master"
name = "layeh.com/gopher-luar"

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,3 @@
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
package main
import "syscall"
@@ -21,7 +19,8 @@ func (v *View) Suspend(usePlugin bool) bool {
// suspend the process
pid := syscall.Getpid()
err := syscall.Kill(pid, syscall.SIGSTOP)
tid := syscall.Gettid()
err := syscall.Tgkill(pid, tid, syscall.SIGSTOP)
if err != nil {
TermMessage(err)
}

View File

@@ -1,9 +1,9 @@
// +build plan9 nacl windows
// +build !linux
package main
func (v *View) Suspend(usePlugin bool) bool {
messenger.Error("Suspend is only supported on Posix")
messenger.Error("Suspend is only supported on Linux")
return false
}

View File

@@ -4,6 +4,8 @@ import (
"io/ioutil"
"os"
"strings"
"github.com/mitchellh/go-homedir"
)
var pluginCompletions []func(string) []string
@@ -20,9 +22,13 @@ func FileComplete(input string) (string, []string) {
var files []os.FileInfo
var err error
if len(dirs) > 1 {
home, _ := homedir.Dir()
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
directories = ReplaceHome(directories)
if strings.HasPrefix(directories, "~") {
directories = strings.Replace(directories, "~", home, 1)
}
files, err = ioutil.ReadDir(directories)
} else {
files, err = ioutil.ReadDir(".")
@@ -142,63 +148,6 @@ func OptionComplete(input string) (string, []string) {
return chosen, suggestions
}
// OptionValueComplete completes values for various options
func OptionValueComplete(inputOpt, input string) (string, []string) {
inputOpt = strings.TrimSpace(inputOpt)
var suggestions []string
localSettings := DefaultLocalSettings()
var optionVal interface{}
for k, option := range globalSettings {
if k == inputOpt {
optionVal = option
}
}
for k, option := range localSettings {
if k == inputOpt {
optionVal = option
}
}
switch optionVal.(type) {
case bool:
if strings.HasPrefix("on", input) {
suggestions = append(suggestions, "on")
} else if strings.HasPrefix("true", input) {
suggestions = append(suggestions, "true")
}
if strings.HasPrefix("off", input) {
suggestions = append(suggestions, "off")
} else if strings.HasPrefix("false", input) {
suggestions = append(suggestions, "false")
}
case string:
switch inputOpt {
case "colorscheme":
_, suggestions = ColorschemeComplete(input)
case "fileformat":
if strings.HasPrefix("unix", input) {
suggestions = append(suggestions, "unix")
}
if strings.HasPrefix("dos", input) {
suggestions = append(suggestions, "dos")
}
case "sucmd":
if strings.HasPrefix("sudo", input) {
suggestions = append(suggestions, "sudo")
}
if strings.HasPrefix("doas", input) {
suggestions = append(suggestions, "doas")
}
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
// MakeCompletion registers a function from a plugin for autocomplete commands
func MakeCompletion(function string) Completion {
pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
@@ -220,7 +169,6 @@ func PluginComplete(complete Completion, input string) (chosen string, suggestio
return
}
// PluginCmdComplete completes with possible choices for the `> plugin` command
func PluginCmdComplete(input string) (chosen string, suggestions []string) {
for _, cmd := range []string{"install", "remove", "search", "update", "list"} {
if strings.HasPrefix(cmd, input) {
@@ -234,7 +182,6 @@ func PluginCmdComplete(input string) (chosen string, suggestions []string) {
return chosen, suggestions
}
// PluginnameComplete completes with the names of loaded plugins
func PluginNameComplete(input string) (chosen string, suggestions []string) {
for _, pp := range GetAllPluginPackages() {
if strings.HasPrefix(pp.Name, input) {

View File

@@ -1,133 +1,101 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"unicode"
"github.com/flynn/json5"
"github.com/zyedidia/json5/encoding/json5"
"github.com/zyedidia/tcell"
)
var bindingsStr map[string]string
var bindings map[Key][]func(*View, bool) bool
var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
var helpBinding string
var kmenuBinding string
var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
"MousePress": (*View).MousePress,
"MouseMultiCursor": (*View).MouseMultiCursor,
}
var bindingActions = map[string]func(*View, bool) bool{
"CursorUp": (*View).CursorUp,
"CursorDown": (*View).CursorDown,
"CursorPageUp": (*View).CursorPageUp,
"CursorPageDown": (*View).CursorPageDown,
"CursorLeft": (*View).CursorLeft,
"CursorRight": (*View).CursorRight,
"CursorStart": (*View).CursorStart,
"CursorEnd": (*View).CursorEnd,
"SelectToStart": (*View).SelectToStart,
"SelectToEnd": (*View).SelectToEnd,
"SelectUp": (*View).SelectUp,
"SelectDown": (*View).SelectDown,
"SelectLeft": (*View).SelectLeft,
"SelectRight": (*View).SelectRight,
"WordRight": (*View).WordRight,
"WordLeft": (*View).WordLeft,
"SelectWordRight": (*View).SelectWordRight,
"SelectWordLeft": (*View).SelectWordLeft,
"DeleteWordRight": (*View).DeleteWordRight,
"DeleteWordLeft": (*View).DeleteWordLeft,
"SelectLine": (*View).SelectLine,
"SelectToStartOfLine": (*View).SelectToStartOfLine,
"SelectToEndOfLine": (*View).SelectToEndOfLine,
"ParagraphPrevious": (*View).ParagraphPrevious,
"ParagraphNext": (*View).ParagraphNext,
"InsertNewline": (*View).InsertNewline,
"InsertSpace": (*View).InsertSpace,
"Backspace": (*View).Backspace,
"Delete": (*View).Delete,
"InsertTab": (*View).InsertTab,
"Save": (*View).Save,
"SaveAll": (*View).SaveAll,
"SaveAs": (*View).SaveAs,
"Find": (*View).Find,
"FindNext": (*View).FindNext,
"FindPrevious": (*View).FindPrevious,
"Center": (*View).Center,
"Undo": (*View).Undo,
"Redo": (*View).Redo,
"Copy": (*View).Copy,
"Cut": (*View).Cut,
"CutLine": (*View).CutLine,
"DuplicateLine": (*View).DuplicateLine,
"DeleteLine": (*View).DeleteLine,
"MoveLinesUp": (*View).MoveLinesUp,
"MoveLinesDown": (*View).MoveLinesDown,
"IndentSelection": (*View).IndentSelection,
"OutdentSelection": (*View).OutdentSelection,
"OutdentLine": (*View).OutdentLine,
"Paste": (*View).Paste,
"PastePrimary": (*View).PastePrimary,
"SelectAll": (*View).SelectAll,
"OpenFile": (*View).OpenFile,
"Start": (*View).Start,
"End": (*View).End,
"PageUp": (*View).PageUp,
"PageDown": (*View).PageDown,
"HalfPageUp": (*View).HalfPageUp,
"HalfPageDown": (*View).HalfPageDown,
"StartOfLine": (*View).StartOfLine,
"EndOfLine": (*View).EndOfLine,
"ToggleHelp": (*View).ToggleHelp,
"ToggleKeyMenu": (*View).ToggleKeyMenu,
"ToggleRuler": (*View).ToggleRuler,
"JumpLine": (*View).JumpLine,
"ClearStatus": (*View).ClearStatus,
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"ToggleOverwriteMode": (*View).ToggleOverwriteMode,
"Escape": (*View).Escape,
"Quit": (*View).Quit,
"QuitAll": (*View).QuitAll,
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
"Unsplit": (*View).Unsplit,
"VSplit": (*View).VSplitBinding,
"HSplit": (*View).HSplitBinding,
"ToggleMacro": (*View).ToggleMacro,
"PlayMacro": (*View).PlayMacro,
"Suspend": (*View).Suspend,
"ScrollUp": (*View).ScrollUpAction,
"ScrollDown": (*View).ScrollDownAction,
"SpawnMultiCursor": (*View).SpawnMultiCursor,
"RemoveMultiCursor": (*View).RemoveMultiCursor,
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
"SkipMultiCursor": (*View).SkipMultiCursor,
"JumpToMatchingBrace": (*View).JumpToMatchingBrace,
"CursorUp": (*View).CursorUp,
"CursorDown": (*View).CursorDown,
"CursorPageUp": (*View).CursorPageUp,
"CursorPageDown": (*View).CursorPageDown,
"CursorLeft": (*View).CursorLeft,
"CursorRight": (*View).CursorRight,
"CursorStart": (*View).CursorStart,
"CursorEnd": (*View).CursorEnd,
"SelectToStart": (*View).SelectToStart,
"SelectToEnd": (*View).SelectToEnd,
"SelectUp": (*View).SelectUp,
"SelectDown": (*View).SelectDown,
"SelectLeft": (*View).SelectLeft,
"SelectRight": (*View).SelectRight,
"WordRight": (*View).WordRight,
"WordLeft": (*View).WordLeft,
"SelectWordRight": (*View).SelectWordRight,
"SelectWordLeft": (*View).SelectWordLeft,
"DeleteWordRight": (*View).DeleteWordRight,
"DeleteWordLeft": (*View).DeleteWordLeft,
"SelectToStartOfLine": (*View).SelectToStartOfLine,
"SelectToEndOfLine": (*View).SelectToEndOfLine,
"InsertNewline": (*View).InsertNewline,
"InsertSpace": (*View).InsertSpace,
"Backspace": (*View).Backspace,
"Delete": (*View).Delete,
"InsertTab": (*View).InsertTab,
"Save": (*View).Save,
"SaveAll": (*View).SaveAll,
"SaveAs": (*View).SaveAs,
"Find": (*View).Find,
"FindNext": (*View).FindNext,
"FindPrevious": (*View).FindPrevious,
"Center": (*View).Center,
"Undo": (*View).Undo,
"Redo": (*View).Redo,
"Copy": (*View).Copy,
"Cut": (*View).Cut,
"CutLine": (*View).CutLine,
"DuplicateLine": (*View).DuplicateLine,
"DeleteLine": (*View).DeleteLine,
"MoveLinesUp": (*View).MoveLinesUp,
"MoveLinesDown": (*View).MoveLinesDown,
"IndentSelection": (*View).IndentSelection,
"OutdentSelection": (*View).OutdentSelection,
"OutdentLine": (*View).OutdentLine,
"Paste": (*View).Paste,
"PastePrimary": (*View).PastePrimary,
"SelectAll": (*View).SelectAll,
"OpenFile": (*View).OpenFile,
"Start": (*View).Start,
"End": (*View).End,
"PageUp": (*View).PageUp,
"PageDown": (*View).PageDown,
"HalfPageUp": (*View).HalfPageUp,
"HalfPageDown": (*View).HalfPageDown,
"StartOfLine": (*View).StartOfLine,
"EndOfLine": (*View).EndOfLine,
"ToggleHelp": (*View).ToggleHelp,
"ToggleRuler": (*View).ToggleRuler,
"JumpLine": (*View).JumpLine,
"ClearStatus": (*View).ClearStatus,
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"Escape": (*View).Escape,
"Quit": (*View).Quit,
"QuitAll": (*View).QuitAll,
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
"Unsplit": (*View).Unsplit,
"VSplit": (*View).VSplitBinding,
"HSplit": (*View).HSplitBinding,
"ToggleMacro": (*View).ToggleMacro,
"PlayMacro": (*View).PlayMacro,
"Suspend": (*View).Suspend,
// This was changed to InsertNewline but I don't want to break backwards compatibility
"InsertEnter": (*View).InsertNewline,
}
var bindingMouse = map[string]tcell.ButtonMask{
"MouseLeft": tcell.Button1,
"MouseMiddle": tcell.Button2,
"MouseRight": tcell.Button3,
"MouseWheelUp": tcell.WheelUp,
"MouseWheelDown": tcell.WheelDown,
"MouseWheelLeft": tcell.WheelLeft,
"MouseWheelRight": tcell.WheelRight,
}
var bindingKeys = map[string]tcell.Key{
"Up": tcell.KeyUp,
"Down": tcell.KeyDown,
@@ -247,8 +215,6 @@ var bindingKeys = map[string]tcell.Key{
"CtrlRightSq": tcell.KeyCtrlRightSq,
"CtrlCarat": tcell.KeyCtrlCarat,
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
"CtrlPageUp": tcell.KeyCtrlPgUp,
"CtrlPageDown": tcell.KeyCtrlPgDn,
"Tab": tcell.KeyTab,
"Esc": tcell.KeyEsc,
"Escape": tcell.KeyEscape,
@@ -264,16 +230,12 @@ var bindingKeys = map[string]tcell.Key{
type Key struct {
keyCode tcell.Key
modifiers tcell.ModMask
buttons tcell.ButtonMask
r rune
escape string
}
// InitBindings initializes the keybindings for micro
func InitBindings() {
bindings = make(map[Key][]func(*View, bool) bool)
bindingsStr = make(map[string]string)
mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
var parsed map[string]string
defaults := DefaultBindings()
@@ -324,14 +286,6 @@ modSearch:
case strings.HasPrefix(k, "Shift"):
k = k[5:]
modifiers |= tcell.ModShift
case strings.HasPrefix(k, "\x1b"):
return Key{
keyCode: -1,
modifiers: modifiers,
buttons: -1,
r: 0,
escape: k,
}, true
default:
break modSearch
}
@@ -342,13 +296,11 @@ modSearch:
// first.
if modifiers&tcell.ModCtrl != 0 {
// see if the key is in bindingKeys with the Ctrl prefix.
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
if code, ok := bindingKeys["Ctrl"+k]; ok {
// It is, we're done.
return Key{
keyCode: code,
modifiers: modifiers,
buttons: -1,
r: 0,
}, true
}
@@ -359,16 +311,6 @@ modSearch:
return Key{
keyCode: code,
modifiers: modifiers,
buttons: -1,
r: 0,
}, true
}
// See if we can find the key in bindingMouse
if code, ok := bindingMouse[k]; ok {
return Key{
modifiers: modifiers,
buttons: code,
r: 0,
}, true
}
@@ -378,13 +320,12 @@ modSearch:
return Key{
keyCode: tcell.KeyRune,
modifiers: modifiers,
buttons: -1,
r: rune(k[0]),
}, true
}
// We don't know what happened.
return Key{buttons: -1}, false
return Key{}, false
}
// findAction will find 'action' using string 'v'
@@ -398,53 +339,6 @@ func findAction(v string) (action func(*View, bool) bool) {
return action
}
func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
action, ok := mouseBindingActions[v]
if !ok {
// If the user seems to be binding a function that doesn't exist
// We hope that it's a lua function that exists and bind it to that
action = LuaFunctionMouseBinding(v)
}
return action
}
// TryBindKey tries to bind a key by writing to configDir/bindings.json
// This function is unused for now
func TryBindKey(k, v string) {
filename := configDir + "/bindings.json"
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
TermMessage("Error reading bindings.json file: " + err.Error())
return
}
conflict := -1
lines := strings.Split(string(input), "\n")
for i, l := range lines {
parts := strings.Split(l, ":")
if len(parts) >= 2 {
if strings.Contains(parts[0], k) {
conflict = i
TermMessage("Warning: Keybinding conflict:", k, " has been overwritten")
}
}
}
binding := fmt.Sprintf(" \"%s\": \"%s\",", k, v)
if conflict == -1 {
lines = append([]string{lines[0], binding}, lines[conflict:]...)
} else {
lines = append(append(lines[:conflict], binding), lines[conflict+1:]...)
}
txt := strings.Join(lines, "\n")
err = ioutil.WriteFile(filename, []byte(txt), 0644)
if err != nil {
TermMessage("Error")
}
}
}
// BindKey takes a key and an action and binds the two together
func BindKey(k, v string) {
key, ok := findKey(k)
@@ -455,52 +349,25 @@ func BindKey(k, v string) {
if v == "ToggleHelp" {
helpBinding = k
}
if v == "ToggleKeyMenu" {
kmenuBinding = k
}
if helpBinding == k && v != "ToggleHelp" {
helpBinding = ""
}
if kmenuBinding == k && v != "ToggleKeyMenu" {
kmenuBinding = ""
}
actionNames := strings.Split(v, ",")
if actionNames[0] == "UnbindKey" {
delete(bindings, key)
delete(mouseBindings, key)
delete(bindingsStr, k)
if len(actionNames) == 1 {
return
actionNames = make([]string, 0, 0)
} else {
actionNames = append(actionNames[:0], actionNames[1:]...)
}
actionNames = append(actionNames[:0], actionNames[1:]...)
}
actions := make([]func(*View, bool) bool, 0, len(actionNames))
mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
for _, actionName := range actionNames {
if strings.HasPrefix(actionName, "Mouse") {
mouseActions = append(mouseActions, findMouseAction(actionName))
} else if strings.HasPrefix(actionName, "command:") {
cmd := strings.SplitN(actionName, ":", 2)[1]
actions = append(actions, CommandAction(cmd))
} else if strings.HasPrefix(actionName, "command-edit:") {
cmd := strings.SplitN(actionName, ":", 2)[1]
actions = append(actions, CommandEditAction(cmd))
} else {
actions = append(actions, findAction(actionName))
}
actions = append(actions, findAction(actionName))
}
if len(actions) > 0 {
// Can't have a binding be both mouse and normal
delete(mouseBindings, key)
bindings[key] = actions
bindingsStr[k] = v
} else if len(mouseActions) > 0 {
// Can't have a binding be both mouse and normal
delete(bindings, key)
mouseBindings[key] = mouseActions
}
bindings[key] = actions
}
// DefaultBindings returns a map containing micro's default keybindings
@@ -530,8 +397,6 @@ func DefaultBindings() map[string]string {
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
@@ -561,10 +426,7 @@ func DefaultBindings() map[string]string {
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"CtrlR": "ToggleRuler",
"CtrlL": "JumpLine",
"Delete": "Delete",
@@ -574,34 +436,22 @@ func DefaultBindings() map[string]string {
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfLine",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
"Alt-p": "CursorUp",
"Alt-n": "CursorDown",
// Integration with file managers
"F1": "ToggleHelp",
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
}

View File

@@ -2,9 +2,7 @@ package main
import (
"bytes"
"crypto/md5"
"encoding/gob"
"errors"
"io"
"io/ioutil"
"os"
@@ -17,16 +15,10 @@ import (
"time"
"unicode/utf8"
"github.com/mitchellh/go-homedir"
"github.com/zyedidia/micro/cmd/micro/highlight"
)
var (
// 0 - no line type detected
// 1 - lf detected
// 2 - crlf detected
fileformat = 0
)
// Buffer stores the text for files that are loaded into the text editor
// It uses a rope to efficiently store the string and contains some
// simple functions for saving and wrapper functions for modifying the rope
@@ -36,9 +28,7 @@ type Buffer struct {
// This stores all the text in the buffer as an array of lines
*LineArray
Cursor Cursor
cursors []*Cursor // for multiple cursors
curCursor int // the current cursor
Cursor Cursor
// Path to the file on disk
Path string
@@ -53,15 +43,11 @@ type Buffer struct {
// Stores the last modification time of the file the buffer is pointing to
ModTime time.Time
// NumLines is the number of lines in the buffer
NumLines int
syntaxDef *highlight.Def
highlighter *highlight.Highlighter
// Hash of the original buffer -- empty if fastdirty is on
origHash [16]byte
// Buffer local settings
Settings map[string]interface{}
}
@@ -74,8 +60,6 @@ type SerializedBuffer struct {
ModTime time.Time
}
// NewBufferFromString creates a new buffer containing the given
// string
func NewBufferFromString(text, path string) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path)
}
@@ -102,12 +86,6 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
}
}
if fileformat == 1 {
b.Settings["fileformat"] = "unix"
} else if fileformat == 2 {
b.Settings["fileformat"] = "dos"
}
absPath, _ := filepath.Abs(path)
b.Path = path
@@ -162,7 +140,7 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
InitLocalSettings(b)
if len(*flagStartPos) == 0 && (b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool)) {
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
// If either savecursor or saveundo is turned on, we need to load the serialized information
// from ~/.config/micro/buffers
file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
@@ -181,7 +159,7 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
}
if b.Settings["saveundo"].(bool) {
// We should only use last time's eventhandler if the file wasn't modified by someone else in the meantime
// We should only use last time's eventhandler if the file wasn't by someone else in the meantime
if b.ModTime == buffer.ModTime {
b.EventHandler = buffer.EventHandler
b.EventHandler.buf = b
@@ -191,22 +169,9 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
file.Close()
}
if !b.Settings["fastdirty"].(bool) {
if size > 50000 {
// If the file is larger than a megabyte fastdirty needs to be on
b.Settings["fastdirty"] = true
} else {
b.origHash = md5.Sum([]byte(b.String()))
}
}
b.cursors = []*Cursor{&b.Cursor}
return b
}
// GetName returns the name that should be displayed in the statusline
// for this buffer
func (b *Buffer) GetName() string {
if b.name == "" {
if b.Path == "" {
@@ -239,7 +204,7 @@ func (b *Buffer) UpdateRules() {
}
ft := b.Settings["filetype"].(string)
if (ft == "Unknown" || ft == "") && !rehighlight {
if ft == "Unknown" || ft == "" {
if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
header := new(highlight.Header)
header.FileType = file.FileType
@@ -252,7 +217,7 @@ func (b *Buffer) UpdateRules() {
rehighlight = true
}
} else {
if file.FileType == ft && !rehighlight {
if file.FileType == ft {
header := new(highlight.Header)
header.FileType = file.FileType
header.FtDetect = ftdetect
@@ -271,6 +236,7 @@ func (b *Buffer) UpdateRules() {
if b.syntaxDef != nil {
highlight.ResolveIncludes(b.syntaxDef, files)
}
files = nil
if b.highlighter == nil || rehighlight {
if b.syntaxDef != nil {
@@ -339,41 +305,6 @@ func (b *Buffer) Update() {
b.NumLines = len(b.lines)
}
// MergeCursors merges any cursors that are at the same position
// into one cursor
func (b *Buffer) MergeCursors() {
var cursors []*Cursor
for i := 0; i < len(b.cursors); i++ {
c1 := b.cursors[i]
if c1 != nil {
for j := 0; j < len(b.cursors); j++ {
c2 := b.cursors[j]
if c2 != nil && i != j && c1.Loc == c2.Loc {
b.cursors[j] = nil
}
}
cursors = append(cursors, c1)
}
}
b.cursors = cursors
for i := range b.cursors {
b.cursors[i].Num = i
}
if b.curCursor >= len(b.cursors) {
b.curCursor = len(b.cursors) - 1
}
}
// UpdateCursors updates all the cursors indicies
func (b *Buffer) UpdateCursors() {
for i, c := range b.cursors {
c.Num = i
}
}
// Save saves the buffer to its default path
func (b *Buffer) Save() error {
return b.SaveAs(b.Path)
@@ -397,7 +328,7 @@ func (b *Buffer) Serialize() error {
b.ModTime,
})
}
err = file.Close()
file.Close()
return err
}
return nil
@@ -406,6 +337,7 @@ func (b *Buffer) Serialize() error {
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
func (b *Buffer) SaveAs(filename string) error {
b.UpdateRules()
dir, _ := homedir.Dir()
if b.Settings["rmtrailingws"].(bool) {
r, _ := regexp.Compile(`[ \t]+$`)
for lineNum, line := range b.Lines(0, b.NumLines) {
@@ -424,60 +356,17 @@ func (b *Buffer) SaveAs(filename string) error {
b.Insert(end, "\n")
}
}
defer func() {
str := b.String()
data := []byte(str)
err := ioutil.WriteFile(filename, data, 0644)
if err == nil {
b.Path = strings.Replace(filename, "~", dir, 1)
b.IsModified = false
b.ModTime, _ = GetModTime(filename)
}()
// Removes any tilde and replaces with the absolute path to home
var absFilename string = ReplaceHome(filename)
// Get the leading path to the file | "." is returned if there's no leading path provided
if dirname := filepath.Dir(absFilename); dirname != "." {
// Check if the parent dirs don't exist
if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
// Prompt to make sure they want to create the dirs that are missing
if yes, canceled := messenger.YesNoPrompt("Parent folders \"" + dirname + "\" do not exist. Create them? (y,n)"); yes && !canceled {
// Create all leading dir(s) since they don't exist
if mkdirallErr := os.MkdirAll(dirname, os.ModePerm); mkdirallErr != nil {
// If there was an error creating the dirs
return mkdirallErr
}
} else {
// If they canceled the creation of leading dirs
return errors.New("Save aborted")
}
}
return b.Serialize()
}
f, err := os.OpenFile(absFilename, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
if err := f.Truncate(0); err != nil {
return err
}
useCrlf := b.Settings["fileformat"] == "dos"
for i, l := range b.lines {
if _, err := f.Write(l.data); err != nil {
return err
}
if i != len(b.lines)-1 {
if useCrlf {
if _, err := f.Write([]byte{'\r', '\n'}); err != nil {
return err
}
} else {
if _, err := f.Write([]byte{'\n'}); err != nil {
return err
}
}
}
}
b.Path = filename
b.IsModified = false
return b.Serialize()
b.ModTime, _ = GetModTime(filename)
return err
}
// SaveAsWithSudo is the same as SaveAs except it uses a neat trick
@@ -486,13 +375,21 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
b.UpdateRules()
b.Path = filename
// Shut down the screen because we're going to interact directly with the shell
screen.Fini()
screen = nil
// The user may have already used sudo in which case we won't need the password
// It's a bit nicer for them if they don't have to enter the password every time
_, err := RunShellCommand("sudo -v")
needPassword := err != nil
// If we need the password, we have to close the screen and ask using the shell
if needPassword {
// Shut down the screen because we're going to interact directly with the shell
screen.Fini()
screen = nil
}
// Set up everything for the command
cmd := exec.Command(globalSettings["sucmd"].(string), "tee", filename)
cmd.Stdin = bytes.NewBufferString(b.SaveString(b.Settings["fileformat"] == "dos"))
cmd := exec.Command("sudo", "tee", filename)
cmd.Stdin = bytes.NewBufferString(b.String())
// This is a trap for Ctrl-C so that it doesn't kill micro
// Instead we trap Ctrl-C to kill the program we're running
@@ -506,10 +403,13 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
// Start the command
cmd.Start()
err := cmd.Wait()
err = cmd.Wait()
// Start the screen back up
InitScreen()
// If we needed the password, we closed the screen, so we have to initialize it again
if needPassword {
// Start the screen back up
InitScreen()
}
if err == nil {
b.IsModified = false
b.ModTime, _ = GetModTime(filename)
@@ -518,15 +418,6 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
return err
}
// Modified returns if this buffer has been modified since
// being opened
func (b *Buffer) Modified() bool {
if b.Settings["fastdirty"].(bool) {
return b.IsModified
}
return b.origHash != md5.Sum([]byte(b.String()))
}
func (b *Buffer) insert(pos Loc, value []byte) {
b.IsModified = true
b.LineArray.insert(pos, value)
@@ -571,7 +462,6 @@ func (b *Buffer) Line(n int) string {
return string(b.lines[n].data)
}
// LinesNum returns the number of lines in the buffer
func (b *Buffer) LinesNum() int {
return len(b.lines)
}
@@ -643,67 +533,3 @@ func (b *Buffer) ClearMatches() {
b.SetState(i, nil)
}
}
func (b *Buffer) clearCursors() {
for i := 1; i < len(b.cursors); i++ {
b.cursors[i] = nil
}
b.cursors = b.cursors[:1]
b.UpdateCursors()
b.Cursor.ResetSelection()
}
var bracePairs = [][2]rune{
[2]rune{'(', ')'},
[2]rune{'{', '}'},
[2]rune{'[', ']'},
}
// FindMatchingBrace returns the location in the buffer of the matching bracket
// It is given a brace type containing the open and closing character, (for example
// '{' and '}') as well as the location to match from
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) Loc {
curLine := []rune(string(b.lines[start.Y].data))
startChar := curLine[start.X]
var i int
if startChar == braceType[0] {
for y := start.Y; y < b.NumLines; y++ {
l := []rune(string(b.lines[y].data))
xInit := 0
if y == start.Y {
xInit = start.X
}
for x := xInit; x < len(l); x++ {
r := l[x]
if r == braceType[0] {
i++
} else if r == braceType[1] {
i--
if i == 0 {
return Loc{x, y}
}
}
}
}
} else if startChar == braceType[1] {
for y := start.Y; y >= 0; y-- {
l := []rune(string(b.lines[y].data))
xInit := len(l) - 1
if y == start.Y {
xInit = start.X
}
for x := xInit; x >= 0; x-- {
r := l[x]
if r == braceType[0] {
i--
if i == 0 {
return Loc{x, y}
}
} else if r == braceType[1] {
i++
}
}
}
}
return start
}

View File

@@ -65,21 +65,6 @@ type CellView struct {
}
func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
if width <= 0 {
return
}
matchingBrace := Loc{-1, -1}
// bracePairs is defined in buffer.go
if buf.Settings["matchbrace"].(bool) {
for _, bp := range bracePairs {
r := buf.Cursor.RuneUnder(buf.Cursor.X)
if r == bp[0] || r == bp[1] {
matchingBrace = buf.FindMatchingBrace(bp, buf.Cursor.Loc)
}
}
}
tabsize := int(buf.Settings["tabsize"].(float64))
softwrap := buf.Settings["softwrap"].(bool)
indentrunes := []rune(buf.Settings["indentchar"].(string))
@@ -148,13 +133,7 @@ func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
char := line[colN]
if viewCol >= 0 {
st := curStyle
if colN == matchingBrace.X && lineN == matchingBrace.Y && !buf.Cursor.HasSelection() {
st = curStyle.Reverse(true)
}
if viewCol < len(c.lines[viewLine]) {
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, char, st, 1}
}
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, char, curStyle, 1}
}
if char == '\t' {
charWidth := tabsize - (viewCol+left)%tabsize
@@ -163,8 +142,7 @@ func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
c.lines[viewLine][viewCol].width = charWidth
indentStyle := curStyle
ch := buf.Settings["indentchar"].(string)
if group, ok := colorscheme["indent-char"]; ok && !IsStrWhitespace(ch) && ch != "" {
if group, ok := colorscheme["indent-char"]; ok {
indentStyle = group
}
@@ -173,7 +151,7 @@ func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
for i := 1; i < charWidth; i++ {
viewCol++
if viewCol >= 0 && viewCol < lineLength && viewCol < len(c.lines[viewLine]) {
if viewCol >= 0 && viewCol < lineLength {
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
}
}
@@ -185,7 +163,7 @@ func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
}
for i := 1; i < charWidth; i++ {
viewCol++
if viewCol >= 0 && viewCol < lineLength && viewCol < len(c.lines[viewLine]) {
if viewCol >= 0 && viewCol < lineLength {
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
}
}

View File

@@ -15,7 +15,7 @@ type Colorscheme map[string]tcell.Style
// The current colorscheme
var colorscheme Colorscheme
// GetColor takes in a syntax group and returns the colorscheme's style for that group
// This takes in a syntax group and returns the colorscheme's style for that group
func GetColor(color string) tcell.Style {
st := defStyle
if color == "" {
@@ -54,7 +54,7 @@ func InitColorscheme() {
Foreground(tcell.ColorDefault).
Background(tcell.ColorDefault)
if screen != nil {
// screen.SetStyle(defStyle)
screen.SetStyle(defStyle)
}
LoadDefaultColorscheme()
@@ -109,7 +109,7 @@ func ParseColorscheme(text string) Colorscheme {
defStyle = style
}
if screen != nil {
// screen.SetStyle(defStyle)
screen.SetStyle(defStyle)
}
} else {
fmt.Println("Color-link statement is not valid:", line)
@@ -252,9 +252,5 @@ func GetColor256(color int) tcell.Color {
tcell.Color253, tcell.Color254, tcell.Color255,
}
if color >= 0 && color < len(colors) {
return colors[color]
}
return tcell.ColorDefault
return colors[color]
}

View File

@@ -1,8 +1,12 @@
package main
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"runtime"
@@ -10,7 +14,7 @@ import (
"strings"
humanize "github.com/dustin/go-humanize"
"github.com/zyedidia/micro/cmd/micro/shellwords"
"github.com/mitchellh/go-homedir"
)
// A Command contains a action (a function to call) as well as information about how to autocomplete the command
@@ -31,32 +35,27 @@ var commandActions map[string]func([]string)
func init() {
commandActions = map[string]func([]string){
"Set": Set,
"SetLocal": SetLocal,
"Show": Show,
"ShowKey": ShowKey,
"Run": Run,
"Bind": Bind,
"Quit": Quit,
"Save": Save,
"Replace": Replace,
"ReplaceAll": ReplaceAll,
"VSplit": VSplit,
"HSplit": HSplit,
"Tab": NewTab,
"Help": Help,
"Eval": Eval,
"ToggleLog": ToggleLog,
"Plugin": PluginCmd,
"Reload": Reload,
"Cd": Cd,
"Pwd": Pwd,
"Open": Open,
"TabSwitch": TabSwitch,
"Term": Term,
"MemUsage": MemUsage,
"Retab": Retab,
"Raw": Raw,
"Set": Set,
"SetLocal": SetLocal,
"Show": Show,
"Run": Run,
"Bind": Bind,
"Quit": Quit,
"Save": Save,
"Replace": Replace,
"VSplit": VSplit,
"HSplit": HSplit,
"Tab": NewTab,
"Help": Help,
"Eval": Eval,
"ToggleLog": ToggleLog,
"Plugin": PluginCmd,
"Reload": Reload,
"Cd": Cd,
"Pwd": Pwd,
"Open": Open,
"TabSwitch": TabSwitch,
"MemUsage": MemUsage,
}
}
@@ -90,54 +89,27 @@ func MakeCommand(name, function string, completions ...Completion) {
// DefaultCommands returns a map containing micro's default commands
func DefaultCommands() map[string]StrCommand {
return map[string]StrCommand{
"set": {"Set", []Completion{OptionCompletion, OptionValueCompletion}},
"setlocal": {"SetLocal", []Completion{OptionCompletion, OptionValueCompletion}},
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
"showkey": {"ShowKey", []Completion{NoCompletion}},
"bind": {"Bind", []Completion{NoCompletion}},
"run": {"Run", []Completion{NoCompletion}},
"quit": {"Quit", []Completion{NoCompletion}},
"save": {"Save", []Completion{NoCompletion}},
"replace": {"Replace", []Completion{NoCompletion}},
"replaceall": {"ReplaceAll", []Completion{NoCompletion}},
"vsplit": {"VSplit", []Completion{FileCompletion, NoCompletion}},
"hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}},
"tab": {"Tab", []Completion{FileCompletion, NoCompletion}},
"help": {"Help", []Completion{HelpCompletion, NoCompletion}},
"eval": {"Eval", []Completion{NoCompletion}},
"log": {"ToggleLog", []Completion{NoCompletion}},
"plugin": {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
"reload": {"Reload", []Completion{NoCompletion}},
"cd": {"Cd", []Completion{FileCompletion}},
"pwd": {"Pwd", []Completion{NoCompletion}},
"open": {"Open", []Completion{FileCompletion}},
"tabswitch": {"TabSwitch", []Completion{NoCompletion}},
"term": {"Term", []Completion{NoCompletion}},
"memusage": {"MemUsage", []Completion{NoCompletion}},
"retab": {"Retab", []Completion{NoCompletion}},
"raw": {"Raw", []Completion{NoCompletion}},
}
}
// CommandEditAction returns a bindable function that opens a prompt with
// the given string and executes the command when the user presses
// enter
func CommandEditAction(prompt string) func(*View, bool) bool {
return func(v *View, usePlugin bool) bool {
input, canceled := messenger.Prompt("> ", prompt, "Command", CommandCompletion)
if !canceled {
HandleCommand(input)
}
return false
}
}
// CommandAction returns a bindable function which executes the
// given command
func CommandAction(cmd string) func(*View, bool) bool {
return func(v *View, usePlugin bool) bool {
HandleCommand(cmd)
return false
"set": {"Set", []Completion{OptionCompletion, NoCompletion}},
"setlocal": {"SetLocal", []Completion{OptionCompletion, NoCompletion}},
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
"bind": {"Bind", []Completion{NoCompletion}},
"run": {"Run", []Completion{NoCompletion}},
"quit": {"Quit", []Completion{NoCompletion}},
"save": {"Save", []Completion{NoCompletion}},
"replace": {"Replace", []Completion{NoCompletion}},
"vsplit": {"VSplit", []Completion{FileCompletion, NoCompletion}},
"hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}},
"tab": {"Tab", []Completion{FileCompletion, NoCompletion}},
"help": {"Help", []Completion{HelpCompletion, NoCompletion}},
"eval": {"Eval", []Completion{NoCompletion}},
"log": {"ToggleLog", []Completion{NoCompletion}},
"plugin": {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
"reload": {"Reload", []Completion{NoCompletion}},
"cd": {"Cd", []Completion{FileCompletion}},
"pwd": {"Pwd", []Completion{NoCompletion}},
"open": {"Open", []Completion{FileCompletion}},
"tabswitch": {"TabSwitch", []Completion{NoCompletion}},
"memusage": {"MemUsage", []Completion{NoCompletion}},
}
}
@@ -224,34 +196,6 @@ func PluginCmd(args []string) {
}
}
// Retab changes all spaces to tabs or all tabs to spaces
// depending on the user's settings
func Retab(args []string) {
CurView().Retab(true)
}
// Raw opens a new raw view which displays the escape sequences micro
// is receiving in real-time
func Raw(args []string) {
buf := NewBufferFromString("", "Raw events")
view := NewView(buf)
view.Buf.Insert(view.Cursor.Loc, "Warning: Showing raw event escape codes\n")
view.Buf.Insert(view.Cursor.Loc, "Use CtrlQ to exit\n")
view.Type = vtRaw
tab := NewTabFromView(view)
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
curTab = len(tabs) - 1
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.views {
v.ToggleTabbar()
}
}
}
}
// TabSwitch switches to a given tab either by name or by number
func TabSwitch(args []string) {
if len(args) > 0 {
@@ -284,19 +228,12 @@ func TabSwitch(args []string) {
// Cd changes the current working directory
func Cd(args []string) {
if len(args) > 0 {
path := ReplaceHome(args[0])
err := os.Chdir(path)
if err != nil {
messenger.Error("Error with cd: ", err)
return
}
wd, _ := os.Getwd()
home, _ := homedir.Dir()
path := strings.Replace(args[0], "~", home, 1)
os.Chdir(path)
for _, tab := range tabs {
for _, view := range tab.views {
if len(view.Buf.name) == 0 {
continue
}
wd, _ := os.Getwd()
view.Buf.Path, _ = MakeRelative(view.Buf.AbsPath, wd)
if p, _ := filepath.Abs(view.Buf.Path); !strings.Contains(p, wd) {
view.Buf.Path = view.Buf.AbsPath
@@ -335,12 +272,7 @@ func Open(args []string) {
if len(args) > 0 {
filename := args[0]
// the filename might or might not be quoted, so unquote first then join the strings.
args, err := shellwords.Split(filename)
if err != nil {
messenger.Error("Error parsing args ", err)
return
}
filename = strings.Join(args, " ")
filename = strings.Join(SplitCommandArgs(filename), " ")
CurView().Open(filename)
} else {
@@ -391,7 +323,8 @@ func VSplit(args []string) {
CurView().VSplit(NewBufferFromString("", ""))
} else {
filename := args[0]
filename = ReplaceHome(filename)
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
@@ -420,7 +353,8 @@ func HSplit(args []string) {
CurView().HSplit(NewBufferFromString("", ""))
} else {
filename := args[0]
filename = ReplaceHome(filename)
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
@@ -460,7 +394,8 @@ func NewTab(args []string) {
CurView().AddTab(true)
} else {
filename := args[0]
filename = ReplaceHome(filename)
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
@@ -538,20 +473,6 @@ func Show(args []string) {
messenger.Message(option)
}
// ShowKey displays the action that a key is bound to
func ShowKey(args []string) {
if len(args) < 1 {
messenger.Error("Please provide a key to show")
return
}
if action, ok := bindingsStr[args[0]]; ok {
messenger.Message(action)
} else {
messenger.Message(args[0], " has no binding")
}
}
// Bind creates a new keybinding
func Bind(args []string) {
if len(args) < 2 {
@@ -564,7 +485,7 @@ func Bind(args []string) {
// Run runs a shell command in the background
func Run(args []string) {
// Run a shell command in the background (openTerm is false)
HandleShellCommand(shellwords.Join(args...), false, true)
HandleShellCommand(JoinCommandArgs(args...), false, true)
}
// Quit closes the main view
@@ -585,35 +506,19 @@ func Save(args []string) {
// Replace runs search and replace
func Replace(args []string) {
if len(args) < 2 || len(args) > 4 {
if len(args) < 2 {
// We need to find both a search and replace expression
messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
return
}
all := false
noRegex := false
if len(args) > 2 {
for _, arg := range args[2:] {
switch arg {
case "-a":
all = true
case "-l":
noRegex = true
default:
messenger.Error("Invalid flag: " + arg)
return
}
}
var flags string
if len(args) == 3 {
// The user included some flags
flags = args[2]
}
search := string(args[0])
if noRegex {
search = regexp.QuoteMeta(search)
}
replace := string(args[1])
regex, err := regexp.Compile("(?m)" + search)
@@ -626,32 +531,7 @@ func Replace(args []string) {
view := CurView()
found := 0
replaceAll := func() {
var deltas []Delta
deltaXOffset := Count(replace) - Count(search)
for i := 0; i < view.Buf.LinesNum(); i++ {
matches := regex.FindAllIndex(view.Buf.lines[i].data, -1)
str := string(view.Buf.lines[i].data)
if matches != nil {
xOffset := 0
for _, m := range matches {
from := Loc{runePos(m[0], str) + xOffset, i}
to := Loc{runePos(m[1], str) + xOffset, i}
xOffset += deltaXOffset
deltas = append(deltas, Delta{replace, from, to})
found++
}
}
}
view.Buf.MultipleReplace(deltas)
}
if all {
replaceAll()
} else {
if strings.Contains(flags, "c") {
for {
// The 'check' flag was used
Search(search, view, true)
@@ -660,7 +540,7 @@ func Replace(args []string) {
}
view.Relocate()
RedrawAll()
choice, canceled := messenger.LetterPrompt("Perform replacement? (y,n,a)", 'y', 'n', 'a')
choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
if canceled {
if view.Cursor.HasSelection() {
view.Cursor.Loc = view.Cursor.CurSelection[0]
@@ -668,27 +548,42 @@ func Replace(args []string) {
}
messenger.Reset()
break
} else if choice == 'a' {
if view.Cursor.HasSelection() {
view.Cursor.Loc = view.Cursor.CurSelection[0]
view.Cursor.ResetSelection()
}
messenger.Reset()
replaceAll()
break
} else if choice == 'y' {
}
if choice {
view.Cursor.DeleteSelection()
view.Buf.Insert(view.Cursor.Loc, replace)
view.Cursor.ResetSelection()
messenger.Reset()
found++
}
if view.Cursor.HasSelection() {
searchStart = view.Cursor.CurSelection[1]
} else {
searchStart = view.Cursor.Loc
if view.Cursor.HasSelection() {
searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf)
} else {
searchStart = ToCharPos(view.Cursor.Loc, view.Buf)
}
continue
}
}
} else {
// var deltas []Delta
for i := 0; i < view.Buf.LinesNum(); i++ {
// view.Buf.lines[i].data = regex.ReplaceAll(view.Buf.lines[i].data, []byte(replace))
for {
m := regex.FindIndex(view.Buf.lines[i].data)
if m != nil {
from := Loc{m[0], i}
to := Loc{m[1], i}
// deltas = append(deltas, Delta{replace, from, to})
view.Buf.Replace(from, to, replace)
found++
} else {
break
}
}
}
// view.Buf.MultipleReplace(deltas)
}
view.Cursor.Relocate()
@@ -701,33 +596,93 @@ func Replace(args []string) {
}
}
// ReplaceAll replaces search term all at once
func ReplaceAll(args []string) {
// aliased to Replace command
Replace(append(args, "-a"))
// RunShellCommand executes a shell command and returns the output/error
func RunShellCommand(input string) (string, error) {
inputCmd := SplitCommandArgs(input)[0]
args := SplitCommandArgs(input)[1:]
cmd := exec.Command(inputCmd, args...)
outputBytes := &bytes.Buffer{}
cmd.Stdout = outputBytes
cmd.Stderr = outputBytes
cmd.Start()
err := cmd.Wait() // wait for command to finish
outstring := outputBytes.String()
return outstring, err
}
// Term opens a terminal in the current view
func Term(args []string) {
var err error
if len(args) == 0 {
err = CurView().StartTerminal([]string{os.Getenv("SHELL"), "-i"}, true, false, "")
// HandleShellCommand runs the shell command
// The openTerm argument specifies whether a terminal should be opened (for viewing output
// or interacting with stdin)
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
inputCmd := SplitCommandArgs(input)[0]
if !openTerm {
// Simply run the command in the background and notify the user when it's done
messenger.Message("Running...")
go func() {
output, err := RunShellCommand(input)
totalLines := strings.Split(output, "\n")
if len(totalLines) < 3 {
if err == nil {
messenger.Message(inputCmd, " exited without error")
} else {
messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
}
} else {
messenger.Message(output)
}
// We have to make sure to redraw
RedrawAll()
}()
} else {
err = CurView().StartTerminal(args, true, false, "")
}
if err != nil {
messenger.Error(err)
// Shut down the screen because we're going to interact directly with the shell
screen.Fini()
screen = nil
args := SplitCommandArgs(input)[1:]
// Set up everything for the command
var outputBuf bytes.Buffer
cmd := exec.Command(inputCmd, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = io.MultiWriter(os.Stdout, &outputBuf)
cmd.Stderr = os.Stderr
// This is a trap for Ctrl-C so that it doesn't kill micro
// Instead we trap Ctrl-C to kill the program we're running
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for range c {
cmd.Process.Kill()
}
}()
cmd.Start()
err := cmd.Wait()
output := outputBuf.String()
if err != nil {
output = err.Error()
}
if waitToFinish {
// This is just so we don't return right away and let the user press enter to return
TermMessage("")
}
// Start the screen back up
InitScreen()
return output
}
return ""
}
// HandleCommand handles input from the user
func HandleCommand(input string) {
args, err := shellwords.Split(input)
if err != nil {
messenger.Error("Error parsing args ", err)
return
}
args := SplitCommandArgs(input)
inputCmd := args[0]
if _, ok := commands[inputCmd]; !ok {

View File

@@ -1,8 +1,6 @@
package main
import (
"github.com/zyedidia/clipboard"
)
import "github.com/zyedidia/clipboard"
// The Cursor struct stores the location of the cursor in the view
// The complicated part about the cursor is storing its location.
@@ -23,27 +21,15 @@ type Cursor struct {
// This is used for line and word selection where it is necessary
// to know what the original selection was
OrigSelection [2]Loc
// Which cursor index is this (for multiple cursors)
Num int
}
// Goto puts the cursor at the given cursor's location and gives
// the current cursor its selection too
// Goto puts the cursor at the given cursor's location and gives the current cursor its selection too
func (c *Cursor) Goto(b Cursor) {
c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
}
// GotoLoc puts the cursor at the given cursor's location and gives
// the current cursor its selection too
func (c *Cursor) GotoLoc(l Loc) {
c.X, c.Y = l.X, l.Y
c.LastVisualX = c.GetVisualX()
}
// CopySelection copies the user's selection to either "primary"
// or "clipboard"
// CopySelection copies the user's selection to either "primary" or "clipboard"
func (c *Cursor) CopySelection(target string) {
if c.HasSelection() {
if target != "primary" || c.buf.Settings["useprimary"].(bool) {
@@ -160,8 +146,7 @@ func (c *Cursor) SelectWord() {
c.Loc = c.CurSelection[1]
}
// AddWordToSelection adds the word the cursor is currently on
// to the selection
// AddWordToSelection adds the word the cursor is currently on to the selection
func (c *Cursor) AddWordToSelection() {
if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
c.CurSelection = c.OrigSelection
@@ -193,8 +178,7 @@ func (c *Cursor) AddWordToSelection() {
c.Loc = c.CurSelection[1]
}
// SelectTo selects from the current cursor location to the given
// location
// SelectTo selects from the current cursor location to the given location
func (c *Cursor) SelectTo(loc Loc) {
if loc.GreaterThan(c.OrigSelection[0]) {
c.SetSelectionStart(c.OrigSelection[0])
@@ -261,19 +245,19 @@ func (c *Cursor) UpN(amount int) {
proposedY := c.Y - amount
if proposedY < 0 {
proposedY = 0
c.LastVisualX = 0
} else if proposedY >= c.buf.NumLines {
proposedY = c.buf.NumLines - 1
}
runes := []rune(c.buf.Line(c.Y))
c.X = c.GetCharPosInLine(proposedY, c.LastVisualX)
if c.X > len(runes) || (amount < 0 && proposedY == c.Y) {
c.X = len(runes)
if proposedY == c.Y {
return
}
c.Y = proposedY
runes := []rune(c.buf.Line(c.Y))
c.X = c.GetCharPosInLine(c.Y, c.LastVisualX)
if c.X > len(runes) {
c.X = len(runes)
}
}
// DownN moves the cursor down N lines (if possible)
@@ -291,8 +275,7 @@ func (c *Cursor) Down() {
c.DownN(1)
}
// Left moves the cursor left one cell (if possible) or to
// the previous line if it is at the beginning
// Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
func (c *Cursor) Left() {
if c.Loc == c.buf.Start() {
return
@@ -306,8 +289,7 @@ func (c *Cursor) Left() {
c.LastVisualX = c.GetVisualX()
}
// Right moves the cursor right one cell (if possible) or
// to the next line if it is at the end
// Right moves the cursor right one cell (if possible) or to the next line if it is at the end
func (c *Cursor) Right() {
if c.Loc == c.buf.End() {
return
@@ -333,9 +315,7 @@ func (c *Cursor) Start() {
c.LastVisualX = c.GetVisualX()
}
// GetCharPosInLine gets the char position of a visual x y
// coordinate (this is necessary because tabs are 1 char but
// 4 visual spaces)
// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
// Get the tab size
tabSize := int(c.buf.Settings["tabsize"].(float64))
@@ -354,25 +334,11 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
func (c *Cursor) GetVisualX() int {
runes := []rune(c.buf.Line(c.Y))
tabSize := int(c.buf.Settings["tabsize"].(float64))
if c.X > len(runes) {
c.X = len(runes) - 1
}
if c.X < 0 {
c.X = 0
}
return StringWidth(string(runes[:c.X]), tabSize)
}
// StoreVisualX stores the current visual x value in the cursor
func (c *Cursor) StoreVisualX() {
c.LastVisualX = c.GetVisualX()
}
// Relocate makes sure that the cursor is inside the bounds
// of the buffer If it isn't, it moves it to be within the
// buffer's lines
// Relocate makes sure that the cursor is inside the bounds of the buffer
// If it isn't, it moves it to be within the buffer's lines
func (c *Cursor) Relocate() {
if c.Y < 0 {
c.Y = 0

View File

@@ -28,7 +28,6 @@ type TextEvent struct {
Time time.Time
}
// A Delta is a change to the buffer
type Delta struct {
Text string
Start Loc
@@ -49,11 +48,6 @@ func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
for i, d := range t.Deltas {
t.Deltas[i].Text = buf.remove(d.Start, d.End)
buf.insert(d.Start, []byte(d.Text))
t.Deltas[i].Start = d.Start
t.Deltas[i].End = Loc{d.Start.X + Count(d.Text), d.Start.Y}
}
for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
}
}
}
@@ -103,65 +97,30 @@ func (eh *EventHandler) ApplyDiff(new string) {
// Insert creates an insert text event and executes it
func (eh *EventHandler) Insert(start Loc, text string) {
e := &TextEvent{
C: *eh.buf.cursors[eh.buf.curCursor],
C: eh.buf.Cursor,
EventType: TextEventInsert,
Deltas: []Delta{{text, start, Loc{0, 0}}},
Deltas: []Delta{Delta{text, start, Loc{0, 0}}},
Time: time.Now(),
}
eh.Execute(e)
e.Deltas[0].End = start.Move(Count(text), eh.buf)
end := e.Deltas[0].End
for _, c := range eh.buf.cursors {
move := func(loc Loc) Loc {
if start.Y != end.Y && loc.GreaterThan(start) {
loc.Y += end.Y - start.Y
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
loc = loc.Move(Count(text), eh.buf)
}
return loc
}
c.Loc = move(c.Loc)
c.CurSelection[0] = move(c.CurSelection[0])
c.CurSelection[1] = move(c.CurSelection[1])
c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1])
c.LastVisualX = c.GetVisualX()
}
}
// Remove creates a remove text event and executes it
func (eh *EventHandler) Remove(start, end Loc) {
e := &TextEvent{
C: *eh.buf.cursors[eh.buf.curCursor],
C: eh.buf.Cursor,
EventType: TextEventRemove,
Deltas: []Delta{{"", start, end}},
Deltas: []Delta{Delta{"", start, end}},
Time: time.Now(),
}
eh.Execute(e)
for _, c := range eh.buf.cursors {
move := func(loc Loc) Loc {
if start.Y != end.Y && loc.GreaterThan(end) {
loc.Y -= end.Y - start.Y
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
loc = loc.Move(-Diff(start, end, eh.buf), eh.buf)
}
return loc
}
c.Loc = move(c.Loc)
c.CurSelection[0] = move(c.CurSelection[0])
c.CurSelection[1] = move(c.CurSelection[1])
c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1])
c.LastVisualX = c.GetVisualX()
}
}
// MultipleReplace creates an multiple insertions executes them
func (eh *EventHandler) MultipleReplace(deltas []Delta) {
e := &TextEvent{
C: *eh.buf.cursors[eh.buf.curCursor],
C: eh.buf.Cursor,
EventType: TextEventReplace,
Deltas: deltas,
Time: time.Now(),
@@ -236,12 +195,8 @@ func (eh *EventHandler) UndoOneEvent() {
// Set the cursor in the right place
teCursor := t.C
if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
t.C = *eh.buf.cursors[teCursor.Num]
eh.buf.cursors[teCursor.Num].Goto(teCursor)
} else {
teCursor.Num = -1
}
t.C = eh.buf.Cursor
eh.buf.Cursor.Goto(teCursor)
// Push it to the redo stack
eh.RedoStack.Push(t)
@@ -283,12 +238,8 @@ func (eh *EventHandler) RedoOneEvent() {
UndoTextEvent(t, eh.buf)
teCursor := t.C
if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
t.C = *eh.buf.cursors[teCursor.Num]
eh.buf.cursors[teCursor.Num].Goto(teCursor)
} else {
teCursor.Num = -1
}
t.C = eh.buf.Cursor
eh.buf.Cursor.Goto(teCursor)
eh.UndoStack.Push(t)
}

View File

@@ -2,7 +2,7 @@ package highlight
import "regexp"
// MatchFiletype will use the list of syntax definitions provided and the filename and first line of the file
// DetectFiletype will use the list of syntax definitions provided and the filename and first line of the file
// to determine the filetype of the file
// It will return the corresponding syntax definition for the filetype
func MatchFiletype(ftdetect [2]*regexp.Regexp, filename string, firstLine []byte) bool {

View File

@@ -1,7 +1,6 @@
package highlight
import (
"errors"
"fmt"
"regexp"
@@ -83,16 +82,6 @@ func init() {
}
func ParseFtDetect(file *File) (r [2]*regexp.Regexp, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
rules := file.yamlSrc
loaded := 0
@@ -123,22 +112,14 @@ func ParseFtDetect(file *File) (r [2]*regexp.Regexp, err error) {
}
}
if loaded == 0 {
return r, errors.New("No detect regexes found")
}
return r, err
}
func ParseFile(input []byte) (f *File, err error) {
// This is just so if we have an error, we can exit cleanly and return the parse error to the user
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
if e := recover(); e != nil {
err = e.(error)
}
}()
@@ -166,12 +147,8 @@ func ParseFile(input []byte) (f *File, err error) {
func ParseDef(f *File, header *Header) (s *Def, err error) {
// This is just so if we have an error, we can exit cleanly and return the parse error to the user
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
if e := recover(); e != nil {
err = e.(error)
}
}()
@@ -234,17 +211,8 @@ func resolveIncludesInRegion(files []*File, region *region) {
}
}
func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
ru = new(rules)
func parseRules(input []interface{}, curRegion *region) (*rules, error) {
rules := new(rules)
for _, v := range input {
rule := v.(map[interface{}]interface{})
@@ -254,7 +222,7 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
switch object := val.(type) {
case string:
if k == "include" {
ru.includes = append(ru.includes, object)
rules.includes = append(rules.includes, object)
} else {
// Pattern
r, err := regexp.Compile(object)
@@ -268,7 +236,7 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
Groups[groupStr] = numGroups
}
groupNum := Groups[groupStr]
ru.patterns = append(ru.patterns, &pattern{groupNum, r})
rules.patterns = append(rules.patterns, &pattern{groupNum, r})
}
case map[interface{}]interface{}:
// region
@@ -276,43 +244,35 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
if err != nil {
return nil, err
}
ru.regions = append(ru.regions, region)
rules.regions = append(rules.regions, region)
default:
return nil, fmt.Errorf("Bad type %T", object)
}
}
}
return ru, nil
return rules, nil
}
func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegion *region) (r *region, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegion *region) (*region, error) {
var err error
r = new(region)
region := new(region)
if _, ok := Groups[group]; !ok {
numGroups++
Groups[group] = numGroups
}
groupNum := Groups[group]
r.group = groupNum
r.parent = prevRegion
region.group = groupNum
region.parent = prevRegion
r.start, err = regexp.Compile(regionInfo["start"].(string))
region.start, err = regexp.Compile(regionInfo["start"].(string))
if err != nil {
return nil, err
}
r.end, err = regexp.Compile(regionInfo["end"].(string))
region.end, err = regexp.Compile(regionInfo["end"].(string))
if err != nil {
return nil, err
@@ -320,7 +280,7 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
// skip is optional
if _, ok := regionInfo["skip"]; ok {
r.skip, err = regexp.Compile(regionInfo["skip"].(string))
region.skip, err = regexp.Compile(regionInfo["skip"].(string))
if err != nil {
return nil, err
@@ -335,20 +295,20 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
Groups[groupStr] = numGroups
}
groupNum := Groups[groupStr]
r.limitGroup = groupNum
region.limitGroup = groupNum
if err != nil {
return nil, err
}
} else {
r.limitGroup = r.group
region.limitGroup = region.group
}
r.rules, err = parseRules(regionInfo["rules"].([]interface{}), r)
region.rules, err = parseRules(regionInfo["rules"].([]interface{}), region)
if err != nil {
return nil, err
}
return r, nil
return region, nil
}

View File

@@ -1,20 +0,0 @@
package main
// DisplayKeyMenu displays the nano-style key menu at the bottom of the screen
func DisplayKeyMenu() {
w, h := screen.Size()
bot := h - 3
display := []string{"^Q Quit, ^S Save, ^O Open, ^G Help, ^E Command Bar, ^K Cut Line", "^F Find, ^Z Undo, ^Y Redo, ^A Select All, ^D Duplicate Line, ^T New Tab"}
for y := 0; y < len(display); y++ {
for x := 0; x < w; x++ {
if x < len(display[y]) {
screen.SetContent(x, bot+y, rune(display[y][x]), nil, defStyle)
} else {
screen.SetContent(x, bot+y, ' ', nil, defStyle)
}
}
}
}

View File

@@ -29,8 +29,6 @@ func runeToByteIndex(n int, txt []byte) int {
return count
}
// A Line contains the data in bytes as well as a highlight state, match
// and a flag for whether the highlighting needs to be updated
type Line struct {
data []byte
@@ -45,12 +43,10 @@ type LineArray struct {
lines []Line
}
// Append efficiently appends lines together
// It allocates an additional 10000 lines if the original estimate
// is incorrect
func Append(slice []Line, data ...Line) []Line {
l := len(slice)
if l+len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]Line, (l+len(data))+10000)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
@@ -75,16 +71,6 @@ func NewLineArray(size int64, reader io.Reader) *LineArray {
n := 0
for {
data, err := br.ReadBytes('\n')
if len(data) > 1 && data[len(data)-2] == '\r' {
data = append(data[:len(data)-2], '\n')
if fileformat == 0 {
fileformat = 2
}
} else if len(data) > 0 {
if fileformat == 0 {
fileformat = 1
}
}
if n >= 1000 && loaded >= 0 {
totalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))
@@ -101,7 +87,7 @@ func NewLineArray(size int64, reader io.Reader) *LineArray {
if err != nil {
if err == io.EOF {
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
la.lines = Append(la.lines, Line{data[:len(data)], nil, nil, false})
// la.lines = Append(la.lines, Line{data[:len(data)]})
}
// Last line was read
@@ -128,23 +114,6 @@ func (la *LineArray) String() string {
return str
}
// SaveString returns the string that should be written to disk when
// the line array is saved
// It is the same as string but uses crlf or lf line endings depending
func (la *LineArray) SaveString(useCrlf bool) string {
str := ""
for i, l := range la.lines {
str += string(l.data)
if i != len(la.lines)-1 {
if useCrlf {
str += "\r"
}
str += "\n"
}
}
return str
}
// NewlineBelow adds a newline below the given line number
func (la *LineArray) NewlineBelow(y int) {
la.lines = append(la.lines, Line{[]byte(" "), nil, nil, false})
@@ -247,22 +216,18 @@ func (la *LineArray) Substr(start, end Loc) string {
return str
}
// State gets the highlight state for the given line number
func (la *LineArray) State(lineN int) highlight.State {
return la.lines[lineN].state
}
// SetState sets the highlight state at the given line number
func (la *LineArray) SetState(lineN int, s highlight.State) {
la.lines[lineN].state = s
}
// SetMatch sets the match at the given line number
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
la.lines[lineN].match = m
}
// Match retrieves the match for the given line number
func (la *LineArray) Match(lineN int) highlight.LineMatch {
return la.lines[lineN].match
}

View File

@@ -54,29 +54,6 @@ type Loc struct {
X, Y int
}
// Diff returns the distance between two locations
func Diff(a, b Loc, buf *Buffer) int {
if a.Y == b.Y {
if a.X > b.X {
return a.X - b.X
}
return b.X - a.X
}
// Make sure a is guaranteed to be less than b
if b.LessThan(a) {
a, b = b, a
}
loc := 0
for i := a.Y + 1; i < b.Y; i++ {
// + 1 for the newline
loc += Count(buf.Line(i)) + 1
}
loc += Count(buf.Line(a.Y)) - a.X + b.X + 1
return loc
}
// LessThan returns true if b is smaller
func (l Loc) LessThan(b Loc) bool {
if l.Y < b.Y {

View File

@@ -1,539 +0,0 @@
package main
import (
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"math/rand"
"net"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
luar "layeh.com/gopher-luar"
lua "github.com/yuin/gopher-lua"
)
var L *lua.LState
func init() {
L = lua.NewState()
L.SetGlobal("import", luar.New(L, Import))
}
// LoadFile loads a lua file
func LoadFile(module string, file string, data string) error {
pluginDef := "local P = {};" + module + " = P;setmetatable(" + module + ", {__index = _G});setfenv(1, P);"
if fn, err := L.Load(strings.NewReader(pluginDef+data), file); err != nil {
return err
} else {
L.Push(fn)
return L.PCall(0, lua.MultRet, nil)
}
}
// Import allows a lua plugin to import a package
func Import(pkg string) *lua.LTable {
switch pkg {
case "fmt":
return importFmt()
case "io":
return importIo()
case "io/ioutil", "ioutil":
return importIoUtil()
case "net":
return importNet()
case "math":
return importMath()
case "math/rand":
return importMathRand()
case "os":
return importOs()
case "runtime":
return importRuntime()
case "path":
return importPath()
case "filepath":
return importFilePath()
case "strings":
return importStrings()
case "regexp":
return importRegexp()
case "errors":
return importErrors()
case "time":
return importTime()
default:
return nil
}
}
func importFmt() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "tErrorf", luar.New(L, fmt.Errorf))
L.SetField(pkg, "Fprint", luar.New(L, fmt.Fprint))
L.SetField(pkg, "Fprintf", luar.New(L, fmt.Fprintf))
L.SetField(pkg, "Fprintln", luar.New(L, fmt.Fprintln))
L.SetField(pkg, "Fscan", luar.New(L, fmt.Fscan))
L.SetField(pkg, "Fscanf", luar.New(L, fmt.Fscanf))
L.SetField(pkg, "Fscanln", luar.New(L, fmt.Fscanln))
L.SetField(pkg, "Print", luar.New(L, fmt.Print))
L.SetField(pkg, "Printf", luar.New(L, fmt.Printf))
L.SetField(pkg, "Println", luar.New(L, fmt.Println))
L.SetField(pkg, "Scan", luar.New(L, fmt.Scan))
L.SetField(pkg, "Scanf", luar.New(L, fmt.Scanf))
L.SetField(pkg, "Scanln", luar.New(L, fmt.Scanln))
L.SetField(pkg, "Sprint", luar.New(L, fmt.Sprint))
L.SetField(pkg, "Sprintf", luar.New(L, fmt.Sprintf))
L.SetField(pkg, "Sprintln", luar.New(L, fmt.Sprintln))
L.SetField(pkg, "Sscan", luar.New(L, fmt.Sscan))
L.SetField(pkg, "Sscanf", luar.New(L, fmt.Sscanf))
L.SetField(pkg, "Sscanln", luar.New(L, fmt.Sscanln))
return pkg
}
func importIo() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Copy", luar.New(L, io.Copy))
L.SetField(pkg, "CopyN", luar.New(L, io.CopyN))
L.SetField(pkg, "EOF", luar.New(L, io.EOF))
L.SetField(pkg, "ErrClosedPipe", luar.New(L, io.ErrClosedPipe))
L.SetField(pkg, "ErrNoProgress", luar.New(L, io.ErrNoProgress))
L.SetField(pkg, "ErrShortBuffer", luar.New(L, io.ErrShortBuffer))
L.SetField(pkg, "ErrShortWrite", luar.New(L, io.ErrShortWrite))
L.SetField(pkg, "ErrUnexpectedEOF", luar.New(L, io.ErrUnexpectedEOF))
L.SetField(pkg, "LimitReader", luar.New(L, io.LimitReader))
L.SetField(pkg, "MultiReader", luar.New(L, io.MultiReader))
L.SetField(pkg, "MultiWriter", luar.New(L, io.MultiWriter))
L.SetField(pkg, "NewSectionReader", luar.New(L, io.NewSectionReader))
L.SetField(pkg, "Pipe", luar.New(L, io.Pipe))
L.SetField(pkg, "ReadAtLeast", luar.New(L, io.ReadAtLeast))
L.SetField(pkg, "ReadFull", luar.New(L, io.ReadFull))
L.SetField(pkg, "TeeReader", luar.New(L, io.TeeReader))
L.SetField(pkg, "WriteString", luar.New(L, io.WriteString))
return pkg
}
func importIoUtil() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "ReadAll", luar.New(L, ioutil.ReadAll))
L.SetField(pkg, "ReadDir", luar.New(L, ioutil.ReadDir))
L.SetField(pkg, "ReadFile", luar.New(L, ioutil.ReadFile))
L.SetField(pkg, "WriteFile", luar.New(L, ioutil.WriteFile))
return pkg
}
func importNet() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "CIDRMask", luar.New(L, net.CIDRMask))
L.SetField(pkg, "Dial", luar.New(L, net.Dial))
L.SetField(pkg, "DialIP", luar.New(L, net.DialIP))
L.SetField(pkg, "DialTCP", luar.New(L, net.DialTCP))
L.SetField(pkg, "DialTimeout", luar.New(L, net.DialTimeout))
L.SetField(pkg, "DialUDP", luar.New(L, net.DialUDP))
L.SetField(pkg, "DialUnix", luar.New(L, net.DialUnix))
L.SetField(pkg, "ErrWriteToConnected", luar.New(L, net.ErrWriteToConnected))
L.SetField(pkg, "FileConn", luar.New(L, net.FileConn))
L.SetField(pkg, "FileListener", luar.New(L, net.FileListener))
L.SetField(pkg, "FilePacketConn", luar.New(L, net.FilePacketConn))
L.SetField(pkg, "FlagBroadcast", luar.New(L, net.FlagBroadcast))
L.SetField(pkg, "FlagLoopback", luar.New(L, net.FlagLoopback))
L.SetField(pkg, "FlagMulticast", luar.New(L, net.FlagMulticast))
L.SetField(pkg, "FlagPointToPoint", luar.New(L, net.FlagPointToPoint))
L.SetField(pkg, "FlagUp", luar.New(L, net.FlagUp))
L.SetField(pkg, "IPv4", luar.New(L, net.IPv4))
L.SetField(pkg, "IPv4Mask", luar.New(L, net.IPv4Mask))
L.SetField(pkg, "IPv4allrouter", luar.New(L, net.IPv4allrouter))
L.SetField(pkg, "IPv4allsys", luar.New(L, net.IPv4allsys))
L.SetField(pkg, "IPv4bcast", luar.New(L, net.IPv4bcast))
L.SetField(pkg, "IPv4len", luar.New(L, net.IPv4len))
L.SetField(pkg, "IPv4zero", luar.New(L, net.IPv4zero))
L.SetField(pkg, "IPv6interfacelocalallnodes", luar.New(L, net.IPv6interfacelocalallnodes))
L.SetField(pkg, "IPv6len", luar.New(L, net.IPv6len))
L.SetField(pkg, "IPv6linklocalallnodes", luar.New(L, net.IPv6linklocalallnodes))
L.SetField(pkg, "IPv6linklocalallrouters", luar.New(L, net.IPv6linklocalallrouters))
L.SetField(pkg, "IPv6loopback", luar.New(L, net.IPv6loopback))
L.SetField(pkg, "IPv6unspecified", luar.New(L, net.IPv6unspecified))
L.SetField(pkg, "IPv6zero", luar.New(L, net.IPv6zero))
L.SetField(pkg, "InterfaceAddrs", luar.New(L, net.InterfaceAddrs))
L.SetField(pkg, "InterfaceByIndex", luar.New(L, net.InterfaceByIndex))
L.SetField(pkg, "InterfaceByName", luar.New(L, net.InterfaceByName))
L.SetField(pkg, "Interfaces", luar.New(L, net.Interfaces))
L.SetField(pkg, "JoinHostPort", luar.New(L, net.JoinHostPort))
L.SetField(pkg, "Listen", luar.New(L, net.Listen))
L.SetField(pkg, "ListenIP", luar.New(L, net.ListenIP))
L.SetField(pkg, "ListenMulticastUDP", luar.New(L, net.ListenMulticastUDP))
L.SetField(pkg, "ListenPacket", luar.New(L, net.ListenPacket))
L.SetField(pkg, "ListenTCP", luar.New(L, net.ListenTCP))
L.SetField(pkg, "ListenUDP", luar.New(L, net.ListenUDP))
L.SetField(pkg, "ListenUnix", luar.New(L, net.ListenUnix))
L.SetField(pkg, "ListenUnixgram", luar.New(L, net.ListenUnixgram))
L.SetField(pkg, "LookupAddr", luar.New(L, net.LookupAddr))
L.SetField(pkg, "LookupCNAME", luar.New(L, net.LookupCNAME))
L.SetField(pkg, "LookupHost", luar.New(L, net.LookupHost))
L.SetField(pkg, "LookupIP", luar.New(L, net.LookupIP))
L.SetField(pkg, "LookupMX", luar.New(L, net.LookupMX))
L.SetField(pkg, "LookupNS", luar.New(L, net.LookupNS))
L.SetField(pkg, "LookupPort", luar.New(L, net.LookupPort))
L.SetField(pkg, "LookupSRV", luar.New(L, net.LookupSRV))
L.SetField(pkg, "LookupTXT", luar.New(L, net.LookupTXT))
L.SetField(pkg, "ParseCIDR", luar.New(L, net.ParseCIDR))
L.SetField(pkg, "ParseIP", luar.New(L, net.ParseIP))
L.SetField(pkg, "ParseMAC", luar.New(L, net.ParseMAC))
L.SetField(pkg, "Pipe", luar.New(L, net.Pipe))
L.SetField(pkg, "ResolveIPAddr", luar.New(L, net.ResolveIPAddr))
L.SetField(pkg, "ResolveTCPAddr", luar.New(L, net.ResolveTCPAddr))
L.SetField(pkg, "ResolveUDPAddr", luar.New(L, net.ResolveUDPAddr))
L.SetField(pkg, "ResolveUnixAddr", luar.New(L, net.ResolveUnixAddr))
L.SetField(pkg, "SplitHostPort", luar.New(L, net.SplitHostPort))
return pkg
}
func importMath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Abs", luar.New(L, math.Abs))
L.SetField(pkg, "Acos", luar.New(L, math.Acos))
L.SetField(pkg, "Acosh", luar.New(L, math.Acosh))
L.SetField(pkg, "Asin", luar.New(L, math.Asin))
L.SetField(pkg, "Asinh", luar.New(L, math.Asinh))
L.SetField(pkg, "Atan", luar.New(L, math.Atan))
L.SetField(pkg, "Atan2", luar.New(L, math.Atan2))
L.SetField(pkg, "Atanh", luar.New(L, math.Atanh))
L.SetField(pkg, "Cbrt", luar.New(L, math.Cbrt))
L.SetField(pkg, "Ceil", luar.New(L, math.Ceil))
L.SetField(pkg, "Copysign", luar.New(L, math.Copysign))
L.SetField(pkg, "Cos", luar.New(L, math.Cos))
L.SetField(pkg, "Cosh", luar.New(L, math.Cosh))
L.SetField(pkg, "Dim", luar.New(L, math.Dim))
L.SetField(pkg, "Erf", luar.New(L, math.Erf))
L.SetField(pkg, "Erfc", luar.New(L, math.Erfc))
L.SetField(pkg, "Exp", luar.New(L, math.Exp))
L.SetField(pkg, "Exp2", luar.New(L, math.Exp2))
L.SetField(pkg, "Expm1", luar.New(L, math.Expm1))
L.SetField(pkg, "Float32bits", luar.New(L, math.Float32bits))
L.SetField(pkg, "Float32frombits", luar.New(L, math.Float32frombits))
L.SetField(pkg, "Float64bits", luar.New(L, math.Float64bits))
L.SetField(pkg, "Float64frombits", luar.New(L, math.Float64frombits))
L.SetField(pkg, "Floor", luar.New(L, math.Floor))
L.SetField(pkg, "Frexp", luar.New(L, math.Frexp))
L.SetField(pkg, "Gamma", luar.New(L, math.Gamma))
L.SetField(pkg, "Hypot", luar.New(L, math.Hypot))
L.SetField(pkg, "Ilogb", luar.New(L, math.Ilogb))
L.SetField(pkg, "Inf", luar.New(L, math.Inf))
L.SetField(pkg, "IsInf", luar.New(L, math.IsInf))
L.SetField(pkg, "IsNaN", luar.New(L, math.IsNaN))
L.SetField(pkg, "J0", luar.New(L, math.J0))
L.SetField(pkg, "J1", luar.New(L, math.J1))
L.SetField(pkg, "Jn", luar.New(L, math.Jn))
L.SetField(pkg, "Ldexp", luar.New(L, math.Ldexp))
L.SetField(pkg, "Lgamma", luar.New(L, math.Lgamma))
L.SetField(pkg, "Log", luar.New(L, math.Log))
L.SetField(pkg, "Log10", luar.New(L, math.Log10))
L.SetField(pkg, "Log1p", luar.New(L, math.Log1p))
L.SetField(pkg, "Log2", luar.New(L, math.Log2))
L.SetField(pkg, "Logb", luar.New(L, math.Logb))
L.SetField(pkg, "Max", luar.New(L, math.Max))
L.SetField(pkg, "Min", luar.New(L, math.Min))
L.SetField(pkg, "Mod", luar.New(L, math.Mod))
L.SetField(pkg, "Modf", luar.New(L, math.Modf))
L.SetField(pkg, "NaN", luar.New(L, math.NaN))
L.SetField(pkg, "Nextafter", luar.New(L, math.Nextafter))
L.SetField(pkg, "Pow", luar.New(L, math.Pow))
L.SetField(pkg, "Pow10", luar.New(L, math.Pow10))
L.SetField(pkg, "Remainder", luar.New(L, math.Remainder))
L.SetField(pkg, "Signbit", luar.New(L, math.Signbit))
L.SetField(pkg, "Sin", luar.New(L, math.Sin))
L.SetField(pkg, "Sincos", luar.New(L, math.Sincos))
L.SetField(pkg, "Sinh", luar.New(L, math.Sinh))
L.SetField(pkg, "Sqrt", luar.New(L, math.Sqrt))
L.SetField(pkg, "Tan", luar.New(L, math.Tan))
L.SetField(pkg, "Tanh", luar.New(L, math.Tanh))
L.SetField(pkg, "Trunc", luar.New(L, math.Trunc))
L.SetField(pkg, "Y0", luar.New(L, math.Y0))
L.SetField(pkg, "Y1", luar.New(L, math.Y1))
L.SetField(pkg, "Yn", luar.New(L, math.Yn))
return pkg
}
func importMathRand() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "ExpFloat64", luar.New(L, rand.ExpFloat64))
L.SetField(pkg, "Float32", luar.New(L, rand.Float32))
L.SetField(pkg, "Float64", luar.New(L, rand.Float64))
L.SetField(pkg, "Int", luar.New(L, rand.Int))
L.SetField(pkg, "Int31", luar.New(L, rand.Int31))
L.SetField(pkg, "Int31n", luar.New(L, rand.Int31n))
L.SetField(pkg, "Int63", luar.New(L, rand.Int63))
L.SetField(pkg, "Int63n", luar.New(L, rand.Int63n))
L.SetField(pkg, "Intn", luar.New(L, rand.Intn))
L.SetField(pkg, "NormFloat64", luar.New(L, rand.NormFloat64))
L.SetField(pkg, "Perm", luar.New(L, rand.Perm))
L.SetField(pkg, "Seed", luar.New(L, rand.Seed))
L.SetField(pkg, "Uint32", luar.New(L, rand.Uint32))
return pkg
}
func importOs() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Args", luar.New(L, os.Args))
L.SetField(pkg, "Chdir", luar.New(L, os.Chdir))
L.SetField(pkg, "Chmod", luar.New(L, os.Chmod))
L.SetField(pkg, "Chown", luar.New(L, os.Chown))
L.SetField(pkg, "Chtimes", luar.New(L, os.Chtimes))
L.SetField(pkg, "Clearenv", luar.New(L, os.Clearenv))
L.SetField(pkg, "Create", luar.New(L, os.Create))
L.SetField(pkg, "DevNull", luar.New(L, os.DevNull))
L.SetField(pkg, "Environ", luar.New(L, os.Environ))
L.SetField(pkg, "ErrExist", luar.New(L, os.ErrExist))
L.SetField(pkg, "ErrInvalid", luar.New(L, os.ErrInvalid))
L.SetField(pkg, "ErrNotExist", luar.New(L, os.ErrNotExist))
L.SetField(pkg, "ErrPermission", luar.New(L, os.ErrPermission))
L.SetField(pkg, "Exit", luar.New(L, os.Exit))
L.SetField(pkg, "Expand", luar.New(L, os.Expand))
L.SetField(pkg, "ExpandEnv", luar.New(L, os.ExpandEnv))
L.SetField(pkg, "FindProcess", luar.New(L, os.FindProcess))
L.SetField(pkg, "Getegid", luar.New(L, os.Getegid))
L.SetField(pkg, "Getenv", luar.New(L, os.Getenv))
L.SetField(pkg, "Geteuid", luar.New(L, os.Geteuid))
L.SetField(pkg, "Getgid", luar.New(L, os.Getgid))
L.SetField(pkg, "Getgroups", luar.New(L, os.Getgroups))
L.SetField(pkg, "Getpagesize", luar.New(L, os.Getpagesize))
L.SetField(pkg, "Getpid", luar.New(L, os.Getpid))
L.SetField(pkg, "Getuid", luar.New(L, os.Getuid))
L.SetField(pkg, "Getwd", luar.New(L, os.Getwd))
L.SetField(pkg, "Hostname", luar.New(L, os.Hostname))
L.SetField(pkg, "Interrupt", luar.New(L, os.Interrupt))
L.SetField(pkg, "IsExist", luar.New(L, os.IsExist))
L.SetField(pkg, "IsNotExist", luar.New(L, os.IsNotExist))
L.SetField(pkg, "IsPathSeparator", luar.New(L, os.IsPathSeparator))
L.SetField(pkg, "IsPermission", luar.New(L, os.IsPermission))
L.SetField(pkg, "Kill", luar.New(L, os.Kill))
L.SetField(pkg, "Lchown", luar.New(L, os.Lchown))
L.SetField(pkg, "Link", luar.New(L, os.Link))
L.SetField(pkg, "Lstat", luar.New(L, os.Lstat))
L.SetField(pkg, "Mkdir", luar.New(L, os.Mkdir))
L.SetField(pkg, "MkdirAll", luar.New(L, os.MkdirAll))
L.SetField(pkg, "ModeAppend", luar.New(L, os.ModeAppend))
L.SetField(pkg, "ModeCharDevice", luar.New(L, os.ModeCharDevice))
L.SetField(pkg, "ModeDevice", luar.New(L, os.ModeDevice))
L.SetField(pkg, "ModeDir", luar.New(L, os.ModeDir))
L.SetField(pkg, "ModeExclusive", luar.New(L, os.ModeExclusive))
L.SetField(pkg, "ModeNamedPipe", luar.New(L, os.ModeNamedPipe))
L.SetField(pkg, "ModePerm", luar.New(L, os.ModePerm))
L.SetField(pkg, "ModeSetgid", luar.New(L, os.ModeSetgid))
L.SetField(pkg, "ModeSetuid", luar.New(L, os.ModeSetuid))
L.SetField(pkg, "ModeSocket", luar.New(L, os.ModeSocket))
L.SetField(pkg, "ModeSticky", luar.New(L, os.ModeSticky))
L.SetField(pkg, "ModeSymlink", luar.New(L, os.ModeSymlink))
L.SetField(pkg, "ModeTemporary", luar.New(L, os.ModeTemporary))
L.SetField(pkg, "ModeType", luar.New(L, os.ModeType))
L.SetField(pkg, "NewFile", luar.New(L, os.NewFile))
L.SetField(pkg, "NewSyscallError", luar.New(L, os.NewSyscallError))
L.SetField(pkg, "O_APPEND", luar.New(L, os.O_APPEND))
L.SetField(pkg, "O_CREATE", luar.New(L, os.O_CREATE))
L.SetField(pkg, "O_EXCL", luar.New(L, os.O_EXCL))
L.SetField(pkg, "O_RDONLY", luar.New(L, os.O_RDONLY))
L.SetField(pkg, "O_RDWR", luar.New(L, os.O_RDWR))
L.SetField(pkg, "O_SYNC", luar.New(L, os.O_SYNC))
L.SetField(pkg, "O_TRUNC", luar.New(L, os.O_TRUNC))
L.SetField(pkg, "O_WRONLY", luar.New(L, os.O_WRONLY))
L.SetField(pkg, "Open", luar.New(L, os.Open))
L.SetField(pkg, "OpenFile", luar.New(L, os.OpenFile))
L.SetField(pkg, "PathListSeparator", luar.New(L, os.PathListSeparator))
L.SetField(pkg, "PathSeparator", luar.New(L, os.PathSeparator))
L.SetField(pkg, "Pipe", luar.New(L, os.Pipe))
L.SetField(pkg, "Readlink", luar.New(L, os.Readlink))
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
L.SetField(pkg, "Rename", luar.New(L, os.Rename))
L.SetField(pkg, "SEEK_CUR", luar.New(L, os.SEEK_CUR))
L.SetField(pkg, "SEEK_END", luar.New(L, os.SEEK_END))
L.SetField(pkg, "SEEK_SET", luar.New(L, os.SEEK_SET))
L.SetField(pkg, "SameFile", luar.New(L, os.SameFile))
L.SetField(pkg, "Setenv", luar.New(L, os.Setenv))
L.SetField(pkg, "StartProcess", luar.New(L, os.StartProcess))
L.SetField(pkg, "Stat", luar.New(L, os.Stat))
L.SetField(pkg, "Stderr", luar.New(L, os.Stderr))
L.SetField(pkg, "Stdin", luar.New(L, os.Stdin))
L.SetField(pkg, "Stdout", luar.New(L, os.Stdout))
L.SetField(pkg, "Symlink", luar.New(L, os.Symlink))
L.SetField(pkg, "TempDir", luar.New(L, os.TempDir))
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
return pkg
}
func importRuntime() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "GC", luar.New(L, runtime.GC))
L.SetField(pkg, "GOARCH", luar.New(L, runtime.GOARCH))
L.SetField(pkg, "GOMAXPROCS", luar.New(L, runtime.GOMAXPROCS))
L.SetField(pkg, "GOOS", luar.New(L, runtime.GOOS))
L.SetField(pkg, "GOROOT", luar.New(L, runtime.GOROOT))
return pkg
}
func importPath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Base", luar.New(L, path.Base))
L.SetField(pkg, "Clean", luar.New(L, path.Clean))
L.SetField(pkg, "Dir", luar.New(L, path.Dir))
L.SetField(pkg, "ErrBadPattern", luar.New(L, path.ErrBadPattern))
L.SetField(pkg, "Ext", luar.New(L, path.Ext))
L.SetField(pkg, "IsAbs", luar.New(L, path.IsAbs))
L.SetField(pkg, "Join", luar.New(L, path.Join))
L.SetField(pkg, "Match", luar.New(L, path.Match))
L.SetField(pkg, "Split", luar.New(L, path.Split))
return pkg
}
func importFilePath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))
L.SetField(pkg, "Dir", luar.New(L, filepath.Dir))
L.SetField(pkg, "EvalSymlinks", luar.New(L, filepath.EvalSymlinks))
L.SetField(pkg, "Ext", luar.New(L, filepath.Ext))
L.SetField(pkg, "FromSlash", luar.New(L, filepath.FromSlash))
L.SetField(pkg, "Glob", luar.New(L, filepath.Glob))
L.SetField(pkg, "HasPrefix", luar.New(L, filepath.HasPrefix))
L.SetField(pkg, "IsAbs", luar.New(L, filepath.IsAbs))
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
L.SetField(pkg, "Match", luar.New(L, filepath.Match))
L.SetField(pkg, "Rel", luar.New(L, filepath.Rel))
L.SetField(pkg, "Split", luar.New(L, filepath.Split))
L.SetField(pkg, "SplitList", luar.New(L, filepath.SplitList))
L.SetField(pkg, "ToSlash", luar.New(L, filepath.ToSlash))
L.SetField(pkg, "VolumeName", luar.New(L, filepath.VolumeName))
return pkg
}
func importStrings() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Contains", luar.New(L, strings.Contains))
L.SetField(pkg, "ContainsAny", luar.New(L, strings.ContainsAny))
L.SetField(pkg, "ContainsRune", luar.New(L, strings.ContainsRune))
L.SetField(pkg, "Count", luar.New(L, strings.Count))
L.SetField(pkg, "EqualFold", luar.New(L, strings.EqualFold))
L.SetField(pkg, "Fields", luar.New(L, strings.Fields))
L.SetField(pkg, "FieldsFunc", luar.New(L, strings.FieldsFunc))
L.SetField(pkg, "HasPrefix", luar.New(L, strings.HasPrefix))
L.SetField(pkg, "HasSuffix", luar.New(L, strings.HasSuffix))
L.SetField(pkg, "Index", luar.New(L, strings.Index))
L.SetField(pkg, "IndexAny", luar.New(L, strings.IndexAny))
L.SetField(pkg, "IndexByte", luar.New(L, strings.IndexByte))
L.SetField(pkg, "IndexFunc", luar.New(L, strings.IndexFunc))
L.SetField(pkg, "IndexRune", luar.New(L, strings.IndexRune))
L.SetField(pkg, "Join", luar.New(L, strings.Join))
L.SetField(pkg, "LastIndex", luar.New(L, strings.LastIndex))
L.SetField(pkg, "LastIndexAny", luar.New(L, strings.LastIndexAny))
L.SetField(pkg, "LastIndexFunc", luar.New(L, strings.LastIndexFunc))
L.SetField(pkg, "Map", luar.New(L, strings.Map))
L.SetField(pkg, "NewReader", luar.New(L, strings.NewReader))
L.SetField(pkg, "NewReplacer", luar.New(L, strings.NewReplacer))
L.SetField(pkg, "Repeat", luar.New(L, strings.Repeat))
L.SetField(pkg, "Replace", luar.New(L, strings.Replace))
L.SetField(pkg, "Split", luar.New(L, strings.Split))
L.SetField(pkg, "SplitAfter", luar.New(L, strings.SplitAfter))
L.SetField(pkg, "SplitAfterN", luar.New(L, strings.SplitAfterN))
L.SetField(pkg, "SplitN", luar.New(L, strings.SplitN))
L.SetField(pkg, "Title", luar.New(L, strings.Title))
L.SetField(pkg, "ToLower", luar.New(L, strings.ToLower))
L.SetField(pkg, "ToLowerSpecial", luar.New(L, strings.ToLowerSpecial))
L.SetField(pkg, "ToTitle", luar.New(L, strings.ToTitle))
L.SetField(pkg, "ToTitleSpecial", luar.New(L, strings.ToTitleSpecial))
L.SetField(pkg, "ToUpper", luar.New(L, strings.ToUpper))
L.SetField(pkg, "ToUpperSpecial", luar.New(L, strings.ToUpperSpecial))
L.SetField(pkg, "Trim", luar.New(L, strings.Trim))
L.SetField(pkg, "TrimFunc", luar.New(L, strings.TrimFunc))
L.SetField(pkg, "TrimLeft", luar.New(L, strings.TrimLeft))
L.SetField(pkg, "TrimLeftFunc", luar.New(L, strings.TrimLeftFunc))
L.SetField(pkg, "TrimPrefix", luar.New(L, strings.TrimPrefix))
L.SetField(pkg, "TrimRight", luar.New(L, strings.TrimRight))
L.SetField(pkg, "TrimRightFunc", luar.New(L, strings.TrimRightFunc))
L.SetField(pkg, "TrimSpace", luar.New(L, strings.TrimSpace))
L.SetField(pkg, "TrimSuffix", luar.New(L, strings.TrimSuffix))
return pkg
}
func importRegexp() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Match", luar.New(L, regexp.Match))
L.SetField(pkg, "MatchReader", luar.New(L, regexp.MatchReader))
L.SetField(pkg, "MatchString", luar.New(L, regexp.MatchString))
L.SetField(pkg, "QuoteMeta", luar.New(L, regexp.QuoteMeta))
L.SetField(pkg, "Compile", luar.New(L, regexp.Compile))
L.SetField(pkg, "CompilePOSIX", luar.New(L, regexp.CompilePOSIX))
L.SetField(pkg, "MustCompile", luar.New(L, regexp.MustCompile))
L.SetField(pkg, "MustCompilePOSIX", luar.New(L, regexp.MustCompilePOSIX))
return pkg
}
func importErrors() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "New", luar.New(L, errors.New))
return pkg
}
func importTime() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "After", luar.New(L, time.After))
L.SetField(pkg, "Sleep", luar.New(L, time.Sleep))
L.SetField(pkg, "Tick", luar.New(L, time.Tick))
L.SetField(pkg, "Since", luar.New(L, time.Since))
L.SetField(pkg, "FixedZone", luar.New(L, time.FixedZone))
L.SetField(pkg, "LoadLocation", luar.New(L, time.LoadLocation))
L.SetField(pkg, "NewTicker", luar.New(L, time.NewTicker))
L.SetField(pkg, "Date", luar.New(L, time.Date))
L.SetField(pkg, "Now", luar.New(L, time.Now))
L.SetField(pkg, "Parse", luar.New(L, time.Parse))
L.SetField(pkg, "ParseDuration", luar.New(L, time.ParseDuration))
L.SetField(pkg, "ParseInLocation", luar.New(L, time.ParseInLocation))
L.SetField(pkg, "Unix", luar.New(L, time.Unix))
L.SetField(pkg, "AfterFunc", luar.New(L, time.AfterFunc))
L.SetField(pkg, "NewTimer", luar.New(L, time.NewTimer))
L.SetField(pkg, "Nanosecond", luar.New(L, time.Nanosecond))
L.SetField(pkg, "Microsecond", luar.New(L, time.Microsecond))
L.SetField(pkg, "Millisecond", luar.New(L, time.Millisecond))
L.SetField(pkg, "Second", luar.New(L, time.Second))
L.SetField(pkg, "Minute", luar.New(L, time.Minute))
L.SetField(pkg, "Hour", luar.New(L, time.Hour))
return pkg
}

View File

@@ -3,14 +3,11 @@ package main
import (
"bufio"
"bytes"
"encoding/gob"
"fmt"
"os"
"strconv"
"github.com/mattn/go-runewidth"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/cmd/micro/shellwords"
"github.com/zyedidia/tcell"
)
@@ -72,11 +69,9 @@ type Messenger struct {
gutterMessage bool
}
// AddLog sends a message to the log view
func (m *Messenger) AddLog(msg ...interface{}) {
logMessage := fmt.Sprint(msg...)
func (m *Messenger) AddLog(msg string) {
buffer := m.getBuffer()
buffer.insert(buffer.End(), []byte(logMessage+"\n"))
buffer.insert(buffer.End(), []byte(msg+"\n"))
buffer.Cursor.Loc = buffer.End()
buffer.Cursor.Relocate()
}
@@ -223,7 +218,6 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
}
}
// Completion represents a type of completion
type Completion int
const (
@@ -234,7 +228,6 @@ const (
OptionCompletion
PluginCmdCompletion
PluginNameCompletion
OptionValueCompletion
)
// Prompt sends the user a message and waits for a response to be typed in
@@ -274,16 +267,9 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
response, canceled = m.response, false
m.history[historyType][len(m.history[historyType])-1] = response
case tcell.KeyTab:
args, err := shellwords.Split(m.response)
if err != nil {
break
}
currentArg := ""
currentArgNum := 0
if len(args) > 0 {
currentArgNum = len(args) - 1
currentArg = args[currentArgNum]
}
args := SplitCommandArgs(m.response)
currentArgNum := len(args) - 1
currentArg := args[currentArgNum]
var completionType Completion
if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
@@ -307,10 +293,6 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
chosen, suggestions = HelpComplete(currentArg)
} else if completionType == OptionCompletion {
chosen, suggestions = OptionComplete(currentArg)
} else if completionType == OptionValueCompletion {
if currentArgNum-1 > 0 {
chosen, suggestions = OptionValueComplete(args[currentArgNum-1], currentArg)
}
} else if completionType == PluginCmdCompletion {
chosen, suggestions = PluginCmdComplete(currentArg)
} else if completionType == PluginNameCompletion {
@@ -323,8 +305,8 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
chosen = chosen + CommonSubstring(suggestions...)
}
if len(suggestions) != 0 && chosen != "" {
m.response = shellwords.Join(append(args[:len(args)-1], chosen)...)
if chosen != "" {
m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
m.cursorx = Count(m.response)
}
}
@@ -349,160 +331,62 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
return response, canceled
}
// UpHistory fetches the previous item in the history
func (m *Messenger) UpHistory(history []string) {
if m.historyNum > 0 {
m.historyNum--
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
}
// DownHistory fetches the next item in the history
func (m *Messenger) DownHistory(history []string) {
if m.historyNum < len(history)-1 {
m.historyNum++
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
}
// CursorLeft moves the cursor one character left
func (m *Messenger) CursorLeft() {
if m.cursorx > 0 {
m.cursorx--
}
}
// CursorRight moves the cursor one character right
func (m *Messenger) CursorRight() {
if m.cursorx < Count(m.response) {
m.cursorx++
}
}
// Start moves the cursor to the start of the line
func (m *Messenger) Start() {
m.cursorx = 0
}
// End moves the cursor to the end of the line
func (m *Messenger) End() {
m.cursorx = Count(m.response)
}
// Backspace deletes one character
func (m *Messenger) Backspace() {
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
m.cursorx--
}
}
// Paste pastes the clipboard
func (m *Messenger) Paste() {
clip, _ := clipboard.ReadAll("clipboard")
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
}
// WordLeft moves the cursor one word to the left
func (m *Messenger) WordLeft() {
response := []rune(m.response)
m.CursorLeft()
if m.cursorx <= 0 {
return
}
for IsWhitespace(response[m.cursorx]) {
if m.cursorx <= 0 {
return
}
m.CursorLeft()
}
m.CursorLeft()
for IsWordChar(string(response[m.cursorx])) {
if m.cursorx <= 0 {
return
}
m.CursorLeft()
}
m.CursorRight()
}
// WordRight moves the cursor one word to the right
func (m *Messenger) WordRight() {
response := []rune(m.response)
if m.cursorx >= len(response) {
return
}
for IsWhitespace(response[m.cursorx]) {
m.CursorRight()
if m.cursorx >= len(response) {
m.CursorRight()
return
}
}
m.CursorRight()
if m.cursorx >= len(response) {
return
}
for IsWordChar(string(response[m.cursorx])) {
m.CursorRight()
if m.cursorx >= len(response) {
return
}
}
}
// DeleteWordLeft deletes one word to the left
func (m *Messenger) DeleteWordLeft() {
m.WordLeft()
m.response = string([]rune(m.response)[:m.cursorx])
}
// HandleEvent handles an event for the prompter
func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
switch e := event.(type) {
case *tcell.EventKey:
if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
for key, actions := range bindings {
if e.Key() == key.keyCode {
if e.Key() == tcell.KeyRune {
if e.Rune() != key.r {
continue
}
}
if e.Modifiers() == key.modifiers {
for _, action := range actions {
funcName := FuncName(action)
switch funcName {
case "main.(*View).CursorUp":
if m.historyNum > 0 {
m.historyNum--
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case "main.(*View).CursorDown":
if m.historyNum < len(history)-1 {
m.historyNum++
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case "main.(*View).CursorLeft":
if m.cursorx > 0 {
m.cursorx--
}
case "main.(*View).CursorRight":
if m.cursorx < Count(m.response) {
m.cursorx++
}
case "main.(*View).CursorStart", "main.(*View).StartOfLine":
m.cursorx = 0
case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
m.cursorx = Count(m.response)
case "main.(*View).Backspace":
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
m.cursorx--
}
case "main.(*View).Paste":
clip, _ := clipboard.ReadAll("clipboard")
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
}
}
}
}
}
}
switch e.Key() {
case tcell.KeyCtrlA:
m.Start()
case tcell.KeyCtrlE:
m.End()
case tcell.KeyUp:
m.UpHistory(history)
case tcell.KeyDown:
m.DownHistory(history)
case tcell.KeyLeft:
if e.Modifiers() == tcell.ModCtrl {
m.Start()
} else if e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
m.WordLeft()
} else {
m.CursorLeft()
}
case tcell.KeyRight:
if e.Modifiers() == tcell.ModCtrl {
m.End()
} else if e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
m.WordRight()
} else {
m.CursorRight()
}
case tcell.KeyBackspace2, tcell.KeyBackspace:
if e.Modifiers() == tcell.ModCtrl || e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
m.DeleteWordLeft()
} else {
m.Backspace()
}
case tcell.KeyCtrlW:
m.DeleteWordLeft()
case tcell.KeyCtrlV:
m.Paste()
case tcell.KeyCtrlF:
m.WordRight()
case tcell.KeyCtrlB:
m.WordLeft()
case tcell.KeyRune:
m.response = Insert(m.response, m.cursorx, string(e.Rune()))
m.cursorx++
@@ -579,10 +463,8 @@ func (m *Messenger) Display() {
if m.hasMessage {
if m.hasPrompt || globalSettings["infobar"].(bool) {
runes := []rune(m.message + m.response)
posx := 0
for x := 0; x < len(runes); x++ {
screen.SetContent(posx, h-1, runes[x], nil, m.style)
posx += runewidth.RuneWidth(runes[x])
screen.SetContent(x, h-1, runes[x], nil, m.style)
}
}
}
@@ -593,59 +475,6 @@ func (m *Messenger) Display() {
}
}
// LoadHistory attempts to load user history from configDir/buffers/history
// into the history map
// The savehistory option must be on
func (m *Messenger) LoadHistory() {
if GetGlobalOption("savehistory").(bool) {
file, err := os.Open(configDir + "/buffers/history")
var decodedMap map[string][]string
if err == nil {
decoder := gob.NewDecoder(file)
err = decoder.Decode(&decodedMap)
file.Close()
if err != nil {
m.Error("Error loading history:", err)
return
}
}
if decodedMap != nil {
m.history = decodedMap
} else {
m.history = make(map[string][]string)
}
} else {
m.history = make(map[string][]string)
}
}
// SaveHistory saves the user's command history to configDir/buffers/history
// only if the savehistory option is on
func (m *Messenger) SaveHistory() {
if GetGlobalOption("savehistory").(bool) {
// Don't save history past 100
for k, v := range m.history {
if len(v) > 100 {
m.history[k] = v[len(m.history[k])-100:]
}
}
file, err := os.Create(configDir + "/buffers/history")
if err == nil {
encoder := gob.NewEncoder(file)
err = encoder.Encode(m.history)
if err != nil {
m.Error("Error saving history:", err)
return
}
file.Close()
}
}
}
// A GutterMessage is a message displayed on the side of the editor
type GutterMessage struct {
lineNum int

View File

@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/go-errors/errors"
@@ -20,6 +21,8 @@ import (
)
const (
synLinesUp = 75 // How many lines up to look to do syntax highlighting
synLinesDown = 75 // How many lines down to look to do syntax highlighting
doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
autosaveTime = 8 // Number of seconds to wait before autosaving
@@ -43,12 +46,14 @@ var (
// Version is the version number or commit hash
// These variables should be set by the linker when compiling
Version = "0.0.0-unknown"
// CommitHash is the commit this version was built on
CommitHash = "Unknown"
// CompileDate is the date this binary was compiled on
Version = "0.0.0-unknown"
CommitHash = "Unknown"
CompileDate = "Unknown"
// L is the lua state
// This is the VM that runs the plugins
L *lua.LState
// The list of views
tabs []*Tab
// This is the currently open tab
@@ -58,10 +63,8 @@ var (
// Channel of jobs running in the background
jobs chan JobFunction
// Event channel
events chan tcell.Event
autosave chan bool
updateterm chan bool
closeterm chan int
events chan tcell.Event
autosave chan bool
)
// LoadInput determines which files should be loaded into buffers
@@ -146,15 +149,6 @@ func InitConfigDir() {
}
configDir = xdgHome + "/micro"
if len(*flagConfigDir) > 0 {
if _, err := os.Stat(*flagConfigDir); os.IsNotExist(err) {
TermMessage("Error: " + *flagConfigDir + " does not exist. Defaulting to " + configDir + ".")
} else {
configDir = *flagConfigDir
return
}
}
if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
// If the xdgHome doesn't exist we should create it
err = os.Mkdir(xdgHome, os.ModePerm)
@@ -205,11 +199,8 @@ func InitScreen() {
os.Setenv("TERM", oldTerm)
}
if GetGlobalOption("mouse").(bool) {
screen.EnableMouse()
}
// screen.SetStyle(defStyle)
screen.SetStyle(defStyle)
screen.EnableMouse()
}
// RedrawAll redraws everything -- all the views and the messenger
@@ -228,9 +219,6 @@ func RedrawAll() {
}
DisplayTabs()
messenger.Display()
if globalSettings["keymenu"].(bool) {
DisplayKeyMenu()
}
screen.Show()
}
@@ -256,29 +244,15 @@ func LoadAll() {
}
}
// Command line flags
// Passing -version as a flag will have micro print out the version number
var flagVersion = flag.Bool("version", false, "Show the version number and information")
var flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.")
var flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
var flagOptions = flag.Bool("options", false, "Show all option help")
func main() {
flag.Usage = func() {
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
fmt.Println("-config-dir dir")
fmt.Println(" \tSpecify a custom location for the configuration directory")
fmt.Println("-startpos LINE,COL")
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
fmt.Println("-options")
fmt.Println(" \tShow all option help")
fmt.Println("-version")
fmt.Println(" \tShow the version number and information")
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
fmt.Println("-option value")
fmt.Println(" \tSet `option` to `value` for this session")
fmt.Println(" \tFor example: `micro -syntax off file.c`")
fmt.Println("\nUse `micro -options` to see the full list of configuration options")
fmt.Print("Micro's options can be set via command line arguments for quick adjustments. For real configuration, please use the bindings.json file (see 'help options').\n\n")
flag.PrintDefaults()
}
optionFlags := make(map[string]*string)
@@ -297,15 +271,6 @@ func main() {
os.Exit(0)
}
if *flagOptions {
// If -options was passed
for k, v := range DefaultGlobalSettings() {
fmt.Printf("-%s value\n", k)
fmt.Printf(" \tThe %s option. Default value: '%v'\n", k, v)
}
os.Exit(0)
}
// Start the Lua VM for running plugins
L = lua.NewState()
defer L.Close()
@@ -345,7 +310,7 @@ func main() {
// Create a new messenger
// This is used for sending the user messages in the bottom of the editor
messenger = new(Messenger)
messenger.LoadHistory()
messenger.history = make(map[string][]string)
// Now we load the input
buffers := LoadInput()
@@ -390,12 +355,6 @@ func main() {
L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
L.SetGlobal("HandleCommand", luar.New(L, HandleCommand))
L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
L.SetGlobal("ExecCommand", luar.New(L, ExecCommand))
L.SetGlobal("RunShellCommand", luar.New(L, RunShellCommand))
L.SetGlobal("RunBackgroundShell", luar.New(L, RunBackgroundShell))
L.SetGlobal("RunInteractiveShell", luar.New(L, RunInteractiveShell))
L.SetGlobal("TermEmuSupported", luar.New(L, TermEmuSupported))
L.SetGlobal("RunTermEmulator", luar.New(L, RunTermEmulator))
L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion))
L.SetGlobal("NewBuffer", luar.New(L, NewBufferFromString))
@@ -405,7 +364,6 @@ func main() {
L.SetGlobal("Loc", luar.New(L, func(x, y int) Loc {
return Loc{x, y}
}))
L.SetGlobal("WorkingDirectory", luar.New(L, os.Getwd))
L.SetGlobal("JoinPaths", luar.New(L, filepath.Join))
L.SetGlobal("DirectoryName", luar.New(L, filepath.Dir))
L.SetGlobal("configDir", luar.New(L, configDir))
@@ -426,33 +384,30 @@ func main() {
L.SetGlobal("AddRuntimeFilesFromDirectory", luar.New(L, PluginAddRuntimeFilesFromDirectory))
L.SetGlobal("AddRuntimeFileFromMemory", luar.New(L, PluginAddRuntimeFileFromMemory))
// Access to Go stdlib
L.SetGlobal("import", luar.New(L, Import))
jobs = make(chan JobFunction, 100)
events = make(chan tcell.Event, 100)
autosave = make(chan bool)
updateterm = make(chan bool)
closeterm = make(chan int)
LoadPlugins()
for _, t := range tabs {
for _, v := range t.views {
GlobalPluginCall("onViewOpen", v)
GlobalPluginCall("onBufferOpen", v.Buf)
for pl := range loadedPlugins {
_, err := Call(pl+".onViewOpen", v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
continue
}
}
}
}
InitColorscheme()
messenger.style = defStyle
// Here is the event loop which runs in a separate thread
go func() {
for {
if screen != nil {
events <- screen.PollEvent()
}
events <- screen.PollEvent()
}
}()
@@ -478,19 +433,11 @@ func main() {
f.function(f.output, f.args...)
continue
case <-autosave:
if CurView().Buf.Path != "" {
CurView().Save(true)
}
case <-updateterm:
continue
case vnum := <-closeterm:
tabs[curTab].views[vnum].CloseTerminal()
CurView().Save(true)
case event = <-events:
}
for event != nil {
didAction := false
switch e := event.(type) {
case *tcell.EventResize:
for _, t := range tabs {
@@ -520,38 +467,24 @@ func main() {
}
}
}
} else if e.Buttons() == tcell.WheelUp || e.Buttons() == tcell.WheelDown {
var view *View
x, y := e.Position()
for _, v := range tabs[curTab].views {
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
view = tabs[curTab].views[v.Num]
}
}
if view != nil {
view.HandleEvent(e)
didAction = true
}
}
}
}
if !didAction {
// This function checks the mouse event for the possibility of changing the current tab
// If the tab was changed it returns true
if TabbarHandleMouseEvent(event) {
break
}
// This function checks the mouse event for the possibility of changing the current tab
// If the tab was changed it returns true
if TabbarHandleMouseEvent(event) {
break
}
if searching {
// Since searching is done in real time, we need to redraw every time
// there is a new event in the search bar so we need a special function
// to run instead of the standard HandleEvent.
HandleSearchEvent(event, CurView())
} else {
// Send it to the view
CurView().HandleEvent(event)
}
if searching {
// Since searching is done in real time, we need to redraw every time
// there is a new event in the search bar so we need a special function
// to run instead of the standard HandleEvent.
HandleSearchEvent(event, CurView())
} else {
// Send it to the view
CurView().HandleEvent(event)
}
select {

View File

@@ -7,7 +7,6 @@ import (
"strings"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/tcell"
"layeh.com/gopher-luar"
)
@@ -61,20 +60,6 @@ func LuaFunctionBinding(function string) func(*View, bool) bool {
}
}
// LuaFunctionMouseBinding is a function generator which takes the name of a lua function
// and creates a function that will call that lua function
// Specifically it creates a function that can be called as a mouse binding because this is used
// to bind mouse actions to lua functions
func LuaFunctionMouseBinding(function string) func(*View, bool, *tcell.EventMouse) bool {
return func(v *View, _ bool, e *tcell.EventMouse) bool {
_, err := Call(function, e)
if err != nil {
TermMessage(err)
}
return false
}
}
func unpack(old []string) []interface{} {
new := make([]interface{}, len(old))
for i, v := range old {
@@ -118,13 +103,10 @@ func LuaFunctionComplete(function string) func(string) []string {
}
}
// LuaFunctionJob returns a function that will call the given lua function
// structured as a job call i.e. the job output and arguments are provided
// to the lua function
func LuaFunctionJob(function string) func(string, ...string) {
return func(output string, args ...string) {
_, err := Call(function, unpack(append([]string{output}, args...))...)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
if err != nil {
TermMessage(err)
}
}
@@ -137,9 +119,11 @@ func luaPluginName(name string) string {
// LoadPlugins loads the pre-installed plugins and the plugins located in ~/.config/micro/plugins
func LoadPlugins() {
loadedPlugins = make(map[string]string)
for _, plugin := range ListRuntimeFiles(RTPlugin) {
pluginName := plugin.Name()
if _, ok := loadedPlugins[pluginName]; ok {
continue
@@ -152,8 +136,9 @@ func LoadPlugins() {
}
pluginLuaName := luaPluginName(pluginName)
pluginDef := "\nlocal P = {}\n" + pluginLuaName + " = P\nsetmetatable(" + pluginLuaName + ", {__index = _G})\nsetfenv(1, P)\n"
if err := LoadFile(pluginLuaName, pluginLuaName, string(data)); err != nil {
if err := L.DoString(pluginDef + string(data)); err != nil {
TermMessage(err)
continue
}
@@ -163,22 +148,11 @@ func LoadPlugins() {
}
if _, err := os.Stat(configDir + "/init.lua"); err == nil {
pluginDef := "\nlocal P = {}\n" + "init" + " = P\nsetmetatable(" + "init" + ", {__index = _G})\nsetfenv(1, P)\n"
data, _ := ioutil.ReadFile(configDir + "/init.lua")
if err := LoadFile("init", configDir+"init.lua", string(data)); err != nil {
if err := L.DoString(pluginDef + string(data)); err != nil {
TermMessage(err)
}
loadedPlugins["init"] = "init"
}
}
// GlobalCall makes a call to a function in every plugin that is currently
// loaded
func GlobalPluginCall(function string, args ...interface{}) {
for pl := range loadedPlugins {
_, err := Call(pl+"."+function, args...)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
continue
}
}
}

View File

@@ -14,8 +14,8 @@ import (
"sync"
"github.com/blang/semver"
"github.com/flynn/json5"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/json5/encoding/json5"
)
var (
@@ -422,7 +422,6 @@ func (pv *PluginVersion) DownloadAndInstall() error {
}
}
// Install files and directory's
for _, f := range z.File {
parts := strings.Split(f.Name, "/")
if allPrefixed {
@@ -435,12 +434,6 @@ func (pv *PluginVersion) DownloadAndInstall() error {
return err
}
} else {
basepath := filepath.Dir(targetName)
if err := os.MkdirAll(basepath, dirPerm); err != nil {
return err
}
content, err := f.Open()
if err != nil {
return err

View File

@@ -1,11 +1,10 @@
package main
import (
"github.com/blang/semver"
"testing"
"github.com/blang/semver"
"github.com/flynn/json5"
"github.com/zyedidia/json5/encoding/json5"
)
func TestDependencyResolving(t *testing.T) {

File diff suppressed because one or more lines are too long

View File

@@ -1,20 +0,0 @@
package main
// ScrollBar represents an optional scrollbar that can be used
type ScrollBar struct {
view *View
}
// Display shows the scrollbar
func (sb *ScrollBar) Display() {
style := defStyle.Reverse(true)
screen.SetContent(sb.view.x+sb.view.Width-1, sb.view.y+sb.pos(), ' ', nil, style)
}
func (sb *ScrollBar) pos() int {
numlines := sb.view.Buf.NumLines
h := sb.view.Height
filepercent := float32(sb.view.Topline) / float32(numlines)
return int(filepercent * float32(h))
}

View File

@@ -2,7 +2,6 @@ package main
import (
"regexp"
"strings"
"github.com/zyedidia/tcell"
)
@@ -12,7 +11,7 @@ var (
lastSearch string
// Where should we start the search down from (or up from)
searchStart Loc
searchStart int
// Is there currently a search in progress
searching bool
@@ -44,7 +43,7 @@ func EndSearch() {
}
}
// ExitSearch exits the search mode, reset active search phrase, and clear status bar
// exit the search mode, reset active search phrase, and clear status bar
func ExitSearch(v *View) {
lastSearch = ""
searching = false
@@ -64,12 +63,7 @@ func HandleSearchEvent(event tcell.Event, v *View) {
// Exit the search mode
ExitSearch(v)
return
case tcell.KeyEnter:
// If the user has pressed Enter, they want this to be the lastSearch
lastSearch = messenger.response
EndSearch()
return
case tcell.KeyCtrlQ, tcell.KeyCtrlC:
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEnter:
// Done
EndSearch()
return
@@ -97,66 +91,6 @@ func HandleSearchEvent(event tcell.Event, v *View) {
return
}
func searchDown(r *regexp.Regexp, v *View, start, end Loc) bool {
for i := start.Y; i <= end.Y; i++ {
var l []byte
var charPos int
if i == start.Y {
runes := []rune(string(v.Buf.lines[i].data))
l = []byte(string(runes[start.X:]))
charPos = start.X
if strings.Contains(r.String(), "^") && start.X != 0 {
continue
}
} else {
l = v.Buf.lines[i].data
}
match := r.FindIndex(l)
if match != nil {
v.Cursor.SetSelectionStart(Loc{charPos + runePos(match[0], string(l)), i})
v.Cursor.SetSelectionEnd(Loc{charPos + runePos(match[1], string(l)), i})
v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
v.Cursor.Loc = v.Cursor.CurSelection[1]
return true
}
}
return false
}
func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
for i := start.Y; i >= end.Y; i-- {
var l []byte
if i == start.Y {
runes := []rune(string(v.Buf.lines[i].data))
l = []byte(string(runes[:start.X]))
if strings.Contains(r.String(), "$") && start.X != Count(string(l)) {
continue
}
} else {
l = v.Buf.lines[i].data
}
match := r.FindIndex(l)
if match != nil {
v.Cursor.SetSelectionStart(Loc{runePos(match[0], string(l)), i})
v.Cursor.SetSelectionEnd(Loc{runePos(match[1], string(l)), i})
v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
v.Cursor.Loc = v.Cursor.CurSelection[1]
return true
}
}
return false
}
// Search searches in the view for the given regex. The down bool
// specifies whether it should search down from the searchStart position
// or up from there
@@ -164,6 +98,15 @@ func Search(searchStr string, v *View, down bool) {
if searchStr == "" {
return
}
var str string
var charPos int
text := v.Buf.String()
if down {
str = string([]rune(text)[searchStart:])
charPos = searchStart
} else {
str = string([]rune(text)[:searchStart])
}
r, err := regexp.Compile(searchStr)
if v.Buf.Settings["ignorecase"].(bool) {
r, err = regexp.Compile("(?i)" + searchStr)
@@ -171,20 +114,37 @@ func Search(searchStr string, v *View, down bool) {
if err != nil {
return
}
matches := r.FindAllStringIndex(str, -1)
var match []int
if matches == nil {
// Search the entire buffer now
matches = r.FindAllStringIndex(text, -1)
charPos = 0
if matches == nil {
v.Cursor.ResetSelection()
return
}
var found bool
if down {
found = searchDown(r, v, searchStart, v.Buf.End())
if !found {
found = searchDown(r, v, v.Buf.Start(), searchStart)
if !down {
match = matches[len(matches)-1]
} else {
match = matches[0]
}
str = text
}
if !down {
match = matches[len(matches)-1]
} else {
found = searchUp(r, v, searchStart, v.Buf.Start())
if !found {
found = searchUp(r, v, v.Buf.End(), searchStart)
}
match = matches[0]
}
if !found {
v.Cursor.ResetSelection()
if match[0] == match[1] {
return
}
v.Cursor.SetSelectionStart(FromCharPos(charPos+runePos(match[0], str), v.Buf))
v.Cursor.SetSelectionEnd(FromCharPos(charPos+runePos(match[1], str), v.Buf))
v.Cursor.Loc = v.Cursor.CurSelection[1]
lastSearch = searchStr
}

View File

@@ -1,8 +1,6 @@
package main
import (
"crypto/md5"
"encoding/json"
"errors"
"io/ioutil"
"os"
@@ -10,8 +8,8 @@ import (
"strconv"
"strings"
"github.com/flynn/json5"
"github.com/zyedidia/glob"
"github.com/zyedidia/json5/encoding/json5"
)
type optionValidator func(string, interface{}) error
@@ -19,8 +17,6 @@ type optionValidator func(string, interface{}) error
// The options that the user can set
var globalSettings map[string]interface{}
var invalidSettings bool
// Options with validators
var optionValidators = map[string]optionValidator{
"tabsize": validatePositiveValue,
@@ -28,12 +24,10 @@ var optionValidators = map[string]optionValidator{
"scrollspeed": validateNonNegativeValue,
"colorscheme": validateColorscheme,
"colorcolumn": validateNonNegativeValue,
"fileformat": validateLineEnding,
}
// InitGlobalSettings initializes the options map and sets all options to their default values
func InitGlobalSettings() {
invalidSettings = false
defaults := DefaultGlobalSettings()
var parsed map[string]interface{}
@@ -44,14 +38,12 @@ func InitGlobalSettings() {
if !strings.HasPrefix(string(input), "null") {
if err != nil {
TermMessage("Error reading settings.json file: " + err.Error())
invalidSettings = true
return
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
invalidSettings = true
}
} else {
writeSettings = true
@@ -79,7 +71,6 @@ func InitGlobalSettings() {
// InitLocalSettings scans the json in settings.json and sets the options locally based
// on whether the buffer matches the glob
func InitLocalSettings(buf *Buffer) {
invalidSettings = false
var parsed map[string]interface{}
filename := configDir + "/settings.json"
@@ -87,36 +78,26 @@ func InitLocalSettings(buf *Buffer) {
input, err := ioutil.ReadFile(filename)
if err != nil {
TermMessage("Error reading settings.json file: " + err.Error())
invalidSettings = true
return
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
invalidSettings = true
}
}
for k, v := range parsed {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if strings.HasPrefix(k, "ft:") {
if buf.Settings["filetype"].(string) == k[3:] {
for k1, v1 := range v.(map[string]interface{}) {
buf.Settings[k1] = v1
}
}
} else {
g, err := glob.Compile(k)
if err != nil {
TermMessage("Error with glob setting ", k, ": ", err)
continue
}
g, err := glob.Compile(k)
if err != nil {
TermMessage("Error with glob setting ", k, ": ", err)
continue
}
if g.MatchString(buf.Path) {
for k1, v1 := range v.(map[string]interface{}) {
buf.Settings[k1] = v1
}
if g.MatchString(buf.Path) {
for k1, v1 := range v.(map[string]interface{}) {
buf.Settings[k1] = v1
}
}
}
@@ -125,11 +106,6 @@ func InitLocalSettings(buf *Buffer) {
// WriteSettings writes the settings to the specified filename as JSON
func WriteSettings(filename string) error {
if invalidSettings {
// Do not write the settings if there was an error when reading them
return nil
}
var err error
if _, e := os.Stat(configDir); e == nil {
parsed := make(map[string]interface{})
@@ -148,7 +124,6 @@ func WriteSettings(filename string) error {
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
invalidSettings = true
}
for k, v := range parsed {
@@ -161,7 +136,7 @@ func WriteSettings(filename string) error {
}
}
txt, _ := json.MarshalIndent(parsed, "", " ")
txt, _ := json5.MarshalIndent(parsed, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return err
@@ -201,42 +176,35 @@ func GetOption(name string) interface{} {
func DefaultGlobalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"keepautoindent": false,
"autosave": false,
"basename": false,
"colorcolumn": float64(0),
"colorscheme": "default",
"cursorline": true,
"eofnewline": false,
"fastdirty": true,
"fileformat": "unix",
"rmtrailingws": false,
"ignorecase": false,
"indentchar": " ",
"infobar": true,
"keepautoindent": false,
"keymenu": false,
"matchbrace": false,
"mouse": true,
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
"pluginrepos": []string{},
"rmtrailingws": false,
"ruler": true,
"savecursor": false,
"savehistory": true,
"saveundo": false,
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"softwrap": false,
"splitbottom": true,
"splitright": true,
"splitRight": true,
"splitBottom": true,
"statusline": true,
"sucmd": "sudo",
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"termtitle": false,
"useprimary": true,
"pluginchannels": []string{
"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
},
"pluginrepos": []string{},
"useprimary": true,
}
}
@@ -245,28 +213,23 @@ func DefaultGlobalSettings() map[string]interface{} {
func DefaultLocalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"keepautoindent": false,
"autosave": false,
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"eofnewline": false,
"fastdirty": true,
"fileformat": "unix",
"rmtrailingws": false,
"filetype": "Unknown",
"ignorecase": false,
"indentchar": " ",
"keepautoindent": false,
"matchbrace": false,
"rmtrailingws": false,
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"softwrap": false,
"splitbottom": true,
"splitright": true,
"splitRight": true,
"splitBottom": true,
"statusline": true,
"syntax": true,
"tabmovement": false,
@@ -326,26 +289,16 @@ func SetOption(option, value string) error {
}
}
if option == "infobar" || option == "keymenu" {
if option == "infobar" {
for _, tab := range tabs {
tab.Resize()
}
}
if option == "mouse" {
if !nativeValue.(bool) {
screen.DisableMouse()
} else {
screen.EnableMouse()
}
}
if len(tabs) != 0 {
if _, ok := CurView().Buf.Settings[option]; ok {
for _, tab := range tabs {
for _, view := range tab.views {
SetLocalOption(option, value, view)
}
if _, ok := CurView().Buf.Settings[option]; ok {
for _, tab := range tabs {
for _, view := range tab.views {
SetLocalOption(option, value, view)
}
}
}
@@ -385,26 +338,6 @@ func SetLocalOption(option, value string, view *View) error {
return err
}
if option == "fastdirty" {
// If it is being turned off, we have to hash every open buffer
var empty [16]byte
for _, tab := range tabs {
for _, v := range tab.views {
if !nativeValue.(bool) {
if v.Buf.origHash == empty {
data, err := ioutil.ReadFile(v.Buf.AbsPath)
if err != nil {
data = []byte{}
}
v.Buf.origHash = md5.Sum(data)
}
} else {
v.Buf.IsModified = v.Buf.Modified()
}
}
}
}
buf.Settings[option] = nativeValue
if option == "statusline" {
@@ -417,10 +350,6 @@ func SetLocalOption(option, value string, view *View) error {
buf.UpdateRules()
}
if option == "fileformat" {
buf.IsModified = true
}
if option == "syntax" {
if !nativeValue.(bool) {
buf.ClearMatches()
@@ -501,17 +430,3 @@ func validateColorscheme(option string, value interface{}) error {
return nil
}
func validateLineEnding(option string, value interface{}) error {
endingType, ok := value.(string)
if !ok {
return errors.New("Expected string type for file format")
}
if endingType != "unix" && endingType != "dos" {
return errors.New("File format must be either 'unix' or 'dos'")
}
return nil
}

View File

@@ -1,129 +0,0 @@
package main
import (
"bytes"
"io"
"os"
"os/exec"
"os/signal"
"strings"
"github.com/zyedidia/micro/cmd/micro/shellwords"
)
// ExecCommand executes a command using exec
// It returns any output/errors
func ExecCommand(name string, arg ...string) (string, error) {
var err error
cmd := exec.Command(name, arg...)
outputBytes := &bytes.Buffer{}
cmd.Stdout = outputBytes
cmd.Stderr = outputBytes
err = cmd.Start()
if err != nil {
return "", err
}
err = cmd.Wait() // wait for command to finish
outstring := outputBytes.String()
return outstring, err
}
// RunShellCommand executes a shell command and returns the output/error
func RunShellCommand(input string) (string, error) {
args, err := shellwords.Split(input)
if err != nil {
return "", err
}
inputCmd := args[0]
return ExecCommand(inputCmd, args[1:]...)
}
func RunBackgroundShell(input string) {
args, err := shellwords.Split(input)
if err != nil {
messenger.Error(err)
return
}
inputCmd := args[0]
messenger.Message("Running...")
go func() {
output, err := RunShellCommand(input)
totalLines := strings.Split(output, "\n")
if len(totalLines) < 3 {
if err == nil {
messenger.Message(inputCmd, " exited without error")
} else {
messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
}
} else {
messenger.Message(output)
}
// We have to make sure to redraw
RedrawAll()
}()
}
func RunInteractiveShell(input string, wait bool, getOutput bool) (string, error) {
args, err := shellwords.Split(input)
if err != nil {
return "", err
}
inputCmd := args[0]
// Shut down the screen because we're going to interact directly with the shell
screen.Fini()
screen = nil
args = args[1:]
// Set up everything for the command
outputBytes := &bytes.Buffer{}
cmd := exec.Command(inputCmd, args...)
cmd.Stdin = os.Stdin
if getOutput {
cmd.Stdout = io.MultiWriter(os.Stdout, outputBytes)
} else {
cmd.Stdout = os.Stdout
}
cmd.Stderr = os.Stderr
// This is a trap for Ctrl-C so that it doesn't kill micro
// Instead we trap Ctrl-C to kill the program we're running
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for range c {
cmd.Process.Kill()
}
}()
cmd.Start()
err = cmd.Wait()
output := outputBytes.String()
if wait {
// This is just so we don't return right away and let the user press enter to return
TermMessage("")
}
// Start the screen back up
InitScreen()
return output, err
}
// HandleShellCommand runs the shell command
// The openTerm argument specifies whether a terminal should be opened (for viewing output
// or interacting with stdin)
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
if !openTerm {
RunBackgroundShell(input)
return ""
} else {
output, _ := RunInteractiveShell(input, waitToFinish, false)
return output
}
}

View File

@@ -1,18 +0,0 @@
// +build linux darwin dragonfly openbsd_amd64 freebsd
package main
import (
"github.com/zyedidia/micro/cmd/micro/shellwords"
)
const TermEmuSupported = true
func RunTermEmulator(input string, wait bool, getOutput bool, callback string) error {
args, err := shellwords.Split(input)
if err != nil {
return err
}
err = CurView().StartTerminal(args, wait, getOutput, callback)
return err
}

View File

@@ -1,11 +0,0 @@
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
package main
import "errors"
const TermEmuSupported = false
func RunTermEmulator(input string, wait bool, getOutput bool) error {
return errors.New("Unsupported operating system")
}

View File

@@ -1,49 +0,0 @@
This is a modified version of `go-shellwords` for the micro editor.
# go-shellwords
[![Coverage Status](https://coveralls.io/repos/mattn/go-shellwords/badge.png?branch=master)](https://coveralls.io/r/mattn/go-shellwords?branch=master)
[![Build Status](https://travis-ci.org/mattn/go-shellwords.svg?branch=master)](https://travis-ci.org/mattn/go-shellwords)
Parse line as shell words.
## Usage
```go
args, err := shellwords.Parse("./foo --bar=baz")
// args should be ["./foo", "--bar=baz"]
```
```go
os.Setenv("FOO", "bar")
p := shellwords.NewParser()
p.ParseEnv = true
args, err := p.Parse("./foo $FOO")
// args should be ["./foo", "bar"]
```
```go
p := shellwords.NewParser()
p.ParseBacktick = true
args, err := p.Parse("./foo `echo $SHELL`")
// args should be ["./foo", "/bin/bash"]
```
```go
shellwords.ParseBacktick = true
p := shellwords.NewParser()
args, err := p.Parse("./foo `echo $SHELL`")
// args should be ["./foo", "/bin/bash"]
```
# Thanks
This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
# License
under the MIT License: http://mattn.mit-license.org/2017
# Author
Yasuhiro Matsumoto (a.k.a mattn)

View File

@@ -1,180 +0,0 @@
package shellwords
import (
"bytes"
"errors"
"os"
"regexp"
)
var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
func isSpace(r rune) bool {
switch r {
case ' ', '\t', '\r', '\n':
return true
}
return false
}
func replaceEnv(s string) string {
return envRe.ReplaceAllStringFunc(s, func(s string) string {
s = s[1:]
if s[0] == '{' {
s = s[1 : len(s)-1]
}
return os.Getenv(s)
})
}
type Parser struct {
Position int
}
func NewParser() *Parser {
return &Parser{0}
}
func (p *Parser) Parse(line string) ([]string, error) {
args := []string{}
buf := ""
var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
backtick := ""
pos := -1
got := false
loop:
for i, r := range line {
if escaped {
buf += string(r)
escaped = false
continue
}
if r == '\\' {
if singleQuoted {
buf += string(r)
} else {
escaped = true
}
continue
}
if isSpace(r) {
if singleQuoted || doubleQuoted || backQuote || dollarQuote {
buf += string(r)
backtick += string(r)
} else if got {
buf = replaceEnv(buf)
args = append(args, buf)
buf = ""
got = false
}
continue
}
switch r {
case '`':
if !singleQuoted && !doubleQuoted && !dollarQuote {
if backQuote {
out, err := shellRun(backtick)
if err != nil {
return nil, err
}
buf = out
}
backtick = ""
backQuote = !backQuote
continue
backtick = ""
backQuote = !backQuote
}
case ')':
if !singleQuoted && !doubleQuoted && !backQuote {
if dollarQuote {
out, err := shellRun(backtick)
if err != nil {
return nil, err
}
buf = out
}
backtick = ""
dollarQuote = !dollarQuote
continue
backtick = ""
dollarQuote = !dollarQuote
}
case '(':
if !singleQuoted && !doubleQuoted && !backQuote {
if !dollarQuote && len(buf) > 0 && buf == "$" {
dollarQuote = true
buf += "("
continue
} else {
return nil, errors.New("invalid command line string")
}
}
case '"':
if !singleQuoted && !dollarQuote {
doubleQuoted = !doubleQuoted
continue
}
case '\'':
if !doubleQuoted && !dollarQuote {
singleQuoted = !singleQuoted
continue
}
case ';', '&', '|', '<', '>':
if !(escaped || singleQuoted || doubleQuoted || backQuote) {
pos = i
break loop
}
}
got = true
buf += string(r)
if backQuote || dollarQuote {
backtick += string(r)
}
}
buf = replaceEnv(buf)
args = append(args, buf)
if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
return nil, errors.New("invalid command line string")
}
p.Position = pos
return args, nil
}
func Split(line string) ([]string, error) {
return NewParser().Parse(line)
}
func Join(args ...string) string {
var buf bytes.Buffer
for i, w := range args {
if i != 0 {
buf.WriteByte(' ')
}
if w == "" {
buf.WriteString("''")
continue
}
for _, b := range w {
switch b {
case ' ', '\t', '\r', '\n':
buf.WriteByte('\\')
buf.WriteString(string(b))
default:
buf.WriteString(string(b))
}
}
}
return buf.String()
}

View File

@@ -1,229 +0,0 @@
package shellwords
import (
"os"
"reflect"
"testing"
)
var testcases = []struct {
line string
expected []string
}{
{`var --bar=baz`, []string{`var`, `--bar=baz`}},
{`var --bar="baz"`, []string{`var`, `--bar=baz`}},
{`var "--bar=baz"`, []string{`var`, `--bar=baz`}},
{`var "--bar='baz'"`, []string{`var`, `--bar='baz'`}},
{"var --bar=`baz`", []string{`var`, "--bar=`baz`"}},
{`var "--bar=\"baz'"`, []string{`var`, `--bar="baz'`}},
{`var "--bar=\'baz\'"`, []string{`var`, `--bar='baz'`}},
{`var --bar='\'`, []string{`var`, `--bar=\`}},
{`var "--bar baz"`, []string{`var`, `--bar baz`}},
{`var --"bar baz"`, []string{`var`, `--bar baz`}},
{`var --"bar baz"`, []string{`var`, `--bar baz`}},
}
func TestSimple(t *testing.T) {
for _, testcase := range testcases {
args, err := Parse(testcase.line)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(args, testcase.expected) {
t.Fatalf("Expected %#v, but %#v:", testcase.expected, args)
}
}
}
func TestError(t *testing.T) {
_, err := Parse("foo '")
if err == nil {
t.Fatal("Should be an error")
}
_, err = Parse(`foo "`)
if err == nil {
t.Fatal("Should be an error")
}
_, err = Parse("foo `")
if err == nil {
t.Fatal("Should be an error")
}
}
func TestLastSpace(t *testing.T) {
args, err := Parse("foo bar\\ ")
if err != nil {
t.Fatal(err)
}
if len(args) != 2 {
t.Fatal("Should have two elements")
}
if args[0] != "foo" {
t.Fatal("1st element should be `foo`")
}
if args[1] != "bar " {
t.Fatal("1st element should be `bar `")
}
}
func TestBacktick(t *testing.T) {
goversion, err := shellRun("go version")
if err != nil {
t.Fatal(err)
}
parser := NewParser()
parser.ParseBacktick = true
args, err := parser.Parse("echo `go version`")
if err != nil {
t.Fatal(err)
}
expected := []string{"echo", goversion}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
args, err = parser.Parse(`echo $(echo foo)`)
if err != nil {
t.Fatal(err)
}
expected = []string{"echo", "foo"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
parser.ParseBacktick = false
args, err = parser.Parse(`echo $(echo "foo")`)
expected = []string{"echo", `$(echo "foo")`}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
args, err = parser.Parse("echo $(`echo1)")
if err != nil {
t.Fatal(err)
}
expected = []string{"echo", "$(`echo1)"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
}
func TestBacktickError(t *testing.T) {
parser := NewParser()
parser.ParseBacktick = true
_, err := parser.Parse("echo `go Version`")
if err == nil {
t.Fatal("Should be an error")
}
expected := "exit status 2:go: unknown subcommand \"Version\"\nRun 'go help' for usage.\n"
if expected != err.Error() {
t.Fatalf("Expected %q, but %q", expected, err.Error())
}
_, err = parser.Parse(`echo $(echo1)`)
if err == nil {
t.Fatal("Should be an error")
}
_, err = parser.Parse(`echo $(echo1`)
if err == nil {
t.Fatal("Should be an error")
}
_, err = parser.Parse(`echo $ (echo1`)
if err == nil {
t.Fatal("Should be an error")
}
_, err = parser.Parse(`echo (echo1`)
if err == nil {
t.Fatal("Should be an error")
}
_, err = parser.Parse(`echo )echo1`)
if err == nil {
t.Fatal("Should be an error")
}
}
func TestEnv(t *testing.T) {
os.Setenv("FOO", "bar")
parser := NewParser()
parser.ParseEnv = true
args, err := parser.Parse("echo $FOO")
if err != nil {
t.Fatal(err)
}
expected := []string{"echo", "bar"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
}
func TestNoEnv(t *testing.T) {
parser := NewParser()
parser.ParseEnv = true
args, err := parser.Parse("echo $BAR")
if err != nil {
t.Fatal(err)
}
expected := []string{"echo", ""}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
}
func TestDupEnv(t *testing.T) {
os.Setenv("FOO", "bar")
os.Setenv("FOO_BAR", "baz")
parser := NewParser()
parser.ParseEnv = true
args, err := parser.Parse("echo $$FOO$")
if err != nil {
t.Fatal(err)
}
expected := []string{"echo", "$bar$"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
args, err = parser.Parse("echo $${FOO_BAR}$")
if err != nil {
t.Fatal(err)
}
expected = []string{"echo", "$baz$"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
}
func TestHaveMore(t *testing.T) {
parser := NewParser()
parser.ParseEnv = true
line := "echo foo; seq 1 10"
args, err := parser.Parse(line)
if err != nil {
t.Fatalf(err.Error())
}
expected := []string{"echo", "foo"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
if parser.Position == 0 {
t.Fatalf("Commands should be remaining")
}
line = string([]rune(line)[parser.Position+1:])
args, err = parser.Parse(line)
if err != nil {
t.Fatalf(err.Error())
}
expected = []string{"seq", "1", "10"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
if parser.Position > 0 {
t.Fatalf("Commands should not be remaining")
}
}

View File

@@ -1,22 +0,0 @@
// +build !windows
package shellwords
import (
"errors"
"os"
"os/exec"
"strings"
)
func shellRun(line string) (string, error) {
shell := os.Getenv("SHELL")
b, err := exec.Command(shell, "-c", line).Output()
if err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
b = eerr.Stderr
}
return "", errors.New(err.Error() + ":" + string(b))
}
return strings.TrimSpace(string(b)), nil
}

View File

@@ -1,20 +0,0 @@
package shellwords
import (
"errors"
"os"
"os/exec"
"strings"
)
func shellRun(line string) (string, error) {
shell := os.Getenv("COMSPEC")
b, err := exec.Command(shell, "/c", line).Output()
if err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
b = eerr.Stderr
}
return "", errors.New(err.Error() + ":" + string(b))
}
return strings.TrimSpace(string(b)), nil
}

View File

@@ -1,7 +1,6 @@
package main
import (
"path"
"strconv"
)
@@ -15,20 +14,13 @@ type Statusline struct {
// Display draws the statusline to the screen
func (sline *Statusline) Display() {
if messenger.hasPrompt && !GetGlobalOption("infobar").(bool) {
return
}
// We'll draw the line at the lowest line in the view
y := sline.view.Height + sline.view.y
file := sline.view.Buf.GetName()
if sline.view.Buf.Settings["basename"].(bool) {
file = path.Base(file)
}
// If the buffer is dirty (has been modified) write a little '+'
if sline.view.Buf.Modified() {
if sline.view.Buf.IsModified {
file += " +"
}
@@ -44,27 +36,13 @@ func (sline *Statusline) Display() {
// Add the filetype
file += " " + sline.view.Buf.FileType()
file += " " + sline.view.Buf.Settings["fileformat"].(string)
rightText := ""
if len(kmenuBinding) > 0 {
if globalSettings["keymenu"].(bool) {
rightText += kmenuBinding + ": hide bindings"
} else {
rightText += kmenuBinding + ": show bindings"
}
}
if len(helpBinding) > 0 {
if len(kmenuBinding) > 0 {
rightText += ", "
}
rightText = helpBinding + " for help "
if sline.view.Type == vtHelp {
rightText += helpBinding + ": close help"
} else {
rightText += helpBinding + ": open help"
rightText = helpBinding + " to close help "
}
}
rightText += " "
statusLineStyle := defStyle.Reverse(true)
if style, ok := colorscheme["statusline"]; ok {
@@ -73,12 +51,6 @@ func (sline *Statusline) Display() {
// Maybe there is a unicode filename?
fileRunes := []rune(file)
if sline.view.Type == vtTerm {
fileRunes = []rune(sline.view.term.title)
rightText = ""
}
viewX := sline.view.x
if viewX != 0 {
screen.SetContent(viewX, y, ' ', nil, statusLineStyle)

View File

@@ -8,8 +8,6 @@ import (
var tabBarOffset int
// A Tab holds an array of views and a splitTree to determine how the
// views should be arranged
type Tab struct {
// This contains all the views in this tab
// There is generally only one view per tab, but you can have
@@ -38,9 +36,6 @@ func NewTabFromView(v *View) *Tab {
if globalSettings["infobar"].(bool) {
t.tree.height--
}
if globalSettings["keymenu"].(bool) {
t.tree.height -= 2
}
t.Resize()
@@ -55,13 +50,10 @@ func (t *Tab) SetNum(num int) {
}
}
// Cleanup cleans up the tree (for example if views have closed)
func (t *Tab) Cleanup() {
t.tree.Cleanup()
}
// Resize handles a resize event from the terminal and resizes
// all child views correctly
func (t *Tab) Resize() {
w, h := screen.Size()
t.tree.width = w
@@ -70,17 +62,11 @@ func (t *Tab) Resize() {
if globalSettings["infobar"].(bool) {
t.tree.height--
}
if globalSettings["keymenu"].(bool) {
t.tree.height -= 2
}
t.tree.ResizeSplits()
for i, v := range t.views {
v.Num = i
if v.Type == vtTerm {
v.term.Resize(v.Width, v.Height)
}
}
}
@@ -105,7 +91,7 @@ func TabbarString() (string, map[int]int) {
}
buf := t.views[t.CurView].Buf
str += buf.GetName()
if buf.Modified() {
if buf.IsModified {
str += " +"
}
if i == curTab {

View File

@@ -1,228 +0,0 @@
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/tcell"
"github.com/zyedidia/terminal"
)
const (
VTIdle = iota // Waiting for a new command
VTRunning // Currently running a command
VTDone // Finished running a command
)
// A Terminal holds information for the terminal emulator
type Terminal struct {
state terminal.State
view *View
vtOld ViewType
term *terminal.VT
title string
status int
selection [2]Loc
wait bool
getOutput bool
output *bytes.Buffer
callback string
}
// HasSelection returns whether this terminal has a valid selection
func (t *Terminal) HasSelection() bool {
return t.selection[0] != t.selection[1]
}
// GetSelection returns the selected text
func (t *Terminal) GetSelection(width int) string {
start := t.selection[0]
end := t.selection[1]
if start.GreaterThan(end) {
start, end = end, start
}
var ret string
var l Loc
for y := start.Y; y <= end.Y; y++ {
for x := 0; x < width; x++ {
l.X, l.Y = x, y
if l.GreaterEqual(start) && l.LessThan(end) {
c, _, _ := t.state.Cell(x, y)
ret += string(c)
}
}
}
return ret
}
// Start begins a new command in this terminal with a given view
func (t *Terminal) Start(execCmd []string, view *View, getOutput bool) error {
if len(execCmd) <= 0 {
return nil
}
cmd := exec.Command(execCmd[0], execCmd[1:]...)
t.output = nil
if getOutput {
t.output = bytes.NewBuffer([]byte{})
}
term, _, err := terminal.Start(&t.state, cmd, t.output)
if err != nil {
return err
}
t.term = term
t.view = view
t.getOutput = getOutput
t.vtOld = view.Type
t.status = VTRunning
t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid)
go func() {
for {
err := term.Parse()
if err != nil {
fmt.Fprintln(os.Stderr, "[Press enter to close]")
break
}
updateterm <- true
}
closeterm <- view.Num
}()
return nil
}
// Resize informs the terminal of a resize event
func (t *Terminal) Resize(width, height int) {
t.term.Resize(width, height)
}
// HandleEvent handles a tcell event by forwarding it to the terminal emulator
// If the event is a mouse event and the program running in the emulator
// does not have mouse support, the emulator will support selections and
// copy-paste
func (t *Terminal) HandleEvent(event tcell.Event) {
if e, ok := event.(*tcell.EventKey); ok {
if t.status == VTDone {
switch e.Key() {
case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter:
t.Close()
t.view.Type = vtDefault
default:
}
}
if e.Key() == tcell.KeyCtrlC && t.HasSelection() {
clipboard.WriteAll(t.GetSelection(t.view.Width), "clipboard")
messenger.Message("Copied selection to clipboard")
} else if t.status != VTDone {
t.WriteString(event.EscSeq())
}
} else if e, ok := event.(*tcell.EventMouse); !ok || t.state.Mode(terminal.ModeMouseMask) {
t.WriteString(event.EscSeq())
} else {
x, y := e.Position()
x -= t.view.x
y += t.view.y
if e.Buttons() == tcell.Button1 {
if !t.view.mouseReleased {
// drag
t.selection[1].X = x
t.selection[1].Y = y
} else {
t.selection[0].X = x
t.selection[0].Y = y
t.selection[1].X = x
t.selection[1].Y = y
}
t.view.mouseReleased = false
} else if e.Buttons() == tcell.ButtonNone {
if !t.view.mouseReleased {
t.selection[1].X = x
t.selection[1].Y = y
}
t.view.mouseReleased = true
}
}
}
// Stop stops execution of the terminal and sets the status
// to VTDone
func (t *Terminal) Stop() {
t.term.File().Close()
t.term.Close()
if t.wait {
t.status = VTDone
} else {
t.Close()
t.view.Type = t.vtOld
}
}
// Close sets the status to VTIdle indicating that the terminal
// is ready for a new command to execute
func (t *Terminal) Close() {
t.status = VTIdle
// call the lua function that the user has given as a callback
if t.getOutput {
_, err := Call(t.callback, t.output.String())
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
}
}
}
// WriteString writes a given string to this terminal's pty
func (t *Terminal) WriteString(str string) {
t.term.File().WriteString(str)
}
// Display displays this terminal in a view
func (t *Terminal) Display() {
divider := 0
if t.view.x != 0 {
divider = 1
dividerStyle := defStyle
if style, ok := colorscheme["divider"]; ok {
dividerStyle = style
}
for i := 0; i < t.view.Height; i++ {
screen.SetContent(t.view.x, t.view.y+i, '|', nil, dividerStyle.Reverse(true))
}
}
t.state.Lock()
defer t.state.Unlock()
var l Loc
for y := 0; y < t.view.Height; y++ {
for x := 0; x < t.view.Width; x++ {
l.X, l.Y = x, y
c, f, b := t.state.Cell(x, y)
fg, bg := int(f), int(b)
if f == terminal.DefaultFG {
fg = int(tcell.ColorDefault)
}
if b == terminal.DefaultBG {
bg = int(tcell.ColorDefault)
}
st := tcell.StyleDefault.Foreground(GetColor256(int(fg))).Background(GetColor256(int(bg)))
if l.LessThan(t.selection[1]) && l.GreaterEqual(t.selection[0]) || l.LessThan(t.selection[0]) && l.GreaterEqual(t.selection[1]) {
st = st.Reverse(true)
}
screen.SetContent(t.view.x+x+divider, t.view.y+y, c, nil, st)
}
}
if t.state.CursorVisible() && tabs[curTab].CurView == t.view.Num {
curx, cury := t.state.Cursor()
screen.ShowCursor(curx+t.view.x+divider, cury+t.view.y)
}
}

View File

@@ -1,6 +1,7 @@
package main
import (
"bytes"
"os"
"path/filepath"
"reflect"
@@ -11,7 +12,6 @@ import (
"unicode/utf8"
"github.com/mattn/go-runewidth"
homedir "github.com/mitchellh/go-homedir"
)
// Util.go is a collection of utility functions that are used throughout
@@ -36,7 +36,11 @@ func NumOccurrences(s string, c byte) int {
// Spaces returns a string with n spaces
func Spaces(n int) string {
return strings.Repeat(" ", n)
var str string
for i := 0; i < n; i++ {
str += " "
}
return str
}
// Min takes the min of two ints
@@ -55,7 +59,6 @@ func Max(a, b int) int {
return b
}
// FSize gets the size of a file
func FSize(f *os.File) int64 {
fi, _ := f.Stat()
// get the size
@@ -246,7 +249,6 @@ func lcs(a, b string) string {
return lcs
}
// CommonSubstring gets a common substring among the inputs
func CommonSubstring(arr ...string) string {
commonStr := arr[0]
@@ -275,17 +277,93 @@ func ShortFuncName(i interface{}) string {
return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
}
// 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 '~'.
func ReplaceHome(path string) string {
if !strings.HasPrefix(path, "~") {
return path
// SplitCommandArgs separates multiple command arguments which may be quoted.
// The returned slice contains at least one string
func SplitCommandArgs(input string) []string {
var result []string
var curQuote *bytes.Buffer
curArg := new(bytes.Buffer)
escape := false
finishQuote := func() {
if curQuote == nil {
return
}
str := curQuote.String()
if unquoted, err := strconv.Unquote(str); err == nil {
str = unquoted
}
curArg.WriteString(str)
curQuote = nil
}
home, err := homedir.Dir()
if err != nil {
messenger.Error("Could not find home directory: ", err)
return path
appendResult := func() {
finishQuote()
escape = false
str := curArg.String()
result = append(result, str)
curArg.Reset()
}
return strings.Replace(path, "~", home, 1)
for _, r := range input {
if r == ' ' && curQuote == nil {
appendResult()
} else {
runeHandled := false
appendRuneToBuff := func() {
if curQuote != nil {
curQuote.WriteRune(r)
} else {
curArg.WriteRune(r)
}
runeHandled = true
}
if r == '"' && curQuote == nil {
curQuote = new(bytes.Buffer)
appendRuneToBuff()
} else {
if curQuote != nil && !escape {
if r == '"' {
appendRuneToBuff()
finishQuote()
} else if r == '\\' {
appendRuneToBuff()
escape = true
continue
}
}
}
if !runeHandled {
appendRuneToBuff()
}
}
escape = false
}
appendResult()
return result
}
// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
func JoinCommandArgs(args ...string) string {
buf := new(bytes.Buffer)
first := true
for _, arg := range args {
if first {
first = false
} else {
buf.WriteRune(' ')
}
quoted := strconv.Quote(arg)
if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
buf.WriteString(quoted)
} else {
buf.WriteString(arg)
}
}
return buf.String()
}

View File

@@ -1,6 +1,7 @@
package main
import (
"reflect"
"testing"
)
@@ -66,6 +67,56 @@ func TestIsWordChar(t *testing.T) {
}
}
func TestJoinAndSplitCommandArgs(t *testing.T) {
tests := []struct {
Query []string
Wanted string
}{
{[]string{`test case`}, `"test case"`},
{[]string{`quote "test"`}, `"quote \"test\""`},
{[]string{`slash\\\ test`}, `"slash\\\\\\ test"`},
{[]string{`path 1`, `path\" 2`}, `"path 1" "path\\\" 2"`},
{[]string{`foo`}, `foo`},
{[]string{`foo\"bar`}, `"foo\\\"bar"`},
{[]string{``}, ``},
{[]string{`"`}, `"\""`},
{[]string{`a`, ``}, `a `},
{[]string{``, ``, ``, ``}, ` `},
{[]string{"\n"}, `"\n"`},
{[]string{"foo\tbar"}, `"foo\tbar"`},
}
for i, test := range tests {
if result := JoinCommandArgs(test.Query...); test.Wanted != result {
t.Errorf("JoinCommandArgs failed at Test %d\nGot: %q", i, result)
}
if result := SplitCommandArgs(test.Wanted); !reflect.DeepEqual(test.Query, result) {
t.Errorf("SplitCommandArgs failed at Test %d\nGot: `%q`", i, result)
}
}
splitTests := []struct {
Query string
Wanted []string
}{
{`"hallo""Welt"`, []string{`halloWelt`}},
{`"hallo" "Welt"`, []string{`hallo`, `Welt`}},
{`\"`, []string{`\"`}},
{`"foo`, []string{`"foo`}},
{`"foo"`, []string{`foo`}},
{`"\"`, []string{`"\"`}},
{`"C:\\"foo.txt`, []string{`C:\foo.txt`}},
{`"\n"new"\n"line`, []string{"\nnew\nline"}},
}
for i, test := range splitTests {
if result := SplitCommandArgs(test.Query); !reflect.DeepEqual(test.Wanted, result) {
t.Errorf("SplitCommandArgs failed at Split-Test %d\nGot: `%q`", i, result)
}
}
}
func TestStringWidth(t *testing.T) {
tabsize := 4
if w := StringWidth("1\t2", tabsize); w != 5 {

View File

@@ -1,21 +1,19 @@
package main
import (
"fmt"
"os"
"reflect"
"strconv"
"strings"
"time"
"github.com/mitchellh/go-homedir"
"github.com/zyedidia/tcell"
)
// The ViewType defines what kind of view this is
type ViewType struct {
Kind int
Readonly bool // The file cannot be edited
Scratch bool // The file cannot be saved
kind int
readonly bool // The file cannot be edited
scratch bool // The file cannot be saved
}
var (
@@ -23,8 +21,6 @@ var (
vtHelp = ViewType{1, true, true}
vtLog = ViewType{2, true, true}
vtScratch = ViewType{3, false, true}
vtRaw = ViewType{4, true, true}
vtTerm = ViewType{5, true, true}
)
// The View struct stores information about a view into a buffer.
@@ -66,7 +62,7 @@ type View struct {
// The buffer
Buf *Buffer
// The statusline
sline *Statusline
sline Statusline
// Since tcell doesn't differentiate between a mouse release event
// and a mouse move event with no keys pressed, we need to keep
@@ -74,12 +70,9 @@ type View struct {
// mouse release events
mouseReleased bool
// We need to keep track of insert key press toggle
isOverwriteMode bool
// This stores when the last click was
// This is useful for detecting double and triple clicks
lastClickTime time.Time
lastLoc Loc
// lastCutTime stores when the last ctrl+k was issued.
// It is used for clearing the clipboard to replace it with fresh cut lines.
@@ -95,16 +88,9 @@ type View struct {
// Same here, just to keep track for mouse move events
tripleClick bool
// The cellview used for displaying and syntax highlighting
cellview *CellView
splitNode *LeafNode
// The scrollbar
scrollbar *ScrollBar
// Virtual terminal
term *Terminal
}
// NewView returns a new fullscreen view
@@ -130,11 +116,7 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
v.messages = make(map[string][]GutterMessage)
v.sline = &Statusline{
view: v,
}
v.scrollbar = &ScrollBar{
v.sline = Statusline{
view: v,
}
@@ -142,8 +124,6 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
v.Height--
}
v.term = new(Terminal)
for pl := range loadedPlugins {
_, err := Call(pl+".onViewOpen", v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
@@ -164,24 +144,6 @@ func (v *View) ToggleStatusLine() {
}
}
// StartTerminal execs a command in this view
func (v *View) StartTerminal(execCmd []string, wait bool, getOutput bool, luaCallback string) error {
err := v.term.Start(execCmd, v, getOutput)
v.term.wait = wait
v.term.callback = luaCallback
if err == nil {
v.term.Resize(v.Width, v.Height)
v.Type = vtTerm
}
return err
}
// CloseTerminal shuts down the tty running in this view
// and returns it to the default view type
func (v *View) CloseTerminal() {
v.term.Stop()
}
// ToggleTabbar creates an extra row for the tabbar if necessary
func (v *View) ToggleTabbar() {
if len(tabs) > 1 {
@@ -207,7 +169,7 @@ func (v *View) paste(clip string) {
}
clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
v.Buf.Insert(v.Cursor.Loc, clip)
// v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
v.freshClip = false
messenger.Message("Pasted clipboard")
}
@@ -236,7 +198,7 @@ func (v *View) ScrollDown(n int) {
// If there are unsaved changes, the user will be asked if the view can be closed
// causing them to lose the unsaved changes
func (v *View) CanClose() bool {
if v.Type == vtDefault && v.Buf.Modified() {
if v.Type == vtDefault && v.Buf.IsModified {
var choice bool
var canceled bool
if v.Buf.Settings["autosave"].(bool) {
@@ -248,12 +210,15 @@ func (v *View) CanClose() bool {
//if char == 'y' {
if choice {
v.Save(true)
return true
} else {
return true
}
} else {
return false
}
} else {
return true
}
return true
return false
}
// OpenBuffer opens a new buffer in this view.
@@ -273,18 +238,13 @@ func (v *View) OpenBuffer(buf *Buffer) {
// Set mouseReleased to true because we assume the mouse is not being pressed when
// the editor is opened
v.mouseReleased = true
// Set isOverwriteMode to false, because we assume we are in the default mode when editor
// is opened
v.isOverwriteMode = false
v.lastClickTime = time.Time{}
GlobalPluginCall("onBufferOpen", v.Buf)
GlobalPluginCall("onViewOpen", v)
}
// Open opens the given file in the view
func (v *View) Open(filename string) {
filename = ReplaceHome(filename)
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
@@ -325,7 +285,7 @@ func (v *View) ReOpen() {
// HSplit opens a horizontal split with the given buffer
func (v *View) HSplit(buf *Buffer) {
i := 0
if v.Buf.Settings["splitbottom"].(bool) {
if v.Buf.Settings["splitBottom"].(bool) {
i = 1
}
v.splitNode.HSplit(buf, v.Num+i)
@@ -334,7 +294,7 @@ func (v *View) HSplit(buf *Buffer) {
// VSplit opens a vertical split with the given buffer
func (v *View) VSplit(buf *Buffer) {
i := 0
if v.Buf.Settings["splitright"].(bool) {
if v.Buf.Settings["splitRight"].(bool) {
i = 1
}
v.splitNode.VSplit(buf, v.Num+i)
@@ -395,10 +355,6 @@ func (v *View) GetSoftWrapLocation(vx, vy int) (int, int) {
return 0, 0
}
// Bottomline returns the line number of the lowest line in the view
// You might think that this is obviously just v.Topline + v.Height
// but if softwrap is enabled things get complicated since one buffer
// line can take up multiple lines in the view
func (v *View) Bottomline() int {
if !v.Buf.Settings["softwrap"].(bool) {
return v.Topline + v.Height
@@ -470,31 +426,6 @@ func (v *View) Relocate() bool {
return ret
}
// GetMouseClickLocation gets the location in the buffer from a mouse click
// on the screen
func (v *View) GetMouseClickLocation(x, y int) (int, int) {
x -= v.lineNumOffset - v.leftCol + v.x
y += v.Topline - v.y
if y-v.Topline > v.Height-1 {
v.ScrollDown(1)
y = v.Height + v.Topline - 1
}
if y < 0 {
y = 0
}
if x < 0 {
x = 0
}
newX, newY := v.GetSoftWrapLocation(x, y)
if newX > Count(v.Buf.Line(newY)) {
newX = Count(v.Buf.Line(newY))
}
return newX, newY
}
// MoveToMouseClick moves the cursor to location x, y assuming x, y were given
// by a mouse click
func (v *View) MoveToMouseClick(x, y int) {
@@ -510,6 +441,7 @@ func (v *View) MoveToMouseClick(x, y int) {
}
x, y = v.GetSoftWrapLocation(x, y)
// x = v.Cursor.GetCharPosInLine(y, x)
if x > Count(v.Buf.Line(y)) {
x = Count(v.Buf.Line(y))
}
@@ -518,68 +450,8 @@ func (v *View) MoveToMouseClick(x, y int) {
v.Cursor.LastVisualX = v.Cursor.GetVisualX()
}
// Execute actions executes the supplied actions
func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
relocate := false
readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"}
for _, action := range actions {
readonlyBindingsResult := false
funcName := ShortFuncName(action)
if v.Type.Readonly == true {
// check for readonly and if true only let key bindings get called if they do not change the contents.
for _, readonlyBindings := range readonlyBindingsList {
if strings.Contains(funcName, readonlyBindings) {
readonlyBindingsResult = true
}
}
}
if !readonlyBindingsResult {
// call the key binding
relocate = action(v, true) || relocate
// Macro
if funcName != "ToggleMacro" && funcName != "PlayMacro" {
if recordingMacro {
curMacro = append(curMacro, action)
}
}
}
}
return relocate
}
// SetCursor sets the view's and buffer's cursor
func (v *View) SetCursor(c *Cursor) bool {
if c == nil {
return false
}
v.Cursor = c
v.Buf.curCursor = c.Num
return true
}
// HandleEvent handles an event passed by the main loop
func (v *View) HandleEvent(event tcell.Event) {
if v.Type == vtTerm {
v.term.HandleEvent(event)
return
}
if v.Type == vtRaw {
v.Buf.Insert(v.Cursor.Loc, reflect.TypeOf(event).String()[7:])
v.Buf.Insert(v.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
switch e := event.(type) {
case *tcell.EventKey:
if e.Key() == tcell.KeyCtrlQ {
v.Quit(true)
}
}
return
}
// This bool determines whether the view is relocated at the end of the function
// By default it's true because most events should cause a relocate
relocate := true
@@ -587,138 +459,149 @@ func (v *View) HandleEvent(event tcell.Event) {
v.Buf.CheckModTime()
switch e := event.(type) {
case *tcell.EventRaw:
for key, actions := range bindings {
if key.keyCode == -1 {
if e.EscSeq() == key.escape {
for _, c := range v.Buf.cursors {
ok := v.SetCursor(c)
if !ok {
break
}
relocate = false
relocate = v.ExecuteActions(actions) || relocate
}
v.SetCursor(&v.Buf.Cursor)
v.Buf.MergeCursors()
break
}
}
}
case *tcell.EventKey:
// Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune
isBinding := false
for key, actions := range bindings {
if e.Key() == key.keyCode {
if e.Key() == tcell.KeyRune {
if e.Rune() != key.r {
continue
}
}
if e.Modifiers() == key.modifiers {
for _, c := range v.Buf.cursors {
ok := v.SetCursor(c)
if !ok {
break
readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"}
if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
for key, actions := range bindings {
if e.Key() == key.keyCode {
if e.Key() == tcell.KeyRune {
if e.Rune() != key.r {
continue
}
}
if e.Modifiers() == key.modifiers {
relocate = false
isBinding = true
relocate = v.ExecuteActions(actions) || relocate
for _, action := range actions {
readonlyBindingsResult := false
funcName := ShortFuncName(action)
if v.Type.readonly == true {
// check for readonly and if true only let key bindings get called if they do not change the contents.
for _, readonlyBindings := range readonlyBindingsList {
if strings.Contains(funcName, readonlyBindings) {
readonlyBindingsResult = true
}
}
}
if !readonlyBindingsResult {
// call the key binding
relocate = action(v, true) || relocate
// Macro
if funcName != "ToggleMacro" && funcName != "PlayMacro" {
if recordingMacro {
curMacro = append(curMacro, action)
}
}
}
}
break
}
v.SetCursor(&v.Buf.Cursor)
v.Buf.MergeCursors()
break
}
}
}
if !isBinding && e.Key() == tcell.KeyRune {
// Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
if v.Type.Readonly == false {
for _, c := range v.Buf.cursors {
v.SetCursor(c)
if v.Type.readonly == false {
// Insert a character
if v.Cursor.HasSelection() {
v.Cursor.DeleteSelection()
v.Cursor.ResetSelection()
}
v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
v.Cursor.Right()
// Insert a character
if v.Cursor.HasSelection() {
v.Cursor.DeleteSelection()
v.Cursor.ResetSelection()
}
if v.isOverwriteMode {
next := v.Cursor.Loc
next.X++
v.Buf.Replace(v.Cursor.Loc, next, string(e.Rune()))
} else {
v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
}
for pl := range loadedPlugins {
_, err := Call(pl+".onRune", string(e.Rune()), v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
}
}
if recordingMacro {
curMacro = append(curMacro, e.Rune())
for pl := range loadedPlugins {
_, err := Call(pl+".onRune", string(e.Rune()), v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
}
}
v.SetCursor(&v.Buf.Cursor)
if recordingMacro {
curMacro = append(curMacro, e.Rune())
}
}
}
case *tcell.EventPaste:
// Check viewtype if readonly don't paste (readonly help and log view etc.)
if v.Type.Readonly == false {
if v.Type.readonly == false {
if !PreActionCall("Paste", v) {
break
}
for _, c := range v.Buf.cursors {
v.SetCursor(c)
v.paste(e.Text())
}
v.SetCursor(&v.Buf.Cursor)
v.paste(e.Text())
PostActionCall("Paste", v)
}
case *tcell.EventMouse:
x, y := e.Position()
x -= v.lineNumOffset - v.leftCol + v.x
y += v.Topline - v.y
// Don't relocate for mouse events
relocate = false
button := e.Buttons()
for key, actions := range bindings {
if button == key.buttons && e.Modifiers() == key.modifiers {
for _, c := range v.Buf.cursors {
ok := v.SetCursor(c)
if !ok {
break
}
relocate = v.ExecuteActions(actions) || relocate
}
v.SetCursor(&v.Buf.Cursor)
v.Buf.MergeCursors()
}
}
for key, actions := range mouseBindings {
if button == key.buttons && e.Modifiers() == key.modifiers {
for _, action := range actions {
action(v, true, e)
}
}
}
switch button {
case tcell.Button1:
// Left click
if v.mouseReleased {
v.MoveToMouseClick(x, y)
if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
if v.doubleClick {
// Triple click
v.lastClickTime = time.Now()
v.tripleClick = true
v.doubleClick = false
v.Cursor.SelectLine()
v.Cursor.CopySelection("primary")
} else {
// Double click
v.lastClickTime = time.Now()
v.doubleClick = true
v.tripleClick = false
v.Cursor.SelectWord()
v.Cursor.CopySelection("primary")
}
} else {
v.doubleClick = false
v.tripleClick = false
v.lastClickTime = time.Now()
v.Cursor.OrigSelection[0] = v.Cursor.Loc
v.Cursor.CurSelection[0] = v.Cursor.Loc
v.Cursor.CurSelection[1] = v.Cursor.Loc
}
v.mouseReleased = false
} else if !v.mouseReleased {
v.MoveToMouseClick(x, y)
if v.tripleClick {
v.Cursor.AddLineToSelection()
} else if v.doubleClick {
v.Cursor.AddWordToSelection()
} else {
v.Cursor.SetSelectionEnd(v.Cursor.Loc)
v.Cursor.CopySelection("primary")
}
}
case tcell.Button2:
// Check viewtype if readonly don't paste (readonly help and log view etc.)
if v.Type.readonly == false {
// Middle mouse button was clicked,
// We should paste primary
v.PastePrimary(true)
}
case tcell.ButtonNone:
// Mouse event with no click
if !v.mouseReleased {
// Mouse was just released
x, y := e.Position()
x -= v.lineNumOffset - v.leftCol + v.x
y += v.Topline - v.y
// Relocating here isn't really necessary because the cursor will
// be in the right place from the last mouse event
// However, if we are running in a terminal that doesn't support mouse motion
@@ -732,6 +615,14 @@ func (v *View) HandleEvent(event tcell.Event) {
}
v.mouseReleased = true
}
case tcell.WheelUp:
// Scroll up
scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
v.ScrollUp(scrollspeed)
case tcell.WheelDown:
// Scroll down
scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
v.ScrollDown(scrollspeed)
}
}
@@ -746,10 +637,6 @@ func (v *View) HandleEvent(event tcell.Event) {
}
}
func (v *View) mainCursor() bool {
return v.Buf.curCursor == len(v.Buf.cursors)-1
}
// GutterMessage creates a message in this view's gutter
func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
lineN--
@@ -798,19 +685,13 @@ func (v *View) openHelp(helpPage string) {
}
}
// DisplayView draws the view to the screen
func (v *View) DisplayView() {
if v.Type == vtTerm {
v.term.Display()
return
}
if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 {
v.leftCol = 0
}
if v.Type == vtLog || v.Type == vtRaw {
// Log or raw views should always follow the cursor...
if v.Type == vtLog {
// Log views should always follow the cursor...
v.Relocate()
}
@@ -874,11 +755,11 @@ func (v *View) DisplayView() {
}
colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
if colorcolumn != 0 && xOffset+colorcolumn-v.leftCol < v.Width {
if colorcolumn != 0 {
style := GetColor("color-column")
fg, _, _ := style.Decompose()
st := defStyle.Background(fg)
screen.SetContent(xOffset+colorcolumn-v.leftCol, yOffset+visualLineN, ' ', nil, st)
screen.SetContent(xOffset+colorcolumn, yOffset+visualLineN, ' ', nil, st)
}
screenX = v.x
@@ -982,20 +863,22 @@ func (v *View) DisplayView() {
}
charLoc := char.realLoc
for _, c := range v.Buf.cursors {
v.SetCursor(c)
if v.Cursor.HasSelection() &&
(charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
// The current character is selected
lineStyle = defStyle.Reverse(true)
if v.Cursor.HasSelection() &&
(charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
// The current character is selected
lineStyle = defStyle.Reverse(true)
if style, ok := colorscheme["selection"]; ok {
lineStyle = style
}
if style, ok := colorscheme["selection"]; ok {
lineStyle = style
}
}
v.SetCursor(&v.Buf.Cursor)
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && !cursorSet {
screen.ShowCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y)
cursorSet = true
}
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
!v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
@@ -1006,16 +889,6 @@ func (v *View) DisplayView() {
screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
for i, c := range v.Buf.cursors {
v.SetCursor(c)
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && (!cursorSet || i != 0) {
ShowMultiCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, i)
cursorSet = true
}
}
v.SetCursor(&v.Buf.Cursor)
lastChar = char
}
}
@@ -1026,27 +899,19 @@ func (v *View) DisplayView() {
var cx, cy int
if lastChar != nil {
lastX = xOffset + lastChar.visualLoc.X + lastChar.width
for i, c := range v.Buf.cursors {
v.SetCursor(c)
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
ShowMultiCursor(lastX, yOffset+lastChar.visualLoc.Y, i)
cx, cy = lastX, yOffset+lastChar.visualLoc.Y
}
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
screen.ShowCursor(lastX, yOffset+lastChar.visualLoc.Y)
cx, cy = lastX, yOffset+lastChar.visualLoc.Y
}
v.SetCursor(&v.Buf.Cursor)
realLoc = Loc{lastChar.realLoc.X + 1, realLineN}
realLoc = Loc{lastChar.realLoc.X, realLineN}
visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
} else if len(line) == 0 {
for i, c := range v.Buf.cursors {
v.SetCursor(c)
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
v.Cursor.Y == realLineN {
ShowMultiCursor(xOffset, yOffset+visualLineN, i)
cx, cy = xOffset, yOffset+visualLineN
}
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
v.Cursor.Y == realLineN {
screen.ShowCursor(xOffset, yOffset+visualLineN)
cx, cy = xOffset, yOffset+visualLineN
}
v.SetCursor(&v.Buf.Cursor)
lastX = xOffset
realLoc = Loc{0, realLineN}
visualLoc = Loc{0, visualLineN}
@@ -1066,7 +931,7 @@ func (v *View) DisplayView() {
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
!v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
for i := lastX; i < xOffset+v.Width-v.lineNumOffset; i++ {
for i := lastX; i < xOffset+v.Width; i++ {
style := GetColor("cursor-line")
fg, _, _ := style.Decompose()
style = style.Background(fg)
@@ -1088,18 +953,6 @@ func (v *View) DisplayView() {
}
}
// ShowMultiCursor will display a cursor at a location
// If i == 0 then the terminal cursor will be used
// Otherwise a fake cursor will be drawn at the position
func ShowMultiCursor(x, y, i int) {
if i == 0 {
screen.ShowCursor(x, y)
} else {
r, _, _, _ := screen.GetContent(x, y)
screen.SetContent(x, y, r, nil, defStyle.Reverse(true))
}
}
// Display renders the view, the cursor, and statusline
func (v *View) Display() {
if globalSettings["termtitle"].(bool) {
@@ -1111,11 +964,6 @@ func (v *View) Display() {
screen.HideCursor()
}
_, screenH := screen.Size()
if v.Buf.Settings["scrollbar"].(bool) {
v.scrollbar.Display()
}
if v.Buf.Settings["statusline"].(bool) {
v.sline.Display()
} else if (v.y + v.Height) != screenH-1 {

View File

@@ -1,23 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<component>
<id>com.github.zyedidia.micro</id>
<name>Micro Text Editor</name>
<summary>A modern and intuitive terminal-based text editor</summary>
<metadata_license>MIT</metadata_license>
<categories>
<category>Development</category>
<category>TextEditor</category>
</categories>
<provides>
<binary>micro</binary>
</provides>
<developer_name>Zachary Yedidia</developer_name>
<screenshots>
<screenshot type="default">
<caption>Micro Text Editor editing its source code.</caption>
<image type="source">https://raw.githubusercontent.com/zyedidia/micro/master/assets/micro-solarized.png</image>
</screenshot>
</screenshots>
<url type="homepage">https://micro-editor.github.io</url>
<url type="bugtracker">https://github.com/zyedidia/micro/issues</url>
<id>com.github.zyedidia.micro</id>
<name>Micro Text Editor</name>
<summary>A modern and intuitive terminal-based text editor</summary>
<url type="homepage">https://micro-editor.github.io</url>
<url type="bugtracker">https://github.com/zyedidia/micro</url>
<metadata_license>MIT</metadata_license>
<categories>
<category>Development</category>
<category>TextEditor</category>
</categories>
<provides>
<binary>micro</binary>
</provides>
<releases>
<release version="1.2.0" date="2017-05-28" />
</releases>
<developer_name>Zachary Yedidia</developer_name>
<screenshots>
<screenshot type="default">
<caption>Micro Text Editor editing its source code.</caption>
<image type="source">https://raw.githubusercontent.com/zyedidia/micro/master/assets/micro-solarized.png</image>
</screenshot>
</screenshots>
</component>

View File

@@ -1,27 +0,0 @@
color-link default "#CCCCCC,#242424"
color-link comment "#707070,#242424"
color-link identifier "#FFC66D,#242424"
color-link constant "#7A9EC2,#242424"
color-link constant.string "#6A8759,#242424"
color-link constant.string.char "#6A8759,#242424"
color-link statement "#CC8242,#242424"
color-link symbol "#CCCCCC,#242424"
color-link preproc "#CC8242,#242424"
color-link type "#CC8242,#242424"
color-link special "#CC8242,#242424"
color-link underlined "#D33682,#242424"
color-link error "bold #CB4B16,#242424"
color-link todo "bold #D33682,#242424"
color-link statusline "#242424,#CCCCCC"
color-link tabbar "#242424,#CCCCCC"
color-link indent-char "#4F4F4F,#242424"
color-link line-number "#666666,#242424"
color-link current-line-number "#666666,#242424"
color-link gutter-error "#CB4B16,#242424"
color-link gutter-warning "#E6DB74,#242424"
color-link cursor-line "#2C2C2C"
color-link color-column "#2C2C2C"
#No extended types; Plain brackets.
color-link type.extended "default"
#color-link symbol.brackets "default"
color-link symbol.tag "#AE81FF,#242424"

View File

@@ -1,3 +1,4 @@
# This is the monokai colorscheme
color-link default "#F8F8F2,#282828"
color-link comment "#75715E,#282828"
color-link identifier "#66D9EF,#282828"
@@ -13,7 +14,7 @@ color-link underlined "#D33682,#282828"
color-link error "bold #CB4B16,#282828"
color-link todo "bold #D33682,#282828"
color-link statusline "#282828,#F8F8F2"
color-link tabbar "#282828,#F8F8F2"
color-link tabbar "#282828,#f8f8f2"
color-link indent-char "#505050,#282828"
color-link line-number "#AAAAAA,#323232"
color-link current-line-number "#AAAAAA,#282828"
@@ -21,7 +22,6 @@ color-link gutter-error "#CB4B16,#282828"
color-link gutter-warning "#E6DB74,#282828"
color-link cursor-line "#323232"
color-link color-column "#323232"
#No extended types; Plain brackets.
#No extended types; plain brackets
color-link type.extended "default"
#color-link symbol.brackets "default"
color-link symbol.tag "#AE81FF,#282828"
color-link symbol.brackets "default"

View File

@@ -0,0 +1,30 @@
#Flamepoint theme
#By CaptainMcClellan
color-link default ""
color-link comment ""
color-link constant ""
color-link constant.bool ""
color-link constant.bool.true ""
color-link constant.bool.false ""
color-link constant.number ""
color-link constant.specialChar ""
color-link constant.string ""
color-link constant.string.url "underline"
color-link identifier ""
color-link identifier.var ""
color-link preproc ""
color-link special ""
color-link statement ""
color-link symbol ""
color-link symbol.brackets ""
color-link symbol.tag ""
color-link type ""
color-link type.keyword ""
color-link error ""
color-link todo ""
color-link cursor-line ""
color-link statusline ""
color-link tabbar ""
color-link color-column ""
color-link gutter-error ""
color-link gutter-warning ""

View File

@@ -0,0 +1 @@
#Funky Cactus theme in true colour.

View File

@@ -0,0 +1,30 @@
#NES
#A color theme only using NES pallette colours
color-link default ""
color-link comment ""
color-link constant ""
color-link constant.bool ""
color-link constant.bool.true ""
color-link constant.bool.false ""
color-link constant.number ""
color-link constant.specialChar ""
color-link constant.string ""
color-link constant.string.url "underline"
color-link identifier ""
color-link identifier.var ""
color-link preproc ""
color-link special ""
color-link statement ""
color-link symbol ""
color-link symbol.brackets ""
color-link symbol.tag ""
color-link type ""
color-link type.keyword ""
color-link error ""
color-link todo ""
color-link cursor-line ""
color-link statusline ""
color-link tabbar ""
color-link color-column ""
color-link gutter-error ""
color-link gutter-warning ""

View File

@@ -1,26 +0,0 @@
color-link default "#e6e1dc,#2b2b2b"
color-link comment "#bc9458,#2b2b2b"
color-link statement "#cc7833,#2b2b2b"
color-link constant "#a5c261,#2b2b2b"
color-link constant.bool "#6d9cbe,#2b2b2b"
color-link type "#6d9cbe,#2b2b2b"
color-link preproc "#cc7833,#2b2b2b"
color-link special "#cc7833,#2b2b2b"
color-link underlined "#cc7833,#2b2b2b"
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 current-line-number "#e6e1dc,#2b2b2b"
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 cursor-line "#353535"
color-link color-column "#353535"
color-link space "underline #e6e1dc,#2b2b2b"
#the Python syntax definition are wrong. This is not how you should do decorators!
color-link brightgreen "#edb753,#2b2b2b"

View File

@@ -1,32 +0,0 @@
# Twilight color scheme
color-link default "#F8F8F8,#141414"
color-link color-column "#1B1B1B"
color-link comment "#5F5A60"
color-link constant "#CF6A4C"
#color-link constant.number "#CF6A4C"
color-link constant.specialChar "#DDF2A4"
color-link constant.string "#8F9D6A"
color-link current-line-number "#868686,#1B1B1B"
color-link cursor-line "#1B1B1B"
color-link divider "#1E1E1E"
color-link error "#D2A8A1"
color-link gutter-error "#9B859D"
color-link gutter-warning "#9B859D"
color-link identifier "#9B703F"
color-link identifier.class "#DAD085"
color-link identifier.var "#7587A6"
color-link indent-char "#515151"
color-link line-number "#868686"
color-link preproc "#E0C589"
color-link special "#E0C589"
color-link statement "#CDA869"
color-link statusline "#515151,#1E1E1E"
color-link symbol "#AC885B"
color-link symbol.brackets "#F8F8F8"
color-link symbol.operator "#CDA869"
color-link symbol.tag "#AC885B"
color-link tabbar "#F2F0EC,#2D2D2D"
color-link todo "#8B98AB"
color-link type "#F9EE98"
color-link type.keyword "#CDA869"
color-link underlined "#8996A8"

View File

@@ -5,69 +5,79 @@ This help page aims to cover two aspects of micro's syntax highlighting engine:
- How to create colorschemes and use them
- How to create syntax files to add to the list of languages micro can highlight
## Colorschemes
Micro comes with a number of colorschemes by default. Here is the list:
### 256 color
* simple: this is the simplest colorscheme. It uses 16 colors which are
set by your terminal
These should work and look nice in most terminals. I recommend these
themes the most.
* mc: A 16-color theme based on the look and feel of GNU Midnight Commander.
This will look great used in conjunction with Midnight Commander.
* nano: A 16-color theme loosely based on GNU nano's syntax highlighting.
* monokai: this is the monokai colorscheme; you may recognize it as
Sublime Text's default colorscheme. It requires true color to
look perfect, but the 256 color approximation looks very good as well.
It's also the default colorscheme.
* `monokai`: this is the monokai colorscheme; you may recognize it as Sublime
Text's default colorscheme. It requires true color to look perfect, but the
256 color approximation looks very good as well. It's also the default
colorscheme.
* `zenburn`
* `gruvbox`
* `darcula`
* `twilight`
* `railscast`
* `bubblegum`: a light colorscheme
* zenburn: The 'zenburn' colorscheme and works well with 256 color terminals
### 16 color
* solarized: this is the solarized colorscheme.
You should have the solarized color palette in your terminal to use it.
These may vary widely based on the 16 colors selected for your terminal.
* solarized-tc: this is the solarized colorscheme for true color; just
make sure your terminal supports true color before using it and that the
MICRO_TRUECOLOR environment variable is set to 1 before starting micro.
* `simple`: this is the simplest colorscheme. It uses 16 colors which are set by
your terminal
* `solarized`: You should have the solarized color palette in your terminal to use this colorscheme properly.
* `cmc-16`
* `cmc-paper`: cmc-16, but on a white background. (Actually light grey
on most ANSI (16-color) terminals)
* `geany`: Colorscheme based on geany's default highlighting.
* atom-dark-tc: this colorscheme is based off of Atom's "dark" colorscheme.
It requires true color to look good.
### True color
* cmc-16: A very nice 16-color theme. Written by contributor CaptainMcClellan
(Collin Warren.) Licensed under the same license as the rest of the themes.
These require terminals that support true color and require `MICRO_TRUECOLOR=1` (this is an environment variable).
* cmc-paper: Basically cmc-16, but on a white background. ( Actually light grey on most
ANSI (16-color) terminals.)
* `solarized-tc`: this is the solarized colorscheme for true color.
* `atom-dark-tc`: this colorscheme is based off of Atom's "dark" colorscheme.
* `cmc-tc`: A true colour variant of the cmc theme. It requires true color to
look its best. Use cmc-16 if your terminal doesn't support true color.
* `gruvbox-tc`: The true color version of the gruvbox colorscheme
* `github-tc`: The true color version of the Github colorscheme
* cmc-tc: A true colour variant of the cmc theme.
It requires true color to look its best. Use cmc-16 if your terminal doesn't support true color.
To enable one of these colorschemes just press CtrlE in micro and type
`set colorscheme solarized`. (or whichever one you choose). You can also use
`set colorscheme monochrome` if you'd prefer to have just the terminal's default
foreground and background colors. Note: This provides no syntax highlighting!
* codeblocks: A colorscheme based on the Code::Blocks IDE's default syntax highlighting.
* codeblocks-paper: Same as codeblocks, but on a white background. ( Actually light grey. )
* github-tc: A colorscheme based on Github's syntax highlighting. Requires true color to look its best.
* paper-tc: A nice minimalist theme with a light background, good for editing documents on.
Requires true color to look its best. Not to be confused with `-paper` suffixed themes.
* geany: Colorscheme based on geany's default highlighting.
* geany-alt-tc: Based on an alternate theme bundled with geany.
* flamepoint-tc: A fire inspired, high intensity true color theme written by CaptainMcClellan.
As with all the other `-tc` suffixed themes, it looks its best on a
To enable one of these colorschemes just press CtrlE in micro and type `set colorscheme solarized`.
(or whichever one you choose). You can also use `set colorscheme monochrome` if you'd prefer
to have just the terminal's default foreground and background colors.
Note: This provides no syntax highlighting!
See `help gimmickcolors` for a list of some true colour themes that are more
just for fun than for serious use. (Though feel free if you want!)
just for fun than for serious use. ( Though feel free if you want! )
---
## Creating a Colorscheme
### Creating a Colorscheme
Micro's colorschemes are also extremely simple to create. The default ones ca
be found
Micro's colorschemes are also extremely simple to create. The default ones can be found
[here](https://github.com/zyedidia/micro/tree/master/runtime/colorschemes).
They are only about 18-30 lines in total.
Basically to create the colorscheme you need to link highlight groups with
actual colors. This is done using the `color-link` command.
Basically to create the colorscheme you need to link highlight groups with actual colors.
This is done using the `color-link` command.
For example, to highlight all comments in green, you would use the command:
@@ -99,22 +109,20 @@ color-link comment "bold red"
There are three different ways to specify the color.
Color terminals usually have 16 colors that are preset by the user. This means
that you cannot depend on those colors always being the same. You can use those
colors with the names `black, red, green, yellow, blue, magenta, cyan, white`
and the bright variants of each one (brightblack, brightred...).
Color terminals usually have 16 colors that are preset by the user. This means that
you cannot depend on those colors always being the same. You can use those colors with
the names `black, red, green, yellow, blue, magenta, cyan, white` and the bright variants
of each one (brightblack, brightred...).
Then you can use the terminals 256 colors by using their numbers 1-256 (numbers
1-16 will refer to the named colors).
Then you can use the terminals 256 colors by using their numbers 1-256 (numbers 1-16 will
refer to the named colors).
If the user's terminal supports true color, then you can also specify colors
exactly using their hex codes. If the terminal is not true color but micro is
told to use a true color colorscheme it will attempt to map the colors to the
available 256 colors.
If the user's terminal supports true color, then you can also specify colors exactly using
their hex codes. If the terminal is not true color but micro is told to use a true color colorscheme
it will attempt to map the colors to the available 256 colors.
Generally colorschemes which require true color terminals to look good are
marked with a `-tc` suffix and colorschemes which supply a white background are
marked with a `-paper` suffix.
Generally colorschemes which require true color terminals to look good are marked with a `-tc` suffix
and colorschemes which supply a white background are marked with a `-paper` suffix.
---
@@ -132,10 +140,9 @@ Here is a list of the colorscheme groups that you can use:
* underlined
* error
* todo
* statusline (Color of the statusline)
* tabbar (Color of the tabbar that lists open files)
* indent-char (Color of the character which indicates tabs if the option is
enabled)
* statusline ( Color of the statusline)
* tabbar ( Color of the tabbar that lists open files.)
* indent-char ( Color of the character which indicates tabs if the option is enabled)
* line-number
* gutter-error
* gutter-warning
@@ -143,30 +150,29 @@ Here is a list of the colorscheme groups that you can use:
* current-line-number
* color-column
* ignore
* divider (Color of the divider between vertical splits)
* divider ( Color of the divider between vertical splits. )
Colorschemes must be placed in the `~/.config/micro/colorschemes` directory to
be used.
Colorschemes must be placed in the `~/.config/micro/colorschemes` directory to be used.
---
In addition to the main colorscheme groups, there are subgroups that you can
specify by adding `.subgroup` to the group. If you're creating your own custom
syntax files, you can make use of your own subgroups.
specify by adding `.subgroup` to the group. If you're creating your own
custom syntax files, you can make use of your own subgroups.
If micro can't match the subgroup, it'll default to the root group, so it's
safe and recommended to use subgroups in your custom syntax files.
If micro can't match the subgroup, it'll default to the root group, so
it's safe and recommended to use subgroups in your custom syntax files.
For example if `constant.string` is found in your colorscheme, micro will us
that for highlighting strings. If it's not found, it will use constant instead.
Micro tries to match the largest set of groups it can find in the colorscheme
definitions, so if, for examle `constant.bool.true` is found then micro will use
that. If `constant.bool.true` is not found but `constant.bool` is found micro
will use `constant.bool`. If not, it uses `constant`.
For example if `constant.string` is found in your colorscheme, micro will
use that for highlighting strings. If it's not found, it will use constant
instead. Micro tries to match the largest set of groups it can find in the
colorscheme definitions, so if, for examle `constant.bool.true` is found then
micro will use that. If `constant.bool.true` is not found but `constant.bool`
is found micro will use `constant.bool`. If not, it uses `constant`.
Here's a list of subgroups used in micro's built-in syntax files.
* comment.bright (Some filetypes have distinctions between types of comments)
* comment.bright ( Some filetypes have distinctions between types of comments.)
* constant.bool
* constant.bool.true
* constant.bool.false
@@ -174,32 +180,29 @@ Here's a list of subgroups used in micro's built-in syntax files.
* constant.specialChar
* constant.string
* constant.string.url
* identifier.class (Also used for functions)
* identifier.class ( Also used for functions. )
* identifier.macro
* identifier.var
* preproc.shebang (The #! at the beginning of a file that tells the os what
script interpreter to use)
* symbol.brackets (`{}()[]` and sometimes `<>`)
* symbol.operator (Color operator symbols differently)
* symbol.tag (For html tags, among other things)
* type.keyword (If you want a special highlight for keywords like `private`)
* preproc.shebang ( The #! at the beginning of a file that tells the os what script interpreter to use. )
* symbol.brackets ( {}()[] and sometimes <> )
* symbol.operator ( Color operator symbols differently. )
* symbol.tag ( For html tags, among other things.)
* type.keyword ( If you want a special highlight for keywords like `private` )
In the future, plugins may also be able to use color groups for styling.
## Syntax files
The syntax files is written in yaml-format and specify how to highlight
languages.
The syntax files is written in yaml-format and specify how to highlight languages.
Micro's builtin syntax highlighting tries very hard to be sane, sensible and
provide ample coverage of the meaningful elements of a language. Micro has
syntax files built in for over 100 languages now! However, there may be
situations where you find Micro's highlighting to be insufficient or not to your
liking. The good news is that you can create your own syntax files, and place them
in `~/.config/micro/syntax` and Micro will use those instead.
Micro's builtin syntax highlighting tries very hard to be sane, sensible
and provide ample coverage of the meaningful elements of a language. Micro has
syntax files built int for over 100 languages now. However, there may be
situations where you find Micro's highlighting to be insufficient or not to
your liking. Good news is you can create syntax files (.micro extension), place them in
`~/.config/micro/syntax` and Micro will use those instead.
### Filetype definition
### Filetype defintion
You must start the syntax file by declaring the filetype:
@@ -216,9 +219,8 @@ detect:
filename: "\\.go$"
```
Micro will match this regex against a given filename to detect the filetype. You
may also provide an optional `header` regex that will check the first line of
the file. For example:
Micro will match this regex against a given filename to detect the filetype. You may also
provide an optional `header` regex that will check the first line of the file. For example:
```
detect:
@@ -228,10 +230,9 @@ detect:
#### Syntax rules
Next you must provide the syntax highlighting rules. There are two types of
rules: patterns and regions. A pattern is matched on a single line and usually a
single word as well. A region highlights between two patterns over multiple
lines and may have rules of its own inside the region.
Next you must provide the syntax highlighting rules. There are two types of rules: patterns and regions.
A pattern is matched on a single line and usually a single word as well. A region highlights between two
patterns over multiple lines and may have rules of its own inside the region.
Here are some example patterns in Go:
@@ -242,8 +243,7 @@ rules:
- preproc: "\\b(package|import|const|var|type|struct|func|go|defer|iota)\\b"
```
The order of patterns does matter as patterns lower in the file will overwrite
the ones defined above them.
The order of patterns does matter as patterns lower in the file will overwrite the ones defined above them.
And here are some example regions for Go:
@@ -269,15 +269,12 @@ And here are some example regions for Go:
- todo: "(TODO|XXX|FIXME):?"
```
Notice how the regions may contain rules inside of them. Any inner rules that
are matched are then skipped when searching for the end of the region. For
example, when highlighting `"foo \" bar"`, since `\"` is matched by an inner
rule in the region, it is skipped. Likewise for `"foo \\" bar`, since `\\` is
matched by an inner rule, it is skipped, and then the `"` is found and the
string ends at the correct place.
Notice how the regions may contain rules inside of them. Any inner rules that are matched are then skipped when searching
for the end of the region. For example, when highlighting `"foo \" bar"`, since `\"` is matched by an inner rule in the
region, it is skipped. Likewise for `"foo \\" bar`, since `\\` is matched by an inner rule, it is skipped, and then the `"`
is found and the string ends at the correct place.
You may also explicitly mark skip regexes if you don't want them to be
highlighted. For example:
You may also explicitly mark skip regexes if you don't want them to be highlighted. For example:
```
- constant.string:
@@ -289,8 +286,8 @@ highlighted. For example:
#### Includes
You may also include rules from other syntax files as embedded languages. For
example, the following is possible for html:
You may also include rules from other syntax files as embedded languages. For example, the following is possible
for html:
```
- default:

View File

@@ -5,24 +5,19 @@ Here are the possible commands that you can use.
* `quit`: Quits micro.
* `save filename?`: Saves the current buffer. If the filename is provided it
will 'save as' the filename.
* `save filename?`: Saves the current buffer. If the filename is provided it will
'save as' the filename.
* `replace "search" "value" flags`: This will replace `search` with `value`.
The `flags` are optional. Possible flags are:
* `-a`: Replace all occurrences at once
* `-l`: Do a literal search instead of a regex search
The `flags` are optional.
At this point, there is only one flag: `c`, which enables `check` mode
which asks if you'd like to perform the replacement each time.
Note that `search` must be a valid regex (unless `-l` is passed). If one
of the arguments does not have any spaces in it, you may omit the quotes.
Note that `search` must be a valid regex. If one of the arguments
does not have any spaces in it, you may omit the quotes.
* `replaceall "search" "value"`: This will replace `search` with `value` without
user confirmation.
See `replace` command for more information.
* `set option value`: sets the option to value. See the `options` help topic for
a list of options you can set.
* `set option value`: sets the option to value. See the `options` help topic
for a list of options you can set.
* `setlocal option value`: sets the option to value locally (only in the current
buffer).
@@ -30,25 +25,27 @@ Here are the possible commands that you can use.
* `show option`: shows the current value of the given option.
* `eval "expression"`: Evaluates a Lua expression. Note that micro will not
print anything so you should use `messenger:Message(...)` to display a value.
print anything so you should use `messenger:Message(...)` to display a
value.
* `run sh-command`: runs the given shell command in the background. The
command's output will be displayed in one line when it finishes running.
* `bind key action`: creates a keybinding from key to action. See the sections
on keybindings above for more info about what keys and actions are available.
* `bind key action`: creates a keybinding from key to action. See the sections on
keybindings above for more info about what keys and actions are available.
* `vsplit filename`: opens a vertical split with `filename`. If no filename is
provided, a vertical split is opened with an empty buffer.
* `hsplit filename`: same as `vsplit` but opens a horizontal split instead of a
vertical split.
* `hsplit filename`: same as `vsplit` but opens a horizontal split instead of
a vertical split.
* `tab filename`: opens the given file in a new tab.
* `tabswitch tab`: This command will switch to the specified tab. The `tab` can
either be a tab number, or a name of a tab.
* `tabswitch tab`: This command will switch to the specified tab.
The `tab` can either be a tab number, or a name of a tab.
* `log`: opens a log of all messages and debug statements.
* `plugin install plugin_name`: installs the given plugin.
@@ -59,15 +56,15 @@ Here are the possible commands that you can use.
* `plugin update`: updates all installed plugins.
* `plugin search plugin_name`: searches for the given plugin. Note that you can
find a list of all available plugins at
* `plugin search plugin_name`: searches for the given plugin.
Note that you can find a list of all available plugins at
github.com/micro-editor/plugin-channel.
You can also see more information about the plugin manager in the
`Plugin Manager` section of the `plugins` help topic.
You can also see more information about the plugin manager
in the `Plugin Manager` section of the `plugins` help topic.
* `plugin available`: list plugins available for download (this includes any
plugins that may be already installed).
* `plugin available`: list plugins available for download (this includes
any plugins that may be already installed).
* `reload`: reloads all runtime files.
@@ -77,29 +74,8 @@ Here are the possible commands that you can use.
* `open filename`: Open a file in the current buffer.
* `retab`: Replaces all leading tabs with spaces or leading spaces with tabs
depending on the value of `tabstospaces`.
* `raw`: Micro will open a new tab and show the escape sequence for every event
it receives from the terminal. This shows you what micro actually sees from
the terminal and helps you see which bindings aren't possible and why. This
is most useful for debugging keybindings.
* `showkey`: Show the action(s) bound to a given key. For example
running `> showkey CtrlC` will display `main.(*View).Copy`. Unfortuately
showkey does not work well for keys bound to plugin actions. For those
it just shows "LuaFunctionBinding."
---
The following commands are provided by the default plugins:
* `lint`: Lint the current file for errors.
# Command Parsing
When running a command, you can use extra syntax that micro will expand before
running the command. To use an argument with a space in it, simply put it in
quotes. You can also use environment variables in the command bar and they
will be expanded to their value. Finally, you can put an expression in backticks
and it will be evaluated by the shell beforehand.

View File

@@ -1,128 +1,224 @@
# Default Keys
#Default Keys
Below are simple charts of the default hotkeys and their functions. For more
information about binding custom hotkeys or changing default bindings, please
run `> help keybindings`
Below are simple charts of the default hotkeys and their functions.
For more information about binding custom hotkeys or changing
default bindings, please run `>help keybindings`
Please remember that *all* keys here are rebindable! If you don't like it, you
can change it!
Please remember that *all* keys here are rebindable!
If you don't like it, you can change it!
### Power user
(We are not responsible for you forgetting what you bind keys to.
Do not open an issue because you forgot your keybindings.)
| Key | Description of function |
|-------- |-------------------------------------------------------------------------------------------------- |
| Ctrl+E | Open a command prompt for running commands (see `> help commands` for a list of valid commands). |
| Tab | In command prompt, it will autocomplete if possible. |
| Ctrl+B | Run a shell command (this will close micro while your command executes). |
#Power user
+--------+---------------------------------------------------------+
| Key | Description of function |
+--------+---------------------------------------------------------+
| Ctrl+E | Switch to the micro command prompt to run a command. |
| | (See `>help commands` for a list of commands. ) |
+--------+---------------------------------------------------------+
| Tab | In command prompt it will auto complete if possible. |
+--------+---------------------------------------------------------+
| Ctrl+B | Run shell commands in micro's current working directory.|
+--------+---------------------------------------------------------+
### Navigation
#Navigation
|--------+---------------------------------------------------------+
| Arrows | Move the cursor around your current document. |
| | (Yes this is rebindable to the vim keys if you want.) |
+--------+---------------------------------------------------------+
| Shift+ | Move and select text. |
| Arrows | |
+--------+---------------------------------------------------------+
| Home | |
| or | |
| Ctrl+ | Move to the beginning of the current line. (Naturally.) |
| Left | |
| Arrow | |
+--------+---------------------------------------------------------+
| End | |
| or | |
| Ctrl+ | Move to the end of the current line. |
| Right | |
| Arrow | |
+--------+---------------------------------------------------------+
| Alt+ | |
| Left | Move cursor one complete word left. |
| Arrow | |
+--------+---------------------------------------------------------+
| Alt+ | |
| Right | Move cursor one complete word right. |
| Arrow | |
+--------+---------------------------------------------------------+
| PageUp | Move cursor up lines quickly. |
+--------+---------------------------------------------------------+
| PageDn | Move cursor down lines quickly. |
+--------+---------------------------------------------------------+
| Ctrl+ | |
| Home | |
| or | Move cursor to start of the document |
| Ctrl+ | |
| Up | |
| Arrow | |
+--------+---------------------------------------------------------+
| Ctrl+ | |
| End | |
| or | Move cursor to end of the document |
| Ctrl+ | |
| Down | |
| Arrow | |
+--------+---------------------------------------------------------+
| Ctrl+L | Jump to line in current file. ( Prompts for line # ) |
+--------+---------------------------------------------------------+
| Ctrl+W | Move between splits open in current tab. |
| | (See vsplit and hsplit in `>help commands`) |
+--------+---------------------------------------------------------+
| Key | Description of function |
|-------------------------- |------------------------------------------------------------------------------------------ |
| Arrows | Move the cursor around |
| Shift+arrows | Move and select text |
| Home or CtrlLeftArrow | Move to the beginning of the current line |
| End or CtrlRightArrow | Move to the end of the current line |
| AltLeftArrow | Move cursor one word left |
| AltRightArrow | Move cursor one word right |
| Alt+{ | Move cursor to previous empty line, or beginning of document |
| Alt+} | Move cursor to next empty line, or end of document |
| PageUp | Move cursor up one page |
| PageDown | Move cursor down one page |
| CtrlHome or CtrlUpArrow | Move cursor to start of document |
| CtrlEnd or CtrlDownArrow | Move cursor to end of document |
| Ctrl+L | Jump to a line in the file (prompts with #) |
| Ctrl+W | Cycle between splits in the current tab (use `> vsplit` or `> hsplit` to create a split) |
#Tabs
+--------+---------------------------------------------------------+
| Ctrl+T | Open a new tab. |
+--------+---------------------------------------------------------+
| Alt+, | Move to the previous tab in the tablist. |
| | (This works like moving between file buffers in nano) |
+--------+---------------------------------------------------------+
| Alt+. | Move to the next tab in the tablist. |
+--------+---------------------------------------------------------+
### Tabs
#Find Operations
+--------+---------------------------------------------------------+
| Ctrl+F | Find text in current file. ( Prompts for text to find.) |
+--------+---------------------------------------------------------+
| Ctrl+N | Find next instance of current search in current file. |
+--------+---------------------------------------------------------+
| Ctrl+P | Find prev instance of current search in current file. |
+--------+---------------------------------------------------------+
| Key | Description of function |
|-------- |------------------------- |
| Ctrl+T | Open a new tab |
| Alt+, | Previous tab |
| Alt+. | Next tab |
#File Operations
+--------+---------------------------------------------------------+
| Ctrl+Q | Close current file. ( Quits micro if last file open. ) |
+--------+---------------------------------------------------------+
| Ctrl+O | Open a file. ( Prompts you to input filename. ) |
+--------+---------------------------------------------------------+
| Ctrl+S | Save current file. |
+--------+---------------------------------------------------------+
### Find Operations
#Text operations
+--------+---------------------------------------------------------+
| Ctrl+A | Select all text in current file. |
+--------+---------------------------------------------------------+
| Alt+ | |
| Shift+ | Select complete word right. |
| Right | |
| Arrow | |
+--------+---------------------------------------------------------+
| Alt+ | |
| Shift+ | Select complete word left. |
| Left | |
| Arrow | |
+--------+---------------------------------------------------------+
| Shift+ | |
| Home | |
| or | Select from the current cursor position to the |
| Ctrl+ | start of the current line. |
| Shift+ | |
| Left | |
| Arrow | |
+--------+---------------------------------------------------------+
| Shift+ | |
| End | |
| or | Select from the current cursor position to the |
| Ctrl+ | end of the current line. |
| Shift+ | |
| Right | |
| Arrow | |
+--------+---------------------------------------------------------+
| Ctrl+ | |
| Shift+ | Select from the current cursor position to the |
| Up | start of the document. |
| Arrow | |
+--------+---------------------------------------------------------+
| Ctrl+ | |
| Shift+ | Select from the current cursor position to the |
| Down | end of the document. |
| Arrow | |
+--------+---------------------------------------------------------+
| Ctrl+X | Cut selected text. |
+--------+---------------------------------------------------------+
| Ctrl+C | Copy selected text. |
+--------+---------------------------------------------------------+
| Ctrl+V | Paste selected text. |
+--------+---------------------------------------------------------+
| Ctrl+K | Cut current line. ( Can then be pasted with Ctrl+V) |
+--------+---------------------------------------------------------+
| Ctrl+D | Duplicate current line. |
+--------+---------------------------------------------------------+
| Ctrl+Z | Undo actions. |
+--------+---------------------------------------------------------+
| Ctrl+Y | Redo actions. |
+--------+---------------------------------------------------------+
| Alt+ | |
| Up | Move current line or selected lines up. |
| Arrow | |
+--------+---------------------------------------------------------+
| Alt+ | |
| Down | Move current line or selected lines down. |
| Arrow | |
+--------+---------------------------------------------------------+
| Alt+ | |
| Ctrl+H | |
| or | Delete complete word left. |
| Alt+ | |
| Back- | |
| space | |
+--------+---------------------------------------------------------+
| Key | Description of function |
|-------- |------------------------------------------ |
| Ctrl+F | Find (opens prompt) |
| Ctrl+N | Find next instance of current search |
| Ctrl+P | Find previous instance of current search |
#Macros
+--------+---------------------------------------------------------+
| | Toggle ON/OFF macro recording. |
| Ctrl+U | Simply press Ctrl+U to begin recording a macro and |
| | press Ctrl+U to stop recording macro. |
+--------+---------------------------------------------------------+
| Ctrl+J | Run your recorded macro. |
+--------+---------------------------------------------------------+
### File Operations
#Other
+--------+---------------------------------------------------------+
| Ctrl+G | Open the help file. |
+--------+---------------------------------------------------------+
| Ctrl+H | Alternate backspace. |
| | (Some old terminals don't support the Backspace key .) |
+--------+---------------------------------------------------------+
| Ctrl+R | Toggle the line number ruler. ( On the lefthand side.) |
+--------+---------------------------------------------------------+
| Key | Description of function |
|-------- |---------------------------------------------------------------- |
| Ctrl+Q | Close current file (quits micro if this is the last file open) |
| Ctrl+O | Open a file (prompts for filename) |
| Ctrl+S | Save current file |
### Text operations
| Key | Description of function |
|--------------------------------- |------------------------------------------ |
| AltShiftRightArrow | Select word right |
| AltShiftLeftArrow | Select word left |
| ShiftHome or CtrlShiftLeftArrow | Select to start of current line |
| ShiftEnd or CtrlShiftRightArrow | Select to end of current line |
| CtrlShiftUpArrow | Select to start of file |
| CtrlShiftDownArrow | Select to end of file |
| Ctrl+X | Cut selected text |
| Ctrl+C | Copy selected text |
| Ctrl+V | Paste |
| Ctrl+K | Cut current line |
| Ctrl+D | Duplicate current line |
| Ctrl+Z | Undo |
| Ctrl+Y | Redo |
| AltUpArrow | Move current line or selected lines up |
| AltDownArrow | Move current line of selected lines down |
| AltBackspace or AltCtrl+H | Delete word left |
| Ctrl+A | Select all |
### Macros
| Key | Description of function |
|-------- |---------------------------------------------------------------------------------- |
| Ctrl+U | Toggle macro recording (press Ctrl+U to start recording and press again to stop) |
| Ctrl+J | Run latest recorded macro |
### Multiple cursors
| Key | Description of function |
|---------------- |---------------------------------------------------------------------------------------------- |
| Alt+N | Create new multiple cursor from selection (will select current word if no current selection) |
| Alt+P | Remove latest multiple cursor |
| Alt+C | Remove all multiple cursors (cancel) |
| Alt+X | Skip multiple cursor selection |
| Ctrl-MouseLeft | Place a multiple cursor at any location |
### Other
| Key | Description of function |
|-------- |----------------------------------------------------------------------------------- |
| Ctrl+G | Open help file |
| Ctrl+H | Backspace (old terminals do not support the backspace key and use Ctrl+H instead) |
| Ctrl+R | Toggle the line number ruler |
### Emacs style actions
| Key | Description of function |
|------- |------------------------- |
| Alt+F | Next word |
| Alt+B | Previous word |
| Alt+A | Move to start of line |
| Alt+E | Move to end of line |
### Function keys.
#Emacs style actions
+--------+---------------------------------------------------------+
| Alt+F | Move to the end of the next word. (To the next space.) |
+--------+---------------------------------------------------------+
| Alt+B | Move to the beginning of the previous word. |
+--------+---------------------------------------------------------+
| Alt+A | Alternate Home key. ( Move to beginning of line. ) |
+--------+---------------------------------------------------------+
| Alt+E | Alternate End key. ( Move to the end of line.) |
+--------+---------------------------------------------------------+
| Alt+P | Move cursor up. ( Same as up key. ) |
+--------+---------------------------------------------------------+
| Alt+N | Move cursor down. ( Same as down key. ) |
+--------+---------------------------------------------------------+
#Function keys.
Warning! The function keys may not work in all terminals!
+--------+---------------------------------------------------------+
| F1 | Open help. |
+--------+---------------------------------------------------------+
| F2 | Save current file. |
+--------+---------------------------------------------------------+
| F3 | Find in current file. ( Same as Ctrl+F ) |
+--------+---------------------------------------------------------+
| F4 | Close current file. (Quit if only file.) |
+--------+---------------------------------------------------------+
| F7 | Find in current file. (Same as Ctrl+F) |
+--------+---------------------------------------------------------+
| F10 | Close current file. |
+--------+---------------------------------------------------------+
| Key | Description of function |
|----- |------------------------- |
| F1 | Open help |
| F2 | Save |
| F3 | Find |
| F4 | Quit |
| F7 | Find |
| F10 | Quit |

View File

@@ -8,7 +8,7 @@ We have included a few colorschemes that are for fun:
Nintendo Entertainment System color palette.
* symbian-tc: Colorscheme based on SymbOS's GUI.
* matrix: Pretend it's 1981 with a colorscheme based on a monochrome
IBM 5151. (Does not include the ghosting and trailing)
IBM 5151. ( Does not include the ghosting and trailing. )
Check the plugin repo periodically for gimmick-color extension packs and genuine
additional themes.
Check the plugin repo periodically for gimmick-color extension packs
and genuine additional themes.

View File

@@ -1,62 +1,51 @@
# Micro help text
Thank you for downloading and using micro.
Micro is a terminal-based text editor that aims to be easy to use and intuitive,
while also taking advantage of the full capabilities of modern terminals.
For a list of the default keybindings press CtrlE and type `help defaultkeys`.
For more information on keybindings see `> help keybindings`.
If you want to see all the keybindings press CtrlE and type `help keybindings`.
See the next section for more information about documentation and help.
## Quick-start
### Quick-start
Press CtrlQ to quit, and CtrlS to save. Press CtrlE to start typing commands and
you can see which commands are available by pressing tab, or by viewing the help
topic `> help commands`. When I write `> ...` I mean press CtrlE and then type
whatever is there.
Press CtrlQ to quit, and CtrlS to save. Press CtrlE to start typing commands
and you can see which commands are available by pressing tab, or by
viewing the help topic `> help commands`. When I write `> ...` I mean press
CtrlE and then type whatever is there.
Move the cursor around with the mouse or the arrow keys. Type
`> help defaultkeys` to get a quick, easy overview of the default hotkeys and
what they do. For more info on rebinding keys, see type `> help keybindings`.
Move the cursor around with the mouse or the arrow keys. Type `>help defaultkeys` to
get a quick, easy overview of the default hotkeys and what they do. For more info
on rebinding keys, see type `>help keybindings`
If the colorscheme doesn't look good, you can change it with
`> set colorscheme ...`. You can press tab to see the available colorschemes, or
see more information with `> help colors`.
If the colorscheme doesn't look good, you can change it with `> set colorscheme ...`.
You can press tab to see the available colorschemes, or see more information with
`> help colors`.
Press CtrlW to move between splits, and type `> vsplit filename` or
`> hsplit filename` to open a new split.
Press CtrlW to move between splits, and type `> vsplit filename` or `> hsplit filename`
to open a new split.
## Accessing more help
### Accessing more help
Micro has a built-in help system much like Vim's (although less extensive).
To use it, press CtrlE to access command mode and type in `help` followed by a
topic. Typing `help` followed by nothing will open this page.
To use it, press CtrlE to access command mode and type in `help` followed by a topic.
Typing `help` followed by nothing will open this page.
Here are the possible help topics that you can read:
* tutorial: A brief tutorial which gives an overview of all the other help
topics
* keybindings: Gives a full list of the default keybindings as well as how to
rebind them
* defaultkeys: Gives a more straight-forward list of the hotkey commands and what
they do.
* tutorial: A brief tutorial which gives an overview of all the other help topics
* keybindings: Gives a full list of the default keybindings as well as how to rebind them
* defaultkeys: Gives a more straight-forward list of the hotkey commands and what they do.
* commands: Gives a list of all the commands and what they do
* options: Gives a list of all the options you can customize
* plugins: Explains how micro's plugin system works and how to create your own
plugins
* colors: Explains micro's colorscheme and syntax highlighting engine and how to
create your own colorschemes or add new languages to the engine
* plugins: Explains how micro's plugin system works and how to create your own plugins
* colors: Explains micro's colorscheme and syntax highlighting engine and how to create your
own colorschemes or add new languages to the engine
For example, to open the help page on plugins you would press CtrlE and type
`help plugins`.
For example, to open the help page on plugins you would press CtrlE and type `help plugins`.
I recommend looking at the `tutorial` help file because it is short for each
section and gives concrete examples of how to use the various configuration
options in micro. However, it does not give the in-depth documentation that the
other topics provide.
I recommend looking at the `tutorial` help file because it is short for each section and
gives concrete examples of how to use the various configuration options in micro. However,
it does not give the in-depth documentation that the other topics provide.

View File

@@ -2,28 +2,25 @@
Micro has a plethora of hotkeys that make it easy and powerful to use and all
hotkeys are fully customizable to your liking.
Custom keybindings are stored internally in micro if changed with the `> bind`
command or you can also be added in the file `~/.config/micro/bindings.json` as
discussed below. For a list of the default keybindings in the json format used
by micro, please see the end of this file. For a more user-friendly list with
explanations of what the default hotkeys are and what they do, please see
`>help defaultkeys`
Custom keybindings are stored internally in micro if changed with the `>bind` command or
you can also be added in the file `~/.config/micro/bindings.json` as discussed below.
For a list of the default keybindings in the json format used by micro, please see
the end of this file. For a more user-friendly list with explanations of what the default
hotkeys are and what they do, please see `>help defaultkeys`
If `~/.config/micro/bindings.json` does not exist, you can simply create it.
Micro will know what to do with it.
You can use the alt keys + arrows to move word by word. Ctrl left and right move
the cursor to the start and end of the line, and ctrl up and down move the
cursor the start and end of the buffer.
You can use the alt keys + arrows to move word by word.
Ctrl left and right move the cursor to the start and end of the line, and
ctrl up and down move the cursor the start and end of the buffer.
You can hold shift with all of these movement actions to select while moving.
# Rebinding keys
## Rebinding keys
The bindings may be rebound using the `~/.config/micro/bindings.json` file. Each
key is bound to an action.
The bindings may be rebound using the `~/.config/micro/bindings.json`
file. Each key is bound to an action.
For example, to bind `Ctrl-y` to undo and `Ctrl-z` to redo, you could put the
following in the `bindings.json` file.
@@ -38,8 +35,8 @@ following in the `bindings.json` file.
In addition to editing your `~/.config/micro/bindings.json`, you can run
`>bind <keycombo> <action>` For a list of bindable actions, see below.
You can also chain commands when rebinding. For example, if you want Alt-s to
save and quit you can bind it like so:
You can also chain commands when rebinding. For example, if you want Alt-s to save
and quit you can bind it like so:
```json
{
@@ -47,86 +44,12 @@ save and quit you can bind it like so:
}
```
## Binding commands
You can also bind a key to execute a command in command mode (see
`help commands`). Simply prepend the binding with `command:`. For example:
```json
{
"Alt-p": "command:pwd"
}
```
Now when you press `Alt-p` the `pwd` command will be executed which will show
your working directory in the infobar.
You can also bind an "editable" command with `command-edit:`. This means that
micro won't immediately execute the command when you press the binding, but
instead just place the string in the infobar in command mode. For example,
you could rebind `CtrlG` to `> help`:
```json
{
"CtrlG": "command-edit:help "
}
```
Now when you press `CtrlG`, `help` will appear in the command bar and your cursor will
be placed after it (note the space in the json that controls the cursor placement).
## Binding raw escape sequences
Only read this section if you are interested in binding keys that aren't on the
list of supported keys for binding.
One of the drawbacks of using a terminal-based editor is that the editor must
get all of its information about key events through the terminal. The terminal
sends these events in the form of escape sequences often (but not always)
starting with `0x1b`.
For example, if micro reads `\x1b[1;5D`, on most terminals this will mean the
user pressed CtrlLeft.
For many key chords though, the terminal won't send any escape code or will send
an escape code already in use. For example for `CtrlBackspace`, my terminal
sends `\u007f` (note this doesn't start with `0x1b`), which it also sends for
`Backspace` meaning micro can't bind `CtrlBackspace`.
However, some terminals do allow you to bind keys to send specific escape
sequences you define. Then from micro you can directly bind those escape
sequences to actions. For example, to bind `CtrlBackspace` you can instruct your
terminal to send `\x1bctrlback` and then bind it in `bindings.json`:
```json
{
"\u001bctrlback": "DeleteWordLeft"
}
```
Here are some instructions for sending raw escapes in different terminals
### iTerm2
In iTerm2, you can do this in `Preferences->Profiles->Keys` then click the `+`,
input your keybinding, and for the `Action` select `Send Escape Sequence`. For
the above example your would type `ctrlback` into the box (the `\x1b`) is
automatically sent by iTerm2.
### Linux using loadkeys
You can do this in linux using the loadkeys program.
Coming soon!
## Unbinding keys
# Unbinding keys
It is also possible to disable any of the default key bindings by use of the
`UnbindKey` action in the user's `bindings.json` file.
## Bindable actions and bindable keys
# Bindable actions and bindable keys
The list of default keybindings contains most of the possible actions and keys
which you can use, but not all of them. Here is a full list of both.
@@ -156,7 +79,6 @@ MoveLinesUp
MoveLinesDown
DeleteWordRight
DeleteWordLeft
SelectLine
SelectToStartOfLine
SelectToEndOfLine
InsertNewline
@@ -191,8 +113,6 @@ HalfPageUp
HalfPageDown
StartOfLine
EndOfLine
ParagraphPrevious
ParagraphNext
ToggleHelp
ToggleRuler
JumpLine
@@ -211,22 +131,7 @@ HSplit
PreviousSplit
ToggleMacro
PlayMacro
Suspend (Unix only)
ScrollUp
ScrollDown
SpawnMultiCursor
RemoveMultiCursor
RemoveAllMultiCursors
SkipMultiCursor
UnbindKey
JumpToMatchingBrace
```
You can also bind some mouse actions (these must be bound to mouse buttons)
```
MousePress
MouseMultiCursor
```
Here is the list of all possible keys you can bind:
@@ -357,19 +262,6 @@ Escape
Enter
```
You can also bind some mouse buttons (they may be bound to normal actions or
mouse actions)
```
MouseLeft
MouseMiddle
MouseRight
MouseWheelUp
MouseWheelDown
MouseWheelLeft
MouseWheelRight
```
# Default keybinding configuration.
```json
@@ -384,10 +276,10 @@ MouseWheelRight
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"CtrlLeft": "StartOfLine",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfLine",
@@ -398,15 +290,14 @@ MouseWheelRight
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"Space": "InsertSpace",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "IndentSelection,InsertTab",
"Backtab": "OutdentSelection,OutdentLine",
"Backtab": "OutdentSelection",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
@@ -445,6 +336,8 @@ MouseWheelRight
"Alt-b": "WordLeft",
"Alt-a": "StartOfLine",
"Alt-e": "EndOfLine",
"Alt-p": "CursorUp",
"Alt-n": "CursorDown",
// Integration with file managers
"F1": "ToggleHelp",
@@ -454,29 +347,15 @@ MouseWheelRight
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
// Multiple cursors bindings
"Alt-n": "SpawnMultiCursor",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
```
## Final notes
#Final notes
Note: On some old terminal emulators and on Windows machines, `CtrlH` should be used
for backspace.
Note: On some old terminal emulators and on Windows machines, `CtrlH` should be
used for backspace.
Additionally, alt keys can be bound by using `Alt-key`. For example `Alt-a` or
`Alt-Up`. Micro supports an optional `-` between modifiers like `Alt` and
`Ctrl` so `Alt-a` could be rewritten as `Alta` (case matters for alt bindings).
This is why in the default keybindings you can see `AltShiftLeft` instead of
`Alt-ShiftLeft` (they are equivalent).
Additionally, alt keys can be bound by using `Alt-key`. For example `Alt-a`
or `Alt-Up`. Micro supports an optional `-` between modifiers like `Alt` and `Ctrl`
so `Alt-a` could be rewritten as `Alta` (case matters for alt bindings). This is
why in the default keybindings you can see `AltShiftLeft` instead of `Alt-ShiftLeft`
(they are equivalent).

Some files were not shown because too many files have changed in this diff Show More