Compare commits

..

1 Commits

Author SHA1 Message Date
Zachary Yedidia
6f302826c8 Add bracket surround 2017-09-13 10:48:53 -04:00
132 changed files with 3507 additions and 8124 deletions

View File

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

15
.gitmodules vendored
View File

@@ -55,18 +55,3 @@
[submodule "cmd/micro/vendor/github.com/flynn/json5"]
path = cmd/micro/vendor/github.com/flynn/json5
url = https://github.com/flynn/json5
[submodule "cmd/micro/vendor/github.com/zyedidia/terminal"]
path = cmd/micro/vendor/github.com/zyedidia/terminal
url = https://github.com/zyedidia/terminal
[submodule "cmd/micro/vendor/github.com/zyedidia/pty"]
path = cmd/micro/vendor/github.com/zyedidia/pty
url = https://github.com/zyedidia/pty
[submodule "cmd/micro/vendor/github.com/smartystreets/goconvey"]
path = cmd/micro/vendor/github.com/smartystreets/goconvey
url = https://github.com/smartystreets/goconvey
[submodule "cmd/micro/vendor/github.com/jtolds/gls"]
path = cmd/micro/vendor/github.com/jtolds/gls
url = https://github.com/jtolds/gls
[submodule "cmd/micro/vendor/github.com/smartystreets/assertions"]
path = cmd/micro/vendor/github.com/smartystreets/assertions
url = https://github.com/smartystreets/assertions

View File

@@ -58,6 +58,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
github.com/gdamore/encoding/LICENSE
================
@@ -394,9 +395,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
github.com/atotto/clipboard/LICENSE
================
github.com/zyedidia/clipboard/LICENSE (fork)
github.com/zyedidia/clipboard/LICENSE
================
Copyright (c) 2013 Ato Araki. All rights reserved.
@@ -428,9 +427,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
github.com/gdamore/tcell/LICENSE
================
github.com/zyedidia/tcell/LICENSE (fork)
github.com/zyedidia/tcell/LICENSE
================
@@ -668,6 +665,39 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang.org/x/sys/LICENSE
================
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang.org/x/text/LICENSE
================
@@ -1134,253 +1164,3 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
github.com/james4k/terminal/LICENSE
================
github.com/zyedidia/terminal/LICENSE (fork)
================
Copyright (C) 2013 James Gray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without liitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and thismssion notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
github.com/kr/pty/License
================
github.com/zyedidia/pty/License (fork)
================
Copyright (c) 2011 Keith Rarick
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall
be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
github.com/smartystreets/goconvey/LICENSE.md
================
Copyright (c) 2016 SmartyStreets, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
NOTE: Various optional and subordinate components carry their own licensing
requirements and restrictions. Use of those components is subject to the terms
and conditions outlined the respective license of each component.
github.com/smartystreets/assertions/LICENSE.md
================
Copyright (c) 2016 SmartyStreets, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
NOTE: Various optional and subordinate components carry their own licensing
requirements and restrictions. Use of those components is subject to the terms
and conditions outlined the respective license of each component.
github.com/jtolds/gls/LICENSE
================
Copyright (c) 2013, Space Monkey, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
github.com/npat-efault/poller/LICENSE.txt
================
github.com/zyedidia/poller/LICENSE.txt (fork)
================
Copyright (c) 2014, Nick Patavalis (npat@efault.net)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
github.com/zyedidia/glob
================
Glob is licensed under the MIT "Expat" License:
Copyright (c) 2016: Zachary Yedidia.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
github.com/dustin/go-humanize/LICENSE
================
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
<http://www.opensource.org/licenses/mit-license.php>
gopkg.in/yaml.v2/LICENSE
================
Copyright 2011-2016 Canonical Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
github.com/lucasb-eyer/go-colorful/LICENSE
================
Copyright (c) 2013 Lucas Beyer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -41,7 +41,6 @@ runtime:
go get -u github.com/jteeuwen/go-bindata/...
$(GOBIN)/go-bindata -nometadata -o runtime.go runtime/...
mv runtime.go cmd/micro
gofmt -w cmd/micro/runtime.go
test:
go test ./cmd/micro

View File

@@ -20,22 +20,6 @@ To see more screenshots of micro, showcasing all of the default colorschemes, se
You can also check out the website for Micro at https://micro-editor.github.io.
# Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Prebuilt binaries](#prebuilt-binaries)
- [Package Managers](#package-managers)
- [Building from source](#building-from-source)
- [MacOS terminal](#macos-terminal)
- [Linux clipboard support](#linux-clipboard-support)
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
- [Plan9, Cygwin](#plan9-cygwin)
- [Usage](#usage)
- [Documentation and Help](#documentation-and-help)
- [Contributing](#contributing)
- - -
# Features
* Easy to use and to install
@@ -46,7 +30,6 @@ You can also check out the website for Micro at https://micro-editor.github.io.
* Sane defaults
* You shouldn't have to configure much out of the box (and it is extremely easy to configure)
* Splits and tabs
* Nano-like menu to help you remember the keybindings
* Extremely good mouse support
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
* Cross platform (It should work on all the platforms Go runs on)
@@ -67,7 +50,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
* Macros
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)) or a tree view ([#249](https://github.com/zyedidia/micro/issues/249)) in the future.
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)), and multiple cursors ([#5](https://github.com/zyedidia/micro/issues/5)) in the future.
# Installation
@@ -86,19 +69,7 @@ and you'll see all the stable releases with the corresponding binaries.
If you'd like to see more information after installing micro, run `micro -version`.
### Installation script
There is a great script which can install micro for you by downloading the latest prebuilt binary. You can find it at https://getmic.ro (the github repo for it is [here](https://github.com/benweissmann/getmic.ro)).
Then you can easily install micro:
$ curl https://getmic.ro | bash
The script will install the micro binary to the current directory.
See the [Github page](https://github.com/benweissmann/getmic.ro) for more information.
### Package managers
### Package Managers
You can install micro using Homebrew on Mac:
@@ -121,20 +92,14 @@ scoop install micro
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
```
snap install micro --classic
```
On OpenBSD, micro is available in the ports tree. It is also available as a binary package.
```
pkg_add -v micro
snap install micro --edge --classic
```
### Building from source
If your operating system does not have a binary release, but does run Go, you can build from source.
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommend setting it to `~/go` if you don't have one).
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommand setting it to `~/go` if you don't have one).
```
go get -d github.com/zyedidia/micro/cmd/micro
@@ -150,7 +115,7 @@ You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd
### MacOS terminal
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Load Preset`. The newest versions also support true color.
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. The newest versions also support true color.
### Linux clipboard support

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

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

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 289 KiB

View File

@@ -1,73 +0,0 @@
.\" micro manual page - micro(1)
.\"
.\" Copyright © 2017 Zachary Yedidia <zyedidia@gmail.com>
.\" Copyright © 2017 Collin Warren <anatoly@somethinghub.com>
.\"
.\" This document is provided under the same licensing as micro.
.\" See \usr\share\doc\micro\LICENSE for more information.
.TH micro 1 "2017-03-28"
.SH NAME
micro \- A modern and intuitive terminal-based text editor
.SH SYNOPSIS
.B micro
.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.
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
.SH OPTIONS
.PP
\-config-dir dir
.RS 4
Specify a custom location for the configuration directory
.RE
.PP
\-startpos LINE,COL
.RS 4
Specify a line and column to start the cursor at when opening a buffer
.RE
.PP
\-options
.RS 4
Show all option help
.RE
.PP
\-version
.RS 4
Show the version number and information
.RE
.SH CONFIGURATION
Micro uses
\fI$XDG_CONFIG_HOME/micro\fR
for configuration by default. If it is not set, micro uses ~/.config/micro.
Two main configuration files are settings.json, containing the user's
settings, and bindings.json, containing the user's custom keybindings.
.SH ENVIRONMENT
Micro's behaviour can be changed by setting environment variables, of which there is currently only one:
\fIMICRO_TRUECOLOR\fR.
When MICRO_TRUECOLOR is set to 1, micro will attempt to treat your terminal as a true-color terminal and will be able to make full use of the true-color colorschemes that are included with micro. If MICRO_TRUECOLOR is not set or is set to 0, then micro will only make use of 256 color features and will internally map true-color colorschemes to the nearest colors available. For more information see micro's documentation.
.SH NOTICE
This manpage is intended only to serve as a quick guide to the invocation of
micro and is not intended to replace the full documentation included with micro
which can be accessed from within micro. Micro tells you what key combination to
press to get help in the lower right.
.SH BUGS
A comprehensive list of bugs will not be listed in this manpage. See the Github
page at \fBhttps://github.com/zyedidia/micro/issues\fP for a list of known bugs
and to report any newly encountered bugs you may find. We strive to correct
bugs as swiftly as possible.
.SH COPYRIGHT
Copyright \(co 2017 Zachary Yedidia, Collin Warren, et al.
See /usr/share/doc/micro/LICENSE and /usr/share/doc/micro/AUTHORS for more information.

View File

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

View File

@@ -7,11 +7,9 @@ import (
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/cmd/micro/shellwords"
"github.com/zyedidia/tcell"
)
@@ -80,7 +78,7 @@ func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
v.Cursor.ResetSelection()
v.Relocate()
}
if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold && (x == v.lastLoc.X && y == v.lastLoc.Y) {
if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
if v.doubleClick {
// Triple click
v.lastClickTime = time.Now()
@@ -121,8 +119,6 @@ func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
}
}
v.lastLoc = Loc{x, y}
if usePlugin {
PostActionCall("MousePress", v, e)
}
@@ -435,11 +431,7 @@ func (v *View) StartOfLine(usePlugin bool) bool {
v.deselect(0)
if v.Cursor.X != 0 {
v.Cursor.Start()
} else {
v.Cursor.StartOfText()
}
v.Cursor.Start()
if usePlugin {
return PostActionCall("StartOfLine", v)
@@ -463,20 +455,6 @@ func (v *View) EndOfLine(usePlugin bool) bool {
return true
}
// SelectLine selects the entire current line
func (v *View) SelectLine(usePlugin bool) bool {
if usePlugin && !PreActionCall("SelectLine", v) {
return false
}
v.Cursor.SelectLine()
if usePlugin {
return PostActionCall("SelectLine", v)
}
return true
}
// SelectToStartOfLine selects to the start of the current line
func (v *View) SelectToStartOfLine(usePlugin bool) bool {
if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
@@ -513,91 +491,6 @@ func (v *View) SelectToEndOfLine(usePlugin bool) bool {
return true
}
// ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
func (v *View) ParagraphPrevious(usePlugin bool) bool {
if usePlugin && !PreActionCall("ParagraphPrevious", v) {
return false
}
var line int
for line = v.Cursor.Y; line > 0; line-- {
if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
v.Cursor.X = 0
v.Cursor.Y = line
break
}
}
// If no empty line found. move cursor to end of buffer
if line == 0 {
v.Cursor.Loc = v.Buf.Start()
}
if usePlugin {
return PostActionCall("ParagraphPrevious", v)
}
return true
}
// ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
func (v *View) ParagraphNext(usePlugin bool) bool {
if usePlugin && !PreActionCall("ParagraphNext", v) {
return false
}
var line int
for line = v.Cursor.Y; line < len(v.Buf.lines); line++ {
if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
v.Cursor.X = 0
v.Cursor.Y = line
break
}
}
// If no empty line found. move cursor to end of buffer
if line == len(v.Buf.lines) {
v.Cursor.Loc = v.Buf.End()
}
if usePlugin {
return PostActionCall("ParagraphNext", v)
}
return true
}
// Retab changes all tabs to spaces or all spaces to tabs depending
// on the user's settings
func (v *View) Retab(usePlugin bool) bool {
if usePlugin && !PreActionCall("Retab", v) {
return false
}
toSpaces := v.Buf.Settings["tabstospaces"].(bool)
tabsize := int(v.Buf.Settings["tabsize"].(float64))
dirty := false
for i := 0; i < v.Buf.NumLines; i++ {
l := v.Buf.Line(i)
ws := GetLeadingWhitespace(l)
if ws != "" {
if toSpaces {
ws = strings.Replace(ws, "\t", Spaces(tabsize), -1)
} else {
ws = strings.Replace(ws, Spaces(tabsize), "\t", -1)
}
}
l = strings.TrimLeft(l, " \t")
v.Buf.lines[i].data = []byte(ws + l)
dirty = true
}
v.Buf.IsModified = dirty
if usePlugin {
return PostActionCall("Retab", v)
}
return true
}
// CursorStart moves the cursor to the start of the buffer
func (v *View) CursorStart(usePlugin bool) bool {
if usePlugin && !PreActionCall("CursorStart", v) {
@@ -700,14 +593,10 @@ func (v *View) InsertNewline(usePlugin bool) bool {
}
ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
cx := v.Cursor.X
v.Buf.Insert(v.Cursor.Loc, "\n")
// v.Cursor.Right()
if v.Buf.Settings["autoindent"].(bool) {
if cx < len(ws) {
ws = ws[0:cx]
}
v.Buf.Insert(v.Cursor.Loc, ws)
// for i := 0; i < len(ws); i++ {
// v.Cursor.Right()
@@ -747,9 +636,9 @@ func (v *View) Backspace(usePlugin bool) bool {
// If the user is using spaces instead of tabs and they are deleting
// whitespace at the start of the line, we should delete as if it's a
// tab (tabSize number of spaces)
lineStart := sliceEnd(v.Buf.LineBytes(v.Cursor.Y), v.Cursor.X)
lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
tabSize := int(v.Buf.Settings["tabsize"].(float64))
if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && utf8.RuneCount(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
loc := v.Cursor.Loc
v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
} else {
@@ -834,15 +723,13 @@ func (v *View) IndentSelection(usePlugin bool) bool {
end := v.Cursor.CurSelection[1]
if end.Y < start.Y {
start, end = end, start
v.Cursor.SetSelectionStart(start)
v.Cursor.SetSelectionEnd(end)
}
startY := start.Y
endY := end.Move(-1, v.Buf).Y
endX := end.Move(-1, v.Buf).X
tabsize := len(v.Buf.IndentString())
for y := startY; y <= endY; y++ {
tabsize := len(v.Buf.IndentString())
v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
if y == startY && start.X > 0 {
v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
@@ -896,8 +783,6 @@ func (v *View) OutdentSelection(usePlugin bool) bool {
end := v.Cursor.CurSelection[1]
if end.Y < start.Y {
start, end = end, start
v.Cursor.SetSelectionStart(start)
v.Cursor.SetSelectionEnd(end)
}
startY := start.Y
@@ -951,7 +836,7 @@ func (v *View) SaveAll(usePlugin bool) bool {
}
for _, t := range tabs {
for _, v := range t.Views {
for _, v := range t.views {
v.Save(false)
}
}
@@ -970,7 +855,7 @@ func (v *View) Save(usePlugin bool) bool {
return false
}
if v.Type.Scratch == true {
if v.Type.scratch == true {
// We can't save any view type with scratch set. eg help and log text
return false
}
@@ -1020,24 +905,19 @@ func (v *View) saveToFile(filename string) {
// SaveAs saves the buffer to disk with the given name
func (v *View) SaveAs(usePlugin bool) bool {
if v.mainCursor() {
if usePlugin && !PreActionCall("SaveAs", v) {
if usePlugin && !PreActionCall("Find", v) {
return false
}
filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
if !canceled {
// the filename might or might not be quoted, so unquote first then join the strings.
args, err := shellwords.Split(filename)
filename = strings.Join(args, " ")
if err != nil {
messenger.Error("Error parsing arguments: ", err)
return false
}
filename = strings.Join(SplitCommandArgs(filename), " ")
v.saveToFile(filename)
}
if usePlugin {
PostActionCall("SaveAs", v)
PostActionCall("Find", v)
}
}
return false
@@ -1219,9 +1099,9 @@ func (v *View) Cut(usePlugin bool) bool {
return PostActionCall("Cut", v)
}
return true
} else {
return v.CutLine(usePlugin)
}
return false
}
// DuplicateLine duplicates the current line or selection
@@ -1380,27 +1260,6 @@ func (v *View) PastePrimary(usePlugin bool) bool {
return true
}
// JumpToMatchingBrace moves the cursor to the matching brace if it is
// currently on a brace
func (v *View) JumpToMatchingBrace(usePlugin bool) bool {
if usePlugin && !PreActionCall("JumpToMatchingBrace", v) {
return false
}
for _, bp := range bracePairs {
r := v.Cursor.RuneUnder(v.Cursor.X)
if r == bp[0] || r == bp[1] {
matchingBrace := v.Buf.FindMatchingBrace(bp, v.Cursor.Loc)
v.Cursor.GotoLoc(matchingBrace)
}
}
if usePlugin {
return PostActionCall("JumpToMatchingBrace", v)
}
return true
}
// SelectAll selects the entire buffer
func (v *View) SelectAll(usePlugin bool) bool {
if usePlugin && !PreActionCall("SelectAll", v) {
@@ -1515,42 +1374,6 @@ func (v *View) PageDown(usePlugin bool) bool {
return false
}
// SelectPageUp selects up one page
func (v *View) SelectPageUp(usePlugin bool) bool {
if usePlugin && !PreActionCall("SelectPageUp", v) {
return false
}
if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = v.Cursor.Loc
}
v.Cursor.UpN(v.Height)
v.Cursor.SelectTo(v.Cursor.Loc)
if usePlugin {
return PostActionCall("SelectPageUp", v)
}
return true
}
// SelectPageDown selects down one page
func (v *View) SelectPageDown(usePlugin bool) bool {
if usePlugin && !PreActionCall("SelectPageDown", v) {
return false
}
if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = v.Cursor.Loc
}
v.Cursor.DownN(v.Height)
v.Cursor.SelectTo(v.Cursor.Loc)
if usePlugin {
return PostActionCall("SelectPageDown", v)
}
return true
}
// CursorPageUp places the cursor a page up
func (v *View) CursorPageUp(usePlugin bool) bool {
if usePlugin && !PreActionCall("CursorPageUp", v) {
@@ -1664,38 +1487,21 @@ func (v *View) JumpLine(usePlugin bool) bool {
}
// Prompt for line number
message := fmt.Sprintf("Jump to line:col (1 - %v) # ", v.Buf.NumLines)
input, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
message := fmt.Sprintf("Jump to line (1 - %v) # ", v.Buf.NumLines)
linestring, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
if canceled {
return false
}
var lineInt int
var colInt int
var err error
if strings.Contains(input, ":") {
split := strings.Split(input, ":")
lineInt, err = strconv.Atoi(split[0])
if err != nil {
messenger.Message("Invalid line number")
return false
}
colInt, err = strconv.Atoi(split[1])
if err != nil {
messenger.Message("Invalid column number")
return false
}
} else {
lineInt, err = strconv.Atoi(input)
if err != nil {
messenger.Message("Invalid line number")
return false
}
lineint, err := strconv.Atoi(linestring)
lineint = lineint - 1 // fix offset
if err != nil {
messenger.Error(err) // return errors
return false
}
lineInt--
// Move cursor and view if possible.
if lineInt < v.Buf.NumLines && lineInt >= 0 {
v.Cursor.X = colInt
v.Cursor.Y = lineInt
if lineint < v.Buf.NumLines && lineint >= 0 {
v.Cursor.X = 0
v.Cursor.Y = lineint
if usePlugin {
return PostActionCall("JumpLine", v)
@@ -1743,25 +1549,6 @@ func (v *View) ToggleHelp(usePlugin bool) bool {
return true
}
// ToggleKeyMenu toggles the keymenu option and resizes all tabs
func (v *View) ToggleKeyMenu(usePlugin bool) bool {
if v.mainCursor() {
if usePlugin && !PreActionCall("ToggleBindings", v) {
return false
}
globalSettings["keymenu"] = !globalSettings["keymenu"].(bool)
for _, tab := range tabs {
tab.Resize()
}
if usePlugin {
return PostActionCall("ToggleBindings", v)
}
}
return true
}
// ShellMode opens a terminal to run a shell command
func (v *View) ShellMode(usePlugin bool) bool {
if v.mainCursor() {
@@ -1800,22 +1587,6 @@ func (v *View) CommandMode(usePlugin bool) bool {
return false
}
// ToggleOverwriteMode lets the user toggle the text overwrite mode
func (v *View) ToggleOverwriteMode(usePlugin bool) bool {
if v.mainCursor() {
if usePlugin && !PreActionCall("ToggleOverwriteMode", v) {
return false
}
v.isOverwriteMode = !v.isOverwriteMode
if usePlugin {
return PostActionCall("ToggleOverwriteMode", v)
}
}
return false
}
// Escape leaves current mode
func (v *View) Escape(usePlugin bool) bool {
if v.mainCursor() {
@@ -1844,12 +1615,12 @@ func (v *View) Quit(usePlugin bool) bool {
// Make sure not to quit if there are unsaved changes
if v.CanClose() {
v.CloseBuffer()
if len(tabs[curTab].Views) > 1 {
if len(tabs[curTab].views) > 1 {
v.splitNode.Delete()
tabs[v.TabNum].Cleanup()
tabs[v.TabNum].Resize()
} else if len(tabs) > 1 {
if len(tabs[v.TabNum].Views) == 1 {
if len(tabs[v.TabNum].views) == 1 {
tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
for i, t := range tabs {
t.SetNum(i)
@@ -1867,7 +1638,6 @@ func (v *View) Quit(usePlugin bool) bool {
}
screen.Fini()
messenger.SaveHistory()
os.Exit(0)
}
}
@@ -1888,7 +1658,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
closeAll := true
for _, tab := range tabs {
for _, v := range tab.Views {
for _, v := range tab.views {
if !v.CanClose() {
closeAll = false
}
@@ -1901,7 +1671,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
if shouldQuit {
for _, tab := range tabs {
for _, v := range tab.Views {
for _, v := range tab.views {
v.CloseBuffer()
}
}
@@ -1911,7 +1681,6 @@ func (v *View) QuitAll(usePlugin bool) bool {
}
screen.Fini()
messenger.SaveHistory()
os.Exit(0)
}
}
@@ -1933,7 +1702,7 @@ func (v *View) AddTab(usePlugin bool) bool {
curTab = len(tabs) - 1
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.Views {
for _, v := range t.views {
v.ToggleTabbar()
}
}
@@ -2026,8 +1795,8 @@ func (v *View) Unsplit(usePlugin bool) bool {
}
curView := tabs[curTab].CurView
for i := len(tabs[curTab].Views) - 1; i >= 0; i-- {
view := tabs[curTab].Views[i]
for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
view := tabs[curTab].views[i]
if view != nil && view.Num != curView {
view.Quit(true)
// messenger.Message("Quit ", view.Buf.Path)
@@ -2049,7 +1818,7 @@ func (v *View) NextSplit(usePlugin bool) bool {
}
tab := tabs[curTab]
if tab.CurView < len(tab.Views)-1 {
if tab.CurView < len(tab.views)-1 {
tab.CurView++
} else {
tab.CurView = 0
@@ -2073,7 +1842,7 @@ func (v *View) PreviousSplit(usePlugin bool) bool {
if tab.CurView > 0 {
tab.CurView--
} else {
tab.CurView = len(tab.Views) - 1
tab.CurView = len(tab.views) - 1
}
if usePlugin {
@@ -2083,6 +1852,68 @@ func (v *View) PreviousSplit(usePlugin bool) bool {
return false
}
func (v *View) WrapBracket(usePlugin bool) bool {
if v.Cursor.HasSelection() {
lockPollEvent = true
event := screen.PollEvent()
lockPollEvent = false
ev, ok := event.(*tcell.EventKey)
if !ok {
return false
}
if !strings.Contains("()[]{}\"'", string(ev.Rune())) {
return false
}
start := v.Cursor.CurSelection[0]
end := v.Cursor.CurSelection[1]
if start.Y != end.Y {
return false
}
close := map[string]string{
")": ")",
"]": "]",
"}": "}",
"(": ")",
"[": "]",
"{": "}",
"\"": "\"",
"'": "'",
}
open := map[string]string{
")": "(",
"]": "[",
"}": "{",
"(": "(",
"[": "[",
"{": "{",
"\"": "\"",
"'": "'",
}
if usePlugin && !PreActionCall("WrapBracket", v) {
return false
}
r := string(ev.Rune())
if start.GreaterThan(end) {
start, end = end, start
v.Buf.Insert(start, open[r])
v.Buf.Insert(end.Move(1, v.Buf), close[r])
v.Cursor.CurSelection[1] = start
} else {
v.Buf.Insert(start, open[r])
v.Buf.Insert(end.Move(1, v.Buf), close[r])
v.Cursor.CurSelection[0] = start
}
if usePlugin {
return PostActionCall("WrapBracket", v)
}
}
return false
}
var curMacro []interface{}
var recordingMacro bool
@@ -2184,54 +2015,6 @@ func (v *View) SpawnMultiCursor(usePlugin bool) bool {
return false
}
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
func (v *View) SpawnMultiCursorSelect(usePlugin bool) bool {
if v.Cursor == &v.Buf.Cursor {
if usePlugin && !PreActionCall("SpawnMultiCursorSelect", v) {
return false
}
// Avoid cases where multiple cursors already exist, that would create problems
if len(v.Buf.cursors) > 1 {
return false
}
var startLine int
var endLine int
a, b := v.Cursor.CurSelection[0].Y, v.Cursor.CurSelection[1].Y
if a > b {
startLine, endLine = b, a
} else {
startLine, endLine = a, b
}
if v.Cursor.HasSelection() {
v.Cursor.ResetSelection()
v.Cursor.GotoLoc(Loc{0, startLine})
for i := startLine; i <= endLine; i++ {
c := &Cursor{
buf: v.Buf,
}
c.GotoLoc(Loc{0, i})
v.Buf.cursors = append(v.Buf.cursors, c)
}
v.Buf.MergeCursors()
v.Buf.UpdateCursors()
} else {
return false
}
if usePlugin {
PostActionCall("SpawnMultiCursorSelect", v)
}
messenger.Message("Added cursors from selection")
}
return false
}
// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
if v.Cursor == &v.Buf.Cursor {

View File

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

View File

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

View File

@@ -1,21 +1,17 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"unicode"
"github.com/flynn/json5"
"github.com/zyedidia/tcell"
)
var bindingsStr map[string]string
var bindings map[Key][]func(*View, bool) bool
var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
var helpBinding string
var kmenuBinding string
var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
"MousePress": (*View).MousePress,
@@ -23,99 +19,91 @@ var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
}
var bindingActions = map[string]func(*View, bool) bool{
"CursorUp": (*View).CursorUp,
"CursorDown": (*View).CursorDown,
"CursorPageUp": (*View).CursorPageUp,
"CursorPageDown": (*View).CursorPageDown,
"CursorLeft": (*View).CursorLeft,
"CursorRight": (*View).CursorRight,
"CursorStart": (*View).CursorStart,
"CursorEnd": (*View).CursorEnd,
"SelectToStart": (*View).SelectToStart,
"SelectToEnd": (*View).SelectToEnd,
"SelectUp": (*View).SelectUp,
"SelectDown": (*View).SelectDown,
"SelectLeft": (*View).SelectLeft,
"SelectRight": (*View).SelectRight,
"WordRight": (*View).WordRight,
"WordLeft": (*View).WordLeft,
"SelectWordRight": (*View).SelectWordRight,
"SelectWordLeft": (*View).SelectWordLeft,
"DeleteWordRight": (*View).DeleteWordRight,
"DeleteWordLeft": (*View).DeleteWordLeft,
"SelectLine": (*View).SelectLine,
"SelectToStartOfLine": (*View).SelectToStartOfLine,
"SelectToEndOfLine": (*View).SelectToEndOfLine,
"ParagraphPrevious": (*View).ParagraphPrevious,
"ParagraphNext": (*View).ParagraphNext,
"InsertNewline": (*View).InsertNewline,
"InsertSpace": (*View).InsertSpace,
"Backspace": (*View).Backspace,
"Delete": (*View).Delete,
"InsertTab": (*View).InsertTab,
"Save": (*View).Save,
"SaveAll": (*View).SaveAll,
"SaveAs": (*View).SaveAs,
"Find": (*View).Find,
"FindNext": (*View).FindNext,
"FindPrevious": (*View).FindPrevious,
"Center": (*View).Center,
"Undo": (*View).Undo,
"Redo": (*View).Redo,
"Copy": (*View).Copy,
"Cut": (*View).Cut,
"CutLine": (*View).CutLine,
"DuplicateLine": (*View).DuplicateLine,
"DeleteLine": (*View).DeleteLine,
"MoveLinesUp": (*View).MoveLinesUp,
"MoveLinesDown": (*View).MoveLinesDown,
"IndentSelection": (*View).IndentSelection,
"OutdentSelection": (*View).OutdentSelection,
"OutdentLine": (*View).OutdentLine,
"Paste": (*View).Paste,
"PastePrimary": (*View).PastePrimary,
"SelectAll": (*View).SelectAll,
"OpenFile": (*View).OpenFile,
"Start": (*View).Start,
"End": (*View).End,
"PageUp": (*View).PageUp,
"PageDown": (*View).PageDown,
"SelectPageUp": (*View).SelectPageUp,
"SelectPageDown": (*View).SelectPageDown,
"HalfPageUp": (*View).HalfPageUp,
"HalfPageDown": (*View).HalfPageDown,
"StartOfLine": (*View).StartOfLine,
"EndOfLine": (*View).EndOfLine,
"ToggleHelp": (*View).ToggleHelp,
"ToggleKeyMenu": (*View).ToggleKeyMenu,
"ToggleRuler": (*View).ToggleRuler,
"JumpLine": (*View).JumpLine,
"ClearStatus": (*View).ClearStatus,
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"ToggleOverwriteMode": (*View).ToggleOverwriteMode,
"Escape": (*View).Escape,
"Quit": (*View).Quit,
"QuitAll": (*View).QuitAll,
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
"Unsplit": (*View).Unsplit,
"VSplit": (*View).VSplitBinding,
"HSplit": (*View).HSplitBinding,
"ToggleMacro": (*View).ToggleMacro,
"PlayMacro": (*View).PlayMacro,
"Suspend": (*View).Suspend,
"ScrollUp": (*View).ScrollUpAction,
"ScrollDown": (*View).ScrollDownAction,
"SpawnMultiCursor": (*View).SpawnMultiCursor,
"SpawnMultiCursorSelect": (*View).SpawnMultiCursorSelect,
"RemoveMultiCursor": (*View).RemoveMultiCursor,
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
"SkipMultiCursor": (*View).SkipMultiCursor,
"JumpToMatchingBrace": (*View).JumpToMatchingBrace,
"CursorUp": (*View).CursorUp,
"CursorDown": (*View).CursorDown,
"CursorPageUp": (*View).CursorPageUp,
"CursorPageDown": (*View).CursorPageDown,
"CursorLeft": (*View).CursorLeft,
"CursorRight": (*View).CursorRight,
"CursorStart": (*View).CursorStart,
"CursorEnd": (*View).CursorEnd,
"SelectToStart": (*View).SelectToStart,
"SelectToEnd": (*View).SelectToEnd,
"SelectUp": (*View).SelectUp,
"SelectDown": (*View).SelectDown,
"SelectLeft": (*View).SelectLeft,
"SelectRight": (*View).SelectRight,
"WordRight": (*View).WordRight,
"WordLeft": (*View).WordLeft,
"SelectWordRight": (*View).SelectWordRight,
"SelectWordLeft": (*View).SelectWordLeft,
"DeleteWordRight": (*View).DeleteWordRight,
"DeleteWordLeft": (*View).DeleteWordLeft,
"SelectToStartOfLine": (*View).SelectToStartOfLine,
"SelectToEndOfLine": (*View).SelectToEndOfLine,
"InsertNewline": (*View).InsertNewline,
"InsertSpace": (*View).InsertSpace,
"Backspace": (*View).Backspace,
"Delete": (*View).Delete,
"InsertTab": (*View).InsertTab,
"Save": (*View).Save,
"SaveAll": (*View).SaveAll,
"SaveAs": (*View).SaveAs,
"Find": (*View).Find,
"FindNext": (*View).FindNext,
"FindPrevious": (*View).FindPrevious,
"Center": (*View).Center,
"Undo": (*View).Undo,
"Redo": (*View).Redo,
"Copy": (*View).Copy,
"Cut": (*View).Cut,
"CutLine": (*View).CutLine,
"DuplicateLine": (*View).DuplicateLine,
"DeleteLine": (*View).DeleteLine,
"MoveLinesUp": (*View).MoveLinesUp,
"MoveLinesDown": (*View).MoveLinesDown,
"IndentSelection": (*View).IndentSelection,
"OutdentSelection": (*View).OutdentSelection,
"OutdentLine": (*View).OutdentLine,
"Paste": (*View).Paste,
"PastePrimary": (*View).PastePrimary,
"SelectAll": (*View).SelectAll,
"OpenFile": (*View).OpenFile,
"WrapBracket": (*View).WrapBracket,
"Start": (*View).Start,
"End": (*View).End,
"PageUp": (*View).PageUp,
"PageDown": (*View).PageDown,
"HalfPageUp": (*View).HalfPageUp,
"HalfPageDown": (*View).HalfPageDown,
"StartOfLine": (*View).StartOfLine,
"EndOfLine": (*View).EndOfLine,
"ToggleHelp": (*View).ToggleHelp,
"ToggleRuler": (*View).ToggleRuler,
"JumpLine": (*View).JumpLine,
"ClearStatus": (*View).ClearStatus,
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"Escape": (*View).Escape,
"Quit": (*View).Quit,
"QuitAll": (*View).QuitAll,
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
"Unsplit": (*View).Unsplit,
"VSplit": (*View).VSplitBinding,
"HSplit": (*View).HSplitBinding,
"ToggleMacro": (*View).ToggleMacro,
"PlayMacro": (*View).PlayMacro,
"Suspend": (*View).Suspend,
"ScrollUp": (*View).ScrollUpAction,
"ScrollDown": (*View).ScrollDownAction,
"SpawnMultiCursor": (*View).SpawnMultiCursor,
"RemoveMultiCursor": (*View).RemoveMultiCursor,
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
"SkipMultiCursor": (*View).SkipMultiCursor,
// This was changed to InsertNewline but I don't want to break backwards compatibility
"InsertEnter": (*View).InsertNewline,
@@ -257,7 +245,6 @@ var bindingKeys = map[string]tcell.Key{
"Escape": tcell.KeyEscape,
"Enter": tcell.KeyEnter,
"Backspace": tcell.KeyBackspace2,
"OldBackspace": tcell.KeyBackspace,
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
"PgUp": tcell.KeyPgUp,
@@ -270,13 +257,11 @@ type Key struct {
modifiers tcell.ModMask
buttons tcell.ButtonMask
r rune
escape string
}
// InitBindings initializes the keybindings for micro
func InitBindings() {
bindings = make(map[Key][]func(*View, bool) bool)
bindingsStr = make(map[string]string)
mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
var parsed map[string]string
@@ -328,29 +313,16 @@ modSearch:
case strings.HasPrefix(k, "Shift"):
k = k[5:]
modifiers |= tcell.ModShift
case strings.HasPrefix(k, "\x1b"):
return Key{
keyCode: -1,
modifiers: modifiers,
buttons: -1,
r: 0,
escape: k,
}, true
default:
break modSearch
}
}
if len(k) == 0 {
return Key{buttons: -1}, false
}
// Control is handled specially, since some character codes in bindingKeys
// are different when Control is depressed. We should check for Control keys
// first.
if modifiers&tcell.ModCtrl != 0 {
// see if the key is in bindingKeys with the Ctrl prefix.
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
if code, ok := bindingKeys["Ctrl"+k]; ok {
// It is, we're done.
return Key{
@@ -416,43 +388,6 @@ func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
return action
}
// TryBindKey tries to bind a key by writing to configDir/bindings.json
// This function is unused for now
func TryBindKey(k, v string) {
filename := configDir + "/bindings.json"
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
TermMessage("Error reading bindings.json file: " + err.Error())
return
}
conflict := -1
lines := strings.Split(string(input), "\n")
for i, l := range lines {
parts := strings.Split(l, ":")
if len(parts) >= 2 {
if strings.Contains(parts[0], k) {
conflict = i
TermMessage("Warning: Keybinding conflict:", k, " has been overwritten")
}
}
}
binding := fmt.Sprintf(" \"%s\": \"%s\",", k, v)
if conflict == -1 {
lines = append([]string{lines[0], binding}, lines[conflict:]...)
} else {
lines = append(append(lines[:conflict], binding), lines[conflict+1:]...)
}
txt := strings.Join(lines, "\n")
err = ioutil.WriteFile(filename, []byte(txt), 0644)
if err != nil {
TermMessage("Error")
}
}
}
// BindKey takes a key and an action and binds the two together
func BindKey(k, v string) {
key, ok := findKey(k)
@@ -463,21 +398,14 @@ func BindKey(k, v string) {
if v == "ToggleHelp" {
helpBinding = k
}
if v == "ToggleKeyMenu" {
kmenuBinding = k
}
if helpBinding == k && v != "ToggleHelp" {
helpBinding = ""
}
if kmenuBinding == k && v != "ToggleKeyMenu" {
kmenuBinding = ""
}
actionNames := strings.Split(v, ",")
if actionNames[0] == "UnbindKey" {
delete(bindings, key)
delete(mouseBindings, key)
delete(bindingsStr, k)
if len(actionNames) == 1 {
return
}
@@ -488,12 +416,6 @@ func BindKey(k, v string) {
for _, actionName := range actionNames {
if strings.HasPrefix(actionName, "Mouse") {
mouseActions = append(mouseActions, findMouseAction(actionName))
} else if strings.HasPrefix(actionName, "command:") {
cmd := strings.SplitN(actionName, ":", 2)[1]
actions = append(actions, CommandAction(cmd))
} else if strings.HasPrefix(actionName, "command-edit:") {
cmd := strings.SplitN(actionName, ":", 2)[1]
actions = append(actions, CommandEditAction(cmd))
} else {
actions = append(actions, findAction(actionName))
}
@@ -503,7 +425,6 @@ func BindKey(k, v string) {
// Can't have a binding be both mouse and normal
delete(mouseBindings, key)
bindings[key] = actions
bindingsStr[k] = v
} else if len(mouseActions) > 0 {
// Can't have a binding be both mouse and normal
delete(bindings, key)
@@ -518,6 +439,7 @@ func DefaultBindings() map[string]string {
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"Alt-j": "WrapBracket",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
@@ -538,8 +460,6 @@ func DefaultBindings() map[string]string {
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
@@ -572,7 +492,6 @@ func DefaultBindings() map[string]string {
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"CtrlR": "ToggleRuler",
"CtrlL": "JumpLine",
"Delete": "Delete",
@@ -582,7 +501,6 @@ func DefaultBindings() map[string]string {
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
@@ -593,6 +511,7 @@ func DefaultBindings() map[string]string {
// "Alt-n": "CursorDown",
// Integration with file managers
"F1": "ToggleHelp",
"F2": "Save",
"F3": "Find",
"F4": "Quit",
@@ -608,7 +527,6 @@ func DefaultBindings() map[string]string {
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",

View File

@@ -1,28 +1,24 @@
package main
import (
"bufio"
"bytes"
"crypto/md5"
"encoding/gob"
"errors"
"io"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
"github.com/mitchellh/go-homedir"
"github.com/zyedidia/micro/cmd/micro/highlight"
)
const LargeFileThreshold = 50000
var (
// 0 - no line type detected
// 1 - lf detected
@@ -56,15 +52,11 @@ type Buffer struct {
// Stores the last modification time of the file the buffer is pointing to
ModTime time.Time
// NumLines is the number of lines in the buffer
NumLines int
syntaxDef *highlight.Def
highlighter *highlight.Highlighter
// Hash of the original buffer -- empty if fastdirty is on
origHash [md5.Size]byte
// Buffer local settings
Settings map[string]interface{}
}
@@ -77,44 +69,15 @@ type SerializedBuffer struct {
ModTime time.Time
}
// NewBufferFromFile opens a new buffer using the given path
// It will also automatically handle `~`, and line/column with filename:l:c
// It will return an empty buffer if the path does not exist
// and an error if the file is a directory
func NewBufferFromFile(path string) (*Buffer, error) {
filename, cursorPosition := GetPathAndCursorPosition(path)
filename = ReplaceHome(filename)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
if err == nil && fileInfo.IsDir() {
return nil, errors.New(filename + " is a directory")
}
defer file.Close()
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBufferFromString("", filename)
} else {
buf = NewBuffer(file, FSize(file), filename, cursorPosition)
}
return buf, nil
}
// NewBufferFromString creates a new buffer containing the given string
func NewBufferFromString(text, path string) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, nil)
return NewBuffer(strings.NewReader(text), int64(len(text)), path)
}
// NewBuffer creates a new buffer from a given reader with a given path
func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string) *Buffer {
// check if the file is already open in a tab. If it's open return the buffer to that tab
func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
if path != "" {
for _, tab := range tabs {
for _, view := range tab.Views {
for _, view := range tab.views {
if view.Buf.Path == path {
return view.Buf
}
@@ -155,19 +118,47 @@ func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []strin
os.Mkdir(configDir+"/buffers/", os.ModePerm)
}
cursorLocation, cursorLocationError := GetBufferCursorLocation(cursorPosition, b)
// Put the cursor at the first spot
cursorStartX := 0
cursorStartY := 0
// If -startpos LINE,COL was passed, use start position LINE,COL
if len(*flagStartPos) > 0 {
positions := strings.Split(*flagStartPos, ",")
if len(positions) == 2 {
lineNum, errPos1 := strconv.Atoi(positions[0])
colNum, errPos2 := strconv.Atoi(positions[1])
if errPos1 == nil && errPos2 == nil {
cursorStartX = colNum
cursorStartY = lineNum - 1
// Check to avoid line overflow
if cursorStartY > b.NumLines {
cursorStartY = b.NumLines - 1
} else if cursorStartY < 0 {
cursorStartY = 0
}
// Check to avoid column overflow
if cursorStartX > len(b.Line(cursorStartY)) {
cursorStartX = len(b.Line(cursorStartY))
} else if cursorStartX < 0 {
cursorStartX = 0
}
}
}
}
b.Cursor = Cursor{
Loc: cursorLocation,
Loc: Loc{
X: cursorStartX,
Y: cursorStartY,
},
buf: b,
}
InitLocalSettings(b)
if cursorLocationError != nil && len(*flagStartPos) == 0 && (b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool)) {
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
// If either savecursor or saveundo is turned on, we need to load the serialized information
// from ~/.config/micro/buffers
file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
defer file.Close()
if err == nil {
var buffer SerializedBuffer
decoder := gob.NewDecoder(file)
@@ -183,22 +174,18 @@ func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []strin
}
if b.Settings["saveundo"].(bool) {
// We should only use last time's eventhandler if the file wasn't modified by someone else in the meantime
// We should only use last time's eventhandler if the file wasn't by someone else in the meantime
if b.ModTime == buffer.ModTime {
b.EventHandler = buffer.EventHandler
b.EventHandler.buf = b
}
}
}
file.Close()
}
if !b.Settings["fastdirty"].(bool) {
if size > LargeFileThreshold {
// If the file is larger than a megabyte fastdirty needs to be on
b.Settings["fastdirty"] = true
} else {
calcHash(b, &b.origHash)
}
if b.Settings["mouse"].(bool) {
screen.EnableMouse()
}
b.cursors = []*Cursor{&b.Cursor}
@@ -206,54 +193,6 @@ func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []strin
return b
}
func GetBufferCursorLocation(cursorPosition []string, b *Buffer) (Loc, error) {
// parse the cursor position. The cursor location is ALWAYS initialised to 0, 0 even when
// an error occurs due to lack of arguments or because the arguments are not numbers
cursorLocation, cursorLocationError := ParseCursorLocation(cursorPosition)
// Put the cursor at the first spot. In the logic for cursor position the -startpos
// flag is processed first and will overwrite any line/col parameters with colons after the filename
if len(*flagStartPos) > 0 || cursorLocationError == nil {
var lineNum, colNum int
var errPos1, errPos2 error
positions := strings.Split(*flagStartPos, ",")
// if the -startpos flag contains enough args use them for the cursor location.
// In this case args passed at the end of the filename will be ignored
if len(positions) == 2 {
lineNum, errPos1 = strconv.Atoi(positions[0])
colNum, errPos2 = strconv.Atoi(positions[1])
}
// if -startpos has invalid arguments, use the arguments from filename.
// This will have a default value (0, 0) even when the filename arguments are invalid
if errPos1 != nil || errPos2 != nil || len(*flagStartPos) == 0 {
// otherwise check if there are any arguments after the filename and use them
lineNum = cursorLocation.Y
colNum = cursorLocation.X
}
// if some arguments were found make sure they don't go outside the file and cause overflows
cursorLocation.Y = lineNum - 1
cursorLocation.X = colNum
// Check to avoid line overflow
if cursorLocation.Y > b.NumLines-1 {
cursorLocation.Y = b.NumLines - 1
} else if cursorLocation.Y < 0 {
cursorLocation.Y = 0
}
// Check to avoid column overflow
if cursorLocation.X > len(b.Line(cursorLocation.Y)) {
cursorLocation.X = len(b.Line(cursorLocation.Y))
} else if cursorLocation.X < 0 {
cursorLocation.X = 0
}
}
return cursorLocation, cursorLocationError
}
// GetName returns the name that should be displayed in the statusline
// for this buffer
func (b *Buffer) GetName() string {
if b.name == "" {
if b.Path == "" {
@@ -386,8 +325,6 @@ func (b *Buffer) Update() {
b.NumLines = len(b.lines)
}
// MergeCursors merges any cursors that are at the same position
// into one cursor
func (b *Buffer) MergeCursors() {
var cursors []*Cursor
for i := 0; i < len(b.cursors); i++ {
@@ -404,17 +341,8 @@ func (b *Buffer) MergeCursors() {
}
b.cursors = cursors
for i := range b.cursors {
b.cursors[i].Num = i
}
if b.curCursor >= len(b.cursors) {
b.curCursor = len(b.cursors) - 1
}
}
// UpdateCursors updates all the cursors indicies
func (b *Buffer) UpdateCursors() {
for i, c := range b.cursors {
c.Num = i
@@ -433,167 +361,56 @@ func (b *Buffer) SaveWithSudo() error {
// Serialize serializes the buffer to configDir/buffers
func (b *Buffer) Serialize() error {
if !b.Settings["savecursor"].(bool) && !b.Settings["saveundo"].(bool) {
return nil
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
file, err := os.Create(configDir + "/buffers/" + EscapePath(b.AbsPath))
if err == nil {
enc := gob.NewEncoder(file)
gob.Register(TextEvent{})
err = enc.Encode(SerializedBuffer{
b.EventHandler,
b.Cursor,
b.ModTime,
})
}
err = file.Close()
return err
}
name := configDir + "/buffers/" + EscapePath(b.AbsPath)
return overwriteFile(name, func(file io.Writer) error {
return gob.NewEncoder(file).Encode(SerializedBuffer{
b.EventHandler,
b.Cursor,
b.ModTime,
})
})
}
func init() {
gob.Register(TextEvent{})
gob.Register(SerializedBuffer{})
return nil
}
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
func (b *Buffer) SaveAs(filename string) error {
b.UpdateRules()
dir, _ := homedir.Dir()
if b.Settings["rmtrailingws"].(bool) {
for i, l := range b.lines {
pos := len(bytes.TrimRightFunc(l.data, unicode.IsSpace))
if pos < len(l.data) {
b.deleteToEnd(Loc{pos, i})
r, _ := regexp.Compile(`[ \t]+$`)
for lineNum, line := range b.Lines(0, b.NumLines) {
indices := r.FindStringIndex(line)
if indices == nil {
continue
}
startLoc := Loc{indices[0], lineNum}
b.deleteToEnd(startLoc)
}
b.Cursor.Relocate()
}
if b.Settings["eofnewline"].(bool) {
end := b.End()
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
b.Insert(end, "\n")
}
}
defer func() {
str := b.SaveString(b.Settings["fileformat"] == "dos")
data := []byte(str)
err := ioutil.WriteFile(filename, data, 0644)
if err == nil {
b.Path = strings.Replace(filename, "~", dir, 1)
b.IsModified = false
b.ModTime, _ = GetModTime(filename)
}()
// Removes any tilde and replaces with the absolute path to home
absFilename := ReplaceHome(filename)
// Get the leading path to the file | "." is returned if there's no leading path provided
if dirname := filepath.Dir(absFilename); dirname != "." {
// Check if the parent dirs don't exist
if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
// Prompt to make sure they want to create the dirs that are missing
if yes, canceled := messenger.YesNoPrompt("Parent folders \"" + dirname + "\" do not exist. Create them? (y,n)"); yes && !canceled {
// Create all leading dir(s) since they don't exist
if mkdirallErr := os.MkdirAll(dirname, os.ModePerm); mkdirallErr != nil {
// If there was an error creating the dirs
return mkdirallErr
}
} else {
// If they canceled the creation of leading dirs
return errors.New("Save aborted")
}
}
return b.Serialize()
}
var fileSize int
err := overwriteFile(absFilename, func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
}
// end of line
var eol []byte
if b.Settings["fileformat"] == "dos" {
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 != nil {
return err
}
if !b.Settings["fastdirty"].(bool) {
if fileSize > LargeFileThreshold {
// For large files 'fastdirty' needs to be on
b.Settings["fastdirty"] = true
} else {
calcHash(b, &b.origHash)
}
}
b.Path = filename
b.IsModified = false
return b.Serialize()
}
// 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, fn func(io.Writer) error) (err error) {
var file *os.File
if file, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return
}
defer func() {
if e := file.Close(); e != nil && err == nil {
err = e
}
}()
w := bufio.NewWriter(file)
if err = fn(w); err != nil {
return
}
err = w.Flush()
return
}
// calcHash calculates md5 hash of all lines in the buffer
func calcHash(b *Buffer, out *[md5.Size]byte) {
h := md5.New()
if len(b.lines) > 0 {
h.Write(b.lines[0].data)
for _, l := range b.lines[1:] {
h.Write([]byte{'\n'})
h.Write(l.data)
}
}
h.Sum((*out)[:0])
b.ModTime, _ = GetModTime(filename)
return err
}
// SaveAsWithSudo is the same as SaveAs except it uses a neat trick
@@ -607,7 +424,7 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
screen = nil
// Set up everything for the command
cmd := exec.Command(globalSettings["sucmd"].(string), "tee", filename)
cmd := exec.Command("sudo", "tee", filename)
cmd.Stdin = bytes.NewBufferString(b.SaveString(b.Settings["fileformat"] == "dos"))
// This is a trap for Ctrl-C so that it doesn't kill micro
@@ -634,19 +451,6 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
return err
}
// Modified returns if this buffer has been modified since
// being opened
func (b *Buffer) Modified() bool {
if b.Settings["fastdirty"].(bool) {
return b.IsModified
}
var buff [md5.Size]byte
calcHash(b, &buff)
return buff != b.origHash
}
func (b *Buffer) insert(pos Loc, value []byte) {
b.IsModified = true
b.LineArray.insert(pos, value)
@@ -676,29 +480,13 @@ func (b *Buffer) End() Loc {
// RuneAt returns the rune at a given location in the buffer
func (b *Buffer) RuneAt(loc Loc) rune {
line := b.LineRunes(loc.Y)
line := []rune(b.Line(loc.Y))
if len(line) > 0 {
return line[loc.X]
}
return '\n'
}
// LineBytes returns a single line as an array of runes
func (b *Buffer) LineBytes(n int) []byte {
if n >= len(b.lines) {
return []byte{}
}
return b.lines[n].data
}
// LineRunes returns a single line as an array of runes
func (b *Buffer) LineRunes(n int) []rune {
if n >= len(b.lines) {
return []rune{}
}
return toRunes(b.lines[n].data)
}
// Line returns a single line
func (b *Buffer) Line(n int) string {
if n >= len(b.lines) {
@@ -707,7 +495,6 @@ func (b *Buffer) Line(n int) string {
return string(b.lines[n].data)
}
// LinesNum returns the number of lines in the buffer
func (b *Buffer) LinesNum() int {
return len(b.lines)
}
@@ -723,16 +510,8 @@ func (b *Buffer) Lines(start, end int) []string {
}
// Len gives the length of the buffer
func (b *Buffer) Len() (n int) {
for _, l := range b.lines {
n += utf8.RuneCount(l.data)
}
if len(b.lines) > 1 {
n += len(b.lines) - 1 // account for newlines
}
return
func (b *Buffer) Len() int {
return Count(b.String())
}
// MoveLinesUp moves the range of lines up one row
@@ -796,58 +575,3 @@ func (b *Buffer) clearCursors() {
b.UpdateCursors()
b.Cursor.ResetSelection()
}
var bracePairs = [][2]rune{
{'(', ')'},
{'{', '}'},
{'[', ']'},
}
// FindMatchingBrace returns the location in the buffer of the matching bracket
// It is given a brace type containing the open and closing character, (for example
// '{' and '}') as well as the location to match from
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) Loc {
curLine := b.LineRunes(start.Y)
startChar := curLine[start.X]
var i int
if startChar == braceType[0] {
for y := start.Y; y < b.NumLines; y++ {
l := b.LineRunes(y)
xInit := 0
if y == start.Y {
xInit = start.X
}
for x := xInit; x < len(l); x++ {
r := l[x]
if r == braceType[0] {
i++
} else if r == braceType[1] {
i--
if i == 0 {
return Loc{x, y}
}
}
}
}
} else if startChar == braceType[1] {
for y := start.Y; y >= 0; y-- {
l := []rune(string(b.lines[y].data))
xInit := len(l) - 1
if y == start.Y {
xInit = start.X
}
for x := xInit; x >= 0; x-- {
r := l[x]
if r == braceType[0] {
i--
if i == 0 {
return Loc{x, y}
}
} else if r == braceType[1] {
i++
}
}
}
}
return start
}

View File

@@ -1,117 +0,0 @@
package main
import (
"testing"
)
func TestGetBufferCursorLocationEmptyArgs(t *testing.T) {
buf := NewBufferFromString("this is my\nbuffer\nfile\nhello", "")
location, err := GetBufferCursorLocation(nil, buf)
assertEqual(t, 0, location.Y)
assertEqual(t, 0, location.X)
// an error is present due to the cursorLocation being nil
assertTrue(t, err != nil)
}
func TestGetBufferCursorLocationStartposFlag(t *testing.T) {
buf := NewBufferFromString("this is my\nbuffer\nfile\nhello", "")
*flagStartPos = "1,2"
location, err := GetBufferCursorLocation(nil, buf)
// note: 1 is subtracted from the line to get the correct index in the buffer
assertTrue(t, 0 == location.Y)
assertTrue(t, 2 == location.X)
// an error is present due to the cursorLocation being nil
assertTrue(t, err != nil)
}
func TestGetBufferCursorLocationInvalidStartposFlag(t *testing.T) {
buf := NewBufferFromString("this is my\nbuffer\nfile\nhello", "")
*flagStartPos = "apples,2"
location, err := GetBufferCursorLocation(nil, buf)
// expect to default to the start of the file, which is 0,0
assertEqual(t, 0, location.Y)
assertEqual(t, 0, location.X)
// an error is present due to the cursorLocation being nil
assertTrue(t, err != nil)
}
func TestGetBufferCursorLocationStartposFlagAndCursorPosition(t *testing.T) {
text := "this is my\nbuffer\nfile\nhello"
cursorPosition := []string{"3", "1"}
buf := NewBufferFromString(text, "")
*flagStartPos = "1,2"
location, err := GetBufferCursorLocation(cursorPosition, buf)
// expect to have the flag positions, not the cursor position
// note: 1 is subtracted from the line to get the correct index in the buffer
assertEqual(t, 0, location.Y)
assertEqual(t, 2, location.X)
assertTrue(t, err == nil)
}
func TestGetBufferCursorLocationCursorPositionAndInvalidStartposFlag(t *testing.T) {
text := "this is my\nbuffer\nfile\nhello"
cursorPosition := []string{"3", "1"}
buf := NewBufferFromString(text, "")
*flagStartPos = "apples,2"
location, err := GetBufferCursorLocation(cursorPosition, buf)
// expect to have the flag positions, not the cursor position
// note: 1 is subtracted from the line to get the correct index in the buffer
assertEqual(t, 2, location.Y)
assertEqual(t, 1, location.X)
// no errors this time as cursorPosition is not nil
assertTrue(t, err == nil)
}
func TestGetBufferCursorLocationNoErrorWhenOverflowWithStartpos(t *testing.T) {
text := "this is my\nbuffer\nfile\nhello"
buf := NewBufferFromString(text, "")
*flagStartPos = "50,50"
location, err := GetBufferCursorLocation(nil, buf)
// expect to have the flag positions, not the cursor position
assertEqual(t, buf.NumLines-1, location.Y)
assertEqual(t, 5, location.X)
// error is expected as cursorPosition is nil
assertTrue(t, err != nil)
}
func TestGetBufferCursorLocationNoErrorWhenOverflowWithCursorPosition(t *testing.T) {
text := "this is my\nbuffer\nfile\nhello"
cursorPosition := []string{"50", "2"}
*flagStartPos = ""
buf := NewBufferFromString(text, "")
location, err := GetBufferCursorLocation(cursorPosition, buf)
// expect to have the flag positions, not the cursor position
assertEqual(t, buf.NumLines-1, location.Y)
assertEqual(t, 2, location.X)
// error is expected as cursorPosition is nil
assertTrue(t, err == nil)
}
//func TestGetBufferCursorLocationColonArgs(t *testing.T) {
// buf := new(Buffer)
//}

View File

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

View File

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

View File

@@ -1,17 +1,20 @@
package main
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"unicode/utf8"
humanize "github.com/dustin/go-humanize"
"github.com/zyedidia/micro/cmd/micro/shellwords"
"github.com/mitchellh/go-homedir"
)
// A Command contains a action (a function to call) as well as information about how to autocomplete the command
@@ -35,7 +38,6 @@ func init() {
"Set": Set,
"SetLocal": SetLocal,
"Show": Show,
"ShowKey": ShowKey,
"Run": Run,
"Bind": Bind,
"Quit": Quit,
@@ -54,10 +56,7 @@ func init() {
"Pwd": Pwd,
"Open": Open,
"TabSwitch": TabSwitch,
"Term": Term,
"MemUsage": MemUsage,
"Retab": Retab,
"Raw": Raw,
}
}
@@ -91,10 +90,9 @@ func MakeCommand(name, function string, completions ...Completion) {
// DefaultCommands returns a map containing micro's default commands
func DefaultCommands() map[string]StrCommand {
return map[string]StrCommand{
"set": {"Set", []Completion{OptionCompletion, OptionValueCompletion}},
"setlocal": {"SetLocal", []Completion{OptionCompletion, OptionValueCompletion}},
"set": {"Set", []Completion{OptionCompletion, NoCompletion}},
"setlocal": {"SetLocal", []Completion{OptionCompletion, NoCompletion}},
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
"showkey": {"ShowKey", []Completion{NoCompletion}},
"bind": {"Bind", []Completion{NoCompletion}},
"run": {"Run", []Completion{NoCompletion}},
"quit": {"Quit", []Completion{NoCompletion}},
@@ -113,32 +111,7 @@ func DefaultCommands() map[string]StrCommand {
"pwd": {"Pwd", []Completion{NoCompletion}},
"open": {"Open", []Completion{FileCompletion}},
"tabswitch": {"TabSwitch", []Completion{NoCompletion}},
"term": {"Term", []Completion{NoCompletion}},
"memusage": {"MemUsage", []Completion{NoCompletion}},
"retab": {"Retab", []Completion{NoCompletion}},
"raw": {"Raw", []Completion{NoCompletion}},
}
}
// CommandEditAction returns a bindable function that opens a prompt with
// the given string and executes the command when the user presses
// enter
func CommandEditAction(prompt string) func(*View, bool) bool {
return func(v *View, usePlugin bool) bool {
input, canceled := messenger.Prompt("> ", prompt, "Command", CommandCompletion)
if !canceled {
HandleCommand(input)
}
return false
}
}
// CommandAction returns a bindable function which executes the
// given command
func CommandAction(cmd string) func(*View, bool) bool {
return func(v *View, usePlugin bool) bool {
HandleCommand(cmd)
return false
}
}
@@ -177,7 +150,7 @@ func PluginCmd(args []string) {
continue
}
}
if !IsSpaces([]byte(removed)) {
if !IsSpaces(removed) {
messenger.Message("Removed ", removed)
} else {
messenger.Error("The requested plugins do not exist")
@@ -225,34 +198,6 @@ func PluginCmd(args []string) {
}
}
// Retab changes all spaces to tabs or all tabs to spaces
// depending on the user's settings
func Retab(args []string) {
CurView().Retab(true)
}
// Raw opens a new raw view which displays the escape sequences micro
// is receiving in real-time
func Raw(args []string) {
buf := NewBufferFromString("", "Raw events")
view := NewView(buf)
view.Buf.Insert(view.Cursor.Loc, "Warning: Showing raw event escape codes\n")
view.Buf.Insert(view.Cursor.Loc, "Use CtrlQ to exit\n")
view.Type = vtRaw
tab := NewTabFromView(view)
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
curTab = len(tabs) - 1
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.Views {
v.ToggleTabbar()
}
}
}
}
// TabSwitch switches to a given tab either by name or by number
func TabSwitch(args []string) {
if len(args) > 0 {
@@ -262,7 +207,7 @@ func TabSwitch(args []string) {
found := false
for _, t := range tabs {
v := t.Views[t.CurView]
v := t.views[t.CurView]
if v.Buf.GetName() == args[0] {
curTab = v.TabNum
found = true
@@ -285,19 +230,12 @@ func TabSwitch(args []string) {
// Cd changes the current working directory
func Cd(args []string) {
if len(args) > 0 {
path := ReplaceHome(args[0])
err := os.Chdir(path)
if err != nil {
messenger.Error("Error with cd: ", err)
return
}
wd, _ := os.Getwd()
home, _ := homedir.Dir()
path := strings.Replace(args[0], "~", home, 1)
os.Chdir(path)
for _, tab := range tabs {
for _, view := range tab.Views {
if len(view.Buf.name) == 0 {
continue
}
for _, view := range tab.views {
wd, _ := os.Getwd()
view.Buf.Path, _ = MakeRelative(view.Buf.AbsPath, wd)
if p, _ := filepath.Abs(view.Buf.Path); !strings.Contains(p, wd) {
view.Buf.Path = view.Buf.AbsPath
@@ -336,12 +274,7 @@ func Open(args []string) {
if len(args) > 0 {
filename := args[0]
// the filename might or might not be quoted, so unquote first then join the strings.
args, err := shellwords.Split(filename)
if err != nil {
messenger.Error("Error parsing args ", err)
return
}
filename = strings.Join(args, " ")
filename = strings.Join(SplitCommandArgs(filename), " ")
CurView().Open(filename)
} else {
@@ -391,11 +324,26 @@ func VSplit(args []string) {
if len(args) == 0 {
CurView().VSplit(NewBufferFromString("", ""))
} else {
buf, err := NewBufferFromFile(args[0])
if err != nil {
messenger.Error(err)
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
if err == nil && fileInfo.IsDir() {
messenger.Error(filename, " is a directory")
return
}
defer file.Close()
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBufferFromString("", filename)
} else {
buf = NewBuffer(file, FSize(file), filename)
}
CurView().VSplit(buf)
}
}
@@ -406,11 +354,26 @@ func HSplit(args []string) {
if len(args) == 0 {
CurView().HSplit(NewBufferFromString("", ""))
} else {
buf, err := NewBufferFromFile(args[0])
if err != nil {
messenger.Error(err)
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
if err == nil && fileInfo.IsDir() {
messenger.Error(filename, " is a directory")
return
}
defer file.Close()
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBufferFromString("", filename)
} else {
buf = NewBuffer(file, FSize(file), filename)
}
CurView().HSplit(buf)
}
}
@@ -432,19 +395,33 @@ func NewTab(args []string) {
if len(args) == 0 {
CurView().AddTab(true)
} else {
buf, err := NewBufferFromFile(args[0])
if err != nil {
messenger.Error(err)
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
if err == nil && fileInfo.IsDir() {
messenger.Error(filename, " is a directory")
return
}
defer file.Close()
var buf *Buffer
if err != nil {
buf = NewBufferFromString("", filename)
} else {
buf = NewBuffer(file, FSize(file), filename)
}
tab := NewTabFromView(NewView(buf))
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
curTab = len(tabs) - 1
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.Views {
for _, v := range t.views {
v.ToggleTabbar()
}
}
@@ -498,20 +475,6 @@ func Show(args []string) {
messenger.Message(option)
}
// ShowKey displays the action that a key is bound to
func ShowKey(args []string) {
if len(args) < 1 {
messenger.Error("Please provide a key to show")
return
}
if action, ok := bindingsStr[args[0]]; ok {
messenger.Message(action)
} else {
messenger.Message(args[0], " has no binding")
}
}
// Bind creates a new keybinding
func Bind(args []string) {
if len(args) < 2 {
@@ -524,7 +487,7 @@ func Bind(args []string) {
// Run runs a shell command in the background
func Run(args []string) {
// Run a shell command in the background (openTerm is false)
HandleShellCommand(shellwords.Join(args...), false, true)
HandleShellCommand(JoinCommandArgs(args...), false, true)
}
// Quit closes the main view
@@ -545,37 +508,25 @@ func Save(args []string) {
// Replace runs search and replace
func Replace(args []string) {
if len(args) < 2 || len(args) > 4 {
if len(args) < 2 || len(args) > 3 {
// We need to find both a search and replace expression
messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
return
}
all := false
noRegex := false
if len(args) > 2 {
for _, arg := range args[2:] {
switch arg {
case "-a":
all = true
case "-l":
noRegex = true
default:
messenger.Error("Invalid flag: " + arg)
return
}
allAtOnce := false
if len(args) == 3 {
// user added -a flag
if args[2] == "-a" {
allAtOnce = true
} else {
messenger.Error("Invalid replace flag: " + args[2])
return
}
}
search := string(args[0])
if noRegex {
search = regexp.QuoteMeta(search)
}
replace := string(args[1])
replaceBytes := []byte(replace)
regex, err := regexp.Compile("(?m)" + search)
if err != nil {
@@ -589,21 +540,28 @@ func Replace(args []string) {
found := 0
replaceAll := func() {
var deltas []Delta
deltaXOffset := Count(replace) - Count(search)
for i := 0; i < view.Buf.LinesNum(); i++ {
newText := regex.ReplaceAllFunc(view.Buf.lines[i].data, func(in []byte) []byte {
found++
return replaceBytes
})
matches := regex.FindAllIndex(view.Buf.lines[i].data, -1)
str := string(view.Buf.lines[i].data)
from := Loc{0, i}
to := Loc{utf8.RuneCount(view.Buf.lines[i].data), i}
if matches != nil {
xOffset := 0
for _, m := range matches {
from := Loc{runePos(m[0], str) + xOffset, i}
to := Loc{runePos(m[1], str) + xOffset, i}
deltas = append(deltas, Delta{string(newText), from, to})
xOffset += deltaXOffset
deltas = append(deltas, Delta{replace, from, to})
found++
}
}
}
view.Buf.MultipleReplace(deltas)
}
if all {
if allAtOnce {
replaceAll()
} else {
for {
@@ -661,27 +619,93 @@ func ReplaceAll(args []string) {
Replace(append(args, "-a"))
}
// Term opens a terminal in the current view
func Term(args []string) {
var err error
if len(args) == 0 {
err = CurView().StartTerminal([]string{os.Getenv("SHELL"), "-i"}, true, false, "")
// RunShellCommand executes a shell command and returns the output/error
func RunShellCommand(input string) (string, error) {
inputCmd := SplitCommandArgs(input)[0]
args := SplitCommandArgs(input)[1:]
cmd := exec.Command(inputCmd, args...)
outputBytes := &bytes.Buffer{}
cmd.Stdout = outputBytes
cmd.Stderr = outputBytes
cmd.Start()
err := cmd.Wait() // wait for command to finish
outstring := outputBytes.String()
return outstring, err
}
// HandleShellCommand runs the shell command
// The openTerm argument specifies whether a terminal should be opened (for viewing output
// or interacting with stdin)
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
inputCmd := SplitCommandArgs(input)[0]
if !openTerm {
// Simply run the command in the background and notify the user when it's done
messenger.Message("Running...")
go func() {
output, err := RunShellCommand(input)
totalLines := strings.Split(output, "\n")
if len(totalLines) < 3 {
if err == nil {
messenger.Message(inputCmd, " exited without error")
} else {
messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
}
} else {
messenger.Message(output)
}
// We have to make sure to redraw
RedrawAll()
}()
} else {
err = CurView().StartTerminal(args, true, false, "")
}
if err != nil {
messenger.Error(err)
// Shut down the screen because we're going to interact directly with the shell
screen.Fini()
screen = nil
args := SplitCommandArgs(input)[1:]
// Set up everything for the command
var outputBuf bytes.Buffer
cmd := exec.Command(inputCmd, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = io.MultiWriter(os.Stdout, &outputBuf)
cmd.Stderr = os.Stderr
// This is a trap for Ctrl-C so that it doesn't kill micro
// Instead we trap Ctrl-C to kill the program we're running
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for range c {
cmd.Process.Kill()
}
}()
cmd.Start()
err := cmd.Wait()
output := outputBuf.String()
if err != nil {
output = err.Error()
}
if waitToFinish {
// This is just so we don't return right away and let the user press enter to return
TermMessage("")
}
// Start the screen back up
InitScreen()
return output
}
return ""
}
// HandleCommand handles input from the user
func HandleCommand(input string) {
args, err := shellwords.Split(input)
if err != nil {
messenger.Error("Error parsing args ", err)
return
}
args := SplitCommandArgs(input)
inputCmd := args[0]
if _, ok := commands[inputCmd]; !ok {

View File

@@ -28,22 +28,13 @@ type Cursor struct {
Num int
}
// Goto puts the cursor at the given cursor's location and gives
// the current cursor its selection too
// Goto puts the cursor at the given cursor's location and gives the current cursor its selection too
func (c *Cursor) Goto(b Cursor) {
c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
}
// GotoLoc puts the cursor at the given cursor's location and gives
// the current cursor its selection too
func (c *Cursor) GotoLoc(l Loc) {
c.X, c.Y = l.X, l.Y
c.LastVisualX = c.GetVisualX()
}
// CopySelection copies the user's selection to either "primary"
// or "clipboard"
// CopySelection copies the user's selection to either "primary" or "clipboard"
func (c *Cursor) CopySelection(target string) {
if c.HasSelection() {
if target != "primary" || c.buf.Settings["useprimary"].(bool) {
@@ -160,8 +151,7 @@ func (c *Cursor) SelectWord() {
c.Loc = c.CurSelection[1]
}
// AddWordToSelection adds the word the cursor is currently on
// to the selection
// AddWordToSelection adds the word the cursor is currently on to the selection
func (c *Cursor) AddWordToSelection() {
if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
c.CurSelection = c.OrigSelection
@@ -193,8 +183,7 @@ func (c *Cursor) AddWordToSelection() {
c.Loc = c.CurSelection[1]
}
// SelectTo selects from the current cursor location to the given
// location
// SelectTo selects from the current cursor location to the given location
func (c *Cursor) SelectTo(loc Loc) {
if loc.GreaterThan(c.OrigSelection[0]) {
c.SetSelectionStart(c.OrigSelection[0])
@@ -269,7 +258,7 @@ func (c *Cursor) UpN(amount int) {
runes := []rune(c.buf.Line(c.Y))
c.X = c.GetCharPosInLine(proposedY, c.LastVisualX)
if c.X > len(runes) || (amount < 0 && proposedY == c.Y) {
if c.X > len(runes) {
c.X = len(runes)
}
@@ -291,8 +280,7 @@ func (c *Cursor) Down() {
c.DownN(1)
}
// Left moves the cursor left one cell (if possible) or to
// the previous line if it is at the beginning
// Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
func (c *Cursor) Left() {
if c.Loc == c.buf.Start() {
return
@@ -306,8 +294,7 @@ func (c *Cursor) Left() {
c.LastVisualX = c.GetVisualX()
}
// Right moves the cursor right one cell (if possible) or
// to the next line if it is at the end
// Right moves the cursor right one cell (if possible) or to the next line if it is at the end
func (c *Cursor) Right() {
if c.Loc == c.buf.End() {
return
@@ -333,21 +320,7 @@ func (c *Cursor) Start() {
c.LastVisualX = c.GetVisualX()
}
// StartOfText moves the cursor to the first non-whitespace rune of
// the line it is on
func (c *Cursor) StartOfText() {
c.Start()
for IsWhitespace(c.RuneUnder(c.X)) {
if c.X == Count(c.buf.Line(c.Y)) {
break
}
c.Right()
}
}
// GetCharPosInLine gets the char position of a visual x y
// coordinate (this is necessary because tabs are 1 char but
// 4 visual spaces)
// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
// Get the tab size
tabSize := int(c.buf.Settings["tabsize"].(float64))
@@ -382,9 +355,8 @@ func (c *Cursor) StoreVisualX() {
c.LastVisualX = c.GetVisualX()
}
// Relocate makes sure that the cursor is inside the bounds
// of the buffer If it isn't, it moves it to be within the
// buffer's lines
// Relocate makes sure that the cursor is inside the bounds of the buffer
// If it isn't, it moves it to be within the buffer's lines
func (c *Cursor) Relocate() {
if c.Y < 0 {
c.Y = 0

View File

@@ -28,7 +28,6 @@ type TextEvent struct {
Time time.Time
}
// A Delta is a change to the buffer
type Delta struct {
Text string
Start Loc

View File

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

View File

@@ -6,50 +6,16 @@ import (
"unicode/utf8"
)
func sliceStart(slc []byte, index int) []byte {
len := len(slc)
i := 0
totalSize := 0
for totalSize < len {
if i >= index {
return slc[totalSize:]
}
_, size := utf8.DecodeRune(slc[totalSize:])
totalSize += size
i++
}
return slc[totalSize:]
}
func sliceEnd(slc []byte, index int) []byte {
len := len(slc)
i := 0
totalSize := 0
for totalSize < len {
if i >= index {
return slc[:totalSize]
}
_, size := utf8.DecodeRune(slc[totalSize:])
totalSize += size
i++
}
return slc[:totalSize]
}
// RunePos returns the rune index of a given byte index
// This could cause problems if the byte index is between code points
func runePos(p int, str []byte) int {
func runePos(p int, str string) int {
if p < 0 {
return 0
}
if p >= len(str) {
return utf8.RuneCount(str)
return utf8.RuneCountInString(str)
}
return utf8.RuneCount(str[:p])
return utf8.RuneCountInString(str[:p])
}
func combineLineMatch(src, dst LineMatch) LineMatch {
@@ -70,7 +36,7 @@ type State *region
// 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
Line(n int) string
LinesNum() int
State(lineN int) State
SetState(lineN int, s State)
@@ -94,7 +60,7 @@ 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, canMatchStart, canMatchEnd bool) []int {
func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []rune, canMatchStart, canMatchEnd bool) []int {
regexStr := regex.String()
if strings.Contains(regexStr, "^") {
if !canMatchStart {
@@ -109,12 +75,12 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchSt
var strbytes []byte
if skip != nil {
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
strbytes = skip.ReplaceAllFunc([]byte(string(str)), func(match []byte) []byte {
res := make([]byte, utf8.RuneCount(match))
return res
})
} else {
strbytes = str
strbytes = []byte(string(str))
}
match := regex.FindIndex(strbytes)
@@ -122,10 +88,10 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchSt
return nil
}
// return []int{match.Index, match.Index + match.Length}
return []int{runePos(match[0], str), runePos(match[1], str)}
return []int{runePos(match[0], string(str)), runePos(match[1], string(str))}
}
func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) [][]int {
func findAllIndex(regex *regexp.Regexp, str []rune, canMatchStart, canMatchEnd bool) [][]int {
regexStr := regex.String()
if strings.Contains(regexStr, "^") {
if !canMatchStart {
@@ -137,16 +103,17 @@ func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd b
return nil
}
}
matches := regex.FindAllIndex(str, -1)
matches := regex.FindAllIndex([]byte(string(str)), -1)
for i, m := range matches {
matches[i][0] = runePos(m[0], str)
matches[i][1] = runePos(m[1], str)
matches[i][0] = runePos(m[0], string(str))
matches[i][1] = runePos(m[1], string(str))
}
return matches
}
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {
lineLen := utf8.RuneCount(line)
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []rune, curRegion *region, statesOnly bool) LineMatch {
// highlights := make(LineMatch)
if start == 0 {
if !statesOnly {
if _, ok := highlights[0]; !ok {
@@ -163,20 +130,20 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
if curRegion.parent == nil {
if !statesOnly {
highlights[start+loc[1]] = 0
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, loc[0]), curRegion, statesOnly)
h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], curRegion, statesOnly)
}
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), statesOnly)
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, 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, false, lineNum, line[:loc[0]], curRegion, statesOnly)
}
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), curRegion.parent, statesOnly)
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, line[loc[1]:], curRegion.parent, statesOnly)
return highlights
}
if lineLen == 0 || statesOnly {
if len(line) == 0 || statesOnly {
if canMatchEnd {
h.lastRegion = curRegion
}
@@ -184,7 +151,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
return highlights
}
firstLoc := []int{lineLen, 0}
firstLoc := []int{len(line), 0}
var firstRegion *region
for _, r := range curRegion.rules.regions {
@@ -196,14 +163,14 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
}
}
}
if firstLoc[0] != lineLen {
if firstLoc[0] != len(line) {
highlights[start+firstLoc[0]] = firstRegion.limitGroup
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), curRegion, statesOnly)
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
h.highlightRegion(highlights, start, false, lineNum, line[:firstLoc[0]], curRegion, statesOnly)
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion, statesOnly)
return highlights
}
fullHighlights := make([]Group, lineLen)
fullHighlights := make([]Group, len([]rune(string(line))))
for i := 0; i < len(fullHighlights); i++ {
fullHighlights[i] = curRegion.group
}
@@ -218,7 +185,9 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
}
for i, h := range fullHighlights {
if i == 0 || h != fullHighlights[i-1] {
// if _, ok := highlights[start+i]; !ok {
highlights[start+i] = h
// }
}
}
@@ -229,16 +198,15 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
return highlights
}
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
lineLen := utf8.RuneCount(line)
if lineLen == 0 {
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []rune, statesOnly bool) LineMatch {
if len(line) == 0 {
if canMatchEnd {
h.lastRegion = nil
}
return highlights
}
firstLoc := []int{lineLen, 0}
firstLoc := []int{len(line), 0}
var firstRegion *region
for _, r := range h.Def.rules.regions {
loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
@@ -249,12 +217,12 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
}
}
}
if firstLoc[0] != lineLen {
if firstLoc[0] != len(line) {
if !statesOnly {
highlights[start+firstLoc[0]] = firstRegion.limitGroup
}
h.highlightEmptyRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), statesOnly)
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
h.highlightEmptyRegion(highlights, start, false, lineNum, line[:firstLoc[0]], statesOnly)
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion, statesOnly)
return highlights
}
@@ -299,7 +267,7 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
var lineMatches []LineMatch
for i := 0; i < len(lines); i++ {
line := []byte(lines[i])
line := []rune(lines[i])
highlights := make(LineMatch)
if i == 0 || h.lastRegion == nil {
@@ -315,7 +283,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.LinesNum(); i++ {
line := input.LineBytes(i)
line := []rune(input.Line(i))
// highlights := make(LineMatch)
if i == 0 || h.lastRegion == nil {
@@ -339,7 +307,7 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
break
}
line := input.LineBytes(i)
line := []rune(input.Line(i))
highlights := make(LineMatch)
var match LineMatch
@@ -363,7 +331,7 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
h.lastRegion = input.State(startline - 1)
}
for i := startline; i < input.LinesNum(); i++ {
line := input.LineBytes(i)
line := []rune(input.Line(i))
// highlights := make(LineMatch)
// var match LineMatch
@@ -385,7 +353,7 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
// ReHighlightLine will rehighlight the state and match for a single line
func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
line := input.LineBytes(lineN)
line := []rune(input.Line(lineN))
highlights := make(LineMatch)
h.lastRegion = nil

View File

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

View File

@@ -29,8 +29,6 @@ func runeToByteIndex(n int, txt []byte) int {
return count
}
// A Line contains the data in bytes as well as a highlight state, match
// and a flag for whether the highlighting needs to be updated
type Line struct {
data []byte
@@ -45,12 +43,10 @@ type LineArray struct {
lines []Line
}
// Append efficiently appends lines together
// It allocates an additional 10000 lines if the original estimate
// is incorrect
func Append(slice []Line, data ...Line) []Line {
l := len(slice)
if l+len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]Line, (l+len(data))+10000)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
@@ -89,6 +85,7 @@ func NewLineArray(size int64, reader io.Reader) *LineArray {
if n >= 1000 && loaded >= 0 {
totalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))
newSlice := make([]Line, len(la.lines), totalLinesNum+10000)
// The copy function is predeclared and works for any slice type.
copy(newSlice, la.lines)
la.lines = newSlice
loaded = -1
@@ -146,9 +143,9 @@ func (la *LineArray) SaveString(useCrlf bool) string {
// NewlineBelow adds a newline below the given line number
func (la *LineArray) NewlineBelow(y int) {
la.lines = append(la.lines, Line{[]byte{' '}, nil, nil, false})
la.lines = append(la.lines, Line{[]byte(" "), nil, nil, false})
copy(la.lines[y+2:], la.lines[y+1:])
la.lines[y+1] = Line{[]byte{}, la.lines[y].state, nil, false}
la.lines[y+1] = Line{[]byte(""), la.lines[y].state, nil, false}
}
// inserts a byte array at a given location
@@ -246,22 +243,18 @@ func (la *LineArray) Substr(start, end Loc) string {
return str
}
// State gets the highlight state for the given line number
func (la *LineArray) State(lineN int) highlight.State {
return la.lines[lineN].state
}
// SetState sets the highlight state at the given line number
func (la *LineArray) SetState(lineN int, s highlight.State) {
la.lines[lineN].state = s
}
// SetMatch sets the match at the given line number
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
la.lines[lineN].match = m
}
// Match retrieves the match for the given line number
func (la *LineArray) Match(lineN int) highlight.LineMatch {
return la.lines[lineN].match
}

View File

@@ -54,7 +54,6 @@ type Loc struct {
X, Y int
}
// Diff returns the distance between two locations
func Diff(a, b Loc, buf *Buffer) int {
if a.Y == b.Y {
if a.X > b.X {

View File

@@ -28,7 +28,6 @@ func init() {
L.SetGlobal("import", luar.New(L, Import))
}
// LoadFile loads a lua file
func LoadFile(module string, file string, data string) error {
pluginDef := "local P = {};" + module + " = P;setmetatable(" + module + ", {__index = _G});setfenv(1, P);"
@@ -40,43 +39,40 @@ func LoadFile(module string, file string, data string) error {
}
}
// Import allows a lua plugin to import a package
func Import(pkg string) *lua.LTable {
switch pkg {
case "fmt":
return importFmt()
return ImportFmt()
case "io":
return importIo()
case "io/ioutil", "ioutil":
return importIoUtil()
return ImportIo()
case "ioutil":
return ImportIoUtil()
case "net":
return importNet()
return ImportNet()
case "math":
return importMath()
case "math/rand":
return importMathRand()
return ImportMath()
case "os":
return importOs()
return ImportOs()
case "runtime":
return importRuntime()
return ImportRuntime()
case "path":
return importPath()
return ImportPath()
case "filepath":
return importFilePath()
return ImportFilePath()
case "strings":
return importStrings()
return ImportStrings()
case "regexp":
return importRegexp()
return ImportRegexp()
case "errors":
return importErrors()
return ImportErrors()
case "time":
return importTime()
return ImportTime()
default:
return nil
}
}
func importFmt() *lua.LTable {
func ImportFmt() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "tErrorf", luar.New(L, fmt.Errorf))
@@ -102,7 +98,7 @@ func importFmt() *lua.LTable {
return pkg
}
func importIo() *lua.LTable {
func ImportIo() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Copy", luar.New(L, io.Copy))
@@ -126,7 +122,7 @@ func importIo() *lua.LTable {
return pkg
}
func importIoUtil() *lua.LTable {
func ImportIoUtil() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "ReadAll", luar.New(L, ioutil.ReadAll))
@@ -137,7 +133,7 @@ func importIoUtil() *lua.LTable {
return pkg
}
func importNet() *lua.LTable {
func ImportNet() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "CIDRMask", luar.New(L, net.CIDRMask))
@@ -205,7 +201,7 @@ func importNet() *lua.LTable {
return pkg
}
func importMath() *lua.LTable {
func ImportMath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Abs", luar.New(L, math.Abs))
@@ -273,7 +269,7 @@ func importMath() *lua.LTable {
return pkg
}
func importMathRand() *lua.LTable {
func ImportMathRand() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "ExpFloat64", luar.New(L, rand.ExpFloat64))
@@ -293,7 +289,7 @@ func importMathRand() *lua.LTable {
return pkg
}
func importOs() *lua.LTable {
func ImportOs() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Args", luar.New(L, os.Args))
@@ -384,7 +380,7 @@ func importOs() *lua.LTable {
return pkg
}
func importRuntime() *lua.LTable {
func ImportRuntime() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "GC", luar.New(L, runtime.GC))
@@ -396,7 +392,7 @@ func importRuntime() *lua.LTable {
return pkg
}
func importPath() *lua.LTable {
func ImportPath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Base", luar.New(L, path.Base))
@@ -412,10 +408,11 @@ func importPath() *lua.LTable {
return pkg
}
func importFilePath() *lua.LTable {
func ImportFilePath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
L.SetField(pkg, "Clean", 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))
@@ -437,7 +434,7 @@ func importFilePath() *lua.LTable {
return pkg
}
func importStrings() *lua.LTable {
func ImportStrings() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Contains", luar.New(L, strings.Contains))
@@ -487,7 +484,7 @@ func importStrings() *lua.LTable {
return pkg
}
func importRegexp() *lua.LTable {
func ImportRegexp() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Match", luar.New(L, regexp.Match))
@@ -502,7 +499,7 @@ func importRegexp() *lua.LTable {
return pkg
}
func importErrors() *lua.LTable {
func ImportErrors() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "New", luar.New(L, errors.New))
@@ -510,7 +507,7 @@ func importErrors() *lua.LTable {
return pkg
}
func importTime() *lua.LTable {
func ImportTime() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "After", luar.New(L, time.After))

View File

@@ -3,14 +3,12 @@ package main
import (
"bufio"
"bytes"
"encoding/gob"
"fmt"
"os"
"strconv"
"github.com/mattn/go-runewidth"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/cmd/micro/shellwords"
"github.com/zyedidia/tcell"
)
@@ -223,7 +221,6 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
}
}
// Completion represents a type of completion
type Completion int
const (
@@ -234,7 +231,6 @@ const (
OptionCompletion
PluginCmdCompletion
PluginNameCompletion
OptionValueCompletion
)
// Prompt sends the user a message and waits for a response to be typed in
@@ -261,11 +257,6 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
event := <-events
switch e := event.(type) {
case *tcell.EventResize:
for _, t := range tabs {
t.Resize()
}
RedrawAll()
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
@@ -279,16 +270,9 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
response, canceled = m.response, false
m.history[historyType][len(m.history[historyType])-1] = response
case tcell.KeyTab:
args, err := shellwords.Split(m.response)
if err != nil {
break
}
currentArg := ""
currentArgNum := 0
if len(args) > 0 {
currentArgNum = len(args) - 1
currentArg = args[currentArgNum]
}
args := SplitCommandArgs(m.response)
currentArgNum := len(args) - 1
currentArg := args[currentArgNum]
var completionType Completion
if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
@@ -312,10 +296,6 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
chosen, suggestions = HelpComplete(currentArg)
} else if completionType == OptionCompletion {
chosen, suggestions = OptionComplete(currentArg)
} else if completionType == OptionValueCompletion {
if currentArgNum-1 > 0 {
chosen, suggestions = OptionValueComplete(args[currentArgNum-1], currentArg)
}
} else if completionType == PluginCmdCompletion {
chosen, suggestions = PluginCmdComplete(currentArg)
} else if completionType == PluginNameCompletion {
@@ -328,8 +308,8 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
chosen = chosen + CommonSubstring(suggestions...)
}
if len(suggestions) != 0 && chosen != "" {
m.response = shellwords.Join(append(args[:len(args)-1], chosen)...)
if chosen != "" {
m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
m.cursorx = Count(m.response)
}
}
@@ -338,7 +318,7 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
m.HandleEvent(event, m.history[historyType])
m.Clear()
for _, v := range tabs[curTab].Views {
for _, v := range tabs[curTab].views {
v.Display()
}
DisplayTabs()
@@ -354,160 +334,62 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
return response, canceled
}
// UpHistory fetches the previous item in the history
func (m *Messenger) UpHistory(history []string) {
if m.historyNum > 0 {
m.historyNum--
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
}
// DownHistory fetches the next item in the history
func (m *Messenger) DownHistory(history []string) {
if m.historyNum < len(history)-1 {
m.historyNum++
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
}
// CursorLeft moves the cursor one character left
func (m *Messenger) CursorLeft() {
if m.cursorx > 0 {
m.cursorx--
}
}
// CursorRight moves the cursor one character right
func (m *Messenger) CursorRight() {
if m.cursorx < Count(m.response) {
m.cursorx++
}
}
// Start moves the cursor to the start of the line
func (m *Messenger) Start() {
m.cursorx = 0
}
// End moves the cursor to the end of the line
func (m *Messenger) End() {
m.cursorx = Count(m.response)
}
// Backspace deletes one character
func (m *Messenger) Backspace() {
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
m.cursorx--
}
}
// Paste pastes the clipboard
func (m *Messenger) Paste() {
clip, _ := clipboard.ReadAll("clipboard")
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
}
// WordLeft moves the cursor one word to the left
func (m *Messenger) WordLeft() {
response := []rune(m.response)
m.CursorLeft()
if m.cursorx <= 0 {
return
}
for IsWhitespace(response[m.cursorx]) {
if m.cursorx <= 0 {
return
}
m.CursorLeft()
}
m.CursorLeft()
for IsWordChar(string(response[m.cursorx])) {
if m.cursorx <= 0 {
return
}
m.CursorLeft()
}
m.CursorRight()
}
// WordRight moves the cursor one word to the right
func (m *Messenger) WordRight() {
response := []rune(m.response)
if m.cursorx >= len(response) {
return
}
for IsWhitespace(response[m.cursorx]) {
m.CursorRight()
if m.cursorx >= len(response) {
m.CursorRight()
return
}
}
m.CursorRight()
if m.cursorx >= len(response) {
return
}
for IsWordChar(string(response[m.cursorx])) {
m.CursorRight()
if m.cursorx >= len(response) {
return
}
}
}
// DeleteWordLeft deletes one word to the left
func (m *Messenger) DeleteWordLeft() {
m.WordLeft()
m.response = string([]rune(m.response)[:m.cursorx])
}
// HandleEvent handles an event for the prompter
func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
switch e := event.(type) {
case *tcell.EventKey:
if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
for key, actions := range bindings {
if e.Key() == key.keyCode {
if e.Key() == tcell.KeyRune {
if e.Rune() != key.r {
continue
}
}
if e.Modifiers() == key.modifiers {
for _, action := range actions {
funcName := FuncName(action)
switch funcName {
case "main.(*View).CursorUp":
if m.historyNum > 0 {
m.historyNum--
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case "main.(*View).CursorDown":
if m.historyNum < len(history)-1 {
m.historyNum++
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case "main.(*View).CursorLeft":
if m.cursorx > 0 {
m.cursorx--
}
case "main.(*View).CursorRight":
if m.cursorx < Count(m.response) {
m.cursorx++
}
case "main.(*View).CursorStart", "main.(*View).StartOfLine":
m.cursorx = 0
case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
m.cursorx = Count(m.response)
case "main.(*View).Backspace":
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
m.cursorx--
}
case "main.(*View).Paste":
clip, _ := clipboard.ReadAll("clipboard")
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
}
}
}
}
}
}
switch e.Key() {
case tcell.KeyCtrlA:
m.Start()
case tcell.KeyCtrlE:
m.End()
case tcell.KeyUp:
m.UpHistory(history)
case tcell.KeyDown:
m.DownHistory(history)
case tcell.KeyLeft:
if e.Modifiers() == tcell.ModCtrl {
m.Start()
} else if e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
m.WordLeft()
} else {
m.CursorLeft()
}
case tcell.KeyRight:
if e.Modifiers() == tcell.ModCtrl {
m.End()
} else if e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
m.WordRight()
} else {
m.CursorRight()
}
case tcell.KeyBackspace2, tcell.KeyBackspace:
if e.Modifiers() == tcell.ModCtrl || e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
m.DeleteWordLeft()
} else {
m.Backspace()
}
case tcell.KeyCtrlW:
m.DeleteWordLeft()
case tcell.KeyCtrlV:
m.Paste()
case tcell.KeyCtrlF:
m.WordRight()
case tcell.KeyCtrlB:
m.WordLeft()
case tcell.KeyRune:
m.response = Insert(m.response, m.cursorx, string(e.Rune()))
m.cursorx++
@@ -598,59 +480,6 @@ func (m *Messenger) Display() {
}
}
// LoadHistory attempts to load user history from configDir/buffers/history
// into the history map
// The savehistory option must be on
func (m *Messenger) LoadHistory() {
if GetGlobalOption("savehistory").(bool) {
file, err := os.Open(configDir + "/buffers/history")
defer file.Close()
var decodedMap map[string][]string
if err == nil {
decoder := gob.NewDecoder(file)
err = decoder.Decode(&decodedMap)
if err != nil {
m.Error("Error loading history:", err)
return
}
}
if decodedMap != nil {
m.history = decodedMap
} else {
m.history = make(map[string][]string)
}
} else {
m.history = make(map[string][]string)
}
}
// SaveHistory saves the user's command history to configDir/buffers/history
// only if the savehistory option is on
func (m *Messenger) SaveHistory() {
if GetGlobalOption("savehistory").(bool) {
// Don't save history past 100
for k, v := range m.history {
if len(v) > 100 {
m.history[k] = v[len(m.history[k])-100:]
}
}
file, err := os.Create(configDir + "/buffers/history")
defer file.Close()
if err == nil {
encoder := gob.NewEncoder(file)
err = encoder.Encode(m.history)
if err != nil {
m.Error("Error saving history:", err)
return
}
}
}
}
// A GutterMessage is a message displayed on the side of the editor
type GutterMessage struct {
lineNum int

View File

@@ -15,7 +15,6 @@ import (
"github.com/mitchellh/go-homedir"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/cmd/micro/terminfo"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/encoding"
"layeh.com/gopher-luar"
@@ -45,10 +44,8 @@ var (
// Version is the version number or commit hash
// These variables should be set by the linker when compiling
Version = "0.0.0-unknown"
// CommitHash is the commit this version was built on
CommitHash = "Unknown"
// CompileDate is the date this binary was compiled on
Version = "0.0.0-unknown"
CommitHash = "Unknown"
CompileDate = "Unknown"
// The list of views
@@ -59,17 +56,12 @@ var (
// Channel of jobs running in the background
jobs chan JobFunction
// Event channel
events chan tcell.Event
autosave chan bool
// Channels for the terminal emulator
updateterm chan bool
closeterm chan int
// How many redraws have happened
numRedraw uint
// Read events on another thread or wait for a temporary read
lockPollEvent bool
)
// LoadInput determines which files should be loaded into buffers
@@ -96,23 +88,30 @@ func LoadInput() []*Buffer {
// Option 1
// We go through each file and load it
for i := 0; i < len(args); i++ {
if strings.HasPrefix(args[i], "+") {
if strings.Contains(args[i], ":") {
split := strings.Split(args[i], ":")
*flagStartPos = split[0][1:] + "," + split[1]
} else {
*flagStartPos = args[i][1:] + ",0"
}
continue
}
filename = args[i]
buf, err := NewBufferFromFile(args[i])
if err != nil {
TermMessage(err)
continue
// Check that the file exists
var input *os.File
if _, e := os.Stat(filename); e == nil {
// If it exists we load it into a buffer
input, err = os.Open(filename)
stat, _ := input.Stat()
defer input.Close()
if err != nil {
TermMessage(err)
continue
}
if stat.IsDir() {
TermMessage("Cannot read", filename, "because it is a directory")
continue
}
}
// If the file didn't exist, input will be empty, and we'll open an empty buffer
buffers = append(buffers, buf)
if input != nil {
buffers = append(buffers, NewBuffer(input, FSize(input), filename))
} else {
buffers = append(buffers, NewBufferFromString("", filename))
}
}
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
// Option 2
@@ -178,9 +177,6 @@ func InitScreen() {
// Should we enable true color?
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
tcelldb := os.Getenv("TCELLDB")
os.Setenv("TCELLDB", configDir+"/.tcelldb")
// In order to enable true color, we have to set the TERM to `xterm-truecolor` when
// initializing tcell, but after that, we can set the TERM back to whatever it was
oldTerm := os.Getenv("TERM")
@@ -192,19 +188,12 @@ func InitScreen() {
var err error
screen, err = tcell.NewScreen()
if err != nil {
fmt.Println(err)
if err == tcell.ErrTermNotFound {
terminfo.WriteDB(configDir + "/.tcelldb")
screen, err = tcell.NewScreen()
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a screen.")
os.Exit(1)
}
} else {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a screen.")
os.Exit(1)
fmt.Println("Micro does not recognize your terminal:", oldTerm)
fmt.Println("Please go to https://github.com/zyedidia/mkinfo to read about how to fix this problem (it should be easy to fix).")
}
os.Exit(1)
}
if err = screen.Init(); err != nil {
fmt.Println(err)
@@ -216,13 +205,7 @@ func InitScreen() {
os.Setenv("TERM", oldTerm)
}
if GetGlobalOption("mouse").(bool) {
screen.EnableMouse()
}
os.Setenv("TCELLDB", tcelldb)
// screen.SetStyle(defStyle)
screen.SetStyle(defStyle)
}
// RedrawAll redraws everything -- all the views and the messenger
@@ -236,20 +219,12 @@ func RedrawAll() {
}
}
for _, v := range tabs[curTab].Views {
for _, v := range tabs[curTab].views {
v.Display()
}
DisplayTabs()
messenger.Display()
if globalSettings["keymenu"].(bool) {
DisplayKeyMenu()
}
screen.Show()
if numRedraw%50 == 0 {
runtime.GC()
}
numRedraw++
}
func LoadAll() {
@@ -268,43 +243,33 @@ func LoadAll() {
InitColorscheme()
for _, tab := range tabs {
for _, v := range tab.Views {
for _, v := range tab.views {
v.Buf.UpdateRules()
}
}
}
// Command line flags
// Passing -version as a flag will have micro print out the version number
var flagVersion = flag.Bool("version", false, "Show the version number and information")
var flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.")
var flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
var flagOptions = flag.Bool("options", false, "Show all option help")
var optionFlagSet = flag.NewFlagSet("option", flag.ExitOnError)
func main() {
flag.Usage = func() {
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
fmt.Println("-config-dir dir")
fmt.Println(" \tSpecify a custom location for the configuration directory")
fmt.Println("-startpos LINE,COL")
fmt.Println("+LINE:COL")
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
fmt.Println(" \tThis can also be done by opening file:LINE:COL")
fmt.Println("-options")
fmt.Println(" \tShow all option help")
fmt.Println("-version")
fmt.Println(" \tShow the version number and information")
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
fmt.Println("-option value")
fmt.Println(" \tSet `option` to `value` for this session")
fmt.Println(" \tFor example: `micro -syntax off file.c`")
fmt.Println("\nUse `micro -options` to see the full list of configuration options")
flag.CommandLine.SetOutput(os.Stdout)
flag.PrintDefaults()
optionFlagSet.SetOutput(os.Stdout)
fmt.Print("\n------------------------------------------------------------------\n")
fmt.Print("Micro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the bindings.json\nfile (see 'help options').\n\n")
optionFlagSet.PrintDefaults()
}
optionFlags := make(map[string]*string)
for k, v := range DefaultGlobalSettings() {
optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'", k, v))
optionFlags[k] = optionFlagSet.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'", k, v))
}
flag.Parse()
@@ -317,15 +282,6 @@ func main() {
os.Exit(0)
}
if *flagOptions {
// If -options was passed
for k, v := range DefaultGlobalSettings() {
fmt.Printf("-%s value\n", k)
fmt.Printf(" \tThe %s option. Default value: '%v'\n", k, v)
}
os.Exit(0)
}
// Start the Lua VM for running plugins
L = lua.NewState()
defer L.Close()
@@ -365,7 +321,7 @@ func main() {
// Create a new messenger
// This is used for sending the user messages in the bottom of the editor
messenger = new(Messenger)
messenger.LoadHistory()
messenger.history = make(map[string][]string)
// Now we load the input
buffers := LoadInput()
@@ -380,7 +336,7 @@ func main() {
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
for _, t := range tabs {
for _, v := range t.Views {
for _, v := range t.views {
v.Center(false)
}
@@ -410,16 +366,9 @@ func main() {
L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
L.SetGlobal("HandleCommand", luar.New(L, HandleCommand))
L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
L.SetGlobal("ExecCommand", luar.New(L, ExecCommand))
L.SetGlobal("RunShellCommand", luar.New(L, RunShellCommand))
L.SetGlobal("RunBackgroundShell", luar.New(L, RunBackgroundShell))
L.SetGlobal("RunInteractiveShell", luar.New(L, RunInteractiveShell))
L.SetGlobal("TermEmuSupported", luar.New(L, TermEmuSupported))
L.SetGlobal("RunTermEmulator", luar.New(L, RunTermEmulator))
L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion))
L.SetGlobal("NewBuffer", luar.New(L, NewBufferFromString))
L.SetGlobal("NewBufferFromFile", luar.New(L, NewBufferFromFile))
L.SetGlobal("RuneStr", luar.New(L, func(r rune) string {
return string(r)
}))
@@ -453,26 +402,29 @@ func main() {
jobs = make(chan JobFunction, 100)
events = make(chan tcell.Event, 100)
autosave = make(chan bool)
updateterm = make(chan bool)
closeterm = make(chan int)
LoadPlugins()
for _, t := range tabs {
for _, v := range t.Views {
GlobalPluginCall("onViewOpen", v)
GlobalPluginCall("onBufferOpen", v.Buf)
for _, v := range t.views {
for pl := range loadedPlugins {
_, err := Call(pl+".onViewOpen", v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
continue
}
}
}
}
InitColorscheme()
messenger.style = defStyle
// Here is the event loop which runs in a separate thread
go func() {
for {
if screen != nil {
if screen != nil && !lockPollEvent {
events <- screen.PollEvent()
time.Sleep(1 * time.Millisecond)
}
}
}()
@@ -499,19 +451,11 @@ func main() {
f.function(f.output, f.args...)
continue
case <-autosave:
if CurView().Buf.Path != "" {
CurView().Save(true)
}
case <-updateterm:
continue
case vnum := <-closeterm:
tabs[curTab].Views[vnum].CloseTerminal()
CurView().Save(true)
case event = <-events:
}
for event != nil {
didAction := false
switch e := event.(type) {
case *tcell.EventResize:
for _, t := range tabs {
@@ -535,44 +479,30 @@ func main() {
if CurView().mouseReleased {
// We loop through each view in the current tab and make sure the current view
// is the one being clicked in
for _, v := range tabs[curTab].Views {
for _, v := range tabs[curTab].views {
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
tabs[curTab].CurView = v.Num
}
}
}
} else if e.Buttons() == tcell.WheelUp || e.Buttons() == tcell.WheelDown {
var view *View
x, y := e.Position()
for _, v := range tabs[curTab].Views {
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
view = tabs[curTab].Views[v.Num]
}
}
if view != nil {
view.HandleEvent(e)
didAction = true
}
}
}
}
if !didAction {
// This function checks the mouse event for the possibility of changing the current tab
// If the tab was changed it returns true
if TabbarHandleMouseEvent(event) {
break
}
// This function checks the mouse event for the possibility of changing the current tab
// If the tab was changed it returns true
if TabbarHandleMouseEvent(event) {
break
}
if searching {
// Since searching is done in real time, we need to redraw every time
// there is a new event in the search bar so we need a special function
// to run instead of the standard HandleEvent.
HandleSearchEvent(event, CurView())
} else {
// Send it to the view
CurView().HandleEvent(event)
}
if searching {
// Since searching is done in real time, we need to redraw every time
// there is a new event in the search bar so we need a special function
// to run instead of the standard HandleEvent.
HandleSearchEvent(event, CurView())
} else {
// Send it to the view
CurView().HandleEvent(event)
}
select {

View File

@@ -61,10 +61,6 @@ func LuaFunctionBinding(function string) func(*View, bool) bool {
}
}
// LuaFunctionMouseBinding is a function generator which takes the name of a lua function
// and creates a function that will call that lua function
// Specifically it creates a function that can be called as a mouse binding because this is used
// to bind mouse actions to lua functions
func LuaFunctionMouseBinding(function string) func(*View, bool, *tcell.EventMouse) bool {
return func(v *View, _ bool, e *tcell.EventMouse) bool {
_, err := Call(function, e)
@@ -118,13 +114,10 @@ func LuaFunctionComplete(function string) func(string) []string {
}
}
// LuaFunctionJob returns a function that will call the given lua function
// structured as a job call i.e. the job output and arguments are provided
// to the lua function
func LuaFunctionJob(function string) func(string, ...string) {
return func(output string, args ...string) {
_, err := Call(function, unpack(append([]string{output}, args...))...)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
if err != nil {
TermMessage(err)
}
}
@@ -153,7 +146,7 @@ func LoadPlugins() {
pluginLuaName := luaPluginName(pluginName)
if err := LoadFile(pluginLuaName, pluginLuaName, string(data)); err != nil {
if err := LoadFile(pluginName, pluginName, string(data)); err != nil {
TermMessage(err)
continue
}
@@ -170,15 +163,3 @@ func LoadPlugins() {
loadedPlugins["init"] = "init"
}
}
// GlobalCall makes a call to a function in every plugin that is currently
// loaded
func GlobalPluginCall(function string, args ...interface{}) {
for pl := range loadedPlugins {
_, err := Call(pl+"."+function, args...)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
continue
}
}
}

View File

@@ -8,6 +8,7 @@ import (
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"sort"
"strings"
@@ -422,7 +423,6 @@ func (pv *PluginVersion) DownloadAndInstall() error {
}
}
// Install files and directory's
for _, f := range z.File {
parts := strings.Split(f.Name, "/")
if allPrefixed {
@@ -435,7 +435,7 @@ func (pv *PluginVersion) DownloadAndInstall() error {
return err
}
} else {
basepath := filepath.Dir(targetName)
basepath := path.Dir(targetName)
if err := os.MkdirAll(basepath, dirPerm); err != nil {
return err
@@ -541,7 +541,7 @@ func (pv PluginVersions) install() {
shouldInstall := true
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
if pv.Version.NE(sel.Version) {
messenger.AddLog("Uninstalling", sel.pack.Name)
messenger.AddLog(fmt.Sprint("Uninstalling %q", sel.pack.Name))
UninstallPlugin(sel.pack.Name)
} else {
shouldInstall = false

View File

@@ -36,7 +36,7 @@ func TestDependencyResolving(t *testing.T) {
if v == nil {
t.Errorf("Failed to resolve %s", name)
} else if expected.NE(v.Version) {
t.Errorf("%s resolved in wrong version %v", name, v)
t.Errorf("%s resolved in wrong version got %s", name, v)
}
}

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -64,12 +64,7 @@ func HandleSearchEvent(event tcell.Event, v *View) {
// Exit the search mode
ExitSearch(v)
return
case tcell.KeyEnter:
// If the user has pressed Enter, they want this to be the lastSearch
lastSearch = messenger.response
EndSearch()
return
case tcell.KeyCtrlQ, tcell.KeyCtrlC:
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEnter:
// Done
EndSearch()
return
@@ -184,7 +179,9 @@ func Search(searchStr string, v *View, down bool) {
found = searchUp(r, v, v.Buf.End(), searchStart)
}
}
if !found {
if found {
lastSearch = searchStr
} else {
v.Cursor.ResetSelection()
}
}

View File

@@ -1,16 +1,13 @@
package main
import (
"crypto/md5"
"encoding/json"
"errors"
"io"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
"sync"
"github.com/flynn/json5"
"github.com/zyedidia/glob"
@@ -102,23 +99,15 @@ func InitLocalSettings(buf *Buffer) {
for k, v := range parsed {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if strings.HasPrefix(k, "ft:") {
if buf.Settings["filetype"].(string) == k[3:] {
for k1, v1 := range v.(map[string]interface{}) {
buf.Settings[k1] = v1
}
}
} else {
g, err := glob.Compile(k)
if err != nil {
TermMessage("Error with glob setting ", k, ": ", err)
continue
}
g, err := glob.Compile(k)
if err != nil {
TermMessage("Error with glob setting ", k, ": ", err)
continue
}
if g.MatchString(buf.Path) {
for k1, v1 := range v.(map[string]interface{}) {
buf.Settings[k1] = v1
}
if g.MatchString(buf.Path) {
for k1, v1 := range v.(map[string]interface{}) {
buf.Settings[k1] = v1
}
}
}
@@ -203,44 +192,37 @@ func GetOption(name string) interface{} {
func DefaultGlobalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"keepautoindent": false,
"autosave": false,
"basename": false,
"colorcolumn": float64(0),
"colorscheme": "default",
"cursorline": true,
"eofnewline": false,
"fastdirty": true,
"fileformat": "unix",
"hidehelp": false,
"rmtrailingws": false,
"ignorecase": false,
"indentchar": " ",
"infobar": true,
"keepautoindent": false,
"keymenu": false,
"matchbrace": false,
"matchbraceleft": false,
"mouse": true,
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
"pluginrepos": []string{},
"rmtrailingws": false,
"ruler": true,
"savecursor": false,
"savehistory": true,
"saveundo": false,
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"softwrap": false,
"splitbottom": true,
"splitright": true,
"splitRight": true,
"splitBottom": true,
"statusline": true,
"sucmd": "sudo",
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"termtitle": false,
"useprimary": true,
"pluginchannels": []string{
"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
},
"pluginrepos": []string{},
"useprimary": true,
"fileformat": "unix",
"mouse": true,
}
}
@@ -249,36 +231,31 @@ func DefaultGlobalSettings() map[string]interface{} {
func DefaultLocalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"keepautoindent": false,
"autosave": false,
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"eofnewline": false,
"fastdirty": true,
"fileformat": "unix",
"rmtrailingws": false,
"filetype": "Unknown",
"hidehelp": false,
"ignorecase": false,
"indentchar": " ",
"keepautoindent": false,
"matchbrace": false,
"matchbraceleft": false,
"rmtrailingws": false,
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"softwrap": false,
"splitbottom": true,
"splitright": true,
"splitRight": true,
"splitBottom": true,
"statusline": true,
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"useprimary": true,
"fileformat": "unix",
"mouse": true,
}
}
@@ -326,32 +303,22 @@ func SetOption(option, value string) error {
// LoadSyntaxFiles()
InitColorscheme()
for _, tab := range tabs {
for _, view := range tab.Views {
for _, view := range tab.views {
view.Buf.UpdateRules()
}
}
}
if option == "infobar" || option == "keymenu" {
if option == "infobar" {
for _, tab := range tabs {
tab.Resize()
}
}
if option == "mouse" {
if !nativeValue.(bool) {
screen.DisableMouse()
} else {
screen.EnableMouse()
}
}
if len(tabs) != 0 {
if _, ok := CurView().Buf.Settings[option]; ok {
for _, tab := range tabs {
for _, view := range tab.Views {
SetLocalOption(option, value, view)
}
if _, ok := CurView().Buf.Settings[option]; ok {
for _, tab := range tabs {
for _, view := range tab.views {
SetLocalOption(option, value, view)
}
}
}
@@ -391,40 +358,6 @@ func SetLocalOption(option, value string, view *View) error {
return err
}
if option == "fastdirty" {
// If it is being turned off, we have to hash every open buffer
var empty [md5.Size]byte
var wg sync.WaitGroup
for _, tab := range tabs {
for _, v := range tab.Views {
if !nativeValue.(bool) {
if v.Buf.origHash == empty {
wg.Add(1)
go func(b *Buffer) { // calculate md5 hash of the file
defer wg.Done()
if file, e := os.Open(b.AbsPath); e == nil {
defer file.Close()
h := md5.New()
if _, e = io.Copy(h, file); e == nil {
h.Sum(b.origHash[:0])
}
}
}(v.Buf)
}
} else {
v.Buf.IsModified = v.Buf.Modified()
}
}
}
wg.Wait()
}
buf.Settings[option] = nativeValue
if option == "statusline" {
@@ -445,9 +378,15 @@ func SetLocalOption(option, value string, view *View) error {
if !nativeValue.(bool) {
buf.ClearMatches()
} else {
if buf.highlighter != nil {
buf.highlighter.HighlightStates(buf)
}
buf.highlighter.HighlightStates(buf)
}
}
if option == "mouse" {
if !nativeValue.(bool) {
screen.DisableMouse()
} else {
screen.EnableMouse()
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -70,9 +70,9 @@ func (l *LeafNode) VSplit(buf *Buffer, splitIndex int) {
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
tab.Views = append(tab.Views, nil)
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
tab.Views[splitIndex] = newView
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
} else {
@@ -94,9 +94,9 @@ func (l *LeafNode) VSplit(buf *Buffer, splitIndex int) {
l.parent.children[search(l.parent.children, l)] = s
l.parent = s
tab.Views = append(tab.Views, nil)
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
tab.Views[splitIndex] = newView
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
}
@@ -123,9 +123,9 @@ func (l *LeafNode) HSplit(buf *Buffer, splitIndex int) {
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
tab.Views = append(tab.Views, nil)
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
tab.Views[splitIndex] = newView
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
} else {
@@ -139,7 +139,7 @@ func (l *LeafNode) HSplit(buf *Buffer, splitIndex int) {
s.parent = l.parent
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
newView.Num = len(tab.Views)
newView.Num = len(tab.views)
if splitIndex == 1 {
s.children = []Node{l, NewLeafNode(newView, s)}
} else {
@@ -148,9 +148,9 @@ func (l *LeafNode) HSplit(buf *Buffer, splitIndex int) {
l.parent.children[search(l.parent.children, l)] = s
l.parent = s
tab.Views = append(tab.Views, nil)
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
tab.Views[splitIndex] = newView
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
}
@@ -167,12 +167,12 @@ func (l *LeafNode) Delete() {
l.parent.children = l.parent.children[:len(l.parent.children)-1]
tab := tabs[l.parent.tabNum]
j := findView(tab.Views, l.view)
copy(tab.Views[j:], tab.Views[j+1:])
tab.Views[len(tab.Views)-1] = nil // or the zero value of T
tab.Views = tab.Views[:len(tab.Views)-1]
j := findView(tab.views, l.view)
copy(tab.views[j:], tab.views[j+1:])
tab.views[len(tab.views)-1] = nil // or the zero value of T
tab.views = tab.views[:len(tab.views)-1]
for i, v := range tab.Views {
for i, v := range tab.views {
v.Num = i
}
if tab.CurView > 0 {

View File

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

View File

@@ -8,13 +8,11 @@ import (
var tabBarOffset int
// A Tab holds an array of views and a splitTree to determine how the
// views should be arranged
type Tab struct {
// This contains all the views in this tab
// There is generally only one view per tab, but you can have
// multiple views with splits
Views []*View
views []*View
// This is the current view for this tab
CurView int
@@ -24,12 +22,12 @@ type Tab struct {
// NewTabFromView creates a new tab and puts the given view in the tab
func NewTabFromView(v *View) *Tab {
t := new(Tab)
t.Views = append(t.Views, v)
t.Views[0].Num = 0
t.views = append(t.views, v)
t.views[0].Num = 0
t.tree = new(SplitTree)
t.tree.kind = VerticalSplit
t.tree.children = []Node{NewLeafNode(t.Views[0], t.tree)}
t.tree.children = []Node{NewLeafNode(t.views[0], t.tree)}
w, h := screen.Size()
t.tree.width = w
@@ -38,9 +36,6 @@ func NewTabFromView(v *View) *Tab {
if globalSettings["infobar"].(bool) {
t.tree.height--
}
if globalSettings["keymenu"].(bool) {
t.tree.height -= 2
}
t.Resize()
@@ -50,18 +45,15 @@ func NewTabFromView(v *View) *Tab {
// SetNum sets all this tab's views to have the correct tab number
func (t *Tab) SetNum(num int) {
t.tree.tabNum = num
for _, v := range t.Views {
for _, v := range t.views {
v.TabNum = num
}
}
// Cleanup cleans up the tree (for example if views have closed)
func (t *Tab) Cleanup() {
t.tree.Cleanup()
}
// Resize handles a resize event from the terminal and resizes
// all child views correctly
func (t *Tab) Resize() {
w, h := screen.Size()
t.tree.width = w
@@ -70,24 +62,18 @@ func (t *Tab) Resize() {
if globalSettings["infobar"].(bool) {
t.tree.height--
}
if globalSettings["keymenu"].(bool) {
t.tree.height -= 2
}
t.tree.ResizeSplits()
for i, v := range t.Views {
for i, v := range t.views {
v.Num = i
if v.Type == vtTerm {
v.term.Resize(v.Width, v.Height)
}
}
}
// CurView returns the current view
func CurView() *View {
curTab := tabs[curTab]
return curTab.Views[curTab.CurView]
return curTab.views[curTab.CurView]
}
// TabbarString returns the string that should be displayed in the tabbar
@@ -103,9 +89,9 @@ func TabbarString() (string, map[int]int) {
} else {
str += " "
}
buf := t.Views[t.CurView].Buf
buf := t.views[t.CurView].Buf
str += buf.GetName()
if buf.Modified() {
if buf.IsModified {
str += " +"
}
if i == curTab {

View File

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

View File

@@ -1,203 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,6 +0,0 @@
# Terminfo parser
This terminfo parser was written by the authors of [tcell](https://github.com/gdamore/tcell). We are using it here
to compile the terminal database if the terminal entry is not found in set of precompiled terminals.
The source for `mkinfo.go` is adapted from tcell's `mkinfo` tool to be more of a library.

View File

@@ -1,512 +0,0 @@
// Copyright 2017 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This command is used to generate suitable configuration files in either
// go syntax or in JSON. It defaults to JSON output on stdout. If no
// term values are specified on the command line, then $TERM is used.
//
// Usage is like this:
//
// mkinfo [-init] [-go file.go] [-json file.json] [-quiet] [-nofatal] [<term>...]
//
// -gzip specifies output should be compressed (json only)
// -go specifies Go output into the named file. Use - for stdout.
// -json specifies JSON output in the named file. Use - for stdout
// -nofatal indicates that errors loading definitions should not be fatal
//
package terminfo
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
)
type termcap struct {
name string
desc string
aliases []string
bools map[string]bool
nums map[string]int
strs map[string]string
}
func (tc *termcap) getnum(s string) int {
return (tc.nums[s])
}
func (tc *termcap) getflag(s string) bool {
return (tc.bools[s])
}
func (tc *termcap) getstr(s string) string {
return (tc.strs[s])
}
const (
NONE = iota
CTRL
ESC
)
func unescape(s string) string {
// Various escapes are in \x format. Control codes are
// encoded as ^M (carat followed by ASCII equivalent).
// Escapes are: \e, \E - escape
// \0 NULL, \n \l \r \t \b \f \s for equivalent C escape.
buf := &bytes.Buffer{}
esc := NONE
for i := 0; i < len(s); i++ {
c := s[i]
switch esc {
case NONE:
switch c {
case '\\':
esc = ESC
case '^':
esc = CTRL
default:
buf.WriteByte(c)
}
case CTRL:
buf.WriteByte(c - 0x40)
esc = NONE
case ESC:
switch c {
case 'E', 'e':
buf.WriteByte(0x1b)
case '0', '1', '2', '3', '4', '5', '6', '7':
if i+2 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' && s[i+2] >= '0' && s[i+2] <= '7' {
buf.WriteByte(((c - '0') * 64) + ((s[i+1] - '0') * 8) + (s[i+2] - '0'))
i = i + 2
} else if c == '0' {
buf.WriteByte(0)
}
case 'n':
buf.WriteByte('\n')
case 'r':
buf.WriteByte('\r')
case 't':
buf.WriteByte('\t')
case 'b':
buf.WriteByte('\b')
case 'f':
buf.WriteByte('\f')
case 's':
buf.WriteByte(' ')
case 'l':
panic("WTF: weird format: " + s)
default:
buf.WriteByte(c)
}
esc = NONE
}
}
return (buf.String())
}
func (tc *termcap) setupterm(name string) error {
cmd := exec.Command("infocmp", "-1", name)
output := &bytes.Buffer{}
cmd.Stdout = output
tc.strs = make(map[string]string)
tc.bools = make(map[string]bool)
tc.nums = make(map[string]int)
err := cmd.Run()
if err != nil {
return err
}
// Now parse the output.
// We get comment lines (starting with "#"), followed by
// a header line that looks like "<name>|<alias>|...|<desc>"
// then capabilities, one per line, starting with a tab and ending
// with a comma and newline.
lines := strings.Split(output.String(), "\n")
for len(lines) > 0 && strings.HasPrefix(lines[0], "#") {
lines = lines[1:]
}
// Ditch trailing empty last line
if lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
header := lines[0]
if strings.HasSuffix(header, ",") {
header = header[:len(header)-1]
}
names := strings.Split(header, "|")
tc.name = names[0]
names = names[1:]
if len(names) > 0 {
tc.desc = names[len(names)-1]
names = names[:len(names)-1]
}
tc.aliases = names
for _, val := range lines[1:] {
if (!strings.HasPrefix(val, "\t")) ||
(!strings.HasSuffix(val, ",")) {
return (errors.New("malformed infocmp: " + val))
}
val = val[1:]
val = val[:len(val)-1]
if k := strings.SplitN(val, "=", 2); len(k) == 2 {
tc.strs[k[0]] = unescape(k[1])
} else if k := strings.SplitN(val, "#", 2); len(k) == 2 {
if u, err := strconv.ParseUint(k[1], 10, 0); err != nil {
return (err)
} else {
tc.nums[k[0]] = int(u)
}
} else {
tc.bools[val] = true
}
}
return nil
}
// This program is used to collect data from the system's terminfo library,
// and write it into Go source code. That is, we maintain our terminfo
// capabilities encoded in the program. It should never need to be run by
// an end user, but developers can use this to add codes for additional
// terminal types.
//
// If a terminal name ending with -truecolor is given, and we cannot find
// one, we will try to fabricate one from either the -256color (if present)
// or the unadorned base name, adding the XTerm specific 24-bit color
// escapes. We believe that all 24-bit capable terminals use the same
// escape sequences, and terminfo has yet to evolve to support this.
func getinfo(name string) (*Terminfo, string, error) {
var tc termcap
addTrueColor := false
if err := tc.setupterm(name); err != nil {
if strings.HasSuffix(name, "-truecolor") {
base := name[:len(name)-len("-truecolor")]
// Probably -256color is closest to what we want
if err = tc.setupterm(base + "-256color"); err != nil {
err = tc.setupterm(base)
}
if err == nil {
addTrueColor = true
}
tc.name = name
}
if err != nil {
return nil, "", err
}
}
t := &Terminfo{}
// If this is an alias record, then just emit the alias
t.Name = tc.name
if t.Name != name {
return t, "", nil
}
t.Aliases = tc.aliases
t.Colors = tc.getnum("colors")
t.Columns = tc.getnum("cols")
t.Lines = tc.getnum("lines")
t.Bell = tc.getstr("bel")
t.Clear = tc.getstr("clear")
t.EnterCA = tc.getstr("smcup")
t.ExitCA = tc.getstr("rmcup")
t.ShowCursor = tc.getstr("cnorm")
t.HideCursor = tc.getstr("civis")
t.AttrOff = tc.getstr("sgr0")
t.Underline = tc.getstr("smul")
t.Bold = tc.getstr("bold")
t.Blink = tc.getstr("blink")
t.Dim = tc.getstr("dim")
t.Reverse = tc.getstr("rev")
t.EnterKeypad = tc.getstr("smkx")
t.ExitKeypad = tc.getstr("rmkx")
t.SetFg = tc.getstr("setaf")
t.SetBg = tc.getstr("setab")
t.SetCursor = tc.getstr("cup")
t.CursorBack1 = tc.getstr("cub1")
t.CursorUp1 = tc.getstr("cuu1")
t.KeyF1 = tc.getstr("kf1")
t.KeyF2 = tc.getstr("kf2")
t.KeyF3 = tc.getstr("kf3")
t.KeyF4 = tc.getstr("kf4")
t.KeyF5 = tc.getstr("kf5")
t.KeyF6 = tc.getstr("kf6")
t.KeyF7 = tc.getstr("kf7")
t.KeyF8 = tc.getstr("kf8")
t.KeyF9 = tc.getstr("kf9")
t.KeyF10 = tc.getstr("kf10")
t.KeyF11 = tc.getstr("kf11")
t.KeyF12 = tc.getstr("kf12")
t.KeyF13 = tc.getstr("kf13")
t.KeyF14 = tc.getstr("kf14")
t.KeyF15 = tc.getstr("kf15")
t.KeyF16 = tc.getstr("kf16")
t.KeyF17 = tc.getstr("kf17")
t.KeyF18 = tc.getstr("kf18")
t.KeyF19 = tc.getstr("kf19")
t.KeyF20 = tc.getstr("kf20")
t.KeyF21 = tc.getstr("kf21")
t.KeyF22 = tc.getstr("kf22")
t.KeyF23 = tc.getstr("kf23")
t.KeyF24 = tc.getstr("kf24")
t.KeyF25 = tc.getstr("kf25")
t.KeyF26 = tc.getstr("kf26")
t.KeyF27 = tc.getstr("kf27")
t.KeyF28 = tc.getstr("kf28")
t.KeyF29 = tc.getstr("kf29")
t.KeyF30 = tc.getstr("kf30")
t.KeyF31 = tc.getstr("kf31")
t.KeyF32 = tc.getstr("kf32")
t.KeyF33 = tc.getstr("kf33")
t.KeyF34 = tc.getstr("kf34")
t.KeyF35 = tc.getstr("kf35")
t.KeyF36 = tc.getstr("kf36")
t.KeyF37 = tc.getstr("kf37")
t.KeyF38 = tc.getstr("kf38")
t.KeyF39 = tc.getstr("kf39")
t.KeyF40 = tc.getstr("kf40")
t.KeyF41 = tc.getstr("kf41")
t.KeyF42 = tc.getstr("kf42")
t.KeyF43 = tc.getstr("kf43")
t.KeyF44 = tc.getstr("kf44")
t.KeyF45 = tc.getstr("kf45")
t.KeyF46 = tc.getstr("kf46")
t.KeyF47 = tc.getstr("kf47")
t.KeyF48 = tc.getstr("kf48")
t.KeyF49 = tc.getstr("kf49")
t.KeyF50 = tc.getstr("kf50")
t.KeyF51 = tc.getstr("kf51")
t.KeyF52 = tc.getstr("kf52")
t.KeyF53 = tc.getstr("kf53")
t.KeyF54 = tc.getstr("kf54")
t.KeyF55 = tc.getstr("kf55")
t.KeyF56 = tc.getstr("kf56")
t.KeyF57 = tc.getstr("kf57")
t.KeyF58 = tc.getstr("kf58")
t.KeyF59 = tc.getstr("kf59")
t.KeyF60 = tc.getstr("kf60")
t.KeyF61 = tc.getstr("kf61")
t.KeyF62 = tc.getstr("kf62")
t.KeyF63 = tc.getstr("kf63")
t.KeyF64 = tc.getstr("kf64")
t.KeyInsert = tc.getstr("kich1")
t.KeyDelete = tc.getstr("kdch1")
t.KeyBackspace = tc.getstr("kbs")
t.KeyHome = tc.getstr("khome")
t.KeyEnd = tc.getstr("kend")
t.KeyUp = tc.getstr("kcuu1")
t.KeyDown = tc.getstr("kcud1")
t.KeyRight = tc.getstr("kcuf1")
t.KeyLeft = tc.getstr("kcub1")
t.KeyPgDn = tc.getstr("knp")
t.KeyPgUp = tc.getstr("kpp")
t.KeyBacktab = tc.getstr("kcbt")
t.KeyExit = tc.getstr("kext")
t.KeyCancel = tc.getstr("kcan")
t.KeyPrint = tc.getstr("kprt")
t.KeyHelp = tc.getstr("khlp")
t.KeyClear = tc.getstr("kclr")
t.AltChars = tc.getstr("acsc")
t.EnterAcs = tc.getstr("smacs")
t.ExitAcs = tc.getstr("rmacs")
t.EnableAcs = tc.getstr("enacs")
t.Mouse = tc.getstr("kmous")
t.KeyShfRight = tc.getstr("kRIT")
t.KeyShfLeft = tc.getstr("kLFT")
t.KeyShfHome = tc.getstr("kHOM")
t.KeyShfEnd = tc.getstr("kEND")
// Terminfo lacks descriptions for a bunch of modified keys,
// but modern XTerm and emulators often have them. Let's add them,
// if the shifted right and left arrows are defined.
if t.KeyShfRight == "\x1b[1;2C" && t.KeyShfLeft == "\x1b[1;2D" {
t.KeyShfUp = "\x1b[1;2A"
t.KeyShfDown = "\x1b[1;2B"
t.KeyMetaUp = "\x1b[1;9A"
t.KeyMetaDown = "\x1b[1;9B"
t.KeyMetaRight = "\x1b[1;9C"
t.KeyMetaLeft = "\x1b[1;9D"
t.KeyAltUp = "\x1b[1;3A"
t.KeyAltDown = "\x1b[1;3B"
t.KeyAltRight = "\x1b[1;3C"
t.KeyAltLeft = "\x1b[1;3D"
t.KeyCtrlUp = "\x1b[1;5A"
t.KeyCtrlDown = "\x1b[1;5B"
t.KeyCtrlRight = "\x1b[1;5C"
t.KeyCtrlLeft = "\x1b[1;5D"
t.KeyAltShfUp = "\x1b[1;4A"
t.KeyAltShfDown = "\x1b[1;4B"
t.KeyAltShfRight = "\x1b[1;4C"
t.KeyAltShfLeft = "\x1b[1;4D"
t.KeyMetaShfUp = "\x1b[1;10A"
t.KeyMetaShfDown = "\x1b[1;10B"
t.KeyMetaShfRight = "\x1b[1;10C"
t.KeyMetaShfLeft = "\x1b[1;10D"
t.KeyCtrlShfUp = "\x1b[1;6A"
t.KeyCtrlShfDown = "\x1b[1;6B"
t.KeyCtrlShfRight = "\x1b[1;6C"
t.KeyCtrlShfLeft = "\x1b[1;6D"
}
// And also for Home and End
if t.KeyShfHome == "\x1b[1;2H" && t.KeyShfEnd == "\x1b[1;2F" {
t.KeyCtrlHome = "\x1b[1;5H"
t.KeyCtrlEnd = "\x1b[1;5F"
t.KeyAltHome = "\x1b[1;9H"
t.KeyAltEnd = "\x1b[1;9F"
t.KeyCtrlShfHome = "\x1b[1;6H"
t.KeyCtrlShfEnd = "\x1b[1;6F"
t.KeyAltShfHome = "\x1b[1;4H"
t.KeyAltShfEnd = "\x1b[1;4F"
t.KeyMetaShfHome = "\x1b[1;10H"
t.KeyMetaShfEnd = "\x1b[1;10F"
}
// And the same thing for rxvt and workalikes (Eterm, aterm, etc.)
// It seems that urxvt at least send ESC as ALT prefix for these,
// although some places seem to indicate a separate ALT key sesquence.
if t.KeyShfRight == "\x1b[c" && t.KeyShfLeft == "\x1b[d" {
t.KeyShfUp = "\x1b[a"
t.KeyShfDown = "\x1b[b"
t.KeyCtrlUp = "\x1b[Oa"
t.KeyCtrlDown = "\x1b[Ob"
t.KeyCtrlRight = "\x1b[Oc"
t.KeyCtrlLeft = "\x1b[Od"
}
if t.KeyShfHome == "\x1b[7$" && t.KeyShfEnd == "\x1b[8$" {
t.KeyCtrlHome = "\x1b[7^"
t.KeyCtrlEnd = "\x1b[8^"
}
// If the kmous entry is present, then we need to record the
// the codes to enter and exit mouse mode. Sadly, this is not
// part of the terminfo databases anywhere that I've found, but
// is an extension. The escape codes are documented in the XTerm
// manual, and all terminals that have kmous are expected to
// use these same codes, unless explicitly configured otherwise
// vi XM. Note that in any event, we only known how to parse either
// x11 or SGR mouse events -- if your terminal doesn't support one
// of these two forms, you maybe out of luck.
t.MouseMode = tc.getstr("XM")
if t.Mouse != "" && t.MouseMode == "" {
// we anticipate that all xterm mouse tracking compatible
// terminals understand mouse tracking (1000), but we hope
// that those that don't understand any-event tracking (1003)
// will at least ignore it. Likewise we hope that terminals
// that don't understand SGR reporting (1006) just ignore it.
t.MouseMode = "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;" +
"\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c"
}
// We only support colors in ANSI 8 or 256 color mode.
if t.Colors < 8 || t.SetFg == "" {
t.Colors = 0
}
if t.SetCursor == "" {
return nil, "", errors.New("terminal not cursor addressable")
}
// For padding, we lookup the pad char. If that isn't present,
// and npc is *not* set, then we assume a null byte.
t.PadChar = tc.getstr("pad")
if t.PadChar == "" {
if !tc.getflag("npc") {
t.PadChar = "\u0000"
}
}
// For some terminals we fabricate a -truecolor entry, that may
// not exist in terminfo.
if addTrueColor {
t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
"48;2;%p4%d;%p5%d;%p6%dm"
}
// For terminals that use "standard" SGR sequences, lets combine the
// foreground and background together.
if strings.HasPrefix(t.SetFg, "\x1b[") &&
strings.HasPrefix(t.SetBg, "\x1b[") &&
strings.HasSuffix(t.SetFg, "m") &&
strings.HasSuffix(t.SetBg, "m") {
fg := t.SetFg[:len(t.SetFg)-1]
r := regexp.MustCompile("%p1")
bg := r.ReplaceAllString(t.SetBg[2:], "%p2")
t.SetFgBg = fg + ";" + bg
}
return t, tc.desc, nil
}
func WriteDB(filename string) error {
var e error
js := []byte{}
args := []string{os.Getenv("TERM")}
tdata := make(map[string]*Terminfo)
descs := make(map[string]string)
for _, term := range args {
if t, desc, e := getinfo(term); e != nil {
return e
} else {
tdata[term] = t
descs[term] = desc
}
}
if len(tdata) == 0 {
// No data.
return errors.New("No data")
}
o := os.Stdout
if o, e = os.Create(filename); e != nil {
return e
}
var w io.WriteCloser
w = o
for _, term := range args {
if t := tdata[term]; t != nil {
js, e = json.Marshal(t)
fmt.Fprintln(w, string(js))
}
// arguably if there is more than one term, this
// should be a javascript array, but that's not how
// we load it. We marshal objects one at a time from
// the file.
}
if e != nil {
return e
}
w.Close()
if w != o {
o.Close()
}
return nil
}

View File

@@ -1,837 +0,0 @@
// Copyright 2017 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package terminfo
import (
"bytes"
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path"
"strconv"
"strings"
"sync"
)
var (
// ErrTermNotFound indicates that a suitable terminal entry could
// not be found. This can result from either not having TERM set,
// or from the TERM failing to support certain minimal functionality,
// in particular absolute cursor addressability (the cup capability)
// is required. For example, legacy "adm3" lacks this capability,
// whereas the slightly newer "adm3a" supports it. This failure
// occurs most often with "dumb".
ErrTermNotFound = errors.New("terminal entry not found")
)
// Terminfo represents a terminfo entry. Note that we use friendly names
// in Go, but when we write out JSON, we use the same names as terminfo.
// The name, aliases and smous, rmous fields do not come from terminfo directly.
type Terminfo struct {
Name string `json:"name"`
Aliases []string `json:"aliases,omitempty"`
Columns int `json:"cols,omitempty"` // cols
Lines int `json:"lines,omitempty"` // lines
Colors int `json:"colors,omitempty"` // colors
Bell string `json:"bell,omitempty"` // bell
Clear string `json:"clear,omitempty"` // clear
EnterCA string `json:"smcup,omitempty"` // smcup
ExitCA string `json:"rmcup,omitempty"` // rmcup
ShowCursor string `json:"cnorm,omitempty"` // cnorm
HideCursor string `json:"civis,omitempty"` // civis
AttrOff string `json:"sgr0,omitempty"` // sgr0
Underline string `json:"smul,omitempty"` // smul
Bold string `json:"bold,omitempty"` // bold
Blink string `json:"blink,omitempty"` // blink
Reverse string `json:"rev,omitempty"` // rev
Dim string `json:"dim,omitempty"` // dim
EnterKeypad string `json:"smkx,omitempty"` // smkx
ExitKeypad string `json:"rmkx,omitempty"` // rmkx
SetFg string `json:"setaf,omitempty"` // setaf
SetBg string `json:"setbg,omitempty"` // setab
SetCursor string `json:"cup,omitempty"` // cup
CursorBack1 string `json:"cub1,omitempty"` // cub1
CursorUp1 string `json:"cuu1,omitempty"` // cuu1
PadChar string `json:"pad,omitempty"` // pad
KeyBackspace string `json:"kbs,omitempty"` // kbs
KeyF1 string `json:"kf1,omitempty"` // kf1
KeyF2 string `json:"kf2,omitempty"` // kf2
KeyF3 string `json:"kf3,omitempty"` // kf3
KeyF4 string `json:"kf4,omitempty"` // kf4
KeyF5 string `json:"kf5,omitempty"` // kf5
KeyF6 string `json:"kf6,omitempty"` // kf6
KeyF7 string `json:"kf7,omitempty"` // kf7
KeyF8 string `json:"kf8,omitempty"` // kf8
KeyF9 string `json:"kf9,omitempty"` // kf9
KeyF10 string `json:"kf10,omitempty"` // kf10
KeyF11 string `json:"kf11,omitempty"` // kf11
KeyF12 string `json:"kf12,omitempty"` // kf12
KeyF13 string `json:"kf13,omitempty"` // kf13
KeyF14 string `json:"kf14,omitempty"` // kf14
KeyF15 string `json:"kf15,omitempty"` // kf15
KeyF16 string `json:"kf16,omitempty"` // kf16
KeyF17 string `json:"kf17,omitempty"` // kf17
KeyF18 string `json:"kf18,omitempty"` // kf18
KeyF19 string `json:"kf19,omitempty"` // kf19
KeyF20 string `json:"kf20,omitempty"` // kf20
KeyF21 string `json:"kf21,omitempty"` // kf21
KeyF22 string `json:"kf22,omitempty"` // kf22
KeyF23 string `json:"kf23,omitempty"` // kf23
KeyF24 string `json:"kf24,omitempty"` // kf24
KeyF25 string `json:"kf25,omitempty"` // kf25
KeyF26 string `json:"kf26,omitempty"` // kf26
KeyF27 string `json:"kf27,omitempty"` // kf27
KeyF28 string `json:"kf28,omitempty"` // kf28
KeyF29 string `json:"kf29,omitempty"` // kf29
KeyF30 string `json:"kf30,omitempty"` // kf30
KeyF31 string `json:"kf31,omitempty"` // kf31
KeyF32 string `json:"kf32,omitempty"` // kf32
KeyF33 string `json:"kf33,omitempty"` // kf33
KeyF34 string `json:"kf34,omitempty"` // kf34
KeyF35 string `json:"kf35,omitempty"` // kf35
KeyF36 string `json:"kf36,omitempty"` // kf36
KeyF37 string `json:"kf37,omitempty"` // kf37
KeyF38 string `json:"kf38,omitempty"` // kf38
KeyF39 string `json:"kf39,omitempty"` // kf39
KeyF40 string `json:"kf40,omitempty"` // kf40
KeyF41 string `json:"kf41,omitempty"` // kf41
KeyF42 string `json:"kf42,omitempty"` // kf42
KeyF43 string `json:"kf43,omitempty"` // kf43
KeyF44 string `json:"kf44,omitempty"` // kf44
KeyF45 string `json:"kf45,omitempty"` // kf45
KeyF46 string `json:"kf46,omitempty"` // kf46
KeyF47 string `json:"kf47,omitempty"` // kf47
KeyF48 string `json:"kf48,omitempty"` // kf48
KeyF49 string `json:"kf49,omitempty"` // kf49
KeyF50 string `json:"kf50,omitempty"` // kf50
KeyF51 string `json:"kf51,omitempty"` // kf51
KeyF52 string `json:"kf52,omitempty"` // kf52
KeyF53 string `json:"kf53,omitempty"` // kf53
KeyF54 string `json:"kf54,omitempty"` // kf54
KeyF55 string `json:"kf55,omitempty"` // kf55
KeyF56 string `json:"kf56,omitempty"` // kf56
KeyF57 string `json:"kf57,omitempty"` // kf57
KeyF58 string `json:"kf58,omitempty"` // kf58
KeyF59 string `json:"kf59,omitempty"` // kf59
KeyF60 string `json:"kf60,omitempty"` // kf60
KeyF61 string `json:"kf61,omitempty"` // kf61
KeyF62 string `json:"kf62,omitempty"` // kf62
KeyF63 string `json:"kf63,omitempty"` // kf63
KeyF64 string `json:"kf64,omitempty"` // kf64
KeyInsert string `json:"kich,omitempty"` // kich1
KeyDelete string `json:"kdch,omitempty"` // kdch1
KeyHome string `json:"khome,omitempty"` // khome
KeyEnd string `json:"kend,omitempty"` // kend
KeyHelp string `json:"khlp,omitempty"` // khlp
KeyPgUp string `json:"kpp,omitempty"` // kpp
KeyPgDn string `json:"knp,omitempty"` // knp
KeyUp string `json:"kcuu1,omitempty"` // kcuu1
KeyDown string `json:"kcud1,omitempty"` // kcud1
KeyLeft string `json:"kcub1,omitempty"` // kcub1
KeyRight string `json:"kcuf1,omitempty"` // kcuf1
KeyBacktab string `json:"kcbt,omitempty"` // kcbt
KeyExit string `json:"kext,omitempty"` // kext
KeyClear string `json:"kclr,omitempty"` // kclr
KeyPrint string `json:"kprt,omitempty"` // kprt
KeyCancel string `json:"kcan,omitempty"` // kcan
Mouse string `json:"kmous,omitempty"` // kmous
MouseMode string `json:"XM,omitempty"` // XM
AltChars string `json:"acsc,omitempty"` // acsc
EnterAcs string `json:"smacs,omitempty"` // smacs
ExitAcs string `json:"rmacs,omitempty"` // rmacs
EnableAcs string `json:"enacs,omitempty"` // enacs
KeyShfRight string `json:"kRIT,omitempty"` // kRIT
KeyShfLeft string `json:"kLFT,omitempty"` // kLFT
KeyShfHome string `json:"kHOM,omitempty"` // kHOM
KeyShfEnd string `json:"kEND,omitempty"` // kEND
// These are non-standard extensions to terminfo. This includes
// true color support, and some additional keys. Its kind of bizarre
// that shifted variants of left and right exist, but not up and down.
// Terminal support for these are going to vary amongst XTerm
// emulations, so don't depend too much on them in your application.
SetFgBg string `json:"_setfgbg,omitempty"` // setfgbg
SetFgBgRGB string `json:"_setfgbgrgb,omitempty"` // setfgbgrgb
SetFgRGB string `json:"_setfrgb,omitempty"` // setfrgb
SetBgRGB string `json:"_setbrgb,omitempty"` // setbrgb
KeyShfUp string `json:"_kscu1,omitempty"` // shift-up
KeyShfDown string `json:"_kscud1,omitempty"` // shift-down
KeyCtrlUp string `json:"_kccu1,omitempty"` // ctrl-up
KeyCtrlDown string `json:"_kccud1,omitempty"` // ctrl-left
KeyCtrlRight string `json:"_kccuf1,omitempty"` // ctrl-right
KeyCtrlLeft string `json:"_kccub1,omitempty"` // ctrl-left
KeyMetaUp string `json:"_kmcu1,omitempty"` // meta-up
KeyMetaDown string `json:"_kmcud1,omitempty"` // meta-left
KeyMetaRight string `json:"_kmcuf1,omitempty"` // meta-right
KeyMetaLeft string `json:"_kmcub1,omitempty"` // meta-left
KeyAltUp string `json:"_kacu1,omitempty"` // alt-up
KeyAltDown string `json:"_kacud1,omitempty"` // alt-left
KeyAltRight string `json:"_kacuf1,omitempty"` // alt-right
KeyAltLeft string `json:"_kacub1,omitempty"` // alt-left
KeyCtrlHome string `json:"_kchome,omitempty"`
KeyCtrlEnd string `json:"_kcend,omitempty"`
KeyMetaHome string `json:"_kmhome,omitempty"`
KeyMetaEnd string `json:"_kmend,omitempty"`
KeyAltHome string `json:"_kahome,omitempty"`
KeyAltEnd string `json:"_kaend,omitempty"`
KeyAltShfUp string `json:"_kascu1,omitempty"`
KeyAltShfDown string `json:"_kascud1,omitempty"`
KeyAltShfLeft string `json:"_kascub1,omitempty"`
KeyAltShfRight string `json:"_kascuf1,omitempty"`
KeyMetaShfUp string `json:"_kmscu1,omitempty"`
KeyMetaShfDown string `json:"_kmscud1,omitempty"`
KeyMetaShfLeft string `json:"_kmscub1,omitempty"`
KeyMetaShfRight string `json:"_kmscuf1,omitempty"`
KeyCtrlShfUp string `json:"_kcscu1,omitempty"`
KeyCtrlShfDown string `json:"_kcscud1,omitempty"`
KeyCtrlShfLeft string `json:"_kcscub1,omitempty"`
KeyCtrlShfRight string `json:"_kcscuf1,omitempty"`
KeyCtrlShfHome string `json:"_kcHOME,omitempty"`
KeyCtrlShfEnd string `json:"_kcEND,omitempty"`
KeyAltShfHome string `json:"_kaHOME,omitempty"`
KeyAltShfEnd string `json:"_kaEND,omitempty"`
KeyMetaShfHome string `json:"_kmHOME,omitempty"`
KeyMetaShfEnd string `json:"_kmEND,omitempty"`
}
type stackElem struct {
s string
i int
isStr bool
isInt bool
}
type stack []stackElem
func (st stack) Push(v string) stack {
e := stackElem{
s: v,
isStr: true,
}
return append(st, e)
}
func (st stack) Pop() (string, stack) {
v := ""
if len(st) > 0 {
e := st[len(st)-1]
st = st[:len(st)-1]
if e.isStr {
v = e.s
} else {
v = strconv.Itoa(e.i)
}
}
return v, st
}
func (st stack) PopInt() (int, stack) {
if len(st) > 0 {
e := st[len(st)-1]
st = st[:len(st)-1]
if e.isInt {
return e.i, st
} else if e.isStr {
i, _ := strconv.Atoi(e.s)
return i, st
}
}
return 0, st
}
func (st stack) PopBool() (bool, stack) {
if len(st) > 0 {
e := st[len(st)-1]
st = st[:len(st)-1]
if e.isStr {
if e.s == "1" {
return true, st
}
return false, st
} else if e.i == 1 {
return true, st
} else {
return false, st
}
}
return false, st
}
func (st stack) PushInt(i int) stack {
e := stackElem{
i: i,
isInt: true,
}
return append(st, e)
}
func (st stack) PushBool(i bool) stack {
if i {
return st.PushInt(1)
}
return st.PushInt(0)
}
func nextch(s string, index int) (byte, int) {
if index < len(s) {
return s[index], index + 1
}
return 0, index
}
// static vars
var svars [26]string
// paramsBuffer handles some persistent state for TParam. Technically we
// could probably dispense with this, but caching buffer arrays gives us
// a nice little performance boost. Furthermore, we know that TParam is
// rarely (never?) called re-entrantly, so we can just reuse the same
// buffers, making it thread-safe by stashing a lock.
type paramsBuffer struct {
out bytes.Buffer
buf bytes.Buffer
lk sync.Mutex
}
// Start initializes the params buffer with the initial string data.
// It also locks the paramsBuffer. The caller must call End() when
// finished.
func (pb *paramsBuffer) Start(s string) {
pb.lk.Lock()
pb.out.Reset()
pb.buf.Reset()
pb.buf.WriteString(s)
}
// End returns the final output from TParam, but it also releases the lock.
func (pb *paramsBuffer) End() string {
s := pb.out.String()
pb.lk.Unlock()
return s
}
// NextCh returns the next input character to the expander.
func (pb *paramsBuffer) NextCh() (byte, error) {
return pb.buf.ReadByte()
}
// PutCh "emits" (rather schedules for output) a single byte character.
func (pb *paramsBuffer) PutCh(ch byte) {
pb.out.WriteByte(ch)
}
// PutString schedules a string for output.
func (pb *paramsBuffer) PutString(s string) {
pb.out.WriteString(s)
}
var pb = &paramsBuffer{}
// TParm takes a terminfo parameterized string, such as setaf or cup, and
// evaluates the string, and returns the result with the parameter
// applied.
func (t *Terminfo) TParm(s string, p ...int) string {
var stk stack
var a, b string
var ai, bi int
var ab bool
var dvars [26]string
var params [9]int
pb.Start(s)
// make sure we always have 9 parameters -- makes it easier
// later to skip checks
for i := 0; i < len(params) && i < len(p); i++ {
params[i] = p[i]
}
nest := 0
for {
ch, err := pb.NextCh()
if err != nil {
break
}
if ch != '%' {
pb.PutCh(ch)
continue
}
ch, err = pb.NextCh()
if err != nil {
// XXX Error
break
}
switch ch {
case '%': // quoted %
pb.PutCh(ch)
case 'i': // increment both parameters (ANSI cup support)
params[0]++
params[1]++
case 'c', 's':
// NB: these, and 'd' below are special cased for
// efficiency. They could be handled by the richer
// format support below, less efficiently.
a, stk = stk.Pop()
pb.PutString(a)
case 'd':
ai, stk = stk.PopInt()
pb.PutString(strconv.Itoa(ai))
case '0', '1', '2', '3', '4', 'x', 'X', 'o', ':':
// This is pretty suboptimal, but this is rarely used.
// None of the mainstream terminals use any of this,
// and it would surprise me if this code is ever
// executed outside of test cases.
f := "%"
if ch == ':' {
ch, _ = pb.NextCh()
}
f += string(ch)
for ch == '+' || ch == '-' || ch == '#' || ch == ' ' {
ch, _ = pb.NextCh()
f += string(ch)
}
for (ch >= '0' && ch <= '9') || ch == '.' {
ch, _ = pb.NextCh()
f += string(ch)
}
switch ch {
case 'd', 'x', 'X', 'o':
ai, stk = stk.PopInt()
pb.PutString(fmt.Sprintf(f, ai))
case 'c', 's':
a, stk = stk.Pop()
pb.PutString(fmt.Sprintf(f, a))
}
case 'p': // push parameter
ch, _ = pb.NextCh()
ai = int(ch - '1')
if ai >= 0 && ai < len(params) {
stk = stk.PushInt(params[ai])
} else {
stk = stk.PushInt(0)
}
case 'P': // pop & store variable
ch, _ = pb.NextCh()
if ch >= 'A' && ch <= 'Z' {
svars[int(ch-'A')], stk = stk.Pop()
} else if ch >= 'a' && ch <= 'z' {
dvars[int(ch-'a')], stk = stk.Pop()
}
case 'g': // recall & push variable
ch, _ = pb.NextCh()
if ch >= 'A' && ch <= 'Z' {
stk = stk.Push(svars[int(ch-'A')])
} else if ch >= 'a' && ch <= 'z' {
stk = stk.Push(dvars[int(ch-'a')])
}
case '\'': // push(char)
ch, _ = pb.NextCh()
pb.NextCh() // must be ' but we don't check
stk = stk.Push(string(ch))
case '{': // push(int)
ai = 0
ch, _ = pb.NextCh()
for ch >= '0' && ch <= '9' {
ai *= 10
ai += int(ch - '0')
ch, _ = pb.NextCh()
}
// ch must be '}' but no verification
stk = stk.PushInt(ai)
case 'l': // push(strlen(pop))
a, stk = stk.Pop()
stk = stk.PushInt(len(a))
case '+':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai + bi)
case '-':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai - bi)
case '*':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai * bi)
case '/':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
if bi != 0 {
stk = stk.PushInt(ai / bi)
} else {
stk = stk.PushInt(0)
}
case 'm': // push(pop mod pop)
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
if bi != 0 {
stk = stk.PushInt(ai % bi)
} else {
stk = stk.PushInt(0)
}
case '&': // AND
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai & bi)
case '|': // OR
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai | bi)
case '^': // XOR
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai ^ bi)
case '~': // bit complement
ai, stk = stk.PopInt()
stk = stk.PushInt(ai ^ -1)
case '!': // logical NOT
ai, stk = stk.PopInt()
stk = stk.PushBool(ai != 0)
case '=': // numeric compare or string compare
b, stk = stk.Pop()
a, stk = stk.Pop()
stk = stk.PushBool(a == b)
case '>': // greater than, numeric
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushBool(ai > bi)
case '<': // less than, numeric
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushBool(ai < bi)
case '?': // start conditional
case 't':
ab, stk = stk.PopBool()
if ab {
// just keep going
break
}
nest = 0
ifloop:
// this loop consumes everything until we hit our else,
// or the end of the conditional
for {
ch, err = pb.NextCh()
if err != nil {
break
}
if ch != '%' {
continue
}
ch, _ = pb.NextCh()
switch ch {
case ';':
if nest == 0 {
break ifloop
}
nest--
case '?':
nest++
case 'e':
if nest == 0 {
break ifloop
}
}
}
case 'e':
// if we got here, it means we didn't use the else
// in the 't' case above, and we should skip until
// the end of the conditional
nest = 0
elloop:
for {
ch, err = pb.NextCh()
if err != nil {
break
}
if ch != '%' {
continue
}
ch, _ = pb.NextCh()
switch ch {
case ';':
if nest == 0 {
break elloop
}
nest--
case '?':
nest++
}
}
case ';': // endif
}
}
return pb.End()
}
// TPuts emits the string to the writer, but expands inline padding
// indications (of the form $<[delay]> where [delay] is msec) to
// a suitable number of padding characters (usually null bytes) based
// upon the supplied baud. At high baud rates, more padding characters
// will be inserted. All Terminfo based strings should be emitted using
// this function.
func (t *Terminfo) TPuts(w io.Writer, s string, baud int) {
for {
beg := strings.Index(s, "$<")
if beg < 0 {
// Most strings don't need padding, which is good news!
io.WriteString(w, s)
return
}
io.WriteString(w, s[:beg])
s = s[beg+2:]
end := strings.Index(s, ">")
if end < 0 {
// unterminated.. just emit bytes unadulterated
io.WriteString(w, "$<"+s)
return
}
val := s[:end]
s = s[end+1:]
padus := 0
unit := 1000
dot := false
loop:
for i := range val {
switch val[i] {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
padus *= 10
padus += int(val[i] - '0')
if dot {
unit *= 10
}
case '.':
if !dot {
dot = true
} else {
break loop
}
default:
break loop
}
}
cnt := int(((baud / 8) * padus) / unit)
for cnt > 0 {
io.WriteString(w, t.PadChar)
cnt--
}
}
}
// TGoto returns a string suitable for addressing the cursor at the given
// row and column. The origin 0, 0 is in the upper left corner of the screen.
func (t *Terminfo) TGoto(col, row int) string {
return t.TParm(t.SetCursor, row, col)
}
// TColor returns a string corresponding to the given foreground and background
// colors. Either fg or bg can be set to -1 to elide.
func (t *Terminfo) TColor(fi, bi int) string {
rv := ""
// As a special case, we map bright colors to lower versions if the
// color table only holds 8. For the remaining 240 colors, the user
// is out of luck. Someday we could create a mapping table, but its
// not worth it.
if t.Colors == 8 {
if fi > 7 && fi < 16 {
fi -= 8
}
if bi > 7 && bi < 16 {
bi -= 8
}
}
if t.Colors > fi && fi >= 0 {
rv += t.TParm(t.SetFg, fi)
}
if t.Colors > bi && bi >= 0 {
rv += t.TParm(t.SetBg, bi)
}
return rv
}
var (
dblock sync.Mutex
terminfos = make(map[string]*Terminfo)
aliases = make(map[string]string)
)
// AddTerminfo can be called to register a new Terminfo entry.
func AddTerminfo(t *Terminfo) {
dblock.Lock()
terminfos[t.Name] = t
for _, x := range t.Aliases {
terminfos[x] = t
}
dblock.Unlock()
}
func loadFromFile(fname string, term string) (*Terminfo, error) {
var e error
var f io.Reader
if f, e = os.Open(fname); e != nil {
return nil, e
}
if strings.HasSuffix(fname, ".gz") {
if f, e = gzip.NewReader(f); e != nil {
return nil, e
}
}
d := json.NewDecoder(f)
for {
t := &Terminfo{}
if e := d.Decode(t); e != nil {
if e == io.EOF {
return nil, ErrTermNotFound
}
return nil, e
}
if t.SetCursor == "" {
// This must be an alias record, return it.
return t, nil
}
if t.Name == term {
return t, nil
}
for _, a := range t.Aliases {
if a == term {
return t, nil
}
}
}
}
// LookupTerminfo attempts to find a definition for the named $TERM.
// It first looks in the builtin database, which should cover just about
// everyone. If it can't find one there, then it will attempt to read
// one from the JSON file located in either $TCELLDB, $HOME/.tcelldb
// or in this package's source directory as database.json).
func LookupTerminfo(name string) (*Terminfo, error) {
if name == "" {
// else on windows: index out of bounds
// on the name[0] reference below
return nil, ErrTermNotFound
}
dblock.Lock()
t := terminfos[name]
dblock.Unlock()
if t == nil {
var files []string
letter := fmt.Sprintf("%02x", name[0])
gzfile := path.Join(letter, name+".gz")
jsfile := path.Join(letter, name)
// Build up the search path. Old versions of tcell used a
// single database file, whereas the new ones locate them
// in JSON (optionally compressed) files.
//
// The search path looks like:
//
// $TCELLDB/x/xterm.gz
// $TCELLDB/x/xterm
// $TCELLDB
// $HOME/.tcelldb/x/xterm.gz
// $HOME/.tcelldb/x/xterm
// $HOME/.tcelldb
// $GOPATH/terminfo/database/x/xterm.gz
// $GOPATH/terminfo/database/x/xterm
//
if pth := os.Getenv("TCELLDB"); pth != "" {
files = append(files, path.Join(pth, gzfile))
files = append(files, path.Join(pth, jsfile))
files = append(files, pth)
}
if pth := os.Getenv("HOME"); pth != "" {
pth = path.Join(pth, ".tcelldb")
files = append(files, path.Join(pth, gzfile))
files = append(files, path.Join(pth, jsfile))
files = append(files, pth)
}
for _, pth := range strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator)) {
pth = path.Join(pth, "src", "github.com", "gdamore", "tcell", "terminfo", "database")
files = append(files, path.Join(pth, gzfile))
files = append(files, path.Join(pth, jsfile))
}
for _, fname := range files {
t, _ = loadFromFile(fname, name)
if t != nil {
break
}
}
if t != nil {
if t.Name != name {
// Check for a database loop (no infinite
// recursion).
dblock.Lock()
if aliases[name] != "" {
dblock.Unlock()
return nil, ErrTermNotFound
}
aliases[name] = t.Name
dblock.Unlock()
return LookupTerminfo(t.Name)
}
dblock.Lock()
terminfos[name] = t
dblock.Unlock()
}
}
if t == nil {
return nil, ErrTermNotFound
}
return t, nil
}

View File

@@ -1,194 +0,0 @@
// Copyright 2016 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package terminfo
import (
"bytes"
"os"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
// This terminfo entry is a stripped down version from
// xterm-256color, but I've added some of my own entries.
var testTerminfo = &Terminfo{
Name: "simulation_test",
Columns: 80,
Lines: 24,
Colors: 256,
Bell: "\a",
Blink: "\x1b2ms$<2>",
Reverse: "\x1b[7m",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
Mouse: "\x1b[M",
MouseMode: "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
PadChar: "\x00",
}
func TestTerminfo(t *testing.T) {
ti := testTerminfo
Convey("Terminfo parameter processing", t, func() {
// This tests %i, and basic parameter strings too
Convey("TGoto works", func() {
s := ti.TGoto(7, 9)
So(s, ShouldEqual, "\x1b[10;8H")
})
// This tests some conditionals
Convey("TParm extended formats work", func() {
s := ti.TParm("A[%p1%2.2X]B", 47)
So(s, ShouldEqual, "A[2F]B")
})
// This tests some conditionals
Convey("TParm colors work", func() {
s := ti.TParm(ti.SetFg, 7)
So(s, ShouldEqual, "\x1b[37m")
s = ti.TParm(ti.SetFg, 15)
So(s, ShouldEqual, "\x1b[97m")
s = ti.TParm(ti.SetFg, 200)
So(s, ShouldEqual, "\x1b[38;5;200m")
})
// This tests variables
Convey("TParm mouse mode works", func() {
s := ti.TParm(ti.MouseMode, 1)
So(s, ShouldEqual, "\x1b[?1000h\x1b[?1003h\x1b[?1006h")
s = ti.TParm(ti.MouseMode, 0)
So(s, ShouldEqual, "\x1b[?1000l\x1b[?1003l\x1b[?1006l")
})
})
Convey("Terminfo delay handling", t, func() {
Convey("19200 baud", func() {
buf := bytes.NewBuffer(nil)
ti.TPuts(buf, ti.Blink, 19200)
s := string(buf.Bytes())
So(s, ShouldEqual, "\x1b2ms\x00\x00\x00\x00")
})
Convey("50 baud", func() {
buf := bytes.NewBuffer(nil)
ti.TPuts(buf, ti.Blink, 50)
s := string(buf.Bytes())
So(s, ShouldEqual, "\x1b2ms")
})
})
}
func TestTerminfoDatabase(t *testing.T) {
Convey("Database Lookups work", t, func() {
Convey("Basic lookup works", func() {
os.Setenv("TCELLDB", "testdata/test1")
ti, err := LookupTerminfo("test1")
So(err, ShouldBeNil)
So(ti, ShouldNotBeNil)
So(ti.Columns, ShouldEqual, 80)
ti, err = LookupTerminfo("alias1")
So(err, ShouldBeNil)
So(ti, ShouldNotBeNil)
So(ti.Columns, ShouldEqual, 80)
os.Setenv("TCELLDB", "testdata")
ti, err = LookupTerminfo("test2")
So(err, ShouldBeNil)
So(ti, ShouldNotBeNil)
So(ti.Columns, ShouldEqual, 80)
So(len(ti.Aliases), ShouldEqual, 1)
So(ti.Aliases[0], ShouldEqual, "alias2")
})
Convey("Incorrect primary name works", func() {
os.Setenv("TCELLDB", "testdata")
ti, err := LookupTerminfo("test3")
So(err, ShouldNotBeNil)
So(ti, ShouldBeNil)
})
Convey("Loops fail", func() {
os.Setenv("TCELLDB", "testdata")
ti, err := LookupTerminfo("loop1")
So(ti, ShouldBeNil)
So(err, ShouldNotBeNil)
})
Convey("Gzip database works", func() {
os.Setenv("TCELLDB", "testdata")
ti, err := LookupTerminfo("test-gzip")
So(err, ShouldBeNil)
So(ti, ShouldNotBeNil)
So(ti.Columns, ShouldEqual, 80)
})
Convey("Gzip alias lookup works", func() {
os.Setenv("TCELLDB", "testdata")
ti, err := LookupTerminfo("alias-gzip")
So(err, ShouldBeNil)
So(ti, ShouldNotBeNil)
So(ti.Columns, ShouldEqual, 80)
})
Convey("Broken alias works", func() {
os.Setenv("TCELLDB", "testdata")
ti, err := LookupTerminfo("alias-none")
So(err, ShouldNotBeNil)
So(ti, ShouldBeNil)
})
Convey("Combined database works", func() {
os.Setenv("TCELLDB", "testdata/combined")
ti, err := LookupTerminfo("combined2")
So(err, ShouldBeNil)
So(ti, ShouldNotBeNil)
So(ti.Lines, ShouldEqual, 102)
ti, err = LookupTerminfo("alias-comb1")
So(err, ShouldBeNil)
So(ti, ShouldNotBeNil)
So(ti.Lines, ShouldEqual, 101)
ti, err = LookupTerminfo("combined3")
So(err, ShouldBeNil)
So(ti, ShouldNotBeNil)
So(ti.Lines, ShouldEqual, 103)
ti, err = LookupTerminfo("combined1")
So(err, ShouldBeNil)
So(ti, ShouldNotBeNil)
So(ti.Lines, ShouldEqual, 101)
})
})
}
func BenchmarkSetFgBg(b *testing.B) {
ti := testTerminfo
for i := 0; i < b.N; i++ {
ti.TParm(ti.SetFg, 100, 200)
ti.TParm(ti.SetBg, 100, 200)
}
}

View File

@@ -1,8 +1,8 @@
package main
import (
"bytes"
"os"
"os/user"
"path/filepath"
"reflect"
"runtime"
@@ -11,9 +11,7 @@ import (
"time"
"unicode/utf8"
"github.com/go-errors/errors"
"github.com/mattn/go-runewidth"
"regexp"
)
// Util.go is a collection of utility functions that are used throughout
@@ -25,54 +23,6 @@ func Count(s string) int {
return utf8.RuneCountInString(s)
}
// Convert byte array to rune array
func toRunes(b []byte) []rune {
runes := make([]rune, 0, utf8.RuneCount(b))
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
runes = append(runes, r)
b = b[size:]
}
return runes
}
func sliceStart(slc []byte, index int) []byte {
len := len(slc)
i := 0
totalSize := 0
for totalSize < len {
if i >= index {
return slc[totalSize:]
}
_, size := utf8.DecodeRune(slc[totalSize:])
totalSize += size
i++
}
return slc[totalSize:]
}
func sliceEnd(slc []byte, index int) []byte {
len := len(slc)
i := 0
totalSize := 0
for totalSize < len {
if i >= index {
return slc[:totalSize]
}
_, size := utf8.DecodeRune(slc[totalSize:])
totalSize += size
i++
}
return slc[:totalSize]
}
// NumOccurrences counts the number of occurrences of a byte in a string
func NumOccurrences(s string, c byte) int {
var n int
@@ -105,7 +55,6 @@ func Max(a, b int) int {
return b
}
// FSize gets the size of a file
func FSize(f *os.File) int64 {
fi, _ := f.Stat()
// get the size
@@ -180,7 +129,7 @@ func GetLeadingWhitespace(str string) string {
}
// IsSpaces checks if a given string is only spaces
func IsSpaces(str []byte) bool {
func IsSpaces(str string) bool {
for _, c := range str {
if c != ' ' {
return false
@@ -296,7 +245,6 @@ func lcs(a, b string) string {
return lcs
}
// CommonSubstring gets a common substring among the inputs
func CommonSubstring(arr ...string) string {
commonStr := arr[0]
@@ -325,76 +273,93 @@ func ShortFuncName(i interface{}) string {
return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
}
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
// home directory. Does nothing if the path does not start with '~'.
func ReplaceHome(path string) string {
if !strings.HasPrefix(path, "~") {
return path
// SplitCommandArgs separates multiple command arguments which may be quoted.
// The returned slice contains at least one string
func SplitCommandArgs(input string) []string {
var result []string
var curQuote *bytes.Buffer
curArg := new(bytes.Buffer)
escape := false
finishQuote := func() {
if curQuote == nil {
return
}
str := curQuote.String()
if unquoted, err := strconv.Unquote(str); err == nil {
str = unquoted
}
curArg.WriteString(str)
curQuote = nil
}
var userData *user.User
var err error
appendResult := func() {
finishQuote()
escape = false
homeString := strings.Split(path, "/")[0]
if homeString == "~" {
userData, err = user.Current()
if err != nil {
messenger.Error("Could not find user: ", err)
}
} else {
userData, err = user.Lookup(homeString[1:])
if err != nil {
if messenger != nil {
messenger.Error("Could not find user: ", err)
str := curArg.String()
result = append(result, str)
curArg.Reset()
}
for _, r := range input {
if r == ' ' && curQuote == nil {
appendResult()
} else {
runeHandled := false
appendRuneToBuff := func() {
if curQuote != nil {
curQuote.WriteRune(r)
} else {
curArg.WriteRune(r)
}
runeHandled = true
}
if r == '"' && curQuote == nil {
curQuote = new(bytes.Buffer)
appendRuneToBuff()
} else {
TermMessage("Could not find user: ", err)
if curQuote != nil && !escape {
if r == '"' {
appendRuneToBuff()
finishQuote()
} else if r == '\\' {
appendRuneToBuff()
escape = true
continue
}
}
}
return ""
if !runeHandled {
appendRuneToBuff()
}
}
escape = false
}
appendResult()
return result
}
// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
func JoinCommandArgs(args ...string) string {
buf := new(bytes.Buffer)
first := true
for _, arg := range args {
if first {
first = false
} else {
buf.WriteRune(' ')
}
quoted := strconv.Quote(arg)
if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
buf.WriteString(quoted)
} else {
buf.WriteString(arg)
}
}
home := userData.HomeDir
return strings.Replace(path, homeString, home, 1)
}
// GetPathAndCursorPosition returns a filename without everything following a `:`
// 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+))?`)
match := re.FindStringSubmatch(path)
// no lines/columns were specified in the path, return just the path with no cursor location
if len(match) == 0 {
return path, nil
} else if match[len(match)-1] != "" {
// if the last capture group match isn't empty then both line and column were provided
return match[1], match[2:]
}
// if it was empty, then only a line was provided, so default to column 0
return match[1], []string{match[2], "0"}
}
func ParseCursorLocation(cursorPositions []string) (Loc, error) {
startpos := Loc{0, 0}
var err error
// if no positions are available exit early
if cursorPositions == nil {
return startpos, errors.New("No cursor positions were provided.")
}
startpos.Y, err = strconv.Atoi(cursorPositions[0])
if err != nil {
messenger.Error("Error parsing cursor position: ", err)
} else {
if len(cursorPositions) > 1 {
startpos.X, err = strconv.Atoi(cursorPositions[1])
if err != nil {
messenger.Error("Error parsing cursor position: ", err)
}
}
}
return startpos, err
return buf.String()
}

View File

@@ -1,6 +1,7 @@
package main
import (
"reflect"
"testing"
)
@@ -66,6 +67,56 @@ func TestIsWordChar(t *testing.T) {
}
}
func TestJoinAndSplitCommandArgs(t *testing.T) {
tests := []struct {
Query []string
Wanted string
}{
{[]string{`test case`}, `"test case"`},
{[]string{`quote "test"`}, `"quote \"test\""`},
{[]string{`slash\\\ test`}, `"slash\\\\\\ test"`},
{[]string{`path 1`, `path\" 2`}, `"path 1" "path\\\" 2"`},
{[]string{`foo`}, `foo`},
{[]string{`foo\"bar`}, `"foo\\\"bar"`},
{[]string{``}, ``},
{[]string{`"`}, `"\""`},
{[]string{`a`, ``}, `a `},
{[]string{``, ``, ``, ``}, ` `},
{[]string{"\n"}, `"\n"`},
{[]string{"foo\tbar"}, `"foo\tbar"`},
}
for i, test := range tests {
if result := JoinCommandArgs(test.Query...); test.Wanted != result {
t.Errorf("JoinCommandArgs failed at Test %d\nGot: %q", i, result)
}
if result := SplitCommandArgs(test.Wanted); !reflect.DeepEqual(test.Query, result) {
t.Errorf("SplitCommandArgs failed at Test %d\nGot: `%q`", i, result)
}
}
splitTests := []struct {
Query string
Wanted []string
}{
{`"hallo""Welt"`, []string{`halloWelt`}},
{`"hallo" "Welt"`, []string{`hallo`, `Welt`}},
{`\"`, []string{`\"`}},
{`"foo`, []string{`"foo`}},
{`"foo"`, []string{`foo`}},
{`"\"`, []string{`"\"`}},
{`"C:\\"foo.txt`, []string{`C:\foo.txt`}},
{`"\n"new"\n"line`, []string{"\nnew\nline"}},
}
for i, test := range splitTests {
if result := SplitCommandArgs(test.Query); !reflect.DeepEqual(test.Wanted, result) {
t.Errorf("SplitCommandArgs failed at Split-Test %d\nGot: `%q`", i, result)
}
}
}
func TestStringWidth(t *testing.T) {
tabsize := 4
if w := StringWidth("1\t2", tabsize); w != 5 {
@@ -103,229 +154,3 @@ func TestWidthOfLargeRunes(t *testing.T) {
t.Error("WidthOfLargeRunes 5 Failed. Got", w)
}
}
func assertEqual(t *testing.T, expected interface{}, result interface{}) {
if expected != result {
t.Fatalf("Expected: %d != Got: %d", expected, result)
}
}
func assertTrue(t *testing.T, condition bool) {
if !condition {
t.Fatalf("Condition was not true. Got false")
}
}
func TestGetPathRelativeWithDot(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("./myfile:10:5")
assertEqual(t, path, "./myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "5", cursorPosition[1])
}
func TestGetPathRelativeWithDotWindows(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition(".\\myfile:10:5")
assertEqual(t, path, ".\\myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, cursorPosition[1], "5")
}
func TestGetPathRelativeNoDot(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("myfile:10:5")
assertEqual(t, path, "myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, cursorPosition[1], "5")
}
func TestGetPathAbsoluteWindows(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("C:\\myfile:10:5")
assertEqual(t, path, "C:\\myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, cursorPosition[1], "5")
path, cursorPosition = GetPathAndCursorPosition("C:/myfile:10:5")
assertEqual(t, path, "C:/myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "5", cursorPosition[1])
}
func TestGetPathAbsoluteUnix(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile:10:5")
assertEqual(t, path, "/home/user/myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "5", cursorPosition[1])
}
func TestGetPathRelativeWithDotWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("./myfile")
assertEqual(t, path, "./myfile")
// no cursor position in filename, nil should be returned
assertTrue(t, cursorPosition == nil)
}
func TestGetPathRelativeWithDotWindowsWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition(".\\myfile")
assertEqual(t, path, ".\\myfile")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathRelativeNoDotWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("myfile")
assertEqual(t, path, "myfile")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathAbsoluteWindowsWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("C:\\myfile")
assertEqual(t, path, "C:\\myfile")
assertTrue(t, cursorPosition == nil)
path, cursorPosition = GetPathAndCursorPosition("C:/myfile")
assertEqual(t, path, "C:/myfile")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathAbsoluteUnixWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile")
assertEqual(t, path, "/home/user/myfile")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathSingleLetterFileRelativePath(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("a:5:6")
assertEqual(t, path, "a")
assertEqual(t, "5", cursorPosition[0])
assertEqual(t, "6", cursorPosition[1])
}
func TestGetPathSingleLetterFileAbsolutePathWindows(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("C:\\a:5:6")
assertEqual(t, path, "C:\\a")
assertEqual(t, "5", cursorPosition[0])
assertEqual(t, "6", cursorPosition[1])
path, cursorPosition = GetPathAndCursorPosition("C:/a:5:6")
assertEqual(t, path, "C:/a")
assertEqual(t, "5", cursorPosition[0])
assertEqual(t, "6", cursorPosition[1])
}
func TestGetPathSingleLetterFileAbsolutePathUnix(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("/home/user/a:5:6")
assertEqual(t, path, "/home/user/a")
assertEqual(t, "5", cursorPosition[0])
assertEqual(t, "6", cursorPosition[1])
}
func TestGetPathSingleLetterFileAbsolutePathWindowsWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("C:\\a")
assertEqual(t, path, "C:\\a")
assertTrue(t, cursorPosition == nil)
path, cursorPosition = GetPathAndCursorPosition("C:/a")
assertEqual(t, path, "C:/a")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathSingleLetterFileAbsolutePathUnixWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("/home/user/a")
assertEqual(t, path, "/home/user/a")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathRelativeWithDotOnlyLine(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("./myfile:10")
assertEqual(t, path, "./myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
}
func TestGetPathRelativeWithDotWindowsOnlyLine(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition(".\\myfile:10")
assertEqual(t, path, ".\\myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
}
func TestGetPathRelativeNoDotOnlyLine(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("myfile:10")
assertEqual(t, path, "myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
}
func TestGetPathAbsoluteWindowsOnlyLine(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("C:\\myfile:10")
assertEqual(t, path, "C:\\myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
path, cursorPosition = GetPathAndCursorPosition("C:/myfile:10")
assertEqual(t, path, "C:/myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
}
func TestGetPathAbsoluteUnixOnlyLine(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile:10")
assertEqual(t, path, "/home/user/myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
}
func TestParseCursorLocationOneArg(t *testing.T) {
location, err := ParseCursorLocation([]string{"3"})
assertEqual(t, 3, location.Y)
assertEqual(t, 0, location.X)
assertEqual(t, nil, err)
}
func TestParseCursorLocationTwoArgs(t *testing.T) {
location, err := ParseCursorLocation([]string{"3", "15"})
assertEqual(t, 3, location.Y)
assertEqual(t, 15, location.X)
assertEqual(t, nil, err)
}
func TestParseCursorLocationNoArgs(t *testing.T) {
location, err := ParseCursorLocation(nil)
// the expected result is the start position - 0, 0
assertEqual(t, 0, location.Y)
assertEqual(t, 0, location.X)
// an error will be present here as the positions we're parsing are a nil
assertTrue(t, err != nil)
}
func TestParseCursorLocationFirstArgNotValidNumber(t *testing.T) {
// the messenger is necessary as ParseCursorLocation
// puts a message in it on error
messenger = new(Messenger)
_, err := ParseCursorLocation([]string{"apples", "1"})
// the expected result is the start position - 0, 0
assertTrue(t, messenger.hasMessage)
assertTrue(t, err != nil)
}
func TestParseCursorLocationSecondArgNotValidNumber(t *testing.T) {
// the messenger is necessary as ParseCursorLocation
// puts a message in it on error
messenger = new(Messenger)
_, err := ParseCursorLocation([]string{"1", "apples"})
// the expected result is the start position - 0, 0
assertTrue(t, messenger.hasMessage)
assertTrue(t, err != nil)
}

View File

@@ -1,20 +1,19 @@
package main
import (
"fmt"
"reflect"
"os"
"strconv"
"strings"
"time"
"github.com/mitchellh/go-homedir"
"github.com/zyedidia/tcell"
)
// The ViewType defines what kind of view this is
type ViewType struct {
Kind int
Readonly bool // The file cannot be edited
Scratch bool // The file cannot be saved
kind int
readonly bool // The file cannot be edited
scratch bool // The file cannot be saved
}
var (
@@ -22,8 +21,6 @@ var (
vtHelp = ViewType{1, true, true}
vtLog = ViewType{2, true, true}
vtScratch = ViewType{3, false, true}
vtRaw = ViewType{4, true, true}
vtTerm = ViewType{5, true, true}
)
// The View struct stores information about a view into a buffer.
@@ -65,7 +62,7 @@ type View struct {
// The buffer
Buf *Buffer
// The statusline
sline *Statusline
sline Statusline
// Since tcell doesn't differentiate between a mouse release event
// and a mouse move event with no keys pressed, we need to keep
@@ -73,12 +70,9 @@ type View struct {
// mouse release events
mouseReleased bool
// We need to keep track of insert key press toggle
isOverwriteMode bool
// This stores when the last click was
// This is useful for detecting double and triple clicks
lastClickTime time.Time
lastLoc Loc
// lastCutTime stores when the last ctrl+k was issued.
// It is used for clearing the clipboard to replace it with fresh cut lines.
@@ -94,16 +88,9 @@ type View struct {
// Same here, just to keep track for mouse move events
tripleClick bool
// The cellview used for displaying and syntax highlighting
cellview *CellView
splitNode *LeafNode
// The scrollbar
scrollbar *ScrollBar
// Virtual terminal
term *Terminal
}
// NewView returns a new fullscreen view
@@ -129,11 +116,7 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
v.messages = make(map[string][]GutterMessage)
v.sline = &Statusline{
view: v,
}
v.scrollbar = &ScrollBar{
v.sline = Statusline{
view: v,
}
@@ -141,8 +124,6 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
v.Height--
}
v.term = new(Terminal)
for pl := range loadedPlugins {
_, err := Call(pl+".onViewOpen", v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
@@ -163,24 +144,6 @@ func (v *View) ToggleStatusLine() {
}
}
// StartTerminal execs a command in this view
func (v *View) StartTerminal(execCmd []string, wait bool, getOutput bool, luaCallback string) error {
err := v.term.Start(execCmd, v, getOutput)
v.term.wait = wait
v.term.callback = luaCallback
if err == nil {
v.term.Resize(v.Width, v.Height)
v.Type = vtTerm
}
return err
}
// CloseTerminal shuts down the tty running in this view
// and returns it to the default view type
func (v *View) CloseTerminal() {
v.term.Stop()
}
// ToggleTabbar creates an extra row for the tabbar if necessary
func (v *View) ToggleTabbar() {
if len(tabs) > 1 {
@@ -198,10 +161,7 @@ func (v *View) ToggleTabbar() {
}
func (v *View) paste(clip string) {
leadingWS := ""
if v.Cursor.X > 0 {
leadingWS = GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
}
leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
if v.Cursor.HasSelection() {
v.Cursor.DeleteSelection()
@@ -238,7 +198,7 @@ func (v *View) ScrollDown(n int) {
// If there are unsaved changes, the user will be asked if the view can be closed
// causing them to lose the unsaved changes
func (v *View) CanClose() bool {
if v.Type == vtDefault && v.Buf.Modified() {
if v.Type == vtDefault && v.Buf.IsModified {
var choice bool
var canceled bool
if v.Buf.Settings["autosave"].(bool) {
@@ -250,12 +210,15 @@ func (v *View) CanClose() bool {
//if char == 'y' {
if choice {
v.Save(true)
return true
} else {
return true
}
} else {
return false
}
} else {
return true
}
return true
return false
}
// OpenBuffer opens a new buffer in this view.
@@ -275,22 +238,31 @@ func (v *View) OpenBuffer(buf *Buffer) {
// Set mouseReleased to true because we assume the mouse is not being pressed when
// the editor is opened
v.mouseReleased = true
// Set isOverwriteMode to false, because we assume we are in the default mode when editor
// is opened
v.isOverwriteMode = false
v.lastClickTime = time.Time{}
GlobalPluginCall("onBufferOpen", v.Buf)
GlobalPluginCall("onViewOpen", v)
}
// Open opens the given file in the view
func (v *View) Open(path string) {
buf, err := NewBufferFromFile(path)
if err != nil {
messenger.Error(err)
func (v *View) Open(filename string) {
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
if err == nil && fileInfo.IsDir() {
messenger.Error(filename, " is a directory")
return
}
defer file.Close()
var buf *Buffer
if err != nil {
messenger.Message(err.Error())
// File does not exist -- create an empty buffer with that name
buf = NewBufferFromString("", filename)
} else {
buf = NewBuffer(file, FSize(file), filename)
}
v.OpenBuffer(buf)
}
@@ -313,7 +285,7 @@ func (v *View) ReOpen() {
// HSplit opens a horizontal split with the given buffer
func (v *View) HSplit(buf *Buffer) {
i := 0
if v.Buf.Settings["splitbottom"].(bool) {
if v.Buf.Settings["splitBottom"].(bool) {
i = 1
}
v.splitNode.HSplit(buf, v.Num+i)
@@ -322,7 +294,7 @@ func (v *View) HSplit(buf *Buffer) {
// VSplit opens a vertical split with the given buffer
func (v *View) VSplit(buf *Buffer) {
i := 0
if v.Buf.Settings["splitright"].(bool) {
if v.Buf.Settings["splitRight"].(bool) {
i = 1
}
v.splitNode.VSplit(buf, v.Num+i)
@@ -383,10 +355,6 @@ func (v *View) GetSoftWrapLocation(vx, vy int) (int, int) {
return 0, 0
}
// Bottomline returns the line number of the lowest line in the view
// You might think that this is obviously just v.Topline + v.Height
// but if softwrap is enabled things get complicated since one buffer
// line can take up multiple lines in the view
func (v *View) Bottomline() int {
if !v.Buf.Settings["softwrap"].(bool) {
return v.Topline + v.Height
@@ -439,7 +407,7 @@ func (v *View) Relocate() bool {
if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
v.Topline = cy - height + 1 + scrollmargin
ret = true
} else if cy >= v.Buf.NumLines-scrollmargin && cy >= height {
} else if cy >= v.Buf.NumLines-scrollmargin && cy > height {
v.Topline = v.Buf.NumLines - height
ret = true
}
@@ -458,31 +426,6 @@ func (v *View) Relocate() bool {
return ret
}
// GetMouseClickLocation gets the location in the buffer from a mouse click
// on the screen
func (v *View) GetMouseClickLocation(x, y int) (int, int) {
x -= v.lineNumOffset - v.leftCol + v.x
y += v.Topline - v.y
if y-v.Topline > v.Height-1 {
v.ScrollDown(1)
y = v.Height + v.Topline - 1
}
if y < 0 {
y = 0
}
if x < 0 {
x = 0
}
newX, newY := v.GetSoftWrapLocation(x, y)
if newX > Count(v.Buf.Line(newY)) {
newX = Count(v.Buf.Line(newY))
}
return newX, newY
}
// MoveToMouseClick moves the cursor to location x, y assuming x, y were given
// by a mouse click
func (v *View) MoveToMouseClick(x, y int) {
@@ -498,6 +441,7 @@ func (v *View) MoveToMouseClick(x, y int) {
}
x, y = v.GetSoftWrapLocation(x, y)
// x = v.Cursor.GetCharPosInLine(y, x)
if x > Count(v.Buf.Line(y)) {
x = Count(v.Buf.Line(y))
}
@@ -506,15 +450,13 @@ func (v *View) MoveToMouseClick(x, y int) {
v.Cursor.LastVisualX = v.Cursor.GetVisualX()
}
// Execute actions executes the supplied actions
func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
relocate := false
readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"}
for _, action := range actions {
readonlyBindingsResult := false
funcName := ShortFuncName(action)
curv := CurView()
if curv.Type.Readonly == true {
if v.Type.readonly == true {
// check for readonly and if true only let key bindings get called if they do not change the contents.
for _, readonlyBindings := range readonlyBindingsList {
if strings.Contains(funcName, readonlyBindings) {
@@ -524,7 +466,7 @@ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
}
if !readonlyBindingsResult {
// call the key binding
relocate = action(curv, true) || relocate
relocate = action(v, true) || relocate
// Macro
if funcName != "ToggleMacro" && funcName != "PlayMacro" {
if recordingMacro {
@@ -537,7 +479,6 @@ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
return relocate
}
// SetCursor sets the view's and buffer's cursor
func (v *View) SetCursor(c *Cursor) bool {
if c == nil {
return false
@@ -550,25 +491,6 @@ func (v *View) SetCursor(c *Cursor) bool {
// HandleEvent handles an event passed by the main loop
func (v *View) HandleEvent(event tcell.Event) {
if v.Type == vtTerm {
v.term.HandleEvent(event)
return
}
if v.Type == vtRaw {
v.Buf.Insert(v.Cursor.Loc, reflect.TypeOf(event).String()[7:])
v.Buf.Insert(v.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
switch e := event.(type) {
case *tcell.EventKey:
if e.Key() == tcell.KeyCtrlQ {
v.Quit(true)
}
}
return
}
// This bool determines whether the view is relocated at the end of the function
// By default it's true because most events should cause a relocate
relocate := true
@@ -576,24 +498,6 @@ func (v *View) HandleEvent(event tcell.Event) {
v.Buf.CheckModTime()
switch e := event.(type) {
case *tcell.EventRaw:
for key, actions := range bindings {
if key.keyCode == -1 {
if e.EscSeq() == key.escape {
for _, c := range v.Buf.cursors {
ok := v.SetCursor(c)
if !ok {
break
}
relocate = false
relocate = v.ExecuteActions(actions) || relocate
}
v.SetCursor(&v.Buf.Cursor)
v.Buf.MergeCursors()
break
}
}
}
case *tcell.EventKey:
// Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune
isBinding := false
@@ -620,10 +524,9 @@ func (v *View) HandleEvent(event tcell.Event) {
}
}
}
if !isBinding && e.Key() == tcell.KeyRune {
// Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
if v.Type.Readonly == false {
if v.Type.readonly == false {
for _, c := range v.Buf.cursors {
v.SetCursor(c)
@@ -632,14 +535,7 @@ func (v *View) HandleEvent(event tcell.Event) {
v.Cursor.DeleteSelection()
v.Cursor.ResetSelection()
}
if v.isOverwriteMode {
next := v.Cursor.Loc
next.X++
v.Buf.Replace(v.Cursor.Loc, next, string(e.Rune()))
} else {
v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
}
v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
for pl := range loadedPlugins {
_, err := Call(pl+".onRune", string(e.Rune()), v)
@@ -657,7 +553,7 @@ func (v *View) HandleEvent(event tcell.Event) {
}
case *tcell.EventPaste:
// Check viewtype if readonly don't paste (readonly help and log view etc.)
if v.Type.Readonly == false {
if v.Type.readonly == false {
if !PreActionCall("Paste", v) {
break
}
@@ -787,19 +683,13 @@ func (v *View) openHelp(helpPage string) {
}
}
// DisplayView draws the view to the screen
func (v *View) DisplayView() {
if v.Type == vtTerm {
v.term.Display()
return
}
if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 {
v.leftCol = 0
}
if v.Type == vtLog || v.Type == vtRaw {
// Log or raw views should always follow the cursor...
if v.Type == vtLog {
// Log views should always follow the cursor...
v.Relocate()
}
@@ -863,11 +753,11 @@ func (v *View) DisplayView() {
}
colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
if colorcolumn != 0 && xOffset+colorcolumn-v.leftCol < v.Width {
if colorcolumn != 0 {
style := GetColor("color-column")
fg, _, _ := style.Decompose()
st := defStyle.Background(fg)
screen.SetContent(xOffset+colorcolumn-v.leftCol, yOffset+visualLineN, ' ', nil, st)
screen.SetContent(xOffset+colorcolumn, yOffset+visualLineN, ' ', nil, st)
}
screenX = v.x
@@ -1100,11 +990,6 @@ func (v *View) Display() {
screen.HideCursor()
}
_, screenH := screen.Size()
if v.Buf.Settings["scrollbar"].(bool) {
v.scrollbar.Display()
}
if v.Buf.Settings["statusline"].(bool) {
v.sline.Display()
} else if (v.y + v.Height) != screenH-1 {

View File

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

View File

@@ -19,8 +19,8 @@ color-link line-number "#666666,#242424"
color-link current-line-number "#666666,#242424"
color-link gutter-error "#CB4B16,#242424"
color-link gutter-warning "#E6DB74,#242424"
color-link cursor-line "default,#2C2C2C"
color-link color-column "default,#2C2C2C"
color-link cursor-line "#2C2C2C"
color-link color-column "#2C2C2C"
#No extended types; Plain brackets.
color-link type.extended "default"
#color-link symbol.brackets "default"

View File

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

View File

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

View File

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

View File

@@ -1,26 +0,0 @@
color-link default "#e6e1dc,#2b2b2b"
color-link comment "#bc9458,#2b2b2b"
color-link statement "#cc7833,#2b2b2b"
color-link constant "#a5c261,#2b2b2b"
color-link constant.bool "#6d9cbe,#2b2b2b"
color-link type "#6d9cbe,#2b2b2b"
color-link preproc "#cc7833,#2b2b2b"
color-link special "#cc7833,#2b2b2b"
color-link underlined "#cc7833,#2b2b2b"
color-link todo "bold #cc7833,#2b2b2b"
color-link error "bold #cc7833,#2b2b2b"
color-link gutter-error "#cc7833,#11151C"
color-link indent-char "#414141,#2b2b2b"
color-link line-number "#a1a1a1,#353535"
color-link current-line-number "#e6e1dc,#2b2b2b"
color-link gutter-warning "#a5c261,#11151C"
color-link symbol "#edb753,#2b2b2b"
color-link identifier "#edb753,#2b2b2b"
color-link statusline "#a1a1a1,#414141"
color-link tabbar "bold #a1a1a1,#414141"
color-link cursor-line "#353535"
color-link color-column "#353535"
color-link space "underline #e6e1dc,#2b2b2b"
#the Python syntax definition are wrong. This is not how you should do decorators!
color-link brightgreen "#edb753,#2b2b2b"

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
# Default Keys
Below are simple charts of the default hotkeys and their functions. For more
information about binding custom hotkeys or changing default bindings, please
run `> help keybindings`
Below are simple charts of the default hotkeys and their functions.
For more information about binding custom hotkeys or changing
default bindings, please run `> help keybindings`
Please remember that *all* keys here are rebindable! If you don't like it, you
can change it!
Please remember that *all* keys here are rebindable!
If you don't like it, you can change it!
### Power user
# Power user
| Key | Description of function |
|-------- |-------------------------------------------------------------------------------------------------- |
@@ -15,7 +15,7 @@ can change it!
| Tab | In command prompt, it will autocomplete if possible. |
| Ctrl+B | Run a shell command (this will close micro while your command executes). |
### Navigation
# Navigation
| Key | Description of function |
|-------------------------- |------------------------------------------------------------------------------------------ |
@@ -25,8 +25,6 @@ can change it!
| End or CtrlRightArrow | Move to the end of the current line |
| AltLeftArrow | Move cursor one word left |
| AltRightArrow | Move cursor one word right |
| Alt+{ | Move cursor to previous empty line, or beginning of document |
| Alt+} | Move cursor to next empty line, or end of document |
| PageUp | Move cursor up one page |
| PageDown | Move cursor down one page |
| CtrlHome or CtrlUpArrow | Move cursor to start of document |
@@ -34,7 +32,7 @@ can change it!
| Ctrl+L | Jump to a line in the file (prompts with #) |
| Ctrl+W | Cycle between splits in the current tab (use `> vsplit` or `> hsplit` to create a split) |
### Tabs
# Tabs
| Key | Description of function |
|-------- |------------------------- |
@@ -42,7 +40,7 @@ can change it!
| Alt+, | Previous tab |
| Alt+. | Next tab |
### Find Operations
# Find Operations
| Key | Description of function |
|-------- |------------------------------------------ |
@@ -50,7 +48,7 @@ can change it!
| Ctrl+N | Find next instance of current search |
| Ctrl+P | Find previous instance of current search |
### File Operations
# File Operations
| Key | Description of function |
|-------- |---------------------------------------------------------------- |
@@ -58,7 +56,7 @@ can change it!
| Ctrl+O | Open a file (prompts for filename) |
| Ctrl+S | Save current file |
### Text operations
# Text operations
| Key | Description of function |
|--------------------------------- |------------------------------------------ |
@@ -80,14 +78,14 @@ can change it!
| AltBackspace or AltCtrl+H | Delete word left |
| Ctrl+A | Select all |
### Macros
# Macros
| Key | Description of function |
|-------- |---------------------------------------------------------------------------------- |
| Ctrl+U | Toggle macro recording (press Ctrl+U to start recording and press again to stop) |
| Ctrl+J | Run latest recorded macro |
### Multiple cursors
# Multiple cursors
| Key | Description of function |
|---------------- |---------------------------------------------------------------------------------------------- |
@@ -95,10 +93,9 @@ can change it!
| Alt+P | Remove latest multiple cursor |
| Alt+C | Remove all multiple cursors (cancel) |
| Alt+X | Skip multiple cursor selection |
| Alt+M | Spawn a new cursor at the beginning of every line in the current selection |
| Ctrl-MouseLeft | Place a multiple cursor at any location |
### Other
# Other
| Key | Description of function |
|-------- |----------------------------------------------------------------------------------- |
@@ -106,7 +103,7 @@ can change it!
| Ctrl+H | Backspace (old terminals do not support the backspace key and use Ctrl+H instead) |
| Ctrl+R | Toggle the line number ruler |
### Emacs style actions
# Emacs style actions
| Key | Description of function |
|------- |------------------------- |
@@ -115,7 +112,7 @@ can change it!
| Alt+A | Move to start of line |
| Alt+E | Move to end of line |
### Function keys.
# Function keys.
Warning! The function keys may not work in all terminals!

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
# Options
### Options
Micro stores all of the user configuration in its configuration directory.
@@ -8,34 +8,11 @@ the config directory.
Here are the options that you can set:
* `autoindent`: when creating a new line use the same indentation as the
previous line.
default value: `true`
* `autosave`: micro will save the buffer every 8 seconds automatically. Micro
also will automatically save and quit when you exit without asking. Be
careful when using this feature, because you might accidentally save a file,
overwriting what was there before.
default value: `false`
* `basename`: in the infobar, show only the basename of the file being edited
rather than the full path.
default value: `false`
* `colorcolumn`: if this is not set to 0, it will display a column at the
specified column. This is useful if you want column 80 to be highlighted
special for example.
default value: `0`
* `colorscheme`: loads the colorscheme stored in
$(configDir)/colorschemes/`option`.micro, This setting is `global only`.
$(configDir)/colorschemes/`option`.micro
This setting is `global only`.
default value: `default`
Note that the default colorschemes (default, solarized, and solarized-tc)
are not located in configDir, because they are embedded in the micro binary.
@@ -43,255 +20,202 @@ Here are the options that you can set:
~/.config/micro/colorschemes/ directory. Micro comes by default with three
colorschemes:
You can read more about micro's colorschemes in the `colors` help topic
(`help colors`).
You can read more about micro's colorschemes in the `colors` help topic
(`help colors`).
* `cursorline`: highlight the line that the cursor is on in a different color
(the color is defined by the colorscheme you are using).
* `colorcolumn`: if this is not set to 0, it will display a column at the specified
column. This is useful if you want column 80 to be highlighted special for example.
default value: `true`
default value: `0`
* `eofnewline`: micro will automatically add a newline to the file.
default value: `false`
* `fastdirty`: this determines what kind of algorithm micro uses to determine if
a buffer is modified or not. When `fastdirty` is on, micro just uses a
boolean `modified` that is set to `true` as soon as the user makes an edit.
This is fast, but can be inaccurate. If `fastdirty` is off, then micro will
hash the current buffer against a hash of the original file (created when the
buffer was loaded). This is more accurate but obviously more resource
intensive. This option is only for people who really care about having
accurate modified status.
default value: `true`
* `fileformat`: this determines what kind of line endings micro will use for the
file. UNIX line endings are just `\n` (lf) whereas dos line endings are
`\r\n` (crlf). The two possible values for this option are `unix` and `dos`.
The fileformat will be automatically detected and displayed on the statusline
but this option is useful if you would like to change the line endings or if
you are starting a new file.
default value: `unix`
* `filetype`: sets the filetype for the current buffer. This setting is
`local only`.
default value: this will be automatically set depending on the file you have
open
* `ignorecase`: perform case-insensitive searches.
default value: `false`
* `indentchar`: sets the indentation character.
default value: ` `
* `infobar`: enables the line at the bottom of the editor where messages are
printed. This option is `global only`.
default value: `true`
* `keepautoindent`: when using autoindent, whitespace is added for you. This
option determines if when you move to the next line without any insertions
the whitespace that was added should be deleted. By default the autoindent
whitespace is deleted if the line was left empty.
default value: `false`
* `keymenu`: display the nano-style key menu at the bottom of the screen. Note
that ToggleKeyMenu is bound to `Alt-g` by default and this is displayed in
the statusline. To disable this, simply by `Alt-g` to `UnbindKey`.
default value: `false`
* `mouse`: whether to enable mouse support. When mouse support is disabled,
usually the terminal will be able to access mouse events which can be useful
if you want to copy from the terminal instead of from micro (if over ssh for
example, because the terminal has access to the local clipboard and micro
does not).
default value: `true`
* `pluginchannels`: contains all the channels micro's plugin manager will search
for plugins in. A channel is simply a list of 'repository' json files which
contain metadata about the given plugin. See the `Plugin Manager` section of
the `plugins` help topic for more information.
default value: `https://github.com/micro-editor/plugin-channel`
* `pluginrepos`: contains all the 'repositories' micro's plugin manager will
search for plugins in. A repository consists of a `repo.json` file which
contains metadata for a single plugin.
default value: ` `
* `rmtrailingws`: micro will automatically trim trailing whitespaces at eol.
default value: `false`
* `ruler`: display line numbers.
default value: `true`
* `savecursor`: remember where the cursor was last time the file was opened and
put it there when you open the file again.
default value: `false`
* `savehistory`: remember command history between closing and re-opening
micro.
default value: `true`
* `saveundo`: when this option is on, undo is saved even after you close a file
so if you close and reopen a file, you can keep undoing.
default value: `false`
* `scrollbar`: display a scroll bar
default value: `false`
* `scrollmargin`: amount of lines you would like to see above and below the
cursor.
default value: `3`
* `scrollspeed`: amount of lines to scroll for one scroll event.
default value: `2`
* `softwrap`: should micro wrap lines that are too long to fit on the screen.
default value: `false`
* `splitbottom`: when a horizontal split is created, should it be created below
the current split?
default value: `true`
* `splitright`: when a vertical split is created, should it be created to the
right of the current split?
default value: `true`
* `statusline`: display the status line at the bottom of the screen.
default value: `true`
* `matchbrace`: highlight matching braces for '()', '{}', '[]'
default value: `false`
* `matchbraceleft`: when matching a closing brace, should matching match the
brace directly under the cursor, or the character to the left? only matters
if `matchbrace` is true
default value: `false`
* `syntax`: turns syntax on or off.
default value: `true`
* `sucmd`: specifies the super user command. On most systems this is "sudo" but
on BSD it can be "doas." This option can be customized and is only used when
saving with su.
default value: `sudo`
* `tabmovement`: navigate spaces at the beginning of lines as if they are tabs
(e.g. move over 4 spaces at once). This option only does anything if
`tabstospaces` is on.
default value: `false`
* `tabsize`: sets the tab size to `option`
default value: `4`
* `indentchar`: sets the indentation character
default value: ` `
* `infobar`: enables the line at the bottom of the editor where messages are printed.
This option is `global only`.
default value: `on`
* `filetype`: sets the filetype for the current buffer. This setting is `local only`
default value: this will be automatically set depending on the file you have open
* `ignorecase`: perform case-insensitive searches
default value: `off`
* `syntax`: turns syntax on or off
default value: `on`
* `tabstospaces`: use spaces instead of tabs
default value: `false`
default value: `off`
* `termtitle`: defines whether or not your terminal's title will be set by micro
when opened.
* `tabmovement`: navigate spaces at the beginning of lines as if they are tabs (e.g. move over 4 spaces at once).
This option only does anything if `tabstospaces` is on.
default value: `false`
default value: `off`
* `useprimary` (only useful on *nix): defines whether or not micro will use the
primary clipboard to copy selections in the background. This does not affect
the normal clipboard using Ctrl-C and Ctrl-V.
* `autoindent`: when creating a new line use the same indentation as the
previous line
default value: `true`
default value: `on`
* `cursorline`: highlight the line that the cursor is on in a different color
(the color is defined by the colorscheme you are using)
default value: `on`
* `ruler`: display line numbers
default value: `on`
* `statusline`: display the status line at the bottom of the screen
default value: `on`
* `savecursor`: remember where the cursor was last time the file was opened and
put it there when you open the file again
default value: `off`
* `saveundo`: when this option is on, undo is saved even after you close a file
so if you close and reopen a file, you can keep undoing
default value: `off`
* `scrollmargin`: amount of lines you would like to see above and below the cursor
default value: `3`
* `scrollspeed`: amount of lines to scroll for one scroll event
default value: `2`
* `softwrap`: should micro wrap lines that are too long to fit on the screen
default value: `off`
* `splitRight`: when a vertical split is created, should it be created to the right of
the current split?
default value: `on`
* `splitBottom`: when a horizontal split is created, should it be created below the
current split?
default value: `on`
* `autosave`: micro will save the buffer every 8 seconds automatically.
Micro also will automatically save and quit when you exit without asking.
Be careful when using this feature, because you might accidentally save a file,
overwriting what was there before.
default value: `off`
* `pluginchannels`: contains all the channels micro's plugin manager will search
for plugins in. A channel is simply a list of 'repository' json files which contain
metadata about the given plugin. See the `Plugin Manager` section of the `plugins` help topic
for more information.
default value: `https://github.com/micro-editor/plugin-channel`
* `pluginrepos`: contains all the 'repositories' micro's plugin manager will search for
plugins in. A repository consists of a `repo.json` file which contains metadata for a
single plugin.
default value: ` `
* `useprimary` (only useful on Linux): defines whether or not micro will use the primary clipboard to copy selections
in the background. This does not affect the normal clipboard using Ctrl-C and Ctrl-V.
default value: `on`
* `keepautoindent`: when using autoindent, whitespace is added for you. This option determines if
when you move to the next line without any insertions the whitespace that was added should be deleted.
By default the autoindent whitespace is deleted if the line was left empty.
default value: `off`
* `termtitle`: defines whether or not your terminal's title will be set by micro when opened.
default value: `off`
* `mouse`: whether to enable mouse support. When mouse support is disabled, usually the terminal will be able
to access mouse events which can be useful if you want to copy from the terminal instead of from micro (if
over ssh for example, because the terminal has access to the local clipboard and micro does not).
default value: `on`
---
Default plugin options:
* `autoclose`: automatically close `{}` `()` `[]` `""` `''`. Provided by the
`autoclose` plugin
* `autoclose`: Automatically close `{}` `()` `[]` `""` `''`. Provided by the `autoclose` plugin
default value: `true`
default value: `on`
* `ftoptions`: by default, micro will set some options based on the filetype. At
the moment, micro will use tabs for makefiles and spaces for python and yaml
files regardless of your settings. If you would like to disable this behavior
turn this option off.
* `linter`: Automatically lint when the file is saved. Provided by the `linter` plugin
default value: `true`
default value: `on`
* `linter`: Automatically lint when the file is saved. Provided by the `linter`
plugin.
* `ftoptions`: by default, micro will set some options based on the filetype. At the moment, micro will
use tabs for makefiles and spaces for python files regardless of your settings. If you would like to
disable this behavior turn this option off.
default value: `true`
default value: `on`
* `fileformat`: this determines what kind of line endings micro will use for the file. Unix line endings
are just `\n` (lf) whereas dos line endings are `\r\n` (crlf). The two possible values for this option
are `unix` and `dos`. The fileformat will be automatically detected and displayed on the statusline but
this option is useful if you would like to change the line endings or if you are starting a new file.
default value: `unix`
Any option you set in the editor will be saved to the file
~/.config/micro/settings.json so, in effect, your configuration file will be
created for you. If you'd like to take your configuration with you to another
machine, simply copy the settings.json to the other machine.
# Global and local settings
## Global and local settings
You can set these settings either globally or locally. Locally means that the setting
won't be saved to `~/.config/micro/settings.json` and that it will only be set in
the current buffer. Setting an option globally is the default, and will set the option
in all buffers.
You can set these settings either globally or locally. Locally means that the
setting won't be saved to `~/.config/micro/settings.json` and that it will only
be set in the current buffer. Setting an option globally is the default, and
will set the option in all buffers.
The `colorscheme` option is global only, and the `filetype` option is local only. To
set an option locally, use `setlocal` instead of `set`.
The `colorscheme` option is global only, and the `filetype` option is local
only. To set an option locally, use `setlocal` instead of `set`.
In the `settings.json` file you can also put set options locally by specifying either
a glob or a filetype. Here is an example which has `tabstospaces` on for all files except Go
files, and `tabsize` 4 for all files except Ruby files:
In the `settings.json` file you can also put set options locally by specifying a glob.
Here is an example which has `tabstospaces` on for all files except Go files, and
`tabsize` 4 for all files except Ruby files:
```json
{
"ft:go": {
"tabstospaces": false
},
"ft:ruby": {
"tabsize": 2
},
"tabstospaces": true,
"tabsize": 4
"*.go": {
"tabstospaces": false
},
"*.rb": {
"tabsize": 2
},
"tabstospaces": true,
"tabsize": 4
}
```
Or similarly you can match with globs:
```json
{
"*.go": {
"tabstospaces": false
},
"*.rb": {
"tabsize": 2
},
"tabstospaces": true,
"tabsize": 4
}
```
As you can see it is quite easy to set options locally using the `settings.json` file.

View File

@@ -4,9 +4,10 @@ Micro supports creating plugins with a simple Lua system. Every plugin has a
main script which is run at startup which should be placed in
`~/.config/micro/plugins/pluginName/pluginName.lua`.
There are a number of callback functions which you can create in your plugin to
run code at times other than startup. The naming scheme is `onAction(view)`. For
example a function which is run every time the user saves the buffer would be:
There are a number of callback functions which you can create in your
plugin to run code at times other than startup. The naming scheme is
`onAction(view)`. For example a function which is run every time the user saves
the buffer would be:
```lua
function onSave(view)
@@ -16,8 +17,7 @@ end
```
The `view` variable is a reference to the view the action is being executed on.
This is almost always the current view, which you can get with `CurView()` as
well.
This is almost always the current view, which you can get with `CurView()` as well.
All available actions are listed in the keybindings section of the help.
@@ -31,28 +31,27 @@ function onMousePress(view, event)
end
```
These functions should also return a boolean specifying whether the view should
be relocated to the cursor or not after the action is complete.
These functions should also return a boolean specifying whether the view
should be relocated to the cursor or not after the action is complete.
Note that these callbacks occur after the action has been completed. If you want
a callback before the action is executed, use `preAction()`. In this case the
boolean returned specifies whether or not the action should be executed after
the lua code completes.
Note that these callbacks occur after the action has been completed. If you
want a callback before the action is executed, use `preAction()`. In this case
the boolean returned specifies whether or not the action should be executed
after the lua code completes.
Another useful callback to know about which is not an action is
Another useful callback to know about which is not a action is
`onViewOpen(view)` which is called whenever a new view is opened and the new
view is passed in. This is useful for setting local options based on the
filetype, for example turning off `tabstospaces` only for Go files when they are
opened.
view is passed in. This is useful for setting local options based on the filetype,
for example turning off `tabstospaces` only for Go files when they are opened.
---
There are a number of functions and variables that are available to you in order
to access the inner workings of micro. Here is a list (the type signatures for
functions are given using Go's type system):
There are a number of functions and variables that are available to you in
order to access the inner workings of micro. Here is a list (the type signatures
for functions are given using Go's type system):
* `OS`: variable which gives the OS micro is currently running on (this is the
same as Go's GOOS variable, so `darwin`, `windows`, `linux`, `freebsd`...)
* `OS`: variable which gives the OS micro is currently running on (this is the same
as Go's GOOS variable, so `darwin`, `windows`, `linux`, `freebsd`...)
* `configDir`: contains the path to the micro configuration files
@@ -62,38 +61,29 @@ functions are given using Go's type system):
* `messenger`: lets you send messages to the user or create prompts
* `NewBuffer(text, path string) *Buffer`: creates a new buffer from a given
reader with a given path
* `NewBuffer(text, path string) *Buffer`: creates a new buffer from a given reader with a given path
* `NewBufferFromFile(path string) *Buffer`: creates a new buffer from a given
path
* `GetLeadingWhitespace() bool`: returns the leading whitespace of the given string
* `GetLeadingWhitespace() bool`: returns the leading whitespace of the given
string
* `IsWordChar(str string) bool`: returns whether or not the string is a 'word
character'
* `IsWordChar(str string) bool`: returns whether or not the string is a 'word character'
* `RuneStr(r rune) string`: returns a string containing the given rune
* `Loc(x, y int) Loc`: returns a new `Loc` struct
* `WorkingDirectory() string`: returns a rooted path name to the current working
directory
* `WorkingDirectory() string`: returns a rooted path name to the current working directory
* `JoinPaths(dir... string) string`: combines multiple directories to a full
path
* `JoinPaths(dir... string) string`: combines multiple directories to a full path
* `DirectoryName(path string)`: returns all but the last element of path,
typically the path's directory
* `DirectoryName(path string)`: returns all but the last element of path ,typically the path's directory
* `GetOption(name string)`: returns the value of the requested option
* `AddOption(name string, value interface{})`: sets the given option with the
given value (`interface{}` means any type in Go)
* `AddOption(name string, value interface{})`: sets the given option with the given
value (`interface{}` means any type in Go)
* `SetOption(option, value string)`: sets the given option to the value. This
will set the option globally, unless it is a local only option.
* `SetOption(option, value string)`: sets the given option to the value. This will
set the option globally, unless it is a local only option.
* `SetLocalOption(option, value string, view *View)`: sets the given option to
the value locally in the given buffer
@@ -101,8 +91,8 @@ functions are given using Go's type system):
* `BindKey(key, action string)`: binds `key` to `action`
* `MakeCommand(name, function string, completions ...Completion)`:
creates a command with `name` which will call `function` when executed. Use 0
for completions to get NoCompletion.
creates a command with `name` which will call `function` when executed.
Use 0 for completions to get NoCompletion.
* `MakeCompletion(function string)`:
creates a `Completion` to use with `MakeCommand`
@@ -111,71 +101,42 @@ functions are given using Go's type system):
* `HandleCommand(cmd string)`: runs the given command
* `ExecCommand(name string, args []string) (string, error)`: exec a (shell) command with the
given arguments. Returns the command's output and a possible error.
* `HandleShellCommand(shellCmd string, interactive bool, waitToClose bool)`: runs the given shell
command. The `interactive` bool specifies whether the command should run in the background. The
`waitToClose` bool only applies if `interactive` is true and means that it should wait before
returning to the editor.
* `RunShellCommand(cmd string) (string, error)`: Run a shell command. This uses `ExecCommand`
under the hood but also does some parsing for the arguments (i.e. quoted arguments). The
function returns the command's output and a possible error.
* `RunBackgroundShell(cmd string)`: Run a shell command in the background.
* `RunInteractiveShell(cmd string, wait bool, getOutput bool) (string, error)`: Run a shell command
by closing micro and running the command interactively. If `wait` is true, a prompt will be
used after the process exits to prevent the terminal from immediately returning to micro, allowing
the user to view the output of the process. If `getOutput` is true, the command's standard output
will be returned. Note that if `getOutput` is true, some interactive commands may not behave
normally because `isatty` will return false.
* `RunTermEmulator(cmd string, wait bool, getOutput bool, callback string) error`: Same as
`RunInteractiveShell` except the command is run within the current split in a terminal emulator.
The `callback` input is a string callback to a lua function which will be called when the process
exits. The output of the process will be provided as the first and only argument to the callback
(it will be empty if `getOutput` is false).
Note that this functionality is only supported on some operating systems (linux, darwin, dragonfly,
openbsd, freebsd). Use the `TermEmuSupported` (see below) boolean to determine if the current
system is supported.
* `TermEmuSupported`: Boolean specifying if the terminal emulator is supported on the version of
micro that is running.
* `ToCharPos(loc Loc, buf *Buffer) int`: returns the character position of a
given x, y location
* `ToCharPos(loc Loc, buf *Buffer) int`: returns the character position of a given x, y location
* `Reload`: (Re)load everything
* `ByteOffset(loc Loc, buf *Buffer) int`: exactly like `ToCharPos` except it it
counts bytes instead of runes
* `ByteOffset(loc Loc, buf *Buffer) int`: exactly like `ToCharPos` except it it counts bytes instead of runes
* `JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...string)`:
Starts running the given process in the background. `onStdout` `onStderr` and
`onExit` are callbacks to lua functions which will be called when the given
actions happen to the background process. `userargs` are the arguments which
will get passed to the callback functions
Starts running the given process in the background. `onStdout` `onStderr` and `onExit`
are callbacks to lua functions which will be called when the given actions happen
to the background process.
`userargs` are the arguments which will get passed to the callback functions
* `JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string)`:
Starts running the given shell command in the background. Note that the
command execute is first parsed by a shell when using this command. It is
executed with `sh -c`.
Starts running the given shell command in the background. Note that the command execute
is first parsed by a shell when using this command. It is executed with `sh -c`.
* `JobSend(cmd *exec.Cmd, data string)`: send a string into the stdin of the job
process
* `JobSend(cmd *exec.Cmd, data string)`: send a string into the stdin of the job process
* `JobStop(cmd *exec.Cmd)`: kill a job
This may seem like a small list of available functions but some of the objects
returned by the functions have many methods. `CurView()` returns a view object
which has all the actions which you can call. For example
`CurView():Save(false)`. You can see the full list of possible actions in the
keybindings help topic. The boolean on all the actions indicates whether or not
the lua callbacks should be run. I would recommend generally sticking to false
when making a plugin to avoid recursive problems, for example if you call
`CurView():Save(true)` in `onSave()`. Just use `CurView():Save(false)` so that
it won't call `onSave()` again.
which has all the actions which you can call. For example `CurView():Save(false)`.
You can see the full list of possible actions in the keybindings help topic.
The boolean on all the actions indicates whether or not the lua callbacks should
be run. I would recommend generally sticking to false when making a plugin to
avoid recursive problems, for example if you call `CurView():Save(true)` in `onSave()`.
Just use `CurView():Save(false)` so that it won't call `onSave()` again.
Using the view object, you can also access the buffer associated with that view
by using `CurView().Buf`, which lets you access the `FileType`, `Path`,
`Name`...
by using `CurView().Buf`, which lets you access the `FileType`, `Path`, `Name`...
The possible methods which you can call using the `messenger` variable are:
@@ -185,8 +146,7 @@ The possible methods which you can call using the `messenger` variable are:
* `messenger.Prompt(prompt, historyType string, completionType Completion) (string, bool)`
* `messenger.AddLog(msg ...interface{})`
#### Note
## Note
Go function signatures use `.` and lua uses `:` so
```go
@@ -200,31 +160,26 @@ messenger:Message()
```
If you want a standard prompt, just use
```lua
messenger:Prompt(prompt, "", 0)
```
Debug or logging your plugin can be done with below lua example code.
```lua
messenger:AddLog("Message goes here ",pluginVariableToPrintHere)
```
In Micro to see your plugin logging output press `CtrlE` then type `log`, a
logging window will open and any logging sent from your plugin will be displayed
here.
In Micro to see your plugin logging output press `CtrlE` then type `log`
A logging window will open and any logging sent from your plugin will be displayed here.
# Accessing the Go standard library
## Accessing the Go standard library
It is possible for your lua code to access many of the functions in the Go
standard library.
It is possible for your lua code to access many of the functions in the Go standard library.
Simply import the package you'd like and then you can use it. For example:
```lua
local ioutil = import("io/ioutil")
local ioutil = import("ioutil")
local fmt = import("fmt")
local data, err = ioutil.ReadFile("SomeFile.txt")
@@ -241,49 +196,26 @@ else
end
```
Here are the packages from the Go standard library that you can access.
Nearly all functions from these packages are supported. For an exact
list of which functions are supported you can look through `lua.go`
(which should be easy to understand).
For a full list of which packages and functions from the standard library
you can access, look at `lua.go` in the source code (it shouldn't be
too hard to look through).
```
fmt
io
io/ioutil
net
math
math/rand
os
runtime
path
filepath
strings
regexp
errors
time
```
# Adding help files, syntax files, or colorschemes in your plugin
For documentation for each of these functions, you can simply look
through the Go standard library documentation.
## Adding help files, syntax files, or colorschemes in your plugin
You can use the `AddRuntimeFile(name, type, path string)` function to add
various kinds of files to your plugin. For example, if you'd like to add a help
topic to your plugin called `test`, you would create a `test.md` file, and call
the function:
You can use the `AddRuntimeFile(name, type, path string)` function to add various kinds of
files to your plugin. For example, if you'd like to add a help topic to your plugin
called `test`, you would create a `test.md` file, and call the function:
```lua
AddRuntimeFile("test", "help", "test.md")
```
Use `AddRuntimeFilesFromDirectory(name, type, dir, pattern)` to add a number of
files to the runtime. To read the content of a runtime file use
`ReadRuntimeFile(fileType, name string)` or `ListRuntimeFiles(fileType string)`
for all runtime files.
Use `AddRuntimeFilesFromDirectory(name, type, dir, pattern)` to add a number of files
to the runtime.
To read the content of a runtime file use `ReadRuntimeFile(fileType, name string)`
or `ListRuntimeFiles(fileType string)` for all runtime files.
## Autocomplete command arguments
# Autocomplete command arguments
See this example to learn how to use `MakeCompletion` and `MakeCommand`
@@ -313,32 +245,27 @@ end
MakeCommand("foo", "example.foo", MakeCompletion("example.complete"))
```
## Default plugins
# Default plugins
For examples of plugins, see the default `autoclose` and `linter` plugins
(stored in the normal micro core repo under `runtime/plugins`) as well as any
plugins that are stored in the official channel
[here](https://github.com/micro-editor/plugin-channel).
(stored in the normal micro core repo under `runtime/plugins`) as well as
any plugins that are stored in the official channel [here](https://github.com/micro-editor/plugin-channel).
# Plugin Manager
## Plugin Manager
Micro also has a built in plugin manager which you can invoke with the
`> plugin ...` command.
Micro also has a built in plugin manager which you can invoke with the `> plugin ...` command.
For the valid commands you can use, see the `command` help topic.
The manager fetches plugins from the channels (which is simply a list of plugin
metadata) which it knows about. By default, micro only knows about the official
channel which is located at github.com/micro-editor/plugin-channel but you can
add your own third-party channels using the `pluginchannels` option and you can
directly link third-party plugins to allow installation through the plugin
manager with the `pluginrepos` option.
The manager fetches plugins from the channels (which is simply a list of plugin metadata)
which it knows about. By default, micro only knows about the official channel which is located
at github.com/micro-editor/plugin-channel but you can add your own third-party channels using
the `pluginchannels` option and you can directly link third-party plugins to allow installation
through the plugin manager with the `pluginrepos` option.
If you'd like to publish a plugin you've made as an official plugin, you should
upload your plugin online (to Github preferably) and add a `repo.json` file.
This file will contain the metadata for your plugin. Here is an example:
If you'd like to publish a plugin you've made as an official plugin, you should upload your
plugin online (to Github preferably) and add a `repo.json` file. This file will contain the
metadata for your plugin. Here is an example:
```json
[{
@@ -357,8 +284,7 @@ This file will contain the metadata for your plugin. Here is an example:
}]
```
Then open a pull request at github.com/micro-editor/plugin-channel adding a link
to the raw `repo.json` that is in your plugin repository. To make updating the
plugin work, the first line of your plugins lua code should contain the version
of the plugin. (Like this: `VERSION = "1.0.0"`) Please make sure to use
[semver](http://semver.org/) for versioning.
Then open a pull request at github.com/micro-editor/plugin-channel adding a link to the
raw `repo.json` that is in your plugin repository.
To make updating the plugin work, the first line of your plugins lua code should contain the version of the plugin. (Like this: `VERSION = "1.0.0"`)
Please make sure to use [semver](http://semver.org/) for versioning.

View File

@@ -1,17 +1,15 @@
# Tutorial
This is a brief intro to micro's configuration system that will give some simple
examples showing how to configure settings, rebind keys, and use `init.lua` to
configure micro to your liking.
This is a brief intro to micro's configuration system that will give some
simple examples showing how to configure settings, rebind keys,
and use `init.lua` to configure micro to your liking.
Hopefully you'll find this useful.
See `> help defaultkeys` for a list an explanation of the default keybindings.
### Plugins
Micro has a plugin manager which can automatically download plugins for you. To
see the 'official' plugins, go to github.com/micro-editor/plugin-channel.
Micro has a plugin manager which can automatically download plugins for you.
To see the plugins 'official' plugins, go to github.com/micro-editor/plugin-channel.
For example, if you'd like to install the snippets plugin (listed in that repo),
just press `CtrlE` to execute a command, and type `plugin install snippets`.
@@ -22,23 +20,23 @@ topic.
### Settings
In micro, your settings are stored in `~/.config/micro/settings.json`, a file
that is created the first time you run micro. It is a json file which holds all
the settings and their values. To change an option, you can either change the
value in the `settings.json` file, or you can type it in directly while using
micro.
that is created the first time you run micro. It is a json file which holds
all the settings and their values. To change an option, you can either
change the value in the `settings.json` file, or you can type it in directly
while using micro.
Simply press CtrlE to go to command mode, and type `set option value` (in the
future, I will use `> set option value` to indicate pressing CtrlE). The change
will take effect immediately and will also be saved to the `settings.json` file
so that the setting will stick even after you close micro.
future, I will use `> set option value` to indicate pressing CtrlE). The
change will take effect immediately and will also be saved to the `settings.json`
file so that the setting will stick even after you close micro.
You can also set options locally which means that the setting will only have the
value you give it in the buffer you set it in. For example, if you have two
splits open, and you type `> setlocal tabsize 2`, the tabsize will only be 2 in
the current buffer. Also micro will not save this local change to the
You can also set options locally which means that the setting will only have
the value you give it in the buffer you set it in. For example, if you have
two splits open, and you type `> setlocal tabsize 2`, the tabsize will only
be 2 in the current buffer. Also micro will not save this local change to the
`settings.json` file. However, you can still set options locally in the
`settings.json` file. For example, if you want the `tabsize` to be 2 only in
Ruby files, and 4 otherwise, you could put the following in `settings.json`:
`settings.json` file. For example, if you want the `tabsize` to be 2 only
in Ruby files, and 4 otherwise, you could put the following in `settings.json`:
```json
{
@@ -56,8 +54,8 @@ If you would like to know more about all the available options, see the
### Keybindings
Keybindings work in much the same way as options. You configure them using the
`~/.config/micro/bindings.json` file.
Keybindings work in much the same way as options. You configure them using
the `~/.config/micro/bindings.json` file.
For example if you would like to bind `CtrlR` to redo you could put the
following in `bindings.json`:
@@ -80,8 +78,8 @@ what actions are available, see the `keybindings` help topic (`> help keybinding
### Configuration with Lua
If you need more power than the json files provide, you can use the `init.lua`
file. Create it in `~/.config/micro`. This file is a lua file that is run when
micro starts and is essentially a one-file plugin.
file. Create it in `~/.config/micro`. This file is a lua file that is run
when micro starts and is essentially a one-file plugin.
I'll show you how to use the `init.lua` file by giving an example of how to
create a binding to `CtrlR` which will execute `go run` on the current file,
@@ -100,8 +98,8 @@ end
BindKey("CtrlR", "init.gorun")
```
Alternatively, you could get rid of the `BindKey` line, and put this line in the
`bindings.json` file:
Alternatively, you could get rid of the `BindKey` line, and put this line in
the `bindings.json` file:
```json
{
@@ -109,5 +107,5 @@ Alternatively, you could get rid of the `BindKey` line, and put this line in the
}
```
For more information about plugins and the lua system that micro uses, see the
`plugins` help topic (`> help plugins`).
For more information about plugins and the lua system that micro uses, see
the `plugins` help topic (`> help plugins`).

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017 Yasuhiro Matsumoto
Copyright (c) 2016 Stepets
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -0,0 +1,109 @@
# utf8.lua
one-file pure-lua 5.1 regex library
This library _is_ the simple way to add utf8 support into your application.
Some examples from http://www.lua.org/manual/5.1/manual.html#5.4 :
```Lua
local utf8 = require "utf8"
local s = "hello world from Lua"
for w in string.gmatch(s, "%a+") do
print(w)
end
--[[
hello
world
from
Lua
]]--
s = "Привет, мир, от Lua"
for w in utf8.gmatch(s, "[^%p%d%s%c]+") do
print(w)
end
--[[
Привет
мир
от
Lua
]]--
local t = {}
s = "from=world, to=Lua"
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
t[k] = v
end
for k,v in pairs(t) do
print(k,v)
end
--[[
to Lua
from world
]]--
t = {}
s = "从=世界, 到=Lua"
for k, v in utf8.gmatch(s, "([^%p%s%c]+)=([^%p%s%c]+)") do
t[k] = v
end
for k,v in pairs(t) do
print(k,v)
end
--[[
到 Lua
从 世界
]]--
local x = string.gsub("hello world", "(%w+)", "%1 %1")
print(x)
--hello hello world world
x = utf8.gsub("Ahoj světe", "([^%p%s%c]+)", "%1 %1")
print(x)
--Ahoj Ahoj světe světe
x = string.gsub("hello world", "%w+", "%0 %0", 1)
print(x)
--hello hello world
x = utf8.gsub("Ahoj světe", "[^%p%s%c]+", "%0 %0", 1)
print(x)
--Ahoj Ahoj světe
x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
print(x)
--world hello Lua from
x = utf8.gsub("γεια κόσμο από Lua", "([^%p%s%c]+)%s*([^%p%s%c]+)", "%2 %1")
print(x)
--κόσμο γεια Lua από
```
Notice, there are some classes that can work only with latin(ASCII) symbols,
for details see: https://github.com/Stepets/utf8.lua/blob/master/utf8.lua#L470
Of course you can do this trick:
```Lua
for k,v in pairs(utf8) do
string[k] = v
end
```
But this can lead to very strange errors. You were warned.
A little bit more interesting examples:
```Lua
local utf8 = require 'utf8'
for k,v in pairs(utf8) do
string[k] = v
end
local str = "пыщпыщ ололоо я водитель нло"
print(str:find("(.л.+)н"))
-- 8 26 ололоо я водитель
print(str:gsub("ло+", "보라"))
-- пыщпыщ о보라보라 я водитель н보라 3
print(str:match("^п[лопыщ ]*я"))
-- пыщпыщ ололоо я
```

File diff suppressed because it is too large Load Diff

View File

@@ -9,15 +9,9 @@ function onViewOpen(view)
local ft = view.Buf.Settings["filetype"]
if ft == "go" or
ft == "makefile" then
if ft == "makefile" or ft == "go" then
SetOption("tabstospaces", "off")
elseif ft == "fish" or
ft == "python" or
ft == "python2" or
ft == "python3" or
ft == "yaml" or
ft == "nim" then
elseif ft == "python" or ft == "python2" or ft == "python3" then
SetOption("tabstospaces", "on")
end
end

View File

@@ -16,40 +16,37 @@ function runLinter()
if OS == "windows" then
devnull = "NUL"
else
devnull = "/dev/null"
devnull = "/dev/null"
end
if ft == "c" then
lint("gcc", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
elseif ft == "c++" then
lint("gcc", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
elseif ft == "d" then
lint("dmd", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", file}, "%f%(%l%):.+: %m")
elseif ft == "go" then
if ft == "go" then
lint("gobuild", "go", {"build", "-o", devnull}, "%f:%l: %m")
lint("golint", "golint", {file}, "%f:%l:%d+: %m")
elseif ft == "java" then
lint("javac", "javac", {"-d", dir, file}, "%f:%l: error: %m")
elseif ft == "javascript" then
lint("jshint", "jshint", {file}, "%f: line %l,.+, %m")
elseif string.match(ft, "literate") then
lint("literate", "lit", {"-c", file}, "%f:%l:%m")
elseif ft == "lua" then
lint("luacheck", "luacheck", {"--no-color", file}, "%f:%l:%d+: %m")
elseif ft == "nim" then
lint("nim", "nim", {"check", "--listFullPaths", "--stdout", "--hints:off", file}, "%f.%l, %d+. %m")
elseif ft == "Objective-C" then
lint("clang", "xcrun", {"clang", "-fsyntax-only", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
elseif ft == "python" then
lint("pyflakes", "pyflakes", {file}, "%f:%l:.-:? %m")
lint("mypy", "mypy", {file}, "%f:%l: %m")
lint("pylint", "pylint", {"--output-format=parseable", "--reports=no", file}, "%f:%l: %m")
elseif ft == "shell" then
lint("shfmt", "shfmt", {file}, "%f:%l:%d+: %m")
elseif ft == "swift" and OS == "darwin" then
elseif ft == "c" then
lint("gcc", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
elseif ft == "c++" then
lint("gcc", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
elseif ft == "swift" and OS == "darwin" then
lint("switfc", "xcrun", {"swiftc", file}, "%f:%l:%d+:.+: %m")
elseif ft == "swift" and OS == "linux" then
elseif ft == "swift" and OS == "linux" then
lint("switfc", "swiftc", {file}, "%f:%l:%d+:.+: %m")
elseif ft == "Objective-C" then
lint("clang", "xcrun", {"clang", "-fsyntax-only", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
elseif ft == "d" then
lint("dmd", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", file}, "%f%(%l%):.+: %m")
elseif ft == "java" then
lint("javac", "javac", {"-d", dir, file}, "%f:%l: error: %m")
elseif ft == "javascript" then
lint("jshint", "jshint", {file}, "%f: line %l,.+, %m")
elseif ft == "nim" then
lint("nim", "nim", {"check", "--listFullPaths", "--stdout", "--hints:off", file}, "%f.%l, %d+. %m")
elseif string.match(ft, "literate") then
lint("literate", "lit", {"-c", file}, "%f:%l:%m")
elseif ft == "yaml" then
lint("yaml", "yamllint", {"--format", "parsable", file}, "%f:%l:%d+:.+ %m")
end

View File

@@ -1,7 +1,4 @@
-- VERSION = "1.0.0"
if GetOption("literate") == nil then
AddOption("literate", true)
end
VERSION = "1.0.0"
function startswith(str, start)
return string.sub(str,1,string.len(start))==start
@@ -19,7 +16,6 @@ function split(string, sep)
end
function onViewOpen(view)
if not GetOption("literate") then return end
if not endswith(view.Buf.Path, ".lit") then
return
end

View File

@@ -1,129 +0,0 @@
# PowerShell syntax highlighting file for micro - https://micro-editor.github.io/
# PowerShell syntax taken from: https://github.com/PowerShell/EditorSyntax
filetype: powershell
detect:
filename: "\\.ps(1|m1|d1)$"
#header: ""
rules:
# - comment.block: # Block Comment
# - comment.doc: # Doc Comment
# - comment.line: # Line Comment
# - comment.shebang: # Shebang Line
# - constant: # Constant
# - constant.bool: # Constant (true, false)
# - constant.interpolation:
# - constant.number: # Constant (null)
# - constant.specialChar:
# - constant.string: # String
# - constant.string.char:
# - constant.string.url: # Uri
# - constant.unicode:
# - identifier: # Also used for functions
# - identifier.class: # Also used for functions
# - identifier.macro:
# - identifier.var:
# - preproc: # Preprocessor
# - preproc.DebugIdentifier: # Preprocessor
# - preproc.shebang: # The #! at the beginning of a file that tells the os what script interpreter to use
# - special: # Special (global|local|private|script|using|workflow)
# - statement: # Statements Keywords
# - statement.built_in:
# - statement.declaration: # Declaration Keywords
# - statement.meta: # Meta
# - statement.reserved: # Reserved Keywords
# - symbol
# - symbol.brackets: # {}()[] and sometimes <>
# - symbol.operator: # Operators
# - symbol.tag: # For html tags, among other things
# - type
# - type.collections: # Collections (array, hashtable)
# - type.ctypes: # CTypes (CBool, CChar, etc.)
# - type.keyword: # If you want a special highlight for keywords like 'private'
# - type.storage: # Storage Types (int, uint, string, etc.)
# Class
- identifier.class: "class +[A-Za-z0-9]+ *((:) +[A-Za-z0-9.]+)?"
- identifier.class: "(function)(?:([[:space:]][A-Za-z0-9]+[[:space:]]*))"
# Verbs taken from PwSh 6.0.2
- identifier: "(Add|Approve|Assert|Backup|Block|Build|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy)[-][A-Za-z0-9]+"
- identifier: "(Debug|Deny|Deploy|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|Format|Get|Grant|Group|Hide)[-][A-Za-z0-9]+"
- identifier: "(Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Mount|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push)[-][A-Za-z0-9]+"
- identifier: "(Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke)[-][A-Za-z0-9]+"
- identifier: "(Save|Search|Select|Send|Set|Show|Skip|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Test|Trace)[-][A-Za-z0-9]+"
- identifier: "(Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Write)[-][A-Za-z0-9]+"
- identifier.var: "\\$(?i)((Global|Local|Private|Script|Using|Workflow)[:])?[A-Za-z0-9]*"
# Expression and types
- type: "\\[\\b([A-Za-z]+|[A-Za-z]+[0-9]+)\\b\\]"
# Keywords
- statement: "\\b(alias|as|begin|break|catch|continue|data|default|define|do|dynamicparam)\\b"
- statement: "\\b(else|elseif|end|exit|finally|for|foreach|foreach-object|from|if|in|inlinescript)\\b"
- statement: "\\b(parallel|param|process|return|switch|throw|trap|try|until|using|var|where|where-object|while)\\b"
# Special Keywords
- special: "\\b(break|continue|exit)\\b"
- symbol.brackets: "(\\{|\\})"
- symbol.brackets: "(\\(|\\))"
- symbol.brackets: "(\\[|\\])"
- symbol.operator: "[\\-+/*=<>?:!~%&|]"
- symbol.operator: "[[:space:]][-](ne|eq|gt|ge|lt|le|like|notlike|match|notmatch|contains|notcontains|in|notin|replace|is|isnot)[[:space:]]"
# Constants
- constant.bool: "\\b\\$(true|false|null)\\b"
- constant.number: "\\b([0-9._]+|0x[A-Fa-f0-9_]+|0b[0-1_]+)[FL]?\\b"
# Expression Mode String
- constant.string:
start: "\""
end: "\""
#skip: "\\\\."
rules:
- constant.specialChar: "\\\\([btnfr]|'|\\\"|\\\\)"
- constant.specialChar: "\\\\u[A-Fa-f0-9]{4}"
# Argument Mode String
- constant.string:
start: "'"
end: "'"
#skip: "\\\\."
rules:
- constant.specialChar: "\\\\([btnfr]|'|\\\"|\\\\)"
- constant.specialChar: "\\\\u[A-Fa-f0-9]{4}"
# Line Comment
- comment:
start: "#"
end: "$"
rules:
- todo: "(TODO|XXX|FIXME|BUG):?"
# Block Comment
- comment:
start: "<#"
end: "#>"
rules:
- todo: "(TODO|XXX|FIXME|BUG):?"
# Embedded C#
- default:
start: "@\""
end: "\"@"
rules:
- include: "csharp"
# Todo
- todo: "(TODO|XXX|FIXME|BUG):?"

View File

@@ -25,7 +25,7 @@ Most the the syntax files here have been converted using that tool.
Note that the tool isn't perfect and though it is unlikely, you may run into some small issues that you will have to fix manually
(about 4 files from this directory had issues after being converted).
=======
# Micro syntax highlighting files
These are the syntax highlighting files for micro. To install them, just

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