Compare commits

..

2 Commits

Author SHA1 Message Date
Zachary Yedidia
3d845eefe3 Increase totalwidth as well for correct tab alignment 2023-02-13 14:28:07 -08:00
Zachary Yedidia
d8576d0fe6 Display a tilde before every uppercase letter 2023-02-13 14:24:49 -08:00
248 changed files with 3921 additions and 8396 deletions

View File

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

9
.github/ISSUE_TEMPLATE vendored Normal file
View File

@@ -0,0 +1,9 @@
## Description of the problem or steps to reproduce
## Specifications
<!-- You can use `micro -version` to get the commit hash. -->
Commit hash:
OS:
Terminal:

View File

@@ -1,25 +0,0 @@
name: Bug Report
description: File a bug report.
title: "<title>"
labels: ["bug", "triage"]
body:
- type: textarea
attributes:
label: Description
description: Description of the problem and steps to reproduce.
validations:
required: true
- type: textarea
attributes:
label: Environment
description: |
examples:
- **Version**: 2.0.15 and/or commit hash ($ micro -version)
- **OS**: Debian
- **Terminal**: ptyxis
value: |
- Version:
- OS:
- Terminal:
validations:
required: true

View File

@@ -1,11 +0,0 @@
name: Feature Request
description: File a feature request.
title: "<title>"
labels: ["feature"]
body:
- type: textarea
attributes:
label: Description
description: Description of the feature.
validations:
required: true

View File

@@ -1 +0,0 @@
blank_issues_enabled: false

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

@@ -4,19 +4,14 @@ jobs:
test:
strategy:
matrix:
go-version: [1.19.x, 1.23.x]
go-version: [1.19.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-go@v5
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
cache: false
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- uses: actions/checkout@v3
- name: Build
run: |

1
.gitignore vendored
View File

@@ -1,7 +1,6 @@
.DS_Store
micro
micro.exe
!cmd/micro
binaries/
tmp.sh

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/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/micro-editor/json5/LICENSE (fork)
github.com/zyedidia/json5/LICENSE (fork)
================
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/micro-editor/terminal/LICENSE (fork)
github.com/zyedidia/terminal/LICENSE (fork)
================
Copyright (C) 2013 James Gray

View File

@@ -5,31 +5,24 @@ VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH
HASH = $(shell git rev-parse --short HEAD)
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
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 "$(shell go env GOOS)" "$(VERSION)")
GOBIN ?= $(shell go env GOPATH)/bin
GOVARS = -X github.com/micro-editor/micro/v2/internal/util.Version=$(VERSION) -X github.com/micro-editor/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/micro-editor/micro/v2/internal/util.CompileDate=$(DATE)'
DEBUGVAR = -X github.com/micro-editor/micro/v2/internal/util.Debug=ON
GOVARS = -X github.com/zyedidia/micro/v2/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/v2/internal/util.CompileDate=$(DATE)'
DEBUGVAR = -X github.com/zyedidia/micro/v2/internal/util.Debug=ON
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
CGO_ENABLED := $(if $(CGO_ENABLED),$(CGO_ENABLED),0)
ADDITIONAL_GO_LINKER_FLAGS := ""
GOHOSTOS = $(shell go env GOHOSTOS)
ifeq ($(GOHOSTOS), darwin)
# 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
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
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 generate
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-all: build
@@ -39,7 +32,7 @@ install: generate
install-all: install
fetch-tags:
git fetch --tags --force
git fetch --tags
generate:
GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime

136
README.md
View File

@@ -1,9 +1,9 @@
<img alt="micro logo" src="./assets/micro-logo-drop.svg" width="500px"/>
![Test Workflow](https://github.com/micro-editor/micro/actions/workflows/test.yaml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/micro-editor/micro/v2)](https://goreportcard.com/report/github.com/micro-editor/micro/v2)
[![Release](https://img.shields.io/github/release/micro-editor/micro.svg?label=Release)](https://github.com/micro-editor/micro/releases)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/micro-editor/micro/blob/master/LICENSE)
![Test Workflow](https://github.com/zyedidia/micro/actions/workflows/test.yaml/badge.svg)
[![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)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Snap Status](https://snapcraft.io/micro/badge.svg)](https://snapcraft.io/micro)
@@ -18,9 +18,25 @@ Here is a picture of micro editing its source code.
![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).
You can also check out the website for Micro at https://micro-editor.github.io.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Prebuilt binaries](#pre-built-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
@@ -37,7 +53,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
- Extremely good mouse support.
- This means mouse dragging to create a selection, double click to select by word, and triple click to select by line.
- Cross-platform (it should work on all the platforms Go runs on).
- 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).
- micro has a built-in plugin manager to automatically install, remove, and update plugins.
- Built-in diff gutter.
@@ -47,32 +63,31 @@ You can also check out the website for Micro at https://micro-editor.github.io.
- Syntax highlighting for over [130 languages](runtime/syntax).
- Color scheme support.
- 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.
- Small and simple.
- Easily configurable.
- Macros.
- Smart highlighting of trailing whitespace and tab vs space errors.
- Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, …
## Installation
To install micro, you can download a [prebuilt binary](https://github.com/micro-editor/micro/releases), or you can build it from source.
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
If you want more information about ways to install micro, see this [wiki page](https://github.com/micro-editor/micro/wiki/Installing-Micro).
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro).
Use `micro -version` to get the version information after installing. It is only guaranteed that you are installing the most recent
stable version if you install from the prebuilt binaries, Homebrew, or Snap.
A desktop entry file and man page can be found in the [assets/packaging](https://github.com/micro-editor/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
Pre-built binaries are distributed in [releases](https://github.com/micro-editor/micro/releases).
Pre-built binaries are distributed with [releases](https://github.com/zyedidia/micro/releases).
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
#### Third-party quick-install script
#### Quick-install script
```bash
curl https://getmic.ro | bash
@@ -85,14 +100,14 @@ The script will place the micro binary in the current directory. From there, you
With [Eget](https://github.com/zyedidia/eget) installed, you can easily get a pre-built binary:
```
eget micro-editor/micro
eget zyedidia/micro
```
Use `--tag VERSION` to download a specific tagged version.
```
eget --tag nightly micro-editor/micro # download the nightly version (compiled every day at midnight UTC)
eget --tag v2.0.8 micro-editor/micro # download version 2.0.8 rather than the latest release
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.
@@ -109,7 +124,7 @@ brew install micro
**Note for Mac:** All micro keybindings use the control or alt (option) key, not the command
key. By default, macOS terminals do not forward alt key events. To fix this, please see
the section on [macOS terminals](https://github.com/micro-editor/micro#macos-terminal) further below.
the section on [macOS terminals](https://github.com/zyedidia/micro#macos-terminal) further below.
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
@@ -117,44 +132,35 @@ On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/in
snap install micro --classic
```
Micro is also available through other package managers on Linux such as dnf, AUR, Nix, and package managers
Micro is also available through other package managers on Linux such dnf, AUR, Nix, and package managers
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:
* distro-specific package managers:
* `dnf install micro` (Fedora).
* `apt install micro` (Ubuntu and Debian).
* `pacman -S micro` (Arch Linux).
* `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/micro-editor/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/).
* Linux: Available in distro-specific package managers.
* `dnf install micro` (Fedora).
* `apt install micro` (Ubuntu and Debian).
* `pacman -S micro` (Arch Linux).
* `emerge app-editors/micro` (Gentoo).
* `zypper install micro-editor` (SUSE)
* `eopkg install micro` (Solus).
* `pacstall -I micro` (Pacstall).
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop).
* `choco install micro`.
* `scoop install micro`.
* `winget install zyedidia.micro`
* OpenBSD: Available in the ports tree and also available as a binary package.
* `pkg_add -v micro`.
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](https://www.pkgsrc.org/)-current:
* `pkd_add -v micro`.
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](http://www.pkgsrc.org/)-current:
* `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))
* macOS with [MacPorts](https://www.macports.org):
* `sudo port install micro`
**Note for Linux desktop environments:**
For interfacing with the local system clipboard, the following tools need to be installed:
* For X11, `xclip` or `xsel`
* For [Wayland](https://wayland.freedesktop.org/), `wl-clipboard`
* 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.
@@ -162,10 +168,10 @@ Without these tools installed, micro will use an internal clipboard for copy and
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.16 or greater and Go modules are enabled.
```
git clone https://github.com/micro-editor/micro
git clone https://github.com/zyedidia/micro
cd micro
make build
sudo mv micro /usr/local/bin # optional
@@ -176,27 +182,23 @@ anywhere you like (for example `/usr/local/bin`).
The command `make install` will install the binary to `$GOPATH/bin` or `$GOBIN`.
You can install directly with `go get` (`go get github.com/micro-editor/micro/cmd/micro`) but this isn't
You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/micro`) but this isn't
recommended because it doesn't build micro with version information (necessary for the plugin manager),
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.
This behavior can simply be overriden by providing `CGO_ENABLED=1` to the build target.
By default, the micro binary will dynamically link with core system libraries (this is generally
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
If you are using macOS, you should consider using [iTerm2](https://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.
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>.
@@ -219,7 +221,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`
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`.
Many of the Windows terminals don't support more than 16 colors, which means
@@ -238,7 +240,7 @@ winpty micro.exe ...
Micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
Plan9 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
@@ -261,14 +263,14 @@ click to enable line selection.
micro has a built-in help system which you can access by pressing <kbd>Ctrl-e</kbd> and typing `help`. Additionally, you can
view the help files here:
- [main help](https://github.com/micro-editor/micro/tree/master/runtime/help/help.md)
- [keybindings](https://github.com/micro-editor/micro/tree/master/runtime/help/keybindings.md)
- [commands](https://github.com/micro-editor/micro/tree/master/runtime/help/commands.md)
- [colors](https://github.com/micro-editor/micro/tree/master/runtime/help/colors.md)
- [options](https://github.com/micro-editor/micro/tree/master/runtime/help/options.md)
- [plugins](https://github.com/micro-editor/micro/tree/master/runtime/help/plugins.md)
- [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
- [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
- [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
- [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
- [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
- [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
I also recommend reading the [tutorial](https://github.com/micro-editor/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.
There is also an unofficial Discord, which you can join at https://discord.gg/nhWR6armnR.
@@ -277,9 +279,9 @@ There is also an unofficial Discord, which you can join at https://discord.gg/nh
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
You can use the [GitHub issue tracker](https://github.com/micro-editor/micro/issues)
You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues)
to report bugs, ask questions, or suggest new features.
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro) or the [Discord](https://discord.gg/nhWR6armnR). You can also use the [Discussions](https://github.com/micro-editor/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) 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.
Sometimes I am unresponsive, and I apologize! If that happens, please ping me.

View File

@@ -9,7 +9,7 @@
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)"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="micro-logo-drop.svg"
width="304.70001"
height="103.2"
@@ -43,23 +43,21 @@
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1080"
inkscape:window-height="1043"
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:zoom="2.5161345"
inkscape:cx="158.97401"
inkscape:cy="109.69207"
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
inkscape:pagecheckerboard="0" /><g
id="g838"
transform="translate(-178,-172.8)"
style="fill:#ffffff;fill-opacity:1;filter:url(#filter1040)"><path
@@ -103,7 +101,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 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>
style="fill:#2e3192" /></svg>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -2,6 +2,13 @@
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<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"
id="Layer_1"
x="0px"
@@ -9,20 +16,13 @@
viewBox="0 0 103.2 103.2"
enable-background="new 0 0 960 560"
xml:space="preserve"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="micro-logo-mark.svg"
inkscape:version="0.91 r13725"
sodipodi:docname="micro-logo-notext.svg"
width="103.2"
height="103.2"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
height="103.2"><metadata
id="metadata9"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs7" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
@@ -32,28 +32,22 @@
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1080"
inkscape:window-width="733"
inkscape:window-height="480"
id="namedview5"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="5.405335"
inkscape:cx="75.573484"
inkscape:cy="51.153166"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:zoom="0.28541667"
inkscape:cx="302"
inkscape:cy="-4"
inkscape:window-x="1699"
inkscape:window-y="277"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" /><path
inkscape:current-layer="Layer_1" /><path
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
id="path3"
inkscape:connector-curvature="0"
style="fill:#2e3192" /><path
style="fill:#ffffff;stroke-width:0.185002"
d="m 29.320064,86.164872 c -1.277771,-0.647664 -1.573829,-1.327981 -1.549788,-3.561297 0.04016,-3.730697 1.622887,-10.030031 5.903272,-23.495306 2.770635,-8.715885 2.799071,-8.822813 3.148729,-11.840154 0.585284,-5.050637 1.565844,-12.45598 1.8369,-13.872547 0.43516,-2.274196 0.976755,-3.690519 1.880879,-4.918684 0.974445,-1.323691 1.896478,-1.826405 3.360953,-1.832474 3.009215,-0.01247 3.55713,2.574946 1.786201,8.434969 -0.742771,2.45784 -2.2493,6.487571 -3.407575,9.114735 -0.420971,0.954834 -1.151241,2.827983 -1.622823,4.162554 -0.839682,2.376289 -0.857669,2.47434 -0.869358,4.739023 -0.01095,2.122185 0.02796,2.3976 0.472736,3.346042 0.91751,1.956495 2.602228,3.131322 5.078862,3.541714 2.587757,0.428804 4.551892,-0.347899 7.187533,-2.842264 2.232774,-2.113092 3.746907,-4.117682 4.998184,-6.617188 1.816108,-3.627792 2.213624,-4.978174 3.527565,-11.983266 0.66466,-3.543546 1.376157,-6.951356 1.581104,-7.57291 0.970636,-2.943689 2.922262,-4.567831 5.096985,-4.241711 1.740397,0.260989 2.500104,1.361773 2.494406,3.614287 -0.0068,2.696563 -2.48184,9.966491 -6.424307,18.870246 l -1.269708,2.867537 0.02005,1.757523 c 0.01504,1.318294 0.119434,2.015481 0.417735,2.789716 1.028756,2.67011 3.517063,4.054736 6.342356,3.529224 3.19144,-0.593617 4.98902,-2.612828 6.217715,-6.984325 0.403553,-1.435775 0.552101,-1.739647 0.850428,-1.739647 0.34646,0 0.356492,0.101757 0.241656,2.451282 -0.238951,4.888854 -1.330826,7.853563 -3.80789,10.339358 -1.255532,1.259957 -1.547319,1.456015 -2.694109,1.81022 -1.395674,0.431082 -3.784736,0.537505 -4.865716,0.216749 -1.759682,-0.522141 -3.031085,-2.027386 -3.686869,-4.364972 -0.336042,-1.197843 -0.516218,-5.455318 -0.283812,-6.706338 0.266094,-1.432359 -0.105859,-1.235144 -0.879069,0.466093 -1.724383,3.794037 -4.750586,7.236231 -8.063683,9.172148 -2.368072,1.383716 -5.903865,2.143782 -8.230062,1.769159 -2.672688,-0.430424 -4.588062,-2.213422 -5.66376,-5.272324 -0.491128,-1.396592 -0.514658,-1.618704 -0.512739,-4.840059 0.0018,-3.093063 -0.02515,-3.376294 -0.321772,-3.376294 -0.414677,0 -0.706335,0.582138 -1.434591,2.863386 -1.443227,4.52088 -2.73082,10.895957 -3.516703,17.411762 l -0.381426,3.162426 0.469219,1.740138 c 0.927877,3.441104 1.066474,4.326417 0.841521,5.375336 -0.537458,2.506081 -2.272098,3.528416 -4.269226,2.516133 z"
id="path210" /></svg>
style="fill:#2e3192" /></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) -->
<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"
id="Layer_1"
x="0px"
@@ -9,20 +16,13 @@
viewBox="0 0 299.89999 103.2"
enable-background="new 0 0 960 560"
xml:space="preserve"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
inkscape:version="0.91 r13725"
sodipodi:docname="micro-logo.svg"
width="299.89999"
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
height="103.2"><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" /><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
pagecolor="#ffffff"
bordercolor="#666666"
@@ -32,24 +32,21 @@
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1080"
inkscape:window-width="1237"
inkscape:window-height="867"
id="namedview17"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="16.645603"
inkscape:cx="65.092264"
inkscape:cy="49.051992"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:zoom="1.1416667"
inkscape:cx="75.655934"
inkscape:cy="-4"
inkscape:window-x="1097"
inkscape:window-y="185"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" /><g
inkscape:current-layer="Layer_1" /><g
id="g3"
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"
@@ -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"
id="path15"
inkscape:connector-curvature="0"
style="fill:#2e3192" /><path
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>
style="fill:#2e3192" /></svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -1,18 +1,13 @@
.TH micro 1 "2025-09-03"
.TH micro 1 "2020-02-10"
.SH NAME
micro \- A modern and intuitive terminal-based text editor
.SH SYNOPSIS
.B micro
.RI [ OPTION ]...\&
.RI [ FILE ]...\&
.RI [+ LINE [: COL ]]\&
.RI [+/ REGEX ]
.br
.B micro
.RI [ OPTION ]...\&
.RI [ FILE [: LINE [: COL ]]]...\&
\& (only if the `parsecursor` option is enabled)
.RB [OPTIONS]
[FILE]\&...
.SH DESCRIPTION
Micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities
of modern terminals. It comes as one single, batteries-included, static binary with no dependencies.
@@ -20,85 +15,73 @@ As the name indicates, micro aims to be somewhat of a successor to the nano edit
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
Use Ctrl-q to quit, Ctrl-s to save, and Ctrl-g to open the in-editor help menu.
.SH OPTIONS
.PP
.B \-clean
\-clean
.RS 4
Clean the configuration directory and exit
Cleans the configuration directory
.RE
.PP
.B \-config-dir
.I dir
\-config-dir dir
.RS 4
Specify a custom location for the configuration directory
.RE
.PP
.IR FILE : LINE [: COL ]
(only if the `parsecursor` option is enabled)
.br
.IR FILE \ + LINE [: COL ]
[FILE]:LINE:COL
.RS 4
Specify a line and column to start the cursor at when opening a buffer
.RE
.PP
.RI +/ REGEX
\-options
.RS 4
Specify a regex to search for when opening a buffer
Show all option help
.RE
.PP
.B \-options
.RS 4
Show all options help and exit
.RE
.PP
.B \-debug
\-debug
.RS 4
Enable debug mode (enables logging to ./log.txt)
.RE
.PP
.B \-profile
\-version
.RS 4
Enable CPU profiling (writes profile info to ./micro.prof so it can be analyzed later with "go tool pprof micro.prof")
.RE
.PP
.B \-version
.RS 4
Show the version number and information and exit
Show the version number and information
.RE
Micro's plugins can be managed at the command line with the following commands.
.RS 4
.PP
.B \-plugin install
.RI [ PLUGIN ]...
.RS 4
Install plugin(s)
.RE
.PP
.B \-plugin remove
.RI [ PLUGIN ]...
\-plugin remove [PLUGIN]...
.RS 4
Remove plugin(s)
.RE
.PP
.B \-plugin update
.RI [ PLUGIN ]...
\-plugin update [PLUGIN]...
.RS 4
Update plugin(s) (if no argument is given, updates all plugins)
.RE
.PP
.B \-plugin search
.RI [ PLUGIN ]...
\-plugin search [PLUGIN]...
.RS 4
Search for a plugin
.RE
.PP
.B \-plugin list
\-plugin list
.RS 4
List installed plugins
.RE
.PP
.B \-plugin available
\-plugin available
.RS 4
List available plugins
.RE
@@ -108,33 +91,35 @@ Micro's options can also be set via command line arguments for quick
adjustments. For real configuration, please use the settings.json
file (see 'help options').
.RS 4
.PP
.BI \-< option >
.I value
\-option value
.RS 4
Set `option` to `value` for this session.
.br
Set `option` to `value` for this session
For example: `micro -syntax off file.c`
.RE
.RE
.PP
Use `micro -options` to see the full list of configuration options.
.SH CONFIGURATION
Micro uses $MICRO_CONFIG_HOME as the configuration directory.
If this environment variable is not set, it uses $XDG_CONFIG_HOME/micro instead.
If that environment variable is not set, it uses ~/.config/micro as the configuration directory.
In the documentation, we use ~/.config/micro to refer to the configuration directory
(even if it may in fact be somewhere else if you have set either of the above environment variables).
.SH NOTICE
This manpage is intended only to serve as a quick guide to the invocation of
micro and is not intended to replace the full documentation included with micro
which can be accessed from within micro. Micro tells you what key combination to
press to get help in the lower right.
.SH BUGS
A comprehensive list of bugs will not be listed in this manpage. See the Github
page at \fBhttps://github.com/micro-editor/micro/issues\fP for a list of known bugs
page at \fBhttps://github.com/zyedidia/micro/issues\fP for a list of known bugs
and to report any newly encountered bugs you may find. We strive to correct
bugs as swiftly as possible.
.SH COPYRIGHT
Copyright \(co 2020 Zachary Yedidia, et al. MIT license.
See \fBhttps://github.com/micro-editor/micro\fP for details.
See \fBhttps://github.com/zyedidia/micro\fP for details.

View File

@@ -3,16 +3,15 @@ package main
import (
"bufio"
"encoding/gob"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
)
func shouldContinue() bool {
@@ -40,16 +39,7 @@ func CleanConfig() {
}
fmt.Println("Cleaning default settings")
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())
}
}
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
// detect unused options
var unusedOptions []string
@@ -77,20 +67,16 @@ func CleanConfig() {
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() {
for _, s := range unusedOptions {
delete(config.GlobalSettings, s)
}
err := config.OverwriteSettings(settingsFile)
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
if err != nil {
if errors.Is(err, util.ErrOverwrite) {
fmt.Println(err.Error())
} else {
fmt.Println("Error overwriting settings.json file: " + err.Error())
}
fmt.Println("Error writing settings.json file: " + err.Error())
}
fmt.Println("Removed unused options")
@@ -99,13 +85,12 @@ func CleanConfig() {
}
// detect incorrectly formatted buffer/ files
buffersPath := filepath.Join(config.ConfigDir, "buffers")
files, err := os.ReadDir(buffersPath)
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
if err == nil {
var badFiles []string
var buffer buffer.SerializedBuffer
for _, f := range files {
fname := filepath.Join(buffersPath, f.Name())
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
file, e := os.Open(fname)
if e == nil {
@@ -120,9 +105,9 @@ func CleanConfig() {
}
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.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() {
removed := 0

View File

@@ -4,7 +4,7 @@ import (
"log"
"os"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// NullWriter simply sends writes into the void
@@ -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
func InitLog() {
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 {
log.Fatalf("error opening file: %v", err)
}

View File

@@ -2,19 +2,18 @@ package main
import (
"log"
"time"
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"
"github.com/micro-editor/micro/v2/internal/action"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
)
func init() {
@@ -48,18 +47,14 @@ func luaImportMicro() *lua.LTable {
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, "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()
}))
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, action.MainTab))
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
return action.Tabs
}))
ulua.L.SetField(pkg, "After", luar.New(ulua.L, func(t time.Duration, f func()) {
time.AfterFunc(t, func() {
timerChan <- f
})
}))
ulua.L.SetField(pkg, "Lock", luar.New(ulua.L, ulua.Lock))
return pkg
}
@@ -73,7 +68,7 @@ func luaImportMicroConfig() *lua.LTable {
ulua.L.SetField(pkg, "OptionComplete", luar.New(ulua.L, action.OptionComplete))
ulua.L.SetField(pkg, "OptionValueComplete", luar.New(ulua.L, action.OptionValueComplete))
ulua.L.SetField(pkg, "NoComplete", luar.New(ulua.L, nil))
ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKeyPlug))
ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKey))
ulua.L.SetField(pkg, "Reload", luar.New(ulua.L, action.ReloadConfig))
ulua.L.SetField(pkg, "AddRuntimeFileFromMemory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFilesFromDirectory))
@@ -88,8 +83,8 @@ func luaImportMicroConfig() *lua.LTable {
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOptionPlug))
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOptionPlug))
ulua.L.SetField(pkg, "GetGlobalOption", luar.New(ulua.L, config.GetGlobalOption))
ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOptionPlug))
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNativePlug))
ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOption))
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNative))
ulua.L.SetField(pkg, "ConfigDir", luar.New(ulua.L, config.ConfigDir))
return pkg

View File

@@ -3,11 +3,10 @@ package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"runtime/pprof"
@@ -18,18 +17,22 @@ import (
"github.com/go-errors/errors"
isatty "github.com/mattn/go-isatty"
"github.com/micro-editor/micro/v2/internal/action"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/clipboard"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/clipboard"
"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/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)
var (
// Event channel
autosave chan bool
// Command line flags
flagVersion = flag.Bool("version", false, "Show the version number and information")
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
@@ -40,36 +43,31 @@ var (
flagClean = flag.Bool("clean", false, "Clean configuration directory")
optionFlags map[string]*string
sighup chan os.Signal
timerChan chan func()
sigterm chan os.Signal
sighup chan os.Signal
)
func InitFlags() {
// Note: keep this in sync with the man page in assets/packaging/micro.1
flag.Usage = func() {
fmt.Println("Usage: micro [OPTION]... [FILE]... [+LINE[:COL]] [+/REGEX]")
fmt.Println(" micro [OPTION]... [FILE[:LINE[:COL]]]... (only if the `parsecursor` option is enabled)")
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
fmt.Println("-clean")
fmt.Println(" \tClean the configuration directory and exit")
fmt.Println(" \tCleans the configuration directory")
fmt.Println("-config-dir dir")
fmt.Println(" \tSpecify a custom location for the configuration directory")
fmt.Println("FILE:LINE[:COL] (only if the `parsecursor` option is enabled)")
fmt.Println("FILE +LINE[:COL]")
fmt.Println("[FILE]:LINE:COL (if the `parsecursor` option is enabled)")
fmt.Println("+LINE:COL")
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
fmt.Println("+/REGEX")
fmt.Println(" \tSpecify a regex to search for when opening a buffer")
fmt.Println("-options")
fmt.Println(" \tShow all options help and exit")
fmt.Println(" \tShow all option help")
fmt.Println("-debug")
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(" \tShow the version number and information and exit")
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(" \tInstall plugin(s)")
fmt.Println("-plugin remove [PLUGIN]...")
@@ -84,7 +82,7 @@ func InitFlags() {
fmt.Println(" \tList available plugins")
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
fmt.Println("-<option> value")
fmt.Println("-option value")
fmt.Println(" \tSet `option` to `value` for this session")
fmt.Println(" \tFor example: `micro -syntax off file.c`")
fmt.Println("\nUse `micro -options` to see the full list of configuration options")
@@ -103,7 +101,7 @@ func InitFlags() {
fmt.Println("Version:", util.Version)
fmt.Println("Commit hash:", util.CommitHash)
fmt.Println("Compiled on", util.CompileDate)
exit(0)
os.Exit(0)
}
if *flagOptions {
@@ -119,7 +117,7 @@ func InitFlags() {
fmt.Printf("-%s value\n", k)
fmt.Printf(" \tDefault value: '%v'\n", v)
}
exit(0)
os.Exit(0)
}
if util.Debug == "OFF" && *flagDebug {
@@ -140,7 +138,7 @@ func DoPluginFlags() {
CleanConfig()
}
exit(0)
os.Exit(0)
}
}
@@ -158,63 +156,50 @@ func LoadInput(args []string) []*buffer.Buffer {
// 3. If there is no input file and the input is a terminal, an empty buffer
// should be opened
var filename string
var input []byte
var err error
buffers := make([]*buffer.Buffer, 0, len(args))
btype := buffer.BTDefault
if !isatty.IsTerminal(os.Stdout.Fd()) {
btype = buffer.BTStdout
}
files := make([]string, 0, len(args))
flagStartPos := buffer.Loc{-1, -1}
posFlagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
posIndex := -1
searchText := ""
searchFlagr := regexp.MustCompile(`^\+\/(.+)$`)
searchIndex := -1
for i, a := range args {
posMatch := posFlagr.FindStringSubmatch(a)
if len(posMatch) == 3 && posMatch[2] != "" {
line, err := strconv.Atoi(posMatch[1])
flagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
for _, a := range args {
match := flagr.FindStringSubmatch(a)
if len(match) == 3 && match[2] != "" {
line, err := strconv.Atoi(match[1])
if err != nil {
screen.TermMessage(err)
continue
}
col, err := strconv.Atoi(posMatch[2])
col, err := strconv.Atoi(match[2])
if err != nil {
screen.TermMessage(err)
continue
}
flagStartPos = buffer.Loc{col - 1, line - 1}
posIndex = i
} else if len(posMatch) == 3 && posMatch[2] == "" {
line, err := strconv.Atoi(posMatch[1])
} else if len(match) == 3 && match[2] == "" {
line, err := strconv.Atoi(match[1])
if err != nil {
screen.TermMessage(err)
continue
}
flagStartPos = buffer.Loc{0, line - 1}
posIndex = i
} else {
searchMatch := searchFlagr.FindStringSubmatch(a)
if len(searchMatch) == 2 {
searchText = searchMatch[1]
searchIndex = i
} else {
files = append(files, a)
}
files = append(files, a)
}
}
command := buffer.Command{
StartCursor: flagStartPos,
SearchRegex: searchText,
SearchAfterStart: searchIndex > posIndex,
}
if len(files) > 0 {
// Option 1
// We go through each file and load it
for i := 0; i < len(files); i++ {
buf, err := buffer.NewBufferFromFileWithCommand(files[i], buffer.BTDefault, command)
buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos)
if err != nil {
screen.TermMessage(err)
continue
@@ -222,80 +207,30 @@ func LoadInput(args []string) []*buffer.Buffer {
// If the file didn't exist, input will be empty, and we'll open an empty buffer
buffers = append(buffers, buf)
}
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
// Option 2
// The input is not a terminal, so something is being piped in
// and we should read from stdin
input, err = ioutil.ReadAll(os.Stdin)
if err != nil {
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
} else {
btype := buffer.BTDefault
if !isatty.IsTerminal(os.Stdout.Fd()) {
btype = buffer.BTStdout
}
if !isatty.IsTerminal(os.Stdin.Fd()) {
// Option 2
// The input is not a terminal, so something is being piped in
// and we should read from stdin
input, err := io.ReadAll(os.Stdin)
if err != nil {
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, buffer.NewBufferFromStringWithCommand(string(input), "", btype, command))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, buffer.NewBufferFromStringWithCommand("", "", btype, command))
}
// Option 3, just open an empty buffer
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
}
return buffers
}
func checkBackup(name string) error {
target := filepath.Join(config.ConfigDir, name)
backup := target + util.BackupSuffix
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() {
defer func() {
if util.Stdout.Len() > 0 {
fmt.Fprint(os.Stdout, util.Stdout.String())
}
exit(0)
os.Exit(0)
}()
var err error
@@ -320,15 +255,7 @@ func main() {
screen.TermMessage(err)
}
config.InitRuntimeFiles(true)
config.InitPlugins()
err = checkBackup("settings.json")
if err != nil {
screen.TermMessage(err)
exit(1)
}
config.InitRuntimeFiles()
err = config.ReadSettings()
if err != nil {
screen.TermMessage(err)
@@ -341,17 +268,12 @@ func main() {
// flag options
for k, v := range optionFlags {
if *v != "" {
nativeValue, err := config.GetNativeValue(k, *v)
nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
if err != nil {
screen.TermMessage(err)
continue
}
if err = config.OptionIsValid(k, nativeValue); err != nil {
screen.TermMessage(err)
continue
}
config.GlobalSettings[k] = nativeValue
config.VolatileSettings[k] = true
}
}
@@ -361,7 +283,7 @@ func main() {
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
exit(1)
os.Exit(1)
}
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
clipErr := clipboard.Initialize(m)
@@ -374,15 +296,13 @@ func main() {
if e, ok := err.(*lua.ApiError); ok {
fmt.Println("Lua API error:", e)
} else {
fmt.Println("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/micro-editor/micro/issues")
fmt.Println("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
}
// immediately backup all buffers with unsaved changes
// backup all open buffers
for _, b := range buffer.OpenBuffers {
if b.Modified() {
b.Backup()
}
b.Backup()
}
exit(1)
os.Exit(1)
}
}()
@@ -391,15 +311,14 @@ func main() {
screen.TermMessage(err)
}
err = checkBackup("bindings.json")
if err != nil {
screen.TermMessage(err)
exit(1)
}
action.InitBindings()
action.InitCommands()
err = config.InitColorscheme()
if err != nil {
screen.TermMessage(err)
}
err = config.RunPluginFn("preinit")
if err != nil {
screen.TermMessage(err)
@@ -428,29 +347,22 @@ func main() {
screen.TermMessage(err)
}
err = config.InitColorscheme()
if err != nil {
screen.TermMessage(err)
}
if clipErr != nil {
log.Println(clipErr, " or change 'clipboard' option")
}
config.StartAutoSave()
if a := config.GetGlobalOption("autosave").(float64); a > 0 {
config.SetAutoTime(a)
config.SetAutoTime(int(a))
config.StartAutoSave()
}
screen.Events = make(chan tcell.Event)
util.Sigterm = make(chan os.Signal, 1)
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(sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
signal.Notify(sighup, syscall.SIGHUP)
timerChan = make(chan func())
// Here is the event loop which runs in a separate thread
go func() {
for {
@@ -501,50 +413,48 @@ func DoEvent() {
select {
case f := <-shell.Jobs:
// 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)
ulua.Lock.Unlock()
case <-config.Autosave:
ulua.Lock.Lock()
for _, b := range buffer.OpenBuffers {
b.AutoSave()
b.Save()
}
ulua.Lock.Unlock()
case <-shell.CloseTerms:
action.Tabs.CloseTerms()
case event = <-screen.Events:
case <-screen.DrawChan():
for len(screen.DrawChan()) > 0 {
<-screen.DrawChan()
}
case f := <-timerChan:
f()
case <-sighup:
exit(0)
case <-util.Sigterm:
exit(0)
}
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)
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
return
}
if event != nil {
_, resize := event.(*tcell.EventResize)
if resize {
action.InfoBar.HandleEvent(event)
action.Tabs.HandleEvent(event)
} else if action.InfoBar.HasPrompt {
action.InfoBar.HandleEvent(event)
} else {
action.Tabs.HandleEvent(event)
os.Exit(0)
case <-sigterm:
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
if screen.Screen != nil {
screen.Screen.Fini()
}
os.Exit(0)
}
err := config.RunPluginFn("onAnyEvent")
if err != nil {
screen.TermMessage(err)
ulua.Lock.Lock()
// if event != nil {
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 (
"fmt"
"io/ioutil"
"log"
"os"
"testing"
"github.com/go-errors/errors"
"github.com/micro-editor/micro/v2/internal/action"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/tcell/v2"
"github.com/stretchr/testify/assert"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell/v2"
)
var tempDir string
@@ -25,7 +26,7 @@ func init() {
func startup(args []string) (tcell.SimulationScreen, error) {
var err error
tempDir, err = os.MkdirTemp("", "micro_test")
tempDir, err = ioutil.TempDir("", "micro_test")
if err != nil {
return nil, err
}
@@ -34,9 +35,7 @@ func startup(args []string) (tcell.SimulationScreen, error) {
return nil, err
}
config.InitRuntimeFiles(true)
config.InitPlugins()
config.InitRuntimeFiles()
err = config.ReadSettings()
if err != nil {
return nil, err
@@ -55,11 +54,9 @@ func startup(args []string) (tcell.SimulationScreen, error) {
if err := recover(); err != nil {
screen.Screen.Fini()
fmt.Println("Micro encountered an error:", err)
// immediately backup all buffers with unsaved changes
// backup all open buffers
for _, b := range buffer.OpenBuffers {
if b.Modified() {
b.Backup()
}
b.Backup()
}
// Print the stack trace too
log.Fatalf(errors.Wrap(err, 2).ErrorStack())
@@ -110,10 +107,7 @@ func handleEvent() {
if e != nil {
screen.Events <- e
}
for len(screen.DrawChan()) > 0 || len(screen.Events) > 0 {
DoEvent()
}
DoEvent()
}
func injectKey(key tcell.Key, r rune, mod tcell.ModMask) {
@@ -155,32 +149,20 @@ func openFile(file string) {
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
}
func findBuffer(file string) *buffer.Buffer {
var buf *buffer.Buffer
for _, b := range buffer.OpenBuffers {
if b.Path == file {
buf = b
}
}
return buf
}
func createTestFile(t *testing.T, content string) string {
f, err := os.CreateTemp(t.TempDir(), "")
func createTestFile(name string, content string) (string, error) {
testf, err := ioutil.TempFile("", name)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := f.Close(); err != nil {
t.Fatal(err)
}
}()
if _, err := f.WriteString(content); err != nil {
t.Fatal(err)
return "", 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) {
@@ -197,12 +179,25 @@ func TestMain(m *testing.M) {
}
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)
if findBuffer(file) == nil {
t.Fatalf("Could not find buffer %s", file)
var buf *buffer.Buffer
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)
@@ -220,23 +215,25 @@ func TestSimpleEdit(t *testing.T) {
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := os.ReadFile(file)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Fatal(err)
t.Error(err)
return
}
assert.Equal(t, "firstfoobar\nbase content\n", string(data))
}
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)
if findBuffer(file) == nil {
t.Fatalf("Could not find buffer %s", file)
}
// buffer:
// base content
// the selections need to happen at different locations to avoid a double click
@@ -265,9 +262,10 @@ func TestMouse(t *testing.T) {
// base content
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := os.ReadFile(file)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Fatal(err)
t.Error(err)
return
}
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
@@ -290,23 +288,25 @@ Ernleȝe test_string æðelen
`
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)
if findBuffer(file) == nil {
t.Fatalf("Could not find buffer %s", file)
}
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string"))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := os.ReadFile(file)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Fatal(err)
t.Error(err)
return
}
assert.Equal(t, srTest2, string(data))
@@ -319,9 +319,10 @@ func TestSearchAndReplace(t *testing.T) {
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err = os.ReadFile(file)
data, err = ioutil.ReadFile(file)
if err != nil {
t.Fatal(err)
t.Error(err)
return
}
assert.Equal(t, srTest3, string(data))

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<component>
<id>com.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>
<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,53 +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.15" date="2025-12-31"/>
<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/micro-editor/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/micro-editor/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/micro-editor/micro</url>
<url type="contribute">https://github.com/micro-editor/micro#contributing</url>
</component>

View File

@@ -1,367 +0,0 @@
{
"$comment": "https://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"autosave": {
"description": "A delay between automatic saves\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"backup": {
"description": "Whether to backup all open buffers\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"backupdir": {
"description": "A directory to store backups\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"clipboard": {
"description": "A way to access the system clipboard\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "integer",
"minimum": 0,
"default": 0
},
"colorscheme": {
"description": "A color scheme\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"diffgutter": {
"description": "Whether to display diff inticators before lines\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"divchars": {
"description": "Divider chars for vertical and horizontal splits\nhttps://github.com/micro-editor/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/micro-editor/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/micro-editor/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/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"fileformat": {
"description": "A line ending format\nhttps://github.com/micro-editor/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/micro-editor/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/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"ignorecase": {
"description": "Whether to perform case-insensitive searches\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"indentchar": {
"description": "An indentation character\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"keepautoindent": {
"description": "Whether add a whitespace while using autoindent\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"matchbrace": {
"description": "Whether to show matching braces\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"matchbracestyle": {
"description": "Whether to underline or highlight matching braces\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"mouse": {
"description": "Whether to enable mouse support\nhttps://github.com/micro-editor/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/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"permbackup": {
"description": "Whether to permanently save backups\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"pluginchannels": {
"description": "A file with list of plugin channels\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "array",
"uniqueItems": true,
"items": {
"description": "A pluging repository\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "string"
},
"default": []
},
"readonly": {
"description": "Whether to forbid buffer editing\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"rmtrailingws": {
"description": "Whether to remove trailing whitespaces\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"ruler": {
"description": "Whether to display line numbers\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"relativeruler": {
"description": "Whether to display relative line numbers\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"savecursor": {
"description": "Whether to save cursor position in files\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"saveundo": {
"description": "Whether to save undo after closing file\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"scrollbar": {
"description": "Whether to save undo after closing file\nhttps://github.com/micro-editor/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/micro-editor/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/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"softwrap": {
"description": "Whether to wrap long lines\nhttps://github.com/micro-editor/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/micro-editor/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/micro-editor/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/micro-editor/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/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"sucmd": {
"description": "A super user command\nhttps://github.com/micro-editor/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/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"tabhighlight": {
"description": "Whether to invert tab character colors\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"tabreverse": {
"description": "Whether to reverse tab bar colors\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"tabsize": {
"description": "A tab size\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "integer",
"default": 4
},
"tabstospaces": {
"description": "Whether to use spaces instead of tabs\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"wordwrap": {
"description": "Whether to wrap long lines by words\nhttps://github.com/micro-editor/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/micro-editor/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
}
},
"additionalProperties": false
}

37
go.mod
View File

@@ -1,40 +1,31 @@
module github.com/micro-editor/micro/v2
module github.com/zyedidia/micro/v2
require (
github.com/blang/semver v3.5.1+incompatible
github.com/dustin/go-humanize v1.0.0
github.com/go-errors/errors v1.0.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-runewidth v0.0.16
github.com/micro-editor/json5 v1.0.1-micro
github.com/micro-editor/tcell/v2 v2.0.13
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5
github.com/mattn/go-isatty v0.0.11
github.com/mattn/go-runewidth v0.0.7
github.com/mitchellh/go-homedir v1.1.0
github.com/sergi/go-diff v1.1.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/glob v0.0.0-20170209203856-dd4023a66dc3
golang.org/x/text v0.4.0
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/pty v1.1.20 // indirect
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8 // indirect
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
golang.org/x/text v0.3.2
gopkg.in/yaml.v2 v2.2.8
layeh.com/gopher-luar v1.0.11
layeh.com/gopher-luar v1.0.7
)
require (
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.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
)
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
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
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.7
go 1.19
go 1.16

68
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/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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -19,57 +17,67 @@ 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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
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/layeh/gopher-luar v1.0.7 h1:wnfZhYiJM748y1A4qYBfcFeMY9HWbdERny+ZL0f/jWc=
github.com/layeh/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/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.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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.13 h1:xyuSpBhSBsUH+bs7FER9IV2/TsQpBmCFiNWJVAEdT68=
github.com/micro-editor/tcell/v2 v2.0.13/go.mod h1:ixpjICpoGp83FZVoLYFJPBwCAslHeTnvgPdhJVPLyy0=
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/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8 h1:woqigIZtZUZxws1zZA99nAvuz2mQrxtWsuZSR9c8I/A=
github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8/go.mod h1:6Yhx5ZJl5942QrNRWLwITArVT9okUXc5c3brgWJMoDc=
github.com/yuin/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 v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/zyedidia/clipper v0.0.0-20220613212750-517cd4a6c524 h1:sWYUMHs1EAlsPERKLkaLCxLM0misLylZMEc9Ip5Csjw=
github.com/zyedidia/clipper v0.0.0-20220613212750-517cd4a6c524/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=
github.com/zyedidia/clipper v0.1.0 h1:e16nhM1RgL3HYcugcHRUpMya1K830TS5uo6LlPJHySg=
github.com/zyedidia/clipper v0.1.0/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=
github.com/zyedidia/clipper v0.1.1 h1:HBgguFNDq/QmSQKBnhy4sMKzILINr139VEgAhftOUTw=
github.com/zyedidia/clipper v0.1.1/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=
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/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/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/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
github.com/zyedidia/pty v1.1.15 h1:XlxMFph7HDvTn4sDG8Klgmb/g4ATGiSj4655vAETp1U=
github.com/zyedidia/pty v1.1.15/go.mod h1:HWbpfrLoVM9FmU+/9NV+PzVQV8jSxgnQLk8fvx0q/i8=
github.com/zyedidia/pty v1.1.19 h1:GouvvD/u+uml5EPFUAt5N3rFQKPBmZuuUXHvzAJhVA0=
github.com/zyedidia/pty v1.1.19/go.mod h1:HWbpfrLoVM9FmU+/9NV+PzVQV8jSxgnQLk8fvx0q/i8=
github.com/zyedidia/pty v1.1.20 h1:mkZ5/UiEjZVMFzoXp8oyJAlbn3b380m5lvFrbx/NL/g=
github.com/zyedidia/pty v1.1.20/go.mod h1:HWbpfrLoVM9FmU+/9NV+PzVQV8jSxgnQLk8fvx0q/i8=
github.com/zyedidia/tcell/v2 v2.0.9 h1:FxXRkE62N0GPHES7EMLtp2rteYqC9r1kVid8vJN1kOE=
github.com/zyedidia/tcell/v2 v2.0.9/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8 h1:53ULv4mmLyQDnqbjVxanckP57WSreWHwTmlLJrJEutY=
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
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 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=

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

View File

@@ -1,11 +1,11 @@
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
package action
import (
"syscall"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/screen"
)
// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.

View File

@@ -4,18 +4,17 @@ import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"unicode"
"github.com/micro-editor/json5"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell/v2"
)
var Binder = map[string]func(e Event, action string){
@@ -24,25 +23,21 @@ var Binder = map[string]func(e Event, action string){
"terminal": TermMapEvent,
}
func writeFile(name string, txt []byte) error {
return util.SafeWrite(name, txt, false)
}
func createBindingsIfNotExist(fname string) {
if _, e := os.Stat(fname); errors.Is(e, fs.ErrNotExist) {
writeFile(fname, []byte("{}"))
if _, e := os.Stat(fname); os.IsNotExist(e) {
ioutil.WriteFile(fname, []byte("{}"), 0644)
}
}
// InitBindings intializes the bindings map by reading from bindings.json
func InitBindings() {
var parsed map[string]any
var parsed map[string]interface{}
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e := os.Stat(filename); e == nil {
input, err := os.ReadFile(filename)
input, err := ioutil.ReadFile(filename)
if err != nil {
screen.TermMessage("Error reading bindings.json file: " + err.Error())
return
@@ -66,7 +61,7 @@ func InitBindings() {
switch val := v.(type) {
case string:
BindKey(k, val, Binder["buffer"])
case map[string]any:
case map[string]interface{}:
bind, ok := Binder[k]
if !ok || bind == nil {
screen.TermMessage(fmt.Sprintf("%s is not a valid pane type", k))
@@ -93,10 +88,6 @@ func BindKey(k, v string, bind func(e Event, a string)) {
return
}
if strings.HasPrefix(k, "\x1b") {
screen.RegisterRawSeq(k)
}
bind(event, v)
// switch e := event.(type) {
@@ -162,6 +153,7 @@ modSearch:
k = k[5:]
modifiers |= tcell.ModShift
case strings.HasPrefix(k, "\x1b"):
screen.Screen.RegisterRawSeq(k)
return RawEvent{
esc: k,
}, true
@@ -181,35 +173,39 @@ modSearch:
// see if the key is in bindingKeys with the Ctrl prefix.
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
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{
code: code,
mod: modifiers,
r: rune(r),
}, true
}
}
// See if we can find the key in bindingKeys
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{
code: code,
mod: modifiers,
r: rune(r),
}, 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
if code, ok := mouseEvents[k]; ok {
return MouseEvent{
btn: code,
mod: modifiers,
state: mstate,
btn: code,
mod: modifiers,
}, true
}
@@ -243,43 +239,16 @@ func findEvent(k string) (Event, error) {
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
}
// TryBindKeyPlug tries to bind a key for the plugin without writing to bindings.json.
// This operation can be rejected by lockbindings to prevent unexpected actions by the user.
func TryBindKeyPlug(k, v string, overwrite bool) (bool, error) {
if l, ok := config.GlobalSettings["lockbindings"]; ok && l.(bool) {
return false, errors.New("bindings is locked by the user")
}
return TryBindKey(k, v, overwrite, false)
}
// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
// Returns true if the keybinding already existed or is binded successfully and a possible error
func TryBindKey(k, v string, overwrite bool, writeToFile bool) (bool, error) {
// Returns true if the keybinding already existed and a possible error
func TryBindKey(k, v string, overwrite bool) (bool, error) {
var e error
var parsed map[string]any
var parsed map[string]interface{}
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := os.ReadFile(filename)
input, err := ioutil.ReadFile(filename)
if err != nil {
return false, errors.New("Error reading bindings.json file: " + err.Error())
}
@@ -295,36 +264,28 @@ func TryBindKey(k, v string, overwrite bool, writeToFile bool) (bool, error) {
}
found := false
var ev string
for ev = range parsed {
for ev := range parsed {
if e, err := findEvent(ev); err == nil {
if eventsEqual(e, key) {
if e == key {
if overwrite {
parsed[ev] = v
}
found = true
break
}
}
}
if found {
if overwrite {
parsed[ev] = v
} else {
return true, nil
}
} else {
if found && !overwrite {
return true, nil
} else if !found {
parsed[k] = v
}
BindKey(k, v, Binder["buffer"])
txt, _ := json.MarshalIndent(parsed, "", " ")
txt = append(txt, '\n')
if writeToFile {
return true, writeFile(filename, txt)
} else {
return true, nil
}
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return false, e
}
@@ -332,12 +293,12 @@ func TryBindKey(k, v string, overwrite bool, writeToFile bool) (bool, error) {
// UnbindKey removes the binding for a key from the bindings.json file
func UnbindKey(k string) error {
var e error
var parsed map[string]any
var parsed map[string]interface{}
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := os.ReadFile(filename)
input, err := ioutil.ReadFile(filename)
if err != nil {
return errors.New("Error reading bindings.json file: " + err.Error())
}
@@ -354,17 +315,13 @@ func UnbindKey(k string) error {
for ev := range parsed {
if e, err := findEvent(ev); err == nil {
if eventsEqual(e, key) {
if e == key {
delete(parsed, ev)
break
}
}
}
if strings.HasPrefix(k, "\x1b") {
screen.UnregisterRawSeq(k)
}
defaults := DefaultBindings("buffer")
if a, ok := defaults[k]; ok {
BindKey(k, a, Binder["buffer"])
@@ -374,8 +331,7 @@ func UnbindKey(k string) error {
}
txt, _ := json.MarshalIndent(parsed, "", " ")
txt = append(txt, '\n')
return writeFile(filename, txt)
return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return e
}

View File

@@ -6,18 +6,17 @@ import (
luar "layeh.com/gopher-luar"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
lua "github.com/yuin/gopher-lua"
"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/display"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)
type BufAction any
// BufKeyAction represents an action bound to a key.
type BufKeyAction func(*BufPane) bool
@@ -45,9 +44,8 @@ func init() {
BufBindings = NewKeyTree()
}
// LuaAction makes an action from a lua function. It returns either a BufKeyAction
// or a BufMouseAction depending on the event type.
func LuaAction(fn string, k Event) BufAction {
// LuaAction makes a BufKeyAction from a lua function.
func LuaAction(fn string) func(*BufPane) bool {
luaFn := strings.Split(fn, ".")
if len(luaFn) <= 1 {
return nil
@@ -57,42 +55,33 @@ func LuaAction(fn string, k Event) BufAction {
if pl == nil {
return nil
}
var action BufAction
switch k.(type) {
case KeyEvent, KeySequenceEvent, RawEvent:
action = BufKeyAction(func(h *BufPane) bool {
val, err := pl.Call(plFn, luar.New(ulua.L, h))
if err != nil {
screen.TermMessage(err)
}
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 func(h *BufPane) bool {
val, err := pl.Call(plFn, luar.New(ulua.L, h))
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) {
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 types []byte
for i := 0; ; i++ {
@@ -100,7 +89,9 @@ func BufMapEvent(k Event, action string) {
break
}
idx := util.IndexAnyUnquoted(action, "&|,")
// TODO: fix problem when complex bindings have these
// characters (escape them?)
idx := strings.IndexAny(action, "&|,")
a := action
if idx >= 0 {
a = action[:idx]
@@ -111,7 +102,7 @@ func BufMapEvent(k Event, action string) {
action = ""
}
var afn BufAction
var afn func(*BufPane) bool
if strings.HasPrefix(a, "command:") {
a = strings.SplitN(a, ":", 2)[1]
afn = CommandAction(a)
@@ -122,7 +113,7 @@ func BufMapEvent(k Event, action string) {
names = append(names, "")
} else if strings.HasPrefix(a, "lua:") {
a = strings.SplitN(a, ":", 2)[1]
afn = LuaAction(a, k)
afn = LuaAction(a)
if afn == nil {
screen.TermMessage("Lua Error:", a, "does not exist")
continue
@@ -138,52 +129,47 @@ func BufMapEvent(k Event, action string) {
} else if f, ok := BufKeyActions[a]; ok {
afn = f
names = append(names, a)
} else if f, ok := BufMouseActions[a]; ok {
afn = f
names = append(names, a)
} else {
screen.TermMessage("Error in bindings: action", a, "does not exist")
continue
}
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 {
var success bool
if _, ok := MultiActions[names[i]]; ok {
success = true
for _, c := range h.Buf.GetCursors() {
h.Buf.SetCurCursor(c.Num)
h.Cursor = c
success = success && h.execAction(a, names[i], te)
innerSuccess := true
for j, c := range cursors {
if c == nil {
continue
}
h.Buf.SetCurCursor(c.Num)
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
h = MainTab().CurPane()
if h == nil {
// stop, in case the current pane is not a BufPane
break
}
if (!success && types[i] == '&') || (success && types[i] == '|') {
break
}
success = innerSuccess
}
return true
}
switch e := k.(type) {
case KeyEvent, KeySequenceEvent, RawEvent:
BufBindings.RegisterKeyBinding(e, BufKeyActionGeneral(func(h *BufPane) bool {
return bufAction(h, nil)
}))
case MouseEvent:
BufBindings.RegisterMouseBinding(e, BufMouseActionGeneral(bufAction))
BufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(bufAction))
}
// BufMapMouse maps a mouse event to an action
func bufMapMouse(k MouseEvent, action string) {
if f, ok := BufMouseActions[action]; ok {
BufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
} else {
// TODO
// delete(BufMouseBindings, k)
bufMapKey(k, action)
}
}
@@ -214,31 +200,32 @@ type BufPane struct {
// Cursor is the currently active buffer cursor
Cursor *buffer.Cursor
// Since tcell doesn't differentiate between a mouse press event
// and a mouse move event with button pressed (nor between a mouse
// release event and a mouse move event with no buttons pressed),
// we need to keep track of whether or not the mouse was previously
// pressed, to determine mouse release and mouse drag events.
// 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
// Since tcell doesn't differentiate between a mouse release event
// and a mouse move event with no keys pressed, we need to keep
// track of whether or not the mouse was pressed (or not released) last event to determine
// mouse release events
mouseReleased bool
// We need to keep track of insert key press toggle
isOverwriteMode bool
// This stores when the last click was
// This is useful for detecting double and triple clicks
lastClickTime time.Time
lastLoc buffer.Loc
// freshClip returns true if one or more lines have been cut to the clipboard
// and have never been pasted yet.
// lastCutTime stores when the last ctrl+k was issued.
// 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
// Was the last mouse event actually a double click?
// 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
DoubleClick bool
doubleClick bool
// Same here, just to keep track for mouse move events
TripleClick bool
tripleClick bool
// Should the current multiple cursor selection search based on word or
// based on selection (false for selection, true for word)
@@ -263,7 +250,7 @@ func newBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
h.tab = tab
h.Cursor = h.Buf.GetActiveCursor()
h.mousePressed = make(map[MouseEvent]bool)
h.mouseReleased = true
return h
}
@@ -289,11 +276,7 @@ func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
func (h *BufPane) finishInitialize() {
h.initialRelocate()
h.initialized = true
err := config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
if err != nil {
screen.TermMessage(err)
}
config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
}
// Resize resizes the pane
@@ -321,26 +304,22 @@ func (h *BufPane) ResizePane(size int) {
}
// PluginCB calls all plugin callbacks with a certain name and displays an
// error if there is one and returns the aggregate boolean response.
// The bufpane is passed as the first argument to the callbacks,
// optional args are passed as the next arguments.
func (h *BufPane) PluginCB(cb string, args ...any) bool {
largs := []lua.LValue{luar.New(ulua.L, h)}
for _, a := range args {
largs = append(largs, luar.New(ulua.L, a))
}
b, err := config.RunPluginFnBool(h.Buf.Settings, cb, largs...)
// error if there is one and returns the aggregrate boolean response
func (h *BufPane) PluginCB(cb string) bool {
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
if err != nil {
screen.TermMessage(err)
}
return b
}
func (h *BufPane) resetMouse() {
for me := range h.mousePressed {
delete(h.mousePressed, me)
// PluginCBRune is the same as PluginCB but also passes a rune to the plugins
func (h *BufPane) PluginCBRune(cb string, r rune) bool {
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
if err != nil {
screen.TermMessage(err)
}
return b
}
// OpenBuffer opens the given buffer in this pane.
@@ -353,7 +332,10 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
h.initialRelocate()
// Set mouseReleased to true because we assume the mouse is not being
// pressed when 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{}
}
@@ -413,40 +395,20 @@ func (h *BufPane) Name() string {
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
func (h *BufPane) HandleEvent(event tcell.Event) {
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) {
@@ -459,44 +421,61 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
h.paste(e.Text())
h.Relocate()
case *tcell.EventKey:
ke := keyEvent(e)
ke := KeyEvent{
code: e.Key(),
mod: metaToAlt(e.Modifiers()),
r: e.Rune(),
}
done := h.DoKeyEvent(ke)
if !done && e.Key() == tcell.KeyRune {
h.DoRuneInsert(e.Rune())
}
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{
btn: e.Buttons(),
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
btn: e.Buttons(),
mod: metaToAlt(e.Modifiers()),
}
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()
@@ -516,14 +495,6 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
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.
@@ -535,10 +506,7 @@ func (h *BufPane) Bindings() *KeyTree {
}
// DoKeyEvent executes a key event by finding the action it is bound
// 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.
// to and executing it (possibly multiple times for multiple cursors)
func (h *BufPane) DoKeyEvent(e Event) bool {
binds := h.Bindings()
action, more := binds.NextEvent(e, nil)
@@ -552,33 +520,30 @@ func (h *BufPane) DoKeyEvent(e Event) bool {
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" {
h.Buf.HasSuggestions = false
}
if !h.PluginCB("pre"+name, te) {
return false
}
_, isMulti := MultiActions[name]
if (!isMulti && cursor == 0) || isMulti {
if h.PluginCB("pre" + name) {
success := action(h)
success = success && h.PluginCB("on"+name)
var success bool
switch a := action.(type) {
case BufKeyAction:
success = a(h)
case BufMouseAction:
success = a(h, te)
}
success = success && h.PluginCB("on"+name, te)
if _, ok := MultiActions[name]; ok {
if recordingMacro {
if name != "ToggleMacro" && name != "PlayMacro" {
curmacro = append(curmacro, action)
if isMulti {
if recordingMacro {
if name != "ToggleMacro" && name != "PlayMacro" {
curmacro = append(curmacro, action)
}
}
}
return success
}
}
return success
return false
}
func (h *BufPane) completeAction(action string) {
@@ -624,7 +589,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
// Insert a character
h.Buf.SetCurCursor(c.Num)
h.Cursor = c
if !h.PluginCB("preRune", string(r)) {
if !h.PluginCBRune("preRune", r) {
continue
}
if c.HasSelection() {
@@ -632,7 +597,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
c.ResetSelection()
}
if h.Buf.OverwriteMode {
if h.isOverwriteMode {
next := c.Loc
next.X++
h.Buf.Replace(c.Loc, next, string(r))
@@ -643,35 +608,27 @@ func (h *BufPane) DoRuneInsert(r rune) {
curmacro = append(curmacro, r)
}
h.Relocate()
h.PluginCB("onRune", string(r))
h.PluginCBRune("onRune", r)
}
}
// VSplitIndex opens the given buffer in a vertical split on the given side.
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
e := NewBufPaneFromBuf(buf, h.tab)
e.splitID = h.tab.GetNode(h.splitID).VSplit(right)
currentPaneIdx := h.tab.GetPane(h.splitID)
if right {
currentPaneIdx++
}
h.tab.AddPane(e, currentPaneIdx)
h.tab.Resize()
h.tab.SetActive(currentPaneIdx)
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
MainTab().Panes = append(MainTab().Panes, e)
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
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 {
e := NewBufPaneFromBuf(buf, h.tab)
e.splitID = h.tab.GetNode(h.splitID).HSplit(bottom)
currentPaneIdx := h.tab.GetPane(h.splitID)
if bottom {
currentPaneIdx++
}
h.tab.AddPane(e, currentPaneIdx)
h.tab.Resize()
h.tab.SetActive(currentPaneIdx)
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
MainTab().Panes = append(MainTab().Panes, e)
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
return e
}
@@ -692,10 +649,6 @@ func (h *BufPane) Close() {
// SetActive marks this pane as active.
func (h *BufPane) SetActive(b bool) {
if h.IsActive() == b {
return
}
h.BWindow.SetActive(b)
if b {
// Display any gutter messages for this line
@@ -711,12 +664,8 @@ func (h *BufPane) SetActive(b bool) {
if none && InfoBar.HasGutter {
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
@@ -729,9 +678,6 @@ var BufKeyActions = map[string]BufKeyAction{
"CursorRight": (*BufPane).CursorRight,
"CursorStart": (*BufPane).CursorStart,
"CursorEnd": (*BufPane).CursorEnd,
"CursorToViewTop": (*BufPane).CursorToViewTop,
"CursorToViewCenter": (*BufPane).CursorToViewCenter,
"CursorToViewBottom": (*BufPane).CursorToViewBottom,
"SelectToStart": (*BufPane).SelectToStart,
"SelectToEnd": (*BufPane).SelectToEnd,
"SelectUp": (*BufPane).SelectUp,
@@ -740,16 +686,10 @@ var BufKeyActions = map[string]BufKeyAction{
"SelectRight": (*BufPane).SelectRight,
"WordRight": (*BufPane).WordRight,
"WordLeft": (*BufPane).WordLeft,
"SubWordRight": (*BufPane).SubWordRight,
"SubWordLeft": (*BufPane).SubWordLeft,
"SelectWordRight": (*BufPane).SelectWordRight,
"SelectWordLeft": (*BufPane).SelectWordLeft,
"SelectSubWordRight": (*BufPane).SelectSubWordRight,
"SelectSubWordLeft": (*BufPane).SelectSubWordLeft,
"DeleteWordRight": (*BufPane).DeleteWordRight,
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
"DeleteSubWordRight": (*BufPane).DeleteSubWordRight,
"DeleteSubWordLeft": (*BufPane).DeleteSubWordLeft,
"SelectLine": (*BufPane).SelectLine,
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
@@ -757,8 +697,6 @@ var BufKeyActions = map[string]BufKeyAction{
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
"ParagraphNext": (*BufPane).ParagraphNext,
"SelectToParagraphPrevious": (*BufPane).SelectToParagraphPrevious,
"SelectToParagraphNext": (*BufPane).SelectToParagraphNext,
"InsertNewline": (*BufPane).InsertNewline,
"Backspace": (*BufPane).Backspace,
"Delete": (*BufPane).Delete,
@@ -770,8 +708,6 @@ var BufKeyActions = map[string]BufKeyAction{
"FindLiteral": (*BufPane).FindLiteral,
"FindNext": (*BufPane).FindNext,
"FindPrevious": (*BufPane).FindPrevious,
"DiffNext": (*BufPane).DiffNext,
"DiffPrevious": (*BufPane).DiffPrevious,
"Center": (*BufPane).Center,
"Undo": (*BufPane).Undo,
"Redo": (*BufPane).Redo,
@@ -779,7 +715,6 @@ var BufKeyActions = map[string]BufKeyAction{
"CopyLine": (*BufPane).CopyLine,
"Cut": (*BufPane).Cut,
"CutLine": (*BufPane).CutLine,
"Duplicate": (*BufPane).Duplicate,
"DuplicateLine": (*BufPane).DuplicateLine,
"DeleteLine": (*BufPane).DeleteLine,
"MoveLinesUp": (*BufPane).MoveLinesUp,
@@ -812,7 +747,6 @@ var BufKeyActions = map[string]BufKeyAction{
"ToggleRuler": (*BufPane).ToggleRuler,
"ToggleHighlightSearch": (*BufPane).ToggleHighlightSearch,
"UnhighlightSearch": (*BufPane).UnhighlightSearch,
"ResetSearch": (*BufPane).ResetSearch,
"ClearStatus": (*BufPane).ClearStatus,
"ShellMode": (*BufPane).ShellMode,
"CommandMode": (*BufPane).CommandMode,
@@ -824,12 +758,8 @@ var BufKeyActions = map[string]BufKeyAction{
"AddTab": (*BufPane).AddTab,
"PreviousTab": (*BufPane).PreviousTab,
"NextTab": (*BufPane).NextTab,
"FirstTab": (*BufPane).FirstTab,
"LastTab": (*BufPane).LastTab,
"NextSplit": (*BufPane).NextSplit,
"PreviousSplit": (*BufPane).PreviousSplit,
"FirstSplit": (*BufPane).FirstSplit,
"LastSplit": (*BufPane).LastSplit,
"Unsplit": (*BufPane).Unsplit,
"VSplit": (*BufPane).VSplitAction,
"HSplit": (*BufPane).HSplitAction,
@@ -845,7 +775,6 @@ var BufKeyActions = map[string]BufKeyAction{
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
"SkipMultiCursorBack": (*BufPane).SkipMultiCursorBack,
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
"JumpLine": (*BufPane).JumpLine,
"Deselect": (*BufPane).Deselect,
@@ -859,8 +788,6 @@ var BufKeyActions = map[string]BufKeyAction{
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
var BufMouseActions = map[string]BufMouseAction{
"MousePress": (*BufPane).MousePress,
"MouseDrag": (*BufPane).MouseDrag,
"MouseRelease": (*BufPane).MouseRelease,
"MouseMultiCursor": (*BufPane).MouseMultiCursor,
}
@@ -885,16 +812,10 @@ var MultiActions = map[string]bool{
"SelectRight": true,
"WordRight": true,
"WordLeft": true,
"SubWordRight": true,
"SubWordLeft": true,
"SelectWordRight": true,
"SelectWordLeft": true,
"SelectSubWordRight": true,
"SelectSubWordLeft": true,
"DeleteWordRight": true,
"DeleteWordLeft": true,
"DeleteSubWordRight": true,
"DeleteSubWordLeft": true,
"SelectLine": true,
"SelectToStartOfLine": true,
"SelectToStartOfText": true,
@@ -912,7 +833,6 @@ var MultiActions = map[string]bool{
"Copy": true,
"Cut": true,
"CutLine": true,
"Duplicate": true,
"DuplicateLine": true,
"DeleteLine": true,
"MoveLinesUp": true,

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ package action
var termdefaults = map[string]string{
"<Ctrl-q><Ctrl-q>": "Exit",
"<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

View File

@@ -41,29 +41,25 @@ var bufdefaults = map[string]string{
"Alt-F": "FindLiteral",
"Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious",
"Alt-[": "DiffPrevious|CursorStart",
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-d": "Duplicate|DuplicateLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
"Alt-,": "PreviousTab|LastTab",
"Alt-.": "NextTab|FirstTab",
"Alt-,": "PreviousTab",
"Alt-.": "NextTab",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab|LastTab",
"CtrlPageDown": "NextTab|FirstTab",
"ShiftPageUp": "SelectPageUp",
"ShiftPageDown": "SelectPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"Ctrl-g": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"Ctrl-r": "ToggleRuler",
@@ -72,7 +68,7 @@ var bufdefaults = map[string]string{
"Ctrl-b": "ShellMode",
"Ctrl-q": "Quit",
"Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit|FirstSplit",
"Ctrl-w": "NextSplit",
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
@@ -94,13 +90,11 @@ var bufdefaults = map[string]string{
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseLeftDrag": "MouseDrag",
"MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"AltShiftUp": "SpawnMultiCursorUp",
@@ -146,7 +140,7 @@ var infodefaults = map[string]string{
"Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "Copy",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-v": "Paste",
@@ -179,10 +173,8 @@ var infodefaults = map[string]string{
"Esc": "AbortCommand",
// Mouse bindings
"MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress",
"MouseLeftDrag": "MouseDrag",
"MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
"MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
}

View File

@@ -1,4 +1,3 @@
//go:build !darwin
// +build !darwin
package action
@@ -44,29 +43,25 @@ var bufdefaults = map[string]string{
"Alt-F": "FindLiteral",
"Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious",
"Alt-[": "DiffPrevious|CursorStart",
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-d": "Duplicate|DuplicateLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
"Alt-,": "PreviousTab|LastTab",
"Alt-.": "NextTab|FirstTab",
"Alt-,": "PreviousTab",
"Alt-.": "NextTab",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab|LastTab",
"CtrlPageDown": "NextTab|FirstTab",
"ShiftPageUp": "SelectPageUp",
"ShiftPageDown": "SelectPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"Ctrl-g": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"Ctrl-r": "ToggleRuler",
@@ -75,7 +70,7 @@ var bufdefaults = map[string]string{
"Ctrl-b": "ShellMode",
"Ctrl-q": "Quit",
"Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit|FirstSplit",
"Ctrl-w": "NextSplit",
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
@@ -97,13 +92,11 @@ var bufdefaults = map[string]string{
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseLeftDrag": "MouseDrag",
"MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
@@ -149,7 +142,7 @@ var infodefaults = map[string]string{
"Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "Copy",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-v": "Paste",
@@ -182,10 +175,8 @@ var infodefaults = map[string]string{
"Esc": "AbortCommand",
// Mouse bindings
"MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress",
"MouseLeftDrag": "MouseDrag",
"MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
"MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
}

View File

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

View File

@@ -1,6 +1,6 @@
package action
import "github.com/micro-editor/micro/v2/internal/buffer"
import "github.com/zyedidia/micro/v2/internal/buffer"
// InfoBar is the global info bar.
var InfoBar *InfoPane
@@ -11,8 +11,7 @@ var LogBufPane *BufPane
// InitGlobals initializes the log buffer and the info bar
func InitGlobals() {
InfoBar = NewInfoBar()
buffer.LogBuf = buffer.NewBufferFromString("", "", buffer.BTLog)
buffer.LogBuf.SetName("Log")
buffer.LogBuf = buffer.NewBufferFromString("", "Log", buffer.BTLog)
}
// GetInfoBar returns the infobar pane

View File

@@ -5,10 +5,9 @@ import (
"sort"
"strings"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/pkg/highlight"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
)
// This file is meant (for now) for autocompletion in command mode, not
@@ -18,7 +17,7 @@ import (
// CommandComplete autocompletes commands
func CommandComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := b.GetArg()
input, argstart := buffer.GetArg(b)
var suggestions []string
for cmd := range commands {
@@ -39,7 +38,7 @@ func CommandComplete(b *buffer.Buffer) ([]string, []string) {
// HelpComplete autocompletes help topics
func HelpComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := b.GetArg()
input, argstart := buffer.GetArg(b)
var suggestions []string
@@ -78,67 +77,19 @@ func colorschemeComplete(input string) (string, []string) {
return chosen, suggestions
}
// filetypeComplete autocompletes filetype
func filetypeComplete(input string) (string, []string) {
var suggestions []string
// We cannot match filetypes just by names of syntax files,
// since those names may be different from the actual filetype values
// specified inside syntax files (e.g. "c++" filetype in cpp.yaml).
// So we need to parse filetype values out of those files.
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
data, err := f.Data()
if err != nil {
continue
}
header, err := highlight.MakeHeaderYaml(data)
if err != nil {
continue
}
// Prevent duplicated defaults
if header.FileType == "off" || header.FileType == "unknown" {
continue
}
if strings.HasPrefix(header.FileType, input) {
suggestions = append(suggestions, header.FileType)
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
headerLoop:
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
data, err := f.Data()
if err != nil {
continue
}
header, err := highlight.MakeHeader(data)
if err != nil {
continue
}
for _, v := range suggestions {
if v == header.FileType {
continue headerLoop
}
}
if strings.HasPrefix(header.FileType, input) {
suggestions = append(suggestions, header.FileType)
}
}
if strings.HasPrefix("off", input) {
suggestions = append(suggestions, "off")
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
return false
}
// OptionComplete autocompletes options
func OptionComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := b.GetArg()
input, argstart := buffer.GetArg(b)
var suggestions []string
for option := range config.GlobalSettings {
@@ -165,7 +116,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X)
input, argstart := b.GetArg()
input, argstart := buffer.GetArg(b)
completeValue := false
args := bytes.Split(l, []byte{' '})
@@ -193,7 +144,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
inputOpt = strings.TrimSpace(inputOpt)
var suggestions []string
// localSettings := config.DefaultLocalSettings()
var optionVal any
var optionVal interface{}
for k, option := range config.GlobalSettings {
if k == inputOpt {
optionVal = option
@@ -221,8 +172,13 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
switch inputOpt {
case "colorscheme":
_, suggestions = colorschemeComplete(input)
case "filetype":
_, suggestions = filetypeComplete(input)
case "fileformat":
if strings.HasPrefix("unix", input) {
suggestions = append(suggestions, "unix")
}
if strings.HasPrefix("dos", input) {
suggestions = append(suggestions, "dos")
}
case "sucmd":
if strings.HasPrefix("sudo", input) {
suggestions = append(suggestions, "sudo")
@@ -230,13 +186,15 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
if strings.HasPrefix("doas", input) {
suggestions = append(suggestions, "doas")
}
default:
if choices, ok := config.OptionChoices[inputOpt]; ok {
for _, choice := range choices {
if strings.HasPrefix(choice, input) {
suggestions = append(suggestions, choice)
}
}
case "clipboard":
if strings.HasPrefix("external", input) {
suggestions = append(suggestions, "external")
}
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
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := b.GetArg()
input, argstart := buffer.GetArg(b)
var suggestions []string
for _, cmd := range PluginCmds {
@@ -274,7 +232,7 @@ func PluginComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X)
input, argstart := b.GetArg()
input, argstart := buffer.GetArg(b)
completeValue := false
args := bytes.Split(l, []byte{' '})

View File

@@ -3,12 +3,12 @@ package action
import (
"bytes"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
"github.com/micro-editor/micro/v2/internal/info"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/info"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)
type InfoKeyAction func(*InfoPane)
@@ -83,22 +83,22 @@ func (h *InfoPane) Close() {
func (h *InfoPane) HandleEvent(event tcell.Event) {
switch e := event.(type) {
case *tcell.EventResize:
// TODO
case *tcell.EventKey:
ke := keyEvent(e)
ke := KeyEvent{
code: e.Key(),
mod: metaToAlt(e.Modifiers()),
r: e.Rune(),
}
done := h.DoKeyEvent(ke)
hasYN := h.HasYN
if e.Key() == tcell.KeyRune && hasYN {
y := e.Rune() == 'y' || e.Rune() == 'Y'
n := e.Rune() == 'n' || e.Rune() == 'N'
if y || n {
h.YNResp = y
if (e.Rune() == 'y' || e.Rune() == 'Y') && hasYN {
h.YNResp = true
h.DonePrompt(false)
} else if (e.Rune() == 'n' || e.Rune() == 'N') && hasYN {
h.YNResp = false
h.DonePrompt(false)
InfoBindings.ResetEvents()
InfoBufBindings.ResetEvents()
}
}
if e.Key() == tcell.KeyRune && !done && !hasYN {
@@ -108,11 +108,7 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
if done && h.HasPrompt && !hasYN {
resp := string(h.LineBytes(0))
hist := h.History[h.PromptType]
if resp != hist[h.HistoryNum] {
h.HistoryNum = len(hist) - 1
hist[h.HistoryNum] = resp
h.HistorySearch = false
}
hist[h.HistoryNum] = resp
if h.EventCallback != nil {
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.
// 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.
// DoKeyEvent executes a key event for the command bar, doing any overridden actions
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
action, more := InfoBindings.NextEvent(e, nil)
if action != nil && !more {
@@ -139,25 +132,11 @@ func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
}
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)
if action != nil && !more {
action(h.BufPane)
done := action(h.BufPane)
InfoBufBindings.ResetEvents()
return true
return done
} else if action == nil && !more {
InfoBufBindings.ResetEvents()
}
@@ -176,18 +155,6 @@ func (h *InfoPane) HistoryDown() {
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
func (h *InfoPane) CommandComplete() {
b := h.Buf
@@ -231,11 +198,9 @@ func (h *InfoPane) AbortCommand() {
// InfoKeyActions contains the list of all possible key actions the infopane could execute
var InfoKeyActions = map[string]InfoKeyAction{
"HistoryUp": (*InfoPane).HistoryUp,
"HistoryDown": (*InfoPane).HistoryDown,
"HistorySearchUp": (*InfoPane).HistorySearchUp,
"HistorySearchDown": (*InfoPane).HistorySearchDown,
"CommandComplete": (*InfoPane).CommandComplete,
"ExecuteCommand": (*InfoPane).ExecuteCommand,
"AbortCommand": (*InfoPane).AbortCommand,
"HistoryUp": (*InfoPane).HistoryUp,
"HistoryDown": (*InfoPane).HistoryDown,
"CommandComplete": (*InfoPane).CommandComplete,
"ExecuteCommand": (*InfoPane).ExecuteCommand,
"AbortCommand": (*InfoPane).AbortCommand,
}

View File

@@ -3,7 +3,7 @@ package action
import (
"bytes"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/tcell/v2"
)
type PaneKeyAction func(Pane) bool

View File

@@ -1,7 +1,7 @@
package action
import (
"github.com/micro-editor/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/display"
)
// A Pane is a general interface for a window in the editor.

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"reflect"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/display"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/tcell/v2"
)
type RawPane struct {
@@ -22,10 +22,7 @@ func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane
func NewRawPane(tab *Tab) *RawPane {
b := buffer.NewBufferFromString("", "", buffer.BTRaw)
b.SetName("Raw event viewer")
w := display.NewBufWindow(0, 0, 0, 0, b)
return NewRawPaneFromWin(b, w, tab)
}

View File

@@ -1,15 +1,12 @@
package action
import (
luar "layeh.com/gopher-luar"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/views"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/views"
"github.com/zyedidia/tcell/v2"
)
// 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()
switch e.Buttons() {
case tcell.Button1:
if my == t.Y && len(t.List) > 1 {
if mx == 0 {
t.Scroll(-4)
} else if mx == t.Width-1 {
t.Scroll(4)
} else {
ind := t.LocFromVisual(buffer.Loc{mx, my})
if ind != -1 {
t.SetActive(ind)
}
}
if my == t.Y && mx == 0 {
t.Scroll(-4)
return
} else if my == t.Y && mx == t.Width-1 {
t.Scroll(4)
return
}
case tcell.ButtonNone:
if t.List[t.Active()].release {
// Mouse release received, while already released
t.ResetMouse()
return
if len(t.List) > 1 {
ind := t.LocFromVisual(buffer.Loc{mx, my})
if ind != -1 {
t.SetActive(ind)
return
}
if my == 0 {
return
}
}
case tcell.WheelUp:
if my == t.Y && len(t.List) > 1 {
if my == t.Y {
t.Scroll(4)
return
}
case tcell.WheelDown:
if my == t.Y && len(t.List) > 1 {
if my == t.Y {
t.Scroll(-4)
return
}
@@ -149,56 +144,6 @@ 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
var Tabs *TabList
@@ -211,13 +156,11 @@ func InitTabs(bufs []*buffer.Buffer) {
for _, b := range bufs[1:] {
if multiopen == "vsplit" {
MainTab().CurPane().VSplitBuf(b)
} else { // default hsplit
} else { // default hsplit
MainTab().CurPane().HSplitBuf(b)
}
}
}
screen.RestartCallback = Tabs.ResetMouse
}
func MainTab() *Tab {
@@ -231,9 +174,6 @@ func MainTab() *Tab {
type Tab struct {
*views.Node
*display.UIWindow
isActive bool
Panes []Pane
active int
@@ -271,40 +211,34 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
// 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
// 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
// and get the event
// If the event is a mouse event in a pane, that pane will become active and get
// the event
func (t *Tab) HandleEvent(event tcell.Event) {
switch e := event.(type) {
case *tcell.EventMouse:
mx, my := e.Position()
btn := e.Buttons()
switch {
case btn & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone:
// button press or drag
switch e.Buttons() {
case tcell.Button1:
wasReleased := t.release
t.release = false
if btn == tcell.Button1 {
if t.resizing != nil {
var size int
if t.resizing.Kind == views.STVert {
size = mx - t.resizing.X
} 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
}
if t.resizing != nil {
var size int
if t.resizing.Kind == views.STVert {
size = mx - t.resizing.X
} 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
}
for i, p := range t.Panes {
v := p.GetView()
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
@@ -314,15 +248,10 @@ func (t *Tab) HandleEvent(event tcell.Event) {
}
}
}
case btn == tcell.ButtonNone:
// button release
case tcell.ButtonNone:
t.resizing = nil
t.release = true
if t.resizing != nil {
t.resizing = nil
return
}
default:
// wheel move
for _, p := range t.Panes {
v := p.GetView()
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
@@ -349,16 +278,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
func (t *Tab) GetPane(splitid uint64) int {
for i, p := range t.Panes {

View File

@@ -1,10 +1,10 @@
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
// +build linux darwin dragonfly openbsd_amd64 freebsd
package action
import (
shellquote "github.com/kballard/go-shellquote"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/shell"
)
// TermEmuSupported is a constant that marks if the terminal emulator is supported
@@ -14,7 +14,7 @@ const TermEmuSupported = true
// if wait is true it will wait for the user to exit by pressing enter once the executable has terminated
// if getOutput is true it will redirect the stdout of the process to a pipe which will be passed to the
// callback which is a function that takes a string and a list of optional user arguments
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []any), userargs []any) error {
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
args, err := shellquote.Split(input)
if err != nil {
return err

View File

@@ -1,4 +1,4 @@
//go:build plan9 || nacl || windows
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
package action
@@ -8,6 +8,6 @@ import "errors"
const TermEmuSupported = false
// RunTermEmulator returns an error for unsupported systems (non-unix systems
func RunTermEmulator(input string, wait bool, getOutput bool, callback func(out string, userargs []any), userargs []any) error {
func RunTermEmulator(input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
return errors.New("Unsupported operating system")
}

View File

@@ -4,13 +4,13 @@ import (
"errors"
"runtime"
"github.com/micro-editor/micro/v2/internal/clipboard"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/micro-editor/tcell/v2"
"github.com/micro-editor/terminal"
"github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/tcell/v2"
"github.com/zyedidia/terminal"
)
type TermKeyAction func(*TermPane)
@@ -81,10 +81,6 @@ func (t *TermPane) SetID(i uint64) {
t.id = i
}
func (t *TermPane) Name() string {
return t.Terminal.Name()
}
func (t *TermPane) SetTab(tab *Tab) {
t.tab = tab
}
@@ -125,7 +121,11 @@ func (t *TermPane) Unsplit() {
// copy-paste
func (t *TermPane) HandleEvent(event tcell.Event) {
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)
if !more {
@@ -159,9 +159,9 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
if t.Status != shell.TTDone {
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())
} else {
} else if e != nil {
x, y := e.Position()
v := t.GetView()
x -= v.X
@@ -188,12 +188,7 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
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 {
t.Quit()
}

View File

@@ -2,12 +2,12 @@ package buffer
import (
"bytes"
"io/fs"
"io/ioutil"
"os"
"sort"
"strings"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// A Completer is a function that takes a buffer and returns info
@@ -64,7 +64,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
// GetWord gets the most recent word separated by any separator
// (whitespace, punctuation, any non alphanumeric character)
func (b *Buffer) GetWord() ([]byte, int) {
func GetWord(b *Buffer) ([]byte, int) {
c := b.GetActiveCursor()
l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X)
@@ -73,17 +73,17 @@ func (b *Buffer) GetWord() ([]byte, int) {
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
}
args := bytes.FieldsFunc(l, util.IsNonWordChar)
args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
input := args[len(args)-1]
return input, c.X - util.CharacterCount(input)
}
// GetArg gets the most recent word (separated by ' ' only)
func (b *Buffer) GetArg() (string, int) {
func GetArg(b *Buffer) (string, int) {
c := b.GetActiveCursor()
l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X)
@@ -104,20 +104,20 @@ func (b *Buffer) GetArg() (string, int) {
// FileComplete autocompletes filenames
func FileComplete(b *Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := b.GetArg()
input, argstart := GetArg(b)
sep := string(os.PathSeparator)
dirs := strings.Split(input, sep)
var files []fs.DirEntry
var files []os.FileInfo
var err error
if len(dirs) > 1 {
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
directories, _ = util.ReplaceHome(directories)
files, err = os.ReadDir(directories)
files, err = ioutil.ReadDir(directories)
} else {
files, err = os.ReadDir(".")
files, err = ioutil.ReadDir(".")
}
if err != nil {
@@ -153,7 +153,7 @@ func FileComplete(b *Buffer) ([]string, []string) {
// BufferComplete autocompletes based on previous words in the buffer
func BufferComplete(b *Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := b.GetWord()
input, argstart := GetWord(b)
if argstart == -1 {
return []string{}, []string{}
@@ -166,7 +166,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
var suggestions []string
for i := c.Y; i >= 0; i-- {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonWordChar)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
strw := string(w)
@@ -179,7 +179,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
}
for i := c.Y + 1; i < b.LinesNum(); i++ {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonWordChar)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
strw := string(w)

View File

@@ -1,26 +1,24 @@
package buffer
import (
"errors"
"fmt"
"io/fs"
"io"
"os"
"path/filepath"
"sync/atomic"
"time"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding"
)
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
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:
The backup was created on %s, and the file is
%s
@@ -32,148 +30,114 @@ The backup was created on %s and its path is:
Options: [r]ecover, [i]gnore, [a]bort: `
const backupSeconds = 8
var backupRequestChan chan *Buffer
type backupRequestType int
func backupThread() {
for {
time.Sleep(time.Second * 8)
const (
backupCreate = iota
backupRemove
)
type backupRequest struct {
buf *SharedBuffer
reqType backupRequestType
for len(backupRequestChan) > 0 {
b := <-backupRequestChan
bfini := atomic.LoadInt32(&(b.fini)) != 0
if !bfini {
b.Backup()
}
}
}
}
var requestedBackups map[*SharedBuffer]bool
func init() {
requestedBackups = make(map[*SharedBuffer]bool)
backupRequestChan = make(chan *Buffer, 10)
go backupThread()
}
func (b *SharedBuffer) RequestBackup() {
backupRequestChan <- backupRequest{buf: b, reqType: backupCreate}
}
func (b *SharedBuffer) CancelBackup() {
backupRequestChan <- backupRequest{buf: b, reqType: backupRemove}
}
func handleBackupRequest(br backupRequest) {
switch br.reqType {
case backupCreate:
// schedule periodic backup
requestedBackups[br.buf] = true
case backupRemove:
br.buf.RemoveBackup()
delete(requestedBackups, br.buf)
}
}
func periodicBackup() {
for buf := range requestedBackups {
err := buf.Backup()
if err == nil {
delete(requestedBackups, buf)
func (b *Buffer) RequestBackup() {
if !b.requestedBackup {
select {
case backupRequestChan <- b:
default:
// channel is full
}
b.requestedBackup = true
}
}
func (b *SharedBuffer) backupDir() string {
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
if backupdir == "" || err != nil {
backupdir = filepath.Join(config.ConfigDir, "backups")
}
return backupdir
}
func (b *SharedBuffer) keepBackup() bool {
return b.forceKeepBackup || b.Settings["permbackup"].(bool)
}
func (b *SharedBuffer) writeBackup(path string) (string, string, error) {
backupdir := b.backupDir()
if _, err := os.Stat(backupdir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return "", "", err
}
if err = os.Mkdir(backupdir, os.ModePerm); err != nil {
return "", "", err
}
}
name, resolveName := util.DetermineEscapePath(backupdir, path)
tmp := name + util.BackupSuffix
_, err := b.overwriteFile(tmp)
if err != nil {
os.Remove(tmp)
return name, resolveName, err
}
err = os.Rename(tmp, name)
if err != nil {
os.Remove(tmp)
return name, resolveName, err
}
if resolveName != "" {
err = util.SafeWrite(resolveName, []byte(path), true)
if err != nil {
return name, resolveName, err
}
}
return name, resolveName, nil
}
func (b *SharedBuffer) removeBackup(path string, resolveName string) {
os.Remove(path)
if resolveName != "" {
os.Remove(resolveName)
}
}
// Backup saves the buffer to the backups directory
func (b *SharedBuffer) Backup() error {
// Backup saves the current buffer to ConfigDir/backups
func (b *Buffer) Backup() error {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
return nil
}
_, _, err := b.writeBackup(b.AbsPath)
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
if backupdir == "" || err != nil {
backupdir = filepath.Join(config.ConfigDir, "backups")
}
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
os.Mkdir(backupdir, os.ModePerm)
}
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
}
// end of line
eol := []byte{'\n'}
// 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
}
// RemoveBackup removes any backup file associated with this buffer
func (b *SharedBuffer) RemoveBackup() {
if b.keepBackup() || b.Path == "" || b.Type != BTDefault {
func (b *Buffer) RemoveBackup() {
if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
return
}
f, resolveName := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
b.removeBackup(f, resolveName)
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
os.Remove(f)
}
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
// Returns true if a backup was applied
func (b *SharedBuffer) ApplyBackup(fsize int64) (bool, bool) {
func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
backupfile, resolveName := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
if info, err := os.Stat(backupfile); err == nil {
backup, err := os.Open(backupfile)
if err == nil {
defer backup.Close()
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)
if choice%3 == 0 {
// recover
b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
b.setModified()
b.isModified = true
return true, true
} else if choice%3 == 1 {
// delete
b.removeBackup(backupfile, resolveName)
os.Remove(backupfile)
} else if choice%3 == 2 {
return false, false
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,11 +5,11 @@ import (
"strings"
"testing"
"github.com/micro-editor/micro/v2/internal/config"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/stretchr/testify/assert"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/util"
)
type operation struct {
@@ -20,7 +20,6 @@ type operation struct {
func init() {
ulua.L = lua.NewState()
config.InitRuntimeFiles(false)
config.InitGlobalSettings()
config.GlobalSettings["backup"] = false
config.GlobalSettings["fastdirty"] = true

View File

@@ -1,8 +1,8 @@
package buffer
import (
"github.com/micro-editor/micro/v2/internal/clipboard"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/util"
)
// InBounds returns whether the given location is a valid character position in the given buffer
@@ -20,14 +20,8 @@ type Cursor struct {
buf *Buffer
Loc
// Last visual x position of the cursor. Used in cursor up/down movements
// for remembering the original x position when moving to a line that is
// shorter than current x position.
// Last cursor x position
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)
CurSelection [2]Loc
@@ -36,11 +30,6 @@ type Cursor struct {
// to know what the original selection was
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)
Num int
}
@@ -49,8 +38,6 @@ func NewCursor(b *Buffer, l Loc) *Cursor {
c := &Cursor{
buf: b,
Loc: l,
NewTrailingWsY: -1,
}
c.StoreVisualX()
return c
@@ -67,9 +54,8 @@ func (c *Cursor) Buf() *Buffer {
// Goto puts the cursor at the given cursor's location and gives
// the current cursor its selection too
func (c *Cursor) Goto(b Cursor) {
c.X, c.Y = 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.StoreVisualX()
}
// 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
func (c *Cursor) GetVisualX(wrap bool) int {
if wrap && c.buf.GetVisualX != nil {
func (c *Cursor) GetVisualX() int {
if c.buf.GetVisualX != nil {
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
func (c *Cursor) Start() {
c.X = 0
c.StoreVisualX()
c.LastVisualX = c.GetVisualX()
}
// 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
func (c *Cursor) End() {
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
c.StoreVisualX()
c.LastVisualX = c.GetVisualX()
}
// CopySelection copies the user's selection to either "primary"
@@ -193,7 +179,7 @@ func (c *Cursor) Deselect(start bool) {
if start {
c.Loc = c.CurSelection[0]
} else {
c.Loc = c.CurSelection[1]
c.Loc = c.CurSelection[1].Move(-1, c.buf)
}
c.ResetSelection()
c.StoreVisualX()
@@ -410,26 +396,13 @@ func (c *Cursor) SelectTo(loc Loc) {
// WordRight moves the cursor one word to the right
func (c *Cursor) WordRight() {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.Right()
return
}
for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.Right()
return
}
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()
for util.IsWordChar(c.RuneUnder(c.X)) {
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
func (c *Cursor) WordLeft() {
if c.X == 0 {
c.Left()
return
}
c.Left()
for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == 0 {
@@ -452,17 +421,6 @@ func (c *Cursor) WordLeft() {
}
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()
for util.IsWordChar(c.RuneUnder(c.X)) {
if c.X == 0 {
@@ -473,144 +431,28 @@ func (c *Cursor) WordLeft() {
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
func (c *Cursor) RuneUnder(x int) rune {
line := c.buf.LineCharacters(c.Y)
if len(line) == 0 || x >= len(line) {
line := c.buf.LineBytes(c.Y)
if len(line) == 0 || x >= util.CharacterCount(line) {
return '\n'
} else if x < 0 {
x = 0
}
return line[x].combc[0]
i := 0
for len(line) > 0 {
r, _, size := util.DecodeCharacter(line)
line = line[size:]
if i == x {
return r
}
i++
}
return '\n'
}
func (c *Cursor) StoreVisualX() {
c.LastVisualX = c.GetVisualX(false)
c.LastWrappedVisualX = c.GetVisualX(true)
c.LastVisualX = c.GetVisualX()
}

View File

@@ -4,11 +4,11 @@ import (
"bytes"
"time"
"github.com/micro-editor/micro/v2/internal/config"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
luar "layeh.com/gopher-luar"
)
@@ -104,11 +104,7 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1])
c.Relocate()
c.StoreVisualX()
}
if useUndo {
eh.updateTrailingWs(t)
c.LastVisualX = c.GetVisualX()
}
}
@@ -241,7 +237,7 @@ func (eh *EventHandler) Execute(t *TextEvent) {
}
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 {
screen.TermMessage(err)
}
@@ -253,11 +249,11 @@ func (eh *EventHandler) Execute(t *TextEvent) {
ExecuteTextEvent(t, eh.buf)
}
// Undo the first event in the undo stack. Returns false if the stack is empty.
func (eh *EventHandler) Undo() bool {
// Undo the first event in the undo stack
func (eh *EventHandler) Undo() {
t := eh.UndoStack.Peek()
if t == nil {
return false
return
}
startTime := t.Time.UnixNano() / int64(time.Millisecond)
@@ -266,16 +262,15 @@ func (eh *EventHandler) Undo() bool {
for {
t = eh.UndoStack.Peek()
if t == nil {
break
return
}
if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
break
return
}
eh.UndoOneEvent()
}
return true
}
// UndoOneEvent undoes one event
@@ -291,20 +286,23 @@ func (eh *EventHandler) UndoOneEvent() {
eh.UndoTextEvent(t)
// Set the cursor in the right place
if t.C.Num >= 0 && t.C.Num < len(eh.cursors) {
eh.cursors[t.C.Num].Goto(t.C)
eh.cursors[t.C.Num].NewTrailingWsY = t.C.NewTrailingWsY
teCursor := t.C
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
t.C = *eh.cursors[teCursor.Num]
eh.cursors[teCursor.Num].Goto(teCursor)
} else {
teCursor.Num = -1
}
// Push it to the redo stack
eh.RedoStack.Push(t)
}
// Redo the first event in the redo stack. Returns false if the stack is empty.
func (eh *EventHandler) Redo() bool {
// Redo the first event in the redo stack
func (eh *EventHandler) Redo() {
t := eh.RedoStack.Peek()
if t == nil {
return false
return
}
startTime := t.Time.UnixNano() / int64(time.Millisecond)
@@ -313,16 +311,15 @@ func (eh *EventHandler) Redo() bool {
for {
t = eh.RedoStack.Peek()
if t == nil {
break
return
}
if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
break
return
}
eh.RedoOneEvent()
}
return true
}
// RedoOneEvent redoes one event
@@ -332,9 +329,12 @@ func (eh *EventHandler) RedoOneEvent() {
return
}
if t.C.Num >= 0 && t.C.Num < len(eh.cursors) {
eh.cursors[t.C.Num].Goto(t.C)
eh.cursors[t.C.Num].NewTrailingWsY = t.C.NewTrailingWsY
teCursor := t.C
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
t.C = *eh.cursors[teCursor.Num]
eh.cursors[teCursor.Num].Goto(teCursor)
} else {
teCursor.Num = -1
}
// Modifies the text event
@@ -342,58 +342,3 @@ func (eh *EventHandler) RedoOneEvent() {
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

@@ -6,10 +6,32 @@ import (
"io"
"sync"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/pkg/highlight"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/pkg/highlight"
)
// Finds the byte index of the nth rune in a byte slice
func runeToByteIndex(n int, txt []byte) int {
if n == 0 {
return 0
}
count := 0
i := 0
for len(txt) > 0 {
_, _, size := util.DecodeCharacter(txt)
txt = txt[size:]
count += size
i++
if i == n {
break
}
}
return count
}
// A searchState contains the search match info for a single line
type searchState struct {
search string
@@ -19,18 +41,15 @@ type searchState struct {
done bool
}
type Character struct {
combc []rune
}
// A Line contains the slice of runes 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
type Line struct {
runes []Character
data []byte
state highlight.State
match highlight.LineMatch
lock sync.Mutex
state highlight.State
match highlight.LineMatch
rehighlight bool
lock sync.Mutex
// The search states for the line, used for highlighting of search matches,
// separately from the syntax highlighting.
@@ -41,24 +60,6 @@ type Line struct {
search map[*Buffer]*searchState
}
// data returns the line as byte slice
func (l Line) data() []byte {
var runes []rune
for _, r := range l.runes {
runes = append(runes, r.combc[0:]...)
}
return []byte(string(runes))
}
// String returns the line as string
func (l Line) String() string {
var runes []rune
for _, r := range l.runes {
runes = append(runes, r.combc[0:]...)
}
return string(runes)
}
const (
// Line ending file formats
FFAuto = 0 // Autodetect format
@@ -74,7 +75,6 @@ type LineArray struct {
lines []Line
Endings FileFormat
initsize uint64
lock sync.Mutex
}
// Append efficiently appends lines together
@@ -94,7 +94,7 @@ func Append(slice []Line, data ...Line) []Line {
return slice
}
// NewLineArray returns a new line array from an array of runes
// NewLineArray returns a new line array from an array of bytes
func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray {
la := new(LineArray)
@@ -116,12 +116,12 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
dlen := len(data)
if dlen > 1 && data[dlen-2] == '\r' {
data = append(data[:dlen-2], '\n')
if la.Endings == FFAuto {
if endings == FFAuto {
la.Endings = FFDos
}
dlen = len(data)
} else if dlen > 0 {
if la.Endings == FFAuto {
if endings == FFAuto {
la.Endings = FFUnix
}
}
@@ -144,33 +144,23 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
loaded += dlen
}
var runes []Character
if err != nil {
if err == io.EOF {
for len(data) > 0 {
combc, s := util.DecodeCombinedCharacter(data)
runes = append(runes, Character{combc})
data = data[s:]
}
la.lines = Append(la.lines, Line{
runes: runes,
state: nil,
match: nil,
data: data,
state: nil,
match: nil,
rehighlight: false,
})
}
// Last line was read
break
} else {
data = data[:dlen-1]
for len(data) > 0 {
combc, s := util.DecodeCombinedCharacter(data)
runes = append(runes, Character{combc})
data = data[s:]
}
la.lines = Append(la.lines, Line{
runes: runes,
state: nil,
match: nil,
data: data[:dlen-1],
state: nil,
match: nil,
rehighlight: false,
})
}
n++
@@ -186,7 +176,7 @@ func (la *LineArray) Bytes() []byte {
// initsize should provide a good estimate
b.Grow(int(la.initsize + 4096))
for i, l := range la.lines {
b.Write(l.data())
b.Write(l.data)
if i != len(la.lines)-1 {
if la.Endings == FFDos {
b.WriteByte('\r')
@@ -200,99 +190,72 @@ func (la *LineArray) Bytes() []byte {
// newlineBelow adds a newline below the given line number
func (la *LineArray) newlineBelow(y int) {
la.lines = append(la.lines, Line{
runes: []Character{},
state: nil,
match: nil,
data: []byte{' '},
state: nil,
match: nil,
rehighlight: false,
})
copy(la.lines[y+2:], la.lines[y+1:])
la.lines[y+1] = Line{
runes: []Character{},
state: la.lines[y].state,
match: nil,
data: []byte{},
state: la.lines[y].state,
match: nil,
rehighlight: false,
}
}
// Inserts a byte array at a given location
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
for i := 0; i < len(value); i++ {
if value[i] == '\n' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') {
la.split(Loc{x, y})
x = 0
y++
var runes []Character
for len(value) > 0 {
combc, s := util.DecodeCombinedCharacter(value)
runes = append(runes, Character{combc})
value = value[s:]
}
x, y := util.Min(pos.X, len(la.lines[pos.Y].runes)), pos.Y
start := -1
outer:
for i, r := range runes {
for j := 0; j < len(r.combc); j++ {
if r.combc[j] == '\n' || (r.combc[j] == '\r' && i < len(runes)-1 && r.combc[j+1] == '\n') {
la.split(Loc{x, y})
if i > 0 && start < len(runes) && start < i {
if start < 0 {
start = 0
}
la.insertRunes(Loc{x, y}, runes[start:i])
}
x = 0
y++
if r.combc[j] == '\r' {
i++
}
if i+1 <= len(runes) {
start = i + 1
}
continue outer
if value[i] == '\r' {
i++
}
continue
}
}
if start < 0 {
la.insertRunes(Loc{x, y}, runes)
} else if start < len(runes) {
la.insertRunes(Loc{x, y}, runes[start:])
la.insertByte(Loc{x, y}, value[i])
x++
}
}
// Inserts a rune array at a given location
func (la *LineArray) insertRunes(pos Loc, runes []Character) {
la.lines[pos.Y].runes = append(la.lines[pos.Y].runes, runes...)
copy(la.lines[pos.Y].runes[pos.X+len(runes):], la.lines[pos.Y].runes[pos.X:])
copy(la.lines[pos.Y].runes[pos.X:], runes)
// InsertByte inserts a byte at a given location
func (la *LineArray) insertByte(pos Loc, value byte) {
la.lines[pos.Y].data = append(la.lines[pos.Y].data, 0)
copy(la.lines[pos.Y].data[pos.X+1:], la.lines[pos.Y].data[pos.X:])
la.lines[pos.Y].data[pos.X] = value
}
// joinLines joins the two lines a and b
func (la *LineArray) joinLines(a, b int) {
la.insertRunes(Loc{len(la.lines[a].runes), a}, la.lines[b].runes)
la.insert(Loc{len(la.lines[a].data), a}, la.lines[b].data)
la.deleteLine(b)
}
// split splits a line at a given position
func (la *LineArray) split(pos Loc) {
la.newlineBelow(pos.Y)
la.insertRunes(Loc{0, pos.Y + 1}, la.lines[pos.Y].runes[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].state = nil
la.lines[pos.Y].match = nil
la.lines[pos.Y+1].match = nil
la.lines[pos.Y].rehighlight = true
la.deleteToEnd(Loc{pos.X, pos.Y})
}
// removes from start to end
func (la *LineArray) remove(start, end Loc) []byte {
la.lock.Lock()
defer la.lock.Unlock()
sub := la.Substr(start, end)
startX := util.Min(start.X, len(la.lines[start.Y].runes))
endX := util.Min(end.X, len(la.lines[end.Y].runes))
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
if start.Y == end.Y {
la.lines[start.Y].runes = append(la.lines[start.Y].runes[:startX], la.lines[start.Y].runes[endX:]...)
la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
} else {
la.deleteLines(start.Y+1, end.Y-1)
la.deleteToEnd(Loc{startX, start.Y})
@@ -304,12 +267,12 @@ func (la *LineArray) remove(start, end Loc) []byte {
// deleteToEnd deletes from the end of a line to the position
func (la *LineArray) deleteToEnd(pos Loc) {
la.lines[pos.Y].runes = la.lines[pos.Y].runes[:pos.X]
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X]
}
// deleteFromStart deletes from the start of a line to the position
func (la *LineArray) deleteFromStart(pos Loc) {
la.lines[pos.Y].runes = la.lines[pos.Y].runes[pos.X+1:]
la.lines[pos.Y].data = la.lines[pos.Y].data[pos.X+1:]
}
// deleteLine deletes the line number
@@ -321,37 +284,29 @@ func (la *LineArray) deleteLines(y1, y2 int) {
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
func (la *LineArray) Substr(start, end Loc) []byte {
startX := util.Min(start.X, len(la.lines[start.Y].runes))
endX := util.Min(end.X, len(la.lines[end.Y].runes))
var runes []rune
if start.Y == end.Y && startX <= endX {
for _, r := range la.lines[start.Y].runes[startX:endX] {
runes = append(runes, r.combc[0:]...)
}
return []byte(string(runes))
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
if start.Y == end.Y {
src := la.lines[start.Y].data[startX:endX]
dest := make([]byte, len(src))
copy(dest, src)
return dest
}
var str []byte
for _, r := range la.lines[start.Y].runes[startX:] {
runes = append(runes, r.combc[0:]...)
}
str = append(str, []byte(string(runes))...)
str := make([]byte, 0, len(la.lines[start.Y+1].data)*(end.Y-start.Y))
str = append(str, la.lines[start.Y].data[startX:]...)
str = append(str, '\n')
for i := start.Y + 1; i <= end.Y-1; i++ {
runes = runes[:0]
for _, r := range la.lines[i].runes {
runes = append(runes, r.combc[0:]...)
}
str = append(str, []byte(string(runes))...)
str = append(str, la.lines[i].data...)
str = append(str, '\n')
}
runes = runes[:0]
for _, r := range la.lines[end.Y].runes[:endX] {
runes = append(runes, r.combc[0:]...)
}
str = append(str, []byte(string(runes))...)
str = append(str, la.lines[end.Y].data[:endX]...)
return str
}
@@ -368,16 +323,7 @@ func (la *LineArray) Start() Loc {
// End returns the location of the last character in the buffer
func (la *LineArray) End() Loc {
numlines := len(la.lines)
return Loc{len(la.lines[numlines-1].runes), numlines - 1}
}
// LineCharacters returns line n as an array of characters
func (la *LineArray) LineCharacters(n int) []Character {
if n >= len(la.lines) || n < 0 {
return []Character{}
}
return la.lines[n].runes
return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
}
// LineBytes returns line n as an array of bytes
@@ -385,21 +331,7 @@ func (la *LineArray) LineBytes(n int) []byte {
if n >= len(la.lines) || n < 0 {
return []byte{}
}
return la.lines[n].data()
}
// LineString returns line n as an string
func (la *LineArray) LineString(n int) string {
if n >= len(la.lines) || n < 0 {
return string("")
}
var runes []rune
for _, r := range la.lines[n].runes {
runes = append(runes, r.combc[0:]...)
}
return string(runes)
return la.lines[n].data
}
// State gets the highlight state for the given line number
@@ -430,14 +362,16 @@ func (la *LineArray) Match(lineN int) highlight.LineMatch {
return la.lines[lineN].match
}
// Locks the whole LineArray
func (la *LineArray) Lock() {
la.lock.Lock()
func (la *LineArray) Rehighlight(lineN int) bool {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
return la.lines[lineN].rehighlight
}
// Unlocks the whole LineArray
func (la *LineArray) Unlock() {
la.lock.Unlock()
func (la *LineArray) SetRehighlight(lineN int, on bool) {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
la.lines[lineN].rehighlight = on
}
// SearchMatch returns true if the location `pos` is within a match
@@ -481,7 +415,7 @@ func (la *LineArray) SearchMatch(b *Buffer, pos Loc) bool {
if !s.done {
s.match = nil
start := Loc{0, lineN}
end := Loc{len(la.lines[lineN].runes), 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 {

View File

@@ -1,7 +1,7 @@
package buffer
import (
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// Loc stores a location
@@ -47,16 +47,6 @@ func (l Loc) LessEqual(b Loc) bool {
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
// Diff returns the distance between two locations
@@ -149,5 +139,10 @@ func ByteOffset(pos Loc, buf *Buffer) int {
// clamps a loc within a buffer
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
import (
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/tcell/v2"
)
type MsgType int
@@ -84,7 +84,7 @@ func (b *Buffer) ClearAllMessages() {
}
type Messager interface {
Message(msg ...any)
Message(msg ...interface{})
}
var prompt Messager

View File

@@ -2,21 +2,21 @@ package buffer
import (
"bufio"
"bytes"
"errors"
"io"
"io/fs"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strings"
"time"
"unicode"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/transform"
)
@@ -24,192 +24,57 @@ import (
// because hashing is too slow
const LargeFileThreshold = 50000
type wrappedFile struct {
name string
writeCloser io.WriteCloser
withSudo bool
screenb bool
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 backupRequest
func init() {
// Both saveRequestChan and backupRequestChan need to be non-buffered
// so the save/backup goroutine receives both save and backup requests
// in the same order the main goroutine sends them.
saveRequestChan = make(chan saveRequest)
backupRequestChan = make(chan backupRequest)
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 br := <-backupRequestChan:
handleBackupRequest(br)
case <-backupTicker.C:
periodicBackup()
}
}
}()
}
func openFile(name string, withSudo bool) (wrappedFile, error) {
var err error
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
// the supplied function with the file as io.Writer object, also making sure the file is
// closed afterwards.
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
var writeCloser io.WriteCloser
var screenb bool
var cmd *exec.Cmd
var sigChan chan os.Signal
if withSudo {
conv := "notrunc"
// TODO: both platforms do not support dd with conv=fsync yet
if !(runtime.GOOS == "illumos" || runtime.GOOS == "netbsd") {
conv += ",fsync"
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
if writeCloser, err = cmd.StdinPipe(); err != nil {
return
}
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "conv="+conv, "of="+name)
writeCloser, err = cmd.StdinPipe()
if err != nil {
return wrappedFile{}, err
}
sigChan = make(chan os.Signal, 1)
signal.Reset(os.Interrupt)
signal.Notify(sigChan, os.Interrupt)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
cmd.Process.Kill()
}()
screenb = screen.TempFini()
// need to start the process now, otherwise when we flush the file
// contents to its stdin it might hang because the kernel's pipe size
// is too small to handle the full file contents all at once
err = cmd.Start()
if err != nil {
screen.TempStart(screenb)
signal.Notify(util.Sigterm, os.Interrupt)
signal.Stop(sigChan)
return wrappedFile{}, err
}
} else {
writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, util.FileMode)
if err != nil {
return wrappedFile{}, err
if e := cmd.Start(); e != nil && err == nil {
return err
}
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return
}
return wrappedFile{name, writeCloser, withSudo, screenb, cmd, sigChan}, nil
}
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
err = fn(w)
w.Flush()
func (wf wrappedFile) Truncate() error {
if wf.withSudo {
// we don't need to stop the screen here, since it is still stopped
// by openFile()
// truncate might not be available on every platfom, so use dd instead
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "count=0", "of="+wf.name)
return cmd.Run()
}
return wf.writeCloser.(*os.File).Truncate(0)
}
func (wf wrappedFile) Write(b *SharedBuffer) (int, error) {
file := bufio.NewWriter(transform.NewWriter(wf.writeCloser, b.encoding.NewEncoder()))
b.Lock()
defer b.Unlock()
if len(b.lines) == 0 {
return 0, nil
if e := writeCloser.Close(); e != nil && err == nil {
err = e
}
// end of line
var eol []byte
if b.Endings == FFDos {
eol = []byte{'\r', '\n'}
} else {
eol = []byte{'\n'}
}
err := wf.Truncate()
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 {
if 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)
err := cmd.Wait()
if err != nil {
return err
}
}
return err
}
func (b *SharedBuffer) overwriteFile(name string) (int, error) {
file, err := openFile(name, false)
if err != nil {
return 0, err
screen.TempStart(screenb)
}
size, err := file.Write(b)
err2 := file.Close()
if err2 != nil && err == nil {
err = err2
}
return size, err
return
}
// Save saves the buffer to its default path
@@ -217,17 +82,9 @@ func (b *Buffer) Save() error {
return b.SaveAs(b.Path)
}
// AutoSave saves the buffer to its default path
func (b *Buffer) AutoSave() error {
if !b.Modified() {
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
func (b *Buffer) SaveAs(filename string) error {
return b.saveToFile(filename, false, false)
return b.saveToFile(filename, false)
}
func (b *Buffer) SaveWithSudo() error {
@@ -235,10 +92,10 @@ func (b *Buffer) SaveWithSudo() 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
if b.Type.Readonly {
return errors.New("Cannot save readonly buffer")
@@ -246,12 +103,16 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
if b.Type.Scratch {
return errors.New("Cannot save scratch buffer")
}
if withSudo && runtime.GOOS == "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 {
leftover := strings.TrimRightFunc(l.String(), unicode.IsSpace)
linelen := len(l.runes)
b.Remove(Loc{len(leftover), i}, Loc{linelen, i})
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
linelen := util.CharacterCount(l.data)
b.Remove(Loc{leftover, i}, Loc{linelen, i})
}
b.RelocateCursors()
@@ -264,35 +125,19 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
}
}
filename, err = util.ReplaceHome(filename)
if err != nil {
return err
}
// Update the last time this file was updated after saving
defer func() {
b.ModTime, _ = util.GetModTime(filename)
err = b.Serialize()
}()
newFile := false
fileInfo, err := os.Stat(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
}
// Removes any tilde and replaces with the absolute path to home
absFilename, _ := util.ReplaceHome(filename)
// Get the leading path to the file | "." is returned if there's no leading path provided
if dirname := filepath.Dir(absFilename); dirname != "." {
// Check if the parent dirs don't exist
if _, statErr := os.Stat(dirname); 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
if b.Settings["mkparents"].(bool) {
// Create all leading dir(s) since they don't exist
@@ -306,94 +151,60 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
}
}
saveResponseChan := make(chan saveResponse)
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)
var fileSize int
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
}
if !b.Settings["fastdirty"].(bool) {
if result.size > LargeFileThreshold {
if fileSize > LargeFileThreshold {
// For large files 'fastdirty' needs to be on
b.Settings["fastdirty"] = true
} else {
b.calcHash(&b.origHash)
calcHash(b, &b.origHash)
}
}
newPath := b.Path != filename
if newPath {
b.RemoveBackup()
}
b.Path = filename
b.AbsPath = absFilename
absPath, _ := filepath.Abs(filename)
b.AbsPath = absPath
b.isModified = false
b.UpdateModTime()
if newPath {
// need to update glob-based and filetype-based settings
b.ReloadSettings(true)
}
err = b.Serialize()
b.UpdateRules()
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 *SharedBuffer) 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)
}
}()
// Try to backup first before writing
backupName, resolveName, err := b.writeBackup(path)
if err != nil {
file.Close()
return 0, err
}
// Backup saved, so cancel pending periodic backup, if any
delete(requestedBackups, b)
b.forceKeepBackup = true
size := 0
{
// If we failed to write or close, keep the backup we made
size, err = file.Write(b)
if err != nil {
file.Close()
return 0, util.OverwriteError{err, backupName}
}
err = file.Close()
if err != nil {
return 0, util.OverwriteError{err, backupName}
}
}
b.forceKeepBackup = false
if !b.keepBackup() {
b.removeBackup(backupName, resolveName)
}
return size, err
}

View File

@@ -2,65 +2,10 @@ package buffer
import (
"regexp"
"unicode/utf8"
"github.com/micro-editor/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 != 0 {
re, err := regexp.Compile(r.String() + `\E`)
if err == nil {
// r contains \Q without closing \E
r = re
}
if padMode == padStart {
r = regexp.MustCompile(".(?:" + r.String() + ")")
} else if padMode == padEnd {
r = regexp.MustCompile("(?:" + r.String() + ").")
} else {
// 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) {
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
if start.Y > b.LinesNum()-1 {
@@ -77,19 +22,30 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
}
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 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}
end := Loc{charpos + util.RunePos(l, match[1]), i}
return [2]Loc{start, end}, true
@@ -114,39 +70,39 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
}
for i := end.Y; i >= start.Y; i-- {
charCount := util.CharacterCount(b.LineBytes(i))
from := Loc{0, i}.Clamp(start, end)
to := Loc{charCount, i}.Clamp(start, end)
l := b.LineBytes(i)
charpos := 0
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)
}
allMatches := r.FindAllIndex(l, -1)
allMatches := b.findAll(r, from, to)
if allMatches != nil {
match := allMatches[len(allMatches)-1]
return [2]Loc{match[0], match[1]}, true
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
}
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
// It returns the start and end location of the match (if found) and
// a boolean indicating if it was found
@@ -190,58 +146,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
// 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
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) {
start, end = end, start
}
charsEnd := util.CharacterCount(b.LineBytes(end.Y))
netrunes := 0
found := 0
var deltas []Delta
for i := start.Y; i <= end.Y; i++ {
l := b.LineBytes(i)
charCount := util.CharacterCount(l)
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
l := b.lines[i].data
charpos := 0
from := Loc{0, i}.Clamp(start, end)
to := Loc{charCount, i}.Clamp(start, end)
matches := b.findAll(search, from, to)
found += len(matches)
for j := len(matches) - 1; j >= 0; j-- {
// if we counted upwards, the different deltas would interfere
match := matches[j]
var newText []byte
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}})
if start.Y == end.Y && i == start.Y {
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
l = util.SliceStart(l, end.X)
}
}
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
result := []byte{}
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
result = search.Expand(result, replace, in, submatches)
}
found++
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)
return found, util.CharacterCount(b.LineBytes(end.Y)) - charsEnd
return found, netrunes
}

View File

@@ -1,15 +1,17 @@
package buffer
import (
"bytes"
"encoding/gob"
"errors"
"io"
"os"
"path/filepath"
"time"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/util"
"golang.org/x/text/encoding"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
)
// The SerializedBuffer holds the types that get serialized when a buffer is saved
@@ -29,30 +31,16 @@ func (b *Buffer) Serialize() error {
return nil
}
var buf bytes.Buffer
err := gob.NewEncoder(&buf).Encode(SerializedBuffer{
b.EventHandler,
b.GetActiveCursor().Loc,
b.ModTime,
})
if err != nil {
name := filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))
return overwriteFile(name, encoding.Nop, func(file io.Writer) error {
err := gob.NewEncoder(file).Encode(SerializedBuffer{
b.EventHandler,
b.GetActiveCursor().Loc,
b.ModTime,
})
return err
}
name, resolveName := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath)
err = util.SafeWrite(name, buf.Bytes(), true)
if err != nil {
return err
}
if resolveName != "" {
err = util.SafeWrite(resolveName, []byte(b.AbsPath), true)
if err != nil {
return err
}
}
return nil
}, false)
}
// Unserialize loads the buffer info from config.ConfigDir/buffers
@@ -62,8 +50,7 @@ func (b *Buffer) Unserialize() error {
if b.Path == "" {
return nil
}
name, _ := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath)
file, err := os.Open(name)
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
if err == nil {
defer file.Close()
var buffer SerializedBuffer

View File

@@ -1,89 +1,36 @@
package buffer
import (
"crypto/md5"
"reflect"
"github.com/micro-editor/micro/v2/internal/config"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/encoding/unicode"
luar "layeh.com/gopher-luar"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
)
func (b *Buffer) ReloadSettings(reloadFiletype bool) {
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 any) {
oldValue := b.Settings[option]
if reflect.DeepEqual(oldValue, nativeValue) {
return
}
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
b.Settings[option] = nativeValue
if option == "fastdirty" {
if !nativeValue.(bool) {
if b.Size() > LargeFileThreshold {
b.Settings["fastdirty"] = true
} else {
if !b.isModified {
b.calcHash(&b.origHash)
} else {
// prevent using an old stale origHash value
b.origHash = [md5.Size]byte{}
if !b.Modified() {
e := calcHash(b, &b.origHash)
if e == ErrFileTooLarge {
b.Settings["fastdirty"] = false
}
}
}
} else if option == "statusline" {
screen.Redraw()
} else if option == "filetype" {
b.ReloadSettings(false)
config.InitRuntimeFiles()
err := config.ReadSettings()
if err != nil {
screen.TermMessage(err)
}
err = config.InitGlobalSettings()
if err != nil {
screen.TermMessage(err)
}
config.InitLocalSettings(b.Settings, b.Path)
b.UpdateRules()
} else if option == "fileformat" {
switch b.Settings["fileformat"].(string) {
case "unix":
@@ -91,7 +38,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue any) {
case "dos":
b.Endings = FFDos
}
b.setModified()
b.isModified = true
} else if option == "syntax" {
if !nativeValue.(bool) {
b.ClearMatches()
@@ -99,13 +46,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue any) {
b.UpdateRules()
}
} 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.setModified()
b.isModified = true
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
b.Type.Readonly = nativeValue.(bool)
} else if option == "hlsearch" {
@@ -114,38 +55,12 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue any) {
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)
}
func (b *Buffer) SetOptionNative(option string, nativeValue any) error {
if err := config.OptionIsValid(option, nativeValue); err != nil {
return err
if b.OptionCallback != nil {
b.OptionCallback(option, nativeValue)
}
b.DoSetOptionNative(option, nativeValue)
b.LocalSettings[option] = true
return nil
}
@@ -155,22 +70,10 @@ func (b *Buffer) SetOption(option, value string) error {
return config.ErrInvalidOption
}
nativeValue, err := config.GetNativeValue(option, value)
nativeValue, err := config.GetNativeValue(option, b.Settings[option], value)
if err != nil {
return err
}
return b.SetOptionNative(option, nativeValue)
}
func (b *Buffer) doCallbacks(option string, oldValue any, newValue any) {
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

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

View File

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

View File

@@ -6,7 +6,7 @@ import (
"strconv"
"strings"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/tcell/v2"
)
// DefStyle is Micro's default style
@@ -52,63 +52,43 @@ func InitColorscheme() error {
Colorscheme = make(map[string]tcell.Style)
DefStyle = tcell.StyleDefault
c, err := LoadDefaultColorscheme()
if err == nil {
Colorscheme = c
} else {
// The colorscheme setting seems broken (maybe because we have not validated
// it earlier, see comment in verifySetting()). So reset it to the default
// colorscheme and try again.
GlobalSettings["colorscheme"] = DefaultGlobalOnlySettings["colorscheme"]
if c, err2 := LoadDefaultColorscheme(); err2 == nil {
Colorscheme = c
}
}
return err
return LoadDefaultColorscheme()
}
// LoadDefaultColorscheme loads the default colorscheme from $(ConfigDir)/colorschemes
func LoadDefaultColorscheme() (map[string]tcell.Style, error) {
var parsedColorschemes []string
return LoadColorscheme(GlobalSettings["colorscheme"].(string), &parsedColorschemes)
func LoadDefaultColorscheme() error {
return LoadColorscheme(GlobalSettings["colorscheme"].(string))
}
// LoadColorscheme loads the given colorscheme from a directory
func LoadColorscheme(colorschemeName string, parsedColorschemes *[]string) (map[string]tcell.Style, error) {
c := make(map[string]tcell.Style)
func LoadColorscheme(colorschemeName string) error {
file := FindRuntimeFile(RTColorscheme, colorschemeName)
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 {
return c, errors.New("Error loading colorscheme: " + err.Error())
return errors.New("Error loading colorscheme: " + err.Error())
} else {
var err error
c, err = ParseColorscheme(file.Name(), string(data), parsedColorschemes)
Colorscheme, err = ParseColorscheme(string(data))
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
// 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
// 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
colorParser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
includeParser := regexp.MustCompile(`include\s+"(.*)"`)
parser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
lines := strings.Split(text, "\n")
c := make(map[string]tcell.Style)
if parsedColorschemes != nil {
*parsedColorschemes = append(*parsedColorschemes, name)
}
lineLoop:
for _, line := range lines {
if strings.TrimSpace(line) == "" ||
strings.TrimSpace(line)[0] == '#' {
@@ -116,30 +96,7 @@ lineLoop:
continue
}
matches := includeParser.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))
matches := parser.FindSubmatch([]byte(line))
if len(matches) == 3 {
link := string(matches[1])
colors := string(matches[2])

View File

@@ -3,8 +3,8 @@ package config
import (
"testing"
"github.com/micro-editor/tcell/v2"
"github.com/stretchr/testify/assert"
"github.com/zyedidia/tcell/v2"
)
func TestSimpleStringToStyle(t *testing.T) {
@@ -65,7 +65,7 @@ color-link constant "#AE81FF,#282828"
color-link constant.string "#E6DB74,#282828"
color-link constant.string.char "#BDE6AD,#282828"`
c, err := ParseColorscheme("testColorscheme", testColorscheme, nil)
c, err := ParseColorscheme(testColorscheme)
assert.Nil(t, err)
fg, bg, _ := c["comment"].Decompose()

View File

@@ -4,8 +4,8 @@ import (
"errors"
"log"
ulua "github.com/micro-editor/micro/v2/internal/lua"
lua "github.com/yuin/gopher-lua"
ulua "github.com/zyedidia/micro/v2/internal/lua"
)
// ErrNoSuchFunction is returned when Call is executed on a function that does not exist
@@ -28,7 +28,7 @@ func LoadAllPlugins() error {
func RunPluginFn(fn string, args ...lua.LValue) error {
var reterr error
for _, p := range Plugins {
if !p.IsLoaded() {
if !p.IsEnabled() {
continue
}
_, 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
// false if any one of them returned false
// also returns an error if any of the plugins had an error
func RunPluginFnBool(settings map[string]any, fn string, args ...lua.LValue) (bool, error) {
func RunPluginFnBool(fn string, args ...lua.LValue) (bool, error) {
var reterr error
retbool := true
for _, p := range Plugins {
if !p.IsLoaded() || (settings != nil && settings[p.Name] == false) {
if !p.IsEnabled() {
continue
}
val, err := p.Call(fn, args...)
@@ -71,11 +71,11 @@ type Plugin struct {
Info *PluginInfo // json file containing info
Srcs []RuntimeFile // lua files
Loaded bool
Builtin bool
Default bool // pre-installed plugin
}
// IsLoaded returns if a plugin is enabled
func (p *Plugin) IsLoaded() bool {
// IsEnabled returns if a plugin is enabled
func (p *Plugin) IsEnabled() bool {
if v, ok := GlobalSettings[p.Name]; ok {
return v.(bool) && p.Loaded
}
@@ -101,7 +101,7 @@ func (p *Plugin) Load() error {
}
}
p.Loaded = true
RegisterCommonOption(p.Name, true)
RegisterGlobalOption(p.Name, true)
return nil
}
@@ -133,7 +133,7 @@ func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
func FindPlugin(name string) *Plugin {
var pl *Plugin
for _, p := range Plugins {
if !p.IsLoaded() {
if !p.IsEnabled() {
continue
}
if p.Name == name {
@@ -143,3 +143,15 @@ func FindPlugin(name string) *Plugin {
}
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"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
@@ -13,10 +14,10 @@ import (
"sync"
"github.com/blang/semver"
"github.com/micro-editor/json5"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/util"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/json5"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/util"
)
var (
@@ -42,7 +43,6 @@ type PluginPackage struct {
Author string
Tags []string
Versions PluginVersions
Builtin bool
}
// PluginPackages is a list of PluginPackage instances.
@@ -76,9 +76,6 @@ func (pp *PluginPackage) String() string {
buf := new(bytes.Buffer)
buf.WriteString("Plugin: ")
buf.WriteString(pp.Name)
if pp.Builtin {
buf.WriteString(" (built-in)")
}
buf.WriteRune('\n')
if pp.Author != "" {
buf.WriteString("Author: ")
@@ -228,7 +225,7 @@ func GetAllPluginPackages(out io.Writer) PluginPackages {
if strs, ok := data.([]string); ok {
return strs
}
if ifs, ok := data.([]any); ok {
if ifs, ok := data.([]interface{}); ok {
result := make([]string, len(ifs))
for i, urlIf := range ifs {
if url, ok := urlIf.(string); ok {
@@ -338,7 +335,7 @@ func isUnknownCoreVersion() bool {
return err != nil
}
func newStaticPluginVersion(name, version string, builtin bool) *PluginVersion {
func newStaticPluginVersion(name, version string) *PluginVersion {
vers, err := semver.ParseTolerant(version)
if err != nil {
@@ -347,8 +344,7 @@ func newStaticPluginVersion(name, version string, builtin bool) *PluginVersion {
}
}
pl := &PluginPackage{
Name: name,
Builtin: builtin,
Name: name,
}
pv := &PluginVersion{
pack: pl,
@@ -363,15 +359,15 @@ func newStaticPluginVersion(name, version string, builtin bool) *PluginVersion {
func GetInstalledVersions(withCore bool) PluginVersions {
result := PluginVersions{}
if withCore {
result = append(result, newStaticPluginVersion(CorePluginName, util.Version, true))
result = append(result, newStaticPluginVersion(CorePluginName, util.Version))
}
for _, p := range Plugins {
if !p.IsLoaded() {
if !p.IsEnabled() {
continue
}
version := GetInstalledPluginVersion(p.Name)
if pv := newStaticPluginVersion(p.Name, version, p.Builtin); pv != nil {
if pv := newStaticPluginVersion(p.Name, version); pv != nil {
result = append(result, pv)
}
}
@@ -400,7 +396,7 @@ func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
return err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
@@ -576,7 +572,7 @@ func (pv PluginVersions) install(out io.Writer) {
// UninstallPlugin deletes the plugin folder of the given plugin
func UninstallPlugin(out io.Writer, name string) {
for _, p := range Plugins {
if !p.IsLoaded() {
if !p.IsEnabled() {
continue
}
if p.Name == name {
@@ -609,7 +605,7 @@ func UpdatePlugins(out io.Writer, plugins []string) {
// if no plugins are specified, update all installed plugins.
if len(plugins) == 0 {
for _, p := range Plugins {
if !p.IsLoaded() || p.Builtin || p.Name == "initlua" {
if !p.IsEnabled() || p.Default {
continue
}
plugins = append(plugins, p.Name)
@@ -618,7 +614,7 @@ func UpdatePlugins(out io.Writer, plugins []string) {
fmt.Fprintln(out, "Checking for plugin updates")
microVersion := PluginVersions{
newStaticPluginVersion(CorePluginName, util.Version, true),
newStaticPluginVersion(CorePluginName, util.Version),
}
var updates = make(PluginDependencies, 0)
@@ -650,14 +646,14 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
if pp == nil {
fmt.Fprintln(out, "Unknown plugin \""+plugin+"\"")
} else if err := pp.IsInstallable(out); err != nil {
fmt.Fprintln(out, "Error installing "+plugin+": ", err)
fmt.Fprintln(out, "Error installing ", plugin, ": ", err)
} else {
for _, installed := range installedVersions {
if pp.Name == installed.Pack().Name {
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
fmt.Fprintln(out, pp.Name, "is already installed but out-of-date: use 'plugin update "+pp.Name+"' to update")
fmt.Fprintln(out, pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
} else {
fmt.Fprintln(out, pp.Name, "is already installed")
fmt.Fprintln(out, pp.Name, " is already installed")
}
}
}
@@ -668,14 +664,10 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
case "remove":
removed := ""
for _, plugin := range args {
if plugin == "initlua" {
fmt.Fprintln(out, "initlua cannot be removed, but can be disabled via settings.")
continue
}
// check if the plugin exists.
for _, p := range Plugins {
if p.Name == plugin && p.Builtin {
fmt.Fprintln(out, p.Name, "is a built-in plugin which cannot be removed, but can be disabled via settings.")
if p.Name == plugin && p.Default {
fmt.Fprintln(out, "Default plugins cannot be removed, but can be disabled via settings.")
continue
}
if p.Name == plugin {
@@ -685,9 +677,8 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
}
}
}
removed = strings.TrimSpace(removed)
if removed != "" {
fmt.Fprintln(out, "Removed", removed)
fmt.Fprintln(out, "Removed ", removed)
} else {
fmt.Fprintln(out, "No plugins removed")
}
@@ -697,17 +688,11 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
plugins := GetInstalledVersions(false)
fmt.Fprintln(out, "The following plugins are currently installed:")
for _, p := range plugins {
if p.Pack().Name == "initlua" {
fmt.Fprintf(out, "%s\n", "initlua")
} else if p.Pack().Builtin {
fmt.Fprintf(out, "%s (built-in)\n", p.Pack().Name)
} else {
fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
}
fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
}
case "search":
plugins := SearchPlugin(out, args)
fmt.Fprintln(out, len(plugins), "plugins found")
fmt.Fprintln(out, len(plugins), " plugins found")
for _, p := range plugins {
fmt.Fprintln(out, "----------------")
fmt.Fprintln(out, p.String())

View File

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

View File

@@ -2,13 +2,15 @@ package config
import (
"errors"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"regexp"
"strings"
rt "github.com/micro-editor/micro/v2/runtime"
rt "github.com/zyedidia/micro/v2/runtime"
)
const (
@@ -38,10 +40,6 @@ var allFiles [][]RuntimeFile
var realFiles [][]RuntimeFile
func init() {
initRuntimeVars()
}
func initRuntimeVars() {
allFiles = make([][]RuntimeFile, NumTypes)
realFiles = make([][]RuntimeFile, NumTypes)
}
@@ -60,6 +58,12 @@ type realFile string
// some asset file
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
type memoryFile struct {
name string
@@ -79,18 +83,22 @@ func (rf realFile) Name() string {
}
func (rf realFile) Data() ([]byte, error) {
return os.ReadFile(string(rf))
return ioutil.ReadFile(string(rf))
}
func (af assetFile) Name() string {
fn := filepath.Base(string(af))
return fn[:len(fn)-len(filepath.Ext(fn))]
fn := path.Base(string(af))
return fn[:len(fn)-len(path.Ext(fn))]
}
func (af assetFile) Data() ([]byte, error) {
return rt.Asset(string(af))
}
func (nf namedFile) Name() string {
return nf.name
}
// AddRuntimeFile registers a file for the given filetype
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
allFiles[fileType] = append(allFiles[fileType], file)
@@ -105,7 +113,7 @@ func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
// AddRuntimeFilesFromDirectory registers each file from the given directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
files, _ := os.ReadDir(directory)
files, _ := ioutil.ReadDir(directory)
for _, f := range files {
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
fullPath := filepath.Join(directory, f.Name())
@@ -121,17 +129,9 @@ func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
if err != nil {
return
}
assetLoop:
for _, f := range files {
if ok, _ := filepath.Match(pattern, f); ok {
af := assetFile(filepath.Join(directory, f))
for _, rf := range realFiles[fileType] {
if af.Name() == rf.Name() {
continue assetLoop
}
}
AddRuntimeFile(fileType, af)
if ok, _ := path.Match(pattern, f); ok {
AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
}
}
}
@@ -158,49 +158,37 @@ func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
return realFiles[fileType]
}
// InitRuntimeFiles initializes all assets files and the config directory.
// If `user` is false, InitRuntimeFiles ignores the config directory and
// initializes asset files only.
func InitRuntimeFiles(user bool) {
// InitRuntimeFiles initializes all assets file and the config directory
func InitRuntimeFiles() {
add := func(fileType RTFiletype, dir, pattern string) {
if user {
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
}
AddRuntimeFilesFromAssets(fileType, filepath.Join("runtime", dir), pattern)
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
}
initRuntimeVars()
add(RTColorscheme, "colorschemes", "*.micro")
add(RTSyntax, "syntax", "*.yaml")
add(RTSyntaxHeader, "syntax", "*.hdr")
add(RTHelp, "help", "*.md")
}
// InitPlugins initializes the plugins
func InitPlugins() {
Plugins = Plugins[:0]
initlua := filepath.Join(ConfigDir, "init.lua")
if _, err := os.Stat(initlua); !os.IsNotExist(err) {
p := new(Plugin)
p.Name = "initlua"
p.DirName = "initlua"
p.Srcs = append(p.Srcs, realFile(initlua))
p.Builtin = false
Plugins = append(Plugins, p)
}
// Search ConfigDir for plugin-scripts
plugdir := filepath.Join(ConfigDir, "plug")
files, _ := os.ReadDir(plugdir)
files, _ := ioutil.ReadDir(plugdir)
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
for _, d := range files {
plugpath := filepath.Join(plugdir, d.Name())
if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
srcs, _ := os.ReadDir(plugpath)
srcs, _ := ioutil.ReadDir(plugpath)
p := new(Plugin)
p.Name = d.Name()
p.DirName = d.Name()
@@ -208,7 +196,7 @@ func InitPlugins() {
if strings.HasSuffix(f.Name(), ".lua") {
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
} else if strings.HasSuffix(f.Name(), ".json") {
data, err := os.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
if err != nil {
continue
}
@@ -230,20 +218,12 @@ func InitPlugins() {
plugdir = filepath.Join("runtime", "plugins")
if files, err := rt.AssetDir(plugdir); err == nil {
outer:
for _, d := range files {
for _, p := range Plugins {
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.Name = d
p.DirName = d
p.Builtin = true
p.Default = true
for _, f := range srcs {
if strings.HasSuffix(f, ".lua") {
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
@@ -300,7 +280,7 @@ func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) e
if _, err := os.Stat(fullpath); err == nil {
AddRealRuntimeFile(filetype, realFile(fullpath))
} else {
fullpath = filepath.Join("runtime", "plugins", pldir, filePath)
fullpath = path.Join("runtime", "plugins", pldir, filePath)
AddRuntimeFile(filetype, assetFile(fullpath))
}
return nil
@@ -317,7 +297,7 @@ func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, dire
if _, err := os.Stat(fullpath); err == nil {
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
} else {
fullpath = filepath.Join("runtime", "plugins", pldir, directory)
fullpath = path.Join("runtime", "plugins", pldir, directory)
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
}
return nil

View File

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

View File

@@ -4,235 +4,60 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"github.com/micro-editor/json5"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/glob"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding/htmlindex"
)
type optionValidator func(string, any) 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]any{
"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": " ", // Deprecated
"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),
"showchars": "",
"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]any{
"autosave": float64(0),
"clipboard": "external",
"colorscheme": "default",
"divchars": "|-",
"divreverse": true,
"fakecursor": defaultFakeCursor(),
"helpsplit": "hsplit",
"infobar": true,
"keymenu": false,
"lockbindings": 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",
}
type optionValidator func(string, interface{}) error
var (
ErrInvalidOption = errors.New("Invalid option")
ErrInvalidValue = errors.New("Invalid value")
ErrOptNotToggleable = errors.New("Option not toggleable")
ErrInvalidOption = errors.New("Invalid option")
ErrInvalidValue = errors.New("Invalid value")
// The options that the user can set
GlobalSettings map[string]any
GlobalSettings map[string]interface{}
// This is the raw parsed json
parsedSettings map[string]any
parsedSettings map[string]interface{}
settingsParseError bool
// ModifiedSettings is a map of settings which should be written to disk
// because they have been modified by the user in this session
ModifiedSettings map[string]bool
// 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() {
ModifiedSettings = make(map[string]bool)
VolatileSettings = make(map[string]bool)
parsedSettings = make(map[string]interface{})
}
func validateParsedSettings() error {
var err error
defaults := DefaultAllSettings()
for k, v := range parsedSettings {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if strings.HasPrefix(k, "ft:") {
for k1, v1 := range v.(map[string]any) {
if _, ok := defaults[k1]; ok {
if e := verifySetting(k1, v1, defaults[k1]); e != nil {
err = e
parsedSettings[k].(map[string]any)[k1] = defaults[k1]
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]any) {
if _, ok := defaults[k1]; ok {
if e := verifySetting(k1, v1, defaults[k1]); e != nil {
err = e
parsedSettings[k].(map[string]any)[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
// Options with validators
var optionValidators = map[string]optionValidator{
"autosave": validateNonNegativeValue,
"clipboard": validateClipboard,
"tabsize": validatePositiveValue,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
"colorscheme": validateColorscheme,
"colorcolumn": validateNonNegativeValue,
"fileformat": validateLineEnding,
"encoding": validateEncoding,
"multiopen": validateMultiOpen,
}
func ReadSettings() error {
parsedSettings = make(map[string]any)
filename := filepath.Join(ConfigDir, "settings.json")
if _, e := os.Stat(filename); e == nil {
input, err := os.ReadFile(filename)
input, err := ioutil.ReadFile(filename)
if err != nil {
settingsParseError = true
return errors.New("Error reading settings.json file: " + err.Error())
@@ -244,97 +69,89 @@ func ReadSettings() error {
settingsParseError = true
return errors.New("Error reading settings.json: " + err.Error())
}
err = validateParsedSettings()
if err != nil {
return err
// check if autosave is a boolean and convert it to float if so
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
}
func ParsedSettings() map[string]any {
s := make(map[string]any)
for k, v := range parsedSettings {
s[k] = v
}
return s
}
func verifySetting(option string, value any, def any) error {
var interfaceArr []any
valType := reflect.TypeOf(value)
defType := reflect.TypeOf(def)
assignable := false
func verifySetting(option string, value reflect.Type, def reflect.Type) bool {
var interfaceArr []interface{}
switch option {
case "pluginrepos", "pluginchannels":
assignable = valType.AssignableTo(reflect.TypeOf(interfaceArr))
return value.AssignableTo(reflect.TypeOf(interfaceArr))
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 option == "colorscheme" {
// Plugins are not initialized yet, so do not verify if the colorscheme
// exists yet, since the colorscheme may be added by a plugin later.
return nil
}
if err := OptionIsValid(option, value); err != nil {
return err
}
return nil
}
// InitGlobalSettings initializes the options map and sets all options to their default values
// Must be called after ReadSettings
func InitGlobalSettings() error {
var err error
GlobalSettings = DefaultAllSettings()
GlobalSettings = DefaultGlobalSettings()
for k, v := range parsedSettings {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if _, ok := GlobalSettings[k]; ok && !verifySetting(k, reflect.TypeOf(v), reflect.TypeOf(GlobalSettings[k])) {
err = 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
}
}
return err
}
// UpdatePathGlobLocals scans the already parsed settings and sets the options locally
// based on whether the path matches a glob
// InitLocalSettings scans the json in settings.json and sets the options locally based
// on whether the filetype or path matches ft or glob local settings
// Must be called after ReadSettings
func UpdatePathGlobLocals(settings map[string]any, path string) {
func InitLocalSettings(settings map[string]interface{}, path string) error {
var parseError error
for k, v := range parsedSettings {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && !strings.HasPrefix(k, "ft:") {
g, _ := glob.Compile(k)
if g.MatchString(path) {
for k1, v1 := range v.(map[string]any) {
settings[k1] = v1
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if strings.HasPrefix(k, "ft:") {
if settings["filetype"].(string) == k[3:] {
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
parseError = 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
// based on whether the filetype matches to "ft:"
// Must be called after ReadSettings
func UpdateFileTypeLocals(settings map[string]any, filetype string) {
for k, v := range parsedSettings {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && strings.HasPrefix(k, "ft:") {
if filetype == k[3:] {
for k1, v1 := range v.(map[string]any) {
if k1 != "filetype" {
if g.MatchString(path) {
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
parseError = 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
}
}
}
}
}
return parseError
}
// WriteSettings writes the settings to the specified filename as JSON
@@ -349,14 +166,13 @@ func WriteSettings(filename string) error {
var err error
if _, e := os.Stat(ConfigDir); e == nil {
defaults := DefaultAllSettings()
defaults := DefaultGlobalSettings()
// remove any options froms parsedSettings that have since been marked as default
for k, v := range parsedSettings {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
cur, okcur := GlobalSettings[k]
_, vol := VolatileSettings[k]
if def, ok := defaults[k]; ok && okcur && !vol && reflect.DeepEqual(cur, def) {
if def, ok := defaults[k]; ok && okcur && reflect.DeepEqual(cur, def) {
delete(parsedSettings, k)
}
}
@@ -372,8 +188,7 @@ func WriteSettings(filename string) error {
}
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
txt = append(txt, '\n')
err = writeFile(filename, txt)
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return err
}
@@ -381,11 +196,11 @@ func WriteSettings(filename string) error {
// OverwriteSettings writes the current settings to settings.json and
// resets any user configuration of local settings present in settings.json
func OverwriteSettings(filename string) error {
settings := make(map[string]any)
settings := make(map[string]interface{})
var err error
if _, e := os.Stat(ConfigDir); e == nil {
defaults := DefaultAllSettings()
defaults := DefaultGlobalSettings()
for k, v := range GlobalSettings {
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
if _, wr := ModifiedSettings[k]; wr {
@@ -394,60 +209,97 @@ func OverwriteSettings(filename string) error {
}
}
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
txt = append(txt, '\n')
err = writeFile(filename, txt)
txt, _ := json.MarshalIndent(settings, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return err
}
// 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 any) error {
return RegisterCommonOption(pl+"."+name, defaultvalue)
}
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue any) error {
return RegisterGlobalOption(pl+"."+name, defaultvalue)
}
// RegisterCommonOption creates a new option
func RegisterCommonOption(name string, defaultvalue any) error {
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
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
}
defaultCommonSettings[name] = defaultvalue
return nil
}
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{}) error {
return RegisterGlobalOption(pl+"."+name, defaultvalue)
}
// RegisterGlobalOption creates a new global-only option
func RegisterGlobalOption(name string, defaultvalue any) error {
if _, ok := GlobalSettings[name]; !ok {
func RegisterGlobalOption(name string, defaultvalue interface{}) error {
if v, ok := GlobalSettings[name]; !ok {
DefaultGlobalOnlySettings[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
}
// GetGlobalOption returns the global value of the given option
func GetGlobalOption(name string) any {
func GetGlobalOption(name string) interface{} {
return GlobalSettings[name]
}
func defaultFileFormat() string {
if runtime.GOOS == "windows" {
return "dos"
}
return "unix"
}
func defaultFakeCursor() bool {
_, wt := os.LookupEnv("WT_SESSION")
if runtime.GOOS == "windows" && !wt {
// enabled for windows consoles where the cursor is slow
return true
}
return false
var defaultCommonSettings = map[string]interface{}{
"autoindent": true,
"autosu": false,
"backup": true,
"backupdir": "",
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"diffgutter": false,
"encoding": "utf-8",
"eofnewline": true,
"fastdirty": false,
"fileformat": "unix",
"filetype": "unknown",
"hlsearch": false,
"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 {
@@ -461,20 +313,63 @@ func GetInfoBarOffset() int {
return offset
}
// DefaultCommonSettings returns a map of all common buffer settings
// and their default values
func DefaultCommonSettings() map[string]any {
commonsettings := make(map[string]any)
// DefaultCommonSettings returns the default global settings for micro
// Note that colorscheme is a global only option
func DefaultCommonSettings() map[string]interface{} {
commonsettings := make(map[string]interface{})
for k, v := range defaultCommonSettings {
commonsettings[k] = v
}
return commonsettings
}
// DefaultAllSettings returns a map of all common buffer & global-only settings
// and their default values
func DefaultAllSettings() map[string]any {
allsettings := make(map[string]any)
// a list of settings that should only be globally modified and their
// default values
var DefaultGlobalOnlySettings = map[string]interface{}{
"autosave": float64(0),
"clipboard": "external",
"colorscheme": "default",
"divchars": "|-",
"divreverse": true,
"fakecursor": false,
"infobar": true,
"keymenu": false,
"mouse": true,
"multiopen": "tab",
"parsecursor": false,
"paste": false,
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
"pluginrepos": []string{},
"savehistory": true,
"sucmd": "sudo",
"tabhighlight": false,
"tabreverse": true,
"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{} {
allsettings := make(map[string]interface{})
for k, v := range defaultCommonSettings {
allsettings[k] = v
}
@@ -485,34 +380,35 @@ func DefaultAllSettings() map[string]any {
}
// GetNativeValue parses and validates a value for a given option
func GetNativeValue(option, value string) (any, error) {
curVal := GetGlobalOption(option)
if curVal == nil {
return nil, ErrInvalidOption
}
switch kind := reflect.TypeOf(curVal).Kind(); kind {
case reflect.Bool:
func GetNativeValue(option string, realValue interface{}, value string) (interface{}, error) {
var native interface{}
kind := reflect.TypeOf(realValue).Kind()
if kind == reflect.Bool {
b, err := util.ParseBool(value)
if err != nil {
return nil, ErrInvalidValue
}
return b, nil
case reflect.String:
return value, nil
case reflect.Float64:
f, err := strconv.ParseFloat(value, 64)
native = b
} else if kind == reflect.String {
native = value
} else if kind == reflect.Float64 {
i, err := strconv.Atoi(value)
if err != nil {
return nil, ErrInvalidValue
}
return f, nil
default:
native = float64(i)
} else {
return nil, ErrInvalidValue
}
if err := OptionIsValid(option, native); err != nil {
return nil, err
}
return native, nil
}
// OptionIsValid checks if a value is valid for a certain option
func OptionIsValid(option string, value any) error {
func OptionIsValid(option string, value interface{}) error {
if validator, ok := optionValidators[option]; ok {
return validator(option, value)
}
@@ -522,21 +418,21 @@ func OptionIsValid(option string, value any) error {
// Option validators
func validatePositiveValue(option string, value any) error {
nativeValue, ok := value.(float64)
func validatePositiveValue(option string, value interface{}) error {
tabsize, ok := value.(float64)
if !ok {
return errors.New("Expected numeric type for " + option)
}
if nativeValue < 1 {
if tabsize < 1 {
return errors.New(option + " must be greater than 0")
}
return nil
}
func validateNonNegativeValue(option string, value any) error {
func validateNonNegativeValue(option string, value interface{}) error {
nativeValue, ok := value.(float64)
if !ok {
@@ -550,27 +446,7 @@ func validateNonNegativeValue(option string, value any) error {
return nil
}
func validateChoice(option string, value any) 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 any) error {
func validateColorscheme(option string, value interface{}) error {
colorscheme, ok := value.(string)
if !ok {
@@ -584,7 +460,53 @@ func validateColorscheme(option string, value any) error {
return nil
}
func validateEncoding(option string, value any) error {
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 {
_, err := htmlindex.Get(value.(string))
return err
}
func validateMultiOpen(option string, value interface{}) error {
val, ok := value.(string)
if !ok {
return errors.New("Expected string type for multiopen")
}
switch val {
case "tab", "hsplit", "vsplit":
default:
return errors.New(option + " must be 'tab', 'hsplit', or 'vsplit'")
}
return nil
}

View File

@@ -2,14 +2,14 @@ package display
import (
"strconv"
"strings"
"unicode"
runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)
// The BufWindow provides a way of displaying a certain section of a buffer.
@@ -47,7 +47,7 @@ func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
// SetBuffer sets this window's buffer.
func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
w.Buf = b
b.OptionCallback = func(option string, nativeValue any) {
b.OptionCallback = func(option string, nativeValue interface{}) {
if option == "softwrap" {
if nativeValue.(bool) {
w.StartCol = 0
@@ -59,15 +59,9 @@ func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
if option == "softwrap" || option == "wordwrap" {
w.Relocate()
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 {
return w.VLocFromLoc(loc).VisualX
@@ -136,11 +130,6 @@ func (w *BufWindow) updateDisplayInfo() {
w.bufHeight--
}
scrollbarWidth := 0
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height && w.Width > 0 {
scrollbarWidth = 1
}
w.hasMessage = len(b.Messages) > 0
// We need to know the string length of the largest line number
@@ -158,16 +147,16 @@ func (w *BufWindow) updateDisplayInfo() {
w.gutterOffset += w.maxLineNumLength + 1
}
if w.gutterOffset > w.Width-scrollbarWidth {
w.gutterOffset = w.Width - scrollbarWidth
}
prevBufWidth := w.bufWidth
w.bufWidth = w.Width - w.gutterOffset - scrollbarWidth
w.bufWidth = w.Width - w.gutterOffset
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
w.bufWidth--
}
if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) {
for _, c := range w.Buf.GetCursors() {
c.LastWrappedVisualX = c.GetVisualX(true)
c.LastVisualX = c.GetVisualX()
}
}
}
@@ -245,7 +234,7 @@ func (w *BufWindow) Relocate() bool {
// horizontal relocation (scrolling)
if !b.Settings["softwrap"].(bool) {
cx := activeC.GetVisualX(false)
cx := activeC.GetVisualX()
rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
if rw == 0 {
rw = 1 // tab or newline
@@ -255,8 +244,8 @@ func (w *BufWindow) Relocate() bool {
w.StartCol = cx
ret = true
}
if cx+rw > w.StartCol+w.bufWidth {
w.StartCol = cx - w.bufWidth + rw
if cx+w.gutterOffset+rw > w.StartCol+w.Width {
w.StartCol = cx - w.Width + w.gutterOffset + rw
ret = true
}
}
@@ -289,17 +278,13 @@ func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
break
}
}
for i := 0; i < 2 && vloc.X < w.gutterOffset; i++ {
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
vloc.X++
}
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
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) {
if vloc.X >= w.gutterOffset {
return
}
symbol := ' '
styleName := ""
@@ -335,28 +320,26 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, vloc
} else {
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
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)
vloc.X++
}
// Write the actual line number
for i := 0; i < len(lineNum) && vloc.X < w.gutterOffset; i++ {
if softwrapped || (w.bufWidth == 0 && w.Buf.Settings["softwrap"] == true) {
for _, ch := range lineNum {
if softwrapped {
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
} 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++
}
// Write the extra space
if vloc.X < w.gutterOffset {
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
vloc.X++
}
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
vloc.X++
}
// getStyle returns the highlight style for the given character position
@@ -391,7 +374,18 @@ func (w *BufWindow) displayBuffer() {
if b.ModifiedThisFrame {
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
}
@@ -399,20 +393,26 @@ func (w *BufWindow) displayBuffer() {
var matchingBraces []buffer.Loc
// bracePairs is defined in buffer.go
if b.Settings["matchbrace"].(bool) {
for _, c := range b.GetCursors() {
if c.HasSelection() {
continue
}
for _, bp := range buffer.BracePairs {
for _, c := range b.GetCursors() {
if c.HasSelection() {
continue
}
curX := c.X
curLoc := c.Loc
mb, left, found := b.FindMatchingBrace(c.Loc)
if found {
matchingBraces = append(matchingBraces, mb)
if !left {
if b.Settings["matchbracestyle"].(string) != "highlight" {
matchingBraces = append(matchingBraces, c.Loc)
r := c.RuneUnder(curX)
rl := c.RuneUnder(curX - 1)
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
mb, left, found := b.FindMatchingBrace(bp, curLoc)
if found {
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))
}
}
}
@@ -451,36 +451,12 @@ func (w *BufWindow) displayBuffer() {
cursors := b.GetCursors()
curStyle := config.DefStyle
// Parse showchars which is in the format of key1=val1,key2=val2,...
spacechars := " "
tabchars := b.Settings["indentchar"].(string)
var indentspacechars string
var indenttabchars string
for _, entry := range strings.Split(b.Settings["showchars"].(string), ",") {
split := strings.SplitN(entry, "=", 2)
if len(split) < 2 {
continue
}
key, val := split[0], split[1]
switch key {
case "space":
spacechars = val
case "tab":
tabchars = val
case "ispace":
indentspacechars = val
case "itab":
indenttabchars = val
}
}
for ; vloc.Y < w.bufHeight; vloc.Y++ {
vloc.X = 0
currentLine := false
for _, c := range cursors {
if !c.HasSelection() && bloc.Y == c.Y && w.active {
if bloc.Y == c.Y && w.active {
currentLine = true
break
}
@@ -507,202 +483,114 @@ func (w *BufWindow) displayBuffer() {
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)
if startStyle != nil {
curStyle = *startStyle
}
bloc.X = bslice
// returns the rune to be drawn, style of it and if the bg should be preserved
getRuneStyle := func(r rune, style tcell.Style, showoffset int, linex int, isplaceholder bool) (rune, tcell.Style, bool) {
if nColsBeforeStart > 0 || vloc.Y < 0 || isplaceholder {
return r, style, false
}
for _, mb := range matchingBraces {
if mb.X == bloc.X && mb.Y == bloc.Y {
if b.Settings["matchbracestyle"].(string) == "highlight" {
if s, ok := config.Colorscheme["match-brace"]; ok {
return r, s, false
} else {
return r, style.Reverse(true), false
}
} else {
return r, style.Underline(true), false
}
}
}
if r != '\t' && r != ' ' {
return r, style, false
}
var indentrunes []rune
switch r {
case '\t':
if bloc.X < leadingwsEnd && indenttabchars != "" {
indentrunes = []rune(indenttabchars)
} else {
indentrunes = []rune(tabchars)
}
case ' ':
if linex%tabsize == 0 && bloc.X < leadingwsEnd && indentspacechars != "" {
indentrunes = []rune(indentspacechars)
} else {
indentrunes = []rune(spacechars)
}
}
var drawrune rune
if showoffset < len(indentrunes) {
drawrune = indentrunes[showoffset]
} else {
// use space if no showchars or after we showed showchars
drawrune = ' '
}
if s, ok := config.Colorscheme["indent-char"]; ok {
fg, _, _ := s.Decompose()
style = style.Foreground(fg)
}
preservebg := false
if b.Settings["hltaberrors"].(bool) && bloc.X < leadingwsEnd {
if s, ok := config.Colorscheme["tab-error"]; ok {
if b.Settings["tabstospaces"].(bool) && r == '\t' {
fg, _, _ := s.Decompose()
style = style.Background(fg)
preservebg = true
} else if !b.Settings["tabstospaces"].(bool) && r == ' ' {
fg, _, _ := s.Decompose()
style = style.Background(fg)
preservebg = 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)
preservebg = true
}
}
}
}
return drawrune, style, preservebg
}
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool, preservebg bool) {
defer func() {
if nColsBeforeStart <= 0 {
vloc.X++
}
nColsBeforeStart--
}()
if nColsBeforeStart > 0 || vloc.Y < 0 {
return
}
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()
_, defBg, _ := config.DefStyle.Decompose()
// syntax or hlsearch highlighting with non-default background takes precedence
// over cursor-line and color-column
if !preservebg && origBg != defBg {
preservebg = true
}
for _, c := range cursors {
if c.HasSelection() &&
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
// The current character is selected
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool) {
if nColsBeforeStart <= 0 && vloc.Y >= 0 {
if highlight {
if w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {
style = config.DefStyle.Reverse(true)
if s, ok := config.Colorscheme["selection"]; ok {
if s, ok := config.Colorscheme["hlsearch"]; ok {
style = s
}
}
if b.Settings["cursorline"].(bool) && w.active && !preservebg &&
!c.HasSelection() && c.Y == bloc.Y {
if s, ok := config.Colorscheme["cursor-line"]; ok {
_, origBg, _ := style.Decompose()
_, defBg, _ := config.DefStyle.Decompose()
// syntax or hlsearch highlighting with non-default background takes precedence
// over cursor-line and color-column
dontOverrideBackground := origBg != defBg
for _, c := range cursors {
if c.HasSelection() &&
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
// The current character is selected
style = config.DefStyle.Reverse(true)
if s, ok := config.Colorscheme["selection"]; ok {
style = s
}
}
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
!c.HasSelection() && c.Y == bloc.Y {
if s, ok := config.Colorscheme["cursor-line"]; ok {
fg, _, _ := s.Decompose()
style = style.Background(fg)
}
}
}
for _, m := range b.Messages {
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
style = style.Underline(true)
break
}
}
if r == '\t' {
indentrunes := []rune(b.Settings["indentchar"].(string))
// if empty indentchar settings, use space
if len(indentrunes) == 0 {
indentrunes = []rune{' '}
}
r = indentrunes[0]
if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
fg, _, _ := s.Decompose()
style = style.Foreground(fg)
}
}
if s, ok := config.Colorscheme["color-column"]; ok {
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
fg, _, _ := s.Decompose()
style = style.Background(fg)
}
}
}
for _, m := range b.Messages {
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
style = style.Underline(true)
break
for _, mb := range matchingBraces {
if mb.X == bloc.X && mb.Y == bloc.Y {
style = style.Underline(true)
}
}
}
if s, ok := config.Colorscheme["color-column"]; ok {
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !preservebg {
fg, _, _ := s.Decompose()
style = style.Background(fg)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
if showcursor {
for _, c := range cursors {
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
}
}
}
}
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
if showcursor {
for _, c := range cursors {
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
}
}
if nColsBeforeStart <= 0 {
vloc.X++
}
nColsBeforeStart--
}
wrap := func() {
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 {
if w.hasMessage {
w.drawGutter(&vloc, &bloc)
}
if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
}
// This will draw an empty line number because the current line is wrapped
if b.Settings["ruler"].(bool) {
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
}
} else {
vloc.X = w.gutterOffset
// This will draw an empty line number because the current line is wrapped
if b.Settings["ruler"].(bool) {
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
}
}
@@ -722,7 +610,7 @@ func (w *BufWindow) displayBuffer() {
wordwidth := 0
totalwidth := w.StartCol - nColsBeforeStart
for len(line) > 0 && vloc.X < maxWidth {
for len(line) > 0 {
r, combc, size := util.DecodeCharacter(line)
line = line[size:]
@@ -731,13 +619,17 @@ func (w *BufWindow) displayBuffer() {
width := 0
linex := totalwidth
switch r {
case '\t':
ts := tabsize - (totalwidth % tabsize)
width = util.Min(ts, maxWidth-vloc.X)
totalwidth += ts
default:
if unicode.IsUpper(r) {
width = 2
totalwidth += 2
break
}
width = runewidth.RuneWidth(r)
totalwidth += width
}
@@ -756,7 +648,7 @@ func (w *BufWindow) displayBuffer() {
// If a word (or just a wide rune) does not fit in the window
if vloc.X+wordwidth > maxWidth && vloc.X > w.gutterOffset {
for vloc.X < maxWidth {
draw(' ', nil, config.DefStyle, false, false, true)
draw(' ', nil, config.DefStyle, false, false)
}
// We either stop or we wrap to draw the word in the next line
@@ -772,17 +664,25 @@ func (w *BufWindow) displayBuffer() {
}
for _, r := range word {
drawrune, drawstyle, preservebg := getRuneStyle(r.r, r.style, 0, linex, false)
draw(drawrune, r.combc, drawstyle, true, true, preservebg)
if unicode.IsUpper(r.r) {
draw('~', nil, r.style, true, true)
draw(r.r, r.combc, r.style, true, false)
bloc.X++
continue
}
// Draw extra characters for tabs or wide runes
for i := 1; i < r.width; i++ {
if r.r == '\t' {
drawrune, drawstyle, preservebg = getRuneStyle('\t', r.style, i, linex+i, false)
} else {
drawrune, drawstyle, preservebg = getRuneStyle(' ', r.style, i, linex+i, true)
draw(r.r, r.combc, r.style, true, true)
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if r.width > 1 {
char := ' '
if r.r != '\t' {
char = '@'
}
for i := 1; i < r.width; i++ {
draw(char, nil, r.style, true, false)
}
draw(drawrune, nil, drawstyle, true, false, preservebg)
}
bloc.X++
}
@@ -827,8 +727,7 @@ func (w *BufWindow) displayBuffer() {
if vloc.X != maxWidth {
// Display newline within a selection
drawrune, drawstyle, preservebg := getRuneStyle(' ', config.DefStyle, 0, totalwidth, true)
draw(drawrune, nil, drawstyle, true, true, preservebg)
draw(' ', nil, config.DefStyle, true, true)
}
bloc.X = w.StartCol
@@ -881,14 +780,8 @@ func (w *BufWindow) displayScrollBar() {
scrollBarStyle = style
}
scrollBarChar := config.GetGlobalOption("scrollbarchar").(string)
if util.CharacterCountInString(scrollBarChar) != 1 {
scrollBarChar = "|"
}
scrollBarRune := []rune(scrollBarChar)
for y := barstart; y < util.Min(barstart+barsize, w.Y+w.bufHeight); y++ {
screen.SetContent(scrollX, y, scrollBarRune[0], nil, scrollBarStyle)
screen.SetContent(scrollX, y, '|', nil, scrollBarStyle)
}
}
}

View File

@@ -2,12 +2,12 @@ package display
import (
runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/info"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/info"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)
type InfoWindow struct {
@@ -122,8 +122,16 @@ func (i *InfoWindow) displayBuffer() {
}
screen.SetContent(vlocX, i.Y, r, combc, style)
vlocX += runewidth.RuneWidth(r)
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
c := r
if j > 0 {
c = ' '
combc = nil
}
screen.SetContent(vlocX, i.Y, c, combc, style)
}
vlocX++
}
nColsBeforeStart--
}
@@ -134,22 +142,29 @@ func (i *InfoWindow) displayBuffer() {
curBX := blocX
r, combc, size := util.DecodeCharacter(line)
draw(r, combc, i.defStyle())
width := 0
char := ' '
switch r {
case '\t':
width = tabsize - (totalwidth % tabsize)
for j := 0; j < width; j++ {
draw(' ', nil, i.defStyle())
}
ts := tabsize - (totalwidth % tabsize)
width = ts
default:
width = runewidth.RuneWidth(r)
draw(r, combc, i.defStyle())
char = '@'
}
blocX++
line = line[size:]
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for j := 1; j < width; j++ {
draw(char, nil, i.defStyle())
}
}
if activeC.X == curBX {
screen.ShowCursor(curVX, i.Y)
}
@@ -245,9 +260,7 @@ func (i *InfoWindow) Display() {
done := false
statusLineStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["statusline.suggestions"]; ok {
statusLineStyle = style
} else if style, ok := config.Colorscheme["statusline"]; ok {
if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style
}
keymenuOffset := 0

View File

@@ -2,8 +2,8 @@ package display
import (
runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/util"
)
// SLoc represents a vertical scrolling location, i.e. a location of a visual line
@@ -291,7 +291,13 @@ func (w *BufWindow) diff(s1, s2 SLoc) int {
// within the buffer boundaries.
func (w *BufWindow) Scroll(s SLoc, n int) SLoc {
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 w.scroll(s, n)

View File

@@ -10,12 +10,12 @@ import (
luar "layeh.com/gopher-luar"
runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
)
// StatusLine represents the information line at the bottom
@@ -47,12 +47,6 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
}
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())
},
@@ -72,7 +66,7 @@ func SetStatusInfoFnLua(fn string) {
return
}
statusInfo[fn] = func(b *buffer.Buffer) string {
if pl == nil || !pl.IsLoaded() {
if pl == nil || !pl.IsEnabled() {
return ""
}
val, err := pl.Call(plFn, luar.New(ulua.L, b))
@@ -96,7 +90,7 @@ func NewStatusLine(win *BufWindow) *StatusLine {
}
// FindOpt finds a given option in the current buffer's settings
func (s *StatusLine) FindOpt(opt string) any {
func (s *StatusLine) FindOpt(opt string) interface{} {
if val, ok := s.win.Buf.Settings[opt]; ok {
return val
}
@@ -116,9 +110,7 @@ func (s *StatusLine) Display() {
// autocomplete suggestions (for the buffer, not for the infowindow)
if b.HasSuggestions && len(b.Suggestions) > 1 {
statusLineStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["statusline.suggestions"]; ok {
statusLineStyle = style
} else if style, ok := config.Colorscheme["statusline"]; ok {
if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style
}
x := 0
@@ -152,7 +144,7 @@ func (s *StatusLine) Display() {
name := match[2 : len(match)-1]
if bytes.HasPrefix(name, []byte("opt")) {
option := name[4:]
return fmt.Append(nil, s.FindOpt(string(option)))
return []byte(fmt.Sprint(s.FindOpt(string(option))))
} else if bytes.HasPrefix(name, []byte("bind")) {
binding := string(name[5:])
for k, v := range config.Bindings["buffer"] {
@@ -175,16 +167,8 @@ func (s *StatusLine) Display() {
rightText = formatParser.ReplaceAllFunc(rightText, formatter)
statusLineStyle := config.DefStyle.Reverse(true)
if s.win.IsActive() {
if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style
}
} else {
if style, ok := config.Colorscheme["statusline.inactive"]; ok {
statusLineStyle = style
} else if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style
}
if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style
}
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)

View File

@@ -2,11 +2,11 @@ package display
import (
runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/tcell/v2"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
)
type TabWindow struct {
@@ -112,10 +112,10 @@ func (w *TabWindow) Display() {
}
return tabBarStyle, tabBarActiveStyle
}
draw := func(r rune, n int, active bool, reversed bool) {
tabBarStyle, tabBarActiveStyle := reverseStyles(reversed)
style := tabBarStyle
if active {
style = tabBarActiveStyle
@@ -147,15 +147,15 @@ func (w *TabWindow) Display() {
} else {
draw(' ', 1, false, tabCharHighlight)
}
for _, c := range n {
draw(c, 1, i == w.active, tabCharHighlight)
}
if i == len(w.Names)-1 {
done = true
}
if i == w.active {
draw(']', 1, true, tabCharHighlight)
draw(' ', 2, true, globalTabReverse)
@@ -163,7 +163,7 @@ func (w *TabWindow) Display() {
draw(' ', 1, false, tabCharHighlight)
draw(' ', 2, false, globalTabReverse)
}
if x >= w.Width {
break
}

View File

@@ -1,13 +1,13 @@
package display
import (
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
"github.com/micro-editor/terminal"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
"github.com/zyedidia/terminal"
)
type TermWindow struct {
@@ -110,8 +110,6 @@ func (w *TermWindow) Display() {
}
if w.State.CursorVisible() && w.active {
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,11 +1,11 @@
package display
import (
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/views"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/views"
)
type UIWindow struct {

View File

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

View File

@@ -1,17 +1,11 @@
package info
import (
"bytes"
"encoding/gob"
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/config"
)
// LoadHistory attempts to load user history from configDir/buffers/history
@@ -21,23 +15,24 @@ func (i *InfoBuf) LoadHistory() {
if config.GetGlobalOption("savehistory").(bool) {
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
var decodedMap map[string][]string
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
i.Error("Error loading history: ", err)
}
return
}
if err == nil {
defer file.Close()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&decodedMap)
defer file.Close()
err = gob.NewDecoder(file).Decode(&decodedMap)
if err != nil {
i.Error("Error decoding history: ", err)
return
if err != nil {
i.Error("Error loading history:", err)
return
}
}
if decodedMap != nil {
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
err := gob.NewEncoder(&buf).Encode(i.History)
if err != nil {
screen.TermMessage("Error encoding history: ", err)
return
}
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
if err == nil {
defer file.Close()
encoder := gob.NewEncoder(file)
filename := filepath.Join(config.ConfigDir, "buffers", "history")
err = util.SafeWrite(filename, buf.Bytes(), true)
if err != nil {
screen.TermMessage("Error saving history: ", err)
return
err = encoder.Encode(i.History)
if err != nil {
i.Error("Error saving history:", err)
return
}
}
}
}
@@ -109,51 +102,3 @@ func (i *InfoBuf) DownHistory(history []string) {
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

@@ -3,11 +3,11 @@ package info
import (
"fmt"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/buffer"
)
// 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 {
*buffer.Buffer
@@ -25,10 +25,6 @@ type InfoBuf struct {
// It's a map of history type -> history array
History map[string][]string
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
HasGutter bool
@@ -55,7 +51,7 @@ func (i *InfoBuf) Close() {
}
// Message sends a message to the user
func (i *InfoBuf) Message(msg ...any) {
func (i *InfoBuf) Message(msg ...interface{}) {
// only display a new message if there isn't an active prompt
// this is to prevent overwriting an existing prompt to the user
if !i.HasPrompt {
@@ -67,7 +63,7 @@ func (i *InfoBuf) Message(msg ...any) {
}
// GutterMessage displays a message and marks it as a gutter message
func (i *InfoBuf) GutterMessage(msg ...any) {
func (i *InfoBuf) GutterMessage(msg ...interface{}) {
i.Message(msg...)
i.HasGutter = true
}
@@ -79,7 +75,7 @@ func (i *InfoBuf) ClearGutter() {
}
// Error sends an error message to the user
func (i *InfoBuf) Error(msg ...any) {
func (i *InfoBuf) Error(msg ...interface{}) {
// only display a new message if there isn't an active prompt
// this is to prevent overwriting an existing prompt to the user
if !i.HasPrompt {
@@ -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.HistoryNum = len(i.History[ptype]) - 1
i.HistorySearch = false
i.PromptType = ptype
i.Msg = prompt
@@ -143,12 +138,13 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
if i.PromptCallback != nil {
if canceled {
i.Replace(i.Start(), i.End(), "")
i.PromptCallback("", true)
h := i.History[i.PromptType]
i.History[i.PromptType] = h[:len(h)-1]
i.PromptCallback("", true)
} else {
resp := string(i.LineBytes(0))
i.Replace(i.Start(), i.End(), "")
i.PromptCallback(resp, false)
h := i.History[i.PromptType]
h[len(h)-1] = resp
@@ -159,8 +155,6 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
break
}
}
i.PromptCallback(resp, false)
}
// i.PromptCallback = nil
}

View File

@@ -17,6 +17,7 @@ import (
"regexp"
"runtime"
"strings"
"sync"
"time"
"unicode/utf8"
@@ -26,6 +27,7 @@ import (
)
var L *lua.LState
var Lock sync.Mutex
// LoadFile loads a lua file
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, "NewSectionReader", luar.New(L, io.NewSectionReader))
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, "ReadFull", luar.New(L, io.ReadFull))
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, "PathSeparator", luar.New(L, os.PathSeparator))
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, "Remove", luar.New(L, os.Remove))
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
L.SetField(pkg, "Rename", luar.New(L, os.Rename))
L.SetField(pkg, "SEEK_CUR", luar.New(L, io.SeekCurrent))
L.SetField(pkg, "SEEK_END", luar.New(L, io.SeekEnd))
L.SetField(pkg, "SEEK_SET", luar.New(L, io.SeekStart))
L.SetField(pkg, "SEEK_CUR", luar.New(L, os.SEEK_CUR))
L.SetField(pkg, "SEEK_END", luar.New(L, os.SEEK_END))
L.SetField(pkg, "SEEK_SET", luar.New(L, os.SEEK_SET))
L.SetField(pkg, "SameFile", luar.New(L, os.SameFile))
L.SetField(pkg, "Setenv", luar.New(L, os.Setenv))
L.SetField(pkg, "StartProcess", luar.New(L, os.StartProcess))
@@ -391,7 +390,6 @@ func importOs() *lua.LTable {
L.SetField(pkg, "TempDir", luar.New(L, os.TempDir))
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
L.SetField(pkg, "UserHomeDir", luar.New(L, os.UserHomeDir))
L.SetField(pkg, "WriteFile", luar.New(L, os.WriteFile))
return pkg
}
@@ -427,6 +425,7 @@ func importPath() *lua.LTable {
func importFilePath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))
@@ -524,16 +523,21 @@ func importErrors() *lua.LTable {
func importTime() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "After", luar.New(L, time.After))
L.SetField(pkg, "Sleep", luar.New(L, time.Sleep))
L.SetField(pkg, "Tick", luar.New(L, time.Tick))
L.SetField(pkg, "Since", luar.New(L, time.Since))
L.SetField(pkg, "FixedZone", luar.New(L, time.FixedZone))
L.SetField(pkg, "LoadLocation", luar.New(L, time.LoadLocation))
L.SetField(pkg, "NewTicker", luar.New(L, time.NewTicker))
L.SetField(pkg, "Date", luar.New(L, time.Date))
L.SetField(pkg, "Now", luar.New(L, time.Now))
L.SetField(pkg, "Parse", luar.New(L, time.Parse))
L.SetField(pkg, "ParseDuration", luar.New(L, time.ParseDuration))
L.SetField(pkg, "ParseInLocation", luar.New(L, time.ParseInLocation))
L.SetField(pkg, "Unix", luar.New(L, time.Unix))
L.SetField(pkg, "AfterFunc", luar.New(L, time.AfterFunc))
L.SetField(pkg, "NewTimer", luar.New(L, time.NewTimer))
L.SetField(pkg, "Nanosecond", luar.New(L, time.Nanosecond))
L.SetField(pkg, "Microsecond", luar.New(L, time.Microsecond))
L.SetField(pkg, "Millisecond", luar.New(L, time.Millisecond))
@@ -541,15 +545,6 @@ func importTime() *lua.LTable {
L.SetField(pkg, "Minute", luar.New(L, time.Minute))
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
}

View File

@@ -14,7 +14,7 @@ import (
// The function must be called when the Screen is not initialized
// This will write the message, and wait for the user
// to press and key to continue
func TermMessage(msg ...any) {
func TermMessage(msg ...interface{}) {
screenb := TempFini()
fmt.Println(msg...)

View File

@@ -6,8 +6,9 @@ import (
"os"
"sync"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)
// Screen is the tcell screen we use to draw to the terminal
@@ -21,10 +22,6 @@ var Screen tcell.Screen
// Events is the channel of tcell events
var Events chan (tcell.Event)
// RestartCallback is called when the screen is restarted after it was
// temporarily shut down
var RestartCallback func()
// The lock is necessary since the screen is polled on a separate thread
var lock sync.Mutex
@@ -32,12 +29,6 @@ var lock sync.Mutex
// written to even if no event user event has occurred
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
func Lock() {
lock.Lock()
@@ -89,7 +80,7 @@ func ShowFakeCursor(x, y int) {
}
func UseFake() bool {
return config.GetGlobalOption("fakecursor").(bool)
return util.FakeCursor || config.GetGlobalOption("fakecursor").(bool)
}
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
@@ -126,34 +117,6 @@ func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
}
}
// 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
func TempFini() bool {
screenWasNil := Screen == nil
@@ -171,10 +134,6 @@ func TempStart(screenWasNil bool) {
if !screenWasNil {
Init()
Unlock()
if RestartCallback != nil {
RestartCallback()
}
}
}
@@ -183,13 +142,10 @@ func Init() error {
drawChan = make(chan bool, 8)
// Should we enable true color?
truecolor := config.GetGlobalOption("truecolor").(string)
if truecolor == "on" || (truecolor == "auto" && os.Getenv("MICRO_TRUECOLOR") == "1") {
os.Setenv("TCELL_TRUECOLOR", "enable")
} else if truecolor == "off" {
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
if !truecolor {
os.Setenv("TCELL_TRUECOLOR", "disable")
} else {
// For "auto", tcell already autodetects truecolor by default
}
var oldTerm string
@@ -231,10 +187,6 @@ func Init() error {
Screen.EnableMouse()
}
for _, r := range rawSeq {
Screen.RegisterRawSeq(r)
}
return nil
}

View File

@@ -24,17 +24,17 @@ func init() {
// JobFunction is a representation of a job (this data structure is what is loaded
// into the jobs channel)
type JobFunction struct {
Function func(string, []any)
Function func(string, []interface{})
Output string
Args []any
Args []interface{}
}
// A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events
type CallbackFile struct {
io.Writer
callback func(string, []any)
args []any
callback func(string, []interface{})
args []interface{}
}
// Job stores the executing command for the job, and the stdin pipe
@@ -53,13 +53,13 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
// JobStart starts a shell command in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []any), userargs ...any) *Job {
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *Job {
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
}
// JobSpawn starts a process with args in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []any), userargs ...any) *Job {
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *Job {
// Set up everything correctly if the functions have been provided
proc := exec.Command(cmdName, cmdArgs...)
var outbuf bytes.Buffer
@@ -78,10 +78,8 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(
go func() {
// Run the process in the background and create the onExit callback
proc.Run()
if onExit != nil {
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
Jobs <- jobFunc
}
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
Jobs <- jobFunc
}()
return &Job{proc, stdin}

View File

@@ -8,10 +8,10 @@ import (
"os"
"os/exec"
"os/signal"
"strings"
shellquote "github.com/kballard/go-shellquote"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/screen"
)
// ExecCommand executes a command using exec
@@ -59,10 +59,15 @@ func RunBackgroundShell(input string) (func() string, error) {
inputCmd := args[0]
return func() string {
output, err := RunCommand(input)
totalLines := strings.Split(output, "\n")
str := output
if err != nil {
str = fmt.Sprint(inputCmd, " exited with error: ", err, ": ", output)
if len(totalLines) < 3 {
if err == nil {
str = fmt.Sprint(inputCmd, " exited without error")
} else {
str = fmt.Sprint(inputCmd, " exited with error: ", err, ": ", output)
}
}
return str
}, nil
@@ -96,30 +101,28 @@ func RunInteractiveShell(input string, wait bool, getOutput bool) (string, error
cmd.Stderr = os.Stderr
// 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
// received
// Instead we trap Ctrl-C to kill the program we're running
c := make(chan os.Signal, 1)
signal.Reset(os.Interrupt)
signal.Notify(c, os.Interrupt)
err = cmd.Start()
if err == nil {
err = cmd.Wait()
if wait {
// This is just so we don't return right away and let the user press enter to return
screen.TermMessage("")
go func() {
for range c {
cmd.Process.Kill()
}
} else {
screen.TermMessage(err)
}
}()
cmd.Start()
err = cmd.Wait()
output := outputBytes.String()
if wait {
// This is just so we don't return right away and let the user press enter to return
screen.TermMessage("")
}
// Start the screen back up
screen.TempStart(screenb)
signal.Notify(util.Sigterm, os.Interrupt)
signal.Stop(c)
return output, err
}

View File

@@ -5,9 +5,9 @@ import (
"os/exec"
"strconv"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/terminal"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/terminal"
)
type TermType int
@@ -69,7 +69,7 @@ func (t *Terminal) GetSelection(width int) string {
}
// Start begins a new command in this terminal with a given view
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []any), userargs []any) error {
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
if len(execCmd) <= 0 {
return nil
}
@@ -78,9 +78,8 @@ func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback f
t.output = nil
if getOutput {
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 {
return err
}
@@ -130,7 +129,7 @@ func (t *Terminal) Close() {
if t.getOutput {
if t.callback != nil {
Jobs <- JobFunction{
Function: func(out string, args []any) {
Function: func(out string, args []interface{}) {
t.callback(out)
},
Output: t.output.String(),

View File

@@ -29,27 +29,11 @@ func isMark(r rune) bool {
// DecodeCharacter returns the next character from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCharacter(b []byte) (rune, []rune, int) {
combc, size := DecodeCombinedCharacter(b)
return combc[0], combc[1:], size
}
// DecodeCharacterInString returns the next character from a string
// A character is a rune along with any accompanying combining runes
func DecodeCharacterInString(str string) (rune, []rune, int) {
combc, size := DecodeCombinedCharacterInString(str)
return combc[0], combc[1:], size
}
// DecodeCombinedCharacter returns the next combined character
// from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCombinedCharacter(b []byte) ([]rune, int) {
var combc []rune
r, size := utf8.DecodeRune(b)
combc = append(combc, r)
b = b[size:]
c, s := utf8.DecodeRune(b)
var combc []rune
for isMark(c) {
combc = append(combc, c)
size += s
@@ -58,18 +42,17 @@ func DecodeCombinedCharacter(b []byte) ([]rune, int) {
c, s = utf8.DecodeRune(b)
}
return combc, size
return r, combc, size
}
// DecodeCombinedCharacterInString is the same as DecodeCombinedCharacter
// but for strings
func DecodeCombinedCharacterInString(str string) ([]rune, int) {
var combc []rune
// DecodeCharacterInString returns the next character from a string
// A character is a rune along with any accompanying combining runes
func DecodeCharacterInString(str string) (rune, []rune, int) {
r, size := utf8.DecodeRuneInString(str)
combc = append(combc, r)
str = str[size:]
c, s := utf8.DecodeRuneInString(str)
var combc []rune
for isMark(c) {
combc = append(combc, c)
size += s
@@ -78,7 +61,7 @@ func DecodeCombinedCharacterInString(str string) ([]rune, int) {
c, s = utf8.DecodeRuneInString(str)
}
return combc, size
return r, combc, size
}
// CharacterCount returns the number of characters in a byte array

View File

@@ -3,13 +3,10 @@ package util
import (
"archive/zip"
"bytes"
"crypto/md5"
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/url"
"os"
"os/user"
"path/filepath"
@@ -19,7 +16,6 @@ import (
"strings"
"time"
"unicode"
"unicode/utf8"
"github.com/blang/semver"
runewidth "github.com/mattn/go-runewidth"
@@ -38,51 +34,14 @@ var (
CompileDate = "Unknown"
// Debug logging
Debug = "OFF"
// FakeCursor is used to disable the terminal cursor and have micro
// draw its own (enabled for windows consoles where the cursor is slow)
FakeCursor = false
// Stdout is a buffer that is written to stdout when micro closes
Stdout *bytes.Buffer
// 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 BackupSuffix = ".micro-backup"
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() {
var err error
SemVersion, err = semver.Make(Version)
@@ -90,6 +49,10 @@ func init() {
fmt.Println("Invalid version: ", Version, err)
}
_, wt := os.LookupEnv("WT_SESSION")
if runtime.GOOS == "windows" && !wt {
FakeCursor = true
}
Stdout = new(bytes.Buffer)
}
@@ -254,56 +217,10 @@ func FSize(f *os.File) int64 {
return fi.Size()
}
// IsWordChar returns whether or not a rune is a 'word character'
// Word characters are defined as numbers, letters or sub-word delimiters
// IsWordChar returns whether or not the string is a 'word character'
// Word characters are defined as numbers, letters, or '_'
func IsWordChar(r rune) bool {
return IsAlphanumeric(r) || IsSubwordDelimiter(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)
return unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_'
}
// Spaces returns a string with n spaces
@@ -354,28 +271,6 @@ func RunePos(b []byte, i int) int {
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
func MakeRelative(path, base string) (string, error) {
if len(path) > 0 {
@@ -420,7 +315,7 @@ func ReplaceHome(path string) (string, error) {
// 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.
func GetPathAndCursorPosition(path string) (string, []string) {
re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?$`)
re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
match := re.FindStringSubmatch(path)
// no lines/columns were specified in the path, return just the path with no cursor location
if len(match) == 0 {
@@ -442,17 +337,8 @@ func GetModTime(path string) (time.Time, error) {
return info.ModTime(), nil
}
func HashStringMd5(str string) string {
return fmt.Sprintf("%x", md5.Sum([]byte(str)))
}
// 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 {
// EscapePath replaces every path separator in a given path with a %
func EscapePath(path string) string {
path = filepath.ToSlash(path)
if runtime.GOOS == "windows" {
// ':' is not valid in a path name on Windows but is ok on Unix
@@ -461,34 +347,6 @@ func EscapePathLegacy(path string) string {
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).
// In case the length of the escaped path (plus the backup extension) exceeds
// the filename length limit, a hash of the path is returned instead. In such
// case the second return value is the name of a file the original path should
// be saved to (since the original path cannot be derived from its hash).
// Otherwise the second return value is an empty string.
func DetermineEscapePath(dir string, path string) (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, ""
}
if len(url)+len(BackupSuffix) > 255 {
hash := HashStringMd5(path)
return filepath.Join(dir, hash), filepath.Join(dir, hash+".path")
}
return url, ""
}
// GetLeadingWhitespace returns the leading whitespace of the given byte array
func GetLeadingWhitespace(b []byte) []byte {
ws := []byte{}
@@ -505,30 +363,8 @@ func GetLeadingWhitespace(b []byte) []byte {
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
func IntOpt(opt any) int {
func IntOpt(opt interface{}) int {
return int(opt.(float64))
}
@@ -586,9 +422,19 @@ func Clamp(val, min, max int) int {
return val
}
// IsNonAlphaNumeric returns if the rune is not a number of letter or underscore.
func IsNonAlphaNumeric(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
}
// IsAutocomplete returns whether a character should begin an autocompletion.
func IsAutocomplete(c rune) bool {
return c == '.' || IsWordChar(c)
return c == '.' || !IsNonAlphaNumeric(c)
}
// ParseSpecial replaces escaped ts with '\t'.
func ParseSpecial(s string) string {
return strings.ReplaceAll(s, "\\t", "\t")
}
// String converts a byte array to a string (for lua plugins)
@@ -661,77 +507,3 @@ func HttpRequest(method string, url string, headers []string) (resp *http.Respon
}
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 := path + BackupSuffix
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
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 {
// cannot resize a lone node
return false
@@ -205,7 +201,7 @@ func (n *Node) ResizeSplit(size int) bool {
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) {
n.W, n.H = w, h
@@ -439,12 +435,11 @@ func (n *Node) VSplit(right bool) uint64 {
}
// 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:])
n.children[len(n.children)-1] = nil
n.children = n.children[:len(n.children)-1]
h := n.Kind == STVert
nonrs, numr := n.getResizeInfo(h)
if numr == 0 {
// This means that this was the last child
@@ -471,62 +466,18 @@ func (n *Node) Unsplit() bool {
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() {
return n.parent.Unsplit()
}
n.parent.flatten()
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)
func (n *Node) String() 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,25 @@ func runePos(p int, str []byte) int {
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
type State *region
// EmptyDef is an empty definition.
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
type LineStates interface {
LineBytes(n int) []byte
@@ -61,8 +77,6 @@ type LineStates interface {
State(lineN int) State
SetState(lineN int, s State)
SetMatch(lineN int, m LineMatch)
Lock()
Unlock()
}
// A Highlighter contains the information needed to highlight a string
@@ -82,7 +96,19 @@ func NewHighlighter(def *Def) *Highlighter {
// color's group (represented as one byte)
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
if skip != nil {
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
@@ -101,7 +127,18 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte) []int {
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)
for i, m := range matches {
matches[i][0] = runePos(m[0], str)
@@ -120,33 +157,52 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
}
}
var firstRegion *region
firstLoc := []int{lineLen, 0}
searchNesting := true
endLoc := findIndex(curRegion.end, curRegion.skip, line)
if endLoc != nil {
if start == endLoc[0] {
searchNesting = false
} else {
firstLoc = endLoc
loc := findIndex(curRegion.end, curRegion.skip, line, start == 0, canMatchEnd)
if loc != nil {
if !statesOnly {
highlights[start+loc[0]] = curRegion.limitGroup
}
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 {
loc := findIndex(r.start, r.skip, line)
if loc != nil {
if loc[0] < firstLoc[0] {
firstLoc = loc
firstRegion = r
}
if lineLen == 0 {
if canMatchEnd {
h.lastRegion = curRegion
}
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 {
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)
return highlights
}
@@ -157,17 +213,11 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
fullHighlights[i] = curRegion.group
}
if searchNesting {
for _, p := range curRegion.rules.patterns {
if curRegion.group == curRegion.limitGroup || p.group == curRegion.limitGroup {
matches := findAllIndex(p.regex, line)
for _, m := range matches {
if (endLoc == nil) || (m[0] < endLoc[0]) {
for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
}
}
}
for _, p := range curRegion.rules.patterns {
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
for _, m := range matches {
for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
}
}
}
@@ -178,25 +228,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 {
h.lastRegion = curRegion
}
@@ -213,10 +244,10 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
return highlights
}
var firstRegion *region
firstLoc := []int{lineLen, 0}
var firstRegion *region
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[0] < firstLoc[0] {
firstLoc = loc
@@ -224,7 +255,7 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
}
}
}
if firstRegion != nil && firstLoc[0] != lineLen {
if firstLoc[0] != lineLen {
if !statesOnly {
highlights[start+firstLoc[0]] = firstRegion.limitGroup
}
@@ -243,7 +274,7 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
fullHighlights := make([]Group, len(line))
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 i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
@@ -289,13 +320,7 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
// HighlightStates correctly sets all states for the buffer
func (h *Highlighter) HighlightStates(input LineStates) {
for i := 0; ; i++ {
input.Lock()
if i >= input.LinesNum() {
input.Unlock()
break
}
for i := 0; i < input.LinesNum(); i++ {
line := input.LineBytes(i)
// highlights := make(LineMatch)
@@ -308,7 +333,6 @@ func (h *Highlighter) HighlightStates(input LineStates) {
curState := h.lastRegion
input.SetState(i, curState)
input.Unlock()
}
}
@@ -317,9 +341,7 @@ func (h *Highlighter) HighlightStates(input LineStates) {
// This assumes that all the states are set correctly
func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
for i := startline; i <= endline; i++ {
input.Lock()
if i >= input.LinesNum() {
input.Unlock()
break
}
@@ -334,7 +356,6 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
}
input.SetMatch(i, match)
input.Unlock()
}
}
@@ -346,19 +367,9 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
h.lastRegion = nil
if startline > 0 {
input.Lock()
if startline-1 < input.LinesNum() {
h.lastRegion = input.State(startline - 1)
}
input.Unlock()
h.lastRegion = input.State(startline - 1)
}
for i := startline; ; i++ {
input.Lock()
if i >= input.LinesNum() {
input.Unlock()
break
}
for i := startline; i < input.LinesNum(); i++ {
line := input.LineBytes(i)
// highlights := make(LineMatch)
@@ -372,7 +383,6 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
lastState := input.State(i)
input.SetState(i, curState)
input.Unlock()
if curState == lastState {
return i
@@ -384,9 +394,6 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
// ReHighlightLine will rehighlight the state and match for a single line
func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
input.Lock()
defer input.Unlock()
line := input.LineBytes(lineN)
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
type Def struct {
*Header
rules *rules
}
type Header struct {
FileType string
FileNameRegex *regexp.Regexp
HeaderRegex *regexp.Regexp
SignatureRegex *regexp.Regexp
FileType string
FtDetect [2]*regexp.Regexp
}
type HeaderYaml struct {
FileType string `yaml:"filetype"`
Detect struct {
FNameRegexStr string `yaml:"filename"`
HeaderRegexStr string `yaml:"header"`
SignatureRegexStr string `yaml:"signature"`
FNameRgx string `yaml:"filename"`
HeaderRgx string `yaml:"header"`
} `yaml:"detect"`
}
type File struct {
FileType string
yamlSrc map[any]any
yamlSrc map[interface{}]interface{}
}
// 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
func MakeHeader(data []byte) (*Header, error) {
lines := bytes.Split(data, []byte{'\n'})
if len(lines) < 4 {
if len(lines) < 3 {
return nil, errors.New("Header file has incorrect format")
}
header := new(Header)
var err error
header.FileType = string(lines[0])
fnameRegexStr := string(lines[1])
headerRegexStr := string(lines[2])
signatureRegexStr := string(lines[3])
fnameRgx := string(lines[1])
headerRgx := string(lines[2])
if fnameRegexStr != "" {
header.FileNameRegex, err = regexp.Compile(fnameRegexStr)
if fnameRgx != "" {
header.FtDetect[0], err = regexp.Compile(fnameRgx)
}
if err == nil && headerRegexStr != "" {
header.HeaderRegex, err = regexp.Compile(headerRegexStr)
}
if err == nil && signatureRegexStr != "" {
header.SignatureRegex, err = regexp.Compile(signatureRegexStr)
if headerRgx != "" {
header.FtDetect[1], err = regexp.Compile(headerRgx)
}
if err != nil {
@@ -137,14 +132,11 @@ func MakeHeaderYaml(data []byte) (*Header, error) {
header := new(Header)
header.FileType = hdrYaml.FileType
if hdrYaml.Detect.FNameRegexStr != "" {
header.FileNameRegex, err = regexp.Compile(hdrYaml.Detect.FNameRegexStr)
if hdrYaml.Detect.FNameRgx != "" {
header.FtDetect[0], err = regexp.Compile(hdrYaml.Detect.FNameRgx)
}
if err == nil && hdrYaml.Detect.HeaderRegexStr != "" {
header.HeaderRegex, err = regexp.Compile(hdrYaml.Detect.HeaderRegexStr)
}
if err == nil && hdrYaml.Detect.SignatureRegexStr != "" {
header.SignatureRegex, err = regexp.Compile(hdrYaml.Detect.SignatureRegexStr)
if hdrYaml.Detect.HeaderRgx != "" {
header.FtDetect[1], err = regexp.Compile(hdrYaml.Detect.HeaderRgx)
}
if err != nil {
@@ -154,37 +146,6 @@ func MakeHeaderYaml(data []byte) (*Header, error) {
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) {
// This is just so if we have an error, we can exit cleanly and return the parse error to the user
defer func() {
@@ -197,7 +158,7 @@ func ParseFile(input []byte) (f *File, err error) {
}
}()
var rules map[any]any
var rules map[interface{}]interface{}
if err = yaml.Unmarshal(input, &rules); err != nil {
return nil, err
}
@@ -209,19 +170,11 @@ func ParseFile(input []byte) (f *File, err error) {
if k == "filetype" {
filetype := v.(string)
if filetype == "" {
return nil, errors.New("empty filetype")
}
f.FileType = filetype
break
}
}
if f.FileType == "" {
return nil, errors.New("missing filetype")
}
return f, err
}
@@ -238,14 +191,14 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
}
}()
src := f.yamlSrc
rules := f.yamlSrc
s = new(Def)
s.Header = header
for k, v := range src {
for k, v := range rules {
if k == "rules" {
inputRules := v.([]any)
inputRules := v.([]interface{})
rules, err := parseRules(inputRules, nil)
if err != nil {
@@ -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
}
@@ -336,7 +284,7 @@ func resolveIncludesInRegion(files []*File, region *region) {
}
}
func parseRules(input []any, curRegion *region) (ru *rules, err error) {
func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
@@ -349,16 +297,12 @@ func parseRules(input []any, curRegion *region) (ru *rules, err error) {
ru = new(rules)
for _, v := range input {
rule := v.(map[any]any)
rule := v.(map[interface{}]interface{})
for k, val := range rule {
group := k
switch object := val.(type) {
case string:
if object == "" {
return nil, fmt.Errorf("Empty rule %s", k)
}
if k == "include" {
ru.includes = append(ru.includes, object)
} else {
@@ -376,7 +320,7 @@ func parseRules(input []any, curRegion *region) (ru *rules, err error) {
groupNum := Groups[groupStr]
ru.patterns = append(ru.patterns, &pattern{groupNum, r})
}
case map[any]any:
case map[interface{}]interface{}:
// region
region, err := parseRegion(group.(string), object, curRegion)
if err != nil {
@@ -392,7 +336,7 @@ func parseRules(input []any, curRegion *region) (ru *rules, err error) {
return ru, nil
}
func parseRegion(group string, regionInfo map[any]any, prevRegion *region) (r *region, err error) {
func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegion *region) (r *region, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
@@ -412,56 +356,30 @@ func parseRegion(group string, regionInfo map[any]any, prevRegion *region) (r *r
r.group = groupNum
r.parent = prevRegion
// start is mandatory
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(regionInfo["start"].(string))
r.start, err = regexp.Compile(start)
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("Missing start in %s", group)
if err != nil {
return nil, err
}
// end is mandatory
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(regionInfo["end"].(string))
r.end, err = regexp.Compile(end)
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("Missing end in %s", group)
if err != nil {
return nil, err
}
// skip is optional
if skip, ok := regionInfo["skip"]; ok {
skip := skip.(string)
if skip == "" {
return nil, fmt.Errorf("Empty skip in %s", group)
}
if _, ok := regionInfo["skip"]; ok {
r.skip, err = regexp.Compile(regionInfo["skip"].(string))
r.skip, err = regexp.Compile(skip)
if err != nil {
return nil, err
}
}
// limit-color is optional
if groupStr, ok := regionInfo["limit-group"]; ok {
groupStr := groupStr.(string)
if groupStr == "" {
return nil, fmt.Errorf("Empty limit-group in %s", group)
}
if _, ok := regionInfo["limit-group"]; ok {
groupStr := regionInfo["limit-group"].(string)
if _, ok := Groups[groupStr]; !ok {
numGroups++
Groups[groupStr] = numGroups
@@ -476,17 +394,10 @@ func parseRegion(group string, regionInfo map[any]any, prevRegion *region) (r *r
r.limitGroup = r.group
}
// rules are optional
if rules, ok := regionInfo["rules"]; ok {
r.rules, err = parseRules(rules.([]any), r)
if err != nil {
return nil, err
}
}
r.rules, err = parseRules(regionInfo["rules"].([]interface{}), r)
if r.rules == nil {
// allow empty rules
r.rules = &rules{}
if err != nil {
return nil, err
}
return r, nil

View File

@@ -28,6 +28,3 @@ color-link color-column "#2D2F31"
#No extended types (bool in C, etc.)
#color-link type.extended "default"
#Plain brackets
color-link match-brace "#1D1F21,#62B1FE"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View File

@@ -26,6 +26,3 @@ color-link color-column "254"
#No extended types (bool in C, &c.) and plain brackets
color-link type.extended "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

@@ -42,6 +42,3 @@ color-link gutter-warning "red"
color-link color-column "cyan"
color-link underlined.url "underline blue, white"
color-link divider "blue"
color-link match-brace "black,cyan"
color-link tab-error "brightred"
color-link trailingws "brightred"

View File

@@ -38,6 +38,3 @@ color-link color-column "#f26522"
color-link constant.bool "bold #55ffff"
color-link constant.bool.true "bold #85ff85"
color-link constant.bool.false "bold #ff8585"
color-link match-brace "#1e2124,#55ffff"
color-link tab-error "#d75f5f"
color-link trailingws "#d75f5f"

View File

@@ -29,6 +29,3 @@ color-link color-column "#2C2C2C"
color-link type.extended "default"
#color-link symbol.brackets "default"
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,31 @@
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 hlsearch "#282828,#E6DB74"
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

@@ -43,7 +43,3 @@ color-link cursor-line "#44475A,#F8F8F2"
color-link color-column "#44475A"
color-link type.extended "default"
color-link match-brace "#282A36,#FF79C6"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View File

@@ -33,6 +33,3 @@ color-link type "bold #3cc83c,#001e28"
color-link type.keyword "bold #5aaae6,#001e28"
color-link type.extended "#ffffff,#001e28"
color-link underlined "#608b4e,#001e28"
color-link match-brace "#001e28,#5aaae6"
color-link tab-error "#d75f5f"
color-link trailingws "#d75f5f"

View File

@@ -33,6 +33,3 @@ color-link type "bold #004080,#f0f0f0"
color-link type.keyword "bold #780050,#f0f0f0"
color-link type.extended "#000000,#f0f0f0"
color-link underlined "#3f7f5f,#f0f0f0"
color-link match-brace "#f0f0f0,#780050"
color-link tab-error "#ff8787"
color-link trailingws "#ff8787"

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