Compare commits

..

No commits in common. "master" and "v2.0.9" have entirely different histories.

278 changed files with 11265 additions and 8535 deletions

View File

@ -1,46 +0,0 @@
name: Nightly builds
on:
workflow_dispatch: # Allows manual trigger
schedule:
- cron: '0 0 * * *'
jobs:
nightly:
strategy:
matrix:
go-version: [1.23.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Setup
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: false
- name: Checkout
uses: actions/checkout@v4
with:
ref: master
fetch-depth: 0
fetch-tags: true
- name: Build
run: tools/cross-compile.sh nightly
- name: Tag
uses: rickstaa/action-create-tag@v1
with:
tag: nightly
force_push_tag: true
message: "Pre-release nightly"
- name: Publish
uses: softprops/action-gh-release@v2
with:
name: nightly
tag_name: nightly
files: binaries/*
prerelease: true
- name: Cleanup
run: rm -rf binaries

View File

@ -1,36 +0,0 @@
name: Release builds
on:
workflow_dispatch: # Allows manual trigger
# push:
# tags:
# - 'v*.*.*' # automatically react on semantic versioned tags
jobs:
release:
strategy:
matrix:
go-version: [1.23.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Setup
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: false
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Build
run: tools/cross-compile.sh
- name: Publish
uses: softprops/action-gh-release@v2
with:
files: binaries/*
- name: Cleanup
run: rm -rf binaries

View File

@ -1,27 +0,0 @@
on: [push, pull_request]
name: Build and Test
jobs:
test:
strategy:
matrix:
go-version: [1.19.x, 1.23.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: false
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Build
run: |
make build
- name: Test
run: |
make test

1
.gitignore vendored
View File

@ -15,5 +15,6 @@ 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

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "tools/go-bindata"]
path = tools/go-bindata
url = https://github.com/zyedidia/go-bindata

11
.travis.yml Normal file
View File

@ -0,0 +1,11 @@
language: go
go:
- "1.13.x"
os:
- linux
- osx
- windows
script:
- go build ./cmd/micro
- go test ./internal/...
- go test ./cmd/...

View File

@ -430,7 +430,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
github.com/gdamore/tcell/LICENSE github.com/gdamore/tcell/LICENSE
================ ================
github.com/micro-editor/tcell/LICENSE (fork) github.com/zyedidia/tcell/LICENSE (fork)
================ ================
@ -1048,7 +1048,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
github.com/flynn/json5/LICENSE github.com/flynn/json5/LICENSE
================ ================
github.com/micro-editor/json5/LICENSE (fork) github.com/zyedidia/json5/LICENSE (fork)
================ ================
Decoder code based on package encoding/json from the Go language. Decoder code based on package encoding/json from the Go language.
@ -1108,7 +1108,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
github.com/james4k/terminal/LICENSE github.com/james4k/terminal/LICENSE
================ ================
github.com/micro-editor/terminal/LICENSE (fork) github.com/zyedidia/terminal/LICENSE (fork)
================ ================
Copyright (C) 2013 James Gray Copyright (C) 2013 James Gray

View File

@ -1,48 +1,58 @@
.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)
HASH = $(shell git rev-parse --short HEAD) 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) \
GOARCH=$(shell go env GOHOSTARCH) \
go run tools/info-plist.go "$(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/'
CGO_ENABLED := $(if $(CGO_ENABLED),$(CGO_ENABLED),0)
ADDITIONAL_GO_LINKER_FLAGS := "" # Builds micro after checking dependencies but without updating the runtime
GOHOSTOS = $(shell go env GOHOSTOS) build:
ifeq ($(GOHOSTOS), darwin) go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Native darwin resp. macOS builds need external and dynamic linking
ADDITIONAL_GO_LINKER_FLAGS += $(shell GOOS=$(GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH) \
go run tools/info-plist.go "$(shell go env GOOS)" "$(VERSION)")
CGO_ENABLED = 1
endif
build: generate build-quick
build-quick:
CGO_ENABLED=$(CGO_ENABLED) go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-dbg: build-dbg:
CGO_ENABLED=$(CGO_ENABLED) go build -trimpath -ldflags "$(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro go build -trimpath -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
build-tags: fetch-tags build build-tags: fetch-tags
go build -trimpath -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 -trimpath -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 --force git fetch --tags
generate: # Builds the runtime
GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime runtime:
git submodule update --init
rm -f runtime/syntax/*.hdr
go run runtime/syntax/make_headers.go runtime/syntax
go build -o tools/bindata ./tools/go-bindata
tools/bindata -pkg config -nomemcopy -nometadata -o runtime.go runtime/...
mv runtime.go internal/config
gofmt -w internal/config/runtime.go
testgen: testgen:
mkdir -p tools/vscode-tests mkdir -p tools/vscode-tests

145
README.md
View File

@ -1,6 +1,6 @@
<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)
@ -18,9 +18,25 @@ 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](https://micro-editor.github.io).
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.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Prebuilt binaries](#prebuilt-binaries)
- [Package Managers](#package-managers)
- [Building from source](#building-from-source)
- [Fully static binary](#fully-static-binary)
- [macOS terminal](#macos-terminal)
- [Linux clipboard support](#linux-clipboard-support)
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
- [Cygwin, Mingw, Plan9](#cygwin-mingw-plan9)
- [Usage](#usage)
- [Documentation and Help](#documentation-and-help)
- [Contributing](#contributing)
- - - - - -
## Features ## Features
@ -37,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.
@ -47,12 +63,11 @@ You can also check out the website for Micro at https://micro-editor.github.io.
- Syntax highlighting for over [130 languages](runtime/syntax). - Syntax highlighting for over [130 languages](runtime/syntax).
- Color scheme support. - Color scheme support.
- By default, micro comes with 16, 256, and true color themes. - By default, micro comes with 16, 256, and true color themes.
- True color support. - True color support (set the `MICRO_TRUECOLOR` environment variable to 1 to enable it).
- Copy and paste with the system clipboard. - Copy and paste with the system clipboard.
- 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
@ -66,13 +81,17 @@ 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. A desktop entry file and man page can be found in the [assets/packaging](https://github.com/zyedidia/micro/tree/master/assets/packaging) directory.
### Pre-built binaries ### Prebuilt binaries
Pre-built binaries are distributed in [releases](https://github.com/zyedidia/micro/releases). All you need to install micro is one file, the binary itself. It's as simple as that!
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`. Download the binary from the [releases](https://github.com/zyedidia/micro/releases) page.
#### Third-party quick-install script ### Installation script
There is a script which can install micro for you by downloading the latest prebuilt binary. You can find it at <https://getmic.ro>.
You can easily install micro by running
```bash ```bash
curl https://getmic.ro | bash curl https://getmic.ro | bash
@ -80,24 +99,7 @@ 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. 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.
#### Eget To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
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
@ -117,52 +119,32 @@ 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.
* `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: * `dnf install micro` (Fedora).
* distro-specific package managers: * `pacman -S micro` (Arch Linux).
* `dnf install micro` (Fedora). * `eopkg install micro` (Solus).
* `apt install micro` (Ubuntu and Debian). * See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
* `pacman -S micro` (Arch Linux). * Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop).
* `emerge app-editors/micro` (Gentoo).
* `zypper install micro-editor` (SUSE)
* `eopkg install micro` (Solus).
* `pacstall -I micro` (Pacstall).
* `apt-get install micro` (ALT Linux)
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
* distro-agnostic package managers:
* `nix profile install nixpkgs#micro` (with [Nix](https://nixos.org/) and flakes enabled)
* `flox install micro` (with [Flox](https://flox.dev))
* Windows: [Chocolatey](https://chocolatey.org), [Scoop](https://scoop.sh/) and [WinGet](https://learn.microsoft.com/en-us/windows/package-manager/winget/).
* `choco install micro`. * `choco install micro`.
* `scoop install micro`. * `scoop install micro`.
* `winget install zyedidia.micro`
* OpenBSD: Available in the ports tree and also available as a binary package. * OpenBSD: Available in the ports tree and also available as a binary package.
* `pkg_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: Available in package managers.
* `sudo port install micro` (with [MacPorts](https://www.macports.org))
* `brew install micro` (with [Homebrew](https://brew.sh/))
* `nix profile install nixpkgs#micro` (with [Nix](https://nixos.org/) and flakes enabled)
* `flox install micro` (with [Flox](https://flox.dev))
**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.19 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
@ -180,20 +162,16 @@ You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/mi
recommended because it doesn't build micro with version information (necessary for the plugin manager), recommended because it doesn't build micro with version information (necessary for the plugin manager),
and doesn't disable debug mode. and doesn't disable debug mode.
### Fully static or dynamically linked binary ### Fully static binary
By default, the micro binary is linked statically to increase the portability of the prebuilt binaries. By default, the micro binary will dynamically link with core system libraries (this is generally
This behavior can simply be overriden by providing `CGO_ENABLED=1` to the build target. recommended for security and portability). However, there is a fully static prebuilt binary that
is provided for amd64 as `linux-static.tar.gz`, and to build a fully static binary from source, run
``` ```
CGO_ENABLED=1 make build CGO_ENABLED=0 make build
``` ```
Afterwards the micro binary will dynamically link with the present core system libraries.
**Note for Mac:**
Native macOS builds are done with `CGO_ENABLED=1` forced set to support adding the "Information Property List" in the linker step.
### macOS terminal ### macOS terminal
If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Presets...`, and select `Esc+` for `Left Option Key` in the same menu. The newest versions also support true color. If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Presets...`, and select `Esc+` for `Left Option Key` in the same menu. The newest versions also support true color.
@ -201,17 +179,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
@ -219,7 +194,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
@ -238,7 +213,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
@ -247,7 +222,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.
@ -271,8 +246,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.
@ -280,6 +253,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.

View File

@ -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

View File

@ -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 103.2 103.2" viewBox="0 0 103.2 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-mark.svg" sodipodi:docname="micro-logo-notext.svg"
width="103.2" width="103.2"
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="metadata9"><rdf:RDF><cc:Work id="metadata9"><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="defs7" /><sodipodi:namedview id="defs7" /><sodipodi:namedview
pagecolor="#ffffff" pagecolor="#ffffff"
bordercolor="#666666" bordercolor="#666666"
@ -32,28 +32,22 @@
guidetolerance="10" guidetolerance="10"
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:window-width="1920" inkscape:window-width="733"
inkscape:window-height="1080" inkscape:window-height="480"
id="namedview5" id="namedview5"
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="5.405335" inkscape:zoom="0.28541667"
inkscape:cx="75.573484" inkscape:cx="302"
inkscape:cy="51.153166" inkscape:cy="-4"
inkscape:window-x="0" inkscape:window-x="1699"
inkscape:window-y="0" inkscape:window-y="277"
inkscape:window-maximized="0" inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" inkscape:current-layer="Layer_1" /><path
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" 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" id="path3"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
style="fill:#2e3192" /><path style="fill:#2e3192" /></svg>
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

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
#!/bin/sh
set -e
if [ "$1" != "upgrade" ]; then
update-alternatives --remove editor /usr/bin/micro
fi

View File

@ -3,8 +3,8 @@ package main
import ( import (
"bufio" "bufio"
"encoding/gob" "encoding/gob"
"errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
@ -12,7 +12,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"
) )
func shouldContinue() bool { func shouldContinue() bool {
@ -24,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
@ -40,16 +42,7 @@ func CleanConfig() {
} }
fmt.Println("Cleaning default settings") fmt.Println("Cleaning default settings")
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
settingsFile := filepath.Join(config.ConfigDir, "settings.json")
err := config.WriteSettings(settingsFile)
if err != nil {
if errors.Is(err, util.ErrOverwrite) {
fmt.Println(err.Error())
} else {
fmt.Println("Error writing settings.json file: " + err.Error())
}
}
// detect unused options // detect unused options
var unusedOptions []string var unusedOptions []string
@ -77,20 +70,16 @@ func CleanConfig() {
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s]) fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
} }
fmt.Printf("These options will be removed from %s\n", settingsFile) fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
if shouldContinue() { if shouldContinue() {
for _, s := range unusedOptions { for _, s := range unusedOptions {
delete(config.GlobalSettings, s) delete(config.GlobalSettings, s)
} }
err := config.OverwriteSettings(settingsFile) err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
if err != nil { if err != nil {
if errors.Is(err, util.ErrOverwrite) { fmt.Println("Error writing settings.json file: " + err.Error())
fmt.Println(err.Error())
} else {
fmt.Println("Error overwriting settings.json file: " + err.Error())
}
} }
fmt.Println("Removed unused options") fmt.Println("Removed unused options")
@ -99,13 +88,12 @@ func CleanConfig() {
} }
// detect incorrectly formatted buffer/ files // detect incorrectly formatted buffer/ files
buffersPath := filepath.Join(config.ConfigDir, "buffers") files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
files, err := os.ReadDir(buffersPath)
if err == nil { if err == nil {
var badFiles []string var badFiles []string
var buffer buffer.SerializedBuffer var buffer buffer.SerializedBuffer
for _, f := range files { for _, f := range files {
fname := filepath.Join(buffersPath, f.Name()) fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
file, e := os.Open(fname) file, e := os.Open(fname)
if e == nil { if e == nil {
@ -120,9 +108,9 @@ func CleanConfig() {
} }
if len(badFiles) > 0 { if len(badFiles) > 0 {
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), buffersPath) fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
fmt.Println("These files store cursor and undo history.") fmt.Println("These files store cursor and undo history.")
fmt.Printf("Removing badly formatted files in %s\n", buffersPath) fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
if shouldContinue() { if shouldContinue() {
removed := 0 removed := 0

View File

@ -18,7 +18,7 @@ func (NullWriter) Write(data []byte) (n int, err error) {
// InitLog sets up the debug log system for micro if it has been enabled by compile-time variables // InitLog sets up the debug log system for micro if it has been enabled by compile-time variables
func InitLog() { func InitLog() {
if util.Debug == "ON" { if util.Debug == "ON" {
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, util.FileMode) f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil { if err != nil {
log.Fatalf("error opening file: %v", err) log.Fatalf("error opening file: %v", err)
} }

View File

@ -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,14 @@ 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, 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()) { ulua.L.SetField(pkg, "Lock", luar.New(ulua.L, ulua.Lock))
time.AfterFunc(t, func() {
timerChan <- f
})
}))
return pkg return pkg
} }
@ -155,7 +150,6 @@ func luaImportMicroUtil() *lua.LTable {
ulua.L.SetField(pkg, "Unzip", luar.New(ulua.L, util.Unzip)) 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, "Version", luar.New(ulua.L, util.Version))
ulua.L.SetField(pkg, "SemVersion", luar.New(ulua.L, util.SemVersion)) 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)

View File

@ -3,14 +3,12 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"io" "io/ioutil"
"log" "log"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"runtime/pprof"
"sort" "sort"
"strconv" "strconv"
"syscall" "syscall"
@ -18,31 +16,33 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
isatty "github.com/mattn/go-isatty" isatty "github.com/mattn/go-isatty"
"github.com/micro-editor/tcell/v2"
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/clipboard"
"github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/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"
) )
var ( var (
// Event channel
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
sighup chan os.Signal sigterm chan os.Signal
sighup chan os.Signal
timerChan chan func()
) )
func InitFlags() { func InitFlags() {
@ -59,13 +59,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]...")
@ -99,7 +96,7 @@ func InitFlags() {
fmt.Println("Version:", util.Version) fmt.Println("Version:", util.Version)
fmt.Println("Commit hash:", util.CommitHash) fmt.Println("Commit hash:", util.CommitHash)
fmt.Println("Compiled on", util.CompileDate) fmt.Println("Compiled on", util.CompileDate)
exit(0) os.Exit(0)
} }
if *flagOptions { if *flagOptions {
@ -115,7 +112,7 @@ func InitFlags() {
fmt.Printf("-%s value\n", k) fmt.Printf("-%s value\n", k)
fmt.Printf(" \tDefault value: '%v'\n", v) fmt.Printf(" \tDefault value: '%v'\n", v)
} }
exit(0) os.Exit(0)
} }
if util.Debug == "OFF" && *flagDebug { if util.Debug == "OFF" && *flagDebug {
@ -136,7 +133,7 @@ func DoPluginFlags() {
CleanConfig() CleanConfig()
} }
exit(0) os.Exit(0)
} }
} }
@ -209,7 +206,7 @@ func LoadInput(args []string) []*buffer.Buffer {
// Option 2 // Option 2
// The input is not a terminal, so something is being piped in // The input is not a terminal, so something is being piped in
// and we should read from stdin // and we should read from stdin
input, err = io.ReadAll(os.Stdin) input, err = ioutil.ReadAll(os.Stdin)
if err != nil { if err != nil {
screen.TermMessage("Error reading from stdin: ", err) screen.TermMessage("Error reading from stdin: ", err)
input = []byte{} input = []byte{}
@ -223,72 +220,23 @@ func LoadInput(args []string) []*buffer.Buffer {
return buffers return buffers
} }
func checkBackup(name string) error {
target := filepath.Join(config.ConfigDir, name)
backup := util.AppendBackupSuffix(target)
if info, err := os.Stat(backup); err == nil {
input, err := os.ReadFile(backup)
if err == nil {
t := info.ModTime()
msg := fmt.Sprintf(buffer.BackupMsg, target, t.Format("Mon Jan _2 at 15:04, 2006"), backup)
choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)
if choice%3 == 0 {
// recover
err := os.WriteFile(target, input, util.FileMode)
if err != nil {
return err
}
return os.Remove(backup)
} else if choice%3 == 1 {
// delete
return os.Remove(backup)
} else if choice%3 == 2 {
// abort
return errors.New("Aborted")
}
}
}
return nil
}
func exit(rc int) {
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
if screen.Screen != nil {
screen.Screen.Fini()
}
os.Exit(rc)
}
func main() { func main() {
defer func() { defer func() {
if util.Stdout.Len() > 0 { if util.Stdout.Len() > 0 {
fmt.Fprint(os.Stdout, util.Stdout.String()) fmt.Fprint(os.Stdout, util.Stdout.String())
} }
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)
@ -296,15 +244,7 @@ func main() {
screen.TermMessage(err) screen.TermMessage(err)
} }
config.InitRuntimeFiles(true) config.InitRuntimeFiles()
config.InitPlugins()
err = checkBackup("settings.json")
if err != nil {
screen.TermMessage(err)
exit(1)
}
err = config.ReadSettings() err = config.ReadSettings()
if err != nil { if err != nil {
screen.TermMessage(err) screen.TermMessage(err)
@ -322,12 +262,7 @@ func main() {
screen.TermMessage(err) screen.TermMessage(err)
continue continue
} }
if err = config.OptionIsValid(k, nativeValue); err != nil {
screen.TermMessage(err)
continue
}
config.GlobalSettings[k] = nativeValue config.GlobalSettings[k] = nativeValue
config.VolatileSettings[k] = true
} }
} }
@ -337,8 +272,14 @@ func main() {
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.") fmt.Println("Fatal: Micro could not initialize a Screen.")
exit(1) os.Exit(1)
} }
sigterm = make(chan os.Signal, 1)
sighup = make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
signal.Notify(sighup, syscall.SIGHUP)
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string)) m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
clipErr := clipboard.Initialize(m) clipErr := clipboard.Initialize(m)
@ -356,7 +297,7 @@ func main() {
for _, b := range buffer.OpenBuffers { for _, b := range buffer.OpenBuffers {
b.Backup() b.Backup()
} }
exit(1) os.Exit(1)
} }
}() }()
@ -365,12 +306,6 @@ func main() {
screen.TermMessage(err) screen.TermMessage(err)
} }
err = checkBackup("bindings.json")
if err != nil {
screen.TermMessage(err)
exit(1)
}
action.InitBindings() action.InitBindings()
action.InitCommands() action.InitCommands()
@ -384,8 +319,6 @@ func main() {
screen.TermMessage(err) screen.TermMessage(err)
} }
action.InitGlobals()
buffer.SetMessager(action.InfoBar)
args := flag.Args() args := flag.Args()
b := LoadInput(args) b := LoadInput(args)
@ -396,6 +329,7 @@ func main() {
} }
action.InitTabs(b) action.InitTabs(b)
action.InitGlobals()
err = config.RunPluginFn("init") err = config.RunPluginFn("init")
if err != nil { if err != nil {
@ -411,20 +345,13 @@ func main() {
log.Println(clipErr, " or change 'clipboard' option") log.Println(clipErr, " or change 'clipboard' option")
} }
config.StartAutoSave()
if a := config.GetGlobalOption("autosave").(float64); a > 0 { if a := config.GetGlobalOption("autosave").(float64); a > 0 {
config.SetAutoTime(a) config.SetAutoTime(int(a))
config.StartAutoSave()
} }
screen.Events = make(chan tcell.Event) screen.Events = make(chan tcell.Event)
util.Sigterm = make(chan os.Signal, 1)
sighup = make(chan os.Signal, 1)
signal.Notify(util.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() {
for { for {
@ -475,52 +402,48 @@ func DoEvent() {
select { select {
case f := <-shell.Jobs: case f := <-shell.Jobs:
// If a new job has finished while running in the background we should execute the callback // If a new job has finished while running in the background we should execute the callback
ulua.Lock.Lock()
f.Function(f.Output, f.Args) f.Function(f.Output, f.Args)
ulua.Lock.Unlock()
case <-config.Autosave: case <-config.Autosave:
ulua.Lock.Lock()
for _, b := range buffer.OpenBuffers { for _, b := range buffer.OpenBuffers {
b.AutoSave() b.Save()
} }
ulua.Lock.Unlock()
case <-shell.CloseTerms: case <-shell.CloseTerms:
action.Tabs.CloseTerms()
case event = <-screen.Events: case event = <-screen.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 b := <-buffer.BackupCompleteChan:
b.RequestedBackup = false
case <-sighup: case <-sighup:
exit(0) for _, b := range buffer.OpenBuffers {
case <-util.Sigterm: if !b.Modified() {
exit(0) b.Fini()
} }
if e, ok := event.(*tcell.EventError); ok {
log.Println("tcell event error: ", e.Error())
if e.Err() == io.EOF {
// shutdown due to terminal closing/becoming inaccessible
exit(0)
} }
return os.Exit(0)
} case <-sigterm:
for _, b := range buffer.OpenBuffers {
if event != nil { if !b.Modified() {
_, resize := event.(*tcell.EventResize) b.Fini()
if resize { }
action.InfoBar.HandleEvent(event)
action.Tabs.HandleEvent(event)
} else if action.InfoBar.HasPrompt {
action.InfoBar.HandleEvent(event)
} else {
action.Tabs.HandleEvent(event)
} }
if screen.Screen != nil {
screen.Screen.Fini()
}
os.Exit(0)
} }
err := config.RunPluginFn("onAnyEvent") ulua.Lock.Lock()
if err != nil { // if event != nil {
screen.TermMessage(err) if action.InfoBar.HasPrompt {
action.InfoBar.HandleEvent(event)
} else {
action.Tabs.HandleEvent(event)
} }
// }
ulua.Lock.Unlock()
} }

View File

@ -2,17 +2,18 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"log" "log"
"os" "os"
"testing" "testing"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/micro-editor/tcell/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"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/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"
) )
var tempDir string var tempDir string
@ -25,7 +26,7 @@ func init() {
func startup(args []string) (tcell.SimulationScreen, error) { func startup(args []string) (tcell.SimulationScreen, error) {
var err error var err error
tempDir, err = os.MkdirTemp("", "micro_test") tempDir, err = ioutil.TempDir("", "micro_test")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -34,9 +35,7 @@ func startup(args []string) (tcell.SimulationScreen, error) {
return nil, err return nil, err
} }
config.InitRuntimeFiles(true) config.InitRuntimeFiles()
config.InitPlugins()
err = config.ReadSettings() err = config.ReadSettings()
if err != nil { if err != nil {
return nil, err return nil, err
@ -61,6 +60,7 @@ func startup(args []string) (tcell.SimulationScreen, error) {
} }
// Print the stack trace too // Print the stack trace too
log.Fatalf(errors.Wrap(err, 2).ErrorStack()) log.Fatalf(errors.Wrap(err, 2).ErrorStack())
os.Exit(1)
} }
}() }()
@ -108,10 +108,7 @@ func handleEvent() {
if e != nil { if e != nil {
screen.Events <- e screen.Events <- e
} }
DoEvent()
for len(screen.DrawChan()) > 0 || len(screen.Events) > 0 {
DoEvent()
}
} }
func injectKey(key tcell.Key, r rune, mod tcell.ModMask) { func injectKey(key tcell.Key, r rune, mod tcell.ModMask) {
@ -153,32 +150,20 @@ func openFile(file string) {
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone) injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
} }
func findBuffer(file string) *buffer.Buffer { func createTestFile(name string, content string) (string, error) {
var buf *buffer.Buffer testf, err := ioutil.TempFile("", name)
for _, b := range buffer.OpenBuffers {
if b.Path == file {
buf = b
}
}
return buf
}
func createTestFile(t *testing.T, content string) string {
f, err := os.CreateTemp(t.TempDir(), "")
if err != nil { if err != nil {
t.Fatal(err) return "", err
}
defer func() {
if err := f.Close(); err != nil {
t.Fatal(err)
}
}()
if _, err := f.WriteString(content); err != nil {
t.Fatal(err)
} }
return f.Name() 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) { func TestMain(m *testing.M) {
@ -186,6 +171,7 @@ func TestMain(m *testing.M) {
sim, err = startup([]string{}) sim, err = startup([]string{})
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
os.Exit(1)
} }
retval := m.Run() retval := m.Run()
@ -195,12 +181,25 @@ func TestMain(m *testing.M) {
} }
func TestSimpleEdit(t *testing.T) { func TestSimpleEdit(t *testing.T) {
file := createTestFile(t, "base content") file, err := createTestFile("micro_simple_edit_test", "base content")
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file) openFile(file)
if findBuffer(file) == nil { var buf *buffer.Buffer
t.Fatalf("Could not find buffer %s", file) for _, b := range buffer.OpenBuffers {
if b.Path == file {
buf = b
}
}
if buf == nil {
t.Errorf("Could not find buffer %s", file)
return
} }
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone) injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
@ -218,23 +217,25 @@ func TestSimpleEdit(t *testing.T) {
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl) injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := os.ReadFile(file) data, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
return
} }
assert.Equal(t, "firstfoobar\nbase content\n", string(data)) assert.Equal(t, "firstfoobar\nbase content\n", string(data))
} }
func TestMouse(t *testing.T) { func TestMouse(t *testing.T) {
file := createTestFile(t, "base content") file, err := createTestFile("micro_mouse_test", "base content")
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file) openFile(file)
if findBuffer(file) == nil {
t.Fatalf("Could not find buffer %s", file)
}
// buffer: // buffer:
// base content // base content
// the selections need to happen at different locations to avoid a double click // the selections need to happen at different locations to avoid a double click
@ -263,9 +264,10 @@ func TestMouse(t *testing.T) {
// base content // base content
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl) injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := os.ReadFile(file) data, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
return
} }
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data)) assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
@ -288,23 +290,25 @@ Ernleȝe test_string æðelen
` `
func TestSearchAndReplace(t *testing.T) { func TestSearchAndReplace(t *testing.T) {
file := createTestFile(t, srTestStart) file, err := createTestFile("micro_search_replace_test", srTestStart)
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file) openFile(file)
if findBuffer(file) == nil {
t.Fatalf("Could not find buffer %s", file)
}
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl) injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string")) injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string"))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone) injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl) injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := os.ReadFile(file) data, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
return
} }
assert.Equal(t, srTest2, string(data)) assert.Equal(t, srTest2, string(data))
@ -317,9 +321,10 @@ func TestSearchAndReplace(t *testing.T) {
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl) injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err = os.ReadFile(file) data, err = ioutil.ReadFile(file)
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
return
} }
assert.Equal(t, srTest3, string(data)) assert.Equal(t, srTest3, string(data))

View 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>

View File

@ -1,52 +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.14" date="2024-08-27"/>
<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>

View File

@ -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": "boolean",
"default": true
},
"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
}

41
go.mod
View File

@ -5,35 +5,28 @@ require (
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0
github.com/go-errors/errors v1.0.1 github.com/go-errors/errors v1.0.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.11
github.com/mattn/go-runewidth v0.0.16 github.com/mattn/go-runewidth v0.0.7
github.com/micro-editor/json5 v1.0.1-micro
github.com/micro-editor/tcell/v2 v2.0.11
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5
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/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/yuin/gopher-lua v1.1.1 github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
github.com/zyedidia/clipper v0.1.1 github.com/zyedidia/clipboard v1.0.3
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
golang.org/x/text v0.4.0 github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
gopkg.in/yaml.v2 v2.2.8 github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
layeh.com/gopher-luar v1.0.11 github.com/zyedidia/pty v2.0.0+incompatible // indirect
github.com/zyedidia/tcell/v2 v2.0.7
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
golang.org/x/text v0.3.2
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.2.7
layeh.com/gopher-luar v1.0.7
) )
require ( replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
github.com/creack/pty v1.1.18 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/zyedidia/poller v1.0.1 // indirect
golang.org/x/sys v0.28.0 // indirect
)
replace github.com/kballard/go-shellquote => github.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5 replace github.com/mattn/go-runewidth => github.com/zyedidia/go-runewidth v0.0.12
replace layeh.com/gopher-luar v1.0.11 => github.com/layeh/gopher-luar v1.0.11 go 1.11
go 1.19

72
go.sum
View File

@ -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,59 +17,69 @@ 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/layeh/gopher-luar v1.0.11 h1:ss6t9OtykOiETBScJylSMPhuYAtOmpH5rSX10/wCcis=
github.com/layeh/gopher-luar v1.0.11/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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5 h1:D7BPnsedXiKo/e8RTFX419/52ICNhU8UKPQGZ/0yiLc=
github.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5/go.mod h1:zaPgW/fDiW4MUfEwxpC+GB/bhvX44NJaNHmRAC9auHQ=
github.com/micro-editor/json5 v1.0.1-micro h1:5Y4MuzhkmW0sQQNPvrIVevIOKi557qsznwjRr4iq1AI=
github.com/micro-editor/json5 v1.0.1-micro/go.mod h1:cmlPHZ1JKOXNse0/3zwwKj/GUpzAVkzx4lZDkpHl4q0=
github.com/micro-editor/tcell/v2 v2.0.11 h1:USjdpBSmbocx2yPARbY19KcUSj+ZerScrdmBqGjzoX4=
github.com/micro-editor/tcell/v2 v2.0.11/go.mod h1:kVYk6NOwYJrboL/7IA7cCupk4o2NzyF/0UMLjeEJN/s=
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5 h1:czSkYUNmHuWS2lv8VreufENEXZNOCGZcXd744YKf8yM=
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5/go.mod h1:OszIG7ockt4osicVHq6gI2QmV4PBDK6H5/Bj8GDGv4Q=
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.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0= github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0 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/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/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8/go.mod h1:6Yhx5ZJl5942QrNRWLwITArVT9okUXc5c3brgWJMoDc=
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 v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= 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 v1.0.3 h1:F/nCDVYMdbDWTmY8s8cJl0tnwX32q96IF09JHM14bUI=
github.com/zyedidia/clipper v0.1.1/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ= github.com/zyedidia/clipboard v1.0.3/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/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/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/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
github.com/zyedidia/tcell/v2 v2.0.6 h1:v0GoNpPYJ+Wbd1RiSL09SUFzoq4eVKTuT5awbW6aqGs=
github.com/zyedidia/tcell/v2 v2.0.6/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/tcell/v2 v2.0.7 h1:kFzCRq9jgx5lOXBT8fVZidbTgVuX0ws++aMCj/MTCYY=
github.com/zyedidia/tcell/v2 v2.0.7/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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 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

View File

@ -1,4 +1,4 @@
//go:build plan9 || nacl || windows // +build plan9 nacl windows
package action package action

View File

@ -1,4 +1,4 @@
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd // +build linux darwin dragonfly solaris openbsd netbsd freebsd
package action package action

View File

@ -4,18 +4,17 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"unicode" "unicode"
"github.com/micro-editor/json5" "github.com/zyedidia/json5"
"github.com/micro-editor/tcell/v2"
"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/tcell/v2"
) )
var Binder = map[string]func(e Event, action string){ var Binder = map[string]func(e Event, action string){
@ -24,13 +23,9 @@ var Binder = map[string]func(e Event, action string){
"terminal": TermMapEvent, "terminal": TermMapEvent,
} }
func writeFile(name string, txt []byte) error {
return util.SafeWrite(name, txt, false)
}
func createBindingsIfNotExist(fname string) { func createBindingsIfNotExist(fname string) {
if _, e := os.Stat(fname); errors.Is(e, fs.ErrNotExist) { if _, e := os.Stat(fname); os.IsNotExist(e) {
writeFile(fname, []byte("{}")) ioutil.WriteFile(fname, []byte("{}"), 0644)
} }
} }
@ -42,7 +37,7 @@ func InitBindings() {
createBindingsIfNotExist(filename) createBindingsIfNotExist(filename)
if _, e := os.Stat(filename); e == nil { if _, e := os.Stat(filename); e == nil {
input, err := os.ReadFile(filename) input, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
screen.TermMessage("Error reading bindings.json file: " + err.Error()) screen.TermMessage("Error reading bindings.json file: " + err.Error())
return return
@ -93,10 +88,6 @@ func BindKey(k, v string, bind func(e Event, a string)) {
return return
} }
if strings.HasPrefix(k, "\x1b") {
screen.RegisterRawSeq(k)
}
bind(event, v) bind(event, v)
// switch e := event.(type) { // switch e := event.(type) {
@ -148,7 +139,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":
@ -162,6 +153,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
@ -181,35 +173,39 @@ modSearch:
// see if the key is in bindingKeys with the Ctrl prefix. // see if the key is in bindingKeys with the Ctrl prefix.
k = string(unicode.ToUpper(rune(k[0]))) + k[1:] k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
if code, ok := keyEvents["Ctrl"+k]; ok { if code, ok := keyEvents["Ctrl"+k]; ok {
var r tcell.Key
// Special case for escape, for some reason tcell doesn't send it with the esc character
if code < 256 && code != 27 {
r = code
}
// It is, we're done.
return KeyEvent{ return KeyEvent{
code: code, code: code,
mod: modifiers, mod: modifiers,
r: rune(r),
}, true }, true
} }
} }
// See if we can find the key in bindingKeys // See if we can find the key in bindingKeys
if code, ok := keyEvents[k]; ok { if code, ok := keyEvents[k]; ok {
var r tcell.Key
// Special case for escape, for some reason tcell doesn't send it with the esc character
if code < 256 && code != 27 {
r = code
}
return KeyEvent{ return KeyEvent{
code: code, code: code,
mod: modifiers, mod: modifiers,
r: rune(r),
}, 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
} }
@ -243,24 +239,6 @@ func findEvent(k string) (Event, error) {
return event, nil 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) {
@ -270,7 +248,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
filename := filepath.Join(config.ConfigDir, "bindings.json") filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename) createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil { if _, e = os.Stat(filename); e == nil {
input, err := os.ReadFile(filename) input, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return false, errors.New("Error reading bindings.json file: " + err.Error()) return false, errors.New("Error reading bindings.json file: " + err.Error())
} }
@ -286,31 +264,28 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
} }
found := false found := false
var ev string for ev := range parsed {
for ev = range parsed {
if e, err := findEvent(ev); err == nil { if e, err := findEvent(ev); err == nil {
if eventsEqual(e, key) { if 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, Binder["buffer"])
txt, _ := json.MarshalIndent(parsed, "", " ") txt, _ := json.MarshalIndent(parsed, "", " ")
txt = append(txt, '\n') return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
return true, writeFile(filename, txt)
} }
return false, e return false, e
} }
@ -323,7 +298,7 @@ func UnbindKey(k string) error {
filename := filepath.Join(config.ConfigDir, "bindings.json") filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename) createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil { if _, e = os.Stat(filename); e == nil {
input, err := os.ReadFile(filename) input, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return errors.New("Error reading bindings.json file: " + err.Error()) return errors.New("Error reading bindings.json file: " + err.Error())
} }
@ -340,17 +315,13 @@ func UnbindKey(k string) error {
for ev := range parsed { for ev := range parsed {
if e, err := findEvent(ev); err == nil { if e, err := findEvent(ev); err == nil {
if eventsEqual(e, key) { if e == key {
delete(parsed, ev) delete(parsed, ev)
break break
} }
} }
} }
if strings.HasPrefix(k, "\x1b") {
screen.UnregisterRawSeq(k)
}
defaults := DefaultBindings("buffer") defaults := DefaultBindings("buffer")
if a, ok := defaults[k]; ok { if a, ok := defaults[k]; ok {
BindKey(k, a, Binder["buffer"]) BindKey(k, a, Binder["buffer"])
@ -360,8 +331,7 @@ func UnbindKey(k string) error {
} }
txt, _ := json.MarshalIndent(parsed, "", " ") txt, _ := json.MarshalIndent(parsed, "", " ")
txt = append(txt, '\n') return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
return writeFile(filename, txt)
} }
return e return e
} }

View File

@ -6,35 +6,27 @@ import (
luar "layeh.com/gopher-luar" luar "layeh.com/gopher-luar"
"github.com/micro-editor/tcell/v2"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
"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/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/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 BufBindings *KeyTree var BufBindings *KeyTree
// BufKeyActionGeneral makes a general pane action from a BufKeyAction.
func BufKeyActionGeneral(a BufKeyAction) PaneKeyAction { func BufKeyActionGeneral(a BufKeyAction) PaneKeyAction {
return func(p Pane) bool { return func(p Pane) bool {
return a(p.(*BufPane)) return a(p.(*BufPane))
} }
} }
// BufMouseActionGeneral makes a general pane mouse action from a BufKeyAction.
func BufMouseActionGeneral(a BufMouseAction) PaneMouseAction { func BufMouseActionGeneral(a BufMouseAction) PaneMouseAction {
return func(p Pane, me *tcell.EventMouse) bool { return func(p Pane, me *tcell.EventMouse) bool {
return a(p.(*BufPane), me) return a(p.(*BufPane), me)
@ -45,9 +37,7 @@ func init() {
BufBindings = NewKeyTree() BufBindings = NewKeyTree()
} }
// 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 +47,33 @@ 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 an event to an action
func BufMapEvent(k Event, action string) { func BufMapEvent(k Event, action string) {
config.Bindings["buffer"][k.Name()] = action config.Bindings["buffer"][k.Name()] = action
var actionfns []BufAction switch e := k.(type) {
case KeyEvent, KeySequenceEvent, RawEvent:
bufMapKey(e, action)
case MouseEvent:
bufMapMouse(e, action)
}
}
func bufMapKey(k Event, action string) {
var actionfns []func(*BufPane) bool
var names []string var names []string
var types []byte var types []byte
for i := 0; ; i++ { for i := 0; ; i++ {
@ -100,7 +81,9 @@ func BufMapEvent(k Event, action string) {
break break
} }
idx := util.IndexAnyUnquoted(action, "&|,") // TODO: fix problem when complex bindings have these
// characters (escape them?)
idx := strings.IndexAny(action, "&|,")
a := action a := action
if idx >= 0 { if idx >= 0 {
a = action[:idx] a = action[:idx]
@ -111,7 +94,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)
@ -122,7 +105,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
@ -138,52 +121,47 @@ 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 { bufAction := func(h *BufPane) bool {
cursors := h.Buf.GetCursors()
success := true
for i, a := range actionfns { for i, a := range actionfns {
var success bool innerSuccess := true
if _, ok := MultiActions[names[i]]; ok { for j, c := range cursors {
success = true if c == nil {
for _, c := range h.Buf.GetCursors() { continue
h.Buf.SetCurCursor(c.Num) }
h.Cursor = c h.Buf.SetCurCursor(c.Num)
success = success && h.execAction(a, names[i], te) h.Cursor = c
if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
innerSuccess = innerSuccess && h.execAction(a, names[i], j)
} else {
break
} }
} else {
h.Buf.SetCurCursor(0)
h.Cursor = h.Buf.GetActiveCursor()
success = h.execAction(a, names[i], te)
} }
// 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()
if h == nil { success = innerSuccess
// stop, in case the current pane is not a BufPane
break
}
if (!success && types[i] == '&') || (success && types[i] == '|') {
break
}
} }
return true return true
} }
switch e := k.(type) { BufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(bufAction))
case KeyEvent, KeySequenceEvent, RawEvent: }
BufBindings.RegisterKeyBinding(e, BufKeyActionGeneral(func(h *BufPane) bool {
return bufAction(h, nil) // BufMapMouse maps a mouse event to an action
})) func bufMapMouse(k MouseEvent, action string) {
case MouseEvent: if f, ok := BufMouseActions[action]; ok {
BufBindings.RegisterMouseBinding(e, BufMouseActionGeneral(bufAction)) BufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
} else {
// TODO
// delete(BufMouseBindings, k)
bufMapKey(k, action)
} }
} }
@ -214,32 +192,36 @@ type BufPane struct {
// Cursor is the currently active buffer cursor // Cursor is the currently active buffer cursor
Cursor *buffer.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
isOverwriteMode bool
// This stores when the last click was // This stores when the last click was
// This is useful for detecting double and triple clicks // This is useful for detecting double and triple clicks
lastClickTime time.Time lastClickTime time.Time
lastLoc buffer.Loc lastLoc buffer.Loc
// freshClip returns true if one or more lines have been cut to the clipboard // lastCutTime stores when the last ctrl+k was issued.
// and have never been pasted yet. // It is used for clearing the clipboard to replace it with fresh cut lines.
lastCutTime time.Time
// freshClip returns true if the clipboard has never been pasted.
freshClip bool freshClip bool
// Was the last mouse event actually a double click? // Was the last mouse event actually a double click?
// Useful for detecting triple clicks -- if a double click is detected // Useful for detecting triple clicks -- if a double click is detected
// but the last mouse event was actually a double click, it's a triple click // but the last mouse event was actually a double click, it's a triple click
DoubleClick bool doubleClick bool
// 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
@ -249,67 +231,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
} }
@ -320,93 +266,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 mode when editor
// is opened
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() {
@ -415,40 +319,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) {
@ -461,44 +345,61 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
h.paste(e.Text()) h.paste(e.Text())
h.Relocate() h.Relocate()
case *tcell.EventKey: case *tcell.EventKey:
ke := keyEvent(e) ke := KeyEvent{
code: e.Key(),
mod: metaToAlt(e.Modifiers()),
r: e.Rune(),
}
done := h.DoKeyEvent(ke) done := h.DoKeyEvent(ke)
if !done && e.Key() == tcell.KeyRune { if !done && e.Key() == tcell.KeyRune {
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(clipboard.PrimaryReg)
}
h.mouseReleased = true
}
}
if !cancel {
me := MouseEvent{ me := MouseEvent{
btn: e.Buttons(), btn: e.Buttons(),
mod: metaToAlt(e.Modifiers()), mod: metaToAlt(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()
@ -518,17 +419,8 @@ 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 { func (h *BufPane) Bindings() *KeyTree {
if h.bindings != nil { if h.bindings != nil {
return h.bindings return h.bindings
@ -537,10 +429,7 @@ func (h *BufPane) Bindings() *KeyTree {
} }
// 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() binds := h.Bindings()
action, more := binds.NextEvent(e, nil) action, more := binds.NextEvent(e, nil)
@ -554,33 +443,30 @@ func (h *BufPane) DoKeyEvent(e Event) bool {
return more return more
} }
func (h *BufPane) execAction(action BufAction, name string, 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
} }
if !h.PluginCB("pre" + name) { _, isMulti := MultiActions[name]
return false if (!isMulti && cursor == 0) || isMulti {
} if h.PluginCB("pre" + name) {
success := action(h)
success = success && h.PluginCB("on"+name)
var success bool if isMulti {
switch a := action.(type) { if recording_macro {
case BufKeyAction: if name != "ToggleMacro" && name != "PlayMacro" {
success = a(h) curmacro = append(curmacro, action)
case BufMouseAction: }
success = a(h, te) }
}
success = success && h.PluginCB("on"+name)
if _, ok := MultiActions[name]; ok {
if recordingMacro {
if name != "ToggleMacro" && name != "PlayMacro" {
curmacro = append(curmacro, action)
} }
return success
} }
} }
return success return false
} }
func (h *BufPane) completeAction(action string) { func (h *BufPane) completeAction(action string) {
@ -634,14 +520,14 @@ func (h *BufPane) DoRuneInsert(r rune) {
c.ResetSelection() c.ResetSelection()
} }
if h.Buf.OverwriteMode { if h.isOverwriteMode {
next := c.Loc next := c.Loc
next.X++ next.X++
h.Buf.Replace(c.Loc, next, string(r)) h.Buf.Replace(c.Loc, next, string(r))
} 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()
@ -649,55 +535,34 @@ 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 = h.tab.GetNode(h.splitID).VSplit(right) e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
currentPaneIdx := h.tab.GetPane(h.splitID) MainTab().Panes = append(MainTab().Panes, e)
if right { MainTab().Resize()
currentPaneIdx++ MainTab().SetActive(len(MainTab().Panes) - 1)
}
h.tab.AddPane(e, currentPaneIdx)
h.tab.Resize()
h.tab.SetActive(currentPaneIdx)
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 = h.tab.GetNode(h.splitID).HSplit(bottom) e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
currentPaneIdx := h.tab.GetPane(h.splitID) MainTab().Panes = append(MainTab().Panes, e)
if bottom { MainTab().Resize()
currentPaneIdx++ MainTab().SetActive(len(MainTab().Panes) - 1)
}
h.tab.AddPane(e, currentPaneIdx)
h.tab.Resize()
h.tab.SetActive(currentPaneIdx)
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
@ -713,12 +578,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
@ -731,9 +592,6 @@ var BufKeyActions = map[string]BufKeyAction{
"CursorRight": (*BufPane).CursorRight, "CursorRight": (*BufPane).CursorRight,
"CursorStart": (*BufPane).CursorStart, "CursorStart": (*BufPane).CursorStart,
"CursorEnd": (*BufPane).CursorEnd, "CursorEnd": (*BufPane).CursorEnd,
"CursorToViewTop": (*BufPane).CursorToViewTop,
"CursorToViewCenter": (*BufPane).CursorToViewCenter,
"CursorToViewBottom": (*BufPane).CursorToViewBottom,
"SelectToStart": (*BufPane).SelectToStart, "SelectToStart": (*BufPane).SelectToStart,
"SelectToEnd": (*BufPane).SelectToEnd, "SelectToEnd": (*BufPane).SelectToEnd,
"SelectUp": (*BufPane).SelectUp, "SelectUp": (*BufPane).SelectUp,
@ -742,16 +600,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,
@ -759,8 +611,6 @@ var BufKeyActions = map[string]BufKeyAction{
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine, "SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
"ParagraphPrevious": (*BufPane).ParagraphPrevious, "ParagraphPrevious": (*BufPane).ParagraphPrevious,
"ParagraphNext": (*BufPane).ParagraphNext, "ParagraphNext": (*BufPane).ParagraphNext,
"SelectToParagraphPrevious": (*BufPane).SelectToParagraphPrevious,
"SelectToParagraphNext": (*BufPane).SelectToParagraphNext,
"InsertNewline": (*BufPane).InsertNewline, "InsertNewline": (*BufPane).InsertNewline,
"Backspace": (*BufPane).Backspace, "Backspace": (*BufPane).Backspace,
"Delete": (*BufPane).Delete, "Delete": (*BufPane).Delete,
@ -772,8 +622,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,
@ -781,7 +629,6 @@ var BufKeyActions = map[string]BufKeyAction{
"CopyLine": (*BufPane).CopyLine, "CopyLine": (*BufPane).CopyLine,
"Cut": (*BufPane).Cut, "Cut": (*BufPane).Cut,
"CutLine": (*BufPane).CutLine, "CutLine": (*BufPane).CutLine,
"Duplicate": (*BufPane).Duplicate,
"DuplicateLine": (*BufPane).DuplicateLine, "DuplicateLine": (*BufPane).DuplicateLine,
"DeleteLine": (*BufPane).DeleteLine, "DeleteLine": (*BufPane).DeleteLine,
"MoveLinesUp": (*BufPane).MoveLinesUp, "MoveLinesUp": (*BufPane).MoveLinesUp,
@ -812,9 +659,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,
"ResetSearch": (*BufPane).ResetSearch,
"ClearStatus": (*BufPane).ClearStatus, "ClearStatus": (*BufPane).ClearStatus,
"ShellMode": (*BufPane).ShellMode, "ShellMode": (*BufPane).ShellMode,
"CommandMode": (*BufPane).CommandMode, "CommandMode": (*BufPane).CommandMode,
@ -826,12 +670,8 @@ var BufKeyActions = map[string]BufKeyAction{
"AddTab": (*BufPane).AddTab, "AddTab": (*BufPane).AddTab,
"PreviousTab": (*BufPane).PreviousTab, "PreviousTab": (*BufPane).PreviousTab,
"NextTab": (*BufPane).NextTab, "NextTab": (*BufPane).NextTab,
"FirstTab": (*BufPane).FirstTab,
"LastTab": (*BufPane).LastTab,
"NextSplit": (*BufPane).NextSplit, "NextSplit": (*BufPane).NextSplit,
"PreviousSplit": (*BufPane).PreviousSplit, "PreviousSplit": (*BufPane).PreviousSplit,
"FirstSplit": (*BufPane).FirstSplit,
"LastSplit": (*BufPane).LastSplit,
"Unsplit": (*BufPane).Unsplit, "Unsplit": (*BufPane).Unsplit,
"VSplit": (*BufPane).VSplitAction, "VSplit": (*BufPane).VSplitAction,
"HSplit": (*BufPane).HSplitAction, "HSplit": (*BufPane).HSplitAction,
@ -847,7 +687,6 @@ var BufKeyActions = map[string]BufKeyAction{
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor, "RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors, "RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
"SkipMultiCursor": (*BufPane).SkipMultiCursor, "SkipMultiCursor": (*BufPane).SkipMultiCursor,
"SkipMultiCursorBack": (*BufPane).SkipMultiCursorBack,
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace, "JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
"JumpLine": (*BufPane).JumpLine, "JumpLine": (*BufPane).JumpLine,
"Deselect": (*BufPane).Deselect, "Deselect": (*BufPane).Deselect,
@ -861,8 +700,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,
} }
@ -887,16 +724,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,
@ -914,7 +745,6 @@ var MultiActions = map[string]bool{
"Copy": true, "Copy": true,
"Cut": true, "Cut": true,
"CutLine": true, "CutLine": true,
"Duplicate": true,
"DuplicateLine": true, "DuplicateLine": true,
"DeleteLine": true, "DeleteLine": true,
"MoveLinesUp": true, "MoveLinesUp": true,

View File

@ -7,7 +7,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -42,7 +41,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},
@ -139,25 +137,23 @@ func (h *BufPane) TextFilterCmd(args []string) {
InfoBar.Error("usage: textfilter arguments") InfoBar.Error("usage: textfilter arguments")
return return
} }
for _, c := range h.Buf.GetCursors() { sel := h.Cursor.GetSelection()
sel := c.GetSelection() if len(sel) == 0 {
if len(sel) == 0 { h.Cursor.SelectWord()
c.SelectWord() sel = h.Cursor.GetSelection()
sel = c.GetSelection()
}
var bout, berr bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = strings.NewReader(string(sel))
cmd.Stderr = &berr
cmd.Stdout = &bout
err := cmd.Run()
if err != nil {
InfoBar.Error(err.Error() + " " + berr.String())
return
}
c.DeleteSelection()
h.Buf.Insert(c.Loc, bout.String())
} }
var bout, berr bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = strings.NewReader(string(sel))
cmd.Stderr = &berr
cmd.Stdout = &bout
err := cmd.Run()
if err != nil {
InfoBar.Error(err.Error() + " " + berr.String())
return
}
h.Cursor.DeleteSelection()
h.Buf.Insert(h.Cursor.Loc, bout.String())
} }
// TabMoveCmd moves the current tab to a given index (starts at 1). The // TabMoveCmd moves the current tab to a given index (starts at 1). The
@ -201,11 +197,10 @@ func (h *BufPane) TabMoveCmd(args []string) {
idxTo = util.Clamp(idxTo, 0, len(Tabs.List)-1) idxTo = util.Clamp(idxTo, 0, len(Tabs.List)-1)
activeTab := Tabs.List[idxFrom] activeTab := Tabs.List[idxFrom]
Tabs.RemoveTab(activeTab.Panes[0].ID()) Tabs.RemoveTab(activeTab.ID())
Tabs.List = append(Tabs.List, nil) Tabs.List = append(Tabs.List, nil)
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:]) copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
Tabs.List[idxTo] = activeTab Tabs.List[idxTo] = activeTab
Tabs.Resize()
Tabs.UpdateNames() Tabs.UpdateNames()
Tabs.SetActive(idxTo) Tabs.SetActive(idxTo)
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1)) // InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
@ -308,8 +303,15 @@ func (h *BufPane) OpenCmd(args []string) {
} }
h.OpenBuffer(b) h.OpenBuffer(b)
} }
if h.Buf.Modified() && !h.Buf.Shared() { if h.Buf.Modified() {
h.closePrompt("Save", open) InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
open()
} else if !canceled && yes {
h.Save()
open()
}
})
} else { } else {
open() open()
} }
@ -327,84 +329,31 @@ 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)
} else {
parsedSettings := config.ParsedSettings()
defaultSettings := config.DefaultAllSettings()
for k := range defaultSettings {
if _, ok := config.VolatileSettings[k]; ok {
// reload should not override volatile settings
continue
}
if _, ok := parsedSettings[k]; ok {
err = doSetGlobalOptionNative(k, parsedSettings[k])
} else {
err = doSetGlobalOptionNative(k, defaultSettings[k])
}
if err != nil {
screen.TermMessage(err)
}
}
} }
err = config.InitGlobalSettings()
if reloadPlugins { if err != nil {
err = config.LoadAllPlugins() screen.TermMessage(err)
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.ReloadSettings(true) b.UpdateRules()
} }
} }
@ -414,93 +363,50 @@ 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, hsplit bool, forceSplit bool) 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 && !forceSplit { if h.Buf.Type == buffer.BTHelp {
h.OpenBuffer(helpBuffer) h.OpenBuffer(helpBuffer)
} else if hsplit {
h.HSplitBuf(helpBuffer)
} else { } else {
h.VSplitBuf(helpBuffer) h.HSplitBuf(helpBuffer)
} }
} }
return nil return nil
} }
// HelpCmd tries to open the given help page according to the split type // HelpCmd tries to open the given help page in a horizontal split
// configured with the "helpsplit" option. It can be overriden by the optional
// arguments "-vpslit" or "-hsplit". In case more than one help page is given
// as argument then it opens all of them with the defined split type.
func (h *BufPane) HelpCmd(args []string) { func (h *BufPane) HelpCmd(args []string) {
hsplit := config.GlobalSettings["helpsplit"] == "hsplit"
if len(args) < 1 { if len(args) < 1 {
// Open the default help if the user just typed "> help" // Open the default help if the user just typed "> help"
h.openHelp("help", hsplit, false) h.openHelp("help")
} else { } else {
var topics []string if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
forceSplit := false err := h.openHelp(args[0])
const errSplit = "hsplit and vsplit are not allowed at the same time" if err != nil {
for _, arg := range args { InfoBar.Error(err)
switch arg {
case "-vsplit":
if forceSplit {
InfoBar.Error(errSplit)
return
}
hsplit = false
forceSplit = true
case "-hsplit":
if forceSplit {
InfoBar.Error(errSplit)
return
}
hsplit = true
forceSplit = true
default:
topics = append(topics, arg)
}
}
if len(topics) < 1 {
// Do the same as without arg
h.openHelp("help", hsplit, forceSplit)
return
}
if len(topics) > 1 {
forceSplit = true
}
for _, topic := range topics {
if config.FindRuntimeFile(config.RTHelp, topic) != nil {
err := h.openHelp(topic, hsplit, forceSplit)
if err != nil {
InfoBar.Error(err)
}
} else {
InfoBar.Error("Sorry, no help for ", topic)
} }
} else {
InfoBar.Error("Sorry, no help for ", args[0])
} }
} }
} }
// VSplitCmd opens one or more vertical splits with the files given as arguments // VSplitCmd opens a vertical split with file given in the first argument
// If no file is given, it opens an empty buffer in a new split // If no file is given, it opens an empty buffer in a new split
func (h *BufPane) VSplitCmd(args []string) { func (h *BufPane) VSplitCmd(args []string) {
if len(args) == 0 { if len(args) == 0 {
@ -509,18 +415,16 @@ func (h *BufPane) VSplitCmd(args []string) {
return return
} }
for _, a := range args { buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault) if err != nil {
if err != nil { InfoBar.Error(err)
InfoBar.Error(err) return
return
}
h.VSplitBuf(buf)
} }
h.VSplitBuf(buf)
} }
// HSplitCmd opens one or more horizontal splits with the files given as arguments // HSplitCmd opens a horizontal split with file given in the first argument
// If no file is given, it opens an empty buffer in a new split // If no file is given, it opens an empty buffer in a new split
func (h *BufPane) HSplitCmd(args []string) { func (h *BufPane) HSplitCmd(args []string) {
if len(args) == 0 { if len(args) == 0 {
@ -529,15 +433,13 @@ func (h *BufPane) HSplitCmd(args []string) {
return return
} }
for _, a := range args { buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault) if err != nil {
if err != nil { InfoBar.Error(err)
InfoBar.Error(err) return
return
}
h.HSplitBuf(buf)
} }
h.HSplitBuf(buf)
} }
// EvalCmd evaluates a lua expression // EvalCmd evaluates a lua expression
@ -545,8 +447,7 @@ func (h *BufPane) EvalCmd(args []string) {
InfoBar.Error("Eval unsupported") InfoBar.Error("Eval unsupported")
} }
// NewTabCmd opens one or more tabs with the files given as arguments // NewTabCmd opens the given file in a new tab
// If no file is given, it opens an empty buffer in a new tab
func (h *BufPane) NewTabCmd(args []string) { func (h *BufPane) NewTabCmd(args []string) {
width, height := screen.Screen.Size() width, height := screen.Screen.Size()
iOffset := config.GetInfoBarOffset() iOffset := config.GetInfoBarOffset()
@ -569,98 +470,73 @@ func (h *BufPane) NewTabCmd(args []string) {
} }
} }
func doSetGlobalOptionNative(option string, nativeValue interface{}) error { func SetGlobalOptionNative(option string, nativeValue interface{}) error {
if reflect.DeepEqual(config.GlobalSettings[option], nativeValue) { local := false
return nil for _, s := range config.LocalSettings {
if s == option {
local = true
break
}
} }
config.GlobalSettings[option] = nativeValue if !local {
config.ModifiedSettings[option] = true config.GlobalSettings[option] = nativeValue
delete(config.VolatileSettings, option) config.ModifiedSettings[option] = true
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 if option == "clipboard" {
m := clipboard.SetMethod(nativeValue.(string))
err := clipboard.Initialize(m)
if err != nil {
return err
}
} 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(nativeValue.(float64)) _, err := pl.Call("init")
} else { if err != nil && err != config.ErrNoSuchFunction {
config.SetAutoTime(0) screen.TermMessage(err)
} }
} else if option == "paste" { } else if !nativeValue.(bool) && pl.Loaded {
screen.Screen.SetPaste(nativeValue.(bool)) _, err := pl.Call("deinit")
} else if option == "clipboard" { if err != nil && err != config.ErrNoSuchFunction {
m := clipboard.SetMethod(nativeValue.(string)) screen.TermMessage(err)
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)
} }
} }
} }
} }
} }
return nil
}
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
if err := config.OptionIsValid(option, nativeValue); err != nil {
return err
}
// check for local option first...
for _, s := range config.LocalSettings {
if s == option {
return MainTab().CurPane().Buf.SetOptionNative(option, nativeValue)
}
}
// ...if it's not local continue with the globals...
if err := doSetGlobalOptionNative(option, nativeValue); err != nil {
return err
}
// ...at last check the buffer locals
for _, b := range buffer.OpenBuffers { for _, b := range buffer.OpenBuffers {
b.DoSetOptionNative(option, nativeValue) b.SetOptionNative(option, nativeValue)
delete(b.LocalSettings, option)
} }
err := config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json")) return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
if err != nil {
if errors.Is(err, util.ErrOverwrite) {
screen.TermMessage(err)
err = errors.Unwrap(err)
}
return err
}
return nil
} }
func SetGlobalOption(option, value string) error { func SetGlobalOption(option, value string) error {
@ -684,10 +560,16 @@ func (h *BufPane) ResetCmd(args []string) {
} }
option := args[0] option := args[0]
defaults := config.DefaultAllSettings()
if _, ok := defaults[option]; ok { defaultGlobals := config.DefaultGlobalSettings()
SetGlobalOptionNative(option, defaults[option]) defaultLocals := config.DefaultCommonSettings()
if _, ok := defaultGlobals[option]; ok {
SetGlobalOptionNative(option, defaultGlobals[option])
return
}
if _, ok := defaultLocals[option]; ok {
h.Buf.SetOptionNative(option, defaultLocals[option])
return return
} }
InfoBar.Error(config.ErrInvalidOption) InfoBar.Error(config.ErrInvalidOption)
@ -752,11 +634,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 {
@ -764,7 +641,7 @@ func (h *BufPane) ShowKeyCmd(args []string) {
return return
} }
event, err := findEvent(parseKeyArg(args[0])) event, err := findEvent(args[0])
if err != nil { if err != nil {
InfoBar.Error(err) InfoBar.Error(err)
return return
@ -783,13 +660,9 @@ 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 {
if errors.Is(err, util.ErrOverwrite) { InfoBar.Error(err)
screen.TermMessage(err)
} else {
InfoBar.Error(err)
}
} }
} }
@ -800,13 +673,9 @@ func (h *BufPane) UnbindCmd(args []string) {
return return
} }
err := UnbindKey(parseKeyArg(args[0])) err := UnbindKey(args[0])
if err != nil { if err != nil {
if errors.Is(err, util.ErrOverwrite) { InfoBar.Error(err)
screen.TermMessage(err)
} else {
InfoBar.Error(err)
}
} }
} }
@ -832,67 +701,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.Cursor.Deselect(true)
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.Cursor.Deselect(true)
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
@ -900,7 +744,7 @@ func (h *BufPane) SaveCmd(args []string) {
if len(args) == 0 { if len(args) == 0 {
h.Save() h.Save()
} else { } else {
h.saveBufToFile(args[0], "SaveAs", nil) h.Buf.SaveAs(args[0])
} }
} }
@ -961,21 +805,19 @@ func (h *BufPane) ReplaceCmd(args []string) {
nreplaced := 0 nreplaced := 0
start := h.Buf.Start() start := h.Buf.Start()
end := h.Buf.End() end := h.Buf.End()
searchLoc := h.Cursor.Loc
selection := h.Cursor.HasSelection() selection := h.Cursor.HasSelection()
if selection { if selection {
start = h.Cursor.CurSelection[0] start = h.Cursor.CurSelection[0]
end = h.Cursor.CurSelection[1] end = h.Cursor.CurSelection[1]
searchLoc = start // otherwise me might start at the end
} }
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)
} }
lastMatchEnd := buffer.Loc{-1, -1} searchLoc := h.Cursor.Loc
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, true)
@ -990,28 +832,15 @@ func (h *BufPane) ReplaceCmd(args []string) {
return return
} }
if lastMatchEnd == locs[1] {
// skip empty match right after previous match
if searchLoc == end {
searchLoc = start
lastMatchEnd = buffer.Loc{-1, -1}
} else {
searchLoc = searchLoc.Move(1, h.Buf)
}
doReplacement()
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)
@ -1021,13 +850,13 @@ func (h *BufPane) ReplaceCmd(args []string) {
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()
return return
} }
lastMatchEnd = searchLoc
doReplacement() doReplacement()
}) })
} }
@ -1059,42 +888,10 @@ func (h *BufPane) ReplaceAllCmd(args []string) {
h.ReplaceCmd(append(args, "-a")) h.ReplaceCmd(append(args, "-a"))
} }
func (h *BufPane) openTerm(args []string, newtab bool) {
t := new(shell.Terminal)
err := t.Start(args, false, true, nil, nil)
if err != nil {
InfoBar.Error(err)
return
}
pane := 0
id := h.ID()
if newtab {
h.AddTab()
id = MainTab().Panes[pane].ID()
} else {
for i, p := range MainTab().Panes {
if p.IsActive() {
pane = i
id = p.ID()
p.Close()
break
}
}
}
v := h.GetView()
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
if err != nil {
InfoBar.Error(err)
return
}
MainTab().Panes[pane] = tp
MainTab().SetActive(pane)
}
// TermCmd opens a terminal in the current view // TermCmd opens a terminal in the current view
func (h *BufPane) TermCmd(args []string) { func (h *BufPane) TermCmd(args []string) {
ps := h.tab.Panes
if !TermEmuSupported { if !TermEmuSupported {
InfoBar.Error("Terminal emulator not supported on this system") InfoBar.Error("Terminal emulator not supported on this system")
return return
@ -1109,19 +906,56 @@ func (h *BufPane) TermCmd(args []string) {
args = []string{sh} args = []string{sh}
} }
term := func(i int, newtab bool) {
t := new(shell.Terminal)
err := t.Start(args, false, true, nil, nil)
if err != nil {
InfoBar.Error(err)
return
}
id := h.ID()
if newtab {
h.AddTab()
i = 0
id = MainTab().Panes[0].ID()
} else {
MainTab().Panes[i].Close()
}
v := h.GetView()
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
if err != nil {
InfoBar.Error(err)
return
}
MainTab().Panes[i] = tp
MainTab().SetActive(i)
}
// If there is only one open file we make a new tab instead of overwriting it // If there is only one open file we make a new tab instead of overwriting it
newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1 newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1
if newtab { if newtab {
h.openTerm(args, true) term(0, true)
return return
} }
if h.Buf.Modified() && !h.Buf.Shared() { for i, p := range ps {
h.closePrompt("Save", func() { if p.ID() == h.ID() {
h.openTerm(args, false) if h.Buf.Modified() {
}) InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
} else { if !canceled && !yes {
h.openTerm(args, false) term(i, false)
} else if !canceled && yes {
h.Save()
term(i, false)
}
})
} else {
term(i, false)
}
}
} }
} }

View File

@ -3,7 +3,7 @@ package action
var termdefaults = map[string]string{ var termdefaults = map[string]string{
"<Ctrl-q><Ctrl-q>": "Exit", "<Ctrl-q><Ctrl-q>": "Exit",
"<Ctrl-e><Ctrl-e>": "CommandMode", "<Ctrl-e><Ctrl-e>": "CommandMode",
"<Ctrl-w><Ctrl-w>": "NextSplit|FirstSplit", "<Ctrl-w><Ctrl-w>": "NextSplit",
} }
// DefaultBindings returns a map containing micro's default keybindings // DefaultBindings returns a map containing micro's default keybindings

View File

@ -38,32 +38,27 @@ var bufdefaults = map[string]string{
"Ctrl-o": "OpenFile", "Ctrl-o": "OpenFile",
"Ctrl-s": "Save", "Ctrl-s": "Save",
"Ctrl-f": "Find", "Ctrl-f": "Find",
"Alt-F": "FindLiteral",
"Ctrl-n": "FindNext", "Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious", "Ctrl-p": "FindPrevious",
"Alt-[": "DiffPrevious|CursorStart",
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo", "Ctrl-z": "Undo",
"Ctrl-y": "Redo", "Ctrl-y": "Redo",
"Ctrl-c": "Copy|CopyLine", "Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut|CutLine", "Ctrl-x": "Cut",
"Ctrl-k": "CutLine", "Ctrl-k": "CutLine",
"Ctrl-d": "Duplicate|DuplicateLine", "Ctrl-d": "DuplicateLine",
"Ctrl-v": "Paste", "Ctrl-v": "Paste",
"Ctrl-a": "SelectAll", "Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab", "Ctrl-t": "AddTab",
"Alt-,": "PreviousTab|LastTab", "Alt-,": "PreviousTab",
"Alt-.": "NextTab|FirstTab", "Alt-.": "NextTab",
"Home": "StartOfTextToggle", "Home": "StartOfTextToggle",
"End": "EndOfLine", "End": "EndOfLine",
"CtrlHome": "CursorStart", "CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd", "CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp", "PageUp": "CursorPageUp",
"PageDown": "CursorPageDown", "PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab|LastTab", "CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab|FirstTab", "CtrlPageDown": "NextTab",
"ShiftPageUp": "SelectPageUp",
"ShiftPageDown": "SelectPageDown",
"Ctrl-g": "ToggleHelp", "Ctrl-g": "ToggleHelp",
"Alt-g": "ToggleKeyMenu", "Alt-g": "ToggleKeyMenu",
"Ctrl-r": "ToggleRuler", "Ctrl-r": "ToggleRuler",
@ -72,7 +67,7 @@ var bufdefaults = map[string]string{
"Ctrl-b": "ShellMode", "Ctrl-b": "ShellMode",
"Ctrl-q": "Quit", "Ctrl-q": "Quit",
"Ctrl-e": "CommandMode", "Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit|FirstSplit", "Ctrl-w": "NextSplit",
"Ctrl-u": "ToggleMacro", "Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro", "Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode", "Insert": "ToggleOverwriteMode",
@ -91,16 +86,14 @@ var bufdefaults = map[string]string{
"F4": "Quit", "F4": "Quit",
"F7": "Find", "F7": "Find",
"F10": "Quit", "F10": "Quit",
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch", "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors",
// 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",
@ -146,8 +139,8 @@ var infodefaults = map[string]string{
"Backtab": "CycleAutocompleteBack", "Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo", "Ctrl-z": "Undo",
"Ctrl-y": "Redo", "Ctrl-y": "Redo",
"Ctrl-c": "Copy|CopyLine", "Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut|CutLine", "Ctrl-x": "Cut",
"Ctrl-k": "CutLine", "Ctrl-k": "CutLine",
"Ctrl-v": "Paste", "Ctrl-v": "Paste",
"Home": "StartOfTextToggle", "Home": "StartOfTextToggle",
@ -179,10 +172,8 @@ var infodefaults = map[string]string{
"Esc": "AbortCommand", "Esc": "AbortCommand",
// Mouse bindings // Mouse bindings
"MouseWheelUp": "HistoryUp", "MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown", "MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress", "MouseLeft": "MousePress",
"MouseLeftDrag": "MouseDrag", "MouseMiddle": "PastePrimary",
"MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
} }

View File

@ -1,4 +1,3 @@
//go:build !darwin
// +build !darwin // +build !darwin
package action package action
@ -41,32 +40,27 @@ var bufdefaults = map[string]string{
"Ctrl-o": "OpenFile", "Ctrl-o": "OpenFile",
"Ctrl-s": "Save", "Ctrl-s": "Save",
"Ctrl-f": "Find", "Ctrl-f": "Find",
"Alt-F": "FindLiteral",
"Ctrl-n": "FindNext", "Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious", "Ctrl-p": "FindPrevious",
"Alt-[": "DiffPrevious|CursorStart",
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo", "Ctrl-z": "Undo",
"Ctrl-y": "Redo", "Ctrl-y": "Redo",
"Ctrl-c": "Copy|CopyLine", "Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut|CutLine", "Ctrl-x": "Cut",
"Ctrl-k": "CutLine", "Ctrl-k": "CutLine",
"Ctrl-d": "Duplicate|DuplicateLine", "Ctrl-d": "DuplicateLine",
"Ctrl-v": "Paste", "Ctrl-v": "Paste",
"Ctrl-a": "SelectAll", "Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab", "Ctrl-t": "AddTab",
"Alt-,": "PreviousTab|LastTab", "Alt-,": "PreviousTab",
"Alt-.": "NextTab|FirstTab", "Alt-.": "NextTab",
"Home": "StartOfTextToggle", "Home": "StartOfTextToggle",
"End": "EndOfLine", "End": "EndOfLine",
"CtrlHome": "CursorStart", "CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd", "CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp", "PageUp": "CursorPageUp",
"PageDown": "CursorPageDown", "PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab|LastTab", "CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab|FirstTab", "CtrlPageDown": "NextTab",
"ShiftPageUp": "SelectPageUp",
"ShiftPageDown": "SelectPageDown",
"Ctrl-g": "ToggleHelp", "Ctrl-g": "ToggleHelp",
"Alt-g": "ToggleKeyMenu", "Alt-g": "ToggleKeyMenu",
"Ctrl-r": "ToggleRuler", "Ctrl-r": "ToggleRuler",
@ -75,7 +69,7 @@ var bufdefaults = map[string]string{
"Ctrl-b": "ShellMode", "Ctrl-b": "ShellMode",
"Ctrl-q": "Quit", "Ctrl-q": "Quit",
"Ctrl-e": "CommandMode", "Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit|FirstSplit", "Ctrl-w": "NextSplit",
"Ctrl-u": "ToggleMacro", "Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro", "Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode", "Insert": "ToggleOverwriteMode",
@ -94,16 +88,14 @@ var bufdefaults = map[string]string{
"F4": "Quit", "F4": "Quit",
"F7": "Find", "F7": "Find",
"F10": "Quit", "F10": "Quit",
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch", "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors",
// 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",
@ -149,8 +141,8 @@ var infodefaults = map[string]string{
"Backtab": "CycleAutocompleteBack", "Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo", "Ctrl-z": "Undo",
"Ctrl-y": "Redo", "Ctrl-y": "Redo",
"Ctrl-c": "Copy|CopyLine", "Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut|CutLine", "Ctrl-x": "Cut",
"Ctrl-k": "CutLine", "Ctrl-k": "CutLine",
"Ctrl-v": "Paste", "Ctrl-v": "Paste",
"Home": "StartOfTextToggle", "Home": "StartOfTextToggle",
@ -182,10 +174,8 @@ var infodefaults = map[string]string{
"Esc": "AbortCommand", "Esc": "AbortCommand",
// Mouse bindings // Mouse bindings
"MouseWheelUp": "HistoryUp", "MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown", "MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress", "MouseLeft": "MousePress",
"MouseLeftDrag": "MouseDrag", "MouseMiddle": "PastePrimary",
"MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
} }

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/micro-editor/tcell/v2" "github.com/zyedidia/tcell/v2"
) )
type Event interface { type Event interface {
@ -44,17 +44,6 @@ func metaToAlt(mod tcell.ModMask) tcell.ModMask {
return mod return mod
} }
func keyEvent(e *tcell.EventKey) KeyEvent {
ke := KeyEvent{
code: e.Key(),
mod: metaToAlt(e.Modifiers()),
}
if e.Key() == tcell.KeyRune {
ke.r = e.Rune()
}
return ke
}
func (k KeyEvent) Name() string { func (k KeyEvent) Name() string {
if k.any { if k.any {
return "<any>" return "<any>"
@ -79,7 +68,7 @@ func (k KeyEvent) Name() string {
if k.code == tcell.KeyRune { if k.code == tcell.KeyRune {
s = string(k.r) s = string(k.r)
} else { } else {
s = fmt.Sprintf("Key[%d]", k.code) s = fmt.Sprintf("Key[%d,%d]", k.code, int(k.r))
} }
} }
if len(m) != 0 { if len(m) != 0 {
@ -111,20 +100,11 @@ func (k KeySequenceEvent) Name() string {
return buf.String() 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 { func (m MouseEvent) Name() string {
@ -142,17 +122,9 @@ func (m MouseEvent) Name() string {
mod = "Ctrl-" mod = "Ctrl-"
} }
state := ""
switch m.state {
case MouseDrag:
state = "Drag"
case MouseRelease:
state = "Release"
}
for k, v := range mouseEvents { for k, v := range mouseEvents {
if v == m.btn { if v == m.btn {
return fmt.Sprintf("%s%s%s", mod, k, state) return fmt.Sprintf("%s%s", mod, k)
} }
} }
return "" return ""
@ -166,7 +138,11 @@ func (m MouseEvent) Name() string {
func ConstructEvent(event tcell.Event) (Event, error) { func ConstructEvent(event tcell.Event) (Event, error) {
switch e := event.(type) { switch e := event.(type) {
case *tcell.EventKey: case *tcell.EventKey:
return keyEvent(e), nil return KeyEvent{
code: e.Key(),
mod: metaToAlt(e.Modifiers()),
r: e.Rune(),
}, nil
case *tcell.EventRaw: case *tcell.EventRaw:
return RawEvent{ return RawEvent{
esc: e.EscSeq(), esc: e.EscSeq(),

View File

@ -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

View File

@ -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,67 +77,19 @@ func colorschemeComplete(input string) (string, []string) {
return chosen, suggestions return chosen, suggestions
} }
// filetypeComplete autocompletes filetype func contains(s []string, e string) bool {
func filetypeComplete(input string) (string, []string) { for _, a := range s {
var suggestions []string if a == e {
return true
// 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: return false
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
} }
// 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 {
@ -165,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{' '})
@ -221,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")
@ -230,13 +186,15 @@ 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: case "clipboard":
if choices, ok := config.OptionChoices[inputOpt]; ok { if strings.HasPrefix("external", input) {
for _, choice := range choices { suggestions = append(suggestions, "external")
if strings.HasPrefix(choice, input) { }
suggestions = append(suggestions, choice) if strings.HasPrefix("internal", input) {
} suggestions = append(suggestions, "internal")
} }
if strings.HasPrefix("terminal", input) {
suggestions = append(suggestions, "terminal")
} }
} }
} }
@ -252,7 +210,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 {
@ -274,7 +232,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{' '})

View File

@ -3,12 +3,12 @@ package action
import ( import (
"bytes" "bytes"
"github.com/micro-editor/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/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"
) )
type InfoKeyAction func(*InfoPane) type InfoKeyAction func(*InfoPane)
@ -83,22 +83,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(e) ke := KeyEvent{
code: e.Key(),
mod: metaToAlt(e.Modifiers()),
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 {
@ -108,11 +108,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)
} }
@ -122,10 +118,7 @@ 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) action, more := InfoBindings.NextEvent(e, nil)
if action != nil && !more { if action != nil && !more {
@ -139,25 +132,11 @@ func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
} }
if !more { if !more {
// If no infopane action found, try to find a bufpane action.
//
// TODO: this is buggy. For example, if the command bar has the following
// two bindings:
//
// "<Ctrl-x><Ctrl-p>": "HistoryUp",
// "<Ctrl-x><Ctrl-v>": "Paste",
//
// 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) action, more = InfoBufBindings.NextEvent(e, nil)
if action != nil && !more { if action != nil && !more {
action(h.BufPane) done := action(h.BufPane)
InfoBufBindings.ResetEvents() InfoBufBindings.ResetEvents()
return true return done
} else if action == nil && !more { } else if action == nil && !more {
InfoBufBindings.ResetEvents() InfoBufBindings.ResetEvents()
} }
@ -176,18 +155,6 @@ func (h *InfoPane) HistoryDown() {
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) CommandComplete() {
b := h.Buf b := h.Buf
@ -231,11 +198,9 @@ func (h *InfoPane) AbortCommand() {
// InfoKeyActions contains the list of all possible key actions the infopane could execute // InfoKeyActions contains the list of all possible key actions the infopane could execute
var InfoKeyActions = map[string]InfoKeyAction{ var InfoKeyActions = map[string]InfoKeyAction{
"HistoryUp": (*InfoPane).HistoryUp, "HistoryUp": (*InfoPane).HistoryUp,
"HistoryDown": (*InfoPane).HistoryDown, "HistoryDown": (*InfoPane).HistoryDown,
"HistorySearchUp": (*InfoPane).HistorySearchUp, "CommandComplete": (*InfoPane).CommandComplete,
"HistorySearchDown": (*InfoPane).HistorySearchDown, "ExecuteCommand": (*InfoPane).ExecuteCommand,
"CommandComplete": (*InfoPane).CommandComplete, "AbortCommand": (*InfoPane).AbortCommand,
"ExecuteCommand": (*InfoPane).ExecuteCommand,
"AbortCommand": (*InfoPane).AbortCommand,
} }

View File

@ -3,7 +3,7 @@ package action
import ( import (
"bytes" "bytes"
"github.com/micro-editor/tcell/v2" "github.com/zyedidia/tcell/v2"
) )
type PaneKeyAction func(Pane) bool type PaneKeyAction func(Pane) bool
@ -229,7 +229,7 @@ func (k *KeyTree) ResetEvents() {
k.cursor.mouseInfo = nil k.cursor.mouseInfo = nil
} }
// RecordedEventsStr returns the list of recorded events as a string // CurrentEventsStr returns the list of recorded events as a string
func (k *KeyTree) RecordedEventsStr() string { func (k *KeyTree) RecordedEventsStr() string {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
for _, e := range k.cursor.recordedEvents { for _, e := range k.cursor.recordedEvents {

View File

@ -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

View File

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/micro-editor/tcell/v2"
"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"
) )
type RawPane struct { type RawPane struct {

View File

@ -1,15 +1,12 @@
package action package action
import ( import (
luar "layeh.com/gopher-luar"
"github.com/micro-editor/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/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"
) )
// 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
@ -107,32 +104,30 @@ func (t *TabList) HandleEvent(event tcell.Event) {
mx, my := e.Position() mx, my := e.Position()
switch e.Buttons() { switch e.Buttons() {
case tcell.Button1: case tcell.Button1:
if my == t.Y && len(t.List) > 1 { if my == t.Y && mx == 0 {
if mx == 0 { t.Scroll(-4)
t.Scroll(-4) return
} else if mx == t.Width-1 { } else if my == t.Y && mx == t.Width-1 {
t.Scroll(4) t.Scroll(4)
} else {
ind := t.LocFromVisual(buffer.Loc{mx, my})
if ind != -1 {
t.SetActive(ind)
}
}
return return
} }
case tcell.ButtonNone: if len(t.List) > 1 {
if t.List[t.Active()].release { ind := t.LocFromVisual(buffer.Loc{mx, my})
// Mouse release received, while already released if ind != -1 {
t.ResetMouse() t.SetActive(ind)
return return
}
if my == 0 {
return
}
} }
case tcell.WheelUp: case tcell.WheelUp:
if my == t.Y && len(t.List) > 1 { if my == t.Y {
t.Scroll(4) t.Scroll(4)
return return
} }
case tcell.WheelDown: case tcell.WheelDown:
if my == t.Y && len(t.List) > 1 { if my == t.Y {
t.Scroll(-4) t.Scroll(-4)
return return
} }
@ -149,75 +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()
}
}
}
}
// CloseTerms notifies term panes that a terminal job has finished.
func (t *TabList) CloseTerms() {
for _, tab := range t.List {
for _, p := range tab.Panes {
if tp, ok := p.(*TermPane); ok {
tp.HandleTermClose()
}
}
}
}
// 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 {
@ -231,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
@ -271,40 +199,34 @@ 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 {
t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my})
if t.resizing != nil {
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
@ -314,15 +236,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
@ -349,16 +266,6 @@ func (t *Tab) SetActive(i int) {
} }
} }
// AddPane adds a pane at a given index
func (t *Tab) AddPane(pane Pane, i int) {
if len(t.Panes) == i {
t.Panes = append(t.Panes, pane)
return
}
t.Panes = append(t.Panes[:i+1], t.Panes[i:]...)
t.Panes[i] = pane
}
// GetPane returns the pane with the given split index // GetPane returns the pane with the given split index
func (t *Tab) GetPane(splitid uint64) int { func (t *Tab) GetPane(splitid uint64) int {
for i, p := range t.Panes { for i, p := range t.Panes {

View File

@ -1,4 +1,4 @@
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd // +build linux darwin dragonfly openbsd_amd64 freebsd
package action package action

View File

@ -1,4 +1,4 @@
//go:build plan9 || nacl || windows // +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
package action package action

View File

@ -4,13 +4,13 @@ import (
"errors" "errors"
"runtime" "runtime"
"github.com/micro-editor/tcell/v2"
"github.com/micro-editor/terminal"
"github.com/zyedidia/micro/v2/internal/clipboard" "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/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/terminal"
) )
type TermKeyAction func(*TermPane) type TermKeyAction func(*TermPane)
@ -81,10 +81,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
} }
@ -125,7 +121,11 @@ 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(e) ke := KeyEvent{
code: e.Key(),
mod: metaToAlt(e.Modifiers()),
r: e.Rune(),
}
action, more := TermBindings.NextEvent(ke, nil) action, more := TermBindings.NextEvent(ke, nil)
if !more { if !more {
@ -159,9 +159,9 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
if t.Status != shell.TTDone { if t.Status != shell.TTDone {
t.WriteString(event.EscSeq()) t.WriteString(event.EscSeq())
} }
} else if e, ok := event.(*tcell.EventMouse); !ok || t.State.Mode(terminal.ModeMouseMask) { } else if e, ok := event.(*tcell.EventMouse); e != nil && (!ok || t.State.Mode(terminal.ModeMouseMask)) {
// t.WriteString(event.EscSeq()) // t.WriteString(event.EscSeq())
} else { } else if e != nil {
x, y := e.Position() x, y := e.Position()
v := t.GetView() v := t.GetView()
x -= v.X x -= v.X
@ -188,12 +188,7 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
t.mouseReleased = true t.mouseReleased = true
} }
} }
}
// HandleTermClose is called when a terminal has finished its job
// and should be closed. If that terminal is this termpane's terminal,
// HandleTermClose will close the terminal and the termpane itself.
func (t *TermPane) HandleTermClose() {
if t.Status == shell.TTClose { if t.Status == shell.TTClose {
t.Quit() t.Quit()
} }

View File

@ -2,7 +2,7 @@ package buffer
import ( import (
"bytes" "bytes"
"io/fs" "io/ioutil"
"os" "os"
"sort" "sort"
"strings" "strings"
@ -64,7 +64,7 @@ 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)
@ -73,17 +73,17 @@ func (b *Buffer) GetWord() ([]byte, int) {
return []byte{}, -1 return []byte{}, -1
} }
if util.IsNonWordChar(b.RuneAt(c.Loc.Move(-1, b))) { if util.IsNonAlphaNumeric(b.RuneAt(c.Loc.Move(-1, b))) {
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,20 +104,20 @@ 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)
var files []fs.DirEntry var files []os.FileInfo
var err error var err error
if len(dirs) > 1 { if len(dirs) > 1 {
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
directories, _ = util.ReplaceHome(directories) directories, _ = util.ReplaceHome(directories)
files, err = os.ReadDir(directories) files, err = ioutil.ReadDir(directories)
} else { } else {
files, err = os.ReadDir(".") files, err = ioutil.ReadDir(".")
} }
if err != nil { if err != nil {
@ -153,7 +153,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 +166,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 +179,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)

View File

@ -1,26 +1,24 @@
package buffer package buffer
import ( import (
"errors"
"fmt" "fmt"
"io/fs" "io"
"os" "os"
"path/filepath" "path/filepath"
"sync/atomic"
"time"
"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"
"golang.org/x/text/encoding"
) )
const BackupMsg = `A backup was detected for: const backupMsg = `A backup was detected for this file. This likely means that micro
crashed while editing this file, or another instance of micro is currently
editing this file.
%s The backup was created on %s, and the file is
This likely means that micro crashed while editing this file,
or another instance of micro is currently editing this file,
or an error occurred while saving this file so it may be corrupted.
The backup was created on %s and its path is:
%s %s
@ -28,114 +26,121 @@ The backup was created on %s and its path 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: `
const backupSeconds = 8 var backupRequestChan chan *Buffer
var BackupCompleteChan 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() { func init() {
BackupCompleteChan = make(chan *Buffer, 10) backupRequestChan = make(chan *Buffer, 10)
go backupThread()
} }
func (b *Buffer) RequestBackup() { func (b *Buffer) RequestBackup() {
if !b.RequestedBackup { if !b.requestedBackup {
select { select {
case backupRequestChan <- b: case backupRequestChan <- b:
default: default:
// channel is full // channel is full
} }
b.RequestedBackup = true b.requestedBackup = true
} }
} }
func (b *Buffer) backupDir() string { // Backup saves the current buffer to ConfigDir/backups
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
if backupdir == "" || err != nil {
backupdir = filepath.Join(config.ConfigDir, "backups")
}
return backupdir
}
func (b *Buffer) keepBackup() bool {
return b.forceKeepBackup || b.Settings["permbackup"].(bool)
}
// Backup saves the current buffer to the backups directory
func (b *Buffer) Backup() error { func (b *Buffer) Backup() 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 := b.backupDir() backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
if _, err := os.Stat(backupdir); errors.Is(err, fs.ErrNotExist) { if backupdir == "" || err != nil {
backupdir = filepath.Join(config.ConfigDir, "backups")
}
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
os.Mkdir(backupdir, os.ModePerm) os.Mkdir(backupdir, os.ModePerm)
} }
name := util.DetermineEscapePath(backupdir, b.AbsPath) name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
if _, err := os.Stat(name); errors.Is(err, fs.ErrNotExist) {
_, err = b.overwriteFile(name) err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
if err == nil { if len(b.lines) == 0 {
BackupCompleteChan <- b return
} }
return err
}
tmp := util.AppendBackupSuffix(name) // end of line
_, err := b.overwriteFile(tmp) eol := []byte{'\n'}
if err != nil {
os.Remove(tmp)
return err
}
err = os.Rename(tmp, name)
if err != nil {
os.Remove(tmp)
return err
}
BackupCompleteChan <- b // write lines
if _, e = file.Write(b.lines[0].data); e != nil {
return
}
for _, l := range b.lines[1:] {
if _, e = file.Write(eol); e != nil {
return
}
if _, e = file.Write(l.data); e != nil {
return
}
}
return
}, 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.keepBackup() || b.Path == "" || b.Type != BTDefault { if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
return return
} }
f := util.DetermineEscapePath(b.backupDir(), b.AbsPath) f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
os.Remove(f) os.Remove(f)
} }
// 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) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
backupfile := util.DetermineEscapePath(b.backupDir(), 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)
if err == nil { if err == nil {
defer backup.Close() defer backup.Close()
t := info.ModTime() t := info.ModTime()
msg := fmt.Sprintf(BackupMsg, b.Path, t.Format("Mon Jan _2 at 15:04, 2006"), backupfile) 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
} }

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -20,14 +20,8 @@ type Cursor struct {
buf *Buffer buf *Buffer
Loc Loc
// Last visual x position of the cursor. Used in cursor up/down movements // Last cursor x position
// for remembering the original x position when moving to a line that is
// shorter than current x position.
LastVisualX int LastVisualX int
// Similar to LastVisualX but takes softwrapping into account, i.e. last
// visual x position in a visual (wrapped) line on the screen, which may be
// different from the line in the buffer.
LastWrappedVisualX int
// The current selection as a range of character numbers (inclusive) // The current selection as a range of character numbers (inclusive)
CurSelection [2]Loc CurSelection [2]Loc
@ -36,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
} }
@ -49,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
@ -67,9 +54,8 @@ func (c *Cursor) Buf() *Buffer {
// Goto puts the cursor at the given cursor's location and gives // Goto puts the cursor at the given cursor's location and gives
// the current cursor its selection too // the current cursor its selection too
func (c *Cursor) Goto(b Cursor) { func (c *Cursor) Goto(b Cursor) {
c.X, c.Y = b.X, b.Y c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
c.StoreVisualX()
} }
// GotoLoc puts the cursor at the given cursor's location and gives // GotoLoc puts the cursor at the given cursor's location and gives
@ -80,8 +66,8 @@ 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(wrap bool) int { func (c *Cursor) GetVisualX() int {
if wrap && c.buf.GetVisualX != nil { if c.buf.GetVisualX != nil {
return c.buf.GetVisualX(c.Loc) return c.buf.GetVisualX(c.Loc)
} }
@ -107,7 +93,7 @@ func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int {
// Start moves the cursor to the start of the line it is on // Start moves the cursor to the start of the line it is on
func (c *Cursor) Start() { func (c *Cursor) Start() {
c.X = 0 c.X = 0
c.StoreVisualX() c.LastVisualX = c.GetVisualX()
} }
// StartOfText moves the cursor to the first non-whitespace rune of // StartOfText moves the cursor to the first non-whitespace rune of
@ -138,7 +124,7 @@ func (c *Cursor) IsStartOfText() bool {
// End moves the cursor to the end of the line it is on // End moves the cursor to the end of the line it is on
func (c *Cursor) End() { func (c *Cursor) End() {
c.X = util.CharacterCount(c.buf.LineBytes(c.Y)) c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
c.StoreVisualX() c.LastVisualX = c.GetVisualX()
} }
// CopySelection copies the user's selection to either "primary" // CopySelection copies the user's selection to either "primary"
@ -193,7 +179,7 @@ func (c *Cursor) Deselect(start bool) {
if start { if start {
c.Loc = c.CurSelection[0] c.Loc = c.CurSelection[0]
} else { } else {
c.Loc = c.CurSelection[1] c.Loc = c.CurSelection[1].Move(-1, c.buf)
} }
c.ResetSelection() c.ResetSelection()
c.StoreVisualX() c.StoreVisualX()
@ -410,26 +396,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)) {
@ -441,10 +414,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 {
@ -452,17 +421,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 {
@ -473,132 +431,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)
@ -622,6 +454,5 @@ func (c *Cursor) RuneUnder(x int) rune {
} }
func (c *Cursor) StoreVisualX() { func (c *Cursor) StoreVisualX() {
c.LastVisualX = c.GetVisualX(false) c.LastVisualX = c.GetVisualX()
c.LastWrappedVisualX = c.GetVisualX(true)
} }

View File

@ -104,11 +104,7 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
c.OrigSelection[0] = move(c.OrigSelection[0]) c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1]) c.OrigSelection[1] = move(c.OrigSelection[1])
c.Relocate() c.Relocate()
c.StoreVisualX() c.LastVisualX = c.GetVisualX()
}
if useUndo {
eh.updateTrailingWs(t)
} }
} }
@ -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)
} }
@ -253,11 +249,11 @@ func (eh *EventHandler) Execute(t *TextEvent) {
ExecuteTextEvent(t, eh.buf) ExecuteTextEvent(t, eh.buf)
} }
// Undo the first event in the undo stack. Returns false if the stack is empty. // Undo the first event in the undo stack
func (eh *EventHandler) Undo() bool { func (eh *EventHandler) Undo() {
t := eh.UndoStack.Peek() t := eh.UndoStack.Peek()
if t == nil { if t == nil {
return false return
} }
startTime := t.Time.UnixNano() / int64(time.Millisecond) startTime := t.Time.UnixNano() / int64(time.Millisecond)
@ -266,16 +262,15 @@ func (eh *EventHandler) Undo() bool {
for { for {
t = eh.UndoStack.Peek() t = eh.UndoStack.Peek()
if t == nil { if t == nil {
break return
} }
if t.Time.UnixNano()/int64(time.Millisecond) < endTime { if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
break return
} }
eh.UndoOneEvent() eh.UndoOneEvent()
} }
return true
} }
// UndoOneEvent undoes one event // UndoOneEvent undoes one event
@ -291,20 +286,23 @@ func (eh *EventHandler) UndoOneEvent() {
eh.UndoTextEvent(t) eh.UndoTextEvent(t)
// Set the cursor in the right place // Set the cursor in the right place
if t.C.Num >= 0 && t.C.Num < len(eh.cursors) { teCursor := t.C
eh.cursors[t.C.Num].Goto(t.C) if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
eh.cursors[t.C.Num].NewTrailingWsY = t.C.NewTrailingWsY t.C = *eh.cursors[teCursor.Num]
eh.cursors[teCursor.Num].Goto(teCursor)
} else {
teCursor.Num = -1
} }
// Push it to the redo stack // Push it to the redo stack
eh.RedoStack.Push(t) eh.RedoStack.Push(t)
} }
// Redo the first event in the redo stack. Returns false if the stack is empty. // Redo the first event in the redo stack
func (eh *EventHandler) Redo() bool { func (eh *EventHandler) Redo() {
t := eh.RedoStack.Peek() t := eh.RedoStack.Peek()
if t == nil { if t == nil {
return false return
} }
startTime := t.Time.UnixNano() / int64(time.Millisecond) startTime := t.Time.UnixNano() / int64(time.Millisecond)
@ -313,16 +311,15 @@ func (eh *EventHandler) Redo() bool {
for { for {
t = eh.RedoStack.Peek() t = eh.RedoStack.Peek()
if t == nil { if t == nil {
break return
} }
if t.Time.UnixNano()/int64(time.Millisecond) > endTime { if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
break return
} }
eh.RedoOneEvent() eh.RedoOneEvent()
} }
return true
} }
// RedoOneEvent redoes one event // RedoOneEvent redoes one event
@ -332,9 +329,12 @@ func (eh *EventHandler) RedoOneEvent() {
return return
} }
if t.C.Num >= 0 && t.C.Num < len(eh.cursors) { teCursor := t.C
eh.cursors[t.C.Num].Goto(t.C) if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
eh.cursors[t.C.Num].NewTrailingWsY = t.C.NewTrailingWsY t.C = *eh.cursors[teCursor.Num]
eh.cursors[teCursor.Num].Goto(teCursor)
} else {
teCursor.Num = -1
} }
// Modifies the text event // Modifies the text event
@ -342,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
}
}

View File

@ -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,23 +173,22 @@ 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' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') {
@ -232,26 +216,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)
@ -285,6 +267,11 @@ func (la *LineArray) deleteLines(y1, y2 int) {
la.lines = la.lines[:y1+copy(la.lines[y1:], la.lines[y2+1:])] la.lines = la.lines[:y1+copy(la.lines[y1:], la.lines[y2+1:])]
} }
// DeleteByte deletes the byte at a position
func (la *LineArray) deleteByte(pos Loc) {
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
}
// Substr returns the string representation between two locations // Substr returns the string representation between two locations
func (la *LineArray) Substr(start, end Loc) []byte { func (la *LineArray) Substr(start, end Loc) []byte {
startX := runeToByteIndex(start.X, la.lines[start.Y].data) startX := runeToByteIndex(start.X, la.lines[start.Y].data)
@ -323,11 +310,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
@ -358,88 +345,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
}
}
} }

View File

@ -47,16 +47,6 @@ func (l Loc) LessEqual(b Loc) bool {
return l == b return l == b
} }
// Clamp clamps a loc between start and end
func (l Loc) Clamp(start, end Loc) Loc {
if l.GreaterEqual(end) {
return end
} else if l.LessThan(start) {
return start
}
return l
}
// The following functions require a buffer to know where newlines are // The following functions require a buffer to know where newlines are
// Diff returns the distance between two locations // Diff returns the distance between two locations
@ -149,5 +139,10 @@ func ByteOffset(pos Loc, buf *Buffer) int {
// clamps a loc within a buffer // clamps a loc within a buffer
func clamp(pos Loc, la *LineArray) Loc { func clamp(pos Loc, la *LineArray) Loc {
return pos.Clamp(la.Start(), la.End()) if pos.GreaterEqual(la.End()) {
return la.End()
} else if pos.LessThan(la.Start()) {
return la.Start()
}
return pos
} }

View File

@ -1,8 +1,8 @@
package buffer package buffer
import ( import (
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/tcell/v2"
) )
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
}

View File

@ -5,19 +5,18 @@ import (
"bytes" "bytes"
"errors" "errors"
"io" "io"
"io/fs"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync/atomic"
"time"
"unicode" "unicode"
"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"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/transform" "golang.org/x/text/transform"
) )
@ -25,178 +24,46 @@ import (
// because hashing is too slow // because hashing is too slow
const LargeFileThreshold = 50000 const LargeFileThreshold = 50000
type wrappedFile struct { // overwriteFile opens the given file for writing, truncating if one exists, and then calls
writeCloser io.WriteCloser // the supplied function with the file as io.Writer object, also making sure the file is
withSudo bool // closed afterwards.
screenb bool func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
cmd *exec.Cmd
sigChan chan os.Signal
}
type saveResponse struct {
size int
err error
}
type saveRequest struct {
buf *Buffer
path string
withSudo bool
newFile bool
saveResponseChan chan saveResponse
}
var saveRequestChan chan saveRequest
var backupRequestChan chan *Buffer
func init() {
saveRequestChan = make(chan saveRequest, 10)
backupRequestChan = make(chan *Buffer, 10)
go func() {
duration := backupSeconds * float64(time.Second)
backupTicker := time.NewTicker(time.Duration(duration))
for {
select {
case sr := <-saveRequestChan:
size, err := sr.buf.safeWrite(sr.path, sr.withSudo, sr.newFile)
sr.saveResponseChan <- saveResponse{size, err}
case <-backupTicker.C:
for len(backupRequestChan) > 0 {
b := <-backupRequestChan
bfini := atomic.LoadInt32(&(b.fini)) != 0
if !bfini {
b.Backup()
}
}
}
}
}()
}
func openFile(name string, withSudo bool) (wrappedFile, error) {
var err error
var writeCloser io.WriteCloser var writeCloser io.WriteCloser
var screenb bool
var cmd *exec.Cmd
var sigChan chan os.Signal
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)
writeCloser, err = cmd.StdinPipe()
if err != nil { if writeCloser, err = cmd.StdinPipe(); err != nil {
return wrappedFile{}, err return
} }
sigChan = make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Reset(os.Interrupt) signal.Notify(c, os.Interrupt)
signal.Notify(sigChan, os.Interrupt) go func() {
<-c
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
err = cmd.Start() }
if err != nil {
screen.TempStart(screenb) screen.TempStart(screenb)
}()
signal.Notify(util.Sigterm, os.Interrupt) } else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
signal.Stop(sigChan) return
return wrappedFile{}, err
}
} else {
writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, util.FileMode)
if err != nil {
return wrappedFile{}, err
}
} }
return wrappedFile{writeCloser, withSudo, screenb, cmd, sigChan}, nil w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
} err = fn(w)
w.Flush()
func (wf wrappedFile) Write(b *Buffer) (int, error) { if e := writeCloser.Close(); e != nil && err == nil {
file := bufio.NewWriter(transform.NewWriter(wf.writeCloser, b.encoding.NewEncoder())) err = e
b.Lock()
defer b.Unlock()
if len(b.lines) == 0 {
return 0, nil
} }
// end of line return
var eol []byte
if b.Endings == FFDos {
eol = []byte{'\r', '\n'}
} else {
eol = []byte{'\n'}
}
if !wf.withSudo {
f := wf.writeCloser.(*os.File)
err := f.Truncate(0)
if err != nil {
return 0, err
}
}
// write lines
size, err := file.Write(b.lines[0].data)
if err != nil {
return 0, err
}
for _, l := range b.lines[1:] {
if _, err = file.Write(eol); err != nil {
return 0, err
}
if _, err = file.Write(l.data); err != nil {
return 0, err
}
size += len(eol) + len(l.data)
}
err = file.Flush()
if err == nil && !wf.withSudo {
// Call Sync() on the file to make sure the content is safely on disk.
f := wf.writeCloser.(*os.File)
err = f.Sync()
}
return size, err
}
func (wf wrappedFile) Close() error {
err := wf.writeCloser.Close()
if wf.withSudo {
// wait for dd to finish and restart the screen if we used sudo
err := wf.cmd.Wait()
screen.TempStart(wf.screenb)
signal.Notify(util.Sigterm, os.Interrupt)
signal.Stop(wf.sigChan)
if err != nil {
return err
}
}
return err
}
func (b *Buffer) overwriteFile(name string) (int, error) {
file, err := openFile(name, false)
if err != nil {
return 0, err
}
size, err := file.Write(b)
err2 := file.Close()
if err2 != nil && err == nil {
err = err2
}
return size, err
} }
// Save saves the buffer to its default path // Save saves the buffer to its default path
@ -204,19 +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 {
// Doing full b.Modified() check every time would be costly, due to the hash
// calculation. So use just isModified even if fastdirty is not set.
if !b.isModified {
return nil
}
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 {
@ -224,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")
@ -239,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))
@ -257,35 +114,19 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
} }
} }
filename, err = util.ReplaceHome(filename) // Update the last time this file was updated after saving
if err != nil { defer func() {
return err b.ModTime, _ = util.GetModTime(filename)
} err = b.Serialize()
}()
newFile := false // Removes any tilde and replaces with the absolute path to home
fileInfo, err := os.Stat(filename) absFilename, _ := util.ReplaceHome(filename)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
newFile = true
}
if err == nil && fileInfo.IsDir() {
return errors.New("Error: " + filename + " is a directory and cannot be saved")
}
if err == nil && !fileInfo.Mode().IsRegular() {
return errors.New("Error: " + filename + " is not a regular file and cannot be saved")
}
absFilename, err := filepath.Abs(filename)
if err != nil {
return err
}
// Get the leading path to the file | "." is returned if there's no leading path provided // Get the leading path to the file | "." is returned if there's no leading path provided
if dirname := filepath.Dir(absFilename); dirname != "." { if dirname := filepath.Dir(absFilename); dirname != "." {
// Check if the parent dirs don't exist // Check if the parent dirs don't exist
if _, statErr := os.Stat(dirname); errors.Is(statErr, fs.ErrNotExist) { if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
// Prompt to make sure they want to create the dirs that are missing // Prompt to make sure they want to create the dirs that are missing
if b.Settings["mkparents"].(bool) { if b.Settings["mkparents"].(bool) {
// Create all leading dir(s) since they don't exist // Create all leading dir(s) since they don't exist
@ -299,22 +140,49 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
} }
} }
saveResponseChan := make(chan saveResponse) var fileSize int
saveRequestChan <- saveRequest{b, absFilename, withSudo, newFile, saveResponseChan}
result := <-saveResponseChan
err = result.err
if err != nil {
if errors.Is(err, util.ErrOverwrite) {
screen.TermMessage(err)
err = errors.Unwrap(err)
b.UpdateModTime() enc, err := htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
return err
}
fwriter := func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
} }
// end of line
var eol []byte
if b.Endings == FFDos {
eol = []byte{'\r', '\n'}
} else {
eol = []byte{'\n'}
}
// write lines
if fileSize, e = file.Write(b.lines[0].data); e != nil {
return
}
for _, l := range b.lines[1:] {
if _, e = file.Write(eol); e != nil {
return
}
if _, e = file.Write(l.data); e != nil {
return
}
fileSize += len(eol) + len(l.data)
}
return
}
if err = overwriteFile(absFilename, enc, fwriter, withSudo); err != nil {
return err return err
} }
if !b.Settings["fastdirty"].(bool) { if !b.Settings["fastdirty"].(bool) {
if result.size > LargeFileThreshold { if fileSize > LargeFileThreshold {
// For large files 'fastdirty' needs to be on // For large files 'fastdirty' needs to be on
b.Settings["fastdirty"] = true b.Settings["fastdirty"] = true
} else { } else {
@ -322,70 +190,10 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
} }
} }
newPath := b.Path != filename
b.Path = filename b.Path = filename
b.AbsPath = absFilename absPath, _ := filepath.Abs(filename)
b.AbsPath = absPath
b.isModified = false b.isModified = false
b.UpdateModTime() b.UpdateRules()
if newPath {
// need to update glob-based and filetype-based settings
b.ReloadSettings(true)
}
err = b.Serialize()
return err return err
} }
// safeWrite writes the buffer to a file in a "safe" way, preventing loss of the
// contents of the file if it fails to write the new contents.
// This means that the file is not overwritten directly but by writing to the
// backup file first.
func (b *Buffer) safeWrite(path string, withSudo bool, newFile bool) (int, error) {
file, err := openFile(path, withSudo)
if err != nil {
return 0, err
}
defer func() {
if newFile && err != nil {
os.Remove(path)
}
}()
backupDir := b.backupDir()
if _, err := os.Stat(backupDir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return 0, err
}
if err = os.Mkdir(backupDir, os.ModePerm); err != nil {
return 0, err
}
}
backupName := util.DetermineEscapePath(backupDir, path)
_, err = b.overwriteFile(backupName)
if err != nil {
os.Remove(backupName)
return 0, err
}
b.forceKeepBackup = true
size, err := file.Write(b)
if err != nil {
err = util.OverwriteError{err, backupName}
return size, err
}
b.forceKeepBackup = false
if !b.keepBackup() {
os.Remove(backupName)
}
err2 := file.Close()
if err2 != nil && err == nil {
err = err2
}
return size, err
}

View File

@ -2,56 +2,10 @@ package buffer
import ( import (
"regexp" "regexp"
"unicode/utf8"
"github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/micro/v2/internal/util"
) )
// We want "^" and "$" to match only the beginning/end of a line, not the
// beginning/end of the search region if it is in the middle of a line.
// In that case we use padded regexps to require a rune before or after
// the match. (This also affects other empty-string patters like "\\b".)
// The following two flags indicate the padding used.
const (
padStart = 1 << iota
padEnd
)
func findLineParams(b *Buffer, start, end Loc, i int, r *regexp.Regexp) ([]byte, int, int, *regexp.Regexp) {
l := b.LineBytes(i)
charpos := 0
padMode := 0
if i == end.Y {
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
if end.X < nchars {
l = util.SliceStart(l, end.X+1)
padMode |= padEnd
}
}
if i == start.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
if start.X > 0 {
charpos = start.X - 1
l = util.SliceEnd(l, charpos)
padMode |= padStart
}
}
if padMode == padStart {
r = regexp.MustCompile(".(?:" + r.String() + ")")
} else if padMode == padEnd {
r = regexp.MustCompile("(?:" + r.String() + ").")
} else if padMode == padStart|padEnd {
r = regexp.MustCompile(".(?:" + r.String() + ").")
}
return l, charpos, padMode, r
}
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)) lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
if start.Y > b.LinesNum()-1 { if start.Y > b.LinesNum()-1 {
@ -68,19 +22,30 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
} }
for i := start.Y; i <= end.Y; i++ { for i := start.Y; i <= end.Y; i++ {
l, charpos, padMode, rPadded := findLineParams(b, start, end, i, r) l := b.LineBytes(i)
charpos := 0
match := rPadded.FindIndex(l) if i == start.Y && start.Y == end.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
}
match := r.FindIndex(l)
if match != nil { if match != nil {
if padMode&padStart != 0 {
_, size := utf8.DecodeRune(l[match[0]:])
match[0] += size
}
if padMode&padEnd != 0 {
_, size := utf8.DecodeLastRune(l[:match[1]])
match[1] -= size
}
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
@ -105,39 +70,38 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
} }
for i := end.Y; i >= start.Y; i-- { for i := end.Y; i >= start.Y; i-- {
charCount := util.CharacterCount(b.LineBytes(i)) l := b.LineBytes(i)
from := Loc{0, i}.Clamp(start, end) charpos := 0
to := Loc{charCount, i}.Clamp(start, end)
allMatches := b.findAll(r, from, to) if i == start.Y && start.Y == end.Y {
if allMatches != nil { nchars := util.CharacterCount(l)
match := allMatches[len(allMatches)-1] start.X = util.Clamp(start.X, 0, nchars)
return [2]Loc{match[0], match[1]}, true end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
}
match := r.FindIndex(l)
if match != nil {
start := Loc{charpos + util.RunePos(l, match[0]), i}
end := Loc{charpos + util.RunePos(l, match[1]), i}
return [2]Loc{start, end}, true
} }
} }
return [2]Loc{}, false return [2]Loc{}, false
} }
func (b *Buffer) findAll(r *regexp.Regexp, start, end Loc) [][2]Loc {
var matches [][2]Loc
loc := start
for {
match, found := b.findDown(r, loc, end)
if !found {
break
}
matches = append(matches, match)
if match[0] != match[1] {
loc = match[1]
} else if match[1] != end {
loc = match[1].Move(1, b)
} else {
break
}
}
return matches
}
// FindNext finds the next occurrence of a given string in the buffer // FindNext finds the next occurrence of a given string in the buffer
// It returns the start and end location of the match (if found) and // It returns the start and end location of the match (if found) and
// a boolean indicating if it was found // a boolean indicating if it was found
@ -181,58 +145,49 @@ 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 characters // 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 on the last line of the range
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
} }
charsEnd := util.CharacterCount(b.LineBytes(end.Y)) netrunes := 0
found := 0 found := 0
var deltas []Delta var deltas []Delta
for i := start.Y; i <= end.Y; i++ { for i := start.Y; i <= end.Y; i++ {
l := b.LineBytes(i) l := b.lines[i].data
charCount := util.CharacterCount(l) charpos := 0
if (i == start.Y && start.X > 0) || (i == end.Y && end.X < charCount) {
// This replacement code works in general, but it creates a separate
// modification for each match. We only use it for the first and last
// lines, which may use padded regexps
from := Loc{0, i}.Clamp(start, end) if start.Y == end.Y && i == start.Y {
to := Loc{charCount, i}.Clamp(start, end) l = util.SliceStart(l, end.X)
matches := b.findAll(search, from, to) l = util.SliceEnd(l, start.X)
found += len(matches) charpos = start.X
} else if i == start.Y {
for j := len(matches) - 1; j >= 0; j-- { l = util.SliceEnd(l, start.X)
// if we counted upwards, the different deltas would interfere charpos = start.X
match := matches[j] } else if i == end.Y {
var newText []byte l = util.SliceStart(l, end.X)
if captureGroups {
newText = search.ReplaceAll(b.Substr(match[0], match[1]), replace)
} else {
newText = replace
}
deltas = append(deltas, Delta{newText, match[0], match[1]})
}
} else {
newLine := search.ReplaceAllFunc(l, func(in []byte) []byte {
found++
var result []byte
if captureGroups {
match := search.FindSubmatchIndex(in)
result = search.Expand(result, replace, in, match)
} else {
result = replace
}
return result
})
deltas = append(deltas, Delta{newLine, Loc{0, i}, Loc{charCount, i}})
} }
} newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
result := []byte{}
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
result = search.Expand(result, replace, in, submatches)
}
found++
if i == end.Y {
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
}
return result
})
from := Loc{charpos, i}
to := Loc{charpos + util.CharacterCount(l), i}
deltas = append(deltas, Delta{newText, from, to})
}
b.MultipleReplace(deltas) b.MultipleReplace(deltas)
return found, util.CharacterCount(b.LineBytes(end.Y)) - charsEnd return found, netrunes
} }

View File

@ -1,13 +1,15 @@
package buffer package buffer
import ( import (
"bytes"
"encoding/gob" "encoding/gob"
"errors" "errors"
"io"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"golang.org/x/text/encoding"
"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"
) )
@ -29,18 +31,16 @@ func (b *Buffer) Serialize() error {
return nil return nil
} }
var buf bytes.Buffer name := filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))
err := gob.NewEncoder(&buf).Encode(SerializedBuffer{
b.EventHandler,
b.GetActiveCursor().Loc,
b.ModTime,
})
if err != nil {
return err
}
name := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath) return overwriteFile(name, encoding.Nop, func(file io.Writer) error {
return util.SafeWrite(name, buf.Bytes(), true) err := gob.NewEncoder(file).Encode(SerializedBuffer{
b.EventHandler,
b.GetActiveCursor().Loc,
b.ModTime,
})
return err
}, false)
} }
// Unserialize loads the buffer info from config.ConfigDir/buffers // Unserialize loads the buffer info from config.ConfigDir/buffers
@ -50,7 +50,7 @@ func (b *Buffer) Unserialize() error {
if b.Path == "" { if b.Path == "" {
return nil return nil
} }
file, err := os.Open(util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath)) file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
if err == nil { if err == nil {
defer file.Close() defer file.Close()
var buffer SerializedBuffer var buffer SerializedBuffer

View File

@ -1,89 +1,26 @@
package buffer package buffer
import ( import (
"crypto/md5"
"reflect"
"github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/screen"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/encoding/unicode"
luar "layeh.com/gopher-luar"
) )
func (b *Buffer) ReloadSettings(reloadFiletype bool) { func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
settings := config.ParsedSettings()
config.UpdatePathGlobLocals(settings, b.AbsPath)
oldFiletype := b.Settings["filetype"].(string)
_, local := b.LocalSettings["filetype"]
_, volatile := config.VolatileSettings["filetype"]
if reloadFiletype && !local && !volatile {
// need to update filetype before updating other settings based on it
b.Settings["filetype"] = "unknown"
if v, ok := settings["filetype"]; ok {
b.Settings["filetype"] = v
}
}
// update syntax rules, which will also update filetype if needed
b.UpdateRules()
curFiletype := b.Settings["filetype"].(string)
if oldFiletype != curFiletype {
b.doCallbacks("filetype", oldFiletype, curFiletype)
}
config.UpdateFileTypeLocals(settings, curFiletype)
for k, v := range config.DefaultCommonSettings() {
if k == "filetype" {
// prevent recursion
continue
}
if _, ok := config.VolatileSettings[k]; ok {
// reload should not override volatile settings
continue
}
if _, ok := b.LocalSettings[k]; ok {
// reload should not override local settings
continue
}
if _, ok := settings[k]; ok {
b.DoSetOptionNative(k, settings[k])
} else {
b.DoSetOptionNative(k, v)
}
}
}
func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
oldValue := b.Settings[option]
if reflect.DeepEqual(oldValue, nativeValue) {
return
}
b.Settings[option] = nativeValue b.Settings[option] = nativeValue
if option == "fastdirty" { if option == "fastdirty" {
if !nativeValue.(bool) { if !nativeValue.(bool) {
if b.Size() > LargeFileThreshold { if !b.Modified() {
b.Settings["fastdirty"] = true e := calcHash(b, &b.origHash)
} else { if e == ErrFileTooLarge {
if !b.isModified { b.Settings["fastdirty"] = false
calcHash(b, &b.origHash)
} else {
// prevent using an old stale origHash value
b.origHash = [md5.Size]byte{}
} }
} }
} }
} else if option == "statusline" { } else if option == "statusline" {
screen.Redraw() screen.Redraw()
} else if option == "filetype" { } else if option == "filetype" {
b.ReloadSettings(false) b.UpdateRules()
} else if option == "fileformat" { } else if option == "fileformat" {
switch b.Settings["fileformat"].(string) { switch b.Settings["fileformat"].(string) {
case "unix": case "unix":
@ -99,53 +36,15 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
b.UpdateRules() b.UpdateRules()
} }
} else if option == "encoding" { } else if option == "encoding" {
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
enc = unicode.UTF8
b.Settings["encoding"] = "utf-8"
}
b.encoding = enc
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)
}
}
}
}
} }
b.doCallbacks(option, oldValue, nativeValue) if b.OptionCallback != nil {
} b.OptionCallback(option, nativeValue)
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
if err := config.OptionIsValid(option, nativeValue); err != nil {
return err
} }
b.DoSetOptionNative(option, nativeValue)
b.LocalSettings[option] = true
return nil return nil
} }
@ -162,15 +61,3 @@ func (b *Buffer) SetOption(option, value string) error {
return b.SetOptionNative(option, nativeValue) return b.SetOptionNative(option, nativeValue)
} }
func (b *Buffer) doCallbacks(option string, oldValue interface{}, newValue interface{}) {
if b.OptionCallback != nil {
b.OptionCallback(option, newValue)
}
if err := config.RunPluginFn("onBufferOptionChanged",
luar.New(ulua.L, b), luar.New(ulua.L, option),
luar.New(ulua.L, oldValue), luar.New(ulua.L, newValue)); err != nil {
screen.TermMessage(err)
}
}

View File

@ -3,7 +3,7 @@ package clipboard
import ( import (
"errors" "errors"
"github.com/zyedidia/clipper" "github.com/zyedidia/clipboard"
) )
type Method int type Method int
@ -35,19 +35,12 @@ const (
PrimaryReg = -2 PrimaryReg = -2
) )
var clipboard clipper.Clipboard
// Initialize attempts to initialize the clipboard using the given method // Initialize attempts to initialize the clipboard using the given method
func Initialize(m Method) error { func Initialize(m Method) error {
var err error var err error
switch m { switch m {
case External: case External:
clips := make([]clipper.Clipboard, 0, len(clipper.Clipboards)+1) err = clipboard.Initialize()
clips = append(clips, &clipper.Custom{
Name: "micro-clip",
})
clips = append(clips, clipper.Clipboards...)
clipboard, err = clipper.GetClipboard(clips...)
} }
if err != nil { if err != nil {
CurrentMethod = Internal CurrentMethod = Internal
@ -111,11 +104,9 @@ func read(r Register, m Method) (string, error) {
case External: case External:
switch r { switch r {
case ClipboardReg: case ClipboardReg:
b, e := clipboard.ReadAll(clipper.RegClipboard) return clipboard.ReadAll("clipboard")
return string(b), e
case PrimaryReg: case PrimaryReg:
b, e := clipboard.ReadAll(clipper.RegPrimary) return clipboard.ReadAll("primary")
return string(b), e
default: default:
return internal.read(r), nil return internal.read(r), nil
} }
@ -141,9 +132,9 @@ func write(text string, r Register, m Method) error {
case External: case External:
switch r { switch r {
case ClipboardReg: case ClipboardReg:
return clipboard.WriteAll(clipper.RegClipboard, []byte(text)) return clipboard.WriteAll(text, "clipboard")
case PrimaryReg: case PrimaryReg:
return clipboard.WriteAll(clipper.RegPrimary, []byte(text)) return clipboard.WriteAll(text, "primary")
default: default:
internal.write(text, r) internal.write(text, r)
} }

View File

@ -4,8 +4,8 @@ import (
"errors" "errors"
"time" "time"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell/v2"
) )
type terminalClipboard struct{} type terminalClipboard struct{}

View File

@ -1,49 +1,44 @@
package config package config
import ( import (
"sync"
"time" "time"
) )
var Autosave chan bool var Autosave chan bool
var autotime chan float64 var autotime int
// lock for autosave
var autolock sync.Mutex
func init() { func init() {
Autosave = make(chan bool) Autosave = make(chan bool)
autotime = make(chan float64)
} }
func SetAutoTime(a float64) { func SetAutoTime(a int) {
autotime <- a autolock.Lock()
autotime = a
autolock.Unlock()
}
func GetAutoTime() int {
autolock.Lock()
a := autotime
autolock.Unlock()
return a
} }
func StartAutoSave() { func StartAutoSave() {
go func() { go func() {
var a float64
var t *time.Timer
var elapsed <-chan time.Time
for { for {
select { autolock.Lock()
case a = <-autotime: a := autotime
if t != nil { autolock.Unlock()
t.Stop() if a < 1 {
for len(elapsed) > 0 { break
<-elapsed
}
}
if a > 0 {
if t != nil {
t.Reset(time.Duration(a * float64(time.Second)))
} else {
t = time.NewTimer(time.Duration(a * float64(time.Second)))
elapsed = t.C
}
}
case <-elapsed:
if a > 0 {
t.Reset(time.Duration(a * float64(time.Second)))
Autosave <- true
}
} }
time.Sleep(time.Duration(a) * time.Second)
Autosave <- true
} }
}() }()
} }

View File

@ -6,13 +6,13 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/micro-editor/tcell/v2" "github.com/zyedidia/tcell/v2"
) )
// 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])
@ -166,22 +131,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,52 +160,49 @@ 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
} }
} }

View File

@ -3,8 +3,8 @@ package config
import ( import (
"testing" "testing"
"github.com/micro-editor/tcell/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zyedidia/tcell/v2"
) )
func TestSimpleStringToStyle(t *testing.T) { func TestSimpleStringToStyle(t *testing.T) {
@ -65,7 +65,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()

View File

@ -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...)
@ -74,8 +74,8 @@ type Plugin struct {
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 {
@ -143,3 +143,15 @@ func FindPlugin(name string) *Plugin {
} }
return pl return pl
} }
// FindAnyPlugin does not require the plugin to be enabled
func FindAnyPlugin(name string) *Plugin {
var pl *Plugin
for _, p := range Plugins {
if p.Name == name {
pl = p
break
}
}
return pl
}

View File

@ -5,6 +5,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -13,8 +14,8 @@ import (
"sync" "sync"
"github.com/blang/semver" "github.com/blang/semver"
"github.com/micro-editor/json5"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/json5"
ulua "github.com/zyedidia/micro/v2/internal/lua" ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/micro/v2/internal/util"
) )
@ -362,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)
@ -395,7 +396,7 @@ func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return err return err
} }
@ -571,7 +572,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 {
@ -604,7 +605,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() || p.Default {
continue continue
} }
plugins = append(plugins, p.Name) plugins = append(plugins, p.Name)

View File

@ -5,7 +5,7 @@ import (
"github.com/blang/semver" "github.com/blang/semver"
"github.com/micro-editor/json5" "github.com/zyedidia/json5"
) )
func TestDependencyResolving(t *testing.T) { func TestDependencyResolving(t *testing.T) {

View File

@ -2,13 +2,13 @@ package config
import ( import (
"errors" "errors"
"io/ioutil"
"log" "log"
"os" "os"
"path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
rt "github.com/zyedidia/micro/v2/runtime"
) )
const ( const (
@ -38,10 +38,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)
} }
@ -60,6 +56,12 @@ type realFile string
// some asset file // some asset file
type assetFile string type assetFile string
// some file on filesystem but with a different name
type namedFile struct {
realFile
name string
}
// a file with the data stored in memory // a file with the data stored in memory
type memoryFile struct { type memoryFile struct {
name string name string
@ -79,16 +81,20 @@ func (rf realFile) Name() string {
} }
func (rf realFile) Data() ([]byte, error) { func (rf realFile) Data() ([]byte, error) {
return os.ReadFile(string(rf)) return ioutil.ReadFile(string(rf))
} }
func (af assetFile) Name() string { func (af assetFile) Name() string {
fn := filepath.Base(string(af)) fn := path.Base(string(af))
return fn[:len(fn)-len(filepath.Ext(fn))] return fn[:len(fn)-len(path.Ext(fn))]
} }
func (af assetFile) Data() ([]byte, error) { func (af assetFile) Data() ([]byte, error) {
return rt.Asset(string(af)) return Asset(string(af))
}
func (nf namedFile) Name() string {
return nf.name
} }
// AddRuntimeFile registers a file for the given filetype // AddRuntimeFile registers a file for the given filetype
@ -105,7 +111,7 @@ 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, _ := os.ReadDir(directory) files, _ := ioutil.ReadDir(directory)
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())
@ -117,21 +123,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 := AssetDir(directory)
if err != nil { if err != nil {
return return
} }
assetLoop:
for _, f := range files { for _, f := range files {
if ok, _ := filepath.Match(pattern, f); ok { if ok, _ := path.Match(pattern, f); ok {
af := assetFile(filepath.Join(directory, f)) AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
for _, rf := range realFiles[fileType] {
if af.Name() == rf.Name() {
continue assetLoop
}
}
AddRuntimeFile(fileType, af)
} }
} }
} }
@ -158,30 +156,19 @@ func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
return realFiles[fileType] return realFiles[fileType]
} }
// InitRuntimeFiles initializes all assets files and the config directory. // InitRuntimeFiles initializes all assets file and the config directory
// If `user` is false, InitRuntimeFiles ignores the config directory and func InitRuntimeFiles() {
// initializes asset files only.
func InitRuntimeFiles(user bool) {
add := func(fileType RTFiletype, dir, pattern string) { add := func(fileType RTFiletype, dir, pattern string) {
if user { AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern) AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
}
AddRuntimeFilesFromAssets(fileType, filepath.Join("runtime", dir), pattern)
} }
initRuntimeVars()
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"
@ -192,14 +179,13 @@ func InitPlugins() {
// Search ConfigDir for plugin-scripts // Search ConfigDir for plugin-scripts
plugdir := filepath.Join(ConfigDir, "plug") plugdir := filepath.Join(ConfigDir, "plug")
files, _ := os.ReadDir(plugdir) files, _ := ioutil.ReadDir(plugdir)
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
for _, d := range files { for _, d := range files {
plugpath := filepath.Join(plugdir, d.Name()) if d.IsDir() {
if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() { srcs, _ := ioutil.ReadDir(filepath.Join(plugdir, d.Name()))
srcs, _ := os.ReadDir(plugpath)
p := new(Plugin) p := new(Plugin)
p.Name = d.Name() p.Name = d.Name()
p.DirName = d.Name() p.DirName = d.Name()
@ -207,7 +193,7 @@ func InitPlugins() {
if strings.HasSuffix(f.Name(), ".lua") { if strings.HasSuffix(f.Name(), ".lua") {
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name()))) p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
} else if strings.HasSuffix(f.Name(), ".json") { } else if strings.HasSuffix(f.Name(), ".json") {
data, err := os.ReadFile(filepath.Join(plugdir, d.Name(), f.Name())) data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
if err != nil { if err != nil {
continue continue
} }
@ -228,17 +214,9 @@ func InitPlugins() {
} }
plugdir = filepath.Join("runtime", "plugins") plugdir = filepath.Join("runtime", "plugins")
if files, err := rt.AssetDir(plugdir); err == nil { if files, err := AssetDir(plugdir); err == nil {
outer:
for _, d := range files { for _, d := range files {
for _, p := range Plugins { if srcs, err := AssetDir(filepath.Join(plugdir, d)); err == nil {
if p.Name == d {
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 := new(Plugin)
p.Name = d p.Name = d
p.DirName = d p.DirName = d
@ -247,7 +225,7 @@ func InitPlugins() {
if strings.HasSuffix(f, ".lua") { if strings.HasSuffix(f, ".lua") {
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f))) p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
} else if strings.HasSuffix(f, ".json") { } else if strings.HasSuffix(f, ".json") {
data, err := rt.Asset(filepath.Join(plugdir, d, f)) data, err := Asset(filepath.Join(plugdir, d, f))
if err != nil { if err != nil {
continue continue
} }
@ -299,7 +277,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 = filepath.Join("runtime", "plugins", pldir, filePath) fullpath = path.Join("runtime", "plugins", pldir, filePath)
AddRuntimeFile(filetype, assetFile(fullpath)) AddRuntimeFile(filetype, assetFile(fullpath))
} }
return nil return nil
@ -316,7 +294,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 = filepath.Join("runtime", "plugins", pldir, directory) fullpath = path.Join("runtime", "plugins", pldir, directory)
AddRuntimeFilesFromAssets(filetype, fullpath, pattern) AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
} }
return nil return nil

View File

@ -7,7 +7,7 @@ import (
) )
func init() { func init() {
InitRuntimeFiles(false) InitRuntimeFiles()
} }
func TestAddFile(t *testing.T) { func TestAddFile(t *testing.T) {

7625
internal/config/runtime.go Normal file

File diff suppressed because one or more lines are too long

View File

@ -4,140 +4,21 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime"
"strconv" "strconv"
"strings" "strings"
"github.com/micro-editor/json5"
"github.com/zyedidia/glob" "github.com/zyedidia/glob"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding/htmlindex" "golang.org/x/text/encoding/htmlindex"
) )
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,
"helpsplit": validateChoice,
"matchbracestyle": validateChoice,
"multiopen": validateChoice,
"pageoverlap": validateNonNegativeValue,
"reload": validateChoice,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
"tabsize": validatePositiveValue,
"truecolor": validateChoice,
}
// a list of settings with pre-defined choices
var OptionChoices = map[string][]string{
"clipboard": {"internal", "external", "terminal"},
"fileformat": {"unix", "dos"},
"helpsplit": {"hsplit", "vsplit"},
"matchbracestyle": {"underline", "highlight"},
"multiopen": {"tab", "hsplit", "vsplit"},
"reload": {"prompt", "auto", "disabled"},
"truecolor": {"auto", "on", "off"},
}
// 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,
"ignorecase": true,
"incsearch": true,
"indentchar": " ",
"keepautoindent": false,
"matchbrace": true,
"matchbraceleft": true,
"matchbracestyle": "underline",
"mkparents": false,
"pageoverlap": float64(2),
"permbackup": false,
"readonly": false,
"relativeruler": false,
"reload": "prompt",
"rmtrailingws": false,
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"smartpaste": true,
"softwrap": false,
"splitbottom": true,
"splitright": true,
"statusformatl": "$(filename) $(modified)$(overwrite)($(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,
"truecolor": "auto",
"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,
"helpsplit": "hsplit",
"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")
@ -152,83 +33,30 @@ var (
// 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 writeFile(name string, txt []byte) error {
return util.SafeWrite(name, txt, false)
}
func init() { func init() {
ModifiedSettings = make(map[string]bool) ModifiedSettings = make(map[string]bool)
VolatileSettings = make(map[string]bool) parsedSettings = make(map[string]interface{})
} }
func validateParsedSettings() error { // Options with validators
var err error var optionValidators = map[string]optionValidator{
defaults := DefaultAllSettings() "autosave": validateNonNegativeValue,
for k, v := range parsedSettings { "clipboard": validateClipboard,
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") { "tabsize": validatePositiveValue,
if strings.HasPrefix(k, "ft:") { "scrollmargin": validateNonNegativeValue,
for k1, v1 := range v.(map[string]interface{}) { "scrollspeed": validateNonNegativeValue,
if _, ok := defaults[k1]; ok { "colorscheme": validateColorscheme,
if e := verifySetting(k1, v1, defaults[k1]); e != nil { "colorcolumn": validateNonNegativeValue,
err = e "fileformat": validateLineEnding,
parsedSettings[k].(map[string]interface{})[k1] = defaults[k1] "encoding": validateEncoding,
continue
}
}
}
} else {
if _, e := glob.Compile(k); e != nil {
err = errors.New("Error with glob setting " + k + ": " + e.Error())
delete(parsedSettings, k)
continue
}
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := defaults[k1]; ok {
if e := verifySetting(k1, v1, defaults[k1]); e != nil {
err = e
parsedSettings[k].(map[string]interface{})[k1] = defaults[k1]
continue
}
}
}
}
continue
}
if k == "autosave" {
// if autosave is a boolean convert it to float
s, ok := v.(bool)
if ok {
if s {
parsedSettings["autosave"] = 8.0
} else {
parsedSettings["autosave"] = 0.0
}
}
continue
}
if _, ok := defaults[k]; ok {
if e := verifySetting(k, v, defaults[k]); e != nil {
err = e
parsedSettings[k] = defaults[k]
continue
}
}
}
return err
} }
func ReadSettings() error { func ReadSettings() error {
parsedSettings = make(map[string]interface{})
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 := os.ReadFile(filename) input, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
settingsParseError = true settingsParseError = true
return errors.New("Error reading settings.json file: " + err.Error()) return errors.New("Error reading settings.json file: " + err.Error())
@ -240,91 +68,89 @@ func ReadSettings() error {
settingsParseError = true settingsParseError = true
return errors.New("Error reading settings.json: " + err.Error()) return errors.New("Error reading settings.json: " + err.Error())
} }
err = validateParsedSettings()
if err != nil { // check if autosave is a boolean and convert it to float if so
return err if v, ok := parsedSettings["autosave"]; ok {
s, ok := v.(bool)
if ok {
if s {
parsedSettings["autosave"] = 8.0
} else {
parsedSettings["autosave"] = 0.0
}
}
} }
} }
} }
return nil return nil
} }
func ParsedSettings() map[string]interface{} { func verifySetting(option string, value reflect.Type, def reflect.Type) bool {
s := make(map[string]interface{})
for k, v := range parsedSettings {
s[k] = v
}
return s
}
func verifySetting(option string, value interface{}, def interface{}) error {
var interfaceArr []interface{} var interfaceArr []interface{}
valType := reflect.TypeOf(value)
defType := reflect.TypeOf(def)
assignable := false
switch option { switch option {
case "pluginrepos", "pluginchannels": case "pluginrepos", "pluginchannels":
assignable = valType.AssignableTo(reflect.TypeOf(interfaceArr)) return value.AssignableTo(reflect.TypeOf(interfaceArr))
default: default:
assignable = defType.AssignableTo(valType) return def.AssignableTo(value)
} }
if !assignable {
return fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", option, valType, def, defType)
}
if err := OptionIsValid(option, value); err != nil {
return err
}
return nil
} }
// InitGlobalSettings initializes the options map and sets all options to their default values // InitGlobalSettings initializes the options map and sets all options to their default values
// Must be called after ReadSettings // Must be called after ReadSettings
func InitGlobalSettings() error { func InitGlobalSettings() error {
var err error var err error
GlobalSettings = DefaultAllSettings() GlobalSettings = DefaultGlobalSettings()
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])) {
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]))
continue
}
GlobalSettings[k] = v GlobalSettings[k] = v
} }
} }
return err return err
} }
// UpdatePathGlobLocals scans the already parsed settings and sets the options locally // InitLocalSettings scans the json in settings.json and sets the options locally based
// based on whether the path matches a glob // on whether the filetype or path matches ft or glob local settings
// Must be called after ReadSettings // Must be called after ReadSettings
func UpdatePathGlobLocals(settings map[string]interface{}, path string) { func InitLocalSettings(settings map[string]interface{}, path string) error {
var parseError error
for k, v := range parsedSettings { for k, v := range parsedSettings {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && !strings.HasPrefix(k, "ft:") { if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
g, _ := glob.Compile(k) if strings.HasPrefix(k, "ft:") {
if g.MatchString(path) { if settings["filetype"].(string) == k[3:] {
for k1, v1 := range v.(map[string]interface{}) { for k1, v1 := range v.(map[string]interface{}) {
settings[k1] = v1 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]))
continue
}
settings[k1] = v1
}
}
} else {
g, err := glob.Compile(k)
if err != nil {
parseError = errors.New("Error with glob setting " + k + ": " + err.Error())
continue
} }
}
}
}
}
// UpdateFileTypeLocals scans the already parsed settings and sets the options locally if g.MatchString(path) {
// based on whether the filetype matches to "ft:" for k1, v1 := range v.(map[string]interface{}) {
// Must be called after ReadSettings if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
func UpdateFileTypeLocals(settings map[string]interface{}, filetype string) { 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]))
for k, v := range parsedSettings { continue
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && strings.HasPrefix(k, "ft:") { }
if filetype == k[3:] {
for k1, v1 := range v.(map[string]interface{}) {
if k1 != "filetype" {
settings[k1] = v1 settings[k1] = v1
} }
} }
} }
} }
} }
return parseError
} }
// WriteSettings writes the settings to the specified filename as JSON // WriteSettings writes the settings to the specified filename as JSON
@ -339,14 +165,13 @@ func WriteSettings(filename string) error {
var err error var err error
if _, e := os.Stat(ConfigDir); e == nil { if _, e := os.Stat(ConfigDir); e == nil {
defaults := DefaultAllSettings() defaults := DefaultGlobalSettings()
// remove any options froms parsedSettings that have since been marked as default // remove any options froms parsedSettings that have since been marked as default
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)
} }
} }
@ -362,8 +187,7 @@ func WriteSettings(filename string) error {
} }
txt, _ := json.MarshalIndent(parsedSettings, "", " ") txt, _ := json.MarshalIndent(parsedSettings, "", " ")
txt = append(txt, '\n') err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
err = writeFile(filename, txt)
} }
return err return err
} }
@ -375,7 +199,7 @@ func OverwriteSettings(filename string) error {
var err error var err error
if _, e := os.Stat(ConfigDir); e == nil { if _, e := os.Stat(ConfigDir); e == nil {
defaults := DefaultAllSettings() defaults := DefaultGlobalSettings()
for k, v := range GlobalSettings { for k, v := range GlobalSettings {
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) { if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
if _, wr := ModifiedSettings[k]; wr { if _, wr := ModifiedSettings[k]; wr {
@ -384,16 +208,26 @@ func OverwriteSettings(filename string) error {
} }
} }
txt, _ := json.MarshalIndent(parsedSettings, "", " ") txt, _ := json.MarshalIndent(settings, "", " ")
txt = append(txt, '\n') err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
err = writeFile(filename, txt)
} }
return err return err
} }
// 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 _, 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] = defaultvalue
}
return nil
} }
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name) // RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
@ -401,21 +235,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
} }
@ -424,11 +255,49 @@ 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" "backupdir": "",
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"diffgutter": false,
"encoding": "utf-8",
"eofnewline": true,
"fastdirty": false,
"fileformat": "unix",
"filetype": "unknown",
"incsearch": true,
"ignorecase": true,
"indentchar": " ",
"keepautoindent": false,
"matchbrace": true,
"mkparents": false,
"permbackup": 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,
"wordwrap": false,
} }
func GetInfoBarOffset() int { func GetInfoBarOffset() int {
@ -442,8 +311,8 @@ func GetInfoBarOffset() int {
return offset return offset
} }
// DefaultCommonSettings returns a map of all common buffer settings // DefaultCommonSettings returns the default global settings for micro
// and their default values // Note that colorscheme is a global only option
func DefaultCommonSettings() map[string]interface{} { func DefaultCommonSettings() map[string]interface{} {
commonsettings := make(map[string]interface{}) commonsettings := make(map[string]interface{})
for k, v := range defaultCommonSettings { for k, v := range defaultCommonSettings {
@ -452,8 +321,47 @@ func DefaultCommonSettings() map[string]interface{} {
return commonsettings return commonsettings
} }
// DefaultAllSettings returns a map of all common buffer & global-only settings // a list of settings that should only be globally modified and their
// and their default values // default values
var DefaultGlobalOnlySettings = map[string]interface{}{
"autosave": float64(0),
"clipboard": "external",
"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
// Note that colorscheme is a global only option
func DefaultGlobalSettings() map[string]interface{} {
globalsettings := make(map[string]interface{})
for k, v := range defaultCommonSettings {
globalsettings[k] = v
}
for k, v := range DefaultGlobalOnlySettings {
globalsettings[k] = v
}
return globalsettings
}
// DefaultAllSettings returns a map of all settings and their
// default values (both common and global settings)
func DefaultAllSettings() map[string]interface{} { func DefaultAllSettings() map[string]interface{} {
allsettings := make(map[string]interface{}) allsettings := make(map[string]interface{})
for k, v := range defaultCommonSettings { for k, v := range defaultCommonSettings {
@ -478,15 +386,18 @@ func GetNativeValue(option string, realValue interface{}, value string) (interfa
} else if kind == reflect.String { } else if kind == reflect.String {
native = value native = value
} else if kind == reflect.Float64 { } else if kind == reflect.Float64 {
f, err := strconv.ParseFloat(value, 64) i, err := strconv.Atoi(value)
if err != nil { if err != nil {
return nil, ErrInvalidValue return nil, ErrInvalidValue
} }
native = f native = float64(i)
} else { } else {
return nil, ErrInvalidValue return nil, ErrInvalidValue
} }
if err := OptionIsValid(option, native); err != nil {
return nil, err
}
return native, nil return native, nil
} }
@ -502,13 +413,13 @@ func OptionIsValid(option string, value interface{}) error {
// Option validators // Option validators
func validatePositiveValue(option string, value interface{}) error { func validatePositiveValue(option string, value interface{}) error {
nativeValue, ok := value.(float64) tabsize, ok := value.(float64)
if !ok { if !ok {
return errors.New("Expected numeric type for " + option) return errors.New("Expected numeric type for " + option)
} }
if nativeValue < 1 { if tabsize < 1 {
return errors.New(option + " must be greater than 0") return errors.New(option + " must be greater than 0")
} }
@ -529,26 +440,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)
@ -563,6 +454,36 @@ func validateColorscheme(option string, value interface{}) error {
return nil return nil
} }
func validateClipboard(option string, value interface{}) error {
val, ok := value.(string)
if !ok {
return errors.New("Expected string type for clipboard")
}
switch val {
case "internal", "external", "terminal":
default:
return errors.New(option + " must be 'internal', 'external', or 'terminal'")
}
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

View File

@ -4,14 +4,15 @@ import (
"strconv" "strconv"
runewidth "github.com/mattn/go-runewidth" runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/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"
"github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
) )
// 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
@ -43,7 +44,6 @@ 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{}) { b.OptionCallback = func(option string, nativeValue interface{}) {
@ -53,50 +53,43 @@ func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
} else { } else {
w.StartLine.Row = 0 w.StartLine.Row = 0
} }
}
if option == "softwrap" || option == "wordwrap" {
w.Relocate() w.Relocate()
for _, c := range w.Buf.GetCursors() { for _, c := range w.Buf.GetCursors() {
c.LastWrappedVisualX = c.GetVisualX(true) c.LastVisualX = c.GetVisualX()
} }
} }
if option == "diffgutter" || option == "ruler" || option == "scrollbar" ||
option == "statusline" {
w.updateDisplayInfo()
w.Relocate()
}
} }
b.GetVisualX = func(loc buffer.Loc) int { b.GetVisualX = func(loc buffer.Loc) int {
return w.VLocFromLoc(loc).VisualX 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.updateDisplayInfo()
w.Relocate() w.Relocate()
if w.Buf.Settings["softwrap"].(bool) {
for _, c := range w.Buf.GetCursors() {
c.LastVisualX = c.GetVisualX()
}
}
} }
// 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
} }
@ -135,11 +128,6 @@ func (w *BufWindow) updateDisplayInfo() {
w.bufHeight-- 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 w.hasMessage = len(b.Messages) > 0
// We need to know the string length of the largest line number // We need to know the string length of the largest line number
@ -157,17 +145,9 @@ func (w *BufWindow) updateDisplayInfo() {
w.gutterOffset += w.maxLineNumLength + 1 w.gutterOffset += w.maxLineNumLength + 1
} }
if w.gutterOffset > w.Width-scrollbarWidth { w.bufWidth = w.Width - w.gutterOffset
w.gutterOffset = w.Width - scrollbarWidth if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
} w.bufWidth--
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.LastWrappedVisualX = c.GetVisualX(true)
}
} }
} }
@ -234,7 +214,7 @@ func (w *BufWindow) Relocate() bool {
w.StartLine = c w.StartLine = c
ret = true ret = true
} }
if c.GreaterThan(w.Scroll(w.StartLine, height-1-scrollmargin)) && c.LessEqual(w.Scroll(bEnd, -scrollmargin)) { if c.GreaterThan(w.Scroll(w.StartLine, height-1-scrollmargin)) && c.LessThan(w.Scroll(bEnd, -scrollmargin+1)) {
w.StartLine = w.Scroll(c, -height+1+scrollmargin) w.StartLine = w.Scroll(c, -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 c.GreaterThan(w.Scroll(bEnd, -scrollmargin)) && c.GreaterThan(w.Scroll(w.StartLine, height-1)) {
@ -244,7 +224,7 @@ func (w *BufWindow) Relocate() bool {
// horizontal relocation (scrolling) // horizontal relocation (scrolling)
if !b.Settings["softwrap"].(bool) { if !b.Settings["softwrap"].(bool) {
cx := activeC.GetVisualX(false) cx := activeC.GetVisualX()
rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X)) rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
if rw == 0 { if rw == 0 {
rw = 1 // tab or newline rw = 1 // tab or newline
@ -254,8 +234,8 @@ func (w *BufWindow) Relocate() bool {
w.StartCol = cx w.StartCol = cx
ret = true ret = true
} }
if cx+rw > w.StartCol+w.bufWidth { if cx+w.gutterOffset+rw > w.StartCol+w.Width {
w.StartCol = cx - w.bufWidth + rw w.StartCol = cx - w.Width + w.gutterOffset + rw
ret = true ret = true
} }
} }
@ -288,17 +268,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 := ""
@ -334,28 +310,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 < w.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
@ -390,7 +364,18 @@ func (w *BufWindow) displayBuffer() {
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
} }
@ -398,20 +383,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))
} }
} }
} }
@ -455,7 +446,7 @@ func (w *BufWindow) displayBuffer() {
currentLine := false currentLine := false
for _, c := range cursors { for _, c := range cursors {
if !c.HasSelection() && bloc.Y == c.Y && w.active { if bloc.Y == c.Y && w.active {
currentLine = true currentLine = true
break break
} }
@ -482,12 +473,6 @@ func (w *BufWindow) displayBuffer() {
vloc.X = w.gutterOffset vloc.X = w.gutterOffset
} }
bline := b.LineBytes(bloc.Y)
blineLen := util.CharacterCount(bline)
leadingwsEnd := len(util.GetLeadingWhitespace(bline))
trailingwsStart := blineLen - util.CharacterCount(util.GetTrailingWhitespace(bline))
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 {
curStyle = *startStyle curStyle = *startStyle
@ -497,51 +482,13 @@ func (w *BufWindow) displayBuffer() {
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool) { draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool) {
if nColsBeforeStart <= 0 && vloc.Y >= 0 { if nColsBeforeStart <= 0 && vloc.Y >= 0 {
if highlight { if highlight {
if w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {
style = config.DefStyle.Reverse(true)
if s, ok := config.Colorscheme["hlsearch"]; ok {
style = s
}
}
_, origBg, _ := style.Decompose() _, origBg, _ := style.Decompose()
_, defBg, _ := config.DefStyle.Decompose() _, defBg, _ := config.DefStyle.Decompose()
// syntax or hlsearch highlighting with non-default background takes precedence // syntax highlighting with non-default background takes precedence
// over cursor-line and color-column // over cursor-line and color-column
dontOverrideBackground := origBg != defBg 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 { for _, c := range cursors {
if c.HasSelection() && if c.HasSelection() &&
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) || (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
@ -594,15 +541,7 @@ func (w *BufWindow) displayBuffer() {
for _, mb := range matchingBraces { for _, mb := range matchingBraces {
if mb.X == bloc.X && mb.Y == bloc.Y { if mb.X == bloc.X && mb.Y == bloc.Y {
if b.Settings["matchbracestyle"].(string) == "highlight" { style = style.Underline(true)
if s, ok := config.Colorscheme["match-brace"]; ok {
style = s
} else {
style = style.Reverse(true)
}
} else {
style = style.Underline(true)
}
} }
} }
} }
@ -625,21 +564,16 @@ func (w *BufWindow) displayBuffer() {
wrap := func() { wrap := func() {
vloc.X = 0 vloc.X = 0
if w.hasMessage {
w.drawGutter(&vloc, &bloc)
}
if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
}
if vloc.Y >= 0 { // This will draw an empty line number because the current line is wrapped
if w.hasMessage { if b.Settings["ruler"].(bool) {
w.drawGutter(&vloc, &bloc) w.drawLineNum(lineNumStyle, true, &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)
}
} else {
vloc.X = w.gutterOffset
} }
} }
@ -659,7 +593,7 @@ func (w *BufWindow) displayBuffer() {
wordwidth := 0 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:] line = line[size:]
@ -817,14 +751,8 @@ func (w *BufWindow) displayScrollBar() {
scrollBarStyle = style 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++ { for y := barstart; y < util.Min(barstart+barsize, w.Y+w.bufHeight); y++ {
screen.SetContent(scrollX, y, scrollBarRune[0], nil, scrollBarStyle) screen.SetContent(scrollX, y, '|', nil, scrollBarStyle)
} }
} }
} }

View File

@ -2,12 +2,12 @@ package display
import ( import (
runewidth "github.com/mattn/go-runewidth" runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/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/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"
) )
type InfoWindow struct { type InfoWindow struct {
@ -260,9 +260,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

View File

@ -30,28 +30,6 @@ func (s SLoc) GreaterThan(b SLoc) bool {
return s.Line == b.Line && s.Row > b.Row 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 // VLoc represents a location in the buffer as a visual location in the
// linewrapped buffer. // linewrapped buffer.
type VLoc struct { type VLoc struct {
@ -291,7 +269,13 @@ func (w *BufWindow) diff(s1, s2 SLoc) int {
// within the buffer boundaries. // within the buffer boundaries.
func (w *BufWindow) Scroll(s SLoc, n int) SLoc { func (w *BufWindow) Scroll(s SLoc, n int) SLoc {
if !w.Buf.Settings["softwrap"].(bool) { if !w.Buf.Settings["softwrap"].(bool) {
s.Line = util.Clamp(s.Line+n, 0, w.Buf.LinesNum()-1) 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 s
} }
return w.scroll(s, n) return w.scroll(s, n)

View File

@ -47,18 +47,6 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
} }
return "" return ""
}, },
"overwrite": func(b *buffer.Buffer) string {
if b.OverwriteMode && !b.Type.Readonly {
return "[ovwr] "
}
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) {
@ -72,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))
@ -116,9 +104,7 @@ func (s *StatusLine) Display() {
// 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
} }
x := 0 x := 0
@ -175,16 +161,8 @@ 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)

View File

@ -2,7 +2,6 @@ package display
import ( import (
runewidth "github.com/mattn/go-runewidth" runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/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,27 +94,16 @@ 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) tabBarActiveStyle := tabBarStyle
if style, ok := config.Colorscheme["tabbar.active"]; ok {
reverseStyles := func(reverse bool) (tcell.Style, tcell.Style) { tabBarActiveStyle = 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, active bool) {
tabBarStyle, tabBarActiveStyle := reverseStyles(reversed)
style := tabBarStyle style := tabBarStyle
if active { if active {
style = tabBarActiveStyle style = tabBarActiveStyle
@ -143,33 +131,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, true)
} else { } else {
draw(' ', 1, false, tabCharHighlight) draw(' ', 1, false)
} }
for _, c := range n { for _, c := range n {
draw(c, 1, i == w.active, tabCharHighlight) draw(c, 1, i == w.active)
} }
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, true)
draw(' ', 2, true, globalTabReverse) draw(' ', 2, true)
} else { } else {
draw(' ', 1, false, tabCharHighlight) draw(' ', 3, false)
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, false)
} }
} }

View File

@ -1,13 +1,13 @@
package display package display
import ( import (
"github.com/micro-editor/tcell/v2"
"github.com/micro-editor/terminal"
"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"
"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/terminal"
) )
type TermWindow struct { type TermWindow struct {
@ -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)
}
} }
} }

View File

@ -1,17 +1,11 @@
package info package info
import ( import (
"bytes"
"encoding/gob" "encoding/gob"
"errors"
"io/fs"
"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/screen"
"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
@ -21,23 +15,24 @@ 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"))
var decodedMap map[string][]string var decodedMap map[string][]string
if err != nil { if err == nil {
if !errors.Is(err, fs.ErrNotExist) { defer file.Close()
i.Error("Error loading history: ", err) decoder := gob.NewDecoder(file)
} err = decoder.Decode(&decodedMap)
return
}
defer file.Close() if err != nil {
err = gob.NewDecoder(file).Decode(&decodedMap) i.Error("Error loading history:", err)
if err != nil { return
i.Error("Error decoding history: ", err) }
return
} }
if decodedMap != nil { if decodedMap != nil {
i.History = decodedMap i.History = decodedMap
} else {
i.History = make(map[string][]string)
} }
} else {
i.History = make(map[string][]string)
} }
} }
@ -52,18 +47,16 @@ func (i *InfoBuf) SaveHistory() {
} }
} }
var buf bytes.Buffer file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
err := gob.NewEncoder(&buf).Encode(i.History) if err == nil {
if err != nil { defer file.Close()
screen.TermMessage("Error encoding history: ", err) encoder := gob.NewEncoder(file)
return
}
filename := filepath.Join(config.ConfigDir, "buffers", "history") err = encoder.Encode(i.History)
err = util.SafeWrite(filename, buf.Bytes(), true) if err != nil {
if err != nil { i.Error("Error saving history:", err)
screen.TermMessage("Error saving history: ", err) return
return }
} }
} }
} }
@ -109,51 +102,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())
}
}

View File

@ -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
@ -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
@ -143,12 +138,13 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
if i.PromptCallback != nil { if i.PromptCallback != nil {
if canceled { if canceled {
i.Replace(i.Start(), i.End(), "") 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.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
@ -159,8 +155,6 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
break break
} }
} }
i.PromptCallback(resp, false)
} }
// i.PromptCallback = nil // i.PromptCallback = nil
} }

View File

@ -17,6 +17,7 @@ import (
"regexp" "regexp"
"runtime" "runtime"
"strings" "strings"
"sync"
"time" "time"
"unicode/utf8" "unicode/utf8"
@ -26,6 +27,7 @@ import (
) )
var L *lua.LState var L *lua.LState
var Lock sync.Mutex
// LoadFile loads a lua file // LoadFile loads a lua file
func LoadFile(module string, file string, data []byte) error { func LoadFile(module string, file string, data []byte) error {
@ -125,7 +127,6 @@ func importIo() *lua.LTable {
L.SetField(pkg, "MultiWriter", luar.New(L, io.MultiWriter)) L.SetField(pkg, "MultiWriter", luar.New(L, io.MultiWriter))
L.SetField(pkg, "NewSectionReader", luar.New(L, io.NewSectionReader)) L.SetField(pkg, "NewSectionReader", luar.New(L, io.NewSectionReader))
L.SetField(pkg, "Pipe", luar.New(L, io.Pipe)) L.SetField(pkg, "Pipe", luar.New(L, io.Pipe))
L.SetField(pkg, "ReadAll", luar.New(L, io.ReadAll))
L.SetField(pkg, "ReadAtLeast", luar.New(L, io.ReadAtLeast)) L.SetField(pkg, "ReadAtLeast", luar.New(L, io.ReadAtLeast))
L.SetField(pkg, "ReadFull", luar.New(L, io.ReadFull)) L.SetField(pkg, "ReadFull", luar.New(L, io.ReadFull))
L.SetField(pkg, "TeeReader", luar.New(L, io.TeeReader)) L.SetField(pkg, "TeeReader", luar.New(L, io.TeeReader))
@ -371,15 +372,13 @@ func importOs() *lua.LTable {
L.SetField(pkg, "PathListSeparator", luar.New(L, os.PathListSeparator)) L.SetField(pkg, "PathListSeparator", luar.New(L, os.PathListSeparator))
L.SetField(pkg, "PathSeparator", luar.New(L, os.PathSeparator)) L.SetField(pkg, "PathSeparator", luar.New(L, os.PathSeparator))
L.SetField(pkg, "Pipe", luar.New(L, os.Pipe)) L.SetField(pkg, "Pipe", luar.New(L, os.Pipe))
L.SetField(pkg, "ReadDir", luar.New(L, os.ReadDir))
L.SetField(pkg, "ReadFile", luar.New(L, os.ReadFile))
L.SetField(pkg, "Readlink", luar.New(L, os.Readlink)) L.SetField(pkg, "Readlink", luar.New(L, os.Readlink))
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))
@ -391,7 +390,6 @@ func importOs() *lua.LTable {
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)) L.SetField(pkg, "UserHomeDir", luar.New(L, os.UserHomeDir))
L.SetField(pkg, "WriteFile", luar.New(L, os.WriteFile))
return pkg return pkg
} }
@ -427,6 +425,7 @@ func importPath() *lua.LTable {
func importFilePath() *lua.LTable { func importFilePath() *lua.LTable {
pkg := L.NewTable() pkg := L.NewTable()
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs)) L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
L.SetField(pkg, "Base", luar.New(L, filepath.Base)) L.SetField(pkg, "Base", luar.New(L, filepath.Base))
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean)) L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))
@ -524,16 +523,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))
@ -541,15 +545,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
} }

View File

@ -6,9 +6,9 @@ import (
"os" "os"
"sync" "sync"
"github.com/micro-editor/tcell/v2"
"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"
) )
// Screen is the tcell screen we use to draw to the terminal // Screen is the tcell screen we use to draw to the terminal
@ -22,10 +22,6 @@ var Screen tcell.Screen
// Events is the channel of tcell events // Events is the channel of tcell events
var Events chan (tcell.Event) 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
@ -33,12 +29,6 @@ var lock sync.Mutex
// written to even if no event user event has occurred // written to even if no event user event has occurred
var drawChan chan bool var drawChan chan bool
// rawSeq is the list of raw escape sequences that are bound to some actions
// via keybindings and thus should be parsed by tcell. We need to register
// them in tcell every time we reinitialize the screen, so we need to remember
// them in a list
var rawSeq = make([]string, 0)
// Lock locks the screen lock // Lock locks the screen lock
func Lock() { func Lock() {
lock.Lock() lock.Lock()
@ -89,10 +79,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
@ -105,7 +91,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)
@ -120,41 +106,13 @@ func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
} }
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
} }
} }
// RegisterRawSeq registers a raw escape sequence that should be parsed by tcell
func RegisterRawSeq(r string) {
for _, seq := range rawSeq {
if seq == r {
return
}
}
rawSeq = append(rawSeq, r)
if Screen != nil {
Screen.RegisterRawSeq(r)
}
}
// UnregisterRawSeq unregisters a raw escape sequence that should be parsed by tcell
func UnregisterRawSeq(r string) {
for i, seq := range rawSeq {
if seq == r {
rawSeq[i] = rawSeq[len(rawSeq)-1]
rawSeq = rawSeq[:len(rawSeq)-1]
}
}
if Screen != nil {
Screen.UnregisterRawSeq(r)
}
}
// TempFini shuts the screen down temporarily // TempFini shuts the screen down temporarily
func TempFini() bool { func TempFini() bool {
screenWasNil := Screen == nil screenWasNil := Screen == nil
@ -172,10 +130,6 @@ func TempStart(screenWasNil bool) {
if !screenWasNil { if !screenWasNil {
Init() Init()
Unlock() Unlock()
if RestartCallback != nil {
RestartCallback()
}
} }
} }
@ -184,13 +138,10 @@ func Init() error {
drawChan = make(chan bool, 8) drawChan = make(chan bool, 8)
// Should we enable true color? // Should we enable true color?
truecolor := config.GetGlobalOption("truecolor").(string) truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
if truecolor == "on" || (truecolor == "auto" && os.Getenv("MICRO_TRUECOLOR") == "1") {
os.Setenv("TCELL_TRUECOLOR", "enable") if !truecolor {
} else if truecolor == "off" {
os.Setenv("TCELL_TRUECOLOR", "disable") os.Setenv("TCELL_TRUECOLOR", "disable")
} else {
// For "auto", tcell already autodetects truecolor by default
} }
var oldTerm string var oldTerm string
@ -232,10 +183,6 @@ func Init() error {
Screen.EnableMouse() Screen.EnableMouse()
} }
for _, r := range rawSeq {
Screen.RegisterRawSeq(r)
}
return nil return nil
} }

View File

@ -78,10 +78,8 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(
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()
if onExit != nil { jobFunc := JobFunction{onExit, outbuf.String(), userargs}
jobFunc := JobFunction{onExit, outbuf.String(), userargs} Jobs <- jobFunc
Jobs <- jobFunc
}
}() }()
return &Job{proc, stdin} return &Job{proc, stdin}

View File

@ -8,10 +8,10 @@ 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"
"github.com/zyedidia/micro/v2/internal/util"
) )
// ExecCommand executes a command using exec // ExecCommand executes a command using exec
@ -59,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
@ -96,30 +101,28 @@ func RunInteractiveShell(input string, wait bool, getOutput bool) (string, error
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
// This is a trap for Ctrl-C so that it doesn't kill micro // This is a trap for Ctrl-C so that it doesn't kill micro
// micro is killed if the signal is ignored only on Windows, so it is // Instead we trap Ctrl-C to kill the program we're running
// received
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Reset(os.Interrupt)
signal.Notify(c, os.Interrupt) signal.Notify(c, os.Interrupt)
err = cmd.Start() go func() {
if err == nil { for range c {
err = cmd.Wait() cmd.Process.Kill()
if wait {
// This is just so we don't return right away and let the user press enter to return
screen.TermMessage("")
} }
} else { }()
screen.TermMessage(err)
} cmd.Start()
err = cmd.Wait()
output := outputBytes.String() output := outputBytes.String()
if wait {
// This is just so we don't return right away and let the user press enter to return
screen.TermMessage("")
}
// Start the screen back up // Start the screen back up
screen.TempStart(screenb) screen.TempStart(screenb)
signal.Notify(util.Sigterm, os.Interrupt)
signal.Stop(c)
return output, err return output, err
} }

View File

@ -5,9 +5,9 @@ import (
"os/exec" "os/exec"
"strconv" "strconv"
"github.com/micro-editor/terminal"
"github.com/zyedidia/micro/v2/internal/buffer" "github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/terminal"
) )
type TermType int type TermType int
@ -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,
}
} }
} }
} }

View File

@ -6,9 +6,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"net/http"
"net/url"
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
@ -18,7 +15,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"
@ -29,7 +25,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"
@ -43,46 +39,8 @@ var (
// Stdout is a buffer that is written to stdout when micro closes // Stdout is a buffer that is written to stdout when micro closes
Stdout *bytes.Buffer Stdout *bytes.Buffer
// Sigterm is a channel where micro exits when written
Sigterm chan os.Signal
// To be used for fails on (over-)write with safe writes
ErrOverwrite = OverwriteError{}
) )
// To be used for file writes before umask is applied
const FileMode os.FileMode = 0666
const OverwriteFailMsg = `An error occurred while writing to the file:
%s
The file may be corrupted now. The good news is that it has been
successfully backed up. Next time you open this file with Micro,
Micro will ask if you want to recover it from the backup.
The backup path is:
%s`
// OverwriteError is a custom error to add additional information
type OverwriteError struct {
What error
BackupName string
}
func (e OverwriteError) Error() string {
return fmt.Sprintf(OverwriteFailMsg, e.What, e.BackupName)
}
func (e OverwriteError) Is(target error) bool {
return target == ErrOverwrite
}
func (e OverwriteError) Unwrap() error {
return e.What
}
func init() { func init() {
var err error var err error
SemVersion, err = semver.Make(Version) SemVersion, err = semver.Make(Version)
@ -258,56 +216,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)
}
// 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
@ -358,28 +270,6 @@ func RunePos(b []byte, i int) int {
return CharacterCount(b[:i]) return CharacterCount(b[:i])
} }
// IndexAnyUnquoted returns the first position in s of a character from chars.
// Escaped (with backslash) and quoted (with single or double quotes) characters
// are ignored. Returns -1 if not successful
func IndexAnyUnquoted(s, chars string) int {
var e bool
var q rune
for i, r := range s {
if e {
e = false
} else if (q == 0 || q == '"') && r == '\\' {
e = true
} else if r == q {
q = 0
} else if q == 0 && (r == '\'' || r == '"') {
q = r
} else if q == 0 && strings.IndexRune(chars, r) >= 0 {
return i
}
}
return -1
}
// MakeRelative will attempt to make a relative path between path and base // MakeRelative will attempt to make a relative path between path and base
func MakeRelative(path, base string) (string, error) { func MakeRelative(path, base string) (string, error) {
if len(path) > 0 { if len(path) > 0 {
@ -424,7 +314,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 {
@ -446,17 +336,8 @@ func GetModTime(path string) (time.Time, error) {
return info.ModTime(), nil return info.ModTime(), nil
} }
func AppendBackupSuffix(path string) string { // EscapePath replaces every path separator in a given path with a %
return path + ".micro-backup" func EscapePath(path string) string {
}
// EscapePathUrl encodes the path in URL query form
func EscapePathUrl(path string) string {
return url.QueryEscape(filepath.ToSlash(path))
}
// EscapePathLegacy replaces every path separator in a given path with a %
func EscapePathLegacy(path string) string {
path = filepath.ToSlash(path) path = filepath.ToSlash(path)
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// ':' is not valid in a path name on Windows but is ok on Unix // ':' is not valid in a path name on Windows but is ok on Unix
@ -465,24 +346,6 @@ func EscapePathLegacy(path string) string {
return strings.ReplaceAll(path, "/", "%") return strings.ReplaceAll(path, "/", "%")
} }
// DetermineEscapePath escapes a path, determining whether it should be escaped
// using URL encoding (preferred, since it encodes unambiguously) or
// legacy encoding with '%' (for backward compatibility, if the legacy-escaped
// path exists in the given directory).
func DetermineEscapePath(dir string, path string) string {
url := filepath.Join(dir, EscapePathUrl(path))
if _, err := os.Stat(url); err == nil {
return url
}
legacy := filepath.Join(dir, EscapePathLegacy(path))
if _, err := os.Stat(legacy); err == nil {
return legacy
}
return url
}
// GetLeadingWhitespace returns the leading whitespace of the given byte array // GetLeadingWhitespace returns the leading whitespace of the given byte array
func GetLeadingWhitespace(b []byte) []byte { func GetLeadingWhitespace(b []byte) []byte {
ws := []byte{} ws := []byte{}
@ -499,28 +362,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))
@ -580,9 +421,16 @@ 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 {
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
}
func IsAutocomplete(c rune) bool { func IsAutocomplete(c rune) bool {
return c == '.' || IsWordChar(c) return c == '.' || !IsNonAlphaNumeric(c)
}
func ParseSpecial(s string) string {
return strings.ReplaceAll(s, "\\t", "\t")
} }
// String converts a byte array to a string (for lua plugins) // String converts a byte array to a string (for lua plugins)
@ -642,90 +490,3 @@ func Unzip(src, dest string) error {
return nil 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)
}
// SafeWrite writes bytes to a file in a "safe" way, preventing loss of the
// contents of the file if it fails to write the new contents.
// This means that the file is not overwritten directly but by writing to a
// temporary file first.
//
// If rename is true, write is performed atomically, by renaming the temporary
// file to the target file after the data is successfully written to the
// temporary file. This guarantees that the file will not remain in a corrupted
// state, but it also has limitations, e.g. the file should not be a symlink
// (otherwise SafeWrite silently replaces this symlink with a regular file),
// the file creation date in Linux is not preserved (since the file inode
// changes) etc. Use SafeWrite with rename=true for files that are only created
// and used by micro for its own needs and are not supposed to be used directly
// by the user.
//
// If rename is false, write is performed by overwriting the target file after
// the data is successfully written to the temporary file.
// This means that the target file may remain corrupted if overwrite fails,
// but in such case the temporary file is preserved as a backup so the file
// can be recovered later. So it is less convenient than atomic write but more
// universal. Use SafeWrite with rename=false for files that may be managed
// directly by the user, like settings.json and bindings.json.
func SafeWrite(path string, bytes []byte, rename bool) error {
var err error
if _, err = os.Stat(path); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
// Force rename for new files!
rename = true
}
var file *os.File
if !rename {
file, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE, FileMode)
if err != nil {
return err
}
defer file.Close()
}
tmp := AppendBackupSuffix(path)
err = os.WriteFile(tmp, bytes, FileMode)
if err != nil {
os.Remove(tmp)
return err
}
if rename {
err = os.Rename(tmp, path)
} else {
err = file.Truncate(0)
if err == nil {
_, err = file.Write(bytes)
}
if err == nil {
err = file.Sync()
}
}
if err != nil {
if rename {
os.Remove(tmp)
} else {
err = OverwriteError{err, tmp}
}
return err
}
if !rename {
os.Remove(tmp)
}
return nil
}

View File

@ -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
@ -439,12 +435,11 @@ func (n *Node) VSplit(right bool) uint64 {
} }
// unsplits the child of a split // unsplits the child of a split
func (n *Node) unsplit(i int) { func (n *Node) unsplit(i int, h bool) {
copy(n.children[i:], n.children[i+1:]) copy(n.children[i:], n.children[i+1:])
n.children[len(n.children)-1] = nil n.children[len(n.children)-1] = nil
n.children = n.children[:len(n.children)-1] n.children = n.children[:len(n.children)-1]
h := n.Kind == STVert
nonrs, numr := n.getResizeInfo(h) nonrs, numr := n.getResizeInfo(h)
if numr == 0 { if numr == 0 {
// This means that this was the last child // This means that this was the last child
@ -471,62 +466,18 @@ func (n *Node) Unsplit() bool {
ind = i ind = i
} }
} }
n.parent.unsplit(ind) if n.parent.Kind == STVert {
n.parent.unsplit(ind, true)
} else {
n.parent.unsplit(ind, false)
}
if n.parent.IsLeaf() { if n.parent.IsLeaf() {
return n.parent.Unsplit() return n.parent.Unsplit()
} }
n.parent.flatten()
return true return true
} }
// flattens the tree by removing unnecessary intermediate parents that have only one child
// and handles the side effect of it
func (n *Node) flatten() {
if n.parent == nil || len(n.children) != 1 {
return
}
ind := 0
for i, c := range n.parent.children {
if c.id == n.id {
ind = i
}
}
// Replace current node with its child node to remove chained parent
successor := n.children[0]
n.parent.children[ind] = successor
successor.parent = n.parent
// Maintain the tree in a consistent state: any child node's kind (horiz vs vert)
// should be the opposite of its parent's kind.
if successor.IsLeaf() {
successor.Kind = n.Kind
} else {
// If the successor node has children, that means it is a chained parent as well.
// Therefore it can be replaced by its own children.
origsize := len(n.parent.children)
// Let's say we have 5 children and want to replace [2] with its children [a] [b] [c]
// [0] [1] [2] [3] [4] --> [0] [1] [a] [b] [c] [3] [4]
// insertcount will be `3 - 1 = 2` in this case
insertcount := len(successor.children) - 1
n.parent.children = append(n.parent.children, make([]*Node, insertcount)...)
copy(n.parent.children[ind+insertcount+1:], n.parent.children[ind+1:origsize])
copy(n.parent.children[ind:], successor.children)
for i := 0; i < len(successor.children); i++ {
n.parent.children[ind+i].parent = n.parent
}
}
// Update propW and propH since the parent of the children has been updated,
// so the children have new siblings
n.parent.markSizes()
}
// String returns the string form of the node and all children (used for debugging) // String returns the string form of the node and all children (used for debugging)
func (n *Node) String() string { func (n *Node) String() string {
var strf func(n *Node, ident int) string var strf func(n *Node, ident int) string

18
pkg/highlight/ftdetect.go Normal file
View 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
}

View File

@ -51,9 +51,24 @@ func runePos(p int, str []byte) int {
return CharacterCount(str[:p]) return CharacterCount(str[:p])
} }
func combineLineMatch(src, dst LineMatch) LineMatch {
for k, v := range src {
if g, ok := dst[k]; ok {
if g == 0 {
dst[k] = v
}
} else {
dst[k] = v
}
}
return dst
}
// 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
@ -61,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
@ -82,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 {
@ -101,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)
@ -120,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
} }
@ -157,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
}
}
}
} }
} }
} }
@ -178,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
} }
@ -213,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
@ -224,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
} }
@ -243,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
@ -289,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)
@ -308,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()
} }
} }
@ -317,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
} }
@ -334,7 +355,6 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
} }
input.SetMatch(i, match) input.SetMatch(i, match)
input.Unlock()
} }
} }
@ -346,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)
@ -372,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
@ -384,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)

View File

@ -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 = &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
@ -476,17 +394,10 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
r.limitGroup = r.group r.limitGroup = r.group
} }
// rules are optional r.rules, err = parseRules(regionInfo["rules"].([]interface{}), r)
if rules, ok := regionInfo["rules"]; ok {
r.rules, err = parseRules(rules.([]interface{}), r)
if err != nil {
return nil, err
}
}
if r.rules == nil { if err != nil {
// allow empty rules return nil, err
r.rules = &rules{}
} }
return r, nil return r, nil

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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.operator "#F92671,#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"

View File

@ -24,8 +24,6 @@ color-link underlined "#FF79C6"
color-link error "bold #FF5555" color-link error "bold #FF5555"
color-link todo "bold #FF79C6" color-link todo "bold #FF79C6"
color-link hlsearch "#282A36,#50FA7B"
color-link diff-added "#50FA7B" color-link diff-added "#50FA7B"
color-link diff-modified "#FFB86C" color-link diff-modified "#FFB86C"
color-link diff-deleted "#FF5555" color-link diff-deleted "#FF5555"
@ -43,7 +41,3 @@ color-link cursor-line "#44475A,#F8F8F2"
color-link color-column "#44475A" color-link color-column "#44475A"
color-link type.extended "default" color-link type.extended "default"
color-link match-brace "#282A36,#FF79C6"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -11,7 +11,6 @@ color-link special "#D26937,#0C1014"
color-link underlined "#EDB443,#0C1014" color-link underlined "#EDB443,#0C1014"
color-link error "bold #C23127,#0C1014" color-link error "bold #C23127,#0C1014"
color-link todo "bold #888CA6,#0C1014" color-link todo "bold #888CA6,#0C1014"
color-link hlsearch "#091F2E,#EDB443"
color-link statusline "#091F2E,#599CAB" color-link statusline "#091F2E,#599CAB"
color-link indent-char "#505050,#0C1014" color-link indent-char "#505050,#0C1014"
color-link line-number "#245361,#11151C" color-link line-number "#245361,#11151C"
@ -24,6 +23,3 @@ color-link gutter-warning "#EDB443,#11151C"
color-link cursor-line "#091F2E" color-link cursor-line "#091F2E"
color-link color-column "#11151C" color-link color-column "#11151C"
color-link symbol "#99D1CE,#0C1014" color-link symbol "#99D1CE,#0C1014"
color-link match-brace "#0C1014,#D26937"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View File

@ -9,10 +9,8 @@ color-link statement "#fb4934,#282828"
color-link preproc "#fb4934,235" color-link preproc "#fb4934,235"
color-link type "#fb4934,#282828" color-link type "#fb4934,#282828"
color-link special "#d79921,#282828" color-link special "#d79921,#282828"
color-link underlined "underline #458588,#282828" color-link underlined "underline #282828"
color-link error "#9d0006,#282828" color-link error "#9d0006,#282828"
color-link todo "bold #ebdbb2,#282828"
color-link hlsearch "#282828,#fabd2f"
color-link diff-added "#00AF00" color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00" color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000" color-link diff-deleted "#D70000"
@ -24,6 +22,3 @@ color-link cursor-line "#3c3836"
color-link color-column "#79740e" color-link color-column "#79740e"
color-link statusline "#ebdbb2,#665c54" color-link statusline "#ebdbb2,#665c54"
color-link tabbar "#ebdbb2,#665c54" color-link tabbar "#ebdbb2,#665c54"
color-link match-brace "#282828,#d3869b"
color-link tab-error "#d75f5f"
color-link trailingws "#d75f5f"

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