Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
319a5cba74 |
261 changed files with 3993 additions and 9901 deletions
22
.github/workflows/test.yaml
vendored
22
.github/workflows/test.yaml
vendored
|
@ -1,22 +0,0 @@
|
||||||
on: [push, pull_request]
|
|
||||||
name: Build and Test
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go-version: [1.19.x]
|
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.go-version }}
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
make build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
make test
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -15,5 +15,7 @@ benchmark_results*
|
||||||
tools/build-version
|
tools/build-version
|
||||||
tools/build-date
|
tools/build-date
|
||||||
tools/info-plist
|
tools/info-plist
|
||||||
|
tools/bindata
|
||||||
tools/vscode-tests/
|
tools/vscode-tests/
|
||||||
*.hdr
|
*.hdr
|
||||||
|
assets_vfsdata.go
|
||||||
|
|
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
9
.travis.yml
Normal file
9
.travis.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- "1.11.x"
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
script:
|
||||||
|
- env GO111MODULE=on make build
|
||||||
|
- env GO111MODULE=on make test
|
41
Makefile
41
Makefile
|
@ -1,4 +1,4 @@
|
||||||
.PHONY: runtime build generate build-quick
|
.PHONY: runtime
|
||||||
|
|
||||||
VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||||
go run tools/build-version.go)
|
go run tools/build-version.go)
|
||||||
|
@ -6,36 +6,46 @@ HASH = $(shell git rev-parse --short HEAD)
|
||||||
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||||
go run tools/build-date.go)
|
go run tools/build-date.go)
|
||||||
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
|
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
|
||||||
GOARCH=$(shell go env GOHOSTARCH) \
|
GOARCH=$(shell go env GOHOSTARCH))
|
||||||
go run tools/info-plist.go "$(shell go env GOOS)" "$(VERSION)")
|
|
||||||
GOBIN ?= $(shell go env GOPATH)/bin
|
GOBIN ?= $(shell go env GOPATH)/bin
|
||||||
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)'
|
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
|
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/'
|
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
|
||||||
|
|
||||||
build: generate build-quick
|
# Builds micro after checking dependencies but without updating the runtime
|
||||||
|
build:
|
||||||
build-quick:
|
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
|
||||||
|
|
||||||
build-dbg:
|
build-dbg:
|
||||||
go build -trimpath -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
|
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
|
||||||
|
|
||||||
build-tags: fetch-tags generate
|
build-tags: fetch-tags
|
||||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||||
|
|
||||||
build-all: build
|
# Builds micro after building the runtime and checking dependencies
|
||||||
|
build-all: runtime build
|
||||||
|
|
||||||
install: generate
|
# Builds micro without checking for dependencies
|
||||||
|
build-quick:
|
||||||
|
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||||
|
|
||||||
|
# Same as 'build' but installs to $GOBIN afterward
|
||||||
|
install:
|
||||||
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||||
|
|
||||||
install-all: install
|
# Same as 'build-all' but installs to $GOBIN afterward
|
||||||
|
install-all: runtime install
|
||||||
|
|
||||||
|
# Same as 'build-quick' but installs to $GOBIN afterward
|
||||||
|
install-quick:
|
||||||
|
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||||
|
|
||||||
fetch-tags:
|
fetch-tags:
|
||||||
git fetch --tags
|
git fetch --tags
|
||||||
|
|
||||||
generate:
|
# Builds the runtime
|
||||||
GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime
|
runtime:
|
||||||
|
go generate ./internal/config
|
||||||
|
|
||||||
testgen:
|
testgen:
|
||||||
mkdir -p tools/vscode-tests
|
mkdir -p tools/vscode-tests
|
||||||
|
@ -48,7 +58,6 @@ testgen:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test ./internal/...
|
go test ./internal/...
|
||||||
go test ./cmd/...
|
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
for i in 1 2 3; do \
|
for i in 1 2 3; do \
|
||||||
|
|
101
README.md
101
README.md
|
@ -1,11 +1,11 @@
|
||||||
<img alt="micro logo" src="./assets/micro-logo-drop.svg" width="500px"/>
|
<img alt="micro logo" src="./assets/micro-logo.svg" width="500px"/>
|
||||||
|
|
||||||
![Test Workflow](https://github.com/zyedidia/micro/actions/workflows/test.yaml/badge.svg)
|
[![Build Status](https://travis-ci.org/zyedidia/micro.svg?branch=master)](https://travis-ci.org/zyedidia/micro)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/zyedidia/micro)](https://goreportcard.com/report/github.com/zyedidia/micro)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/zyedidia/micro)](https://goreportcard.com/report/github.com/zyedidia/micro)
|
||||||
[![Release](https://img.shields.io/github/release/zyedidia/micro.svg?label=Release)](https://github.com/zyedidia/micro/releases)
|
[![Release](https://img.shields.io/github/release/zyedidia/micro.svg?label=Release)](https://github.com/zyedidia/micro/releases)
|
||||||
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
|
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
|
||||||
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
[![Snap Status](https://snapcraft.io/micro/badge.svg)](https://snapcraft.io/micro)
|
[![Snap Status](https://build.snapcraft.io/badge/zyedidia/micro.svg)](https://build.snapcraft.io/user/zyedidia/micro)
|
||||||
|
|
||||||
**micro** is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the capabilities
|
**micro** is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the capabilities
|
||||||
of modern terminals. It comes as a single, batteries-included, static binary with no dependencies; you can download and use it right now!
|
of modern terminals. It comes as a single, batteries-included, static binary with no dependencies; you can download and use it right now!
|
||||||
|
@ -17,7 +17,7 @@ Here is a picture of micro editing its source code.
|
||||||
|
|
||||||
![Screenshot](./assets/micro-solarized.png)
|
![Screenshot](./assets/micro-solarized.png)
|
||||||
|
|
||||||
To see more screenshots of micro, showcasing some of the default color schemes, see [here](https://micro-editor.github.io).
|
To see more screenshots of micro, showcasing some of the default color schemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
|
||||||
|
|
||||||
You can also check out the website for Micro at https://micro-editor.github.io.
|
You can also check out the website for Micro at https://micro-editor.github.io.
|
||||||
|
|
||||||
|
@ -25,14 +25,14 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||||
|
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Prebuilt binaries](#pre-built-binaries)
|
- [Prebuilt binaries](#prebuilt-binaries)
|
||||||
- [Package Managers](#package-managers)
|
- [Package Managers](#package-managers)
|
||||||
- [Building from source](#building-from-source)
|
- [Building from source](#building-from-source)
|
||||||
- [Fully static binary](#fully-static-binary)
|
- [Fully static binary](#fully-static-binary)
|
||||||
- [macOS terminal](#macos-terminal)
|
- [macOS terminal](#macos-terminal)
|
||||||
- [Linux clipboard support](#linux-clipboard-support)
|
- [Linux clipboard support](#linux-clipboard-support)
|
||||||
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
|
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
|
||||||
- [Cygwin, Mingw, Plan9](#cygwin-mingw-plan9)
|
- [Plan9, Cygwin, Mingw](#plan9-cygwin-mingw)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Documentation and Help](#documentation-and-help)
|
- [Documentation and Help](#documentation-and-help)
|
||||||
- [Contributing](#contributing)
|
- [Contributing](#contributing)
|
||||||
|
@ -53,7 +53,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||||
- Extremely good mouse support.
|
- Extremely good mouse support.
|
||||||
- This means mouse dragging to create a selection, double click to select by word, and triple click to select by line.
|
- This means mouse dragging to create a selection, double click to select by word, and triple click to select by line.
|
||||||
- Cross-platform (it should work on all the platforms Go runs on).
|
- Cross-platform (it should work on all the platforms Go runs on).
|
||||||
- Note that while Windows is supported, Mingw/Cygwin is not (see below).
|
- Note that while Windows is supported Mingw/Cygwin is not (see below).
|
||||||
- Plugin system (plugins are written in Lua).
|
- Plugin system (plugins are written in Lua).
|
||||||
- micro has a built-in plugin manager to automatically install, remove, and update plugins.
|
- micro has a built-in plugin manager to automatically install, remove, and update plugins.
|
||||||
- Built-in diff gutter.
|
- Built-in diff gutter.
|
||||||
|
@ -68,7 +68,6 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||||
- Small and simple.
|
- Small and simple.
|
||||||
- Easily configurable.
|
- Easily configurable.
|
||||||
- Macros.
|
- Macros.
|
||||||
- Smart highlighting of trailing whitespace and tab vs space errors.
|
|
||||||
- Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, …
|
- Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, …
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -80,40 +79,23 @@ If you want more information about ways to install micro, see this [wiki page](h
|
||||||
Use `micro -version` to get the version information after installing. It is only guaranteed that you are installing the most recent
|
Use `micro -version` to get the version information after installing. It is only guaranteed that you are installing the most recent
|
||||||
stable version if you install from the prebuilt binaries, Homebrew, or Snap.
|
stable version if you install from the prebuilt binaries, Homebrew, or Snap.
|
||||||
|
|
||||||
A desktop entry file and man page can be found in the [assets/packaging](https://github.com/zyedidia/micro/tree/master/assets/packaging) directory.
|
### Prebuilt binaries
|
||||||
|
|
||||||
### Pre-built binaries
|
All you need to install micro is one file, the binary itself. It's as simple as that!
|
||||||
|
|
||||||
Pre-built binaries are distributed in [releases](https://github.com/zyedidia/micro/releases).
|
Download the binary from the [releases](https://github.com/zyedidia/micro/releases) page.
|
||||||
|
|
||||||
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
|
### Installation script
|
||||||
|
|
||||||
#### Quick-install script
|
There is a script which can install micro for you by downloading the latest prebuilt binary. You can find it at <https://getmic.ro>.
|
||||||
|
|
||||||
|
You can easily install micro by running
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl https://getmic.ro | bash
|
curl https://getmic.ro | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
The script will place the micro binary in the current directory. From there, you can move it to a directory on your path of your choosing (e.g. `sudo mv micro /usr/bin`). See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
|
The script will place the micro binary in the current directory. See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
|
||||||
|
|
||||||
#### Eget
|
|
||||||
|
|
||||||
With [Eget](https://github.com/zyedidia/eget) installed, you can easily get a pre-built binary:
|
|
||||||
|
|
||||||
```
|
|
||||||
eget zyedidia/micro
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `--tag VERSION` to download a specific tagged version.
|
|
||||||
|
|
||||||
```
|
|
||||||
eget --tag nightly zyedidia/micro # download the nightly version (compiled every day at midnight UTC)
|
|
||||||
eget --tag v2.0.8 zyedidia/micro # download version 2.0.8 rather than the latest release
|
|
||||||
```
|
|
||||||
|
|
||||||
You can install `micro` by adding `--to /usr/local/bin` to the `eget` command, or move the binary manually to a directory on your `$PATH` after the download completes.
|
|
||||||
|
|
||||||
See [Eget](https://github.com/zyedidia/eget) for more information.
|
|
||||||
|
|
||||||
### Package managers
|
### Package managers
|
||||||
|
|
||||||
|
@ -133,19 +115,17 @@ On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/in
|
||||||
snap install micro --classic
|
snap install micro --classic
|
||||||
```
|
```
|
||||||
|
|
||||||
Micro is also available through other package managers on Linux such as dnf, AUR, Nix, and package managers
|
**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 apt, dnf, AUR, Nix, and package managers
|
||||||
for other operating systems. These packages are not guaranteed to be up-to-date.
|
for other operating systems. These packages are not guaranteed to be up-to-date.
|
||||||
|
|
||||||
<!-- * `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`). At the moment, this package (2.0.1-1) is outdated and has a known bug where debug mode is enabled. -->
|
|
||||||
|
|
||||||
* Linux: Available in distro-specific package managers.
|
* Linux: Available in distro-specific package managers.
|
||||||
|
* `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`).
|
||||||
* `dnf install micro` (Fedora).
|
* `dnf install micro` (Fedora).
|
||||||
* `apt install micro` (Ubuntu and Debian).
|
* `yay -S micro` (Arch Linux).
|
||||||
* `pacman -S micro` (Arch Linux).
|
|
||||||
* `emerge app-editors/micro` (Gentoo).
|
|
||||||
* `zypper install micro-editor` (SUSE)
|
|
||||||
* `eopkg install micro` (Solus).
|
|
||||||
* `pacstall -I micro` (Pacstall).
|
|
||||||
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
|
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
|
||||||
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop).
|
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop).
|
||||||
* `choco install micro`.
|
* `choco install micro`.
|
||||||
|
@ -154,22 +134,12 @@ for other operating systems. These packages are not guaranteed to be up-to-date.
|
||||||
* `pkd_add -v micro`.
|
* `pkd_add -v micro`.
|
||||||
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](http://www.pkgsrc.org/)-current:
|
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](http://www.pkgsrc.org/)-current:
|
||||||
* `pkg_add micro`
|
* `pkg_add micro`
|
||||||
* macOS with [MacPorts](https://www.macports.org):
|
|
||||||
* `sudo port install micro`
|
|
||||||
|
|
||||||
**Note for Linux desktop environments:**
|
|
||||||
|
|
||||||
For interfacing with the local system clipboard, the following tools need to be installed:
|
|
||||||
* For X11, `xclip` or `xsel`
|
|
||||||
* For [Wayland](https://wayland.freedesktop.org/), `wl-clipboard`
|
|
||||||
|
|
||||||
Without these tools installed, micro will use an internal clipboard for copy and paste, but it won't be accessible to external applications.
|
|
||||||
|
|
||||||
### Building from source
|
### Building from source
|
||||||
|
|
||||||
If your operating system does not have a binary release, but does run Go, you can build from source.
|
If your operating system does not have a binary release, but does run Go, you can build from source.
|
||||||
|
|
||||||
Make sure that you have Go version 1.16 or greater and Go modules are enabled.
|
Make sure that you have Go version 1.11 or greater and Go modules are enabled.
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/zyedidia/micro
|
git clone https://github.com/zyedidia/micro
|
||||||
|
@ -204,17 +174,14 @@ If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) i
|
||||||
If you still insist on using the default Mac terminal, be sure to set `Use Option key as Meta key` under
|
If you still insist on using the default Mac terminal, be sure to set `Use Option key as Meta key` under
|
||||||
`Preferences->Profiles->Keyboard` to use <kbd>option</kbd> as <kbd>alt</kbd>.
|
`Preferences->Profiles->Keyboard` to use <kbd>option</kbd> as <kbd>alt</kbd>.
|
||||||
|
|
||||||
### WSL and Windows Console
|
### Linux clipboard support
|
||||||
|
|
||||||
If you use micro within WSL, it is highly recommended that you use the [Windows
|
On Linux, clipboard support requires:
|
||||||
Terminal](https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701?hl=en-us&gl=us)
|
|
||||||
instead of the default Windows Console.
|
|
||||||
|
|
||||||
If you must use Windows Console for some reason, note that there is a bug in
|
- On X11, the `xclip` or `xsel` commands (for Ubuntu: `sudo apt install xclip`)
|
||||||
Windows Console WSL that causes a font change whenever micro tries to access
|
- On Wayland, the `wl-clipboard` command
|
||||||
the external clipboard via powershell. To fix this, use an internal clipboard
|
|
||||||
with `set clipboard internal` (though your system clipboard will no longer be
|
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.
|
||||||
available in micro).
|
|
||||||
|
|
||||||
### Colors and syntax highlighting
|
### Colors and syntax highlighting
|
||||||
|
|
||||||
|
@ -222,7 +189,7 @@ If you open micro and it doesn't seem like syntax highlighting is working, this
|
||||||
you are using a terminal which does not support 256 color mode. Try changing the color scheme to `simple`
|
you are using a terminal which does not support 256 color mode. Try changing the color scheme to `simple`
|
||||||
by pressing <kbd>Ctrl-e</kbd> in micro and typing `set colorscheme simple`.
|
by pressing <kbd>Ctrl-e</kbd> in micro and typing `set colorscheme simple`.
|
||||||
|
|
||||||
If you are using the default Ubuntu terminal, to enable 256 color mode make sure your `TERM` variable is set
|
If you are using the default Ubuntu terminal, to enable 256 make sure your `TERM` variable is set
|
||||||
to `xterm-256color`.
|
to `xterm-256color`.
|
||||||
|
|
||||||
Many of the Windows terminals don't support more than 16 colors, which means
|
Many of the Windows terminals don't support more than 16 colors, which means
|
||||||
|
@ -241,7 +208,7 @@ winpty micro.exe ...
|
||||||
|
|
||||||
Micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
|
Micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
|
||||||
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
|
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
|
||||||
Plan9 or Cygwin (although this may change in the future). Micro also doesn't support NaCl (which is deprecated anyway).
|
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (which is deprecated anyway).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
@ -250,7 +217,7 @@ Once you have built the editor, start it by running `micro path/to/file.txt` or
|
||||||
micro also supports creating buffers from `stdin`:
|
micro also supports creating buffers from `stdin`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ip a | micro
|
ifconfig | micro
|
||||||
```
|
```
|
||||||
|
|
||||||
You can move the cursor around with the arrow keys and mouse.
|
You can move the cursor around with the arrow keys and mouse.
|
||||||
|
@ -274,8 +241,6 @@ view the help files here:
|
||||||
I also recommend reading the [tutorial](https://github.com/zyedidia/micro/tree/master/runtime/help/tutorial.md) for
|
I also recommend reading the [tutorial](https://github.com/zyedidia/micro/tree/master/runtime/help/tutorial.md) for
|
||||||
a brief introduction to the more powerful configuration features micro offers.
|
a brief introduction to the more powerful configuration features micro offers.
|
||||||
|
|
||||||
There is also an unofficial Discord, which you can join at https://discord.gg/nhWR6armnR.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
|
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
|
||||||
|
@ -283,6 +248,6 @@ If you find any bugs, please report them! I am also happy to accept pull request
|
||||||
You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues)
|
You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues)
|
||||||
to report bugs, ask questions, or suggest new features.
|
to report bugs, ask questions, or suggest new features.
|
||||||
|
|
||||||
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro) or the [Discord](https://discord.gg/nhWR6armnR). You can also use the [Discussions](https://github.com/zyedidia/micro/discussions) section on Github for a forum-like setting or for Q&A.
|
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro).
|
||||||
|
|
||||||
Sometimes I am unresponsive, and I apologize! If that happens, please ping me.
|
Sometimes I am unresponsive, and I apologize! If that happens, please ping me.
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
id="Layer_1"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
viewBox="0 0 304.70001 103.2"
|
|
||||||
enable-background="new 0 0 960 560"
|
|
||||||
xml:space="preserve"
|
|
||||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
|
||||||
sodipodi:docname="micro-logo-drop.svg"
|
|
||||||
width="304.70001"
|
|
||||||
height="103.2"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
|
||||||
id="metadata21"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs19"><filter
|
|
||||||
style="color-interpolation-filters:sRGB"
|
|
||||||
inkscape:label="Blur"
|
|
||||||
id="filter1040"
|
|
||||||
x="-0.028037383"
|
|
||||||
y="-0.10549451"
|
|
||||||
width="1.0560748"
|
|
||||||
height="1.210989"><feGaussianBlur
|
|
||||||
stdDeviation="2 2"
|
|
||||||
result="blur"
|
|
||||||
id="feGaussianBlur1038" /></filter></defs><sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1080"
|
|
||||||
id="namedview17"
|
|
||||||
showgrid="false"
|
|
||||||
fit-margin-top="0"
|
|
||||||
fit-margin-left="0"
|
|
||||||
fit-margin-right="0"
|
|
||||||
fit-margin-bottom="0"
|
|
||||||
inkscape:zoom="13.204388"
|
|
||||||
inkscape:cx="71.832181"
|
|
||||||
inkscape:cy="63.956011"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="Layer_1"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:deskcolor="#d1d1d1" /><g
|
|
||||||
id="g838"
|
|
||||||
transform="translate(-178,-172.8)"
|
|
||||||
style="fill:#ffffff;fill-opacity:1;filter:url(#filter1040)"><path
|
|
||||||
d="m 306.8,213.8 v -2.6 c 1.6,-0.1 2.9,-0.4 4.1,-0.8 1.2,-0.4 2.5,-1 4,-1.8 h 2.3 v 5.2 c 2.4,-1.9 4.2,-3.1 5.5,-3.8 2,-1 4,-1.5 5.8,-1.5 1.3,0 2.5,0.2 3.7,0.7 1.2,0.5 2.2,1 2.9,1.7 0.7,0.7 1.4,1.6 1.9,2.8 2.2,-1.9 4.2,-3.3 6,-4 1.9,-0.8 3.7,-1.2 5.6,-1.2 1.8,0 3.4,0.4 4.8,1.1 1.4,0.8 2.4,1.7 3,2.8 0.6,1.1 0.9,2.8 0.9,5 v 14.4 c 0,1.5 0,2.4 0.1,2.6 0.1,0.4 0.3,0.8 0.7,1.1 0.3,0.4 0.7,0.6 1.2,0.7 0.4,0.1 1.2,0.2 2.4,0.2 h 1 v 2.6 h -15.5 v -2.6 c 1.8,0 2.9,-0.1 3.5,-0.4 0.5,-0.2 0.9,-0.6 1.2,-1.2 0.3,-0.6 0.4,-1.6 0.4,-3.2 v -13.7 c 0,-1.7 -0.2,-2.9 -0.5,-3.6 -0.3,-0.7 -0.9,-1.2 -1.7,-1.7 -0.8,-0.4 -1.8,-0.7 -3,-0.7 -1.5,0 -3,0.4 -4.6,1.2 -2.2,1.1 -3.9,2.3 -5.1,3.6 v 14.8 c 0,1.4 0.1,2.4 0.2,2.8 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.5 1.1,0.6 0.4,0.1 1.5,0.2 3.1,0.2 v 2.6 h -15.3 v -2.6 h 0.9 c 1.2,0 2.1,-0.1 2.6,-0.4 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.2,-0.5 0.3,-1.5 0.3,-2.9 v -13.2 c 0,-1.9 -0.2,-3.3 -0.5,-3.9 -0.3,-0.7 -0.9,-1.3 -1.7,-1.7 -0.8,-0.5 -1.8,-0.7 -3,-0.7 -1.3,0 -2.7,0.3 -4.1,1 -2,1 -3.9,2.2 -5.6,3.8 v 15.9 c 0,1 0.1,1.6 0.4,2.1 0.3,0.4 0.7,0.8 1.2,1.1 0.6,0.3 1.3,0.4 2.3,0.4 h 1.1 v 2.6 h -15.6 v -2.6 h 0.8 c 1.4,0 2.4,-0.1 2.8,-0.3 0.7,-0.3 1.1,-0.8 1.4,-1.5 0.2,-0.4 0.2,-1.3 0.2,-2.9 v -18.1 h -5.1 z"
|
|
||||||
id="path828"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#ffffff;fill-opacity:1" /><path
|
|
||||||
d="m 366.4,213.7 v -2.6 c 1.7,-0.2 3.2,-0.5 4.3,-0.9 1.2,-0.4 2.5,-1 4,-1.7 h 2.3 v 24.9 c 0,0.9 0.1,1.5 0.4,2 0.2,0.4 0.6,0.8 1,0.9 0.4,0.2 1.3,0.3 2.4,0.3 h 1.5 v 2.6 h -15.9 v -2.6 h 1.3 c 1.4,0 2.3,-0.1 2.8,-0.4 0.5,-0.2 0.8,-0.6 1,-1.1 0.2,-0.5 0.3,-1.5 0.3,-3.2 v -18.3 h -5.4 z m 7.9,-19.2 c 1,0 1.8,0.3 2.5,1 0.7,0.7 1.1,1.5 1.1,2.5 0,1 -0.4,1.8 -1.1,2.5 -0.7,0.7 -1.6,1.1 -2.5,1.1 -1,0 -1.8,-0.4 -2.5,-1.1 -0.7,-0.7 -1.1,-1.6 -1.1,-2.5 0,-1 0.4,-1.8 1.1,-2.5 0.6,-0.6 1.5,-1 2.5,-1 z"
|
|
||||||
id="path830"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#ffffff;fill-opacity:1" /><path
|
|
||||||
d="m 413.1,230.6 2,1.6 c -3.9,5.2 -8.6,7.8 -14,7.8 -4.2,0 -7.8,-1.5 -10.7,-4.5 -2.9,-3 -4.4,-6.8 -4.4,-11.3 0,-3 0.7,-5.7 2,-8.1 1.3,-2.4 3.2,-4.2 5.6,-5.6 2.4,-1.3 5.2,-2 8.3,-2 3.6,0 6.5,0.9 8.9,2.6 2.4,1.7 3.6,3.5 3.6,5.3 0,1 -0.3,1.7 -0.8,2.2 -0.5,0.5 -1.2,0.8 -1.9,0.8 -0.4,0 -0.7,-0.1 -1.1,-0.3 -0.4,-0.2 -0.7,-0.5 -1.1,-0.9 -0.2,-0.2 -0.5,-0.8 -0.9,-1.7 -0.6,-1.2 -1,-2 -1.3,-2.4 -0.6,-0.8 -1.4,-1.5 -2.4,-2 -0.9,-0.5 -2,-0.7 -3.1,-0.7 -1.8,0 -3.4,0.5 -4.9,1.5 -1.5,1 -2.7,2.4 -3.6,4.3 -0.9,1.9 -1.3,4.2 -1.3,6.8 0,4.1 1.1,7.3 3.3,9.7 1.9,2.1 4.1,3.1 6.7,3.1 1.2,0 2.4,-0.2 3.6,-0.6 1.2,-0.4 2.4,-1 3.5,-1.8 0.9,-0.6 2.2,-1.8 4,-3.8 z"
|
|
||||||
id="path832"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#ffffff;fill-opacity:1" /><path
|
|
||||||
d="m 418.7,213.7 v -2.6 c 1.5,-0.1 2.8,-0.4 4,-0.8 1.2,-0.4 2.5,-1 4,-1.9 h 2.3 v 5.9 c 1.5,-1.8 3.2,-3.2 5.1,-4.3 1.9,-1.1 3.7,-1.6 5.2,-1.6 1.5,0 2.7,0.4 3.6,1.1 0.9,0.7 1.3,1.6 1.3,2.6 0,0.7 -0.3,1.4 -0.9,2 -0.6,0.6 -1.3,0.9 -2.1,0.9 -0.4,0 -0.7,-0.1 -1,-0.2 -0.3,-0.1 -0.7,-0.3 -1.2,-0.7 -1.1,-0.7 -2.1,-1.1 -2.9,-1.1 -1,0 -2.2,0.4 -3.4,1.3 -1.6,1.1 -2.8,2.2 -3.7,3.3 V 232 c 0,1.2 0.1,2.1 0.2,2.5 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.6 1.1,0.7 0.5,0.1 1.3,0.2 2.4,0.2 h 1 v 2.6 h -16 v -2.6 h 1.3 c 1.3,0 2.1,-0.1 2.5,-0.3 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.3,-0.5 0.4,-1.5 0.4,-3 v -18.3 h -5.1 z"
|
|
||||||
id="path834"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#ffffff;fill-opacity:1" /><path
|
|
||||||
d="m 462.8,208.5 c 3,0 5.7,0.6 7.9,1.9 2.2,1.3 4,3.1 5.3,5.5 1.3,2.4 1.9,5.2 1.9,8.3 0,3.1 -0.7,5.9 -2,8.3 -1.3,2.4 -3.1,4.3 -5.4,5.5 -2.3,1.3 -5,1.9 -8.1,1.9 -5,0 -8.8,-1.6 -11.3,-4.7 -2.5,-3.1 -3.8,-6.8 -3.8,-11 0,-3.1 0.7,-5.8 2,-8.2 1.3,-2.4 3.1,-4.2 5.5,-5.6 2.4,-1.2 5.1,-1.9 8,-1.9 z m -0.2,3 c -2.4,0 -4.4,0.9 -6,2.8 -2.1,2.3 -3.1,5.7 -3.1,10.1 0,4.3 0.9,7.5 2.6,9.7 1.6,2 3.8,3 6.5,3 1.8,0 3.3,-0.5 4.7,-1.4 1.4,-0.9 2.5,-2.4 3.3,-4.5 0.8,-2 1.3,-4.4 1.3,-7.2 0,-2.7 -0.5,-5.1 -1.4,-7.2 -0.7,-1.7 -1.8,-3 -3.2,-4 -1.3,-0.8 -2.9,-1.3 -4.7,-1.3 z"
|
|
||||||
id="path836"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#ffffff;fill-opacity:1" /></g><g
|
|
||||||
id="g3"
|
|
||||||
transform="translate(-178,-172.8)"><path
|
|
||||||
d="m 306.8,213.8 v -2.6 c 1.6,-0.1 2.9,-0.4 4.1,-0.8 1.2,-0.4 2.5,-1 4,-1.8 h 2.3 v 5.2 c 2.4,-1.9 4.2,-3.1 5.5,-3.8 2,-1 4,-1.5 5.8,-1.5 1.3,0 2.5,0.2 3.7,0.7 1.2,0.5 2.2,1 2.9,1.7 0.7,0.7 1.4,1.6 1.9,2.8 2.2,-1.9 4.2,-3.3 6,-4 1.9,-0.8 3.7,-1.2 5.6,-1.2 1.8,0 3.4,0.4 4.8,1.1 1.4,0.8 2.4,1.7 3,2.8 0.6,1.1 0.9,2.8 0.9,5 v 14.4 c 0,1.5 0,2.4 0.1,2.6 0.1,0.4 0.3,0.8 0.7,1.1 0.3,0.4 0.7,0.6 1.2,0.7 0.4,0.1 1.2,0.2 2.4,0.2 h 1 v 2.6 h -15.5 v -2.6 c 1.8,0 2.9,-0.1 3.5,-0.4 0.5,-0.2 0.9,-0.6 1.2,-1.2 0.3,-0.6 0.4,-1.6 0.4,-3.2 v -13.7 c 0,-1.7 -0.2,-2.9 -0.5,-3.6 -0.3,-0.7 -0.9,-1.2 -1.7,-1.7 -0.8,-0.4 -1.8,-0.7 -3,-0.7 -1.5,0 -3,0.4 -4.6,1.2 -2.2,1.1 -3.9,2.3 -5.1,3.6 v 14.8 c 0,1.4 0.1,2.4 0.2,2.8 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.5 1.1,0.6 0.4,0.1 1.5,0.2 3.1,0.2 v 2.6 h -15.3 v -2.6 h 0.9 c 1.2,0 2.1,-0.1 2.6,-0.4 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.2,-0.5 0.3,-1.5 0.3,-2.9 v -13.2 c 0,-1.9 -0.2,-3.3 -0.5,-3.9 -0.3,-0.7 -0.9,-1.3 -1.7,-1.7 -0.8,-0.5 -1.8,-0.7 -3,-0.7 -1.3,0 -2.7,0.3 -4.1,1 -2,1 -3.9,2.2 -5.6,3.8 v 15.9 c 0,1 0.1,1.6 0.4,2.1 0.3,0.4 0.7,0.8 1.2,1.1 0.6,0.3 1.3,0.4 2.3,0.4 h 1.1 v 2.6 h -15.6 v -2.6 h 0.8 c 1.4,0 2.4,-0.1 2.8,-0.3 0.7,-0.3 1.1,-0.8 1.4,-1.5 0.2,-0.4 0.2,-1.3 0.2,-2.9 v -18.1 h -5.1 z"
|
|
||||||
id="path5"
|
|
||||||
inkscape:connector-curvature="0" /><path
|
|
||||||
d="m 366.4,213.7 v -2.6 c 1.7,-0.2 3.2,-0.5 4.3,-0.9 1.2,-0.4 2.5,-1 4,-1.7 h 2.3 v 24.9 c 0,0.9 0.1,1.5 0.4,2 0.2,0.4 0.6,0.8 1,0.9 0.4,0.2 1.3,0.3 2.4,0.3 h 1.5 v 2.6 h -15.9 v -2.6 h 1.3 c 1.4,0 2.3,-0.1 2.8,-0.4 0.5,-0.2 0.8,-0.6 1,-1.1 0.2,-0.5 0.3,-1.5 0.3,-3.2 v -18.3 h -5.4 z m 7.9,-19.2 c 1,0 1.8,0.3 2.5,1 0.7,0.7 1.1,1.5 1.1,2.5 0,1 -0.4,1.8 -1.1,2.5 -0.7,0.7 -1.6,1.1 -2.5,1.1 -1,0 -1.8,-0.4 -2.5,-1.1 -0.7,-0.7 -1.1,-1.6 -1.1,-2.5 0,-1 0.4,-1.8 1.1,-2.5 0.6,-0.6 1.5,-1 2.5,-1 z"
|
|
||||||
id="path7"
|
|
||||||
inkscape:connector-curvature="0" /><path
|
|
||||||
d="m 413.1,230.6 2,1.6 c -3.9,5.2 -8.6,7.8 -14,7.8 -4.2,0 -7.8,-1.5 -10.7,-4.5 -2.9,-3 -4.4,-6.8 -4.4,-11.3 0,-3 0.7,-5.7 2,-8.1 1.3,-2.4 3.2,-4.2 5.6,-5.6 2.4,-1.3 5.2,-2 8.3,-2 3.6,0 6.5,0.9 8.9,2.6 2.4,1.7 3.6,3.5 3.6,5.3 0,1 -0.3,1.7 -0.8,2.2 -0.5,0.5 -1.2,0.8 -1.9,0.8 -0.4,0 -0.7,-0.1 -1.1,-0.3 -0.4,-0.2 -0.7,-0.5 -1.1,-0.9 -0.2,-0.2 -0.5,-0.8 -0.9,-1.7 -0.6,-1.2 -1,-2 -1.3,-2.4 -0.6,-0.8 -1.4,-1.5 -2.4,-2 -0.9,-0.5 -2,-0.7 -3.1,-0.7 -1.8,0 -3.4,0.5 -4.9,1.5 -1.5,1 -2.7,2.4 -3.6,4.3 -0.9,1.9 -1.3,4.2 -1.3,6.8 0,4.1 1.1,7.3 3.3,9.7 1.9,2.1 4.1,3.1 6.7,3.1 1.2,0 2.4,-0.2 3.6,-0.6 1.2,-0.4 2.4,-1 3.5,-1.8 0.9,-0.6 2.2,-1.8 4,-3.8 z"
|
|
||||||
id="path9"
|
|
||||||
inkscape:connector-curvature="0" /><path
|
|
||||||
d="m 418.7,213.7 v -2.6 c 1.5,-0.1 2.8,-0.4 4,-0.8 1.2,-0.4 2.5,-1 4,-1.9 h 2.3 v 5.9 c 1.5,-1.8 3.2,-3.2 5.1,-4.3 1.9,-1.1 3.7,-1.6 5.2,-1.6 1.5,0 2.7,0.4 3.6,1.1 0.9,0.7 1.3,1.6 1.3,2.6 0,0.7 -0.3,1.4 -0.9,2 -0.6,0.6 -1.3,0.9 -2.1,0.9 -0.4,0 -0.7,-0.1 -1,-0.2 -0.3,-0.1 -0.7,-0.3 -1.2,-0.7 -1.1,-0.7 -2.1,-1.1 -2.9,-1.1 -1,0 -2.2,0.4 -3.4,1.3 -1.6,1.1 -2.8,2.2 -3.7,3.3 V 232 c 0,1.2 0.1,2.1 0.2,2.5 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.6 1.1,0.7 0.5,0.1 1.3,0.2 2.4,0.2 h 1 v 2.6 h -16 v -2.6 h 1.3 c 1.3,0 2.1,-0.1 2.5,-0.3 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.3,-0.5 0.4,-1.5 0.4,-3 v -18.3 h -5.1 z"
|
|
||||||
id="path11"
|
|
||||||
inkscape:connector-curvature="0" /><path
|
|
||||||
d="m 462.8,208.5 c 3,0 5.7,0.6 7.9,1.9 2.2,1.3 4,3.1 5.3,5.5 1.3,2.4 1.9,5.2 1.9,8.3 0,3.1 -0.7,5.9 -2,8.3 -1.3,2.4 -3.1,4.3 -5.4,5.5 -2.3,1.3 -5,1.9 -8.1,1.9 -5,0 -8.8,-1.6 -11.3,-4.7 -2.5,-3.1 -3.8,-6.8 -3.8,-11 0,-3.1 0.7,-5.8 2,-8.2 1.3,-2.4 3.1,-4.2 5.5,-5.6 2.4,-1.2 5.1,-1.9 8,-1.9 z m -0.2,3 c -2.4,0 -4.4,0.9 -6,2.8 -2.1,2.3 -3.1,5.7 -3.1,10.1 0,4.3 0.9,7.5 2.6,9.7 1.6,2 3.8,3 6.5,3 1.8,0 3.3,-0.5 4.7,-1.4 1.4,-0.9 2.5,-2.4 3.3,-4.5 0.8,-2 1.3,-4.4 1.3,-7.2 0,-2.7 -0.5,-5.1 -1.4,-7.2 -0.7,-1.7 -1.8,-3 -3.2,-4 -1.3,-0.8 -2.9,-1.3 -4.7,-1.3 z"
|
|
||||||
id="path13"
|
|
||||||
inkscape:connector-curvature="0" /></g><path
|
|
||||||
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 h -0.2 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 h -0.7 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 h 0.8 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
|
|
||||||
id="path15"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#2e3192" /><path
|
|
||||||
style="fill:#ffffff;stroke-width:0.0757324"
|
|
||||||
d="m 30.026506,86.559353 c -1.017302,-0.241662 -1.787869,-0.887419 -2.143612,-1.796406 -0.545654,-1.394246 -0.158934,-4.812615 1.126179,-9.954732 1.255925,-5.025324 2.459082,-9.096362 5.109736,-17.289458 0.344312,-1.064257 1.654133,-5.2136 1.888607,-5.982859 0.296596,-0.97307 0.598551,-2.708021 0.79743,-4.581811 0.108312,-1.020494 0.246431,-2.186451 0.306932,-2.591018 0.0605,-0.404565 0.178758,-1.341754 0.262796,-2.082641 0.224837,-1.982189 0.649291,-5.218012 0.916787,-6.98913 0.444542,-2.943359 0.753682,-4.198397 1.354756,-5.499991 0.686842,-1.487323 1.771061,-2.655188 2.805126,-3.021538 0.542395,-0.19216 1.381388,-0.270583 1.982594,-0.185316 1.252526,0.17764 1.883508,0.754167 2.211742,2.020866 0.313761,1.21084 -0.05565,3.930951 -0.877141,6.458782 -1.290698,3.971623 -2.036395,5.990995 -2.986916,8.088674 -1.185138,2.61545 -2.712212,6.873258 -2.939609,8.196258 -0.49042,2.853282 0.04972,5.146283 1.578225,6.6999 0.913915,0.928929 2.023939,1.521458 3.413442,1.82209 0.903748,0.195534 2.608483,0.179674 3.407958,-0.03171 1.383427,-0.365777 2.763884,-1.250325 4.377299,-2.804821 3.163126,-3.047616 5.113532,-6.222841 6.797438,-11.066108 0.353971,-1.018094 0.493359,-1.574562 0.749316,-2.991429 0.271014,-1.500218 1.040858,-5.574621 1.51657,-8.026458 0.08082,-0.416528 0.218253,-1.149239 0.305416,-1.628246 0.472088,-2.594388 1.148516,-4.178722 2.330295,-5.458032 0.763841,-0.826879 1.674493,-1.206419 2.894632,-1.206419 1.24359,0 2.138991,0.401576 2.574266,1.154526 0.974305,1.685378 0.683954,4.053139 -1.163626,9.489195 -0.954432,2.808181 -2.572717,6.998752 -3.493593,9.046702 -0.971745,2.161077 -2.201912,5.041664 -2.441809,5.717796 l -0.268706,0.757324 0.09021,1.120423 c 0.212423,2.638199 0.889316,4.086035 2.469149,5.281365 0.932959,0.705895 1.786459,0.982601 3.026274,0.981126 2.426542,-0.0029 4.480731,-1.028876 5.685658,-2.839769 0.811784,-1.220036 1.58443,-3.158397 2.044887,-5.130071 l 0.207813,-0.889855 h 0.356374 0.356373 l 0.04799,0.892492 c 0.0554,1.030319 -0.04881,3.015268 -0.219241,4.175846 -0.345822,2.354993 -1.040859,4.427262 -1.983165,5.91286 -0.701565,1.106055 -1.958204,2.491062 -2.717404,2.994989 -1.555814,1.032691 -4.187858,1.499135 -6.161832,1.091984 -0.603718,-0.124523 -1.72865,-0.689523 -2.178956,-1.094387 -1.477985,-1.328835 -2.187139,-3.341642 -2.360358,-6.699454 -0.08196,-1.588814 0.0522,-3.504923 0.298559,-4.263967 0.05681,-0.175039 0.04587,-0.208265 -0.06857,-0.208265 -0.09667,0 -0.197671,0.148268 -0.348229,0.511194 -0.711765,1.715746 -1.965261,3.867832 -3.142896,5.395934 -0.680786,0.883388 -2.612844,2.822501 -3.483678,3.496397 -2.517073,1.947843 -5.073167,2.951502 -8.060525,3.164993 -1.592379,0.1138 -2.868371,-0.07567 -4.016971,-0.596469 -1.69649,-0.769225 -3.109446,-2.469115 -3.819014,-4.594555 -0.614034,-1.839276 -0.863382,-4.754214 -0.580679,-6.788275 0.05951,-0.428202 0.126068,-0.957467 0.147897,-1.176145 l 0.03969,-0.397595 H 37.651633 37.254872 L 36.96284,53.90253 c -0.705326,1.783387 -1.458627,4.293583 -2.085205,6.948448 -1.027173,4.352223 -1.56307,7.486558 -2.197428,12.852248 -0.310323,2.624858 -0.310577,2.629265 -0.189513,3.294359 0.13956,0.766706 0.417018,1.85334 0.68249,2.672894 0.306093,0.944956 0.565598,2.296449 0.565598,2.945615 0,1.819491 -0.751236,3.258298 -2.006909,3.84374 -0.402074,0.187462 -1.15114,0.231172 -1.705369,0.09951 z"
|
|
||||||
id="path218" /></svg>
|
|
Before Width: | Height: | Size: 16 KiB |
|
@ -1,59 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
id="Layer_1"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
viewBox="0 0 103.2 103.2"
|
|
||||||
enable-background="new 0 0 960 560"
|
|
||||||
xml:space="preserve"
|
|
||||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
|
||||||
sodipodi:docname="micro-logo-mark.svg"
|
|
||||||
width="103.2"
|
|
||||||
height="103.2"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
|
||||||
id="metadata9"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs7" /><sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1080"
|
|
||||||
id="namedview5"
|
|
||||||
showgrid="false"
|
|
||||||
fit-margin-top="0"
|
|
||||||
fit-margin-left="0"
|
|
||||||
fit-margin-right="0"
|
|
||||||
fit-margin-bottom="0"
|
|
||||||
inkscape:zoom="5.405335"
|
|
||||||
inkscape:cx="75.573484"
|
|
||||||
inkscape:cy="51.153166"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="Layer_1"
|
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1" /><path
|
|
||||||
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
|
|
||||||
id="path3"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#2e3192" /><path
|
|
||||||
style="fill:#ffffff;stroke-width:0.185002"
|
|
||||||
d="m 29.320064,86.164872 c -1.277771,-0.647664 -1.573829,-1.327981 -1.549788,-3.561297 0.04016,-3.730697 1.622887,-10.030031 5.903272,-23.495306 2.770635,-8.715885 2.799071,-8.822813 3.148729,-11.840154 0.585284,-5.050637 1.565844,-12.45598 1.8369,-13.872547 0.43516,-2.274196 0.976755,-3.690519 1.880879,-4.918684 0.974445,-1.323691 1.896478,-1.826405 3.360953,-1.832474 3.009215,-0.01247 3.55713,2.574946 1.786201,8.434969 -0.742771,2.45784 -2.2493,6.487571 -3.407575,9.114735 -0.420971,0.954834 -1.151241,2.827983 -1.622823,4.162554 -0.839682,2.376289 -0.857669,2.47434 -0.869358,4.739023 -0.01095,2.122185 0.02796,2.3976 0.472736,3.346042 0.91751,1.956495 2.602228,3.131322 5.078862,3.541714 2.587757,0.428804 4.551892,-0.347899 7.187533,-2.842264 2.232774,-2.113092 3.746907,-4.117682 4.998184,-6.617188 1.816108,-3.627792 2.213624,-4.978174 3.527565,-11.983266 0.66466,-3.543546 1.376157,-6.951356 1.581104,-7.57291 0.970636,-2.943689 2.922262,-4.567831 5.096985,-4.241711 1.740397,0.260989 2.500104,1.361773 2.494406,3.614287 -0.0068,2.696563 -2.48184,9.966491 -6.424307,18.870246 l -1.269708,2.867537 0.02005,1.757523 c 0.01504,1.318294 0.119434,2.015481 0.417735,2.789716 1.028756,2.67011 3.517063,4.054736 6.342356,3.529224 3.19144,-0.593617 4.98902,-2.612828 6.217715,-6.984325 0.403553,-1.435775 0.552101,-1.739647 0.850428,-1.739647 0.34646,0 0.356492,0.101757 0.241656,2.451282 -0.238951,4.888854 -1.330826,7.853563 -3.80789,10.339358 -1.255532,1.259957 -1.547319,1.456015 -2.694109,1.81022 -1.395674,0.431082 -3.784736,0.537505 -4.865716,0.216749 -1.759682,-0.522141 -3.031085,-2.027386 -3.686869,-4.364972 -0.336042,-1.197843 -0.516218,-5.455318 -0.283812,-6.706338 0.266094,-1.432359 -0.105859,-1.235144 -0.879069,0.466093 -1.724383,3.794037 -4.750586,7.236231 -8.063683,9.172148 -2.368072,1.383716 -5.903865,2.143782 -8.230062,1.769159 -2.672688,-0.430424 -4.588062,-2.213422 -5.66376,-5.272324 -0.491128,-1.396592 -0.514658,-1.618704 -0.512739,-4.840059 0.0018,-3.093063 -0.02515,-3.376294 -0.321772,-3.376294 -0.414677,0 -0.706335,0.582138 -1.434591,2.863386 -1.443227,4.52088 -2.73082,10.895957 -3.516703,17.411762 l -0.381426,3.162426 0.469219,1.740138 c 0.927877,3.441104 1.066474,4.326417 0.841521,5.375336 -0.537458,2.506081 -2.272098,3.528416 -4.269226,2.516133 z"
|
|
||||||
id="path210" /></svg>
|
|
Before Width: | Height: | Size: 6.2 KiB |
|
@ -2,6 +2,13 @@
|
||||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="Layer_1"
|
id="Layer_1"
|
||||||
x="0px"
|
x="0px"
|
||||||
|
@ -9,20 +16,13 @@
|
||||||
viewBox="0 0 299.89999 103.2"
|
viewBox="0 0 299.89999 103.2"
|
||||||
enable-background="new 0 0 960 560"
|
enable-background="new 0 0 960 560"
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
inkscape:version="0.91 r13725"
|
||||||
sodipodi:docname="micro-logo.svg"
|
sodipodi:docname="micro-logo.svg"
|
||||||
width="299.89999"
|
width="299.89999"
|
||||||
height="103.2"
|
height="103.2"><metadata
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
|
||||||
id="metadata21"><rdf:RDF><cc:Work
|
id="metadata21"><rdf:RDF><cc:Work
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
id="defs19" /><sodipodi:namedview
|
id="defs19" /><sodipodi:namedview
|
||||||
pagecolor="#ffffff"
|
pagecolor="#ffffff"
|
||||||
bordercolor="#666666"
|
bordercolor="#666666"
|
||||||
|
@ -32,24 +32,21 @@
|
||||||
guidetolerance="10"
|
guidetolerance="10"
|
||||||
inkscape:pageopacity="0"
|
inkscape:pageopacity="0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:window-width="1920"
|
inkscape:window-width="1237"
|
||||||
inkscape:window-height="1080"
|
inkscape:window-height="867"
|
||||||
id="namedview17"
|
id="namedview17"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
fit-margin-top="0"
|
fit-margin-top="0"
|
||||||
fit-margin-left="0"
|
fit-margin-left="0"
|
||||||
fit-margin-right="0"
|
fit-margin-right="0"
|
||||||
fit-margin-bottom="0"
|
fit-margin-bottom="0"
|
||||||
inkscape:zoom="16.645603"
|
inkscape:zoom="1.1416667"
|
||||||
inkscape:cx="65.092264"
|
inkscape:cx="75.655934"
|
||||||
inkscape:cy="49.051992"
|
inkscape:cy="-4"
|
||||||
inkscape:window-x="0"
|
inkscape:window-x="1097"
|
||||||
inkscape:window-y="0"
|
inkscape:window-y="185"
|
||||||
inkscape:window-maximized="0"
|
inkscape:window-maximized="0"
|
||||||
inkscape:current-layer="Layer_1"
|
inkscape:current-layer="Layer_1" /><g
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1" /><g
|
|
||||||
id="g3"
|
id="g3"
|
||||||
transform="translate(-178,-172.8)"><path
|
transform="translate(-178,-172.8)"><path
|
||||||
d="m 306.8,213.8 0,-2.6 c 1.6,-0.1 2.9,-0.4 4.1,-0.8 1.2,-0.4 2.5,-1 4,-1.8 l 2.3,0 0,5.2 c 2.4,-1.9 4.2,-3.1 5.5,-3.8 2,-1 4,-1.5 5.8,-1.5 1.3,0 2.5,0.2 3.7,0.7 1.2,0.5 2.2,1 2.9,1.7 0.7,0.7 1.4,1.6 1.9,2.8 2.2,-1.9 4.2,-3.3 6,-4 1.9,-0.8 3.7,-1.2 5.6,-1.2 1.8,0 3.4,0.4 4.8,1.1 1.4,0.8 2.4,1.7 3,2.8 0.6,1.1 0.9,2.8 0.9,5 l 0,14.4 c 0,1.5 0,2.4 0.1,2.6 0.1,0.4 0.3,0.8 0.7,1.1 0.3,0.4 0.7,0.6 1.2,0.7 0.4,0.1 1.2,0.2 2.4,0.2 l 1,0 0,2.6 -15.5,0 0,-2.6 c 1.8,0 2.9,-0.1 3.5,-0.4 0.5,-0.2 0.9,-0.6 1.2,-1.2 0.3,-0.6 0.4,-1.6 0.4,-3.2 l 0,-13.7 c 0,-1.7 -0.2,-2.9 -0.5,-3.6 -0.3,-0.7 -0.9,-1.2 -1.7,-1.7 -0.8,-0.4 -1.8,-0.7 -3,-0.7 -1.5,0 -3,0.4 -4.6,1.2 -2.2,1.1 -3.9,2.3 -5.1,3.6 l 0,14.8 c 0,1.4 0.1,2.4 0.2,2.8 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.5 1.1,0.6 0.4,0.1 1.5,0.2 3.1,0.2 l 0,2.6 -15.3,0 0,-2.6 0.9,0 c 1.2,0 2.1,-0.1 2.6,-0.4 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.2,-0.5 0.3,-1.5 0.3,-2.9 l 0,-13.2 c 0,-1.9 -0.2,-3.3 -0.5,-3.9 -0.3,-0.7 -0.9,-1.3 -1.7,-1.7 -0.8,-0.5 -1.8,-0.7 -3,-0.7 -1.3,0 -2.7,0.3 -4.1,1 -2,1 -3.9,2.2 -5.6,3.8 l 0,15.9 c 0,1 0.1,1.6 0.4,2.1 0.3,0.4 0.7,0.8 1.2,1.1 0.6,0.3 1.3,0.4 2.3,0.4 l 1.1,0 0,2.6 -15.6,0 0,-2.6 0.8,0 c 1.4,0 2.4,-0.1 2.8,-0.3 0.7,-0.3 1.1,-0.8 1.4,-1.5 0.2,-0.4 0.2,-1.3 0.2,-2.9 l 0,-18.1 -5.1,0 z"
|
d="m 306.8,213.8 0,-2.6 c 1.6,-0.1 2.9,-0.4 4.1,-0.8 1.2,-0.4 2.5,-1 4,-1.8 l 2.3,0 0,5.2 c 2.4,-1.9 4.2,-3.1 5.5,-3.8 2,-1 4,-1.5 5.8,-1.5 1.3,0 2.5,0.2 3.7,0.7 1.2,0.5 2.2,1 2.9,1.7 0.7,0.7 1.4,1.6 1.9,2.8 2.2,-1.9 4.2,-3.3 6,-4 1.9,-0.8 3.7,-1.2 5.6,-1.2 1.8,0 3.4,0.4 4.8,1.1 1.4,0.8 2.4,1.7 3,2.8 0.6,1.1 0.9,2.8 0.9,5 l 0,14.4 c 0,1.5 0,2.4 0.1,2.6 0.1,0.4 0.3,0.8 0.7,1.1 0.3,0.4 0.7,0.6 1.2,0.7 0.4,0.1 1.2,0.2 2.4,0.2 l 1,0 0,2.6 -15.5,0 0,-2.6 c 1.8,0 2.9,-0.1 3.5,-0.4 0.5,-0.2 0.9,-0.6 1.2,-1.2 0.3,-0.6 0.4,-1.6 0.4,-3.2 l 0,-13.7 c 0,-1.7 -0.2,-2.9 -0.5,-3.6 -0.3,-0.7 -0.9,-1.2 -1.7,-1.7 -0.8,-0.4 -1.8,-0.7 -3,-0.7 -1.5,0 -3,0.4 -4.6,1.2 -2.2,1.1 -3.9,2.3 -5.1,3.6 l 0,14.8 c 0,1.4 0.1,2.4 0.2,2.8 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.5 1.1,0.6 0.4,0.1 1.5,0.2 3.1,0.2 l 0,2.6 -15.3,0 0,-2.6 0.9,0 c 1.2,0 2.1,-0.1 2.6,-0.4 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.2,-0.5 0.3,-1.5 0.3,-2.9 l 0,-13.2 c 0,-1.9 -0.2,-3.3 -0.5,-3.9 -0.3,-0.7 -0.9,-1.3 -1.7,-1.7 -0.8,-0.5 -1.8,-0.7 -3,-0.7 -1.3,0 -2.7,0.3 -4.1,1 -2,1 -3.9,2.2 -5.6,3.8 l 0,15.9 c 0,1 0.1,1.6 0.4,2.1 0.3,0.4 0.7,0.8 1.2,1.1 0.6,0.3 1.3,0.4 2.3,0.4 l 1.1,0 0,2.6 -15.6,0 0,-2.6 0.8,0 c 1.4,0 2.4,-0.1 2.8,-0.3 0.7,-0.3 1.1,-0.8 1.4,-1.5 0.2,-0.4 0.2,-1.3 0.2,-2.9 l 0,-18.1 -5.1,0 z"
|
||||||
|
@ -70,7 +67,4 @@
|
||||||
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
|
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
|
||||||
id="path15"
|
id="path15"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
style="fill:#2e3192" /><path
|
style="fill:#2e3192" /></svg>
|
||||||
style="fill:#ffffff;stroke-width:0.0600759"
|
|
||||||
d="m 30.192709,86.597991 c -0.530828,-0.09608 -1.19875,-0.411872 -1.578921,-0.746511 -0.792953,-0.697985 -1.054327,-1.680313 -0.947823,-3.562219 0.16271,-2.875042 0.852662,-6.034057 2.963728,-13.569713 0.66017,-2.356543 0.955814,-3.307037 3.762987,-12.097989 1.219825,-3.820007 1.435496,-4.505244 1.616654,-5.136492 0.306236,-1.067081 0.590331,-2.663175 0.753866,-4.235353 0.08592,-0.826044 0.236455,-2.096649 0.334514,-2.823568 0.09806,-0.726919 0.246246,-1.916422 0.329306,-2.643341 0.08306,-0.726918 0.231698,-1.902905 0.330307,-2.613302 0.09861,-0.710398 0.231242,-1.724179 0.294741,-2.252848 0.19473,-1.621264 0.604712,-4.037809 0.845956,-4.986301 0.495326,-1.947452 1.158621,-3.216325 2.26111,-4.325467 0.731983,-0.736399 1.547763,-1.051329 2.723316,-1.051329 1.344787,0 2.103359,0.409522 2.539237,1.370828 0.373167,0.823003 0.432731,1.702332 0.227502,3.358553 -0.206897,1.669687 -0.429401,2.498899 -1.62432,6.053417 -0.891865,2.653022 -1.418886,4.025585 -2.237847,5.828196 -0.890733,1.960586 -1.401439,3.281416 -2.291175,5.925621 -0.696894,2.071095 -0.858755,3.003396 -0.79649,4.587665 0.05016,1.276299 0.270881,2.168068 0.761945,3.078469 1.114561,2.066325 3.341124,3.259541 6.082361,3.259541 0.831865,0 1.52957,-0.113832 2.245267,-0.366322 1.037155,-0.365895 1.69838,-0.767468 2.829986,-1.718697 2.058613,-1.730473 4.031033,-4.098263 5.356083,-6.429706 1.132231,-1.992175 2.742129,-5.986041 2.978686,-7.389579 0.126006,-0.747618 0.37151,-2.073261 0.753923,-4.070941 0.459374,-2.399719 0.965049,-5.073707 1.26106,-6.668427 0.439666,-2.368642 0.948255,-3.731056 1.831386,-4.905927 1.000947,-1.33161 1.919678,-1.818989 3.424905,-1.816884 1.371199,0.0019 2.259901,0.453797 2.692584,1.369104 0.199937,0.42295 0.37898,1.160518 0.431897,1.779189 0.0423,0.494585 -0.08313,1.707742 -0.270194,2.613303 -0.520247,2.51845 -2.995194,9.527499 -4.836622,13.697311 -0.189691,0.429543 -0.709117,1.619046 -1.154281,2.64334 -0.445164,1.024295 -0.903857,2.078627 -1.019317,2.342962 -0.593057,1.357747 -0.644155,1.607255 -0.563046,2.7493 0.142046,2.000035 0.604952,3.420811 1.436759,4.409774 0.719848,0.85585 1.902762,1.62255 2.859809,1.853569 0.533147,0.128695 1.669602,0.128252 2.472607,-9.67e-4 1.437635,-0.231339 2.769133,-0.900566 3.72751,-1.873493 1.098243,-1.114915 2.227996,-3.662559 2.785802,-6.282105 l 0.13752,-0.645816 h 0.37414 0.37414 l 0.04419,0.94284 c 0.124949,2.666054 -0.382363,6.016009 -1.237138,8.16926 -0.848692,2.137927 -2.617365,4.354096 -4.156972,5.208738 -1.58257,0.878493 -4.420415,1.19721 -6.111929,0.68643 -0.649563,-0.196146 -1.47209,-0.685817 -1.961392,-1.167665 -1.354216,-1.333585 -1.999054,-3.254244 -2.18916,-6.52045 -0.03525,-0.60571 -0.04689,-1.38515 -0.02584,-1.732089 0.04435,-0.731258 0.257009,-2.357205 0.335205,-2.562875 0.04613,-0.121335 0.03516,-0.140427 -0.08025,-0.139702 -0.11259,7.09e-4 -0.171074,0.09313 -0.370649,0.58574 -0.571777,1.411317 -1.625409,3.288777 -2.58713,4.609988 -2.555402,3.510606 -5.935984,6.014779 -9.311242,6.897323 -1.386313,0.362485 -1.927076,0.42829 -3.514441,0.427668 -1.398071,-5.41e-4 -1.500695,-0.0084 -2.047014,-0.157216 -1.248806,-0.340101 -2.244463,-0.904197 -3.05944,-1.733346 -1.343156,-1.366511 -2.129105,-3.116872 -2.494126,-5.554581 -0.150028,-1.001927 -0.191427,-3.616227 -0.06949,-4.388291 0.05195,-0.328906 0.113311,-0.84947 0.136367,-1.156809 l 0.04192,-0.558799 -0.380315,0.01812 -0.380315,0.01812 -0.231805,0.570721 c -1.478913,3.641182 -3.072314,10.383891 -3.918324,16.580955 -0.190557,1.395837 -0.701916,5.676121 -0.706953,5.917479 -0.0093,0.446744 0.454257,2.427922 0.818884,3.499628 0.121802,0.358001 0.382754,1.549663 0.538684,2.459961 0.04595,0.268246 -0.06655,1.468043 -0.178759,1.906478 -0.165253,0.645686 -0.477741,1.20884 -0.915337,1.649588 -0.463951,0.467293 -0.819805,0.689321 -1.309045,0.816755 -0.410787,0.106995 -0.564727,0.106887 -1.159735,-7.81e-4 z"
|
|
||||||
id="path240" /></svg>
|
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 7.7 KiB |
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ]; then
|
|
||||||
update-alternatives --install /usr/bin/editor editor /usr/bin/micro 40 \
|
|
||||||
--slave /usr/share/man/man1/editor.1 editor.1 \
|
|
||||||
/usr/share/man/man1/micro.1
|
|
||||||
fi
|
|
|
@ -1,7 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [ "$1" != "upgrade" ]; then
|
|
||||||
update-alternatives --remove editor /usr/bin/micro
|
|
||||||
fi
|
|
|
@ -14,8 +14,6 @@ of modern terminals. It comes as one single, batteries-included, static binary w
|
||||||
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
|
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
|
||||||
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
|
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
|
||||||
|
|
||||||
Use Ctrl-q to quit, Ctrl-s to save, and Ctrl-g to open the in-editor help menu.
|
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.PP
|
.PP
|
||||||
\-clean
|
\-clean
|
||||||
|
@ -53,7 +51,7 @@ Enable debug mode (enables logging to ./log.txt)
|
||||||
Show the version number and information
|
Show the version number and information
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
Micro's plugins can be managed at the command line with the following commands.
|
Micro's plugin's can be managed at the command line with the following commands.
|
||||||
.RS 4
|
.RS 4
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
|
@ -121,5 +119,5 @@ and to report any newly encountered bugs you may find. We strive to correct
|
||||||
bugs as swiftly as possible.
|
bugs as swiftly as possible.
|
||||||
|
|
||||||
.SH COPYRIGHT
|
.SH COPYRIGHT
|
||||||
Copyright \(co 2020 Zachary Yedidia, et al. MIT license.
|
Copyright \(co 2020 Zachary Yedidia, et al.
|
||||||
See \fBhttps://github.com/zyedidia/micro\fP for details.
|
See /usr/share/doc/micro/LICENSE and /usr/share/doc/micro/AUTHORS for more information.
|
||||||
|
|
|
@ -6,10 +6,10 @@ Comment=Edit text files in a terminal
|
||||||
|
|
||||||
Icon=micro
|
Icon=micro
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Utility;TextEditor;Development;
|
Categories=terminal;TextEditor;
|
||||||
Keywords=text;editor;syntax;terminal;
|
Keywords=text;editor;syntax;terminal;
|
||||||
|
|
||||||
Exec=micro %F
|
Exec=micro %U
|
||||||
StartupNotify=false
|
StartupNotify=false
|
||||||
Terminal=true
|
Terminal=true
|
||||||
MimeType=text/plain;text/x-chdr;text/x-csrc;text/x-c++hdr;text/x-c++src;text/x-java;text/x-dsrc;text/x-pascal;text/x-perl;text/x-python;application/x-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/xml;text/html;text/css;text/x-sql;text/x-diff;
|
MimeType=text/plain;text/x-chdr;text/x-csrc;text/x-c++hdr;text/x-c++src;text/x-java;text/x-dsrc;text/x-pascal;text/x-perl;text/x-python;application/x-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/xml;text/html;text/css;text/x-sql;text/x-diff;
|
||||||
|
|
|
@ -23,9 +23,12 @@ func shouldContinue() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
text = strings.TrimRight(text, "\r\n")
|
if len(text) <= 1 {
|
||||||
|
// default continue
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return len(text) == 0 || strings.ToLower(text)[0] == 'y'
|
return strings.ToLower(text)[0] == 'y'
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanConfig performs cleanup in the user's configuration directory
|
// CleanConfig performs cleanup in the user's configuration directory
|
||||||
|
@ -38,9 +41,6 @@ func CleanConfig() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Cleaning default settings")
|
|
||||||
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
|
||||||
|
|
||||||
// detect unused options
|
// detect unused options
|
||||||
var unusedOptions []string
|
var unusedOptions []string
|
||||||
defaultSettings := config.DefaultAllSettings()
|
defaultSettings := config.DefaultAllSettings()
|
||||||
|
@ -94,13 +94,14 @@ func CleanConfig() {
|
||||||
file, e := os.Open(fname)
|
file, e := os.Open(fname)
|
||||||
|
|
||||||
if e == nil {
|
if e == nil {
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
decoder := gob.NewDecoder(file)
|
decoder := gob.NewDecoder(file)
|
||||||
err = decoder.Decode(&buffer)
|
err = decoder.Decode(&buffer)
|
||||||
|
|
||||||
if err != nil && f.Name() != "history" {
|
if err != nil && f.Name() != "history" {
|
||||||
badFiles = append(badFiles, fname)
|
badFiles = append(badFiles, fname)
|
||||||
}
|
}
|
||||||
file.Close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,21 +111,15 @@ func CleanConfig() {
|
||||||
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
|
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
|
||||||
|
|
||||||
if shouldContinue() {
|
if shouldContinue() {
|
||||||
removed := 0
|
|
||||||
for _, f := range badFiles {
|
for _, f := range badFiles {
|
||||||
err := os.Remove(f)
|
err := os.Remove(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
removed++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if removed == 0 {
|
fmt.Println("Removed badly formatted files")
|
||||||
fmt.Println("Failed to remove files")
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Removed %d badly formatted files\n", removed)
|
|
||||||
}
|
|
||||||
fmt.Print("\n\n")
|
fmt.Print("\n\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
|
||||||
|
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
luar "layeh.com/gopher-luar"
|
luar "layeh.com/gopher-luar"
|
||||||
|
@ -48,18 +47,15 @@ func luaImportMicro() *lua.LTable {
|
||||||
ulua.L.SetField(pkg, "InfoBar", luar.New(ulua.L, action.GetInfoBar))
|
ulua.L.SetField(pkg, "InfoBar", luar.New(ulua.L, action.GetInfoBar))
|
||||||
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, log.Println))
|
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, log.Println))
|
||||||
ulua.L.SetField(pkg, "SetStatusInfoFn", luar.New(ulua.L, display.SetStatusInfoFnLua))
|
ulua.L.SetField(pkg, "SetStatusInfoFn", luar.New(ulua.L, display.SetStatusInfoFnLua))
|
||||||
ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() *action.BufPane {
|
ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() action.Pane {
|
||||||
return action.MainTab().CurPane()
|
return action.MainTab().CurPane()
|
||||||
}))
|
}))
|
||||||
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, action.MainTab))
|
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, func() *action.Tab {
|
||||||
|
return action.MainTab()
|
||||||
|
}))
|
||||||
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
|
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
|
||||||
return action.Tabs
|
return action.Tabs
|
||||||
}))
|
}))
|
||||||
ulua.L.SetField(pkg, "After", luar.New(ulua.L, func(t time.Duration, f func()) {
|
|
||||||
time.AfterFunc(t, func() {
|
|
||||||
timerChan <- f
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
|
|
||||||
return pkg
|
return pkg
|
||||||
}
|
}
|
||||||
|
@ -123,9 +119,6 @@ func luaImportMicroBuffer() *lua.LTable {
|
||||||
ulua.L.SetField(pkg, "Loc", luar.New(ulua.L, func(x, y int) buffer.Loc {
|
ulua.L.SetField(pkg, "Loc", luar.New(ulua.L, func(x, y int) buffer.Loc {
|
||||||
return buffer.Loc{x, y}
|
return buffer.Loc{x, y}
|
||||||
}))
|
}))
|
||||||
ulua.L.SetField(pkg, "SLoc", luar.New(ulua.L, func(line, row int) display.SLoc {
|
|
||||||
return display.SLoc{line, row}
|
|
||||||
}))
|
|
||||||
ulua.L.SetField(pkg, "BTDefault", luar.New(ulua.L, buffer.BTDefault.Kind))
|
ulua.L.SetField(pkg, "BTDefault", luar.New(ulua.L, buffer.BTDefault.Kind))
|
||||||
ulua.L.SetField(pkg, "BTHelp", luar.New(ulua.L, buffer.BTHelp.Kind))
|
ulua.L.SetField(pkg, "BTHelp", luar.New(ulua.L, buffer.BTHelp.Kind))
|
||||||
ulua.L.SetField(pkg, "BTLog", luar.New(ulua.L, buffer.BTLog.Kind))
|
ulua.L.SetField(pkg, "BTLog", luar.New(ulua.L, buffer.BTLog.Kind))
|
||||||
|
@ -152,10 +145,6 @@ func luaImportMicroUtil() *lua.LTable {
|
||||||
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
|
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
|
||||||
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
|
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
|
||||||
ulua.L.SetField(pkg, "String", luar.New(ulua.L, util.String))
|
ulua.L.SetField(pkg, "String", luar.New(ulua.L, util.String))
|
||||||
ulua.L.SetField(pkg, "Unzip", luar.New(ulua.L, util.Unzip))
|
|
||||||
ulua.L.SetField(pkg, "Version", luar.New(ulua.L, util.Version))
|
|
||||||
ulua.L.SetField(pkg, "SemVersion", luar.New(ulua.L, util.SemVersion))
|
|
||||||
ulua.L.SetField(pkg, "HttpRequest", luar.New(ulua.L, util.HttpRequest))
|
|
||||||
ulua.L.SetField(pkg, "CharacterCountInString", luar.New(ulua.L, util.CharacterCountInString))
|
ulua.L.SetField(pkg, "CharacterCountInString", luar.New(ulua.L, util.CharacterCountInString))
|
||||||
ulua.L.SetField(pkg, "RuneStr", luar.New(ulua.L, func(r rune) string {
|
ulua.L.SetField(pkg, "RuneStr", luar.New(ulua.L, func(r rune) string {
|
||||||
return string(r)
|
return string(r)
|
||||||
|
|
|
@ -3,17 +3,12 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
|
@ -21,29 +16,26 @@ import (
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
"github.com/zyedidia/micro/v2/internal/action"
|
"github.com/zyedidia/micro/v2/internal/action"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/shell"
|
"github.com/zyedidia/micro/v2/internal/shell"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// Event channel
|
||||||
|
events chan tcell.Event
|
||||||
|
autosave chan bool
|
||||||
|
|
||||||
// Command line flags
|
// Command line flags
|
||||||
flagVersion = flag.Bool("version", false, "Show the version number and information")
|
flagVersion = flag.Bool("version", false, "Show the version number and information")
|
||||||
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
||||||
flagOptions = flag.Bool("options", false, "Show all option help")
|
flagOptions = flag.Bool("options", false, "Show all option help")
|
||||||
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
|
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
|
||||||
flagProfile = flag.Bool("profile", false, "Enable CPU profiling (writes profile info to ./micro.prof)")
|
|
||||||
flagPlugin = flag.String("plugin", "", "Plugin command")
|
flagPlugin = flag.String("plugin", "", "Plugin command")
|
||||||
flagClean = flag.Bool("clean", false, "Clean configuration directory")
|
flagClean = flag.Bool("clean", false, "Clean configuration directory")
|
||||||
optionFlags map[string]*string
|
optionFlags map[string]*string
|
||||||
|
|
||||||
sigterm chan os.Signal
|
|
||||||
sighup chan os.Signal
|
|
||||||
|
|
||||||
timerChan chan func()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitFlags() {
|
func InitFlags() {
|
||||||
|
@ -60,13 +52,10 @@ func InitFlags() {
|
||||||
fmt.Println(" \tShow all option help")
|
fmt.Println(" \tShow all option help")
|
||||||
fmt.Println("-debug")
|
fmt.Println("-debug")
|
||||||
fmt.Println(" \tEnable debug mode (enables logging to ./log.txt)")
|
fmt.Println(" \tEnable debug mode (enables logging to ./log.txt)")
|
||||||
fmt.Println("-profile")
|
|
||||||
fmt.Println(" \tEnable CPU profiling (writes profile info to ./micro.prof")
|
|
||||||
fmt.Println(" \tso it can be analyzed later with \"go tool pprof micro.prof\")")
|
|
||||||
fmt.Println("-version")
|
fmt.Println("-version")
|
||||||
fmt.Println(" \tShow the version number and information")
|
fmt.Println(" \tShow the version number and information")
|
||||||
|
|
||||||
fmt.Print("\nMicro's plugins can be managed at the command line with the following commands.\n")
|
fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
|
||||||
fmt.Println("-plugin install [PLUGIN]...")
|
fmt.Println("-plugin install [PLUGIN]...")
|
||||||
fmt.Println(" \tInstall plugin(s)")
|
fmt.Println(" \tInstall plugin(s)")
|
||||||
fmt.Println("-plugin remove [PLUGIN]...")
|
fmt.Println("-plugin remove [PLUGIN]...")
|
||||||
|
@ -143,7 +132,7 @@ func DoPluginFlags() {
|
||||||
|
|
||||||
// LoadInput determines which files should be loaded into buffers
|
// LoadInput determines which files should be loaded into buffers
|
||||||
// based on the input stored in flag.Args()
|
// based on the input stored in flag.Args()
|
||||||
func LoadInput(args []string) []*buffer.Buffer {
|
func LoadInput() []*buffer.Buffer {
|
||||||
// There are a number of ways micro should start given its input
|
// There are a number of ways micro should start given its input
|
||||||
|
|
||||||
// 1. If it is given a files in flag.Args(), it should open those
|
// 1. If it is given a files in flag.Args(), it should open those
|
||||||
|
@ -158,6 +147,7 @@ func LoadInput(args []string) []*buffer.Buffer {
|
||||||
var filename string
|
var filename string
|
||||||
var input []byte
|
var input []byte
|
||||||
var err error
|
var err error
|
||||||
|
args := flag.Args()
|
||||||
buffers := make([]*buffer.Buffer, 0, len(args))
|
buffers := make([]*buffer.Buffer, 0, len(args))
|
||||||
|
|
||||||
btype := buffer.BTDefault
|
btype := buffer.BTDefault
|
||||||
|
@ -232,21 +222,15 @@ func main() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// runtime.SetCPUProfileRate(400)
|
||||||
|
// f, _ := os.Create("micro.prof")
|
||||||
|
// pprof.StartCPUProfile(f)
|
||||||
|
// defer pprof.StopCPUProfile()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
InitFlags()
|
InitFlags()
|
||||||
|
|
||||||
if *flagProfile {
|
|
||||||
f, err := os.Create("micro.prof")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("error creating CPU profile: ", err)
|
|
||||||
}
|
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
|
||||||
log.Fatal("error starting CPU profile: ", err)
|
|
||||||
}
|
|
||||||
defer pprof.StopCPUProfile()
|
|
||||||
}
|
|
||||||
|
|
||||||
InitLog()
|
InitLog()
|
||||||
|
|
||||||
err = config.InitConfigDir(*flagConfigDir)
|
err = config.InitConfigDir(*flagConfigDir)
|
||||||
|
@ -254,9 +238,7 @@ func main() {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.InitRuntimeFiles(true)
|
config.InitRuntimeFiles()
|
||||||
config.InitPlugins()
|
|
||||||
|
|
||||||
err = config.ReadSettings()
|
err = config.ReadSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
|
@ -275,35 +257,23 @@ func main() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
config.GlobalSettings[k] = nativeValue
|
config.GlobalSettings[k] = nativeValue
|
||||||
config.VolatileSettings[k] = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DoPluginFlags()
|
DoPluginFlags()
|
||||||
|
|
||||||
err = screen.Init()
|
screen.Init()
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
|
|
||||||
clipErr := clipboard.Initialize(m)
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
if screen.Screen != nil {
|
screen.Screen.Fini()
|
||||||
screen.Screen.Fini()
|
fmt.Println("Micro encountered an error:", err)
|
||||||
}
|
|
||||||
if e, ok := err.(*lua.ApiError); ok {
|
|
||||||
fmt.Println("Lua API error:", e)
|
|
||||||
} else {
|
|
||||||
fmt.Println("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
|
|
||||||
}
|
|
||||||
// backup all open buffers
|
// backup all open buffers
|
||||||
for _, b := range buffer.OpenBuffers {
|
for _, b := range buffer.OpenBuffers {
|
||||||
b.Backup()
|
b.Backup(false)
|
||||||
}
|
}
|
||||||
|
// Print the stack trace too
|
||||||
|
fmt.Print(errors.Wrap(err, 2).ErrorStack())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -321,15 +291,7 @@ func main() {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = config.RunPluginFn("preinit")
|
b := LoadInput()
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
action.InitGlobals()
|
|
||||||
buffer.SetMessager(action.InfoBar)
|
|
||||||
args := flag.Args()
|
|
||||||
b := LoadInput(args)
|
|
||||||
|
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
// No buffers to open
|
// No buffers to open
|
||||||
|
@ -338,34 +300,14 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
action.InitTabs(b)
|
action.InitTabs(b)
|
||||||
|
action.InitGlobals()
|
||||||
|
|
||||||
err = config.RunPluginFn("init")
|
err = config.RunPluginFn("init")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = config.RunPluginFn("postinit")
|
events = make(chan tcell.Event)
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if clipErr != nil {
|
|
||||||
log.Println(clipErr, " or change 'clipboard' option")
|
|
||||||
}
|
|
||||||
|
|
||||||
if a := config.GetGlobalOption("autosave").(float64); a > 0 {
|
|
||||||
config.SetAutoTime(int(a))
|
|
||||||
config.StartAutoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.Events = make(chan tcell.Event)
|
|
||||||
|
|
||||||
sigterm = make(chan os.Signal, 1)
|
|
||||||
sighup = make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGABRT)
|
|
||||||
signal.Notify(sighup, syscall.SIGHUP)
|
|
||||||
|
|
||||||
timerChan = make(chan func())
|
|
||||||
|
|
||||||
// Here is the event loop which runs in a separate thread
|
// Here is the event loop which runs in a separate thread
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -374,7 +316,7 @@ func main() {
|
||||||
e := screen.Screen.PollEvent()
|
e := screen.Screen.PollEvent()
|
||||||
screen.Unlock()
|
screen.Unlock()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
screen.Events <- e
|
events <- e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -387,12 +329,15 @@ func main() {
|
||||||
|
|
||||||
// wait for initial resize event
|
// wait for initial resize event
|
||||||
select {
|
select {
|
||||||
case event := <-screen.Events:
|
case event := <-events:
|
||||||
action.Tabs.HandleEvent(event)
|
action.Tabs.HandleEvent(event)
|
||||||
case <-time.After(10 * time.Millisecond):
|
case <-time.After(10 * time.Millisecond):
|
||||||
// time out after 10ms
|
// time out after 10ms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since this loop is very slow (waits for user input every time) it's
|
||||||
|
// okay to be inefficient and run it via a function every time
|
||||||
|
// We do this so we can recover from panics without crashing the editor
|
||||||
for {
|
for {
|
||||||
DoEvent()
|
DoEvent()
|
||||||
}
|
}
|
||||||
|
@ -402,6 +347,16 @@ func main() {
|
||||||
func DoEvent() {
|
func DoEvent() {
|
||||||
var event tcell.Event
|
var event tcell.Event
|
||||||
|
|
||||||
|
// recover from errors without crashing the editor
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if e, ok := err.(*lua.ApiError); ok {
|
||||||
|
screen.TermMessage("Lua API error:", e)
|
||||||
|
} else {
|
||||||
|
screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
// Display everything
|
// Display everything
|
||||||
screen.Screen.Fill(' ', config.DefStyle)
|
screen.Screen.Fill(' ', config.DefStyle)
|
||||||
screen.Screen.HideCursor()
|
screen.Screen.HideCursor()
|
||||||
|
@ -420,69 +375,19 @@ func DoEvent() {
|
||||||
f.Function(f.Output, f.Args)
|
f.Function(f.Output, f.Args)
|
||||||
case <-config.Autosave:
|
case <-config.Autosave:
|
||||||
for _, b := range buffer.OpenBuffers {
|
for _, b := range buffer.OpenBuffers {
|
||||||
b.AutoSave()
|
b.Save()
|
||||||
}
|
}
|
||||||
case <-shell.CloseTerms:
|
case <-shell.CloseTerms:
|
||||||
case event = <-screen.Events:
|
case event = <-events:
|
||||||
case <-screen.DrawChan():
|
case <-screen.DrawChan():
|
||||||
for len(screen.DrawChan()) > 0 {
|
for len(screen.DrawChan()) > 0 {
|
||||||
<-screen.DrawChan()
|
<-screen.DrawChan()
|
||||||
}
|
}
|
||||||
case f := <-timerChan:
|
|
||||||
f()
|
|
||||||
case <-sighup:
|
|
||||||
for _, b := range buffer.OpenBuffers {
|
|
||||||
if !b.Modified() {
|
|
||||||
b.Fini()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
case <-sigterm:
|
|
||||||
for _, b := range buffer.OpenBuffers {
|
|
||||||
if !b.Modified() {
|
|
||||||
b.Fini()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if screen.Screen != nil {
|
|
||||||
screen.Screen.Fini()
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if e, ok := event.(*tcell.EventError); ok {
|
if action.InfoBar.HasPrompt {
|
||||||
log.Println("tcell event error: ", e.Error())
|
action.InfoBar.HandleEvent(event)
|
||||||
|
} else {
|
||||||
if e.Err() == io.EOF {
|
action.Tabs.HandleEvent(event)
|
||||||
// shutdown due to terminal closing/becoming inaccessible
|
|
||||||
for _, b := range buffer.OpenBuffers {
|
|
||||||
if !b.Modified() {
|
|
||||||
b.Fini()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if screen.Screen != nil {
|
|
||||||
screen.Screen.Fini()
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if event != nil {
|
|
||||||
_, resize := event.(*tcell.EventResize)
|
|
||||||
if resize {
|
|
||||||
action.InfoBar.HandleEvent(event)
|
|
||||||
action.Tabs.HandleEvent(event)
|
|
||||||
} else if action.InfoBar.HasPrompt {
|
|
||||||
action.InfoBar.HandleEvent(event)
|
|
||||||
} else {
|
|
||||||
action.Tabs.HandleEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := config.RunPluginFn("onAnyEvent")
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,357 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"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/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var tempDir string
|
|
||||||
var sim tcell.SimulationScreen
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
screen.Events = make(chan tcell.Event, 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
func startup(args []string) (tcell.SimulationScreen, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
tempDir, err = ioutil.TempDir("", "micro_test")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = config.InitConfigDir(tempDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config.InitRuntimeFiles(true)
|
|
||||||
config.InitPlugins()
|
|
||||||
|
|
||||||
err = config.ReadSettings()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = config.InitGlobalSettings()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := screen.InitSimScreen()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
screen.Screen.Fini()
|
|
||||||
fmt.Println("Micro encountered an error:", err)
|
|
||||||
// backup all open buffers
|
|
||||||
for _, b := range buffer.OpenBuffers {
|
|
||||||
b.Backup()
|
|
||||||
}
|
|
||||||
// Print the stack trace too
|
|
||||||
log.Fatalf(errors.Wrap(err, 2).ErrorStack())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = config.LoadAllPlugins()
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
action.InitBindings()
|
|
||||||
action.InitCommands()
|
|
||||||
|
|
||||||
err = config.InitColorscheme()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b := LoadInput(args)
|
|
||||||
|
|
||||||
if len(b) == 0 {
|
|
||||||
return nil, errors.New("No buffers opened")
|
|
||||||
}
|
|
||||||
|
|
||||||
action.InitTabs(b)
|
|
||||||
action.InitGlobals()
|
|
||||||
|
|
||||||
err = config.RunPluginFn("init")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.InjectResize()
|
|
||||||
handleEvent()
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanup() {
|
|
||||||
os.RemoveAll(tempDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleEvent() {
|
|
||||||
screen.Lock()
|
|
||||||
e := screen.Screen.PollEvent()
|
|
||||||
screen.Unlock()
|
|
||||||
if e != nil {
|
|
||||||
screen.Events <- e
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(screen.DrawChan()) > 0 || len(screen.Events) > 0 {
|
|
||||||
DoEvent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func injectKey(key tcell.Key, r rune, mod tcell.ModMask) {
|
|
||||||
sim.InjectKey(key, r, mod)
|
|
||||||
handleEvent()
|
|
||||||
}
|
|
||||||
|
|
||||||
func injectMouse(x, y int, buttons tcell.ButtonMask, mod tcell.ModMask) {
|
|
||||||
sim.InjectMouse(x, y, buttons, mod)
|
|
||||||
handleEvent()
|
|
||||||
}
|
|
||||||
|
|
||||||
func injectString(str string) {
|
|
||||||
// the tcell simulation screen event channel can only handle
|
|
||||||
// 10 events at once, so we need to divide up the key events
|
|
||||||
// into chunks of 10 and handle the 10 events before sending
|
|
||||||
// another chunk of events
|
|
||||||
iters := len(str) / 10
|
|
||||||
extra := len(str) % 10
|
|
||||||
|
|
||||||
for i := 0; i < iters; i++ {
|
|
||||||
s := i * 10
|
|
||||||
e := i*10 + 10
|
|
||||||
sim.InjectKeyBytes([]byte(str[s:e]))
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
handleEvent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sim.InjectKeyBytes([]byte(str[len(str)-extra:]))
|
|
||||||
for i := 0; i < extra; i++ {
|
|
||||||
handleEvent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func openFile(file string) {
|
|
||||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
|
||||||
injectString(fmt.Sprintf("open %s", file))
|
|
||||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findBuffer(file string) *buffer.Buffer {
|
|
||||||
var buf *buffer.Buffer
|
|
||||||
for _, b := range buffer.OpenBuffers {
|
|
||||||
if b.Path == file {
|
|
||||||
buf = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTestFile(name string, content string) (string, error) {
|
|
||||||
testf, err := ioutil.TempFile("", name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := testf.Write([]byte(content)); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := testf.Close(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return testf.Name(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
var err error
|
|
||||||
sim, err = startup([]string{})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
retval := m.Run()
|
|
||||||
cleanup()
|
|
||||||
|
|
||||||
os.Exit(retval)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSimpleEdit(t *testing.T) {
|
|
||||||
file, err := createTestFile("micro_simple_edit_test", "base content")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(file)
|
|
||||||
|
|
||||||
openFile(file)
|
|
||||||
|
|
||||||
if findBuffer(file) == nil {
|
|
||||||
t.Errorf("Could not find buffer %s", file)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
|
||||||
injectKey(tcell.KeyUp, 0, tcell.ModNone)
|
|
||||||
injectString("first line")
|
|
||||||
|
|
||||||
// test both kinds of backspace
|
|
||||||
for i := 0; i < len("ne"); i++ {
|
|
||||||
injectKey(tcell.KeyBackspace, rune(tcell.KeyBackspace), tcell.ModNone)
|
|
||||||
}
|
|
||||||
for i := 0; i < len(" li"); i++ {
|
|
||||||
injectKey(tcell.KeyBackspace2, rune(tcell.KeyBackspace2), tcell.ModNone)
|
|
||||||
}
|
|
||||||
injectString("foobar")
|
|
||||||
|
|
||||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "firstfoobar\nbase content\n", string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMouse(t *testing.T) {
|
|
||||||
file, err := createTestFile("micro_mouse_test", "base content")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(file)
|
|
||||||
|
|
||||||
openFile(file)
|
|
||||||
|
|
||||||
if findBuffer(file) == nil {
|
|
||||||
t.Errorf("Could not find buffer %s", file)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// buffer:
|
|
||||||
// base content
|
|
||||||
// the selections need to happen at different locations to avoid a double click
|
|
||||||
injectMouse(3, 0, tcell.Button1, tcell.ModNone)
|
|
||||||
injectKey(tcell.KeyLeft, 0, tcell.ModNone)
|
|
||||||
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
|
|
||||||
injectString("secondline")
|
|
||||||
// buffer:
|
|
||||||
// secondlinebase content
|
|
||||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
|
||||||
// buffer:
|
|
||||||
// secondline
|
|
||||||
// base content
|
|
||||||
injectMouse(2, 0, tcell.Button1, tcell.ModNone)
|
|
||||||
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
|
|
||||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
|
||||||
// buffer:
|
|
||||||
//
|
|
||||||
// secondline
|
|
||||||
// base content
|
|
||||||
injectKey(tcell.KeyUp, 0, tcell.ModNone)
|
|
||||||
injectString("firstline")
|
|
||||||
// buffer:
|
|
||||||
// firstline
|
|
||||||
// secondline
|
|
||||||
// base content
|
|
||||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
var srTestStart = `foo
|
|
||||||
foo
|
|
||||||
foofoofoo
|
|
||||||
Ernleȝe foo æðelen
|
|
||||||
`
|
|
||||||
var srTest2 = `test_string
|
|
||||||
test_string
|
|
||||||
test_stringtest_stringtest_string
|
|
||||||
Ernleȝe test_string æðelen
|
|
||||||
`
|
|
||||||
var srTest3 = `test_foo
|
|
||||||
test_string
|
|
||||||
test_footest_stringtest_foo
|
|
||||||
Ernleȝe test_string æðelen
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestSearchAndReplace(t *testing.T) {
|
|
||||||
file, err := createTestFile("micro_search_replace_test", srTestStart)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(file)
|
|
||||||
|
|
||||||
openFile(file)
|
|
||||||
|
|
||||||
if findBuffer(file) == nil {
|
|
||||||
t.Errorf("Could not find buffer %s", file)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
|
||||||
injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string"))
|
|
||||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
|
||||||
|
|
||||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, srTest2, string(data))
|
|
||||||
|
|
||||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
|
||||||
injectString(fmt.Sprintf("replace %s %s", "string", "foo"))
|
|
||||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
|
||||||
injectString("ynyny")
|
|
||||||
injectKey(tcell.KeyEscape, 0, tcell.ModNone)
|
|
||||||
|
|
||||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
|
||||||
|
|
||||||
data, err = ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, srTest3, string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultiCursor(t *testing.T) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettingsPersistence(t *testing.T) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
// more tests (rendering, tabs, plugins)?
|
|
23
data/com.github.zyedidia.micro.metainfo.xml
Normal file
23
data/com.github.zyedidia.micro.metainfo.xml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component>
|
||||||
|
<id>com.github.zyedidia.micro</id>
|
||||||
|
<name>Micro Text Editor</name>
|
||||||
|
<summary>A modern and intuitive terminal-based text editor</summary>
|
||||||
|
<metadata_license>MIT</metadata_license>
|
||||||
|
<categories>
|
||||||
|
<category>Development</category>
|
||||||
|
<category>TextEditor</category>
|
||||||
|
</categories>
|
||||||
|
<provides>
|
||||||
|
<binary>micro</binary>
|
||||||
|
</provides>
|
||||||
|
<developer_name>Zachary Yedidia</developer_name>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<caption>Micro Text Editor editing its source code.</caption>
|
||||||
|
<image type="source">https://raw.githubusercontent.com/zyedidia/micro/master/assets/micro-solarized.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
<url type="homepage">https://micro-editor.github.io</url>
|
||||||
|
<url type="bugtracker">https://github.com/zyedidia/micro/issues</url>
|
||||||
|
</component>
|
|
@ -1,51 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<component type="desktop-application">
|
|
||||||
<id>io.github.zyedidia.micro</id>
|
|
||||||
<launchable type="desktop-id">micro.desktop</launchable>
|
|
||||||
<name>Micro Text Editor</name>
|
|
||||||
<summary>A modern and intuitive terminal-based text editor</summary>
|
|
||||||
<description>
|
|
||||||
<p>
|
|
||||||
micro is a terminal-based text editor that aims to be easy to use and
|
|
||||||
intuitive, while also taking advantage of the capabilities of modern terminals.
|
|
||||||
It comes as a single, batteries-included, static binary with no dependencies;
|
|
||||||
you can download and use it right now!
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
As its name indicates, micro aims to be somewhat of a successor to the nano
|
|
||||||
editor by being easy to install and use. It strives to be enjoyable as a full-time
|
|
||||||
editor for people who prefer to work in a terminal, or those who regularly
|
|
||||||
edit files over SSH.
|
|
||||||
</p>
|
|
||||||
</description>
|
|
||||||
<metadata_license>MIT</metadata_license>
|
|
||||||
<project_license>MIT</project_license>
|
|
||||||
<categories>
|
|
||||||
<category>Development</category>
|
|
||||||
<category>TextEditor</category>
|
|
||||||
</categories>
|
|
||||||
<releases>
|
|
||||||
<release version="2.0.13" date="2023-10-22"/>
|
|
||||||
<release version="2.0.12" date="2023-09-06"/>
|
|
||||||
<release version="2.0.11" date="2022-08-01"/>
|
|
||||||
</releases>
|
|
||||||
<provides>
|
|
||||||
<binary>micro</binary>
|
|
||||||
<id>com.github.zyedidia.micro</id>
|
|
||||||
</provides>
|
|
||||||
<developer_name>Zachary Yedidia</developer_name>
|
|
||||||
<screenshots>
|
|
||||||
<screenshot type="default">
|
|
||||||
<caption>Micro Text Editor editing its source code</caption>
|
|
||||||
<image type="source">https://raw.githubusercontent.com/zyedidia/micro/master/assets/micro-solarized.png</image>
|
|
||||||
</screenshot>
|
|
||||||
</screenshots>
|
|
||||||
<content_rating type="oars-1.1" />
|
|
||||||
<url type="homepage">https://micro-editor.github.io</url>
|
|
||||||
<url type="bugtracker">https://github.com/zyedidia/micro/issues</url>
|
|
||||||
<url type="faq">https://micro-editor.github.io/about.html</url>
|
|
||||||
<url type="help">https://micro-editor.github.io/about.html</url>
|
|
||||||
<url type="contact">https://github.com/zyedidia</url>
|
|
||||||
<url type="vcs-browser">https://github.com/zyedidia/micro</url>
|
|
||||||
<url type="contribute">https://github.com/zyedidia/micro#contributing</url>
|
|
||||||
</component>
|
|
367
data/micro.json
367
data/micro.json
|
@ -1,367 +0,0 @@
|
||||||
{
|
|
||||||
"$comment": "https://github.com/zyedidia/micro",
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "options",
|
|
||||||
"description": "A micro editor config schema",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"autoindent": {
|
|
||||||
"description": "Whether to use the same indentation as a previous line\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"autosave": {
|
|
||||||
"description": "A delay between automatic saves\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"autosu": {
|
|
||||||
"description": "Whether attempt to use super user privileges\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"backup": {
|
|
||||||
"description": "Whether to backup all open buffers\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"backupdir": {
|
|
||||||
"description": "A directory to store backups\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"default": ""
|
|
||||||
},
|
|
||||||
"basename": {
|
|
||||||
"description": "Whether to show a basename instead of a full path\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"clipboard": {
|
|
||||||
"description": "A way to access the system clipboard\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"external",
|
|
||||||
"terminal",
|
|
||||||
"internal"
|
|
||||||
],
|
|
||||||
"default": "external"
|
|
||||||
},
|
|
||||||
"colorcolumn": {
|
|
||||||
"description": "A position to display a column\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"colorscheme": {
|
|
||||||
"description": "A color scheme\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"atom-dark",
|
|
||||||
"bubblegum",
|
|
||||||
"cmc-16",
|
|
||||||
"cmc-tc",
|
|
||||||
"darcula",
|
|
||||||
"default",
|
|
||||||
"dracula-tc",
|
|
||||||
"dukedark-tc",
|
|
||||||
"dukelight-tc",
|
|
||||||
"dukeubuntu-tc",
|
|
||||||
"geany",
|
|
||||||
"gotham",
|
|
||||||
"gruvbox",
|
|
||||||
"gruvbox-tc",
|
|
||||||
"material-tc",
|
|
||||||
"monokai-dark",
|
|
||||||
"monokai",
|
|
||||||
"one-dark",
|
|
||||||
"railscast",
|
|
||||||
"simple",
|
|
||||||
"solarized",
|
|
||||||
"solarized-tc",
|
|
||||||
"sunny-day",
|
|
||||||
"twilight",
|
|
||||||
"zenburn"
|
|
||||||
],
|
|
||||||
"default": "default"
|
|
||||||
},
|
|
||||||
"cursorline": {
|
|
||||||
"description": "Whether to highlight a line with a cursor with a different color\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"diffgutter": {
|
|
||||||
"description": "Whether to display diff inticators before lines\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"divchars": {
|
|
||||||
"description": "Divider chars for vertical and horizontal splits\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"default": "|-"
|
|
||||||
},
|
|
||||||
"divreverse": {
|
|
||||||
"description": "Whether to use inversed color scheme colors for splits\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"encoding": {
|
|
||||||
"description": "An encoding used to open and save files\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"default": "utf-8"
|
|
||||||
},
|
|
||||||
"eofnewline": {
|
|
||||||
"description": "Whether to add a missing trailing new line\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"fastdirty": {
|
|
||||||
"description": "Whether to use a fast algorithm to determine whether a file is changed\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"fileformat": {
|
|
||||||
"description": "A line ending format\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"unix",
|
|
||||||
"dos"
|
|
||||||
],
|
|
||||||
"default": "unix"
|
|
||||||
},
|
|
||||||
"filetype": {
|
|
||||||
"description": "A filetype for the current buffer\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"default": "unknown"
|
|
||||||
},
|
|
||||||
"hlsearch": {
|
|
||||||
"description": "Whether to highlight all instances of a searched text after a successful search\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"incsearch": {
|
|
||||||
"description": "Whether to enable an incremental search in `Find` prompt\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"ignorecase": {
|
|
||||||
"description": "Whether to perform case-insensitive searches\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"indentchar": {
|
|
||||||
"description": "An indentation character\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"maxLength": 1,
|
|
||||||
"default": " "
|
|
||||||
},
|
|
||||||
"infobar": {
|
|
||||||
"description": "Whether to enable a line at the bottom where messages are printed\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"keepautoindent": {
|
|
||||||
"description": "Whether add a whitespace while using autoindent\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"keymenu": {
|
|
||||||
"description": "Whether to display nano-style key menu at the bottom\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"matchbrace": {
|
|
||||||
"description": "Whether to show matching braces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"matchbracestyle": {
|
|
||||||
"description": "Whether to underline or highlight matching braces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"underline",
|
|
||||||
"highlight"
|
|
||||||
],
|
|
||||||
"default": "underline"
|
|
||||||
},
|
|
||||||
"mkparents": {
|
|
||||||
"description": "Whether to create missing directories\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"mouse": {
|
|
||||||
"description": "Whether to enable mouse support\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"paste": {
|
|
||||||
"description": "Whether to treat characters sent from the terminal in a single chunk as a paste event\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"parsecursor": {
|
|
||||||
"description": "Whether to extract a line number and a column to open files with from file names\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"permbackup": {
|
|
||||||
"description": "Whether to permanently save backups\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"pluginchannels": {
|
|
||||||
"description": "A file with list of plugin channels\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"default": "https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"
|
|
||||||
},
|
|
||||||
"pluginrepos": {
|
|
||||||
"description": "Plugin repositories\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": true,
|
|
||||||
"items": {
|
|
||||||
"description": "A pluging repository\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": []
|
|
||||||
},
|
|
||||||
"readonly": {
|
|
||||||
"description": "Whether to forbid buffer editing\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"rmtrailingws": {
|
|
||||||
"description": "Whether to remove trailing whitespaces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"ruler": {
|
|
||||||
"description": "Whether to display line numbers\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"relativeruler": {
|
|
||||||
"description": "Whether to display relative line numbers\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"savecursor": {
|
|
||||||
"description": "Whether to save cursor position in files\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"savehistory": {
|
|
||||||
"description": "Whether to save command history between closing and re-opening editor\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"saveundo": {
|
|
||||||
"description": "Whether to save undo after closing file\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"scrollbar": {
|
|
||||||
"description": "Whether to save undo after closing file\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"scrollmargin": {
|
|
||||||
"description": "A margin at which a view starts scrolling when a cursor approaches an edge of a view\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 3
|
|
||||||
},
|
|
||||||
"scrollspeed": {
|
|
||||||
"description": "Line count to scroll for one scroll event\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 2
|
|
||||||
},
|
|
||||||
"smartpaste": {
|
|
||||||
"description": "Whether to add a leading whitespace while pasting multiple lines\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"softwrap": {
|
|
||||||
"description": "Whether to wrap long lines\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"splitbottom": {
|
|
||||||
"description": "Whether to create a new horizontal split below the current one\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"splitright": {
|
|
||||||
"description": "Whether to create a new vertical split right of the current one\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"statusformatl": {
|
|
||||||
"description": "Format string of left-justified part of the statusline\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"default": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)"
|
|
||||||
},
|
|
||||||
"statusformatr": {
|
|
||||||
"description": "Format string of right-justified part of the statusline\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"default": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help"
|
|
||||||
},
|
|
||||||
"statusline": {
|
|
||||||
"description": "Whether to display a status line\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"default": "sudo"
|
|
||||||
},
|
|
||||||
"sucmd": {
|
|
||||||
"description": "A super user command\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "string",
|
|
||||||
"default": "sudo",
|
|
||||||
"examples": [
|
|
||||||
"sudo",
|
|
||||||
"doas"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"syntax": {
|
|
||||||
"description": "Whether to enable a syntax highlighting\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"tabmovement": {
|
|
||||||
"description": "Whether to navigate spaces at the beginning of lines as if they are tabs\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"tabhighlight": {
|
|
||||||
"description": "Whether to invert tab character colors\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"tabreverse": {
|
|
||||||
"description": "Whether to reverse tab bar colors\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"tabsize": {
|
|
||||||
"description": "A tab size\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 4
|
|
||||||
},
|
|
||||||
"tabstospaces": {
|
|
||||||
"description": "Whether to use spaces instead of tabs\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"useprimary": {
|
|
||||||
"description": "Whether to use primary clipboard to copy selections in the background\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"wordwrap": {
|
|
||||||
"description": "Whether to wrap long lines by words\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"xterm": {
|
|
||||||
"description": "Whether to assume that the current terminal is `xterm`\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
23
go.mod
23
go.mod
|
@ -8,23 +8,28 @@ require (
|
||||||
github.com/mattn/go-isatty v0.0.11
|
github.com/mattn/go-isatty v0.0.11
|
||||||
github.com/mattn/go-runewidth v0.0.7
|
github.com/mattn/go-runewidth v0.0.7
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
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/sergi/go-diff v1.1.0
|
||||||
|
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
|
||||||
|
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
|
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
|
||||||
github.com/zyedidia/clipper v0.1.1
|
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834
|
||||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
|
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/json5 v0.0.0-20200102012142-2da050b1a98d
|
||||||
github.com/zyedidia/tcell/v2 v2.0.10 // indirect
|
github.com/zyedidia/pty v2.0.0+incompatible // indirect
|
||||||
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef
|
github.com/zyedidia/tcell v1.4.6
|
||||||
golang.org/x/text v0.3.8
|
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
golang.org/x/text v0.3.2
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
|
||||||
|
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.2.7
|
||||||
layeh.com/gopher-luar v1.0.7
|
layeh.com/gopher-luar v1.0.7
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
|
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
|
||||||
|
|
||||||
replace github.com/mattn/go-runewidth => github.com/zyedidia/go-runewidth v0.0.12
|
replace github.com/mattn/go-runewidth => github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059
|
||||||
|
|
||||||
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.7
|
go 1.11
|
||||||
|
|
||||||
go 1.16
|
|
||||||
|
|
79
go.sum
79
go.sum
|
@ -3,8 +3,6 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
|
||||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -19,85 +17,66 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/layeh/gopher-luar v1.0.7 h1:wnfZhYiJM748y1A4qYBfcFeMY9HWbdERny+ZL0f/jWc=
|
|
||||||
github.com/layeh/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059 h1:/+h2b6i15wh4EWsFkfdNdBE1jjGA872tpXEyhPM5aYg=
|
||||||
|
github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
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 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
|
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
|
||||||
|
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||||
|
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0=
|
||||||
|
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||||
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8 h1:woqigIZtZUZxws1zZA99nAvuz2mQrxtWsuZSR9c8I/A=
|
|
||||||
github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8/go.mod h1:6Yhx5ZJl5942QrNRWLwITArVT9okUXc5c3brgWJMoDc=
|
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
|
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||||
github.com/zyedidia/clipper v0.1.1 h1:HBgguFNDq/QmSQKBnhy4sMKzILINr139VEgAhftOUTw=
|
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834 h1:0nOfq3JwYRiY3+nwfWVQYEaXDmGCQgj3RKoqTifLzP4=
|
||||||
github.com/zyedidia/clipper v0.1.1/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=
|
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 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
|
||||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
|
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
|
||||||
github.com/zyedidia/go-runewidth v0.0.12 h1:aHWj8qL3aH7caRzoPBJXe1pEaZBXHpKtfTuiBo5p74Q=
|
|
||||||
github.com/zyedidia/go-runewidth v0.0.12/go.mod h1:vF8djYdLmG8BJaUZ4CznFYCJ3pFR8m4B4VinTvTTarU=
|
|
||||||
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655 h1:Z3RhH6hvcSx7eX6Q/pP6YVsgea/1eMDG99vtWwi3nK4=
|
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655 h1:Z3RhH6hvcSx7eX6Q/pP6YVsgea/1eMDG99vtWwi3nK4=
|
||||||
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655/go.mod h1:1sTqqO+kcYzZp43M5VsJe1tns9IzlSeC9jB6c2+o/5Y=
|
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655/go.mod h1:1sTqqO+kcYzZp43M5VsJe1tns9IzlSeC9jB6c2+o/5Y=
|
||||||
|
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5 h1:Zs6mpwXvlqpF9zHl5XaN0p5V4J9XvP+WBuiuXyIgqvc=
|
||||||
|
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5/go.mod h1:c1r+Ob9tUTPB0FKWO1+x+Hsc/zNa45WdGq7Y38Ybip0=
|
||||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d h1:zmDMkh22zXOB7gz8jFaI4GpI7llsPgzm38/jG0UgxjE=
|
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d h1:zmDMkh22zXOB7gz8jFaI4GpI7llsPgzm38/jG0UgxjE=
|
||||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I=
|
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I=
|
||||||
github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
|
github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
|
||||||
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||||
github.com/zyedidia/tcell/v2 v2.0.9 h1:FxXRkE62N0GPHES7EMLtp2rteYqC9r1kVid8vJN1kOE=
|
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
|
||||||
github.com/zyedidia/tcell/v2 v2.0.9/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
|
||||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8 h1:53ULv4mmLyQDnqbjVxanckP57WSreWHwTmlLJrJEutY=
|
github.com/zyedidia/tcell v1.4.6 h1:8OYvZpUyqYQ3nigenBwOtnY3fXWEHekbm6QYchBeOxs=
|
||||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
github.com/zyedidia/tcell v1.4.6/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
||||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230320201625-54f6acdada4a h1:W4TWa++Wk6uRGxZoxr2nPX1TpIEl+Wxv0mTtocG4TYc=
|
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
|
||||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230320201625-54f6acdada4a/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
|
||||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230831153116-061c5b2c7260 h1:SCAmAacT5BxZsmOFdFy5zwwi6nj1MjA60gydjKdTgXo=
|
|
||||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230831153116-061c5b2c7260/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
|
||||||
github.com/zyedidia/tcell/v2 v2.0.10 h1:6fbbYAx/DYc9A//4jU1OeBrxtc9qJxYCZXCtGQbtTWU=
|
|
||||||
github.com/zyedidia/tcell/v2 v2.0.10/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
|
||||||
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef h1:LeB4Qs0Tss4r/Qh8pfsTTqagDYHysfKJLYzAH3MVfu0=
|
|
||||||
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef/go.mod h1:zeb8MJdcCObFKVvur3n2B4BANIPuo2Q8r4iiNs9Enx0=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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.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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
layeh.com/gopher-luar v1.0.7 h1:53iv6CCkRs5wyofZ+qVXcyAYQOIG52s6pt4xkqZdq7k=
|
||||||
|
layeh.com/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,26 +3,18 @@ package action
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/zyedidia/json5"
|
"github.com/zyedidia/json5"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Binder = map[string]func(e Event, action string){
|
|
||||||
"command": InfoMapEvent,
|
|
||||||
"buffer": BufMapEvent,
|
|
||||||
"terminal": TermMapEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
func createBindingsIfNotExist(fname string) {
|
func createBindingsIfNotExist(fname string) {
|
||||||
if _, e := os.Stat(fname); os.IsNotExist(e) {
|
if _, e := os.Stat(fname); os.IsNotExist(e) {
|
||||||
ioutil.WriteFile(fname, []byte("{}"), 0644)
|
ioutil.WriteFile(fname, []byte("{}"), 0644)
|
||||||
|
@ -31,7 +23,10 @@ func createBindingsIfNotExist(fname string) {
|
||||||
|
|
||||||
// InitBindings intializes the bindings map by reading from bindings.json
|
// InitBindings intializes the bindings map by reading from bindings.json
|
||||||
func InitBindings() {
|
func InitBindings() {
|
||||||
var parsed map[string]interface{}
|
config.Bindings = DefaultBindings()
|
||||||
|
|
||||||
|
var parsed map[string]string
|
||||||
|
defaults := DefaultBindings()
|
||||||
|
|
||||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||||
createBindingsIfNotExist(filename)
|
createBindingsIfNotExist(filename)
|
||||||
|
@ -49,93 +44,34 @@ func InitBindings() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for p, bind := range Binder {
|
for k, v := range defaults {
|
||||||
defaults := DefaultBindings(p)
|
BindKey(k, v)
|
||||||
|
|
||||||
for k, v := range defaults {
|
|
||||||
BindKey(k, v, bind)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range parsed {
|
for k, v := range parsed {
|
||||||
switch val := v.(type) {
|
BindKey(k, v)
|
||||||
case string:
|
|
||||||
BindKey(k, val, Binder["buffer"])
|
|
||||||
case map[string]interface{}:
|
|
||||||
bind, ok := Binder[k]
|
|
||||||
if !ok || bind == nil {
|
|
||||||
screen.TermMessage(fmt.Sprintf("%s is not a valid pane type", k))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for e, a := range val {
|
|
||||||
s, ok := a.(string)
|
|
||||||
if !ok {
|
|
||||||
screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
|
|
||||||
} else {
|
|
||||||
BindKey(e, s, bind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BindKey(k, v string, bind func(e Event, a string)) {
|
func BindKey(k, v string) {
|
||||||
event, err := findEvent(k)
|
event, ok := findEvent(k)
|
||||||
if err != nil {
|
if !ok {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(k, "is not a bindable event")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(k, "\x1b") {
|
switch e := event.(type) {
|
||||||
screen.Screen.RegisterRawSeq(k)
|
case KeyEvent:
|
||||||
|
BufMapKey(e, v)
|
||||||
|
case MouseEvent:
|
||||||
|
BufMapMouse(e, v)
|
||||||
|
case RawEvent:
|
||||||
|
BufMapKey(e, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
bind(event, v)
|
config.Bindings[k] = v
|
||||||
|
|
||||||
// switch e := event.(type) {
|
|
||||||
// case KeyEvent:
|
|
||||||
// InfoMapKey(e, v)
|
|
||||||
// case KeySequenceEvent:
|
|
||||||
// InfoMapKey(e, v)
|
|
||||||
// case MouseEvent:
|
|
||||||
// InfoMapMouse(e, v)
|
|
||||||
// case RawEvent:
|
|
||||||
// InfoMapKey(e, v)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var r = regexp.MustCompile("<(.+?)>")
|
// findEvent will find binding Key 'b' using string 'k'
|
||||||
|
func findEvent(k string) (b Event, ok bool) {
|
||||||
func findEvents(k string) (b KeySequenceEvent, ok bool, err error) {
|
|
||||||
var events []Event = nil
|
|
||||||
for len(k) > 0 {
|
|
||||||
groups := r.FindStringSubmatchIndex(k)
|
|
||||||
|
|
||||||
if len(groups) > 3 {
|
|
||||||
if events == nil {
|
|
||||||
events = make([]Event, 0, 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
e, ok := findSingleEvent(k[groups[2]:groups[3]])
|
|
||||||
if !ok {
|
|
||||||
return KeySequenceEvent{}, false, errors.New("Invalid event " + k[groups[2]:groups[3]])
|
|
||||||
}
|
|
||||||
|
|
||||||
events = append(events, e)
|
|
||||||
|
|
||||||
k = k[groups[3]+1:]
|
|
||||||
} else {
|
|
||||||
return KeySequenceEvent{}, false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return KeySequenceEvent{events}, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findSingleEvent will find binding Key 'b' using string 'k'
|
|
||||||
func findSingleEvent(k string) (b Event, ok bool) {
|
|
||||||
modifiers := tcell.ModNone
|
modifiers := tcell.ModNone
|
||||||
|
|
||||||
// First, we'll strip off all the modifiers in the name and add them to the
|
// First, we'll strip off all the modifiers in the name and add them to the
|
||||||
|
@ -143,7 +79,7 @@ func findSingleEvent(k string) (b Event, ok bool) {
|
||||||
modSearch:
|
modSearch:
|
||||||
for {
|
for {
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(k, "-") && k != "-":
|
case strings.HasPrefix(k, "-"):
|
||||||
// We optionally support dashes between modifiers
|
// We optionally support dashes between modifiers
|
||||||
k = k[1:]
|
k = k[1:]
|
||||||
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
|
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
|
||||||
|
@ -157,6 +93,7 @@ modSearch:
|
||||||
k = k[5:]
|
k = k[5:]
|
||||||
modifiers |= tcell.ModShift
|
modifiers |= tcell.ModShift
|
||||||
case strings.HasPrefix(k, "\x1b"):
|
case strings.HasPrefix(k, "\x1b"):
|
||||||
|
screen.Screen.RegisterRawSeq(k)
|
||||||
return RawEvent{
|
return RawEvent{
|
||||||
esc: k,
|
esc: k,
|
||||||
}, true
|
}, true
|
||||||
|
@ -165,7 +102,7 @@ modSearch:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if k == "" {
|
if len(k) == 0 {
|
||||||
return KeyEvent{}, false
|
return KeyEvent{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,20 +141,11 @@ modSearch:
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
var mstate MouseState = MousePress
|
|
||||||
if strings.HasSuffix(k, "Drag") {
|
|
||||||
k = k[:len(k)-4]
|
|
||||||
mstate = MouseDrag
|
|
||||||
} else if strings.HasSuffix(k, "Release") {
|
|
||||||
k = k[:len(k)-7]
|
|
||||||
mstate = MouseRelease
|
|
||||||
}
|
|
||||||
// See if we can find the key in bindingMouse
|
// See if we can find the key in bindingMouse
|
||||||
if code, ok := mouseEvents[k]; ok {
|
if code, ok := mouseEvents[k]; ok {
|
||||||
return MouseEvent{
|
return MouseEvent{
|
||||||
btn: code,
|
btn: code,
|
||||||
mod: modifiers,
|
mod: modifiers,
|
||||||
state: mstate,
|
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,46 +162,11 @@ modSearch:
|
||||||
return KeyEvent{}, false
|
return KeyEvent{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func findEvent(k string) (Event, error) {
|
|
||||||
var event Event
|
|
||||||
event, ok, err := findEvents(k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
event, ok = findSingleEvent(k)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New(k + " is not a bindable event")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return event, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func eventsEqual(e1 Event, e2 Event) bool {
|
|
||||||
seq1, ok1 := e1.(KeySequenceEvent)
|
|
||||||
seq2, ok2 := e2.(KeySequenceEvent)
|
|
||||||
if ok1 && ok2 {
|
|
||||||
if len(seq1.keys) != len(seq2.keys) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := 0; i < len(seq1.keys); i++ {
|
|
||||||
if seq1.keys[i] != seq2.keys[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return e1 == e2
|
|
||||||
}
|
|
||||||
|
|
||||||
// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
|
// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
|
||||||
// Returns true if the keybinding already existed and a possible error
|
// Returns true if the keybinding already existed and a possible error
|
||||||
func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||||
var e error
|
var e error
|
||||||
var parsed map[string]interface{}
|
var parsed map[string]string
|
||||||
|
|
||||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||||
createBindingsIfNotExist(filename)
|
createBindingsIfNotExist(filename)
|
||||||
|
@ -288,33 +181,31 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||||
return false, errors.New("Error reading bindings.json: " + err.Error())
|
return false, errors.New("Error reading bindings.json: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := findEvent(k)
|
key, ok := findEvent(k)
|
||||||
if err != nil {
|
if !ok {
|
||||||
return false, err
|
return false, errors.New("Invalid event " + k)
|
||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
var ev string
|
for ev := range parsed {
|
||||||
for ev = range parsed {
|
if e, ok := findEvent(ev); ok {
|
||||||
if e, err := findEvent(ev); err == nil {
|
if e == key {
|
||||||
if eventsEqual(e, key) {
|
if overwrite {
|
||||||
|
parsed[ev] = v
|
||||||
|
}
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if found {
|
if found && !overwrite {
|
||||||
if overwrite {
|
return true, nil
|
||||||
parsed[ev] = v
|
} else if !found {
|
||||||
} else {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
parsed[k] = v
|
parsed[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
BindKey(k, v, Binder["buffer"])
|
BindKey(k, v)
|
||||||
|
|
||||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||||
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||||
|
@ -325,7 +216,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||||
// UnbindKey removes the binding for a key from the bindings.json file
|
// UnbindKey removes the binding for a key from the bindings.json file
|
||||||
func UnbindKey(k string) error {
|
func UnbindKey(k string) error {
|
||||||
var e error
|
var e error
|
||||||
var parsed map[string]interface{}
|
var parsed map[string]string
|
||||||
|
|
||||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||||
createBindingsIfNotExist(filename)
|
createBindingsIfNotExist(filename)
|
||||||
|
@ -340,30 +231,26 @@ func UnbindKey(k string) error {
|
||||||
return errors.New("Error reading bindings.json: " + err.Error())
|
return errors.New("Error reading bindings.json: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := findEvent(k)
|
key, ok := findEvent(k)
|
||||||
if err != nil {
|
if !ok {
|
||||||
return err
|
return errors.New("Invalid event " + k)
|
||||||
}
|
}
|
||||||
|
|
||||||
for ev := range parsed {
|
for ev := range parsed {
|
||||||
if e, err := findEvent(ev); err == nil {
|
if e, ok := findEvent(ev); ok {
|
||||||
if eventsEqual(e, key) {
|
if e == key {
|
||||||
delete(parsed, ev)
|
delete(parsed, ev)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(k, "\x1b") {
|
defaults := DefaultBindings()
|
||||||
screen.Screen.UnregisterRawSeq(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
defaults := DefaultBindings("buffer")
|
|
||||||
if a, ok := defaults[k]; ok {
|
if a, ok := defaults[k]; ok {
|
||||||
BindKey(k, a, Binder["buffer"])
|
BindKey(k, a)
|
||||||
} else if _, ok := config.Bindings["buffer"][k]; ok {
|
} else if _, ok := config.Bindings[k]; ok {
|
||||||
BufUnmap(key)
|
BufUnmap(key)
|
||||||
delete(config.Bindings["buffer"], k)
|
delete(config.Bindings, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||||
|
@ -373,9 +260,9 @@ func UnbindKey(k string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var mouseEvents = map[string]tcell.ButtonMask{
|
var mouseEvents = map[string]tcell.ButtonMask{
|
||||||
"MouseLeft": tcell.ButtonPrimary,
|
"MouseLeft": tcell.Button1,
|
||||||
"MouseMiddle": tcell.ButtonMiddle,
|
"MouseMiddle": tcell.Button2,
|
||||||
"MouseRight": tcell.ButtonSecondary,
|
"MouseRight": tcell.Button3,
|
||||||
"MouseWheelUp": tcell.WheelUp,
|
"MouseWheelUp": tcell.WheelUp,
|
||||||
"MouseWheelDown": tcell.WheelDown,
|
"MouseWheelDown": tcell.WheelDown,
|
||||||
"MouseWheelLeft": tcell.WheelLeft,
|
"MouseWheelLeft": tcell.WheelLeft,
|
||||||
|
|
|
@ -12,42 +12,23 @@ import (
|
||||||
"github.com/zyedidia/micro/v2/internal/display"
|
"github.com/zyedidia/micro/v2/internal/display"
|
||||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/tcell"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BufAction interface{}
|
|
||||||
|
|
||||||
// BufKeyAction represents an action bound to a key.
|
|
||||||
type BufKeyAction func(*BufPane) bool
|
type BufKeyAction func(*BufPane) bool
|
||||||
|
|
||||||
// BufMouseAction is an action that must be bound to a mouse event.
|
|
||||||
type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
|
type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
|
||||||
|
|
||||||
// BufBindings stores the bindings for the buffer pane type.
|
var BufKeyBindings map[Event]BufKeyAction
|
||||||
var BufBindings *KeyTree
|
var BufKeyStrings map[Event]string
|
||||||
|
var BufMouseBindings map[MouseEvent]BufMouseAction
|
||||||
// BufKeyActionGeneral makes a general pane action from a BufKeyAction.
|
|
||||||
func BufKeyActionGeneral(a BufKeyAction) PaneKeyAction {
|
|
||||||
return func(p Pane) bool {
|
|
||||||
return a(p.(*BufPane))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufMouseActionGeneral makes a general pane mouse action from a BufKeyAction.
|
|
||||||
func BufMouseActionGeneral(a BufMouseAction) PaneMouseAction {
|
|
||||||
return func(p Pane, me *tcell.EventMouse) bool {
|
|
||||||
return a(p.(*BufPane), me)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
BufBindings = NewKeyTree()
|
BufKeyBindings = make(map[Event]BufKeyAction)
|
||||||
|
BufKeyStrings = make(map[Event]string)
|
||||||
|
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LuaAction makes an action from a lua function. It returns either a BufKeyAction
|
func LuaAction(fn string) func(*BufPane) bool {
|
||||||
// or a BufMouseAction depending on the event type.
|
|
||||||
func LuaAction(fn string, k Event) BufAction {
|
|
||||||
luaFn := strings.Split(fn, ".")
|
luaFn := strings.Split(fn, ".")
|
||||||
if len(luaFn) <= 1 {
|
if len(luaFn) <= 1 {
|
||||||
return nil
|
return nil
|
||||||
|
@ -57,42 +38,23 @@ func LuaAction(fn string, k Event) BufAction {
|
||||||
if pl == nil {
|
if pl == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return func(h *BufPane) bool {
|
||||||
var action BufAction
|
val, err := pl.Call(plFn, luar.New(ulua.L, h))
|
||||||
switch k.(type) {
|
if err != nil {
|
||||||
case KeyEvent, KeySequenceEvent, RawEvent:
|
screen.TermMessage(err)
|
||||||
action = BufKeyAction(func(h *BufPane) bool {
|
}
|
||||||
val, err := pl.Call(plFn, luar.New(ulua.L, h))
|
if v, ok := val.(lua.LBool); !ok {
|
||||||
if err != nil {
|
return false
|
||||||
screen.TermMessage(err)
|
} else {
|
||||||
}
|
return bool(v)
|
||||||
if v, ok := val.(lua.LBool); !ok {
|
}
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return bool(v)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
case MouseEvent:
|
|
||||||
action = BufMouseAction(func(h *BufPane, te *tcell.EventMouse) bool {
|
|
||||||
val, err := pl.Call(plFn, luar.New(ulua.L, h), luar.New(ulua.L, te))
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
if v, ok := val.(lua.LBool); !ok {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return bool(v)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return action
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BufMapEvent maps an event to an action
|
// BufMapKey maps a key event to an action
|
||||||
func BufMapEvent(k Event, action string) {
|
func BufMapKey(k Event, action string) {
|
||||||
config.Bindings["buffer"][k.Name()] = action
|
BufKeyStrings[k] = action
|
||||||
|
var actionfns []func(*BufPane) bool
|
||||||
var actionfns []BufAction
|
|
||||||
var names []string
|
var names []string
|
||||||
var types []byte
|
var types []byte
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
|
@ -113,7 +75,7 @@ func BufMapEvent(k Event, action string) {
|
||||||
action = ""
|
action = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var afn BufAction
|
var afn func(*BufPane) bool
|
||||||
if strings.HasPrefix(a, "command:") {
|
if strings.HasPrefix(a, "command:") {
|
||||||
a = strings.SplitN(a, ":", 2)[1]
|
a = strings.SplitN(a, ":", 2)[1]
|
||||||
afn = CommandAction(a)
|
afn = CommandAction(a)
|
||||||
|
@ -124,7 +86,7 @@ func BufMapEvent(k Event, action string) {
|
||||||
names = append(names, "")
|
names = append(names, "")
|
||||||
} else if strings.HasPrefix(a, "lua:") {
|
} else if strings.HasPrefix(a, "lua:") {
|
||||||
a = strings.SplitN(a, ":", 2)[1]
|
a = strings.SplitN(a, ":", 2)[1]
|
||||||
afn = LuaAction(a, k)
|
afn = LuaAction(a)
|
||||||
if afn == nil {
|
if afn == nil {
|
||||||
screen.TermMessage("Lua Error:", a, "does not exist")
|
screen.TermMessage("Lua Error:", a, "does not exist")
|
||||||
continue
|
continue
|
||||||
|
@ -140,20 +102,16 @@ func BufMapEvent(k Event, action string) {
|
||||||
} else if f, ok := BufKeyActions[a]; ok {
|
} else if f, ok := BufKeyActions[a]; ok {
|
||||||
afn = f
|
afn = f
|
||||||
names = append(names, a)
|
names = append(names, a)
|
||||||
} else if f, ok := BufMouseActions[a]; ok {
|
|
||||||
afn = f
|
|
||||||
names = append(names, a)
|
|
||||||
} else {
|
} else {
|
||||||
screen.TermMessage("Error in bindings: action", a, "does not exist")
|
screen.TermMessage("Error in bindings: action", a, "does not exist")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
actionfns = append(actionfns, afn)
|
actionfns = append(actionfns, afn)
|
||||||
}
|
}
|
||||||
bufAction := func(h *BufPane, te *tcell.EventMouse) bool {
|
BufKeyBindings[k] = func(h *BufPane) bool {
|
||||||
cursors := h.Buf.GetCursors()
|
cursors := h.Buf.GetCursors()
|
||||||
success := true
|
success := true
|
||||||
for i, a := range actionfns {
|
for i, a := range actionfns {
|
||||||
innerSuccess := true
|
|
||||||
for j, c := range cursors {
|
for j, c := range cursors {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
continue
|
continue
|
||||||
|
@ -161,41 +119,37 @@ func BufMapEvent(k Event, action string) {
|
||||||
h.Buf.SetCurCursor(c.Num)
|
h.Buf.SetCurCursor(c.Num)
|
||||||
h.Cursor = c
|
h.Cursor = c
|
||||||
if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
|
if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
|
||||||
innerSuccess = innerSuccess && h.execAction(a, names[i], j, te)
|
success = h.execAction(a, names[i], j)
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if the action changed the current pane, update the reference
|
// if the action changed the current pane, update the reference
|
||||||
h = MainTab().CurPane()
|
h = MainTab().CurPane()
|
||||||
success = innerSuccess
|
|
||||||
if h == nil {
|
|
||||||
// stop, in case the current pane is not a BufPane
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch e := k.(type) {
|
// BufMapMouse maps a mouse event to an action
|
||||||
case KeyEvent, KeySequenceEvent, RawEvent:
|
func BufMapMouse(k MouseEvent, action string) {
|
||||||
BufBindings.RegisterKeyBinding(e, BufKeyActionGeneral(func(h *BufPane) bool {
|
if f, ok := BufMouseActions[action]; ok {
|
||||||
return bufAction(h, nil)
|
BufMouseBindings[k] = f
|
||||||
}))
|
} else {
|
||||||
case MouseEvent:
|
delete(BufMouseBindings, k)
|
||||||
BufBindings.RegisterMouseBinding(e, BufMouseActionGeneral(bufAction))
|
BufMapKey(k, action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BufUnmap unmaps a key or mouse event from any action
|
// BufUnmap unmaps a key or mouse event from any action
|
||||||
func BufUnmap(k Event) {
|
func BufUnmap(k Event) {
|
||||||
// TODO
|
delete(BufKeyBindings, k)
|
||||||
// delete(BufKeyBindings, k)
|
delete(BufKeyStrings, k)
|
||||||
//
|
|
||||||
// switch e := k.(type) {
|
switch e := k.(type) {
|
||||||
// case MouseEvent:
|
case MouseEvent:
|
||||||
// delete(BufMouseBindings, e)
|
delete(BufMouseBindings, e)
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The BufPane connects the buffer and the window
|
// The BufPane connects the buffer and the window
|
||||||
|
@ -206,23 +160,15 @@ func BufUnmap(k Event) {
|
||||||
type BufPane struct {
|
type BufPane struct {
|
||||||
display.BWindow
|
display.BWindow
|
||||||
|
|
||||||
// Buf is the buffer this BufPane views
|
|
||||||
Buf *buffer.Buffer
|
Buf *buffer.Buffer
|
||||||
// Bindings stores the association of key events and actions
|
|
||||||
bindings *KeyTree
|
|
||||||
|
|
||||||
// Cursor is the currently active buffer cursor
|
Cursor *buffer.Cursor // the active cursor
|
||||||
Cursor *buffer.Cursor
|
|
||||||
|
|
||||||
// Since tcell doesn't differentiate between a mouse press event
|
// Since tcell doesn't differentiate between a mouse release event
|
||||||
// and a mouse move event with button pressed (nor between a mouse
|
// and a mouse move event with no keys pressed, we need to keep
|
||||||
// release event and a mouse move event with no buttons pressed),
|
// track of whether or not the mouse was pressed (or not released) last event to determine
|
||||||
// we need to keep track of whether or not the mouse was previously
|
// mouse release events
|
||||||
// pressed, to determine mouse release and mouse drag events.
|
mouseReleased bool
|
||||||
// Moreover, since in case of a release event tcell doesn't tell us
|
|
||||||
// which button was released, we need to keep track of which
|
|
||||||
// (possibly multiple) buttons were pressed previously.
|
|
||||||
mousePressed map[MouseEvent]bool
|
|
||||||
|
|
||||||
// We need to keep track of insert key press toggle
|
// We need to keep track of insert key press toggle
|
||||||
isOverwriteMode bool
|
isOverwriteMode bool
|
||||||
|
@ -245,6 +191,9 @@ type BufPane struct {
|
||||||
// Same here, just to keep track for mouse move events
|
// Same here, just to keep track for mouse move events
|
||||||
tripleClick bool
|
tripleClick bool
|
||||||
|
|
||||||
|
// Last search stores the last successful search for FindNext and FindPrev
|
||||||
|
lastSearch string
|
||||||
|
lastSearchRegex bool
|
||||||
// Should the current multiple cursor selection search based on word or
|
// Should the current multiple cursor selection search based on word or
|
||||||
// based on selection (false for selection, true for word)
|
// based on selection (false for selection, true for word)
|
||||||
multiWord bool
|
multiWord bool
|
||||||
|
@ -254,67 +203,31 @@ type BufPane struct {
|
||||||
|
|
||||||
// remember original location of a search in case the search is canceled
|
// remember original location of a search in case the search is canceled
|
||||||
searchOrig buffer.Loc
|
searchOrig buffer.Loc
|
||||||
|
|
||||||
// The pane may not yet be fully initialized after its creation
|
|
||||||
// since we may not know the window geometry yet. In such case we finish
|
|
||||||
// its initialization a bit later, after the initial resize.
|
|
||||||
initialized bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
|
func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
|
||||||
h := new(BufPane)
|
h := new(BufPane)
|
||||||
h.Buf = buf
|
h.Buf = buf
|
||||||
h.BWindow = win
|
h.BWindow = win
|
||||||
h.tab = tab
|
h.tab = tab
|
||||||
|
|
||||||
h.Cursor = h.Buf.GetActiveCursor()
|
h.Cursor = h.Buf.GetActiveCursor()
|
||||||
h.mousePressed = make(map[MouseEvent]bool)
|
h.mouseReleased = true
|
||||||
|
|
||||||
|
config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
|
||||||
|
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBufPane creates a new buffer pane with the given window.
|
|
||||||
func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
|
|
||||||
h := newBufPane(buf, win, tab)
|
|
||||||
h.finishInitialize()
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBufPaneFromBuf constructs a new pane from the given buffer and automatically
|
|
||||||
// creates a buf window.
|
|
||||||
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
|
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
|
||||||
w := display.NewBufWindow(0, 0, 0, 0, buf)
|
w := display.NewBufWindow(0, 0, 0, 0, buf)
|
||||||
h := newBufPane(buf, w, tab)
|
return NewBufPane(buf, w, tab)
|
||||||
// Postpone finishing initializing the pane until we know the actual geometry
|
|
||||||
// of the buf window.
|
|
||||||
return h
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make sure splitID and tab are set before finishInitialize is called
|
|
||||||
func (h *BufPane) finishInitialize() {
|
|
||||||
h.initialRelocate()
|
|
||||||
h.initialized = true
|
|
||||||
|
|
||||||
err := config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resize resizes the pane
|
|
||||||
func (h *BufPane) Resize(width, height int) {
|
|
||||||
h.BWindow.Resize(width, height)
|
|
||||||
if !h.initialized {
|
|
||||||
h.finishInitialize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTab sets this pane's tab.
|
|
||||||
func (h *BufPane) SetTab(t *Tab) {
|
func (h *BufPane) SetTab(t *Tab) {
|
||||||
h.tab = t
|
h.tab = t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab returns this pane's tab.
|
|
||||||
func (h *BufPane) Tab() *Tab {
|
func (h *BufPane) Tab() *Tab {
|
||||||
return h.tab
|
return h.tab
|
||||||
}
|
}
|
||||||
|
@ -325,96 +238,51 @@ func (h *BufPane) ResizePane(size int) {
|
||||||
h.tab.Resize()
|
h.tab.Resize()
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginCB calls all plugin callbacks with a certain name and displays an
|
// PluginCB calls all plugin callbacks with a certain name and
|
||||||
// error if there is one and returns the aggregate boolean response
|
// displays an error if there is one and returns the aggregrate
|
||||||
|
// boolean response
|
||||||
func (h *BufPane) PluginCB(cb string) bool {
|
func (h *BufPane) PluginCB(cb string) bool {
|
||||||
b, err := config.RunPluginFnBool(h.Buf.Settings, cb, luar.New(ulua.L, h))
|
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginCBRune is the same as PluginCB but also passes a rune to the plugins
|
// PluginCBRune is the same as PluginCB but also passes a rune to
|
||||||
|
// the plugins
|
||||||
func (h *BufPane) PluginCBRune(cb string, r rune) bool {
|
func (h *BufPane) PluginCBRune(cb string, r rune) bool {
|
||||||
b, err := config.RunPluginFnBool(h.Buf.Settings, cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
|
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BufPane) resetMouse() {
|
|
||||||
for me := range h.mousePressed {
|
|
||||||
delete(h.mousePressed, me)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenBuffer opens the given buffer in this pane.
|
|
||||||
func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
|
func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
|
||||||
h.Buf.Close()
|
h.Buf.Close()
|
||||||
h.Buf = b
|
h.Buf = b
|
||||||
h.BWindow.SetBuffer(b)
|
h.BWindow.SetBuffer(b)
|
||||||
h.Cursor = b.GetActiveCursor()
|
h.Cursor = b.GetActiveCursor()
|
||||||
h.Resize(h.GetView().Width, h.GetView().Height)
|
h.Resize(h.GetView().Width, h.GetView().Height)
|
||||||
h.initialRelocate()
|
h.Relocate()
|
||||||
// Set mouseReleased to true because we assume the mouse is not being
|
// Set mouseReleased to true because we assume the mouse is not being pressed when
|
||||||
// pressed when the editor is opened
|
// the editor is opened
|
||||||
h.resetMouse()
|
h.mouseReleased = true
|
||||||
// Set isOverwriteMode to false, because we assume we are in the default
|
// Set isOverwriteMode to false, because we assume we are in the default mode when editor
|
||||||
// mode when editor is opened
|
// is opened
|
||||||
h.isOverwriteMode = false
|
h.isOverwriteMode = false
|
||||||
h.lastClickTime = time.Time{}
|
h.lastClickTime = time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GotoLoc moves the cursor to a new location and adjusts the view accordingly.
|
|
||||||
// Use GotoLoc when the new location may be far away from the current location.
|
|
||||||
func (h *BufPane) GotoLoc(loc buffer.Loc) {
|
|
||||||
sloc := h.SLocFromLoc(loc)
|
|
||||||
d := h.Diff(h.SLocFromLoc(h.Cursor.Loc), sloc)
|
|
||||||
|
|
||||||
h.Cursor.GotoLoc(loc)
|
|
||||||
|
|
||||||
// If the new location is far away from the previous one,
|
|
||||||
// ensure the cursor is at 25% of the window height
|
|
||||||
height := h.BufView().Height
|
|
||||||
if util.Abs(d) >= height {
|
|
||||||
v := h.GetView()
|
|
||||||
v.StartLine = h.Scroll(sloc, -height/4)
|
|
||||||
h.ScrollAdjust()
|
|
||||||
v.StartCol = 0
|
|
||||||
}
|
|
||||||
h.Relocate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *BufPane) initialRelocate() {
|
|
||||||
sloc := h.SLocFromLoc(h.Cursor.Loc)
|
|
||||||
height := h.BufView().Height
|
|
||||||
|
|
||||||
// If the initial cursor location is far away from the beginning
|
|
||||||
// of the buffer, ensure the cursor is at 25% of the window height
|
|
||||||
v := h.GetView()
|
|
||||||
if h.Diff(display.SLoc{0, 0}, sloc) < height {
|
|
||||||
v.StartLine = display.SLoc{0, 0}
|
|
||||||
} else {
|
|
||||||
v.StartLine = h.Scroll(sloc, -height/4)
|
|
||||||
h.ScrollAdjust()
|
|
||||||
}
|
|
||||||
v.StartCol = 0
|
|
||||||
h.Relocate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns this pane's split id.
|
|
||||||
func (h *BufPane) ID() uint64 {
|
func (h *BufPane) ID() uint64 {
|
||||||
return h.splitID
|
return h.splitID
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetID sets the split ID of this pane.
|
|
||||||
func (h *BufPane) SetID(i uint64) {
|
func (h *BufPane) SetID(i uint64) {
|
||||||
h.splitID = i
|
h.splitID = i
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the BufPane's name.
|
|
||||||
func (h *BufPane) Name() string {
|
func (h *BufPane) Name() string {
|
||||||
n := h.Buf.GetName()
|
n := h.Buf.GetName()
|
||||||
if h.Buf.Modified() {
|
if h.Buf.Modified() {
|
||||||
|
@ -423,40 +291,20 @@ func (h *BufPane) Name() string {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReOpen reloads the file opened in the bufpane from disk
|
|
||||||
func (h *BufPane) ReOpen() {
|
|
||||||
h.Buf.ReOpen()
|
|
||||||
h.Relocate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *BufPane) getReloadSetting() string {
|
|
||||||
reloadSetting := h.Buf.Settings["reload"]
|
|
||||||
return reloadSetting.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleEvent executes the tcell event properly
|
// HandleEvent executes the tcell event properly
|
||||||
func (h *BufPane) HandleEvent(event tcell.Event) {
|
func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||||
if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
|
if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
|
||||||
reload := h.getReloadSetting()
|
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
|
||||||
|
if canceled {
|
||||||
|
h.Buf.DisableReload()
|
||||||
|
}
|
||||||
|
if !yes || canceled {
|
||||||
|
h.Buf.UpdateModTime()
|
||||||
|
} else {
|
||||||
|
h.Buf.ReOpen()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if reload == "prompt" {
|
|
||||||
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
|
|
||||||
if canceled {
|
|
||||||
h.Buf.DisableReload()
|
|
||||||
}
|
|
||||||
if !yes || canceled {
|
|
||||||
h.Buf.UpdateModTime()
|
|
||||||
} else {
|
|
||||||
h.ReOpen()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if reload == "auto" {
|
|
||||||
h.ReOpen()
|
|
||||||
} else if reload == "disabled" {
|
|
||||||
h.Buf.DisableReload()
|
|
||||||
} else {
|
|
||||||
InfoBar.Message("Invalid reload setting")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
|
@ -471,7 +319,7 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||||
case *tcell.EventKey:
|
case *tcell.EventKey:
|
||||||
ke := KeyEvent{
|
ke := KeyEvent{
|
||||||
code: e.Key(),
|
code: e.Key(),
|
||||||
mod: metaToAlt(e.Modifiers()),
|
mod: e.Modifiers(),
|
||||||
r: e.Rune(),
|
r: e.Rune(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,37 +328,50 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||||
h.DoRuneInsert(e.Rune())
|
h.DoRuneInsert(e.Rune())
|
||||||
}
|
}
|
||||||
case *tcell.EventMouse:
|
case *tcell.EventMouse:
|
||||||
if e.Buttons() != tcell.ButtonNone {
|
cancel := false
|
||||||
|
switch e.Buttons() {
|
||||||
|
case tcell.Button1:
|
||||||
|
_, my := e.Position()
|
||||||
|
if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
|
||||||
|
cancel = true
|
||||||
|
}
|
||||||
|
case tcell.ButtonNone:
|
||||||
|
// Mouse event with no click
|
||||||
|
if !h.mouseReleased {
|
||||||
|
// Mouse was just released
|
||||||
|
|
||||||
|
// mx, my := e.Position()
|
||||||
|
// mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
|
||||||
|
|
||||||
|
// we could finish the selection based on the release location as described
|
||||||
|
// below but when the mouse click is within the scroll margin this will
|
||||||
|
// cause a scroll and selection even for a simple mouse click which is
|
||||||
|
// not good
|
||||||
|
// for terminals that don't support mouse motion events, selection via
|
||||||
|
// the mouse won't work but this is ok
|
||||||
|
|
||||||
|
// Relocating here isn't really necessary because the cursor will
|
||||||
|
// be in the right place from the last mouse event
|
||||||
|
// However, if we are running in a terminal that doesn't support mouse motion
|
||||||
|
// events, this still allows the user to make selections, except only after they
|
||||||
|
// release the mouse
|
||||||
|
|
||||||
|
// if !h.doubleClick && !h.tripleClick {
|
||||||
|
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||||
|
// }
|
||||||
|
if h.Cursor.HasSelection() {
|
||||||
|
h.Cursor.CopySelection("primary")
|
||||||
|
}
|
||||||
|
h.mouseReleased = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cancel {
|
||||||
me := MouseEvent{
|
me := MouseEvent{
|
||||||
btn: e.Buttons(),
|
btn: e.Buttons(),
|
||||||
mod: metaToAlt(e.Modifiers()),
|
mod: e.Modifiers(),
|
||||||
state: MousePress,
|
|
||||||
}
|
|
||||||
isDrag := len(h.mousePressed) > 0
|
|
||||||
|
|
||||||
if e.Buttons() & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone {
|
|
||||||
h.mousePressed[me] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if isDrag {
|
|
||||||
me.state = MouseDrag
|
|
||||||
}
|
}
|
||||||
h.DoMouseEvent(me, e)
|
h.DoMouseEvent(me, e)
|
||||||
} else {
|
|
||||||
// Mouse event with no click - mouse was just released.
|
|
||||||
// If there were multiple mouse buttons pressed, we don't know which one
|
|
||||||
// was actually released, so we assume they all were released.
|
|
||||||
pressed := len(h.mousePressed) > 0
|
|
||||||
for me := range h.mousePressed {
|
|
||||||
delete(h.mousePressed, me)
|
|
||||||
|
|
||||||
me.state = MouseRelease
|
|
||||||
h.DoMouseEvent(me, e)
|
|
||||||
}
|
|
||||||
if !pressed {
|
|
||||||
// Propagate the mouse release in case the press wasn't for this BufPane
|
|
||||||
Tabs.ResetMouse()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.Buf.MergeCursors()
|
h.Buf.MergeCursors()
|
||||||
|
@ -530,43 +391,18 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||||
InfoBar.ClearGutter()
|
InfoBar.ClearGutter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cursors := h.Buf.GetCursors()
|
|
||||||
for _, c := range cursors {
|
|
||||||
if c.NewTrailingWsY != c.Y && (!c.HasSelection() ||
|
|
||||||
(c.NewTrailingWsY != c.CurSelection[0].Y && c.NewTrailingWsY != c.CurSelection[1].Y)) {
|
|
||||||
c.NewTrailingWsY = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bindings returns the current bindings tree for this buffer.
|
|
||||||
func (h *BufPane) Bindings() *KeyTree {
|
|
||||||
if h.bindings != nil {
|
|
||||||
return h.bindings
|
|
||||||
}
|
|
||||||
return BufBindings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoKeyEvent executes a key event by finding the action it is bound
|
// DoKeyEvent executes a key event by finding the action it is bound
|
||||||
// to and executing it (possibly multiple times for multiple cursors).
|
// to and executing it (possibly multiple times for multiple cursors)
|
||||||
// Returns true if the action was executed OR if there are more keys
|
|
||||||
// remaining to process before executing an action (if this is a key
|
|
||||||
// sequence event). Returns false if no action found.
|
|
||||||
func (h *BufPane) DoKeyEvent(e Event) bool {
|
func (h *BufPane) DoKeyEvent(e Event) bool {
|
||||||
binds := h.Bindings()
|
if action, ok := BufKeyBindings[e]; ok {
|
||||||
action, more := binds.NextEvent(e, nil)
|
return action(h)
|
||||||
if action != nil && !more {
|
|
||||||
action(h)
|
|
||||||
binds.ResetEvents()
|
|
||||||
return true
|
|
||||||
} else if action == nil && !more {
|
|
||||||
binds.ResetEvents()
|
|
||||||
}
|
}
|
||||||
return more
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BufPane) execAction(action BufAction, name string, cursor int, te *tcell.EventMouse) bool {
|
func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
|
||||||
if name != "Autocomplete" && name != "CycleAutocompleteBack" {
|
if name != "Autocomplete" && name != "CycleAutocompleteBack" {
|
||||||
h.Buf.HasSuggestions = false
|
h.Buf.HasSuggestions = false
|
||||||
}
|
}
|
||||||
|
@ -574,17 +410,11 @@ func (h *BufPane) execAction(action BufAction, name string, cursor int, te *tcel
|
||||||
_, isMulti := MultiActions[name]
|
_, isMulti := MultiActions[name]
|
||||||
if (!isMulti && cursor == 0) || isMulti {
|
if (!isMulti && cursor == 0) || isMulti {
|
||||||
if h.PluginCB("pre" + name) {
|
if h.PluginCB("pre" + name) {
|
||||||
var success bool
|
success := action(h)
|
||||||
switch a := action.(type) {
|
|
||||||
case BufKeyAction:
|
|
||||||
success = a(h)
|
|
||||||
case BufMouseAction:
|
|
||||||
success = a(h, te)
|
|
||||||
}
|
|
||||||
success = success && h.PluginCB("on"+name)
|
success = success && h.PluginCB("on"+name)
|
||||||
|
|
||||||
if isMulti {
|
if isMulti {
|
||||||
if recordingMacro {
|
if recording_macro {
|
||||||
if name != "ToggleMacro" && name != "PlayMacro" {
|
if name != "ToggleMacro" && name != "PlayMacro" {
|
||||||
curmacro = append(curmacro, action)
|
curmacro = append(curmacro, action)
|
||||||
}
|
}
|
||||||
|
@ -603,34 +433,22 @@ func (h *BufPane) completeAction(action string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BufPane) HasKeyEvent(e Event) bool {
|
func (h *BufPane) HasKeyEvent(e Event) bool {
|
||||||
// TODO
|
_, ok := BufKeyBindings[e]
|
||||||
return true
|
return ok
|
||||||
// _, ok := BufKeyBindings[e]
|
|
||||||
// return ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoMouseEvent executes a mouse event by finding the action it is bound
|
// DoMouseEvent executes a mouse event by finding the action it is bound
|
||||||
// to and executing it
|
// to and executing it
|
||||||
func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
|
func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
|
||||||
binds := h.Bindings()
|
if action, ok := BufMouseBindings[e]; ok {
|
||||||
action, _ := binds.NextEvent(e, te)
|
if action(h, te) {
|
||||||
if action != nil {
|
h.Relocate()
|
||||||
action(h)
|
}
|
||||||
binds.ResetEvents()
|
|
||||||
return true
|
return true
|
||||||
|
} else if h.HasKeyEvent(e) {
|
||||||
|
return h.DoKeyEvent(e)
|
||||||
}
|
}
|
||||||
// TODO
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// if action, ok := BufMouseBindings[e]; ok {
|
|
||||||
// if action(h, te) {
|
|
||||||
// h.Relocate()
|
|
||||||
// }
|
|
||||||
// return true
|
|
||||||
// } else if h.HasKeyEvent(e) {
|
|
||||||
// return h.DoKeyEvent(e)
|
|
||||||
// }
|
|
||||||
// return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoRuneInsert inserts a given rune into the current buffer
|
// DoRuneInsert inserts a given rune into the current buffer
|
||||||
|
@ -656,7 +474,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
||||||
} else {
|
} else {
|
||||||
h.Buf.Insert(c.Loc, string(r))
|
h.Buf.Insert(c.Loc, string(r))
|
||||||
}
|
}
|
||||||
if recordingMacro {
|
if recording_macro {
|
||||||
curmacro = append(curmacro, r)
|
curmacro = append(curmacro, r)
|
||||||
}
|
}
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
|
@ -664,7 +482,6 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VSplitIndex opens the given buffer in a vertical split on the given side.
|
|
||||||
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
|
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
|
||||||
e := NewBufPaneFromBuf(buf, h.tab)
|
e := NewBufPaneFromBuf(buf, h.tab)
|
||||||
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
|
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
|
||||||
|
@ -673,8 +490,6 @@ func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
|
||||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
// HSplitIndex opens the given buffer in a horizontal split on the given side.
|
|
||||||
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
|
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
|
||||||
e := NewBufPaneFromBuf(buf, h.tab)
|
e := NewBufPaneFromBuf(buf, h.tab)
|
||||||
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
|
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
|
||||||
|
@ -684,27 +499,17 @@ func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
// VSplitBuf opens the given buffer in a new vertical split.
|
|
||||||
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
|
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||||
return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
|
return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HSplitBuf opens the given buffer in a new horizontal split.
|
|
||||||
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
|
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||||
return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
|
return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close this pane.
|
|
||||||
func (h *BufPane) Close() {
|
func (h *BufPane) Close() {
|
||||||
h.Buf.Close()
|
h.Buf.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetActive marks this pane as active.
|
|
||||||
func (h *BufPane) SetActive(b bool) {
|
func (h *BufPane) SetActive(b bool) {
|
||||||
if h.IsActive() == b {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.BWindow.SetActive(b)
|
h.BWindow.SetActive(b)
|
||||||
if b {
|
if b {
|
||||||
// Display any gutter messages for this line
|
// Display any gutter messages for this line
|
||||||
|
@ -720,12 +525,8 @@ func (h *BufPane) SetActive(b bool) {
|
||||||
if none && InfoBar.HasGutter {
|
if none && InfoBar.HasGutter {
|
||||||
InfoBar.ClearGutter()
|
InfoBar.ClearGutter()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := config.RunPluginFn("onSetActive", luar.New(ulua.L, h))
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
|
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
|
||||||
|
@ -746,16 +547,10 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||||
"SelectRight": (*BufPane).SelectRight,
|
"SelectRight": (*BufPane).SelectRight,
|
||||||
"WordRight": (*BufPane).WordRight,
|
"WordRight": (*BufPane).WordRight,
|
||||||
"WordLeft": (*BufPane).WordLeft,
|
"WordLeft": (*BufPane).WordLeft,
|
||||||
"SubWordRight": (*BufPane).SubWordRight,
|
|
||||||
"SubWordLeft": (*BufPane).SubWordLeft,
|
|
||||||
"SelectWordRight": (*BufPane).SelectWordRight,
|
"SelectWordRight": (*BufPane).SelectWordRight,
|
||||||
"SelectWordLeft": (*BufPane).SelectWordLeft,
|
"SelectWordLeft": (*BufPane).SelectWordLeft,
|
||||||
"SelectSubWordRight": (*BufPane).SelectSubWordRight,
|
|
||||||
"SelectSubWordLeft": (*BufPane).SelectSubWordLeft,
|
|
||||||
"DeleteWordRight": (*BufPane).DeleteWordRight,
|
"DeleteWordRight": (*BufPane).DeleteWordRight,
|
||||||
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
|
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
|
||||||
"DeleteSubWordRight": (*BufPane).DeleteSubWordRight,
|
|
||||||
"DeleteSubWordLeft": (*BufPane).DeleteSubWordLeft,
|
|
||||||
"SelectLine": (*BufPane).SelectLine,
|
"SelectLine": (*BufPane).SelectLine,
|
||||||
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
|
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
|
||||||
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
|
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
|
||||||
|
@ -774,8 +569,6 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||||
"FindLiteral": (*BufPane).FindLiteral,
|
"FindLiteral": (*BufPane).FindLiteral,
|
||||||
"FindNext": (*BufPane).FindNext,
|
"FindNext": (*BufPane).FindNext,
|
||||||
"FindPrevious": (*BufPane).FindPrevious,
|
"FindPrevious": (*BufPane).FindPrevious,
|
||||||
"DiffNext": (*BufPane).DiffNext,
|
|
||||||
"DiffPrevious": (*BufPane).DiffPrevious,
|
|
||||||
"Center": (*BufPane).Center,
|
"Center": (*BufPane).Center,
|
||||||
"Undo": (*BufPane).Undo,
|
"Undo": (*BufPane).Undo,
|
||||||
"Redo": (*BufPane).Redo,
|
"Redo": (*BufPane).Redo,
|
||||||
|
@ -813,8 +606,6 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||||
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
|
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
|
||||||
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
|
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
|
||||||
"ToggleRuler": (*BufPane).ToggleRuler,
|
"ToggleRuler": (*BufPane).ToggleRuler,
|
||||||
"ToggleHighlightSearch": (*BufPane).ToggleHighlightSearch,
|
|
||||||
"UnhighlightSearch": (*BufPane).UnhighlightSearch,
|
|
||||||
"ClearStatus": (*BufPane).ClearStatus,
|
"ClearStatus": (*BufPane).ClearStatus,
|
||||||
"ShellMode": (*BufPane).ShellMode,
|
"ShellMode": (*BufPane).ShellMode,
|
||||||
"CommandMode": (*BufPane).CommandMode,
|
"CommandMode": (*BufPane).CommandMode,
|
||||||
|
@ -822,7 +613,6 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||||
"Escape": (*BufPane).Escape,
|
"Escape": (*BufPane).Escape,
|
||||||
"Quit": (*BufPane).Quit,
|
"Quit": (*BufPane).Quit,
|
||||||
"QuitAll": (*BufPane).QuitAll,
|
"QuitAll": (*BufPane).QuitAll,
|
||||||
"ForceQuit": (*BufPane).ForceQuit,
|
|
||||||
"AddTab": (*BufPane).AddTab,
|
"AddTab": (*BufPane).AddTab,
|
||||||
"PreviousTab": (*BufPane).PreviousTab,
|
"PreviousTab": (*BufPane).PreviousTab,
|
||||||
"NextTab": (*BufPane).NextTab,
|
"NextTab": (*BufPane).NextTab,
|
||||||
|
@ -845,8 +635,6 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||||
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
||||||
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
||||||
"JumpLine": (*BufPane).JumpLine,
|
"JumpLine": (*BufPane).JumpLine,
|
||||||
"Deselect": (*BufPane).Deselect,
|
|
||||||
"ClearInfo": (*BufPane).ClearInfo,
|
|
||||||
"None": (*BufPane).None,
|
"None": (*BufPane).None,
|
||||||
|
|
||||||
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
||||||
|
@ -856,8 +644,6 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||||
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
|
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
|
||||||
var BufMouseActions = map[string]BufMouseAction{
|
var BufMouseActions = map[string]BufMouseAction{
|
||||||
"MousePress": (*BufPane).MousePress,
|
"MousePress": (*BufPane).MousePress,
|
||||||
"MouseDrag": (*BufPane).MouseDrag,
|
|
||||||
"MouseRelease": (*BufPane).MouseRelease,
|
|
||||||
"MouseMultiCursor": (*BufPane).MouseMultiCursor,
|
"MouseMultiCursor": (*BufPane).MouseMultiCursor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -882,16 +668,10 @@ var MultiActions = map[string]bool{
|
||||||
"SelectRight": true,
|
"SelectRight": true,
|
||||||
"WordRight": true,
|
"WordRight": true,
|
||||||
"WordLeft": true,
|
"WordLeft": true,
|
||||||
"SubWordRight": true,
|
|
||||||
"SubWordLeft": true,
|
|
||||||
"SelectWordRight": true,
|
"SelectWordRight": true,
|
||||||
"SelectWordLeft": true,
|
"SelectWordLeft": true,
|
||||||
"SelectSubWordRight": true,
|
|
||||||
"SelectSubWordLeft": true,
|
|
||||||
"DeleteWordRight": true,
|
"DeleteWordRight": true,
|
||||||
"DeleteWordLeft": true,
|
"DeleteWordLeft": true,
|
||||||
"DeleteSubWordRight": true,
|
|
||||||
"DeleteSubWordLeft": true,
|
|
||||||
"SelectLine": true,
|
"SelectLine": true,
|
||||||
"SelectToStartOfLine": true,
|
"SelectToStartOfLine": true,
|
||||||
"SelectToStartOfText": true,
|
"SelectToStartOfText": true,
|
||||||
|
@ -906,7 +686,6 @@ var MultiActions = map[string]bool{
|
||||||
"FindNext": true,
|
"FindNext": true,
|
||||||
"FindPrevious": true,
|
"FindPrevious": true,
|
||||||
"CopyLine": true,
|
"CopyLine": true,
|
||||||
"Copy": true,
|
|
||||||
"Cut": true,
|
"Cut": true,
|
||||||
"CutLine": true,
|
"CutLine": true,
|
||||||
"DuplicateLine": true,
|
"DuplicateLine": true,
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
|
|
||||||
shellquote "github.com/kballard/go-shellquote"
|
shellquote "github.com/kballard/go-shellquote"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/shell"
|
"github.com/zyedidia/micro/v2/internal/shell"
|
||||||
|
@ -41,7 +40,6 @@ func InitCommands() {
|
||||||
"unbind": {(*BufPane).UnbindCmd, nil},
|
"unbind": {(*BufPane).UnbindCmd, nil},
|
||||||
"quit": {(*BufPane).QuitCmd, nil},
|
"quit": {(*BufPane).QuitCmd, nil},
|
||||||
"goto": {(*BufPane).GotoCmd, nil},
|
"goto": {(*BufPane).GotoCmd, nil},
|
||||||
"jump": {(*BufPane).JumpCmd, nil},
|
|
||||||
"save": {(*BufPane).SaveCmd, nil},
|
"save": {(*BufPane).SaveCmd, nil},
|
||||||
"replace": {(*BufPane).ReplaceCmd, nil},
|
"replace": {(*BufPane).ReplaceCmd, nil},
|
||||||
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
|
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
|
||||||
|
@ -330,30 +328,13 @@ func (h *BufPane) ToggleLogCmd(args []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadCmd reloads all files (syntax files, colorschemes, plugins...)
|
// ReloadCmd reloads all files (syntax files, colorschemes...)
|
||||||
func (h *BufPane) ReloadCmd(args []string) {
|
func (h *BufPane) ReloadCmd(args []string) {
|
||||||
reloadRuntime(true)
|
ReloadConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadConfig reloads only the configuration
|
|
||||||
func ReloadConfig() {
|
func ReloadConfig() {
|
||||||
reloadRuntime(false)
|
config.InitRuntimeFiles()
|
||||||
}
|
|
||||||
|
|
||||||
func reloadRuntime(reloadPlugins bool) {
|
|
||||||
if reloadPlugins {
|
|
||||||
err := config.RunPluginFn("deinit")
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.InitRuntimeFiles(true)
|
|
||||||
|
|
||||||
if reloadPlugins {
|
|
||||||
config.InitPlugins()
|
|
||||||
}
|
|
||||||
|
|
||||||
err := config.ReadSettings()
|
err := config.ReadSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
|
@ -362,36 +343,14 @@ func reloadRuntime(reloadPlugins bool) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if reloadPlugins {
|
|
||||||
err = config.LoadAllPlugins()
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InitBindings()
|
InitBindings()
|
||||||
InitCommands()
|
InitCommands()
|
||||||
|
|
||||||
if reloadPlugins {
|
|
||||||
err = config.RunPluginFn("preinit")
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
err = config.RunPluginFn("init")
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
err = config.RunPluginFn("postinit")
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = config.InitColorscheme()
|
err = config.InitColorscheme()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range buffer.OpenBuffers {
|
for _, b := range buffer.OpenBuffers {
|
||||||
b.UpdateRules()
|
b.UpdateRules()
|
||||||
}
|
}
|
||||||
|
@ -403,24 +362,22 @@ func (h *BufPane) ReopenCmd(args []string) {
|
||||||
InfoBar.YNPrompt("Save file before reopen?", func(yes, canceled bool) {
|
InfoBar.YNPrompt("Save file before reopen?", func(yes, canceled bool) {
|
||||||
if !canceled && yes {
|
if !canceled && yes {
|
||||||
h.Save()
|
h.Save()
|
||||||
h.ReOpen()
|
h.Buf.ReOpen()
|
||||||
} else if !canceled {
|
} else if !canceled {
|
||||||
h.ReOpen()
|
h.Buf.ReOpen()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
h.ReOpen()
|
h.Buf.ReOpen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BufPane) openHelp(page string) error {
|
func (h *BufPane) openHelp(page string) error {
|
||||||
if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {
|
if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {
|
||||||
return errors.New(fmt.Sprintf("Unable to load help text for %s: %v", page, err))
|
return errors.New(fmt.Sprint("Unable to load help text", page, "\n", err))
|
||||||
} else {
|
} else {
|
||||||
helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp)
|
helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp)
|
||||||
helpBuffer.SetName("Help " + page)
|
helpBuffer.SetName("Help " + page)
|
||||||
helpBuffer.SetOptionNative("hltaberrors", false)
|
|
||||||
helpBuffer.SetOptionNative("hltrailingws", false)
|
|
||||||
|
|
||||||
if h.Buf.Type == buffer.BTHelp {
|
if h.Buf.Type == buffer.BTHelp {
|
||||||
h.OpenBuffer(helpBuffer)
|
h.OpenBuffer(helpBuffer)
|
||||||
|
@ -513,61 +470,55 @@ func (h *BufPane) NewTabCmd(args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||||
// check for local option first...
|
local := false
|
||||||
for _, s := range config.LocalSettings {
|
for _, s := range config.LocalSettings {
|
||||||
if s == option {
|
if s == option {
|
||||||
MainTab().CurPane().Buf.SetOptionNative(option, nativeValue)
|
local = true
|
||||||
return nil
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...if it's not local continue with the globals
|
if !local {
|
||||||
config.GlobalSettings[option] = nativeValue
|
config.GlobalSettings[option] = nativeValue
|
||||||
config.ModifiedSettings[option] = true
|
config.ModifiedSettings[option] = true
|
||||||
delete(config.VolatileSettings, option)
|
|
||||||
|
|
||||||
if option == "colorscheme" {
|
if option == "colorscheme" {
|
||||||
// LoadSyntaxFiles()
|
// LoadSyntaxFiles()
|
||||||
config.InitColorscheme()
|
config.InitColorscheme()
|
||||||
for _, b := range buffer.OpenBuffers {
|
for _, b := range buffer.OpenBuffers {
|
||||||
b.UpdateRules()
|
b.UpdateRules()
|
||||||
}
|
}
|
||||||
} else if option == "infobar" || option == "keymenu" {
|
} else if option == "infobar" || option == "keymenu" {
|
||||||
Tabs.Resize()
|
Tabs.Resize()
|
||||||
} else if option == "mouse" {
|
} else if option == "mouse" {
|
||||||
if !nativeValue.(bool) {
|
if !nativeValue.(bool) {
|
||||||
screen.Screen.DisableMouse()
|
screen.Screen.DisableMouse()
|
||||||
|
} else {
|
||||||
|
screen.Screen.EnableMouse()
|
||||||
|
}
|
||||||
|
} else if option == "autosave" {
|
||||||
|
if nativeValue.(float64) > 0 {
|
||||||
|
config.SetAutoTime(int(nativeValue.(float64)))
|
||||||
|
config.StartAutoSave()
|
||||||
|
} else {
|
||||||
|
config.SetAutoTime(0)
|
||||||
|
}
|
||||||
|
} else if option == "paste" {
|
||||||
|
screen.Screen.SetPaste(nativeValue.(bool))
|
||||||
} else {
|
} else {
|
||||||
screen.Screen.EnableMouse()
|
for _, pl := range config.Plugins {
|
||||||
}
|
if option == pl.Name {
|
||||||
} else if option == "autosave" {
|
if nativeValue.(bool) && !pl.Loaded {
|
||||||
if nativeValue.(float64) > 0 {
|
pl.Load()
|
||||||
config.SetAutoTime(int(nativeValue.(float64)))
|
_, err := pl.Call("init")
|
||||||
config.StartAutoSave()
|
if err != nil && err != config.ErrNoSuchFunction {
|
||||||
} else {
|
screen.TermMessage(err)
|
||||||
config.SetAutoTime(0)
|
}
|
||||||
}
|
} else if !nativeValue.(bool) && pl.Loaded {
|
||||||
} else if option == "paste" {
|
_, err := pl.Call("deinit")
|
||||||
screen.Screen.SetPaste(nativeValue.(bool))
|
if err != nil && err != config.ErrNoSuchFunction {
|
||||||
} else if option == "clipboard" {
|
screen.TermMessage(err)
|
||||||
m := clipboard.SetMethod(nativeValue.(string))
|
}
|
||||||
err := clipboard.Initialize(m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, pl := range config.Plugins {
|
|
||||||
if option == pl.Name {
|
|
||||||
if nativeValue.(bool) && !pl.Loaded {
|
|
||||||
pl.Load()
|
|
||||||
_, err := pl.Call("init")
|
|
||||||
if err != nil && err != config.ErrNoSuchFunction {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
} else if !nativeValue.(bool) && pl.Loaded {
|
|
||||||
_, err := pl.Call("deinit")
|
|
||||||
if err != nil && err != config.ErrNoSuchFunction {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -676,11 +627,6 @@ func (h *BufPane) ShowCmd(args []string) {
|
||||||
InfoBar.Message(option)
|
InfoBar.Message(option)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeyArg(arg string) string {
|
|
||||||
// If this is a raw escape sequence, convert it to its raw byte form
|
|
||||||
return strings.ReplaceAll(arg, "\\x1b", "\x1b")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowKeyCmd displays the action that a key is bound to
|
// ShowKeyCmd displays the action that a key is bound to
|
||||||
func (h *BufPane) ShowKeyCmd(args []string) {
|
func (h *BufPane) ShowKeyCmd(args []string) {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
|
@ -688,12 +634,7 @@ func (h *BufPane) ShowKeyCmd(args []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
event, err := findEvent(parseKeyArg(args[0]))
|
if action, ok := config.Bindings[args[0]]; ok {
|
||||||
if err != nil {
|
|
||||||
InfoBar.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if action, ok := config.Bindings["buffer"][event.Name()]; ok {
|
|
||||||
InfoBar.Message(action)
|
InfoBar.Message(action)
|
||||||
} else {
|
} else {
|
||||||
InfoBar.Message(args[0], " has no binding")
|
InfoBar.Message(args[0], " has no binding")
|
||||||
|
@ -707,7 +648,7 @@ func (h *BufPane) BindCmd(args []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := TryBindKey(parseKeyArg(args[0]), args[1], true)
|
_, err := TryBindKey(args[0], args[1], true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -720,7 +661,7 @@ func (h *BufPane) UnbindCmd(args []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := UnbindKey(parseKeyArg(args[0]))
|
err := UnbindKey(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -748,65 +689,42 @@ func (h *BufPane) QuitCmd(args []string) {
|
||||||
// position in the buffer
|
// position in the buffer
|
||||||
// For example: `goto line`, or `goto line:col`
|
// For example: `goto line`, or `goto line:col`
|
||||||
func (h *BufPane) GotoCmd(args []string) {
|
func (h *BufPane) GotoCmd(args []string) {
|
||||||
line, col, err := h.parseLineCol(args)
|
|
||||||
if err != nil {
|
|
||||||
InfoBar.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if line < 0 {
|
|
||||||
line = h.Buf.LinesNum() + 1 + line
|
|
||||||
}
|
|
||||||
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
|
|
||||||
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
|
|
||||||
|
|
||||||
h.RemoveAllMultiCursors()
|
|
||||||
h.GotoLoc(buffer.Loc{col, line})
|
|
||||||
}
|
|
||||||
|
|
||||||
// JumpCmd is a command that will send the cursor to a certain relative
|
|
||||||
// position in the buffer
|
|
||||||
// For example: `jump line`, `jump -line`, or `jump -line:col`
|
|
||||||
func (h *BufPane) JumpCmd(args []string) {
|
|
||||||
line, col, err := h.parseLineCol(args)
|
|
||||||
if err != nil {
|
|
||||||
InfoBar.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
line = h.Buf.GetActiveCursor().Y + 1 + line
|
|
||||||
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
|
|
||||||
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
|
|
||||||
|
|
||||||
h.RemoveAllMultiCursors()
|
|
||||||
h.GotoLoc(buffer.Loc{col, line})
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseLineCol is a helper to parse the input of GotoCmd and JumpCmd
|
|
||||||
func (h *BufPane) parseLineCol(args []string) (line int, col int, err error) {
|
|
||||||
if len(args) <= 0 {
|
if len(args) <= 0 {
|
||||||
return 0, 0, errors.New("Not enough arguments")
|
InfoBar.Error("Not enough arguments")
|
||||||
}
|
|
||||||
|
|
||||||
line, col = 0, 0
|
|
||||||
if strings.Contains(args[0], ":") {
|
|
||||||
parts := strings.SplitN(args[0], ":", 2)
|
|
||||||
line, err = strconv.Atoi(parts[0])
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
col, err = strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
line, err = strconv.Atoi(args[0])
|
h.RemoveAllMultiCursors()
|
||||||
if err != nil {
|
if strings.Contains(args[0], ":") {
|
||||||
return 0, 0, err
|
parts := strings.SplitN(args[0], ":", 2)
|
||||||
|
line, err := strconv.Atoi(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
InfoBar.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
col, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
InfoBar.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if line < 0 {
|
||||||
|
line = h.Buf.LinesNum() + 1 + line
|
||||||
|
}
|
||||||
|
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
|
||||||
|
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
|
||||||
|
h.Cursor.GotoLoc(buffer.Loc{col, line})
|
||||||
|
} else {
|
||||||
|
line, err := strconv.Atoi(args[0])
|
||||||
|
if err != nil {
|
||||||
|
InfoBar.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if line < 0 {
|
||||||
|
line = h.Buf.LinesNum() + 1 + line
|
||||||
|
}
|
||||||
|
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
|
||||||
|
h.Cursor.GotoLoc(buffer.Loc{0, line})
|
||||||
}
|
}
|
||||||
|
h.Relocate()
|
||||||
}
|
}
|
||||||
|
|
||||||
return line, col, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveCmd saves the buffer optionally with an argument file name
|
// SaveCmd saves the buffer optionally with an argument file name
|
||||||
|
@ -881,16 +799,16 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||||
end = h.Cursor.CurSelection[1]
|
end = h.Cursor.CurSelection[1]
|
||||||
}
|
}
|
||||||
if all {
|
if all {
|
||||||
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace, !noRegex)
|
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace)
|
||||||
} else {
|
} else {
|
||||||
inRange := func(l buffer.Loc) bool {
|
inRange := func(l buffer.Loc) bool {
|
||||||
return l.GreaterEqual(start) && l.LessEqual(end)
|
return l.GreaterEqual(start) && l.LessEqual(end)
|
||||||
}
|
}
|
||||||
|
|
||||||
searchLoc := h.Cursor.Loc
|
searchLoc := start
|
||||||
var doReplacement func()
|
var doReplacement func()
|
||||||
doReplacement = func() {
|
doReplacement = func() {
|
||||||
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, true)
|
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, !noRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -898,30 +816,27 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||||
if !found || !inRange(locs[0]) || !inRange(locs[1]) {
|
if !found || !inRange(locs[0]) || !inRange(locs[1]) {
|
||||||
h.Cursor.ResetSelection()
|
h.Cursor.ResetSelection()
|
||||||
h.Buf.RelocateCursors()
|
h.Buf.RelocateCursors()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Cursor.SetSelectionStart(locs[0])
|
h.Cursor.SetSelectionStart(locs[0])
|
||||||
h.Cursor.SetSelectionEnd(locs[1])
|
h.Cursor.SetSelectionEnd(locs[1])
|
||||||
h.GotoLoc(locs[0])
|
h.Cursor.GotoLoc(locs[0])
|
||||||
h.Buf.LastSearch = search
|
|
||||||
h.Buf.LastSearchRegex = true
|
h.Relocate()
|
||||||
h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
|
|
||||||
|
|
||||||
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
|
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
|
||||||
if !canceled && yes {
|
if !canceled && yes {
|
||||||
_, nrunes := h.Buf.ReplaceRegex(locs[0], locs[1], regex, replace, !noRegex)
|
_, nrunes := h.Buf.ReplaceRegex(locs[0], locs[1], regex, replace)
|
||||||
|
|
||||||
searchLoc = locs[0]
|
searchLoc = locs[0]
|
||||||
searchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf)
|
searchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf)
|
||||||
if end.Y == locs[1].Y {
|
end.Move(nrunes, h.Buf)
|
||||||
end = end.Move(nrunes, h.Buf)
|
|
||||||
}
|
|
||||||
h.Cursor.Loc = searchLoc
|
h.Cursor.Loc = searchLoc
|
||||||
nreplaced++
|
nreplaced++
|
||||||
} else if !canceled && !yes {
|
} else if !canceled && !yes {
|
||||||
searchLoc = locs[1]
|
searchLoc = locs[0]
|
||||||
|
searchLoc.X += util.CharacterCount(replace)
|
||||||
} else if canceled {
|
} else if canceled {
|
||||||
h.Cursor.ResetSelection()
|
h.Cursor.ResetSelection()
|
||||||
h.Buf.RelocateCursors()
|
h.Buf.RelocateCursors()
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
package action
|
|
||||||
|
|
||||||
var termdefaults = map[string]string{
|
|
||||||
"<Ctrl-q><Ctrl-q>": "Exit",
|
|
||||||
"<Ctrl-e><Ctrl-e>": "CommandMode",
|
|
||||||
"<Ctrl-w><Ctrl-w>": "NextSplit",
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultBindings returns a map containing micro's default keybindings
|
|
||||||
func DefaultBindings(pane string) map[string]string {
|
|
||||||
switch pane {
|
|
||||||
case "command":
|
|
||||||
return infodefaults
|
|
||||||
case "buffer":
|
|
||||||
return bufdefaults
|
|
||||||
case "terminal":
|
|
||||||
return termdefaults
|
|
||||||
default:
|
|
||||||
return map[string]string{}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,186 +1,107 @@
|
||||||
package action
|
package action
|
||||||
|
|
||||||
var bufdefaults = map[string]string{
|
// DefaultBindings returns a map containing micro's default keybindings
|
||||||
"Up": "CursorUp",
|
func DefaultBindings() map[string]string {
|
||||||
"Down": "CursorDown",
|
return map[string]string{
|
||||||
"Right": "CursorRight",
|
"Up": "CursorUp",
|
||||||
"Left": "CursorLeft",
|
"Down": "CursorDown",
|
||||||
"ShiftUp": "SelectUp",
|
"Right": "CursorRight",
|
||||||
"ShiftDown": "SelectDown",
|
"Left": "CursorLeft",
|
||||||
"ShiftLeft": "SelectLeft",
|
"ShiftUp": "SelectUp",
|
||||||
"ShiftRight": "SelectRight",
|
"ShiftDown": "SelectDown",
|
||||||
"AltLeft": "WordLeft",
|
"ShiftLeft": "SelectLeft",
|
||||||
"AltRight": "WordRight",
|
"ShiftRight": "SelectRight",
|
||||||
"AltUp": "MoveLinesUp",
|
"AltLeft": "WordLeft",
|
||||||
"AltDown": "MoveLinesDown",
|
"AltRight": "WordRight",
|
||||||
"AltShiftRight": "SelectWordRight",
|
"AltUp": "MoveLinesUp",
|
||||||
"AltShiftLeft": "SelectWordLeft",
|
"AltDown": "MoveLinesDown",
|
||||||
"CtrlLeft": "StartOfTextToggle",
|
"AltShiftRight": "SelectWordRight",
|
||||||
"CtrlRight": "EndOfLine",
|
"AltShiftLeft": "SelectWordLeft",
|
||||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
"CtrlLeft": "StartOfTextToggle",
|
||||||
"ShiftHome": "SelectToStartOfTextToggle",
|
"CtrlRight": "EndOfLine",
|
||||||
"CtrlShiftRight": "SelectToEndOfLine",
|
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||||
"ShiftEnd": "SelectToEndOfLine",
|
"ShiftHome": "SelectToStartOfTextToggle",
|
||||||
"CtrlUp": "CursorStart",
|
"CtrlShiftRight": "SelectToEndOfLine",
|
||||||
"CtrlDown": "CursorEnd",
|
"ShiftEnd": "SelectToEndOfLine",
|
||||||
"CtrlShiftUp": "SelectToStart",
|
"CtrlUp": "CursorStart",
|
||||||
"CtrlShiftDown": "SelectToEnd",
|
"CtrlDown": "CursorEnd",
|
||||||
"Alt-{": "ParagraphPrevious",
|
"CtrlShiftUp": "SelectToStart",
|
||||||
"Alt-}": "ParagraphNext",
|
"CtrlShiftDown": "SelectToEnd",
|
||||||
"Enter": "InsertNewline",
|
"Alt-{": "ParagraphPrevious",
|
||||||
"CtrlH": "Backspace",
|
"Alt-}": "ParagraphNext",
|
||||||
"Backspace": "Backspace",
|
"Enter": "InsertNewline",
|
||||||
"OldBackspace": "Backspace",
|
"CtrlH": "Backspace",
|
||||||
"Alt-CtrlH": "DeleteWordLeft",
|
"Backspace": "Backspace",
|
||||||
"Alt-Backspace": "DeleteWordLeft",
|
"Alt-CtrlH": "DeleteWordLeft",
|
||||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
"Alt-Backspace": "DeleteWordLeft",
|
||||||
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||||
"Ctrl-o": "OpenFile",
|
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
||||||
"Ctrl-s": "Save",
|
"CtrlO": "OpenFile",
|
||||||
"Ctrl-f": "Find",
|
"CtrlS": "Save",
|
||||||
"Alt-F": "FindLiteral",
|
"CtrlF": "Find",
|
||||||
"Ctrl-n": "FindNext",
|
"CtrlN": "FindNext",
|
||||||
"Ctrl-p": "FindPrevious",
|
"CtrlP": "FindPrevious",
|
||||||
"Alt-[": "DiffPrevious|CursorStart",
|
"CtrlZ": "Undo",
|
||||||
"Alt-]": "DiffNext|CursorEnd",
|
"CtrlY": "Redo",
|
||||||
"Ctrl-z": "Undo",
|
"CtrlC": "CopyLine|Copy",
|
||||||
"Ctrl-y": "Redo",
|
"CtrlX": "Cut",
|
||||||
"Ctrl-c": "CopyLine|Copy",
|
"CtrlK": "CutLine",
|
||||||
"Ctrl-x": "Cut",
|
"CtrlD": "DuplicateLine",
|
||||||
"Ctrl-k": "CutLine",
|
"CtrlV": "Paste",
|
||||||
"Ctrl-d": "DuplicateLine",
|
"CtrlA": "SelectAll",
|
||||||
"Ctrl-v": "Paste",
|
"CtrlT": "AddTab",
|
||||||
"Ctrl-a": "SelectAll",
|
"Alt,": "PreviousTab",
|
||||||
"Ctrl-t": "AddTab",
|
"Alt.": "NextTab",
|
||||||
"Alt-,": "PreviousTab",
|
"Home": "StartOfTextToggle",
|
||||||
"Alt-.": "NextTab",
|
"End": "EndOfLine",
|
||||||
"Home": "StartOfTextToggle",
|
"CtrlHome": "CursorStart",
|
||||||
"End": "EndOfLine",
|
"CtrlEnd": "CursorEnd",
|
||||||
"CtrlHome": "CursorStart",
|
"PageUp": "CursorPageUp",
|
||||||
"CtrlEnd": "CursorEnd",
|
"PageDown": "CursorPageDown",
|
||||||
"PageUp": "CursorPageUp",
|
"CtrlPageUp": "PreviousTab",
|
||||||
"PageDown": "CursorPageDown",
|
"CtrlPageDown": "NextTab",
|
||||||
"CtrlPageUp": "PreviousTab",
|
"CtrlG": "ToggleHelp",
|
||||||
"CtrlPageDown": "NextTab",
|
"Alt-g": "ToggleKeyMenu",
|
||||||
"Ctrl-g": "ToggleHelp",
|
"CtrlR": "ToggleRuler",
|
||||||
"Alt-g": "ToggleKeyMenu",
|
"CtrlL": "command-edit:goto ",
|
||||||
"Ctrl-r": "ToggleRuler",
|
"Delete": "Delete",
|
||||||
"Ctrl-l": "command-edit:goto ",
|
"CtrlB": "ShellMode",
|
||||||
"Delete": "Delete",
|
"CtrlQ": "Quit",
|
||||||
"Ctrl-b": "ShellMode",
|
"CtrlE": "CommandMode",
|
||||||
"Ctrl-q": "Quit",
|
"CtrlW": "NextSplit",
|
||||||
"Ctrl-e": "CommandMode",
|
"CtrlU": "ToggleMacro",
|
||||||
"Ctrl-w": "NextSplit",
|
"CtrlJ": "PlayMacro",
|
||||||
"Ctrl-u": "ToggleMacro",
|
"Insert": "ToggleOverwriteMode",
|
||||||
"Ctrl-j": "PlayMacro",
|
|
||||||
"Insert": "ToggleOverwriteMode",
|
|
||||||
|
|
||||||
// Emacs-style keybindings
|
// Emacs-style keybindings
|
||||||
"Alt-f": "WordRight",
|
"Alt-f": "WordRight",
|
||||||
"Alt-b": "WordLeft",
|
"Alt-b": "WordLeft",
|
||||||
"Alt-a": "StartOfText",
|
"Alt-a": "StartOfText",
|
||||||
"Alt-e": "EndOfLine",
|
"Alt-e": "EndOfLine",
|
||||||
// "Alt-p": "CursorUp",
|
// "Alt-p": "CursorUp",
|
||||||
// "Alt-n": "CursorDown",
|
// "Alt-n": "CursorDown",
|
||||||
|
|
||||||
// Integration with file managers
|
// Integration with file managers
|
||||||
"F2": "Save",
|
"F2": "Save",
|
||||||
"F3": "Find",
|
"F3": "Find",
|
||||||
"F4": "Quit",
|
"F4": "Quit",
|
||||||
"F7": "Find",
|
"F7": "Find",
|
||||||
"F10": "Quit",
|
"F10": "Quit",
|
||||||
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
|
"Esc": "Escape",
|
||||||
|
|
||||||
// Mouse bindings
|
// Mouse bindings
|
||||||
"MouseWheelUp": "ScrollUp",
|
"MouseWheelUp": "ScrollUp",
|
||||||
"MouseWheelDown": "ScrollDown",
|
"MouseWheelDown": "ScrollDown",
|
||||||
"MouseLeft": "MousePress",
|
"MouseLeft": "MousePress",
|
||||||
"MouseLeftDrag": "MouseDrag",
|
"MouseMiddle": "PastePrimary",
|
||||||
"MouseLeftRelease": "MouseRelease",
|
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||||
"MouseMiddle": "PastePrimary",
|
|
||||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
|
||||||
|
|
||||||
"Alt-n": "SpawnMultiCursor",
|
"Alt-n": "SpawnMultiCursor",
|
||||||
"AltShiftUp": "SpawnMultiCursorUp",
|
"AltShiftUp": "SpawnMultiCursorUp",
|
||||||
"AltShiftDown": "SpawnMultiCursorDown",
|
"AltShiftDown": "SpawnMultiCursorDown",
|
||||||
"Alt-m": "SpawnMultiCursorSelect",
|
"Alt-m": "SpawnMultiCursorSelect",
|
||||||
"Alt-p": "RemoveMultiCursor",
|
"Alt-p": "RemoveMultiCursor",
|
||||||
"Alt-c": "RemoveAllMultiCursors",
|
"Alt-c": "RemoveAllMultiCursors",
|
||||||
"Alt-x": "SkipMultiCursor",
|
"Alt-x": "SkipMultiCursor",
|
||||||
}
|
}
|
||||||
|
|
||||||
var infodefaults = map[string]string{
|
|
||||||
"Up": "HistoryUp",
|
|
||||||
"Down": "HistoryDown",
|
|
||||||
"Right": "CursorRight",
|
|
||||||
"Left": "CursorLeft",
|
|
||||||
"ShiftUp": "SelectUp",
|
|
||||||
"ShiftDown": "SelectDown",
|
|
||||||
"ShiftLeft": "SelectLeft",
|
|
||||||
"ShiftRight": "SelectRight",
|
|
||||||
"AltLeft": "WordLeft",
|
|
||||||
"AltRight": "WordRight",
|
|
||||||
"AltUp": "CursorStart",
|
|
||||||
"AltDown": "CursorEnd",
|
|
||||||
"AltShiftRight": "SelectWordRight",
|
|
||||||
"AltShiftLeft": "SelectWordLeft",
|
|
||||||
"CtrlLeft": "StartOfTextToggle",
|
|
||||||
"CtrlRight": "EndOfLine",
|
|
||||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
|
||||||
"ShiftHome": "SelectToStartOfTextToggle",
|
|
||||||
"CtrlShiftRight": "SelectToEndOfLine",
|
|
||||||
"ShiftEnd": "SelectToEndOfLine",
|
|
||||||
"CtrlUp": "CursorStart",
|
|
||||||
"CtrlDown": "CursorEnd",
|
|
||||||
"CtrlShiftUp": "SelectToStart",
|
|
||||||
"CtrlShiftDown": "SelectToEnd",
|
|
||||||
"Enter": "ExecuteCommand",
|
|
||||||
"CtrlH": "Backspace",
|
|
||||||
"Backspace": "Backspace",
|
|
||||||
"OldBackspace": "Backspace",
|
|
||||||
"Alt-CtrlH": "DeleteWordLeft",
|
|
||||||
"Alt-Backspace": "DeleteWordLeft",
|
|
||||||
"Tab": "CommandComplete",
|
|
||||||
"Backtab": "CycleAutocompleteBack",
|
|
||||||
"Ctrl-z": "Undo",
|
|
||||||
"Ctrl-y": "Redo",
|
|
||||||
"Ctrl-c": "CopyLine|Copy",
|
|
||||||
"Ctrl-x": "Cut",
|
|
||||||
"Ctrl-k": "CutLine",
|
|
||||||
"Ctrl-v": "Paste",
|
|
||||||
"Home": "StartOfTextToggle",
|
|
||||||
"End": "EndOfLine",
|
|
||||||
"CtrlHome": "CursorStart",
|
|
||||||
"CtrlEnd": "CursorEnd",
|
|
||||||
"Delete": "Delete",
|
|
||||||
"Ctrl-q": "AbortCommand",
|
|
||||||
"Ctrl-e": "EndOfLine",
|
|
||||||
"Ctrl-a": "StartOfLine",
|
|
||||||
"Ctrl-w": "DeleteWordLeft",
|
|
||||||
"Insert": "ToggleOverwriteMode",
|
|
||||||
"Ctrl-b": "WordLeft",
|
|
||||||
"Ctrl-f": "WordRight",
|
|
||||||
"Ctrl-d": "DeleteWordLeft",
|
|
||||||
"Ctrl-m": "ExecuteCommand",
|
|
||||||
"Ctrl-n": "HistoryDown",
|
|
||||||
"Ctrl-p": "HistoryUp",
|
|
||||||
"Ctrl-u": "SelectToStart",
|
|
||||||
|
|
||||||
// Emacs-style keybindings
|
|
||||||
"Alt-f": "WordRight",
|
|
||||||
"Alt-b": "WordLeft",
|
|
||||||
"Alt-a": "StartOfText",
|
|
||||||
"Alt-e": "EndOfLine",
|
|
||||||
|
|
||||||
// Integration with file managers
|
|
||||||
"F10": "AbortCommand",
|
|
||||||
"Esc": "AbortCommand",
|
|
||||||
|
|
||||||
// Mouse bindings
|
|
||||||
"MouseWheelUp": "HistoryUp",
|
|
||||||
"MouseWheelDown": "HistoryDown",
|
|
||||||
"MouseLeft": "MousePress",
|
|
||||||
"MouseLeftDrag": "MouseDrag",
|
|
||||||
"MouseLeftRelease": "MouseRelease",
|
|
||||||
"MouseMiddle": "PastePrimary",
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,189 +1,109 @@
|
||||||
//go:build !darwin
|
|
||||||
// +build !darwin
|
// +build !darwin
|
||||||
|
|
||||||
package action
|
package action
|
||||||
|
|
||||||
var bufdefaults = map[string]string{
|
// DefaultBindings returns a map containing micro's default keybindings
|
||||||
"Up": "CursorUp",
|
func DefaultBindings() map[string]string {
|
||||||
"Down": "CursorDown",
|
return map[string]string{
|
||||||
"Right": "CursorRight",
|
"Up": "CursorUp",
|
||||||
"Left": "CursorLeft",
|
"Down": "CursorDown",
|
||||||
"ShiftUp": "SelectUp",
|
"Right": "CursorRight",
|
||||||
"ShiftDown": "SelectDown",
|
"Left": "CursorLeft",
|
||||||
"ShiftLeft": "SelectLeft",
|
"ShiftUp": "SelectUp",
|
||||||
"ShiftRight": "SelectRight",
|
"ShiftDown": "SelectDown",
|
||||||
"CtrlLeft": "WordLeft",
|
"ShiftLeft": "SelectLeft",
|
||||||
"CtrlRight": "WordRight",
|
"ShiftRight": "SelectRight",
|
||||||
"AltUp": "MoveLinesUp",
|
"CtrlLeft": "WordLeft",
|
||||||
"AltDown": "MoveLinesDown",
|
"CtrlRight": "WordRight",
|
||||||
"CtrlShiftRight": "SelectWordRight",
|
"AltUp": "MoveLinesUp",
|
||||||
"CtrlShiftLeft": "SelectWordLeft",
|
"AltDown": "MoveLinesDown",
|
||||||
"AltLeft": "StartOfTextToggle",
|
"CtrlShiftRight": "SelectWordRight",
|
||||||
"AltRight": "EndOfLine",
|
"CtrlShiftLeft": "SelectWordLeft",
|
||||||
"AltShiftLeft": "SelectToStartOfTextToggle",
|
"AltLeft": "StartOfTextToggle",
|
||||||
"ShiftHome": "SelectToStartOfTextToggle",
|
"AltRight": "EndOfLine",
|
||||||
"AltShiftRight": "SelectToEndOfLine",
|
"AltShiftLeft": "SelectToStartOfTextToggle",
|
||||||
"ShiftEnd": "SelectToEndOfLine",
|
"ShiftHome": "SelectToStartOfTextToggle",
|
||||||
"CtrlUp": "CursorStart",
|
"AltShiftRight": "SelectToEndOfLine",
|
||||||
"CtrlDown": "CursorEnd",
|
"ShiftEnd": "SelectToEndOfLine",
|
||||||
"CtrlShiftUp": "SelectToStart",
|
"CtrlUp": "CursorStart",
|
||||||
"CtrlShiftDown": "SelectToEnd",
|
"CtrlDown": "CursorEnd",
|
||||||
"Alt-{": "ParagraphPrevious",
|
"CtrlShiftUp": "SelectToStart",
|
||||||
"Alt-}": "ParagraphNext",
|
"CtrlShiftDown": "SelectToEnd",
|
||||||
"Enter": "InsertNewline",
|
"Alt-{": "ParagraphPrevious",
|
||||||
"CtrlH": "Backspace",
|
"Alt-}": "ParagraphNext",
|
||||||
"Backspace": "Backspace",
|
"Enter": "InsertNewline",
|
||||||
"OldBackspace": "Backspace",
|
"CtrlH": "Backspace",
|
||||||
"Alt-CtrlH": "DeleteWordLeft",
|
"Backspace": "Backspace",
|
||||||
"Alt-Backspace": "DeleteWordLeft",
|
"Alt-CtrlH": "DeleteWordLeft",
|
||||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
"Alt-Backspace": "DeleteWordLeft",
|
||||||
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||||
"Ctrl-o": "OpenFile",
|
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
||||||
"Ctrl-s": "Save",
|
"CtrlO": "OpenFile",
|
||||||
"Ctrl-f": "Find",
|
"CtrlS": "Save",
|
||||||
"Alt-F": "FindLiteral",
|
"CtrlF": "Find",
|
||||||
"Ctrl-n": "FindNext",
|
"CtrlN": "FindNext",
|
||||||
"Ctrl-p": "FindPrevious",
|
"CtrlP": "FindPrevious",
|
||||||
"Alt-[": "DiffPrevious|CursorStart",
|
"CtrlZ": "Undo",
|
||||||
"Alt-]": "DiffNext|CursorEnd",
|
"CtrlY": "Redo",
|
||||||
"Ctrl-z": "Undo",
|
"CtrlC": "CopyLine|Copy",
|
||||||
"Ctrl-y": "Redo",
|
"CtrlX": "Cut",
|
||||||
"Ctrl-c": "CopyLine|Copy",
|
"CtrlK": "CutLine",
|
||||||
"Ctrl-x": "Cut",
|
"CtrlD": "DuplicateLine",
|
||||||
"Ctrl-k": "CutLine",
|
"CtrlV": "Paste",
|
||||||
"Ctrl-d": "DuplicateLine",
|
"CtrlA": "SelectAll",
|
||||||
"Ctrl-v": "Paste",
|
"CtrlT": "AddTab",
|
||||||
"Ctrl-a": "SelectAll",
|
"Alt,": "PreviousTab",
|
||||||
"Ctrl-t": "AddTab",
|
"Alt.": "NextTab",
|
||||||
"Alt-,": "PreviousTab",
|
"Home": "StartOfTextToggle",
|
||||||
"Alt-.": "NextTab",
|
"End": "EndOfLine",
|
||||||
"Home": "StartOfTextToggle",
|
"CtrlHome": "CursorStart",
|
||||||
"End": "EndOfLine",
|
"CtrlEnd": "CursorEnd",
|
||||||
"CtrlHome": "CursorStart",
|
"PageUp": "CursorPageUp",
|
||||||
"CtrlEnd": "CursorEnd",
|
"PageDown": "CursorPageDown",
|
||||||
"PageUp": "CursorPageUp",
|
"CtrlPageUp": "PreviousTab",
|
||||||
"PageDown": "CursorPageDown",
|
"CtrlPageDown": "NextTab",
|
||||||
"CtrlPageUp": "PreviousTab",
|
"CtrlG": "ToggleHelp",
|
||||||
"CtrlPageDown": "NextTab",
|
"Alt-g": "ToggleKeyMenu",
|
||||||
"Ctrl-g": "ToggleHelp",
|
"CtrlR": "ToggleRuler",
|
||||||
"Alt-g": "ToggleKeyMenu",
|
"CtrlL": "command-edit:goto ",
|
||||||
"Ctrl-r": "ToggleRuler",
|
"Delete": "Delete",
|
||||||
"Ctrl-l": "command-edit:goto ",
|
"CtrlB": "ShellMode",
|
||||||
"Delete": "Delete",
|
"CtrlQ": "Quit",
|
||||||
"Ctrl-b": "ShellMode",
|
"CtrlE": "CommandMode",
|
||||||
"Ctrl-q": "Quit",
|
"CtrlW": "NextSplit",
|
||||||
"Ctrl-e": "CommandMode",
|
"CtrlU": "ToggleMacro",
|
||||||
"Ctrl-w": "NextSplit",
|
"CtrlJ": "PlayMacro",
|
||||||
"Ctrl-u": "ToggleMacro",
|
"Insert": "ToggleOverwriteMode",
|
||||||
"Ctrl-j": "PlayMacro",
|
|
||||||
"Insert": "ToggleOverwriteMode",
|
|
||||||
|
|
||||||
// Emacs-style keybindings
|
// Emacs-style keybindings
|
||||||
"Alt-f": "WordRight",
|
"Alt-f": "WordRight",
|
||||||
"Alt-b": "WordLeft",
|
"Alt-b": "WordLeft",
|
||||||
"Alt-a": "StartOfText",
|
"Alt-a": "StartOfText",
|
||||||
"Alt-e": "EndOfLine",
|
"Alt-e": "EndOfLine",
|
||||||
// "Alt-p": "CursorUp",
|
// "Alt-p": "CursorUp",
|
||||||
// "Alt-n": "CursorDown",
|
// "Alt-n": "CursorDown",
|
||||||
|
|
||||||
// Integration with file managers
|
// Integration with file managers
|
||||||
"F2": "Save",
|
"F2": "Save",
|
||||||
"F3": "Find",
|
"F3": "Find",
|
||||||
"F4": "Quit",
|
"F4": "Quit",
|
||||||
"F7": "Find",
|
"F7": "Find",
|
||||||
"F10": "Quit",
|
"F10": "Quit",
|
||||||
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
|
"Esc": "Escape",
|
||||||
|
|
||||||
// Mouse bindings
|
// Mouse bindings
|
||||||
"MouseWheelUp": "ScrollUp",
|
"MouseWheelUp": "ScrollUp",
|
||||||
"MouseWheelDown": "ScrollDown",
|
"MouseWheelDown": "ScrollDown",
|
||||||
"MouseLeft": "MousePress",
|
"MouseLeft": "MousePress",
|
||||||
"MouseLeftDrag": "MouseDrag",
|
"MouseMiddle": "PastePrimary",
|
||||||
"MouseLeftRelease": "MouseRelease",
|
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||||
"MouseMiddle": "PastePrimary",
|
|
||||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
|
||||||
|
|
||||||
"Alt-n": "SpawnMultiCursor",
|
"Alt-n": "SpawnMultiCursor",
|
||||||
"Alt-m": "SpawnMultiCursorSelect",
|
"Alt-m": "SpawnMultiCursorSelect",
|
||||||
"AltShiftUp": "SpawnMultiCursorUp",
|
"AltShiftUp": "SpawnMultiCursorUp",
|
||||||
"AltShiftDown": "SpawnMultiCursorDown",
|
"AltShiftDown": "SpawnMultiCursorDown",
|
||||||
"Alt-p": "RemoveMultiCursor",
|
"Alt-p": "RemoveMultiCursor",
|
||||||
"Alt-c": "RemoveAllMultiCursors",
|
"Alt-c": "RemoveAllMultiCursors",
|
||||||
"Alt-x": "SkipMultiCursor",
|
"Alt-x": "SkipMultiCursor",
|
||||||
}
|
}
|
||||||
|
|
||||||
var infodefaults = map[string]string{
|
|
||||||
"Up": "HistoryUp",
|
|
||||||
"Down": "HistoryDown",
|
|
||||||
"Right": "CursorRight",
|
|
||||||
"Left": "CursorLeft",
|
|
||||||
"ShiftUp": "SelectUp",
|
|
||||||
"ShiftDown": "SelectDown",
|
|
||||||
"ShiftLeft": "SelectLeft",
|
|
||||||
"ShiftRight": "SelectRight",
|
|
||||||
"AltLeft": "StartOfTextToggle",
|
|
||||||
"AltRight": "EndOfLine",
|
|
||||||
"AltUp": "CursorStart",
|
|
||||||
"AltDown": "CursorEnd",
|
|
||||||
"AltShiftRight": "SelectWordRight",
|
|
||||||
"AltShiftLeft": "SelectWordLeft",
|
|
||||||
"CtrlLeft": "WordLeft",
|
|
||||||
"CtrlRight": "WordRight",
|
|
||||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
|
||||||
"ShiftHome": "SelectToStartOfTextToggle",
|
|
||||||
"CtrlShiftRight": "SelectToEndOfLine",
|
|
||||||
"ShiftEnd": "SelectToEndOfLine",
|
|
||||||
"CtrlUp": "CursorStart",
|
|
||||||
"CtrlDown": "CursorEnd",
|
|
||||||
"CtrlShiftUp": "SelectToStart",
|
|
||||||
"CtrlShiftDown": "SelectToEnd",
|
|
||||||
"Enter": "ExecuteCommand",
|
|
||||||
"CtrlH": "Backspace",
|
|
||||||
"Backspace": "Backspace",
|
|
||||||
"OldBackspace": "Backspace",
|
|
||||||
"Alt-CtrlH": "DeleteWordLeft",
|
|
||||||
"Alt-Backspace": "DeleteWordLeft",
|
|
||||||
"Tab": "CommandComplete",
|
|
||||||
"Backtab": "CycleAutocompleteBack",
|
|
||||||
"Ctrl-z": "Undo",
|
|
||||||
"Ctrl-y": "Redo",
|
|
||||||
"Ctrl-c": "CopyLine|Copy",
|
|
||||||
"Ctrl-x": "Cut",
|
|
||||||
"Ctrl-k": "CutLine",
|
|
||||||
"Ctrl-v": "Paste",
|
|
||||||
"Home": "StartOfTextToggle",
|
|
||||||
"End": "EndOfLine",
|
|
||||||
"CtrlHome": "CursorStart",
|
|
||||||
"CtrlEnd": "CursorEnd",
|
|
||||||
"Delete": "Delete",
|
|
||||||
"Ctrl-q": "AbortCommand",
|
|
||||||
"Ctrl-e": "EndOfLine",
|
|
||||||
"Ctrl-a": "StartOfLine",
|
|
||||||
"Ctrl-w": "DeleteWordLeft",
|
|
||||||
"Insert": "ToggleOverwriteMode",
|
|
||||||
"Ctrl-b": "WordLeft",
|
|
||||||
"Ctrl-f": "WordRight",
|
|
||||||
"Ctrl-d": "DeleteWordLeft",
|
|
||||||
"Ctrl-m": "ExecuteCommand",
|
|
||||||
"Ctrl-n": "HistoryDown",
|
|
||||||
"Ctrl-p": "HistoryUp",
|
|
||||||
"Ctrl-u": "SelectToStart",
|
|
||||||
|
|
||||||
// Emacs-style keybindings
|
|
||||||
"Alt-f": "WordRight",
|
|
||||||
"Alt-b": "WordLeft",
|
|
||||||
"Alt-a": "StartOfText",
|
|
||||||
"Alt-e": "EndOfLine",
|
|
||||||
|
|
||||||
// Integration with file managers
|
|
||||||
"F10": "AbortCommand",
|
|
||||||
"Esc": "AbortCommand",
|
|
||||||
|
|
||||||
// Mouse bindings
|
|
||||||
"MouseWheelUp": "HistoryUp",
|
|
||||||
"MouseWheelDown": "HistoryDown",
|
|
||||||
"MouseLeft": "MousePress",
|
|
||||||
"MouseLeftDrag": "MouseDrag",
|
|
||||||
"MouseLeftRelease": "MouseRelease",
|
|
||||||
"MouseMiddle": "PastePrimary",
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,10 @@
|
||||||
package action
|
package action
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"github.com/zyedidia/tcell"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event interface {
|
type Event interface{}
|
||||||
Name() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawEvent is simply an escape code
|
// RawEvent is simply an escape code
|
||||||
// We allow users to directly bind escape codes
|
// We allow users to directly bind escape codes
|
||||||
|
@ -20,10 +13,6 @@ type RawEvent struct {
|
||||||
esc string
|
esc string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r RawEvent) Name() string {
|
|
||||||
return r.esc
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyEvent is a key event containing a key code,
|
// KeyEvent is a key event containing a key code,
|
||||||
// some possible modifiers (alt, ctrl, etc...) and
|
// some possible modifiers (alt, ctrl, etc...) and
|
||||||
// a rune if it was simply a character press
|
// a rune if it was simply a character press
|
||||||
|
@ -33,145 +22,17 @@ type KeyEvent struct {
|
||||||
code tcell.Key
|
code tcell.Key
|
||||||
mod tcell.ModMask
|
mod tcell.ModMask
|
||||||
r rune
|
r rune
|
||||||
any bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func metaToAlt(mod tcell.ModMask) tcell.ModMask {
|
|
||||||
if mod&tcell.ModMeta != 0 {
|
|
||||||
mod &= ^tcell.ModMeta
|
|
||||||
mod |= tcell.ModAlt
|
|
||||||
}
|
|
||||||
return mod
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k KeyEvent) Name() string {
|
|
||||||
if k.any {
|
|
||||||
return "<any>"
|
|
||||||
}
|
|
||||||
s := ""
|
|
||||||
m := []string{}
|
|
||||||
if k.mod&tcell.ModShift != 0 {
|
|
||||||
m = append(m, "Shift")
|
|
||||||
}
|
|
||||||
if k.mod&tcell.ModAlt != 0 {
|
|
||||||
m = append(m, "Alt")
|
|
||||||
}
|
|
||||||
if k.mod&tcell.ModMeta != 0 {
|
|
||||||
m = append(m, "Meta")
|
|
||||||
}
|
|
||||||
if k.mod&tcell.ModCtrl != 0 {
|
|
||||||
m = append(m, "Ctrl")
|
|
||||||
}
|
|
||||||
|
|
||||||
ok := false
|
|
||||||
if s, ok = tcell.KeyNames[k.code]; !ok {
|
|
||||||
if k.code == tcell.KeyRune {
|
|
||||||
s = string(k.r)
|
|
||||||
} else {
|
|
||||||
s = fmt.Sprintf("Key[%d,%d]", k.code, int(k.r))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(m) != 0 {
|
|
||||||
if k.mod&tcell.ModCtrl != 0 && strings.HasPrefix(s, "Ctrl-") {
|
|
||||||
s = s[5:]
|
|
||||||
if len(s) == 1 {
|
|
||||||
s = strings.ToLower(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s-%s", strings.Join(m, "-"), s)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// A KeySequence defines a list of consecutive
|
|
||||||
// events. All events in the sequence must be KeyEvents
|
|
||||||
// or MouseEvents.
|
|
||||||
type KeySequenceEvent struct {
|
|
||||||
keys []Event
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k KeySequenceEvent) Name() string {
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
for _, e := range k.keys {
|
|
||||||
buf.WriteByte('<')
|
|
||||||
buf.WriteString(e.Name())
|
|
||||||
buf.WriteByte('>')
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type MouseState int
|
|
||||||
|
|
||||||
const (
|
|
||||||
MousePress = iota
|
|
||||||
MouseDrag
|
|
||||||
MouseRelease
|
|
||||||
)
|
|
||||||
|
|
||||||
// MouseEvent is a mouse event with a mouse button and
|
// MouseEvent is a mouse event with a mouse button and
|
||||||
// any possible key modifiers
|
// any possible key modifiers
|
||||||
type MouseEvent struct {
|
type MouseEvent struct {
|
||||||
btn tcell.ButtonMask
|
btn tcell.ButtonMask
|
||||||
mod tcell.ModMask
|
mod tcell.ModMask
|
||||||
state MouseState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m MouseEvent) Name() string {
|
type KeyAction func(Handler) bool
|
||||||
mod := ""
|
type MouseAction func(Handler, tcell.EventMouse) bool
|
||||||
if m.mod&tcell.ModShift != 0 {
|
|
||||||
mod = "Shift-"
|
|
||||||
}
|
|
||||||
if m.mod&tcell.ModAlt != 0 {
|
|
||||||
mod = "Alt-"
|
|
||||||
}
|
|
||||||
if m.mod&tcell.ModMeta != 0 {
|
|
||||||
mod = "Meta-"
|
|
||||||
}
|
|
||||||
if m.mod&tcell.ModCtrl != 0 {
|
|
||||||
mod = "Ctrl-"
|
|
||||||
}
|
|
||||||
|
|
||||||
state := ""
|
|
||||||
switch m.state {
|
|
||||||
case MouseDrag:
|
|
||||||
state = "Drag"
|
|
||||||
case MouseRelease:
|
|
||||||
state = "Release"
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range mouseEvents {
|
|
||||||
if v == m.btn {
|
|
||||||
return fmt.Sprintf("%s%s%s", mod, k, state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConstructEvent takes a tcell event and returns a micro
|
|
||||||
// event. Note that tcell events can't express certain
|
|
||||||
// micro events such as key sequences. This function is
|
|
||||||
// mostly used for debugging/raw panes or constructing
|
|
||||||
// intermediate micro events while parsing a sequence.
|
|
||||||
func ConstructEvent(event tcell.Event) (Event, error) {
|
|
||||||
switch e := event.(type) {
|
|
||||||
case *tcell.EventKey:
|
|
||||||
return KeyEvent{
|
|
||||||
code: e.Key(),
|
|
||||||
mod: metaToAlt(e.Modifiers()),
|
|
||||||
r: e.Rune(),
|
|
||||||
}, nil
|
|
||||||
case *tcell.EventRaw:
|
|
||||||
return RawEvent{
|
|
||||||
esc: e.EscSeq(),
|
|
||||||
}, nil
|
|
||||||
case *tcell.EventMouse:
|
|
||||||
return MouseEvent{
|
|
||||||
btn: e.Buttons(),
|
|
||||||
mod: metaToAlt(e.Modifiers()),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("No micro event equivalent")
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Handler will take a tcell event and execute it
|
// A Handler will take a tcell event and execute it
|
||||||
// appropriately
|
// appropriately
|
||||||
|
|
|
@ -2,10 +2,7 @@ package action
|
||||||
|
|
||||||
import "github.com/zyedidia/micro/v2/internal/buffer"
|
import "github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
|
|
||||||
// InfoBar is the global info bar.
|
|
||||||
var InfoBar *InfoPane
|
var InfoBar *InfoPane
|
||||||
|
|
||||||
// LogBufPane is a global log buffer.
|
|
||||||
var LogBufPane *BufPane
|
var LogBufPane *BufPane
|
||||||
|
|
||||||
// InitGlobals initializes the log buffer and the info bar
|
// InitGlobals initializes the log buffer and the info bar
|
||||||
|
@ -24,6 +21,13 @@ func WriteLog(s string) {
|
||||||
buffer.WriteLog(s)
|
buffer.WriteLog(s)
|
||||||
if LogBufPane != nil {
|
if LogBufPane != nil {
|
||||||
LogBufPane.CursorEnd()
|
LogBufPane.CursorEnd()
|
||||||
|
v := LogBufPane.GetView()
|
||||||
|
endY := buffer.LogBuf.End().Y
|
||||||
|
|
||||||
|
if endY > v.StartLine+v.Height {
|
||||||
|
v.StartLine = buffer.LogBuf.End().Y - v.Height + 2
|
||||||
|
LogBufPane.SetView(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,4 +37,12 @@ func WriteLog(s string) {
|
||||||
func (h *BufPane) OpenLogBuf() {
|
func (h *BufPane) OpenLogBuf() {
|
||||||
LogBufPane = h.HSplitBuf(buffer.LogBuf)
|
LogBufPane = h.HSplitBuf(buffer.LogBuf)
|
||||||
LogBufPane.CursorEnd()
|
LogBufPane.CursorEnd()
|
||||||
|
|
||||||
|
v := LogBufPane.GetView()
|
||||||
|
endY := buffer.LogBuf.End().Y
|
||||||
|
|
||||||
|
if endY > v.StartLine+v.Height {
|
||||||
|
v.StartLine = buffer.LogBuf.End().Y - v.Height + 2
|
||||||
|
LogBufPane.SetView(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/micro/v2/pkg/highlight"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file is meant (for now) for autocompletion in command mode, not
|
// This file is meant (for now) for autocompletion in command mode, not
|
||||||
|
@ -18,7 +17,7 @@ import (
|
||||||
// CommandComplete autocompletes commands
|
// CommandComplete autocompletes commands
|
||||||
func CommandComplete(b *buffer.Buffer) ([]string, []string) {
|
func CommandComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
input, argstart := b.GetArg()
|
input, argstart := buffer.GetArg(b)
|
||||||
|
|
||||||
var suggestions []string
|
var suggestions []string
|
||||||
for cmd := range commands {
|
for cmd := range commands {
|
||||||
|
@ -39,7 +38,7 @@ func CommandComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
// HelpComplete autocompletes help topics
|
// HelpComplete autocompletes help topics
|
||||||
func HelpComplete(b *buffer.Buffer) ([]string, []string) {
|
func HelpComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
input, argstart := b.GetArg()
|
input, argstart := buffer.GetArg(b)
|
||||||
|
|
||||||
var suggestions []string
|
var suggestions []string
|
||||||
|
|
||||||
|
@ -78,63 +77,6 @@ func colorschemeComplete(input string) (string, []string) {
|
||||||
return chosen, suggestions
|
return chosen, suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
// filetypeComplete autocompletes filetype
|
|
||||||
func filetypeComplete(input string) (string, []string) {
|
|
||||||
var suggestions []string
|
|
||||||
|
|
||||||
// We cannot match filetypes just by names of syntax files,
|
|
||||||
// since those names may be different from the actual filetype values
|
|
||||||
// specified inside syntax files (e.g. "c++" filetype in cpp.yaml).
|
|
||||||
// So we need to parse filetype values out of those files.
|
|
||||||
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
|
|
||||||
data, err := f.Data()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
header, err := highlight.MakeHeaderYaml(data)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Prevent duplicated defaults
|
|
||||||
if header.FileType == "off" || header.FileType == "unknown" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(header.FileType, input) {
|
|
||||||
suggestions = append(suggestions, header.FileType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
headerLoop:
|
|
||||||
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
|
|
||||||
data, err := f.Data()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
header, err := highlight.MakeHeader(data)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, v := range suggestions {
|
|
||||||
if v == header.FileType {
|
|
||||||
continue headerLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(header.FileType, input) {
|
|
||||||
suggestions = append(suggestions, header.FileType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix("off", input) {
|
|
||||||
suggestions = append(suggestions, "off")
|
|
||||||
}
|
|
||||||
|
|
||||||
var chosen string
|
|
||||||
if len(suggestions) == 1 {
|
|
||||||
chosen = suggestions[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return chosen, suggestions
|
|
||||||
}
|
|
||||||
|
|
||||||
func contains(s []string, e string) bool {
|
func contains(s []string, e string) bool {
|
||||||
for _, a := range s {
|
for _, a := range s {
|
||||||
if a == e {
|
if a == e {
|
||||||
|
@ -147,7 +89,7 @@ func contains(s []string, e string) bool {
|
||||||
// OptionComplete autocompletes options
|
// OptionComplete autocompletes options
|
||||||
func OptionComplete(b *buffer.Buffer) ([]string, []string) {
|
func OptionComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
input, argstart := b.GetArg()
|
input, argstart := buffer.GetArg(b)
|
||||||
|
|
||||||
var suggestions []string
|
var suggestions []string
|
||||||
for option := range config.GlobalSettings {
|
for option := range config.GlobalSettings {
|
||||||
|
@ -174,7 +116,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
l := b.LineBytes(c.Y)
|
l := b.LineBytes(c.Y)
|
||||||
l = util.SliceStart(l, c.X)
|
l = util.SliceStart(l, c.X)
|
||||||
input, argstart := b.GetArg()
|
input, argstart := buffer.GetArg(b)
|
||||||
|
|
||||||
completeValue := false
|
completeValue := false
|
||||||
args := bytes.Split(l, []byte{' '})
|
args := bytes.Split(l, []byte{' '})
|
||||||
|
@ -230,8 +172,13 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
switch inputOpt {
|
switch inputOpt {
|
||||||
case "colorscheme":
|
case "colorscheme":
|
||||||
_, suggestions = colorschemeComplete(input)
|
_, suggestions = colorschemeComplete(input)
|
||||||
case "filetype":
|
case "fileformat":
|
||||||
_, suggestions = filetypeComplete(input)
|
if strings.HasPrefix("unix", input) {
|
||||||
|
suggestions = append(suggestions, "unix")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix("dos", input) {
|
||||||
|
suggestions = append(suggestions, "dos")
|
||||||
|
}
|
||||||
case "sucmd":
|
case "sucmd":
|
||||||
if strings.HasPrefix("sudo", input) {
|
if strings.HasPrefix("sudo", input) {
|
||||||
suggestions = append(suggestions, "sudo")
|
suggestions = append(suggestions, "sudo")
|
||||||
|
@ -239,14 +186,6 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
if strings.HasPrefix("doas", input) {
|
if strings.HasPrefix("doas", input) {
|
||||||
suggestions = append(suggestions, "doas")
|
suggestions = append(suggestions, "doas")
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
if choices, ok := config.OptionChoices[inputOpt]; ok {
|
|
||||||
for _, choice := range choices {
|
|
||||||
if strings.HasPrefix(choice, input) {
|
|
||||||
suggestions = append(suggestions, choice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(suggestions)
|
sort.Strings(suggestions)
|
||||||
|
@ -261,7 +200,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
// PluginCmdComplete autocompletes the plugin command
|
// PluginCmdComplete autocompletes the plugin command
|
||||||
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
|
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
input, argstart := b.GetArg()
|
input, argstart := buffer.GetArg(b)
|
||||||
|
|
||||||
var suggestions []string
|
var suggestions []string
|
||||||
for _, cmd := range PluginCmds {
|
for _, cmd := range PluginCmds {
|
||||||
|
@ -283,7 +222,7 @@ func PluginComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
l := b.LineBytes(c.Y)
|
l := b.LineBytes(c.Y)
|
||||||
l = util.SliceStart(l, c.X)
|
l = util.SliceStart(l, c.X)
|
||||||
input, argstart := b.GetArg()
|
input, argstart := buffer.GetArg(b)
|
||||||
|
|
||||||
completeValue := false
|
completeValue := false
|
||||||
args := bytes.Split(l, []byte{' '})
|
args := bytes.Split(l, []byte{' '})
|
||||||
|
|
|
@ -2,60 +2,17 @@ package action
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"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/display"
|
||||||
"github.com/zyedidia/micro/v2/internal/info"
|
"github.com/zyedidia/micro/v2/internal/info"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InfoKeyAction func(*InfoPane)
|
type InfoKeyAction func(*InfoPane)
|
||||||
|
|
||||||
var InfoBindings *KeyTree
|
|
||||||
var InfoBufBindings *KeyTree
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
InfoBindings = NewKeyTree()
|
|
||||||
InfoBufBindings = NewKeyTree()
|
|
||||||
}
|
|
||||||
|
|
||||||
func InfoMapEvent(k Event, action string) {
|
|
||||||
config.Bindings["command"][k.Name()] = action
|
|
||||||
|
|
||||||
switch e := k.(type) {
|
|
||||||
case KeyEvent, KeySequenceEvent, RawEvent:
|
|
||||||
infoMapKey(e, action)
|
|
||||||
case MouseEvent:
|
|
||||||
infoMapMouse(e, action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func infoMapKey(k Event, action string) {
|
|
||||||
if f, ok := InfoKeyActions[action]; ok {
|
|
||||||
InfoBindings.RegisterKeyBinding(k, InfoKeyActionGeneral(f))
|
|
||||||
} else if f, ok := BufKeyActions[action]; ok {
|
|
||||||
InfoBufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func infoMapMouse(k MouseEvent, action string) {
|
|
||||||
// TODO: map mouse
|
|
||||||
if f, ok := BufMouseActions[action]; ok {
|
|
||||||
InfoBufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
|
|
||||||
} else {
|
|
||||||
infoMapKey(k, action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func InfoKeyActionGeneral(a InfoKeyAction) PaneKeyAction {
|
|
||||||
return func(p Pane) bool {
|
|
||||||
a(p.(*InfoPane))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type InfoPane struct {
|
type InfoPane struct {
|
||||||
*BufPane
|
*BufPane
|
||||||
*info.InfoBuf
|
*info.InfoBuf
|
||||||
|
@ -65,7 +22,6 @@ func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
|
||||||
ip := new(InfoPane)
|
ip := new(InfoPane)
|
||||||
ip.InfoBuf = ib
|
ip.InfoBuf = ib
|
||||||
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
|
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
|
||||||
ip.BufPane.bindings = InfoBufBindings
|
|
||||||
|
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
@ -83,26 +39,22 @@ func (h *InfoPane) Close() {
|
||||||
|
|
||||||
func (h *InfoPane) HandleEvent(event tcell.Event) {
|
func (h *InfoPane) HandleEvent(event tcell.Event) {
|
||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
case *tcell.EventResize:
|
|
||||||
// TODO
|
|
||||||
case *tcell.EventKey:
|
case *tcell.EventKey:
|
||||||
ke := KeyEvent{
|
ke := KeyEvent{
|
||||||
code: e.Key(),
|
code: e.Key(),
|
||||||
mod: metaToAlt(e.Modifiers()),
|
mod: e.Modifiers(),
|
||||||
r: e.Rune(),
|
r: e.Rune(),
|
||||||
}
|
}
|
||||||
|
|
||||||
done := h.DoKeyEvent(ke)
|
done := h.DoKeyEvent(ke)
|
||||||
hasYN := h.HasYN
|
hasYN := h.HasYN
|
||||||
if e.Key() == tcell.KeyRune && hasYN {
|
if e.Key() == tcell.KeyRune && hasYN {
|
||||||
y := e.Rune() == 'y' || e.Rune() == 'Y'
|
if e.Rune() == 'y' && hasYN {
|
||||||
n := e.Rune() == 'n' || e.Rune() == 'N'
|
h.YNResp = true
|
||||||
if y || n {
|
h.DonePrompt(false)
|
||||||
h.YNResp = y
|
} else if e.Rune() == 'n' && hasYN {
|
||||||
|
h.YNResp = false
|
||||||
h.DonePrompt(false)
|
h.DonePrompt(false)
|
||||||
|
|
||||||
InfoBindings.ResetEvents()
|
|
||||||
InfoBufBindings.ResetEvents()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e.Key() == tcell.KeyRune && !done && !hasYN {
|
if e.Key() == tcell.KeyRune && !done && !hasYN {
|
||||||
|
@ -112,11 +64,7 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
|
||||||
if done && h.HasPrompt && !hasYN {
|
if done && h.HasPrompt && !hasYN {
|
||||||
resp := string(h.LineBytes(0))
|
resp := string(h.LineBytes(0))
|
||||||
hist := h.History[h.PromptType]
|
hist := h.History[h.PromptType]
|
||||||
if resp != hist[h.HistoryNum] {
|
hist[h.HistoryNum] = resp
|
||||||
h.HistoryNum = len(hist) - 1
|
|
||||||
hist[h.HistoryNum] = resp
|
|
||||||
h.HistorySearch = false
|
|
||||||
}
|
|
||||||
if h.EventCallback != nil {
|
if h.EventCallback != nil {
|
||||||
h.EventCallback(resp)
|
h.EventCallback(resp)
|
||||||
}
|
}
|
||||||
|
@ -126,74 +74,106 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoKeyEvent executes a key event for the command bar, doing any overridden actions.
|
// DoKeyEvent executes a key event for the command bar, doing any overridden actions
|
||||||
// Returns true if the action was executed OR if there are more keys remaining
|
|
||||||
// to process before executing an action (if this is a key sequence event).
|
|
||||||
// Returns false if no action found.
|
|
||||||
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
|
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
|
||||||
action, more := InfoBindings.NextEvent(e, nil)
|
done := false
|
||||||
if action != nil && !more {
|
if action, ok := BufKeyBindings[e]; ok {
|
||||||
action(h)
|
estr := BufKeyStrings[e]
|
||||||
InfoBindings.ResetEvents()
|
for _, s := range InfoNones {
|
||||||
|
if s == estr {
|
||||||
return true
|
return false
|
||||||
} else if action == nil && !more {
|
}
|
||||||
InfoBindings.ResetEvents()
|
}
|
||||||
// return false //TODO:?
|
for s, a := range InfoOverrides {
|
||||||
}
|
// TODO this is a hack and really we should have support
|
||||||
|
// for having binding overrides for different buffers
|
||||||
if !more {
|
if strings.HasPrefix(estr, s) {
|
||||||
// If no infopane action found, try to find a bufpane action.
|
done = true
|
||||||
//
|
a(h)
|
||||||
// TODO: this is buggy. For example, if the command bar has the following
|
break
|
||||||
// two bindings:
|
}
|
||||||
//
|
}
|
||||||
// "<Ctrl-x><Ctrl-p>": "HistoryUp",
|
if !done {
|
||||||
// "<Ctrl-x><Ctrl-v>": "Paste",
|
done = action(h.BufPane)
|
||||||
//
|
|
||||||
// the 2nd binding (with a bufpane action) doesn't work, since <Ctrl-x>
|
|
||||||
// has been already consumed by the 1st binding (with an infopane action).
|
|
||||||
//
|
|
||||||
// We should either iterate both InfoBindings and InfoBufBindings keytrees
|
|
||||||
// together, or just use the same keytree for both infopane and bufpane
|
|
||||||
// bindings.
|
|
||||||
action, more = InfoBufBindings.NextEvent(e, nil)
|
|
||||||
if action != nil && !more {
|
|
||||||
action(h.BufPane)
|
|
||||||
InfoBufBindings.ResetEvents()
|
|
||||||
return true
|
|
||||||
} else if action == nil && !more {
|
|
||||||
InfoBufBindings.ResetEvents()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return done
|
||||||
return more
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistoryUp cycles history up
|
// InfoNones is a list of actions that should have no effect when executed
|
||||||
func (h *InfoPane) HistoryUp() {
|
// by an infohandler
|
||||||
|
var InfoNones = []string{
|
||||||
|
"Save",
|
||||||
|
"SaveAll",
|
||||||
|
"SaveAs",
|
||||||
|
"Find",
|
||||||
|
"FindNext",
|
||||||
|
"FindPrevious",
|
||||||
|
"Center",
|
||||||
|
"DuplicateLine",
|
||||||
|
"MoveLinesUp",
|
||||||
|
"MoveLinesDown",
|
||||||
|
"OpenFile",
|
||||||
|
"Start",
|
||||||
|
"End",
|
||||||
|
"PageUp",
|
||||||
|
"PageDown",
|
||||||
|
"SelectPageUp",
|
||||||
|
"SelectPageDown",
|
||||||
|
"HalfPageUp",
|
||||||
|
"HalfPageDown",
|
||||||
|
"ToggleHelp",
|
||||||
|
"ToggleKeyMenu",
|
||||||
|
"ToggleDiffGutter",
|
||||||
|
"ToggleRuler",
|
||||||
|
"JumpLine",
|
||||||
|
"ClearStatus",
|
||||||
|
"ShellMode",
|
||||||
|
"CommandMode",
|
||||||
|
"AddTab",
|
||||||
|
"PreviousTab",
|
||||||
|
"NextTab",
|
||||||
|
"NextSplit",
|
||||||
|
"PreviousSplit",
|
||||||
|
"Unsplit",
|
||||||
|
"VSplit",
|
||||||
|
"HSplit",
|
||||||
|
"ToggleMacro",
|
||||||
|
"PlayMacro",
|
||||||
|
"Suspend",
|
||||||
|
"ScrollUp",
|
||||||
|
"ScrollDown",
|
||||||
|
"SpawnMultiCursor",
|
||||||
|
"SpawnMultiCursorSelect",
|
||||||
|
"RemoveMultiCursor",
|
||||||
|
"RemoveAllMultiCursors",
|
||||||
|
"SkipMultiCursor",
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoOverrides is the list of actions which have been overridden
|
||||||
|
// by the infohandler
|
||||||
|
var InfoOverrides = map[string]InfoKeyAction{
|
||||||
|
"CursorUp": (*InfoPane).CursorUp,
|
||||||
|
"CursorDown": (*InfoPane).CursorDown,
|
||||||
|
"InsertNewline": (*InfoPane).InsertNewline,
|
||||||
|
"Autocomplete": (*InfoPane).Autocomplete,
|
||||||
|
"Escape": (*InfoPane).Escape,
|
||||||
|
"Quit": (*InfoPane).Quit,
|
||||||
|
"QuitAll": (*InfoPane).QuitAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
// CursorUp cycles history up
|
||||||
|
func (h *InfoPane) CursorUp() {
|
||||||
h.UpHistory(h.History[h.PromptType])
|
h.UpHistory(h.History[h.PromptType])
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistoryDown cycles history down
|
// CursorDown cycles history down
|
||||||
func (h *InfoPane) HistoryDown() {
|
func (h *InfoPane) CursorDown() {
|
||||||
h.DownHistory(h.History[h.PromptType])
|
h.DownHistory(h.History[h.PromptType])
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistorySearchUp fetches the previous history item beginning with the text
|
|
||||||
// in the infobuffer before cursor
|
|
||||||
func (h *InfoPane) HistorySearchUp() {
|
|
||||||
h.SearchUpHistory(h.History[h.PromptType])
|
|
||||||
}
|
|
||||||
|
|
||||||
// HistorySearchDown fetches the next history item beginning with the text
|
|
||||||
// in the infobuffer before cursor
|
|
||||||
func (h *InfoPane) HistorySearchDown() {
|
|
||||||
h.SearchDownHistory(h.History[h.PromptType])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Autocomplete begins autocompletion
|
// Autocomplete begins autocompletion
|
||||||
func (h *InfoPane) CommandComplete() {
|
func (h *InfoPane) Autocomplete() {
|
||||||
b := h.Buf
|
b := h.Buf
|
||||||
if b.HasSuggestions {
|
if b.HasSuggestions {
|
||||||
b.CycleAutocomplete(true)
|
b.CycleAutocomplete(true)
|
||||||
|
@ -221,25 +201,24 @@ func (h *InfoPane) CommandComplete() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecuteCommand completes the prompt
|
// InsertNewline completes the prompt
|
||||||
func (h *InfoPane) ExecuteCommand() {
|
func (h *InfoPane) InsertNewline() {
|
||||||
if !h.HasYN {
|
if !h.HasYN {
|
||||||
h.DonePrompt(false)
|
h.DonePrompt(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AbortCommand cancels the prompt
|
// Quit cancels the prompt
|
||||||
func (h *InfoPane) AbortCommand() {
|
func (h *InfoPane) Quit() {
|
||||||
h.DonePrompt(true)
|
h.DonePrompt(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InfoKeyActions contains the list of all possible key actions the infopane could execute
|
// QuitAll cancels the prompt
|
||||||
var InfoKeyActions = map[string]InfoKeyAction{
|
func (h *InfoPane) QuitAll() {
|
||||||
"HistoryUp": (*InfoPane).HistoryUp,
|
h.DonePrompt(true)
|
||||||
"HistoryDown": (*InfoPane).HistoryDown,
|
}
|
||||||
"HistorySearchUp": (*InfoPane).HistorySearchUp,
|
|
||||||
"HistorySearchDown": (*InfoPane).HistorySearchDown,
|
// Escape cancels the prompt
|
||||||
"CommandComplete": (*InfoPane).CommandComplete,
|
func (h *InfoPane) Escape() {
|
||||||
"ExecuteCommand": (*InfoPane).ExecuteCommand,
|
h.DonePrompt(true)
|
||||||
"AbortCommand": (*InfoPane).AbortCommand,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,261 +0,0 @@
|
||||||
package action
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PaneKeyAction func(Pane) bool
|
|
||||||
type PaneMouseAction func(Pane, *tcell.EventMouse) bool
|
|
||||||
type PaneKeyAnyAction func(Pane, []KeyEvent) bool
|
|
||||||
|
|
||||||
// A KeyTreeNode stores a single node in the KeyTree (trie). The
|
|
||||||
// children are stored as a map, and any node may store a list of
|
|
||||||
// actions (the list will be nil if no actions correspond to a certain
|
|
||||||
// node)
|
|
||||||
type KeyTreeNode struct {
|
|
||||||
children map[Event]*KeyTreeNode
|
|
||||||
|
|
||||||
// Only one of these actions may be active in the current
|
|
||||||
// mode, and only one will be returned. If multiple actions
|
|
||||||
// are active, it is undefined which one will be the one
|
|
||||||
// returned.
|
|
||||||
actions []TreeAction
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKeyTreeNode() *KeyTreeNode {
|
|
||||||
n := new(KeyTreeNode)
|
|
||||||
n.children = make(map[Event]*KeyTreeNode)
|
|
||||||
n.actions = []TreeAction{}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// A TreeAction stores an action, and a set of mode constraints for
|
|
||||||
// the action to be active.
|
|
||||||
type TreeAction struct {
|
|
||||||
// only one of these can be non-nil
|
|
||||||
action PaneKeyAction
|
|
||||||
any PaneKeyAnyAction
|
|
||||||
mouse PaneMouseAction
|
|
||||||
|
|
||||||
modes []ModeConstraint
|
|
||||||
}
|
|
||||||
|
|
||||||
// A KeyTree is a data structure for storing keybindings. It maps
|
|
||||||
// key events to actions, and maintains a set of currently enabled
|
|
||||||
// modes, which affects the action that is returned for a key event.
|
|
||||||
// The tree acts like a Trie for Events to handle sequence events.
|
|
||||||
type KeyTree struct {
|
|
||||||
root *KeyTreeNode
|
|
||||||
modes map[string]bool
|
|
||||||
|
|
||||||
cursor KeyTreeCursor
|
|
||||||
}
|
|
||||||
|
|
||||||
// A KeyTreeCursor keeps track of the current location within the
|
|
||||||
// tree, and stores any information from previous events that may
|
|
||||||
// be needed to execute the action (values of wildcard events or
|
|
||||||
// mouse events)
|
|
||||||
type KeyTreeCursor struct {
|
|
||||||
node *KeyTreeNode
|
|
||||||
|
|
||||||
recordedEvents []Event
|
|
||||||
wildcards []KeyEvent
|
|
||||||
mouseInfo *tcell.EventMouse
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeClosure uses the information stored in a key tree cursor to construct
|
|
||||||
// a PaneKeyAction from a TreeAction (which may have a PaneKeyAction, PaneMouseAction,
|
|
||||||
// or AnyAction)
|
|
||||||
func (k *KeyTreeCursor) MakeClosure(a TreeAction) PaneKeyAction {
|
|
||||||
if a.action != nil {
|
|
||||||
return a.action
|
|
||||||
} else if a.any != nil {
|
|
||||||
return func(p Pane) bool {
|
|
||||||
return a.any(p, k.wildcards)
|
|
||||||
}
|
|
||||||
} else if a.mouse != nil {
|
|
||||||
return func(p Pane) bool {
|
|
||||||
return a.mouse(p, k.mouseInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKeyTree allocates and returns an empty key tree
|
|
||||||
func NewKeyTree() *KeyTree {
|
|
||||||
root := NewKeyTreeNode()
|
|
||||||
tree := new(KeyTree)
|
|
||||||
|
|
||||||
tree.root = root
|
|
||||||
tree.modes = make(map[string]bool)
|
|
||||||
tree.cursor = KeyTreeCursor{
|
|
||||||
node: root,
|
|
||||||
wildcards: []KeyEvent{},
|
|
||||||
mouseInfo: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree
|
|
||||||
}
|
|
||||||
|
|
||||||
// A ModeConstraint specifies that an action can only be executed
|
|
||||||
// while a certain mode is enabled or disabled.
|
|
||||||
type ModeConstraint struct {
|
|
||||||
mode string
|
|
||||||
disabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterKeyBinding registers a PaneKeyAction with an Event.
|
|
||||||
func (k *KeyTree) RegisterKeyBinding(e Event, a PaneKeyAction) {
|
|
||||||
k.registerBinding(e, TreeAction{
|
|
||||||
action: a,
|
|
||||||
any: nil,
|
|
||||||
mouse: nil,
|
|
||||||
modes: nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterKeyAnyBinding registers a PaneKeyAnyAction with an Event.
|
|
||||||
// The event should contain an "any" event.
|
|
||||||
func (k *KeyTree) RegisterKeyAnyBinding(e Event, a PaneKeyAnyAction) {
|
|
||||||
k.registerBinding(e, TreeAction{
|
|
||||||
action: nil,
|
|
||||||
any: a,
|
|
||||||
mouse: nil,
|
|
||||||
modes: nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterMouseBinding registers a PaneMouseAction with an Event.
|
|
||||||
// The event should contain a mouse event.
|
|
||||||
func (k *KeyTree) RegisterMouseBinding(e Event, a PaneMouseAction) {
|
|
||||||
k.registerBinding(e, TreeAction{
|
|
||||||
action: nil,
|
|
||||||
any: nil,
|
|
||||||
mouse: a,
|
|
||||||
modes: nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KeyTree) registerBinding(e Event, a TreeAction) {
|
|
||||||
switch ev := e.(type) {
|
|
||||||
case KeyEvent, MouseEvent, RawEvent:
|
|
||||||
newNode, ok := k.root.children[e]
|
|
||||||
if !ok {
|
|
||||||
newNode = NewKeyTreeNode()
|
|
||||||
k.root.children[e] = newNode
|
|
||||||
}
|
|
||||||
// newNode.actions = append(newNode.actions, a)
|
|
||||||
newNode.actions = []TreeAction{a}
|
|
||||||
case KeySequenceEvent:
|
|
||||||
n := k.root
|
|
||||||
for _, key := range ev.keys {
|
|
||||||
newNode, ok := n.children[key]
|
|
||||||
if !ok {
|
|
||||||
newNode = NewKeyTreeNode()
|
|
||||||
n.children[key] = newNode
|
|
||||||
}
|
|
||||||
|
|
||||||
n = newNode
|
|
||||||
}
|
|
||||||
// n.actions = append(n.actions, a)
|
|
||||||
n.actions = []TreeAction{a}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextEvent returns the action for the current sequence where e is the next
|
|
||||||
// event. Even if the action was registered as a PaneKeyAnyAction or PaneMouseAction,
|
|
||||||
// it will be returned as a PaneKeyAction closure where the appropriate arguments
|
|
||||||
// have been provided.
|
|
||||||
// If no action is associated with the given Event, or mode constraints are not
|
|
||||||
// met for that action, nil is returned.
|
|
||||||
// A boolean is returned to indicate if there is a conflict with this action. A
|
|
||||||
// conflict occurs when there is an active action for this event but there are
|
|
||||||
// bindings associated with further sequences starting with this event. The
|
|
||||||
// calling function can decide what to do about the conflict (e.g. use a
|
|
||||||
// timeout).
|
|
||||||
func (k *KeyTree) NextEvent(e Event, mouse *tcell.EventMouse) (PaneKeyAction, bool) {
|
|
||||||
n := k.cursor.node
|
|
||||||
c, ok := n.children[e]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
more := len(c.children) > 0
|
|
||||||
|
|
||||||
k.cursor.node = c
|
|
||||||
|
|
||||||
k.cursor.recordedEvents = append(k.cursor.recordedEvents, e)
|
|
||||||
|
|
||||||
switch ev := e.(type) {
|
|
||||||
case KeyEvent:
|
|
||||||
if ev.any {
|
|
||||||
k.cursor.wildcards = append(k.cursor.wildcards, ev)
|
|
||||||
}
|
|
||||||
case MouseEvent:
|
|
||||||
k.cursor.mouseInfo = mouse
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.actions) > 0 {
|
|
||||||
// check if actions are active
|
|
||||||
for _, a := range c.actions {
|
|
||||||
active := true
|
|
||||||
for _, mc := range a.modes {
|
|
||||||
// if any mode constraint is not met, the action is not active
|
|
||||||
hasMode := k.modes[mc.mode]
|
|
||||||
if hasMode != mc.disabled {
|
|
||||||
active = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if active {
|
|
||||||
// the first active action to be found is returned
|
|
||||||
return k.cursor.MakeClosure(a), more
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, more
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetEvents sets the current sequence back to the initial value.
|
|
||||||
func (k *KeyTree) ResetEvents() {
|
|
||||||
k.cursor.node = k.root
|
|
||||||
k.cursor.wildcards = []KeyEvent{}
|
|
||||||
k.cursor.recordedEvents = []Event{}
|
|
||||||
k.cursor.mouseInfo = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordedEventsStr returns the list of recorded events as a string
|
|
||||||
func (k *KeyTree) RecordedEventsStr() string {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
for _, e := range k.cursor.recordedEvents {
|
|
||||||
buf.WriteString(e.Name())
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteBinding removes any currently active actions associated with the
|
|
||||||
// given event.
|
|
||||||
func (k *KeyTree) DeleteBinding(e Event) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteAllBindings removes all actions associated with the given event,
|
|
||||||
// regardless of whether they are active or not.
|
|
||||||
func (k *KeyTree) DeleteAllBindings(e Event) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMode enables or disabled a given mode
|
|
||||||
func (k *KeyTree) SetMode(mode string, en bool) {
|
|
||||||
k.modes[mode] = en
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasMode returns if the given mode is currently active
|
|
||||||
func (k *KeyTree) HasMode(mode string) bool {
|
|
||||||
return k.modes[mode]
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"github.com/zyedidia/micro/v2/internal/display"
|
"github.com/zyedidia/micro/v2/internal/display"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Pane is a general interface for a window in the editor.
|
|
||||||
type Pane interface {
|
type Pane interface {
|
||||||
Handler
|
Handler
|
||||||
display.Window
|
display.Window
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/display"
|
"github.com/zyedidia/micro/v2/internal/display"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RawPane struct {
|
type RawPane struct {
|
||||||
|
@ -36,8 +36,8 @@ func (h *RawPane) HandleEvent(event tcell.Event) {
|
||||||
|
|
||||||
h.Buf.Insert(h.Cursor.Loc, reflect.TypeOf(event).String()[7:])
|
h.Buf.Insert(h.Cursor.Loc, reflect.TypeOf(event).String()[7:])
|
||||||
|
|
||||||
e, err := ConstructEvent(event)
|
switch e := event.(type) {
|
||||||
if err == nil {
|
case *tcell.EventKey:
|
||||||
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %s", e.Name()))
|
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %s", e.Name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
package action
|
package action
|
||||||
|
|
||||||
import (
|
import (
|
||||||
luar "layeh.com/gopher-luar"
|
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/display"
|
"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/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/views"
|
"github.com/zyedidia/micro/v2/internal/views"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The TabList is a list of tabs and a window to display the tab bar
|
// The TabList is a list of tabs and a window to display the tab bar
|
||||||
|
@ -124,12 +121,6 @@ func (t *TabList) HandleEvent(event tcell.Event) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case tcell.ButtonNone:
|
|
||||||
if t.List[t.Active()].release {
|
|
||||||
// Mouse release received, while already released
|
|
||||||
t.ResetMouse()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case tcell.WheelUp:
|
case tcell.WheelUp:
|
||||||
if my == t.Y {
|
if my == t.Y {
|
||||||
t.Scroll(4)
|
t.Scroll(4)
|
||||||
|
@ -153,64 +144,11 @@ func (t *TabList) Display() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TabList) SetActive(a int) {
|
|
||||||
t.TabWindow.SetActive(a)
|
|
||||||
|
|
||||||
for i, p := range t.List {
|
|
||||||
if i == a {
|
|
||||||
if !p.isActive {
|
|
||||||
p.isActive = true
|
|
||||||
|
|
||||||
err := config.RunPluginFn("onSetActive", luar.New(ulua.L, p.CurPane()))
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.isActive = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetMouse resets the mouse release state after the screen was stopped
|
|
||||||
// or the pane changed.
|
|
||||||
// This prevents situations in which mouse releases are received at the wrong place
|
|
||||||
// and the mouse state is still pressed.
|
|
||||||
func (t *TabList) ResetMouse() {
|
|
||||||
for _, tab := range t.List {
|
|
||||||
if !tab.release && tab.resizing != nil {
|
|
||||||
tab.resizing = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tab.release = true
|
|
||||||
|
|
||||||
for _, p := range tab.Panes {
|
|
||||||
if bp, ok := p.(*BufPane); ok {
|
|
||||||
bp.resetMouse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tabs is the global tab list
|
// Tabs is the global tab list
|
||||||
var Tabs *TabList
|
var Tabs *TabList
|
||||||
|
|
||||||
func InitTabs(bufs []*buffer.Buffer) {
|
func InitTabs(bufs []*buffer.Buffer) {
|
||||||
multiopen := config.GetGlobalOption("multiopen").(string)
|
Tabs = NewTabList(bufs)
|
||||||
if multiopen == "tab" {
|
|
||||||
Tabs = NewTabList(bufs)
|
|
||||||
} else {
|
|
||||||
Tabs = NewTabList(bufs[:1])
|
|
||||||
for _, b := range bufs[1:] {
|
|
||||||
if multiopen == "vsplit" {
|
|
||||||
MainTab().CurPane().VSplitBuf(b)
|
|
||||||
} else { // default hsplit
|
|
||||||
MainTab().CurPane().HSplitBuf(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.RestartCallback = Tabs.ResetMouse
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MainTab() *Tab {
|
func MainTab() *Tab {
|
||||||
|
@ -224,9 +162,6 @@ func MainTab() *Tab {
|
||||||
type Tab struct {
|
type Tab struct {
|
||||||
*views.Node
|
*views.Node
|
||||||
*display.UIWindow
|
*display.UIWindow
|
||||||
|
|
||||||
isActive bool
|
|
||||||
|
|
||||||
Panes []Pane
|
Panes []Pane
|
||||||
active int
|
active int
|
||||||
|
|
||||||
|
@ -240,7 +175,6 @@ func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
|
||||||
t := new(Tab)
|
t := new(Tab)
|
||||||
t.Node = views.NewRoot(x, y, width, height)
|
t.Node = views.NewRoot(x, y, width, height)
|
||||||
t.UIWindow = display.NewUIWindow(t.Node)
|
t.UIWindow = display.NewUIWindow(t.Node)
|
||||||
t.release = true
|
|
||||||
|
|
||||||
e := NewBufPaneFromBuf(b, t)
|
e := NewBufPaneFromBuf(b, t)
|
||||||
e.SetID(t.ID())
|
e.SetID(t.ID())
|
||||||
|
@ -253,7 +187,6 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
|
||||||
t := new(Tab)
|
t := new(Tab)
|
||||||
t.Node = views.NewRoot(x, y, width, height)
|
t.Node = views.NewRoot(x, y, width, height)
|
||||||
t.UIWindow = display.NewUIWindow(t.Node)
|
t.UIWindow = display.NewUIWindow(t.Node)
|
||||||
t.release = true
|
|
||||||
pane.SetTab(t)
|
pane.SetTab(t)
|
||||||
pane.SetID(t.ID())
|
pane.SetID(t.ID())
|
||||||
|
|
||||||
|
@ -264,40 +197,35 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
|
||||||
// HandleEvent takes a tcell event and usually dispatches it to the current
|
// HandleEvent takes a tcell event and usually dispatches it to the current
|
||||||
// active pane. However if the event is a resize or a mouse event where the user
|
// active pane. However if the event is a resize or a mouse event where the user
|
||||||
// is interacting with the UI (resizing splits) then the event is consumed here
|
// is interacting with the UI (resizing splits) then the event is consumed here
|
||||||
// If the event is a mouse press event in a pane, that pane will become active
|
// If the event is a mouse event in a pane, that pane will become active and get
|
||||||
// and get the event
|
// the event
|
||||||
func (t *Tab) HandleEvent(event tcell.Event) {
|
func (t *Tab) HandleEvent(event tcell.Event) {
|
||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
case *tcell.EventMouse:
|
case *tcell.EventMouse:
|
||||||
mx, my := e.Position()
|
mx, my := e.Position()
|
||||||
btn := e.Buttons()
|
switch e.Buttons() {
|
||||||
switch {
|
case tcell.Button1:
|
||||||
case btn & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone:
|
|
||||||
// button press or drag
|
|
||||||
wasReleased := t.release
|
wasReleased := t.release
|
||||||
t.release = false
|
t.release = false
|
||||||
|
if t.resizing != nil {
|
||||||
if btn == tcell.Button1 {
|
var size int
|
||||||
if t.resizing != nil {
|
if t.resizing.Kind == views.STVert {
|
||||||
var size int
|
size = mx - t.resizing.X
|
||||||
if t.resizing.Kind == views.STVert {
|
} else {
|
||||||
size = mx - t.resizing.X
|
size = my - t.resizing.Y + 1
|
||||||
} else {
|
|
||||||
size = my - t.resizing.Y + 1
|
|
||||||
}
|
|
||||||
t.resizing.ResizeSplit(size)
|
|
||||||
t.Resize()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if wasReleased {
|
|
||||||
t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my})
|
|
||||||
if t.resizing != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
t.resizing.ResizeSplit(size)
|
||||||
|
t.Resize()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if wasReleased {
|
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 {
|
for i, p := range t.Panes {
|
||||||
v := p.GetView()
|
v := p.GetView()
|
||||||
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
|
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
|
||||||
|
@ -307,15 +235,10 @@ func (t *Tab) HandleEvent(event tcell.Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case btn == tcell.ButtonNone:
|
case tcell.ButtonNone:
|
||||||
// button release
|
t.resizing = nil
|
||||||
t.release = true
|
t.release = true
|
||||||
if t.resizing != nil {
|
|
||||||
t.resizing = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
// wheel move
|
|
||||||
for _, p := range t.Panes {
|
for _, p := range t.Panes {
|
||||||
v := p.GetView()
|
v := p.GetView()
|
||||||
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
|
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
|
||||||
|
|
|
@ -24,10 +24,7 @@ func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callba
|
||||||
}
|
}
|
||||||
|
|
||||||
t := new(shell.Terminal)
|
t := new(shell.Terminal)
|
||||||
err = t.Start(args, getOutput, wait, callback, userargs)
|
t.Start(args, getOutput, wait, callback, userargs)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.AddTab()
|
h.AddTab()
|
||||||
id := MainTab().Panes[0].ID()
|
id := MainTab().Panes[0].ID()
|
||||||
|
|
|
@ -4,52 +4,14 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
"github.com/zyedidia/clipboard"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
|
||||||
"github.com/zyedidia/micro/v2/internal/display"
|
"github.com/zyedidia/micro/v2/internal/display"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/shell"
|
"github.com/zyedidia/micro/v2/internal/shell"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
"github.com/zyedidia/terminal"
|
"github.com/zyedidia/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TermKeyAction func(*TermPane)
|
|
||||||
|
|
||||||
var TermBindings *KeyTree
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
TermBindings = NewKeyTree()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TermKeyActionGeneral(a TermKeyAction) PaneKeyAction {
|
|
||||||
return func(p Pane) bool {
|
|
||||||
a(p.(*TermPane))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TermMapEvent(k Event, action string) {
|
|
||||||
config.Bindings["terminal"][k.Name()] = action
|
|
||||||
|
|
||||||
switch e := k.(type) {
|
|
||||||
case KeyEvent, KeySequenceEvent, RawEvent:
|
|
||||||
termMapKey(e, action)
|
|
||||||
case MouseEvent:
|
|
||||||
termMapMouse(e, action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func termMapKey(k Event, action string) {
|
|
||||||
if f, ok := TermKeyActions[action]; ok {
|
|
||||||
TermBindings.RegisterKeyBinding(k, TermKeyActionGeneral(f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func termMapMouse(k MouseEvent, action string) {
|
|
||||||
// TODO: map mouse
|
|
||||||
termMapKey(k, action)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TermPane struct {
|
type TermPane struct {
|
||||||
*shell.Terminal
|
*shell.Terminal
|
||||||
display.Window
|
display.Window
|
||||||
|
@ -81,10 +43,6 @@ func (t *TermPane) SetID(i uint64) {
|
||||||
t.id = i
|
t.id = i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TermPane) Name() string {
|
|
||||||
return t.Terminal.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TermPane) SetTab(tab *Tab) {
|
func (t *TermPane) SetTab(tab *Tab) {
|
||||||
t.tab = tab
|
t.tab = tab
|
||||||
}
|
}
|
||||||
|
@ -95,7 +53,6 @@ func (t *TermPane) Tab() *Tab {
|
||||||
|
|
||||||
func (t *TermPane) Close() {}
|
func (t *TermPane) Close() {}
|
||||||
|
|
||||||
// Quit closes this termpane
|
|
||||||
func (t *TermPane) Quit() {
|
func (t *TermPane) Quit() {
|
||||||
t.Close()
|
t.Close()
|
||||||
if len(MainTab().Panes) > 1 {
|
if len(MainTab().Panes) > 1 {
|
||||||
|
@ -109,7 +66,6 @@ func (t *TermPane) Quit() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsplit removes this split
|
|
||||||
func (t *TermPane) Unsplit() {
|
func (t *TermPane) Unsplit() {
|
||||||
n := MainTab().GetNode(t.id)
|
n := MainTab().GetNode(t.id)
|
||||||
n.Unsplit()
|
n.Unsplit()
|
||||||
|
@ -125,26 +81,6 @@ func (t *TermPane) Unsplit() {
|
||||||
// copy-paste
|
// copy-paste
|
||||||
func (t *TermPane) HandleEvent(event tcell.Event) {
|
func (t *TermPane) HandleEvent(event tcell.Event) {
|
||||||
if e, ok := event.(*tcell.EventKey); ok {
|
if e, ok := event.(*tcell.EventKey); ok {
|
||||||
ke := KeyEvent{
|
|
||||||
code: e.Key(),
|
|
||||||
mod: metaToAlt(e.Modifiers()),
|
|
||||||
r: e.Rune(),
|
|
||||||
}
|
|
||||||
action, more := TermBindings.NextEvent(ke, nil)
|
|
||||||
|
|
||||||
if !more {
|
|
||||||
if action != nil {
|
|
||||||
action(t)
|
|
||||||
TermBindings.ResetEvents()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
TermBindings.ResetEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
if more {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Status == shell.TTDone {
|
if t.Status == shell.TTDone {
|
||||||
switch e.Key() {
|
switch e.Key() {
|
||||||
case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter:
|
case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter:
|
||||||
|
@ -154,7 +90,7 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e.Key() == tcell.KeyCtrlC && t.HasSelection() {
|
if e.Key() == tcell.KeyCtrlC && t.HasSelection() {
|
||||||
clipboard.Write(t.GetSelection(t.GetView().Width), clipboard.ClipboardReg)
|
clipboard.WriteAll(t.GetSelection(t.GetView().Width), "clipboard")
|
||||||
InfoBar.Message("Copied selection to clipboard")
|
InfoBar.Message("Copied selection to clipboard")
|
||||||
} else if t.Status != shell.TTDone {
|
} else if t.Status != shell.TTDone {
|
||||||
t.WriteString(event.EscSeq())
|
t.WriteString(event.EscSeq())
|
||||||
|
@ -198,41 +134,6 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit closes the termpane
|
|
||||||
func (t *TermPane) Exit() {
|
|
||||||
t.Terminal.Close()
|
|
||||||
t.Quit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommandMode opens the termpane's command mode
|
|
||||||
func (t *TermPane) CommandMode() {
|
|
||||||
InfoBar.Prompt("> ", "", "TerminalCommand", nil, func(resp string, canceled bool) {
|
|
||||||
if !canceled {
|
|
||||||
t.HandleCommand(resp)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextSplit moves to the next split
|
|
||||||
func (t *TermPane) NextSplit() {
|
|
||||||
a := t.tab.active
|
|
||||||
if a < len(t.tab.Panes)-1 {
|
|
||||||
a++
|
|
||||||
} else {
|
|
||||||
a = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
t.tab.SetActive(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleCommand handles a command for the term pane
|
|
||||||
func (t *TermPane) HandleCommand(input string) {
|
func (t *TermPane) HandleCommand(input string) {
|
||||||
InfoBar.Error("Commands are unsupported in term for now")
|
InfoBar.Error("Commands are unsupported in term for now")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TermKeyActions contains the list of all possible key actions the termpane could execute
|
|
||||||
var TermKeyActions = map[string]TermKeyAction{
|
|
||||||
"Exit": (*TermPane).Exit,
|
|
||||||
"CommandMode": (*TermPane).CommandMode,
|
|
||||||
"NextSplit": (*TermPane).NextSplit,
|
|
||||||
}
|
|
||||||
|
|
|
@ -54,6 +54,8 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
|
||||||
end := c.Loc
|
end := c.Loc
|
||||||
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
|
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
|
||||||
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
|
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
|
||||||
|
} else {
|
||||||
|
// end = start.Move(1, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Replace(start, end, b.Completions[b.CurSuggestion])
|
b.Replace(start, end, b.Completions[b.CurSuggestion])
|
||||||
|
@ -64,26 +66,26 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
|
||||||
|
|
||||||
// GetWord gets the most recent word separated by any separator
|
// GetWord gets the most recent word separated by any separator
|
||||||
// (whitespace, punctuation, any non alphanumeric character)
|
// (whitespace, punctuation, any non alphanumeric character)
|
||||||
func (b *Buffer) GetWord() ([]byte, int) {
|
func GetWord(b *Buffer) ([]byte, int) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
l := b.LineBytes(c.Y)
|
l := b.LineBytes(c.Y)
|
||||||
l = util.SliceStart(l, c.X)
|
l = util.SliceStart(l, c.X)
|
||||||
|
|
||||||
if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc.Move(-1, b))) {
|
if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc)) {
|
||||||
return []byte{}, -1
|
return []byte{}, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.IsNonWordChar(b.RuneAt(c.Loc.Move(-1, b))) {
|
if util.IsNonAlphaNumeric(b.RuneAt(c.Loc)) {
|
||||||
return []byte{}, c.X
|
return []byte{}, c.X
|
||||||
}
|
}
|
||||||
|
|
||||||
args := bytes.FieldsFunc(l, util.IsNonWordChar)
|
args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
|
||||||
input := args[len(args)-1]
|
input := args[len(args)-1]
|
||||||
return input, c.X - util.CharacterCount(input)
|
return input, c.X - util.CharacterCount(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetArg gets the most recent word (separated by ' ' only)
|
// GetArg gets the most recent word (separated by ' ' only)
|
||||||
func (b *Buffer) GetArg() (string, int) {
|
func GetArg(b *Buffer) (string, int) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
l := b.LineBytes(c.Y)
|
l := b.LineBytes(c.Y)
|
||||||
l = util.SliceStart(l, c.X)
|
l = util.SliceStart(l, c.X)
|
||||||
|
@ -104,7 +106,7 @@ func (b *Buffer) GetArg() (string, int) {
|
||||||
// FileComplete autocompletes filenames
|
// FileComplete autocompletes filenames
|
||||||
func FileComplete(b *Buffer) ([]string, []string) {
|
func FileComplete(b *Buffer) ([]string, []string) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
input, argstart := b.GetArg()
|
input, argstart := GetArg(b)
|
||||||
|
|
||||||
sep := string(os.PathSeparator)
|
sep := string(os.PathSeparator)
|
||||||
dirs := strings.Split(input, sep)
|
dirs := strings.Split(input, sep)
|
||||||
|
@ -153,7 +155,7 @@ func FileComplete(b *Buffer) ([]string, []string) {
|
||||||
// BufferComplete autocompletes based on previous words in the buffer
|
// BufferComplete autocompletes based on previous words in the buffer
|
||||||
func BufferComplete(b *Buffer) ([]string, []string) {
|
func BufferComplete(b *Buffer) ([]string, []string) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
input, argstart := b.GetWord()
|
input, argstart := GetWord(b)
|
||||||
|
|
||||||
if argstart == -1 {
|
if argstart == -1 {
|
||||||
return []string{}, []string{}
|
return []string{}, []string{}
|
||||||
|
@ -166,7 +168,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
|
||||||
var suggestions []string
|
var suggestions []string
|
||||||
for i := c.Y; i >= 0; i-- {
|
for i := c.Y; i >= 0; i-- {
|
||||||
l := b.LineBytes(i)
|
l := b.LineBytes(i)
|
||||||
words := bytes.FieldsFunc(l, util.IsNonWordChar)
|
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
|
||||||
for _, w := range words {
|
for _, w := range words {
|
||||||
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
|
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
|
||||||
strw := string(w)
|
strw := string(w)
|
||||||
|
@ -179,7 +181,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
|
||||||
}
|
}
|
||||||
for i := c.Y + 1; i < b.LinesNum(); i++ {
|
for i := c.Y + 1; i < b.LinesNum(); i++ {
|
||||||
l := b.LineBytes(i)
|
l := b.LineBytes(i)
|
||||||
words := bytes.FieldsFunc(l, util.IsNonWordChar)
|
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
|
||||||
for _, w := range words {
|
for _, w := range words {
|
||||||
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
|
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
|
||||||
strw := string(w)
|
strw := string(w)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
|
@ -26,60 +25,32 @@ The backup was created on %s, and the file is
|
||||||
When the buffer is closed, the backup will be removed.
|
When the buffer is closed, the backup will be removed.
|
||||||
* 'ignore' will ignore the backup, discarding its changes. The backup file
|
* 'ignore' will ignore the backup, discarding its changes. The backup file
|
||||||
will be removed.
|
will be removed.
|
||||||
* 'abort' will abort the open operation, and instead open an empty buffer.
|
|
||||||
|
|
||||||
Options: [r]ecover, [i]gnore, [a]bort: `
|
Options: [r]ecover, [i]gnore: `
|
||||||
|
|
||||||
var backupRequestChan chan *Buffer
|
|
||||||
|
|
||||||
func backupThread() {
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Second * 8)
|
|
||||||
|
|
||||||
for len(backupRequestChan) > 0 {
|
|
||||||
b := <-backupRequestChan
|
|
||||||
bfini := atomic.LoadInt32(&(b.fini)) != 0
|
|
||||||
if !bfini {
|
|
||||||
b.Backup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
backupRequestChan = make(chan *Buffer, 10)
|
|
||||||
|
|
||||||
go backupThread()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Buffer) RequestBackup() {
|
|
||||||
if !b.requestedBackup {
|
|
||||||
select {
|
|
||||||
case backupRequestChan <- b:
|
|
||||||
default:
|
|
||||||
// channel is full
|
|
||||||
}
|
|
||||||
b.requestedBackup = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backup saves the current buffer to ConfigDir/backups
|
// Backup saves the current buffer to ConfigDir/backups
|
||||||
func (b *Buffer) Backup() error {
|
func (b *Buffer) Backup(checkTime bool) error {
|
||||||
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
|
if checkTime {
|
||||||
if backupdir == "" || err != nil {
|
sub := time.Now().Sub(b.lastbackup)
|
||||||
backupdir = filepath.Join(config.ConfigDir, "backups")
|
if sub < time.Duration(backupTime)*time.Millisecond {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.lastbackup = time.Now()
|
||||||
|
|
||||||
|
backupdir := filepath.Join(config.ConfigDir, "backups")
|
||||||
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
||||||
os.Mkdir(backupdir, os.ModePerm)
|
os.Mkdir(backupdir, os.ModePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
|
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
|
||||||
|
|
||||||
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
|
err := overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
|
||||||
if len(b.lines) == 0 {
|
if len(b.lines) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -103,14 +74,12 @@ func (b *Buffer) Backup() error {
|
||||||
return
|
return
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
b.requestedBackup = false
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveBackup removes any backup file associated with this buffer
|
// RemoveBackup removes any backup file associated with this buffer
|
||||||
func (b *Buffer) RemoveBackup() {
|
func (b *Buffer) RemoveBackup() {
|
||||||
if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||||
|
@ -119,8 +88,8 @@ func (b *Buffer) RemoveBackup() {
|
||||||
|
|
||||||
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
|
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
|
||||||
// Returns true if a backup was applied
|
// Returns true if a backup was applied
|
||||||
func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
|
func (b *Buffer) ApplyBackup(fsize int64) bool {
|
||||||
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
|
if b.Settings["backup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
|
||||||
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||||
if info, err := os.Stat(backupfile); err == nil {
|
if info, err := os.Stat(backupfile); err == nil {
|
||||||
backup, err := os.Open(backupfile)
|
backup, err := os.Open(backupfile)
|
||||||
|
@ -128,22 +97,20 @@ func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
|
||||||
defer backup.Close()
|
defer backup.Close()
|
||||||
t := info.ModTime()
|
t := info.ModTime()
|
||||||
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
|
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
|
||||||
choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)
|
choice := screen.TermPrompt(msg, []string{"r", "i", "recover", "ignore"}, true)
|
||||||
|
|
||||||
if choice%3 == 0 {
|
if choice%2 == 0 {
|
||||||
// recover
|
// recover
|
||||||
b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
|
b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
|
||||||
b.isModified = true
|
b.isModified = true
|
||||||
return true, true
|
return true
|
||||||
} else if choice%3 == 1 {
|
} else if choice%2 == 1 {
|
||||||
// delete
|
// delete
|
||||||
os.Remove(backupfile)
|
os.Remove(backupfile)
|
||||||
} else if choice%3 == 2 {
|
|
||||||
return false, false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, true
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
luar "layeh.com/gopher-luar"
|
luar "layeh.com/gopher-luar"
|
||||||
|
@ -57,7 +56,7 @@ var (
|
||||||
BTLog = BufType{2, true, true, false}
|
BTLog = BufType{2, true, true, false}
|
||||||
// BTScratch is a buffer that cannot be saved (for scratch work)
|
// BTScratch is a buffer that cannot be saved (for scratch work)
|
||||||
BTScratch = BufType{3, false, true, false}
|
BTScratch = BufType{3, false, true, false}
|
||||||
// BTRaw is a buffer that shows raw terminal events
|
// BTRaw is is a buffer that shows raw terminal events
|
||||||
BTRaw = BufType{4, false, true, false}
|
BTRaw = BufType{4, false, true, false}
|
||||||
// BTInfo is a buffer for inputting information
|
// BTInfo is a buffer for inputting information
|
||||||
BTInfo = BufType{5, false, true, false}
|
BTInfo = BufType{5, false, true, false}
|
||||||
|
@ -103,7 +102,9 @@ type SharedBuffer struct {
|
||||||
diffLock sync.RWMutex
|
diffLock sync.RWMutex
|
||||||
diff map[int]DiffStatus
|
diff map[int]DiffStatus
|
||||||
|
|
||||||
requestedBackup bool
|
// counts the number of edits
|
||||||
|
// resets every backupTime edits
|
||||||
|
lastbackup time.Time
|
||||||
|
|
||||||
// ReloadDisabled allows the user to disable reloads if they
|
// ReloadDisabled allows the user to disable reloads if they
|
||||||
// are viewing a file that is constantly changing
|
// are viewing a file that is constantly changing
|
||||||
|
@ -146,20 +147,18 @@ func (b *SharedBuffer) remove(start, end Loc) []byte {
|
||||||
func (b *SharedBuffer) MarkModified(start, end int) {
|
func (b *SharedBuffer) MarkModified(start, end int) {
|
||||||
b.ModifiedThisFrame = true
|
b.ModifiedThisFrame = true
|
||||||
|
|
||||||
|
if !b.Settings["syntax"].(bool) || b.SyntaxDef == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
start = util.Clamp(start, 0, len(b.lines)-1)
|
start = util.Clamp(start, 0, len(b.lines)-1)
|
||||||
end = util.Clamp(end, 0, len(b.lines)-1)
|
end = util.Clamp(end, 0, len(b.lines)-1)
|
||||||
|
|
||||||
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
|
l := -1
|
||||||
l := -1
|
|
||||||
for i := start; i <= end; i++ {
|
|
||||||
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
|
|
||||||
}
|
|
||||||
b.Highlighter.HighlightMatches(b, start, l)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := start; i <= end; i++ {
|
for i := start; i <= end; i++ {
|
||||||
b.LineArray.invalidateSearchMatches(i)
|
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
|
||||||
}
|
}
|
||||||
|
b.Highlighter.HighlightMatches(b, start, l)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableReload disables future reloads of this sharedbuffer
|
// DisableReload disables future reloads of this sharedbuffer
|
||||||
|
@ -183,34 +182,13 @@ type DiffStatus byte
|
||||||
// The syntax highlighting info must be stored with the buffer because the syntax
|
// The syntax highlighting info must be stored with the buffer because the syntax
|
||||||
// highlighter attaches information to each line of the buffer for optimization
|
// highlighter attaches information to each line of the buffer for optimization
|
||||||
// purposes so it doesn't have to rehighlight everything on every update.
|
// purposes so it doesn't have to rehighlight everything on every update.
|
||||||
// Likewise for the search highlighting.
|
|
||||||
type Buffer struct {
|
type Buffer struct {
|
||||||
*EventHandler
|
*EventHandler
|
||||||
*SharedBuffer
|
*SharedBuffer
|
||||||
|
|
||||||
fini int32
|
|
||||||
cursors []*Cursor
|
cursors []*Cursor
|
||||||
curCursor int
|
curCursor int
|
||||||
StartCursor Loc
|
StartCursor Loc
|
||||||
|
|
||||||
// OptionCallback is called after a buffer option value is changed.
|
|
||||||
// The display module registers its OptionCallback to ensure the buffer window
|
|
||||||
// is properly updated when needed. This is a workaround for the fact that
|
|
||||||
// the buffer module cannot directly call the display's API (it would mean
|
|
||||||
// a circular dependency between packages).
|
|
||||||
OptionCallback func(option string, nativeValue interface{})
|
|
||||||
|
|
||||||
// The display module registers its own GetVisualX function for getting
|
|
||||||
// the correct visual x location of a cursor when softwrap is used.
|
|
||||||
// This is hacky. Maybe it would be better to move all the visual x logic
|
|
||||||
// from buffer to display, but it would require rewriting a lot of code.
|
|
||||||
GetVisualX func(loc Loc) int
|
|
||||||
|
|
||||||
// Last search stores the last successful search
|
|
||||||
LastSearch string
|
|
||||||
LastSearchRegex bool
|
|
||||||
// HighlightSearch enables highlighting all instances of the last successful search
|
|
||||||
HighlightSearch bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
|
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
|
||||||
|
@ -234,39 +212,21 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.OpenFile(filename, os.O_WRONLY, 0)
|
file, err := os.Open(filename)
|
||||||
readonly := os.IsPermission(err)
|
fileInfo, _ := os.Stat(filename)
|
||||||
f.Close()
|
|
||||||
|
|
||||||
fileInfo, serr := os.Stat(filename)
|
if err == nil && fileInfo.IsDir() {
|
||||||
if serr != nil && !os.IsNotExist(serr) {
|
|
||||||
return nil, serr
|
|
||||||
}
|
|
||||||
if serr == nil && fileInfo.IsDir() {
|
|
||||||
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
|
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(filename)
|
defer file.Close()
|
||||||
if err == nil {
|
|
||||||
defer file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf *Buffer
|
var buf *Buffer
|
||||||
if os.IsNotExist(err) {
|
if err != nil {
|
||||||
// File does not exist -- create an empty buffer with that name
|
// File does not exist -- create an empty buffer with that name
|
||||||
buf = NewBufferFromString("", filename, btype)
|
buf = NewBufferFromString("", filename, btype)
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
} else {
|
||||||
buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
|
buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
|
||||||
if buf == nil {
|
|
||||||
return nil, errors.New("could not open file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if readonly && prompt != nil {
|
|
||||||
prompt.Message(fmt.Sprintf("Warning: file is readonly - %s will be attempted when saving", config.GlobalSettings["sucmd"].(string)))
|
|
||||||
// buf.SetOptionNative("readonly", true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf, nil
|
return buf, nil
|
||||||
|
@ -296,10 +256,7 @@ func NewBufferFromString(text, path string, btype BufType) *Buffer {
|
||||||
// Places the cursor at startcursor. If startcursor is -1, -1 places the
|
// Places the cursor at startcursor. If startcursor is -1, -1 places the
|
||||||
// cursor at an autodetected location (based on savecursor or :LINE:COL)
|
// cursor at an autodetected location (based on savecursor or :LINE:COL)
|
||||||
func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufType) *Buffer {
|
func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufType) *Buffer {
|
||||||
absPath, err := filepath.Abs(path)
|
absPath, _ := filepath.Abs(path)
|
||||||
if err != nil {
|
|
||||||
absPath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
b := new(Buffer)
|
b := new(Buffer)
|
||||||
|
|
||||||
|
@ -314,7 +271,6 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasBackup := false
|
|
||||||
if !found {
|
if !found {
|
||||||
b.SharedBuffer = new(SharedBuffer)
|
b.SharedBuffer = new(SharedBuffer)
|
||||||
b.Type = btype
|
b.Type = btype
|
||||||
|
@ -322,38 +278,23 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||||
b.AbsPath = absPath
|
b.AbsPath = absPath
|
||||||
b.Path = path
|
b.Path = path
|
||||||
|
|
||||||
// this is a little messy since we need to know some settings to read
|
|
||||||
// the file properly, but some settings depend on the filetype, which
|
|
||||||
// we don't know until reading the file. We first read the settings
|
|
||||||
// into a local variable and then use that to determine the encoding,
|
|
||||||
// readonly, and fileformat necessary for reading the file and
|
|
||||||
// assigning the filetype.
|
|
||||||
settings := config.DefaultCommonSettings()
|
|
||||||
b.Settings = config.DefaultCommonSettings()
|
b.Settings = config.DefaultCommonSettings()
|
||||||
for k, v := range config.GlobalSettings {
|
for k, v := range config.GlobalSettings {
|
||||||
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
|
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
|
||||||
// make sure setting is not global-only
|
// make sure setting is not global-only
|
||||||
settings[k] = v
|
|
||||||
b.Settings[k] = v
|
b.Settings[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.InitLocalSettings(settings, absPath)
|
config.InitLocalSettings(b.Settings, path)
|
||||||
b.Settings["readonly"] = settings["readonly"]
|
|
||||||
b.Settings["filetype"] = settings["filetype"]
|
|
||||||
b.Settings["syntax"] = settings["syntax"]
|
|
||||||
|
|
||||||
enc, err := htmlindex.Get(settings["encoding"].(string))
|
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
enc = unicode.UTF8
|
enc = unicode.UTF8
|
||||||
b.Settings["encoding"] = "utf-8"
|
b.Settings["encoding"] = "utf-8"
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
hasBackup := b.ApplyBackup(size)
|
||||||
hasBackup, ok = b.ApplyBackup(size)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return NewBufferFromString("", "", btype)
|
|
||||||
}
|
|
||||||
if !hasBackup {
|
if !hasBackup {
|
||||||
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
|
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
|
||||||
|
|
||||||
|
@ -362,7 +303,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
// for empty files, use the fileformat setting instead of
|
// for empty files, use the fileformat setting instead of
|
||||||
// autodetection
|
// autodetection
|
||||||
switch settings["fileformat"] {
|
switch b.Settings["fileformat"] {
|
||||||
case "unix":
|
case "unix":
|
||||||
ff = FFUnix
|
ff = FFUnix
|
||||||
case "dos":
|
case "dos":
|
||||||
|
@ -399,10 +340,12 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||||
|
|
||||||
if startcursor.X != -1 && startcursor.Y != -1 {
|
if startcursor.X != -1 && startcursor.Y != -1 {
|
||||||
b.StartCursor = startcursor
|
b.StartCursor = startcursor
|
||||||
} else if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
|
} else {
|
||||||
err := b.Unserialize()
|
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
|
||||||
if err != nil {
|
err := b.Unserialize()
|
||||||
screen.TermMessage(err)
|
if err != nil {
|
||||||
|
screen.TermMessage(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,14 +356,12 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||||
if size > LargeFileThreshold {
|
if size > LargeFileThreshold {
|
||||||
// If the file is larger than LargeFileThreshold fastdirty needs to be on
|
// If the file is larger than LargeFileThreshold fastdirty needs to be on
|
||||||
b.Settings["fastdirty"] = true
|
b.Settings["fastdirty"] = true
|
||||||
} else if !hasBackup {
|
} else {
|
||||||
// since applying a backup does not save the applied backup to disk, we should
|
|
||||||
// not calculate the original hash based on the backup data
|
|
||||||
calcHash(b, &b.origHash)
|
calcHash(b, &b.origHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
|
err := config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
}
|
}
|
||||||
|
@ -430,15 +371,6 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseOpenBuffers removes all open buffers
|
|
||||||
func CloseOpenBuffers() {
|
|
||||||
for i, buf := range OpenBuffers {
|
|
||||||
buf.Fini()
|
|
||||||
OpenBuffers[i] = nil
|
|
||||||
}
|
|
||||||
OpenBuffers = OpenBuffers[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes this buffer from the list of open buffers
|
// Close removes this buffer from the list of open buffers
|
||||||
func (b *Buffer) Close() {
|
func (b *Buffer) Close() {
|
||||||
for i, buf := range OpenBuffers {
|
for i, buf := range OpenBuffers {
|
||||||
|
@ -463,8 +395,6 @@ func (b *Buffer) Fini() {
|
||||||
if b.Type == BTStdout {
|
if b.Type == BTStdout {
|
||||||
fmt.Fprint(util.Stdout, string(b.Bytes()))
|
fmt.Fprint(util.Stdout, string(b.Bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.StoreInt32(&(b.fini), int32(1))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the name that should be displayed in the statusline
|
// GetName returns the name that should be displayed in the statusline
|
||||||
|
@ -483,7 +413,7 @@ func (b *Buffer) GetName() string {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetName changes the name for this buffer
|
//SetName changes the name for this buffer
|
||||||
func (b *Buffer) SetName(s string) {
|
func (b *Buffer) SetName(s string) {
|
||||||
b.name = s
|
b.name = s
|
||||||
}
|
}
|
||||||
|
@ -495,7 +425,7 @@ func (b *Buffer) Insert(start Loc, text string) {
|
||||||
b.EventHandler.active = b.curCursor
|
b.EventHandler.active = b.curCursor
|
||||||
b.EventHandler.Insert(start, text)
|
b.EventHandler.Insert(start, text)
|
||||||
|
|
||||||
b.RequestBackup()
|
go b.Backup(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,7 +436,7 @@ func (b *Buffer) Remove(start, end Loc) {
|
||||||
b.EventHandler.active = b.curCursor
|
b.EventHandler.active = b.curCursor
|
||||||
b.EventHandler.Remove(start, end)
|
b.EventHandler.Remove(start, end)
|
||||||
|
|
||||||
b.RequestBackup()
|
go b.Backup(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,13 +498,6 @@ func (b *Buffer) RelocateCursors() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeselectCursors removes selection from all cursors
|
|
||||||
func (b *Buffer) DeselectCursors() {
|
|
||||||
for _, c := range b.cursors {
|
|
||||||
c.Deselect(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuneAt returns the rune at a given location in the buffer
|
// RuneAt returns the rune at a given location in the buffer
|
||||||
func (b *Buffer) RuneAt(loc Loc) rune {
|
func (b *Buffer) RuneAt(loc Loc) rune {
|
||||||
line := b.LineBytes(loc.Y)
|
line := b.LineBytes(loc.Y)
|
||||||
|
@ -583,38 +506,16 @@ func (b *Buffer) RuneAt(loc Loc) rune {
|
||||||
for len(line) > 0 {
|
for len(line) > 0 {
|
||||||
r, _, size := util.DecodeCharacter(line)
|
r, _, size := util.DecodeCharacter(line)
|
||||||
line = line[size:]
|
line = line[size:]
|
||||||
|
i++
|
||||||
|
|
||||||
if i == loc.X {
|
if i == loc.X {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return '\n'
|
return '\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
// WordAt returns the word around a given location in the buffer
|
|
||||||
func (b *Buffer) WordAt(loc Loc) []byte {
|
|
||||||
if len(b.LineBytes(loc.Y)) == 0 || !util.IsWordChar(b.RuneAt(loc)) {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
|
|
||||||
start := loc
|
|
||||||
end := loc.Move(1, b)
|
|
||||||
|
|
||||||
for start.X > 0 && util.IsWordChar(b.RuneAt(start.Move(-1, b))) {
|
|
||||||
start.X--
|
|
||||||
}
|
|
||||||
|
|
||||||
lineLen := util.CharacterCount(b.LineBytes(loc.Y))
|
|
||||||
for end.X < lineLen && util.IsWordChar(b.RuneAt(end)) {
|
|
||||||
end.X++
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.Substr(start, end)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modified returns if this buffer has been modified since
|
// Modified returns if this buffer has been modified since
|
||||||
// being opened
|
// being opened
|
||||||
func (b *Buffer) Modified() bool {
|
func (b *Buffer) Modified() bool {
|
||||||
|
@ -632,22 +533,6 @@ func (b *Buffer) Modified() bool {
|
||||||
return buff != b.origHash
|
return buff != b.origHash
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the number of bytes in the current buffer
|
|
||||||
func (b *Buffer) Size() int {
|
|
||||||
nb := 0
|
|
||||||
for i := 0; i < b.LinesNum(); i++ {
|
|
||||||
nb += len(b.LineBytes(i))
|
|
||||||
|
|
||||||
if i != b.LinesNum()-1 {
|
|
||||||
if b.Endings == FFDos {
|
|
||||||
nb++ // carriage return
|
|
||||||
}
|
|
||||||
nb++ // newline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nb
|
|
||||||
}
|
|
||||||
|
|
||||||
// calcHash calculates md5 hash of all lines in the buffer
|
// calcHash calculates md5 hash of all lines in the buffer
|
||||||
func calcHash(b *Buffer, out *[md5.Size]byte) error {
|
func calcHash(b *Buffer, out *[md5.Size]byte) error {
|
||||||
h := md5.New()
|
h := md5.New()
|
||||||
|
@ -682,103 +567,6 @@ func calcHash(b *Buffer, out *[md5.Size]byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDefFromFile(f config.RuntimeFile, header *highlight.Header) *highlight.Def {
|
|
||||||
data, err := f.Data()
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if header == nil {
|
|
||||||
header, err = highlight.MakeHeaderYaml(data)
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage("Error parsing header for syntax file " + f.Name() + ": " + err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := highlight.ParseFile(data)
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
syndef, err := highlight.ParseDef(file, header)
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return syndef
|
|
||||||
}
|
|
||||||
|
|
||||||
// findRealRuntimeSyntaxDef finds a specific syntax definition
|
|
||||||
// in the user's custom syntax files
|
|
||||||
func findRealRuntimeSyntaxDef(name string, header *highlight.Header) *highlight.Def {
|
|
||||||
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
|
|
||||||
if f.Name() == name {
|
|
||||||
syndef := parseDefFromFile(f, header)
|
|
||||||
if syndef != nil {
|
|
||||||
return syndef
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findRuntimeSyntaxDef finds a specific syntax definition
|
|
||||||
// in the built-in syntax files
|
|
||||||
func findRuntimeSyntaxDef(name string, header *highlight.Header) *highlight.Def {
|
|
||||||
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
|
|
||||||
if f.Name() == name {
|
|
||||||
syndef := parseDefFromFile(f, header)
|
|
||||||
if syndef != nil {
|
|
||||||
return syndef
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveIncludes(syndef *highlight.Def) {
|
|
||||||
includes := highlight.GetIncludes(syndef)
|
|
||||||
if len(includes) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var files []*highlight.File
|
|
||||||
for _, f := range config.ListRuntimeFiles(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)
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, i := range includes {
|
|
||||||
if header.FileType == i {
|
|
||||||
file, err := highlight.ParseFile(data)
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(files) >= len(includes) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
highlight.ResolveIncludes(syndef, files)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRules updates the syntax rules and filetype for this buffer
|
// UpdateRules updates the syntax rules and filetype for this buffer
|
||||||
// This is called when the colorscheme changes
|
// This is called when the colorscheme changes
|
||||||
func (b *Buffer) UpdateRules() {
|
func (b *Buffer) UpdateRules() {
|
||||||
|
@ -787,32 +575,13 @@ func (b *Buffer) UpdateRules() {
|
||||||
}
|
}
|
||||||
ft := b.Settings["filetype"].(string)
|
ft := b.Settings["filetype"].(string)
|
||||||
if ft == "off" {
|
if ft == "off" {
|
||||||
b.ClearMatches()
|
|
||||||
b.SyntaxDef = nil
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b.SyntaxDef = nil
|
|
||||||
|
|
||||||
// syntaxFileInfo is an internal helper structure
|
|
||||||
// to store properties of one single syntax file
|
|
||||||
type syntaxFileInfo struct {
|
|
||||||
header *highlight.Header
|
|
||||||
fileName string
|
|
||||||
syntaxDef *highlight.Def
|
|
||||||
}
|
|
||||||
|
|
||||||
fnameMatches := []syntaxFileInfo{}
|
|
||||||
headerMatches := []syntaxFileInfo{}
|
|
||||||
syntaxFile := ""
|
syntaxFile := ""
|
||||||
foundDef := false
|
foundDef := false
|
||||||
var header *highlight.Header
|
var header *highlight.Header
|
||||||
// search for the syntax file in the user's custom syntax files
|
// search for the syntax file in the user's custom syntax files
|
||||||
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
|
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
|
||||||
if f.Name() == "default" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := f.Data()
|
data, err := f.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||||
|
@ -820,149 +589,118 @@ func (b *Buffer) UpdateRules() {
|
||||||
}
|
}
|
||||||
|
|
||||||
header, err = highlight.MakeHeaderYaml(data)
|
header, err = highlight.MakeHeaderYaml(data)
|
||||||
|
file, err := highlight.ParseFile(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage("Error parsing header for syntax file " + f.Name() + ": " + err.Error())
|
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
matchedFileType := false
|
if ((ft == "unknown" || ft == "") && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
|
||||||
matchedFileName := false
|
|
||||||
matchedFileHeader := false
|
|
||||||
|
|
||||||
if ft == "unknown" || ft == "" {
|
|
||||||
if header.MatchFileName(b.Path) {
|
|
||||||
matchedFileName = true
|
|
||||||
}
|
|
||||||
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {
|
|
||||||
matchedFileHeader = true
|
|
||||||
}
|
|
||||||
} else if header.FileType == ft {
|
|
||||||
matchedFileType = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if matchedFileType || matchedFileName || matchedFileHeader {
|
|
||||||
file, err := highlight.ParseFile(data)
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
syndef, err := highlight.ParseDef(file, header)
|
syndef, err := highlight.ParseDef(file, header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
b.SyntaxDef = syndef
|
||||||
if matchedFileType {
|
syntaxFile = f.Name()
|
||||||
b.SyntaxDef = syndef
|
foundDef = true
|
||||||
syntaxFile = f.Name()
|
break
|
||||||
foundDef = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if matchedFileName {
|
|
||||||
fnameMatches = append(fnameMatches, syntaxFileInfo{header, f.Name(), syndef})
|
|
||||||
} else if matchedFileHeader {
|
|
||||||
headerMatches = append(headerMatches, syntaxFileInfo{header, f.Name(), syndef})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundDef {
|
// search in the default syntax files
|
||||||
// search for the syntax file in the built-in syntax files
|
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
|
||||||
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
|
data, err := f.Data()
|
||||||
data, err := f.Data()
|
if err != nil {
|
||||||
if err != nil {
|
screen.TermMessage("Error loading syntax header file " + f.Name() + ": " + err.Error())
|
||||||
screen.TermMessage("Error loading syntax header file " + f.Name() + ": " + err.Error())
|
continue
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
|
|
||||||
header, err = highlight.MakeHeader(data)
|
header, err = highlight.MakeHeader(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage("Error reading syntax header file", f.Name(), err)
|
screen.TermMessage("Error reading syntax header file", f.Name(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ft == "unknown" || ft == "" {
|
if ft == "unknown" || ft == "" {
|
||||||
if header.MatchFileName(b.Path) {
|
if highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data) {
|
||||||
fnameMatches = append(fnameMatches, syntaxFileInfo{header, f.Name(), nil})
|
|
||||||
}
|
|
||||||
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {
|
|
||||||
headerMatches = append(headerMatches, syntaxFileInfo{header, f.Name(), nil})
|
|
||||||
}
|
|
||||||
} else if header.FileType == ft {
|
|
||||||
syntaxFile = f.Name()
|
syntaxFile = f.Name()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
} else if header.FileType == ft {
|
||||||
}
|
syntaxFile = f.Name()
|
||||||
|
break
|
||||||
if syntaxFile == "" {
|
|
||||||
matches := fnameMatches
|
|
||||||
if len(matches) == 0 {
|
|
||||||
matches = headerMatches
|
|
||||||
}
|
|
||||||
|
|
||||||
length := len(matches)
|
|
||||||
if length > 0 {
|
|
||||||
signatureMatch := false
|
|
||||||
if length > 1 {
|
|
||||||
// multiple matching syntax files found, try to resolve the ambiguity
|
|
||||||
// using signatures
|
|
||||||
detectlimit := util.IntOpt(b.Settings["detectlimit"])
|
|
||||||
lineCount := len(b.lines)
|
|
||||||
limit := lineCount
|
|
||||||
if detectlimit > 0 && lineCount > detectlimit {
|
|
||||||
limit = detectlimit
|
|
||||||
}
|
|
||||||
|
|
||||||
matchLoop:
|
|
||||||
for _, m := range matches {
|
|
||||||
if m.header.HasFileSignature() {
|
|
||||||
for i := 0; i < limit; i++ {
|
|
||||||
if m.header.MatchFileSignature(b.lines[i].data) {
|
|
||||||
syntaxFile = m.fileName
|
|
||||||
if m.syntaxDef != nil {
|
|
||||||
b.SyntaxDef = m.syntaxDef
|
|
||||||
foundDef = true
|
|
||||||
}
|
|
||||||
header = m.header
|
|
||||||
signatureMatch = true
|
|
||||||
break matchLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if length == 1 || !signatureMatch {
|
|
||||||
syntaxFile = matches[0].fileName
|
|
||||||
if matches[0].syntaxDef != nil {
|
|
||||||
b.SyntaxDef = matches[0].syntaxDef
|
|
||||||
foundDef = true
|
|
||||||
}
|
|
||||||
header = matches[0].header
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if syntaxFile != "" && !foundDef {
|
if syntaxFile != "" && !foundDef {
|
||||||
// we found a syntax file using a syntax header file
|
// we found a syntax file using a syntax header file
|
||||||
b.SyntaxDef = findRuntimeSyntaxDef(syntaxFile, header)
|
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
|
||||||
}
|
if f.Name() == syntaxFile {
|
||||||
|
data, err := f.Data()
|
||||||
|
if err != nil {
|
||||||
|
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if b.SyntaxDef != nil {
|
file, err := highlight.ParseFile(data)
|
||||||
b.Settings["filetype"] = b.SyntaxDef.FileType
|
if err != nil {
|
||||||
} else {
|
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||||
// search for the default file in the user's custom syntax files
|
continue
|
||||||
b.SyntaxDef = findRealRuntimeSyntaxDef("default", nil)
|
}
|
||||||
if b.SyntaxDef == nil {
|
|
||||||
// search for the default file in the built-in syntax files
|
syndef, err := highlight.ParseDef(file, header)
|
||||||
b.SyntaxDef = findRuntimeSyntaxDef("default", nil)
|
if err != nil {
|
||||||
|
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.SyntaxDef = syndef
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.SyntaxDef != nil {
|
if b.SyntaxDef != nil && highlight.HasIncludes(b.SyntaxDef) {
|
||||||
resolveIncludes(b.SyntaxDef)
|
includes := highlight.GetIncludes(b.SyntaxDef)
|
||||||
|
|
||||||
|
var files []*highlight.File
|
||||||
|
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
|
||||||
|
data, err := f.Data()
|
||||||
|
if err != nil {
|
||||||
|
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
header, err := highlight.MakeHeaderYaml(data)
|
||||||
|
if err != nil {
|
||||||
|
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range includes {
|
||||||
|
if header.FileType == i {
|
||||||
|
file, err := highlight.ParseFile(data)
|
||||||
|
if err != nil {
|
||||||
|
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files = append(files, file)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(files) >= len(includes) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
highlight.ResolveIncludes(b.SyntaxDef, files)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Highlighter == nil || syntaxFile != "" {
|
||||||
|
if b.SyntaxDef != nil {
|
||||||
|
b.Settings["filetype"] = b.SyntaxDef.FileType
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.SyntaxDef = &highlight.EmptyDef
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.SyntaxDef != nil {
|
if b.SyntaxDef != nil {
|
||||||
|
@ -1064,7 +802,7 @@ func (b *Buffer) MergeCursors() {
|
||||||
b.EventHandler.active = b.curCursor
|
b.EventHandler.active = b.curCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCursors updates all the cursors indices
|
// UpdateCursors updates all the cursors indicies
|
||||||
func (b *Buffer) UpdateCursors() {
|
func (b *Buffer) UpdateCursors() {
|
||||||
b.EventHandler.cursors = b.cursors
|
b.EventHandler.cursors = b.cursors
|
||||||
b.EventHandler.active = b.curCursor
|
b.EventHandler.active = b.curCursor
|
||||||
|
@ -1140,14 +878,34 @@ var BracePairs = [][2]rune{
|
||||||
{'[', ']'},
|
{'[', ']'},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) findMatchingBrace(braceType [2]rune, start Loc, char rune) (Loc, bool) {
|
// FindMatchingBrace returns the location in the buffer of the matching bracket
|
||||||
|
// It is given a brace type containing the open and closing character, (for example
|
||||||
|
// '{' and '}') as well as the location to match from
|
||||||
|
// TODO: maybe can be more efficient with utf8 package
|
||||||
|
// returns the location of the matching brace
|
||||||
|
// if the boolean returned is true then the original matching brace is one character left
|
||||||
|
// of the starting location
|
||||||
|
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, bool) {
|
||||||
|
curLine := []rune(string(b.LineBytes(start.Y)))
|
||||||
|
startChar := ' '
|
||||||
|
if start.X >= 0 && start.X < len(curLine) {
|
||||||
|
startChar = curLine[start.X]
|
||||||
|
}
|
||||||
|
leftChar := ' '
|
||||||
|
if start.X-1 >= 0 && start.X-1 < len(curLine) {
|
||||||
|
leftChar = curLine[start.X-1]
|
||||||
|
}
|
||||||
var i int
|
var i int
|
||||||
if char == braceType[0] {
|
if startChar == braceType[0] || leftChar == braceType[0] {
|
||||||
for y := start.Y; y < b.LinesNum(); y++ {
|
for y := start.Y; y < b.LinesNum(); y++ {
|
||||||
l := []rune(string(b.LineBytes(y)))
|
l := []rune(string(b.LineBytes(y)))
|
||||||
xInit := 0
|
xInit := 0
|
||||||
if y == start.Y {
|
if y == start.Y {
|
||||||
xInit = start.X
|
if startChar == braceType[0] {
|
||||||
|
xInit = start.X
|
||||||
|
} else {
|
||||||
|
xInit = start.X - 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for x := xInit; x < len(l); x++ {
|
for x := xInit; x < len(l); x++ {
|
||||||
r := l[x]
|
r := l[x]
|
||||||
|
@ -1156,74 +914,42 @@ func (b *Buffer) findMatchingBrace(braceType [2]rune, start Loc, char rune) (Loc
|
||||||
} else if r == braceType[1] {
|
} else if r == braceType[1] {
|
||||||
i--
|
i--
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
return Loc{x, y}, true
|
if startChar == braceType[0] {
|
||||||
|
return Loc{x, y}, false, true
|
||||||
|
}
|
||||||
|
return Loc{x, y}, true, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if char == braceType[1] {
|
} else if startChar == braceType[1] || leftChar == braceType[1] {
|
||||||
for y := start.Y; y >= 0; y-- {
|
for y := start.Y; y >= 0; y-- {
|
||||||
l := []rune(string(b.lines[y].data))
|
l := []rune(string(b.lines[y].data))
|
||||||
xInit := len(l) - 1
|
xInit := len(l) - 1
|
||||||
if y == start.Y {
|
if y == start.Y {
|
||||||
xInit = start.X
|
if leftChar == braceType[1] {
|
||||||
|
xInit = start.X - 1
|
||||||
|
} else {
|
||||||
|
xInit = start.X
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for x := xInit; x >= 0; x-- {
|
for x := xInit; x >= 0; x-- {
|
||||||
r := l[x]
|
r := l[x]
|
||||||
if r == braceType[1] {
|
if r == braceType[0] {
|
||||||
i++
|
|
||||||
} else if r == braceType[0] {
|
|
||||||
i--
|
i--
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
return Loc{x, y}, true
|
if leftChar == braceType[1] {
|
||||||
|
return Loc{x, y}, true, true
|
||||||
|
}
|
||||||
|
return Loc{x, y}, false, true
|
||||||
}
|
}
|
||||||
|
} else if r == braceType[1] {
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return start, false
|
return start, true, false
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a brace character (for example '{' or ']') at the given start location,
|
|
||||||
// FindMatchingBrace returns the location of the matching brace for it (for example '}'
|
|
||||||
// or '['). The second returned value is true if there was no matching brace found
|
|
||||||
// for given starting location but it was found for the location one character left
|
|
||||||
// of it. The third returned value is true if the matching brace was found at all.
|
|
||||||
func (b *Buffer) FindMatchingBrace(start Loc) (Loc, bool, bool) {
|
|
||||||
// TODO: maybe can be more efficient with utf8 package
|
|
||||||
curLine := []rune(string(b.LineBytes(start.Y)))
|
|
||||||
|
|
||||||
// first try to find matching brace for the given location (it has higher priority)
|
|
||||||
if start.X >= 0 && start.X < len(curLine) {
|
|
||||||
startChar := curLine[start.X]
|
|
||||||
|
|
||||||
for _, bp := range BracePairs {
|
|
||||||
if startChar == bp[0] || startChar == bp[1] {
|
|
||||||
mb, found := b.findMatchingBrace(bp, start, startChar)
|
|
||||||
if found {
|
|
||||||
return mb, false, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// failed to find matching brace for the given location, so try to find matching
|
|
||||||
// brace for the location one character left of it
|
|
||||||
if start.X-1 >= 0 && start.X-1 < len(curLine) {
|
|
||||||
leftChar := curLine[start.X-1]
|
|
||||||
left := Loc{start.X - 1, start.Y}
|
|
||||||
|
|
||||||
for _, bp := range BracePairs {
|
|
||||||
if leftChar == bp[0] || leftChar == bp[1] {
|
|
||||||
mb, found := b.findMatchingBrace(bp, left, leftChar)
|
|
||||||
if found {
|
|
||||||
return mb, true, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return start, false, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retab changes all tabs to spaces or vice versa
|
// Retab changes all tabs to spaces or vice versa
|
||||||
|
@ -1238,18 +964,14 @@ func (b *Buffer) Retab() {
|
||||||
ws := util.GetLeadingWhitespace(l)
|
ws := util.GetLeadingWhitespace(l)
|
||||||
if len(ws) != 0 {
|
if len(ws) != 0 {
|
||||||
if toSpaces {
|
if toSpaces {
|
||||||
ws = bytes.ReplaceAll(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize))
|
ws = bytes.Replace(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize), -1)
|
||||||
} else {
|
} else {
|
||||||
ws = bytes.ReplaceAll(ws, bytes.Repeat([]byte{' '}, tabsize), []byte{'\t'})
|
ws = bytes.Replace(ws, bytes.Repeat([]byte{' '}, tabsize), []byte{'\t'}, -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
l = bytes.TrimLeft(l, " \t")
|
l = bytes.TrimLeft(l, " \t")
|
||||||
|
|
||||||
b.Lock()
|
|
||||||
b.lines[i].data = append(ws, l...)
|
b.lines[i].data = append(ws, l...)
|
||||||
b.Unlock()
|
|
||||||
|
|
||||||
b.MarkModified(i, i)
|
b.MarkModified(i, i)
|
||||||
dirty = true
|
dirty = true
|
||||||
}
|
}
|
||||||
|
@ -1292,7 +1014,7 @@ func (b *Buffer) Write(bytes []byte) (n int, err error) {
|
||||||
return len(bytes), nil
|
return len(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) updateDiff(synchronous bool) {
|
func (b *Buffer) updateDiffSync() {
|
||||||
b.diffLock.Lock()
|
b.diffLock.Lock()
|
||||||
defer b.diffLock.Unlock()
|
defer b.diffLock.Unlock()
|
||||||
|
|
||||||
|
@ -1303,16 +1025,7 @@ func (b *Buffer) updateDiff(synchronous bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
differ := dmp.New()
|
differ := dmp.New()
|
||||||
|
baseRunes, bufferRunes, _ := differ.DiffLinesToRunes(string(b.diffBase), string(b.Bytes()))
|
||||||
if !synchronous {
|
|
||||||
b.Lock()
|
|
||||||
}
|
|
||||||
bytes := b.Bytes()
|
|
||||||
if !synchronous {
|
|
||||||
b.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
baseRunes, bufferRunes, _ := differ.DiffLinesToRunes(string(b.diffBase), string(bytes))
|
|
||||||
diffs := differ.DiffMainRunes(baseRunes, bufferRunes, false)
|
diffs := differ.DiffMainRunes(baseRunes, bufferRunes, false)
|
||||||
lineN := 0
|
lineN := 0
|
||||||
|
|
||||||
|
@ -1341,9 +1054,13 @@ func (b *Buffer) updateDiff(synchronous bool) {
|
||||||
|
|
||||||
// UpdateDiff computes the diff between the diff base and the buffer content.
|
// UpdateDiff computes the diff between the diff base and the buffer content.
|
||||||
// The update may be performed synchronously or asynchronously.
|
// The update may be performed synchronously or asynchronously.
|
||||||
|
// UpdateDiff calls the supplied callback when the update is complete.
|
||||||
|
// The argument passed to the callback is set to true if and only if
|
||||||
|
// the update was performed synchronously.
|
||||||
// If an asynchronous update is already pending when UpdateDiff is called,
|
// If an asynchronous update is already pending when UpdateDiff is called,
|
||||||
// UpdateDiff does not schedule another update.
|
// UpdateDiff does not schedule another update, in which case the callback
|
||||||
func (b *Buffer) UpdateDiff() {
|
// is not called.
|
||||||
|
func (b *Buffer) UpdateDiff(callback func(bool)) {
|
||||||
if b.updateDiffTimer != nil {
|
if b.updateDiffTimer != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1354,18 +1071,20 @@ func (b *Buffer) UpdateDiff() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if lineCount < 1000 {
|
if lineCount < 1000 {
|
||||||
b.updateDiff(true)
|
b.updateDiffSync()
|
||||||
|
callback(true)
|
||||||
} else if lineCount < 30000 {
|
} else if lineCount < 30000 {
|
||||||
b.updateDiffTimer = time.AfterFunc(500*time.Millisecond, func() {
|
b.updateDiffTimer = time.AfterFunc(500*time.Millisecond, func() {
|
||||||
b.updateDiffTimer = nil
|
b.updateDiffTimer = nil
|
||||||
b.updateDiff(false)
|
b.updateDiffSync()
|
||||||
screen.Redraw()
|
callback(false)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Don't compute diffs for very large files
|
// Don't compute diffs for very large files
|
||||||
b.diffLock.Lock()
|
b.diffLock.Lock()
|
||||||
b.diff = make(map[int]DiffStatus)
|
b.diff = make(map[int]DiffStatus)
|
||||||
b.diffLock.Unlock()
|
b.diffLock.Unlock()
|
||||||
|
callback(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1377,7 +1096,9 @@ func (b *Buffer) SetDiffBase(diffBase []byte) {
|
||||||
} else {
|
} else {
|
||||||
b.diffBaseLineCount = strings.Count(string(diffBase), "\n")
|
b.diffBaseLineCount = strings.Count(string(diffBase), "\n")
|
||||||
}
|
}
|
||||||
b.UpdateDiff()
|
b.UpdateDiff(func(synchronous bool) {
|
||||||
|
screen.Redraw()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiffStatus returns the diff status for a line in the buffer
|
// DiffStatus returns the diff status for a line in the buffer
|
||||||
|
@ -1388,47 +1109,6 @@ func (b *Buffer) DiffStatus(lineN int) DiffStatus {
|
||||||
return b.diff[lineN]
|
return b.diff[lineN]
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindNextDiffLine returns the line number of the next block of diffs.
|
|
||||||
// If `startLine` is already in a block of diffs, lines in that block are skipped.
|
|
||||||
func (b *Buffer) FindNextDiffLine(startLine int, forward bool) (int, error) {
|
|
||||||
if b.diff == nil {
|
|
||||||
return 0, errors.New("no diff data")
|
|
||||||
}
|
|
||||||
startStatus, ok := b.diff[startLine]
|
|
||||||
if !ok {
|
|
||||||
startStatus = DSUnchanged
|
|
||||||
}
|
|
||||||
curLine := startLine
|
|
||||||
for {
|
|
||||||
curStatus, ok := b.diff[curLine]
|
|
||||||
if !ok {
|
|
||||||
curStatus = DSUnchanged
|
|
||||||
}
|
|
||||||
if curLine < 0 || curLine > b.LinesNum() {
|
|
||||||
return 0, errors.New("no next diff hunk")
|
|
||||||
}
|
|
||||||
if curStatus != startStatus {
|
|
||||||
if startStatus != DSUnchanged && curStatus == DSUnchanged {
|
|
||||||
// Skip over the block of unchanged text
|
|
||||||
startStatus = DSUnchanged
|
|
||||||
} else {
|
|
||||||
return curLine, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if forward {
|
|
||||||
curLine++
|
|
||||||
} else {
|
|
||||||
curLine--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchMatch returns true if the given location is within a match of the last search.
|
|
||||||
// It is used for search highlighting
|
|
||||||
func (b *Buffer) SearchMatch(pos Loc) bool {
|
|
||||||
return b.LineArray.SearchMatch(b, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteLog writes a string to the log buffer
|
// WriteLog writes a string to the log buffer
|
||||||
func WriteLog(s string) {
|
func WriteLog(s string) {
|
||||||
LogBuf.EventHandler.Insert(LogBuf.End(), s)
|
LogBuf.EventHandler.Insert(LogBuf.End(), s)
|
||||||
|
|
|
@ -20,7 +20,6 @@ type operation struct {
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ulua.L = lua.NewState()
|
ulua.L = lua.NewState()
|
||||||
config.InitRuntimeFiles(false)
|
|
||||||
config.InitGlobalSettings()
|
config.InitGlobalSettings()
|
||||||
config.GlobalSettings["backup"] = false
|
config.GlobalSettings["backup"] = false
|
||||||
config.GlobalSettings["fastdirty"] = true
|
config.GlobalSettings["fastdirty"] = true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package buffer
|
package buffer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
"github.com/zyedidia/clipboard"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,11 +30,6 @@ type Cursor struct {
|
||||||
// to know what the original selection was
|
// to know what the original selection was
|
||||||
OrigSelection [2]Loc
|
OrigSelection [2]Loc
|
||||||
|
|
||||||
// The line number where a new trailing whitespace has been added
|
|
||||||
// or -1 if there is no new trailing whitespace at this cursor.
|
|
||||||
// This is used for checking if a trailing whitespace should be highlighted
|
|
||||||
NewTrailingWsY int
|
|
||||||
|
|
||||||
// Which cursor index is this (for multiple cursors)
|
// Which cursor index is this (for multiple cursors)
|
||||||
Num int
|
Num int
|
||||||
}
|
}
|
||||||
|
@ -43,8 +38,6 @@ func NewCursor(b *Buffer, l Loc) *Cursor {
|
||||||
c := &Cursor{
|
c := &Cursor{
|
||||||
buf: b,
|
buf: b,
|
||||||
Loc: l,
|
Loc: l,
|
||||||
|
|
||||||
NewTrailingWsY: -1,
|
|
||||||
}
|
}
|
||||||
c.StoreVisualX()
|
c.StoreVisualX()
|
||||||
return c
|
return c
|
||||||
|
@ -74,10 +67,6 @@ func (c *Cursor) GotoLoc(l Loc) {
|
||||||
|
|
||||||
// GetVisualX returns the x value of the cursor in visual spaces
|
// GetVisualX returns the x value of the cursor in visual spaces
|
||||||
func (c *Cursor) GetVisualX() int {
|
func (c *Cursor) GetVisualX() int {
|
||||||
if c.buf.GetVisualX != nil {
|
|
||||||
return c.buf.GetVisualX(c.Loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.X <= 0 {
|
if c.X <= 0 {
|
||||||
c.X = 0
|
c.X = 0
|
||||||
return 0
|
return 0
|
||||||
|
@ -136,10 +125,10 @@ func (c *Cursor) End() {
|
||||||
|
|
||||||
// CopySelection copies the user's selection to either "primary"
|
// CopySelection copies the user's selection to either "primary"
|
||||||
// or "clipboard"
|
// or "clipboard"
|
||||||
func (c *Cursor) CopySelection(target clipboard.Register) {
|
func (c *Cursor) CopySelection(target string) {
|
||||||
if c.HasSelection() {
|
if c.HasSelection() {
|
||||||
if target != clipboard.PrimaryReg || c.buf.Settings["useprimary"].(bool) {
|
if target != "primary" || c.buf.Settings["useprimary"].(bool) {
|
||||||
clipboard.WriteMulti(string(c.GetSelection()), target, c.Num, c.buf.NumCursors())
|
clipboard.WriteAll(string(c.GetSelection()), target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -403,26 +392,13 @@ func (c *Cursor) SelectTo(loc Loc) {
|
||||||
|
|
||||||
// WordRight moves the cursor one word to the right
|
// WordRight moves the cursor one word to the right
|
||||||
func (c *Cursor) WordRight() {
|
func (c *Cursor) WordRight() {
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
|
||||||
c.Right()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for util.IsWhitespace(c.RuneUnder(c.X)) {
|
for util.IsWhitespace(c.RuneUnder(c.X)) {
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||||
|
c.Right()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Right()
|
c.Right()
|
||||||
}
|
}
|
||||||
if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) &&
|
|
||||||
util.IsNonWordChar(c.RuneUnder(c.X+1)) {
|
|
||||||
for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Right()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Right()
|
c.Right()
|
||||||
for util.IsWordChar(c.RuneUnder(c.X)) {
|
for util.IsWordChar(c.RuneUnder(c.X)) {
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||||
|
@ -434,10 +410,6 @@ func (c *Cursor) WordRight() {
|
||||||
|
|
||||||
// WordLeft moves the cursor one word to the left
|
// WordLeft moves the cursor one word to the left
|
||||||
func (c *Cursor) WordLeft() {
|
func (c *Cursor) WordLeft() {
|
||||||
if c.X == 0 {
|
|
||||||
c.Left()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Left()
|
c.Left()
|
||||||
for util.IsWhitespace(c.RuneUnder(c.X)) {
|
for util.IsWhitespace(c.RuneUnder(c.X)) {
|
||||||
if c.X == 0 {
|
if c.X == 0 {
|
||||||
|
@ -445,17 +417,6 @@ func (c *Cursor) WordLeft() {
|
||||||
}
|
}
|
||||||
c.Left()
|
c.Left()
|
||||||
}
|
}
|
||||||
if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) &&
|
|
||||||
util.IsNonWordChar(c.RuneUnder(c.X-1)) {
|
|
||||||
for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Left()
|
|
||||||
}
|
|
||||||
c.Right()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Left()
|
c.Left()
|
||||||
for util.IsWordChar(c.RuneUnder(c.X)) {
|
for util.IsWordChar(c.RuneUnder(c.X)) {
|
||||||
if c.X == 0 {
|
if c.X == 0 {
|
||||||
|
@ -466,132 +427,6 @@ func (c *Cursor) WordLeft() {
|
||||||
c.Right()
|
c.Right()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubWordRight moves the cursor one sub-word to the right
|
|
||||||
func (c *Cursor) SubWordRight() {
|
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
|
||||||
c.Right()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
for util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Right()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Right()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if util.IsSubwordDelimiter(c.RuneUnder(c.X)) {
|
|
||||||
for util.IsSubwordDelimiter(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Right()
|
|
||||||
}
|
|
||||||
if util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if util.IsUpperLetter(c.RuneUnder(c.X)) &&
|
|
||||||
util.IsUpperLetter(c.RuneUnder(c.X+1)) {
|
|
||||||
for util.IsUpperAlphanumeric(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Right()
|
|
||||||
}
|
|
||||||
if util.IsLowerAlphanumeric(c.RuneUnder(c.X)) {
|
|
||||||
c.Left()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.Right()
|
|
||||||
for util.IsLowerAlphanumeric(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Right()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubWordLeft moves the cursor one sub-word to the left
|
|
||||||
func (c *Cursor) SubWordLeft() {
|
|
||||||
if c.X == 0 {
|
|
||||||
c.Left()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Left()
|
|
||||||
if util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
for util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Left()
|
|
||||||
}
|
|
||||||
c.Right()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Left()
|
|
||||||
}
|
|
||||||
c.Right()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if util.IsSubwordDelimiter(c.RuneUnder(c.X)) {
|
|
||||||
for util.IsSubwordDelimiter(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Left()
|
|
||||||
}
|
|
||||||
if util.IsWhitespace(c.RuneUnder(c.X)) {
|
|
||||||
c.Right()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.X == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if util.IsUpperLetter(c.RuneUnder(c.X)) &&
|
|
||||||
util.IsUpperLetter(c.RuneUnder(c.X-1)) {
|
|
||||||
for util.IsUpperAlphanumeric(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Left()
|
|
||||||
}
|
|
||||||
if !util.IsUpperAlphanumeric(c.RuneUnder(c.X)) {
|
|
||||||
c.Right()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for util.IsLowerAlphanumeric(c.RuneUnder(c.X)) {
|
|
||||||
if c.X == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Left()
|
|
||||||
}
|
|
||||||
if !util.IsAlphanumeric(c.RuneUnder(c.X)) {
|
|
||||||
c.Right()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuneUnder returns the rune under the given x position
|
// RuneUnder returns the rune under the given x position
|
||||||
func (c *Cursor) RuneUnder(x int) rune {
|
func (c *Cursor) RuneUnder(x int) rune {
|
||||||
line := c.buf.LineBytes(c.Y)
|
line := c.buf.LineBytes(c.Y)
|
||||||
|
|
|
@ -106,10 +106,6 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
|
||||||
c.Relocate()
|
c.Relocate()
|
||||||
c.LastVisualX = c.GetVisualX()
|
c.LastVisualX = c.GetVisualX()
|
||||||
}
|
}
|
||||||
|
|
||||||
if useUndo {
|
|
||||||
eh.updateTrailingWs(t)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecuteTextEvent runs a text event
|
// ExecuteTextEvent runs a text event
|
||||||
|
@ -241,7 +237,7 @@ func (eh *EventHandler) Execute(t *TextEvent) {
|
||||||
}
|
}
|
||||||
eh.UndoStack.Push(t)
|
eh.UndoStack.Push(t)
|
||||||
|
|
||||||
b, err := config.RunPluginFnBool(nil, "onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
|
b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
}
|
}
|
||||||
|
@ -294,7 +290,6 @@ func (eh *EventHandler) UndoOneEvent() {
|
||||||
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
|
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
|
||||||
t.C = *eh.cursors[teCursor.Num]
|
t.C = *eh.cursors[teCursor.Num]
|
||||||
eh.cursors[teCursor.Num].Goto(teCursor)
|
eh.cursors[teCursor.Num].Goto(teCursor)
|
||||||
eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY
|
|
||||||
} else {
|
} else {
|
||||||
teCursor.Num = -1
|
teCursor.Num = -1
|
||||||
}
|
}
|
||||||
|
@ -338,7 +333,6 @@ func (eh *EventHandler) RedoOneEvent() {
|
||||||
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
|
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
|
||||||
t.C = *eh.cursors[teCursor.Num]
|
t.C = *eh.cursors[teCursor.Num]
|
||||||
eh.cursors[teCursor.Num].Goto(teCursor)
|
eh.cursors[teCursor.Num].Goto(teCursor)
|
||||||
eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY
|
|
||||||
} else {
|
} else {
|
||||||
teCursor.Num = -1
|
teCursor.Num = -1
|
||||||
}
|
}
|
||||||
|
@ -348,58 +342,3 @@ func (eh *EventHandler) RedoOneEvent() {
|
||||||
|
|
||||||
eh.UndoStack.Push(t)
|
eh.UndoStack.Push(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateTrailingWs updates the cursor's trailing whitespace status after a text event
|
|
||||||
func (eh *EventHandler) updateTrailingWs(t *TextEvent) {
|
|
||||||
if len(t.Deltas) != 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
text := t.Deltas[0].Text
|
|
||||||
start := t.Deltas[0].Start
|
|
||||||
end := t.Deltas[0].End
|
|
||||||
|
|
||||||
c := eh.cursors[eh.active]
|
|
||||||
isEol := func(loc Loc) bool {
|
|
||||||
return loc.X == util.CharacterCount(eh.buf.LineBytes(loc.Y))
|
|
||||||
}
|
|
||||||
if t.EventType == TextEventInsert && c.Loc == end && isEol(end) {
|
|
||||||
var addedTrailingWs bool
|
|
||||||
addedAfterWs := false
|
|
||||||
addedWsOnly := false
|
|
||||||
if start.Y == end.Y {
|
|
||||||
addedTrailingWs = util.HasTrailingWhitespace(text)
|
|
||||||
addedWsOnly = util.IsBytesWhitespace(text)
|
|
||||||
addedAfterWs = start.X > 0 && util.IsWhitespace(c.buf.RuneAt(Loc{start.X - 1, start.Y}))
|
|
||||||
} else {
|
|
||||||
lastnl := bytes.LastIndex(text, []byte{'\n'})
|
|
||||||
addedTrailingWs = util.HasTrailingWhitespace(text[lastnl+1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
if addedTrailingWs && !(addedAfterWs && addedWsOnly) {
|
|
||||||
c.NewTrailingWsY = c.Y
|
|
||||||
} else if !addedTrailingWs {
|
|
||||||
c.NewTrailingWsY = -1
|
|
||||||
}
|
|
||||||
} else if t.EventType == TextEventRemove && c.Loc == start && isEol(start) {
|
|
||||||
removedAfterWs := util.HasTrailingWhitespace(eh.buf.LineBytes(start.Y))
|
|
||||||
var removedWsOnly bool
|
|
||||||
if start.Y == end.Y {
|
|
||||||
removedWsOnly = util.IsBytesWhitespace(text)
|
|
||||||
} else {
|
|
||||||
firstnl := bytes.Index(text, []byte{'\n'})
|
|
||||||
removedWsOnly = util.IsBytesWhitespace(text[:firstnl])
|
|
||||||
}
|
|
||||||
|
|
||||||
if removedAfterWs && !removedWsOnly {
|
|
||||||
c.NewTrailingWsY = c.Y
|
|
||||||
} else if !removedAfterWs {
|
|
||||||
c.NewTrailingWsY = -1
|
|
||||||
}
|
|
||||||
} else if c.NewTrailingWsY != -1 && start.Y != end.Y && c.Loc.GreaterThan(start) &&
|
|
||||||
((t.EventType == TextEventInsert && c.Y == c.NewTrailingWsY+(end.Y-start.Y)) ||
|
|
||||||
(t.EventType == TextEventRemove && c.Y == c.NewTrailingWsY-(end.Y-start.Y))) {
|
|
||||||
// The cursor still has its new trailingws
|
|
||||||
// but its line number was shifted by insert or remove of lines above
|
|
||||||
c.NewTrailingWsY = c.Y
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -32,31 +32,15 @@ func runeToByteIndex(n int, txt []byte) int {
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
// A searchState contains the search match info for a single line
|
|
||||||
type searchState struct {
|
|
||||||
search string
|
|
||||||
useRegex bool
|
|
||||||
ignorecase bool
|
|
||||||
match [][2]int
|
|
||||||
done bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Line contains the data in bytes as well as a highlight state, match
|
// A Line contains the data in bytes as well as a highlight state, match
|
||||||
// and a flag for whether the highlighting needs to be updated
|
// and a flag for whether the highlighting needs to be updated
|
||||||
type Line struct {
|
type Line struct {
|
||||||
data []byte
|
data []byte
|
||||||
|
|
||||||
state highlight.State
|
state highlight.State
|
||||||
match highlight.LineMatch
|
match highlight.LineMatch
|
||||||
lock sync.Mutex
|
rehighlight bool
|
||||||
|
lock sync.Mutex
|
||||||
// The search states for the line, used for highlighting of search matches,
|
|
||||||
// separately from the syntax highlighting.
|
|
||||||
// A map is used because the line array may be shared between multiple buffers
|
|
||||||
// (multiple instances of the same file opened in different edit panes)
|
|
||||||
// which have distinct searches, so in the general case there are multiple
|
|
||||||
// searches per a line, one search per a Buffer containing this line.
|
|
||||||
search map[*Buffer]*searchState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -74,7 +58,6 @@ type LineArray struct {
|
||||||
lines []Line
|
lines []Line
|
||||||
Endings FileFormat
|
Endings FileFormat
|
||||||
initsize uint64
|
initsize uint64
|
||||||
lock sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append efficiently appends lines together
|
// Append efficiently appends lines together
|
||||||
|
@ -116,12 +99,12 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||||
dlen := len(data)
|
dlen := len(data)
|
||||||
if dlen > 1 && data[dlen-2] == '\r' {
|
if dlen > 1 && data[dlen-2] == '\r' {
|
||||||
data = append(data[:dlen-2], '\n')
|
data = append(data[:dlen-2], '\n')
|
||||||
if la.Endings == FFAuto {
|
if endings == FFAuto {
|
||||||
la.Endings = FFDos
|
la.Endings = FFDos
|
||||||
}
|
}
|
||||||
dlen = len(data)
|
dlen = len(data)
|
||||||
} else if dlen > 0 {
|
} else if dlen > 0 {
|
||||||
if la.Endings == FFAuto {
|
if endings == FFAuto {
|
||||||
la.Endings = FFUnix
|
la.Endings = FFUnix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,18 +130,20 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
la.lines = Append(la.lines, Line{
|
la.lines = Append(la.lines, Line{
|
||||||
data: data,
|
data: data[:],
|
||||||
state: nil,
|
state: nil,
|
||||||
match: nil,
|
match: nil,
|
||||||
|
rehighlight: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Last line was read
|
// Last line was read
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
la.lines = Append(la.lines, Line{
|
la.lines = Append(la.lines, Line{
|
||||||
data: data[:dlen-1],
|
data: data[:dlen-1],
|
||||||
state: nil,
|
state: nil,
|
||||||
match: nil,
|
match: nil,
|
||||||
|
rehighlight: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
n++
|
n++
|
||||||
|
@ -188,34 +173,28 @@ func (la *LineArray) Bytes() []byte {
|
||||||
// newlineBelow adds a newline below the given line number
|
// newlineBelow adds a newline below the given line number
|
||||||
func (la *LineArray) newlineBelow(y int) {
|
func (la *LineArray) newlineBelow(y int) {
|
||||||
la.lines = append(la.lines, Line{
|
la.lines = append(la.lines, Line{
|
||||||
data: []byte{' '},
|
data: []byte{' '},
|
||||||
state: nil,
|
state: nil,
|
||||||
match: nil,
|
match: nil,
|
||||||
|
rehighlight: false,
|
||||||
})
|
})
|
||||||
copy(la.lines[y+2:], la.lines[y+1:])
|
copy(la.lines[y+2:], la.lines[y+1:])
|
||||||
la.lines[y+1] = Line{
|
la.lines[y+1] = Line{
|
||||||
data: []byte{},
|
data: []byte{},
|
||||||
state: la.lines[y].state,
|
state: la.lines[y].state,
|
||||||
match: nil,
|
match: nil,
|
||||||
|
rehighlight: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserts a byte array at a given location
|
// Inserts a byte array at a given location
|
||||||
func (la *LineArray) insert(pos Loc, value []byte) {
|
func (la *LineArray) insert(pos Loc, value []byte) {
|
||||||
la.lock.Lock()
|
|
||||||
defer la.lock.Unlock()
|
|
||||||
|
|
||||||
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
|
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
|
||||||
for i := 0; i < len(value); i++ {
|
for i := 0; i < len(value); i++ {
|
||||||
if value[i] == '\n' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') {
|
if value[i] == '\n' {
|
||||||
la.split(Loc{x, y})
|
la.split(Loc{x, y})
|
||||||
x = 0
|
x = 0
|
||||||
y++
|
y++
|
||||||
|
|
||||||
if value[i] == '\r' {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
la.insertByte(Loc{x, y}, value[i])
|
la.insertByte(Loc{x, y}, value[i])
|
||||||
|
@ -232,26 +211,24 @@ func (la *LineArray) insertByte(pos Loc, value byte) {
|
||||||
|
|
||||||
// joinLines joins the two lines a and b
|
// joinLines joins the two lines a and b
|
||||||
func (la *LineArray) joinLines(a, b int) {
|
func (la *LineArray) joinLines(a, b int) {
|
||||||
la.lines[a].data = append(la.lines[a].data, la.lines[b].data...)
|
la.insert(Loc{len(la.lines[a].data), a}, la.lines[b].data)
|
||||||
la.deleteLine(b)
|
la.deleteLine(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// split splits a line at a given position
|
// split splits a line at a given position
|
||||||
func (la *LineArray) split(pos Loc) {
|
func (la *LineArray) split(pos Loc) {
|
||||||
la.newlineBelow(pos.Y)
|
la.newlineBelow(pos.Y)
|
||||||
la.lines[pos.Y+1].data = append(la.lines[pos.Y+1].data, la.lines[pos.Y].data[pos.X:]...)
|
la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y].data[pos.X:])
|
||||||
la.lines[pos.Y+1].state = la.lines[pos.Y].state
|
la.lines[pos.Y+1].state = la.lines[pos.Y].state
|
||||||
la.lines[pos.Y].state = nil
|
la.lines[pos.Y].state = nil
|
||||||
la.lines[pos.Y].match = nil
|
la.lines[pos.Y].match = nil
|
||||||
la.lines[pos.Y+1].match = nil
|
la.lines[pos.Y+1].match = nil
|
||||||
|
la.lines[pos.Y].rehighlight = true
|
||||||
la.deleteToEnd(Loc{pos.X, pos.Y})
|
la.deleteToEnd(Loc{pos.X, pos.Y})
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes from start to end
|
// removes from start to end
|
||||||
func (la *LineArray) remove(start, end Loc) []byte {
|
func (la *LineArray) remove(start, end Loc) []byte {
|
||||||
la.lock.Lock()
|
|
||||||
defer la.lock.Unlock()
|
|
||||||
|
|
||||||
sub := la.Substr(start, end)
|
sub := la.Substr(start, end)
|
||||||
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
||||||
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
|
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
|
||||||
|
@ -328,11 +305,11 @@ func (la *LineArray) End() Loc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LineBytes returns line n as an array of bytes
|
// LineBytes returns line n as an array of bytes
|
||||||
func (la *LineArray) LineBytes(lineN int) []byte {
|
func (la *LineArray) LineBytes(n int) []byte {
|
||||||
if lineN >= len(la.lines) || lineN < 0 {
|
if n >= len(la.lines) || n < 0 {
|
||||||
return []byte{}
|
return []byte{}
|
||||||
}
|
}
|
||||||
return la.lines[lineN].data
|
return la.lines[n].data
|
||||||
}
|
}
|
||||||
|
|
||||||
// State gets the highlight state for the given line number
|
// State gets the highlight state for the given line number
|
||||||
|
@ -363,88 +340,14 @@ func (la *LineArray) Match(lineN int) highlight.LineMatch {
|
||||||
return la.lines[lineN].match
|
return la.lines[lineN].match
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locks the whole LineArray
|
func (la *LineArray) Rehighlight(lineN int) bool {
|
||||||
func (la *LineArray) Lock() {
|
la.lines[lineN].lock.Lock()
|
||||||
la.lock.Lock()
|
defer la.lines[lineN].lock.Unlock()
|
||||||
|
return la.lines[lineN].rehighlight
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlocks the whole LineArray
|
func (la *LineArray) SetRehighlight(lineN int, on bool) {
|
||||||
func (la *LineArray) Unlock() {
|
la.lines[lineN].lock.Lock()
|
||||||
la.lock.Unlock()
|
defer la.lines[lineN].lock.Unlock()
|
||||||
}
|
la.lines[lineN].rehighlight = on
|
||||||
|
|
||||||
// SearchMatch returns true if the location `pos` is within a match
|
|
||||||
// of the last search for the buffer `b`.
|
|
||||||
// It is used for efficient highlighting of search matches (separately
|
|
||||||
// from the syntax highlighting).
|
|
||||||
// SearchMatch searches for the matches if it is called first time
|
|
||||||
// for the given line or if the line was modified. Otherwise the
|
|
||||||
// previously found matches are used.
|
|
||||||
//
|
|
||||||
// The buffer `b` needs to be passed because the line array may be shared
|
|
||||||
// between multiple buffers (multiple instances of the same file opened
|
|
||||||
// in different edit panes) which have distinct searches, so SearchMatch
|
|
||||||
// needs to know which search to match against.
|
|
||||||
func (la *LineArray) SearchMatch(b *Buffer, pos Loc) bool {
|
|
||||||
if b.LastSearch == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
lineN := pos.Y
|
|
||||||
if la.lines[lineN].search == nil {
|
|
||||||
la.lines[lineN].search = make(map[*Buffer]*searchState)
|
|
||||||
}
|
|
||||||
s, ok := la.lines[lineN].search[b]
|
|
||||||
if !ok {
|
|
||||||
// Note: here is a small harmless leak: when the buffer `b` is closed,
|
|
||||||
// `s` is not deleted from the map. It means that the buffer
|
|
||||||
// will not be garbage-collected until the line array is garbage-collected,
|
|
||||||
// i.e. until all the buffers sharing this file are closed.
|
|
||||||
s = new(searchState)
|
|
||||||
la.lines[lineN].search[b] = s
|
|
||||||
}
|
|
||||||
if !ok || s.search != b.LastSearch || s.useRegex != b.LastSearchRegex ||
|
|
||||||
s.ignorecase != b.Settings["ignorecase"].(bool) {
|
|
||||||
s.search = b.LastSearch
|
|
||||||
s.useRegex = b.LastSearchRegex
|
|
||||||
s.ignorecase = b.Settings["ignorecase"].(bool)
|
|
||||||
s.done = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.done {
|
|
||||||
s.match = nil
|
|
||||||
start := Loc{0, lineN}
|
|
||||||
end := Loc{util.CharacterCount(la.lines[lineN].data), lineN}
|
|
||||||
for start.X < end.X {
|
|
||||||
m, found, _ := b.FindNext(b.LastSearch, start, end, start, true, b.LastSearchRegex)
|
|
||||||
if !found {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s.match = append(s.match, [2]int{m[0].X, m[1].X})
|
|
||||||
|
|
||||||
start.X = m[1].X
|
|
||||||
if m[1].X == m[0].X {
|
|
||||||
start.X = m[1].X + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.done = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range s.match {
|
|
||||||
if pos.X >= m[0] && pos.X < m[1] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// invalidateSearchMatches marks search matches for the given line as outdated.
|
|
||||||
// It is called when the line is modified.
|
|
||||||
func (la *LineArray) invalidateSearchMatches(lineN int) {
|
|
||||||
if la.lines[lineN].search != nil {
|
|
||||||
for _, s := range la.lines[lineN].search {
|
|
||||||
s.done = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package buffer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MsgType int
|
type MsgType int
|
||||||
|
@ -82,13 +82,3 @@ func (b *Buffer) ClearMessages(owner string) {
|
||||||
func (b *Buffer) ClearAllMessages() {
|
func (b *Buffer) ClearAllMessages() {
|
||||||
b.Messages = make([]*Message, 0)
|
b.Messages = make([]*Message, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Messager interface {
|
|
||||||
Message(msg ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
var prompt Messager
|
|
||||||
|
|
||||||
func SetMessager(m Messager) {
|
|
||||||
prompt = m
|
|
||||||
}
|
|
||||||
|
|
|
@ -29,11 +29,9 @@ const LargeFileThreshold = 50000
|
||||||
// closed afterwards.
|
// closed afterwards.
|
||||||
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
|
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
|
||||||
var writeCloser io.WriteCloser
|
var writeCloser io.WriteCloser
|
||||||
var screenb bool
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
|
|
||||||
if withSudo {
|
if withSudo {
|
||||||
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
|
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
|
||||||
|
|
||||||
if writeCloser, err = cmd.StdinPipe(); err != nil {
|
if writeCloser, err = cmd.StdinPipe(); err != nil {
|
||||||
return
|
return
|
||||||
|
@ -46,43 +44,23 @@ func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error,
|
||||||
cmd.Process.Kill()
|
cmd.Process.Kill()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
screenb = screen.TempFini()
|
defer func() {
|
||||||
// need to start the process now, otherwise when we flush the file
|
screenb := screen.TempFini()
|
||||||
// contents to its stdin it might hang because the kernel's pipe size
|
if e := cmd.Run(); e != nil && err == nil {
|
||||||
// is too small to handle the full file contents all at once
|
err = e
|
||||||
if e := cmd.Start(); e != nil && err == nil {
|
}
|
||||||
screen.TempStart(screenb)
|
screen.TempStart(screenb)
|
||||||
return err
|
}()
|
||||||
}
|
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
|
||||||
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
|
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
|
||||||
err = fn(w)
|
err = fn(w)
|
||||||
|
w.Flush()
|
||||||
|
|
||||||
if err2 := w.Flush(); err2 != nil && err == nil {
|
if e := writeCloser.Close(); e != nil && err == nil {
|
||||||
err = err2
|
err = e
|
||||||
}
|
|
||||||
// Call Sync() on the file to make sure the content is safely on disk.
|
|
||||||
// Does not work with sudo as we don't have direct access to the file.
|
|
||||||
if !withSudo {
|
|
||||||
f := writeCloser.(*os.File)
|
|
||||||
if err2 := f.Sync(); err2 != nil && err == nil {
|
|
||||||
err = err2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err2 := writeCloser.Close(); err2 != nil && err == nil {
|
|
||||||
err = err2
|
|
||||||
}
|
|
||||||
|
|
||||||
if withSudo {
|
|
||||||
// wait for dd to finish and restart the screen if we used sudo
|
|
||||||
err := cmd.Wait()
|
|
||||||
screen.TempStart(screenb)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -93,14 +71,9 @@ func (b *Buffer) Save() error {
|
||||||
return b.SaveAs(b.Path)
|
return b.SaveAs(b.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoSave saves the buffer to its default path
|
|
||||||
func (b *Buffer) AutoSave() error {
|
|
||||||
return b.saveToFile(b.Path, false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
|
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
|
||||||
func (b *Buffer) SaveAs(filename string) error {
|
func (b *Buffer) SaveAs(filename string) error {
|
||||||
return b.saveToFile(filename, false, false)
|
return b.saveToFile(filename, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) SaveWithSudo() error {
|
func (b *Buffer) SaveWithSudo() error {
|
||||||
|
@ -108,10 +81,10 @@ func (b *Buffer) SaveWithSudo() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) SaveAsWithSudo(filename string) error {
|
func (b *Buffer) SaveAsWithSudo(filename string) error {
|
||||||
return b.saveToFile(filename, true, false)
|
return b.saveToFile(filename, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error {
|
func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||||
var err error
|
var err error
|
||||||
if b.Type.Readonly {
|
if b.Type.Readonly {
|
||||||
return errors.New("Cannot save readonly buffer")
|
return errors.New("Cannot save readonly buffer")
|
||||||
|
@ -123,7 +96,7 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
||||||
return errors.New("Save with sudo not supported on Windows")
|
return errors.New("Save with sudo not supported on Windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !autoSave && b.Settings["rmtrailingws"].(bool) {
|
if b.Settings["rmtrailingws"].(bool) {
|
||||||
for i, l := range b.lines {
|
for i, l := range b.lines {
|
||||||
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
|
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
|
||||||
|
|
||||||
|
@ -136,7 +109,7 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
||||||
|
|
||||||
if b.Settings["eofnewline"].(bool) {
|
if b.Settings["eofnewline"].(bool) {
|
||||||
end := b.End()
|
end := b.End()
|
||||||
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
|
if b.RuneAt(Loc{end.X, end.Y}) != '\n' {
|
||||||
b.insert(end, []byte{'\n'})
|
b.insert(end, []byte{'\n'})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||||
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
|
|
||||||
if start.Y > b.LinesNum()-1 {
|
|
||||||
start.X = lastcn - 1
|
|
||||||
}
|
|
||||||
if end.Y > b.LinesNum()-1 {
|
|
||||||
end.X = lastcn
|
|
||||||
}
|
|
||||||
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
||||||
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
|
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
|
||||||
|
|
||||||
|
@ -55,13 +48,6 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||||
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
|
|
||||||
if start.Y > b.LinesNum()-1 {
|
|
||||||
start.X = lastcn - 1
|
|
||||||
}
|
|
||||||
if end.Y > b.LinesNum()-1 {
|
|
||||||
end.X = lastcn
|
|
||||||
}
|
|
||||||
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
||||||
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
|
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
|
||||||
|
|
||||||
|
@ -91,10 +77,9 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||||
l = util.SliceStart(l, end.X)
|
l = util.SliceStart(l, end.X)
|
||||||
}
|
}
|
||||||
|
|
||||||
allMatches := r.FindAllIndex(l, -1)
|
match := r.FindIndex(l)
|
||||||
|
|
||||||
if allMatches != nil {
|
if match != nil {
|
||||||
match := allMatches[len(allMatches)-1]
|
|
||||||
start := Loc{charpos + util.RunePos(l, match[0]), i}
|
start := Loc{charpos + util.RunePos(l, match[0]), i}
|
||||||
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
||||||
return [2]Loc{start, end}, true
|
return [2]Loc{start, end}, true
|
||||||
|
@ -147,8 +132,8 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
|
||||||
|
|
||||||
// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
|
// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
|
||||||
// and returns the number of replacements made and the number of runes
|
// and returns the number of replacements made and the number of runes
|
||||||
// added or removed on the last line of the range
|
// added or removed
|
||||||
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte, captureGroups bool) (int, int) {
|
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) {
|
||||||
if start.GreaterThan(end) {
|
if start.GreaterThan(end) {
|
||||||
start, end = end, start
|
start, end = end, start
|
||||||
}
|
}
|
||||||
|
@ -172,18 +157,12 @@ func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []b
|
||||||
l = util.SliceStart(l, end.X)
|
l = util.SliceStart(l, end.X)
|
||||||
}
|
}
|
||||||
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
||||||
var result []byte
|
result := []byte{}
|
||||||
if captureGroups {
|
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
|
||||||
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
|
result = search.Expand(result, replace, in, submatches)
|
||||||
result = search.Expand(result, replace, in, submatches)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = replace
|
|
||||||
}
|
}
|
||||||
found++
|
found++
|
||||||
if i == end.Y {
|
netrunes += util.CharacterCount(in) - util.CharacterCount(result)
|
||||||
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
|
|
||||||
}
|
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -51,8 +51,8 @@ func (b *Buffer) Unserialize() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
|
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
|
||||||
|
defer file.Close()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer file.Close()
|
|
||||||
var buffer SerializedBuffer
|
var buffer SerializedBuffer
|
||||||
decoder := gob.NewDecoder(file)
|
decoder := gob.NewDecoder(file)
|
||||||
err = decoder.Decode(&buffer)
|
err = decoder.Decode(&buffer)
|
||||||
|
|
|
@ -10,26 +10,14 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||||
|
|
||||||
if option == "fastdirty" {
|
if option == "fastdirty" {
|
||||||
if !nativeValue.(bool) {
|
if !nativeValue.(bool) {
|
||||||
if !b.Modified() {
|
e := calcHash(b, &b.origHash)
|
||||||
e := calcHash(b, &b.origHash)
|
if e == ErrFileTooLarge {
|
||||||
if e == ErrFileTooLarge {
|
b.Settings["fastdirty"] = false
|
||||||
b.Settings["fastdirty"] = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if option == "statusline" {
|
} else if option == "statusline" {
|
||||||
screen.Redraw()
|
screen.Redraw()
|
||||||
} else if option == "filetype" {
|
} else if option == "filetype" {
|
||||||
config.InitRuntimeFiles(true)
|
|
||||||
err := config.ReadSettings()
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
err = config.InitGlobalSettings()
|
|
||||||
if err != nil {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
config.InitLocalSettings(b.Settings, b.Path)
|
|
||||||
b.UpdateRules()
|
b.UpdateRules()
|
||||||
} else if option == "fileformat" {
|
} else if option == "fileformat" {
|
||||||
switch b.Settings["fileformat"].(string) {
|
switch b.Settings["fileformat"].(string) {
|
||||||
|
@ -49,35 +37,6 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||||
b.isModified = true
|
b.isModified = true
|
||||||
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
|
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
|
||||||
b.Type.Readonly = nativeValue.(bool)
|
b.Type.Readonly = nativeValue.(bool)
|
||||||
} else if option == "hlsearch" {
|
|
||||||
for _, buf := range OpenBuffers {
|
|
||||||
if b.SharedBuffer == buf.SharedBuffer {
|
|
||||||
buf.HighlightSearch = nativeValue.(bool)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, pl := range config.Plugins {
|
|
||||||
if option == pl.Name {
|
|
||||||
if nativeValue.(bool) {
|
|
||||||
if !pl.Loaded {
|
|
||||||
pl.Load()
|
|
||||||
}
|
|
||||||
_, err := pl.Call("init")
|
|
||||||
if err != nil && err != config.ErrNoSuchFunction {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
} else if !nativeValue.(bool) && pl.Loaded {
|
|
||||||
_, err := pl.Call("deinit")
|
|
||||||
if err != nil && err != config.ErrNoSuchFunction {
|
|
||||||
screen.TermMessage(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.OptionCallback != nil {
|
|
||||||
b.OptionCallback(option, nativeValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,163 +0,0 @@
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/zyedidia/clipper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Method int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// External relies on external tools for accessing the clipboard
|
|
||||||
// These include xclip, xsel, wl-clipboard for linux, pbcopy/pbpaste on Mac,
|
|
||||||
// and Syscalls on Windows.
|
|
||||||
External Method = iota
|
|
||||||
// Terminal uses the terminal to manage the clipboard via OSC 52. Many
|
|
||||||
// terminals do not support OSC 52, in which case this method won't work.
|
|
||||||
Terminal
|
|
||||||
// Internal just manages the clipboard with an internal buffer and doesn't
|
|
||||||
// attempt to interface with the system clipboard
|
|
||||||
Internal
|
|
||||||
)
|
|
||||||
|
|
||||||
// CurrentMethod is the method used to store clipboard information
|
|
||||||
var CurrentMethod Method = Internal
|
|
||||||
|
|
||||||
// A Register is a buffer used to store text. The system clipboard has the 'clipboard'
|
|
||||||
// and 'primary' (linux-only) registers, but other registers may be used internal to micro.
|
|
||||||
type Register int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ClipboardReg is the main system clipboard
|
|
||||||
ClipboardReg Register = -1
|
|
||||||
// PrimaryReg is the system primary clipboard (linux only)
|
|
||||||
PrimaryReg = -2
|
|
||||||
)
|
|
||||||
|
|
||||||
var clipboard clipper.Clipboard
|
|
||||||
|
|
||||||
// Initialize attempts to initialize the clipboard using the given method
|
|
||||||
func Initialize(m Method) error {
|
|
||||||
var err error
|
|
||||||
switch m {
|
|
||||||
case External:
|
|
||||||
clips := make([]clipper.Clipboard, 0, len(clipper.Clipboards)+1)
|
|
||||||
clips = append(clips, &clipper.Custom{
|
|
||||||
Name: "micro-clip",
|
|
||||||
})
|
|
||||||
clips = append(clips, clipper.Clipboards...)
|
|
||||||
clipboard, err = clipper.GetClipboard(clips...)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
CurrentMethod = Internal
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMethod changes the clipboard access method
|
|
||||||
func SetMethod(m string) Method {
|
|
||||||
switch m {
|
|
||||||
case "internal":
|
|
||||||
CurrentMethod = Internal
|
|
||||||
case "external":
|
|
||||||
CurrentMethod = External
|
|
||||||
case "terminal":
|
|
||||||
CurrentMethod = Terminal
|
|
||||||
}
|
|
||||||
return CurrentMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads from a clipboard register
|
|
||||||
func Read(r Register) (string, error) {
|
|
||||||
return read(r, CurrentMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes text to a clipboard register
|
|
||||||
func Write(text string, r Register) error {
|
|
||||||
return write(text, r, CurrentMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMulti reads text from a clipboard register for a certain multi-cursor
|
|
||||||
func ReadMulti(r Register, num, ncursors int) (string, error) {
|
|
||||||
clip, err := Read(r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if ValidMulti(r, clip, ncursors) {
|
|
||||||
return multi.getText(r, num), nil
|
|
||||||
}
|
|
||||||
return clip, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMulti writes text to a clipboard register for a certain multi-cursor
|
|
||||||
func WriteMulti(text string, r Register, num int, ncursors int) error {
|
|
||||||
return writeMulti(text, r, num, ncursors, CurrentMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidMulti checks if the internal multi-clipboard is valid and up-to-date
|
|
||||||
// with the system clipboard
|
|
||||||
func ValidMulti(r Register, clip string, ncursors int) bool {
|
|
||||||
return multi.isValid(r, clip, ncursors)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeMulti(text string, r Register, num int, ncursors int, m Method) error {
|
|
||||||
multi.writeText(text, r, num, ncursors)
|
|
||||||
return write(multi.getAllText(r), r, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func read(r Register, m Method) (string, error) {
|
|
||||||
switch m {
|
|
||||||
case External:
|
|
||||||
switch r {
|
|
||||||
case ClipboardReg:
|
|
||||||
b, e := clipboard.ReadAll(clipper.RegClipboard)
|
|
||||||
return string(b), e
|
|
||||||
case PrimaryReg:
|
|
||||||
b, e := clipboard.ReadAll(clipper.RegPrimary)
|
|
||||||
return string(b), e
|
|
||||||
default:
|
|
||||||
return internal.read(r), nil
|
|
||||||
}
|
|
||||||
case Internal:
|
|
||||||
return internal.read(r), nil
|
|
||||||
case Terminal:
|
|
||||||
switch r {
|
|
||||||
case ClipboardReg:
|
|
||||||
// terminal paste works by sending an esc sequence to the
|
|
||||||
// terminal to trigger a paste event
|
|
||||||
return terminal.read("clipboard")
|
|
||||||
case PrimaryReg:
|
|
||||||
return terminal.read("primary")
|
|
||||||
default:
|
|
||||||
return internal.read(r), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", errors.New("Invalid clipboard method")
|
|
||||||
}
|
|
||||||
|
|
||||||
func write(text string, r Register, m Method) error {
|
|
||||||
switch m {
|
|
||||||
case External:
|
|
||||||
switch r {
|
|
||||||
case ClipboardReg:
|
|
||||||
return clipboard.WriteAll(clipper.RegClipboard, []byte(text))
|
|
||||||
case PrimaryReg:
|
|
||||||
return clipboard.WriteAll(clipper.RegPrimary, []byte(text))
|
|
||||||
default:
|
|
||||||
internal.write(text, r)
|
|
||||||
}
|
|
||||||
case Internal:
|
|
||||||
internal.write(text, r)
|
|
||||||
case Terminal:
|
|
||||||
switch r {
|
|
||||||
case ClipboardReg:
|
|
||||||
return terminal.write(text, "c")
|
|
||||||
case PrimaryReg:
|
|
||||||
return terminal.write(text, "p")
|
|
||||||
default:
|
|
||||||
internal.write(text, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package clipboard
|
|
||||||
|
|
||||||
type internalClipboard map[Register]string
|
|
||||||
|
|
||||||
var internal internalClipboard
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
internal = make(internalClipboard)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c internalClipboard) read(r Register) string {
|
|
||||||
return c[r]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c internalClipboard) write(text string, r Register) {
|
|
||||||
c[r] = text
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// For storing multi cursor clipboard contents
|
|
||||||
type multiClipboard map[Register][]string
|
|
||||||
|
|
||||||
var multi multiClipboard
|
|
||||||
|
|
||||||
func (c multiClipboard) getAllText(r Register) string {
|
|
||||||
content := c[r]
|
|
||||||
if content == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
for _, s := range content {
|
|
||||||
buf.WriteString(s)
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c multiClipboard) getText(r Register, num int) string {
|
|
||||||
content := c[r]
|
|
||||||
if content == nil || len(content) <= num {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return content[num]
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValid checks if the text stored in this multi-clipboard is the same as the
|
|
||||||
// text stored in the system clipboard (provided as an argument), and therefore
|
|
||||||
// if it is safe to use the multi-clipboard for pasting instead of the system
|
|
||||||
// clipboard.
|
|
||||||
func (c multiClipboard) isValid(r Register, clipboard string, ncursors int) bool {
|
|
||||||
content := c[r]
|
|
||||||
if content == nil || len(content) != ncursors {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return clipboard == c.getAllText(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c multiClipboard) writeText(text string, r Register, num int, ncursors int) {
|
|
||||||
content := c[r]
|
|
||||||
if content == nil || len(content) != ncursors {
|
|
||||||
content = make([]string, ncursors, ncursors)
|
|
||||||
c[r] = content
|
|
||||||
}
|
|
||||||
|
|
||||||
if num >= ncursors {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
content[num] = text
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
multi = make(multiClipboard)
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type terminalClipboard struct{}
|
|
||||||
|
|
||||||
var terminal terminalClipboard
|
|
||||||
|
|
||||||
func (t terminalClipboard) read(reg string) (string, error) {
|
|
||||||
screen.Screen.GetClipboard(reg)
|
|
||||||
// wait at most 200ms for response
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <-screen.Events:
|
|
||||||
e, ok := event.(*tcell.EventPaste)
|
|
||||||
if ok {
|
|
||||||
return e.Text(), nil
|
|
||||||
}
|
|
||||||
case <-time.After(200 * time.Millisecond):
|
|
||||||
return "", errors.New("No clipboard received from terminal")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t terminalClipboard) write(text, reg string) error {
|
|
||||||
return screen.Screen.SetClipboard(text, reg)
|
|
||||||
}
|
|
|
@ -31,13 +31,14 @@ func GetAutoTime() int {
|
||||||
func StartAutoSave() {
|
func StartAutoSave() {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
autolock.Lock()
|
if autotime < 1 {
|
||||||
a := autotime
|
break
|
||||||
autolock.Unlock()
|
}
|
||||||
if a < 1 {
|
time.Sleep(time.Duration(autotime) * time.Second)
|
||||||
|
// it's possible autotime was changed while sleeping
|
||||||
|
if autotime < 1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(a) * time.Second)
|
|
||||||
Autosave <- true
|
Autosave <- true
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -6,13 +6,13 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefStyle is Micro's default style
|
// Micro's default style
|
||||||
var DefStyle tcell.Style = tcell.StyleDefault
|
var DefStyle tcell.Style = tcell.StyleDefault
|
||||||
|
|
||||||
// Colorscheme is the current colorscheme
|
// The current colorscheme
|
||||||
var Colorscheme map[string]tcell.Style
|
var Colorscheme map[string]tcell.Style
|
||||||
|
|
||||||
// GetColor takes in a syntax group and returns the colorscheme's style for that group
|
// GetColor takes in a syntax group and returns the colorscheme's style for that group
|
||||||
|
@ -52,55 +52,43 @@ func InitColorscheme() error {
|
||||||
Colorscheme = make(map[string]tcell.Style)
|
Colorscheme = make(map[string]tcell.Style)
|
||||||
DefStyle = tcell.StyleDefault
|
DefStyle = tcell.StyleDefault
|
||||||
|
|
||||||
c, err := LoadDefaultColorscheme()
|
return LoadDefaultColorscheme()
|
||||||
if err == nil {
|
|
||||||
Colorscheme = c
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadDefaultColorscheme loads the default colorscheme from $(ConfigDir)/colorschemes
|
// LoadDefaultColorscheme loads the default colorscheme from $(ConfigDir)/colorschemes
|
||||||
func LoadDefaultColorscheme() (map[string]tcell.Style, error) {
|
func LoadDefaultColorscheme() error {
|
||||||
var parsedColorschemes []string
|
return LoadColorscheme(GlobalSettings["colorscheme"].(string))
|
||||||
return LoadColorscheme(GlobalSettings["colorscheme"].(string), &parsedColorschemes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadColorscheme loads the given colorscheme from a directory
|
// LoadColorscheme loads the given colorscheme from a directory
|
||||||
func LoadColorscheme(colorschemeName string, parsedColorschemes *[]string) (map[string]tcell.Style, error) {
|
func LoadColorscheme(colorschemeName string) error {
|
||||||
c := make(map[string]tcell.Style)
|
|
||||||
file := FindRuntimeFile(RTColorscheme, colorschemeName)
|
file := FindRuntimeFile(RTColorscheme, colorschemeName)
|
||||||
if file == nil {
|
if file == nil {
|
||||||
return c, errors.New(colorschemeName + " is not a valid colorscheme")
|
return errors.New(colorschemeName + " is not a valid colorscheme")
|
||||||
}
|
}
|
||||||
if data, err := file.Data(); err != nil {
|
if data, err := file.Data(); err != nil {
|
||||||
return c, errors.New("Error loading colorscheme: " + err.Error())
|
return errors.New("Error loading colorscheme: " + err.Error())
|
||||||
} else {
|
} else {
|
||||||
var err error
|
Colorscheme, err = ParseColorscheme(string(data))
|
||||||
c, err = ParseColorscheme(file.Name(), string(data), parsedColorschemes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
|
// ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
|
||||||
// Colorschemes are made up of color-link statements linking a color group to a list of colors
|
// Colorschemes are made up of color-link statements linking a color group to a list of colors
|
||||||
// For example, color-link keyword (blue,red) makes all keywords have a blue foreground and
|
// For example, color-link keyword (blue,red) makes all keywords have a blue foreground and
|
||||||
// red background
|
// red background
|
||||||
func ParseColorscheme(name string, text string, parsedColorschemes *[]string) (map[string]tcell.Style, error) {
|
func ParseColorscheme(text string) (map[string]tcell.Style, error) {
|
||||||
var err error
|
var err error
|
||||||
colorParser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
|
parser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
|
||||||
includeParser := regexp.MustCompile(`include\s+"(.*)"`)
|
|
||||||
lines := strings.Split(text, "\n")
|
lines := strings.Split(text, "\n")
|
||||||
|
|
||||||
c := make(map[string]tcell.Style)
|
c := make(map[string]tcell.Style)
|
||||||
|
|
||||||
if parsedColorschemes != nil {
|
|
||||||
*parsedColorschemes = append(*parsedColorschemes, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
lineLoop:
|
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if strings.TrimSpace(line) == "" ||
|
if strings.TrimSpace(line) == "" ||
|
||||||
strings.TrimSpace(line)[0] == '#' {
|
strings.TrimSpace(line)[0] == '#' {
|
||||||
|
@ -108,30 +96,7 @@ lineLoop:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := includeParser.FindSubmatch([]byte(line))
|
matches := parser.FindSubmatch([]byte(line))
|
||||||
if len(matches) == 2 {
|
|
||||||
// support includes only in case parsedColorschemes are given
|
|
||||||
if parsedColorschemes != nil {
|
|
||||||
include := string(matches[1])
|
|
||||||
for _, name := range *parsedColorschemes {
|
|
||||||
// check for circular includes...
|
|
||||||
if name == include {
|
|
||||||
// ...and prevent them
|
|
||||||
continue lineLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
includeScheme, err := LoadColorscheme(include, parsedColorschemes)
|
|
||||||
if err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
for k, v := range includeScheme {
|
|
||||||
c[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
matches = colorParser.FindSubmatch([]byte(line))
|
|
||||||
if len(matches) == 3 {
|
if len(matches) == 3 {
|
||||||
link := string(matches[1])
|
link := string(matches[1])
|
||||||
colors := string(matches[2])
|
colors := string(matches[2])
|
||||||
|
@ -152,11 +117,16 @@ lineLoop:
|
||||||
|
|
||||||
// StringToStyle returns a style from a string
|
// StringToStyle returns a style from a string
|
||||||
// The strings must be in the format "extra foregroundcolor,backgroundcolor"
|
// The strings must be in the format "extra foregroundcolor,backgroundcolor"
|
||||||
// The 'extra' can be bold, reverse, italic or underline
|
// The 'extra' can be bold, reverse, or underline
|
||||||
func StringToStyle(str string) tcell.Style {
|
func StringToStyle(str string) tcell.Style {
|
||||||
var fg, bg string
|
var fg, bg string
|
||||||
spaceSplit := strings.Split(str, " ")
|
spaceSplit := strings.Split(str, " ")
|
||||||
split := strings.Split(spaceSplit[len(spaceSplit)-1], ",")
|
var split []string
|
||||||
|
if len(spaceSplit) > 1 {
|
||||||
|
split = strings.Split(spaceSplit[1], ",")
|
||||||
|
} else {
|
||||||
|
split = strings.Split(str, ",")
|
||||||
|
}
|
||||||
if len(split) > 1 {
|
if len(split) > 1 {
|
||||||
fg, bg = split[0], split[1]
|
fg, bg = split[0], split[1]
|
||||||
} else {
|
} else {
|
||||||
|
@ -166,22 +136,15 @@ func StringToStyle(str string) tcell.Style {
|
||||||
bg = strings.TrimSpace(bg)
|
bg = strings.TrimSpace(bg)
|
||||||
|
|
||||||
var fgColor, bgColor tcell.Color
|
var fgColor, bgColor tcell.Color
|
||||||
var ok bool
|
if fg == "" {
|
||||||
if fg == "" || fg == "default" {
|
|
||||||
fgColor, _, _ = DefStyle.Decompose()
|
fgColor, _, _ = DefStyle.Decompose()
|
||||||
} else {
|
} else {
|
||||||
fgColor, ok = StringToColor(fg)
|
fgColor = StringToColor(fg)
|
||||||
if !ok {
|
|
||||||
fgColor, _, _ = DefStyle.Decompose()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if bg == "" || bg == "default" {
|
if bg == "" {
|
||||||
_, bgColor, _ = DefStyle.Decompose()
|
_, bgColor, _ = DefStyle.Decompose()
|
||||||
} else {
|
} else {
|
||||||
bgColor, ok = StringToColor(bg)
|
bgColor = StringToColor(bg)
|
||||||
if !ok {
|
|
||||||
_, bgColor, _ = DefStyle.Decompose()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
style := DefStyle.Foreground(fgColor).Background(bgColor)
|
style := DefStyle.Foreground(fgColor).Background(bgColor)
|
||||||
|
@ -202,59 +165,95 @@ func StringToStyle(str string) tcell.Style {
|
||||||
|
|
||||||
// StringToColor returns a tcell color from a string representation of a color
|
// StringToColor returns a tcell color from a string representation of a color
|
||||||
// We accept either bright... or light... to mean the brighter version of a color
|
// We accept either bright... or light... to mean the brighter version of a color
|
||||||
func StringToColor(str string) (tcell.Color, bool) {
|
func StringToColor(str string) tcell.Color {
|
||||||
switch str {
|
switch str {
|
||||||
case "black":
|
case "black":
|
||||||
return tcell.ColorBlack, true
|
return tcell.ColorBlack
|
||||||
case "red":
|
case "red":
|
||||||
return tcell.ColorMaroon, true
|
return tcell.ColorMaroon
|
||||||
case "green":
|
case "green":
|
||||||
return tcell.ColorGreen, true
|
return tcell.ColorGreen
|
||||||
case "yellow":
|
case "yellow":
|
||||||
return tcell.ColorOlive, true
|
return tcell.ColorOlive
|
||||||
case "blue":
|
case "blue":
|
||||||
return tcell.ColorNavy, true
|
return tcell.ColorNavy
|
||||||
case "magenta":
|
case "magenta":
|
||||||
return tcell.ColorPurple, true
|
return tcell.ColorPurple
|
||||||
case "cyan":
|
case "cyan":
|
||||||
return tcell.ColorTeal, true
|
return tcell.ColorTeal
|
||||||
case "white":
|
case "white":
|
||||||
return tcell.ColorSilver, true
|
return tcell.ColorSilver
|
||||||
case "brightblack", "lightblack":
|
case "brightblack", "lightblack":
|
||||||
return tcell.ColorGray, true
|
return tcell.ColorGray
|
||||||
case "brightred", "lightred":
|
case "brightred", "lightred":
|
||||||
return tcell.ColorRed, true
|
return tcell.ColorRed
|
||||||
case "brightgreen", "lightgreen":
|
case "brightgreen", "lightgreen":
|
||||||
return tcell.ColorLime, true
|
return tcell.ColorLime
|
||||||
case "brightyellow", "lightyellow":
|
case "brightyellow", "lightyellow":
|
||||||
return tcell.ColorYellow, true
|
return tcell.ColorYellow
|
||||||
case "brightblue", "lightblue":
|
case "brightblue", "lightblue":
|
||||||
return tcell.ColorBlue, true
|
return tcell.ColorBlue
|
||||||
case "brightmagenta", "lightmagenta":
|
case "brightmagenta", "lightmagenta":
|
||||||
return tcell.ColorFuchsia, true
|
return tcell.ColorFuchsia
|
||||||
case "brightcyan", "lightcyan":
|
case "brightcyan", "lightcyan":
|
||||||
return tcell.ColorAqua, true
|
return tcell.ColorAqua
|
||||||
case "brightwhite", "lightwhite":
|
case "brightwhite", "lightwhite":
|
||||||
return tcell.ColorWhite, true
|
return tcell.ColorWhite
|
||||||
case "default":
|
case "default":
|
||||||
return tcell.ColorDefault, true
|
return tcell.ColorDefault
|
||||||
default:
|
default:
|
||||||
// Check if this is a 256 color
|
// Check if this is a 256 color
|
||||||
if num, err := strconv.Atoi(str); err == nil {
|
if num, err := strconv.Atoi(str); err == nil {
|
||||||
return GetColor256(num), true
|
return GetColor256(num)
|
||||||
}
|
}
|
||||||
// Check if this is a truecolor hex value
|
// Probably a truecolor hex value
|
||||||
if len(str) == 7 && str[0] == '#' {
|
return tcell.GetColor(str)
|
||||||
return tcell.GetColor(str), true
|
|
||||||
}
|
|
||||||
return tcell.ColorDefault, false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetColor256 returns the tcell color for a number between 0 and 255
|
// GetColor256 returns the tcell color for a number between 0 and 255
|
||||||
func GetColor256(color int) tcell.Color {
|
func GetColor256(color int) tcell.Color {
|
||||||
if color == 0 {
|
colors := []tcell.Color{tcell.ColorBlack, tcell.ColorMaroon, tcell.ColorGreen,
|
||||||
return tcell.ColorDefault
|
tcell.ColorOlive, tcell.ColorNavy, tcell.ColorPurple,
|
||||||
|
tcell.ColorTeal, tcell.ColorSilver, tcell.ColorGray,
|
||||||
|
tcell.ColorRed, tcell.ColorLime, tcell.ColorYellow,
|
||||||
|
tcell.ColorBlue, tcell.ColorFuchsia, tcell.ColorAqua,
|
||||||
|
tcell.ColorWhite, tcell.Color16, tcell.Color17, tcell.Color18, tcell.Color19, tcell.Color20,
|
||||||
|
tcell.Color21, tcell.Color22, tcell.Color23, tcell.Color24, tcell.Color25, tcell.Color26, tcell.Color27, tcell.Color28,
|
||||||
|
tcell.Color29, tcell.Color30, tcell.Color31, tcell.Color32, tcell.Color33, tcell.Color34, tcell.Color35, tcell.Color36,
|
||||||
|
tcell.Color37, tcell.Color38, tcell.Color39, tcell.Color40, tcell.Color41, tcell.Color42, tcell.Color43, tcell.Color44,
|
||||||
|
tcell.Color45, tcell.Color46, tcell.Color47, tcell.Color48, tcell.Color49, tcell.Color50, tcell.Color51, tcell.Color52,
|
||||||
|
tcell.Color53, tcell.Color54, tcell.Color55, tcell.Color56, tcell.Color57, tcell.Color58, tcell.Color59, tcell.Color60,
|
||||||
|
tcell.Color61, tcell.Color62, tcell.Color63, tcell.Color64, tcell.Color65, tcell.Color66, tcell.Color67, tcell.Color68,
|
||||||
|
tcell.Color69, tcell.Color70, tcell.Color71, tcell.Color72, tcell.Color73, tcell.Color74, tcell.Color75, tcell.Color76,
|
||||||
|
tcell.Color77, tcell.Color78, tcell.Color79, tcell.Color80, tcell.Color81, tcell.Color82, tcell.Color83, tcell.Color84,
|
||||||
|
tcell.Color85, tcell.Color86, tcell.Color87, tcell.Color88, tcell.Color89, tcell.Color90, tcell.Color91, tcell.Color92,
|
||||||
|
tcell.Color93, tcell.Color94, tcell.Color95, tcell.Color96, tcell.Color97, tcell.Color98, tcell.Color99, tcell.Color100,
|
||||||
|
tcell.Color101, tcell.Color102, tcell.Color103, tcell.Color104, tcell.Color105, tcell.Color106, tcell.Color107, tcell.Color108,
|
||||||
|
tcell.Color109, tcell.Color110, tcell.Color111, tcell.Color112, tcell.Color113, tcell.Color114, tcell.Color115, tcell.Color116,
|
||||||
|
tcell.Color117, tcell.Color118, tcell.Color119, tcell.Color120, tcell.Color121, tcell.Color122, tcell.Color123, tcell.Color124,
|
||||||
|
tcell.Color125, tcell.Color126, tcell.Color127, tcell.Color128, tcell.Color129, tcell.Color130, tcell.Color131, tcell.Color132,
|
||||||
|
tcell.Color133, tcell.Color134, tcell.Color135, tcell.Color136, tcell.Color137, tcell.Color138, tcell.Color139, tcell.Color140,
|
||||||
|
tcell.Color141, tcell.Color142, tcell.Color143, tcell.Color144, tcell.Color145, tcell.Color146, tcell.Color147, tcell.Color148,
|
||||||
|
tcell.Color149, tcell.Color150, tcell.Color151, tcell.Color152, tcell.Color153, tcell.Color154, tcell.Color155, tcell.Color156,
|
||||||
|
tcell.Color157, tcell.Color158, tcell.Color159, tcell.Color160, tcell.Color161, tcell.Color162, tcell.Color163, tcell.Color164,
|
||||||
|
tcell.Color165, tcell.Color166, tcell.Color167, tcell.Color168, tcell.Color169, tcell.Color170, tcell.Color171, tcell.Color172,
|
||||||
|
tcell.Color173, tcell.Color174, tcell.Color175, tcell.Color176, tcell.Color177, tcell.Color178, tcell.Color179, tcell.Color180,
|
||||||
|
tcell.Color181, tcell.Color182, tcell.Color183, tcell.Color184, tcell.Color185, tcell.Color186, tcell.Color187, tcell.Color188,
|
||||||
|
tcell.Color189, tcell.Color190, tcell.Color191, tcell.Color192, tcell.Color193, tcell.Color194, tcell.Color195, tcell.Color196,
|
||||||
|
tcell.Color197, tcell.Color198, tcell.Color199, tcell.Color200, tcell.Color201, tcell.Color202, tcell.Color203, tcell.Color204,
|
||||||
|
tcell.Color205, tcell.Color206, tcell.Color207, tcell.Color208, tcell.Color209, tcell.Color210, tcell.Color211, tcell.Color212,
|
||||||
|
tcell.Color213, tcell.Color214, tcell.Color215, tcell.Color216, tcell.Color217, tcell.Color218, tcell.Color219, tcell.Color220,
|
||||||
|
tcell.Color221, tcell.Color222, tcell.Color223, tcell.Color224, tcell.Color225, tcell.Color226, tcell.Color227, tcell.Color228,
|
||||||
|
tcell.Color229, tcell.Color230, tcell.Color231, tcell.Color232, tcell.Color233, tcell.Color234, tcell.Color235, tcell.Color236,
|
||||||
|
tcell.Color237, tcell.Color238, tcell.Color239, tcell.Color240, tcell.Color241, tcell.Color242, tcell.Color243, tcell.Color244,
|
||||||
|
tcell.Color245, tcell.Color246, tcell.Color247, tcell.Color248, tcell.Color249, tcell.Color250, tcell.Color251, tcell.Color252,
|
||||||
|
tcell.Color253, tcell.Color254, tcell.Color255,
|
||||||
}
|
}
|
||||||
return tcell.PaletteColor(color)
|
|
||||||
|
if color >= 0 && color < len(colors) {
|
||||||
|
return colors[color]
|
||||||
|
}
|
||||||
|
|
||||||
|
return tcell.ColorDefault
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSimpleStringToStyle(t *testing.T) {
|
func TestSimpleStringToStyle(t *testing.T) {
|
||||||
|
@ -26,18 +26,6 @@ func TestAttributeStringToStyle(t *testing.T) {
|
||||||
assert.NotEqual(t, 0, attr&tcell.AttrBold)
|
assert.NotEqual(t, 0, attr&tcell.AttrBold)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiAttributesStringToStyle(t *testing.T) {
|
|
||||||
s := StringToStyle("bold italic underline cyan,brightcyan")
|
|
||||||
|
|
||||||
fg, bg, attr := s.Decompose()
|
|
||||||
|
|
||||||
assert.Equal(t, tcell.ColorTeal, fg)
|
|
||||||
assert.Equal(t, tcell.ColorAqua, bg)
|
|
||||||
assert.NotEqual(t, 0, attr&tcell.AttrBold)
|
|
||||||
assert.NotEqual(t, 0, attr&tcell.AttrItalic)
|
|
||||||
assert.NotEqual(t, 0, attr&tcell.AttrUnderline)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestColor256StringToStyle(t *testing.T) {
|
func TestColor256StringToStyle(t *testing.T) {
|
||||||
s := StringToStyle("128,60")
|
s := StringToStyle("128,60")
|
||||||
|
|
||||||
|
@ -65,7 +53,7 @@ color-link constant "#AE81FF,#282828"
|
||||||
color-link constant.string "#E6DB74,#282828"
|
color-link constant.string "#E6DB74,#282828"
|
||||||
color-link constant.string.char "#BDE6AD,#282828"`
|
color-link constant.string.char "#BDE6AD,#282828"`
|
||||||
|
|
||||||
c, err := ParseColorscheme("testColorscheme", testColorscheme, nil)
|
c, err := ParseColorscheme(testColorscheme)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
fg, bg, _ := c["comment"].Decompose()
|
fg, bg, _ := c["comment"].Decompose()
|
||||||
|
|
|
@ -4,12 +4,4 @@ const (
|
||||||
DoubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
|
DoubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
|
||||||
)
|
)
|
||||||
|
|
||||||
var Bindings map[string]map[string]string
|
var Bindings map[string]string
|
||||||
|
|
||||||
func init() {
|
|
||||||
Bindings = map[string]map[string]string{
|
|
||||||
"command": make(map[string]string),
|
|
||||||
"buffer": make(map[string]string),
|
|
||||||
"terminal": make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ func LoadAllPlugins() error {
|
||||||
func RunPluginFn(fn string, args ...lua.LValue) error {
|
func RunPluginFn(fn string, args ...lua.LValue) error {
|
||||||
var reterr error
|
var reterr error
|
||||||
for _, p := range Plugins {
|
for _, p := range Plugins {
|
||||||
if !p.IsLoaded() {
|
if !p.IsEnabled() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err := p.Call(fn, args...)
|
_, err := p.Call(fn, args...)
|
||||||
|
@ -42,11 +42,11 @@ func RunPluginFn(fn string, args ...lua.LValue) error {
|
||||||
// RunPluginFnBool runs a function in all plugins and returns
|
// RunPluginFnBool runs a function in all plugins and returns
|
||||||
// false if any one of them returned false
|
// false if any one of them returned false
|
||||||
// also returns an error if any of the plugins had an error
|
// also returns an error if any of the plugins had an error
|
||||||
func RunPluginFnBool(settings map[string]interface{}, fn string, args ...lua.LValue) (bool, error) {
|
func RunPluginFnBool(fn string, args ...lua.LValue) (bool, error) {
|
||||||
var reterr error
|
var reterr error
|
||||||
retbool := true
|
retbool := true
|
||||||
for _, p := range Plugins {
|
for _, p := range Plugins {
|
||||||
if !p.IsLoaded() || (settings != nil && settings[p.Name] == false) {
|
if !p.IsEnabled() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val, err := p.Call(fn, args...)
|
val, err := p.Call(fn, args...)
|
||||||
|
@ -71,11 +71,11 @@ type Plugin struct {
|
||||||
Info *PluginInfo // json file containing info
|
Info *PluginInfo // json file containing info
|
||||||
Srcs []RuntimeFile // lua files
|
Srcs []RuntimeFile // lua files
|
||||||
Loaded bool
|
Loaded bool
|
||||||
Default bool // pre-installed plugin
|
Default bool // pre-installed plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsLoaded returns if a plugin is enabled
|
// IsEnabled returns if a plugin is enabled
|
||||||
func (p *Plugin) IsLoaded() bool {
|
func (p *Plugin) IsEnabled() bool {
|
||||||
if v, ok := GlobalSettings[p.Name]; ok {
|
if v, ok := GlobalSettings[p.Name]; ok {
|
||||||
return v.(bool) && p.Loaded
|
return v.(bool) && p.Loaded
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ func (p *Plugin) Load() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Loaded = true
|
p.Loaded = true
|
||||||
RegisterCommonOption(p.Name, true)
|
RegisterGlobalOption(p.Name, true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
|
||||||
func FindPlugin(name string) *Plugin {
|
func FindPlugin(name string) *Plugin {
|
||||||
var pl *Plugin
|
var pl *Plugin
|
||||||
for _, p := range Plugins {
|
for _, p := range Plugins {
|
||||||
if !p.IsLoaded() {
|
if !p.IsEnabled() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if p.Name == name {
|
if p.Name == name {
|
||||||
|
|
|
@ -363,7 +363,7 @@ func GetInstalledVersions(withCore bool) PluginVersions {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range Plugins {
|
for _, p := range Plugins {
|
||||||
if !p.IsLoaded() {
|
if !p.IsEnabled() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
version := GetInstalledPluginVersion(p.Name)
|
version := GetInstalledPluginVersion(p.Name)
|
||||||
|
@ -479,7 +479,9 @@ func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
|
||||||
result := make(PluginVersions, 0)
|
result := make(PluginVersions, 0)
|
||||||
p := pl.Get(name)
|
p := pl.Get(name)
|
||||||
if p != nil {
|
if p != nil {
|
||||||
result = append(result, p.Versions...)
|
for _, v := range p.Versions {
|
||||||
|
result = append(result, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -572,7 +574,7 @@ func (pv PluginVersions) install(out io.Writer) {
|
||||||
// UninstallPlugin deletes the plugin folder of the given plugin
|
// UninstallPlugin deletes the plugin folder of the given plugin
|
||||||
func UninstallPlugin(out io.Writer, name string) {
|
func UninstallPlugin(out io.Writer, name string) {
|
||||||
for _, p := range Plugins {
|
for _, p := range Plugins {
|
||||||
if !p.IsLoaded() {
|
if !p.IsEnabled() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if p.Name == name {
|
if p.Name == name {
|
||||||
|
@ -605,7 +607,7 @@ func UpdatePlugins(out io.Writer, plugins []string) {
|
||||||
// if no plugins are specified, update all installed plugins.
|
// if no plugins are specified, update all installed plugins.
|
||||||
if len(plugins) == 0 {
|
if len(plugins) == 0 {
|
||||||
for _, p := range Plugins {
|
for _, p := range Plugins {
|
||||||
if !p.IsLoaded() || p.Default {
|
if !p.IsEnabled() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
plugins = append(plugins, p.Name)
|
plugins = append(plugins, p.Name)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:generate go run runtime_generate.go ../../runtime/syntax ../../runtime
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -10,7 +11,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
rt "github.com/zyedidia/micro/v2/runtime"
|
"github.com/zyedidia/micro/v2/internal/vfsutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -40,10 +41,6 @@ var allFiles [][]RuntimeFile
|
||||||
var realFiles [][]RuntimeFile
|
var realFiles [][]RuntimeFile
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
initRuntimeVars()
|
|
||||||
}
|
|
||||||
|
|
||||||
func initRuntimeVars() {
|
|
||||||
allFiles = make([][]RuntimeFile, NumTypes)
|
allFiles = make([][]RuntimeFile, NumTypes)
|
||||||
realFiles = make([][]RuntimeFile, NumTypes)
|
realFiles = make([][]RuntimeFile, NumTypes)
|
||||||
}
|
}
|
||||||
|
@ -96,7 +93,7 @@ func (af assetFile) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (af assetFile) Data() ([]byte, error) {
|
func (af assetFile) Data() ([]byte, error) {
|
||||||
return rt.Asset(string(af))
|
return vfsutil.ReadFile(Assets, string(af))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nf namedFile) Name() string {
|
func (nf namedFile) Name() string {
|
||||||
|
@ -117,7 +114,10 @@ func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||||
// AddRuntimeFilesFromDirectory registers each file from the given directory for
|
// AddRuntimeFilesFromDirectory registers each file from the given directory for
|
||||||
// the filetype which matches the file-pattern
|
// the filetype which matches the file-pattern
|
||||||
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
|
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
|
||||||
files, _ := ioutil.ReadDir(directory)
|
files, err := ioutil.ReadDir(directory)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
||||||
fullPath := filepath.Join(directory, f.Name())
|
fullPath := filepath.Join(directory, f.Name())
|
||||||
|
@ -129,21 +129,13 @@ func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string
|
||||||
// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
|
// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
|
||||||
// the filetype which matches the file-pattern
|
// the filetype which matches the file-pattern
|
||||||
func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
|
func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
|
||||||
files, err := rt.AssetDir(directory)
|
files, err := vfsutil.ReadDir(Assets, directory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
assetLoop:
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
if ok, _ := path.Match(pattern, f); ok {
|
if ok, _ := path.Match(pattern, f.Name()); ok {
|
||||||
af := assetFile(path.Join(directory, f))
|
AddRuntimeFile(fileType, assetFile(path.Join(directory, f.Name())))
|
||||||
for _, rf := range realFiles[fileType] {
|
|
||||||
if af.Name() == rf.Name() {
|
|
||||||
continue assetLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AddRuntimeFile(fileType, af)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,30 +162,68 @@ func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
|
||||||
return realFiles[fileType]
|
return realFiles[fileType]
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitRuntimeFiles initializes all assets files and the config directory.
|
func addPlugin(dirname string, plugdir string, isID func(string) bool, vfs bool) {
|
||||||
// If `user` is false, InitRuntimeFiles ignores the config directory and
|
var srcs []os.FileInfo
|
||||||
// initializes asset files only.
|
var err error
|
||||||
func InitRuntimeFiles(user bool) {
|
if vfs {
|
||||||
add := func(fileType RTFiletype, dir, pattern string) {
|
srcs, err = vfsutil.ReadDir(Assets, filepath.Join(plugdir, dirname))
|
||||||
if user {
|
} else {
|
||||||
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
|
srcs, err = ioutil.ReadDir(filepath.Join(plugdir, dirname))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := new(Plugin)
|
||||||
|
p.Name = dirname
|
||||||
|
p.DirName = dirname
|
||||||
|
p.Default = vfs
|
||||||
|
for _, f := range srcs {
|
||||||
|
if strings.HasSuffix(f.Name(), ".lua") {
|
||||||
|
if vfs {
|
||||||
|
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, dirname, f.Name())))
|
||||||
|
} else {
|
||||||
|
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, dirname, f.Name())))
|
||||||
|
}
|
||||||
|
} else if strings.HasSuffix(f.Name(), ".json") {
|
||||||
|
var data []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if vfs {
|
||||||
|
data, err = vfsutil.ReadFile(Assets, filepath.Join(plugdir, dirname, f.Name()))
|
||||||
|
} else {
|
||||||
|
data, err = ioutil.ReadFile(filepath.Join(plugdir, dirname, f.Name()))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.Info, err = NewPluginInfo(data)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.Name = p.Info.Name
|
||||||
}
|
}
|
||||||
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initRuntimeVars()
|
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
||||||
|
log.Println(p.Name, "is not a plugin")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Plugins = append(Plugins, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitRuntimeFiles initializes all assets file and the config directory
|
||||||
|
func InitRuntimeFiles() {
|
||||||
|
add := func(fileType RTFiletype, dir, pattern string) {
|
||||||
|
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
|
||||||
|
AddRuntimeFilesFromAssets(fileType, dir, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
add(RTColorscheme, "colorschemes", "*.micro")
|
add(RTColorscheme, "colorschemes", "*.micro")
|
||||||
add(RTSyntax, "syntax", "*.yaml")
|
add(RTSyntax, "syntax", "*.yaml")
|
||||||
add(RTSyntaxHeader, "syntax", "*.hdr")
|
add(RTSyntaxHeader, "syntax", "*.hdr")
|
||||||
add(RTHelp, "help", "*.md")
|
add(RTHelp, "help", "*.md")
|
||||||
}
|
|
||||||
|
|
||||||
// InitPlugins initializes the plugins
|
|
||||||
func InitPlugins() {
|
|
||||||
Plugins = Plugins[:0]
|
|
||||||
initlua := filepath.Join(ConfigDir, "init.lua")
|
initlua := filepath.Join(ConfigDir, "init.lua")
|
||||||
|
|
||||||
if _, err := os.Stat(initlua); !os.IsNotExist(err) {
|
if _, err := os.Stat(initlua); !os.IsNotExist(err) {
|
||||||
p := new(Plugin)
|
p := new(Plugin)
|
||||||
p.Name = "initlua"
|
p.Name = "initlua"
|
||||||
|
@ -204,77 +234,24 @@ func InitPlugins() {
|
||||||
|
|
||||||
// Search ConfigDir for plugin-scripts
|
// Search ConfigDir for plugin-scripts
|
||||||
plugdir := filepath.Join(ConfigDir, "plug")
|
plugdir := filepath.Join(ConfigDir, "plug")
|
||||||
files, _ := ioutil.ReadDir(plugdir)
|
files, err := ioutil.ReadDir(plugdir)
|
||||||
|
|
||||||
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
|
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
|
||||||
|
|
||||||
for _, d := range files {
|
if err == nil {
|
||||||
plugpath := filepath.Join(plugdir, d.Name())
|
for _, d := range files {
|
||||||
if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
|
if d.IsDir() {
|
||||||
srcs, _ := ioutil.ReadDir(plugpath)
|
addPlugin(d.Name(), plugdir, isID, false)
|
||||||
p := new(Plugin)
|
|
||||||
p.Name = d.Name()
|
|
||||||
p.DirName = d.Name()
|
|
||||||
for _, f := range srcs {
|
|
||||||
if strings.HasSuffix(f.Name(), ".lua") {
|
|
||||||
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
|
||||||
} else if strings.HasSuffix(f.Name(), ".json") {
|
|
||||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.Info, err = NewPluginInfo(data)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.Name = p.Info.Name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
|
||||||
log.Println(p.Name, "is not a plugin")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
Plugins = append(Plugins, p)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugdir = filepath.Join("runtime", "plugins")
|
plugdir = "plugins"
|
||||||
if files, err := rt.AssetDir(plugdir); err == nil {
|
files, err = vfsutil.ReadDir(Assets, plugdir)
|
||||||
outer:
|
if err == nil {
|
||||||
for _, d := range files {
|
for _, d := range files {
|
||||||
for _, p := range Plugins {
|
if d.IsDir() {
|
||||||
if p.Name == d {
|
addPlugin(d.Name(), plugdir, isID, true)
|
||||||
log.Println(p.Name, "built-in plugin overridden by user-defined one")
|
|
||||||
continue outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if srcs, err := rt.AssetDir(filepath.Join(plugdir, d)); err == nil {
|
|
||||||
p := new(Plugin)
|
|
||||||
p.Name = d
|
|
||||||
p.DirName = d
|
|
||||||
p.Default = true
|
|
||||||
for _, f := range srcs {
|
|
||||||
if strings.HasSuffix(f, ".lua") {
|
|
||||||
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
|
|
||||||
} else if strings.HasSuffix(f, ".json") {
|
|
||||||
data, err := rt.Asset(filepath.Join(plugdir, d, f))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.Info, err = NewPluginInfo(data)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.Name = p.Info.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
|
||||||
log.Println(p.Name, "is not a plugin")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
Plugins = append(Plugins, p)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,7 +288,7 @@ func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) e
|
||||||
if _, err := os.Stat(fullpath); err == nil {
|
if _, err := os.Stat(fullpath); err == nil {
|
||||||
AddRealRuntimeFile(filetype, realFile(fullpath))
|
AddRealRuntimeFile(filetype, realFile(fullpath))
|
||||||
} else {
|
} else {
|
||||||
fullpath = path.Join("runtime", "plugins", pldir, filePath)
|
fullpath = path.Join("plugins", pldir, filePath)
|
||||||
AddRuntimeFile(filetype, assetFile(fullpath))
|
AddRuntimeFile(filetype, assetFile(fullpath))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -328,7 +305,7 @@ func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, dire
|
||||||
if _, err := os.Stat(fullpath); err == nil {
|
if _, err := os.Stat(fullpath); err == nil {
|
||||||
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
||||||
} else {
|
} else {
|
||||||
fullpath = path.Join("runtime", "plugins", pldir, directory)
|
fullpath = path.Join("plugins", pldir, directory)
|
||||||
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
InitRuntimeFiles(false)
|
InitRuntimeFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddFile(t *testing.T) {
|
func TestAddFile(t *testing.T) {
|
||||||
|
|
112
internal/config/runtime_generate.go
Normal file
112
internal/config/runtime_generate.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
// This script generates the embedded runtime filesystem, and also creates
|
||||||
|
// syntax header metadata which makes loading syntax files at runtime faster
|
||||||
|
// Invoke as go run runtime_generate.go syntaxDir runtimeDir
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/shurcooL/vfsgen"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HeaderYaml struct {
|
||||||
|
FileType string `yaml:"filetype"`
|
||||||
|
Detect struct {
|
||||||
|
FNameRgx string `yaml:"filename"`
|
||||||
|
HeaderRgx string `yaml:"header"`
|
||||||
|
} `yaml:"detect"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Header struct {
|
||||||
|
FileType string
|
||||||
|
FNameRgx string
|
||||||
|
HeaderRgx string
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert(name string) {
|
||||||
|
filename := name + ".yaml"
|
||||||
|
var hdr HeaderYaml
|
||||||
|
source, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal(source, &hdr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
encode(name, hdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(name string, c HeaderYaml) {
|
||||||
|
f, _ := os.Create(name + ".hdr")
|
||||||
|
f.WriteString(c.FileType + "\n")
|
||||||
|
f.WriteString(c.Detect.FNameRgx + "\n")
|
||||||
|
f.WriteString(c.Detect.HeaderRgx + "\n")
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(name string) Header {
|
||||||
|
data, _ := ioutil.ReadFile(name + ".hdr")
|
||||||
|
strs := bytes.Split(data, []byte{'\n'})
|
||||||
|
var hdr Header
|
||||||
|
hdr.FileType = string(strs[0])
|
||||||
|
hdr.FNameRgx = string(strs[1])
|
||||||
|
hdr.HeaderRgx = string(strs[2])
|
||||||
|
|
||||||
|
return hdr
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
orig, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Couldn't get cwd")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
log.Fatalln("Not enough arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
syntaxDir := os.Args[1]
|
||||||
|
assetDir := os.Args[2]
|
||||||
|
|
||||||
|
os.Chdir(syntaxDir)
|
||||||
|
files, _ := ioutil.ReadDir(".")
|
||||||
|
|
||||||
|
// first remove all existing header files (clean the directory)
|
||||||
|
for _, f := range files {
|
||||||
|
fname := f.Name()
|
||||||
|
if strings.HasSuffix(fname, ".hdr") {
|
||||||
|
os.Remove(fname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now create a header file for each yaml
|
||||||
|
for _, f := range files {
|
||||||
|
fname := f.Name()
|
||||||
|
if strings.HasSuffix(fname, ".yaml") {
|
||||||
|
convert(fname[:len(fname)-5])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the assets_vfsdata.go file for embedding in the binary
|
||||||
|
os.Chdir(orig)
|
||||||
|
|
||||||
|
var assets http.FileSystem = http.Dir(assetDir)
|
||||||
|
err = vfsgen.Generate(assets, vfsgen.Options{
|
||||||
|
PackageName: "config",
|
||||||
|
VariableName: "Assets",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -20,116 +19,6 @@ import (
|
||||||
|
|
||||||
type optionValidator func(string, interface{}) error
|
type optionValidator func(string, interface{}) error
|
||||||
|
|
||||||
// a list of settings that need option validators
|
|
||||||
var optionValidators = map[string]optionValidator{
|
|
||||||
"autosave": validateNonNegativeValue,
|
|
||||||
"clipboard": validateChoice,
|
|
||||||
"colorcolumn": validateNonNegativeValue,
|
|
||||||
"colorscheme": validateColorscheme,
|
|
||||||
"detectlimit": validateNonNegativeValue,
|
|
||||||
"encoding": validateEncoding,
|
|
||||||
"fileformat": validateChoice,
|
|
||||||
"matchbracestyle": validateChoice,
|
|
||||||
"multiopen": validateChoice,
|
|
||||||
"reload": validateChoice,
|
|
||||||
"scrollmargin": validateNonNegativeValue,
|
|
||||||
"scrollspeed": validateNonNegativeValue,
|
|
||||||
"tabsize": validatePositiveValue,
|
|
||||||
}
|
|
||||||
|
|
||||||
// a list of settings with pre-defined choices
|
|
||||||
var OptionChoices = map[string][]string{
|
|
||||||
"clipboard": {"internal", "external", "terminal"},
|
|
||||||
"fileformat": {"unix", "dos"},
|
|
||||||
"matchbracestyle": {"underline", "highlight"},
|
|
||||||
"multiopen": {"tab", "hsplit", "vsplit"},
|
|
||||||
"reload": {"prompt", "auto", "disabled"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// a list of settings that can be globally and locally modified and their
|
|
||||||
// default values
|
|
||||||
var defaultCommonSettings = map[string]interface{}{
|
|
||||||
"autoindent": true,
|
|
||||||
"autosu": false,
|
|
||||||
"backup": true,
|
|
||||||
"backupdir": "",
|
|
||||||
"basename": false,
|
|
||||||
"colorcolumn": float64(0),
|
|
||||||
"cursorline": true,
|
|
||||||
"detectlimit": float64(100),
|
|
||||||
"diffgutter": false,
|
|
||||||
"encoding": "utf-8",
|
|
||||||
"eofnewline": true,
|
|
||||||
"fastdirty": false,
|
|
||||||
"fileformat": defaultFileFormat(),
|
|
||||||
"filetype": "unknown",
|
|
||||||
"hlsearch": false,
|
|
||||||
"hltaberrors": false,
|
|
||||||
"hltrailingws": false,
|
|
||||||
"incsearch": true,
|
|
||||||
"ignorecase": true,
|
|
||||||
"indentchar": " ",
|
|
||||||
"keepautoindent": false,
|
|
||||||
"matchbrace": true,
|
|
||||||
"matchbracestyle": "underline",
|
|
||||||
"mkparents": false,
|
|
||||||
"permbackup": false,
|
|
||||||
"readonly": false,
|
|
||||||
"reload": "prompt",
|
|
||||||
"rmtrailingws": false,
|
|
||||||
"ruler": true,
|
|
||||||
"relativeruler": false,
|
|
||||||
"savecursor": false,
|
|
||||||
"saveundo": false,
|
|
||||||
"scrollbar": false,
|
|
||||||
"scrollmargin": float64(3),
|
|
||||||
"scrollspeed": float64(2),
|
|
||||||
"smartpaste": true,
|
|
||||||
"softwrap": false,
|
|
||||||
"splitbottom": true,
|
|
||||||
"splitright": true,
|
|
||||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
|
||||||
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
|
||||||
"statusline": true,
|
|
||||||
"syntax": true,
|
|
||||||
"tabmovement": false,
|
|
||||||
"tabsize": float64(4),
|
|
||||||
"tabstospaces": false,
|
|
||||||
"useprimary": true,
|
|
||||||
"wordwrap": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// a list of settings that should only be globally modified and their
|
|
||||||
// default values
|
|
||||||
var DefaultGlobalOnlySettings = map[string]interface{}{
|
|
||||||
"autosave": float64(0),
|
|
||||||
"clipboard": "external",
|
|
||||||
"colorscheme": "default",
|
|
||||||
"divchars": "|-",
|
|
||||||
"divreverse": true,
|
|
||||||
"fakecursor": false,
|
|
||||||
"infobar": true,
|
|
||||||
"keymenu": false,
|
|
||||||
"mouse": true,
|
|
||||||
"multiopen": "tab",
|
|
||||||
"parsecursor": false,
|
|
||||||
"paste": false,
|
|
||||||
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
|
|
||||||
"pluginrepos": []string{},
|
|
||||||
"savehistory": true,
|
|
||||||
"scrollbarchar": "|",
|
|
||||||
"sucmd": "sudo",
|
|
||||||
"tabhighlight": false,
|
|
||||||
"tabreverse": true,
|
|
||||||
"xterm": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// a list of settings that should never be globally modified
|
|
||||||
var LocalSettings = []string{
|
|
||||||
"filetype",
|
|
||||||
"readonly",
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidOption = errors.New("Invalid option")
|
ErrInvalidOption = errors.New("Invalid option")
|
||||||
ErrInvalidValue = errors.New("Invalid value")
|
ErrInvalidValue = errors.New("Invalid value")
|
||||||
|
@ -138,37 +27,41 @@ var (
|
||||||
GlobalSettings map[string]interface{}
|
GlobalSettings map[string]interface{}
|
||||||
|
|
||||||
// This is the raw parsed json
|
// This is the raw parsed json
|
||||||
parsedSettings map[string]interface{}
|
parsedSettings map[string]interface{}
|
||||||
settingsParseError bool
|
|
||||||
|
|
||||||
// ModifiedSettings is a map of settings which should be written to disk
|
// ModifiedSettings is a map of settings which should be written to disk
|
||||||
// because they have been modified by the user in this session
|
// because they have been modified by the user in this session
|
||||||
ModifiedSettings map[string]bool
|
ModifiedSettings map[string]bool
|
||||||
|
|
||||||
// VolatileSettings is a map of settings which should not be written to disk
|
|
||||||
// because they have been temporarily set for this session only
|
|
||||||
VolatileSettings map[string]bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ModifiedSettings = make(map[string]bool)
|
ModifiedSettings = make(map[string]bool)
|
||||||
VolatileSettings = make(map[string]bool)
|
|
||||||
parsedSettings = make(map[string]interface{})
|
parsedSettings = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Options with validators
|
||||||
|
var optionValidators = map[string]optionValidator{
|
||||||
|
"autosave": validateNonNegativeValue,
|
||||||
|
"tabsize": validatePositiveValue,
|
||||||
|
"scrollmargin": validateNonNegativeValue,
|
||||||
|
"scrollspeed": validateNonNegativeValue,
|
||||||
|
"colorscheme": validateColorscheme,
|
||||||
|
"colorcolumn": validateNonNegativeValue,
|
||||||
|
"fileformat": validateLineEnding,
|
||||||
|
"encoding": validateEncoding,
|
||||||
|
}
|
||||||
|
|
||||||
func ReadSettings() error {
|
func ReadSettings() error {
|
||||||
filename := filepath.Join(ConfigDir, "settings.json")
|
filename := filepath.Join(ConfigDir, "settings.json")
|
||||||
if _, e := os.Stat(filename); e == nil {
|
if _, e := os.Stat(filename); e == nil {
|
||||||
input, err := ioutil.ReadFile(filename)
|
input, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
settingsParseError = true
|
|
||||||
return errors.New("Error reading settings.json file: " + err.Error())
|
return errors.New("Error reading settings.json file: " + err.Error())
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(string(input), "null") {
|
if !strings.HasPrefix(string(input), "null") {
|
||||||
// Unmarshal the input into the parsed map
|
// Unmarshal the input into the parsed map
|
||||||
err = json5.Unmarshal(input, &parsedSettings)
|
err = json5.Unmarshal(input, &parsedSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
settingsParseError = true
|
|
||||||
return errors.New("Error reading settings.json: " + err.Error())
|
return errors.New("Error reading settings.json: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +100,7 @@ func InitGlobalSettings() error {
|
||||||
for k, v := range parsedSettings {
|
for k, v := range parsedSettings {
|
||||||
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||||
if _, ok := GlobalSettings[k]; ok && !verifySetting(k, reflect.TypeOf(v), reflect.TypeOf(GlobalSettings[k])) {
|
if _, ok := GlobalSettings[k]; ok && !verifySetting(k, reflect.TypeOf(v), reflect.TypeOf(GlobalSettings[k])) {
|
||||||
err = fmt.Errorf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k]))
|
err = errors.New(fmt.Sprintf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k])))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +121,7 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||||
if settings["filetype"].(string) == k[3:] {
|
if settings["filetype"].(string) == k[3:] {
|
||||||
for k1, v1 := range v.(map[string]interface{}) {
|
for k1, v1 := range v.(map[string]interface{}) {
|
||||||
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
|
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
|
||||||
parseError = fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1]))
|
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
settings[k1] = v1
|
settings[k1] = v1
|
||||||
|
@ -244,7 +137,7 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||||
if g.MatchString(path) {
|
if g.MatchString(path) {
|
||||||
for k1, v1 := range v.(map[string]interface{}) {
|
for k1, v1 := range v.(map[string]interface{}) {
|
||||||
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
|
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
|
||||||
parseError = fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1]))
|
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
settings[k1] = v1
|
settings[k1] = v1
|
||||||
|
@ -258,14 +151,6 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||||
|
|
||||||
// WriteSettings writes the settings to the specified filename as JSON
|
// WriteSettings writes the settings to the specified filename as JSON
|
||||||
func WriteSettings(filename string) error {
|
func WriteSettings(filename string) error {
|
||||||
if settingsParseError {
|
|
||||||
// Don't write settings if there was a parse error
|
|
||||||
// because this will delete the settings.json if it
|
|
||||||
// is invalid. Instead we should allow the user to fix
|
|
||||||
// it manually.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if _, e := os.Stat(ConfigDir); e == nil {
|
if _, e := os.Stat(ConfigDir); e == nil {
|
||||||
defaults := DefaultGlobalSettings()
|
defaults := DefaultGlobalSettings()
|
||||||
|
@ -274,8 +159,7 @@ func WriteSettings(filename string) error {
|
||||||
for k, v := range parsedSettings {
|
for k, v := range parsedSettings {
|
||||||
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||||
cur, okcur := GlobalSettings[k]
|
cur, okcur := GlobalSettings[k]
|
||||||
_, vol := VolatileSettings[k]
|
if def, ok := defaults[k]; ok && okcur && reflect.DeepEqual(cur, def) {
|
||||||
if def, ok := defaults[k]; ok && okcur && !vol && reflect.DeepEqual(cur, def) {
|
|
||||||
delete(parsedSettings, k)
|
delete(parsedSettings, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,7 +204,18 @@ func OverwriteSettings(filename string) error {
|
||||||
|
|
||||||
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
|
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
|
||||||
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
||||||
return RegisterCommonOption(pl+"."+name, defaultvalue)
|
name = pl + "." + name
|
||||||
|
if v, ok := GlobalSettings[name]; !ok {
|
||||||
|
defaultCommonSettings[name] = defaultvalue
|
||||||
|
GlobalSettings[name] = defaultvalue
|
||||||
|
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Error writing settings.json file: " + err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defaultCommonSettings[name] = v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
|
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
|
||||||
|
@ -328,21 +223,18 @@ func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{})
|
||||||
return RegisterGlobalOption(pl+"."+name, defaultvalue)
|
return RegisterGlobalOption(pl+"."+name, defaultvalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterCommonOption creates a new option
|
|
||||||
func RegisterCommonOption(name string, defaultvalue interface{}) error {
|
|
||||||
if _, ok := GlobalSettings[name]; !ok {
|
|
||||||
GlobalSettings[name] = defaultvalue
|
|
||||||
}
|
|
||||||
defaultCommonSettings[name] = defaultvalue
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterGlobalOption creates a new global-only option
|
// RegisterGlobalOption creates a new global-only option
|
||||||
func RegisterGlobalOption(name string, defaultvalue interface{}) error {
|
func RegisterGlobalOption(name string, defaultvalue interface{}) error {
|
||||||
if _, ok := GlobalSettings[name]; !ok {
|
if v, ok := GlobalSettings[name]; !ok {
|
||||||
|
DefaultGlobalOnlySettings[name] = defaultvalue
|
||||||
GlobalSettings[name] = defaultvalue
|
GlobalSettings[name] = defaultvalue
|
||||||
|
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Error writing settings.json file: " + err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DefaultGlobalOnlySettings[name] = v
|
||||||
}
|
}
|
||||||
DefaultGlobalOnlySettings[name] = defaultvalue
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,11 +243,45 @@ func GetGlobalOption(name string) interface{} {
|
||||||
return GlobalSettings[name]
|
return GlobalSettings[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultFileFormat() string {
|
var defaultCommonSettings = map[string]interface{}{
|
||||||
if runtime.GOOS == "windows" {
|
"autoindent": true,
|
||||||
return "dos"
|
"autosu": false,
|
||||||
}
|
"backup": true,
|
||||||
return "unix"
|
"basename": false,
|
||||||
|
"colorcolumn": float64(0),
|
||||||
|
"cursorline": true,
|
||||||
|
"diffgutter": false,
|
||||||
|
"encoding": "utf-8",
|
||||||
|
"eofnewline": true,
|
||||||
|
"fastdirty": false,
|
||||||
|
"fileformat": "unix",
|
||||||
|
"filetype": "unknown",
|
||||||
|
"ignorecase": false,
|
||||||
|
"indentchar": " ",
|
||||||
|
"keepautoindent": false,
|
||||||
|
"matchbrace": true,
|
||||||
|
"mkparents": false,
|
||||||
|
"readonly": false,
|
||||||
|
"rmtrailingws": false,
|
||||||
|
"ruler": true,
|
||||||
|
"relativeruler": false,
|
||||||
|
"savecursor": false,
|
||||||
|
"saveundo": false,
|
||||||
|
"scrollbar": false,
|
||||||
|
"scrollmargin": float64(3),
|
||||||
|
"scrollspeed": float64(2),
|
||||||
|
"smartpaste": true,
|
||||||
|
"softwrap": false,
|
||||||
|
"splitbottom": true,
|
||||||
|
"splitright": true,
|
||||||
|
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||||
|
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
||||||
|
"statusline": true,
|
||||||
|
"syntax": true,
|
||||||
|
"tabmovement": false,
|
||||||
|
"tabsize": float64(4),
|
||||||
|
"tabstospaces": false,
|
||||||
|
"useprimary": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetInfoBarOffset() int {
|
func GetInfoBarOffset() int {
|
||||||
|
@ -379,6 +305,31 @@ func DefaultCommonSettings() map[string]interface{} {
|
||||||
return commonsettings
|
return commonsettings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a list of settings that should only be globally modified and their
|
||||||
|
// default values
|
||||||
|
var DefaultGlobalOnlySettings = map[string]interface{}{
|
||||||
|
"autosave": float64(0),
|
||||||
|
"colorscheme": "default",
|
||||||
|
"divchars": "|-",
|
||||||
|
"divreverse": true,
|
||||||
|
"infobar": true,
|
||||||
|
"keymenu": false,
|
||||||
|
"mouse": true,
|
||||||
|
"parsecursor": false,
|
||||||
|
"paste": false,
|
||||||
|
"savehistory": true,
|
||||||
|
"sucmd": "sudo",
|
||||||
|
"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
|
||||||
|
var LocalSettings = []string{
|
||||||
|
"filetype",
|
||||||
|
"readonly",
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultGlobalSettings returns the default global settings for micro
|
// DefaultGlobalSettings returns the default global settings for micro
|
||||||
// Note that colorscheme is a global only option
|
// Note that colorscheme is a global only option
|
||||||
func DefaultGlobalSettings() map[string]interface{} {
|
func DefaultGlobalSettings() map[string]interface{} {
|
||||||
|
@ -472,26 +423,6 @@ func validateNonNegativeValue(option string, value interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateChoice(option string, value interface{}) error {
|
|
||||||
if choices, ok := OptionChoices[option]; ok {
|
|
||||||
val, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("Expected string type for " + option)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range choices {
|
|
||||||
if val == v {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
choicesStr := strings.Join(choices, ", ")
|
|
||||||
return errors.New(option + " must be one of: " + choicesStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("Option has no pre-defined choices")
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateColorscheme(option string, value interface{}) error {
|
func validateColorscheme(option string, value interface{}) error {
|
||||||
colorscheme, ok := value.(string)
|
colorscheme, ok := value.(string)
|
||||||
|
|
||||||
|
@ -506,6 +437,20 @@ func validateColorscheme(option string, value interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateLineEnding(option string, value interface{}) error {
|
||||||
|
endingType, ok := value.(string)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return errors.New("Expected string type for file format")
|
||||||
|
}
|
||||||
|
|
||||||
|
if endingType != "unix" && endingType != "dos" {
|
||||||
|
return errors.New("File format must be either 'unix' or 'dos'")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func validateEncoding(option string, value interface{}) error {
|
func validateEncoding(option string, value interface{}) error {
|
||||||
_, err := htmlindex.Get(value.(string))
|
_, err := htmlindex.Get(value.(string))
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -8,10 +8,11 @@ import (
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The BufWindow provides a way of displaying a certain section of a buffer.
|
// The BufWindow provides a way of displaying a certain section
|
||||||
|
// of a buffer
|
||||||
type BufWindow struct {
|
type BufWindow struct {
|
||||||
*View
|
*View
|
||||||
|
|
||||||
|
@ -22,20 +23,15 @@ type BufWindow struct {
|
||||||
|
|
||||||
sline *StatusLine
|
sline *StatusLine
|
||||||
|
|
||||||
bufWidth int
|
gutterOffset int
|
||||||
bufHeight int
|
drawStatus bool
|
||||||
gutterOffset int
|
|
||||||
hasMessage bool
|
|
||||||
maxLineNumLength int
|
|
||||||
drawDivider bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBufWindow creates a new window at a location in the screen with a width and height
|
// NewBufWindow creates a new window at a location in the screen with a width and height
|
||||||
func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
|
func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
|
||||||
w := new(BufWindow)
|
w := new(BufWindow)
|
||||||
w.View = new(View)
|
w.View = new(View)
|
||||||
w.X, w.Y, w.Width, w.Height = x, y, width, height
|
w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf
|
||||||
w.SetBuffer(buf)
|
|
||||||
w.active = true
|
w.active = true
|
||||||
|
|
||||||
w.sline = NewStatusLine(w)
|
w.sline = NewStatusLine(w)
|
||||||
|
@ -43,128 +39,31 @@ func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBuffer sets this window's buffer.
|
|
||||||
func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
|
func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
|
||||||
w.Buf = b
|
w.Buf = b
|
||||||
b.OptionCallback = func(option string, nativeValue interface{}) {
|
|
||||||
if option == "softwrap" {
|
|
||||||
if nativeValue.(bool) {
|
|
||||||
w.StartCol = 0
|
|
||||||
} else {
|
|
||||||
w.StartLine.Row = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if option == "softwrap" || option == "wordwrap" {
|
|
||||||
w.Relocate()
|
|
||||||
for _, c := range w.Buf.GetCursors() {
|
|
||||||
c.LastVisualX = c.GetVisualX()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.GetVisualX = func(loc buffer.Loc) int {
|
|
||||||
return w.VLocFromLoc(loc).VisualX
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetView gets the view.
|
|
||||||
func (w *BufWindow) GetView() *View {
|
func (w *BufWindow) GetView() *View {
|
||||||
return w.View
|
return w.View
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetView sets the view.
|
|
||||||
func (w *BufWindow) SetView(view *View) {
|
func (w *BufWindow) SetView(view *View) {
|
||||||
w.View = view
|
w.View = view
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize resizes this window.
|
|
||||||
func (w *BufWindow) Resize(width, height int) {
|
func (w *BufWindow) Resize(width, height int) {
|
||||||
w.Width, w.Height = width, height
|
w.Width, w.Height = width, height
|
||||||
w.updateDisplayInfo()
|
|
||||||
|
|
||||||
w.Relocate()
|
w.Relocate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetActive marks the window as active.
|
|
||||||
func (w *BufWindow) SetActive(b bool) {
|
func (w *BufWindow) SetActive(b bool) {
|
||||||
w.active = b
|
w.active = b
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsActive returns true if this window is active.
|
|
||||||
func (w *BufWindow) IsActive() bool {
|
func (w *BufWindow) IsActive() bool {
|
||||||
return w.active
|
return w.active
|
||||||
}
|
}
|
||||||
|
|
||||||
// BufView returns the width, height and x,y location of the actual buffer.
|
|
||||||
// It is not exactly the same as the whole window which also contains gutter,
|
|
||||||
// ruler, scrollbar and statusline.
|
|
||||||
func (w *BufWindow) BufView() View {
|
|
||||||
return View{
|
|
||||||
X: w.X + w.gutterOffset,
|
|
||||||
Y: w.Y,
|
|
||||||
Width: w.bufWidth,
|
|
||||||
Height: w.bufHeight,
|
|
||||||
StartLine: w.StartLine,
|
|
||||||
StartCol: w.StartCol,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufWindow) updateDisplayInfo() {
|
|
||||||
b := w.Buf
|
|
||||||
|
|
||||||
w.drawDivider = false
|
|
||||||
if !b.Settings["statusline"].(bool) {
|
|
||||||
_, h := screen.Screen.Size()
|
|
||||||
infoY := h
|
|
||||||
if config.GetGlobalOption("infobar").(bool) {
|
|
||||||
infoY--
|
|
||||||
}
|
|
||||||
if w.Y+w.Height != infoY {
|
|
||||||
w.drawDivider = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.bufHeight = w.Height
|
|
||||||
if b.Settings["statusline"].(bool) || w.drawDivider {
|
|
||||||
w.bufHeight--
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollbarWidth := 0
|
|
||||||
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height && w.Width > 0 {
|
|
||||||
scrollbarWidth = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
w.hasMessage = len(b.Messages) > 0
|
|
||||||
|
|
||||||
// We need to know the string length of the largest line number
|
|
||||||
// so we can pad appropriately when displaying line numbers
|
|
||||||
w.maxLineNumLength = len(strconv.Itoa(b.LinesNum()))
|
|
||||||
|
|
||||||
w.gutterOffset = 0
|
|
||||||
if w.hasMessage {
|
|
||||||
w.gutterOffset += 2
|
|
||||||
}
|
|
||||||
if b.Settings["diffgutter"].(bool) {
|
|
||||||
w.gutterOffset++
|
|
||||||
}
|
|
||||||
if b.Settings["ruler"].(bool) {
|
|
||||||
w.gutterOffset += w.maxLineNumLength + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.gutterOffset > w.Width-scrollbarWidth {
|
|
||||||
w.gutterOffset = w.Width - scrollbarWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
prevBufWidth := w.bufWidth
|
|
||||||
w.bufWidth = w.Width - w.gutterOffset - scrollbarWidth
|
|
||||||
|
|
||||||
if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) {
|
|
||||||
for _, c := range w.Buf.GetCursors() {
|
|
||||||
c.LastVisualX = c.GetVisualX()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) {
|
func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) {
|
||||||
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
||||||
width := 0
|
width := 0
|
||||||
|
@ -207,49 +106,63 @@ func (w *BufWindow) Clear() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bottomline returns the line number of the lowest line in the view
|
||||||
|
// You might think that this is obviously just v.StartLine + v.Height
|
||||||
|
// but if softwrap is enabled things get complicated since one buffer
|
||||||
|
// line can take up multiple lines in the view
|
||||||
|
func (w *BufWindow) Bottomline() int {
|
||||||
|
if !w.Buf.Settings["softwrap"].(bool) {
|
||||||
|
h := w.StartLine + w.Height - 1
|
||||||
|
if w.drawStatus {
|
||||||
|
h--
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
l := w.LocFromVisual(buffer.Loc{0, w.Y + w.Height})
|
||||||
|
|
||||||
|
return l.Y
|
||||||
|
}
|
||||||
|
|
||||||
// Relocate moves the view window so that the cursor is in view
|
// Relocate moves the view window so that the cursor is in view
|
||||||
// This is useful if the user has scrolled far away, and then starts typing
|
// This is useful if the user has scrolled far away, and then starts typing
|
||||||
// Returns true if the window location is moved
|
// Returns true if the window location is moved
|
||||||
func (w *BufWindow) Relocate() bool {
|
func (w *BufWindow) Relocate() bool {
|
||||||
b := w.Buf
|
b := w.Buf
|
||||||
height := w.bufHeight
|
// how many buffer lines are in the view
|
||||||
|
height := w.Bottomline() + 1 - w.StartLine
|
||||||
|
h := w.Height
|
||||||
|
if w.drawStatus {
|
||||||
|
h--
|
||||||
|
}
|
||||||
ret := false
|
ret := false
|
||||||
activeC := w.Buf.GetActiveCursor()
|
activeC := w.Buf.GetActiveCursor()
|
||||||
|
cy := activeC.Y
|
||||||
scrollmargin := int(b.Settings["scrollmargin"].(float64))
|
scrollmargin := int(b.Settings["scrollmargin"].(float64))
|
||||||
|
if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 {
|
||||||
c := w.SLocFromLoc(activeC.Loc)
|
w.StartLine = cy - scrollmargin
|
||||||
bStart := SLoc{0, 0}
|
|
||||||
bEnd := w.SLocFromLoc(b.End())
|
|
||||||
|
|
||||||
if c.LessThan(w.Scroll(w.StartLine, scrollmargin)) && c.GreaterThan(w.Scroll(bStart, scrollmargin-1)) {
|
|
||||||
w.StartLine = w.Scroll(c, -scrollmargin)
|
|
||||||
ret = true
|
ret = true
|
||||||
} else if c.LessThan(w.StartLine) {
|
} else if cy < w.StartLine {
|
||||||
w.StartLine = c
|
w.StartLine = cy
|
||||||
ret = true
|
ret = true
|
||||||
}
|
}
|
||||||
if c.GreaterThan(w.Scroll(w.StartLine, height-1-scrollmargin)) && c.LessEqual(w.Scroll(bEnd, -scrollmargin)) {
|
if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin {
|
||||||
w.StartLine = w.Scroll(c, -height+1+scrollmargin)
|
w.StartLine = cy - height + 1 + scrollmargin
|
||||||
ret = true
|
ret = true
|
||||||
} else if c.GreaterThan(w.Scroll(bEnd, -scrollmargin)) && c.GreaterThan(w.Scroll(w.StartLine, height-1)) {
|
} else if cy >= b.LinesNum()-scrollmargin && cy >= height {
|
||||||
w.StartLine = w.Scroll(bEnd, -height+1)
|
w.StartLine = b.LinesNum() - height
|
||||||
ret = true
|
ret = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// horizontal relocation (scrolling)
|
// horizontal relocation (scrolling)
|
||||||
if !b.Settings["softwrap"].(bool) {
|
if !b.Settings["softwrap"].(bool) {
|
||||||
cx := activeC.GetVisualX()
|
cx := activeC.GetVisualX()
|
||||||
rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
|
|
||||||
if rw == 0 {
|
|
||||||
rw = 1 // tab or newline
|
|
||||||
}
|
|
||||||
|
|
||||||
if cx < w.StartCol {
|
if cx < w.StartCol {
|
||||||
w.StartCol = cx
|
w.StartCol = cx
|
||||||
ret = true
|
ret = true
|
||||||
}
|
}
|
||||||
if cx+w.gutterOffset+rw > w.StartCol+w.Width {
|
if cx+w.gutterOffset+1 > w.StartCol+w.Width {
|
||||||
w.StartCol = cx - w.Width + w.gutterOffset + rw
|
w.StartCol = cx - w.Width + w.gutterOffset + 1
|
||||||
ret = true
|
ret = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,18 +171,123 @@ func (w *BufWindow) Relocate() bool {
|
||||||
|
|
||||||
// LocFromVisual takes a visual location (x and y position) and returns the
|
// LocFromVisual takes a visual location (x and y position) and returns the
|
||||||
// position in the buffer corresponding to the visual location
|
// position in the buffer corresponding to the visual location
|
||||||
|
// Computing the buffer location requires essentially drawing the entire screen
|
||||||
|
// to account for complications like softwrap, wide characters, and horizontal scrolling
|
||||||
// If the requested position does not correspond to a buffer location it returns
|
// If the requested position does not correspond to a buffer location it returns
|
||||||
// the nearest position
|
// the nearest position
|
||||||
func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||||
vx := svloc.X - w.X - w.gutterOffset
|
b := w.Buf
|
||||||
if vx < 0 {
|
|
||||||
vx = 0
|
hasMessage := len(b.Messages) > 0
|
||||||
|
bufHeight := w.Height
|
||||||
|
if w.drawStatus {
|
||||||
|
bufHeight--
|
||||||
}
|
}
|
||||||
vloc := VLoc{
|
|
||||||
SLoc: w.Scroll(w.StartLine, svloc.Y-w.Y),
|
bufWidth := w.Width
|
||||||
VisualX: vx + w.StartCol,
|
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
||||||
|
bufWidth--
|
||||||
}
|
}
|
||||||
return w.LocFromVLoc(vloc)
|
|
||||||
|
// We need to know the string length of the largest line number
|
||||||
|
// so we can pad appropriately when displaying line numbers
|
||||||
|
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
|
||||||
|
|
||||||
|
tabsize := int(b.Settings["tabsize"].(float64))
|
||||||
|
softwrap := b.Settings["softwrap"].(bool)
|
||||||
|
|
||||||
|
// this represents the current draw position
|
||||||
|
// within the current window
|
||||||
|
vloc := buffer.Loc{X: 0, Y: 0}
|
||||||
|
|
||||||
|
// this represents the current draw position in the buffer (char positions)
|
||||||
|
bloc := buffer.Loc{X: -1, Y: w.StartLine}
|
||||||
|
|
||||||
|
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
|
||||||
|
vloc.X = 0
|
||||||
|
if hasMessage {
|
||||||
|
vloc.X += 2
|
||||||
|
}
|
||||||
|
if b.Settings["diffgutter"].(bool) {
|
||||||
|
vloc.X++
|
||||||
|
}
|
||||||
|
if b.Settings["ruler"].(bool) {
|
||||||
|
vloc.X += maxLineNumLength + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
line := b.LineBytes(bloc.Y)
|
||||||
|
line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize)
|
||||||
|
bloc.X = bslice
|
||||||
|
|
||||||
|
draw := func() {
|
||||||
|
if nColsBeforeStart <= 0 {
|
||||||
|
vloc.X++
|
||||||
|
}
|
||||||
|
nColsBeforeStart--
|
||||||
|
}
|
||||||
|
|
||||||
|
totalwidth := w.StartCol - nColsBeforeStart
|
||||||
|
|
||||||
|
if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
|
||||||
|
return bloc
|
||||||
|
}
|
||||||
|
for len(line) > 0 {
|
||||||
|
if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
|
||||||
|
return bloc
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, size := util.DecodeCharacter(line)
|
||||||
|
draw()
|
||||||
|
width := 0
|
||||||
|
|
||||||
|
switch r {
|
||||||
|
case '\t':
|
||||||
|
ts := tabsize - (totalwidth % tabsize)
|
||||||
|
width = ts
|
||||||
|
default:
|
||||||
|
width = runewidth.RuneWidth(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||||
|
if width > 1 {
|
||||||
|
for i := 1; i < width; i++ {
|
||||||
|
if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
|
||||||
|
return bloc
|
||||||
|
}
|
||||||
|
draw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bloc.X++
|
||||||
|
line = line[size:]
|
||||||
|
|
||||||
|
totalwidth += width
|
||||||
|
|
||||||
|
// If we reach the end of the window then we either stop or we wrap for softwrap
|
||||||
|
if vloc.X >= bufWidth {
|
||||||
|
if !softwrap {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
vloc.Y++
|
||||||
|
if vloc.Y >= bufHeight {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
vloc.X = w.gutterOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vloc.Y+w.Y == svloc.Y {
|
||||||
|
return bloc
|
||||||
|
}
|
||||||
|
|
||||||
|
if bloc.Y+1 >= b.LinesNum() || vloc.Y+1 >= bufHeight {
|
||||||
|
return bloc
|
||||||
|
}
|
||||||
|
|
||||||
|
bloc.X = w.StartCol
|
||||||
|
bloc.Y++
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.Loc{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
|
func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||||
|
@ -282,17 +300,13 @@ func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i < 2 && vloc.X < w.gutterOffset; i++ {
|
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
||||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
vloc.X++
|
||||||
vloc.X++
|
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
||||||
}
|
vloc.X++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
|
func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||||
if vloc.X >= w.gutterOffset {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol := ' '
|
symbol := ' '
|
||||||
styleName := ""
|
styleName := ""
|
||||||
|
|
||||||
|
@ -320,7 +334,7 @@ func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool
|
||||||
vloc.X++
|
vloc.X++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
|
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||||
cursorLine := w.Buf.GetActiveCursor().Loc.Y
|
cursorLine := w.Buf.GetActiveCursor().Loc.Y
|
||||||
var lineInt int
|
var lineInt int
|
||||||
if w.Buf.Settings["relativeruler"] == false || cursorLine == bloc.Y {
|
if w.Buf.Settings["relativeruler"] == false || cursorLine == bloc.Y {
|
||||||
|
@ -328,28 +342,26 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, vloc
|
||||||
} else {
|
} else {
|
||||||
lineInt = bloc.Y - cursorLine
|
lineInt = bloc.Y - cursorLine
|
||||||
}
|
}
|
||||||
lineNum := []rune(strconv.Itoa(util.Abs(lineInt)))
|
lineNum := strconv.Itoa(util.Abs(lineInt))
|
||||||
|
|
||||||
// Write the spaces before the line number if necessary
|
// Write the spaces before the line number if necessary
|
||||||
for i := 0; i < w.maxLineNumLength-len(lineNum) && vloc.X < w.gutterOffset; i++ {
|
for i := 0; i < maxLineNumLength-len(lineNum); i++ {
|
||||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||||
vloc.X++
|
vloc.X++
|
||||||
}
|
}
|
||||||
// Write the actual line number
|
// Write the actual line number
|
||||||
for i := 0; i < len(lineNum) && vloc.X < w.gutterOffset; i++ {
|
for _, ch := range lineNum {
|
||||||
if softwrapped || (w.bufWidth == 0 && w.Buf.Settings["softwrap"] == true) {
|
if softwrapped {
|
||||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||||
} else {
|
} else {
|
||||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, lineNum[i], nil, lineNumStyle)
|
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
|
||||||
}
|
}
|
||||||
vloc.X++
|
vloc.X++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the extra space
|
// Write the extra space
|
||||||
if vloc.X < w.gutterOffset {
|
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
vloc.X++
|
||||||
vloc.X++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getStyle returns the highlight style for the given character position
|
// getStyle returns the highlight style for the given character position
|
||||||
|
@ -380,11 +392,31 @@ func (w *BufWindow) displayBuffer() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
maxWidth := w.gutterOffset + w.bufWidth
|
hasMessage := len(b.Messages) > 0
|
||||||
|
bufHeight := w.Height
|
||||||
|
if w.drawStatus {
|
||||||
|
bufHeight--
|
||||||
|
}
|
||||||
|
|
||||||
|
bufWidth := w.Width
|
||||||
|
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
||||||
|
bufWidth--
|
||||||
|
}
|
||||||
|
|
||||||
if b.ModifiedThisFrame {
|
if b.ModifiedThisFrame {
|
||||||
if b.Settings["diffgutter"].(bool) {
|
if b.Settings["diffgutter"].(bool) {
|
||||||
b.UpdateDiff()
|
b.UpdateDiff(func(synchronous bool) {
|
||||||
|
// If the diff was updated asynchronously, the outer call to
|
||||||
|
// displayBuffer might already be completed and we need to
|
||||||
|
// schedule a redraw in order to display the new diff.
|
||||||
|
// Note that this cannot lead to an infinite recursion
|
||||||
|
// because the modifications were cleared above so there won't
|
||||||
|
// be another call to UpdateDiff when displayBuffer is called
|
||||||
|
// during the redraw.
|
||||||
|
if !synchronous {
|
||||||
|
screen.Redraw()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
b.ModifiedThisFrame = false
|
b.ModifiedThisFrame = false
|
||||||
}
|
}
|
||||||
|
@ -392,20 +424,26 @@ func (w *BufWindow) displayBuffer() {
|
||||||
var matchingBraces []buffer.Loc
|
var matchingBraces []buffer.Loc
|
||||||
// bracePairs is defined in buffer.go
|
// bracePairs is defined in buffer.go
|
||||||
if b.Settings["matchbrace"].(bool) {
|
if b.Settings["matchbrace"].(bool) {
|
||||||
for _, c := range b.GetCursors() {
|
for _, bp := range buffer.BracePairs {
|
||||||
if c.HasSelection() {
|
for _, c := range b.GetCursors() {
|
||||||
continue
|
if c.HasSelection() {
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
curX := c.X
|
||||||
|
curLoc := c.Loc
|
||||||
|
|
||||||
mb, left, found := b.FindMatchingBrace(c.Loc)
|
r := c.RuneUnder(curX)
|
||||||
if found {
|
rl := c.RuneUnder(curX - 1)
|
||||||
matchingBraces = append(matchingBraces, mb)
|
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
|
||||||
if !left {
|
mb, left, found := b.FindMatchingBrace(bp, curLoc)
|
||||||
if b.Settings["matchbracestyle"].(string) != "highlight" {
|
if found {
|
||||||
matchingBraces = append(matchingBraces, c.Loc)
|
matchingBraces = append(matchingBraces, mb)
|
||||||
|
if !left {
|
||||||
|
matchingBraces = append(matchingBraces, curLoc)
|
||||||
|
} else {
|
||||||
|
matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
matchingBraces = append(matchingBraces, c.Loc.Move(-1, b))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -424,27 +462,25 @@ func (w *BufWindow) displayBuffer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
softwrap := b.Settings["softwrap"].(bool)
|
// We need to know the string length of the largest line number
|
||||||
wordwrap := softwrap && b.Settings["wordwrap"].(bool)
|
// so we can pad appropriately when displaying line numbers
|
||||||
|
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
|
||||||
|
|
||||||
|
softwrap := b.Settings["softwrap"].(bool)
|
||||||
tabsize := util.IntOpt(b.Settings["tabsize"])
|
tabsize := util.IntOpt(b.Settings["tabsize"])
|
||||||
colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
|
colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
|
||||||
|
|
||||||
// this represents the current draw position
|
// this represents the current draw position
|
||||||
// within the current window
|
// within the current window
|
||||||
vloc := buffer.Loc{X: 0, Y: 0}
|
vloc := buffer.Loc{X: 0, Y: 0}
|
||||||
if softwrap {
|
|
||||||
// the start line may be partially out of the current window
|
|
||||||
vloc.Y = -w.StartLine.Row
|
|
||||||
}
|
|
||||||
|
|
||||||
// this represents the current draw position in the buffer (char positions)
|
// this represents the current draw position in the buffer (char positions)
|
||||||
bloc := buffer.Loc{X: -1, Y: w.StartLine.Line}
|
bloc := buffer.Loc{X: -1, Y: w.StartLine}
|
||||||
|
|
||||||
cursors := b.GetCursors()
|
cursors := b.GetCursors()
|
||||||
|
|
||||||
curStyle := config.DefStyle
|
curStyle := config.DefStyle
|
||||||
for ; vloc.Y < w.bufHeight; vloc.Y++ {
|
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
|
||||||
vloc.X = 0
|
vloc.X = 0
|
||||||
|
|
||||||
currentLine := false
|
currentLine := false
|
||||||
|
@ -460,27 +496,19 @@ func (w *BufWindow) displayBuffer() {
|
||||||
s = curNumStyle
|
s = curNumStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
if vloc.Y >= 0 {
|
if hasMessage {
|
||||||
if w.hasMessage {
|
w.drawGutter(&vloc, &bloc)
|
||||||
w.drawGutter(&vloc, &bloc)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Settings["diffgutter"].(bool) {
|
|
||||||
w.drawDiffGutter(s, false, &vloc, &bloc)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Settings["ruler"].(bool) {
|
|
||||||
w.drawLineNum(s, false, &vloc, &bloc)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vloc.X = w.gutterOffset
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bline := b.LineBytes(bloc.Y)
|
if b.Settings["diffgutter"].(bool) {
|
||||||
blineLen := util.CharacterCount(bline)
|
w.drawDiffGutter(s, false, &vloc, &bloc)
|
||||||
|
}
|
||||||
|
|
||||||
leadingwsEnd := len(util.GetLeadingWhitespace(bline))
|
if b.Settings["ruler"].(bool) {
|
||||||
trailingwsStart := blineLen - util.CharacterCount(util.GetTrailingWhitespace(bline))
|
w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.gutterOffset = vloc.X
|
||||||
|
|
||||||
line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
|
line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
|
||||||
if startStyle != nil {
|
if startStyle != nil {
|
||||||
|
@ -488,116 +516,68 @@ func (w *BufWindow) displayBuffer() {
|
||||||
}
|
}
|
||||||
bloc.X = bslice
|
bloc.X = bslice
|
||||||
|
|
||||||
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool) {
|
draw := func(r rune, combc []rune, style tcell.Style, showcursor bool) {
|
||||||
if nColsBeforeStart <= 0 && vloc.Y >= 0 {
|
if nColsBeforeStart <= 0 {
|
||||||
if highlight {
|
_, origBg, _ := style.Decompose()
|
||||||
if w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {
|
_, defBg, _ := config.DefStyle.Decompose()
|
||||||
|
|
||||||
|
// syntax highlighting with non-default background takes precedence
|
||||||
|
// over cursor-line and color-column
|
||||||
|
dontOverrideBackground := origBg != defBg
|
||||||
|
|
||||||
|
for _, c := range cursors {
|
||||||
|
if c.HasSelection() &&
|
||||||
|
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
|
||||||
|
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
|
||||||
|
// The current character is selected
|
||||||
style = config.DefStyle.Reverse(true)
|
style = config.DefStyle.Reverse(true)
|
||||||
if s, ok := config.Colorscheme["hlsearch"]; ok {
|
|
||||||
|
if s, ok := config.Colorscheme["selection"]; ok {
|
||||||
style = s
|
style = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, origBg, _ := style.Decompose()
|
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
|
||||||
_, defBg, _ := config.DefStyle.Decompose()
|
!c.HasSelection() && c.Y == bloc.Y {
|
||||||
|
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
||||||
// syntax or hlsearch highlighting with non-default background takes precedence
|
|
||||||
// over cursor-line and color-column
|
|
||||||
dontOverrideBackground := origBg != defBg
|
|
||||||
|
|
||||||
if b.Settings["hltaberrors"].(bool) {
|
|
||||||
if s, ok := config.Colorscheme["tab-error"]; ok {
|
|
||||||
isTab := (r == '\t') || (r == ' ' && !showcursor)
|
|
||||||
if (b.Settings["tabstospaces"].(bool) && isTab) ||
|
|
||||||
(!b.Settings["tabstospaces"].(bool) && bloc.X < leadingwsEnd && r == ' ' && !isTab) {
|
|
||||||
fg, _, _ := s.Decompose()
|
|
||||||
style = style.Background(fg)
|
|
||||||
dontOverrideBackground = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Settings["hltrailingws"].(bool) {
|
|
||||||
if s, ok := config.Colorscheme["trailingws"]; ok {
|
|
||||||
if bloc.X >= trailingwsStart && bloc.X < blineLen {
|
|
||||||
hl := true
|
|
||||||
for _, c := range cursors {
|
|
||||||
if c.NewTrailingWsY == bloc.Y {
|
|
||||||
hl = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hl {
|
|
||||||
fg, _, _ := s.Decompose()
|
|
||||||
style = style.Background(fg)
|
|
||||||
dontOverrideBackground = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cursors {
|
|
||||||
if c.HasSelection() &&
|
|
||||||
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
|
|
||||||
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
|
|
||||||
// The current character is selected
|
|
||||||
style = config.DefStyle.Reverse(true)
|
|
||||||
|
|
||||||
if s, ok := config.Colorscheme["selection"]; ok {
|
|
||||||
style = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
|
|
||||||
!c.HasSelection() && c.Y == bloc.Y {
|
|
||||||
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
|
||||||
fg, _, _ := s.Decompose()
|
|
||||||
style = style.Background(fg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range b.Messages {
|
|
||||||
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
|
|
||||||
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
|
|
||||||
style = style.Underline(true)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r == '\t' {
|
|
||||||
indentrunes := []rune(b.Settings["indentchar"].(string))
|
|
||||||
// if empty indentchar settings, use space
|
|
||||||
if len(indentrunes) == 0 {
|
|
||||||
indentrunes = []rune{' '}
|
|
||||||
}
|
|
||||||
|
|
||||||
r = indentrunes[0]
|
|
||||||
if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
|
|
||||||
fg, _, _ := s.Decompose()
|
|
||||||
style = style.Foreground(fg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
|
||||||
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
|
|
||||||
fg, _, _ := s.Decompose()
|
fg, _, _ := s.Decompose()
|
||||||
style = style.Background(fg)
|
style = style.Background(fg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, mb := range matchingBraces {
|
for _, m := range b.Messages {
|
||||||
if mb.X == bloc.X && mb.Y == bloc.Y {
|
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
|
||||||
if b.Settings["matchbracestyle"].(string) == "highlight" {
|
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
|
||||||
if s, ok := config.Colorscheme["match-brace"]; ok {
|
style = style.Underline(true)
|
||||||
style = s
|
break
|
||||||
} else {
|
}
|
||||||
style = style.Reverse(true)
|
}
|
||||||
}
|
|
||||||
} else {
|
if r == '\t' {
|
||||||
style = style.Underline(true)
|
indentrunes := []rune(b.Settings["indentchar"].(string))
|
||||||
}
|
// if empty indentchar settings, use space
|
||||||
}
|
if indentrunes == nil || len(indentrunes) == 0 {
|
||||||
|
indentrunes = []rune{' '}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = indentrunes[0]
|
||||||
|
if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
|
||||||
|
fg, _, _ := s.Decompose()
|
||||||
|
style = style.Foreground(fg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||||
|
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
|
||||||
|
fg, _, _ := s.Decompose()
|
||||||
|
style = style.Background(fg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mb := range matchingBraces {
|
||||||
|
if mb.X == bloc.X && mb.Y == bloc.Y {
|
||||||
|
style = style.Underline(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -610,122 +590,63 @@ func (w *BufWindow) displayBuffer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if nColsBeforeStart <= 0 {
|
|
||||||
vloc.X++
|
vloc.X++
|
||||||
}
|
}
|
||||||
nColsBeforeStart--
|
nColsBeforeStart--
|
||||||
}
|
}
|
||||||
|
|
||||||
wrap := func() {
|
|
||||||
vloc.X = 0
|
|
||||||
if w.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, &vloc, &bloc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type glyph struct {
|
|
||||||
r rune
|
|
||||||
combc []rune
|
|
||||||
style tcell.Style
|
|
||||||
width int
|
|
||||||
}
|
|
||||||
|
|
||||||
var word []glyph
|
|
||||||
if wordwrap {
|
|
||||||
word = make([]glyph, 0, w.bufWidth)
|
|
||||||
} else {
|
|
||||||
word = make([]glyph, 0, 1)
|
|
||||||
}
|
|
||||||
wordwidth := 0
|
|
||||||
|
|
||||||
totalwidth := w.StartCol - nColsBeforeStart
|
totalwidth := w.StartCol - nColsBeforeStart
|
||||||
for len(line) > 0 && vloc.X < maxWidth {
|
for len(line) > 0 {
|
||||||
r, combc, size := util.DecodeCharacter(line)
|
r, combc, size := util.DecodeCharacter(line)
|
||||||
line = line[size:]
|
|
||||||
|
|
||||||
loc := buffer.Loc{X: bloc.X + len(word), Y: bloc.Y}
|
curStyle, _ = w.getStyle(curStyle, bloc)
|
||||||
curStyle, _ = w.getStyle(curStyle, loc)
|
|
||||||
|
draw(r, combc, curStyle, true)
|
||||||
|
|
||||||
width := 0
|
width := 0
|
||||||
|
|
||||||
|
char := ' '
|
||||||
switch r {
|
switch r {
|
||||||
case '\t':
|
case '\t':
|
||||||
ts := tabsize - (totalwidth % tabsize)
|
ts := tabsize - (totalwidth % tabsize)
|
||||||
width = util.Min(ts, maxWidth-vloc.X)
|
width = ts
|
||||||
totalwidth += ts
|
|
||||||
default:
|
default:
|
||||||
width = runewidth.RuneWidth(r)
|
width = runewidth.RuneWidth(r)
|
||||||
totalwidth += width
|
char = '@'
|
||||||
}
|
}
|
||||||
|
|
||||||
word = append(word, glyph{r, combc, curStyle, width})
|
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||||
wordwidth += width
|
if width > 1 {
|
||||||
|
for i := 1; i < width; i++ {
|
||||||
// Collect a complete word to know its width.
|
draw(char, nil, curStyle, false)
|
||||||
// If wordwrap is off, every single character is a complete "word".
|
|
||||||
if wordwrap {
|
|
||||||
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bloc.X++
|
||||||
|
line = line[size:]
|
||||||
|
|
||||||
// If a word (or just a wide rune) does not fit in the window
|
totalwidth += width
|
||||||
if vloc.X+wordwidth > maxWidth && vloc.X > w.gutterOffset {
|
|
||||||
for vloc.X < maxWidth {
|
|
||||||
draw(' ', nil, config.DefStyle, false, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We either stop or we wrap to draw the word in the next line
|
|
||||||
if !softwrap {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
vloc.Y++
|
|
||||||
if vloc.Y >= w.bufHeight {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
wrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range word {
|
|
||||||
draw(r.r, r.combc, r.style, true, true)
|
|
||||||
|
|
||||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
|
||||||
if r.width > 1 {
|
|
||||||
char := ' '
|
|
||||||
if r.r != '\t' {
|
|
||||||
char = '@'
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i < r.width; i++ {
|
|
||||||
draw(char, nil, r.style, true, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bloc.X++
|
|
||||||
}
|
|
||||||
|
|
||||||
word = word[:0]
|
|
||||||
wordwidth = 0
|
|
||||||
|
|
||||||
// If we reach the end of the window then we either stop or we wrap for softwrap
|
// If we reach the end of the window then we either stop or we wrap for softwrap
|
||||||
if vloc.X >= maxWidth {
|
if vloc.X >= bufWidth {
|
||||||
if !softwrap {
|
if !softwrap {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
vloc.Y++
|
vloc.Y++
|
||||||
if vloc.Y >= w.bufHeight {
|
if vloc.Y >= bufHeight {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
wrap()
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -740,7 +661,7 @@ func (w *BufWindow) displayBuffer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := vloc.X; i < maxWidth; i++ {
|
for i := vloc.X; i < bufWidth; i++ {
|
||||||
curStyle := style
|
curStyle := style
|
||||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||||
if colorcolumn != 0 && i-w.gutterOffset+w.StartCol == colorcolumn {
|
if colorcolumn != 0 && i-w.gutterOffset+w.StartCol == colorcolumn {
|
||||||
|
@ -751,9 +672,9 @@ func (w *BufWindow) displayBuffer() {
|
||||||
screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
|
screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
if vloc.X != maxWidth {
|
if vloc.X != bufWidth {
|
||||||
// Display newline within a selection
|
// Display newline within a selection
|
||||||
draw(' ', nil, config.DefStyle, true, true)
|
draw(' ', nil, config.DefStyle, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
bloc.X = w.StartCol
|
bloc.X = w.StartCol
|
||||||
|
@ -765,9 +686,18 @@ func (w *BufWindow) displayBuffer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *BufWindow) displayStatusLine() {
|
func (w *BufWindow) displayStatusLine() {
|
||||||
|
_, h := screen.Screen.Size()
|
||||||
|
infoY := h
|
||||||
|
if config.GetGlobalOption("infobar").(bool) {
|
||||||
|
infoY--
|
||||||
|
}
|
||||||
|
|
||||||
if w.Buf.Settings["statusline"].(bool) {
|
if w.Buf.Settings["statusline"].(bool) {
|
||||||
|
w.drawStatus = true
|
||||||
w.sline.Display()
|
w.sline.Display()
|
||||||
} else if w.drawDivider {
|
} else if w.Y+w.Height != infoY {
|
||||||
|
w.drawStatus = true
|
||||||
|
|
||||||
divchars := config.GetGlobalOption("divchars").(string)
|
divchars := config.GetGlobalOption("divchars").(string)
|
||||||
if util.CharacterCountInString(divchars) != 2 {
|
if util.CharacterCountInString(divchars) != 2 {
|
||||||
divchars = "|-"
|
divchars = "|-"
|
||||||
|
@ -789,39 +719,31 @@ func (w *BufWindow) displayStatusLine() {
|
||||||
for x := w.X; x < w.X+w.Width; x++ {
|
for x := w.X; x < w.X+w.Width; x++ {
|
||||||
screen.SetContent(x, w.Y+w.Height-1, divchar, combc, dividerStyle)
|
screen.SetContent(x, w.Y+w.Height-1, divchar, combc, dividerStyle)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
w.drawStatus = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *BufWindow) displayScrollBar() {
|
func (w *BufWindow) displayScrollBar() {
|
||||||
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
||||||
scrollX := w.X + w.Width - 1
|
scrollX := w.X + w.Width - 1
|
||||||
|
bufHeight := w.Height
|
||||||
|
if w.drawStatus {
|
||||||
|
bufHeight--
|
||||||
|
}
|
||||||
barsize := int(float64(w.Height) / float64(w.Buf.LinesNum()) * float64(w.Height))
|
barsize := int(float64(w.Height) / float64(w.Buf.LinesNum()) * float64(w.Height))
|
||||||
if barsize < 1 {
|
if barsize < 1 {
|
||||||
barsize = 1
|
barsize = 1
|
||||||
}
|
}
|
||||||
barstart := w.Y + int(float64(w.StartLine.Line)/float64(w.Buf.LinesNum())*float64(w.Height))
|
barstart := w.Y + int(float64(w.StartLine)/float64(w.Buf.LinesNum())*float64(w.Height))
|
||||||
|
for y := barstart; y < util.Min(barstart+barsize, w.Y+bufHeight); y++ {
|
||||||
scrollBarStyle := config.DefStyle.Reverse(true)
|
screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
|
||||||
if style, ok := config.Colorscheme["scrollbar"]; ok {
|
|
||||||
scrollBarStyle = style
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollBarChar := config.GetGlobalOption("scrollbarchar").(string)
|
|
||||||
if util.CharacterCountInString(scrollBarChar) != 1 {
|
|
||||||
scrollBarChar = "|"
|
|
||||||
}
|
|
||||||
scrollBarRune := []rune(scrollBarChar)
|
|
||||||
|
|
||||||
for y := barstart; y < util.Min(barstart+barsize, w.Y+w.bufHeight); y++ {
|
|
||||||
screen.SetContent(scrollX, y, scrollBarRune[0], nil, scrollBarStyle)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display displays the buffer and the statusline
|
// Display displays the buffer and the statusline
|
||||||
func (w *BufWindow) Display() {
|
func (w *BufWindow) Display() {
|
||||||
w.updateDisplayInfo()
|
|
||||||
|
|
||||||
w.displayStatusLine()
|
w.displayStatusLine()
|
||||||
w.displayScrollBar()
|
w.displayScrollBar()
|
||||||
w.displayBuffer()
|
w.displayBuffer()
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/zyedidia/micro/v2/internal/info"
|
"github.com/zyedidia/micro/v2/internal/info"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InfoWindow struct {
|
type InfoWindow struct {
|
||||||
|
@ -72,23 +72,6 @@ func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
|
||||||
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
|
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *InfoWindow) BufView() View {
|
|
||||||
return View{
|
|
||||||
X: 0,
|
|
||||||
Y: i.Y,
|
|
||||||
Width: i.Width,
|
|
||||||
Height: 1,
|
|
||||||
StartLine: SLoc{0, 0},
|
|
||||||
StartCol: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InfoWindow) Scroll(s SLoc, n int) SLoc { return s }
|
|
||||||
func (i *InfoWindow) Diff(s1, s2 SLoc) int { return 0 }
|
|
||||||
func (i *InfoWindow) SLocFromLoc(loc buffer.Loc) SLoc { return SLoc{0, 0} }
|
|
||||||
func (i *InfoWindow) VLocFromLoc(loc buffer.Loc) VLoc { return VLoc{SLoc{0, 0}, loc.X} }
|
|
||||||
func (i *InfoWindow) LocFromVLoc(vloc VLoc) buffer.Loc { return buffer.Loc{vloc.VisualX, 0} }
|
|
||||||
|
|
||||||
func (i *InfoWindow) Clear() {
|
func (i *InfoWindow) Clear() {
|
||||||
for x := 0; x < i.Width; x++ {
|
for x := 0; x < i.Width; x++ {
|
||||||
screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
|
screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
|
||||||
|
@ -260,9 +243,7 @@ func (i *InfoWindow) Display() {
|
||||||
done := false
|
done := false
|
||||||
|
|
||||||
statusLineStyle := config.DefStyle.Reverse(true)
|
statusLineStyle := config.DefStyle.Reverse(true)
|
||||||
if style, ok := config.Colorscheme["statusline.suggestions"]; ok {
|
if style, ok := config.Colorscheme["statusline"]; ok {
|
||||||
statusLineStyle = style
|
|
||||||
} else if style, ok := config.Colorscheme["statusline"]; ok {
|
|
||||||
statusLineStyle = style
|
statusLineStyle = style
|
||||||
}
|
}
|
||||||
keymenuOffset := 0
|
keymenuOffset := 0
|
||||||
|
|
|
@ -1,348 +0,0 @@
|
||||||
package display
|
|
||||||
|
|
||||||
import (
|
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SLoc represents a vertical scrolling location, i.e. a location of a visual line
|
|
||||||
// in the buffer. When softwrap is enabled, a buffer line may be displayed as
|
|
||||||
// multiple visual lines (rows). So SLoc stores a number of a line in the buffer
|
|
||||||
// and a number of a row within this line.
|
|
||||||
type SLoc struct {
|
|
||||||
Line, Row int
|
|
||||||
}
|
|
||||||
|
|
||||||
// LessThan returns true if s is less b
|
|
||||||
func (s SLoc) LessThan(b SLoc) bool {
|
|
||||||
if s.Line < b.Line {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return s.Line == b.Line && s.Row < b.Row
|
|
||||||
}
|
|
||||||
|
|
||||||
// GreaterThan returns true if s is bigger than b
|
|
||||||
func (s SLoc) GreaterThan(b SLoc) bool {
|
|
||||||
if s.Line > b.Line {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return s.Line == b.Line && s.Row > b.Row
|
|
||||||
}
|
|
||||||
|
|
||||||
// LessEqual returns true if s is less than or equal to b
|
|
||||||
func (s SLoc) LessEqual(b SLoc) bool {
|
|
||||||
if s.Line < b.Line {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if s.Line == b.Line && s.Row < b.Row {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return s == b
|
|
||||||
}
|
|
||||||
|
|
||||||
// GreaterEqual returns true if s is bigger than or equal to b
|
|
||||||
func (s SLoc) GreaterEqual(b SLoc) bool {
|
|
||||||
if s.Line > b.Line {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if s.Line == b.Line && s.Row > b.Row {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return s == b
|
|
||||||
}
|
|
||||||
|
|
||||||
// VLoc represents a location in the buffer as a visual location in the
|
|
||||||
// linewrapped buffer.
|
|
||||||
type VLoc struct {
|
|
||||||
SLoc
|
|
||||||
VisualX int
|
|
||||||
}
|
|
||||||
|
|
||||||
type SoftWrap interface {
|
|
||||||
Scroll(s SLoc, n int) SLoc
|
|
||||||
Diff(s1, s2 SLoc) int
|
|
||||||
SLocFromLoc(loc buffer.Loc) SLoc
|
|
||||||
VLocFromLoc(loc buffer.Loc) VLoc
|
|
||||||
LocFromVLoc(vloc VLoc) buffer.Loc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufWindow) getVLocFromLoc(loc buffer.Loc) VLoc {
|
|
||||||
vloc := VLoc{SLoc: SLoc{loc.Y, 0}, VisualX: 0}
|
|
||||||
|
|
||||||
if loc.X <= 0 {
|
|
||||||
return vloc
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.bufWidth <= 0 {
|
|
||||||
return vloc
|
|
||||||
}
|
|
||||||
|
|
||||||
wordwrap := w.Buf.Settings["wordwrap"].(bool)
|
|
||||||
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
|
||||||
|
|
||||||
line := w.Buf.LineBytes(loc.Y)
|
|
||||||
x := 0
|
|
||||||
totalwidth := 0
|
|
||||||
|
|
||||||
wordwidth := 0
|
|
||||||
wordoffset := 0
|
|
||||||
|
|
||||||
for len(line) > 0 {
|
|
||||||
r, _, size := util.DecodeCharacter(line)
|
|
||||||
line = line[size:]
|
|
||||||
|
|
||||||
width := 0
|
|
||||||
switch r {
|
|
||||||
case '\t':
|
|
||||||
ts := tabsize - (totalwidth % tabsize)
|
|
||||||
width = util.Min(ts, w.bufWidth-vloc.VisualX)
|
|
||||||
totalwidth += ts
|
|
||||||
default:
|
|
||||||
width = runewidth.RuneWidth(r)
|
|
||||||
totalwidth += width
|
|
||||||
}
|
|
||||||
|
|
||||||
wordwidth += width
|
|
||||||
|
|
||||||
// Collect a complete word to know its width.
|
|
||||||
// If wordwrap is off, every single character is a complete "word".
|
|
||||||
if wordwrap {
|
|
||||||
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
|
|
||||||
if x < loc.X {
|
|
||||||
wordoffset += width
|
|
||||||
x++
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a word (or just a wide rune) does not fit in the window
|
|
||||||
if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
|
|
||||||
vloc.Row++
|
|
||||||
vloc.VisualX = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if x == loc.X {
|
|
||||||
vloc.VisualX += wordoffset
|
|
||||||
return vloc
|
|
||||||
}
|
|
||||||
x++
|
|
||||||
|
|
||||||
vloc.VisualX += wordwidth
|
|
||||||
|
|
||||||
wordwidth = 0
|
|
||||||
wordoffset = 0
|
|
||||||
|
|
||||||
if vloc.VisualX >= w.bufWidth {
|
|
||||||
vloc.Row++
|
|
||||||
vloc.VisualX = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vloc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
|
|
||||||
loc := buffer.Loc{X: 0, Y: svloc.Line}
|
|
||||||
|
|
||||||
if w.bufWidth <= 0 {
|
|
||||||
return loc
|
|
||||||
}
|
|
||||||
|
|
||||||
wordwrap := w.Buf.Settings["wordwrap"].(bool)
|
|
||||||
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
|
||||||
|
|
||||||
line := w.Buf.LineBytes(svloc.Line)
|
|
||||||
vloc := VLoc{SLoc: SLoc{svloc.Line, 0}, VisualX: 0}
|
|
||||||
|
|
||||||
totalwidth := 0
|
|
||||||
|
|
||||||
var widths []int
|
|
||||||
if wordwrap {
|
|
||||||
widths = make([]int, 0, w.bufWidth)
|
|
||||||
} else {
|
|
||||||
widths = make([]int, 0, 1)
|
|
||||||
}
|
|
||||||
wordwidth := 0
|
|
||||||
|
|
||||||
for len(line) > 0 {
|
|
||||||
r, _, size := util.DecodeCharacter(line)
|
|
||||||
line = line[size:]
|
|
||||||
|
|
||||||
width := 0
|
|
||||||
switch r {
|
|
||||||
case '\t':
|
|
||||||
ts := tabsize - (totalwidth % tabsize)
|
|
||||||
width = util.Min(ts, w.bufWidth-vloc.VisualX)
|
|
||||||
totalwidth += ts
|
|
||||||
default:
|
|
||||||
width = runewidth.RuneWidth(r)
|
|
||||||
totalwidth += width
|
|
||||||
}
|
|
||||||
|
|
||||||
widths = append(widths, width)
|
|
||||||
wordwidth += width
|
|
||||||
|
|
||||||
// Collect a complete word to know its width.
|
|
||||||
// If wordwrap is off, every single character is a complete "word".
|
|
||||||
if wordwrap {
|
|
||||||
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a word (or just a wide rune) does not fit in the window
|
|
||||||
if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
|
|
||||||
if vloc.Row == svloc.Row {
|
|
||||||
if wordwrap {
|
|
||||||
// it's a word, not a wide rune
|
|
||||||
loc.X--
|
|
||||||
}
|
|
||||||
return loc
|
|
||||||
}
|
|
||||||
vloc.Row++
|
|
||||||
vloc.VisualX = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range widths {
|
|
||||||
vloc.VisualX += widths[i]
|
|
||||||
if vloc.Row == svloc.Row && vloc.VisualX > svloc.VisualX {
|
|
||||||
return loc
|
|
||||||
}
|
|
||||||
loc.X++
|
|
||||||
}
|
|
||||||
|
|
||||||
widths = widths[:0]
|
|
||||||
wordwidth = 0
|
|
||||||
|
|
||||||
if vloc.VisualX >= w.bufWidth {
|
|
||||||
vloc.Row++
|
|
||||||
vloc.VisualX = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return loc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufWindow) getRowCount(line int) int {
|
|
||||||
eol := buffer.Loc{X: util.CharacterCount(w.Buf.LineBytes(line)), Y: line}
|
|
||||||
return w.getVLocFromLoc(eol).Row + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufWindow) scrollUp(s SLoc, n int) SLoc {
|
|
||||||
for n > 0 {
|
|
||||||
if n <= s.Row {
|
|
||||||
s.Row -= n
|
|
||||||
n = 0
|
|
||||||
} else if s.Line > 0 {
|
|
||||||
s.Line--
|
|
||||||
n -= s.Row + 1
|
|
||||||
s.Row = w.getRowCount(s.Line) - 1
|
|
||||||
} else {
|
|
||||||
s.Row = 0
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufWindow) scrollDown(s SLoc, n int) SLoc {
|
|
||||||
for n > 0 {
|
|
||||||
rc := w.getRowCount(s.Line)
|
|
||||||
if n < rc-s.Row {
|
|
||||||
s.Row += n
|
|
||||||
n = 0
|
|
||||||
} else if s.Line < w.Buf.LinesNum()-1 {
|
|
||||||
s.Line++
|
|
||||||
n -= rc - s.Row
|
|
||||||
s.Row = 0
|
|
||||||
} else {
|
|
||||||
s.Row = rc - 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufWindow) scroll(s SLoc, n int) SLoc {
|
|
||||||
if n < 0 {
|
|
||||||
return w.scrollUp(s, -n)
|
|
||||||
}
|
|
||||||
return w.scrollDown(s, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufWindow) diff(s1, s2 SLoc) int {
|
|
||||||
n := 0
|
|
||||||
for s1.LessThan(s2) {
|
|
||||||
if s1.Line < s2.Line {
|
|
||||||
n += w.getRowCount(s1.Line) - s1.Row
|
|
||||||
s1.Line++
|
|
||||||
s1.Row = 0
|
|
||||||
} else {
|
|
||||||
n += s2.Row - s1.Row
|
|
||||||
s1.Row = s2.Row
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scroll returns the location which is n visual lines below the location s
|
|
||||||
// i.e. the result of scrolling n lines down. n can be negative,
|
|
||||||
// which means scrolling up. The returned location is guaranteed to be
|
|
||||||
// within the buffer boundaries.
|
|
||||||
func (w *BufWindow) Scroll(s SLoc, n int) SLoc {
|
|
||||||
if !w.Buf.Settings["softwrap"].(bool) {
|
|
||||||
s.Line += n
|
|
||||||
if s.Line < 0 {
|
|
||||||
s.Line = 0
|
|
||||||
}
|
|
||||||
if s.Line > w.Buf.LinesNum()-1 {
|
|
||||||
s.Line = w.Buf.LinesNum() - 1
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return w.scroll(s, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diff returns the difference (the vertical distance) between two SLocs.
|
|
||||||
func (w *BufWindow) Diff(s1, s2 SLoc) int {
|
|
||||||
if !w.Buf.Settings["softwrap"].(bool) {
|
|
||||||
return s2.Line - s1.Line
|
|
||||||
}
|
|
||||||
if s1.GreaterThan(s2) {
|
|
||||||
return -w.diff(s2, s1)
|
|
||||||
}
|
|
||||||
return w.diff(s1, s2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SLocFromLoc takes a position in the buffer and returns the location
|
|
||||||
// of the visual line containing this position.
|
|
||||||
func (w *BufWindow) SLocFromLoc(loc buffer.Loc) SLoc {
|
|
||||||
if !w.Buf.Settings["softwrap"].(bool) {
|
|
||||||
return SLoc{loc.Y, 0}
|
|
||||||
}
|
|
||||||
return w.getVLocFromLoc(loc).SLoc
|
|
||||||
}
|
|
||||||
|
|
||||||
// VLocFromLoc takes a position in the buffer and returns the corresponding
|
|
||||||
// visual location in the linewrapped buffer.
|
|
||||||
func (w *BufWindow) VLocFromLoc(loc buffer.Loc) VLoc {
|
|
||||||
if !w.Buf.Settings["softwrap"].(bool) {
|
|
||||||
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
|
||||||
|
|
||||||
visualx := util.StringWidth(w.Buf.LineBytes(loc.Y), loc.X, tabsize)
|
|
||||||
return VLoc{SLoc{loc.Y, 0}, visualx}
|
|
||||||
}
|
|
||||||
return w.getVLocFromLoc(loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocFromVLoc takes a visual location in the linewrapped buffer and returns
|
|
||||||
// the position in the buffer corresponding to this visual location.
|
|
||||||
func (w *BufWindow) LocFromVLoc(vloc VLoc) buffer.Loc {
|
|
||||||
if !w.Buf.Settings["softwrap"].(bool) {
|
|
||||||
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
|
||||||
|
|
||||||
x := util.GetCharPosInLine(w.Buf.LineBytes(vloc.Line), vloc.VisualX, tabsize)
|
|
||||||
return buffer.Loc{x, vloc.Line}
|
|
||||||
}
|
|
||||||
return w.getLocFromVLoc(vloc)
|
|
||||||
}
|
|
|
@ -47,12 +47,6 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
},
|
},
|
||||||
"lines": func(b *buffer.Buffer) string {
|
|
||||||
return strconv.Itoa(b.LinesNum())
|
|
||||||
},
|
|
||||||
"percentage": func(b *buffer.Buffer) string {
|
|
||||||
return strconv.Itoa((b.GetActiveCursor().Y + 1) * 100 / b.LinesNum())
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetStatusInfoFnLua(fn string) {
|
func SetStatusInfoFnLua(fn string) {
|
||||||
|
@ -66,7 +60,7 @@ func SetStatusInfoFnLua(fn string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
statusInfo[fn] = func(b *buffer.Buffer) string {
|
statusInfo[fn] = func(b *buffer.Buffer) string {
|
||||||
if pl == nil || !pl.IsLoaded() {
|
if pl == nil || !pl.IsEnabled() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
val, err := pl.Call(plFn, luar.New(ulua.L, b))
|
val, err := pl.Call(plFn, luar.New(ulua.L, b))
|
||||||
|
@ -104,17 +98,17 @@ func (s *StatusLine) Display() {
|
||||||
// We'll draw the line at the lowest line in the window
|
// We'll draw the line at the lowest line in the window
|
||||||
y := s.win.Height + s.win.Y - 1
|
y := s.win.Height + s.win.Y - 1
|
||||||
|
|
||||||
winX := s.win.X
|
|
||||||
|
|
||||||
b := s.win.Buf
|
b := s.win.Buf
|
||||||
// autocomplete suggestions (for the buffer, not for the infowindow)
|
// autocomplete suggestions (for the buffer, not for the infowindow)
|
||||||
if b.HasSuggestions && len(b.Suggestions) > 1 {
|
if b.HasSuggestions && len(b.Suggestions) > 1 {
|
||||||
statusLineStyle := config.DefStyle.Reverse(true)
|
statusLineStyle := config.DefStyle.Reverse(true)
|
||||||
if style, ok := config.Colorscheme["statusline.suggestions"]; ok {
|
if style, ok := config.Colorscheme["statusline"]; ok {
|
||||||
statusLineStyle = style
|
|
||||||
} else if style, ok := config.Colorscheme["statusline"]; ok {
|
|
||||||
statusLineStyle = style
|
statusLineStyle = style
|
||||||
}
|
}
|
||||||
|
keymenuOffset := 0
|
||||||
|
if config.GetGlobalOption("keymenu").(bool) {
|
||||||
|
keymenuOffset = len(keydisplay)
|
||||||
|
}
|
||||||
x := 0
|
x := 0
|
||||||
for j, sug := range b.Suggestions {
|
for j, sug := range b.Suggestions {
|
||||||
style := statusLineStyle
|
style := statusLineStyle
|
||||||
|
@ -122,13 +116,13 @@ func (s *StatusLine) Display() {
|
||||||
style = style.Reverse(true)
|
style = style.Reverse(true)
|
||||||
}
|
}
|
||||||
for _, r := range sug {
|
for _, r := range sug {
|
||||||
screen.SetContent(winX+x, y, r, nil, style)
|
screen.SetContent(x, y-keymenuOffset, r, nil, style)
|
||||||
x++
|
x++
|
||||||
if x >= s.win.Width {
|
if x >= s.win.Width {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
|
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||||
x++
|
x++
|
||||||
if x >= s.win.Width {
|
if x >= s.win.Width {
|
||||||
return
|
return
|
||||||
|
@ -136,7 +130,7 @@ func (s *StatusLine) Display() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for x < s.win.Width {
|
for x < s.win.Width {
|
||||||
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
|
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||||
x++
|
x++
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -149,7 +143,7 @@ func (s *StatusLine) Display() {
|
||||||
return []byte(fmt.Sprint(s.FindOpt(string(option))))
|
return []byte(fmt.Sprint(s.FindOpt(string(option))))
|
||||||
} else if bytes.HasPrefix(name, []byte("bind")) {
|
} else if bytes.HasPrefix(name, []byte("bind")) {
|
||||||
binding := string(name[5:])
|
binding := string(name[5:])
|
||||||
for k, v := range config.Bindings["buffer"] {
|
for k, v := range config.Bindings {
|
||||||
if v == binding {
|
if v == binding {
|
||||||
return []byte(k)
|
return []byte(k)
|
||||||
}
|
}
|
||||||
|
@ -169,21 +163,14 @@ func (s *StatusLine) Display() {
|
||||||
rightText = formatParser.ReplaceAllFunc(rightText, formatter)
|
rightText = formatParser.ReplaceAllFunc(rightText, formatter)
|
||||||
|
|
||||||
statusLineStyle := config.DefStyle.Reverse(true)
|
statusLineStyle := config.DefStyle.Reverse(true)
|
||||||
if s.win.IsActive() {
|
if style, ok := config.Colorscheme["statusline"]; ok {
|
||||||
if style, ok := config.Colorscheme["statusline"]; ok {
|
statusLineStyle = style
|
||||||
statusLineStyle = style
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if style, ok := config.Colorscheme["statusline.inactive"]; ok {
|
|
||||||
statusLineStyle = style
|
|
||||||
} else if style, ok := config.Colorscheme["statusline"]; ok {
|
|
||||||
statusLineStyle = style
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)
|
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)
|
||||||
rightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)
|
rightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)
|
||||||
|
|
||||||
|
winX := s.win.X
|
||||||
for x := 0; x < s.win.Width; x++ {
|
for x := 0; x < s.win.Width; x++ {
|
||||||
if x < leftLen {
|
if x < leftLen {
|
||||||
r, combc, size := util.DecodeCharacter(leftText)
|
r, combc, size := util.DecodeCharacter(leftText)
|
||||||
|
|
|
@ -2,7 +2,6 @@ package display
|
||||||
|
|
||||||
import (
|
import (
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
|
@ -95,31 +94,12 @@ func (w *TabWindow) Display() {
|
||||||
x := -w.hscroll
|
x := -w.hscroll
|
||||||
done := false
|
done := false
|
||||||
|
|
||||||
globalTabReverse := config.GetGlobalOption("tabreverse").(bool)
|
tabBarStyle := config.DefStyle.Reverse(true)
|
||||||
globalTabHighlight := config.GetGlobalOption("tabhighlight").(bool)
|
if style, ok := config.Colorscheme["tabbar"]; ok {
|
||||||
|
tabBarStyle = style
|
||||||
// xor of reverse and tab highlight to get tab character (as in filename and surrounding characters) reverse state
|
|
||||||
tabCharHighlight := (globalTabReverse || globalTabHighlight) && !(globalTabReverse && globalTabHighlight)
|
|
||||||
|
|
||||||
reverseStyles := func(reverse bool) (tcell.Style, tcell.Style) {
|
|
||||||
tabBarStyle := config.DefStyle.Reverse(reverse)
|
|
||||||
if style, ok := config.Colorscheme["tabbar"]; ok {
|
|
||||||
tabBarStyle = style
|
|
||||||
}
|
|
||||||
tabBarActiveStyle := tabBarStyle
|
|
||||||
if style, ok := config.Colorscheme["tabbar.active"]; ok {
|
|
||||||
tabBarActiveStyle = style
|
|
||||||
}
|
|
||||||
return tabBarStyle, tabBarActiveStyle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
draw := func(r rune, n int, active bool, reversed bool) {
|
draw := func(r rune, n int) {
|
||||||
tabBarStyle, tabBarActiveStyle := reverseStyles(reversed)
|
|
||||||
|
|
||||||
style := tabBarStyle
|
|
||||||
if active {
|
|
||||||
style = tabBarActiveStyle
|
|
||||||
}
|
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
rw := runewidth.RuneWidth(r)
|
rw := runewidth.RuneWidth(r)
|
||||||
for j := 0; j < rw; j++ {
|
for j := 0; j < rw; j++ {
|
||||||
|
@ -134,7 +114,7 @@ func (w *TabWindow) Display() {
|
||||||
} else if x == 0 && w.hscroll > 0 {
|
} else if x == 0 && w.hscroll > 0 {
|
||||||
screen.SetContent(0, w.Y, '<', nil, tabBarStyle)
|
screen.SetContent(0, w.Y, '<', nil, tabBarStyle)
|
||||||
} else if x >= 0 && x < w.Width {
|
} else if x >= 0 && x < w.Width {
|
||||||
screen.SetContent(x, w.Y, c, nil, style)
|
screen.SetContent(x, w.Y, c, nil, tabBarStyle)
|
||||||
}
|
}
|
||||||
x++
|
x++
|
||||||
}
|
}
|
||||||
|
@ -143,33 +123,28 @@ func (w *TabWindow) Display() {
|
||||||
|
|
||||||
for i, n := range w.Names {
|
for i, n := range w.Names {
|
||||||
if i == w.active {
|
if i == w.active {
|
||||||
draw('[', 1, true, tabCharHighlight)
|
draw('[', 1)
|
||||||
} else {
|
} else {
|
||||||
draw(' ', 1, false, tabCharHighlight)
|
draw(' ', 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range n {
|
for _, c := range n {
|
||||||
draw(c, 1, i == w.active, tabCharHighlight)
|
draw(c, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if i == len(w.Names)-1 {
|
if i == len(w.Names)-1 {
|
||||||
done = true
|
done = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if i == w.active {
|
if i == w.active {
|
||||||
draw(']', 1, true, tabCharHighlight)
|
draw(']', 1)
|
||||||
draw(' ', 2, true, globalTabReverse)
|
draw(' ', 2)
|
||||||
} else {
|
} else {
|
||||||
draw(' ', 1, false, tabCharHighlight)
|
draw(' ', 3)
|
||||||
draw(' ', 2, false, globalTabReverse)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if x >= w.Width {
|
if x >= w.Width {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if x < w.Width {
|
if x < w.Width {
|
||||||
draw(' ', w.Width-x, false, globalTabReverse)
|
draw(' ', w.Width-x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/shell"
|
"github.com/zyedidia/micro/v2/internal/shell"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
"github.com/zyedidia/terminal"
|
"github.com/zyedidia/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ func (w *TermWindow) Display() {
|
||||||
if b == terminal.DefaultBG {
|
if b == terminal.DefaultBG {
|
||||||
bg = int(tcell.ColorDefault)
|
bg = int(tcell.ColorDefault)
|
||||||
}
|
}
|
||||||
st := tcell.StyleDefault.Foreground(config.GetColor256(fg)).Background(config.GetColor256(bg))
|
st := tcell.StyleDefault.Foreground(config.GetColor256(int(fg))).Background(config.GetColor256(int(bg)))
|
||||||
|
|
||||||
if l.LessThan(w.Selection[1]) && l.GreaterEqual(w.Selection[0]) || l.LessThan(w.Selection[0]) && l.GreaterEqual(w.Selection[1]) {
|
if l.LessThan(w.Selection[1]) && l.GreaterEqual(w.Selection[0]) || l.LessThan(w.Selection[0]) && l.GreaterEqual(w.Selection[1]) {
|
||||||
st = st.Reverse(true)
|
st = st.Reverse(true)
|
||||||
|
@ -110,8 +110,6 @@ func (w *TermWindow) Display() {
|
||||||
}
|
}
|
||||||
if w.State.CursorVisible() && w.active {
|
if w.State.CursorVisible() && w.active {
|
||||||
curx, cury := w.State.Cursor()
|
curx, cury := w.State.Cursor()
|
||||||
if curx < w.Width && cury < w.Height {
|
screen.ShowCursor(curx+w.X, cury+w.Y)
|
||||||
screen.ShowCursor(curx+w.X, cury+w.Y)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,14 +38,15 @@ func (w *UIWindow) drawNode(n *views.Node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, c := range cs {
|
for i, c := range cs {
|
||||||
if c.Kind == views.STVert {
|
if c.IsLeaf() && c.Kind == views.STVert {
|
||||||
if i != len(cs)-1 {
|
if i != len(cs)-1 {
|
||||||
for h := 0; h < c.H; h++ {
|
for h := 0; h < c.H; h++ {
|
||||||
screen.SetContent(c.X+c.W, c.Y+h, divchar, combc, dividerStyle)
|
screen.SetContent(c.X+c.W, c.Y+h, divchar, combc, dividerStyle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
w.drawNode(c)
|
||||||
}
|
}
|
||||||
w.drawNode(c)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,32 +54,32 @@ func (w *UIWindow) Display() {
|
||||||
w.drawNode(w.root)
|
w.drawNode(w.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *UIWindow) GetMouseSplitNode(vloc buffer.Loc) *views.Node {
|
func (w *UIWindow) GetMouseSplitID(vloc buffer.Loc) uint64 {
|
||||||
var mouseLoc func(*views.Node) *views.Node
|
var mouseLoc func(*views.Node) uint64
|
||||||
mouseLoc = func(n *views.Node) *views.Node {
|
mouseLoc = func(n *views.Node) uint64 {
|
||||||
cs := n.Children()
|
cs := n.Children()
|
||||||
for i, c := range cs {
|
for i, c := range cs {
|
||||||
if c.Kind == views.STVert {
|
if c.Kind == views.STVert {
|
||||||
if i != len(cs)-1 {
|
if i != len(cs)-1 {
|
||||||
if vloc.X == c.X+c.W && vloc.Y >= c.Y && vloc.Y < c.Y+c.H {
|
if vloc.X == c.X+c.W && vloc.Y >= c.Y && vloc.Y < c.Y+c.H {
|
||||||
return c
|
return c.ID()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if c.Kind == views.STHoriz {
|
} else if c.Kind == views.STHoriz {
|
||||||
if i != len(cs)-1 {
|
if i != len(cs)-1 {
|
||||||
if vloc.Y == c.Y+c.H-1 && vloc.X >= c.X && vloc.X < c.X+c.W {
|
if vloc.Y == c.Y+c.H-1 && vloc.X >= c.X && vloc.X < c.X+c.W {
|
||||||
return c
|
return c.ID()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, c := range cs {
|
for _, c := range cs {
|
||||||
m := mouseLoc(c)
|
m := mouseLoc(c)
|
||||||
if m != nil {
|
if m != 0 {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return 0
|
||||||
}
|
}
|
||||||
return mouseLoc(w.root)
|
return mouseLoc(w.root)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,10 @@ type View struct {
|
||||||
X, Y int // X,Y location of the view
|
X, Y int // X,Y location of the view
|
||||||
Width, Height int // Width and height of the view
|
Width, Height int // Width and height of the view
|
||||||
|
|
||||||
// Start line of the view (for vertical scroll)
|
// Start line and start column of the view (vertical/horizontal scroll)
|
||||||
StartLine SLoc
|
|
||||||
|
|
||||||
// Start column of the view (for horizontal scroll)
|
|
||||||
// note that since the starting column of every line is different if the view
|
// note that since the starting column of every line is different if the view
|
||||||
// is scrolled, StartCol is a visual index (will be the same for every line)
|
// is scrolled, StartCol is a visual index (will be the same for every line)
|
||||||
StartCol int
|
StartLine, StartCol int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Window interface {
|
type Window interface {
|
||||||
|
@ -31,7 +28,5 @@ type Window interface {
|
||||||
|
|
||||||
type BWindow interface {
|
type BWindow interface {
|
||||||
Window
|
Window
|
||||||
SoftWrap
|
|
||||||
SetBuffer(b *buffer.Buffer)
|
SetBuffer(b *buffer.Buffer)
|
||||||
BufView() View
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,8 @@ import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadHistory attempts to load user history from configDir/buffers/history
|
// LoadHistory attempts to load user history from configDir/buffers/history
|
||||||
|
@ -16,9 +14,9 @@ import (
|
||||||
func (i *InfoBuf) LoadHistory() {
|
func (i *InfoBuf) LoadHistory() {
|
||||||
if config.GetGlobalOption("savehistory").(bool) {
|
if config.GetGlobalOption("savehistory").(bool) {
|
||||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
|
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||||
|
defer file.Close()
|
||||||
var decodedMap map[string][]string
|
var decodedMap map[string][]string
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer file.Close()
|
|
||||||
decoder := gob.NewDecoder(file)
|
decoder := gob.NewDecoder(file)
|
||||||
err = decoder.Decode(&decodedMap)
|
err = decoder.Decode(&decodedMap)
|
||||||
|
|
||||||
|
@ -50,8 +48,8 @@ func (i *InfoBuf) SaveHistory() {
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
|
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||||
|
defer file.Close()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer file.Close()
|
|
||||||
encoder := gob.NewEncoder(file)
|
encoder := gob.NewEncoder(file)
|
||||||
|
|
||||||
err = encoder.Encode(i.History)
|
err = encoder.Encode(i.History)
|
||||||
|
@ -63,30 +61,6 @@ func (i *InfoBuf) SaveHistory() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddToHistory adds a new item to the history for the prompt type `ptype`.
|
|
||||||
// This function is not used by micro itself. It is useful for plugins
|
|
||||||
// which add their own items to the history, bypassing the infobar command line.
|
|
||||||
func (i *InfoBuf) AddToHistory(ptype string, item string) {
|
|
||||||
if i.HasPrompt && i.PromptType == ptype {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := i.History[ptype]; !ok {
|
|
||||||
i.History[ptype] = []string{item}
|
|
||||||
} else {
|
|
||||||
i.History[ptype] = append(i.History[ptype], item)
|
|
||||||
|
|
||||||
// avoid duplicates
|
|
||||||
h := i.History[ptype]
|
|
||||||
for j := len(h) - 2; j >= 0; j-- {
|
|
||||||
if h[j] == h[len(h)-1] {
|
|
||||||
i.History[ptype] = append(h[:j], h[j+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpHistory fetches the previous item in the history
|
// UpHistory fetches the previous item in the history
|
||||||
func (i *InfoBuf) UpHistory(history []string) {
|
func (i *InfoBuf) UpHistory(history []string) {
|
||||||
if i.HistoryNum > 0 && i.HasPrompt && !i.HasYN {
|
if i.HistoryNum > 0 && i.HasPrompt && !i.HasYN {
|
||||||
|
@ -104,51 +78,3 @@ func (i *InfoBuf) DownHistory(history []string) {
|
||||||
i.Buffer.GetActiveCursor().GotoLoc(i.End())
|
i.Buffer.GetActiveCursor().GotoLoc(i.End())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchUpHistory fetches the previous item in the history
|
|
||||||
// beginning with the text in the infobuffer before cursor
|
|
||||||
func (i *InfoBuf) SearchUpHistory(history []string) {
|
|
||||||
if i.HistoryNum > 0 && i.HasPrompt && !i.HasYN {
|
|
||||||
i.searchHistory(history, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchDownHistory fetches the next item in the history
|
|
||||||
// beginning with the text in the infobuffer before cursor
|
|
||||||
func (i *InfoBuf) SearchDownHistory(history []string) {
|
|
||||||
if i.HistoryNum < len(history)-1 && i.HasPrompt && !i.HasYN {
|
|
||||||
i.searchHistory(history, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InfoBuf) searchHistory(history []string, down bool) {
|
|
||||||
line := string(i.LineBytes(0))
|
|
||||||
c := i.Buffer.GetActiveCursor()
|
|
||||||
|
|
||||||
if !i.HistorySearch || !strings.HasPrefix(line, i.HistorySearchPrefix) {
|
|
||||||
i.HistorySearch = true
|
|
||||||
i.HistorySearchPrefix = util.SliceStartStr(line, c.X)
|
|
||||||
}
|
|
||||||
|
|
||||||
found := -1
|
|
||||||
if down {
|
|
||||||
for j := i.HistoryNum + 1; j < len(history); j++ {
|
|
||||||
if strings.HasPrefix(history[j], i.HistorySearchPrefix) {
|
|
||||||
found = j
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for j := i.HistoryNum - 1; j >= 0; j-- {
|
|
||||||
if strings.HasPrefix(history[j], i.HistorySearchPrefix) {
|
|
||||||
found = j
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if found != -1 {
|
|
||||||
i.HistoryNum = found
|
|
||||||
i.Replace(i.Start(), i.End(), history[found])
|
|
||||||
c.GotoLoc(i.End())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// The InfoBuf displays messages and other info at the bottom of the screen.
|
// The InfoBuf displays messages and other info at the bottom of the screen.
|
||||||
// It is represented as a buffer and a message with a style.
|
// It is respresented as a buffer and a message with a style.
|
||||||
type InfoBuf struct {
|
type InfoBuf struct {
|
||||||
*buffer.Buffer
|
*buffer.Buffer
|
||||||
|
|
||||||
|
@ -25,10 +25,6 @@ type InfoBuf struct {
|
||||||
// It's a map of history type -> history array
|
// It's a map of history type -> history array
|
||||||
History map[string][]string
|
History map[string][]string
|
||||||
HistoryNum int
|
HistoryNum int
|
||||||
// HistorySearch indicates whether we are searching for history items
|
|
||||||
// beginning with HistorySearchPrefix
|
|
||||||
HistorySearch bool
|
|
||||||
HistorySearchPrefix string
|
|
||||||
|
|
||||||
// Is the current message a message from the gutter
|
// Is the current message a message from the gutter
|
||||||
HasGutter bool
|
HasGutter bool
|
||||||
|
@ -58,7 +54,7 @@ func (i *InfoBuf) Close() {
|
||||||
func (i *InfoBuf) Message(msg ...interface{}) {
|
func (i *InfoBuf) Message(msg ...interface{}) {
|
||||||
// only display a new message if there isn't an active prompt
|
// only display a new message if there isn't an active prompt
|
||||||
// this is to prevent overwriting an existing prompt to the user
|
// this is to prevent overwriting an existing prompt to the user
|
||||||
if !i.HasPrompt {
|
if i.HasPrompt == false {
|
||||||
displayMessage := fmt.Sprint(msg...)
|
displayMessage := fmt.Sprint(msg...)
|
||||||
// if there is no active prompt then style and display the message as normal
|
// if there is no active prompt then style and display the message as normal
|
||||||
i.Msg = displayMessage
|
i.Msg = displayMessage
|
||||||
|
@ -82,7 +78,7 @@ func (i *InfoBuf) ClearGutter() {
|
||||||
func (i *InfoBuf) Error(msg ...interface{}) {
|
func (i *InfoBuf) Error(msg ...interface{}) {
|
||||||
// only display a new message if there isn't an active prompt
|
// only display a new message if there isn't an active prompt
|
||||||
// this is to prevent overwriting an existing prompt to the user
|
// this is to prevent overwriting an existing prompt to the user
|
||||||
if !i.HasPrompt {
|
if i.HasPrompt == false {
|
||||||
// if there is no active prompt then style and display the message as normal
|
// if there is no active prompt then style and display the message as normal
|
||||||
i.Msg = fmt.Sprint(msg...)
|
i.Msg = fmt.Sprint(msg...)
|
||||||
i.HasMessage, i.HasError = false, true
|
i.HasMessage, i.HasError = false, true
|
||||||
|
@ -106,7 +102,6 @@ func (i *InfoBuf) Prompt(prompt string, msg string, ptype string, eventcb func(s
|
||||||
i.History[ptype] = append(i.History[ptype], "")
|
i.History[ptype] = append(i.History[ptype], "")
|
||||||
}
|
}
|
||||||
i.HistoryNum = len(i.History[ptype]) - 1
|
i.HistoryNum = len(i.History[ptype]) - 1
|
||||||
i.HistorySearch = false
|
|
||||||
|
|
||||||
i.PromptType = ptype
|
i.PromptType = ptype
|
||||||
i.Msg = prompt
|
i.Msg = prompt
|
||||||
|
@ -142,28 +137,18 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
|
||||||
if !hadYN {
|
if !hadYN {
|
||||||
if i.PromptCallback != nil {
|
if i.PromptCallback != nil {
|
||||||
if canceled {
|
if canceled {
|
||||||
i.Replace(i.Start(), i.End(), "")
|
i.PromptCallback("", true)
|
||||||
h := i.History[i.PromptType]
|
h := i.History[i.PromptType]
|
||||||
i.History[i.PromptType] = h[:len(h)-1]
|
i.History[i.PromptType] = h[:len(h)-1]
|
||||||
i.PromptCallback("", true)
|
|
||||||
} else {
|
} else {
|
||||||
resp := string(i.LineBytes(0))
|
resp := string(i.LineBytes(0))
|
||||||
i.Replace(i.Start(), i.End(), "")
|
i.PromptCallback(resp, false)
|
||||||
h := i.History[i.PromptType]
|
h := i.History[i.PromptType]
|
||||||
h[len(h)-1] = resp
|
h[len(h)-1] = resp
|
||||||
|
|
||||||
// avoid duplicates
|
|
||||||
for j := len(h) - 2; j >= 0; j-- {
|
|
||||||
if h[j] == h[len(h)-1] {
|
|
||||||
i.History[i.PromptType] = append(h[:j], h[j+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i.PromptCallback(resp, false)
|
|
||||||
}
|
}
|
||||||
// i.PromptCallback = nil
|
// i.PromptCallback = nil
|
||||||
}
|
}
|
||||||
|
i.Replace(i.Start(), i.End(), "")
|
||||||
}
|
}
|
||||||
if i.YNCallback != nil && hadYN {
|
if i.YNCallback != nil && hadYN {
|
||||||
i.YNCallback(i.YNResp, canceled)
|
i.YNCallback(i.YNResp, canceled)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package lua
|
package lua
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -10,7 +9,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -20,7 +18,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
luar "layeh.com/gopher-luar"
|
luar "layeh.com/gopher-luar"
|
||||||
)
|
)
|
||||||
|
@ -72,12 +69,6 @@ func Import(pkg string) *lua.LTable {
|
||||||
return importTime()
|
return importTime()
|
||||||
case "unicode/utf8", "utf8":
|
case "unicode/utf8", "utf8":
|
||||||
return importUtf8()
|
return importUtf8()
|
||||||
case "humanize":
|
|
||||||
return importHumanize()
|
|
||||||
case "net/http", "http":
|
|
||||||
return importHTTP()
|
|
||||||
case "archive/zip":
|
|
||||||
return importArchiveZip()
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -374,9 +365,9 @@ func importOs() *lua.LTable {
|
||||||
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
|
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
|
||||||
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
|
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
|
||||||
L.SetField(pkg, "Rename", luar.New(L, os.Rename))
|
L.SetField(pkg, "Rename", luar.New(L, os.Rename))
|
||||||
L.SetField(pkg, "SEEK_CUR", luar.New(L, io.SeekCurrent))
|
L.SetField(pkg, "SEEK_CUR", luar.New(L, os.SEEK_CUR))
|
||||||
L.SetField(pkg, "SEEK_END", luar.New(L, io.SeekEnd))
|
L.SetField(pkg, "SEEK_END", luar.New(L, os.SEEK_END))
|
||||||
L.SetField(pkg, "SEEK_SET", luar.New(L, io.SeekStart))
|
L.SetField(pkg, "SEEK_SET", luar.New(L, os.SEEK_SET))
|
||||||
L.SetField(pkg, "SameFile", luar.New(L, os.SameFile))
|
L.SetField(pkg, "SameFile", luar.New(L, os.SameFile))
|
||||||
L.SetField(pkg, "Setenv", luar.New(L, os.Setenv))
|
L.SetField(pkg, "Setenv", luar.New(L, os.Setenv))
|
||||||
L.SetField(pkg, "StartProcess", luar.New(L, os.StartProcess))
|
L.SetField(pkg, "StartProcess", luar.New(L, os.StartProcess))
|
||||||
|
@ -387,7 +378,6 @@ func importOs() *lua.LTable {
|
||||||
L.SetField(pkg, "Symlink", luar.New(L, os.Symlink))
|
L.SetField(pkg, "Symlink", luar.New(L, os.Symlink))
|
||||||
L.SetField(pkg, "TempDir", luar.New(L, os.TempDir))
|
L.SetField(pkg, "TempDir", luar.New(L, os.TempDir))
|
||||||
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
|
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
|
||||||
L.SetField(pkg, "UserHomeDir", luar.New(L, os.UserHomeDir))
|
|
||||||
|
|
||||||
return pkg
|
return pkg
|
||||||
}
|
}
|
||||||
|
@ -521,16 +511,21 @@ func importErrors() *lua.LTable {
|
||||||
func importTime() *lua.LTable {
|
func importTime() *lua.LTable {
|
||||||
pkg := L.NewTable()
|
pkg := L.NewTable()
|
||||||
|
|
||||||
|
L.SetField(pkg, "After", luar.New(L, time.After))
|
||||||
L.SetField(pkg, "Sleep", luar.New(L, time.Sleep))
|
L.SetField(pkg, "Sleep", luar.New(L, time.Sleep))
|
||||||
|
L.SetField(pkg, "Tick", luar.New(L, time.Tick))
|
||||||
L.SetField(pkg, "Since", luar.New(L, time.Since))
|
L.SetField(pkg, "Since", luar.New(L, time.Since))
|
||||||
L.SetField(pkg, "FixedZone", luar.New(L, time.FixedZone))
|
L.SetField(pkg, "FixedZone", luar.New(L, time.FixedZone))
|
||||||
L.SetField(pkg, "LoadLocation", luar.New(L, time.LoadLocation))
|
L.SetField(pkg, "LoadLocation", luar.New(L, time.LoadLocation))
|
||||||
|
L.SetField(pkg, "NewTicker", luar.New(L, time.NewTicker))
|
||||||
L.SetField(pkg, "Date", luar.New(L, time.Date))
|
L.SetField(pkg, "Date", luar.New(L, time.Date))
|
||||||
L.SetField(pkg, "Now", luar.New(L, time.Now))
|
L.SetField(pkg, "Now", luar.New(L, time.Now))
|
||||||
L.SetField(pkg, "Parse", luar.New(L, time.Parse))
|
L.SetField(pkg, "Parse", luar.New(L, time.Parse))
|
||||||
L.SetField(pkg, "ParseDuration", luar.New(L, time.ParseDuration))
|
L.SetField(pkg, "ParseDuration", luar.New(L, time.ParseDuration))
|
||||||
L.SetField(pkg, "ParseInLocation", luar.New(L, time.ParseInLocation))
|
L.SetField(pkg, "ParseInLocation", luar.New(L, time.ParseInLocation))
|
||||||
L.SetField(pkg, "Unix", luar.New(L, time.Unix))
|
L.SetField(pkg, "Unix", luar.New(L, time.Unix))
|
||||||
|
L.SetField(pkg, "AfterFunc", luar.New(L, time.AfterFunc))
|
||||||
|
L.SetField(pkg, "NewTimer", luar.New(L, time.NewTimer))
|
||||||
L.SetField(pkg, "Nanosecond", luar.New(L, time.Nanosecond))
|
L.SetField(pkg, "Nanosecond", luar.New(L, time.Nanosecond))
|
||||||
L.SetField(pkg, "Microsecond", luar.New(L, time.Microsecond))
|
L.SetField(pkg, "Microsecond", luar.New(L, time.Microsecond))
|
||||||
L.SetField(pkg, "Millisecond", luar.New(L, time.Millisecond))
|
L.SetField(pkg, "Millisecond", luar.New(L, time.Millisecond))
|
||||||
|
@ -538,15 +533,6 @@ func importTime() *lua.LTable {
|
||||||
L.SetField(pkg, "Minute", luar.New(L, time.Minute))
|
L.SetField(pkg, "Minute", luar.New(L, time.Minute))
|
||||||
L.SetField(pkg, "Hour", luar.New(L, time.Hour))
|
L.SetField(pkg, "Hour", luar.New(L, time.Hour))
|
||||||
|
|
||||||
// TODO: these raw Go timer APIs don't provide any synchronization
|
|
||||||
// with micro. Stop exposing them to lua once plugins switch to using
|
|
||||||
// the safer micro.After() interface instead. See issue #3209
|
|
||||||
L.SetField(pkg, "After", luar.New(L, time.After))
|
|
||||||
L.SetField(pkg, "AfterFunc", luar.New(L, time.AfterFunc))
|
|
||||||
L.SetField(pkg, "NewTicker", luar.New(L, time.NewTicker))
|
|
||||||
L.SetField(pkg, "NewTimer", luar.New(L, time.NewTimer))
|
|
||||||
L.SetField(pkg, "Tick", luar.New(L, time.Tick))
|
|
||||||
|
|
||||||
return pkg
|
return pkg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,31 +556,3 @@ func importUtf8() *lua.LTable {
|
||||||
|
|
||||||
return pkg
|
return pkg
|
||||||
}
|
}
|
||||||
|
|
||||||
func importHumanize() *lua.LTable {
|
|
||||||
pkg := L.NewTable()
|
|
||||||
|
|
||||||
L.SetField(pkg, "Bytes", luar.New(L, humanize.Bytes))
|
|
||||||
L.SetField(pkg, "Ordinal", luar.New(L, humanize.Ordinal))
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
func importHTTP() *lua.LTable {
|
|
||||||
pkg := L.NewTable()
|
|
||||||
|
|
||||||
L.SetField(pkg, "Get", luar.New(L, http.Get))
|
|
||||||
L.SetField(pkg, "Post", luar.New(L, http.Post))
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
func importArchiveZip() *lua.LTable {
|
|
||||||
pkg := L.NewTable()
|
|
||||||
|
|
||||||
L.SetField(pkg, "OpenReader", luar.New(L, zip.OpenReader))
|
|
||||||
L.SetField(pkg, "NewReader", luar.New(L, zip.NewReader))
|
|
||||||
L.SetField(pkg, "NewWriter", luar.New(L, zip.NewWriter))
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package screen
|
package screen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Screen is the tcell screen we use to draw to the terminal
|
// Screen is the tcell screen we use to draw to the terminal
|
||||||
|
@ -19,13 +19,6 @@ import (
|
||||||
// same time too.
|
// same time too.
|
||||||
var Screen tcell.Screen
|
var Screen tcell.Screen
|
||||||
|
|
||||||
// Events is the channel of tcell events
|
|
||||||
var Events chan (tcell.Event)
|
|
||||||
|
|
||||||
// RestartCallback is called when the screen is restarted after it was
|
|
||||||
// temporarily shut down
|
|
||||||
var RestartCallback func()
|
|
||||||
|
|
||||||
// The lock is necessary since the screen is polled on a separate thread
|
// The lock is necessary since the screen is polled on a separate thread
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
|
|
||||||
|
@ -83,10 +76,6 @@ func ShowFakeCursor(x, y int) {
|
||||||
lastCursor.style = style
|
lastCursor.style = style
|
||||||
}
|
}
|
||||||
|
|
||||||
func UseFake() bool {
|
|
||||||
return util.FakeCursor || config.GetGlobalOption("fakecursor").(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
|
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
|
||||||
// reset previous locations of the cursor
|
// reset previous locations of the cursor
|
||||||
// Fake cursors are also necessary to display multiple cursors
|
// Fake cursors are also necessary to display multiple cursors
|
||||||
|
@ -99,7 +88,7 @@ func ShowFakeCursorMulti(x, y int) {
|
||||||
// if enabled or using the terminal cursor otherwise
|
// if enabled or using the terminal cursor otherwise
|
||||||
// By default only the windows console will use a fake cursor
|
// By default only the windows console will use a fake cursor
|
||||||
func ShowCursor(x, y int) {
|
func ShowCursor(x, y int) {
|
||||||
if UseFake() {
|
if util.FakeCursor {
|
||||||
ShowFakeCursor(x, y)
|
ShowFakeCursor(x, y)
|
||||||
} else {
|
} else {
|
||||||
Screen.ShowCursor(x, y)
|
Screen.ShowCursor(x, y)
|
||||||
|
@ -109,12 +98,12 @@ func ShowCursor(x, y int) {
|
||||||
// SetContent sets a cell at a point on the screen and makes sure that it is
|
// SetContent sets a cell at a point on the screen and makes sure that it is
|
||||||
// synced with the last cursor location
|
// synced with the last cursor location
|
||||||
func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
|
func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
|
||||||
if !Screen.CanDisplay(mainc, true) {
|
if !unicode.IsPrint(mainc) {
|
||||||
mainc = '<27>'
|
mainc = '<27>'
|
||||||
}
|
}
|
||||||
|
|
||||||
Screen.SetContent(x, y, mainc, combc, style)
|
Screen.SetContent(x, y, mainc, combc, style)
|
||||||
if UseFake() && lastCursor.x == x && lastCursor.y == y {
|
if util.FakeCursor && lastCursor.x == x && lastCursor.y == y {
|
||||||
lastCursor.r = mainc
|
lastCursor.r = mainc
|
||||||
lastCursor.style = style
|
lastCursor.style = style
|
||||||
lastCursor.combc = combc
|
lastCursor.combc = combc
|
||||||
|
@ -138,15 +127,11 @@ func TempStart(screenWasNil bool) {
|
||||||
if !screenWasNil {
|
if !screenWasNil {
|
||||||
Init()
|
Init()
|
||||||
Unlock()
|
Unlock()
|
||||||
|
|
||||||
if RestartCallback != nil {
|
|
||||||
RestartCallback()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init creates and initializes the tcell screen
|
// Init creates and initializes the tcell screen
|
||||||
func Init() error {
|
func Init() {
|
||||||
drawChan = make(chan bool, 8)
|
drawChan = make(chan bool, 8)
|
||||||
|
|
||||||
// Should we enable true color?
|
// Should we enable true color?
|
||||||
|
@ -157,67 +142,30 @@ func Init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldTerm string
|
var oldTerm string
|
||||||
modifiedTerm := false
|
if config.GetGlobalOption("xterm").(bool) {
|
||||||
setXterm := func() {
|
|
||||||
oldTerm = os.Getenv("TERM")
|
oldTerm = os.Getenv("TERM")
|
||||||
os.Setenv("TERM", "xterm-256color")
|
os.Setenv("TERM", "xterm-256color")
|
||||||
modifiedTerm = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.GetGlobalOption("xterm").(bool) {
|
|
||||||
setXterm()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initilize tcell
|
// Initilize tcell
|
||||||
var err error
|
var err error
|
||||||
Screen, err = tcell.NewScreen()
|
Screen, err = tcell.NewScreen()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Warning: during screen initialization:", err)
|
fmt.Println(err)
|
||||||
log.Println("Falling back to TERM=xterm-256color")
|
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||||
setXterm()
|
os.Exit(1)
|
||||||
Screen, err = tcell.NewScreen()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err = Screen.Init(); err != nil {
|
if err = Screen.Init(); err != nil {
|
||||||
return err
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
Screen.SetPaste(config.GetGlobalOption("paste").(bool))
|
|
||||||
|
|
||||||
// restore TERM
|
// restore TERM
|
||||||
if modifiedTerm {
|
if config.GetGlobalOption("xterm").(bool) {
|
||||||
os.Setenv("TERM", oldTerm)
|
os.Setenv("TERM", oldTerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.GetGlobalOption("mouse").(bool) {
|
if config.GetGlobalOption("mouse").(bool) {
|
||||||
Screen.EnableMouse()
|
Screen.EnableMouse()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitSimScreen initializes a simulation screen for testing purposes
|
|
||||||
func InitSimScreen() (tcell.SimulationScreen, error) {
|
|
||||||
drawChan = make(chan bool, 8)
|
|
||||||
|
|
||||||
// Initilize tcell
|
|
||||||
var err error
|
|
||||||
s := tcell.NewSimulationScreen("")
|
|
||||||
if s == nil {
|
|
||||||
return nil, errors.New("Failed to get a simulation screen")
|
|
||||||
}
|
|
||||||
if err = s.Init(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.SetSize(80, 24)
|
|
||||||
Screen = s
|
|
||||||
|
|
||||||
if config.GetGlobalOption("mouse").(bool) {
|
|
||||||
Screen.EnableMouse()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,12 +37,6 @@ type CallbackFile struct {
|
||||||
args []interface{}
|
args []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Job stores the executing command for the job, and the stdin pipe
|
|
||||||
type Job struct {
|
|
||||||
*exec.Cmd
|
|
||||||
Stdin io.WriteCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *CallbackFile) Write(data []byte) (int, error) {
|
func (f *CallbackFile) Write(data []byte) (int, error) {
|
||||||
// This is either stderr or stdout
|
// This is either stderr or stdout
|
||||||
// In either case we create a new job function callback and put it in the jobs channel
|
// In either case we create a new job function callback and put it in the jobs channel
|
||||||
|
@ -53,13 +47,13 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
|
||||||
|
|
||||||
// JobStart starts a shell command in the background with the given callbacks
|
// JobStart starts a shell command in the background with the given callbacks
|
||||||
// It returns an *exec.Cmd as the job id
|
// It returns an *exec.Cmd as the job id
|
||||||
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *Job {
|
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
|
||||||
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
|
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobSpawn starts a process with args in the background with the given callbacks
|
// JobSpawn starts a process with args in the background with the given callbacks
|
||||||
// It returns an *exec.Cmd as the job id
|
// It returns an *exec.Cmd as the job id
|
||||||
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *Job {
|
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
|
||||||
// Set up everything correctly if the functions have been provided
|
// Set up everything correctly if the functions have been provided
|
||||||
proc := exec.Command(cmdName, cmdArgs...)
|
proc := exec.Command(cmdName, cmdArgs...)
|
||||||
var outbuf bytes.Buffer
|
var outbuf bytes.Buffer
|
||||||
|
@ -73,24 +67,28 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(
|
||||||
} else {
|
} else {
|
||||||
proc.Stderr = &outbuf
|
proc.Stderr = &outbuf
|
||||||
}
|
}
|
||||||
stdin, _ := proc.StdinPipe()
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// Run the process in the background and create the onExit callback
|
// Run the process in the background and create the onExit callback
|
||||||
proc.Run()
|
proc.Run()
|
||||||
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
|
jobFunc := JobFunction{onExit, string(outbuf.Bytes()), userargs}
|
||||||
Jobs <- jobFunc
|
Jobs <- jobFunc
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return &Job{proc, stdin}
|
return proc
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobStop kills a job
|
// JobStop kills a job
|
||||||
func JobStop(j *Job) {
|
func JobStop(cmd *exec.Cmd) {
|
||||||
j.Process.Kill()
|
cmd.Process.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobSend sends the given data into the job's stdin stream
|
// JobSend sends the given data into the job's stdin stream
|
||||||
func JobSend(j *Job, data string) {
|
func JobSend(cmd *exec.Cmd, data string) {
|
||||||
j.Stdin.Write([]byte(data))
|
stdin, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stdin.Write([]byte(data))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
|
||||||
shellquote "github.com/kballard/go-shellquote"
|
shellquote "github.com/kballard/go-shellquote"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
|
@ -58,10 +59,15 @@ func RunBackgroundShell(input string) (func() string, error) {
|
||||||
inputCmd := args[0]
|
inputCmd := args[0]
|
||||||
return func() string {
|
return func() string {
|
||||||
output, err := RunCommand(input)
|
output, err := RunCommand(input)
|
||||||
|
totalLines := strings.Split(output, "\n")
|
||||||
|
|
||||||
str := output
|
str := output
|
||||||
if err != nil {
|
if len(totalLines) < 3 {
|
||||||
str = fmt.Sprint(inputCmd, " exited with error: ", err, ": ", output)
|
if err == nil {
|
||||||
|
str = fmt.Sprint(inputCmd, " exited without error")
|
||||||
|
} else {
|
||||||
|
str = fmt.Sprint(inputCmd, " exited with error: ", err, ": ", output)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
@ -78,9 +78,8 @@ func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback f
|
||||||
t.output = nil
|
t.output = nil
|
||||||
if getOutput {
|
if getOutput {
|
||||||
t.output = bytes.NewBuffer([]byte{})
|
t.output = bytes.NewBuffer([]byte{})
|
||||||
cmd.Stdout = t.output
|
|
||||||
}
|
}
|
||||||
Term, _, err := terminal.Start(&t.State, cmd)
|
Term, _, err := terminal.Start(&t.State, cmd, t.output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -129,13 +128,7 @@ func (t *Terminal) Close() {
|
||||||
// call the lua function that the user has given as a callback
|
// call the lua function that the user has given as a callback
|
||||||
if t.getOutput {
|
if t.getOutput {
|
||||||
if t.callback != nil {
|
if t.callback != nil {
|
||||||
Jobs <- JobFunction{
|
t.callback(t.output.String())
|
||||||
Function: func(out string, args []interface{}) {
|
|
||||||
t.callback(out)
|
|
||||||
},
|
|
||||||
Output: t.output.String(),
|
|
||||||
Args: nil,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -16,7 +13,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
|
@ -27,7 +23,7 @@ var (
|
||||||
|
|
||||||
// Version is the version number or commit hash
|
// Version is the version number or commit hash
|
||||||
Version = "0.0.0-unknown"
|
Version = "0.0.0-unknown"
|
||||||
// SemVersion is the Semantic version
|
// Semantic version
|
||||||
SemVersion semver.Version
|
SemVersion semver.Version
|
||||||
// CommitHash is the commit this version was built on
|
// CommitHash is the commit this version was built on
|
||||||
CommitHash = "Unknown"
|
CommitHash = "Unknown"
|
||||||
|
@ -50,8 +46,7 @@ func init() {
|
||||||
fmt.Println("Invalid version: ", Version, err)
|
fmt.Println("Invalid version: ", Version, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, wt := os.LookupEnv("WT_SESSION")
|
if runtime.GOOS == "windows" {
|
||||||
if runtime.GOOS == "windows" && !wt {
|
|
||||||
FakeCursor = true
|
FakeCursor = true
|
||||||
}
|
}
|
||||||
Stdout = new(bytes.Buffer)
|
Stdout = new(bytes.Buffer)
|
||||||
|
@ -218,68 +213,10 @@ func FSize(f *os.File) int64 {
|
||||||
return fi.Size()
|
return fi.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWordChar returns whether or not a rune is a 'word character'
|
// IsWordChar returns whether or not the string is a 'word character'
|
||||||
// Word characters are defined as numbers, letters or sub-word delimiters
|
// Word characters are defined as numbers, letters, or '_'
|
||||||
func IsWordChar(r rune) bool {
|
func IsWordChar(r rune) bool {
|
||||||
return IsAlphanumeric(r) || IsSubwordDelimiter(r)
|
return unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_'
|
||||||
}
|
|
||||||
|
|
||||||
// IsNonWordChar returns whether or not a rune is not a 'word character'
|
|
||||||
// Non word characters are defined as all characters not being numbers, letters or sub-word delimiters
|
|
||||||
// See IsWordChar()
|
|
||||||
func IsNonWordChar(r rune) bool {
|
|
||||||
return !IsWordChar(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUpperWordChar returns whether or not a rune is an 'upper word character'
|
|
||||||
// Upper word characters are defined as numbers, upper-case letters or sub-word delimiters
|
|
||||||
func IsUpperWordChar(r rune) bool {
|
|
||||||
return IsUpperAlphanumeric(r) || IsSubwordDelimiter(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLowerWordChar returns whether or not a rune is a 'lower word character'
|
|
||||||
// Lower word characters are defined as numbers, lower-case letters or sub-word delimiters
|
|
||||||
func IsLowerWordChar(r rune) bool {
|
|
||||||
return IsLowerAlphanumeric(r) || IsSubwordDelimiter(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSubwordDelimiter returns whether or not a rune is a 'sub-word delimiter character'
|
|
||||||
// i.e. is considered a part of the word and is used as a delimiter between sub-words of the word.
|
|
||||||
// For now the only sub-word delimiter character is '_'.
|
|
||||||
func IsSubwordDelimiter(r rune) bool {
|
|
||||||
return r == '_'
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAlphanumeric returns whether or not a rune is an 'alphanumeric character'
|
|
||||||
// Alphanumeric characters are defined as numbers or letters
|
|
||||||
func IsAlphanumeric(r rune) bool {
|
|
||||||
return unicode.IsLetter(r) || unicode.IsNumber(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUpperAlphanumeric returns whether or not a rune is an 'upper alphanumeric character'
|
|
||||||
// Upper alphanumeric characters are defined as numbers or upper-case letters
|
|
||||||
func IsUpperAlphanumeric(r rune) bool {
|
|
||||||
return IsUpperLetter(r) || unicode.IsNumber(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLowerAlphanumeric returns whether or not a rune is a 'lower alphanumeric character'
|
|
||||||
// Lower alphanumeric characters are defined as numbers or lower-case letters
|
|
||||||
func IsLowerAlphanumeric(r rune) bool {
|
|
||||||
return IsLowerLetter(r) || unicode.IsNumber(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUpperLetter returns whether or not a rune is an 'upper letter character'
|
|
||||||
// Upper letter characters are defined as upper-case letters
|
|
||||||
func IsUpperLetter(r rune) bool {
|
|
||||||
// unicode.IsUpper() returns true for letters only
|
|
||||||
return unicode.IsUpper(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLowerLetter returns whether or not a rune is a 'lower letter character'
|
|
||||||
// Lower letter characters are defined as lower-case letters
|
|
||||||
func IsLowerLetter(r rune) bool {
|
|
||||||
// unicode.IsLower() returns true for letters only
|
|
||||||
return unicode.IsLower(r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spaces returns a string with n spaces
|
// Spaces returns a string with n spaces
|
||||||
|
@ -374,7 +311,7 @@ func ReplaceHome(path string) (string, error) {
|
||||||
// This is used for opening files like util.go:10:5 to specify a line and column
|
// This is used for opening files like util.go:10:5 to specify a line and column
|
||||||
// Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
|
// Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
|
||||||
func GetPathAndCursorPosition(path string) (string, []string) {
|
func GetPathAndCursorPosition(path string) (string, []string) {
|
||||||
re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?$`)
|
re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
|
||||||
match := re.FindStringSubmatch(path)
|
match := re.FindStringSubmatch(path)
|
||||||
// no lines/columns were specified in the path, return just the path with no cursor location
|
// no lines/columns were specified in the path, return just the path with no cursor location
|
||||||
if len(match) == 0 {
|
if len(match) == 0 {
|
||||||
|
@ -399,11 +336,7 @@ func GetModTime(path string) (time.Time, error) {
|
||||||
// EscapePath replaces every path separator in a given path with a %
|
// EscapePath replaces every path separator in a given path with a %
|
||||||
func EscapePath(path string) string {
|
func EscapePath(path string) string {
|
||||||
path = filepath.ToSlash(path)
|
path = filepath.ToSlash(path)
|
||||||
if runtime.GOOS == "windows" {
|
return strings.Replace(path, "/", "%", -1)
|
||||||
// ':' is not valid in a path name on Windows but is ok on Unix
|
|
||||||
path = strings.ReplaceAll(path, ":", "%")
|
|
||||||
}
|
|
||||||
return strings.ReplaceAll(path, "/", "%")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLeadingWhitespace returns the leading whitespace of the given byte array
|
// GetLeadingWhitespace returns the leading whitespace of the given byte array
|
||||||
|
@ -422,28 +355,6 @@ func GetLeadingWhitespace(b []byte) []byte {
|
||||||
return ws
|
return ws
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTrailingWhitespace returns the trailing whitespace of the given byte array
|
|
||||||
func GetTrailingWhitespace(b []byte) []byte {
|
|
||||||
ws := []byte{}
|
|
||||||
for len(b) > 0 {
|
|
||||||
r, size := utf8.DecodeLastRune(b)
|
|
||||||
if IsWhitespace(r) {
|
|
||||||
ws = append([]byte(string(r)), ws...)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
b = b[:len(b)-size]
|
|
||||||
}
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasTrailingWhitespace returns true if the given byte array ends with a whitespace
|
|
||||||
func HasTrailingWhitespace(b []byte) bool {
|
|
||||||
r, _ := utf8.DecodeLastRune(b)
|
|
||||||
return IsWhitespace(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntOpt turns a float64 setting to an int
|
// IntOpt turns a float64 setting to an int
|
||||||
func IntOpt(opt interface{}) int {
|
func IntOpt(opt interface{}) int {
|
||||||
return int(opt.(float64))
|
return int(opt.(float64))
|
||||||
|
@ -503,83 +414,19 @@ func Clamp(val, min, max int) int {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAutocomplete returns whether a character should begin an autocompletion.
|
func IsNonAlphaNumeric(c rune) bool {
|
||||||
func IsAutocomplete(c rune) bool {
|
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
|
||||||
return c == '.' || IsWordChar(c)
|
}
|
||||||
|
|
||||||
|
func IsAutocomplete(c rune) bool {
|
||||||
|
return c == '.' || !IsNonAlphaNumeric(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSpecial replaces escaped ts with '\t'.
|
|
||||||
func ParseSpecial(s string) string {
|
func ParseSpecial(s string) string {
|
||||||
return strings.ReplaceAll(s, "\\t", "\t")
|
return strings.Replace(s, "\\t", "\t", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String converts a byte array to a string (for lua plugins)
|
// String converts a byte array to a string (for lua plugins)
|
||||||
func String(s []byte) string {
|
func String(s []byte) string {
|
||||||
return string(s)
|
return string(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unzip unzips a file to given folder
|
|
||||||
func Unzip(src, dest string) error {
|
|
||||||
r, err := zip.OpenReader(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
os.MkdirAll(dest, 0755)
|
|
||||||
|
|
||||||
// Closure to address file descriptors issue with all the deferred .Close() methods
|
|
||||||
extractAndWriteFile := func(f *zip.File) error {
|
|
||||||
rc, err := f.Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
path := filepath.Join(dest, f.Name)
|
|
||||||
|
|
||||||
// Check for ZipSlip (Directory traversal)
|
|
||||||
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
|
|
||||||
return fmt.Errorf("illegal file path: %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.FileInfo().IsDir() {
|
|
||||||
os.MkdirAll(path, f.Mode())
|
|
||||||
} else {
|
|
||||||
os.MkdirAll(filepath.Dir(path), f.Mode())
|
|
||||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(f, rc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range r.File {
|
|
||||||
err := extractAndWriteFile(f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HttpRequest returns a new http.Client for making custom requests (for lua plugins)
|
|
||||||
func HttpRequest(method string, url string, headers []string) (resp *http.Response, err error) {
|
|
||||||
client := http.Client{}
|
|
||||||
req, err := http.NewRequest(method, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i := 0; i < len(headers); i += 2 {
|
|
||||||
req.Header.Add(headers[i], headers[i+1])
|
|
||||||
}
|
|
||||||
return client.Do(req)
|
|
||||||
}
|
|
||||||
|
|
21
internal/vfsutil/file.go
Normal file
21
internal/vfsutil/file.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package vfsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File implements http.FileSystem using the native file system restricted to a
|
||||||
|
// specific file served at root.
|
||||||
|
//
|
||||||
|
// While the FileSystem.Open method takes '/'-separated paths, a File's string
|
||||||
|
// value is a filename on the native file system, not a URL, so it is separated
|
||||||
|
// by filepath.Separator, which isn't necessarily '/'.
|
||||||
|
type File string
|
||||||
|
|
||||||
|
func (f File) Open(name string) (http.File, error) {
|
||||||
|
if name != "/" {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
|
||||||
|
}
|
||||||
|
return os.Open(string(f))
|
||||||
|
}
|
39
internal/vfsutil/vfsutil.go
Normal file
39
internal/vfsutil/vfsutil.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// Package vfsutil implements some I/O utility functions for http.FileSystem.
|
||||||
|
package vfsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadDir reads the contents of the directory associated with file and
|
||||||
|
// returns a slice of FileInfo values in directory order.
|
||||||
|
func ReadDir(fs http.FileSystem, name string) ([]os.FileInfo, error) {
|
||||||
|
f, err := fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return f.Readdir(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns the FileInfo structure describing file.
|
||||||
|
func Stat(fs http.FileSystem, name string) (os.FileInfo, error) {
|
||||||
|
f, err := fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return f.Stat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile reads the file named by path from fs and returns the contents.
|
||||||
|
func ReadFile(fs http.FileSystem, path string) ([]byte, error) {
|
||||||
|
rc, err := fs.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
return ioutil.ReadAll(rc)
|
||||||
|
}
|
146
internal/vfsutil/walk.go
Normal file
146
internal/vfsutil/walk.go
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
package vfsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
pathpkg "path"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Walk walks the filesystem rooted at root, calling walkFn for each file or
|
||||||
|
// directory in the filesystem, including root. All errors that arise visiting files
|
||||||
|
// and directories are filtered by walkFn. The files are walked in lexical
|
||||||
|
// order.
|
||||||
|
func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error {
|
||||||
|
info, err := Stat(fs, root)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(root, nil, err)
|
||||||
|
}
|
||||||
|
return walk(fs, root, info, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDirNames reads the directory named by dirname and returns
|
||||||
|
// a sorted list of directory entries.
|
||||||
|
func readDirNames(fs http.FileSystem, dirname string) ([]string, error) {
|
||||||
|
fis, err := ReadDir(fs, dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
names := make([]string, len(fis))
|
||||||
|
for i := range fis {
|
||||||
|
names[i] = fis[i].Name()
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk recursively descends path, calling walkFn.
|
||||||
|
func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||||
|
err := walkFn(path, info, nil)
|
||||||
|
if err != nil {
|
||||||
|
if info.IsDir() && err == filepath.SkipDir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := readDirNames(fs, path)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
filename := pathpkg.Join(path, name)
|
||||||
|
fileInfo, err := Stat(fs, filename)
|
||||||
|
if err != nil {
|
||||||
|
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = walk(fs, filename, fileInfo, walkFn)
|
||||||
|
if err != nil {
|
||||||
|
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkFilesFunc is the type of the function called for each file or directory visited by WalkFiles.
|
||||||
|
// It's like filepath.WalkFunc, except it provides an additional ReadSeeker parameter for file being visited.
|
||||||
|
type WalkFilesFunc func(path string, info os.FileInfo, rs io.ReadSeeker, err error) error
|
||||||
|
|
||||||
|
// WalkFiles walks the filesystem rooted at root, calling walkFn for each file or
|
||||||
|
// directory in the filesystem, including root. In addition to FileInfo, it passes an
|
||||||
|
// ReadSeeker to walkFn for each file it visits.
|
||||||
|
func WalkFiles(fs http.FileSystem, root string, walkFn WalkFilesFunc) error {
|
||||||
|
file, info, err := openStat(fs, root)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(root, nil, nil, err)
|
||||||
|
}
|
||||||
|
return walkFiles(fs, root, info, file, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// walkFiles recursively descends path, calling walkFn.
|
||||||
|
// It closes the input file after it's done with it, so the caller shouldn't.
|
||||||
|
func walkFiles(fs http.FileSystem, path string, info os.FileInfo, file http.File, walkFn WalkFilesFunc) error {
|
||||||
|
err := walkFn(path, info, file, nil)
|
||||||
|
file.Close()
|
||||||
|
if err != nil {
|
||||||
|
if info.IsDir() && err == filepath.SkipDir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := readDirNames(fs, path)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(path, info, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
filename := pathpkg.Join(path, name)
|
||||||
|
file, fileInfo, err := openStat(fs, filename)
|
||||||
|
if err != nil {
|
||||||
|
if err := walkFn(filename, nil, nil, err); err != nil && err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = walkFiles(fs, filename, fileInfo, file, walkFn)
|
||||||
|
// file is closed by walkFiles, so we don't need to close it here.
|
||||||
|
if err != nil {
|
||||||
|
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openStat performs Open and Stat and returns results, or first error encountered.
|
||||||
|
// The caller is responsible for closing the returned file when done.
|
||||||
|
func openStat(fs http.FileSystem, name string) (http.File, os.FileInfo, error) {
|
||||||
|
f, err := fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return f, fi, nil
|
||||||
|
}
|
93
internal/vfsutil/walk_test.go
Normal file
93
internal/vfsutil/walk_test.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package vfsutil_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/shurcooL/httpfs/vfsutil"
|
||||||
|
"golang.org/x/tools/godoc/vfs/httpfs"
|
||||||
|
"golang.org/x/tools/godoc/vfs/mapfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleWalk() {
|
||||||
|
var fs http.FileSystem = httpfs.New(mapfs.New(map[string]string{
|
||||||
|
"zzz-last-file.txt": "It should be visited last.",
|
||||||
|
"a-file.txt": "It has stuff.",
|
||||||
|
"another-file.txt": "Also stuff.",
|
||||||
|
"folderA/entry-A.txt": "Alpha.",
|
||||||
|
"folderA/entry-B.txt": "Beta.",
|
||||||
|
}))
|
||||||
|
|
||||||
|
walkFn := func(path string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("can't stat file %s: %v\n", path, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Println(path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := vfsutil.Walk(fs, "/", walkFn)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// /
|
||||||
|
// /a-file.txt
|
||||||
|
// /another-file.txt
|
||||||
|
// /folderA
|
||||||
|
// /folderA/entry-A.txt
|
||||||
|
// /folderA/entry-B.txt
|
||||||
|
// /zzz-last-file.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWalkFiles() {
|
||||||
|
var fs http.FileSystem = httpfs.New(mapfs.New(map[string]string{
|
||||||
|
"zzz-last-file.txt": "It should be visited last.",
|
||||||
|
"a-file.txt": "It has stuff.",
|
||||||
|
"another-file.txt": "Also stuff.",
|
||||||
|
"folderA/entry-A.txt": "Alpha.",
|
||||||
|
"folderA/entry-B.txt": "Beta.",
|
||||||
|
}))
|
||||||
|
|
||||||
|
walkFn := func(path string, fi os.FileInfo, r io.ReadSeeker, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("can't stat file %s: %v\n", path, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Println(path)
|
||||||
|
if !fi.IsDir() {
|
||||||
|
b, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("can't read file %s: %v\n", path, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Printf("%q\n", b)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := vfsutil.WalkFiles(fs, "/", walkFn)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// /
|
||||||
|
// /a-file.txt
|
||||||
|
// "It has stuff."
|
||||||
|
// /another-file.txt
|
||||||
|
// "Also stuff."
|
||||||
|
// /folderA
|
||||||
|
// /folderA/entry-A.txt
|
||||||
|
// "Alpha."
|
||||||
|
// /folderA/entry-B.txt
|
||||||
|
// "Beta."
|
||||||
|
// /zzz-last-file.txt
|
||||||
|
// "It should be visited last."
|
||||||
|
}
|
|
@ -185,10 +185,6 @@ func (n *Node) hResizeSplit(i int, size int) bool {
|
||||||
|
|
||||||
// ResizeSplit resizes a certain split to a given size
|
// ResizeSplit resizes a certain split to a given size
|
||||||
func (n *Node) ResizeSplit(size int) bool {
|
func (n *Node) ResizeSplit(size int) bool {
|
||||||
// TODO: `size < 0` does not work for some reason
|
|
||||||
if size <= 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(n.parent.children) <= 1 {
|
if len(n.parent.children) <= 1 {
|
||||||
// cannot resize a lone node
|
// cannot resize a lone node
|
||||||
return false
|
return false
|
||||||
|
@ -205,7 +201,7 @@ func (n *Node) ResizeSplit(size int) bool {
|
||||||
return n.parent.hResizeSplit(ind, size)
|
return n.parent.hResizeSplit(ind, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize sets this node's size and resizes all children accordingly
|
// Resize sets this node's size and resizes all children accordlingly
|
||||||
func (n *Node) Resize(w, h int) {
|
func (n *Node) Resize(w, h int) {
|
||||||
n.W, n.H = w, h
|
n.W, n.H = w, h
|
||||||
|
|
||||||
|
|
18
pkg/highlight/ftdetect.go
Normal file
18
pkg/highlight/ftdetect.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package highlight
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
// MatchFiletype will use the list of syntax definitions provided and the filename and first line of the file
|
||||||
|
// to determine the filetype of the file
|
||||||
|
// It will return the corresponding syntax definition for the filetype
|
||||||
|
func MatchFiletype(ftdetect [2]*regexp.Regexp, filename string, firstLine []byte) bool {
|
||||||
|
if ftdetect[0] != nil && ftdetect[0].MatchString(filename) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ftdetect[1] != nil {
|
||||||
|
return ftdetect[1].Match(firstLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -67,6 +67,8 @@ func combineLineMatch(src, dst LineMatch) LineMatch {
|
||||||
// A State represents the region at the end of a line
|
// A State represents the region at the end of a line
|
||||||
type State *region
|
type State *region
|
||||||
|
|
||||||
|
var EmptyDef = Def{nil, &rules{}}
|
||||||
|
|
||||||
// LineStates is an interface for a buffer-like object which can also store the states and matches for every line
|
// LineStates is an interface for a buffer-like object which can also store the states and matches for every line
|
||||||
type LineStates interface {
|
type LineStates interface {
|
||||||
LineBytes(n int) []byte
|
LineBytes(n int) []byte
|
||||||
|
@ -74,8 +76,6 @@ type LineStates interface {
|
||||||
State(lineN int) State
|
State(lineN int) State
|
||||||
SetState(lineN int, s State)
|
SetState(lineN int, s State)
|
||||||
SetMatch(lineN int, m LineMatch)
|
SetMatch(lineN int, m LineMatch)
|
||||||
Lock()
|
|
||||||
Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Highlighter contains the information needed to highlight a string
|
// A Highlighter contains the information needed to highlight a string
|
||||||
|
@ -95,7 +95,19 @@ func NewHighlighter(def *Def) *Highlighter {
|
||||||
// color's group (represented as one byte)
|
// color's group (represented as one byte)
|
||||||
type LineMatch map[int]Group
|
type LineMatch map[int]Group
|
||||||
|
|
||||||
func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte) []int {
|
func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) []int {
|
||||||
|
regexStr := regex.String()
|
||||||
|
if strings.Contains(regexStr, "^") {
|
||||||
|
if !canMatchStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(regexStr, "$") {
|
||||||
|
if !canMatchEnd {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var strbytes []byte
|
var strbytes []byte
|
||||||
if skip != nil {
|
if skip != nil {
|
||||||
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
|
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
|
||||||
|
@ -114,7 +126,18 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte) []int {
|
||||||
return []int{runePos(match[0], str), runePos(match[1], str)}
|
return []int{runePos(match[0], str), runePos(match[1], str)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAllIndex(regex *regexp.Regexp, str []byte) [][]int {
|
func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) [][]int {
|
||||||
|
regexStr := regex.String()
|
||||||
|
if strings.Contains(regexStr, "^") {
|
||||||
|
if !canMatchStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(regexStr, "$") {
|
||||||
|
if !canMatchEnd {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
matches := regex.FindAllIndex(str, -1)
|
matches := regex.FindAllIndex(str, -1)
|
||||||
for i, m := range matches {
|
for i, m := range matches {
|
||||||
matches[i][0] = runePos(m[0], str)
|
matches[i][0] = runePos(m[0], str)
|
||||||
|
@ -133,33 +156,52 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstRegion *region
|
loc := findIndex(curRegion.end, curRegion.skip, line, start == 0, canMatchEnd)
|
||||||
firstLoc := []int{lineLen, 0}
|
if loc != nil {
|
||||||
searchNesting := true
|
if !statesOnly {
|
||||||
endLoc := findIndex(curRegion.end, curRegion.skip, line)
|
highlights[start+loc[0]] = curRegion.limitGroup
|
||||||
if endLoc != nil {
|
|
||||||
if start == endLoc[0] {
|
|
||||||
searchNesting = false
|
|
||||||
} else {
|
|
||||||
firstLoc = endLoc
|
|
||||||
}
|
}
|
||||||
|
if curRegion.parent == nil {
|
||||||
|
if !statesOnly {
|
||||||
|
highlights[start+loc[1]] = 0
|
||||||
|
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, loc[0]), curRegion, statesOnly)
|
||||||
|
}
|
||||||
|
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), statesOnly)
|
||||||
|
return highlights
|
||||||
|
}
|
||||||
|
if !statesOnly {
|
||||||
|
highlights[start+loc[1]] = curRegion.parent.group
|
||||||
|
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, loc[0]), curRegion, statesOnly)
|
||||||
|
}
|
||||||
|
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), curRegion.parent, statesOnly)
|
||||||
|
return highlights
|
||||||
}
|
}
|
||||||
if searchNesting {
|
|
||||||
for _, r := range curRegion.rules.regions {
|
if lineLen == 0 {
|
||||||
loc := findIndex(r.start, r.skip, line)
|
if canMatchEnd {
|
||||||
if loc != nil {
|
h.lastRegion = curRegion
|
||||||
if loc[0] < firstLoc[0] {
|
}
|
||||||
firstLoc = loc
|
|
||||||
firstRegion = r
|
return highlights
|
||||||
}
|
}
|
||||||
|
|
||||||
|
firstLoc := []int{lineLen, 0}
|
||||||
|
|
||||||
|
var firstRegion *region
|
||||||
|
for _, r := range curRegion.rules.regions {
|
||||||
|
loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
|
||||||
|
if loc != nil {
|
||||||
|
if loc[0] < firstLoc[0] {
|
||||||
|
firstLoc = loc
|
||||||
|
firstRegion = r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if firstRegion != nil && firstLoc[0] != lineLen {
|
if firstLoc[0] != lineLen {
|
||||||
if !statesOnly {
|
if !statesOnly {
|
||||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||||
}
|
}
|
||||||
h.highlightEmptyRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), statesOnly)
|
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), curRegion, statesOnly)
|
||||||
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
|
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
|
||||||
return highlights
|
return highlights
|
||||||
}
|
}
|
||||||
|
@ -170,17 +212,11 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||||
fullHighlights[i] = curRegion.group
|
fullHighlights[i] = curRegion.group
|
||||||
}
|
}
|
||||||
|
|
||||||
if searchNesting {
|
for _, p := range curRegion.rules.patterns {
|
||||||
for _, p := range curRegion.rules.patterns {
|
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
|
||||||
if curRegion.group == curRegion.limitGroup || p.group == curRegion.limitGroup {
|
for _, m := range matches {
|
||||||
matches := findAllIndex(p.regex, line)
|
for i := m[0]; i < m[1]; i++ {
|
||||||
for _, m := range matches {
|
fullHighlights[i] = p.group
|
||||||
if ((endLoc == nil) || (m[0] < endLoc[0])) {
|
|
||||||
for i := m[0]; i < m[1]; i++ {
|
|
||||||
fullHighlights[i] = p.group
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,25 +227,6 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loc := endLoc
|
|
||||||
if loc != nil {
|
|
||||||
if !statesOnly {
|
|
||||||
highlights[start+loc[0]] = curRegion.limitGroup
|
|
||||||
}
|
|
||||||
if curRegion.parent == nil {
|
|
||||||
if !statesOnly {
|
|
||||||
highlights[start+loc[1]] = 0
|
|
||||||
}
|
|
||||||
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), statesOnly)
|
|
||||||
return highlights
|
|
||||||
}
|
|
||||||
if !statesOnly {
|
|
||||||
highlights[start+loc[1]] = curRegion.parent.group
|
|
||||||
}
|
|
||||||
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), curRegion.parent, statesOnly)
|
|
||||||
return highlights
|
|
||||||
}
|
|
||||||
|
|
||||||
if canMatchEnd {
|
if canMatchEnd {
|
||||||
h.lastRegion = curRegion
|
h.lastRegion = curRegion
|
||||||
}
|
}
|
||||||
|
@ -226,10 +243,10 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
|
||||||
return highlights
|
return highlights
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstRegion *region
|
|
||||||
firstLoc := []int{lineLen, 0}
|
firstLoc := []int{lineLen, 0}
|
||||||
|
var firstRegion *region
|
||||||
for _, r := range h.Def.rules.regions {
|
for _, r := range h.Def.rules.regions {
|
||||||
loc := findIndex(r.start, r.skip, line)
|
loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
|
||||||
if loc != nil {
|
if loc != nil {
|
||||||
if loc[0] < firstLoc[0] {
|
if loc[0] < firstLoc[0] {
|
||||||
firstLoc = loc
|
firstLoc = loc
|
||||||
|
@ -237,7 +254,7 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if firstRegion != nil && firstLoc[0] != lineLen {
|
if firstLoc[0] != lineLen {
|
||||||
if !statesOnly {
|
if !statesOnly {
|
||||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||||
}
|
}
|
||||||
|
@ -256,7 +273,7 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
|
||||||
|
|
||||||
fullHighlights := make([]Group, len(line))
|
fullHighlights := make([]Group, len(line))
|
||||||
for _, p := range h.Def.rules.patterns {
|
for _, p := range h.Def.rules.patterns {
|
||||||
matches := findAllIndex(p.regex, line)
|
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
|
||||||
for _, m := range matches {
|
for _, m := range matches {
|
||||||
for i := m[0]; i < m[1]; i++ {
|
for i := m[0]; i < m[1]; i++ {
|
||||||
fullHighlights[i] = p.group
|
fullHighlights[i] = p.group
|
||||||
|
@ -302,13 +319,7 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
|
||||||
|
|
||||||
// HighlightStates correctly sets all states for the buffer
|
// HighlightStates correctly sets all states for the buffer
|
||||||
func (h *Highlighter) HighlightStates(input LineStates) {
|
func (h *Highlighter) HighlightStates(input LineStates) {
|
||||||
for i := 0; ; i++ {
|
for i := 0; i < input.LinesNum(); i++ {
|
||||||
input.Lock()
|
|
||||||
if i >= input.LinesNum() {
|
|
||||||
input.Unlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
line := input.LineBytes(i)
|
line := input.LineBytes(i)
|
||||||
// highlights := make(LineMatch)
|
// highlights := make(LineMatch)
|
||||||
|
|
||||||
|
@ -321,7 +332,6 @@ func (h *Highlighter) HighlightStates(input LineStates) {
|
||||||
curState := h.lastRegion
|
curState := h.lastRegion
|
||||||
|
|
||||||
input.SetState(i, curState)
|
input.SetState(i, curState)
|
||||||
input.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,9 +340,7 @@ func (h *Highlighter) HighlightStates(input LineStates) {
|
||||||
// This assumes that all the states are set correctly
|
// This assumes that all the states are set correctly
|
||||||
func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
|
func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
|
||||||
for i := startline; i <= endline; i++ {
|
for i := startline; i <= endline; i++ {
|
||||||
input.Lock()
|
|
||||||
if i >= input.LinesNum() {
|
if i >= input.LinesNum() {
|
||||||
input.Unlock()
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +355,6 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
|
||||||
}
|
}
|
||||||
|
|
||||||
input.SetMatch(i, match)
|
input.SetMatch(i, match)
|
||||||
input.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,19 +366,9 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
|
||||||
|
|
||||||
h.lastRegion = nil
|
h.lastRegion = nil
|
||||||
if startline > 0 {
|
if startline > 0 {
|
||||||
input.Lock()
|
h.lastRegion = input.State(startline - 1)
|
||||||
if startline-1 < input.LinesNum() {
|
|
||||||
h.lastRegion = input.State(startline - 1)
|
|
||||||
}
|
|
||||||
input.Unlock()
|
|
||||||
}
|
}
|
||||||
for i := startline; ; i++ {
|
for i := startline; i < input.LinesNum(); i++ {
|
||||||
input.Lock()
|
|
||||||
if i >= input.LinesNum() {
|
|
||||||
input.Unlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
line := input.LineBytes(i)
|
line := input.LineBytes(i)
|
||||||
// highlights := make(LineMatch)
|
// highlights := make(LineMatch)
|
||||||
|
|
||||||
|
@ -385,7 +382,6 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
|
||||||
lastState := input.State(i)
|
lastState := input.State(i)
|
||||||
|
|
||||||
input.SetState(i, curState)
|
input.SetState(i, curState)
|
||||||
input.Unlock()
|
|
||||||
|
|
||||||
if curState == lastState {
|
if curState == lastState {
|
||||||
return i
|
return i
|
||||||
|
@ -397,9 +393,6 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
|
||||||
|
|
||||||
// ReHighlightLine will rehighlight the state and match for a single line
|
// ReHighlightLine will rehighlight the state and match for a single line
|
||||||
func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
|
func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
|
||||||
input.Lock()
|
|
||||||
defer input.Unlock()
|
|
||||||
|
|
||||||
line := input.LineBytes(lineN)
|
line := input.LineBytes(lineN)
|
||||||
highlights := make(LineMatch)
|
highlights := make(LineMatch)
|
||||||
|
|
||||||
|
|
|
@ -33,28 +33,27 @@ func (g Group) String() string {
|
||||||
// Then it has the rules which define how to highlight the file
|
// Then it has the rules which define how to highlight the file
|
||||||
type Def struct {
|
type Def struct {
|
||||||
*Header
|
*Header
|
||||||
|
|
||||||
rules *rules
|
rules *rules
|
||||||
}
|
}
|
||||||
|
|
||||||
type Header struct {
|
type Header struct {
|
||||||
FileType string
|
FileType string
|
||||||
FileNameRegex *regexp.Regexp
|
FtDetect [2]*regexp.Regexp
|
||||||
HeaderRegex *regexp.Regexp
|
|
||||||
SignatureRegex *regexp.Regexp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeaderYaml struct {
|
type HeaderYaml struct {
|
||||||
FileType string `yaml:"filetype"`
|
FileType string `yaml:"filetype"`
|
||||||
Detect struct {
|
Detect struct {
|
||||||
FNameRegexStr string `yaml:"filename"`
|
FNameRgx string `yaml:"filename"`
|
||||||
HeaderRegexStr string `yaml:"header"`
|
HeaderRgx string `yaml:"header"`
|
||||||
SignatureRegexStr string `yaml:"signature"`
|
|
||||||
} `yaml:"detect"`
|
} `yaml:"detect"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
FileType string
|
FileType string
|
||||||
yamlSrc map[interface{}]interface{}
|
|
||||||
|
yamlSrc map[interface{}]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Pattern is one simple syntax rule
|
// A Pattern is one simple syntax rule
|
||||||
|
@ -98,24 +97,20 @@ func init() {
|
||||||
// A yaml file might take ~400us to parse while a header file only takes ~20us
|
// A yaml file might take ~400us to parse while a header file only takes ~20us
|
||||||
func MakeHeader(data []byte) (*Header, error) {
|
func MakeHeader(data []byte) (*Header, error) {
|
||||||
lines := bytes.Split(data, []byte{'\n'})
|
lines := bytes.Split(data, []byte{'\n'})
|
||||||
if len(lines) < 4 {
|
if len(lines) < 3 {
|
||||||
return nil, errors.New("Header file has incorrect format")
|
return nil, errors.New("Header file has incorrect format")
|
||||||
}
|
}
|
||||||
header := new(Header)
|
header := new(Header)
|
||||||
var err error
|
var err error
|
||||||
header.FileType = string(lines[0])
|
header.FileType = string(lines[0])
|
||||||
fnameRegexStr := string(lines[1])
|
fnameRgx := string(lines[1])
|
||||||
headerRegexStr := string(lines[2])
|
headerRgx := string(lines[2])
|
||||||
signatureRegexStr := string(lines[3])
|
|
||||||
|
|
||||||
if fnameRegexStr != "" {
|
if fnameRgx != "" {
|
||||||
header.FileNameRegex, err = regexp.Compile(fnameRegexStr)
|
header.FtDetect[0], err = regexp.Compile(fnameRgx)
|
||||||
}
|
}
|
||||||
if err == nil && headerRegexStr != "" {
|
if headerRgx != "" {
|
||||||
header.HeaderRegex, err = regexp.Compile(headerRegexStr)
|
header.FtDetect[1], err = regexp.Compile(headerRgx)
|
||||||
}
|
|
||||||
if err == nil && signatureRegexStr != "" {
|
|
||||||
header.SignatureRegex, err = regexp.Compile(signatureRegexStr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -137,14 +132,11 @@ func MakeHeaderYaml(data []byte) (*Header, error) {
|
||||||
header := new(Header)
|
header := new(Header)
|
||||||
header.FileType = hdrYaml.FileType
|
header.FileType = hdrYaml.FileType
|
||||||
|
|
||||||
if hdrYaml.Detect.FNameRegexStr != "" {
|
if hdrYaml.Detect.FNameRgx != "" {
|
||||||
header.FileNameRegex, err = regexp.Compile(hdrYaml.Detect.FNameRegexStr)
|
header.FtDetect[0], err = regexp.Compile(hdrYaml.Detect.FNameRgx)
|
||||||
}
|
}
|
||||||
if err == nil && hdrYaml.Detect.HeaderRegexStr != "" {
|
if hdrYaml.Detect.HeaderRgx != "" {
|
||||||
header.HeaderRegex, err = regexp.Compile(hdrYaml.Detect.HeaderRegexStr)
|
header.FtDetect[1], err = regexp.Compile(hdrYaml.Detect.HeaderRgx)
|
||||||
}
|
|
||||||
if err == nil && hdrYaml.Detect.SignatureRegexStr != "" {
|
|
||||||
header.SignatureRegex, err = regexp.Compile(hdrYaml.Detect.SignatureRegexStr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -154,37 +146,6 @@ func MakeHeaderYaml(data []byte) (*Header, error) {
|
||||||
return header, nil
|
return header, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchFileName will check the given file name with the stored regex
|
|
||||||
func (header *Header) MatchFileName(filename string) bool {
|
|
||||||
if header.FileNameRegex != nil {
|
|
||||||
return header.FileNameRegex.MatchString(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (header *Header) MatchFileHeader(firstLine []byte) bool {
|
|
||||||
if header.HeaderRegex != nil {
|
|
||||||
return header.HeaderRegex.Match(firstLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasFileSignature checks the presence of a stored signature
|
|
||||||
func (header *Header) HasFileSignature() bool {
|
|
||||||
return header.SignatureRegex != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchFileSignature will check the given line with the stored regex
|
|
||||||
func (header *Header) MatchFileSignature(line []byte) bool {
|
|
||||||
if header.SignatureRegex != nil {
|
|
||||||
return header.SignatureRegex.Match(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseFile(input []byte) (f *File, err error) {
|
func ParseFile(input []byte) (f *File, err error) {
|
||||||
// This is just so if we have an error, we can exit cleanly and return the parse error to the user
|
// This is just so if we have an error, we can exit cleanly and return the parse error to the user
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -209,19 +170,11 @@ func ParseFile(input []byte) (f *File, err error) {
|
||||||
if k == "filetype" {
|
if k == "filetype" {
|
||||||
filetype := v.(string)
|
filetype := v.(string)
|
||||||
|
|
||||||
if filetype == "" {
|
|
||||||
return nil, errors.New("empty filetype")
|
|
||||||
}
|
|
||||||
|
|
||||||
f.FileType = filetype
|
f.FileType = filetype
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.FileType == "" {
|
|
||||||
return nil, errors.New("missing filetype")
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, err
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,12 +191,12 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
src := f.yamlSrc
|
rules := f.yamlSrc
|
||||||
|
|
||||||
s = new(Def)
|
s = new(Def)
|
||||||
s.Header = header
|
s.Header = header
|
||||||
|
|
||||||
for k, v := range src {
|
for k, v := range rules {
|
||||||
if k == "rules" {
|
if k == "rules" {
|
||||||
inputRules := v.([]interface{})
|
inputRules := v.([]interface{})
|
||||||
|
|
||||||
|
@ -256,11 +209,6 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.rules == nil {
|
|
||||||
// allow empty rules
|
|
||||||
s.rules = new(rules)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,10 +303,6 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
|
||||||
|
|
||||||
switch object := val.(type) {
|
switch object := val.(type) {
|
||||||
case string:
|
case string:
|
||||||
if object == "" {
|
|
||||||
return nil, fmt.Errorf("Empty rule %s", k)
|
|
||||||
}
|
|
||||||
|
|
||||||
if k == "include" {
|
if k == "include" {
|
||||||
ru.includes = append(ru.includes, object)
|
ru.includes = append(ru.includes, object)
|
||||||
} else {
|
} else {
|
||||||
|
@ -412,56 +356,30 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
|
||||||
r.group = groupNum
|
r.group = groupNum
|
||||||
r.parent = prevRegion
|
r.parent = prevRegion
|
||||||
|
|
||||||
// start is mandatory
|
r.start, err = regexp.Compile(regionInfo["start"].(string))
|
||||||
if start, ok := regionInfo["start"]; ok {
|
|
||||||
start := start.(string)
|
|
||||||
if start == "" {
|
|
||||||
return nil, fmt.Errorf("Empty start in %s", group)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.start, err = regexp.Compile(start)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("Missing start in %s", group)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// end is mandatory
|
r.end, err = regexp.Compile(regionInfo["end"].(string))
|
||||||
if end, ok := regionInfo["end"]; ok {
|
|
||||||
end := end.(string)
|
|
||||||
if end == "" {
|
|
||||||
return nil, fmt.Errorf("Empty end in %s", group)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.end, err = regexp.Compile(end)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("Missing end in %s", group)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip is optional
|
// skip is optional
|
||||||
if skip, ok := regionInfo["skip"]; ok {
|
if _, ok := regionInfo["skip"]; ok {
|
||||||
skip := skip.(string)
|
r.skip, err = regexp.Compile(regionInfo["skip"].(string))
|
||||||
if skip == "" {
|
|
||||||
return nil, fmt.Errorf("Empty skip in %s", group)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.skip, err = regexp.Compile(skip)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// limit-color is optional
|
// limit-color is optional
|
||||||
if groupStr, ok := regionInfo["limit-group"]; ok {
|
if _, ok := regionInfo["limit-group"]; ok {
|
||||||
groupStr := groupStr.(string)
|
groupStr := regionInfo["limit-group"].(string)
|
||||||
if groupStr == "" {
|
|
||||||
return nil, fmt.Errorf("Empty limit-group in %s", group)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := Groups[groupStr]; !ok {
|
if _, ok := Groups[groupStr]; !ok {
|
||||||
numGroups++
|
numGroups++
|
||||||
Groups[groupStr] = numGroups
|
Groups[groupStr] = numGroups
|
||||||
|
|
|
@ -11,7 +11,6 @@ color-link special "#A6E22E,#1D1F21"
|
||||||
color-link underlined "#D33682,#1D1F21"
|
color-link underlined "#D33682,#1D1F21"
|
||||||
color-link error "bold #FF4444,#1D1F21"
|
color-link error "bold #FF4444,#1D1F21"
|
||||||
color-link todo "bold #FF8844,#1D1F21"
|
color-link todo "bold #FF8844,#1D1F21"
|
||||||
color-link hlsearch "#000000,#B4EC85"
|
|
||||||
color-link statusline "#1D1F21,#C5C8C6"
|
color-link statusline "#1D1F21,#C5C8C6"
|
||||||
color-link tabbar "#1D1F21,#C5C8C6"
|
color-link tabbar "#1D1F21,#C5C8C6"
|
||||||
color-link indent-char "#505050,#1D1F21"
|
color-link indent-char "#505050,#1D1F21"
|
||||||
|
@ -28,6 +27,3 @@ color-link color-column "#2D2F31"
|
||||||
#No extended types (bool in C, etc.)
|
#No extended types (bool in C, etc.)
|
||||||
#color-link type.extended "default"
|
#color-link type.extended "default"
|
||||||
#Plain brackets
|
#Plain brackets
|
||||||
color-link match-brace "#1D1F21,#62B1FE"
|
|
||||||
color-link tab-error "#D75F5F"
|
|
||||||
color-link trailingws "#D75F5F"
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ color-link special "167,231"
|
||||||
color-link error "231, 160"
|
color-link error "231, 160"
|
||||||
color-link underlined "underline 241,231"
|
color-link underlined "underline 241,231"
|
||||||
color-link todo "246,231"
|
color-link todo "246,231"
|
||||||
color-link hlsearch "231,136"
|
|
||||||
color-link statusline "241,254"
|
color-link statusline "241,254"
|
||||||
color-link tabbar "241,254"
|
color-link tabbar "241,254"
|
||||||
color-link diff-added "34"
|
color-link diff-added "34"
|
||||||
|
@ -26,6 +25,3 @@ color-link color-column "254"
|
||||||
#No extended types (bool in C, &c.) and plain brackets
|
#No extended types (bool in C, &c.) and plain brackets
|
||||||
color-link type.extended "241,231"
|
color-link type.extended "241,231"
|
||||||
color-link symbol.brackets "241,231"
|
color-link symbol.brackets "241,231"
|
||||||
color-link match-brace "231,28"
|
|
||||||
color-link tab-error "210"
|
|
||||||
color-link trailingws "210"
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ color-link special "magenta"
|
||||||
color-link ignore "default"
|
color-link ignore "default"
|
||||||
color-link error "bold ,brightred"
|
color-link error "bold ,brightred"
|
||||||
color-link todo "underline black,brightyellow"
|
color-link todo "underline black,brightyellow"
|
||||||
color-link hlsearch "white,darkgreen"
|
|
||||||
color-link indent-char ",brightgreen"
|
color-link indent-char ",brightgreen"
|
||||||
color-link line-number "green"
|
color-link line-number "green"
|
||||||
color-link line-number.scrollbar "green"
|
color-link line-number.scrollbar "green"
|
||||||
|
@ -42,6 +41,3 @@ color-link gutter-warning "red"
|
||||||
color-link color-column "cyan"
|
color-link color-column "cyan"
|
||||||
color-link underlined.url "underline blue, white"
|
color-link underlined.url "underline blue, white"
|
||||||
color-link divider "blue"
|
color-link divider "blue"
|
||||||
color-link match-brace "black,cyan"
|
|
||||||
color-link tab-error "brightred"
|
|
||||||
color-link trailingws "brightred"
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ color-link special "#b57edc"
|
||||||
color-link ignore "default"
|
color-link ignore "default"
|
||||||
color-link error "bold ,#e34234"
|
color-link error "bold ,#e34234"
|
||||||
color-link todo "bold underline #888888,#f26522"
|
color-link todo "bold underline #888888,#f26522"
|
||||||
color-link hlsearch "#b7b7b7,#32593d"
|
|
||||||
color-link indent-char ",#bdecb6"
|
color-link indent-char ",#bdecb6"
|
||||||
color-link line-number "#bdecb6,#36393e"
|
color-link line-number "#bdecb6,#36393e"
|
||||||
color-link line-number.scrollbar "#3eb489"
|
color-link line-number.scrollbar "#3eb489"
|
||||||
|
@ -38,6 +37,3 @@ color-link color-column "#f26522"
|
||||||
color-link constant.bool "bold #55ffff"
|
color-link constant.bool "bold #55ffff"
|
||||||
color-link constant.bool.true "bold #85ff85"
|
color-link constant.bool.true "bold #85ff85"
|
||||||
color-link constant.bool.false "bold #ff8585"
|
color-link constant.bool.false "bold #ff8585"
|
||||||
color-link match-brace "#1e2124,#55ffff"
|
|
||||||
color-link tab-error "#d75f5f"
|
|
||||||
color-link trailingws "#d75f5f"
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ color-link special "#CC8242,#242424"
|
||||||
color-link underlined "#D33682,#242424"
|
color-link underlined "#D33682,#242424"
|
||||||
color-link error "bold #CB4B16,#242424"
|
color-link error "bold #CB4B16,#242424"
|
||||||
color-link todo "bold #D33682,#242424"
|
color-link todo "bold #D33682,#242424"
|
||||||
color-link hlsearch "#CCCCCC,#32593D"
|
|
||||||
color-link statusline "#242424,#CCCCCC"
|
color-link statusline "#242424,#CCCCCC"
|
||||||
color-link tabbar "#242424,#CCCCCC"
|
color-link tabbar "#242424,#CCCCCC"
|
||||||
color-link indent-char "#4F4F4F,#242424"
|
color-link indent-char "#4F4F4F,#242424"
|
||||||
|
@ -29,6 +28,3 @@ color-link color-column "#2C2C2C"
|
||||||
color-link type.extended "default"
|
color-link type.extended "default"
|
||||||
#color-link symbol.brackets "default"
|
#color-link symbol.brackets "default"
|
||||||
color-link symbol.tag "#AE81FF,#242424"
|
color-link symbol.tag "#AE81FF,#242424"
|
||||||
color-link match-brace "#242424,#7A9EC2"
|
|
||||||
color-link tab-error "#D75F5F"
|
|
||||||
color-link trailingws "#D75F5F"
|
|
||||||
|
|
|
@ -1 +1,30 @@
|
||||||
include "monokai"
|
color-link default "#F8F8F2,#282828"
|
||||||
|
color-link comment "#75715E,#282828"
|
||||||
|
color-link identifier "#66D9EF,#282828"
|
||||||
|
color-link constant "#AE81FF,#282828"
|
||||||
|
color-link constant.string "#E6DB74,#282828"
|
||||||
|
color-link constant.string.char "#BDE6AD,#282828"
|
||||||
|
color-link statement "#F92672,#282828"
|
||||||
|
color-link symbol "#F92672,#282828"
|
||||||
|
color-link preproc "#CB4B16,#282828"
|
||||||
|
color-link type "#66D9EF,#282828"
|
||||||
|
color-link special "#A6E22E,#282828"
|
||||||
|
color-link underlined "#D33682,#282828"
|
||||||
|
color-link error "bold #CB4B16,#282828"
|
||||||
|
color-link todo "bold #D33682,#282828"
|
||||||
|
color-link statusline "#282828,#F8F8F2"
|
||||||
|
color-link tabbar "#282828,#F8F8F2"
|
||||||
|
color-link indent-char "#505050,#282828"
|
||||||
|
color-link line-number "#AAAAAA,#323232"
|
||||||
|
color-link current-line-number "#AAAAAA,#282828"
|
||||||
|
color-link diff-added "#00AF00"
|
||||||
|
color-link diff-modified "#FFAF00"
|
||||||
|
color-link diff-deleted "#D70000"
|
||||||
|
color-link gutter-error "#CB4B16,#282828"
|
||||||
|
color-link gutter-warning "#E6DB74,#282828"
|
||||||
|
color-link cursor-line "#323232"
|
||||||
|
color-link color-column "#323232"
|
||||||
|
#No extended types; Plain brackets.
|
||||||
|
color-link type.extended "default"
|
||||||
|
#color-link symbol.brackets "default"
|
||||||
|
color-link symbol.tag "#AE81FF,#282828"
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
color-link default "#F8F8F2,#282A36"
|
|
||||||
color-link comment "#6272A4"
|
|
||||||
|
|
||||||
color-link identifier "#50FA7B"
|
|
||||||
color-link identifier.class "#8BE9FD"
|
|
||||||
color-link identifier.var "#F8F8F2"
|
|
||||||
|
|
||||||
color-link constant "#BD93F9"
|
|
||||||
color-link constant.number "#F8F8F2"
|
|
||||||
color-link constant.string "#F1FA8C"
|
|
||||||
|
|
||||||
color-link symbol "#FF79C6"
|
|
||||||
color-link symbol.brackets "#F8F8F2"
|
|
||||||
color-link symbol.tag "#AE81FF"
|
|
||||||
|
|
||||||
color-link type "italic #8BE9FD"
|
|
||||||
color-link type.keyword "#FF79C6"
|
|
||||||
|
|
||||||
color-link special "#FF79C6"
|
|
||||||
color-link statement "#FF79C6"
|
|
||||||
color-link preproc "#FF79C6"
|
|
||||||
|
|
||||||
color-link underlined "#FF79C6"
|
|
||||||
color-link error "bold #FF5555"
|
|
||||||
color-link todo "bold #FF79C6"
|
|
||||||
|
|
||||||
color-link hlsearch "#282A36,#50FA7B"
|
|
||||||
|
|
||||||
color-link diff-added "#50FA7B"
|
|
||||||
color-link diff-modified "#FFB86C"
|
|
||||||
color-link diff-deleted "#FF5555"
|
|
||||||
|
|
||||||
color-link gutter-error "#FF5555"
|
|
||||||
color-link gutter-warning "#E6DB74"
|
|
||||||
|
|
||||||
color-link statusline "#282A36,#F8F8F2"
|
|
||||||
color-link tabbar "#282A36,#F8F8F2"
|
|
||||||
color-link indent-char "#6272A4"
|
|
||||||
color-link line-number "#6272A4"
|
|
||||||
color-link current-line-number "#F8F8F2"
|
|
||||||
|
|
||||||
color-link cursor-line "#44475A,#F8F8F2"
|
|
||||||
color-link color-column "#44475A"
|
|
||||||
color-link type.extended "default"
|
|
||||||
|
|
||||||
color-link match-brace "#282A36,#FF79C6"
|
|
||||||
|
|
||||||
color-link tab-error "#D75F5F"
|
|
||||||
color-link trailingws "#D75F5F"
|
|
|
@ -15,7 +15,6 @@ color-link divider "#001e28,#d0d0d0"
|
||||||
color-link error "#cb4b16,#001e28"
|
color-link error "#cb4b16,#001e28"
|
||||||
color-link gutter-error "#cb4b16,#001e28"
|
color-link gutter-error "#cb4b16,#001e28"
|
||||||
color-link gutter-warning "#fce94f,#001e28"
|
color-link gutter-warning "#fce94f,#001e28"
|
||||||
color-link hlsearch "#ffffff,#005028"
|
|
||||||
color-link identifier "#00c8a0,#001e28"
|
color-link identifier "#00c8a0,#001e28"
|
||||||
color-link identifier.class "#00c8a0,#001e28"
|
color-link identifier.class "#00c8a0,#001e28"
|
||||||
color-link indent-char "#a0a0a0,#001e28"
|
color-link indent-char "#a0a0a0,#001e28"
|
||||||
|
@ -33,6 +32,3 @@ color-link type "bold #3cc83c,#001e28"
|
||||||
color-link type.keyword "bold #5aaae6,#001e28"
|
color-link type.keyword "bold #5aaae6,#001e28"
|
||||||
color-link type.extended "#ffffff,#001e28"
|
color-link type.extended "#ffffff,#001e28"
|
||||||
color-link underlined "#608b4e,#001e28"
|
color-link underlined "#608b4e,#001e28"
|
||||||
color-link match-brace "#001e28,#5aaae6"
|
|
||||||
color-link tab-error "#d75f5f"
|
|
||||||
color-link trailingws "#d75f5f"
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ color-link divider "#f0f0f0,#004080"
|
||||||
color-link error "#500000,#f0f0f0"
|
color-link error "#500000,#f0f0f0"
|
||||||
color-link gutter-error "#500000,#f0f0f0"
|
color-link gutter-error "#500000,#f0f0f0"
|
||||||
color-link gutter-warning "#dcc800,#f0f0f0"
|
color-link gutter-warning "#dcc800,#f0f0f0"
|
||||||
color-link hlsearch "#000000,#b8d8e8"
|
|
||||||
color-link identifier "bold #0078a0,#f0f0f0"
|
color-link identifier "bold #0078a0,#f0f0f0"
|
||||||
color-link identifier.class "bold #0078a0,#f0f0f0"
|
color-link identifier.class "bold #0078a0,#f0f0f0"
|
||||||
color-link indent-char "#404040,#f0f0f0"
|
color-link indent-char "#404040,#f0f0f0"
|
||||||
|
@ -33,6 +32,3 @@ color-link type "bold #004080,#f0f0f0"
|
||||||
color-link type.keyword "bold #780050,#f0f0f0"
|
color-link type.keyword "bold #780050,#f0f0f0"
|
||||||
color-link type.extended "#000000,#f0f0f0"
|
color-link type.extended "#000000,#f0f0f0"
|
||||||
color-link underlined "#3f7f5f,#f0f0f0"
|
color-link underlined "#3f7f5f,#f0f0f0"
|
||||||
color-link match-brace "#f0f0f0,#780050"
|
|
||||||
color-link tab-error "#ff8787"
|
|
||||||
color-link trailingws "#ff8787"
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ color-link divider "#2d0023,#d0d0d0"
|
||||||
color-link error "#cb4b16,#2d0023"
|
color-link error "#cb4b16,#2d0023"
|
||||||
color-link gutter-error "#cb4b16,#2d0023"
|
color-link gutter-error "#cb4b16,#2d0023"
|
||||||
color-link gutter-warning "#fce94f,#2d0023"
|
color-link gutter-warning "#fce94f,#2d0023"
|
||||||
color-link hlsearch "#ffffff,#005028"
|
|
||||||
color-link identifier "#00c8a0,#2d0023"
|
color-link identifier "#00c8a0,#2d0023"
|
||||||
color-link identifier.class "#00c8a0,#2d0023"
|
color-link identifier.class "#00c8a0,#2d0023"
|
||||||
color-link indent-char "#a0a0a0,#2d0023"
|
color-link indent-char "#a0a0a0,#2d0023"
|
||||||
|
@ -33,6 +32,3 @@ color-link type "bold #3cc83c,#2d0023"
|
||||||
color-link type.keyword "bold #5aaae6,#2d0023"
|
color-link type.keyword "bold #5aaae6,#2d0023"
|
||||||
color-link type.extended "#ffffff,#2d0023"
|
color-link type.extended "#ffffff,#2d0023"
|
||||||
color-link underlined "#886484,#2d0023"
|
color-link underlined "#886484,#2d0023"
|
||||||
color-link match-brace "#2d0023,#5aaae6"
|
|
||||||
color-link tab-error "#d75f5f"
|
|
||||||
color-link trailingws "#d75f5f"
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ color-link type "blue"
|
||||||
color-link type.extended "default"
|
color-link type.extended "default"
|
||||||
color-link error "red"
|
color-link error "red"
|
||||||
color-link todo "bold cyan"
|
color-link todo "bold cyan"
|
||||||
color-link hlsearch "black,brightcyan"
|
|
||||||
color-link indent-char "bold black"
|
color-link indent-char "bold black"
|
||||||
color-link line-number ""
|
color-link line-number ""
|
||||||
color-link current-line-number ""
|
color-link current-line-number ""
|
||||||
|
@ -24,6 +23,3 @@ color-link diff-modified "yellow"
|
||||||
color-link diff-deleted "red"
|
color-link diff-deleted "red"
|
||||||
color-link gutter-error ",red"
|
color-link gutter-error ",red"
|
||||||
color-link gutter-warning "red"
|
color-link gutter-warning "red"
|
||||||
color-link match-brace "black,cyan"
|
|
||||||
color-link tab-error "brightred"
|
|
||||||
color-link trailingws "brightred"
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue