Compare commits

..

136 Commits

Author SHA1 Message Date
Zachary Yedidia
ca9d102267 Start insert performance improvements 2020-02-09 14:30:20 -05:00
Zachary Yedidia
c3d120ccdf Fix errcheck in clean 2020-02-09 00:42:16 -05:00
Zachary Yedidia
a4a6445e1d Merge 2020-02-09 00:40:55 -05:00
Zachary Yedidia
13e30a63eb Minor improvements 2020-02-09 00:40:50 -05:00
Indiana Kernick
e561952781 Add jsonnet syntax file (#1320) 2020-02-09 00:23:49 -05:00
Tommy
9fe58bf84f csharp bracket highlighting problem #1172 (#1199) 2020-02-09 00:21:27 -05:00
Zachary Yedidia
e84f85340b Merge 2020-02-09 00:19:21 -05:00
Zachary Yedidia
aaf90c6f52 Merge branch 'Paalon-master' 2020-02-09 00:19:11 -05:00
Zachary Yedidia
0962e1bfba Merge branch 'master' of https://github.com/Paalon/micro into Paalon-master 2020-02-09 00:19:02 -05:00
Zachary Yedidia
2f45644d14 Merge pull request #1324 from konsumer/master
add support for input and scalar defintiions (for graphql-tools schema)
2020-02-09 00:18:09 -05:00
Zachary Yedidia
c2a2316c28 Merge pull request #1321 from zonuexe/add/php-fn-keyword
Add `fn` keyword (arrow function) added in PHP 7.4 to coloring
2020-02-09 00:17:41 -05:00
Zachary Yedidia
6957e83cdb Merge pull request #1333 from the-sushi/patch-1
Add Forth highlighting
2020-02-09 00:17:24 -05:00
Zachary Yedidia
ce91e41e5a Update third party licenses 2020-02-09 00:03:03 -05:00
Zachary Yedidia
6d99d34eb0 Fix unsplit crash
Fixes #1488
2020-02-08 21:06:13 -05:00
Zachary Yedidia
b77980082c Fix to allow readonly to be disabled 2020-02-08 19:37:37 -05:00
Rein F
2fd59adffa Show that the file is readonly (#1486)
* Show that the file is readonly)

* change the (readonly) statusline msg into [ro]
2020-02-08 19:34:35 -05:00
Zachary Yedidia
57c34e2248 More plugin docs and improve doc formatting 2020-02-08 18:31:06 -05:00
Zachary Yedidia
6514b77e0d Enable autosave option
The autosave option is now specified as an integer, which denotes
the number of seconds to wait between saving the file. If the option
is 0, then autosaving is disabled. If the option is given by the user
as a boolean, it will be converted to 8 if true, and 0 if false.

Fixes #1479
2020-02-08 16:53:08 -05:00
Zachary Yedidia
8a907956d1 Use actual lua functions for callbacks instead of strings 2020-02-08 15:49:41 -05:00
Zachary Yedidia
c4bfa825a1 Merge pull request #1277 from coolreader18/patch-1
Update README about Windows terminal color support.
2020-02-07 20:27:51 -05:00
Zachary Yedidia
b0c50d371f Merge pull request #1423 from caligari87/patch-1
Add ZScript language highlighting
2020-02-07 20:18:05 -05:00
Zachary Yedidia
b4d4119572 Merge branch 'neutralinsomniac-fix_defaultkeys_help' 2020-02-07 20:17:17 -05:00
Zachary Yedidia
674258787e Add Mac special cases 2020-02-07 20:16:59 -05:00
Zachary Yedidia
2354412922 Add mac keybinds 2020-02-07 20:14:35 -05:00
Zachary Yedidia
3ac30343b8 Merge branch 'fix_defaultkeys_help' of https://github.com/neutralinsomniac/micro into neutralinsomniac-fix_defaultkeys_help 2020-02-07 20:09:44 -05:00
Zachary Yedidia
37343350ca Merge 2020-02-07 20:04:58 -05:00
Zachary Yedidia
d9e9d4403f Merge branch 'msiism-master' 2020-02-07 20:04:50 -05:00
Zachary Yedidia
741f494841 Merge branch 'master' of https://github.com/msiism/micro into msiism-master 2020-02-07 20:04:44 -05:00
Zachary Yedidia
69b6c724fc Merge pull request #1252 from SunflowerFuchs/patch-1
Update zsh.yaml
2020-02-07 20:03:53 -05:00
Zachary Yedidia
31936358c1 Merge pull request #1264 from krerkkiat/issue-1237
Update sh.yaml
2020-02-07 20:03:22 -05:00
Zachary Yedidia
fe58ff5753 Merge 2020-02-07 19:57:13 -05:00
Zachary Yedidia
9aaafe5dcf Merge 2020-02-07 19:56:57 -05:00
Zachary Yedidia
c2bd5e4eec Merge branch 'dbeef-master' 2020-02-07 19:42:14 -05:00
Zachary Yedidia
98ddb62af4 Update docs 2020-02-07 19:41:11 -05:00
Zachary Yedidia
24a684cff2 Merge branch 'master' of https://github.com/dbeef/micro into dbeef-master 2020-02-07 19:37:56 -05:00
Zachary Yedidia
b4e7e981f3 Support paste action in terminal 2020-02-07 19:17:17 -05:00
Zachary Yedidia
e73549c61a Merge pull request #1485 from LevitatingBusinessMan/terminal_impr
Terminal improvements
2020-02-07 19:12:05 -05:00
Rein F
e759d4087b Fix for issue 2 in #1484
Exit message when exiting terminal now isnt visibile in other views
2020-02-08 00:15:37 +01:00
Zachary Yedidia
106ba48079 Add some docs for linter, comment, status 2020-02-07 11:32:12 -05:00
Zachary Yedidia
ef768e36f3 Merge 2020-02-06 19:26:33 -05:00
Zachary Yedidia
f5e1f93ee5 Update docs 2020-02-06 19:26:27 -05:00
Zachary Yedidia
a52c0c2907 Add StartOfText options to multiactions 2020-02-06 17:10:32 -05:00
Zachary Yedidia
be7d27bc49 Action callbacks for lua actions 2020-02-06 11:12:34 -05:00
Zachary Yedidia
f6a9c482a6 Allow plugins to resize panes 2020-02-05 17:16:31 -05:00
Zachary Yedidia
6e3f38b271 Add scrolling to command bar autocompletion 2020-02-02 20:17:46 -05:00
Zachary Yedidia
8483f1da1e Make curpane only return bufpanes 2020-02-02 17:12:50 -05:00
Zachary Yedidia
28ed47e358 Fix cycleback in infopane 2020-02-02 16:16:53 -05:00
Zachary Yedidia
6a1b8f4a4f Add option to clean unused settings and other parts of config 2020-02-02 15:30:06 -05:00
Zachary Yedidia
dba8ef2fdd Use namespaces for plugin options 2020-02-02 14:35:30 -05:00
Zachary Yedidia
b0624cb66e Add support for plugin manager within micro 2020-02-02 14:20:39 -05:00
Zachary Yedidia
09ea82c97e Disable current line num style if no cursorline 2020-02-02 00:34:28 -05:00
Zachary Yedidia
d94b81b8e6 Synchronize undo and redo chunks
Fixes #1372
Fxies #1471
2020-02-02 00:14:56 -05:00
Zachary Yedidia
bcb1947a0a Add plugin manager 2020-02-01 23:54:38 -05:00
Zachary Yedidia
b0b5d7b392 Add CurPane and CurTab functions for plugins 2020-02-01 12:20:08 -05:00
Zachary Yedidia
2598d8ad70 Update colorschemes and add new ones
This commit updates the colorschemes and adds some new ones:

* gotham (https://github.com/novln/micro-gotham-colors)
* monokai-dark (https://github.com/Theodus/micro-monokai-dark)
* one-dark (https://github.com/joseluisq/micro-one-dark)
* sunny-day (https://github.com/dwwmmn/micro-sunny-day)
2020-01-31 15:05:55 -05:00
Zachary Yedidia
f731e422ea Improve lua interface 2020-01-31 14:21:27 -05:00
Jeremy O'Brien
abcdeb01e9 Fix defaultkeys help doc to match reality 2020-01-31 13:10:11 -05:00
Zachary Yedidia
d326a9cddd Merge 2020-01-31 00:56:20 -05:00
Zachary Yedidia
e3131a0779 Add text event callback 2020-01-31 00:56:15 -05:00
Zachary Yedidia
46c5a81b0d Fix callback cancelation 2020-01-30 18:04:17 -05:00
Zachary Yedidia
59146cabb1 Add callback option to linter 2020-01-30 18:00:17 -05:00
Zachary Yedidia
35e3bddea0 Modify linter and add plugin cmd 2020-01-30 17:51:04 -05:00
Zachary Yedidia
016b8dcc4c Do not add non-plugin directories in plug/ 2020-01-28 23:49:51 -05:00
Zachary Yedidia
03228762d4 Don't call plugin if nil 2020-01-28 22:06:58 -05:00
Zachary Yedidia
953f5a0eff Highlight in parallel 2020-01-28 20:54:14 -05:00
Zachary Yedidia
477bdb3dc8 Empty highlighting for unknown filetypes 2020-01-28 18:34:44 -05:00
Zachary Yedidia
d74f40882d Don't rehighlight if there are no modifications 2020-01-28 17:15:02 -05:00
Zachary Yedidia
d965e8de4f Fix copy-paste error in docs 2020-01-26 21:39:10 -05:00
Zachary Yedidia
866b3c9238 Resize tabbar properly
Ref #1467
2020-01-26 00:44:34 -05:00
Zachary Yedidia
3252324d24 Don't indent empty lines
Fixes #1472
2020-01-26 00:40:40 -05:00
Zachary Yedidia
8e7a016917 Tab horizontal scrolling should not be negative
Fixes #1467
2020-01-25 13:17:13 -05:00
Zachary Yedidia
cf41a587a3 Split the actions StartOfLine and StartOfText
The default keybindings now use StartOfText which moves the cursor
to the start of the text on the current line instead of the actual
start of the line (if the line begins with whitespace).

Fixes #1468
2020-01-25 13:02:13 -05:00
Zachary Yedidia
1dc1c65565 Update tutorial docs 2020-01-21 20:37:51 -05:00
Zachary Yedidia
97ee344268 Fix some issues with syntax highlighting regions
Fixes #1464
2020-01-20 23:43:47 -05:00
Zachary Yedidia
b658f94e5a Change ctrl-arrow default binding for non-Mac OSes
On non-Mac operating systems, the default for CtrlLeft/CtrlRight
is now WordLeft and WordRight instead of moving the cursor to the
start and end of lines (now rebound to Alt-Left/Right by default).
Default keybindings are unchanged on Mac.

Fixes #1465
2020-01-20 22:35:00 -05:00
Zachary Yedidia
0abe427026 Make readonly and filetype local-only 2020-01-20 22:03:32 -05:00
Rein F
063389afdf updated runtime 2020-01-18 19:05:54 +01:00
Rein F
1e998ab0e4 Updated javascript.yaml syntaf file 2020-01-18 19:05:33 +01:00
Zachary Yedidia
b3e40a2644 Make debug mode flag, plugins can access logbuf 2020-01-15 22:25:08 -05:00
Zachary Yedidia
fa4103f7aa Merge 2020-01-15 20:09:33 -05:00
Zachary Yedidia
17f0eb80cd Readonly should only apply to default buffers
Ref #1298
2020-01-15 20:09:17 -05:00
Zachary Yedidia
35dfb1830b Merge pull request #1459 from wgj/osx_terminal_opt_key
Add suggestions for MacOS Terminal.app users
2020-01-14 16:54:02 -05:00
Zachary Yedidia
76f09adb48 Merge pull request #1456 from srishanbhattarai/patch-2
Include mingw and fix README section link
2020-01-14 16:53:54 -05:00
Weston Johnson
289c7147e4 Add suggestions for MacOS Terminal.app users 2020-01-14 14:42:05 -07:00
Krerkkiat Chusap
be34e1241c Update julia.yml (#1262)
Add export to the keywords. This should fixes zyedidia/micro#1215.
2020-01-11 13:41:52 -05:00
Srishan Bhattarai
83ea8d8be9 Include mingw and fix README section link 2020-01-09 15:16:15 -06:00
Zachary Yedidia
7c90f4d6f1 Merge 2020-01-06 12:29:39 -05:00
Zachary Yedidia
61a90f7666 Update xml.yaml 2020-01-06 12:29:33 -05:00
Serge Voilokov
8d373cde6e Add golang keywords (#1455)
* Add golang keywords

* Update runtime
2020-01-06 12:06:44 -05:00
Zachary Yedidia
6a465500bc Properly handle empty args with new shellquote lib
Fixes #1454
2020-01-06 11:38:21 -05:00
Zachary Yedidia
f3e8413e77 More doc updates 2020-01-06 00:01:49 -05:00
Zachary Yedidia
f2a1e2337f Update tcell to include true color fix
Fixes #1452
2020-01-05 21:26:53 -05:00
Zachary Yedidia
c7f36f9480 Don't indent softwrap if ruler is off
Ref #1450
2020-01-05 20:32:29 -05:00
Zachary Yedidia
955bde4abc Minor view fix 2020-01-05 15:02:52 -05:00
Zachary Yedidia
afb03aa37f Small doc update 2020-01-05 13:21:46 -05:00
Zachary Yedidia
6c3814dfac Better message for gob error 2020-01-05 12:45:27 -05:00
Zachary Yedidia
d234e9ec41 Add cycleautocompleteback action 2020-01-04 15:51:15 -05:00
Bonnie
c2c0325384 Fix #1383: "Save with Sudo" rewrite (#1424)
* Rewrite save with sudo (Fixes #1383)

* Combine overrideFile & overrideFileAsRoot into 1 function
2020-01-03 17:39:12 -05:00
Zachary Yedidia
dfb6bc0312 Fix save callback issue 2020-01-03 17:38:50 -05:00
Zachary Yedidia
0c6a7e2837 Update options docs and new docs on copy-paste 2020-01-03 13:39:39 -05:00
Zachary Yedidia
ddc8bf455e Set filetype to 'off' to disable completely
Ref #1427
2020-01-02 19:00:42 -05:00
Zachary Yedidia
2855ae204c Replace shellwords with shellquote 2020-01-02 18:30:51 -05:00
Zachary Yedidia
0bf54ff0e7 Don't crash if only file to open is directory 2020-01-02 15:25:07 -05:00
Zachary Yedidia
50ff45c213 Some documentation updates 2020-01-02 15:10:28 -05:00
Zachary Yedidia
eb2b546600 Merge 2020-01-02 12:43:52 -05:00
Zachary Yedidia
dc4da37908 Add "paste" option to enable aggressive pasting
Ref #1043
2020-01-02 12:42:39 -05:00
Zachary Yedidia
9333354fc8 Fix save with sudo on mac 2020-01-02 01:25:00 -05:00
Zachary Yedidia
b557ed2221 Fix PluginAddRuntimeFile 2020-01-02 01:18:16 -05:00
Zachary Yedidia
021f8da6f1 update readme 2020-01-01 23:00:46 -05:00
Zachary Yedidia
6d0128059b Finish support for a fake cursor 2020-01-01 22:40:51 -05:00
Zachary Yedidia
d6dd838abd Better support for fake cursor 2020-01-01 21:29:18 -05:00
Zachary Yedidia
938fb7983a Remove no longer necessary terminfo package 2020-01-01 20:58:01 -05:00
Zachary Yedidia
d9e262c394 Use fake cursor for windows 2020-01-01 20:47:05 -05:00
Zachary Yedidia
e98be1a1e5 Update deps 2020-01-01 20:44:45 -05:00
Zachary Yedidia
41fe7d090e Update tcell
Ref #1447
2020-01-01 18:24:39 -05:00
Zachary Yedidia
aadf5b40ec Update tcell
This update includes a fix for screen flashing on Windows.

Fixes #1447
2020-01-01 17:57:16 -05:00
Zachary Yedidia
ebf6d69f26 Update building from source info 2020-01-01 17:41:59 -05:00
Zachary Yedidia
ebf616399e Update tcell 2020-01-01 17:26:49 -05:00
Zachary Yedidia
08708f79bf Update tcell 2020-01-01 17:16:18 -05:00
Zachary Yedidia
d7b39fe7a5 Disable true color by default 2019-12-31 23:09:33 -05:00
Zachary Yedidia
48ace1c530 Add extra nightly release message 2019-12-31 22:49:21 -05:00
Zachary Yedidia
fde1cc563f Update tcell 2019-12-31 22:46:30 -05:00
Zachary Yedidia
abf07a8357 Update runtime 2019-12-31 22:42:35 -05:00
Sterling Parker
0827968f6b Create zscript.yaml 2019-12-04 11:52:22 -07:00
0xdbeef
89ac5d7de2 SpawnMultiCursorDown / SpawnMultiCursorUp 2019-11-17 11:22:25 +01:00
Ender - Josh Pritsker
612ebb2e17 Add Forth highlighting 2019-05-25 21:01:24 -07:00
David Konsumer
26172b5101 add support for input and scalar defintiions (for graphql-tools schema) 2019-05-16 16:25:51 -07:00
USAMI Kenta
51691ed7bf Reclassified PHP keywords 2019-05-12 20:14:17 +09:00
USAMI Kenta
5acbccf0b2 Add fn keyword (arrow function) added in PHP 7.4 to coloring
https://wiki.php.net/rfc/arrow_functions_v2
2019-05-12 18:58:25 +09:00
Koki Fushimi
87661ef308 Update keywords for infix operators 2019-04-09 06:57:58 +09:00
Koki Fushimi
58e11c0b2d Update Julia keywords 2019-04-09 06:39:46 +09:00
coolreader18
457a4f8f98 Update README regarding windows terminal colors 2019-02-09 21:39:28 -06:00
Krerkkiat Chusap
e3fd914e0b Update sh.yaml
This adds back the lowercase characters to the identifier name.
2019-01-14 04:03:11 -05:00
Pascal
6aa5aa540b Update zsh.yaml
Copied comment start from sh.yaml so that stuff like `$#` doesn't count as comments
2018-12-13 09:55:19 +01:00
Michael Siegel
81bad4d089 Merge pull request #1 from msiism/msiism-patch-1
Say "syntax highlighting" instead of just "syntax"
2018-11-25 19:13:26 +00:00
Michael Siegel
9f4da789db Say "syntax highlighting" instead of just "syntax" 2018-11-25 19:06:10 +00:00
126 changed files with 4355 additions and 4447 deletions

View File

@@ -1,4 +1,4 @@
Third party libraries
Third party libraries directly used by micro and their licenses
================
github.com/golang/go/LICENSE
@@ -637,37 +637,6 @@ github.com/zyedidia/tcell/LICENSE (fork)
limitations under the License.
golang.org/x/net/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
================
@@ -1079,6 +1048,8 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
github.com/flynn/json5/LICENSE
================
github.com/zyedidia/json5/LICENSE (fork)
================
Decoder code based on package encoding/json from the Go language.
@@ -1189,82 +1160,6 @@ 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)
@@ -1362,10 +1257,35 @@ 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
================
github.com/kballard/go-shellquote/LICENSE
===============
Copyright (c) 2013 Lucas Beyer
Copyright (C) 2014 Kevin Ballard
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/stretchr/testify
=================
MIT License
Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1374,8 +1294,8 @@ 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 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,

View File

@@ -12,12 +12,9 @@ GOVARS = -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github
# Builds micro after checking dependencies but without updating the runtime
build:
go build -mod=readonly -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-dbg:
go build -mod=readonly -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-mod:
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Builds micro after building the runtime and checking dependencies
@@ -25,18 +22,18 @@ build-all: runtime build
# Builds micro without checking for dependencies
build-quick:
go build -mod=readonly -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Same as 'build' but installs to $GOBIN afterward
install:
go install -mod=readonly -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Same as 'build-all' but installs to $GOBIN afterward
install-all: runtime install
# Same as 'build-quick' but installs to $GOBIN afterward
install-quick:
go install -mod=readonly -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Builds the runtime
runtime:

View File

@@ -29,7 +29,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
- [MacOS terminal](#macos-terminal)
- [Linux clipboard support](#linux-clipboard-support)
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
- [Plan9, Cygwin](#plan9-cygwin)
- [Plan9, Cygwin, Mingw](#plan9-cygwin-mingw)
- [Usage](#usage)
- [Documentation and Help](#documentation-and-help)
- [Contributing](#contributing)
@@ -50,25 +50,20 @@ You can also check out the website for Micro at https://micro-editor.github.io.
* Extremely good mouse support
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
* Cross platform (It should work on all the platforms Go runs on)
* Note that while Windows is supported, there are still some bugs that need to be worked out
* Note that while Windows is supported Mingw/Cygwin is not (see below)
* Plugin system (plugins are written in Lua)
* Micro has a built-in plugin manager to automatically install, remove, and update all your plugins
* Persistent undo
* Automatic linting and error notifications
* Syntax highlighting (for over [90 languages](runtime/syntax)!)
* Syntax highlighting (for over [120 languages](runtime/syntax)!)
* Colorscheme support
* By default, micro comes with 16, 256, and true color themes.
* True color support (set the `MICRO_TRUECOLOR` env variable to 1 to enable it)
* Snippets
* The snippet plugin can be installed with `> plugin install snippets`
* True color support (set the `MICRO_TRUECOLOR` environment variable to 1 to enable it)
* Copy and paste with the system clipboard
* Small and simple
* Easily configurable
* Macros
* 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.
# Installation
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
@@ -106,52 +101,51 @@ You can install micro using Homebrew on Mac:
brew install micro
```
On Windows, you can install micro through [Chocolatey](https://chocolatey.org/) or [Scoop](https://github.com/lukesampson/scoop):
```
choco install micro
```
or
```
scoop install micro
```
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
On Debian 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.
Homebrew and snap are the two "officially" maintained package manager distributions of micro.
```
pkg_add -v micro
```
Micro is also available through other package managers on Linux such as AUR, Nix, and package managers
for other operating systems:
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop)
* `choco install micro`
* `scoop install micro`
* OpenBSD: Available in the ports tree and also available as a binary package
* `pkd_add -v micro`
### 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.11 or greater and Go modules are enabled.
```
go get -d github.com/zyedidia/micro/cmd/micro
cd $GOPATH/src/github.com/zyedidia/micro
make install
git clone https://github.com/zyedidia/micro
cd micro
make build
sudo mv micro /usr/local/bin # optional
```
The binary will then be installed to `$GOPATH/bin` (or your `$GOBIN`).
The binary will be placed in the current directory and can be moved to
anywhere you like (for example `/usr/local/bin`).
Please make sure that when you are working with micro's code, you are working on your `GOPATH`.
The command `make install` will install the binary to `$GOPATH/bin` or `$GOBIN`.
You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd/micro`) but this isn't recommended because it doesn't build micro with version information which is useful for the plugin manager.
You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/micro`) but this isn't
recommended because it doesn't build micro with version information, and doesn't disable debug mode.
### 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 still insist on using the default Mac terminal, be sure to set `Use Option key as Meta key` under
`Preferences->Profiles->Keyboard` to use <kbd>option</kbd> as <kbd>alt</kbd>.
### Linux clipboard support
On Linux, clipboard support requires the 'xclip' or 'xsel' commands to be installed.
@@ -167,20 +161,23 @@ If you don't have xclip or xsel, micro will use an internal clipboard for copy a
### Colors and syntax highlighting
If you open micro and it doesn't seem like syntax highlighting is working, this is probably because
you are using a terminal which does not support 256 color. Try changing the colorscheme to `simple`
you are using a terminal which does not support 256 colors. Try changing the colorscheme to `simple`
by pressing CtrlE in micro and typing `set colorscheme simple`.
If you are using the default Ubuntu terminal, to enable 256 make sure your `TERM` variable is set
to `xterm-256color`.
Many of the Windows terminals don't support more than 16 colors, which means
Many older Windows terminals don't support more than 16 colors, which means
that micro's default colorscheme won't look very good. You can either set
the colorscheme to `simple`, or download a better terminal emulator, like
mintty.
mintty. However, if you are on a recent version of Windows 10, the default
`cmd.exe` should do fine.
### Plan9, Cygwin
### Plan9, Cygwin, Mingw
Please note that micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
These platforms are unfortunately not supported.
Micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).

143
cmd/micro/clean.go Normal file
View File

@@ -0,0 +1,143 @@
package main
import (
"bufio"
"encoding/gob"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
)
func shouldContinue() bool {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Continue [Y/n]: ")
text, err := reader.ReadString('\n')
if err != nil {
fmt.Println(err)
return false
}
if len(text) <= 1 {
// default continue
return true
}
return strings.ToLower(text)[0] == 'y'
}
// CleanConfig performs cleanup in the user's configuration directory
func CleanConfig() {
fmt.Println("Cleaning your configuration directory at", config.ConfigDir)
fmt.Printf("Please consider backing up %s before continuing\n", config.ConfigDir)
if !shouldContinue() {
fmt.Println("Stopping early")
return
}
// detect unused options
var unusedOptions []string
defaultSettings := config.DefaultAllSettings()
for k := range config.GlobalSettings {
if _, ok := defaultSettings[k]; !ok {
valid := false
for _, p := range config.Plugins {
if strings.HasPrefix(k, p.Name+".") || k == p.Name {
valid = true
}
}
if !valid {
unusedOptions = append(unusedOptions, k)
}
}
}
if len(unusedOptions) > 0 {
fmt.Println("The following options are unused:")
sort.Strings(unusedOptions)
for _, s := range unusedOptions {
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
}
fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
if shouldContinue() {
for _, s := range unusedOptions {
delete(config.GlobalSettings, s)
}
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
if err != nil {
fmt.Println("Error writing settings.json file: " + err.Error())
}
fmt.Println("Removed unused options")
fmt.Print("\n\n")
}
}
// detect incorrectly formatted buffer/ files
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
if err == nil {
var badFiles []string
var buffer buffer.SerializedBuffer
for _, f := range files {
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
file, e := os.Open(fname)
if e == nil {
defer file.Close()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&buffer)
if err != nil && f.Name() != "history" {
badFiles = append(badFiles, fname)
}
}
}
if len(badFiles) > 0 {
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
fmt.Println("These files store cursor and undo history.")
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
if shouldContinue() {
for _, f := range badFiles {
err := os.Remove(f)
if err != nil {
fmt.Println(err)
continue
}
}
fmt.Println("Removed badly formatted files")
fmt.Print("\n\n")
}
}
}
// detect plugins/ directory
plugins := filepath.Join(config.ConfigDir, "plugins")
if stat, err := os.Stat(plugins); err == nil && stat.IsDir() {
fmt.Printf("Found directory %s\n", plugins)
fmt.Printf("Plugins should now be stored in %s\n", filepath.Join(config.ConfigDir, "plug"))
fmt.Printf("Removing %s\n", plugins)
if shouldContinue() {
os.RemoveAll(plugins)
}
fmt.Print("\n\n")
}
fmt.Println("Done cleaning")
}

View File

@@ -21,6 +21,7 @@ func init() {
ulua.L.SetGlobal("import", luar.New(ulua.L, LuaImport))
}
// LuaImport is meant to be called from lua by a plugin and will import the given micro package
func LuaImport(pkg string) *lua.LTable {
switch pkg {
case "micro":
@@ -46,6 +47,12 @@ func luaImportMicro() *lua.LTable {
ulua.L.SetField(pkg, "InfoBar", luar.New(ulua.L, action.GetInfoBar))
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, log.Println))
ulua.L.SetField(pkg, "SetStatusInfoFn", luar.New(ulua.L, display.SetStatusInfoFnLua))
ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() action.Pane {
return action.MainTab().CurPane()
}))
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, func() *action.Tab {
return action.MainTab()
}))
return pkg
}
@@ -53,7 +60,7 @@ func luaImportMicro() *lua.LTable {
func luaImportMicroConfig() *lua.LTable {
pkg := ulua.L.NewTable()
ulua.L.SetField(pkg, "MakeCommand", luar.New(ulua.L, action.LuaMakeCommand))
ulua.L.SetField(pkg, "MakeCommand", luar.New(ulua.L, action.MakeCommand))
ulua.L.SetField(pkg, "FileComplete", luar.New(ulua.L, buffer.FileComplete))
ulua.L.SetField(pkg, "HelpComplete", luar.New(ulua.L, action.HelpComplete))
ulua.L.SetField(pkg, "OptionComplete", luar.New(ulua.L, action.OptionComplete))
@@ -62,16 +69,17 @@ func luaImportMicroConfig() *lua.LTable {
ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKey))
ulua.L.SetField(pkg, "Reload", luar.New(ulua.L, action.ReloadConfig))
ulua.L.SetField(pkg, "AddRuntimeFileFromMemory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFilesFromDirectory))
ulua.L.SetField(pkg, "AddRuntimeFile", luar.New(ulua.L, config.PluginAddRuntimeFile))
ulua.L.SetField(pkg, "ListRuntimeFiles", luar.New(ulua.L, config.PluginListRuntimeFiles))
ulua.L.SetField(pkg, "ReadRuntimeFile", luar.New(ulua.L, config.PluginReadRuntimeFile))
ulua.L.SetField(pkg, "NewRTFiletype", luar.New(ulua.L, config.NewRTFiletype))
ulua.L.SetField(pkg, "RTColorscheme", luar.New(ulua.L, config.RTColorscheme))
ulua.L.SetField(pkg, "RTSyntax", luar.New(ulua.L, config.RTSyntax))
ulua.L.SetField(pkg, "RTHelp", luar.New(ulua.L, config.RTHelp))
ulua.L.SetField(pkg, "RTPlugin", luar.New(ulua.L, config.RTPlugin))
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOption))
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOption))
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOptionPlug))
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOptionPlug))
ulua.L.SetField(pkg, "GetGlobalOption", luar.New(ulua.L, config.GetGlobalOption))
ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOption))
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNative))
@@ -113,10 +121,15 @@ func luaImportMicroBuffer() *lua.LTable {
ulua.L.SetField(pkg, "BTScratch", luar.New(ulua.L, buffer.BTScratch.Kind))
ulua.L.SetField(pkg, "BTRaw", luar.New(ulua.L, buffer.BTRaw.Kind))
ulua.L.SetField(pkg, "BTInfo", luar.New(ulua.L, buffer.BTInfo.Kind))
ulua.L.SetField(pkg, "NewBuffer", luar.New(ulua.L, func(text, path string) *buffer.Buffer {
return buffer.NewBufferFromString(text, path, buffer.BTDefault)
}))
ulua.L.SetField(pkg, "NewBufferFromFile", luar.New(ulua.L, func(path string) (*buffer.Buffer, error) {
return buffer.NewBufferFromFile(path, buffer.BTDefault)
}))
ulua.L.SetField(pkg, "ByteOffset", luar.New(ulua.L, buffer.ByteOffset))
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, buffer.WriteLog))
ulua.L.SetField(pkg, "LogBuf", luar.New(ulua.L, buffer.GetLogBuf))
return pkg
}
@@ -127,6 +140,10 @@ func luaImportMicroUtil() *lua.LTable {
ulua.L.SetField(pkg, "RuneAt", luar.New(ulua.L, util.LuaRuneAt))
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
ulua.L.SetField(pkg, "String", luar.New(ulua.L, util.String))
ulua.L.SetField(pkg, "RuneStr", luar.New(ulua.L, func(r rune) string {
return string(r)
}))
return pkg
}

View File

@@ -5,7 +5,9 @@ import (
"fmt"
"io/ioutil"
"os"
"runtime"
"sort"
"time"
"github.com/go-errors/errors"
isatty "github.com/mattn/go-isatty"
@@ -27,22 +29,42 @@ var (
flagVersion = flag.Bool("version", false, "Show the version number and information")
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
flagOptions = flag.Bool("options", false, "Show all option help")
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
flagPlugin = flag.String("plugin", "", "Plugin command")
flagClean = flag.Bool("clean", false, "Clean configuration directory")
optionFlags map[string]*string
)
func InitFlags() {
flag.Usage = func() {
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
fmt.Println("-clean")
fmt.Println(" \tCleans the configuration directory")
fmt.Println("-config-dir dir")
fmt.Println(" \tSpecify a custom location for the configuration directory")
fmt.Println("[FILE]:LINE:COL")
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("-debug")
fmt.Println(" \tEnable debug mode (enables logging to ./log.txt)")
fmt.Println("-version")
fmt.Println(" \tShow the version number and information")
fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
fmt.Println("-plugin install [PLUGIN]...")
fmt.Println(" \tInstall plugin(s)")
fmt.Println("-plugin remove [PLUGIN]...")
fmt.Println(" \tRemove plugin(s)")
fmt.Println("-plugin update [PLUGIN]...")
fmt.Println(" \tUpdate plugin(s) (if no argument is given, updates all plugins)")
fmt.Println("-plugin search [PLUGIN]...")
fmt.Println(" \tSearch for a plugin")
fmt.Println("-plugin list")
fmt.Println(" \tList installed plugins")
fmt.Println("-plugin available")
fmt.Println(" \tList available plugins")
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
fmt.Println("-option value")
fmt.Println(" \tSet `option` to `value` for this session")
@@ -81,6 +103,27 @@ func InitFlags() {
}
os.Exit(0)
}
if util.Debug == "OFF" && *flagDebug {
util.Debug = "ON"
}
}
// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
func DoPluginFlags() {
if *flagClean || *flagPlugin != "" {
config.LoadAllPlugins()
if *flagPlugin != "" {
args := flag.Args()
config.PluginCommand(os.Stdout, *flagPlugin, args)
} else if *flagClean {
CleanConfig()
}
os.Exit(0)
}
}
// LoadInput determines which files should be loaded into buffers
@@ -143,10 +186,10 @@ func main() {
var err error
InitLog()
InitFlags()
InitLog()
err = config.InitConfigDir(*flagConfigDir)
if err != nil {
screen.TermMessage(err)
@@ -171,25 +214,10 @@ func main() {
}
}
DoPluginFlags()
screen.Init()
action.InitBindings()
action.InitCommands()
err = config.InitColorscheme()
if err != nil {
screen.TermMessage(err)
}
err = config.LoadAllPlugins()
if err != nil {
screen.TermMessage(err)
}
err = config.RunPluginFn("init")
if err != nil {
screen.TermMessage(err)
}
// If we have an error, we can exit cleanly and not completely
// mess up the terminal being worked in
// In other words we need to shut down tcell before the program crashes
@@ -207,10 +235,35 @@ func main() {
}
}()
err = config.LoadAllPlugins()
if err != nil {
screen.TermMessage(err)
}
action.InitBindings()
action.InitCommands()
err = config.InitColorscheme()
if err != nil {
screen.TermMessage(err)
}
b := LoadInput()
if len(b) == 0 {
// No buffers to open
screen.Screen.Fini()
runtime.Goexit()
}
action.InitTabs(b)
action.InitGlobals()
err = config.RunPluginFn("init")
if err != nil {
screen.TermMessage(err)
}
events = make(chan tcell.Event)
// Here is the event loop which runs in a separate thread
@@ -225,6 +278,22 @@ func main() {
}
}()
// clear the drawchan so we don't redraw excessively
// if someone requested a redraw before we started displaying
for len(screen.DrawChan) > 0 {
<-screen.DrawChan
}
var event tcell.Event
// wait for initial resize event
select {
case event = <-events:
action.Tabs.HandleEvent(event)
case <-time.After(10 * time.Millisecond):
// time out after 10ms
}
for {
// Display everything
screen.Screen.Fill(' ', config.DefStyle)
@@ -237,13 +306,13 @@ func main() {
action.InfoBar.Display()
screen.Screen.Show()
var event tcell.Event
event = nil
// Check for new events
select {
case f := <-shell.Jobs:
// If a new job has finished while running in the background we should execute the callback
f.Function(f.Output, f.Args...)
f.Function(f.Output, f.Args)
case <-config.Autosave:
for _, b := range buffer.OpenBuffers {
b.Save()

11
go.mod
View File

@@ -3,21 +3,24 @@ module github.com/zyedidia/micro
require (
github.com/blang/semver v3.5.1+incompatible
github.com/dustin/go-humanize v1.0.0
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633
github.com/gdamore/tcell v1.3.0
github.com/go-errors/errors v1.0.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-isatty v0.0.11
github.com/mattn/go-runewidth v0.0.7
github.com/mitchellh/go-homedir v1.1.0
github.com/sergi/go-diff v1.1.0
github.com/stretchr/testify v1.4.0
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
github.com/zyedidia/poller v0.0.0-20170616160828-ab09682913b7 // indirect
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/pty v2.0.0+incompatible // indirect
github.com/zyedidia/tcell v0.0.0-20200101033337-8f44670b0885
github.com/zyedidia/tcell v1.4.2
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
golang.org/x/text v0.3.2
gopkg.in/yaml.v2 v2.2.7
layeh.com/gopher-luar v1.0.7
)
go 1.11

42
go.sum
View File

@@ -1,38 +1,39 @@
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633 h1:xJMmr4GMYIbALX5edyoDIOQpc2bOQTeJiWMeCl9lX/8=
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633/go.mod h1:NJDK3/o7abx6PP54EOe0G0n0RLmhCo9xv61gUYpI0EY=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
@@ -41,26 +42,16 @@ github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197 h1:gYTNnAW6azuB
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
github.com/zyedidia/poller v0.0.0-20170616160828-ab09682913b7 h1:G2kKl91VH6VynNSOqOPL9E58iFHWIamXp+feOi7KOyo=
github.com/zyedidia/poller v0.0.0-20170616160828-ab09682913b7/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
github.com/zyedidia/poller v2.0.0+incompatible h1:DMOvB0EXz2JTokqOKfxPWN/8xXFJbO+m4vNhMkOY7Lo=
github.com/zyedidia/poller v2.0.0+incompatible/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
github.com/zyedidia/pty v1.1.1 h1:SGOF3egDZGCGbpB5DPlg+yabbAuVkUtXVeNTDFeEytM=
github.com/zyedidia/pty v1.1.1/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5 h1:Zs6mpwXvlqpF9zHl5XaN0p5V4J9XvP+WBuiuXyIgqvc=
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5/go.mod h1:c1r+Ob9tUTPB0FKWO1+x+Hsc/zNa45WdGq7Y38Ybip0=
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d h1:zmDMkh22zXOB7gz8jFaI4GpI7llsPgzm38/jG0UgxjE=
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I=
github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b h1:cryFENlMxJJrkimVx/CUMFDCxC4vpmey2x3A3tAgTNM=
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b/go.mod h1:yXgdp23+aW8OMENYVBvpKoeiBtjaVWJ9HhpPDu6LBfM=
github.com/zyedidia/tcell v0.0.0-20191227234059-2c574ec1b972 h1:tqsPH3tvIF9LQMWQODln09mJEV1ZlwC1qc5dTplj+eI=
github.com/zyedidia/tcell v0.0.0-20191227234059-2c574ec1b972/go.mod h1:yXgdp23+aW8OMENYVBvpKoeiBtjaVWJ9HhpPDu6LBfM=
github.com/zyedidia/tcell v0.0.0-20191228235154-5b9bbc0d56c7 h1:Qw5255OIirY741L/Kt5KLitfzfhpk29x+A193zByTS0=
github.com/zyedidia/tcell v0.0.0-20191228235154-5b9bbc0d56c7/go.mod h1:yXgdp23+aW8OMENYVBvpKoeiBtjaVWJ9HhpPDu6LBfM=
github.com/zyedidia/tcell v0.0.0-20200101010555-3d6f590fde0b h1:WOYC0HHwRKNvDcvVbm5QXfjD+uoBvnqqSxRdjlsXIq0=
github.com/zyedidia/tcell v0.0.0-20200101010555-3d6f590fde0b/go.mod h1:b7qO+6WpCTSDPZ3ru7R2FNZeEtcNpo+X8m+5Py0/l64=
github.com/zyedidia/tcell v0.0.0-20200101030217-abaa2102dece h1:Qj15gTpYWVtGA9K7LFkiuBbk5BjMfJJZhrw3e+zKI2k=
github.com/zyedidia/tcell v0.0.0-20200101030217-abaa2102dece/go.mod h1:b7qO+6WpCTSDPZ3ru7R2FNZeEtcNpo+X8m+5Py0/l64=
github.com/zyedidia/tcell v0.0.0-20200101033337-8f44670b0885 h1:BB7VLUCQCQm7vQYrePdVzoCtbacfdiHfGJ7NzkoEYeA=
github.com/zyedidia/tcell v0.0.0-20200101033337-8f44670b0885/go.mod h1:b7qO+6WpCTSDPZ3ru7R2FNZeEtcNpo+X8m+5Py0/l64=
github.com/zyedidia/tcell v1.4.2 h1:JWMDs6O1saINPIR5M3kNqlWJwkfnBZeZDZszEJi3BW8=
github.com/zyedidia/tcell v1.4.2/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -72,6 +63,7 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -7,13 +7,13 @@ import (
"time"
"unicode/utf8"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/pkg/shellwords"
"github.com/zyedidia/tcell"
)
@@ -283,15 +283,18 @@ func (h *BufPane) SelectWordLeft() bool {
return true
}
// StartOfLine moves the cursor to the start of the text of the line
func (h *BufPane) StartOfText() bool {
h.Cursor.Deselect(true)
h.Cursor.StartOfText()
h.Relocate()
return true
}
// StartOfLine moves the cursor to the start of the line
func (h *BufPane) StartOfLine() bool {
h.Cursor.Deselect(true)
h.Cursor.StartOfText()
// if h.Cursor.X != 0 {
// h.Cursor.Start()
// } else {
// h.Cursor.StartOfText()
// }
h.Cursor.Start()
h.Relocate()
return true
}
@@ -311,6 +314,17 @@ func (h *BufPane) SelectLine() bool {
return true
}
// SelectToStartOfText selects to the start of the text on the current line
func (h *BufPane) SelectToStartOfText() bool {
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
h.Cursor.StartOfText()
h.Cursor.SelectTo(h.Cursor.Loc)
h.Relocate()
return true
}
// SelectToStartOfLine selects to the start of the current line
func (h *BufPane) SelectToStartOfLine() bool {
if !h.Cursor.HasSelection() {
@@ -534,12 +548,14 @@ func (h *BufPane) IndentSelection() bool {
tabsize := int(h.Buf.Settings["tabsize"].(float64))
indentsize := len(h.Buf.IndentString(tabsize))
for y := startY; y <= endY; y++ {
h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
if y == startY && start.X > 0 {
h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
}
if y == endY {
h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
if len(h.Buf.LineBytes(y)) > 0 {
h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
if y == startY && start.X > 0 {
h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
}
if y == endY {
h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
}
}
}
h.Buf.RelocateCursors()
@@ -611,6 +627,19 @@ func (h *BufPane) Autocomplete() bool {
return b.Autocomplete(buffer.BufferComplete)
}
// CycleAutocompleteBack cycles back in the autocomplete suggestion list
func (h *BufPane) CycleAutocompleteBack() bool {
if h.Cursor.HasSelection() {
return false
}
if h.Buf.HasSuggestions {
h.Buf.CycleAutocomplete(false)
return true
}
return false
}
// InsertTab inserts a tab or spaces
func (h *BufPane) InsertTab() bool {
b := h.Buf
@@ -636,7 +665,10 @@ func (h *BufPane) Save() bool {
if h.Buf.Path == "" {
h.SaveAs()
} else {
h.saveBufToFile(h.Buf.Path, "Save")
noPrompt := h.saveBufToFile(h.Buf.Path, "Save")
if noPrompt {
return true
}
}
return false
@@ -647,13 +679,20 @@ func (h *BufPane) SaveAs() bool {
InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
if !canceled {
// the filename might or might not be quoted, so unquote first then join the strings.
args, err := shellwords.Split(resp)
filename := strings.Join(args, " ")
args, err := shellquote.Split(resp)
if err != nil {
InfoBar.Error("Error parsing arguments: ", err)
return
}
h.saveBufToFile(filename, "SaveAs")
if len(args) == 0 {
InfoBar.Error("No filename given")
return
}
filename := strings.Join(args, " ")
noPrompt := h.saveBufToFile(filename, "SaveAs")
if noPrompt {
h.completeAction("SaveAs")
}
}
})
return false
@@ -661,7 +700,7 @@ func (h *BufPane) SaveAs() bool {
// This function saves the buffer to `filename` and changes the buffer's path and name
// to `filename` if the save is successful
func (h *BufPane) saveBufToFile(filename string, action string) {
func (h *BufPane) saveBufToFile(filename string, action string) bool {
err := h.Buf.SaveAs(filename)
if err != nil {
if strings.HasSuffix(err.Error(), "permission denied") {
@@ -678,6 +717,7 @@ func (h *BufPane) saveBufToFile(filename string, action string) {
h.completeAction(action)
}
})
return false
} else {
InfoBar.Error(err)
}
@@ -685,8 +725,8 @@ func (h *BufPane) saveBufToFile(filename string, action string) {
h.Buf.Path = filename
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
h.completeAction(action)
}
return true
}
// Find opens a prompt and searches forward for the input
@@ -1238,20 +1278,20 @@ func (h *BufPane) Quit() bool {
}
}
if h.Buf.Modified() {
// if config.GlobalSettings["autosave"].(float64) > 0 {
// autosave on means we automatically save when quitting
// h.Save()
// quit()
// } else {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
quit()
} else if !canceled && yes {
h.Save()
quit()
}
})
// }
if config.GlobalSettings["autosave"].(float64) > 0 {
// autosave on means we automatically save when quitting
h.Save()
quit()
} else {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
quit()
} else if !canceled && yes {
h.Save()
quit()
}
})
}
} else {
quit()
}
@@ -1333,38 +1373,42 @@ func (h *BufPane) HSplitAction() bool {
// Unsplit closes all splits in the current tab except the active one
func (h *BufPane) Unsplit() bool {
n := MainTab().GetNode(h.splitID)
n.Unsplit()
tab := h.tab
n := tab.GetNode(h.splitID)
ok := n.Unsplit()
if ok {
tab.RemovePane(tab.GetPane(h.splitID))
tab.Resize()
tab.SetActive(len(tab.Panes) - 1)
MainTab().RemovePane(MainTab().GetPane(h.splitID))
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
return true
return true
}
return false
}
// NextSplit changes the view to the next split
func (h *BufPane) NextSplit() bool {
a := MainTab().active
if a < len(MainTab().Panes)-1 {
a := h.tab.active
if a < len(h.tab.Panes)-1 {
a++
} else {
a = 0
}
MainTab().SetActive(a)
h.tab.SetActive(a)
return true
}
// PreviousSplit changes the view to the previous split
func (h *BufPane) PreviousSplit() bool {
a := MainTab().active
a := h.tab.active
if a > 0 {
a--
} else {
a = len(MainTab().Panes) - 1
a = len(h.tab.Panes) - 1
}
MainTab().SetActive(a)
h.tab.SetActive(a)
return true
}
@@ -1443,6 +1487,41 @@ func (h *BufPane) SpawnMultiCursor() bool {
return true
}
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
func (h *BufPane) SpawnMultiCursorUp() bool {
if h.Cursor.Y == 0 {
return false
} else {
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
h.Cursor.Relocate()
}
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
h.Buf.AddCursor(c)
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
h.Buf.MergeCursors()
h.Relocate()
return true
}
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y more.
func (h *BufPane) SpawnMultiCursorDown() bool {
if h.Cursor.Y+1 == h.Buf.LinesNum() {
return false
} else {
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
h.Cursor.Relocate()
}
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
h.Buf.AddCursor(c)
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
h.Buf.MergeCursors()
h.Relocate()
return true
}
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
func (h *BufPane) SpawnMultiCursorSelect() bool {
// Avoid cases where multiple cursors already exist, that would create problems

View File

@@ -8,7 +8,7 @@ import (
"strings"
"unicode"
"github.com/flynn/json5"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/tcell"
@@ -386,107 +386,3 @@ var keyEvents = map[string]tcell.Key{
"PgUp": tcell.KeyPgUp,
"PgDown": tcell.KeyPgDn,
}
// DefaultBindings returns a map containing micro's default keybindings
func DefaultBindings() map[string]string {
return map[string]string{
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfLine",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfLine",
"ShiftHome": "SelectToStartOfLine",
"CtrlShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "OutdentSelection|OutdentLine",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
"CtrlN": "FindNext",
"CtrlP": "FindPrevious",
"CtrlZ": "Undo",
"CtrlY": "Redo",
"CtrlC": "Copy",
"CtrlX": "Cut",
"CtrlK": "CutLine",
"CtrlD": "DuplicateLine",
"CtrlV": "Paste",
"CtrlA": "SelectAll",
"CtrlT": "AddTab",
"Alt,": "PreviousTab",
"Alt.": "NextTab",
"Home": "StartOfLine",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"CtrlR": "ToggleRuler",
"CtrlL": "command-edit:goto ",
"Delete": "Delete",
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfLine",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Integration with file managers
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
}

View File

@@ -91,7 +91,14 @@ func BufMapKey(k Event, action string) {
screen.TermMessage("Lua Error:", a, "does not exist")
continue
}
names = append(names, "")
split := strings.SplitN(a, ".", 2)
if len(split) > 1 {
a = strings.Title(split[0]) + strings.Title(split[1])
} else {
a = strings.Title(a)
}
names = append(names, a)
} else if f, ok := BufKeyActions[a]; ok {
afn = f
names = append(names, a)
@@ -175,15 +182,17 @@ type BufPane struct {
multiWord bool
splitID uint64
tab *Tab
// remember original location of a search in case the search is canceled
searchOrig buffer.Loc
}
func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
h := new(BufPane)
h.Buf = buf
h.BWindow = win
h.tab = tab
h.Cursor = h.Buf.GetActiveCursor()
h.mouseReleased = true
@@ -193,9 +202,23 @@ func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
return h
}
func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
w := display.NewBufWindow(0, 0, 0, 0, buf)
return NewBufPane(buf, w)
return NewBufPane(buf, w, tab)
}
func (h *BufPane) SetTab(t *Tab) {
h.tab = t
}
func (h *BufPane) Tab() *Tab {
return h.tab
}
func (h *BufPane) ResizePane(size int) {
n := h.tab.GetNode(h.splitID)
n.ResizeSplit(size)
h.tab.Resize()
}
// PluginCB calls all plugin callbacks with a certain name and
@@ -355,7 +378,7 @@ func (h *BufPane) DoKeyEvent(e Event) bool {
}
func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
if name != "Autocomplete" {
if name != "Autocomplete" && name != "CycleAutocompleteBack" {
h.Buf.HasSuggestions = false
}
@@ -433,22 +456,29 @@ func (h *BufPane) DoRuneInsert(r rune) {
}
}
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
e := NewBufPaneFromBuf(buf)
e.splitID = MainTab().GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
e := NewBufPaneFromBuf(buf, h.tab)
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
MainTab().Panes = append(MainTab().Panes, e)
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
return e
}
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
e := NewBufPaneFromBuf(buf)
e.splitID = MainTab().GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
e := NewBufPaneFromBuf(buf, h.tab)
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
MainTab().Panes = append(MainTab().Panes, e)
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
return e
}
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
}
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
}
func (h *BufPane) Close() {
h.Buf.Close()
}
@@ -497,6 +527,7 @@ var BufKeyActions = map[string]BufKeyAction{
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
"SelectLine": (*BufPane).SelectLine,
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
"ParagraphNext": (*BufPane).ParagraphNext,
@@ -523,6 +554,7 @@ var BufKeyActions = map[string]BufKeyAction{
"IndentSelection": (*BufPane).IndentSelection,
"OutdentSelection": (*BufPane).OutdentSelection,
"Autocomplete": (*BufPane).Autocomplete,
"CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
"OutdentLine": (*BufPane).OutdentLine,
"Paste": (*BufPane).Paste,
"PastePrimary": (*BufPane).PastePrimary,
@@ -536,6 +568,7 @@ var BufKeyActions = map[string]BufKeyAction{
"SelectPageDown": (*BufPane).SelectPageDown,
"HalfPageUp": (*BufPane).HalfPageUp,
"HalfPageDown": (*BufPane).HalfPageDown,
"StartOfText": (*BufPane).StartOfText,
"StartOfLine": (*BufPane).StartOfLine,
"EndOfLine": (*BufPane).EndOfLine,
"ToggleHelp": (*BufPane).ToggleHelp,
@@ -562,6 +595,8 @@ var BufKeyActions = map[string]BufKeyAction{
"ScrollUp": (*BufPane).ScrollUpAction,
"ScrollDown": (*BufPane).ScrollDownAction,
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
"SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
"SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
@@ -606,6 +641,7 @@ var MultiActions = map[string]bool{
"DeleteWordLeft": true,
"SelectLine": true,
"SelectToStartOfLine": true,
"SelectToStartOfText": true,
"SelectToEndOfLine": true,
"ParagraphPrevious": true,
"ParagraphNext": true,
@@ -629,6 +665,7 @@ var MultiActions = map[string]bool{
"SelectPageUp": true,
"SelectPageDown": true,
"StartOfLine": true,
"StartOfText": true,
"EndOfLine": true,
"JumpToMatchingBrace": true,
}

View File

@@ -12,16 +12,12 @@ import (
"strings"
"unicode/utf8"
luar "layeh.com/gopher-luar"
lua "github.com/yuin/gopher-lua"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/pkg/shellwords"
)
// A Command contains information about how to execute a command
@@ -71,29 +67,9 @@ func InitCommands() {
// MakeCommand is a function to easily create new commands
// This can be called by plugins in Lua so that plugins can define their own commands
func LuaMakeCommand(name, function string, completer buffer.Completer) {
action := LuaFunctionCommand(function)
commands[name] = Command{action, completer}
}
// LuaFunctionCommand returns a normal function
// so that a command can be bound to a lua function
func LuaFunctionCommand(fn string) func(*BufPane, []string) {
luaFn := strings.Split(fn, ".")
if len(luaFn) <= 1 {
return nil
}
plName, plFn := luaFn[0], luaFn[1]
pl := config.FindPlugin(plName)
if pl == nil {
return nil
}
return func(bp *BufPane, args []string) {
luaArgs := []lua.LValue{luar.New(ulua.L, bp), luar.New(ulua.L, args)}
_, err := pl.Call(plFn, luaArgs...)
if err != nil {
screen.TermMessage(err)
}
func MakeCommand(name string, action func(bp *BufPane, args []string), completer buffer.Completer) {
if action != nil {
commands[name] = Command{action, completer}
}
}
@@ -120,106 +96,20 @@ func CommandAction(cmd string) BufKeyAction {
}
}
var PluginCmds = []string{"list", "info", "version"}
var PluginCmds = []string{"install", "remove", "update", "available", "list", "search"}
// PluginCmd installs, removes, updates, lists, or searches for given plugins
func (h *BufPane) PluginCmd(args []string) {
if len(args) <= 0 {
InfoBar.Error("Not enough arguments, see 'help commands'")
if len(args) < 1 {
InfoBar.Error("Not enough arguments")
return
}
valid := true
switch args[0] {
case "list":
for _, pl := range config.Plugins {
var en string
if pl.IsEnabled() {
en = "enabled"
} else {
en = "disabled"
}
WriteLog(fmt.Sprintf("%s: %s", pl.Name, en))
if pl.Default {
WriteLog(" (default)\n")
} else {
WriteLog("\n")
}
}
WriteLog("Default plugins come pre-installed with micro.")
case "version":
if len(args) <= 1 {
InfoBar.Error("No plugin provided to give info for")
return
}
found := false
for _, pl := range config.Plugins {
if pl.Name == args[1] {
found = true
if pl.Info == nil {
InfoBar.Message("Sorry no version for", pl.Name)
return
}
WriteLog("Version: " + pl.Info.Vstr + "\n")
}
}
if !found {
InfoBar.Message(args[1], "is not installed")
}
case "info":
if len(args) <= 1 {
InfoBar.Error("No plugin provided to give info for")
return
}
found := false
for _, pl := range config.Plugins {
if pl.Name == args[1] {
found = true
if pl.Info == nil {
InfoBar.Message("Sorry no info for ", pl.Name)
return
}
var buffer bytes.Buffer
buffer.WriteString("Name: ")
buffer.WriteString(pl.Info.Name)
buffer.WriteString("\n")
buffer.WriteString("Description: ")
buffer.WriteString(pl.Info.Desc)
buffer.WriteString("\n")
buffer.WriteString("Website: ")
buffer.WriteString(pl.Info.Site)
buffer.WriteString("\n")
buffer.WriteString("Installation link: ")
buffer.WriteString(pl.Info.Install)
buffer.WriteString("\n")
buffer.WriteString("Version: ")
buffer.WriteString(pl.Info.Vstr)
buffer.WriteString("\n")
buffer.WriteString("Requirements:")
buffer.WriteString("\n")
for _, r := range pl.Info.Require {
buffer.WriteString(" - ")
buffer.WriteString(r)
buffer.WriteString("\n")
}
WriteLog(buffer.String())
}
}
if !found {
InfoBar.Message(args[1], "is not installed")
return
}
default:
InfoBar.Error("Not a valid plugin command")
return
}
if valid && h.Buf.Type != buffer.BTLog {
if h.Buf.Type != buffer.BTLog {
OpenLogBuf(h)
}
config.PluginCommand(buffer.LogBuf, args[0], args[1:])
}
// RetabCmd changes all spaces to tabs or all tabs to spaces
@@ -233,7 +123,7 @@ func (h *BufPane) RetabCmd(args []string) {
func (h *BufPane) RawCmd(args []string) {
width, height := screen.Screen.Size()
iOffset := config.GetInfoBarOffset()
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane())
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane(nil))
Tabs.AddTab(tp)
Tabs.SetActive(len(Tabs.List) - 1)
}
@@ -344,11 +234,14 @@ func (h *BufPane) OpenCmd(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)
args, err := shellquote.Split(filename)
if err != nil {
InfoBar.Error("Error parsing args ", err)
return
}
if len(args) == 0 {
return
}
filename = strings.Join(args, " ")
open := func() {
@@ -497,6 +390,7 @@ func (h *BufPane) HSplitCmd(args []string) {
// EvalCmd evaluates a lua expression
func (h *BufPane) EvalCmd(args []string) {
InfoBar.Error("Eval unsupported")
}
// NewTabCmd opens the given file in a new tab
@@ -523,37 +417,55 @@ func (h *BufPane) NewTabCmd(args []string) {
}
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
config.GlobalSettings[option] = nativeValue
local := false
for _, s := range config.LocalSettings {
if s == option {
local = true
break
}
}
if option == "colorscheme" {
// LoadSyntaxFiles()
config.InitColorscheme()
for _, b := range buffer.OpenBuffers {
b.UpdateRules()
}
} else if option == "infobar" || option == "keymenu" {
Tabs.Resize()
} else if option == "mouse" {
if !nativeValue.(bool) {
screen.Screen.DisableMouse()
if !local {
config.GlobalSettings[option] = nativeValue
if option == "colorscheme" {
// LoadSyntaxFiles()
config.InitColorscheme()
for _, b := range buffer.OpenBuffers {
b.UpdateRules()
}
} else if option == "infobar" || option == "keymenu" {
Tabs.Resize()
} else if option == "mouse" {
if !nativeValue.(bool) {
screen.Screen.DisableMouse()
} else {
screen.Screen.EnableMouse()
}
} else if option == "autosave" {
if nativeValue.(float64) > 0 {
config.SetAutoTime(int(nativeValue.(float64)))
config.StartAutoSave()
} else {
config.SetAutoTime(0)
}
} else if option == "paste" {
screen.Screen.SetPaste(nativeValue.(bool))
} else {
screen.Screen.EnableMouse()
}
// } else if option == "autosave" {
// if nativeValue.(float64) > 0 {
// config.SetAutoTime(int(nativeValue.(float64)))
// config.StartAutoSave()
// } else {
// config.SetAutoTime(0)
// }
} else {
for _, pl := range config.Plugins {
if option == pl.Name {
if nativeValue.(bool) && !pl.Loaded {
pl.Load()
pl.Call("init")
} else if !nativeValue.(bool) && pl.Loaded {
pl.Call("deinit")
for _, pl := range config.Plugins {
if option == pl.Name {
if nativeValue.(bool) && !pl.Loaded {
pl.Load()
_, err := pl.Call("init")
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
} else if !nativeValue.(bool) && pl.Loaded {
_, err := pl.Call("deinit")
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}
}
}
@@ -703,7 +615,7 @@ func (h *BufPane) UnbindCmd(args []string) {
// RunCmd runs a shell command in the background
func (h *BufPane) RunCmd(args []string) {
runf, err := shell.RunBackgroundShell(shellwords.Join(args...))
runf, err := shell.RunBackgroundShell(shellquote.Join(args...))
if err != nil {
InfoBar.Error(err)
} else {
@@ -893,7 +805,7 @@ func (h *BufPane) ReplaceAllCmd(args []string) {
// TermCmd opens a terminal in the current view
func (h *BufPane) TermCmd(args []string) {
ps := MainTab().Panes
ps := h.tab.Panes
if len(args) == 0 {
sh := os.Getenv("SHELL")
@@ -906,7 +818,7 @@ func (h *BufPane) TermCmd(args []string) {
term := func(i int, newtab bool) {
t := new(shell.Terminal)
t.Start(args, false, true, "", nil)
t.Start(args, false, true, nil, nil)
id := h.ID()
if newtab {
@@ -918,7 +830,7 @@ func (h *BufPane) TermCmd(args []string) {
}
v := h.GetView()
MainTab().Panes[i] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id)
MainTab().Panes[i] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
MainTab().SetActive(i)
}
@@ -950,12 +862,16 @@ func (h *BufPane) TermCmd(args []string) {
// HandleCommand handles input from the user
func (h *BufPane) HandleCommand(input string) {
args, err := shellwords.Split(input)
args, err := shellquote.Split(input)
if err != nil {
InfoBar.Error("Error parsing args ", err)
return
}
if len(args) == 0 {
return
}
inputCmd := args[0]
if _, ok := commands[inputCmd]; !ok {

View File

@@ -0,0 +1,107 @@
package action
// DefaultBindings returns a map containing micro's default keybindings
func DefaultBindings() map[string]string {
return map[string]string{
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfText",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfText",
"ShiftHome": "SelectToStartOfText",
"CtrlShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
"CtrlN": "FindNext",
"CtrlP": "FindPrevious",
"CtrlZ": "Undo",
"CtrlY": "Redo",
"CtrlC": "Copy",
"CtrlX": "Cut",
"CtrlK": "CutLine",
"CtrlD": "DuplicateLine",
"CtrlV": "Paste",
"CtrlA": "SelectAll",
"CtrlT": "AddTab",
"Alt,": "PreviousTab",
"Alt.": "NextTab",
"Home": "StartOfText",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"CtrlR": "ToggleRuler",
"CtrlL": "command-edit:goto ",
"Delete": "Delete",
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfText",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Integration with file managers
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"AltShiftUp": "SpawnMultiCursorUp",
"AltShiftDown": "SpawnMultiCursorDown",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
}

View File

@@ -0,0 +1,109 @@
// +build !darwin
package action
// DefaultBindings returns a map containing micro's default keybindings
func DefaultBindings() map[string]string {
return map[string]string{
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"CtrlLeft": "WordLeft",
"CtrlRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"CtrlShiftRight": "SelectWordRight",
"CtrlShiftLeft": "SelectWordLeft",
"AltLeft": "StartOfText",
"AltRight": "EndOfLine",
"AltShiftLeft": "SelectToStartOfText",
"ShiftHome": "SelectToStartOfText",
"AltShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
"CtrlN": "FindNext",
"CtrlP": "FindPrevious",
"CtrlZ": "Undo",
"CtrlY": "Redo",
"CtrlC": "Copy",
"CtrlX": "Cut",
"CtrlK": "CutLine",
"CtrlD": "DuplicateLine",
"CtrlV": "Paste",
"CtrlA": "SelectAll",
"CtrlT": "AddTab",
"Alt,": "PreviousTab",
"Alt.": "NextTab",
"Home": "StartOfText",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"CtrlR": "ToggleRuler",
"CtrlL": "command-edit:goto ",
"Delete": "Delete",
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfText",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Integration with file managers
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
"AltShiftUp": "SpawnMultiCursorUp",
"AltShiftDown": "SpawnMultiCursorDown",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
}

View File

@@ -197,7 +197,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
return completions, suggestions
}
// OptionComplete autocompletes options
// PluginCmdComplete autocompletes the plugin command
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := buffer.GetArg(b)
@@ -253,51 +253,28 @@ func PluginComplete(b *buffer.Buffer) ([]string, []string) {
return completions, suggestions
}
// // MakeCompletion registers a function from a plugin for autocomplete commands
// func MakeCompletion(function string) Completion {
// pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
// return Completion(-len(pluginCompletions))
// }
// PluginNameComplete completes with the names of loaded plugins
// func PluginNameComplete(b *buffer.Buffer) ([]string, []string) {
// c := b.GetActiveCursor()
// input, argstart := buffer.GetArg(b)
//
// // PluginComplete autocompletes from plugin function
// func PluginComplete(complete Completion, input string) (chosen string, suggestions []string) {
// idx := int(-complete) - 1
//
// if len(pluginCompletions) <= idx {
// return "", nil
// }
// suggestions = pluginCompletions[idx](input)
//
// if len(suggestions) == 1 {
// chosen = suggestions[0]
// }
// 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) {
// suggestions = append(suggestions, cmd)
// }
// }
//
// if len(suggestions) == 1 {
// chosen = suggestions[0]
// }
// return chosen, suggestions
// }
//
// // PluginnameComplete completes with the names of loaded plugins
// func PluginNameComplete(input string) (chosen string, suggestions []string) {
// for _, pp := range GetAllPluginPackages() {
// var suggestions []string
// for _, pp := range config.GetAllPluginPackages(nil) {
// if strings.HasPrefix(pp.Name, input) {
// suggestions = append(suggestions, pp.Name)
// }
// }
//
// if len(suggestions) == 1 {
// chosen = suggestions[0]
// sort.Strings(suggestions)
// completions := make([]string, len(suggestions))
// for i := range suggestions {
// completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
// }
// return chosen, suggestions
// return completions, suggestions
// }
// // MakeCompletion registers a function from a plugin for autocomplete commands
// func MakeCompletion(function string) Completion {
// pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
// return Completion(-len(pluginCompletions))
// }

View File

@@ -17,10 +17,10 @@ type InfoPane struct {
*info.InfoBuf
}
func NewInfoPane(ib *info.InfoBuf, w display.BWindow) *InfoPane {
func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
ip := new(InfoPane)
ip.InfoBuf = ib
ip.BufPane = NewBufPane(ib.Buffer, w)
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
return ip
}
@@ -28,7 +28,7 @@ func NewInfoPane(ib *info.InfoBuf, w display.BWindow) *InfoPane {
func NewInfoBar() *InfoPane {
ib := info.NewBuffer()
w := display.NewInfoWindow(ib)
return NewInfoPane(ib, w)
return NewInfoPane(ib, w, nil)
}
func (h *InfoPane) Close() {
@@ -73,6 +73,7 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
}
}
// DoKeyEvent executes a key event for the command bar, doing any overridden actions
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
done := false
if action, ok := BufKeyBindings[e]; ok {
@@ -85,7 +86,7 @@ func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
for s, a := range InfoOverrides {
// TODO this is a hack and really we should have support
// for having binding overrides for different buffers
if strings.Contains(estr, s) {
if strings.HasPrefix(estr, s) {
done = true
a(h)
break
@@ -154,7 +155,6 @@ var InfoOverrides = map[string]InfoKeyAction{
"CursorDown": (*InfoPane).CursorDown,
"InsertNewline": (*InfoPane).InsertNewline,
"Autocomplete": (*InfoPane).Autocomplete,
"OutdentLine": (*InfoPane).CycleBack,
"Escape": (*InfoPane).Escape,
"Quit": (*InfoPane).Quit,
"QuitAll": (*InfoPane).QuitAll,
@@ -196,13 +196,6 @@ func (h *InfoPane) Autocomplete() {
}
}
// CycleBack cycles back in the autocomplete suggestion list
func (h *InfoPane) CycleBack() {
if h.Buf.HasSuggestions {
h.Buf.CycleAutocomplete(false)
}
}
// InsertNewline completes the prompt
func (h *InfoPane) InsertNewline() {
if !h.HasYN {

View File

@@ -11,4 +11,6 @@ type Pane interface {
SetID(i uint64)
Name() string
Close()
SetTab(t *Tab)
Tab() *Tab
}

View File

@@ -13,17 +13,17 @@ type RawPane struct {
*BufPane
}
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow) *RawPane {
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane {
rh := new(RawPane)
rh.BufPane = NewBufPane(b, win)
rh.BufPane = NewBufPane(b, win, tab)
return rh
}
func NewRawPane() *RawPane {
func NewRawPane(tab *Tab) *RawPane {
b := buffer.NewBufferFromString("", "", buffer.BTRaw)
w := display.NewBufWindow(0, 0, 0, 0, b)
return NewRawPaneFromWin(b, w)
return NewRawPaneFromWin(b, w, tab)
}
func (h *RawPane) HandleEvent(event tcell.Event) {

View File

@@ -91,6 +91,7 @@ func (t *TabList) Resize() {
t.List[0].Node.Resize(w, h-iOffset)
t.List[0].Resize()
}
t.TabWindow.Resize(w, h)
}
// HandleEvent checks for a resize event or a mouse event on the tab bar
@@ -166,7 +167,7 @@ func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
t.Node = views.NewRoot(x, y, width, height)
t.UIWindow = display.NewUIWindow(t.Node)
e := NewBufPaneFromBuf(b)
e := NewBufPaneFromBuf(b, t)
e.SetID(t.ID())
t.Panes = append(t.Panes, e)
@@ -177,7 +178,7 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
t := new(Tab)
t.Node = views.NewRoot(x, y, width, height)
t.UIWindow = display.NewUIWindow(t.Node)
pane.SetTab(t)
pane.SetID(t.ID())
t.Panes = append(t.Panes, pane)
@@ -195,7 +196,6 @@ func (t *Tab) HandleEvent(event tcell.Event) {
mx, my := e.Position()
switch e.Buttons() {
case tcell.Button1:
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
if t.resizing != nil {
var size int
if t.resizing.Kind == views.STVert {
@@ -208,6 +208,7 @@ func (t *Tab) HandleEvent(event tcell.Event) {
return
}
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
if resizeID != 0 {
t.resizing = t.GetNode(uint64(resizeID))
return
@@ -283,6 +284,10 @@ func (t *Tab) Resize() {
}
// CurPane returns the currently active pane
func (t *Tab) CurPane() Pane {
return t.Panes[t.active]
func (t *Tab) CurPane() *BufPane {
p, ok := t.Panes[t.active].(*BufPane)
if !ok {
return nil
}
return p
}

View File

@@ -3,17 +3,25 @@
package action
import (
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/pkg/shellwords"
)
// TermEmuSupported is a constant that marks if the terminal emulator is supported
const TermEmuSupported = true
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback string, userargs []interface{}) error {
args, err := shellwords.Split(input)
// RunTermEmulator starts a terminal emulator from a bufpane with the given input (command)
// if wait is true it will wait for the user to exit by pressing enter once the executable has terminated
// if getOutput is true it will redirect the stdout of the process to a pipe which will be passed to the
// callback which is a function that takes a string and a list of optional user arguments
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
args, err := shellquote.Split(input)
if err != nil {
return err
}
if len(args) == 0 {
return nil
}
t := new(shell.Terminal)
t.Start(args, getOutput, wait, callback, userargs)
@@ -22,7 +30,7 @@ func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callba
id := MainTab().Panes[0].ID()
v := h.GetView()
MainTab().Panes[0] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id)
MainTab().Panes[0] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
MainTab().SetActive(0)
return nil

View File

@@ -4,8 +4,10 @@ package action
import "errors"
// TermEmuSupported is a constant that marks if the terminal emulator is supported
const TermEmuSupported = false
func RunTermEmulator(input string, wait bool, getOutput bool, callback string, userargs []interface{}) error {
// RunTermEmulator returns an error for unsupported systems (non-unix systems
func RunTermEmulator(input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
return errors.New("Unsupported operating system")
}

View File

@@ -17,14 +17,16 @@ type TermPane struct {
mouseReleased bool
id uint64
tab *Tab
}
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64) *TermPane {
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64, tab *Tab) *TermPane {
th := new(TermPane)
th.Terminal = t
th.id = id
th.mouseReleased = true
th.Window = display.NewTermWindow(x, y, w, h, t)
th.tab = tab
return th
}
@@ -36,6 +38,14 @@ func (t *TermPane) SetID(i uint64) {
t.id = i
}
func (t *TermPane) SetTab(tab *Tab) {
t.tab = tab
}
func (t *TermPane) Tab() *Tab {
return t.tab
}
func (t *TermPane) Close() {}
func (t *TermPane) Quit() {
@@ -80,8 +90,12 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
} else if t.Status != shell.TTDone {
t.WriteString(event.EscSeq())
}
} else if _, ok := event.(*tcell.EventPaste); ok {
if t.Status != shell.TTDone {
t.WriteString(event.EscSeq())
}
} else if e, ok := event.(*tcell.EventMouse); e != nil && (!ok || t.State.Mode(terminal.ModeMouseMask)) {
t.WriteString(event.EscSeq())
// t.WriteString(event.EscSeq())
} else if e != nil {
x, y := e.Position()
v := t.GetView()

View File

@@ -29,7 +29,7 @@ Options: [r]ecover, [i]gnore: `
// Backup saves the current buffer to ConfigDir/backups
func (b *Buffer) Backup(checkTime bool) error {
if !b.Settings["backup"].(bool) || b.Path == "" {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
return nil
}
@@ -71,14 +71,14 @@ func (b *Buffer) Backup(checkTime bool) error {
}
}
return
})
}, false)
return err
}
// RemoveBackup removes any backup file associated with this buffer
func (b *Buffer) RemoveBackup() {
if !b.Settings["backup"].(bool) || b.Path == "" {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
return
}
f := config.ConfigDir + "/backups/" + util.EscapePath(b.AbsPath)
@@ -88,7 +88,7 @@ func (b *Buffer) RemoveBackup() {
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
// Returns true if a backup was applied
func (b *Buffer) ApplyBackup(fsize int64) bool {
if b.Settings["backup"].(bool) && len(b.Path) > 0 {
if b.Settings["backup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
backupfile := config.ConfigDir + "/backups/" + util.EscapePath(b.AbsPath)
if info, err := os.Stat(backupfile); err == nil {
backup, err := os.Open(backupfile)

View File

@@ -6,10 +6,12 @@ import (
"errors"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
@@ -28,8 +30,11 @@ import (
const backupTime = 8000
var (
// OpenBuffers is a list of the currently open buffers
OpenBuffers []*Buffer
LogBuf *Buffer
// LogBuf is a reference to the log buffer which can be opened with the
// `> log` command
LogBuf *Buffer
)
// The BufType defines what kind of buffer this is
@@ -41,16 +46,26 @@ type BufType struct {
}
var (
// BTDefault is a default buffer
BTDefault = BufType{0, false, false, true}
BTHelp = BufType{1, true, true, true}
BTLog = BufType{2, true, true, false}
// BTHelp is a help buffer
BTHelp = BufType{1, true, true, true}
// BTLog is a log buffer
BTLog = BufType{2, true, true, false}
// BTScratch is a buffer that cannot be saved (for scratch work)
BTScratch = BufType{3, false, true, false}
BTRaw = BufType{4, false, true, false}
BTInfo = BufType{5, false, true, false}
// BTRaw is is a buffer that shows raw terminal events
BTRaw = BufType{4, false, true, false}
// BTInfo is a buffer for inputting information
BTInfo = BufType{5, false, true, false}
// ErrFileTooLarge is returned when the file is too large to hash
// (fastdirty is automatically enabled)
ErrFileTooLarge = errors.New("File is too large to hash")
)
// SharedBuffer is a struct containing info that is shared among buffers
// that have the same file open
type SharedBuffer struct {
*LineArray
// Stores the last modification time of the file the buffer is pointing to
@@ -62,16 +77,24 @@ type SharedBuffer struct {
// Whether or not suggestions can be autocompleted must be shared because
// it changes based on how the buffer has changed
HasSuggestions bool
// Modifications is the list of modified regions for syntax highlighting
Modifications []Loc
}
func (b *SharedBuffer) insert(pos Loc, value []byte) {
b.isModified = true
b.HasSuggestions = false
b.LineArray.insert(pos, value)
// b.Modifications is cleared every screen redraw so it's
// ok to append duplicates
b.Modifications = append(b.Modifications, Loc{pos.Y, pos.Y + bytes.Count(value, []byte{'\n'})})
}
func (b *SharedBuffer) remove(start, end Loc) []byte {
b.isModified = true
b.HasSuggestions = false
b.Modifications = append(b.Modifications, Loc{start.Y, start.Y})
return b.LineArray.remove(start, end)
}
@@ -97,8 +120,12 @@ type Buffer struct {
// Name of the buffer on the status line
name string
SyntaxDef *highlight.Def
Highlighter *highlight.Highlighter
// SyntaxDef represents the syntax highlighting definition being used
// This stores the highlighting rules and filetype detection info
SyntaxDef *highlight.Def
// The Highlighter struct actually performs the highlighting
Highlighter *highlight.Highlighter
HighlightLock sync.Mutex
// Hash of the original buffer -- empty if fastdirty is on
origHash [md5.Size]byte
@@ -133,7 +160,7 @@ func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
fileInfo, _ := os.Stat(filename)
if err == nil && fileInfo.IsDir() {
return nil, errors.New(filename + " is a directory")
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
}
defer file.Close()
@@ -211,7 +238,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
b.EventHandler = NewEventHandler(b.SharedBuffer, b.cursors)
}
if b.Settings["readonly"].(bool) {
if b.Settings["readonly"].(bool) && b.Type == BTDefault {
b.Type.Readonly = true
}
@@ -260,6 +287,8 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
screen.TermMessage(err)
}
b.Modifications = make([]Loc, 0, 10)
OpenBuffers = append(OpenBuffers, b)
return b
@@ -304,6 +333,7 @@ func (b *Buffer) SetName(s string) {
b.name = s
}
// Insert inserts the given string of text at the start location
func (b *Buffer) Insert(start Loc, text string) {
if !b.Type.Readonly {
b.EventHandler.cursors = b.cursors
@@ -314,6 +344,7 @@ func (b *Buffer) Insert(start Loc, text string) {
}
}
// Remove removes the characters between the start and end locations
func (b *Buffer) Remove(start, end Loc) {
if !b.Type.Readonly {
b.EventHandler.cursors = b.cursors
@@ -324,6 +355,16 @@ func (b *Buffer) Remove(start, end Loc) {
}
}
// ClearModifications clears the list of modified lines in this buffer
// The list of modified lines is used for syntax highlighting so that
// we can selectively highlight only the necessary lines
// This function should be called every time this buffer is drawn to
// the screen
func (b *Buffer) ClearModifications() {
// clear slice without resetting the cap
b.Modifications = b.Modifications[:0]
}
// FileType returns the buffer's filetype
func (b *Buffer) FileType() string {
return b.Settings["filetype"].(string)
@@ -372,6 +413,7 @@ func (b *Buffer) ReOpen() error {
return err
}
// RelocateCursors relocates all cursors (makes sure they are in the buffer)
func (b *Buffer) RelocateCursors() {
for _, c := range b.cursors {
c.Relocate()
@@ -453,8 +495,11 @@ func (b *Buffer) UpdateRules() {
if !b.Type.Syntax {
return
}
syntaxFile := ""
ft := b.Settings["filetype"].(string)
if ft == "off" {
return
}
syntaxFile := ""
var header *highlight.Header
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
data, err := f.Data()
@@ -483,6 +528,7 @@ func (b *Buffer) UpdateRules() {
if syntaxFile == "" {
// search for the syntax file in the user's custom syntax files
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
log.Println("real runtime file", f.Name())
data, err := f.Data()
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
@@ -496,7 +542,7 @@ func (b *Buffer) UpdateRules() {
continue
}
if (ft == "unknown" || ft == "" && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
if ((ft == "unknown" || ft == "") && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
syndef, err := highlight.ParseDef(file, header)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
@@ -570,10 +616,19 @@ func (b *Buffer) UpdateRules() {
if b.Highlighter == nil || syntaxFile != "" {
if b.SyntaxDef != nil {
b.Settings["filetype"] = b.SyntaxDef.FileType
b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
if b.Settings["syntax"].(bool) {
}
} else {
b.SyntaxDef = &highlight.EmptyDef
}
if b.SyntaxDef != nil {
b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
if b.Settings["syntax"].(bool) {
go func() {
b.Highlighter.HighlightStates(b)
}
b.Highlighter.HighlightMatches(b, 0, b.End().Y)
screen.DrawChan <- true
}()
}
}
}
@@ -854,12 +909,12 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
}
startpos.Y, err = strconv.Atoi(cursorPositions[0])
startpos.Y -= 1
startpos.Y--
if err == nil {
if len(cursorPositions) > 1 {
startpos.X, err = strconv.Atoi(cursorPositions[1])
if startpos.X > 0 {
startpos.X -= 1
startpos.X--
}
}
}
@@ -872,7 +927,17 @@ func (b *Buffer) Line(i int) string {
return string(b.LineBytes(i))
}
func (b *Buffer) Write(bytes []byte) (n int, err error) {
b.EventHandler.InsertBytes(b.End(), bytes)
return len(bytes), nil
}
// WriteLog writes a string to the log buffer
func WriteLog(s string) {
LogBuf.EventHandler.Insert(LogBuf.End(), s)
}
// GetLogBuf returns the log buffer
func GetLogBuf() *Buffer {
return LogBuf
}

View File

@@ -1,10 +1,16 @@
package buffer
import (
"bytes"
"log"
"time"
"unicode/utf8"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
luar "layeh.com/gopher-luar"
)
const (
@@ -17,7 +23,7 @@ const (
// TextEventReplace represents a replace event
TextEventReplace = 0
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
undoThreshold = 1000 // If two events are less than n milliseconds apart, undo both of them
)
// TextEvent holds data for a manipulation on some text that can be undone
@@ -107,26 +113,49 @@ func (eh *EventHandler) ApplyDiff(new string) {
// Insert creates an insert text event and executes it
func (eh *EventHandler) Insert(start Loc, textStr string) {
text := []byte(textStr)
eh.InsertBytes(start, text)
}
// InsertBytes creates an insert text event and executes it
func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
e := &TextEvent{
C: *eh.cursors[eh.active],
EventType: TextEventInsert,
Deltas: []Delta{{text, start, Loc{0, 0}}},
Time: time.Now(),
}
// oldl := eh.buf.LinesNum()
eh.Execute(e)
e.Deltas[0].End = start.MoveLA(utf8.RuneCount(text), eh.buf.LineArray)
// linecount := eh.buf.LinesNum() - oldl
textcount := utf8.RuneCount(text)
lastnl := bytes.LastIndex(text, []byte{'\n'})
var endX int
var textX int
if lastnl >= 0 {
endX = utf8.RuneCount(text[lastnl:])
textX = endX
} else {
// endX = start.X + textcount
textX = textcount
}
e.Deltas[0].End = start.MoveLA(textcount, eh.buf.LineArray)
// e.Deltas[0].End = clamp(Loc{endX, start.Y + linecount}, eh.buf.LineArray)
end := e.Deltas[0].End
for _, c := range eh.cursors {
move := func(loc Loc) Loc {
log.Println("move", loc)
if start.Y != end.Y && loc.GreaterThan(start) {
loc.Y += end.Y - start.Y
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
loc = loc.MoveLA(utf8.RuneCount(text), eh.buf.LineArray)
loc.Y += end.Y - start.Y
loc.X += textX
}
return loc
}
c.Loc = move(c.Loc)
c.Relocate()
c.CurSelection[0] = move(c.CurSelection[0])
c.CurSelection[1] = move(c.CurSelection[1])
c.OrigSelection[0] = move(c.OrigSelection[0])
@@ -187,16 +216,14 @@ func (eh *EventHandler) Execute(t *TextEvent) {
}
eh.UndoStack.Push(t)
// TODO: Call plugins on text events
// for pl := range loadedPlugins {
// ret, err := Call(pl+".onBeforeTextEvent", t)
// if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
// screen.TermMessage(err)
// }
// if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
// return
// }
// }
b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
if err != nil {
screen.TermMessage(err)
}
if !b {
return
}
ExecuteTextEvent(t, eh.buf)
}
@@ -209,8 +236,7 @@ func (eh *EventHandler) Undo() {
}
startTime := t.Time.UnixNano() / int64(time.Millisecond)
eh.UndoOneEvent()
endTime := startTime - (startTime % undoThreshold)
for {
t = eh.UndoStack.Peek()
@@ -218,10 +244,9 @@ func (eh *EventHandler) Undo() {
return
}
if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
return
}
startTime = t.Time.UnixNano() / int64(time.Millisecond)
eh.UndoOneEvent()
}
@@ -261,8 +286,7 @@ func (eh *EventHandler) Redo() {
}
startTime := t.Time.UnixNano() / int64(time.Millisecond)
eh.RedoOneEvent()
endTime := startTime - (startTime % undoThreshold) + undoThreshold
for {
t = eh.RedoStack.Peek()
@@ -270,7 +294,7 @@ func (eh *EventHandler) Redo() {
return
}
if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
return
}

View File

@@ -3,6 +3,7 @@ package buffer
import (
"bufio"
"io"
"sync"
"unicode/utf8"
"github.com/zyedidia/micro/pkg/highlight"
@@ -38,6 +39,7 @@ type Line struct {
state highlight.State
match highlight.LineMatch
rehighlight bool
lock sync.Mutex
}
const (
@@ -124,12 +126,22 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
if err != nil {
if err == io.EOF {
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
la.lines = Append(la.lines, Line{
data: data[:],
state: nil,
match: nil,
rehighlight: false,
})
}
// Last line was read
break
} else {
la.lines = Append(la.lines, Line{data[:dlen-1], nil, nil, false})
la.lines = Append(la.lines, Line{
data: data[:dlen-1],
state: nil,
match: nil,
rehighlight: false,
})
}
n++
}
@@ -155,9 +167,19 @@ func (la *LineArray) Bytes() []byte {
// newlineBelow adds a newline below the given line number
func (la *LineArray) newlineBelow(y int) {
la.lines = append(la.lines, Line{[]byte{' '}, nil, nil, false})
la.lines = append(la.lines, Line{
data: []byte{' '},
state: nil,
match: nil,
rehighlight: 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{
data: []byte{},
state: la.lines[y].state,
match: nil,
rehighlight: false,
}
}
// Inserts a byte array at a given location
@@ -285,28 +307,40 @@ func (la *LineArray) LineBytes(n int) []byte {
// State gets the highlight state for the given line number
func (la *LineArray) State(lineN int) highlight.State {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
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].lock.Lock()
defer la.lines[lineN].lock.Unlock()
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].lock.Lock()
defer la.lines[lineN].lock.Unlock()
la.lines[lineN].match = m
}
// Match retrieves the match for the given line number
func (la *LineArray) Match(lineN int) highlight.LineMatch {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
return la.lines[lineN].match
}
func (la *LineArray) Rehighlight(lineN int) bool {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
return la.lines[lineN].rehighlight
}
func (la *LineArray) SetRehighlight(lineN int, on bool) {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
la.lines[lineN].rehighlight = on
}

View File

@@ -135,3 +135,13 @@ func ByteOffset(pos Loc, buf *Buffer) int {
loc += len(buf.Line(y)[:x])
return loc
}
// clamps a loc within a buffer
func clamp(pos Loc, la *LineArray) Loc {
if pos.GreaterEqual(la.End()) {
return la.End().MoveLA(-1, la)
} else if pos.LessThan(la.Start()) {
return la.Start()
}
return pos
}

View File

@@ -13,13 +13,19 @@ const (
MTError
)
// Message represents the information for a gutter message
type Message struct {
Msg string
// The Msg iteslf
Msg string
// Start and End locations for the message
Start, End Loc
Kind MsgType
Owner string
// The Kind stores the message type
Kind MsgType
// The Owner of the message
Owner string
}
// NewMessage creates a new gutter message
func NewMessage(owner string, msg string, start, end Loc, kind MsgType) *Message {
return &Message{
Msg: msg,
@@ -30,6 +36,7 @@ func NewMessage(owner string, msg string, start, end Loc, kind MsgType) *Message
}
}
// NewMessageAtLine creates a new gutter message at a given line
func NewMessageAtLine(owner string, msg string, line int, kind MsgType) *Message {
start := Loc{-1, line - 1}
end := start

View File

@@ -8,6 +8,7 @@ import (
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"unicode"
"unicode/utf8"
@@ -26,70 +27,42 @@ const LargeFileThreshold = 50000
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
// the supplied function with the file as io.Writer object, also making sure the file is
// closed afterwards.
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error) (err error) {
var file *os.File
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
var writeCloser io.WriteCloser
if file, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return
}
if withSudo {
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
defer func() {
if e := file.Close(); e != nil && err == nil {
err = e
if writeCloser, err = cmd.StdinPipe(); err != nil {
return
}
}()
w := transform.NewWriter(file, enc.NewEncoder())
// w := bufio.NewWriter(file)
if err = fn(w); err != nil {
return
}
// err = w.Flush()
return
}
// overwriteFileAsRoot executes dd as root and then calls the supplied function
// with dd's standard input as an io.Writer object. Dd opens the given file for writing,
// truncating it if it exists, and writes what it receives on its standard input to the file.
func overwriteFileAsRoot(name string, enc encoding.Encoding, fn func(io.Writer) error) (err error) {
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "status=none", "bs=4K", "of="+name)
var stdin io.WriteCloser
screenb := screen.TempFini()
// 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 {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
cmd.Process.Kill()
}
}()
}()
if stdin, err = cmd.StdinPipe(); err != nil {
defer func() {
screenb := screen.TempFini()
if e := cmd.Run(); e != nil && err == nil {
err = e
}
screen.TempStart(screenb)
}()
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return
}
if err = cmd.Start(); err != nil {
return
w := transform.NewWriter(writeCloser, enc.NewEncoder())
err = fn(w)
if e := writeCloser.Close(); e != nil && err == nil {
err = e
}
e := fn(stdin)
if err = stdin.Close(); err != nil {
return
}
if err = cmd.Wait(); err != nil {
return
}
screen.TempStart(screenb)
return e
return
}
// Save saves the buffer to its default path
@@ -118,6 +91,9 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
if b.Type.Scratch {
return errors.New("Cannot save scratch buffer")
}
if withSudo && runtime.GOOS == "windows" {
return errors.New("Save with sudo not supported on Windows")
}
b.UpdateRules()
if b.Settings["rmtrailingws"].(bool) {
@@ -201,13 +177,7 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
return
}
if withSudo {
err = overwriteFileAsRoot(absFilename, enc, fwriter)
} else {
err = overwriteFile(absFilename, enc, fwriter)
}
if err != nil {
if err = overwriteFile(absFilename, enc, fwriter, withSudo); err != nil {
return err
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"io"
"os"
"path/filepath"
"time"
"golang.org/x/text/encoding"
@@ -39,23 +40,24 @@ func (b *Buffer) Serialize() error {
b.ModTime,
})
return err
})
}, false)
}
// Unserialize loads the buffer info from config.ConfigDir/buffers
func (b *Buffer) Unserialize() error {
// If either savecursor or saveundo is turned on, we need to load the serialized information
// from ~/.config/micro/buffers
if b.Path == "" {
return nil
}
file, err := os.Open(config.ConfigDir + "/buffers/" + util.EscapePath(b.AbsPath))
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
defer file.Close()
if err == nil {
var buffer SerializedBuffer
decoder := gob.NewDecoder(file)
err = decoder.Decode(&buffer)
if err != nil {
return errors.New(err.Error() + "\nYou may want to remove the files in ~/.config/micro/buffers (these files store the information for the 'saveundo' and 'savecursor' options) if this problem persists.")
return errors.New(err.Error() + "\nYou may want to remove the files in ~/.config/micro/buffers (these files\nstore the information for the 'saveundo' and 'savecursor' options) if\nthis problem persists.\nThis may be caused by upgrading to version 2.0, and removing the 'buffers'\ndirectory will reset the cursor and undo history and solve the problem.")
}
if b.Settings["savecursor"].(bool) {
b.StartCursor = buffer.Cursor

View File

@@ -35,7 +35,7 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
}
} else if option == "encoding" {
b.isModified = true
} else if option == "readonly" {
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
b.Type.Readonly = nativeValue.(bool)
}

View File

@@ -2,11 +2,13 @@ package config
import (
"errors"
"log"
lua "github.com/yuin/gopher-lua"
ulua "github.com/zyedidia/micro/internal/lua"
)
// ErrNoSuchFunction is returned when Call is executed on a function that does not exist
var ErrNoSuchFunction = errors.New("No such function exists")
// LoadAllPlugins loads all detected plugins (in runtime/plugins and ConfigDir/plugins)
@@ -55,15 +57,14 @@ func RunPluginFnBool(fn string, args ...lua.LValue) (bool, error) {
reterr = errors.New("Plugin " + p.Name + ": " + err.Error())
continue
}
if v, ok := val.(lua.LBool); !ok {
reterr = errors.New(p.Name + "." + fn + " should return a boolean")
} else {
if v, ok := val.(lua.LBool); ok {
retbool = retbool && bool(v)
}
}
return retbool, reterr
}
// Plugin stores information about the source files/info for a plugin
type Plugin struct {
DirName string // name of plugin folder
Name string // name of plugin
@@ -73,6 +74,7 @@ type Plugin struct {
Default bool // pre-installed plugin
}
// IsEnabled returns if a plugin is enabled
func (p *Plugin) IsEnabled() bool {
if v, ok := GlobalSettings[p.Name]; ok {
return v.(bool) && p.Loaded
@@ -80,13 +82,15 @@ func (p *Plugin) IsEnabled() bool {
return true
}
// Plugins is a list of all detected plugins (enabled or disabled)
var Plugins []*Plugin
// Load creates an option for the plugin and runs all source files
func (p *Plugin) Load() error {
if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
return nil
}
for _, f := range p.Srcs {
if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
return nil
}
dat, err := f.Data()
if err != nil {
return err
@@ -95,14 +99,19 @@ func (p *Plugin) Load() error {
if err != nil {
return err
}
p.Loaded = true
RegisterGlobalOption(p.Name, true)
}
p.Loaded = true
RegisterGlobalOption(p.Name, true)
return nil
}
// Call calls a given function in this plugin
func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
plug := ulua.L.GetGlobal(p.Name)
if plug == lua.LNil {
log.Println("Plugin does not exist:", p.Name, "at", p.DirName, ":", p)
return nil, nil
}
luafn := ulua.L.GetField(plug, fn)
if luafn == lua.LNil {
return nil, ErrNoSuchFunction
@@ -120,7 +129,23 @@ func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
return ret, nil
}
// FindPlugin returns the plugin with the given name
func FindPlugin(name string) *Plugin {
var pl *Plugin
for _, p := range Plugins {
if !p.IsEnabled() {
continue
}
if p.Name == name {
pl = p
break
}
}
return pl
}
// FindAnyPlugin does not require the plugin to be enabled
func FindAnyPlugin(name string) *Plugin {
var pl *Plugin
for _, p := range Plugins {
if p.Name == name {

View File

@@ -0,0 +1,712 @@
package config
import (
"archive/zip"
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/blang/semver"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/json5"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/util"
)
var (
allPluginPackages PluginPackages
)
// CorePluginName is a plugin dependency name for the micro core.
const CorePluginName = "micro"
// PluginChannel contains an url to a json list of PluginRepository
type PluginChannel string
// PluginChannels is a slice of PluginChannel
type PluginChannels []PluginChannel
// PluginRepository contains an url to json file containing PluginPackages
type PluginRepository string
// PluginPackage contains the meta-data of a plugin and all available versions
type PluginPackage struct {
Name string
Description string
Author string
Tags []string
Versions PluginVersions
}
// PluginPackages is a list of PluginPackage instances.
type PluginPackages []*PluginPackage
// PluginVersion descripes a version of a PluginPackage. Containing a version, download url and also dependencies.
type PluginVersion struct {
pack *PluginPackage
Version semver.Version
Url string
Require PluginDependencies
}
func (pv *PluginVersion) Pack() *PluginPackage {
return pv.pack
}
// PluginVersions is a slice of PluginVersion
type PluginVersions []*PluginVersion
// PluginDependency descripes a dependency to another plugin or micro itself.
type PluginDependency struct {
Name string
Range semver.Range
}
// PluginDependencies is a slice of PluginDependency
type PluginDependencies []*PluginDependency
func (pp *PluginPackage) String() string {
buf := new(bytes.Buffer)
buf.WriteString("Plugin: ")
buf.WriteString(pp.Name)
buf.WriteRune('\n')
if pp.Author != "" {
buf.WriteString("Author: ")
buf.WriteString(pp.Author)
buf.WriteRune('\n')
}
if pp.Description != "" {
buf.WriteRune('\n')
buf.WriteString(pp.Description)
}
return buf.String()
}
func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackages {
wgQuery := new(sync.WaitGroup)
wgQuery.Add(count)
results := make(chan PluginPackages)
wgDone := new(sync.WaitGroup)
wgDone.Add(1)
var packages PluginPackages
for i := 0; i < count; i++ {
go func(i int) {
results <- fetcher(i)
wgQuery.Done()
}(i)
}
go func() {
packages = make(PluginPackages, 0)
for res := range results {
packages = append(packages, res...)
}
wgDone.Done()
}()
wgQuery.Wait()
close(results)
wgDone.Wait()
return packages
}
// Fetch retrieves all available PluginPackages from the given channels
func (pc PluginChannels) Fetch(out io.Writer) PluginPackages {
return fetchAllSources(len(pc), func(i int) PluginPackages {
return pc[i].Fetch(out)
})
}
// Fetch retrieves all available PluginPackages from the given channel
func (pc PluginChannel) Fetch(out io.Writer) PluginPackages {
resp, err := http.Get(string(pc))
if err != nil {
fmt.Fprintln(out, "Failed to query plugin channel:\n", err)
return PluginPackages{}
}
defer resp.Body.Close()
decoder := json5.NewDecoder(resp.Body)
var repositories []PluginRepository
if err := decoder.Decode(&repositories); err != nil {
fmt.Fprintln(out, "Failed to decode channel data:\n", err)
return PluginPackages{}
}
return fetchAllSources(len(repositories), func(i int) PluginPackages {
return repositories[i].Fetch(out)
})
}
// Fetch retrieves all available PluginPackages from the given repository
func (pr PluginRepository) Fetch(out io.Writer) PluginPackages {
resp, err := http.Get(string(pr))
if err != nil {
fmt.Fprintln(out, "Failed to query plugin repository:\n", err)
return PluginPackages{}
}
defer resp.Body.Close()
decoder := json5.NewDecoder(resp.Body)
var plugins PluginPackages
if err := decoder.Decode(&plugins); err != nil {
fmt.Fprintln(out, "Failed to decode repository data:\n", err)
return PluginPackages{}
}
if len(plugins) > 0 {
return PluginPackages{plugins[0]}
}
return nil
// return plugins
}
// UnmarshalJSON unmarshals raw json to a PluginVersion
func (pv *PluginVersion) UnmarshalJSON(data []byte) error {
var values struct {
Version semver.Version
Url string
Require map[string]string
}
if err := json5.Unmarshal(data, &values); err != nil {
return err
}
pv.Version = values.Version
pv.Url = values.Url
pv.Require = make(PluginDependencies, 0)
for k, v := range values.Require {
// don't add the dependency if it's the core and
// we have a unknown version number.
// in that case just accept that dependency (which equals to not adding it.)
if k != CorePluginName || !isUnknownCoreVersion() {
if vRange, err := semver.ParseRange(v); err == nil {
pv.Require = append(pv.Require, &PluginDependency{k, vRange})
}
}
}
return nil
}
// UnmarshalJSON unmarshals raw json to a PluginPackage
func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
var values struct {
Name string
Description string
Author string
Tags []string
Versions PluginVersions
}
if err := json5.Unmarshal(data, &values); err != nil {
return err
}
pp.Name = values.Name
pp.Description = values.Description
pp.Author = values.Author
pp.Tags = values.Tags
pp.Versions = values.Versions
for _, v := range pp.Versions {
v.pack = pp
}
return nil
}
// GetAllPluginPackages gets all PluginPackages which may be available.
func GetAllPluginPackages(out io.Writer) PluginPackages {
if allPluginPackages == nil {
getOption := func(name string) []string {
data := GetGlobalOption(name)
if strs, ok := data.([]string); ok {
return strs
}
if ifs, ok := data.([]interface{}); ok {
result := make([]string, len(ifs))
for i, urlIf := range ifs {
if url, ok := urlIf.(string); ok {
result[i] = url
} else {
return nil
}
}
return result
}
return nil
}
channels := PluginChannels{}
for _, url := range getOption("pluginchannels") {
channels = append(channels, PluginChannel(url))
}
repos := []PluginRepository{}
for _, url := range getOption("pluginrepos") {
repos = append(repos, PluginRepository(url))
}
allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {
if i == 0 {
return channels.Fetch(out)
}
return repos[i-1].Fetch(out)
})
}
return allPluginPackages
}
func (pv PluginVersions) find(ppName string) *PluginVersion {
for _, v := range pv {
if v.pack.Name == ppName {
return v
}
}
return nil
}
// Len returns the number of pluginversions in this slice
func (pv PluginVersions) Len() int {
return len(pv)
}
// Swap two entries of the slice
func (pv PluginVersions) Swap(i, j int) {
pv[i], pv[j] = pv[j], pv[i]
}
// Less returns true if the version at position i is greater then the version at position j (used for sorting)
func (pv PluginVersions) Less(i, j int) bool {
return pv[i].Version.GT(pv[j].Version)
}
// Match returns true if the package matches a given search text
func (pp PluginPackage) Match(text string) bool {
text = strings.ToLower(text)
for _, t := range pp.Tags {
if strings.ToLower(t) == text {
return true
}
}
if strings.Contains(strings.ToLower(pp.Name), text) {
return true
}
if strings.Contains(strings.ToLower(pp.Description), text) {
return true
}
return false
}
// IsInstallable returns true if the package can be installed.
func (pp PluginPackage) IsInstallable(out io.Writer) error {
_, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
&PluginDependency{
Name: pp.Name,
Range: semver.Range(func(v semver.Version) bool { return true }),
}})
return err
}
// SearchPlugin retrieves a list of all PluginPackages which match the given search text and
// could be or are already installed
func SearchPlugin(out io.Writer, texts []string) (plugins PluginPackages) {
plugins = make(PluginPackages, 0)
pluginLoop:
for _, pp := range GetAllPluginPackages(out) {
for _, text := range texts {
if !pp.Match(text) {
continue pluginLoop
}
}
if err := pp.IsInstallable(out); err == nil {
plugins = append(plugins, pp)
}
}
return
}
func isUnknownCoreVersion() bool {
_, err := semver.ParseTolerant(util.Version)
return err != nil
}
func newStaticPluginVersion(name, version string) *PluginVersion {
vers, err := semver.ParseTolerant(version)
if err != nil {
if vers, err = semver.ParseTolerant("0.0.0-" + version); err != nil {
vers = semver.MustParse("0.0.0-unknown")
}
}
pl := &PluginPackage{
Name: name,
}
pv := &PluginVersion{
pack: pl,
Version: vers,
}
pl.Versions = PluginVersions{pv}
return pv
}
// GetInstalledVersions returns a list of all currently installed plugins including an entry for
// micro itself. This can be used to resolve dependencies.
func GetInstalledVersions(withCore bool) PluginVersions {
result := PluginVersions{}
if withCore {
result = append(result, newStaticPluginVersion(CorePluginName, util.Version))
}
for _, p := range Plugins {
if !p.IsEnabled() {
continue
}
version := GetInstalledPluginVersion(p.Name)
if pv := newStaticPluginVersion(p.Name, version); pv != nil {
result = append(result, pv)
}
}
return result
}
// GetInstalledPluginVersion returns the string of the exported VERSION variable of a loaded plugin
func GetInstalledPluginVersion(name string) string {
plugin := ulua.L.GetGlobal(name)
if plugin != lua.LNil {
version := ulua.L.GetField(plugin, "VERSION")
if str, ok := version.(lua.LString); ok {
return string(str)
}
}
return ""
}
// DownloadAndInstall downloads and installs the given plugin and version
func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
fmt.Fprintf(out, "Downloading %q (%s) from %q\n", pv.pack.Name, pv.Version, pv.Url)
resp, err := http.Get(pv.Url)
if err != nil {
return err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
zipbuf := bytes.NewReader(data)
z, err := zip.NewReader(zipbuf, zipbuf.Size())
if err != nil {
return err
}
targetDir := filepath.Join(ConfigDir, "plug", pv.pack.Name)
dirPerm := os.FileMode(0755)
if err = os.MkdirAll(targetDir, dirPerm); err != nil {
return err
}
// Check if all files in zip are in the same directory.
// this might be the case if the plugin zip contains the whole plugin dir
// instead of its content.
var prefix string
allPrefixed := false
for i, f := range z.File {
parts := strings.Split(f.Name, "/")
if i == 0 {
prefix = parts[0]
} else if parts[0] != prefix {
allPrefixed = false
break
} else {
// switch to true since we have at least a second file
allPrefixed = true
}
}
// Install files and directory's
for _, f := range z.File {
parts := strings.Split(f.Name, "/")
if allPrefixed {
parts = parts[1:]
}
targetName := filepath.Join(targetDir, filepath.Join(parts...))
if f.FileInfo().IsDir() {
if err := os.MkdirAll(targetName, dirPerm); err != nil {
return err
}
} else {
basepath := filepath.Dir(targetName)
if err := os.MkdirAll(basepath, dirPerm); err != nil {
return err
}
content, err := f.Open()
if err != nil {
return err
}
defer content.Close()
target, err := os.Create(targetName)
if err != nil {
return err
}
defer target.Close()
if _, err = io.Copy(target, content); err != nil {
return err
}
}
}
return nil
}
func (pl PluginPackages) Get(name string) *PluginPackage {
for _, p := range pl {
if p.Name == name {
return p
}
}
return nil
}
func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
result := make(PluginVersions, 0)
p := pl.Get(name)
if p != nil {
for _, v := range p.Versions {
result = append(result, v)
}
}
return result
}
func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {
m := make(map[string]*PluginDependency)
for _, r := range req {
m[r.Name] = r
}
for _, o := range other {
cur, ok := m[o.Name]
if ok {
m[o.Name] = &PluginDependency{
o.Name,
o.Range.AND(cur.Range),
}
} else {
m[o.Name] = o
}
}
result := make(PluginDependencies, 0, len(m))
for _, v := range m {
result = append(result, v)
}
return result
}
// Resolve resolves dependencies between different plugins
func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {
if len(open) == 0 {
return selectedVersions, nil
}
currentRequirement, stillOpen := open[0], open[1:]
if currentRequirement != nil {
if selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {
if currentRequirement.Range(selVersion.Version) {
return all.Resolve(selectedVersions, stillOpen)
}
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
}
availableVersions := all.GetAllVersions(currentRequirement.Name)
sort.Sort(availableVersions)
for _, version := range availableVersions {
if currentRequirement.Range(version.Version) {
resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
if err == nil {
return resolved, nil
}
}
}
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
}
return selectedVersions, nil
}
func (pv PluginVersions) install(out io.Writer) {
anyInstalled := false
currentlyInstalled := GetInstalledVersions(true)
for _, sel := range pv {
if sel.pack.Name != CorePluginName {
shouldInstall := true
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
if pv.Version.NE(sel.Version) {
fmt.Fprintln(out, "Uninstalling", sel.pack.Name)
UninstallPlugin(out, sel.pack.Name)
} else {
shouldInstall = false
}
}
if shouldInstall {
if err := sel.DownloadAndInstall(out); err != nil {
fmt.Fprintln(out, err)
return
}
anyInstalled = true
}
}
}
if anyInstalled {
fmt.Fprintln(out, "One or more plugins installed.")
} else {
fmt.Fprintln(out, "Nothing to install / update")
}
}
// UninstallPlugin deletes the plugin folder of the given plugin
func UninstallPlugin(out io.Writer, name string) {
for _, p := range Plugins {
if !p.IsEnabled() {
continue
}
if p.Name == name {
p.Loaded = false
if err := os.RemoveAll(filepath.Join(ConfigDir, "plug", p.DirName)); err != nil {
fmt.Fprintln(out, err)
return
}
break
}
}
}
// Install installs the plugin
func (pl PluginPackage) Install(out io.Writer) {
selected, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
&PluginDependency{
Name: pl.Name,
Range: semver.Range(func(v semver.Version) bool { return true }),
}})
if err != nil {
fmt.Fprintln(out, err)
return
}
selected.install(out)
}
// UpdatePlugins updates the given plugins
func UpdatePlugins(out io.Writer, plugins []string) {
// if no plugins are specified, update all installed plugins.
if len(plugins) == 0 {
for _, p := range Plugins {
if !p.IsEnabled() {
continue
}
plugins = append(plugins, p.Name)
}
}
fmt.Fprintln(out, "Checking for plugin updates")
microVersion := PluginVersions{
newStaticPluginVersion(CorePluginName, util.Version),
}
var updates = make(PluginDependencies, 0)
for _, name := range plugins {
pv := GetInstalledPluginVersion(name)
r, err := semver.ParseRange(">=" + pv) // Try to get newer versions.
if err == nil {
updates = append(updates, &PluginDependency{
Name: name,
Range: r,
})
}
}
selected, err := GetAllPluginPackages(out).Resolve(microVersion, updates)
if err != nil {
fmt.Fprintln(out, err)
return
}
selected.install(out)
}
func PluginCommand(out io.Writer, cmd string, args []string) {
switch cmd {
case "install":
installedVersions := GetInstalledVersions(false)
for _, plugin := range args {
pp := GetAllPluginPackages(out).Get(plugin)
if pp == nil {
fmt.Fprintln(out, "Unknown plugin \""+plugin+"\"")
} else if err := pp.IsInstallable(out); err != nil {
fmt.Fprintln(out, "Error installing ", plugin, ": ", err)
} else {
for _, installed := range installedVersions {
if pp.Name == installed.Pack().Name {
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
fmt.Fprintln(out, pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
} else {
fmt.Fprintln(out, pp.Name, " is already installed")
}
}
}
pp.Install(out)
}
}
case "remove":
removed := ""
for _, plugin := range args {
// check if the plugin exists.
for _, p := range Plugins {
if p.Name == plugin && p.Default {
fmt.Fprintln(out, "Default plugins cannot be removed, but can be disabled via settings.")
continue
}
if p.Name == plugin {
UninstallPlugin(out, plugin)
removed += plugin + " "
continue
}
}
}
if removed != "" {
fmt.Fprintln(out, "Removed ", removed)
} else {
fmt.Fprintln(out, "No plugins removed")
}
case "update":
UpdatePlugins(out, args)
case "list":
plugins := GetInstalledVersions(false)
fmt.Fprintln(out, "The following plugins are currently installed:")
for _, p := range plugins {
fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
}
case "search":
plugins := SearchPlugin(out, args)
fmt.Fprintln(out, len(plugins), " plugins found")
for _, p := range plugins {
fmt.Fprintln(out, "----------------")
fmt.Fprintln(out, p.String())
}
fmt.Fprintln(out, "----------------")
case "available":
packages := GetAllPluginPackages(out)
fmt.Fprintln(out, "Available Plugins:")
for _, pkg := range packages {
fmt.Fprintln(out, pkg.Name)
}
default:
fmt.Fprintln(out, "Invalid plugin command")
}
}

View File

@@ -0,0 +1,56 @@
package config
import (
"testing"
"github.com/blang/semver"
"github.com/zyedidia/json5"
)
func TestDependencyResolving(t *testing.T) {
js := `
[{
"Name": "Foo",
"Versions": [{ "Version": "1.0.0" }, { "Version": "1.5.0" },{ "Version": "2.0.0" }]
}, {
"Name": "Bar",
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": ">1.0.0 <2.0.0"} }]
}, {
"Name": "Unresolvable",
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": "<=1.0.0", "Bar": ">0.0.0"} }]
}]
`
var all PluginPackages
err := json5.Unmarshal([]byte(js), &all)
if err != nil {
t.Error(err)
}
selected, err := all.Resolve(PluginVersions{}, PluginDependencies{
&PluginDependency{"Bar", semver.MustParseRange(">=1.0.0")},
})
check := func(name, version string) {
v := selected.find(name)
expected := semver.MustParse(version)
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)
}
}
if err != nil {
t.Error(err)
} else {
check("Foo", "1.5.0")
check("Bar", "1.0.0")
}
selected, err = all.Resolve(PluginVersions{}, PluginDependencies{
&PluginDependency{"Unresolvable", semver.MustParseRange(">0.0.0")},
})
if err == nil {
t.Error("Unresolvable package resolved:", selected)
}
}

View File

@@ -7,12 +7,9 @@ import (
)
var (
ErrMissingName = errors.New("Missing or empty name field")
ErrMissingDesc = errors.New("Missing or empty description field")
ErrMissingSite = errors.New("Missing or empty website field")
ErrMissingInstall = errors.New("Missing or empty install field")
ErrMissingVstr = errors.New("Missing or empty versions field")
ErrMissingRequire = errors.New("Missing or empty require field")
ErrMissingName = errors.New("Missing or empty name field")
ErrMissingDesc = errors.New("Missing or empty description field")
ErrMissingSite = errors.New("Missing or empty website field")
)
// PluginInfo contains all the needed info about a plugin
@@ -27,19 +24,16 @@ var (
// Vstr: version
// Require: list of dependencies and requirements
type PluginInfo struct {
Name string `json:"name"`
Desc string `json:"description"`
Site string `json:"website"`
Install string `json:"install"`
Vstr string `json:"version"`
Require []string `json:"require"`
Name string `json:"Name"`
Desc string `json:"Description"`
Site string `json:"Website"`
}
// NewPluginInfo parses a JSON input into a valid PluginInfo struct
// Returns an error if there are any missing fields or any invalid fields
// There are no optional fields in a plugin info json file
func NewPluginInfo(data []byte) (*PluginInfo, error) {
var info PluginInfo
var info []PluginInfo
dec := json.NewDecoder(bytes.NewReader(data))
// dec.DisallowUnknownFields() // Force errors
@@ -48,19 +42,5 @@ func NewPluginInfo(data []byte) (*PluginInfo, error) {
return nil, err
}
// if len(info.Name) == 0 {
// return nil, ErrMissingName
// } else if len(info.Desc) == 0 {
// return nil, ErrMissingDesc
// } else if len(info.Site) == 0 {
// return nil, ErrMissingSite
// } else if len(info.Install) == 0 {
// return nil, ErrMissingInstall
// } else if len(info.Vstr) == 0 {
// return nil, ErrMissingVstr
// } else if len(info.Require) == 0 {
// return nil, ErrMissingRequire
// }
return &info, nil
return &info[0], nil
}

View File

@@ -17,10 +17,13 @@ const (
RTHelp = 2
RTPlugin = 3
RTSyntaxHeader = 4
NumTypes = 5 // How many filetypes are there
)
type RTFiletype byte
var (
NumTypes = 5 // How many filetypes are there
)
type RTFiletype int
// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
type RuntimeFile interface {
@@ -31,8 +34,21 @@ type RuntimeFile interface {
}
// allFiles contains all available files, mapped by filetype
var allFiles [NumTypes][]RuntimeFile
var realFiles [NumTypes][]RuntimeFile
var allFiles [][]RuntimeFile
var realFiles [][]RuntimeFile
func init() {
allFiles = make([][]RuntimeFile, NumTypes)
realFiles = make([][]RuntimeFile, NumTypes)
}
// NewRTFiletype creates a new RTFiletype
func NewRTFiletype() int {
NumTypes++
allFiles = append(allFiles, []RuntimeFile{})
realFiles = append(realFiles, []RuntimeFile{})
return NumTypes - 1
}
// some file on filesystem
type realFile string
@@ -176,18 +192,22 @@ func InitRuntimeFiles() {
for _, f := range srcs {
if strings.HasSuffix(f.Name(), ".lua") {
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
} else if f.Name() == "info.json" {
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), "info.json"))
} else if strings.HasSuffix(f.Name(), ".json") {
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
if err != nil {
continue
}
p.Info, _ = NewPluginInfo(data)
p.Info, err = NewPluginInfo(data)
if err != nil {
log.Println(err)
continue
}
p.Name = p.Info.Name
}
}
if !isID(p.Name) {
log.Println("Invalid plugin name", p.Name)
if !isID(p.Name) || len(p.Srcs) <= 0 {
log.Println(p.Name, "is not a plugin")
continue
}
Plugins = append(Plugins, p)
@@ -205,17 +225,21 @@ func InitRuntimeFiles() {
for _, f := range srcs {
if strings.HasSuffix(f, ".lua") {
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
} else if f == "info.json" {
data, err := Asset(filepath.Join(plugdir, d, "info.json"))
} else if strings.HasSuffix(f, ".json") {
data, err := Asset(filepath.Join(plugdir, d, f))
if err != nil {
continue
}
p.Info, _ = NewPluginInfo(data)
p.Info, err = NewPluginInfo(data)
if err != nil {
log.Println(err)
continue
}
p.Name = p.Info.Name
}
}
if !isID(p.Name) {
log.Println("Invalid plugin name", p.Name)
if !isID(p.Name) || len(p.Srcs) <= 0 {
log.Println(p.Name, "is not a plugin")
continue
}
Plugins = append(Plugins, p)
@@ -253,7 +277,7 @@ func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) e
pldir := pl.DirName
fullpath := filepath.Join(ConfigDir, "plug", pldir, filePath)
if _, err := os.Stat(fullpath); err == nil {
AddRuntimeFile(filetype, realFile(fullpath))
AddRealRuntimeFile(filetype, realFile(fullpath))
} else {
fullpath = path.Join("runtime", "plugins", pldir, filePath)
AddRuntimeFile(filetype, assetFile(fullpath))
@@ -280,5 +304,5 @@ func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, dire
// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
func PluginAddRuntimeFileFromMemory(filetype RTFiletype, filename, data string) {
AddRuntimeFile(filetype, memoryFile{filename, []byte(data)})
AddRealRuntimeFile(filetype, memoryFile{filename, []byte(data)})
}

File diff suppressed because one or more lines are too long

View File

@@ -5,12 +5,13 @@ import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"github.com/flynn/json5"
"github.com/zyedidia/glob"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/internal/util"
"golang.org/x/text/encoding/htmlindex"
)
@@ -34,7 +35,7 @@ func init() {
// Options with validators
var optionValidators = map[string]optionValidator{
// "autosave": validateNonNegativeValue,
"autosave": validateNonNegativeValue,
"tabsize": validatePositiveValue,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
@@ -45,7 +46,7 @@ var optionValidators = map[string]optionValidator{
}
func ReadSettings() error {
filename := ConfigDir + "/settings.json"
filename := filepath.Join(ConfigDir, "settings.json")
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
@@ -57,6 +58,18 @@ func ReadSettings() error {
if err != nil {
return errors.New("Error reading settings.json: " + err.Error())
}
// check if autosave is a boolean and convert it to float if so
if v, ok := parsedSettings["autosave"]; ok {
s, ok := v.(bool)
if ok {
if s {
parsedSettings["autosave"] = 8.0
} else {
parsedSettings["autosave"] = 0.0
}
}
}
}
}
return nil
@@ -119,12 +132,22 @@ func WriteSettings(filename string) error {
return err
}
// RegisterCommonOption creates a new option. This is meant to be called by plugins to add options.
func RegisterCommonOption(name string, defaultvalue interface{}) error {
func OverwriteSettings(filename string) error {
var err error
if _, e := os.Stat(ConfigDir); e == nil {
txt, _ := json.MarshalIndent(GlobalSettings, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return err
}
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
name = pl + "." + name
if v, ok := GlobalSettings[name]; !ok {
defaultCommonSettings[name] = defaultvalue
GlobalSettings[name] = defaultvalue
err := WriteSettings(ConfigDir + "/settings.json")
err := WriteSettings(filepath.Join(ConfigDir, "/settings.json"))
if err != nil {
return errors.New("Error writing settings.json file: " + err.Error())
}
@@ -134,11 +157,17 @@ func RegisterCommonOption(name string, defaultvalue interface{}) error {
return nil
}
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{}) error {
return RegisterGlobalOption(pl+"."+name, defaultvalue)
}
// RegisterGlobalOption creates a new global-only option
func RegisterGlobalOption(name string, defaultvalue interface{}) error {
if v, ok := GlobalSettings[name]; !ok {
defaultGlobalSettings[name] = defaultvalue
GlobalSettings[name] = defaultvalue
err := WriteSettings(ConfigDir + "/settings.json")
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
if err != nil {
return errors.New("Error writing settings.json file: " + err.Error())
}
@@ -181,7 +210,7 @@ var defaultCommonSettings = map[string]interface{}{
"softwrap": false,
"splitbottom": true,
"splitright": true,
"statusformatl": "$(filename) $(modified)($(line),$(col)) | ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
"statusline": true,
"syntax": true,
@@ -212,15 +241,25 @@ func DefaultCommonSettings() map[string]interface{} {
return commonsettings
}
// a list of settings that should only be globally modified and their
// default values
var defaultGlobalSettings = map[string]interface{}{
// "autosave": float64(0),
"colorscheme": "default",
"infobar": true,
"keymenu": false,
"mouse": true,
"savehistory": true,
"sucmd": "sudo",
"termtitle": false,
"autosave": float64(0),
"colorscheme": "default",
"infobar": true,
"keymenu": false,
"mouse": true,
"paste": false,
"savehistory": true,
"sucmd": "sudo",
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
"pluginrepos": []string{},
}
// a list of settings that should never be globally modified
var LocalSettings = []string{
"filetype",
"readonly",
}
// DefaultGlobalSettings returns the default global settings for micro
@@ -249,6 +288,7 @@ func DefaultAllSettings() map[string]interface{} {
return allsettings
}
// GetNativeValue parses and validates a value for a given option
func GetNativeValue(option string, realValue interface{}, value string) (interface{}, error) {
var native interface{}
kind := reflect.TypeOf(realValue).Kind()

View File

@@ -4,12 +4,12 @@ import (
"strconv"
"unicode/utf8"
"github.com/zyedidia/tcell"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/tcell"
)
// The BufWindow provides a way of displaying a certain section
@@ -102,7 +102,7 @@ func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style)
func (w *BufWindow) Clear() {
for y := 0; y < w.Height; y++ {
for x := 0; x < w.Width; x++ {
screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
}
}
}
@@ -188,6 +188,11 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
bufHeight--
}
bufWidth := w.Width
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
bufWidth--
}
// We need to know the string length of the largest line number
// so we can pad appropriately when displaying line numbers
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
@@ -259,7 +264,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
totalwidth += width
// If we reach the end of the window then we either stop or we wrap for softwrap
if vloc.X >= w.Width {
if vloc.X >= bufWidth {
if !softwrap {
break
} else {
@@ -269,7 +274,9 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
}
vloc.X = 0
// This will draw an empty line number because the current line is wrapped
vloc.X += maxLineNumLength + 1
if b.Settings["ruler"].(bool) {
vloc.X += maxLineNumLength + 1
}
}
}
}
@@ -298,9 +305,9 @@ func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
break
}
}
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
vloc.X++
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
vloc.X++
}
@@ -309,21 +316,21 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxL
// Write the spaces before the line number if necessary
for i := 0; i < maxLineNumLength-len(lineNum); i++ {
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
vloc.X++
}
// Write the actual line number
for _, ch := range lineNum {
if softwrapped {
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
} else {
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
}
vloc.X++
}
// Write the extra space
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
vloc.X++
}
@@ -340,10 +347,9 @@ func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.
func (w *BufWindow) showCursor(x, y int, main bool) {
if w.active {
if main {
screen.Screen.ShowCursor(x, y)
screen.ShowCursor(x, y)
} else {
r, _, _, _ := screen.Screen.GetContent(x, y)
screen.Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
screen.ShowFakeCursorMulti(x, y)
}
}
}
@@ -352,6 +358,10 @@ func (w *BufWindow) showCursor(x, y int, main bool) {
func (w *BufWindow) displayBuffer() {
b := w.Buf
if w.Height <= 0 || w.Width <= 0 {
return
}
hasMessage := len(b.Messages) > 0
bufHeight := w.Height
if w.drawStatus {
@@ -364,17 +374,14 @@ func (w *BufWindow) displayBuffer() {
}
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
for _, c := range b.GetCursors() {
// rehighlight starting from where the cursor is
start := c.Y
if start > 0 && b.Rehighlight(start-1) {
b.Highlighter.ReHighlightLine(b, start-1)
b.SetRehighlight(start-1, false)
for _, r := range b.Modifications {
final := -1
for i := r.X; i <= r.Y; i++ {
final = util.Max(b.Highlighter.ReHighlightStates(b, i), final)
}
b.Highlighter.ReHighlightStates(b, start)
b.Highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight)
b.Highlighter.HighlightMatches(b, r.X, final+1)
}
b.ClearModifications()
}
var matchingBraces []buffer.Loc
@@ -409,7 +416,11 @@ func (w *BufWindow) displayBuffer() {
}
curNumStyle := config.DefStyle
if style, ok := config.Colorscheme["current-line-number"]; ok {
curNumStyle = style
if !b.Settings["cursorline"].(bool) {
curNumStyle = lineNumStyle
} else {
curNumStyle = style
}
}
// We need to know the string length of the largest line number
@@ -514,7 +525,7 @@ func (w *BufWindow) displayBuffer() {
}
}
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
if showcursor {
for _, c := range cursors {
@@ -569,7 +580,9 @@ func (w *BufWindow) displayBuffer() {
}
vloc.X = 0
// This will draw an empty line number because the current line is wrapped
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
if b.Settings["ruler"].(bool) {
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
}
}
}
}
@@ -592,17 +605,13 @@ func (w *BufWindow) displayBuffer() {
curStyle = style.Background(fg)
}
}
screen.Screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
}
for _, c := range cursors {
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
}
if vloc.X != bufWidth {
draw(' ', curStyle, true)
}
draw(' ', curStyle, false)
bloc.X = w.StartCol
bloc.Y++
if bloc.Y >= b.LinesNum() {
@@ -624,7 +633,7 @@ func (w *BufWindow) displayStatusLine() {
} else if w.Y+w.Height != infoY {
w.drawStatus = true
for x := w.X; x < w.X+w.Width; x++ {
screen.Screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
}
} else {
w.drawStatus = false
@@ -644,7 +653,7 @@ func (w *BufWindow) displayScrollBar() {
}
barstart := w.Y + int(float64(w.StartLine)/float64(w.Buf.LinesNum())*float64(w.Height))
for y := barstart; y < util.Min(barstart+barsize, w.Y+bufHeight); y++ {
screen.Screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
}
}
}

View File

@@ -15,6 +15,8 @@ import (
type InfoWindow struct {
*info.InfoBuf
*View
hscroll int
}
func (i *InfoWindow) errStyle() tcell.Style {
@@ -74,7 +76,7 @@ func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
func (i *InfoWindow) Clear() {
for x := 0; x < i.Width; x++ {
screen.Screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
}
}
@@ -111,7 +113,7 @@ func (i *InfoWindow) displayBuffer() {
if j > 0 {
c = ' '
}
screen.Screen.SetContent(vlocX, i.Y, c, nil, style)
screen.SetContent(vlocX, i.Y, c, nil, style)
}
vlocX++
}
@@ -121,7 +123,7 @@ func (i *InfoWindow) displayBuffer() {
totalwidth := blocX - nColsBeforeStart
for len(line) > 0 {
if activeC.X == blocX {
screen.Screen.ShowCursor(vlocX, i.Y)
screen.ShowCursor(vlocX, i.Y)
}
r, size := utf8.DecodeRune(line)
@@ -155,7 +157,7 @@ func (i *InfoWindow) displayBuffer() {
}
}
if activeC.X == blocX {
screen.Screen.ShowCursor(vlocX, i.Y)
screen.ShowCursor(vlocX, i.Y)
}
}
@@ -167,14 +169,44 @@ func (i *InfoWindow) displayKeyMenu() {
for y := 0; y < len(keydisplay); y++ {
for x := 0; x < i.Width; x++ {
if x < len(keydisplay[y]) {
screen.Screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
} else {
screen.Screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
}
}
}
}
func (i *InfoWindow) totalSize() int {
sum := 0
for _, n := range i.Suggestions {
sum += runewidth.StringWidth(n) + 1
}
return sum
}
func (i *InfoWindow) scrollToSuggestion() {
x := 0
s := i.totalSize()
for j, n := range i.Suggestions {
c := utf8.RuneCountInString(n)
if j == i.CurSuggestion {
if x+c >= i.hscroll+i.Width {
i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)
} else if x < i.hscroll {
i.hscroll = util.Clamp(x-1, 0, s-i.Width)
}
break
}
x += c + 1
}
if s-i.Width <= 0 {
i.hscroll = 0
}
}
func (i *InfoWindow) Display() {
x := 0
if config.GetGlobalOption("keymenu").(bool) {
@@ -194,7 +226,7 @@ func (i *InfoWindow) Display() {
display := i.Msg
for _, c := range display {
screen.Screen.SetContent(x, i.Y, c, nil, style)
screen.SetContent(x, i.Y, c, nil, style)
x += runewidth.RuneWidth(c)
}
@@ -204,6 +236,11 @@ func (i *InfoWindow) Display() {
}
if i.HasSuggestions && len(i.Suggestions) > 1 {
i.scrollToSuggestion()
x := -i.hscroll
done := false
statusLineStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style
@@ -212,29 +249,43 @@ func (i *InfoWindow) Display() {
if config.GetGlobalOption("keymenu").(bool) {
keymenuOffset = len(keydisplay)
}
x := 0
draw := func(r rune, s tcell.Style) {
y := i.Y - keymenuOffset - 1
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
c := r
if j > 0 {
c = ' '
}
if x == i.Width-1 && !done {
screen.SetContent(i.Width-1, y, '>', nil, s)
x++
break
} else if x == 0 && i.hscroll > 0 {
screen.SetContent(0, y, '<', nil, s)
} else if x >= 0 && x < i.Width {
screen.SetContent(x, y, c, nil, s)
}
x++
}
}
for j, s := range i.Suggestions {
style := statusLineStyle
if i.CurSuggestion == j {
style = style.Reverse(true)
}
for _, r := range s {
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
x++
if x >= i.Width {
return
}
}
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, ' ', nil, statusLineStyle)
x++
if x >= i.Width {
return
draw(r, style)
// screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
}
draw(' ', statusLineStyle)
}
for x < i.Width {
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, ' ', nil, statusLineStyle)
x++
draw(' ', statusLineStyle)
}
}
}

View File

@@ -47,6 +47,9 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
if b.Modified() {
return "+ "
}
if b.Type.Readonly {
return "[ro] "
}
return ""
},
}
@@ -118,13 +121,13 @@ func (s *StatusLine) Display() {
style = style.Reverse(true)
}
for _, r := range sug {
screen.Screen.SetContent(x, y-keymenuOffset, r, nil, style)
screen.SetContent(x, y-keymenuOffset, r, nil, style)
x++
if x >= s.win.Width {
return
}
}
screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
x++
if x >= s.win.Width {
return
@@ -132,7 +135,7 @@ func (s *StatusLine) Display() {
}
for x < s.win.Width {
screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
x++
}
return
@@ -184,7 +187,7 @@ func (s *StatusLine) Display() {
c = ' '
x++
}
screen.Screen.SetContent(winX+x, y, c, nil, statusLineStyle)
screen.SetContent(winX+x, y, c, nil, statusLineStyle)
}
} else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
r, size := utf8.DecodeRune(rightText)
@@ -196,10 +199,10 @@ func (s *StatusLine) Display() {
c = ' '
x++
}
screen.Screen.SetContent(winX+x, y, c, nil, statusLineStyle)
screen.SetContent(winX+x, y, c, nil, statusLineStyle)
}
} else {
screen.Screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
}
}
}

View File

@@ -25,6 +25,10 @@ func NewTabWindow(w int, y int) *TabWindow {
return tw
}
func (w *TabWindow) Resize(width, height int) {
w.width = width
}
func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
x := -w.hscroll
@@ -45,13 +49,18 @@ func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
func (w *TabWindow) Scroll(amt int) {
w.hscroll += amt
w.hscroll = util.Clamp(w.hscroll, 0, w.TotalSize()-w.width)
s := w.TotalSize()
w.hscroll = util.Clamp(w.hscroll, 0, s-w.width)
if s-w.width <= 0 {
w.hscroll = 0
}
}
func (w *TabWindow) TotalSize() int {
sum := 2
for _, n := range w.Names {
sum += utf8.RuneCountInString(n) + 4
sum += runewidth.StringWidth(n) + 4
}
return sum - 4
}
@@ -64,6 +73,7 @@ func (w *TabWindow) SetActive(a int) {
w.active = a
x := 2
s := w.TotalSize()
for i, n := range w.Names {
c := utf8.RuneCountInString(n)
if i == a {
@@ -76,6 +86,10 @@ func (w *TabWindow) SetActive(a int) {
}
x += c + 4
}
if s-w.width <= 0 {
w.hscroll = 0
}
}
func (w *TabWindow) Display() {
@@ -91,13 +105,13 @@ func (w *TabWindow) Display() {
c = ' '
}
if x == w.width-1 && !done {
screen.Screen.SetContent(w.width-1, w.Y, '>', nil, config.DefStyle.Reverse(true))
screen.SetContent(w.width-1, w.Y, '>', nil, config.DefStyle.Reverse(true))
x++
break
} else if x == 0 && w.hscroll > 0 {
screen.Screen.SetContent(0, w.Y, '<', nil, config.DefStyle.Reverse(true))
screen.SetContent(0, w.Y, '<', nil, config.DefStyle.Reverse(true))
} else if x >= 0 && x < w.width {
screen.Screen.SetContent(x, w.Y, c, nil, config.DefStyle.Reverse(true))
screen.SetContent(x, w.Y, c, nil, config.DefStyle.Reverse(true))
}
x++
}

View File

@@ -51,7 +51,7 @@ func (w *TermWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
func (w *TermWindow) Clear() {
for y := 0; y < w.Height; y++ {
for x := 0; x < w.Width; x++ {
screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
}
}
}
@@ -88,7 +88,7 @@ func (w *TermWindow) Display() {
st = st.Reverse(true)
}
screen.Screen.SetContent(w.X+x, w.Y+y, c, nil, st)
screen.SetContent(w.X+x, w.Y+y, c, nil, st)
}
}
if config.GetGlobalOption("statusline").(bool) {
@@ -103,14 +103,14 @@ func (w *TermWindow) Display() {
if x < textLen {
r, size := utf8.DecodeRune(text)
text = text[size:]
screen.Screen.SetContent(w.X+x, w.Y+w.Height, r, nil, statusLineStyle)
screen.SetContent(w.X+x, w.Y+w.Height, r, nil, statusLineStyle)
} else {
screen.Screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
}
}
}
if w.State.CursorVisible() && w.active {
curx, cury := w.State.Cursor()
screen.Screen.ShowCursor(curx+w.X, cury+w.Y)
screen.ShowCursor(curx+w.X, cury+w.Y)
}
}

View File

@@ -28,7 +28,7 @@ func (w *UIWindow) drawNode(n *views.Node) {
if c.IsLeaf() && c.Kind == views.STVert {
if i != len(cs)-1 {
for h := 0; h < c.H; h++ {
screen.Screen.SetContent(c.X+c.W, c.Y+h, '|', nil, dividerStyle.Reverse(true))
screen.SetContent(c.X+c.W, c.Y+h, '|', nil, dividerStyle.Reverse(true))
}
}
} else {

View File

@@ -2,14 +2,8 @@ package info
import (
"fmt"
"strings"
"github.com/zyedidia/micro/internal/buffer"
luar "layeh.com/gopher-luar"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
)
// The InfoBuf displays messages and other info at the bottom of the screen.
@@ -134,63 +128,6 @@ func (i *InfoBuf) YNPrompt(prompt string, donecb func(bool, bool)) {
i.YNCallback = donecb
}
// PlugPrompt provides a plugin interface for calling "Prompt" with the appropriate Lua callbacks
func (i *InfoBuf) PlugPrompt(prompt string, msg string, ptype string, eventcb string, donecb string) {
eventLuaFn := strings.Split(eventcb, ".")
doneLuaFn := strings.Split(donecb, ".")
var luaEventcb func(string)
var luaDonecb func(string, bool)
if len(eventLuaFn) == 2 {
plName, plFn := doneLuaFn[0], doneLuaFn[1]
pl := config.FindPlugin(plName)
if pl != nil {
luaEventcb = func(resp string) {
_, err := pl.Call(plFn, luar.New(ulua.L, resp))
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}
}
if len(doneLuaFn) == 2 {
plName, plFn := doneLuaFn[0], doneLuaFn[1]
pl := config.FindPlugin(plName)
if pl != nil {
luaDonecb = func(resp string, canceled bool) {
_, err := pl.Call(plFn, luar.New(ulua.L, resp), luar.New(ulua.L, canceled))
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}
}
i.Prompt(prompt, msg, ptype, luaEventcb, luaDonecb)
}
// PlugYNPrompt provides a plugin interface for calling "YNPrompt" with the appropriate Lua callbacks
func (i *InfoBuf) PlugYNPrompt(prompt string, donecb string) {
doneLuaFn := strings.Split(donecb, ".")
var luaDonecb func(bool, bool)
if len(doneLuaFn) == 2 {
plName, plFn := doneLuaFn[0], doneLuaFn[1]
pl := config.FindPlugin(plName)
if pl != nil {
luaDonecb = func(resp bool, canceled bool) {
_, err := pl.Call(plFn, luar.New(ulua.L, resp), luar.New(ulua.L, canceled))
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}
}
i.YNPrompt(prompt, luaDonecb)
}
// DonePrompt finishes the current prompt and indicates whether or not it was canceled
func (i *InfoBuf) DonePrompt(canceled bool) {
hadYN := i.HasYN

View File

@@ -6,7 +6,7 @@ import (
"sync"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/pkg/terminfo"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/tcell"
)
@@ -17,21 +17,85 @@ import (
// screen. TODO: maybe we should worry about polling and drawing at the
// same time too.
var Screen tcell.Screen
// The lock is necessary since the screen is polled on a separate thread
var lock sync.Mutex
// DrawChan is a channel that will cause the screen to redraw when
// written to even if no event user event has occurred
var DrawChan chan bool
// Lock locks the screen lock
func Lock() {
lock.Lock()
}
// Unlock unlocks the screen lock
func Unlock() {
lock.Unlock()
}
// Redraw schedules a redraw with the draw channel
func Redraw() {
DrawChan <- true
}
type screenCell struct {
x, y int
r rune
combc []rune
style tcell.Style
}
var lastCursor screenCell
// ShowFakeCursor displays a cursor at the given position by modifying the
// style of the given column instead of actually using the terminal cursor
// This can be useful in certain terminals such as the windows console where
// modifying the cursor location is slow and frequent modifications cause flashing
// This keeps track of the most recent fake cursor location and resets it when
// a new fake cursor location is specified
func ShowFakeCursor(x, y int) {
r, combc, style, _ := Screen.GetContent(x, y)
Screen.SetContent(lastCursor.x, lastCursor.y, lastCursor.r, lastCursor.combc, lastCursor.style)
Screen.SetContent(x, y, r, combc, config.DefStyle.Reverse(true))
lastCursor.x, lastCursor.y = x, y
lastCursor.r = r
lastCursor.combc = combc
lastCursor.style = style
}
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
// reset previous locations of the cursor
// Fake cursors are also necessary to display multiple cursors
func ShowFakeCursorMulti(x, y int) {
r, _, _, _ := Screen.GetContent(x, y)
Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
}
// ShowCursor puts the cursor at the given location using a fake cursor
// if enabled or using the terminal cursor otherwise
// By default only the windows console will use a fake cursor
func ShowCursor(x, y int) {
if util.FakeCursor {
ShowFakeCursor(x, y)
} else {
Screen.ShowCursor(x, y)
}
}
// SetContent sets a cell at a point on the screen and makes sure that it is
// synced with the last cursor location
func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
Screen.SetContent(x, y, mainc, combc, style)
if util.FakeCursor && lastCursor.x == x && lastCursor.y == y {
lastCursor.r = mainc
lastCursor.style = style
lastCursor.combc = combc
}
}
// TempFini shuts the screen down temporarily
func TempFini() bool {
screenWasNil := Screen == nil
@@ -59,52 +123,24 @@ func Init() {
// Should we enable true color?
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
tcelldb := os.Getenv("TCELLDB")
os.Setenv("TCELLDB", config.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")
if truecolor {
os.Setenv("TERM", "xterm-truecolor")
if !truecolor {
os.Setenv("TCELL_TRUECOLOR", "disable")
}
// Initilize tcell
var err error
Screen, err = tcell.NewScreen()
if err != nil {
if err == tcell.ErrTermNotFound {
err = terminfo.WriteDB(config.ConfigDir + "/.tcelldb")
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not create terminal database file", config.ConfigDir+"/.tcelldb")
os.Exit(1)
}
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(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
os.Exit(1)
}
if err = Screen.Init(); err != nil {
fmt.Println(err)
os.Exit(1)
}
// Now we can put the TERM back to what it was before
if truecolor {
os.Setenv("TERM", oldTerm)
}
if config.GetGlobalOption("mouse").(bool) {
Screen.EnableMouse()
}
os.Setenv("TCELLDB", tcelldb)
}

View File

@@ -4,14 +4,6 @@ import (
"bytes"
"io"
"os/exec"
"strings"
luar "layeh.com/gopher-luar"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
)
var Jobs chan JobFunction
@@ -32,7 +24,7 @@ func init() {
// JobFunction is a representation of a job (this data structure is what is loaded
// into the jobs channel)
type JobFunction struct {
Function func(string, ...interface{})
Function func(string, []interface{})
Output string
Args []interface{}
}
@@ -41,7 +33,7 @@ type JobFunction struct {
type CallbackFile struct {
io.Writer
callback func(string, ...interface{})
callback func(string, []interface{})
args []interface{}
}
@@ -55,23 +47,23 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
// JobStart starts a shell command in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
}
// JobSpawn starts a process with args in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
// Set up everything correctly if the functions have been provided
proc := exec.Command(cmdName, cmdArgs...)
var outbuf bytes.Buffer
if onStdout != "" {
proc.Stdout = &CallbackFile{&outbuf, luaFunctionJob(onStdout), userargs}
if onStdout != nil {
proc.Stdout = &CallbackFile{&outbuf, onStdout, userargs}
} else {
proc.Stdout = &outbuf
}
if onStderr != "" {
proc.Stderr = &CallbackFile{&outbuf, luaFunctionJob(onStderr), userargs}
if onStderr != nil {
proc.Stderr = &CallbackFile{&outbuf, onStderr, userargs}
} else {
proc.Stderr = &outbuf
}
@@ -79,7 +71,7 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit strin
go func() {
// Run the process in the background and create the onExit callback
proc.Run()
jobFunc := JobFunction{luaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
jobFunc := JobFunction{onExit, string(outbuf.Bytes()), userargs}
Jobs <- jobFunc
}()
@@ -100,25 +92,3 @@ func JobSend(cmd *exec.Cmd, data string) {
stdin.Write([]byte(data))
}
// 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(fn string) func(string, ...interface{}) {
luaFn := strings.Split(fn, ".")
if len(luaFn) <= 1 {
return nil
}
plName, plFn := luaFn[0], luaFn[1]
pl := config.FindPlugin(plName)
if pl == nil {
return nil
}
return func(output string, args ...interface{}) {
luaArgs := []lua.LValue{luar.New(ulua.L, output), luar.New(ulua.L, args)}
_, err := pl.Call(plFn, luaArgs...)
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}

View File

@@ -2,6 +2,7 @@ package shell
import (
"bytes"
"errors"
"fmt"
"io"
"os"
@@ -9,8 +10,8 @@ import (
"os/signal"
"strings"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/pkg/shellwords"
)
// ExecCommand executes a command using exec
@@ -32,10 +33,13 @@ func ExecCommand(name string, arg ...string) (string, error) {
// RunCommand executes a shell command and returns the output/error
func RunCommand(input string) (string, error) {
args, err := shellwords.Split(input)
args, err := shellquote.Split(input)
if err != nil {
return "", err
}
if len(args) == 0 {
return "", errors.New("No arguments")
}
inputCmd := args[0]
return ExecCommand(inputCmd, args[1:]...)
@@ -45,10 +49,13 @@ func RunCommand(input string) (string, error) {
// It returns a function which will run the command and returns a string
// message result
func RunBackgroundShell(input string) (func() string, error) {
args, err := shellwords.Split(input)
args, err := shellquote.Split(input)
if err != nil {
return nil, err
}
if len(args) == 0 {
return nil, errors.New("No arguments")
}
inputCmd := args[0]
return func() string {
output, err := RunCommand(input)
@@ -68,10 +75,13 @@ func RunBackgroundShell(input string) (func() string, error) {
// RunInteractiveShell runs a shellcommand interactively
func RunInteractiveShell(input string, wait bool, getOutput bool) (string, error) {
args, err := shellwords.Split(input)
args, err := shellquote.Split(input)
if err != nil {
return "", err
}
if len(args) == 0 {
return "", errors.New("No arguments")
}
inputCmd := args[0]
// Shut down the screen because we're going to interact directly with the shell

View File

@@ -2,19 +2,12 @@ package shell
import (
"bytes"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/terminal"
luar "layeh.com/gopher-luar"
)
type TermType int
@@ -76,7 +69,7 @@ func (t *Terminal) GetSelection(width int) string {
}
// Start begins a new command in this terminal with a given view
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback string, userargs []interface{}) error {
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
if len(execCmd) <= 0 {
return nil
}
@@ -95,27 +88,16 @@ func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback s
t.Status = TTRunning
t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid)
t.wait = wait
luaFn := strings.Split(callback, ".")
if len(luaFn) >= 2 {
plName, plFn := luaFn[0], luaFn[1]
pl := config.FindPlugin(plName)
if pl != nil {
t.callback = func(out string) {
luaArgs := []lua.LValue{luar.New(ulua.L, out), luar.New(ulua.L, userargs)}
_, err := pl.Call(plFn, luaArgs...)
if err != nil {
screen.TermMessage(err)
}
}
}
t.callback = func(out string) {
callback(out, userargs)
}
go func() {
for {
err := Term.Parse()
if err != nil {
fmt.Fprintln(os.Stderr, "[Press enter to close]")
Term.Write([]byte("Press enter to close"))
screen.Redraw()
break
}
screen.Redraw()

View File

@@ -4,6 +4,8 @@ import (
"unicode/utf8"
)
// LuaRuneAt is a helper function for lua plugins to return the rune
// at an index within a string
func LuaRuneAt(str string, runeidx int) string {
i := 0
for len(str) > 0 {
@@ -20,6 +22,7 @@ func LuaRuneAt(str string, runeidx int) string {
return ""
}
// LuaGetLeadingWhitespace returns the leading whitespace of a string (used by lua plugins)
func LuaGetLeadingWhitespace(s string) string {
ws := []byte{}
for len(s) > 0 {
@@ -35,6 +38,7 @@ func LuaGetLeadingWhitespace(s string) string {
return string(ws)
}
// LuaIsWordChar returns true if the first rune in a string is a word character
func LuaIsWordChar(s string) bool {
r, _ := utf8.DecodeRuneInString(s)
return IsWordChar(r)

View File

@@ -7,6 +7,7 @@ import (
"os/user"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"time"
@@ -30,6 +31,9 @@ var (
CompileDate = "Unknown"
// Debug logging
Debug = "ON"
// FakeCursor is used to disable the terminal cursor and have micro
// draw its own (enabled for windows consoles where the cursor is slow)
FakeCursor = false
)
func init() {
@@ -38,6 +42,10 @@ func init() {
if err != nil {
fmt.Println("Invalid version: ", Version, err)
}
if runtime.GOOS == "windows" {
FakeCursor = true
}
}
// SliceEnd returns a byte slice where the index is a rune index
@@ -267,7 +275,6 @@ func MakeRelative(path, base string) (string, error) {
return path, nil
}
// TODO: consider changing because of snap segfault
// 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, error) {
@@ -410,3 +417,8 @@ func IsNonAlphaNumeric(c rune) bool {
func ParseSpecial(s string) string {
return strings.Replace(s, "\\t", "\t", -1)
}
// String converts a byte array to a string (for lua plugins)
func String(s []byte) string {
return string(s)
}

View File

@@ -456,9 +456,9 @@ func (n *Node) unsplit(i int, h bool) {
// Unsplit deletes this split and resizes everything
// else accordingly
func (n *Node) Unsplit() {
if !n.IsLeaf() {
return
func (n *Node) Unsplit() bool {
if !n.IsLeaf() || n.parent == nil {
return false
}
ind := 0
for i, c := range n.parent.children {
@@ -473,8 +473,9 @@ func (n *Node) Unsplit() {
}
if n.parent.IsLeaf() {
n.parent.Unsplit()
return n.parent.Unsplit()
}
return true
}
// String returns the string form of the node and all children (used for debugging)

View File

@@ -68,6 +68,8 @@ func combineLineMatch(src, dst LineMatch) LineMatch {
// A State represents the region at the end of a line
type State *region
var EmptyDef = Def{nil, &rules{}}
// LineStates is an interface for a buffer-like object which can also store the states and matches for every line
type LineStates interface {
LineBytes(n int) []byte
@@ -176,7 +178,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
return highlights
}
if lineLen == 0 || statesOnly {
if lineLen == 0 {
if canMatchEnd {
h.lastRegion = curRegion
}
@@ -197,28 +199,32 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
}
}
if firstLoc[0] != lineLen {
highlights[start+firstLoc[0]] = firstRegion.limitGroup
if !statesOnly {
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)
return highlights
}
fullHighlights := make([]Group, lineLen)
for i := 0; i < len(fullHighlights); i++ {
fullHighlights[i] = curRegion.group
}
if !statesOnly {
fullHighlights := make([]Group, lineLen)
for i := 0; i < len(fullHighlights); i++ {
fullHighlights[i] = curRegion.group
}
for _, p := range curRegion.rules.patterns {
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
for _, m := range matches {
for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
for _, p := range curRegion.rules.patterns {
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
for _, m := range matches {
for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
}
}
}
}
for i, h := range fullHighlights {
if i == 0 || h != fullHighlights[i-1] {
highlights[start+i] = h
for i, h := range fullHighlights {
if i == 0 || h != fullHighlights[i-1] {
highlights[start+i] = h
}
}
}
@@ -354,8 +360,9 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
}
// ReHighlightStates will scan down from `startline` and set the appropriate end of line state
// for each line until it comes across the same state in two consecutive lines
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
// for each line until it comes across a line whose state does not change
// returns the number of the final line
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
// lines := input.LineData()
h.lastRegion = nil
@@ -378,9 +385,11 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
input.SetState(i, curState)
if curState == lastState {
break
return i
}
}
return input.LinesNum() - 1
}
// ReHighlightLine will rehighlight the state and match for a single line

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2017 Yasuhiro Matsumoto
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

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

@@ -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,518 +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 strings.HasPrefix(k[1], "0x") {
if u, err := strconv.ParseUint(k[1][2:], 16, 0); err != nil {
return (err)
} else {
tc.nums[k[0]] = int(u)
}
} else 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,838 +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.ReadCloser
if f, e = os.Open(fname); e != nil {
return nil, e
}
defer f.Close()
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

@@ -20,5 +20,5 @@ color-link line-number "246,254"
color-link cursor-line "254"
color-link color-column "254"
#No extended types (bool in C, &c.) and plain brackets
color-link type.extended "default"
color-link symbol.brackets "default"
color-link type.extended "241,231"
color-link symbol.brackets "241,231"

View File

@@ -1,37 +0,0 @@
#CaptainMcClellan's personal color scheme.
#Paper version
color-link default "black,white"
color-link comment "bold black"
color-link constant "cyan"
color-link constant.bool "bold cyan"
color-link constant.bool.true "bold green"
color-link constant.bool.false "bold red"
color-link constant.string "bold yellow"
color-link constant.string.url "underline blue, white"
color-link constant.number "constant"
color-link constant.specialChar "bold magenta"
color-link identifier "bold red"
color-link identifier.macro "bold red"
color-link identifier.var "bold blue"
color-link identifier.class "bold green"
color-link preproc "bold cyan"
color-link statement "bold yellow"
color-link symbol "red"
color-link symbol.brackets "blue"
color-link type "green"
color-link type.keyword "bold green"
color-link special "magenta"
color-link ignore "default"
color-link error ",brightred"
color-link todo "black,brightyellow"
color-link indent-char ",brightgreen"
color-link line-number "green"
color-link line-number.scrollbar "green"
color-link statusline "white,blue"
color-link tabbar "white,blue"
color-link current-line-number "red"
color-link current-line-number.scroller "red"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link color-column "cyan"
color-link underlined.url "underline blue, white"

View File

@@ -15,12 +15,12 @@ color-link todo "bold #D33682,#242424"
color-link statusline "#242424,#CCCCCC"
color-link tabbar "#242424,#CCCCCC"
color-link indent-char "#4F4F4F,#242424"
color-link line-number "#666666,#242424"
color-link line-number "#666666,#2C2C2C"
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

@@ -1,7 +1,6 @@
#Geany
color-link comment "red"
color-link constant "default"
color-link constant.number
color-link constant.string "bold yellow"
color-link identifier "default"
color-link preproc "cyan"
@@ -20,4 +19,4 @@ color-link statusline "black,white"
color-link tabbar "black,white"
color-link color-column "bold geren"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link gutter-warning "red"

View File

@@ -1,24 +0,0 @@
#True color theme based on Github's syntax highlighting.
#Warning, this is based on how it rendered in my Firefox!
#Yours may look different.
color-link comment "bold #969896"
color-link constant "#0086B9"
color-link constant.number "#0086B9"
color-link constant.specialChar "bold #1836BD"
color-link constant.string "bold #1836BD"
color-link constant.bool "#0086B9"
color-link identifier "#A71D5D"
color-link preproc "bold #A71D5D"
color-link special "#A71D5D"
color-link statement "#A71D5D"
color-link symbol "default"
color-link type "#A71D5D"
color-link error "bold ,#E34234"
color-link todo "white"
color-link indent-char "default"
color-link line-number "bold #969896"
color-link current-line-number "bold #969896"
color-link gutter-error "bold ,#E34234"
color-link gutter-warning "bold #f26522"
color-link statusline "bold #c8c9cb,#24292e"
color-link tabbar "bold #c8c9cb,#24292e"

View File

@@ -0,0 +1,22 @@
color-link default "#99D1CE,#0C1014"
color-link comment "#245361,#0C1014"
color-link identifier "#599CAB,#0C1014"
color-link constant "#D26937,#0C1014"
color-link constant.string "#2AA889,#0C1014"
color-link constant.string.char "#D3EBE9,#0C1014"
color-link statement "#599CAB,#0C1014"
color-link preproc "#C23127,#0C1014"
color-link type "#D26937,#0C1014"
color-link special "#D26937,#0C1014"
color-link underlined "#EDB443,#0C1014"
color-link error "bold #C23127,#0C1014"
color-link todo "bold #888CA6,#0C1014"
color-link statusline "#091F2E,#599CAB"
color-link indent-char "#505050,#0C1014"
color-link line-number "#245361,#11151C"
color-link current-line-number "#599CAB,#11151C"
color-link gutter-error "#C23127,#11151C"
color-link gutter-warning "#EDB443,#11151C"
color-link cursor-line "#091F2E"
color-link color-column "#11151C"
color-link symbol "#99D1CE,#0C1014"

View File

@@ -13,8 +13,8 @@ color-link underlined "underline #282828"
color-link error "#9d0006,#282828"
color-link gutter-error "#fb4934,#282828"
color-link gutter-warning "#d79921,#282828"
color-link line-number "#665c54,#282828"
color-link current-line-number "#665c54,#3c3836"
color-link line-number "#665c54,#3c3836"
color-link current-line-number "#d79921,#282828"
color-link cursor-line "#3c3836"
color-link color-column "#79740e"
color-link statusline "#ebdbb2,#665c54"

View File

@@ -12,8 +12,8 @@ color-link underlined "underline 109,235"
color-link error "235,124"
color-link todo "bold 223,235"
color-link line-number "243,237"
color-link current-line-number "172,237"
color-link current-line-number "172,235"
color-link cursor-line "237"
color-link color-column "237"
color-link statusline "223,237"
color-link tabbar "223,237"
color-link tabbar "223,237"

View File

@@ -1,25 +0,0 @@
#A colorscheme based on Code::Blocks IDE
#but with a white background.
color-link default "black,white"
color-link comment "bold black"
color-link constant "blue"
color-link constant.number "bold magenta"
color-link constant.string "bold blue"
color-link identifier "black"
color-link preproc "green"
color-link statement "blue"
color-link symbol "red"
color-link symbol.brackets "blue"
color-link type "blue"
color-link special "magenta"
color-link ignore "default"
color-link error "bold white,brightred"
color-link todo "bold black,brightyellow"
color-link indent-char "bold black"
color-link line-number "black,white"
color-link statusline "white,red"
color-link tabbar "white,red"
color-link current-line-number "red,black"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link color-column "black"

View File

@@ -1,23 +0,0 @@
#Theme based on Code::Blocks IDE's default syntax highlighting.
color-link comment "bold black"
color-link constant "blue"
color-link constant.string "bold blue"
color-link constant.number "bold magenta"
color-link identifier "default"
color-link preproc "green"
color-link statement "blue"
color-link symbol "red"
color-link symbol.brackets "blue"
color-link type "blue"
color-link special "magenta"
color-link ignore "default"
color-link error ",brightred"
color-link todo "bold black,brightyellow"
color-link indent-char "bold black"
color-link line-number "black,white"
color-link statusline "white,red"
color-link tabbar "white,red"
color-link current-line-number "red"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link color-column "white"

View File

@@ -1,31 +0,0 @@
#Funky Cactus theme
color-link comment "bold black"
color-link constant "cyan"
color-link constant.bool "bold cyan"
color-link constant.bool.true "bold green"
color-link constant.bool.false "bold red"
color-link constant.string "yellow"
color-link constant.number "constant"
color-link constant.specialChar "bold magenta"
color-link identifier "bold red"
color-link identifier.macro "bold red"
color-link identifier.var "bold blue"
color-link identifier.class "bold green"
color-link preproc "bold cyan"
color-link statement "bold yellow"
color-link symbol "red"
color-link symbol.brackets "blue"
color-link type "green"
color-link type.keyword "bold green"
color-link special "magenta"
color-link ignore "default"
color-link error "bold ,brightred"
color-link todo "underline ,brightyellow"
color-link indent-char "bold ,brightgreen"
color-link line-number "green"
color-link statusline "black,green"
color-link tabbar "black,magenta"
color-link current-line-number "bold magenta"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link color-column "bold green"

View File

@@ -1,23 +0,0 @@
#Gameboy theme
color-link default "#3f3f3f,#bfc180"
color-link comment "#7d7343"
color-link constant "#7d7343"
color-link identifier "#ddde7d"
color-link preproc "#ddde7d,#7d7343"
color-link special "#7d7343"
color-link statement "#7d7343"
color-link symbol "#7d7343"
color-link type "#7d7343"
color-link error "#ddde7d,#7d7343"
color-link todo "#7d7343,#ddde7d"
color-link statusline "#ddde7d,#7d7343"
color-link tabbar "#ddde7d,#7d7343"
color-link color-column "#7d7343"
color-link line-number "#ddde7d,#7d7343"
color-link current-line-number "#3f3f3f,#bfc180"
color-link gutter-error "#ddde7d,#7d7343"
color-link gutter-warning "default"
#3f3f3f
#7d7343
#bfc180
#ddde76

View File

@@ -1,21 +0,0 @@
#Geany Alternate theme
color-link default "#000000,#fefefe"
color-link comment "#808080"
color-link constant "default"
color-link constant.bool "#003030"
color-link constant.number "#300008"
color-link constant.string "#008000"
color-link identifier "default"
color-link preproc "#bbbb77"
color-link special "#003030"
color-link statement "#003030"
color-link symbol "#300008"
color-link symbol.tag "bold #4e9d71"
color-link type "#003030"
color-link error "#a52a2a"
color-link todo "#ffa500"
color-link line-number "#000000,#d0d0d0"
color-link current-line-number "#000000,#d0d0d0"
color-link color-column "#c2ebc2"
color-link cursor-line "#f0f0f0"
color-link type.extended "default"

View File

@@ -1,24 +0,0 @@
#Theme based on Github's syntax highlighting.
color-link comment "bold black"
color-link constant "cyan"
color-link constant.number "cyan"
color-link constant.specialChar "bold blue"
color-link constant.string "bold blue"
color-link constant.bool "cyan"
color-link identifier "magenta"
color-link preproc "bold magenta"
color-link special "magenta"
color-link statement "magenta"
color-link symbol "default"
color-link type "magenta"
color-link error "bold ,brightred"
color-link todo "white"
color-link indent-char "default"
color-link line-number "bold black"
color-link current-line-number "bold black"
color-link gutter-error ",red"
color-link gutter-warning "bold yellow"
color-link statusline "bold white,black"
color-link tabbar "bold white,black"
#Plain brackets.
#color-link symbol.brackets "default"

View File

@@ -1,25 +0,0 @@
#Midnight Commander inspired theme.
color-link default "white,blue"
color-link comment "bold black"
color-link constant "bold white"
color-link constant.string "bold yellow"
color-link identifier "bold red"
color-link statement "bold cyan"
color-link symbol "white"
color-link symbol.brackets "white"
color-link symbol.tag "bold green"
color-link preproc "black,cyan"
color-link type "green"
color-link special "magenta"
color-link ignore "default"
color-link error ",brightred"
color-link todo ",brightyellow"
color-link indent-char ",cyan"
color-link line-number "green"
color-link statusline "black,cyan"
color-link tabbar "black,cyan"
color-link current-line-number "black,cyan"
color-link cursor-line "black,cyan"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link color-column "cyan"

View File

@@ -1,5 +0,0 @@
#Monochrome Paper theme.
#Edit your files on a white background without colors.
color-link default "black,white"
color-link statusline "white,black"
color-link tabbar "white,black"

View File

@@ -1,3 +0,0 @@
#Monochrome
#This makes micro use only the terminal's default
# foreground and background colours.

View File

@@ -1,30 +0,0 @@
#Colorscheme styled after default Debian nano.
color-link comment "bold blue"
color-link comment.bright "cyan"
color-link constant "red"
color-link constant.bool "yellow"
color-link constant.bool.true "bold green"
color-link constant.bool.false "bold red"
color-link constant.number "default"
color-link constant.specialChar "bold magenta"
color-link constant.string "bold yellow"
color-link identifier "bold blue"
color-link identifier.macro "bold red"
color-link statement "bold green"
color-link symbol "green"
#color-link symbol.tag "blue"
color-link preproc "brightcyan"
color-link type "green"
color-link special "magenta"
color-link ignore "default"
color-link error "white,black"
color-link todo "bold cyan"
color-link indent-char ",green"
color-link line-number "default"
color-link current-line-number "default"
color-link gutter-error ",white"
color-link gutter-warning "white"
color-link cursor-line "default"
color-link color-column "white"
#No extended types ( bool in C ); Plain brackets
color-link type.extended "default"

View File

@@ -1,22 +0,0 @@
#Paper theme, true color edition
#Edit on an *actual* white background!
color-link default "#000000,#efefef"
color-link comment ""
color-link constant ""
color-link constant.string ""
color-link constant.string.url "underline #0000dd"
color-link identifier ""
color-link identifier.var ""
color-link special ""
color-link statement ""
color-link symbol ""
color-link symbol.brackets ""
color-link symbol.tag ""
color-link type ""
color-link statusline ""
color-link tabbar ""
color-link error ""
color-link todo ""
color-link color-column ""
color-link gutter-error ""
color-link gutter-warning ""

View File

@@ -1,27 +0,0 @@
#Paper theme, Edit on a white background.
color-link default "black,white"
color-link comment "bold black"
color-link constant "cyan"
color-link constant.string "bold green"
color-link identifier "blue"
color-link identifier.macro "bold red"
color-link identifier.var "bold blue"
color-link identifier.class "bold green"
color-link statement "green"
color-link symbol "red"
color-link symbol.brackets "default"
color-link symbol.tag "bold blue"
color-link preproc "bold cyan"
color-link type "green"
color-link special "magenta"
color-link ignore "default"
color-link error ",brightred"
color-link todo ",brightyellow"
color-link indent-char ",brightgreen"
color-link line-number "black"
color-link statusline "white,black"
color-link tabbar "white,black"
color-link current-line-number "blue"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link color-column "black"

View File

@@ -1,23 +0,0 @@
#Symbian
color-link default "#000000,#ff8a00"
color-link comment "#8c0000"
color-link constant "#8c0000"
color-link identifier "#ffff8c"
color-link preproc "#ffff8c,#8c0000"
color-link special "#8c0000"
color-link statement "#8c0000"
color-link symbol "#8c0000"
color-link type "#8c0000"
color-link error "#ffff8c,#8c0000"
color-link todo "#8c0000,#ffff8c"
color-link statusline "#ffff8c,#8c0000"
color-link tabbar "#ffff8c,#8c0000"
color-link color-column "#8c0000"
color-link line-number "#ffff8c,#8c0000"
color-link current-line-number "#000000,#ff8a00"
color-link gutter-error "#ffff8c,#8c0000"
color-link gutter-warning "default"
#000000
#8c0000
#ff8a00
#ffff8c

View File

@@ -5,7 +5,7 @@ color-link constant.number "#F78C6A,#263238"
color-link constant.specialChar "#89DDF3,#263238"
color-link constant.string "#C3E88D,#263238"
color-link current-line-number "#80DEEA,#263238"
color-link cursor-line "#3b4d56"
color-link cursor-line "#283942"
color-link default "#EEFFFF,#263238"
color-link divider "#263238,#80DEEA"
color-link error "bold #263238,#F07178"
@@ -14,7 +14,7 @@ color-link gutter-warning "#EEFFFF,#FFF176"
color-link identifier "#82AAFF,#263238"
color-link identifier.macro "#FFCB6B,#263238"
color-link indent-char "#505050,#263238"
color-link line-number "#656866,#263238"
color-link line-number "#656866,#283942"
color-link preproc "#C792EA,#263238"
color-link special "#C792EA,#263238"
color-link statement "#C792EA,#263238"

View File

@@ -0,0 +1,21 @@
color-link default "#D5D8D6,#1D0000"
color-link comment "#75715E"
color-link identifier "#66D9EF"
color-link constant "#AE81FF"
color-link constant.string "#E6DB74"
color-link constant.string.char "#BDE6AD"
color-link statement "#F92672"
color-link preproc "#CB4B16"
color-link type "#66D9EF"
color-link special "#A6E22E"
color-link underlined "#D33682"
color-link error "bold #CB4B16"
color-link todo "bold #D33682"
color-link statusline "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"
color-link line-number "#AAAAAA,#282828"
color-link current-line-number "#AAAAAA,#1D0000"
color-link gutter-error "#CB4B16"
color-link gutter-warning "#E6DB74"
color-link cursor-line "#323232"
color-link color-column "#323232"

View File

@@ -0,0 +1,32 @@
color-link default "#ABB2BF,#21252C"
color-link color-column "#282C34"
color-link comment "#5C6370"
color-link constant "#C678DD"
color-link constant.number "#E5C07B"
color-link constant.string "#98C379"
color-link constant.string.char "#BDE6AD"
color-link constant.specialChar "#DDF2A4"
color-link current-line-number "#C6C6C6,#21252C"
color-link cursor-line "#282C34"
color-link divider "#1E1E1E"
color-link error "#D2A8A1"
color-link gutter-error "#9B859D"
color-link gutter-warning "#9B859D"
color-link identifier "#61AFEF"
color-link identifier.class "#C678DD"
color-link identifier.var "#C678DD"
color-link indent-char "#515151"
color-link line-number "#636D83,#282C34"
color-link preproc "#E0C589"
color-link special "#E0C589"
color-link statement "#C678DD"
color-link statusline "#282828,#ABB2BF"
color-link symbol "#AC885B"
color-link symbol.brackets "#ABB2BF"
color-link symbol.operator "#C678DD"
color-link symbol.tag "#AC885B"
color-link tabbar "#F2F0EC,#2D2D2D"
color-link todo "#8B98AB"
color-link type "#66D9EF"
color-link type.keyword "#C678DD"
color-link underlined "#8996A8"

View File

@@ -0,0 +1,22 @@
color-link default "0,230"
color-link comment "244"
color-link constant.string "17"
color-link constant "88"
color-link identifier "22"
color-link statement "0,230"
color-link symbol "89"
color-link preproc "22"
color-link type "88"
color-link special "22"
color-link underlined "61,230"
color-link error "88"
color-link todo "210"
color-link statusline "233,229"
color-link tabbar "233,229"
color-link indent-char "229"
color-link line-number "244"
color-link gutter-error "88"
color-link gutter-warning "88"
color-link cursor-line "229"
#color-link color-column "196"
color-link current-line-number "246"

View File

@@ -2,37 +2,57 @@
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.
* 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
To change your colorscheme, press CtrlE in micro to bring up the command
prompt, and type:
```
set colorscheme monokai
set colorscheme twilight
```
(or whichever colorscheme you choose).
Micro comes with a number of colorschemes by default. Modern terminals tend to
have three different kinds of color support. The most common is 256 color where
the terminal provides 256 standardized colors (except the first 16 may be configured
by the user). A 256-color theme requires a terminal with 256 color support and
is the most portable.
Micro comes with a number of colorschemes by default. The colorschemes that you
can display will depend on what kind of color support your terminal has.
A 16-color theme uses the 16 user-configurable colors (or 16 default colors on
old terminals). These colorschemes are guranteed to work, but won't look great
unless the 16 colors are configured to the user's liking. Using a 16-color theme
will also preserve the terminal's theme because the terminal usually uses its 16
colors for prompts or other coloring.
Modern terminals tend to have a palette of 16 user-configurable colors (these
colors can often be configured in the terminal preferences), and additional
color support comes in three flavors.
Some terminals support "true color" with 16 million colors (using standard RGB values).
There is no one standard for this color support among terminals so this method
is not guaranteed to work. Usually truecolor must also be enabled by the user. The
colorschemes using true color will look exactly as intended. If true color is not
supported, a true color colorscheme will approximate its colors to 256-color.
* 16-color: A colorscheme that uses the 16 default colors will always work but
will only look good if the 16 default colors have been configured to the
user's liking. Using a colorscheme that only uses the 16 colors from the
terminal palette will also preserve the terminal's theme from other
applications since the terminal will often use those same colors for other
applications. Default colorschemes of this type include `simple` and
`solarized`.
* 256-color: Almost all terminals support displaying an additional 240 colors
on top of the 16 user-configurable colors (creating 256 colors total).
Colorschemes which use 256-color are portable because they will look the
same regardless of the configured 16-color palette. However, the color
range is fairly limited due to the small number of colors available.
Default 256-color colorschemes include `monokai`, `twilight`, `zenburn`,
`darcula` and more.
* true-color: Some terminals support displaying "true color" with 16 million
colors using standard RGB values. This mode will be able to support
displaying any colorscheme, but it should be noted that the user-configured
16-color palette is ignored when using true-color mode (this means the
colors while using the terminal emulator will be slightly off). Not all
terminals support true color but at this point most do. True color
support in micro is off by default but can be enabled by setting the
environment variable `MICRO_TRUECOLOR` to 1. In addition your terminal
must support it (usually indicated by setting `$COLORTERM` to `truecolor`).
True-color colorschemes in micro typically end with `-tc`, such as
`solarized-tc`, `atom-dark-tc`, `material-tc`, etc... If true color is not
enabled but a true color colorscheme is used, micro will do its best to
approximate the colors to the available 256 colors.
Here is the list of colorschemes:
@@ -54,14 +74,18 @@ themes the most.
These may vary widely based on the 16 colors selected for your terminal.
* `simple`
* `solarized` (must have the solarized color palette in your terminal to use this colorscheme properly)
* `solarized` (must have the solarized color palette in your terminal to use
this colorscheme properly)
* `cmc-16`
* `cmc-paper`
* `geany`
### True color
These require terminals that support true color and require `MICRO_TRUECOLOR=1` (this is an environment variable).
True color requires your terminal to support it. This means that the
environment variable `COLORTERM` should have the value `truecolor`, `24bit`,
or `24-bit`. In addition, to enable true color in micro, the environment
variable `MICRO_TRUECOLOR` must be set to 1.
* `solarized-tc`: this is the solarized colorscheme for true color.
* `atom-dark-tc`: this colorscheme is based off of Atom's "dark" colorscheme.
@@ -73,11 +97,16 @@ These require terminals that support true color and require `MICRO_TRUECOLOR=1`
## Creating a Colorscheme
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).
be found
[here](https://github.com/zyedidia/micro/tree/master/runtime/colorschemes).
They are only about 18-30 lines in total.
Custom colorschemes should be placed in the `~/.config/micro/colorschemes`
directory.
Basically to create the colorscheme you need to link highlight groups with
A number of custom directives are placed in a `.micro` file. Colorschemes are
typically only 18-30 lines in total.
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:
@@ -171,9 +200,9 @@ 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`.
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.
@@ -206,9 +235,9 @@ 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.
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.
### Filetype definition
@@ -218,7 +247,7 @@ You must start the syntax file by declaring the filetype:
filetype: go
```
#### Detect definition
### Detect definition
Then you must provide information about how to detect the filetype:
@@ -227,9 +256,9 @@ 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,11 +266,11 @@ detect:
header: "%YAML"
```
#### Syntax rules
### 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
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:
@@ -316,3 +345,16 @@ example, the following is possible for html:
rules:
- include: "css"
```
## Syntax file headers
Syntax file headers are an optimization and it is likely you do not need to
worry about them.
Syntax file headers are files that contain only the filetype and the detection
regular expressions for a given syntax file. They have a `.hdr` suffix and are
used by default only for the pre-installed syntax files. Header files allow
micro to parse the syntax files much faster when checking the filetype of a
certain file. Custom syntax files may provide header files in
`~/.config/micro/syntax` as well but it is not necessary (only do this if you
have many (100+) custom syntax files and want to improve performance).

View File

@@ -1,14 +1,35 @@
# Possible commands
# Command bar
You can execute an editor command by pressing `Ctrl-e` followed by the command.
Here are the possible commands that you can use.
The command bar is opened by pressing CtrlE. It is a single-line buffer,
meaning that all keybindings from a normal buffer are supported (as well
as mouse and selection).
* `quit`: Quits micro.
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, put it in
quotes. The command bar parser uses the same rules for parsing arguments that
`/bin/sh` would use (single quotes, double quotes, escaping). The command bar
does not look up environment variables.
* `save filename?`: Saves the current buffer. If the filename is provided it
will 'save as' the filename.
# Commands
* `replace "search" "value" flags`: This will replace `search` with `value`.
Micro provides the following commands that can be executed at the command-bar
by pressing `CtrlE` and entering the command. Arguments are placed in single
quotes here but these are not necessary when entering the command in micro.
* `bind 'key' 'action'`: creates a keybinding from key to action. See the
`keybindings` documentation for more information about binding keys.
This command will modify `bindings.json` and overwrite any bindings to
`key` that already exist.
* `help 'topic'?`: opens the corresponding help topic. If no topic is provided
opens the default help screen.
* `save 'filename'?`: saves the current buffer. If the file is provided it
will 'save as' the filename.
* `quit`: quits micro.
* `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
@@ -16,84 +37,82 @@ Here are the possible commands that you can use.
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.
* `replaceall "search" "value"`: This will replace `search` with `value` without
user confirmation.
* `replaceall 'search' 'value'`: this will replace all occurrences of `search`
with `value` without user confirmation.
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. This will modify your
`settings.json` with the new value.
* `setlocal option value`: sets the option to value locally (only in the current
buffer).
* `setlocal 'option' 'value'`: sets the option to value locally (only in the
current buffer). This will *not* modify `settings.json`.
* `show option`: shows the current value of the given option.
* `show 'option'`: shows the current value of the given option.
* `run sh-command`: runs the given shell command in the background. The
* `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.
* `vsplit 'filename'`: opens a vertical split with `filename`. If no filename
is provided, a vertical split is opened with an empty buffer.
* `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.
* `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.
* `textfilter 'sh-command'`: filters the current selection through a shell
command as standard input and replaces the selection with the stdout of
the shell command. For example, to sort a list of numbers, first select
them, and then execute `> textfilter sort -n`.
* `textfilter sh-command`: filters the current selection through a shell command
as standard input and replaces the selection with the stdout of the shell command.
For example, to sort a list of numbers, first select them, and then execute
`> textfilter sort -n`.
* `log`: opens a log of all messages and debug statements.
* `plugin list`: lists all installed plugins.
* `plugin version pl`: shows version for specified plugin.
* `plugin install 'pl'`: install a plugin.
* `plugin info pl`: shows additional info for specified plugin.
* `plugin remove 'pl'`: remove a plugin.
* `plugin update 'pl'`: update a plugin (if no arguments are provided
updates all plugins).
* `plugin search 'pl'`: search available plugins for a keyword.
* `plugin available`: show available plugins that can be installed.
* `reload`: reloads all runtime files.
* `cd path`: Change the working directory to the given `path`.
* `cd 'path'`: Change the working directory to the given `path`.
* `pwd`: Print the current working directory.
* `open filename`: Open a file in the current buffer.
* `open 'filename'`: Open a file in the current buffer.
* `reset option`: resets the given option to its default value
* `reset 'option'`: resets the given option to its default value
* `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
* `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."
running `> showkey CtrlC` will display `Copy`.
* `term exec?`: Open a terminal emulator running the given executable. If no
executable is given, this will open the default shell in the terminal
emulator.
---
The following commands are provided by the default plugins:
* `lint`: Lint the current file for errors.
* `comment`: automatically comment or uncomment current selection or line.
# 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.

109
runtime/help/copypaste.md Normal file
View File

@@ -0,0 +1,109 @@
Copy and paste are essential features in micro but can be
confusing to get right especially when running micro over SSH
because there are multiple methods. This help document will explain
the various methods for copying and pasting, how they work,
and the best methods for doing so over SSH.
# Pasting
## Micro paste events
Micro is an application that runs within the terminal. This means
that the terminal sends micro events, such as key events, mouse
events, resize events, and paste events. Micro's default keybinding
for paste is Ctrl-v. This means that when micro receives the key
event saying Ctrl-v has been pressed from the terminal, it will
attempt to access the system clipboard and effect a paste. The
system clipboard will be accessed through `pbpaste` on MacOS
(installed by default), `xclip` or `xsel` on Linux (these
applications must be installed by the user) or a system call on
Windows.
## Terminal paste events
For certain keypresses, the terminal will not send an event to
micro and will instead do something itself. In this document,
such keypresses will be called "terminal keybindings." Often
there will be a terminal keybinding for pasting and copying. On
MacOS these are Command-v and Command-c and on Linux Ctrl-Shift-v
and Ctrl-Shift-c. When the terminal keybinding for paste is
executed, your terminal will access the system clipboard, and send
micro either a paste event or a list of key events (one key for each
character in the paste), depending on whether or not your terminal
supports sending paste events (called bracketed paste).
If your terminal supports bracketed paste, then it will send a paste
event and everything will work well. However, if your terminal
sends a list of key events, this can cause issues because micro
will think you manually entered each character and may add closing
brackets or automatic indentation, which will mess up the pasted
text. To avoid this, you can temporarily enable the `paste` option
while you perform the paste. When paste option is on, micro will
aggregate lists of multiple key events into larger paste events.
It is a good idea to disable the `paste` option during normal use
as occasionally if you are typing quickly, the terminal will send
the key events as lists of characters that were in fact manually
entered.
## Pasting over SSH
When working over SSH, micro is running on the remote machine and
your terminal is running on your local machine. Therefore if you
would like to paste, using Ctrl-v (micro's keybinding) will not
work because when micro attempts to access the system clipboard,
it will access the remote machine's clipboard rather than the local
machine's clipboard. On the other hand, the terminal keybinding
for paste will access your local clipboard and send the text over
the network as a paste event, which is what you want.
## Recommendations
The recommended method of pasting is the following:
* If you are not working over SSH, use the micro keybinding (Ctrl-v
by default) to perform pastes. If on Linux, install `xclip` or
`xsel` beforehand.
* If you are working over SSH, use the terminal keybinding
(Ctrl-Shift-v or Command-v) to perform pastes. If your terminal
does not support bracketed paste, when performing a paste first
enable the `paste` option, and when finished disable the option.
# Copying
Copying follows a similar discussion to the one above about pasting.
The primary difference is before performing a copy, the application
doing the copy must be told what text needs to be copied.
Micro has a keybinding (Ctrl-c) for copying and will access the system
clipboard to perform the copy. The text that micro will copy into is
the text that is currently selected in micro (usually such text is
displayed with a white background). When the `mouse` option is enabled,
the mouse can be used to select text, as well as other keybindings,
such as ShiftLeft, etc...
The terminal also has a keybinding (Ctrl-Shift-c or Command-c) to perform
a copy, and the text that it copies is the text selected by the terminal's
selection (*not* micro's selection). To select text with the terminal
selection, micro's mouse support must first be disabled by turning the
`mouse` option off. The terminal, unlike micro, has no sense of different
buffers/splits and what the different characters being displayed are. This
means that for copying multiple lines using the terminal selection, you
should first disable line numbers (turn off the `ruler` option), otherwise
they might be part of your selection and copied.
## Recommendations
The recommended method of copying is the following:
* If you are not working over SSH, use the micro keybinding (Ctrl-c by
default) to perform copies. If on Linux, install `xclip` or `xsel`
beforehand.
* If you are working over SSH, use the terminal keybinding
(Ctrl-Shift-c or Command-c) to perform copies. You must first disable
the `mouse` option to perform a terminal selection, and you may wish
to disable line numbers (`ruler` option) and close other splits. This
method will only be able to copy characters that are displayed on the
screen (you will not be able to copy more than one page's worth of
characters).

View File

@@ -17,22 +17,24 @@ can change it!
### Navigation
| Key | Description of function |
|--------------------------- |------------------------------------------------------------------------------------------ |
| Arrows | Move the cursor around |
| Shift+arrows | Move and select text |
| Home or Ctrl+LeftArrow | Move to the beginning of the current line |
| End or Ctrl+RightArrow | Move to the end of the current line |
| Alt+LeftArrow | Move cursor one word left |
| Alt+RightArrow | 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 |
| Ctrl+Home or Ctrl+UpArrow | Move cursor to start of document |
| Ctrl+End or Ctrl+DownArrow | Move cursor to end of document |
| 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) |
| Key | Description of function |
|---------------------------- |------------------------------------------------------------------------------------------ |
| Arrows | Move the cursor around |
| Shift+arrows | Move and select text |
| Alt(Ctrl on Mac)+LeftArrow | Move to the beginning of the current line |
| Alt(Ctrl on Mac)+RightArrow | Move to the end of the current line |
| Home | Move to the beginning of the current line |
| End | Move to the end of the current line |
| Ctrl(Alt on Mac)+LeftArrow | Move cursor one word left |
| Ctrl(Alt on Mac)+RightArrow | Move cursor one word right |
| 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 |
| Ctrl+Home or Ctrl+UpArrow | Move cursor to start of document |
| Ctrl+End or Ctrl+DownArrow | Move cursor to end of document |
| 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
@@ -62,10 +64,12 @@ can change it!
| Key | Description of function |
|------------------------------------ |------------------------------------------ |
| Alt+Shift+RightArrow | Select word right |
| Alt+Shift+LeftArrow | Select word left |
| Shift+Home or Ctrl+Shift+LeftArrow | Select to start of current line |
| Shift+End or Ctrl+Shift+RightArrow | Select to end of current line |
| Ctrl(Alt on Mac)+Shift+RightArrow | Select word right |
| Ctrl(Alt on Mac)+Shift+LeftArrow | Select word left |
| Alt(Ctrl on Mac)+Shift+LeftArrow | Select to start of current line |
| Alt(Ctrl on Mac)+Shift+RightArrow | Select to end of current line |
| Shift+Home | Select to start of current line |
| Shift+End | Select to end of current line |
| Ctrl+Shift+UpArrow | Select to start of file |
| Ctrl+Shift+DownArrow | Select to end of file |
| Ctrl+X | Cut selected text |
@@ -92,6 +96,8 @@ can change it!
| Key | Description of function |
|------------------ |---------------------------------------------------------------------------------------------- |
| Alt+N | Create new multiple cursor from selection (will select current word if no current selection) |
| AltShiftUp | Spawn a new cursor on the line above the current one |
| AltShiftDown | Spawn a new cursor on the line below the current one |
| Alt+P | Remove latest multiple cursor |
| Alt+C | Remove all multiple cursors (cancel) |
| Alt+X | Skip multiple cursor selection |

View File

@@ -1,36 +1,38 @@
# Micro help text
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.
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.
To open the command bar, press CtrlE. This enables a `>` prompt for typing
commands. From now on when the documentation says to run a command such as
`> help`, this means press CtrlE and type `help` (and press enter to execute
the command).
commands. From now on when the documentation says to run a command such as `>
help`, this means press CtrlE and type `help` (and press enter to execute the
command).
For a list of the default keybindings run `> help defaultkeys`.
For more information on keybindings see `> help keybindings`.
## 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`.
Press Ctrl-q to quit, and Ctrl-s 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`.
Move the cursor around with the mouse or the arrow keys. Run
`> 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`.
`> set colorscheme ...`. You can press tab to see the available colorschemes,
or see more information about colorschemes and syntax highlighting with `> help
colors`.
Press CtrlW to move between splits, and type `> vsplit filename` or
Press Ctrl-w to move between splits, and type `> vsplit filename` or
`> hsplit filename` to open a new split.
## Accessing more help
Micro has a built-in help system much like Vim's (although less extensive).
Micro has a built-in help system which can be accessed with the `help` command.
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.
@@ -41,14 +43,14 @@ Here are the possible help topics that you can read:
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.
* 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
* 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 run `> help plugins`.

View File

@@ -14,16 +14,16 @@ at the end of this document).
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
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
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.
@@ -58,9 +58,9 @@ bindings, tab is bound as
"Tab": "Autocomplete|IndentSelection|InsertTab"
```
This means that if the `Autocomplete` action is successful, the chain will abort.
Otherwise, it will try `IndentSelection`, and if that fails too, it will
execute `InsertTab`.
This means that if the `Autocomplete` action is successful, the chain will
abort. Otherwise, it will try `IndentSelection`, and if that fails too, it
will execute `InsertTab`.
## Binding commands
@@ -87,8 +87,9 @@ you could rebind `CtrlG` to `> 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).
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
@@ -103,15 +104,15 @@ 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`.
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`:
sequences to actions. For example, to bind `CtrlBackspace` you can instruct
your terminal to send `\x1bctrlback` and then bind it in `bindings.json`:
```json
{
@@ -123,9 +124,9 @@ 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
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
@@ -230,6 +231,8 @@ Suspend (Unix only)
ScrollUp
ScrollDown
SpawnMultiCursor
SpawnMultiCursorUp
SpawnMultiCursorDown
SpawnMultiCursorSelect
RemoveMultiCursor
RemoveAllMultiCursors
@@ -390,6 +393,10 @@ MouseWheelRight
# Default keybinding configuration.
A select few keybindings are different on MacOS compared to other
operating systems. This is because different OSes have different
conventions for text editing defaults.
```json
{
"Up": "CursorUp",
@@ -404,13 +411,19 @@ MouseWheelRight
"AltRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfLine",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfLine",
"CtrlShiftRight": "SelectWordRight",
"CtrlShiftLeft": "SelectWordLeft",
"AltLeft": "StartOfLine",
"AltRight": "EndOfLine",
"AltShiftRight": "SelectWordRight", (Mac)
"AltShiftLeft": "SelectWordLeft", (Mac)
"CtrlLeft": "StartOfText", (Mac)
"CtrlRight": "EndOfLine", (Mac)
"AltShiftLeft": "SelectToStartOfLine",
"CtrlShiftLeft": "SelectToStartOfText", (Mac)
"ShiftHome": "SelectToStartOfLine",
"CtrlShiftRight": "SelectToEndOfLine",
"AltShiftRight": "SelectToEndOfLine",
"CtrlShiftRight": "SelectToEndOfLine", (Mac)
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
@@ -483,11 +496,13 @@ MouseWheelRight
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor"
"Alt-n": "SpawnMultiCursor",
"AltShiftUp": "SpawnMultiCursorUp",
"AltShiftDown": "SpawnMultiCursorDown",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
```
@@ -502,8 +517,8 @@ Additionally, alt keys can be bound by using `Alt-key`. For example `Alt-a` or
This is why in the default keybindings you can see `AltShiftLeft` instead of
`Alt-ShiftLeft` (they are equivalent).
Please note that terminal emulators are strange applications and micro only receives
key events that the terminal decides to send. Some terminal emulators may not
send certain events even if this document says micro can receive the event. To see
exactly what micro receives from the terminal when you press a key, run the `> raw`
command.
Please note that terminal emulators are strange applications and micro only
receives key events that the terminal decides to send. Some terminal emulators
may not send certain events even if this document says micro can receive the
event. To see exactly what micro receives from the terminal when you press a
key, run the `> raw` command.

View File

@@ -2,11 +2,14 @@
Micro stores all of the user configuration in its configuration directory.
Micro uses the `$XDG_CONFIG_HOME/micro` as the configuration directory. As per
the XDG spec, if `$XDG_CONFIG_HOME` is not set, `~/.config/micro` is used as
the config directory.
Micro uses `$MICRO_CONFIG_HOME` as the configuration directory. If this
environment variable is not set, it uses `$XDG_CONFIG_HOME/micro` instead. If
that environment variable is not set, it uses `~/.config/micro` as the
configuration directory. In the documentation, we use `~/.config/micro` to
refer to the configuration directory (even if it may in fact be somewhere else
if you have set either of the above environment variables).
Here are the options that you can set:
Here are the available options:
* `autoindent`: when creating a new line, use the same indentation as the
previous line.
@@ -16,11 +19,12 @@ Here are the options that you can set:
* `backup`: micro will automatically keep backups of all open buffers. Backups
are stored in `~/.config/micro/backups` and are removed when the buffer is
closed cleanly. In the case of a system crash or a micro crash, the contents
of the buffer can be recovered automatically by opening the file that
was being edited before the crash, or manually by searching for the backup
in the backup directory. Backups are made in the background when a buffer is
of the buffer can be recovered automatically by opening the file that was
being edited before the crash, or manually by searching for the backup in
the backup directory. Backups are made in the background when a buffer is
modified and the latest backup is more than 8 seconds old, or when micro
detects a crash. It is highly recommended that you leave this feature enabled.
detects a crash. It is highly recommended that you leave this feature
enabled.
default value: `true`
@@ -40,8 +44,9 @@ Here are the options that you can set:
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.
Note that the default colorschemes (default, solarized, and solarized-tc)
are not located in configDir, because they are embedded in the micro
binary.
The colorscheme can be selected from all the files in the
~/.config/micro/colorschemes/ directory. Micro comes by default with three
@@ -64,32 +69,32 @@ Here are the options that you can set:
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
* `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
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` (linefeed) whereas dos line endings are
`\r\n` (carriage return + linefeed). The two possible values for this option
are `unix` and `dos`. The fileformat will be automatically detected (when you
open an existing file) 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.
* `fileformat`: this determines what kind of line endings micro will use for
the file. UNIX line endings are just `\n` (linefeed) whereas dos line
endings are `\r\n` (carriage return + linefeed). The two possible values for
this option are `unix` and `dos`. The fileformat will be automatically
detected (when you open an existing file) 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`.
* `filetype`: sets the filetype for the current buffer. Set this option to
`off` to completely disable filetype detection.
default value: this will be automatically set depending on the file you have
open
default value: `unknown`. This will be automatically overridden depending
on the file you open.
* `ignorecase`: perform case-insensitive searches.
@@ -106,8 +111,9 @@ Here are the options that you can set:
* `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 to remove trailing whitespace.
By default, the autoindent whitespace is deleted if the line was left empty.
the whitespace that was added should be deleted to remove trailing
whitespace. By default, the autoindent whitespace is deleted if the line
was left empty.
default value: `false`
@@ -122,13 +128,13 @@ Here are the options that you can set:
default value: `true`
* `mkparents`: if a file is opened on a path that does not exist, the file cannot
be saved because the parent directories don't exist. This option lets micro
automatically create the parent directories in such a situation.
* `mkparents`: if a file is opened on a path that does not exist, the file
cannot be saved because the parent directories don't exist. This option lets
micro automatically create the parent directories in such a situation.
default value: `false`
* `mouse`: whether to enable mouse support. When mouse support is disabled,
* `mouse`: 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
@@ -136,7 +142,22 @@ Here are the options that you can set:
default value: `true`
* `rmtrailingws`: micro will automatically trim trailing whitespaces at eol.
* `paste`: Treat characters sent from the terminal in a single chunk as a paste
event rather than a series of manual key presses. If you are pasting using
the terminal keybinding (not Ctrl-v, which is micro's default paste
keybinding) then it is a good idea to enable this option during the paste
and disable once the paste is over. See `> help copypaste` for details about
copying and pasting in a terminal environment.
default value: `false`
* `readonly`: when enabled, disallows edits to the buffer. It is recommended
to only ever set this option locally using `setlocal`.
default value: `false`
* `rmtrailingws`: micro will automatically trim trailing whitespaces at ends of
lines.
default value: `false`
@@ -145,17 +166,19 @@ Here are the options that you can set:
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.
put it there when you open the file again. Information is saved to
`~/.config/micro/buffers/`
default value: `false`
* `savehistory`: remember command history between closing and re-opening
micro.
micro. Information is saved to `~/.config/micro/buffers/history`.
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.
so if you close and reopen a file, you can keep undoing. Information is
saved to `~/.config/micro/buffers/`.
default value: `false`
@@ -163,8 +186,8 @@ Here are the options that you can set:
default value: `false`
* `scrollmargin`: amount of lines you would like to see above and below the
cursor.
* `scrollmargin`: margin at which the view starts scrolling when the cursor
approaches the edge of the view.
default value: `3`
@@ -172,23 +195,23 @@ Here are the options that you can set:
default value: `2`
* `smartpaste`: should micro add leading whitespace when pasting multiple lines?
* `smartpaste`: add leading whitespace when pasting multiple lines.
This will attempt to preserve the current indentation level when pasting an
unindented block.
default value: `true`
* `softwrap`: should micro wrap lines that are too long to fit on the screen.
* `softwrap`: 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?
* `splitbottom`: when a horizontal split is created, create it 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?
* `splitright`: when a vertical split is created, create it to the right of the
current split.
default value: `true`
@@ -198,14 +221,13 @@ Here are the options that you can set:
The `opt` and `bind` directives take either an option or an action afterward
and fill in the value of the option or the key bound to the action.
default value: `$(filename) $(modified)($(line),$(col)) $(opt:filetype)
$(opt:fileformat) $(opt:encoding)`
default value: `$(filename) $(modified)($(line),$(col)) $(status.paste)|
ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)`
* `statusformatl`: format string definition for the left-justified part of the
* `statusformatr`: format string definition for the right-justified part of the
statusline.
default value: `$(bind:ToggleKeyMenu): show bindings, $(bind:ToggleHelp):
toggle help`
default value: `$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help`
* `statusline`: display the status line at the bottom of the screen.
@@ -217,7 +239,7 @@ Here are the options that you can set:
default value: `sudo`
* `syntax`: turns syntax on or off.
* `syntax`: enables syntax highlighting.
default value: `true`
@@ -227,31 +249,26 @@ Here are the options that you can set:
default value: `false`
* `tabsize`: sets the tab size to `option`
* `tabsize`: the size in spaces that a tab character should be displayed with.
default value: `4`
* `tabstospaces`: use spaces instead of tabs
* `tabstospaces`: use spaces instead of tabs.
default value: `false`
* `termtitle`: defines whether or not your terminal's title will be set by micro
when opened.
default value: `false`
* `useprimary` (only useful on *nix): defines whether or not micro will use the
* `useprimary` (only useful on unix): 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.
the normal clipboard using Ctrl-c and Ctrl-v.
default value: `true`
---
Plugin options: all plugins come with a special option to enable or disable them. THe option
is a boolean with the same name as the plugin itself.
Plugin options: all plugins come with a special option to enable or disable
them. The option is a boolean with the same name as the plugin itself.
Any option you set in the editor will be saved to the file
Any option you set in the editor will be saved to the file
~/.config/micro/settings.json so, in effect, your configuration file will be
created for you. If you'd like to take your configuration with you to another
machine, simply copy the settings.json to the other machine.
@@ -261,14 +278,15 @@ machine, simply copy the settings.json to the other machine.
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.
will set the option in all buffers. Use the `setlocal` command to set an option
locally rather than globally.
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
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:
```json
{

View File

@@ -9,12 +9,36 @@ plugin's website, dependencies, etc... Here is an example info file
from the go plugin, which has the following file structure:
```
~/.config/micro/plug/go-plugin
~/.config/micro/plug/go-plugin/
go.lua
info.json
help/
go-plugin.md
```
info.json:
The `go.lua` file contains the main code for the plugin, though the
code may be distributed across multiple Lua files. The `info.json`
file contains information about the plugin such as the website,
description, version, and any requirements. Plugins may also
have additional files which can be added to micro's runtime files,
of which there are 5 types:
* Colorschemes
* Syntax files
* Help files
* Plugin files
* Syntax header files
In most cases, a plugin will want to add help files, but in certain
cases a plugin may also want to add colorschemes or syntax files. It
is unlikely for a plugin to need to add plugin files at runtime or
syntax header files. No directory structure is enforced but keeping
runtime files in their own directories is good practice.
# Info file
The `info.json` for the Go plugin is the following:
```
{
"name": "go",
@@ -35,6 +59,10 @@ the website should point to a valid website. The install field should
provide info about installing the plugin, or point to a website that
provides information.
Note that the name of the plugin is defined by the name field in
the `info.json` and not by the installation path. Some functions micro
exposes to plugins require passing the name of the plugin.
## Lua callbacks
Plugins use Lua but also have access to many functions both from micro
@@ -69,8 +97,8 @@ function onSave(bp)
end
```
The `bp` variable is a reference to the bufpane the action is being executed within.
This is almost always the current bufpane.
The `bp` variable is a reference to the bufpane the action is being executed
within. This is almost always the current bufpane.
All available actions are listed in the keybindings section of the help.
@@ -84,8 +112,8 @@ function onMousePress(view, event)
end
```
These functions should also return a boolean specifying whether the bufpane should
be relocated to the cursor or not after the action is complete.
These functions should also return a boolean specifying whether the bufpane
should be relocated to the cursor or not after the action is complete.
## Accessing micro functions
@@ -98,71 +126,208 @@ local micro = import("micro")
micro.Log("Hello")
```
The packages and functions are listed below:
The packages and functions are listed below (in Go type signatures):
* `micro`
- `TermMessage(msg interface{}...)`
- `TermError()`
- `InfoBar()`
- `Log(msg interface{}...)`
- `SetStatusInfoFn`
* `micro/config`
- `MakeCommand`
- `FileComplete`
- `HelpComplete`
- `OptionComplete`
- `OptionValueComplete`
- `NoComplete`
- `TryBindKey`
- `Reload`
- `AddRuntimeFilesFromDirectory`
- `AddRuntimeFileFromMemory`
- `AddRuntimeFile`
- `ListRuntimeFiles`
- `ReadRuntimeFile`
- `RTColorscheme`
- `RTSyntax`
- `RTHelp`
- `RTPlugin`
- `RegisterCommonOption`
- `RegisterGlobalOption`
* `micro/shell`
- `ExecCommand`
- `RunCommand`
- `RunBackgroundShell`
- `RunInteractiveShell`
- `JobStart`
- `JobSpawn`
- `JobStop`
- `JobStop`
- `RunTermEmulator`
- `TermEmuSupported`
* `micro/buffer`
- `NewMessage`
- `NewMessageAtLine`
- `MTInfo`
- `MTWarning`
- `MTError`
- `Loc`
- `BTDefault`
- `BTLog`
- `BTRaw`
- `BTInfo`
- `NewBufferFromFile`
- `ByteOffset`
* `micro/util`
- `RuneAt`
- `GetLeadingWhitespace`
- `IsWordChar`
- `TermMessage(msg interface{}...)`: temporarily close micro and print a
message
- `TermError(filename string, lineNum int, err string)`: temporarily close
micro and print an error formatted as `filename, lineNum: err`.
- `InfoBar()`: return the infobar BufPane object.
- `Log(msg interface{}...)`: write a message to `log.txt` (requires
`-debug` flag, or binary built with `build-dbg`).
- `SetStatusInfoFn(fn string)`: register the given lua function as
accessible from the statusline formatting options
* `micro/config`
- `MakeCommand(name string, action func(bp *BufPane, args[]string),
completer buffer.Completer)`:
create a command with the given name, and lua callback function when
the command is run. A completer may also be given to specify how
autocompletion should work with the custom command.
- `FileComplete`: autocomplete using files in the current directory
- `HelpComplete`: autocomplete using names of help documents
- `OptionComplete`: autocomplete using names of options
- `OptionValueComplete`: autocomplete using names of options, and valid
values afterwards
- `NoComplete`: no autocompletion suggestions
- `TryBindKey(k, v string, overwrite bool) (bool, error)`: bind the key
`k` to the string `v` in the `bindings.json` file. If `overwrite` is
true, this will overwrite any existing binding to key `k`. Returns true
if the binding was made, and a possible error (for example writing to
`bindings.json` can cause an error).
- `Reload()`: reload configuration files.
- `AddRuntimeFileFromMemory(filetype RTFiletype, filename, data string)`:
add a runtime file to the `filetype` runtime filetype, with name
`filename` and data `data`.
- `AddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype,
directory, pattern string)`:
add runtime files for the given plugin with the given RTFiletype from
a directory within the plugin root. Only adds files that match the
pattern using Go's `filepath.Match`
- `AddRuntimeFile(plugin string, filetype RTFiletype, filepath string)`:
add a given file inside the plugin root directory as a runtime file
to the given RTFiletype category.
- `ListRuntimeFiles(fileType RTFiletype) []string`: returns a list of
names of runtime files of the given type.
- `ReadRuntimeFile(fileType RTFiletype, name string) string`: returns the
contents of a given runtime file.
- `NewRTFiletype() int`: creates a new RTFiletype, and returns its value.
- `RTColorscheme`: runtime files for colorschemes.
- `RTSyntax`: runtime files for syntax files.
- `RTHelp`: runtime files for help documents.
- `RTPlugin`: runtime files for plugin source code.
- `RegisterCommonOption(pl string, name string, defaultvalue interface{})`:
registers a new option with for the given plugin. The name of the
option will be `pl.name`, and will have the given default value. Since
this registers a common option, the option will be modifiable on a
per-buffer basis, while also having a global value (in the
GlobalSettings map).
- `RegisterGlobalOption(pl string, name string, defaultvalue interface{})`:
same as `RegisterCommonOption` but the option cannot be modified
locally to each buffer.
- `GetGlobalOption(name string) interface{}`: returns the value of a
given plugin in the `GlobalSettings` map.
- `SetGlobalOption(option, value string) error`: sets an option to a
given value. Same as using the `> set` command. This will parse the
value to the actual value type.
- `SetGlobalOptionNative(option string, value interface{}) error`: sets
an option to a given value, where the type of value is the actual
type of the value internally.
* `micro/shell`
- `ExecCommand(name string, arg ...string) (string, error)`: runs an
executable with the given arguments, and pipes the output (stderr
and stdout) of the executable to an internal buffer, which is
returned as a string, along with a possible error.
- `RunCommand(input string) (string, error)`: same as `ExecCommand`,
except this uses micro's argument parser to parse the arguments from
the input. For example `cat 'hello world.txt' file.txt`, will pass
two arguments in the `ExecCommand` argument list (quoting arguments
will preserve spaces).
- `RunBackgroundShell(input string) (func() string, error)`: returns a
function that will run the given shell command and return its output.
- `RunInteractiveShell(input string, wait bool, getOutput bool)
(string, error)`:
temporarily closes micro and runs the given command in the terminal.
If `wait` is true, micro will wait for the user to press enter before
returning to text editing. If `getOutput` is true, micro redirect
stdout from the command to the returned string.
- `JobStart(cmd string, onStdout, onStderr,
onExit func(string, []interface{}), userargs ...interface{})
*exec.Cmd`:
Starts a background job by running the shell on the given command
(using `sh -c`). Three callbacks can be provided which will be called
when the command generates stdout, stderr, or exits. The userargs will
be passed to the callbacks, along with the output as the first
argument of the callback.
- `JobSpawn(cmd string, cmdArgs []string, onStdout, onStderr,
onExit func(string, []interface{}), userargs ...interface{})
*exec.Cmd`:
same as `JobStart`, except doesn't run the command through the shell
and instead takes as inputs the list of arguments.
- `JobStop(cmd *exec.Cmd)`: kills a job.
- `JobSend(cmd *exec.Cmd, data string)`: sends some data to a job's stdin.
- `RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool,
callback func(out string, userargs []interface{}),
userargs []interface{}) error`:
starts a terminal emulator from a given BufPane with the input command.
If `wait` is true it will wait for the user to exit by pressing enter
once the executable has terminated and if `getOutput` is true it will
redirect the stdout of the process to a pipe which will be passed to
the callback which is a function that takes a string and a list of
optional user arguments. This function returns an error on systems
where the terminal emulator is not supported.
- `TermEmuSupported`: true on systems where the terminal emulator is
supported and false otherwise. Supported systems:
* Linux
* MacOS
* Dragonfly
* OpenBSD
* FreeBSD
* `micro/buffer`
- `NewMessage(owner string, msg string, start, end, Loc, kind MsgType)
*Message`:
creates a new message with an owner over a range given by the start
and end locations.
- `NewMessageAtLine(owner string, msg string, line int, kindMsgType)
*Message`:
creates a new message with owner, type and message at a given line.
- `MTInfo`: info message.
- `MTWarning`: warning message.
- `MTError` error message.
- `Loc(x, y int) Loc`: creates a new location struct.
- `BTDefault`: default buffer type.
- `BTLog`: log buffer type.
- `BTRaw`: raw buffer type.
- `BTInfo`: info buffer type.
- `NewBuffer(text, path string) *Buffer`: creates a new buffer with the
given text at a certain path.
- `NewBufferFromFile(path string) (*Buffer, error)`: creates a new
buffer by reading from disk at the given path.
- `ByteOffset(pos Loc, buf *Buffer) int`: returns the byte index of the
given position in a buffer.
- `Log(s string)`: writes a string to the log buffer.
- `LogBuf() *Buffer`: returns the log buffer.
* `micro/util`
- `RuneAt(str string, idx int) string`: returns the utf8 rune at a
given index within a string.
- `GetLeadingWhitespace(s string) string`: returns the leading
whitespace of a string.
- `IsWordChar(s string) bool`: returns true if the first rune in a
string is a word character.
- `String(b []byte) string`: converts a byte array to a string.
- `RuneStr(r rune) string`: converts a rune to a string.
This may seem like a small list of available functions but some of the objects
returned by the functions have many methods. The Lua plugin may access any
public methods of an object returned by any of the functions above. For example,
with a BufPane object called `bp`, you could called the `Save` function in Lua
with `bp:Save()`.
public methods of an object returned by any of the functions above.
Unfortunately it is not possible to list all the available functions on this
page. Please go to the internal documentation at
https://godoc.org/github.com/zyedidia/micro to see the full list of available
methods. Note that only methods of types that are available to plugins via
the functions above can be called from a plugin. For an even more detailed
reference see the source code on Github.
Note that Lua uses the `:` syntax to call a function rather than Go's `.` syntax.
For example, with a BufPane object called `bp`, you could call the `Save`
function in Lua with `bp:Save()`.
Note that Lua uses the `:` syntax to call a function rather than Go's `.`
syntax.
```go
micro.InfoBar().Message()
@@ -222,68 +387,56 @@ errors
time
```
For documentation for each of these functions, you can simply look
through the Go standard library documentation.
For documentation for each of these functions, see the Go standard
library documentation at https://golang.org/pkg/ (for the packages
exposed to micro plugins). The Lua standard library is also available
to plugins though it is rather small.
## 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 string, type config.RTFiletype,
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")
config = import("micro/config")
config.AddRuntimeFile("test", config.RTHelp, "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.
## Autocomplete command arguments
See this example to learn how to use `MakeCompletion` and `MakeCommand`
```lua
local function StartsWith(String,Start)
String = String:upper()
Start = Start:upper()
return string.sub(String,1,string.len(Start))==Start
end
function complete(input)
local allCompletions = {"Hello", "World", "Foo", "Bar"}
local result = {}
for i,v in pairs(allCompletions) do
if StartsWith(v, input) then
table.insert(result, v)
end
end
return result
end
function foo(arg)
messenger:Message(arg)
end
MakeCommand("foo", "example.foo", MakeCompletion("example.complete"))
```
for all runtime files. In addition, there is `AddRuntimeFileFromMemory` which
adds a runtime file based on a string that may have been constructed at
runtime.
## 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).
There are 6 default plugins that come pre-installed with micro. These are
* `autoclose`: automatically closes brackets, quotes, etc...
* `comment`: provides automatic commenting for a number of languages
* `ftoptions`: alters some default options depending on the filetype
* `linter`: provides extensible linting for many languages
* `literate`: provides advanced syntax highlighting for the Literate
programming tool.
* `status`: provides some extensions to the status line (integration with
Git and more).
See `> help linter`, `> help comment`, and `> help status` for additional
documentation specific to those plugins.
These are good examples for many use-cases if you are looking to write
your own plugins.
## Plugin Manager
Micro also has a built in plugin manager which you can invoke with the
`> plugin ...` command.
`> plugin ...` command, or in the shell with `micro -plugin ...`.
For the valid commands you can use, see the `commands` help topic.
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
@@ -300,6 +453,7 @@ This file will contain the metadata for your plugin. Here is an example:
[{
"Name": "pluginname",
"Description": "Here is a nice concise description of my plugin",
"Website": "https://github.com/user/plugin",
"Tags": ["python", "linting"],
"Versions": [
{
@@ -313,8 +467,9 @@ 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,8 +1,8 @@
# 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.
@@ -16,13 +16,13 @@ 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
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.
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
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
@@ -60,41 +60,51 @@ following in `bindings.json`:
Very simple.
You can also bind keys while in micro by using the `> bind key action` command,
but the bindings you make with the command won't be saved to the `bindings.json`
file.
but the bindings you make with the command won't be saved to the
`bindings.json` file.
For more information about keybindings, like which keys can be bound, and
what actions are available, see the `keybindings` help topic (`> help keybindings`).
For more information about keybindings, like which keys can be bound, and what
actions are available, see the `keybindings` help topic (`> help keybindings`).
### 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.
micro starts and is essentially a one-file plugin. The plugin name is
`initlua`.
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,
This example will show you how to use the `init.lua` file by creating a binding
to `CtrlR` which will execute the bash command `go run` on the current file,
given that the current file is a Go file.
You can do that by putting the following in `init.lua`:
```lua
function gorun()
local buf = CurView().Buf -- The current buffer
if buf:FileType() == "go" then
HandleShellCommand("go run " .. buf.Path, true, true) -- the first true means don't run it in the background
end
local config = import("micro/config")
local shell = import("micro/shell")
function init()
-- true means overwrite any existing binding to CtrlR
-- this will modify the bindings.json file
config.TryBindKey("CtrlR", "lua:initlua.gorun", true)
end
BindKey("CtrlR", "init.gorun")
function gorun(bp)
local buf = bp.Buf
if buf:FileType() == "go" then
-- the true means run in the foreground
-- the false means send output to stdout (instead of returning it)
shell.RunInteractiveShell("go run " .. buf.Path, true, false)
end
end
```
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 `TryBindKey` line, and put this line in
the `bindings.json` file:
```json
{
"CtrlR": "init.gorun"
"CtrlR": "lua:initlua.gorun"
}
```

View File

@@ -1,3 +1,5 @@
VERSION = "1.0.0"
local uutil = import("micro/util")
local utf8 = import("utf8")
local autoclosePairs = {"\"\"", "''", "``", "()", "{}", "[]"}

View File

@@ -1,10 +0,0 @@
{
"name": "autoclose",
"description": "Automatically places closing characters for quotes, parentheses, brackets, etc...",
"website": "https://github.com/zyedidia/micro",
"install": "https://github.com/zyedidia/micro",
"version": "1.0.0",
"require": [
"micro >= 2.0.0"
]
}

View File

@@ -1,3 +1,5 @@
VERSION = "1.0.0"
local util = import("micro/util")
local config = import("micro/config")
local buffer = import("micro/buffer")
@@ -102,5 +104,8 @@ function string.starts(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
config.MakeCommand("comment", "comment.comment", config.NoComplete)
config.TryBindKey("Alt-/", "lua:comment.comment", false)
function init()
config.MakeCommand("comment", comment, config.NoComplete)
config.TryBindKey("Alt-/", "lua:comment.comment", false)
config.AddRuntimeFile("comment", config.RTHelp, "help/comment.md")
end

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