Compare commits

...

101 Commits

Author SHA1 Message Date
Zachary Yedidia
5c8a2332d9 Use unicode.Mark for combining unicode range 2020-05-20 18:01:10 -04:00
Zachary Yedidia
ff0683d6d0 Final touches for combining character support 2020-05-20 17:00:56 -04:00
Zachary Yedidia
79c0ea17ad Use CharacterCount over RuneCount 2020-05-20 16:47:08 -04:00
Zachary Yedidia
bdff221870 Use DecodeCharacter over DecodeRune 2020-05-20 16:43:12 -04:00
Zachary Yedidia
65be5efd83 Merge branch 'p-e-w-buffer-benchmarks' 2020-05-20 15:29:02 -04:00
Zachary Yedidia
a491dd1c52 Merge branch 'buffer-benchmarks' of https://github.com/p-e-w/micro into p-e-w-buffer-benchmarks 2020-05-20 15:28:36 -04:00
Zachary Yedidia
d7ab44253f Update tcell and support italics in colorschemes
Closes #1640
2020-05-17 12:48:34 -04:00
Zachary Yedidia
0a6720498f Merge branch 'master' of https://github.com/zyedidia/micro 2020-05-17 12:23:21 -04:00
dmaluka
a150eef6f9 Fix end line number in HighlightMatches (#1662)
There is a bit of mess in the usage of HighlightMatches: in some places
we assume that it updates lines from startline to endline inclusive,
in other places we assume it's non-inclusive.
This fix makes it always inclusive.

In particular, it fixes a bug: when we open a file which has no
newline at the end, the last line isn't highlighted.
2020-05-17 16:05:34 -04:00
Zachary Yedidia
c46257222c Add support for FindLiteral
Use the FindLiteral action to use Find without regex support.

Fixes #1661
2020-05-17 12:22:33 -04:00
jsyedidia
299af4a3db Update hlint to 3.0 syntax (#1659) 2020-05-16 13:06:55 -04:00
Jeff Warner
d0f7ecf9ca Adds command "tabmove ±n", for better tab management (#1636)
* Adds command "tabmove ±n", for better tab management

* Added tabmove to help:commands

* Replace uses of util.Min, util.Max with util.Clamp

Browsing code and discovered `util.Clamp`, ideal for this section of my code

* oops, missed an arg

* Typo, again
2020-05-14 21:51:49 -04:00
dmaluka
fb35e0312a Fix unbind of a rune (#1649)
Fix problem with non-working unbind of a rune key.
E.g. after the following commands:

bind "n" "FindNext"
unbind "n"

Observed result: "n" key still triggers FindNext action
Expected result: "n" key inserts "n" rune
2020-05-14 21:50:28 -04:00
Zachary Yedidia
30395b1f67 Remove outdated c++ highlighter
Fixes #1652
2020-05-14 21:37:19 -04:00
Zachary Yedidia
ddf70953fe Support snake case autocompletion
Fixes #1655
2020-05-14 21:34:17 -04:00
Zachary Yedidia
55e97596d3 Fix movelinesup when selection is not complete 2020-05-07 19:39:17 -04:00
Zachary Yedidia
66dc48ce9b Improve readme 2020-05-04 22:21:46 -04:00
Zachary Yedidia
c490a94700 Update makefile 2020-05-04 10:34:16 -04:00
Zachary Yedidia
eff89a98a7 Fix v2 import path for go mod 2020-05-04 10:16:15 -04:00
Zachary Yedidia
221d8f462a Merge branch 'jwarner112-jwarner112-copyline' 2020-04-30 00:54:11 -04:00
Zachary Yedidia
7a23878250 gofmt 2020-04-30 00:54:02 -04:00
Jeff Warner
5d3e4fc3d9 Adds CopyLine action, the new default action for CtrlC if cursor has no selection 2020-04-29 21:06:54 -07:00
Indiana Kernick
f52fbfa1f0 Add .inl as a C++ file extension (#1630) 2020-04-29 20:01:59 -04:00
Zachary Yedidia
d60626c64b Merge 2020-04-25 17:01:20 -04:00
Zachary Yedidia
aaac0b1e6f Better actions error message 2020-04-25 17:01:16 -04:00
Some person
0f984131fb Update coffeescript.yaml (#1571)
* Update coffeescript.yaml

We need much much more modern coffeescript standards, the current one has broken `0x123456` (hex) and single quotes, and doesn't support multiline comments. This PR aims to fix that. I'm no regexp expert, I just based this off JS', so tell me if I did anything wrong.

* Update coffeescript.yaml
2020-04-22 23:12:56 -04:00
Zachary Yedidia
eb7189dcdb Make cursor follow selections
Fixes #1624
2020-04-21 09:33:21 -04:00
Zachary Yedidia
74523d28c5 Merge 2020-04-20 23:13:10 -04:00
Zachary Yedidia
f894f0a26e Update clipboard version 2020-04-20 23:13:01 -04:00
2pac
a067ce1f41 implemented circular tab movement (#1619)
Co-authored-by: 2pac <tarasyarema@pm.me>
2020-04-17 13:42:48 -04:00
Zachary Yedidia
f59468642d Update runtime 2020-04-10 17:27:57 -04:00
Zachary Yedidia
85e85b7ccc Merge 2020-04-10 17:27:34 -04:00
Zachary Yedidia
8f5888e7bf Use StartCol in colorcolumn calculation
Fixes #1615
2020-04-10 17:27:11 -04:00
Ján Jančár
f0da73bae2 Add StartOfTextToggle and SelectToStartOfTextToggle actions. (#1612)
These actions reintroduce the behavior of micro where the Home key
toggles between the start of text (first) and the start of the line.
The same applies for the variant with selection. This commit also
sets these bindings as the defaults.
2020-04-10 17:21:02 -04:00
Zachary Yedidia
d92deacf99 Ensure mouse release before focus change
Fixes #1613
2020-04-10 15:58:43 -04:00
trrbl
ffd7b5c770 Support csharp-script syntax. (#1425)
```
#!/usr/bin/env dotnet-script
// Set Runtime
#! "netcoreapp3.0"
// Imports
#load "myAssembly.dll"
#r "nuget:CliWrap,2.5.0"
```
This syntax file basically imports the `csharp` rules and adds it's custom pre-processors.
2020-04-10 13:57:36 -04:00
Ján Jančár
bd8c0d25c8 Add sagemath syntax highlight based on python3. (#1227) 2020-04-09 12:20:48 -04:00
Ján Jančár
052a36b896 Fix docs regarding "Home" key and "StartOfText" and "StartOfLine". (#1611) 2020-04-07 12:58:30 -04:00
jsyedidia
a76bf02f5f Add Haskell linter hlint to linter plugin (#1610) 2020-04-05 14:05:01 -04:00
Nikita Bobko
92f4cb7ef7 Clarify regex for git commit --verbose (#1606) 2020-04-05 14:04:36 -04:00
Zachary Yedidia
1cf9537340 Fix python3 syntax file and make python3 default
The python3 syntax had "filename" instead of "filetype"
as the header. This commit also makes standard py extensions
use the python3 highlighting and requires .py2 or a python2
env to use python2 highlighting because python3 is the standard
python now.

Fixes #1592
2020-03-24 11:42:23 -04:00
Zachary Yedidia
60c8c81da3 Relocate during replace
Fixes #1587
2020-03-24 11:33:52 -04:00
Zachary Yedidia
c76a973877 Merge 2020-03-24 11:17:12 -04:00
Zachary Yedidia
6def99ce24 Clarify replace message if replacing in selection 2020-03-24 11:14:54 -04:00
Koki Fushimi
26930ca81f Better Julia syntax. (#1567)
* Fix regex syntax and change to match one or more spaces.

* Add constant `nothing` and `missing`.

* Add Inf and NaN to constant numbers.
2020-03-24 10:59:48 -04:00
Hugo Locurcio
cd379cd838 Clarify the Find operation being regex-enabled (#1561)
This makes it more obvious that the Find option accepts regular
expressions as input.

See discussion in #1560.
2020-03-24 10:59:40 -04:00
allanderek
ee157f6503 Add elm as a default comment type in the comment plugin. (#1586) 2020-03-24 10:56:50 -04:00
Zachary Yedidia
48ca19873f Better ordering for reading syntax files
Ref #1580
2020-03-24 10:52:15 -04:00
Zachary Yedidia
fee5528309 Fix term emulator crash if invalid exec given
Ref #1583
2020-03-24 10:22:10 -04:00
Zachary Yedidia
671a188802 Support +LINE:COL flag syntax for cursor pos
Closes #1566
2020-03-24 10:10:44 -04:00
Zachary Yedidia
18d540583b Don't clear infobar if not enabled
Fixes #1584
2020-03-17 14:21:36 -04:00
Zachary Yedidia
943ea15fa3 Fix linter c++ entry
Fixes #1578
2020-03-14 15:40:05 -04:00
Cafe Duke
2ef57977d7 Add color schemes dukeubuntu-tc, dukedark-tc and dukelight-tc (#1547)
* Duke ubuntu, dark and light color schemes

* Duke color schemes: Change bgcolor for line number and cursor line

Co-authored-by: Raghunandan.Seshadri <raghubs81@gmail.com>
Co-authored-by: Raghunandan Seshadri <raghunandan.seshadri@oracle.com>
2020-03-07 22:07:43 -05:00
Zachary Yedidia
527750b68d Copy selection to primary on mouse release
Fixes #1558
2020-03-05 16:00:40 -05:00
Zachary Yedidia
629efe5eb7 Add JumpLine action back
You can bind to "command-edit:goto ", but binding to the action
"JumpLine" will have the same effect now.

Fixes #1550
2020-03-02 20:09:19 -05:00
Andrew Havens
a19cd2e6d0 Add support for Fastlane and Cocoapods file syntax highlighting (#1544) 2020-03-02 20:03:28 -05:00
Philipp Emanuel Weidmann
d038d3040f Add more sophisticated buffer benchmark system 2020-03-01 13:20:10 +05:30
Zachary Yedidia
9e8d76f2fa If stdout is a pipe, output to the pipe
If you run micro as `micro | cat` for example, micro will disallow
you from saving the file, and when you quit the buffer, the contents
will be sent to the pipe. This allows one to use micro as part of
an interactive unix pipeline.

Closes #1524
2020-02-27 12:39:19 -05:00
Zachary Yedidia
8a9a14562f Use bytes.Buffer for LineArray.Bytes 2020-02-27 11:27:00 -05:00
Zachary Yedidia
a6f5dee45c Fix custom syntax files not highlighting
Fixes #1530
2020-02-27 00:58:52 -05:00
Zachary Yedidia
b12886b066 Improve buffer test 2020-02-25 23:59:27 -05:00
Zachary Yedidia
56f5b475eb Improve buffer test 2020-02-25 23:21:50 -05:00
Zachary Yedidia
c51f84955e Update runtime 2020-02-25 21:08:22 -05:00
Zachary Yedidia
e4bf1e9984 Undo event chunks instead of single events 2020-02-25 20:53:48 -05:00
Zachary Yedidia
917e2922a1 Update readme 2020-02-25 20:40:57 -05:00
Zachary Yedidia
59e8f3aa3e mod tidy 2020-02-25 20:28:02 -05:00
Zachary Yedidia
53bda0cfa7 Fix buffer tests and selection bug
Fixes #1528
Ref #1526
2020-02-25 20:24:02 -05:00
Zachary Yedidia
f059541e0d Merge branch 'buffer-tests' of https://github.com/p-e-w/micro into buffer-unit-tests 2020-02-25 10:30:31 -05:00
Zachary Yedidia
d78fe81e21 line_array insert for eofnewline and make default
Makes the `eofnewline` option enabled by default.

Fixes #1525
2020-02-24 22:31:05 -05:00
josh
25b9342fbe fix eofnewline not running on files with 1 rune (#1535) 2020-02-24 22:26:51 -05:00
Zachary Yedidia
70bcf9f618 Fix text transformation bug
This fixes the remaining text transformation tests.

Ref #1526
2020-02-24 20:11:11 -05:00
Roman Kornev
8848388411 Hide ISSUE_TEMPLATE version help into a comment (#1532)
Because some people don't remove it
2020-02-24 13:49:45 -05:00
Zachary Yedidia
dff8b33e9c Apply basename option in tabbar as well 2020-02-24 13:48:37 -05:00
Zachary Yedidia
8a2048e7f6 Use tabbar color group, and mark modified tabs
Fixes #1523
2020-02-24 13:45:10 -05:00
Zachary Yedidia
0174d7dba4 Move multi-cursors correctly after newlines
Fixes #1527
2020-02-24 13:39:34 -05:00
Zachary Yedidia
e1827480c9 Filename completion for all non-command prompts
Fixes #1529
2020-02-24 13:00:55 -05:00
Zachary Yedidia
d8584d1ddb Debug off using default "go build"
Ref #1469
2020-02-24 12:55:59 -05:00
Philipp Emanuel Weidmann
f0cdc3cabb Add buffer test and benchmark suite (and tool to generate it) 2020-02-22 08:51:38 +05:30
Zachary Yedidia
2ef4f83358 Fix issue with simultaneous buffers 2020-02-19 17:40:54 -05:00
Zachary Yedidia
a9120ce270 Share more buffer elements and fix rehighlight
Fixes #1521
2020-02-19 14:41:30 -05:00
Zachary Yedidia
190d9d0609 Tweak version build script 2020-02-19 05:27:33 +00:00
Zachary Yedidia
cf3fdb344a Merge 2020-02-18 21:40:36 -05:00
Zachary Yedidia
b91242124c Go lint the current directory of file
Closes #1520
2020-02-18 21:40:14 -05:00
Zachary Yedidia
5ffc19f159 Use filecomplete for shell mode 2020-02-17 22:29:33 -05:00
Zachary Yedidia
cc994b6241 Fix relocation with softwrap on small buffers
Fixes #1512
2020-02-15 15:38:20 -05:00
Zachary Yedidia
087e7207f7 Add 'xterm' option
Ref #1489
2020-02-15 12:53:17 -05:00
Zachary Yedidia
db32b84cd1 Relocate after rune insert
Fixes #1510
2020-02-14 15:52:20 -05:00
Zachary Yedidia
00006aa2b4 Update snap version info 2020-02-13 21:27:07 -05:00
Zachary Yedidia
600d8558b2 Change some default option values 2020-02-13 20:51:56 -05:00
Zachary Yedidia
4874823240 Fix makefile tags dependencies 2020-02-13 20:00:35 -05:00
Zachary Yedidia
6a0e4b5564 Fetch tags before snapcraft build 2020-02-13 19:57:31 -05:00
Zachary Yedidia
b2d7c8c5d4 Merge 2020-02-13 19:49:27 -05:00
Zachary Yedidia
38f88ade60 Search and replace within a selection
Closes #1098
2020-02-13 19:48:48 -05:00
Tonus1
8348cc8ec2 Add transparency (#1509) 2020-02-13 18:15:32 -05:00
Zachary Yedidia
743d42e417 Syntax file change 2020-02-13 16:50:44 -05:00
Zachary Yedidia
faa207907c Handle terminal paste and raw events in info bar 2020-02-13 16:10:35 -05:00
Zachary Yedidia
9d1c489574 Merge 2020-02-13 16:09:16 -05:00
Zachary Yedidia
30ed25859a Support regex capture groups in replace command
See https://golang.org/pkg/regexp/syntax/ for the
supported syntax. Here are some examples:

```
replace "(foo)" "$1-bar"
replace "(foo)" "${1}-bar"
replace "(?P<group>foo)" "$group-bar"
replace "(?P<group>foo)" "$group-bar"
replace "(?P<key>\w+):\s+(?P<value>\w+)$" "$key=$value"
```

Closes #1115
2020-02-13 16:05:56 -05:00
Simon Ser
d2288c5f66 readme: document clipboard support on Wayland (#1508) 2020-02-13 14:00:18 -05:00
Zachary Yedidia
a07ee26b05 Fix gutter offset when softwrap is enabled 2020-02-13 11:04:10 -05:00
Zachary Yedidia
a7ce85d6f6 Make default plugin options more explicit
Ref #1305
2020-02-12 15:34:13 -05:00
86 changed files with 4389 additions and 1178 deletions

View File

@@ -2,7 +2,7 @@
## Specifications
You can use `micro -version` to get the commit hash.
<!-- You can use `micro -version` to get the commit hash. -->
Commit hash:
OS:

2
.gitignore vendored
View File

@@ -11,8 +11,10 @@ todo.txt
test.txt
log.txt
*.old
benchmark_results*
tools/build-version
tools/build-date
tools/info-plist
tools/bindata
tools/vscode-tests/
*.hdr

View File

@@ -8,14 +8,19 @@ DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH))
GOBIN ?= $(shell go env GOPATH)/bin
GOVARS = -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/internal/util.CompileDate=$(DATE)' -X github.com/zyedidia/micro/internal/util.Debug=OFF
GOVARS = -X github.com/zyedidia/micro/v2/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/v2/internal/util.CompileDate=$(DATE)'
DEBUGVAR = -X github.com/zyedidia/micro/v2/internal/util.Debug=ON
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
# Builds micro after checking dependencies but without updating the runtime
build:
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-dbg:
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
build-tags: fetch-tags
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Builds micro after building the runtime and checking dependencies
build-all: runtime build
@@ -35,6 +40,9 @@ install-all: runtime install
install-quick:
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
fetch-tags:
git fetch --tags
# Builds the runtime
runtime:
git submodule update --init
@@ -44,8 +52,34 @@ runtime:
mv runtime.go internal/config
gofmt -w internal/config/runtime.go
testgen:
mkdir -p tools/vscode-tests
cd tools/vscode-tests && \
curl --remote-name-all $(VSCODE_TESTS_BASE_URL){editableTextModelAuto,editableTextModel,model.line}.test.ts
tsc tools/vscode-tests/*.ts > /dev/null; true
go run tools/testgen.go tools/vscode-tests/*.js > buffer_generated_test.go
mv buffer_generated_test.go internal/buffer
gofmt -w internal/buffer/buffer_generated_test.go
test:
go test ./internal/...
bench:
for i in 1 2 3; do \
go test -bench=. ./internal/...; \
done > benchmark_results
benchstat benchmark_results
bench-baseline:
for i in 1 2 3; do \
go test -bench=. ./internal/...; \
done > benchmark_results_baseline
bench-compare:
for i in 1 2 3; do \
go test -bench=. ./internal/...; \
done > benchmark_results
benchstat benchmark_results_baseline benchmark_results
clean:
rm -f micro

View File

@@ -91,7 +91,7 @@ Running `micro -version` will give you the version information.
There is a script which can install micro for you by downloading the latest prebuilt binary. You can find it at <https://getmic.ro>.
Then you can easily install micro:
You can easily install micro by running
```bash
curl https://getmic.ro | bash
@@ -107,6 +107,10 @@ You can install micro using Homebrew on Mac:
brew install micro
```
**Note for Mac:** All micro keybindings use the control or alt (option) key, not the command
key. By default, macOS terminals do not forward alt key events. To fix this, please see
the section on [macOS terminals](https://github.com/zyedidia/micro#macos-terminal) further below.
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
```
@@ -120,6 +124,10 @@ via `apt`:
sudo apt install micro
```
**Note for Linux:** for interfacing with the local system clipboard, `xclip` or `xsel`
must be installed. Please see the section on [Linux clipboard support](https://github.com/zyedidia/micro#linux-clipboard-support)
further below.
Micro is also available through other package managers on Linux such as AUR, Nix, and package managers
for other operating systems:
@@ -128,6 +136,8 @@ for other operating systems:
* `scoop install micro`
* OpenBSD: Available in the ports tree and also available as a binary package
* `pkd_add -v micro`
* Arch Linux, CRUX, Termux for Android
* See details in the [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro)
### Building from source
@@ -170,15 +180,12 @@ If you still insist on using the default Mac terminal, be sure to set `Use Optio
### Linux clipboard support
On Linux, clipboard support requires the `xclip` or `xsel` commands to be installed.
On Linux, clipboard support requires:
For Ubuntu:
- On X11, the `xclip` or `xsel` commands (for Ubuntu: `sudo apt install xclip`)
- On Wayland, the `wl-clipboard` command
```sh
sudo apt-get install xclip
```
If you don't have `xclip` or `xsel`, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
If you don't have these commands, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
### Colors and syntax highlighting

View File

@@ -10,8 +10,8 @@ import (
"sort"
"strings"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
)
func shouldContinue() bool {

View File

@@ -4,7 +4,7 @@ import (
"log"
"os"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// NullWriter simply sends writes into the void

View File

@@ -6,14 +6,14 @@ import (
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"
"github.com/zyedidia/micro/internal/action"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/display"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
)
func init() {

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"io/ioutil"
"os"
"regexp"
"runtime"
"sort"
"time"
@@ -12,12 +13,12 @@ import (
"github.com/go-errors/errors"
isatty "github.com/mattn/go-isatty"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/action"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -44,6 +45,7 @@ func InitFlags() {
fmt.Println("-config-dir dir")
fmt.Println(" \tSpecify a custom location for the configuration directory")
fmt.Println("[FILE]:LINE:COL")
fmt.Println("+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")
@@ -147,11 +149,32 @@ func LoadInput() []*buffer.Buffer {
args := flag.Args()
buffers := make([]*buffer.Buffer, 0, len(args))
if len(args) > 0 {
btype := buffer.BTDefault
if !isatty.IsTerminal(os.Stdout.Fd()) {
btype = buffer.BTStdout
}
files := make([]string, 0, len(args))
flagStartPos := ""
flagr := regexp.MustCompile(`^\+\d+(:\d+)?$`)
for _, a := range args {
if flagr.MatchString(a) {
flagStartPos = a[1:]
} else {
if flagStartPos != "" {
files = append(files, a+":"+flagStartPos)
flagStartPos = ""
} else {
files = append(files, a)
}
}
}
if len(files) > 0 {
// Option 1
// We go through each file and load it
for i := 0; i < len(args); i++ {
buf, err := buffer.NewBufferFromFile(args[i], buffer.BTDefault)
for i := 0; i < len(files); i++ {
buf, err := buffer.NewBufferFromFile(files[i], btype)
if err != nil {
screen.TermMessage(err)
continue
@@ -168,17 +191,22 @@ func LoadInput() []*buffer.Buffer {
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
}
return buffers
}
func main() {
defer os.Exit(0)
defer func() {
if util.Stdout.Len() > 0 {
fmt.Fprint(os.Stdout, util.Stdout.String())
}
os.Exit(0)
}()
// runtime.SetCPUProfileRate(400)
// f, _ := os.Create("micro.prof")

8
go.mod
View File

@@ -1,4 +1,4 @@
module github.com/zyedidia/micro
module github.com/zyedidia/micro/v2
require (
github.com/blang/semver v3.5.1+incompatible
@@ -8,17 +8,19 @@ require (
github.com/mattn/go-isatty v0.0.11
github.com/mattn/go-runewidth v0.0.7
github.com/mitchellh/go-homedir v1.1.0
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff
github.com/sergi/go-diff v1.1.0
github.com/stretchr/testify v1.4.0
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/pty v2.0.0+incompatible // indirect
github.com/zyedidia/tcell v1.4.4
github.com/zyedidia/tcell v1.4.5
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
golang.org/x/text v0.3.2
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.2.7
layeh.com/gopher-luar v1.0.7
)

10
go.sum
View File

@@ -29,6 +29,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
@@ -40,6 +42,8 @@ github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197 h1:gYTNnAW6azuB3BbA6QYWO/H4F2ABSOjjw3Z03tlXd2c=
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834 h1:0nOfq3JwYRiY3+nwfWVQYEaXDmGCQgj3RKoqTifLzP4=
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5 h1:Zs6mpwXvlqpF9zHl5XaN0p5V4J9XvP+WBuiuXyIgqvc=
@@ -50,10 +54,10 @@ github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
github.com/zyedidia/tcell v1.4.3 h1:s0nmxj22Jj+vyt4nVmnB6kbnwRxEay1zfS0j8IsbtfI=
github.com/zyedidia/tcell v1.4.3/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/tcell v1.4.4 h1:o34LXujNuSueuyTy+5eoQW+rQr8g0UbY8k1NczZyskQ=
github.com/zyedidia/tcell v1.4.4/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/tcell v1.4.5 h1:JFmOiWLxr3Fsk2vjRL3n8oRUoJeyrazGhkhZqW31kEY=
github.com/zyedidia/tcell v1.4.5/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -67,6 +71,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=

View File

@@ -5,15 +5,14 @@ import (
"runtime"
"strings"
"time"
"unicode/utf8"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -175,7 +174,7 @@ func (h *BufPane) CursorRight() bool {
if tabstospaces && tabmovement {
tabsize := int(h.Buf.Settings["tabsize"].(float64))
line := h.Buf.LineBytes(h.Cursor.Y)
if h.Cursor.X+tabsize < utf8.RuneCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
if h.Cursor.X+tabsize < util.CharacterCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
for i := 0; i < tabsize; i++ {
h.Cursor.Right()
}
@@ -283,7 +282,7 @@ func (h *BufPane) SelectWordLeft() bool {
return true
}
// StartOfLine moves the cursor to the start of the text of the line
// StartOfText moves the cursor to the start of the text of the line
func (h *BufPane) StartOfText() bool {
h.Cursor.Deselect(true)
h.Cursor.StartOfText()
@@ -291,6 +290,19 @@ func (h *BufPane) StartOfText() bool {
return true
}
// StartOfTextToggle toggles the cursor between the start of the text of the line
// and the start of the line
func (h *BufPane) StartOfTextToggle() bool {
h.Cursor.Deselect(true)
if h.Cursor.IsStartOfText() {
h.Cursor.Start()
} else {
h.Cursor.StartOfText()
}
h.Relocate()
return true
}
// StartOfLine moves the cursor to the start of the line
func (h *BufPane) StartOfLine() bool {
h.Cursor.Deselect(true)
@@ -325,6 +337,22 @@ func (h *BufPane) SelectToStartOfText() bool {
return true
}
// SelectToStartOfTextToggle toggles the selection between the start of the text
// on the current line and the start of the line
func (h *BufPane) SelectToStartOfTextToggle() bool {
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
if h.Cursor.IsStartOfText() {
h.Cursor.Start()
} else {
h.Cursor.StartOfText()
}
h.Cursor.SelectTo(h.Cursor.Loc)
h.Relocate()
return true
}
// SelectToStartOfLine selects to the start of the current line
func (h *BufPane) SelectToStartOfLine() bool {
if !h.Cursor.HasSelection() {
@@ -396,6 +424,7 @@ func (h *BufPane) CursorStart() bool {
h.Cursor.Deselect(true)
h.Cursor.X = 0
h.Cursor.Y = 0
h.Cursor.StoreVisualX()
h.Relocate()
return true
}
@@ -456,7 +485,7 @@ func (h *BufPane) InsertNewline() bool {
// Remove the whitespaces if keepautoindent setting is off
if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
line := h.Buf.LineBytes(h.Cursor.Y - 1)
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
}
}
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
@@ -481,7 +510,7 @@ func (h *BufPane) Backspace() bool {
// tab (tabSize number of spaces)
lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
tabSize := int(h.Buf.Settings["tabsize"].(float64))
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
loc := h.Cursor.Loc
h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
} else {
@@ -772,10 +801,23 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
// Find opens a prompt and searches forward for the input
func (h *BufPane) Find() bool {
return h.find(true)
}
// FindLiteral is the same as Find() but does not support regular expressions
func (h *BufPane) FindLiteral() bool {
return h.find(false)
}
func (h *BufPane) find(useRegex bool) bool {
h.searchOrig = h.Cursor.Loc
InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
prompt := "Find: "
if useRegex {
prompt = "Find (regex): "
}
InfoBar.Prompt(prompt, "", "Find", func(resp string) {
// Event callback
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
@@ -790,7 +832,7 @@ func (h *BufPane) Find() bool {
}, func(resp string, canceled bool) {
// Finished callback
if !canceled {
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -801,6 +843,7 @@ func (h *BufPane) Find() bool {
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
h.lastSearch = resp
h.lastSearchRegex = useRegex
} else {
h.Cursor.ResetSelection()
InfoBar.Message("No matches found")
@@ -824,7 +867,7 @@ func (h *BufPane) FindNext() bool {
if h.Cursor.HasSelection() {
searchLoc = h.Cursor.CurSelection[1]
}
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -851,7 +894,7 @@ func (h *BufPane) FindPrevious() bool {
if h.Cursor.HasSelection() {
searchLoc = h.Cursor.CurSelection[0]
}
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -899,6 +942,25 @@ func (h *BufPane) Copy() bool {
return true
}
// Copy the current line to the clipboard
func (h *BufPane) CopyLine() bool {
if h.Cursor.HasSelection() {
return false
} else {
h.Cursor.SelectLine()
h.Cursor.CopySelection("clipboard")
h.freshClip = true
if clipboard.Unsupported {
InfoBar.Message("Copied line (install xclip for external clipboard)")
} else {
InfoBar.Message("Copied line")
}
}
h.Cursor.Deselect(true)
h.Relocate()
return true
}
// CutLine cuts the current line to the clipboard
func (h *BufPane) CutLine() bool {
h.Cursor.SelectLine()
@@ -978,15 +1040,26 @@ func (h *BufPane) MoveLinesUp() bool {
}
start := h.Cursor.CurSelection[0].Y
end := h.Cursor.CurSelection[1].Y
sel := 1
if start > end {
end, start = start, end
sel = 0
}
compensate := false
if h.Cursor.CurSelection[sel].X != 0 {
end++
} else {
compensate = true
}
h.Buf.MoveLinesUp(
start,
end,
)
h.Cursor.CurSelection[1].Y -= 1
if compensate {
h.Cursor.CurSelection[sel].Y -= 1
}
} else {
if h.Cursor.Loc.Y == 0 {
InfoBar.Message("Cannot move further up")
@@ -1011,8 +1084,14 @@ func (h *BufPane) MoveLinesDown() bool {
}
start := h.Cursor.CurSelection[0].Y
end := h.Cursor.CurSelection[1].Y
sel := 1
if start > end {
end, start = start, end
sel = 0
}
if h.Cursor.CurSelection[sel].X != 0 {
end++
}
h.Buf.MoveLinesDown(
@@ -1119,6 +1198,16 @@ func (h *BufPane) OpenFile() bool {
return true
}
// OpenFile opens a new file in the buffer
func (h *BufPane) JumpLine() bool {
InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
if !canceled {
h.HandleCommand(resp)
}
})
return true
}
// Start moves the viewport to the start of the buffer
func (h *BufPane) Start() bool {
v := h.GetView()
@@ -1406,8 +1495,9 @@ func (h *BufPane) AddTab() bool {
// PreviousTab switches to the previous tab in the tab list
func (h *BufPane) PreviousTab() bool {
a := Tabs.Active()
Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
tabsLen := len(Tabs.List)
a := Tabs.Active() + tabsLen
Tabs.SetActive((a - 1) % tabsLen)
return true
}
@@ -1415,7 +1505,8 @@ func (h *BufPane) PreviousTab() bool {
// NextTab switches to the next tab in the tab list
func (h *BufPane) NextTab() bool {
a := Tabs.Active()
Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
Tabs.SetActive((a + 1) % len(Tabs.List))
return true
}
@@ -1567,7 +1658,7 @@ func (h *BufPane) SpawnMultiCursorUp() bool {
return true
}
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y more.
// SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
func (h *BufPane) SpawnMultiCursorDown() bool {
if h.Cursor.Y+1 == h.Buf.LinesNum() {
return false

View File

@@ -5,7 +5,7 @@ package action
import (
"syscall"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/screen"
)
// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.

View File

@@ -10,8 +10,8 @@ import (
"unicode"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell"
)
@@ -249,6 +249,7 @@ func UnbindKey(k string) error {
if a, ok := defaults[k]; ok {
BindKey(k, a)
} else if _, ok := config.Bindings[k]; ok {
BufUnmap(key)
delete(config.Bindings, k)
}

View File

@@ -7,11 +7,11 @@ import (
luar "layeh.com/gopher-luar"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/display"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell"
)
@@ -103,7 +103,7 @@ func BufMapKey(k Event, action string) {
afn = f
names = append(names, a)
} else {
screen.TermMessage("Error:", a, "does not exist")
screen.TermMessage("Error in bindings: action", a, "does not exist")
continue
}
actionfns = append(actionfns, afn)
@@ -139,6 +139,17 @@ func BufMapMouse(k MouseEvent, action string) {
}
}
// BufUnmap unmaps a key or mouse event from any action
func BufUnmap(k Event) {
delete(BufKeyBindings, k)
delete(BufKeyStrings, k)
switch e := k.(type) {
case MouseEvent:
delete(BufMouseBindings, e)
}
}
// The BufPane connects the buffer and the window
// It provides a cursor (or multiple) and defines a set of actions
// that can be taken on the buffer
@@ -179,7 +190,8 @@ type BufPane struct {
tripleClick bool
// Last search stores the last successful search for FindNext and FindPrev
lastSearch string
lastSearch string
lastSearchRegex bool
// Should the current multiple cursor selection search based on word or
// based on selection (false for selection, true for word)
multiWord bool
@@ -270,7 +282,11 @@ func (h *BufPane) SetID(i uint64) {
}
func (h *BufPane) Name() string {
return h.Buf.GetName()
n := h.Buf.GetName()
if h.Buf.Modified() {
n += " +"
}
return n
}
// HandleEvent executes the tcell event properly
@@ -339,10 +355,11 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
// release the mouse
// if !h.doubleClick && !h.tripleClick {
// h.Cursor.Loc = mouseLoc
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
// h.Cursor.CopySelection("primary")
// }
if h.Cursor.HasSelection() {
h.Cursor.CopySelection("primary")
}
h.mouseReleased = true
}
}
@@ -458,6 +475,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
if recording_macro {
curmacro = append(curmacro, r)
}
h.Relocate()
h.PluginCBRune("onRune", r)
}
}
@@ -511,106 +529,111 @@ func (h *BufPane) SetActive(b bool) {
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
var BufKeyActions = map[string]BufKeyAction{
"CursorUp": (*BufPane).CursorUp,
"CursorDown": (*BufPane).CursorDown,
"CursorPageUp": (*BufPane).CursorPageUp,
"CursorPageDown": (*BufPane).CursorPageDown,
"CursorLeft": (*BufPane).CursorLeft,
"CursorRight": (*BufPane).CursorRight,
"CursorStart": (*BufPane).CursorStart,
"CursorEnd": (*BufPane).CursorEnd,
"SelectToStart": (*BufPane).SelectToStart,
"SelectToEnd": (*BufPane).SelectToEnd,
"SelectUp": (*BufPane).SelectUp,
"SelectDown": (*BufPane).SelectDown,
"SelectLeft": (*BufPane).SelectLeft,
"SelectRight": (*BufPane).SelectRight,
"WordRight": (*BufPane).WordRight,
"WordLeft": (*BufPane).WordLeft,
"SelectWordRight": (*BufPane).SelectWordRight,
"SelectWordLeft": (*BufPane).SelectWordLeft,
"DeleteWordRight": (*BufPane).DeleteWordRight,
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
"SelectLine": (*BufPane).SelectLine,
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
"ParagraphNext": (*BufPane).ParagraphNext,
"InsertNewline": (*BufPane).InsertNewline,
"Backspace": (*BufPane).Backspace,
"Delete": (*BufPane).Delete,
"InsertTab": (*BufPane).InsertTab,
"Save": (*BufPane).Save,
"SaveAll": (*BufPane).SaveAll,
"SaveAs": (*BufPane).SaveAs,
"Find": (*BufPane).Find,
"FindNext": (*BufPane).FindNext,
"FindPrevious": (*BufPane).FindPrevious,
"Center": (*BufPane).Center,
"Undo": (*BufPane).Undo,
"Redo": (*BufPane).Redo,
"Copy": (*BufPane).Copy,
"Cut": (*BufPane).Cut,
"CutLine": (*BufPane).CutLine,
"DuplicateLine": (*BufPane).DuplicateLine,
"DeleteLine": (*BufPane).DeleteLine,
"MoveLinesUp": (*BufPane).MoveLinesUp,
"MoveLinesDown": (*BufPane).MoveLinesDown,
"IndentSelection": (*BufPane).IndentSelection,
"OutdentSelection": (*BufPane).OutdentSelection,
"Autocomplete": (*BufPane).Autocomplete,
"CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
"OutdentLine": (*BufPane).OutdentLine,
"IndentLine": (*BufPane).IndentLine,
"Paste": (*BufPane).Paste,
"PastePrimary": (*BufPane).PastePrimary,
"SelectAll": (*BufPane).SelectAll,
"OpenFile": (*BufPane).OpenFile,
"Start": (*BufPane).Start,
"End": (*BufPane).End,
"PageUp": (*BufPane).PageUp,
"PageDown": (*BufPane).PageDown,
"SelectPageUp": (*BufPane).SelectPageUp,
"SelectPageDown": (*BufPane).SelectPageDown,
"HalfPageUp": (*BufPane).HalfPageUp,
"HalfPageDown": (*BufPane).HalfPageDown,
"StartOfText": (*BufPane).StartOfText,
"StartOfLine": (*BufPane).StartOfLine,
"EndOfLine": (*BufPane).EndOfLine,
"ToggleHelp": (*BufPane).ToggleHelp,
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
"ToggleRuler": (*BufPane).ToggleRuler,
"ClearStatus": (*BufPane).ClearStatus,
"ShellMode": (*BufPane).ShellMode,
"CommandMode": (*BufPane).CommandMode,
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
"Escape": (*BufPane).Escape,
"Quit": (*BufPane).Quit,
"QuitAll": (*BufPane).QuitAll,
"AddTab": (*BufPane).AddTab,
"PreviousTab": (*BufPane).PreviousTab,
"NextTab": (*BufPane).NextTab,
"NextSplit": (*BufPane).NextSplit,
"PreviousSplit": (*BufPane).PreviousSplit,
"Unsplit": (*BufPane).Unsplit,
"VSplit": (*BufPane).VSplitAction,
"HSplit": (*BufPane).HSplitAction,
"ToggleMacro": (*BufPane).ToggleMacro,
"PlayMacro": (*BufPane).PlayMacro,
"Suspend": (*BufPane).Suspend,
"ScrollUp": (*BufPane).ScrollUpAction,
"ScrollDown": (*BufPane).ScrollDownAction,
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
"SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
"SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
"None": (*BufPane).None,
"CursorUp": (*BufPane).CursorUp,
"CursorDown": (*BufPane).CursorDown,
"CursorPageUp": (*BufPane).CursorPageUp,
"CursorPageDown": (*BufPane).CursorPageDown,
"CursorLeft": (*BufPane).CursorLeft,
"CursorRight": (*BufPane).CursorRight,
"CursorStart": (*BufPane).CursorStart,
"CursorEnd": (*BufPane).CursorEnd,
"SelectToStart": (*BufPane).SelectToStart,
"SelectToEnd": (*BufPane).SelectToEnd,
"SelectUp": (*BufPane).SelectUp,
"SelectDown": (*BufPane).SelectDown,
"SelectLeft": (*BufPane).SelectLeft,
"SelectRight": (*BufPane).SelectRight,
"WordRight": (*BufPane).WordRight,
"WordLeft": (*BufPane).WordLeft,
"SelectWordRight": (*BufPane).SelectWordRight,
"SelectWordLeft": (*BufPane).SelectWordLeft,
"DeleteWordRight": (*BufPane).DeleteWordRight,
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
"SelectLine": (*BufPane).SelectLine,
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
"SelectToStartOfTextToggle": (*BufPane).SelectToStartOfTextToggle,
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
"ParagraphNext": (*BufPane).ParagraphNext,
"InsertNewline": (*BufPane).InsertNewline,
"Backspace": (*BufPane).Backspace,
"Delete": (*BufPane).Delete,
"InsertTab": (*BufPane).InsertTab,
"Save": (*BufPane).Save,
"SaveAll": (*BufPane).SaveAll,
"SaveAs": (*BufPane).SaveAs,
"Find": (*BufPane).Find,
"FindLiteral": (*BufPane).FindLiteral,
"FindNext": (*BufPane).FindNext,
"FindPrevious": (*BufPane).FindPrevious,
"Center": (*BufPane).Center,
"Undo": (*BufPane).Undo,
"Redo": (*BufPane).Redo,
"Copy": (*BufPane).Copy,
"CopyLine": (*BufPane).CopyLine,
"Cut": (*BufPane).Cut,
"CutLine": (*BufPane).CutLine,
"DuplicateLine": (*BufPane).DuplicateLine,
"DeleteLine": (*BufPane).DeleteLine,
"MoveLinesUp": (*BufPane).MoveLinesUp,
"MoveLinesDown": (*BufPane).MoveLinesDown,
"IndentSelection": (*BufPane).IndentSelection,
"OutdentSelection": (*BufPane).OutdentSelection,
"Autocomplete": (*BufPane).Autocomplete,
"CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
"OutdentLine": (*BufPane).OutdentLine,
"IndentLine": (*BufPane).IndentLine,
"Paste": (*BufPane).Paste,
"PastePrimary": (*BufPane).PastePrimary,
"SelectAll": (*BufPane).SelectAll,
"OpenFile": (*BufPane).OpenFile,
"Start": (*BufPane).Start,
"End": (*BufPane).End,
"PageUp": (*BufPane).PageUp,
"PageDown": (*BufPane).PageDown,
"SelectPageUp": (*BufPane).SelectPageUp,
"SelectPageDown": (*BufPane).SelectPageDown,
"HalfPageUp": (*BufPane).HalfPageUp,
"HalfPageDown": (*BufPane).HalfPageDown,
"StartOfText": (*BufPane).StartOfText,
"StartOfTextToggle": (*BufPane).StartOfTextToggle,
"StartOfLine": (*BufPane).StartOfLine,
"EndOfLine": (*BufPane).EndOfLine,
"ToggleHelp": (*BufPane).ToggleHelp,
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
"ToggleRuler": (*BufPane).ToggleRuler,
"ClearStatus": (*BufPane).ClearStatus,
"ShellMode": (*BufPane).ShellMode,
"CommandMode": (*BufPane).CommandMode,
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
"Escape": (*BufPane).Escape,
"Quit": (*BufPane).Quit,
"QuitAll": (*BufPane).QuitAll,
"AddTab": (*BufPane).AddTab,
"PreviousTab": (*BufPane).PreviousTab,
"NextTab": (*BufPane).NextTab,
"NextSplit": (*BufPane).NextSplit,
"PreviousSplit": (*BufPane).PreviousSplit,
"Unsplit": (*BufPane).Unsplit,
"VSplit": (*BufPane).VSplitAction,
"HSplit": (*BufPane).HSplitAction,
"ToggleMacro": (*BufPane).ToggleMacro,
"PlayMacro": (*BufPane).PlayMacro,
"Suspend": (*BufPane).Suspend,
"ScrollUp": (*BufPane).ScrollUpAction,
"ScrollDown": (*BufPane).ScrollDownAction,
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
"SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
"SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
"JumpLine": (*BufPane).JumpLine,
"None": (*BufPane).None,
// This was changed to InsertNewline but I don't want to break backwards compatibility
"InsertEnter": (*BufPane).InsertNewline,
@@ -627,54 +650,57 @@ var BufMouseActions = map[string]BufMouseAction{
// Generally actions that modify global editor state like quitting or
// saving should not be included in this list
var MultiActions = map[string]bool{
"CursorUp": true,
"CursorDown": true,
"CursorPageUp": true,
"CursorPageDown": true,
"CursorLeft": true,
"CursorRight": true,
"CursorStart": true,
"CursorEnd": true,
"SelectToStart": true,
"SelectToEnd": true,
"SelectUp": true,
"SelectDown": true,
"SelectLeft": true,
"SelectRight": true,
"WordRight": true,
"WordLeft": true,
"SelectWordRight": true,
"SelectWordLeft": true,
"DeleteWordRight": true,
"DeleteWordLeft": true,
"SelectLine": true,
"SelectToStartOfLine": true,
"SelectToStartOfText": true,
"SelectToEndOfLine": true,
"ParagraphPrevious": true,
"ParagraphNext": true,
"InsertNewline": true,
"Backspace": true,
"Delete": true,
"InsertTab": true,
"FindNext": true,
"FindPrevious": true,
"Cut": true,
"CutLine": true,
"DuplicateLine": true,
"DeleteLine": true,
"MoveLinesUp": true,
"MoveLinesDown": true,
"IndentSelection": true,
"OutdentSelection": true,
"OutdentLine": true,
"IndentLine": true,
"Paste": true,
"PastePrimary": true,
"SelectPageUp": true,
"SelectPageDown": true,
"StartOfLine": true,
"StartOfText": true,
"EndOfLine": true,
"JumpToMatchingBrace": true,
"CursorUp": true,
"CursorDown": true,
"CursorPageUp": true,
"CursorPageDown": true,
"CursorLeft": true,
"CursorRight": true,
"CursorStart": true,
"CursorEnd": true,
"SelectToStart": true,
"SelectToEnd": true,
"SelectUp": true,
"SelectDown": true,
"SelectLeft": true,
"SelectRight": true,
"WordRight": true,
"WordLeft": true,
"SelectWordRight": true,
"SelectWordLeft": true,
"DeleteWordRight": true,
"DeleteWordLeft": true,
"SelectLine": true,
"SelectToStartOfLine": true,
"SelectToStartOfText": true,
"SelectToStartOfTextToggle": true,
"SelectToEndOfLine": true,
"ParagraphPrevious": true,
"ParagraphNext": true,
"InsertNewline": true,
"Backspace": true,
"Delete": true,
"InsertTab": true,
"FindNext": true,
"FindPrevious": true,
"CopyLine": true,
"Cut": true,
"CutLine": true,
"DuplicateLine": true,
"DeleteLine": true,
"MoveLinesUp": true,
"MoveLinesDown": true,
"IndentSelection": true,
"OutdentSelection": true,
"OutdentLine": true,
"IndentLine": true,
"Paste": true,
"PastePrimary": true,
"SelectPageUp": true,
"SelectPageDown": true,
"StartOfLine": true,
"StartOfText": true,
"StartOfTextToggle": true,
"EndOfLine": true,
"JumpToMatchingBrace": true,
}

View File

@@ -10,14 +10,13 @@ import (
"regexp"
"strconv"
"strings"
"unicode/utf8"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
)
// A Command contains information about how to execute a command
@@ -56,6 +55,7 @@ func InitCommands() {
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
"pwd": {(*BufPane).PwdCmd, nil},
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
"tabmove": {(*BufPane).TabMoveCmd, nil},
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
"term": {(*BufPane).TermCmd, nil},
"memusage": {(*BufPane).MemUsageCmd, nil},
@@ -155,6 +155,56 @@ func (h *BufPane) TextFilterCmd(args []string) {
h.Buf.Insert(h.Cursor.Loc, bout.String())
}
// TabMoveCmd moves the current tab to a given index (starts at 1). The
// displaced tabs are moved up.
func (h *BufPane) TabMoveCmd(args []string) {
if len(args) <= 0 {
InfoBar.Error("Not enough arguments: provide an index, starting at 1")
return
}
if len(args[0]) <= 0 {
InfoBar.Error("Invalid argument: empty string")
return
}
num, err := strconv.Atoi(args[0])
if err != nil {
InfoBar.Error("Invalid argument: ", err)
return
}
// Preserve sign for relative move, if one exists
var shiftDirection byte
if strings.Contains("-+", string([]byte{args[0][0]})) {
shiftDirection = args[0][0]
}
// Relative positions -> absolute positions
idxFrom := Tabs.Active()
idxTo := 0
offset := util.Abs(num)
if shiftDirection == '-' {
idxTo = idxFrom - offset
} else if shiftDirection == '+' {
idxTo = idxFrom + offset
} else {
idxTo = offset - 1
}
// Restrain position to within the valid range
idxTo = util.Clamp(idxTo, 0, len(Tabs.List)-1)
activeTab := Tabs.List[idxFrom]
Tabs.RemoveTab(activeTab.ID())
Tabs.List = append(Tabs.List, nil)
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
Tabs.List[idxTo] = activeTab
Tabs.UpdateNames()
Tabs.SetActive(idxTo)
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
}
// TabSwitchCmd switches to a given tab either by name or by number
func (h *BufPane) TabSwitchCmd(args []string) {
if len(args) > 0 {
@@ -652,7 +702,7 @@ func (h *BufPane) GotoCmd(args []string) {
return
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
col = util.Clamp(col-1, 0, utf8.RuneCount(h.Buf.LineBytes(line)))
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
h.Cursor.GotoLoc(buffer.Loc{col, line})
} else {
line, err := strconv.Atoi(args[0])
@@ -732,23 +782,23 @@ func (h *BufPane) ReplaceCmd(args []string) {
nreplaced := 0
start := h.Buf.Start()
// end := h.Buf.End()
// if h.Cursor.HasSelection() {
// start = h.Cursor.CurSelection[0]
// end = h.Cursor.CurSelection[1]
// }
end := h.Buf.End()
selection := h.Cursor.HasSelection()
if selection {
start = h.Cursor.CurSelection[0]
end = h.Cursor.CurSelection[1]
}
if all {
nreplaced = h.Buf.ReplaceRegex(start, h.Buf.End(), regex, replace)
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace)
} else {
inRange := func(l buffer.Loc) bool {
return l.GreaterEqual(start) && l.LessEqual(h.Buf.End())
return l.GreaterEqual(start) && l.LessEqual(end)
}
searchLoc := start
searching := true
var doReplacement func()
doReplacement = func() {
locs, found, err := h.Buf.FindNext(search, start, h.Buf.End(), searchLoc, true, !noRegex)
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, !noRegex)
if err != nil {
InfoBar.Error(err)
return
@@ -761,40 +811,50 @@ func (h *BufPane) ReplaceCmd(args []string) {
h.Cursor.SetSelectionStart(locs[0])
h.Cursor.SetSelectionEnd(locs[1])
h.Cursor.GotoLoc(locs[0])
h.Relocate()
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
if !canceled && yes {
h.Buf.Replace(locs[0], locs[1], replaceStr)
_, nrunes := h.Buf.ReplaceRegex(locs[0], locs[1], regex, replace)
searchLoc = locs[0]
searchLoc.X += utf8.RuneCount(replace)
searchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf)
end.Move(nrunes, h.Buf)
h.Cursor.Loc = searchLoc
nreplaced++
} else if !canceled && !yes {
searchLoc = locs[0]
searchLoc.X += utf8.RuneCount(replace)
searchLoc.X += util.CharacterCount(replace)
} else if canceled {
h.Cursor.ResetSelection()
h.Buf.RelocateCursors()
return
}
if searching {
doReplacement()
}
doReplacement()
})
}
doReplacement()
}
h.Buf.RelocateCursors()
h.Relocate()
var s string
if nreplaced > 1 {
InfoBar.Message("Replaced ", nreplaced, " occurrences of ", search)
s = fmt.Sprintf("Replaced %d occurrences of %s", nreplaced, search)
} else if nreplaced == 1 {
InfoBar.Message("Replaced ", nreplaced, " occurrence of ", search)
s = fmt.Sprintf("Replaced 1 occurrence of %s", search)
} else {
InfoBar.Message("Nothing matched ", search)
s = fmt.Sprintf("Nothing matched %s", search)
}
if selection {
s += " in selection"
}
InfoBar.Message(s)
}
// ReplaceAllCmd replaces search term all at once
@@ -823,7 +883,11 @@ func (h *BufPane) TermCmd(args []string) {
term := func(i int, newtab bool) {
t := new(shell.Terminal)
t.Start(args, false, true, nil, nil)
err := t.Start(args, false, true, nil, nil)
if err != nil {
InfoBar.Error(err)
return
}
id := h.ID()
if newtab {

View File

@@ -17,10 +17,10 @@ func DefaultBindings() map[string]string {
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfText",
"CtrlLeft": "StartOfTextToggle",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfText",
"ShiftHome": "SelectToStartOfText",
"CtrlShiftLeft": "SelectToStartOfTextToggle",
"ShiftHome": "SelectToStartOfTextToggle",
"CtrlShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
@@ -43,7 +43,7 @@ func DefaultBindings() map[string]string {
"CtrlP": "FindPrevious",
"CtrlZ": "Undo",
"CtrlY": "Redo",
"CtrlC": "Copy",
"CtrlC": "CopyLine|Copy",
"CtrlX": "Cut",
"CtrlK": "CutLine",
"CtrlD": "DuplicateLine",
@@ -52,7 +52,7 @@ func DefaultBindings() map[string]string {
"CtrlT": "AddTab",
"Alt,": "PreviousTab",
"Alt.": "NextTab",
"Home": "StartOfText",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",

View File

@@ -19,10 +19,10 @@ func DefaultBindings() map[string]string {
"AltDown": "MoveLinesDown",
"CtrlShiftRight": "SelectWordRight",
"CtrlShiftLeft": "SelectWordLeft",
"AltLeft": "StartOfText",
"AltLeft": "StartOfTextToggle",
"AltRight": "EndOfLine",
"AltShiftLeft": "SelectToStartOfText",
"ShiftHome": "SelectToStartOfText",
"AltShiftLeft": "SelectToStartOfTextToggle",
"ShiftHome": "SelectToStartOfTextToggle",
"AltShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
@@ -45,7 +45,7 @@ func DefaultBindings() map[string]string {
"CtrlP": "FindPrevious",
"CtrlZ": "Undo",
"CtrlY": "Redo",
"CtrlC": "Copy",
"CtrlC": "CopyLine|Copy",
"CtrlX": "Cut",
"CtrlK": "CutLine",
"CtrlD": "DuplicateLine",
@@ -54,7 +54,7 @@ func DefaultBindings() map[string]string {
"CtrlT": "AddTab",
"Alt,": "PreviousTab",
"Alt.": "NextTab",
"Home": "StartOfText",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",

View File

@@ -1,6 +1,6 @@
package action
import "github.com/zyedidia/micro/internal/buffer"
import "github.com/zyedidia/micro/v2/internal/buffer"
var InfoBar *InfoPane
var LogBufPane *BufPane

View File

@@ -5,9 +5,9 @@ import (
"sort"
"strings"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
)
// This file is meant (for now) for autocompletion in command mode, not

View File

@@ -4,9 +4,10 @@ import (
"bytes"
"strings"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/internal/info"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/info"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -68,7 +69,7 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
h.EventCallback(resp)
}
}
case *tcell.EventMouse:
default:
h.BufPane.HandleEvent(event)
}
}
@@ -186,14 +187,17 @@ func (h *InfoPane) Autocomplete() {
args := bytes.Split(l, []byte{' '})
cmd := string(args[0])
if len(args) == 1 {
b.Autocomplete(CommandComplete)
} else {
if action, ok := commands[cmd]; ok {
if h.PromptType == "Command" {
if len(args) == 1 {
b.Autocomplete(CommandComplete)
} else if action, ok := commands[cmd]; ok {
if action.completer != nil {
b.Autocomplete(action.completer)
}
}
} else {
// by default use filename autocompletion
b.Autocomplete(buffer.FileComplete)
}
}

View File

@@ -1,7 +1,7 @@
package action
import (
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/v2/internal/display"
)
type Pane interface {

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"reflect"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/tcell"
)

View File

@@ -1,11 +1,11 @@
package action
import (
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/views"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/views"
"github.com/zyedidia/tcell"
)
@@ -166,6 +166,8 @@ type Tab struct {
active int
resizing *views.Node // node currently being resized
// captures whether the mouse is released
release bool
}
// NewTabFromBuffer creates a new tab from the given buffer
@@ -203,6 +205,8 @@ func (t *Tab) HandleEvent(event tcell.Event) {
mx, my := e.Position()
switch e.Buttons() {
case tcell.Button1:
wasReleased := t.release
t.release = false
if t.resizing != nil {
var size int
if t.resizing.Kind == views.STVert {
@@ -215,22 +219,25 @@ func (t *Tab) HandleEvent(event tcell.Event) {
return
}
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
if resizeID != 0 {
t.resizing = t.GetNode(uint64(resizeID))
return
}
if wasReleased {
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
if resizeID != 0 {
t.resizing = t.GetNode(uint64(resizeID))
return
}
for i, p := range t.Panes {
v := p.GetView()
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
if inpane {
t.SetActive(i)
break
for i, p := range t.Panes {
v := p.GetView()
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
if inpane {
t.SetActive(i)
break
}
}
}
case tcell.ButtonNone:
t.resizing = nil
t.release = true
default:
for _, p := range t.Panes {
v := p.GetView()

View File

@@ -4,7 +4,7 @@ package action
import (
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/v2/internal/shell"
)
// TermEmuSupported is a constant that marks if the terminal emulator is supported

View File

@@ -5,9 +5,9 @@ import (
"runtime"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/tcell"
"github.com/zyedidia/terminal"
)

View File

@@ -6,9 +6,8 @@ import (
"os"
"sort"
"strings"
"unicode/utf8"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// A Completer is a function that takes a buffer and returns info
@@ -54,7 +53,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
start := c.Loc
end := c.Loc
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
start = end.Move(-utf8.RuneCountInString(b.Completions[prevSuggestion]), b)
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
} else {
// end = start.Move(1, b)
}
@@ -82,7 +81,7 @@ func GetWord(b *Buffer) ([]byte, int) {
args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
input := args[len(args)-1]
return input, c.X - utf8.RuneCount(input)
return input, c.X - util.CharacterCount(input)
}
// GetArg gets the most recent word (separated by ' ' only)
@@ -98,7 +97,7 @@ func GetArg(b *Buffer) (string, int) {
if i == len(args)-1 {
break
}
argstart += utf8.RuneCount(a) + 1
argstart += util.CharacterCount(a) + 1
}
return input, argstart
@@ -162,7 +161,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
return []string{}, []string{}
}
inputLen := utf8.RuneCount(input)
inputLen := util.CharacterCount(input)
suggestionsSet := make(map[string]struct{})
@@ -171,7 +170,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
strw := string(w)
if _, ok := suggestionsSet[strw]; !ok {
suggestionsSet[strw] = struct{}{}
@@ -184,7 +183,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
strw := string(w)
if _, ok := suggestionsSet[strw]; !ok {
suggestionsSet[strw] = struct{}{}

View File

@@ -7,9 +7,9 @@ import (
"path/filepath"
"time"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding"
)

View File

@@ -5,25 +5,25 @@ import (
"bytes"
"crypto/md5"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
luar "layeh.com/gopher-luar"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/pkg/highlight"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/pkg/highlight"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
@@ -60,6 +60,9 @@ var (
BTRaw = BufType{4, false, true, false}
// BTInfo is a buffer for inputting information
BTInfo = BufType{5, false, true, false}
// BTStdout is a buffer that only writes to stdout
// when closed
BTStdout = BufType{6, false, true, true}
// ErrFileTooLarge is returned when the file is too large to hash
// (fastdirty is automatically enabled)
@@ -75,6 +78,34 @@ type SharedBuffer struct {
// Type of the buffer (e.g. help, raw, scratch etc..)
Type BufType
// Path to the file on disk
Path string
// Absolute path to the file on disk
AbsPath string
// Name of the buffer on the status line
name string
toStdout bool
// Settings customized by the user
Settings map[string]interface{}
Suggestions []string
Completions []string
CurSuggestion int
Messages []*Message
updateDiffTimer *time.Timer
diffBase []byte
diffBaseLineCount int
diffLock sync.RWMutex
diff map[int]DiffStatus
// counts the number of edits
// resets every backupTime edits
lastbackup time.Time
// ReloadDisabled allows the user to disable reloads if they
// are viewing a file that is constantly changing
ReloadDisabled bool
@@ -84,8 +115,13 @@ type SharedBuffer struct {
// it changes based on how the buffer has changed
HasSuggestions bool
// Modifications is the list of modified regions for syntax highlighting
Modifications []Loc
// The Highlighter struct actually performs the highlighting
Highlighter *highlight.Highlighter
// SyntaxDef represents the syntax highlighting definition being used
// This stores the highlighting rules and filetype detection info
SyntaxDef *highlight.Def
ModifiedThisFrame bool
// Hash of the original buffer -- empty if fastdirty is on
origHash [md5.Size]byte
@@ -96,17 +132,35 @@ func (b *SharedBuffer) insert(pos Loc, value []byte) {
b.HasSuggestions = false
b.LineArray.insert(pos, value)
// b.Modifications is cleared every screen redraw so it's
// ok to append duplicates
b.Modifications = append(b.Modifications, Loc{pos.Y, pos.Y + bytes.Count(value, []byte{'\n'})})
inslines := bytes.Count(value, []byte{'\n'})
b.MarkModified(pos.Y, pos.Y+inslines)
}
func (b *SharedBuffer) remove(start, end Loc) []byte {
b.isModified = true
b.HasSuggestions = false
b.Modifications = append(b.Modifications, Loc{start.Y, start.Y})
defer b.MarkModified(start.Y, end.Y)
return b.LineArray.remove(start, end)
}
// MarkModified marks the buffer as modified for this frame
// and performs rehighlighting if syntax highlighting is enabled
func (b *SharedBuffer) MarkModified(start, end int) {
b.ModifiedThisFrame = true
if !b.Settings["syntax"].(bool) || b.SyntaxDef == nil {
return
}
start = util.Clamp(start, 0, len(b.lines)-1)
end = util.Clamp(end, 0, len(b.lines)-1)
l := -1
for i := start; i <= end; i++ {
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
}
b.Highlighter.HighlightMatches(b, start, l)
}
// DisableReload disables future reloads of this sharedbuffer
func (b *SharedBuffer) DisableReload() {
b.ReloadDisabled = true
@@ -135,39 +189,6 @@ type Buffer struct {
cursors []*Cursor
curCursor int
StartCursor Loc
// Path to the file on disk
Path string
// Absolute path to the file on disk
AbsPath string
// Name of the buffer on the status line
name string
// SyntaxDef represents the syntax highlighting definition being used
// This stores the highlighting rules and filetype detection info
SyntaxDef *highlight.Def
// The Highlighter struct actually performs the highlighting
Highlighter *highlight.Highlighter
HighlightLock sync.Mutex
// Settings customized by the user
Settings map[string]interface{}
Suggestions []string
Completions []string
CurSuggestion int
Messages []*Message
updateDiffTimer *time.Timer
diffBase []byte
diffBaseLineCount int
diffLock sync.RWMutex
diff map[int]DiffStatus
// counts the number of edits
// resets every backupTime edits
lastbackup time.Time
}
// NewBufferFromFile opens a new buffer using the given path
@@ -222,23 +243,6 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
b := new(Buffer)
b.Settings = config.DefaultCommonSettings()
for k, v := range config.GlobalSettings {
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
// make sure setting is not global-only
b.Settings[k] = v
}
}
config.InitLocalSettings(b.Settings, path)
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
enc = unicode.UTF8
b.Settings["encoding"] = "utf-8"
}
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
found := false
if len(path) > 0 {
for _, buf := range OpenBuffers {
@@ -250,28 +254,44 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
}
}
b.Path = path
b.AbsPath = absPath
if !found {
b.SharedBuffer = new(SharedBuffer)
b.Type = btype
b.AbsPath = absPath
b.Path = path
b.Settings = config.DefaultCommonSettings()
for k, v := range config.GlobalSettings {
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
// make sure setting is not global-only
b.Settings[k] = v
}
}
config.InitLocalSettings(b.Settings, path)
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
enc = unicode.UTF8
b.Settings["encoding"] = "utf-8"
}
hasBackup := b.ApplyBackup(size)
if !hasBackup {
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
}
b.EventHandler = NewEventHandler(b.SharedBuffer, b.cursors)
// The last time this file was modified
b.UpdateModTime()
}
if b.Settings["readonly"].(bool) && b.Type == BTDefault {
b.Type.Readonly = true
}
// The last time this file was modified
b.UpdateModTime()
switch b.Endings {
case FFUnix:
b.Settings["fileformat"] = "unix"
@@ -280,6 +300,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
}
b.UpdateRules()
// init local settings again now that we know the filetype
config.InitLocalSettings(b.Settings, b.Path)
if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); os.IsNotExist(err) {
@@ -300,7 +321,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
b.AddCursor(NewCursor(b, b.StartCursor))
b.GetActiveCursor().Relocate()
if !b.Settings["fastdirty"].(bool) {
if !b.Settings["fastdirty"].(bool) && !found {
if size > LargeFileThreshold {
// If the file is larger than LargeFileThreshold fastdirty needs to be on
b.Settings["fastdirty"] = true
@@ -309,13 +330,11 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
}
}
err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
err := config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
if err != nil {
screen.TermMessage(err)
}
b.Modifications = make([]Loc, 0, 10)
OpenBuffers = append(OpenBuffers, b)
return b
@@ -341,18 +360,26 @@ func (b *Buffer) Fini() {
b.Serialize()
}
b.RemoveBackup()
if b.Type == BTStdout {
fmt.Fprint(util.Stdout, string(b.Bytes()))
}
}
// GetName returns the name that should be displayed in the statusline
// for this buffer
func (b *Buffer) GetName() string {
if b.name == "" {
name := b.name
if name == "" {
if b.Path == "" {
return "No name"
}
return b.Path
name = b.Path
}
return b.name
if b.Settings["basename"].(bool) {
return path.Base(name)
}
return name
}
//SetName changes the name for this buffer
@@ -382,16 +409,6 @@ func (b *Buffer) Remove(start, end Loc) {
}
}
// ClearModifications clears the list of modified lines in this buffer
// The list of modified lines is used for syntax highlighting so that
// we can selectively highlight only the necessary lines
// This function should be called every time this buffer is drawn to
// the screen
func (b *Buffer) ClearModifications() {
// clear slice without resetting the cap
b.Modifications = b.Modifications[:0]
}
// FileType returns the buffer's filetype
func (b *Buffer) FileType() string {
return b.Settings["filetype"].(string)
@@ -456,7 +473,7 @@ func (b *Buffer) RuneAt(loc Loc) rune {
if len(line) > 0 {
i := 0
for len(line) > 0 {
r, size := utf8.DecodeRune(line)
r, _, size := util.DecodeCharacter(line)
line = line[size:]
i++
@@ -530,7 +547,37 @@ func (b *Buffer) UpdateRules() {
return
}
syntaxFile := ""
foundDef := false
var header *highlight.Header
// search for the syntax file in the user's custom syntax files
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
data, err := f.Data()
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
header, err = highlight.MakeHeaderYaml(data)
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
if ((ft == "unknown" || ft == "") && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
syndef, err := highlight.ParseDef(file, header)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
b.SyntaxDef = syndef
syntaxFile = f.Name()
foundDef = true
break
}
}
// search in the default syntax files
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
data, err := f.Data()
if err != nil {
@@ -555,34 +602,8 @@ func (b *Buffer) UpdateRules() {
}
}
if syntaxFile == "" {
// search for the syntax file in the user's custom syntax files
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
log.Println("real runtime file", f.Name())
data, err := f.Data()
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
header, err = highlight.MakeHeaderYaml(data)
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
if ((ft == "unknown" || ft == "") && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
syndef, err := highlight.ParseDef(file, header)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
b.SyntaxDef = syndef
break
}
}
} else {
if syntaxFile != "" && !foundDef {
// we found a syntax file using a syntax header file
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
if f.Name() == syntaxFile {
data, err := f.Data()
@@ -776,7 +797,6 @@ func (b *Buffer) ClearCursors() {
b.UpdateCursors()
b.curCursor = 0
b.GetActiveCursor().ResetSelection()
log.Println("Cleared cursors:", len(b.cursors))
}
// MoveLinesUp moves the range of lines up one row
@@ -788,7 +808,7 @@ func (b *Buffer) MoveLinesUp(start int, end int) {
if end == len(b.lines) {
b.Insert(
Loc{
utf8.RuneCount(b.lines[end-1].data),
util.CharacterCount(b.lines[end-1].data),
end - 1,
},
"\n"+l,
@@ -922,7 +942,7 @@ func (b *Buffer) Retab() {
l = bytes.TrimLeft(l, " \t")
b.lines[i].data = append(ws, l...)
b.Modifications = append(b.Modifications, Loc{i, i})
b.MarkModified(i, i)
dirty = true
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,322 @@
package buffer
import (
"math/rand"
"strings"
"testing"
"github.com/stretchr/testify/assert"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/util"
)
type operation struct {
start Loc
end Loc
text []string
}
func init() {
ulua.L = lua.NewState()
config.InitGlobalSettings()
config.GlobalSettings["backup"] = false
config.GlobalSettings["fastdirty"] = true
}
func check(t *testing.T, before []string, operations []operation, after []string) {
assert := assert.New(t)
b := NewBufferFromString(strings.Join(before, "\n"), "", BTDefault)
assert.NotEqual("", b.GetName())
assert.Equal(false, b.ExternallyModified())
assert.Equal(false, b.Modified())
assert.Equal(1, b.NumCursors())
checkText := func(lines []string) {
assert.Equal([]byte(strings.Join(lines, "\n")), b.Bytes())
assert.Equal(len(lines), b.LinesNum())
for i, s := range lines {
assert.Equal(s, b.Line(i))
assert.Equal([]byte(s), b.LineBytes(i))
}
}
checkText(before)
var cursors []*Cursor
for _, op := range operations {
cursor := NewCursor(b, op.start)
cursor.SetSelectionStart(op.start)
cursor.SetSelectionEnd(op.end)
b.AddCursor(cursor)
cursors = append(cursors, cursor)
}
assert.Equal(1+len(operations), b.NumCursors())
for i, op := range operations {
cursor := cursors[i]
b.SetCurCursor(cursor.Num)
cursor.DeleteSelection()
b.Insert(cursor.Loc, strings.Join(op.text, "\n"))
}
checkText(after)
// must have exactly two events per operation (delete and insert)
for range operations {
b.UndoOneEvent()
b.UndoOneEvent()
}
checkText(before)
for i, op := range operations {
cursor := cursors[i]
if op.start == op.end {
assert.Equal(op.start, cursor.Loc)
} else {
assert.Equal(op.start, cursor.CurSelection[0])
assert.Equal(op.end, cursor.CurSelection[1])
}
}
for range operations {
b.RedoOneEvent()
b.RedoOneEvent()
}
checkText(after)
b.Close()
}
const maxLineLength = 200
var alphabet = []rune(" abcdeäم📚")
func randomString(length int) string {
runes := make([]rune, length)
for i := range runes {
runes[i] = alphabet[rand.Intn(len(alphabet))]
}
return string(runes)
}
func randomText(nLines int) string {
lines := make([]string, nLines)
for i := range lines {
lines[i] = randomString(rand.Intn(maxLineLength + 1))
}
return strings.Join(lines, "\n")
}
func benchCreateAndClose(testingB *testing.B, nLines int) {
rand.Seed(int64(nLines))
text := randomText(nLines)
testingB.ResetTimer()
for i := 0; i < testingB.N; i++ {
b := NewBufferFromString(text, "", BTDefault)
b.Close()
}
}
func benchRead(testingB *testing.B, nLines int) {
rand.Seed(int64(nLines))
b := NewBufferFromString(randomText(nLines), "", BTDefault)
testingB.ResetTimer()
for i := 0; i < testingB.N; i++ {
b.Bytes()
for j := 0; j < b.LinesNum(); j++ {
b.Line(j)
b.LineBytes(j)
}
}
testingB.StopTimer()
b.Close()
}
func benchEdit(testingB *testing.B, nLines, nCursors int) {
rand.Seed(int64(nLines + nCursors))
b := NewBufferFromString(randomText(nLines), "", BTDefault)
regionSize := nLines / nCursors
operations := make([]operation, nCursors)
for i := range operations {
startLine := (i * regionSize) + rand.Intn(regionSize-5)
startColumn := rand.Intn(util.CharacterCountInString(b.Line(startLine)) + 1)
endLine := startLine + 1 + rand.Intn(5)
endColumn := rand.Intn(util.CharacterCountInString(b.Line(endLine)) + 1)
operations[i] = operation{
start: Loc{startColumn, startLine},
end: Loc{endColumn, endLine},
text: []string{randomText(2 + rand.Intn(4))},
}
}
testingB.ResetTimer()
for i := 0; i < testingB.N; i++ {
b.SetCursors([]*Cursor{})
var cursors []*Cursor
for _, op := range operations {
cursor := NewCursor(b, op.start)
cursor.SetSelectionStart(op.start)
cursor.SetSelectionEnd(op.end)
b.AddCursor(cursor)
cursors = append(cursors, cursor)
}
for j, op := range operations {
cursor := cursors[j]
b.SetCurCursor(cursor.Num)
cursor.DeleteSelection()
b.Insert(cursor.Loc, op.text[0])
}
for b.UndoStack.Peek() != nil {
b.UndoOneEvent()
}
}
testingB.StopTimer()
b.Close()
}
func BenchmarkCreateAndClose10Lines(b *testing.B) {
benchCreateAndClose(b, 10)
}
func BenchmarkCreateAndClose100Lines(b *testing.B) {
benchCreateAndClose(b, 100)
}
func BenchmarkCreateAndClose1000Lines(b *testing.B) {
benchCreateAndClose(b, 1000)
}
func BenchmarkCreateAndClose10000Lines(b *testing.B) {
benchCreateAndClose(b, 10000)
}
func BenchmarkCreateAndClose100000Lines(b *testing.B) {
benchCreateAndClose(b, 100000)
}
func BenchmarkCreateAndClose1000000Lines(b *testing.B) {
benchCreateAndClose(b, 1000000)
}
func BenchmarkRead10Lines(b *testing.B) {
benchRead(b, 10)
}
func BenchmarkRead100Lines(b *testing.B) {
benchRead(b, 100)
}
func BenchmarkRead1000Lines(b *testing.B) {
benchRead(b, 1000)
}
func BenchmarkRead10000Lines(b *testing.B) {
benchRead(b, 10000)
}
func BenchmarkRead100000Lines(b *testing.B) {
benchRead(b, 100000)
}
func BenchmarkRead1000000Lines(b *testing.B) {
benchRead(b, 1000000)
}
func BenchmarkEdit10Lines1Cursor(b *testing.B) {
benchEdit(b, 10, 1)
}
func BenchmarkEdit100Lines1Cursor(b *testing.B) {
benchEdit(b, 100, 1)
}
func BenchmarkEdit100Lines10Cursors(b *testing.B) {
benchEdit(b, 100, 10)
}
func BenchmarkEdit1000Lines1Cursor(b *testing.B) {
benchEdit(b, 1000, 1)
}
func BenchmarkEdit1000Lines10Cursors(b *testing.B) {
benchEdit(b, 1000, 10)
}
func BenchmarkEdit1000Lines100Cursors(b *testing.B) {
benchEdit(b, 1000, 100)
}
func BenchmarkEdit10000Lines1Cursor(b *testing.B) {
benchEdit(b, 10000, 1)
}
func BenchmarkEdit10000Lines10Cursors(b *testing.B) {
benchEdit(b, 10000, 10)
}
func BenchmarkEdit10000Lines100Cursors(b *testing.B) {
benchEdit(b, 10000, 100)
}
func BenchmarkEdit10000Lines1000Cursors(b *testing.B) {
benchEdit(b, 10000, 1000)
}
func BenchmarkEdit100000Lines1Cursor(b *testing.B) {
benchEdit(b, 100000, 1)
}
func BenchmarkEdit100000Lines10Cursors(b *testing.B) {
benchEdit(b, 100000, 10)
}
func BenchmarkEdit100000Lines100Cursors(b *testing.B) {
benchEdit(b, 100000, 100)
}
func BenchmarkEdit100000Lines1000Cursors(b *testing.B) {
benchEdit(b, 100000, 1000)
}
func BenchmarkEdit1000000Lines1Cursor(b *testing.B) {
benchEdit(b, 1000000, 1)
}
func BenchmarkEdit1000000Lines10Cursors(b *testing.B) {
benchEdit(b, 1000000, 10)
}
func BenchmarkEdit1000000Lines100Cursors(b *testing.B) {
benchEdit(b, 1000000, 100)
}
func BenchmarkEdit1000000Lines1000Cursors(b *testing.B) {
benchEdit(b, 1000000, 1000)
}

View File

@@ -1,15 +1,13 @@
package buffer
import (
"unicode/utf8"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// InBounds returns whether the given location is a valid character position in the given buffer
func InBounds(pos Loc, buf *Buffer) bool {
if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > utf8.RuneCount(buf.LineBytes(pos.Y)) {
if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > util.CharacterCount(buf.LineBytes(pos.Y)) {
return false
}
@@ -76,8 +74,8 @@ func (c *Cursor) GetVisualX() int {
bytes := c.buf.LineBytes(c.Y)
tabsize := int(c.buf.Settings["tabsize"].(float64))
if c.X > utf8.RuneCount(bytes) {
c.X = utf8.RuneCount(bytes) - 1
if c.X > util.CharacterCount(bytes) {
c.X = util.CharacterCount(bytes) - 1
}
return util.StringWidth(bytes, c.X, tabsize)
@@ -102,16 +100,29 @@ func (c *Cursor) Start() {
func (c *Cursor) StartOfText() {
c.Start()
for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
break
}
c.Right()
}
}
// IsStartOfText returns whether the cursor is at the first
// non-whitespace rune of the line it is on
func (c *Cursor) IsStartOfText() bool {
x := 0
for util.IsWhitespace(c.RuneUnder(x)) {
if x == util.CharacterCount(c.buf.LineBytes(c.Y)) {
break
}
x++
}
return c.X == x
}
// End moves the cursor to the end of the line it is on
func (c *Cursor) End() {
c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
c.LastVisualX = c.GetVisualX()
}
@@ -229,8 +240,8 @@ func (c *Cursor) UpN(amount int) {
bytes := c.buf.LineBytes(proposedY)
c.X = c.GetCharPosInLine(bytes, c.LastVisualX)
if c.X > utf8.RuneCount(bytes) || (amount < 0 && proposedY == c.Y) {
c.X = utf8.RuneCount(bytes)
if c.X > util.CharacterCount(bytes) || (amount < 0 && proposedY == c.Y) {
c.X = util.CharacterCount(bytes)
}
c.Y = proposedY
@@ -272,7 +283,7 @@ func (c *Cursor) Right() {
if c.Loc == c.buf.End() {
return
}
if c.X < utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X < util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.X++
} else {
c.Down()
@@ -293,8 +304,8 @@ func (c *Cursor) Relocate() {
if c.X < 0 {
c.X = 0
} else if c.X > utf8.RuneCount(c.buf.LineBytes(c.Y)) {
c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
} else if c.X > util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
}
}
@@ -320,7 +331,7 @@ func (c *Cursor) SelectWord() {
c.SetSelectionStart(Loc{backward, c.Y})
c.OrigSelection[0] = c.CurSelection[0]
lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1
for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
forward++
}
@@ -352,7 +363,7 @@ func (c *Cursor) AddWordToSelection() {
if c.Loc.GreaterThan(c.OrigSelection[1]) {
forward := c.X
lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1
for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
forward++
}
@@ -379,7 +390,7 @@ func (c *Cursor) SelectTo(loc Loc) {
// WordRight moves the cursor one word to the right
func (c *Cursor) WordRight() {
for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.Right()
return
}
@@ -387,7 +398,7 @@ func (c *Cursor) WordRight() {
}
c.Right()
for util.IsWordChar(c.RuneUnder(c.X)) {
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
return
}
c.Right()
@@ -416,14 +427,14 @@ func (c *Cursor) WordLeft() {
// RuneUnder returns the rune under the given x position
func (c *Cursor) RuneUnder(x int) rune {
line := c.buf.LineBytes(c.Y)
if len(line) == 0 || x >= utf8.RuneCount(line) {
if len(line) == 0 || x >= util.CharacterCount(line) {
return '\n'
} else if x < 0 {
x = 0
}
i := 0
for len(line) > 0 {
r, size := utf8.DecodeRune(line)
r, _, size := util.DecodeCharacter(line)
line = line[size:]
if i == x {

View File

@@ -3,12 +3,12 @@ package buffer
import (
"bytes"
"time"
"unicode/utf8"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
luar "layeh.com/gopher-luar"
)
@@ -41,6 +41,73 @@ type Delta struct {
End Loc
}
// DoTextEvent runs a text event
func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
oldl := eh.buf.LinesNum()
if useUndo {
eh.Execute(t)
} else {
ExecuteTextEvent(t, eh.buf)
}
if len(t.Deltas) != 1 {
return
}
text := t.Deltas[0].Text
start := t.Deltas[0].Start
lastnl := -1
var endX int
var textX int
if t.EventType == TextEventInsert {
linecount := eh.buf.LinesNum() - oldl
textcount := util.CharacterCount(text)
lastnl = bytes.LastIndex(text, []byte{'\n'})
if lastnl >= 0 {
endX = util.CharacterCount(text[lastnl+1:])
textX = endX
} else {
endX = start.X + textcount
textX = textcount
}
t.Deltas[0].End = clamp(Loc{endX, start.Y + linecount}, eh.buf.LineArray)
}
end := t.Deltas[0].End
for _, c := range eh.cursors {
move := func(loc Loc) Loc {
if t.EventType == TextEventInsert {
if start.Y != loc.Y && loc.GreaterThan(start) {
loc.Y += end.Y - start.Y
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
loc.Y += end.Y - start.Y
if lastnl >= 0 {
loc.X += textX - start.X
} else {
loc.X += textX
}
}
return loc
} else {
if loc.Y != end.Y && loc.GreaterThan(end) {
loc.Y -= end.Y - start.Y
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
}
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.Relocate()
c.LastVisualX = c.GetVisualX()
}
}
// ExecuteTextEvent runs a text event
func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
if t.EventType == TextEventInsert {
@@ -56,7 +123,7 @@ func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
t.Deltas[i].Text = buf.remove(d.Start, d.End)
buf.insert(d.Start, d.Text)
t.Deltas[i].Start = d.Start
t.Deltas[i].End = Loc{d.Start.X + utf8.RuneCount(d.Text), d.Start.Y}
t.Deltas[i].End = Loc{d.Start.X + util.CharacterCount(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]
@@ -65,9 +132,9 @@ func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
}
// UndoTextEvent undoes a text event
func UndoTextEvent(t *TextEvent, buf *SharedBuffer) {
func (eh *EventHandler) UndoTextEvent(t *TextEvent) {
t.EventType = -t.EventType
ExecuteTextEvent(t, buf)
eh.DoTextEvent(t, false)
}
// EventHandler executes text manipulations and allows undoing and redoing
@@ -99,12 +166,12 @@ func (eh *EventHandler) ApplyDiff(new string) {
loc := eh.buf.Start()
for _, d := range diff {
if d.Type == dmp.DiffDelete {
eh.Remove(loc, loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray))
eh.Remove(loc, loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray))
} else {
if d.Type == dmp.DiffInsert {
eh.Insert(loc, d.Text)
}
loc = loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray)
loc = loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray)
}
}
}
@@ -117,6 +184,9 @@ func (eh *EventHandler) Insert(start Loc, textStr string) {
// InsertBytes creates an insert text event and executes it
func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
if len(text) == 0 {
return
}
start = clamp(start, eh.buf.LineArray)
e := &TextEvent{
C: *eh.cursors[eh.active],
@@ -124,50 +194,14 @@ func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
Deltas: []Delta{{text, start, Loc{0, 0}}},
Time: time.Now(),
}
oldl := eh.buf.LinesNum()
eh.Execute(e)
linecount := eh.buf.LinesNum() - oldl
textcount := utf8.RuneCount(text)
lastnl := bytes.LastIndex(text, []byte{'\n'})
var endX int
var textX int
if lastnl >= 0 {
endX = utf8.RuneCount(text[lastnl+1:])
textX = endX
} else {
endX = start.X + textcount
textX = textcount
}
e.Deltas[0].End = clamp(Loc{endX, start.Y + linecount}, eh.buf.LineArray)
end := e.Deltas[0].End
for _, c := range eh.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.Y += end.Y - start.Y
if lastnl >= 0 {
loc.X = textX
} else {
loc.X += textX
}
}
return loc
}
c.Loc = move(c.Loc)
c.Relocate()
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()
}
eh.DoTextEvent(e, true)
}
// Remove creates a remove text event and executes it
func (eh *EventHandler) Remove(start, end Loc) {
if start == end {
return
}
start = clamp(start, eh.buf.LineArray)
end = clamp(end, eh.buf.LineArray)
e := &TextEvent{
@@ -176,24 +210,7 @@ func (eh *EventHandler) Remove(start, end Loc) {
Deltas: []Delta{{[]byte{}, start, end}},
Time: time.Now(),
}
eh.Execute(e)
for _, c := range eh.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.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
}
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()
}
eh.DoTextEvent(e, true)
}
// MultipleReplace creates an multiple insertions executes them
@@ -264,10 +281,9 @@ func (eh *EventHandler) UndoOneEvent() {
if t == nil {
return
}
// Undo it
// Modifies the text event
UndoTextEvent(t, eh.buf)
eh.UndoTextEvent(t)
// Set the cursor in the right place
teCursor := t.C
@@ -313,9 +329,6 @@ func (eh *EventHandler) RedoOneEvent() {
return
}
// Modifies the text event
UndoTextEvent(t, eh.buf)
teCursor := t.C
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
t.C = *eh.cursors[teCursor.Num]
@@ -324,5 +337,8 @@ func (eh *EventHandler) RedoOneEvent() {
teCursor.Num = -1
}
// Modifies the text event
eh.UndoTextEvent(t)
eh.UndoStack.Push(t)
}

View File

@@ -2,11 +2,12 @@ package buffer
import (
"bufio"
"bytes"
"io"
"sync"
"unicode/utf8"
"github.com/zyedidia/micro/pkg/highlight"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/pkg/highlight"
)
// Finds the byte index of the nth rune in a byte slice
@@ -18,7 +19,7 @@ func runeToByteIndex(n int, txt []byte) int {
count := 0
i := 0
for len(txt) > 0 {
_, size := utf8.DecodeRune(txt)
_, _, size := util.DecodeCharacter(txt)
txt = txt[size:]
count += size
@@ -152,17 +153,19 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
// Bytes returns the string that should be written to disk when
// the line array is saved
func (la *LineArray) Bytes() []byte {
str := make([]byte, 0, la.initsize+1000) // initsize should provide a good estimate
b := new(bytes.Buffer)
// initsize should provide a good estimate
b.Grow(int(la.initsize + 4096))
for i, l := range la.lines {
str = append(str, l.data...)
b.Write(l.data)
if i != len(la.lines)-1 {
if la.Endings == FFDos {
str = append(str, '\r')
b.WriteByte('\r')
}
str = append(str, '\n')
b.WriteByte('\n')
}
}
return str
return b.Bytes()
}
// newlineBelow adds a newline below the given line number
@@ -296,7 +299,7 @@ func (la *LineArray) Start() Loc {
// End returns the location of the last character in the buffer
func (la *LineArray) End() Loc {
numlines := len(la.lines)
return Loc{utf8.RuneCount(la.lines[numlines-1].data), numlines - 1}
return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
}
// LineBytes returns line n as an array of bytes

View File

@@ -1,9 +1,7 @@
package buffer
import (
"unicode/utf8"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// Loc stores a location
@@ -68,9 +66,9 @@ func DiffLA(a, b Loc, buf *LineArray) int {
loc := 0
for i := a.Y + 1; i < b.Y; i++ {
// + 1 for the newline
loc += utf8.RuneCount(buf.LineBytes(i)) + 1
loc += util.CharacterCount(buf.LineBytes(i)) + 1
}
loc += utf8.RuneCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
loc += util.CharacterCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
return loc
}
@@ -80,7 +78,7 @@ func (l Loc) right(buf *LineArray) Loc {
return Loc{l.X + 1, l.Y}
}
var res Loc
if l.X < utf8.RuneCount(buf.LineBytes(l.Y)) {
if l.X < util.CharacterCount(buf.LineBytes(l.Y)) {
res = Loc{l.X + 1, l.Y}
} else {
res = Loc{0, l.Y + 1}
@@ -97,12 +95,12 @@ func (l Loc) left(buf *LineArray) Loc {
if l.X > 0 {
res = Loc{l.X - 1, l.Y}
} else {
res = Loc{utf8.RuneCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
res = Loc{util.CharacterCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
}
return res
}
// Move moves the cursor n characters to the left or right
// MoveLA moves the cursor n characters to the left or right
// It moves the cursor left if n is negative
func (l Loc) MoveLA(n int, buf *LineArray) Loc {
if n > 0 {
@@ -117,9 +115,12 @@ func (l Loc) MoveLA(n int, buf *LineArray) Loc {
return l
}
func (l Loc) Diff(a, b Loc, buf *Buffer) int {
return DiffLA(a, b, buf.LineArray)
// Diff returns the difference between two locs
func (l Loc) Diff(b Loc, buf *Buffer) int {
return DiffLA(l, b, buf.LineArray)
}
// Move moves a loc n characters
func (l Loc) Move(n int, buf *Buffer) Loc {
return l.MoveLA(n, buf.LineArray)
}

View File

@@ -1,7 +1,7 @@
package buffer
import (
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/tcell"
)

View File

@@ -11,11 +11,10 @@ import (
"path/filepath"
"runtime"
"unicode"
"unicode/utf8"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/transform"
@@ -100,9 +99,9 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
b.UpdateRules()
if b.Settings["rmtrailingws"].(bool) {
for i, l := range b.lines {
leftover := utf8.RuneCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
linelen := utf8.RuneCount(l.data)
linelen := util.CharacterCount(l.data)
b.Remove(Loc{leftover, i}, Loc{linelen, i})
}
@@ -111,8 +110,8 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
if b.Settings["eofnewline"].(bool) {
end := b.End()
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
b.Insert(end, "\n")
if b.RuneAt(Loc{end.X, end.Y}) != '\n' {
b.insert(end, []byte{'\n'})
}
}

View File

@@ -2,9 +2,8 @@ package buffer
import (
"regexp"
"unicode/utf8"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
@@ -20,19 +19,19 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
charpos := 0
if i == start.Y && start.Y == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
}
@@ -61,19 +60,19 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
charpos := 0
if i == start.Y && start.Y == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
}
@@ -120,24 +119,27 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
if down {
l, found = b.findDown(r, from, end)
if !found {
l, found = b.findDown(r, start, from)
l, found = b.findDown(r, start, end)
}
} else {
l, found = b.findUp(r, from, start)
if !found {
l, found = b.findUp(r, end, from)
l, found = b.findUp(r, end, start)
}
}
return l, found, nil
}
// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
// and returns the number of replacements made
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) int {
// and returns the number of replacements made and the number of runes
// added or removed
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) {
if start.GreaterThan(end) {
start, end = end, start
}
netrunes := 0
found := 0
var deltas []Delta
for i := start.Y; i <= end.Y; i++ {
@@ -155,16 +157,21 @@ func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []b
l = util.SliceStart(l, end.X)
}
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
result := []byte{}
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
result = search.Expand(result, replace, in, submatches)
}
found++
return replace
netrunes += util.CharacterCount(in) - util.CharacterCount(result)
return result
})
from := Loc{charpos, i}
to := Loc{charpos + utf8.RuneCount(l), i}
to := Loc{charpos + util.CharacterCount(l), i}
deltas = append(deltas, Delta{newText, from, to})
}
b.MultipleReplace(deltas)
return found
return found, netrunes
}

View File

@@ -10,8 +10,8 @@ import (
"golang.org/x/text/encoding"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
)
// The SerializedBuffer holds the types that get serialized when a buffer is saved

View File

@@ -1,8 +1,8 @@
package buffer
import (
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
)
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {

View File

@@ -151,6 +151,9 @@ func StringToStyle(str string) tcell.Style {
if strings.Contains(str, "bold") {
style = style.Bold(true)
}
if strings.Contains(str, "italic") {
style = style.Italic(true)
}
if strings.Contains(str, "reverse") {
style = style.Reverse(true)
}

View File

@@ -5,7 +5,7 @@ import (
"log"
lua "github.com/yuin/gopher-lua"
ulua "github.com/zyedidia/micro/internal/lua"
ulua "github.com/zyedidia/micro/v2/internal/lua"
)
// ErrNoSuchFunction is returned when Call is executed on a function that does not exist

View File

@@ -16,8 +16,8 @@ import (
"github.com/blang/semver"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/json5"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/util"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/util"
)
var (

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,7 @@ import (
"github.com/zyedidia/glob"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding/htmlindex"
)
@@ -184,15 +184,15 @@ func GetGlobalOption(name string) interface{} {
var defaultCommonSettings = map[string]interface{}{
"autoindent": true,
"autosu": true,
"autosu": false,
"backup": true,
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"diffgutter": false,
"encoding": "utf-8",
"eofnewline": false,
"fastdirty": true,
"eofnewline": true,
"fastdirty": false,
"fileformat": "unix",
"filetype": "unknown",
"ignorecase": false,
@@ -256,6 +256,7 @@ var DefaultGlobalOnlySettings = map[string]interface{}{
"sucmd": "sudo",
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
"pluginrepos": []string{},
"xterm": false,
}
// a list of settings that should never be globally modified

View File

@@ -2,13 +2,12 @@ package display
import (
"strconv"
"unicode/utf8"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -73,9 +72,9 @@ func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style)
curStyle := config.DefStyle
var s *tcell.Style
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := util.DecodeCharacter(b)
curStyle, found := w.getStyle(curStyle, bloc, r)
curStyle, found := w.getStyle(curStyle, bloc)
if found {
s = &curStyle
}
@@ -136,9 +135,6 @@ func (w *BufWindow) Relocate() bool {
if w.drawStatus {
h--
}
if b.LinesNum() <= h {
height = w.Height
}
ret := false
activeC := w.Buf.GetActiveCursor()
cy := activeC.Y
@@ -240,7 +236,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
return bloc
}
r, size := utf8.DecodeRune(line)
r, _, size := util.DecodeCharacter(line)
draw()
width := 0
@@ -275,14 +271,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
if vloc.Y >= bufHeight {
break
}
vloc.X = 0
if b.Settings["diffgutter"].(bool) {
vloc.X++
}
// This will draw an empty line number because the current line is wrapped
if b.Settings["ruler"].(bool) {
vloc.X += maxLineNumLength + 1
}
vloc.X = w.gutterOffset
}
}
}
@@ -370,7 +359,7 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxL
// getStyle returns the highlight style for the given character position
// If there is no change to the current highlight style it just returns that
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) {
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc) (tcell.Style, bool) {
if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
s := config.GetColor(group.String())
return s, true
@@ -407,21 +396,7 @@ func (w *BufWindow) displayBuffer() {
bufWidth--
}
if len(b.Modifications) > 0 {
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
for _, r := range b.Modifications {
rx := util.Clamp(r.X, 0, b.LinesNum())
ry := util.Clamp(r.Y, 0, b.LinesNum())
final := -1
for i := rx; i <= ry; i++ {
final = util.Max(b.Highlighter.ReHighlightStates(b, i), final)
}
b.Highlighter.HighlightMatches(b, rx, final+1)
}
}
b.ClearModifications()
if b.ModifiedThisFrame {
if b.Settings["diffgutter"].(bool) {
b.UpdateDiff(func(synchronous bool) {
// If the diff was updated asynchronously, the outer call to
@@ -436,6 +411,7 @@ func (w *BufWindow) displayBuffer() {
}
})
}
b.ModifiedThisFrame = false
}
var matchingBraces []buffer.Loc
@@ -533,7 +509,7 @@ func (w *BufWindow) displayBuffer() {
}
bloc.X = bslice
draw := func(r rune, style tcell.Style, showcursor bool) {
draw := func(r rune, combc []rune, style tcell.Style, showcursor bool) {
if nColsBeforeStart <= 0 {
for _, c := range cursors {
if c.HasSelection() &&
@@ -579,7 +555,7 @@ func (w *BufWindow) displayBuffer() {
}
if s, ok := config.Colorscheme["color-column"]; ok {
if colorcolumn != 0 && vloc.X-w.gutterOffset == colorcolumn {
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn {
fg, _, _ := s.Decompose()
style = style.Background(fg)
}
@@ -591,7 +567,7 @@ func (w *BufWindow) displayBuffer() {
}
}
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
if showcursor {
for _, c := range cursors {
@@ -607,10 +583,11 @@ func (w *BufWindow) displayBuffer() {
totalwidth := w.StartCol - nColsBeforeStart
for len(line) > 0 {
r, size := utf8.DecodeRune(line)
curStyle, _ = w.getStyle(curStyle, bloc, r)
r, combc, size := util.DecodeCharacter(line)
draw(r, curStyle, true)
curStyle, _ = w.getStyle(curStyle, bloc)
draw(r, combc, curStyle, true)
width := 0
@@ -627,7 +604,7 @@ func (w *BufWindow) displayBuffer() {
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for i := 1; i < width; i++ {
draw(char, curStyle, false)
draw(char, nil, curStyle, false)
}
}
bloc.X++
@@ -645,9 +622,13 @@ func (w *BufWindow) displayBuffer() {
break
}
vloc.X = 0
if hasMessage {
w.drawGutter(&vloc, &bloc)
}
if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
}
// This will draw an empty line number because the current line is wrapped
if b.Settings["ruler"].(bool) {
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
@@ -669,7 +650,7 @@ func (w *BufWindow) displayBuffer() {
for i := vloc.X; i < bufWidth; i++ {
curStyle := style
if s, ok := config.Colorscheme["color-column"]; ok {
if colorcolumn != 0 && i-w.gutterOffset == colorcolumn {
if colorcolumn != 0 && i-w.gutterOffset+w.StartCol == colorcolumn {
fg, _, _ := s.Decompose()
curStyle = style.Background(fg)
}
@@ -678,7 +659,7 @@ func (w *BufWindow) displayBuffer() {
}
if vloc.X != bufWidth {
draw(' ', curStyle, true)
draw(' ', nil, curStyle, true)
}
bloc.X = w.StartCol

View File

@@ -1,14 +1,12 @@
package display
import (
"unicode/utf8"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/info"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/info"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -70,7 +68,7 @@ func (i *InfoWindow) IsActive() bool { return true }
func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
c := i.Buffer.GetActiveCursor()
l := i.Buffer.LineBytes(0)
n := utf8.RuneCountInString(i.Msg)
n := util.CharacterCountInString(i.Msg)
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
}
@@ -86,13 +84,13 @@ func (i *InfoWindow) displayBuffer() {
activeC := b.GetActiveCursor()
blocX := 0
vlocX := utf8.RuneCountInString(i.Msg)
vlocX := util.CharacterCountInString(i.Msg)
tabsize := 4
line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize)
blocX = bslice
draw := func(r rune, style tcell.Style) {
draw := func(r rune, combc []rune, style tcell.Style) {
if nColsBeforeStart <= 0 {
bloc := buffer.Loc{X: blocX, Y: 0}
if activeC.HasSelection() &&
@@ -112,8 +110,9 @@ func (i *InfoWindow) displayBuffer() {
c := r
if j > 0 {
c = ' '
combc = nil
}
screen.SetContent(vlocX, i.Y, c, nil, style)
screen.SetContent(vlocX, i.Y, c, combc, style)
}
vlocX++
}
@@ -124,9 +123,9 @@ func (i *InfoWindow) displayBuffer() {
for len(line) > 0 {
curVX := vlocX
curBX := blocX
r, size := utf8.DecodeRune(line)
r, combc, size := util.DecodeCharacter(line)
draw(r, i.defStyle())
draw(r, combc, i.defStyle())
width := 0
@@ -146,7 +145,7 @@ func (i *InfoWindow) displayBuffer() {
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for j := 1; j < width; j++ {
draw(char, i.defStyle())
draw(char, nil, i.defStyle())
}
}
if activeC.X == curBX {
@@ -191,7 +190,7 @@ func (i *InfoWindow) scrollToSuggestion() {
s := i.totalSize()
for j, n := range i.Suggestions {
c := utf8.RuneCountInString(n)
c := util.CharacterCountInString(n)
if j == i.CurSuggestion {
if x+c >= i.hscroll+i.Width {
i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)
@@ -209,13 +208,13 @@ func (i *InfoWindow) scrollToSuggestion() {
}
func (i *InfoWindow) Display() {
i.Clear()
x := 0
if config.GetGlobalOption("keymenu").(bool) {
i.displayKeyMenu()
}
if i.HasPrompt || config.GlobalSettings["infobar"].(bool) {
i.Clear()
x := 0
if config.GetGlobalOption("keymenu").(bool) {
i.displayKeyMenu()
}
if !i.HasPrompt && !i.HasMessage && !i.HasError {
return
}

View File

@@ -3,21 +3,19 @@ package display
import (
"bytes"
"fmt"
"path"
"regexp"
"strconv"
"strings"
"unicode/utf8"
luar "layeh.com/gopher-luar"
runewidth "github.com/mattn/go-runewidth"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
)
// StatusLine represents the information line at the bottom
@@ -32,9 +30,6 @@ type StatusLine struct {
var statusInfo = map[string]func(*buffer.Buffer) string{
"filename": func(b *buffer.Buffer) string {
if b.Settings["basename"].(bool) {
return path.Base(b.GetName())
}
return b.GetName()
},
"line": func(b *buffer.Buffer) string {
@@ -172,34 +167,36 @@ func (s *StatusLine) Display() {
statusLineStyle = style
}
leftLen := util.StringWidth(leftText, utf8.RuneCount(leftText), 1)
rightLen := util.StringWidth(rightText, utf8.RuneCount(rightText), 1)
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)
rightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)
winX := s.win.X
for x := 0; x < s.win.Width; x++ {
if x < leftLen {
r, size := utf8.DecodeRune(leftText)
r, combc, size := util.DecodeCharacter(leftText)
leftText = leftText[size:]
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
c := r
if j > 0 {
c = ' '
combc = nil
x++
}
screen.SetContent(winX+x, y, c, nil, statusLineStyle)
screen.SetContent(winX+x, y, c, combc, statusLineStyle)
}
} else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
r, size := utf8.DecodeRune(rightText)
r, combc, size := util.DecodeCharacter(rightText)
rightText = rightText[size:]
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
c := r
if j > 0 {
c = ' '
combc = nil
x++
}
screen.SetContent(winX+x, y, c, nil, statusLineStyle)
screen.SetContent(winX+x, y, c, combc, statusLineStyle)
}
} else {
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)

View File

@@ -1,13 +1,11 @@
package display
import (
"unicode/utf8"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
)
type TabWindow struct {
@@ -34,7 +32,7 @@ func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
for i, n := range w.Names {
x++
s := utf8.RuneCountInString(n)
s := util.CharacterCountInString(n)
if vloc.Y == w.Y && vloc.X < x+s {
return i
}
@@ -75,7 +73,7 @@ func (w *TabWindow) SetActive(a int) {
s := w.TotalSize()
for i, n := range w.Names {
c := utf8.RuneCountInString(n)
c := util.CharacterCountInString(n)
if i == a {
if x+c >= w.hscroll+w.Width {
w.hscroll = util.Clamp(x+c+1-w.Width, 0, s-w.Width)
@@ -96,6 +94,11 @@ func (w *TabWindow) Display() {
x := -w.hscroll
done := false
tabBarStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["tabbar"]; ok {
tabBarStyle = style
}
draw := func(r rune, n int) {
for i := 0; i < n; i++ {
rw := runewidth.RuneWidth(r)
@@ -105,13 +108,13 @@ func (w *TabWindow) Display() {
c = ' '
}
if x == w.Width-1 && !done {
screen.SetContent(w.Width-1, w.Y, '>', nil, config.DefStyle.Reverse(true))
screen.SetContent(w.Width-1, w.Y, '>', nil, tabBarStyle)
x++
break
} else if x == 0 && w.hscroll > 0 {
screen.SetContent(0, w.Y, '<', nil, config.DefStyle.Reverse(true))
screen.SetContent(0, w.Y, '<', nil, tabBarStyle)
} else if x >= 0 && x < w.Width {
screen.SetContent(x, w.Y, c, nil, config.DefStyle.Reverse(true))
screen.SetContent(x, w.Y, c, nil, tabBarStyle)
}
x++
}

View File

@@ -1,12 +1,11 @@
package display
import (
"unicode/utf8"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
"github.com/zyedidia/terminal"
)
@@ -98,12 +97,12 @@ func (w *TermWindow) Display() {
}
text := []byte(w.Name())
textLen := utf8.RuneCount(text)
textLen := util.CharacterCount(text)
for x := 0; x < w.Width; x++ {
if x < textLen {
r, size := utf8.DecodeRune(text)
r, combc, size := util.DecodeCharacter(text)
text = text[size:]
screen.SetContent(w.X+x, w.Y+w.Height, r, nil, statusLineStyle)
screen.SetContent(w.X+x, w.Y+w.Height, r, combc, statusLineStyle)
} else {
screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
}

View File

@@ -1,10 +1,10 @@
package display
import (
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/views"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/views"
)
type UIWindow struct {

View File

@@ -1,7 +1,7 @@
package display
import (
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/v2/internal/buffer"
)
type View struct {

View File

@@ -5,7 +5,7 @@ import (
"os"
"path/filepath"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/v2/internal/config"
)
// LoadHistory attempts to load user history from configDir/buffers/history

View File

@@ -3,7 +3,7 @@ package info
import (
"fmt"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/v2/internal/buffer"
)
// The InfoBuf displays messages and other info at the bottom of the screen.

View File

@@ -5,8 +5,8 @@ import (
"os"
"sync"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -136,6 +136,12 @@ func Init() {
os.Setenv("TCELL_TRUECOLOR", "disable")
}
var oldTerm string
if config.GetGlobalOption("xterm").(bool) {
oldTerm = os.Getenv("TERM")
os.Setenv("TERM", "xterm-256color")
}
// Initilize tcell
var err error
Screen, err = tcell.NewScreen()
@@ -149,6 +155,11 @@ func Init() {
os.Exit(1)
}
// restore TERM
if config.GetGlobalOption("xterm").(bool) {
os.Setenv("TERM", oldTerm)
}
if config.GetGlobalOption("mouse").(bool) {
Screen.EnableMouse()
}

View File

@@ -11,7 +11,7 @@ import (
"strings"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/screen"
)
// ExecCommand executes a command using exec

View File

@@ -5,8 +5,8 @@ import (
"os/exec"
"strconv"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/terminal"
)

View File

@@ -1,15 +1,11 @@
package util
import (
"unicode/utf8"
)
// LuaRuneAt is a helper function for lua plugins to return the rune
// at an index within a string
func LuaRuneAt(str string, runeidx int) string {
i := 0
for len(str) > 0 {
r, size := utf8.DecodeRuneInString(str)
r, _, size := DecodeCharacterInString(str)
str = str[size:]
@@ -26,7 +22,7 @@ func LuaRuneAt(str string, runeidx int) string {
func LuaGetLeadingWhitespace(s string) string {
ws := []byte{}
for len(s) > 0 {
r, size := utf8.DecodeRuneInString(s)
r, _, size := DecodeCharacterInString(s)
if r == ' ' || r == '\t' {
ws = append(ws, byte(r))
} else {
@@ -40,6 +36,6 @@ func LuaGetLeadingWhitespace(s string) string {
// LuaIsWordChar returns true if the first rune in a string is a word character
func LuaIsWordChar(s string) bool {
r, _ := utf8.DecodeRuneInString(s)
r, _, _ := DecodeCharacterInString(s)
return IsWordChar(r)
}

86
internal/util/unicode.go Normal file
View File

@@ -0,0 +1,86 @@
package util
import (
"unicode"
"unicode/utf8"
)
// Unicode is annoying. A "code point" (rune in Go-speak) may need up to
// 4 bytes to represent it. In general, a code point will represent a
// complete character, but this is not always the case. A character with
// accents may be made up of multiple code points (the code point for the
// original character, and additional code points for each accent/marking).
// The functions below are meant to help deal with these additional "combining"
// code points. In underlying operations (search, replace, etc...), micro will
// treat a character with combining code points as just the original code point.
// For rendering, micro will display the combining characters. It's not perfect
// but it's pretty good.
// DecodeCharacter returns the next character from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCharacter(b []byte) (rune, []rune, int) {
r, size := utf8.DecodeRune(b)
b = b[size:]
c, s := utf8.DecodeRune(b)
var combc []rune
for unicode.In(c, unicode.Mark) {
combc = append(combc, c)
size += s
b = b[s:]
c, s = utf8.DecodeRune(b)
}
return r, combc, size
}
// DecodeCharacterInString returns the next character from a string
// A character is a rune along with any accompanying combining runes
func DecodeCharacterInString(str string) (rune, []rune, int) {
r, size := utf8.DecodeRuneInString(str)
str = str[size:]
c, s := utf8.DecodeRuneInString(str)
var combc []rune
for unicode.In(c, unicode.Mark) {
combc = append(combc, c)
size += s
str = str[s:]
c, s = utf8.DecodeRuneInString(str)
}
return r, combc, size
}
// CharacterCount returns the number of characters in a byte array
// Similar to utf8.RuneCount but for unicode characters
func CharacterCount(b []byte) int {
s := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
if !unicode.In(r, unicode.Mark) {
s++
}
b = b[size:]
}
return s
}
// CharacterCount returns the number of characters in a string
// Similar to utf8.RuneCountInString but for unicode characters
func CharacterCountInString(str string) int {
s := 0
for _, r := range str {
if !unicode.In(r, unicode.Mark) {
s++
}
}
return s
}

View File

@@ -1,6 +1,7 @@
package util
import (
"bytes"
"errors"
"fmt"
"os"
@@ -12,7 +13,6 @@ import (
"strings"
"time"
"unicode"
"unicode/utf8"
"github.com/blang/semver"
runewidth "github.com/mattn/go-runewidth"
@@ -30,10 +30,13 @@ var (
// CompileDate is the date this binary was compiled on
CompileDate = "Unknown"
// Debug logging
Debug = "ON"
Debug = "OFF"
// FakeCursor is used to disable the terminal cursor and have micro
// draw its own (enabled for windows consoles where the cursor is slow)
FakeCursor = false
// Stdout is a buffer that is written to stdout when micro closes
Stdout *bytes.Buffer
)
func init() {
@@ -46,6 +49,7 @@ func init() {
if runtime.GOOS == "windows" {
FakeCursor = true
}
Stdout = new(bytes.Buffer)
}
// SliceEnd returns a byte slice where the index is a rune index
@@ -59,7 +63,7 @@ func SliceEnd(slc []byte, index int) []byte {
return slc[totalSize:]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -77,7 +81,7 @@ func SliceEndStr(str string, index int) string {
return str[totalSize:]
}
_, size := utf8.DecodeRuneInString(str[totalSize:])
_, _, size := DecodeCharacterInString(str[totalSize:])
totalSize += size
i++
}
@@ -96,7 +100,7 @@ func SliceStart(slc []byte, index int) []byte {
return slc[:totalSize]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -114,7 +118,7 @@ func SliceStartStr(str string, index int) string {
return str[:totalSize]
}
_, size := utf8.DecodeRuneInString(str[totalSize:])
_, _, size := DecodeCharacterInString(str[totalSize:])
totalSize += size
i++
}
@@ -130,7 +134,7 @@ func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {
width := 0
i := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
w := 0
switch r {
@@ -167,7 +171,7 @@ func StringWidth(b []byte, n, tabsize int) int {
i := 0
width := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
b = b[size:]
switch r {
@@ -260,7 +264,7 @@ func IsBytesWhitespace(b []byte) bool {
// RunePos returns the rune index of a given byte index
// Make sure the byte index is not between code points
func RunePos(b []byte, i int) int {
return utf8.RuneCount(b[:i])
return CharacterCount(b[:i])
}
// MakeRelative will attempt to make a relative path between path and base
@@ -339,7 +343,7 @@ func EscapePath(path string) string {
func GetLeadingWhitespace(b []byte) []byte {
ws := []byte{}
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
if r == ' ' || r == '\t' {
ws = append(ws, byte(r))
} else {
@@ -365,7 +369,7 @@ func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
i := 0 // char pos
width := 0 // string visual width
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
b = b[size:]
switch r {
@@ -411,7 +415,7 @@ func Clamp(val, min, max int) int {
}
func IsNonAlphaNumeric(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
}
func ParseSpecial(s string) string {

View File

@@ -3,7 +3,6 @@ package highlight
import (
"regexp"
"strings"
"unicode/utf8"
)
func sliceStart(slc []byte, index int) []byte {
@@ -15,7 +14,7 @@ func sliceStart(slc []byte, index int) []byte {
return slc[totalSize:]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -32,7 +31,7 @@ func sliceEnd(slc []byte, index int) []byte {
return slc[:totalSize]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -47,9 +46,9 @@ func runePos(p int, str []byte) int {
return 0
}
if p >= len(str) {
return utf8.RuneCount(str)
return CharacterCount(str)
}
return utf8.RuneCount(str[:p])
return CharacterCount(str[:p])
}
func combineLineMatch(src, dst LineMatch) LineMatch {
@@ -112,7 +111,7 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchSt
var strbytes []byte
if skip != nil {
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
res := make([]byte, utf8.RuneCount(match))
res := make([]byte, CharacterCount(match))
return res
})
} else {
@@ -148,7 +147,7 @@ func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd b
}
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {
lineLen := utf8.RuneCount(line)
lineLen := CharacterCount(line)
if start == 0 {
if !statesOnly {
if _, ok := highlights[0]; !ok {
@@ -236,7 +235,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
}
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
lineLen := utf8.RuneCount(line)
lineLen := CharacterCount(line)
if lineLen == 0 {
if canMatchEnd {
h.lastRegion = nil
@@ -336,11 +335,11 @@ func (h *Highlighter) HighlightStates(input LineStates) {
}
}
// HighlightMatches sets the matches for each line in between startline and endline
// HighlightMatches sets the matches for each line from startline to endline
// It sets all other matches in the buffer to nil to conserve memory
// This assumes that all the states are set correctly
func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
for i := startline; i < endline; i++ {
for i := startline; i <= endline; i++ {
if i >= input.LinesNum() {
break
}

75
pkg/highlight/unicode.go Normal file
View File

@@ -0,0 +1,75 @@
package highlight
import (
"unicode"
"unicode/utf8"
)
// DecodeCharacter returns the next character from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCharacter(b []byte) (rune, []rune, int) {
r, size := utf8.DecodeRune(b)
b = b[size:]
c, s := utf8.DecodeRune(b)
var combc []rune
for unicode.In(c, unicode.Mark) {
combc = append(combc, c)
size += s
b = b[s:]
c, s = utf8.DecodeRune(b)
}
return r, combc, size
}
// DecodeCharacterInString returns the next character from a string
// A character is a rune along with any accompanying combining runes
func DecodeCharacterInString(str string) (rune, []rune, int) {
r, size := utf8.DecodeRuneInString(str)
str = str[size:]
c, s := utf8.DecodeRuneInString(str)
var combc []rune
for unicode.In(c, unicode.Mark) {
combc = append(combc, c)
size += s
str = str[s:]
c, s = utf8.DecodeRuneInString(str)
}
return r, combc, size
}
// CharacterCount returns the number of characters in a byte array
// Similar to utf8.RuneCount but for unicode characters
func CharacterCount(b []byte) int {
s := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
if !unicode.In(r, unicode.Mark) {
s++
}
b = b[size:]
}
return s
}
// CharacterCount returns the number of characters in a string
// Similar to utf8.RuneCountInString but for unicode characters
func CharacterCountInString(str string) int {
s := 0
for _, r := range str {
if !unicode.In(r, unicode.Mark) {
s++
}
}
return s
}

View File

@@ -0,0 +1,34 @@
color-link color-column "#001e28"
color-link comment "#608b4e,#001e28"
color-link constant.bool "#fd971f,#001e28"
color-link constant "#fd971f,#001e28"
color-link constant.string "#a0f000,#001e28"
color-link constant.string.char "#a0f000,#001e28"
color-link constant.string.url "#a0f000,#001e28"
color-link current-line-number "bold #fd971f,#001e28"
color-link cursor-line "#001923"
color-link default "#ffffff,#001e28"
color-link diff-added "#00c8a0,#001e28"
color-link diff-modified "#fd971f,#001e28"
color-link diff-deleted "#cb4b16,#001e28"
color-link divider "#001e28,#d0d0d0"
color-link error "#cb4b16,#001e28"
color-link gutter-error "#cb4b16,#001e28"
color-link gutter-warning "#fce94f,#001e28"
color-link identifier "#00c8a0,#001e28"
color-link identifier.class "#00c8a0,#001e28"
color-link indent-char "#a0a0a0,#001e28"
color-link line-number "#a0a0a0,#001923"
color-link preproc "bold #5aaae6,#001e28"
color-link special "#a6e22e,#001e28"
color-link statement "bold #5aaae6,#001e28"
color-link statusline "#ffffff,#0078c8"
color-link symbol "#00c8a0,#001e28"
color-link symbol.brackets "#ffffff,#001e28"
color-link symbol.tag "bold #5aaae6,#001e28"
color-link tabbar "#001e28,#ffffff"
color-link todo "#fce94f,#001e28"
color-link type "bold #3cc83c,#001e28"
color-link type.keyword "bold #5aaae6,#001e28"
color-link type.extended "#ffffff,#001e28"
color-link underlined "#608b4e,#001e28"

View File

@@ -0,0 +1,34 @@
color-link color-column "#f0f0f0"
color-link comment "#3f7f5f,#f0f0f0"
color-link constant.bool "#641e00,#f0f0f0"
color-link constant "#641e00,#f0f0f0"
color-link constant.string "#0000ff,#f0f0f0"
color-link constant.string.char "#0000ff,#f0f0f0"
color-link constant.string.url "#0000ff,#f0f0f0"
color-link current-line-number "bold #004080,#f0f0f0"
color-link cursor-line "#e6e6e6"
color-link default "#000000,#f0f0f0"
color-link diff-added "#008040,#f0f0f0"
color-link diff-modified "#641e00,#f0f0f0"
color-link diff-deleted "#500000,#f0f0f0"
color-link divider "#f0f0f0,#004080"
color-link error "#500000,#f0f0f0"
color-link gutter-error "#500000,#f0f0f0"
color-link gutter-warning "#dcc800,#f0f0f0"
color-link identifier "bold #0078a0,#f0f0f0"
color-link identifier.class "bold #0078a0,#f0f0f0"
color-link indent-char "#404040,#f0f0f0"
color-link line-number "#404040,#e6e6e6"
color-link preproc "bold #780050,#f0f0f0"
color-link special "bold #0078a0,#f0f0f0"
color-link statement "bold #780050,#f0f0f0"
color-link statusline "#ffffff,#0078c8"
color-link symbol "bold #0078a0,#f0f0f0"
color-link symbol.brackets "#000000,#f0f0f0"
color-link symbol.tag "bold #780050,#f0f0f0"
color-link tabbar "#f0f0f0,#004080"
color-link todo "#dcc800,#f0f0f0"
color-link type "bold #004080,#f0f0f0"
color-link type.keyword "bold #780050,#f0f0f0"
color-link type.extended "#000000,#f0f0f0"
color-link underlined "#3f7f5f,#f0f0f0"

View File

@@ -0,0 +1,34 @@
color-link color-column "#2d0023"
color-link comment "#886484,#2d0023"
color-link constant.bool "#fd971f,#2d0023"
color-link constant "#fd971f,#2d0023"
color-link constant.string "#a0f000,#2d0023"
color-link constant.string.char "#a0f000,#2d0023"
color-link constant.string.url "#a0f000,#2d0023"
color-link current-line-number "bold #fd971f,#2d0023"
color-link cursor-line "#230019"
color-link default "#ffffff,#2d0023"
color-link diff-added "#00c8a0,#2d0023"
color-link diff-modified "#fd971f,#2d0023"
color-link diff-deleted "#cb4b16,#2d0023"
color-link divider "#2d0023,#d0d0d0"
color-link error "#cb4b16,#2d0023"
color-link gutter-error "#cb4b16,#2d0023"
color-link gutter-warning "#fce94f,#2d0023"
color-link identifier "#00c8a0,#2d0023"
color-link identifier.class "#00c8a0,#2d0023"
color-link indent-char "#a0a0a0,#2d0023"
color-link line-number "#a0a0a0,#230019"
color-link preproc "bold #5aaae6,#2d0023"
color-link special "#a6e22e,#2d0023"
color-link statement "bold #5aaae6,#2d0023"
color-link statusline "#ffffff,#0078c8"
color-link symbol "#00c8a0,#2d0023"
color-link symbol.brackets "#ffffff,#2d0023"
color-link symbol.tag "bold #5aaae6,#2d0023"
color-link tabbar "#2d0023,#ffffff"
color-link todo "#fce94f,#2d0023"
color-link type "bold #3cc83c,#2d0023"
color-link type.keyword "bold #5aaae6,#2d0023"
color-link type.extended "#ffffff,#2d0023"
color-link underlined "#886484,#2d0023"

View File

@@ -20,6 +20,8 @@ set colorscheme twilight
Micro comes with a number of colorschemes by default. The colorschemes that you
can display will depend on what kind of color support your terminal has.
Omit color-link default "[fg color],[bg color]" will make the background color match the terminal's, and transparency if set.
Modern terminals tend to have a palette of 16 user-configurable colors (these
colors can often be configured in the terminal preferences), and additional
color support comes in three flavors.
@@ -130,7 +132,7 @@ If you would like no foreground you can just use a comma with nothing in front:
color-link comment ",blue"
```
You can also put bold, or underline in front of the color:
You can also put bold, italic, or underline in front of the color:
```
color-link comment "bold red"

View File

@@ -62,6 +62,11 @@ quotes here but these are not necessary when entering the command in micro.
* `tab 'filename'`: opens the given file in a new tab.
* `tabmove '[-+]?n'`: Moves the active tab to another slot. `n` is an integer.
If `n` is prefixed with `-` or `+`, then it represents a relative position
(e.g. `tabmove +2` moves the tab to the right by `2`). If `n` has no prefix,
it represents an absolute position (e.g. `tabmove 2` moves the tab to slot `2`).
* `tabswitch 'tab'`: This command will switch to the specified tab. The `tab`
can either be a tab number, or a name of a tab.

View File

@@ -23,7 +23,7 @@ can change it!
| Shift+arrows | Move and select text |
| Alt(Ctrl on Mac)+LeftArrow | Move to the beginning of the current line |
| Alt(Ctrl on Mac)+RightArrow | Move to the end of the current line |
| Home | Move to the beginning of the current line |
| Home | Move to the beginning of text on the current line |
| End | Move to the end of the current line |
| Ctrl(Alt on Mac)+LeftArrow | Move cursor one word left |
| Ctrl(Alt on Mac)+RightArrow | Move cursor one word right |

View File

@@ -162,6 +162,8 @@ SelectUp
SelectDown
SelectLeft
SelectRight
SelectToStartOfText
SelectToStartOfTextToggle
WordRight
WordLeft
SelectWordRight
@@ -183,11 +185,13 @@ Save
SaveAll
SaveAs
Find
FindLiteral
FindNext
FindPrevious
Undo
Redo
Copy
CopyLine
Cut
CutLine
DuplicateLine
@@ -209,6 +213,8 @@ HalfPageUp
HalfPageDown
StartOfLine
EndOfLine
StartOfText
StartOfTextToggle
ParagraphPrevious
ParagraphNext
ToggleHelp
@@ -245,6 +251,9 @@ JumpToMatchingBrace
Autocomplete
```
The `StartOfTextToggle` and `SelectToStartOfTextToggle` actions toggle between
jumping to the start of the text (first) and start of the line.
You can also bind some mouse actions (these must be bound to mouse buttons)
```
@@ -410,21 +419,21 @@ conventions for text editing defaults.
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltLeft": "WordLeft", (Mac)
"AltRight": "WordRight", (Mac)
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"CtrlShiftRight": "SelectWordRight",
"CtrlShiftLeft": "SelectWordLeft",
"AltLeft": "StartOfLine",
"AltLeft": "StartOfTextToggle",
"AltRight": "EndOfLine",
"AltShiftRight": "SelectWordRight", (Mac)
"AltShiftLeft": "SelectWordLeft", (Mac)
"CtrlLeft": "StartOfText", (Mac)
"CtrlRight": "EndOfLine", (Mac)
"AltShiftLeft": "SelectToStartOfLine",
"CtrlShiftLeft": "SelectToStartOfText", (Mac)
"ShiftHome": "SelectToStartOfLine",
"AltShiftLeft": "SelectToStartOfTextToggle",
"CtrlShiftLeft": "SelectToStartOfTextToggle", (Mac)
"ShiftHome": "SelectToStartOfTextToggle",
"AltShiftRight": "SelectToEndOfLine",
"CtrlShiftRight": "SelectToEndOfLine", (Mac)
"ShiftEnd": "SelectToEndOfLine",
@@ -448,7 +457,7 @@ conventions for text editing defaults.
"CtrlP": "FindPrevious",
"CtrlZ": "Undo",
"CtrlY": "Redo",
"CtrlC": "Copy",
"CtrlC": "CopyLine|Copy",
"CtrlX": "Cut",
"CtrlK": "CutLine",
"CtrlD": "DuplicateLine",
@@ -457,7 +466,7 @@ conventions for text editing defaults.
"CtrlT": "AddTab",
"Alt,": "PreviousTab",
"Alt.": "NextTab",
"Home": "StartOfLine",
"Home": "StartOfText",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",

View File

@@ -44,8 +44,8 @@ Here are the available options:
default value: `true`
* `basename`: in the infobar, show only the basename of the file being edited
rather than the full path.
* `basename`: in the infobar and tabbar, show only the basename of the file
being edited rather than the full path.
default value: `false`
@@ -85,9 +85,10 @@ Here are the available options:
default value: `utf-8`
* `eofnewline`: micro will automatically add a newline to the file.
* `eofnewline`: micro will automatically add a newline to the end of the
file if one does not exist.
default value: `false`
default value: `true`
* `fastdirty`: this determines what kind of algorithm micro uses to determine
if a buffer is modified or not. When `fastdirty` is on, micro just uses a
@@ -95,10 +96,10 @@ Here are the available options:
This is fast, but can be inaccurate. If `fastdirty` is off, then micro will
hash the current buffer against a hash of the original file (created when
the buffer was loaded). This is more accurate but obviously more resource
intensive. This option is only for people who really care about having
accurate modified status.
intensive. This option will be automatically disabled if the file size
exceeds 50KB.
default value: `true`
default value: `false`
* `fileformat`: this determines what kind of line endings micro will use for
the file. UNIX line endings are just `\n` (linefeed) whereas dos line
@@ -296,11 +297,33 @@ Here are the available options:
default value: `true`
* `xterm`: micro will assume that the terminal it is running in conforms to
`xterm-256color` regardless of what the `$TERM` variable actually contains.
Enabling this option may cause unwanted effects if your terminal in fact
does not conform to the `xterm-256color` standard.
Default value: `false`
---
Plugin options: all plugins come with a special option to enable or disable
them. The option is a boolean with the same name as the plugin itself.
By default, the following plugins are provided, each with an option to enable
or disable them:
* `autoclose`: automatically closes brackets, quotes, etc...
* `comment`: provides automatic commenting for a number of languages
* `ftoptions`: alters some default options depending on the filetype
* `linter`: provides extensible linting for many languages
* `literate`: provides advanced syntax highlighting for the Literate
programming tool.
* `status`: provides some extensions to the status line (integration with
Git and more).
* `diff`: integrates the `diffgutter` option with Git. If you are in a Git
directory, the diff gutter will show changes with respect to the most
recent Git commit rather than the diff since opening the file.
Any option you set in the editor will be saved to the file
~/.config/micro/settings.json so, in effect, your configuration file will be
created for you. If you'd like to take your configuration with you to another

View File

@@ -391,6 +391,9 @@ There are 6 default plugins that come pre-installed with micro. These are
programming tool.
* `status`: provides some extensions to the status line (integration with
Git and more).
* `diff`: integrates the `diffgutter` option with Git. If you are in a Git
directory, the diff gutter will show changes with respect to the most
recent Git commit rather than the diff since opening the file.
See `> help linter`, `> help comment`, and `> help status` for additional
documentation specific to those plugins.

View File

@@ -23,6 +23,7 @@ ft["javascript"] = "// %s"
ft["ruby"] = "# %s"
ft["d"] = "// %s"
ft["swift"] = "// %s"
ft["elm"] = "-- %s"
function onBufferOpen(buf)
if buf.Settings["commenttype"] == nil then

View File

@@ -6,6 +6,8 @@ local filepath = import("path/filepath")
local shell = import("micro/shell")
local buffer = import("micro/buffer")
local config = import("micro/config")
local util = import("micro/util")
local os = import("os")
local linters = {}
@@ -64,10 +66,11 @@ function init()
end
makeLinter("gcc", "c", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
makeLinter("gcc", "c++", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
makeLinter("g++", "c++", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
makeLinter("dmd", "d", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", "%f"}, "%f%(%l%):.+: %m")
makeLinter("gobuild", "go", "go", {"build", "-o", devnull}, "%f:%l:%c:? %m")
makeLinter("gobuild", "go", "go", {"build", "-o", devnull, "%d"}, "%f:%l:%c:? %m")
-- makeLinter("golint", "go", "golint", {"%f"}, "%f:%l:%c: %m")
makeLinter("hlint", "haskell", "hlint", {"%f"}, "%f:%l:%c.-: %m")
makeLinter("javac", "java", "javac", {"-d", "%d", "%f"}, "%f:%l: error: %m")
makeLinter("jshint", "javascript", "jshint", {"%f"}, "%f: line %l,.+, %m")
makeLinter("literate", "literate", "lit", {"-c", "%f"}, "%f:%l:%m", {}, false, true)
@@ -102,7 +105,7 @@ end
function runLinter(buf)
local ft = buf:FileType()
local file = buf.Path
local dir = filepath.Dir(file)
local dir = "." .. util.RuneStr(os.PathSeparator) .. filepath.Dir(file)
for k, v in pairs(linters) do
local ftmatch = ft == v.filetype

View File

@@ -1,28 +0,0 @@
filetype: c++
detect:
filename: "\\.c(c|pp|xx)$|\\.h(h|pp|xx)$|\\.ii?$|\\.(def)$"
rules:
- identifier: "\\b[A-Z_][0-9A-Z_]+\\b"
- type: "\\b(auto|float|double|bool|char|int|short|long|sizeof|enum|void|static|const|constexpr|struct|union|typedef|extern|(un)?signed|inline)\\b"
- type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\\b"
- statement: "\\b(class|namespace|template|public|protected|private|typename|this|friend|virtual|using|mutable|volatile|register|explicit)\\b"
- statement: "\\b(for|if|while|do|else|case|default|switch)\\b"
- statement: "\\b(try|throw|catch|operator|new|delete)\\b"
- special: "\\b(goto|continue|break|return)\\b"
- preproc: "^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error)"
- constant: "'([^'\\\\]|(\\\\[\"'abfnrtv\\\\]))'|'\\\\(([0-3]?[0-7]{1,2}))'|'\\\\x[0-9A-Fa-f]{1,2}'"
- statement: "__attribute__[[:space:]]*\\(\\([^)]*\\)\\)|__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__"
- symbol.operator: "[.:;,+*|=!\\%]|<|>|/|-|&"
- symbol.brackets: "[(){}]|\\[|\\]"
- constant.number: "\\b[0-9]+\\b|\\b0x[0-9A-Fa-f]+\\b"
- constant.bool: "\\b(true|false)\\b|NULL"
- constant.string: "\"(\\\\.|[^\"])*\""
- comment: "//.*"
- comment:
start: "/\\*"
end: "\\*/"
rules: []
- indent-char.whitespace: "[[:space:]]+$"

View File

@@ -12,6 +12,9 @@ rules:
- statement: "\\b(debugger|switch|while|do|class|extends|super)\\b"
- statement: "\\b(undefined|then|unless|until|loop|of|by|when)\\b"
- constant.bool: "\\b(true|false|yes|no|on|off)\\b"
- constant.number: "\\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[lL][uU]?)?\\b"
- constant.number: "\\b[-+]?([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+)([EePp][+-]?[0-9]+)?[fFlL]?"
- constant.number: "\\b[-+]?([0-9]+[EePp][+-]?[0-9]+)[fFlL]?"
- identifier: "@[A-Za-z0-9_]*"
- constant.string:
@@ -20,10 +23,23 @@ rules:
skip: "\\\\."
rules:
- constant.specialChar: "\\\\."
- constant.string:
start: "'"
end: "'"
skip: "\\\\."
rules:
- constant.specialChar: "\\\\."
- comment:
start: "#"
end: "$"
rules:
- todo: "(TODO|XXX|FIXME):?"
- comment:
start: "###"
end: "###"
rules:
- todo: "(TODO|XXX|FIXME)"

8
runtime/syntax/csx.yaml Normal file
View File

@@ -0,0 +1,8 @@
filetype: csharp-script
detect:
filename: "\\.csx$"
header: "^#!.*/(env +)?dotnet-script( |$)"
rules:
- include: "csharp"
- preproc: "\\B(\\#!|\\#[r|load|]+\\b)"

View File

@@ -26,7 +26,7 @@ rules:
# Diffs (i.e. git commit --verbose)
- default:
start: "^diff"
start: "^diff --git"
# Diff output puts a space before file contents on each line so this
# should never match valid diff output and extend highlighting to the
# end of the file

View File

@@ -8,13 +8,14 @@ rules:
# built-in objects
- constant.bool: "\\b(true|false)\\b"
- constant: "\\b(nothing|missing)\\b"
# built-in attributes
- constant: "__[A-Za-z0-9_]+__"
# definitions
- identifier: "[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[(]"
# keywords
- statement: "\\b(baremodule|begin|break|catch|const|continue|do|else|elseif|end|export|finally|for|function|global|if|import|let|local|macro|module|quote|return|struct|try|using|while)\\b"
- statement: "\\b(abstract type|primitive type|mutable struct\\b)"
- statement: "\\b(abstract\\s+type|primitive\\s+type|mutable\\s+struct)\\b"
# decorators
- identifier.macro: "@[A-Za-z0-9_]+"
# operators
@@ -22,7 +23,7 @@ rules:
# parentheses
- symbol.brackets: "([(){}]|\\[|\\])"
# numbers
- constant.number: "\\b([0-9]+(_[0-9]+)*|0x[0-9a-fA-F]+(_[0-9a-fA-F]+)*|0b[01]+(_[01]+)*|0o[0-7]+(_[0-7]+)*)\\b"
- constant.number: "\\b([0-9]+(_[0-9]+)*|0x[0-9a-fA-F]+(_[0-9a-fA-F]+)*|0b[01]+(_[01]+)*|0o[0-7]+(_[0-7]+)*|Inf(16|32|64)?|NaN(16|32|64)?)\\b"
- constant.string: "\"(\\\\.|[^\"])*\"|'(\\\\.|[^']){1}'"

View File

@@ -1,8 +1,8 @@
filetype: python
filetype: python2
detect:
filename: "\\.py$"
header: "^#!.*/(env +)?python( |$)"
filename: "\\.py2$"
header: "^#!.*/(env +)?python2$"
rules:

View File

@@ -1,8 +1,8 @@
filename: python3
filetype: python
detect:
filename: "\\.py3$"
header: "^#!.*/(env +)?python3$"
filename: "\\.py(3)?$"
header: "^#!.*/(env +)?python(3)?$"
rules:
# built-in objects

View File

@@ -1,7 +1,7 @@
filetype: ruby
detect:
filename: "\\.(rb|rake|gemspec)$|^(Gemfile|config.ru|Rakefile|Capfile|Vagrantfile|Guardfile)$"
filename: "\\.(rb|rake|gemspec)$|^(Gemfile|config.ru|Rakefile|Capfile|Vagrantfile|Guardfile|Appfile|Fastfile|Pluginfile|Podfile)$"
header: "^#!.*/(env +)?ruby( |$)"
rules:

60
runtime/syntax/sage.yaml Normal file
View File

@@ -0,0 +1,60 @@
filetype: sage
detect:
filename: "\\.sage$"
header: "^#!.*/(env +)?sage( |$)"
rules:
# built-in objects
- constant: "\\b(None|self|True|False)\\b"
# built-in attributes
- constant: "\\b(__bases__|__builtin__|__class__|__debug__|__dict__|__doc__|__file__|__members__|__methods__|__name__|__self__)\\b"
# built-in functions
- identifier: "\\b(abs|apply|callable|chr|cmp|compile|delattr|dir|divmod|eval|exec|execfile|filter|format|getattr|globals|hasattr|hash|help|hex|id|input|intern|isinstance|issubclass|len|locals|max|min|next|oct|open|ord|pow|range|raw_input|reduce|reload|repr|round|setattr|unichr|vars|zip|__import__)\\b"
# special method names
- identifier: "\\b(__abs__|__add__|__and__|__call__|__cmp__|__coerce__|__complex__|__concat__|__contains__|__del__|__delattr__|__delitem__|__dict__|__delslice__|__div__|__divmod__|__float__|__getattr__|__getitem__|__getslice__|__hash__|__hex__|__init__|__int__|__inv__|__invert__|__len__|__long__|__lshift__|__mod__|__mul__|__neg__|__nonzero__|__oct__|__or__|__pos__|__pow__|__radd__|__rand__|__rcmp__|__rdiv__|__rdivmod__|__repeat__|__repr__|__rlshift__|__rmod__|__rmul__|__ror__|__rpow__|__rrshift__|__rshift__|__rsub__|__rxor__|__setattr__|__setitem__|__setslice__|__str__|__sub__|__xor__)\\b"
# types
- type: "\\b(basestring|bool|buffer|bytearray|bytes|classmethod|complex|dict|enumerate|file|float|frozenset|int|list|long|map|memoryview|object|property|reversed|set|slice|staticmethod|str|super|tuple|type|unicode|xrange)\\b"
# definitions
- identifier: "def [a-zA-Z_0-9]+"
# keywords
- statement: "\\b(and|as|assert|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|return|try|while|with|yield)\\b"
# decorators
- brightgreen: "@.*[(]"
# operators
- statement: "([.:;,+*|=!\\%@]|<|>|/|-|&)"
# parentheses
- statement: "([(){}]|\\[|\\])"
# numbers
- constant.number: "\\b[0-9]+\\b"
- comment:
start: "\"\"\""
end: "\"\"\""
rules: []
- comment:
start: "'''"
end: "'''"
rules: []
- constant.string:
start: "\""
end: "\""
skip: "\\\\."
rules:
- constant.specialChar: "\\\\."
- constant.string:
start: "'"
end: "'"
skip: "\\\\."
rules:
- constant.specialChar: "\\\\."
- comment:
start: "#"
end: "$"
rules: []

View File

@@ -1,7 +1,6 @@
filetype: v
detect:
filename: "\\.v$"
rules:
# Conditionals and control flow

View File

@@ -1,7 +1,7 @@
filetype: verilog
detect:
filename: "\\.(vh|sv|svh)$"
filename: "\\.(v|vh|sv|svh)$"
rules:
- preproc: "\\b(module|package|program|endmodule|endpackage|endprogram)\\b"

View File

@@ -1,11 +1,11 @@
name: micro
version: git
summary: A modern and intuitive terminal-based text editor
description: |
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.
confinement: classic
adopt-info: micro
apps:
micro:
@@ -19,7 +19,13 @@ parts:
build-packages: [make]
build-snaps: [go]
build-attributes: [no-patchelf]
override-pull: |
snapcraftctl pull
version="$(go run $SNAPCRAFT_PART_SRC/tools/build-version.go)"
[ -n "$(echo $version | grep "dev")" ] && grade=devel || grade=stable
snapcraftctl set-version "$version"
snapcraftctl set-grade "$grade"
override-build: |
make build
make build-tags
mkdir $SNAPCRAFT_PART_INSTALL/bin
mv ./micro $SNAPCRAFT_PART_INSTALL/bin/

View File

@@ -53,7 +53,7 @@ func main() {
}
// If we don't have any tag assume "dev"
if tag == "" {
if tag == "" || strings.HasPrefix(tag, "nightly") {
tag = "dev"
}
// Get the most likely next version:

255
tools/testgen.go Normal file
View File

@@ -0,0 +1,255 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"regexp"
"strings"
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/parser"
)
type walker struct {
nodes []ast.Node
}
func (w *walker) Enter(node ast.Node) ast.Visitor {
w.nodes = append(w.nodes, node)
return w
}
func (w *walker) Exit(node ast.Node) {
}
func getAllNodes(node ast.Node) []ast.Node {
w := &walker{}
ast.Walk(w, node)
return w.nodes
}
func getCalls(node ast.Node, name string) []*ast.CallExpression {
nodes := []*ast.CallExpression{}
for _, n := range getAllNodes(node) {
if ce, ok := n.(*ast.CallExpression); ok {
var calleeName string
switch callee := ce.Callee.(type) {
case *ast.Identifier:
calleeName = callee.Name
case *ast.DotExpression:
calleeName = callee.Identifier.Name
default:
continue
}
if calleeName == name {
nodes = append(nodes, ce)
}
}
}
return nodes
}
func getPropertyValue(node ast.Node, key string) ast.Expression {
for _, p := range node.(*ast.ObjectLiteral).Value {
if p.Key == key {
return p.Value
}
}
return nil
}
type operation struct {
startLine int
startColumn int
endLine int
endColumn int
text []string
}
type check struct {
before []string
operations []operation
after []string
}
type test struct {
description string
checks []check
}
func stringSliceToGoSource(slice []string) string {
var b strings.Builder
b.WriteString("[]string{\n")
for _, s := range slice {
b.WriteString(fmt.Sprintf("%#v,\n", s))
}
b.WriteString("}")
return b.String()
}
func testToGoTest(test test, name string) string {
var b strings.Builder
b.WriteString("func Test")
b.WriteString(name)
b.WriteString("(t *testing.T) {\n")
for _, c := range test.checks {
b.WriteString("check(\n")
b.WriteString("t,\n")
b.WriteString(fmt.Sprintf("%v,\n", stringSliceToGoSource(c.before)))
b.WriteString("[]operation{\n")
for _, op := range c.operations {
b.WriteString("operation{\n")
b.WriteString(fmt.Sprintf("start: Loc{%v, %v},\n", op.startColumn, op.startLine))
b.WriteString(fmt.Sprintf("end: Loc{%v, %v},\n", op.endColumn, op.endLine))
b.WriteString(fmt.Sprintf("text: %v,\n", stringSliceToGoSource(op.text)))
b.WriteString("},\n")
}
b.WriteString("},\n")
b.WriteString(fmt.Sprintf("%v,\n", stringSliceToGoSource(c.after)))
b.WriteString(")\n")
}
b.WriteString("}\n")
return b.String()
}
func nodeToStringSlice(node ast.Node) []string {
var result []string
for _, s := range node.(*ast.ArrayLiteral).Value {
result = append(result, s.(*ast.StringLiteral).Value)
}
return result
}
func nodeToStringSlice2(node ast.Node) []string {
var result []string
for _, o := range node.(*ast.ArrayLiteral).Value {
result = append(result, getPropertyValue(o, "text").(*ast.StringLiteral).Value)
}
return result
}
func nodeToInt(node ast.Node) int {
return int(node.(*ast.NumberLiteral).Value.(int64))
}
func getChecks(node ast.Node) []check {
checks := []check{}
for _, ce := range getCalls(node, "testApplyEdits") {
if len(ce.ArgumentList) != 3 {
// Wrong function
continue
}
before := nodeToStringSlice2(ce.ArgumentList[0])
after := nodeToStringSlice2(ce.ArgumentList[2])
var operations []operation
for _, op := range ce.ArgumentList[1].(*ast.ArrayLiteral).Value {
args := getPropertyValue(op, "range").(*ast.NewExpression).ArgumentList
operations = append(operations, operation{
startLine: nodeToInt(args[0]) - 1,
startColumn: nodeToInt(args[1]) - 1,
endLine: nodeToInt(args[2]) - 1,
endColumn: nodeToInt(args[3]) - 1,
text: []string{getPropertyValue(op, "text").(*ast.StringLiteral).Value},
})
}
checks = append(checks, check{before, operations, after})
}
for _, ce := range getCalls(node, "testApplyEditsWithSyncedModels") {
if len(ce.ArgumentList) > 3 && ce.ArgumentList[3].(*ast.BooleanLiteral).Value {
// inputEditsAreInvalid == true
continue
}
before := nodeToStringSlice(ce.ArgumentList[0])
after := nodeToStringSlice(ce.ArgumentList[2])
var operations []operation
for _, op := range getCalls(ce.ArgumentList[1], "editOp") {
operations = append(operations, operation{
startLine: nodeToInt(op.ArgumentList[0]) - 1,
startColumn: nodeToInt(op.ArgumentList[1]) - 1,
endLine: nodeToInt(op.ArgumentList[2]) - 1,
endColumn: nodeToInt(op.ArgumentList[3]) - 1,
text: nodeToStringSlice(op.ArgumentList[4]),
})
}
checks = append(checks, check{before, operations, after})
}
return checks
}
func getTests(node ast.Node) []test {
tests := []test{}
for _, ce := range getCalls(node, "test") {
description := ce.ArgumentList[0].(*ast.StringLiteral).Value
body := ce.ArgumentList[1].(*ast.FunctionLiteral).Body
checks := getChecks(body)
if len(checks) > 0 {
tests = append(tests, test{description, checks})
}
}
return tests
}
func main() {
var tests []test
for _, filename := range os.Args[1:] {
source, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatalln(err)
}
program, err := parser.ParseFile(nil, "", source, parser.IgnoreRegExpErrors)
if err != nil {
log.Fatalln(err)
}
tests = append(tests, getTests(program)...)
}
if len(tests) == 0 {
log.Fatalln("no tests found!")
}
fmt.Println("// This file is generated from VSCode model tests by the testgen tool.")
fmt.Println("// DO NOT EDIT THIS FILE BY HAND; your changes will be overwritten!\n")
fmt.Println("package buffer")
fmt.Println(`import "testing"`)
re := regexp.MustCompile(`[^\w]`)
usedNames := map[string]bool{}
for _, test := range tests {
name := strings.Title(strings.ToLower(test.description))
name = re.ReplaceAllLiteralString(name, "")
if name == "" {
name = "Unnamed"
}
if usedNames[name] {
for i := 2; ; i++ {
newName := fmt.Sprintf("%v_%v", name, i)
if !usedNames[newName] {
name = newName
break
}
}
}
usedNames[name] = true
fmt.Println(testToGoTest(test, name))
}
}