Compare commits

...

580 Commits

Author SHA1 Message Date
Zachary Yedidia
7c71995aaf Merge branch 'utkarsh2102-update-readme' 2020-02-12 14:26:33 -05:00
Zachary Yedidia
357c4b0fcd Update readme 2020-02-12 14:26:17 -05:00
Zachary Yedidia
0d4f85304b Merge branch 'update-readme' of https://github.com/utkarsh2102/micro into utkarsh2102-update-readme 2020-02-12 14:22:33 -05:00
Zachary Yedidia
e18e41eb45 Merge branch 'seitokaichou-autosu' 2020-02-12 14:15:37 -05:00
Zachary Yedidia
5519f053ac Merge branch 'autosu' of https://github.com/seitokaichou/micro into seitokaichou-autosu 2020-02-12 14:15:30 -05:00
Zachary Yedidia
2f4b3b2a8c Merge branch 'jawahars16-bug-endless-reload-prompt' 2020-02-12 13:56:17 -05:00
Zachary Yedidia
ea290e4fb5 Merge branch 'bug-endless-reload-prompt' of https://github.com/jawahars16/micro into jawahars16-bug-endless-reload-prompt 2020-02-12 13:56:00 -05:00
Zachary Yedidia
8b414e6187 Update readme toc 2020-02-12 13:38:14 -05:00
Zachary Yedidia
e7ef81ed97 Share hash across equivalent buffers for fastdirty=off 2020-02-12 13:32:42 -05:00
Zachary Yedidia
12c286f9b1 Introduce IndentLine action
Closes #1476
2020-02-12 13:30:24 -05:00
Zachary Yedidia
7b5bc8fe37 Fix issue with global/local settings 2020-02-12 13:18:59 -05:00
Zachary Yedidia
bad78797bb Clicking tabbar arrow scrolls and fix multicursor
Closes #1503
2020-02-12 13:05:15 -05:00
Zachary Yedidia
bf1258578c Expose OpenLogBuf to plugins 2020-02-12 12:35:40 -05:00
Zachary Yedidia
6588f02f7b Only highlight matching brace if one is found
Fixes #1505
2020-02-12 01:32:23 -05:00
Zachary Yedidia
5e9c6375d0 Only fetch tags if no tags are found at all
Fixes #1504
2020-02-12 01:24:25 -05:00
Zachary Yedidia
7d47659481 Fix deleteLines off-by-one error
Fixes #1501
2020-02-12 01:16:11 -05:00
Zachary Yedidia
dcd4bae96f Clamp modifications
Fixes #1502
2020-02-12 00:55:52 -05:00
Zachary Yedidia
4f628bf30b Remove incorrect plugin documentation 2020-02-12 00:22:32 -05:00
Utkarsh Gupta
36e83a46e4 Update installation instruction on Debian systems 2020-02-12 07:45:07 +05:30
Zachary Yedidia
1a64ffb88b Don't expose draw channel to outside packages 2020-02-11 20:39:26 -05:00
Zachary Yedidia
7c77927913 Update tcell to v1.4.4 2020-02-11 20:34:22 -05:00
Zachary Yedidia
8224037080 Don't block when redraw channel becomes full
Fixes #1497
2020-02-11 20:03:32 -05:00
Zachary Yedidia
d7e3fc99f1 Merge 2020-02-11 19:13:41 -05:00
Zachary Yedidia
feaf3951d2 Update haskell syntax file 2020-02-11 19:13:36 -05:00
Zachary Yedidia
399c629076 Update readme 2020-02-11 16:40:15 -05:00
Zachary Yedidia
47ed7447f1 Update screenshot 2020-02-11 16:30:58 -05:00
Zachary Yedidia
62a98ea3c5 Typo 2020-02-11 14:50:15 -05:00
Zachary Yedidia
0a9194b883 Fix tag fetching in build-version.go 2020-02-11 14:46:19 -05:00
Zachary Yedidia
a938917b9b Fetch tags if none are found 2020-02-11 14:14:34 -05:00
Zachary Yedidia
695d4c2b1b Use filepath.Join more 2020-02-11 13:09:17 -05:00
Zachary Yedidia
34724b941a Recover from internal errors without crashing 2020-02-11 00:50:24 -05:00
Zachary Yedidia
8176e8c6f8 Improve one-dark colorscheme divider 2020-02-10 23:37:21 -05:00
Zachary Yedidia
0d5b1cd64d Update readme buttons 2020-02-10 22:58:25 -05:00
Zachary Yedidia
9fd5b133ea Add svg logo 2020-02-10 22:40:16 -05:00
Zachary Yedidia
432b57a070 Merge branch 'FJduFou-patch-1' 2020-02-10 22:37:03 -05:00
François-Joseph du Fou
ee55732be3 Update README.md 2020-02-10 22:36:57 -05:00
François-Joseph du Fou
90304fb472 better top
add a center logo + 3 news buttons
2020-02-10 22:36:02 -05:00
Zachary Yedidia
f6aec1af5f Update readme 2020-02-10 22:19:47 -05:00
Zachary Yedidia
b77b61d677 Merge branch 'Calinou-improve-readme' 2020-02-10 22:17:51 -05:00
Zachary Yedidia
05a0598f16 Merge branch 'improve-readme' of https://github.com/Calinou/micro into Calinou-improve-readme 2020-02-10 22:17:46 -05:00
Zachary Yedidia
daa6f122a7 Add note in readme about winpty 2020-02-10 22:10:01 -05:00
Zachary Yedidia
71f5f043fb Merge 2020-02-10 19:56:17 -05:00
Zachary Yedidia
f3eaf99665 Draw FakeCursor in infobar when on a character
Fixes #1496
2020-02-10 19:55:13 -05:00
Zachary Yedidia
c88c1b84da Term should return error on unsupported systems
Fixes #1494
2020-02-10 19:09:03 -05:00
Zachary Yedidia
e1e310a96e Document all options 2020-02-10 15:07:00 -05:00
Zachary Yedidia
d29b941be0 Merge 2020-02-10 14:59:47 -05:00
Zachary Yedidia
cde696915d Merge branch 'sum01-issue_1008' 2020-02-10 14:59:40 -05:00
Zachary Yedidia
185b8de17b Merge branch 'issue_1008' of https://github.com/sum01/micro into sum01-issue_1008 2020-02-10 14:59:31 -05:00
Kyle Barron
d65285ee54 Update documentation to include Material colorscheme (#1279) 2020-02-10 14:53:27 -05:00
Zachary Yedidia
848bd1ba8c Fix rehighlight for retab 2020-02-10 14:49:08 -05:00
Zachary Yedidia
00205aa6a7 Update readme 2020-02-10 00:38:57 -05:00
Zachary Yedidia
ecb9fd5a8a Change diffgutter default to false 2020-02-10 00:30:13 -05:00
Zachary Yedidia
bdf9e6d3a4 Merge branch 'diff-gutter' of https://github.com/p-e-w/micro 2020-02-10 00:28:43 -05:00
Zachary Yedidia
3ed77dbb2e Sanitize inputs to insert and remove 2020-02-10 00:18:08 -05:00
Zachary Yedidia
57a992c4a3 Merge 2020-02-09 22:40:20 -05:00
Zachary Yedidia
c63614213d Update tcell dep 2020-02-09 22:40:18 -05:00
Rein F
b7a54fa74a updated man page (#1492) 2020-02-09 21:53:42 -05:00
Zachary Yedidia
63046ae909 Don't autocomplete in the middle of a word
Fixes #1490
2020-02-09 16:46:53 -05:00
Zachary Yedidia
af48e4b79b Fix save callbacks
Fixes #1491
2020-02-09 16:36:15 -05:00
Zachary Yedidia
4e73d0779b Create bindings.json if it does not exist 2020-02-09 16:27:39 -05:00
Zachary Yedidia
6f424f3213 Properly flush bufio writer 2020-02-09 15:36:31 -05:00
Zachary Yedidia
e110e93e0f Improve disk performance with buffered io 2020-02-09 15:21:23 -05:00
Zachary Yedidia
8ddf335e68 Improve remove performance 2020-02-09 14:58:37 -05:00
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
Philipp Emanuel Weidmann
de33eac058 Add diff gutter 2020-02-08 13:26:24 +05:30
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
Zachary Yedidia
a2f7080602 Raw event support with new tcell 2019-12-31 22:34:43 -05:00
Zachary Yedidia
a2916c0e32 Escape sequence support 2019-12-31 21:50:26 -05:00
Zachary Yedidia
0301e3539e Use upstream updated zyedidia tcell 2019-12-31 20:15:45 -05:00
Zachary Yedidia
6632ab0a77 Switch to gdamore/tcell 2019-12-31 17:53:16 -05:00
Zachary Yedidia
466c48da31 Merge 2019-12-31 17:53:00 -05:00
Zachary Yedidia
2c72a3755c Fix openbuffer view creation 2019-12-31 17:52:55 -05:00
Zachary Yedidia
92054aa649 Merge 2019-12-31 16:49:21 -05:00
Zachary Yedidia
4b5be43e60 Create all parents of micro config automatically
Ref #1184
2019-12-31 16:48:45 -05:00
Zachary Yedidia
d18864e607 Add linux static binary to release scripts 2019-12-31 16:23:37 -05:00
Zachary Yedidia
4010a16784 Add fully static linux build to automated builder
Ref #1184
2019-12-31 16:20:54 -05:00
Zachary Yedidia
01c6ea26b8 Merge 2019-12-31 16:12:59 -05:00
Zachary Yedidia
d83b912b3b Add xclip message for pasting if unsupported 2019-12-31 16:12:39 -05:00
Zachary Yedidia
604d78de0f Merge pull request #1327 from Osmose/git-commit-diff
Fix #1314: Add support for diffs from `git commit --verbose`.
2019-12-30 14:43:50 -05:00
Zachary Yedidia
9e8420aab5 Merge pull request #1437 from serebit/patch-2
Enable syntax highlighting for Kotlin script files
2019-12-30 14:42:48 -05:00
Zachary Yedidia
dc3d6f5bc3 Merge branch 'ariasuni-fix-xml-highlighting' 2019-12-30 14:40:58 -05:00
Zachary Yedidia
93431a9ddf Merge branch 'fix-xml-highlighting' of https://github.com/ariasuni/micro into ariasuni-fix-xml-highlighting 2019-12-30 14:40:41 -05:00
Zachary Yedidia
ec6be38391 Delete runtime.go 2019-12-30 14:32:36 -05:00
Zachary Yedidia
3777bcf295 Merge pull request #1386 from jncraton/docfix
Minor grammar fix addressing #1377
2019-12-30 14:28:46 -05:00
Zachary Yedidia
3d09dfe574 Merge pull request #1393 from raziel2244/patch-1
ES6 - ES2019 additions
2019-12-30 14:24:33 -05:00
Zachary Yedidia
532f932712 Merge branch 'master' into patch-1 2019-12-30 14:24:27 -05:00
Zachary Yedidia
eeeb927a1d Merge pull request #1361 from Lisiadito/master
fix #1318. fix html comments and make them work multiline
2019-12-30 14:23:27 -05:00
Zachary Yedidia
f9ce549663 Merge pull request #1287 from didactic-drunk/ruby_syntax
Ruby syntax improvements.
2019-12-30 14:23:06 -05:00
Zachary Yedidia
5a715e7a0b Merge pull request #1281 from Calinou/highlight-nimscript
Highlight NimScript files (.nims) as Nim
2019-12-30 14:22:38 -05:00
Zachary Yedidia
e420872a27 Merge branch 'master' into highlight-nimscript 2019-12-30 14:22:22 -05:00
Zachary Yedidia
cef32d4ac7 Merge pull request #1315 from matbesancon/patch-1
Update julia.yaml
2019-12-30 14:21:20 -05:00
Zachary Yedidia
35375a6ea2 Merge pull request #1406 from LeapofAzzam/LeapofAzzam-patch-1
Update vi syntax
2019-12-30 14:20:27 -05:00
Zachary Yedidia
60eec0eccd Merge pull request #1412 from tommyshem/batSyntaxHighlighting
Add windows .bat syntax highlighting file #1388
2019-12-30 14:20:13 -05:00
Zachary Yedidia
97665573c7 Merge pull request #1426 from Nergel3/master
vue syntax (+typescript) & svelte syntax
2019-12-30 14:19:59 -05:00
Zachary Yedidia
f874377573 add system verilog syntax file 2019-12-30 14:05:06 -05:00
Zachary Yedidia
ce868faece Merge pull request #1445 from spytheman/spytheman-v-micro-highlight-syntax
Added V syntax support
2019-12-30 12:27:31 -05:00
Delyan Angelov
e1c1e402a9 Add V syntax highlighting.
V is a new general purpose language, inspired mainly by Go, Rust, Pascal and C.
Main site: https://vlang.io/
Github: https://github.com/vlang/v
2019-12-30 16:13:15 +02:00
Zachary Yedidia
3b66a3364c Fix some formatting 2019-12-29 22:02:14 -05:00
Zachary Yedidia
9b03a3dc6d Add message if xclip/xsel not found
Ref #1236
Fixes #1031
2019-12-29 21:43:29 -05:00
Zachary Yedidia
ff24ad5fa8 Fix race condition with events channel 2019-12-29 18:53:59 -05:00
Zachary Yedidia
5180634947 Merge 2019-12-29 18:23:22 -05:00
Zachary Yedidia
da643a0c1f Run action completion on saves with prompts at the right time 2019-12-29 18:23:17 -05:00
Zachary Yedidia
cd6765673f Support tcell EventPaste 2019-12-29 13:45:08 -05:00
Zachary Yedidia
1b73abcfd0 Fix formatting in plugin info.json 2019-12-29 00:03:21 -05:00
Zachary Yedidia
f3778baaf4 SetGlobalOption access for plugins 2019-12-28 23:40:44 -05:00
Zachary Yedidia
cf1f9fa007 Use MICRO_CONFIG_HOME before trying XDG_CONFIG_HOME 2019-12-28 23:10:51 -05:00
Zachary Yedidia
34619e111f Add GetGlobalOption access for plugins 2019-12-28 22:48:38 -05:00
Zachary Yedidia
29a5cef559 Update default plugins slightly 2019-12-28 22:39:57 -05:00
Zachary Yedidia
bd83c6a8a9 Remove detect requirement and detect in jinja file
Ref #1415
2019-12-28 22:27:44 -05:00
Zachary Yedidia
4b0348f64a Merge 2019-12-28 21:57:11 -05:00
Zachary Yedidia
5b52b8a60f Support includes 2019-12-28 21:57:03 -05:00
Zachary Yedidia
c0e6dad88b Merge pull request #1443 from onodera-punpun/patch-1
Replace tab with spaces
2019-12-28 21:56:40 -05:00
Zachary Yedidia
a61616d79e More efficient loading for default syntax files
This change introduces header files for syntax files. The header
files only contain the filetype and detection info and can be
parsed much faster than parsing a full yaml file. To determine
which filetype a file is, only scanning the headers is necessary
and afterwards only one yaml file needs to be parsed. Use the
make_headers.go file to generate the header files. Micro expects
that all default syntax files will have header files and that
custom user syntax files may or may not have them. Resolving
includes within syntax has not yet been implemented. This
optimization improves startup time.

Ref #1427
2019-12-28 21:26:22 -05:00
Zachary Yedidia
8663014bbe Add support for syntax headers and update tcell 2019-12-28 18:53:51 -05:00
Camille
351c7b099a Replace tab with spaces 2019-12-28 22:38:41 +01:00
Zachary Yedidia
c2e7fd34a7 Fix issues related to tabbar/infobar mouse events
Fixes #1440
2019-12-28 15:56:56 -05:00
Zachary Yedidia
a3e61a6e71 Merge 2019-12-28 12:04:48 -05:00
Zachary Yedidia
bd0c172667 Improve mouse selection performance 2019-12-28 12:04:43 -05:00
Zachary Yedidia
7746039724 Fix windows compilation in auto-builder 2019-12-28 01:56:03 +00:00
Zachary Yedidia
629f20720a Fix add runtime file for local plugins 2019-12-27 20:28:25 -05:00
Zachary Yedidia
2a3d7720f3 Merge 2019-12-27 18:43:53 -05:00
Zachary Yedidia
b47cf33c3b Update tcell version 2019-12-27 18:43:47 -05:00
Zachary Yedidia
5fba432d78 Use makefile to build for cross compilation 2019-12-27 17:26:03 +00:00
Zachary Yedidia
b1efabaaed Command binding fix 2019-12-27 00:43:45 -05:00
Zachary Yedidia
185d54d664 Search and replace fixes 2019-12-27 00:06:02 -05:00
Zachary Yedidia
96322a6df9 Update makefile and vendor script 2019-12-26 22:03:30 -05:00
Zachary Yedidia
42af0816d5 No patchelf for snap build
Ref #1078
2019-12-26 21:41:19 -05:00
Zachary Yedidia
8368989341 Clean up build tools 2019-12-26 20:43:43 -05:00
Zachary Yedidia
9e60d468ca Use osusergo build tag 2019-12-26 20:32:33 -05:00
Campbell Jones
da32457037 Enable syntax highlighting for Kotlin script files
In addition, make the following changes to the kotlin syntax highlighting: 
- Add new unsigned types to type.storage
- Add const as a statement keyword
- Remove typeof from type keywords
2019-12-26 19:13:23 -05:00
Zachary Yedidia
2d2dbfebff Fix snap install metadata 2019-12-26 18:54:40 -05:00
Zachary Yedidia
6681387b47 Support for file reloading if changed externally 2019-12-26 17:59:23 -05:00
Zachary Yedidia
6a4a915188 Support arm64 in cross compilation script
Ref #1431
2019-12-26 17:06:55 -05:00
Zachary Yedidia
6ba66320f0 Don't forward tabbar mouse events to panes
Fixes #1435
2019-12-26 17:02:02 -05:00
Zachary Yedidia
1367084a18 Merge 2019-12-26 16:57:33 -05:00
Zachary Yedidia
ec2976b069 Scroll up as much as possible
Fixes #1434
2019-12-26 16:57:09 -05:00
Zachary Yedidia
bad8ed7473 Fix poller mod version for osx 2019-12-26 21:53:45 +00:00
Zachary Yedidia
e912b7de12 Fix go module issue 2019-12-26 16:37:02 -05:00
Zachary Yedidia
8570ff9a8c Remove autosave option
With the new backup option, the autosave option is no longer useful.
Since it never really worked well in the first place, it has been
removed.

Closes #1420
2019-12-26 14:35:48 -05:00
Zachary Yedidia
7f7ad29671 Improve lua interface for statusline 2019-12-26 12:46:10 -05:00
Zachary Yedidia
a95dab078e Minor edit to statusline format
Ref #1432
2019-12-26 12:25:42 -05:00
Zachary Yedidia
f8218e0648 Fix bottomline when softwrap enabled 2019-12-25 19:44:58 -05:00
Zachary Yedidia
e66d01e989 Some documentation 2019-12-25 19:37:51 -05:00
Zachary Yedidia
05331e1fa2 Support rc tags in build version 2019-12-25 17:42:57 -05:00
Zachary Yedidia
c5d3008d85 Clean unused go modules 2019-12-25 17:23:39 -05:00
Zachary Yedidia
922a57b3e7 Merge branch refactor2.0
The code from the refactor that I have been working on is
now more or less ready to be merged. These changes make some
breaking changes, notably with regards to the plugin
interface. Once a lot more documentation has been written, I
will release this code as micro 2.0. There are a lot of new
features, and in the coming days I will try to go through
the open issues to see exactly which ones are addressed by
the new features, and write lots more documentation
regarding what has been implemented.

Some highlights include:

* Simple autocompletion.
    * Autocompletion (tab by default) will do a simple
      "buffer completion" which will autocomplete according
      to words used elsewhere in the buffer. In the future
      plugin support could be added along with support for
      interfacing with language-specific autocompletion
      tools.
* Automatic backups.
    * Backup files are stored in `~/.config/micro/backups`
      for every open buffer and are saved roughly every 8
      seconds if the buffer is being modified. Backups
      are removed when the buffer is closed, but if micro
      or the system crashes, any unsaved changes can be
      recovered by re-opening the file (micro will auto-
      recover) or by manually viewing the backup in the
      `~/.config/micro/backups` directory.
* Configurable statusline.
* Configurable linter plugin.
* Resizeable splits.
* Complete re-organization of the code to support better go
  modules and maintain a better directory structure.
* Better plugin interface with better access to the Go
  standard library and internal Micro functions (lots of
  documentation still needs to be written).
    * Documentation still needs to be written, but in the
      meantime please see the default plugins as examples
      as they have been converted from their old versions
      to be compatible with the new interface.
* Buffer synchronization when the same file is opened
  multiple times.
* Keybindings and mouse support in the command bar.
* Support for non-utf8 encodings.
* General QoL improvements and bug fixes.
    * Notably I believe the autoclose plugin crash issue is
      fixed.
* No more plugin manager.
    * Plugin installation will now be performed manually
      by git cloning into the `~/.config/micro/plug`
      directory. This may not be a highlight for some but
      I believe it is much simpler, and there is no need
      to have a heavyweight dependency manager. Perhaps
      in the future, a good command-line tool can be made
      to manage plugins if people would find it useful.
* Other features that I have forgotten.

Next I plan to write up more documentation for all the new
features, and make a "release candidate" for micro 2.0. I
will also be working to fix any bugs that come up (hopefully
not too many, but this is a big change and bound to have
some issues). After release I hope to focus more on
optimization (for example loading syntax files is currently
somewhat inefficient, and the bottleneck for startup time #1427).

Sorry for not being so active recently, but I hope merging
this big change can help me get back to more regular
development. Thanks to everyone for using micro and for
giving feedback and engaging with development online (even
if I don't always respond).

Merry Christmas!

Issues that are fixed/affected by this change:

Ref #1419 (configurable statusline)
Ref #1413 (cursor behaves better)
Ref #1401 (softwrap problems)
Ref #1383 (better save with sudo)
Ref #1424 (better save with sudo)
Ref #1382 (go modules)
Ref #1381 (install plugins from command line)
Ref #1357 (sorting -- textfilter)
Ref #1351 (custom linting)
Ref #1350 (sudo problem might be fixed)
Ref #1298 (readonly option)
Ref #1250 (autoclose bug)
Ref #1239 (go modules)
Ref #813  (autoclose bug)
Ref #812  (cursor sync across same buffers)
Ref #770  (resizeable panes)
Ref #635  (keybindings in infobar)
Ref #596  (disable builtin plugins)
Ref #550  (backups)
Ref #174  (autocompletion)
2019-12-25 17:07:30 -05:00
Zachary Yedidia
ff6f28e366 Autocompletion fix for infobuffer 2019-12-25 17:05:11 -05:00
Zachary Yedidia
4951f155ea Support for more complex action chaining 2019-12-25 17:05:11 -05:00
Zachary Yedidia
94ff79e7b2 Lua prompt support and plugin improvements 2019-12-25 17:05:11 -05:00
Zachary Yedidia
3b306c1d3b Better softwrap 2019-12-25 17:05:11 -05:00
Zachary Yedidia
432f1f3363 Minor relocate improvement 2019-12-25 17:05:11 -05:00
Zachary Yedidia
93734f5668 Fix highlighting issue 2019-12-25 17:05:11 -05:00
Zachary Yedidia
b527e4fe42 Reoragnize slightly 2019-12-25 17:05:11 -05:00
Zachary Yedidia
3f22501b1a Improved save with sudo 2019-12-25 17:05:11 -05:00
Zachary Yedidia
fc706bc404 No backups for no name files 2019-12-25 17:05:11 -05:00
Zachary Yedidia
c4d5d7c195 Better backup behavior 2019-12-25 17:05:11 -05:00
Zachary Yedidia
a9bb1f35da Improve selection display 2019-12-25 17:05:11 -05:00
Zachary Yedidia
04e5acb1f8 Minor highlighting fixes 2019-12-25 17:05:11 -05:00
Zachary Yedidia
e42cf3663b Backup support 2019-12-25 17:05:11 -05:00
Zachary Yedidia
a86a6c464e Start implementing backup system 2019-12-25 17:05:11 -05:00
Zachary Yedidia
88b8fc713d Proper scrollbar location for hsplits 2019-12-25 17:05:11 -05:00
Zachary Yedidia
9127152d93 Fix goto issue 2019-12-25 17:05:11 -05:00
Zachary Yedidia
dde52132cf Update tcell version 2019-12-25 17:05:11 -05:00
Zachary Yedidia
ba594abfad Clearer status bar 2019-12-25 17:05:11 -05:00
Zachary Yedidia
5075c91fd4 Fix rebase issue 2019-12-25 17:05:11 -05:00
Zachary Yedidia
5e28ed4271 Add textfilter command 2019-12-25 17:05:11 -05:00
Zachary Yedidia
d29994ada9 Close file 2019-12-25 17:05:11 -05:00
Zachary Yedidia
7f32d31108 Fix plugin names 2019-12-25 17:05:11 -05:00
Zachary Yedidia
aa66435353 Better plugin docs 2019-12-25 17:05:11 -05:00
Zachary Yedidia
e79869978b Use plugin name defined in info and require it to be an identifier 2019-12-25 17:05:11 -05:00
Zachary Yedidia
b41fc10b8f Update some docs 2019-12-25 17:05:11 -05:00
Zachary Yedidia
5dfaaf8856 Update runtime 2019-12-25 17:05:11 -05:00
Zachary Yedidia
4dccfc095d Add visual scroll bar 2019-12-25 17:05:11 -05:00
Zachary Yedidia
bc3f845c0d Remove semver from rebase 2019-12-25 17:05:11 -05:00
Zachary Yedidia
6f6b263d10 Add some plugin functions 2019-12-25 17:05:11 -05:00
Zachary Yedidia
b68461cf72 Terminal plugin callback support 2019-12-25 17:05:11 -05:00
Zachary Yedidia
199d65017f Auto init settings if config doesn't exist 2019-12-25 17:05:11 -05:00
Zachary Yedidia
d2f8adb8ff Support multiactions 2019-12-25 17:05:11 -05:00
Zachary Yedidia
5b18edf865 Small improvement for replace command 2019-12-25 17:05:11 -05:00
Zachary Yedidia
ac3a5154c0 Update version tool to support rc versions 2019-12-25 17:05:11 -05:00
Zachary Yedidia
adaddba696 Add plugin info.json support 2019-12-25 17:05:11 -05:00
Zachary Yedidia
26c545267d Support column marking in linter 2019-12-25 17:05:11 -05:00
Zachary Yedidia
3d40e91690 Add log and plugin list command 2019-12-25 17:05:11 -05:00
Zachary Yedidia
7217911c3a Add macro and QuitAll support 2019-12-25 17:05:11 -05:00
Zachary Yedidia
24eb6fee25 Add buftype access for plugins 2019-12-25 17:05:11 -05:00
Zachary Yedidia
65cd6c4605 Fix minor matchbrace issue 2019-12-25 17:05:11 -05:00
Zachary Yedidia
d1e713ce08 Add better matchbrace 2019-12-25 17:05:11 -05:00
Zachary Yedidia
f39a916e5f Fix minor autosave race condition 2019-12-25 17:05:11 -05:00
Zachary Yedidia
c0293b5d0e Add autosave option 2019-12-25 17:05:11 -05:00
Zachary Yedidia
bc6dd990e5 Improve gutter messages 2019-12-25 17:05:11 -05:00
Zachary Yedidia
ccb5904591 Add mkparents option 2019-12-25 17:05:11 -05:00
Zachary Yedidia
9eed8bc247 Remove local settings 2019-12-25 17:05:11 -05:00
Zachary Yedidia
763e635fea Add literate plugin support 2019-12-25 17:05:11 -05:00
Zachary Yedidia
e18f6f832f Add goto command 2019-12-25 17:05:10 -05:00
Zachary Yedidia
fc4811c1ab Add comment plugin support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
be136a4648 Full extensible linter support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
4027081e0e Add linter plugin support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
e7e0272968 Jobs and gutter messages for plugins 2019-12-25 17:05:10 -05:00
Zachary Yedidia
e3ae38e54a Autoclose plugin support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
a47e1f0ca5 Allow any plugin to be enabled or disabled via settings 2019-12-25 17:05:10 -05:00
Zachary Yedidia
576036f251 Update ftoptions and statusline plugin configuration options 2019-12-25 17:05:10 -05:00
Zachary Yedidia
23a76e1381 Add indentchar option 2019-12-25 17:05:10 -05:00
Zachary Yedidia
55e33badd0 Add readonly option 2019-12-25 17:05:10 -05:00
Zachary Yedidia
5bd54747b3 Fix history for YN prompt 2019-12-25 17:05:10 -05:00
Zachary Yedidia
bf15f5c585 Support filetype option as command line option 2019-12-25 17:05:10 -05:00
Zachary Yedidia
809b95d290 Add reset command and statusline format string options 2019-12-25 17:05:10 -05:00
Zachary Yedidia
8d85cae4c0 Add autocomplete 2019-12-25 17:05:10 -05:00
Zachary Yedidia
a5cf06026a Fix infobar style 2019-12-25 17:05:10 -05:00
Zachary Yedidia
7cd5024e34 Small fixes 2019-12-25 17:05:10 -05:00
Zachary Yedidia
0f4f60c018 Update docs 2019-12-25 17:05:10 -05:00
Zachary Yedidia
aa305c2676 Implement buffer opening at a location 2019-12-25 17:05:10 -05:00
Zachary Yedidia
aa774164a7 Fix relocate bug 2019-12-25 17:05:10 -05:00
Zachary Yedidia
47a129b70f Unicode support improvement 2019-12-25 17:05:10 -05:00
Zachary Yedidia
c93d7a1b35 Add hidehelp support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
995e1dc704 Add tabmovement support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
adfeaf52ba Fix serialization 2019-12-25 17:05:10 -05:00
Zachary Yedidia
f5f4154d4c Fix some search bugs 2019-12-25 17:05:10 -05:00
Zachary Yedidia
74ee256260 Revert "Some plugin helpers"
This reverts commit 75f9d7d9122f5b475c4ff323cca7cc068ea4e411.
2019-12-25 17:05:10 -05:00
Zachary Yedidia
d45f8b4d23 Some plugin helpers 2019-12-25 17:05:10 -05:00
Zachary Yedidia
3335f377a9 Some plugin callbacks 2019-12-25 17:05:10 -05:00
Zachary Yedidia
5ab6c9795f Load plugins 2019-12-25 17:05:10 -05:00
Zachary Yedidia
15dff722b0 Remove plugin manager 2019-12-25 17:05:10 -05:00
Zachary Yedidia
a2b9acd153 Some plugin manager improvements 2019-12-25 17:05:10 -05:00
Zachary Yedidia
4497daaef1 Resolve versions in plugin manager 2019-12-25 17:05:10 -05:00
Zachary Yedidia
cf2d5dbfe2 update travis 2019-12-25 17:05:10 -05:00
Zachary Yedidia
739dd28652 Fix test dependencies and travis build 2019-12-25 17:05:10 -05:00
Zachary Yedidia
39446df749 update makefile 2019-12-25 17:05:10 -05:00
Zachary Yedidia
7cd83b4361 Fix tooling dependencies 2019-12-25 17:05:10 -05:00
Zachary Yedidia
0612af1590 Change project layout and use go.mod 2019-12-25 17:05:10 -05:00
Zachary Yedidia
c7f2c9c704 More plugin manager work 2019-12-25 17:05:10 -05:00
Zachary Yedidia
f4a3465a08 Start plugin support and plugin manager 2019-12-25 17:05:10 -05:00
Zachary Yedidia
453e96358a Fix option flags 2019-12-25 17:05:10 -05:00
Zachary Yedidia
b97ded9058 Fix view relocate bug 2019-12-25 17:05:10 -05:00
Zachary Yedidia
253790de99 Sort suggestions and cycle back 2019-12-25 17:05:10 -05:00
Zachary Yedidia
ef18fc572c Add more option support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
0e4faf108d Finish autocomplete 2019-12-25 17:05:10 -05:00
Zachary Yedidia
ad487807a5 Remove chardet dependency 2019-12-25 17:05:10 -05:00
Zachary Yedidia
ad50d7aa56 Add reopen cmd and other encodings support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
ef3f081347 Add colorcolumn 2019-12-25 17:05:10 -05:00
Zachary Yedidia
bc1d6b6f94 Add more infobar autocomplete 2019-12-25 17:05:10 -05:00
Zachary Yedidia
fc7058d47c Add infobar autocomplete 2019-12-25 17:05:10 -05:00
Zachary Yedidia
ab37e6ad6c Add support for binding command and command-edit 2019-12-25 17:05:10 -05:00
Zachary Yedidia
4bdf788091 Add replace all alias 2019-12-25 17:05:10 -05:00
Zachary Yedidia
8c687e8279 Support raw pane 2019-12-25 17:05:10 -05:00
Zachary Yedidia
9336e09532 Revert "Use byte slice for insert"
This reverts commit 0c844c2f5b.
2019-12-25 17:05:10 -05:00
Zachary Yedidia
069f7d20bc Add save and save as 2019-12-25 17:05:10 -05:00
Zachary Yedidia
212b0f8c71 Add keymenu 2019-12-25 17:05:10 -05:00
Zachary Yedidia
254b892a3b Fix multi cursor relocate 2019-12-25 17:05:10 -05:00
Zachary Yedidia
1a710272f8 Prompt trim fix 2019-12-25 17:05:10 -05:00
Zachary Yedidia
a3885bfb12 Add search and replace 2019-12-25 17:05:10 -05:00
Zachary Yedidia
df968db5a3 Proper help toggle 2019-12-25 17:05:10 -05:00
Zachary Yedidia
538f0117bc Fix yn callback bug 2019-12-25 17:05:10 -05:00
Zachary Yedidia
4a5b759f16 Fix fileformat 2019-12-25 17:05:10 -05:00
Zachary Yedidia
3380170af8 Add retab 2019-12-25 17:05:10 -05:00
Zachary Yedidia
467d384789 Add more actions 2019-12-25 17:05:10 -05:00
Zachary Yedidia
1563ab93dd Use byte slice for insert 2019-12-25 17:05:10 -05:00
Zachary Yedidia
812c7761dc Correct infobar and statusline options 2019-12-25 17:05:10 -05:00
Zachary Yedidia
055fff2b08 Fix redraw 2019-12-25 17:05:10 -05:00
Zachary Yedidia
5671e039b9 Fix multi buffer same file cursors 2019-12-25 17:05:10 -05:00
Zachary Yedidia
224cbe5093 Add help 2019-12-25 17:05:10 -05:00
Zachary Yedidia
eb49052a48 Add bind and unbind commands 2019-12-25 17:05:10 -05:00
Zachary Yedidia
5825353f64 Add some commands 2019-12-25 17:05:10 -05:00
Zachary Yedidia
8fa34f23d8 Handle same file open in multiple buffers 2019-12-25 17:05:10 -05:00
Zachary Yedidia
a5e7122b30 Add almost full option support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
6c1db53b65 Fix scroll problem 2019-12-25 17:05:10 -05:00
Zachary Yedidia
b9f7939018 Add term statusline 2019-12-25 17:05:10 -05:00
Zachary Yedidia
5701ed211a Fix empty splits and single terms 2019-12-25 17:05:10 -05:00
Zachary Yedidia
8858c03b3b Add raw event support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
2f7858ce25 Gutter message support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
94ab77e2e0 Fix mouse bug 2019-12-25 17:05:10 -05:00
Zachary Yedidia
fb3923f344 Open default shell if no term args 2019-12-25 17:05:10 -05:00
Zachary Yedidia
354c9efc8f Move bindings location in code 2019-12-25 17:05:10 -05:00
Zachary Yedidia
149b3ae89f Fix small tab problem 2019-12-25 17:05:10 -05:00
Zachary Yedidia
0f1483dc8c Almost done terminal emulator 2019-12-25 17:05:10 -05:00
Zachary Yedidia
4146730aaf Start terminal emulator 2019-12-25 17:05:10 -05:00
Zachary Yedidia
c479c9d91a Add shell command support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
0febfd2c80 Better tab mUI 2019-12-25 17:05:10 -05:00
Zachary Yedidia
eec4e535b4 Add tabbar and tab mouse support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
8aa05cf409 Begin tab implementation 2019-12-25 17:05:10 -05:00
Zachary Yedidia
fe773c00d2 Implement split resizing 2019-12-25 17:05:10 -05:00
Zachary Yedidia
f2cb7d2fc1 Implement unsplitting 2019-12-25 17:05:10 -05:00
Zachary Yedidia
4412b44b47 Add showkey 2019-12-25 17:05:10 -05:00
Zachary Yedidia
9cf283e312 Resizing work 2019-12-25 17:05:10 -05:00
Zachary Yedidia
305f4debff Split improvements 2019-12-25 17:05:10 -05:00
Zachary Yedidia
93aed1ab9f Fix some split bugs 2019-12-25 17:05:10 -05:00
Zachary Yedidia
778bfd5cd3 Merge cursors after any event 2019-12-25 17:05:10 -05:00
Zachary Yedidia
16e5f55323 YN callbacks and better multi cursor 2019-12-25 17:05:10 -05:00
Zachary Yedidia
1ac4a8e7d3 Split improvements 2019-12-25 17:05:10 -05:00
Zachary Yedidia
541daf212e Start working on splits 2019-12-25 17:05:10 -05:00
Zachary Yedidia
d4c410f3dc Infobar history 2019-12-25 17:05:10 -05:00
Zachary Yedidia
4b50599411 Complete multicursor support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
6cf09f9843 Find next and prev 2019-12-25 17:05:10 -05:00
Zachary Yedidia
37a4cbfd98 Implement searching 2019-12-25 17:05:10 -05:00
Zachary Yedidia
0f37c0b0bf Add multi cursor support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
80fe992957 Fix infobar prompt 2019-12-25 17:05:10 -05:00
Zachary Yedidia
e97005f05d Working horizontal scrolling 2019-12-25 17:05:10 -05:00
Zachary Yedidia
5335c60d6c Fix sub bug 2019-12-25 17:05:10 -05:00
Zachary Yedidia
b8b245f305 Add mouse support 2019-12-25 17:05:10 -05:00
Zachary Yedidia
3d2cc3298e Finish non global actions 2019-12-25 17:05:10 -05:00
Zachary Yedidia
a89ddea619 Fix error 2019-12-25 17:05:10 -05:00
Zachary Yedidia
6562e3b48d Start implementing commands 2019-12-25 17:05:10 -05:00
Zachary Yedidia
c01995c1b6 Reorganize info bar 2019-12-25 17:05:10 -05:00
Zachary Yedidia
78ce7a5f0f Minor infobar improvements 2019-12-25 17:05:10 -05:00
Zachary Yedidia
afe24698ea Infobar prompts 2019-12-25 17:05:10 -05:00
Zachary Yedidia
c50e0cb932 Add infobar 2019-12-25 17:05:10 -05:00
Zachary Yedidia
e9a4238a3f More actions and view relocation 2019-12-25 17:05:10 -05:00
Zachary Yedidia
02b71a514a Add some comments 2019-12-25 17:05:10 -05:00
Zachary Yedidia
9f066f2fbf Rehighlighting 2019-12-25 17:05:10 -05:00
Zachary Yedidia
12d727fb93 Add some more actions 2019-12-25 17:05:10 -05:00
Zachary Yedidia
31cf5a15ce Fix serialization 2019-12-25 17:05:10 -05:00
Zachary Yedidia
31fb3f2df2 More actions 2019-12-25 17:05:10 -05:00
Zachary Yedidia
7d87e6db99 More actions and window organization 2019-12-25 17:05:10 -05:00
Zachary Yedidia
06d596e780 Synchronize screen 2019-12-25 17:05:10 -05:00
Zachary Yedidia
d7b3f961b4 Action subpackage 2019-12-25 17:05:10 -05:00
Zachary Yedidia
c3e2085e3c Cursor improvements 2019-12-25 17:05:10 -05:00
Zachary Yedidia
dd619b3ff5 Reorganize file structure 2019-12-25 17:05:10 -05:00
Zachary Yedidia
dc68183fc1 Start refactor 2019-12-25 17:05:10 -05:00
Zachary Yedidia
d9735e5c3b Update readme 2019-12-25 16:17:31 -05:00
Zachary Yedidia
bd9307483d Merge pull request #1363 from andradei/patch-1
Separate keys with + sign for consistency
2019-12-19 10:23:48 -05:00
Zachary Yedidia
fd8fc3acfa Merge pull request #1410 from serge-v/textfilter
Add textfilter command
2019-12-19 10:15:49 -05:00
Zachary Yedidia
f932cfb7f1 Merge pull request #1409 from serge-v/syntax
Add mc, godoc, proto syntax files
2019-12-19 10:05:38 -05:00
Zachary Yedidia
8817d711b9 Merge pull request #1415 from dullbananas/master
Improve syntax files
2019-12-19 10:05:30 -05:00
Zachary Yedidia
1956a49f9b Merge pull request #1421 from j-mortara/master
Corrected tex comment start separator
2019-12-19 10:01:28 -05:00
nergel3
5d81fc7815 vue syntax (+typescript) & svelte syntax 2019-12-17 11:38:22 +01:00
Sterling Parker
0827968f6b Create zscript.yaml 2019-12-04 11:52:22 -07:00
Johann Mortara
5b869cb836 Corrected tex comment start separator 2019-11-29 12:05:54 +01:00
ariasuni
2d475fbca8 Remove unreliable XML entity handling from XML syntax 2019-11-27 17:39:31 +01:00
ariasuni
36862137db Improve XML character entities highlighting and comment 2019-11-27 14:54:53 +01:00
ariasuni
34c12e1282 Fix XML character entities (e.g. <) highlighting 2019-11-27 14:54:53 +01:00
ariasuni
8b0c858f28 Make XML highlighting more fine-grained 2019-11-27 14:54:53 +01:00
ariasuni
daa0d67c6f Fix XML highlighting when tags are spread on multiple lines 2019-11-27 14:54:53 +01:00
ariasuni
aaac5466d1 Set XML filetype for .svg and files with xml version tag 2019-11-27 14:54:53 +01:00
0xdbeef
89ac5d7de2 SpawnMultiCursorDown / SpawnMultiCursorUp 2019-11-17 11:22:25 +01:00
Dull Bananas
166e227c9f Merge branch 'master' into master 2019-11-15 18:46:13 -07:00
Dull Bananas
7d1dc1183c Improve JavaScript syntax 2019-11-15 18:42:17 -07:00
Dull Bananas
4662f0c500 Remove old code 2019-11-15 18:38:33 -07:00
Dull Bananas
78fd9fb225 Add jinja syntax 2019-11-15 18:37:41 -07:00
tommyshem
be0dcd5d10 Add windows .bat syntax highlighting file #1388 2019-11-14 01:11:02 +00:00
Serge Voilokov
1857aa4067 Add proto syntax file 2019-11-06 07:23:04 -05:00
Serge Voilokov
7a51490591 Add textfilter command 2019-11-05 23:27:35 -05:00
Serge Voilokov
1fc5b316ab Add mc, godoc syntax files 2019-11-05 22:57:36 -05:00
Leap of Azzam
9e0d3c7cbe Update vi syntax 2019-10-26 15:51:38 +07:00
Elliot Thomas
f12061ea88 ES6 - ES2019 additions
symbol.operator: Spread/rest operator.
statement: Generator function, Promise resolve and reject.
type: New built-in types, including section for TypedArrays.
constant.string: Template literal.
2019-10-02 09:50:46 +01:00
Jon Craton
e87917f1e1 Minor grammar fix addressing #1377 2019-09-13 11:07:11 -04:00
prez
aae0f4630e Added option to automatically save files with sucmd 2019-09-11 15:38:15 +02:00
Patrick Weingaertner
523592be26 fix #1318. fix html comments and make them work multiline 2019-09-05 09:41:35 +02:00
Isaac Andrade
e9337da43f Separate keys with + sign for consistency
Some textual changes (without changing formatting) were made to table header lines.
This is a tiny and almost inconsequential change to improve readability.
2019-07-31 12:48:51 -06:00
Zachary Yedidia
3a8898dadd Merge pull request #1340 from dullbananas/improve-help
Add detail to help
2019-07-25 19:58:50 -07:00
Zachary Yedidia
1c4e2eb09f Merge pull request #1356 from dullbananas/master
Add missing keywords for Python syntax
2019-07-25 19:58:08 -07:00
Patrick Weingärtner
c11af1c19c ( #1358 ) add vue single-file component syntax highlighting (#1359)
* add vue single-file component syntax highlighting

* remove unnecessary new line
2019-07-25 19:57:53 -07:00
Zachary Yedidia
4f35eed615 Merge pull request #1360 from Lisiadito/js
add TODO to javascript highlighting
2019-07-25 19:57:33 -07:00
Patrick Weingaertner
3a4bdb0db6 add TODO to JS highlighting 2019-07-25 23:22:15 +02:00
Antonino Siena
73093f9497 Added Ziglang syntax support (#1354)
* Added Ziglang syntax support

* Removed unused constant string markup

* Added 'fn' as keyword

* Added constant matching ending with numbers and proper hexadecimal matching

* Added missing types

* Added all keywords in alphabetical order
2019-07-06 01:51:12 -04:00
Zachary Yedidia
3644ef4a5a Merge pull request #1353 from jakobnissen/master
Updated Julia number and string syntax highlighting
2019-07-06 01:50:58 -04:00
Zachary Yedidia
8b4943fc26 Merge pull request #1352 from Akito13/master
Additional file extensions
2019-07-06 01:50:46 -04:00
Zachary Yedidia
a90b17c855 Merge pull request #1331 from corbuntus/master
Dlang character syntax highlighting
2019-07-06 01:50:28 -04:00
Zachary Yedidia
9a8b7ab757 Merge pull request #1310 from coolreader18/elm-syntax
Add Elm syntax file
2019-07-06 01:49:57 -04:00
Dull Bananas
9ec07b595b Add missing keywords for Python syntax 2019-07-03 16:53:53 -07:00
Jakob Nybo Nissen
9bfc35656c Updated Julia number and string syntax highlighting 2019-07-01 12:47:16 +02:00
Akito13
b2b933c6c1 Additional file extensions
* Added support for Nimscript files
2019-07-01 03:30:58 +02:00
Dull Bananas
beea8d42d5 Add detail 2019-05-29 21:17:56 -07:00
Ender - Josh Pritsker
612ebb2e17 Add Forth highlighting 2019-05-25 21:01:24 -07:00
corbuntus
957b3aaea7 Fix zyedidia#1330: Dlang character syntax highlighting 2019-05-24 20:12:45 +02:00
Osmose
a189d08c30 Fix #1314: Add support for diffs from git commit --verbose. 2019-05-21 17:26:06 -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
Didactic Drunk
e33489c04f Ruby syntax improvements.
String interpolation for Crystal syntax.
2019-04-30 11:29:42 -07:00
Mathieu Besançon
bcb8765049 Update julia.yaml
Some keywords are not in Julia and were removed. `include` is a standard function with no special property (no syntax-level highlight required)
2019-04-29 15:29:21 +02:00
coolreader18
7e34eabb0e Add Elm syntax file 2019-04-25 22:45:23 -05: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
Hugo Locurcio
1739e0c09c Highlight NimScript files (.nims) as Nim 2019-02-16 20:00:38 +01:00
Jawahar Suresh Babu
c46695bb57 Fixed the issue #1049 - Endless prompt to reload changes 2019-02-12 07:20:07 +05:30
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
Zachary Yedidia
2c219ba647 Merge pull request #1253 from ColinRioux/master
Fixes missing syntax highlighting for TCL
2019-01-04 22:58:35 -05:00
Colin Rioux
ca9b1d7b14 Fixes #1249 2018-12-21 12:50:33 -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
Zachary Yedidia
001498eee4 Update runtime 2018-12-10 14:33:21 -05:00
Zachary Yedidia
3c87d1cfb4 Merge pull request #1200 from Calinou/add-systemd-timer-section
Add [Timer] section to systemd highlighting
2018-12-10 14:32:06 -05:00
Zachary Yedidia
54183ec4d2 Merge pull request #1206 from kylebarron/material-colorscheme
Add Material colorscheme
2018-12-10 14:31:48 -05:00
Zachary Yedidia
6f3548e7ce Merge pull request #1223 from piaph/syntax_highlight_files_in_gentoo_portage_folders
Changed regex for Gentoo etc-portage to include detection of folders
2018-12-10 14:31:26 -05:00
Zachary Yedidia
2a0d78b86d Merge pull request #1201 from Calinou/use-more-ini-highlighting
Highlight .tscn, .tres and project.godot files using INI syntax
2018-12-10 14:31:05 -05:00
Zachary Yedidia
ba98e973c4 Merge pull request #1202 from luizbills/patch-1
add 'from' and 'of' keywords in javascript syntax file
2018-12-10 14:30:25 -05:00
Zachary Yedidia
3515f254c4 Merge pull request #1203 from luizbills/patch-2
detect '.mjs' as javascript file
2018-12-10 14:30:08 -05:00
Zachary Yedidia
2823058806 Merge pull request #1220 from yvendruscolo/patch-1
match .edn files
2018-12-10 14:29:53 -05:00
Zachary Yedidia
5c2fc92332 Merge pull request #1205 from kylebarron/python-syntax-fixes
Use symbol.operator and symbol.brackets scopes correctly in Python syntax file
2018-12-10 14:28:21 -05:00
Zachary Yedidia
64a6779482 Merge pull request #1207 from kylebarron/stata-syntax
Add Stata syntax file
2018-12-10 14:28:03 -05:00
Zachary Yedidia
49b6cf3673 Merge pull request #1233 from teresy/simplify-index
simplify cases of strings.Index with strings.Contains
2018-12-10 14:27:45 -05:00
Zachary Yedidia
37bd454679 Merge pull request #1234 from kylebarron/python-docstring
Python syntax: docstring should be string, not comment
2018-12-10 14:27:30 -05:00
Zachary Yedidia
f9e8d8b9a0 Merge pull request #1241 from Danmou/patch-1
Allow more ways to write booleans in YAML
2018-12-10 14:27:16 -05:00
Zachary Yedidia
e289d44034 Merge pull request #1242 from dwwmmn/dwwmmn-erl
Add syntax file for Erlang
2018-12-10 14:26:59 -05: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
Drew Malzahn
2fd85cb033 Add syntax file for Erlang
Syntax hilighting for Erlang. Comment definition taken from:

d953339a56/runtime/syntax/ocaml.yaml
2018-11-23 09:37:51 -05:00
Daniel Mouritzen
8bda0a6b45 Allow more ways to write booleans in YAML
See http://yaml.org/type/bool.html and http://yaml.org/spec/1.2/spec.html#id2805071
2018-11-22 11:00:23 +01:00
Kyle Barron
fd48a3841e Python syntax: docstring should be string, not comment 2018-11-04 12:36:39 -05:00
teresy
a69cc72c9d simplify cases of strings.Index with strings.Contains 2018-11-02 18:57:28 -04:00
Pia Philipsson
57c681eddf Changed filename detection for Gentoo etc-portage to include detection of folders 2018-10-19 09:47:51 +02:00
yvendruscolo
ec6943b1c9 match .edn files
there was no file/match for edn (Clojure's json) files, so that would solve it
2018-10-16 10:39:09 -03:00
Zachary Yedidia
e071a4f8e2 Better bounds checks for search
Fixes #1217
2018-10-14 17:58:44 -04:00
Kyle Barron
04420de96a Add Stata syntax file 2018-10-02 15:46:38 -04:00
Kyle Barron
bfc9d4a195 Add identifier.macro color 2018-10-02 15:45:40 -04:00
Kyle Barron
e3955882e4 Add Material colorscheme 2018-10-02 14:44:13 -04:00
Kyle Barron
e0ce419357 Use symbol.operator and symbol.brackets scopes correctly 2018-10-02 13:54:29 -04:00
Luiz Paulo "Bills
2d0ec82baa add 'of' statement 2018-09-29 23:23:42 -03:00
Luiz Paulo "Bills
a0a154d957 detect '.mjs' as javascript file
`.mjs` extension will be used as ECMAScript Modules
2018-09-29 14:43:10 -03:00
Luiz Paulo "Bills
fa05d63d11 add 'from' in javascript syntax file 2018-09-29 14:40:20 -03:00
Hugo Locurcio
249405355a Add [Timer] section to systemd highlighting 2018-09-29 12:15:50 +02:00
Hugo Locurcio
dab18e2fee Highlight .tscn, .tres and project.godot files using INI syntax
This also removes header detection for INI syntax, which could
occasionally cause other file types (such as systemd service files)
to be detected as INI.
2018-09-29 12:14:15 +02:00
Zachary Yedidia
de35d00ba7 Merge pull request #1194 from MJBrune/patch-1
Added an s to command(s)
2018-09-24 16:22:16 -04:00
Michael Brune
f68149489e Added an s to command(s)
Adding an S seems more intuitive here. The command you are being asked to run there completes to:
`help commands`
not `help command` as one might expect.
Although maybe help aliases might also be something to consider?
2018-09-24 13:12:01 -07:00
Zachary Yedidia
1013b03314 Merge 2018-09-21 23:18:58 -04:00
Zachary Yedidia
96284a1feb LoadAll should reload plugins too
Fixes #1189
2018-09-21 23:18:47 -04:00
Zachary Yedidia
d2b51a59d6 Merge pull request #1173 from sc0ttj/enable-auto-highlighting-for-ash-shell
Update sh.yaml to support Ash scripts
2018-09-03 16:57:50 -04:00
Scott Jarvis
0e56c0c816 Update sh.yaml
support Ash as well as Bash, Sh, Dash.
2018-09-02 11:52:26 +00:00
Zachary Yedidia
f40abc1a59 Fix infocmp parser
Ref #1167
2018-08-29 13:01:38 -04:00
Zachary Yedidia
0a6948c8ac Merge 2018-08-29 12:16:18 -04:00
Zachary Yedidia
9db7991a1d Handle hex codes in infocmp output 2018-08-29 12:16:11 -04:00
Zachary Yedidia
7339afcf73 Add tcelldb error check 2018-08-28 14:26:21 -04:00
Zachary Yedidia
9cbe2c62de Merge pull request #1166 from rexy712/master
Fix UpN to handle going from long line to short line
2018-08-25 19:35:00 -04:00
rexy712
6e9b8c1bd5 Fixed UpN Cursor functionality to properly handle moving from long line to shorter line 2018-08-25 14:49:58 -07:00
Zachary Yedidia
e11d9deb6e Merge pull request #1165 from ev-dev/master
Basic syntax highlighting for the GraphQL language based on the official specification
2018-08-25 17:39:13 -04:00
Zachary Yedidia
1d93433bfb Merge pull request #1148 from Calinou/improve-gdscript-syntax
Improve the GDScript syntax file
2018-08-25 17:38:56 -04:00
Zachary Yedidia
45643f397b Merge pull request #1147 from Calinou/fix-c-keyword-highlighting
Fix some keywords being mistakenly highlighted in C syntax
2018-08-25 17:38:29 -04:00
Visual-Knowledge
33d9b8f60b Basic syntax highlighting for Graphql based on the official specification 2018-08-24 03:25:40 -07:00
Zachary Yedidia
6140dabca8 Merge pull request #1160 from supbish/fix-sh-comment
Fix shell comments; fixes #1114
2018-08-20 21:03:00 -07:00
supbish
27db63433f Fix shell comments; fixes #1114 2018-08-20 16:22:07 -04:00
Zachary Yedidia
bcdab882bc Update runtime 2018-08-18 15:25:42 -07:00
Zachary Yedidia
32b8c51992 Merge pull request #1158 from supbish/lua-syntax
Lua syntax improvements; fixes #1155, fixes #1136
2018-08-18 15:25:03 -07:00
supbish
4be3e9122c Lua syntax improvements; fixes #1155, fixes #1136 2018-08-18 07:00:51 -04:00
Zachary Yedidia
d0f8bede41 Merge pull request #1157 from supbish/smart-paste-indent
Add "smartpaste" option; fixes #1156
2018-08-17 21:23:42 -07:00
supbish
905e984f29 Add "smartpaste" option; fixes #1156 2018-08-17 22:37:19 -04:00
Zachary Yedidia
44e417c2f4 Merge pull request #1154 from supbish/luatabs
Add GetTabs Lua function
2018-08-15 11:56:10 -04:00
supbish
e03fab8daa Add GetTabs Lua function 2018-08-15 11:18:27 -04:00
Camille
1ab493de59 Only show basename of file in tabs unless there are mutliple tabs with the same basename (fixes #1079) (#1081)
* Only show basename of file in tabs unless there are mutliple tabs with the same basename (fixes #1079)

* Small fix
2018-08-10 16:54:19 -04:00
Zachary Yedidia
f56621a4bd Bump version 2018-08-10 13:45:03 -04:00
Hugo Locurcio
497ca2c66b Improve the GDScript syntax file
More keywords are now recognized. Some leftover syntax definitions
from Python 3 that are not allowed in GDScript were also removed.
2018-08-07 15:16:23 +02:00
Hugo Locurcio
18ca06d9be Fix some keywords being mistakenly highlighted in C syntax 2018-08-07 14:44:53 +02:00
Hugo Locurcio
a732d03b4d Improve writing style and formatting in README 2018-08-07 14:35:07 +02:00
Zachary Yedidia
1856891622 Update nightly release script to not duplicate nightlies 2018-07-20 00:24:02 +00:00
djmnzp
8a250f7d95 Update ats syntax (#1141)
* Multiple changes
 - Fixed overlapping between the macros and some statements.
 - Added "t" and "abs" as types.
 - Removed "fun0", "fun1", "clo0", "clo1", ..., "prf" from types and added them to the special block as effects.
 - Added "lin", "lincloptr0" and "lincloptr1" as effects.
 - Added "do" and "static" as statements.
 - Added "tupz!" and "prerr!" to the special block.
 - Fixed some typos.

* Updated regex for exhaustive types

* Final touches

* Removed "t" from types

* Minor fix

* Improved support for floats and integers
Make it comply with https://github.com/Hibou57/PostiATS-Utilities/blob/master/doc/lexemes-guide.md

* Chars are now interpreted as strings
Less troubling when working with '"' inside chars or multiline strings

* Reverted strings and chars from multiline to one line
For some reason, having strings on the same line as other symbols breaks the highlighting on the latter

* Add "ldouble" type
2018-07-16 15:37:57 -04:00
Zachary Yedidia
7a013f666e Update runtime and auto-gofmt runtime in make 2018-07-02 12:22:32 -04:00
Zachary Yedidia
41a24e61d6 Merge pull request #1135 from whilei/gofmt-2018-Jun-17-00-39
gofmt
2018-07-02 12:22:05 -04:00
djmnzp
d953339a56 Added syntax highlighting for ATS (#1137)
* Added syntax highlighting for ATS

* Fixed "////" comment not working as intended
Added a hack to make it impossible to match the end of the comment

* Fixed typo, added '#' and '@' as symbols
2018-07-02 12:19:38 -04:00
ia
76e1d7a3a7 all: gofmt
Run standard gofmt command on project root.

- go version go1.10.3 darwin/amd64

Signed-off-by: ia <isaac.ardis@gmail.com>
2018-06-17 00:41:57 +02:00
Zachary Yedidia
91b65001c9 Fix php syntax file
Fixes #1109
2018-06-04 15:13:58 -04:00
Dimitar Borislavov Tasev
aa74b1233c Fix -startpos flag being ignored (#1129)
* Refactored cursor location login into a function. Fixed buffer overflow when line position is 1 more than file lines

* Fixed crash when -startpos has an invalid argument

* Adapted tests to new interface

* Fixed bug where -startpos with lines 0 and 1 would both be on the first line

* Changed Fatalf format back to digits

* Fixed issues with buffer cursor location. Added tests for new function

* ParseCursorLocation will now return an error when path doesnt contain line/col

* Fixed off-by-one line error

* Fixed tests to account for subtracting 1 from the line index
2018-06-04 12:27:27 -04:00
Zachary Yedidia
61baa73d70 Merge pull request #1125 from nabeelomer/master
F# Configuration
2018-06-03 17:13:22 -04:00
Dimitar Borislavov Tasev
efe343b37c Allows opening files using full path on Windows (#1126)
* Now can open Windows full-path from command line arg

Example that now works: micro.exe D:\myfile.txt

* Now correctly retrieves the path from the input path string. Except for single-letter filenames

* Fixed line/cols, need to make the code prettier

* Fixed path matching with regex by @Pariador

* Fixed not stripping the line/col args from file path

* Added tests for ParseCursorLocation
2018-06-03 17:13:03 -04:00
Nabeel Omer
cc8e9a7e06 F# Configuration 2018-05-29 20:02:58 +05:30
Sean Charles
d7f7d845b9 Elixir configuration (#1118)
* Elixir configuration

* added exunit support

* end added
2018-05-26 10:08:35 -04:00
Zachary Yedidia
8f0418c9a8 Merge pull request #1119 from mbesancon/patch-3
Update julia.yaml
2018-05-26 10:08:19 -04:00
Maxim
71af765b4e Code optimisation (#1117)
* Making sure output files are always closed, plus hash calculation optimisation.

* Parallel hash calculation.

* Minor changes.

* Removed unnecessary memory allocations while trimming trailing whitespace.

* Buffered write.
2018-05-26 10:07:53 -04:00
mbesancon
c0f279ffe8 Update julia.yaml
added struct to keywords
2018-05-25 12:04:12 -04:00
JT Olio
ae9bb763fb a few miscellaneous fixes and improvements (#1105)
* add binding for more primitive backspace

* support selecting page up and page down

* fix matchbraceleft for braces that start on x=0

* fix multiline copy-paste indenting

let's say you have two lines like

  <space><space>line1
  <space><space>line2

so you start from cursor x=0 and select both lines, then paste.
we don't want any leading whitespace in this case, because the
cursor is already at x=0 and the selection already includes
whitespace.
2018-05-12 21:31:57 -04:00
sum01
28267b9eb2 Add back ?
Accidentally removed
2018-02-06 13:41:56 -05:00
sum01
cad43914b0 Html syntax fixes #1008
Note that there's a TODO with if/when 'limit-rules' are added.
Till that's added, any 'style' and 'script' blocks will be missing highlighting on their identifiers.
The actual contents (CSS or JS) will still work correctly though.
2018-02-05 23:13:57 -05:00
276 changed files with 24192 additions and 20834 deletions

9
.gitignore vendored
View File

@@ -7,3 +7,12 @@ tmp.sh
test/
.idea/
packages/
todo.txt
test.txt
log.txt
*.old
tools/build-version
tools/build-date
tools/info-plist
tools/bindata
*.hdr

75
.gitmodules vendored
View File

@@ -1,72 +1,3 @@
[submodule "cmd/micro/vendor/github.com/blang/semver"]
path = cmd/micro/vendor/github.com/blang/semver
url = https://github.com/blang/semver
[submodule "cmd/micro/vendor/github.com/dustin/go-humanize"]
path = cmd/micro/vendor/github.com/dustin/go-humanize
url = https://github.com/dustin/go-humanize
[submodule "cmd/micro/vendor/github.com/go-errors/errors"]
path = cmd/micro/vendor/github.com/go-errors/errors
url = https://github.com/go-errors/errors
[submodule "cmd/micro/vendor/github.com/mattn/go-isatty"]
path = cmd/micro/vendor/github.com/mattn/go-isatty
url = https://github.com/mattn/go-isatty
[submodule "cmd/micro/vendor/github.com/mattn/go-runewidth"]
path = cmd/micro/vendor/github.com/mattn/go-runewidth
url = https://github.com/mattn/go-runewidth
[submodule "cmd/micro/vendor/github.com/mitchellh/go-homedir"]
path = cmd/micro/vendor/github.com/mitchellh/go-homedir
url = https://github.com/mitchellh/go-homedir
[submodule "cmd/micro/vendor/github.com/sergi/go-diff"]
path = cmd/micro/vendor/github.com/sergi/go-diff
url = https://github.com/sergi/go-diff
[submodule "cmd/micro/vendor/github.com/yuin/gopher-lua"]
path = cmd/micro/vendor/github.com/yuin/gopher-lua
url = https://github.com/yuin/gopher-lua
[submodule "cmd/micro/vendor/golang.org/x/net"]
path = cmd/micro/vendor/golang.org/x/net
url = https://go.googlesource.com/net
[submodule "cmd/micro/vendor/github.com/zyedidia/clipboard"]
path = cmd/micro/vendor/github.com/zyedidia/clipboard
url = https://github.com/zyedidia/clipboard
[submodule "cmd/micro/vendor/github.com/zyedidia/glob"]
path = cmd/micro/vendor/github.com/zyedidia/glob
url = https://github.com/zyedidia/glob
[submodule "cmd/micro/vendor/github.com/zyedidia/tcell"]
path = cmd/micro/vendor/github.com/zyedidia/tcell
url = https://github.com/zyedidia/tcell
[submodule "cmd/micro/vendor/github.com/gdamore/encoding"]
path = cmd/micro/vendor/github.com/gdamore/encoding
url = https://github.com/gdamore/encoding
[submodule "cmd/micro/vendor/golang.org/x/text"]
path = cmd/micro/vendor/golang.org/x/text
url = https://go.googlesource.com/text
[submodule "cmd/micro/vendor/github.com/lucasb-eyer/go-colorful"]
path = cmd/micro/vendor/github.com/lucasb-eyer/go-colorful
url = https://github.com/lucasb-eyer/go-colorful
[submodule "cmd/micro/vendor/layeh.com/gopher-luar"]
path = cmd/micro/vendor/layeh.com/gopher-luar
url = https://github.com/layeh/gopher-luar
[submodule "cmd/micro/vendor/gopkg.in/yaml.v2"]
path = cmd/micro/vendor/gopkg.in/yaml.v2
url = https://gopkg.in/yaml.v2
[submodule "cmd/micro/vendor/github.com/zyedidia/poller"]
path = cmd/micro/vendor/github.com/zyedidia/poller
url = https://github.com/zyedidia/poller
[submodule "cmd/micro/vendor/github.com/flynn/json5"]
path = cmd/micro/vendor/github.com/flynn/json5
url = https://github.com/flynn/json5
[submodule "cmd/micro/vendor/github.com/zyedidia/terminal"]
path = cmd/micro/vendor/github.com/zyedidia/terminal
url = https://github.com/zyedidia/terminal
[submodule "cmd/micro/vendor/github.com/zyedidia/pty"]
path = cmd/micro/vendor/github.com/zyedidia/pty
url = https://github.com/zyedidia/pty
[submodule "cmd/micro/vendor/github.com/smartystreets/goconvey"]
path = cmd/micro/vendor/github.com/smartystreets/goconvey
url = https://github.com/smartystreets/goconvey
[submodule "cmd/micro/vendor/github.com/jtolds/gls"]
path = cmd/micro/vendor/github.com/jtolds/gls
url = https://github.com/jtolds/gls
[submodule "cmd/micro/vendor/github.com/smartystreets/assertions"]
path = cmd/micro/vendor/github.com/smartystreets/assertions
url = https://github.com/smartystreets/assertions
[submodule "tools/go-bindata"]
path = tools/go-bindata
url = https://github.com/zyedidia/go-bindata

View File

@@ -1,2 +1,9 @@
language: go
script: make test
go:
- "1.11.x"
os:
- linux
- osx
script:
- env GO111MODULE=on make build
- env GO111MODULE=on make test

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2016-2017: Zachary Yedidia, et al.
Copyright (c) 2016-2020: Zachary Yedidia, et al.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

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

@@ -1,49 +1,51 @@
.PHONY: runtime
VERSION := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
go run tools/build-version.go)
HASH := $(shell git rev-parse --short HEAD)
DATE := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
HASH = $(shell git rev-parse --short HEAD)
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
go run tools/build-date.go)
ADDITIONAL_GO_LINKER_FLAGS := $(shell GOOS=$(shell go env GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH) \
go run tools/info-plist.go "$(VERSION)")
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH))
GOBIN ?= $(shell go env GOPATH)/bin
GOVARS = -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/internal/util.CompileDate=$(DATE)' -X github.com/zyedidia/micro/internal/util.Debug=OFF
# Builds micro after checking dependencies but without updating the runtime
build: update
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build:
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-dbg:
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Builds micro after building the runtime and checking dependencies
build-all: runtime build
# Builds micro without checking for dependencies
build-quick:
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(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: update
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
install:
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 -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
update:
git pull
git submodule update --init
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Builds the runtime
runtime:
go get -u github.com/jteeuwen/go-bindata/...
$(GOBIN)/go-bindata -nometadata -o runtime.go runtime/...
mv runtime.go cmd/micro
git submodule update --init
go run runtime/syntax/make_headers.go runtime/syntax
go build -o tools/bindata ./tools/go-bindata
tools/bindata -pkg config -nomemcopy -nometadata -o runtime.go runtime/...
mv runtime.go internal/config
gofmt -w internal/config/runtime.go
test:
go test ./cmd/micro
go test ./internal/...
clean:
rm -f micro

211
README.md
View File

@@ -1,75 +1,76 @@
# ![Micro](./assets/logo.png)
<img alt="micro logo" src="./assets/micro-logo.svg" width="500px"/>
[![Build Status](https://travis-ci.org/zyedidia/micro.svg?branch=master)](https://travis-ci.org/zyedidia/micro)
[![Go Report Card](https://goreportcard.com/badge/github.com/zyedidia/micro)](https://goreportcard.com/report/github.com/zyedidia/micro)
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Release](https://img.shields.io/github/release/zyedidia/micro.svg?label=Release)](https://github.com/zyedidia/micro/releases)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Snap Status](https://build.snapcraft.io/badge/zyedidia/micro.svg)](https://build.snapcraft.io/user/zyedidia/micro)
Micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities
of modern terminals. It comes as one single, batteries-included, static binary with no dependencies, and you can download and use it right now.
**micro** is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the capabilities
of modern terminals. It comes as a single, batteries-included, static binary with no dependencies; you can download and use it right now!
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
As its name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use.
It strives to be enjoyable as a full-time editor for people who prefer to work in a terminal, or those who regularly edit files over SSH.
Here is a picture of micro editing its source code.
![Screenshot](./assets/micro-solarized.png)
To see more screenshots of micro, showcasing all of the default colorschemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
To see more screenshots of micro, showcasing some of the default color schemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
You can also check out the website for Micro at https://micro-editor.github.io.
# Table of Contents
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Prebuilt binaries](#prebuilt-binaries)
- [Package Managers](#package-managers)
- [Building from source](#building-from-source)
- [MacOS terminal](#macos-terminal)
- [Fully static binary](#fully-static-binary)
- [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)
- - -
# Features
## Features
* Easy to use and to install
* No dependencies or external files are needed -- just the binary you can download further down the page
* Multiple cursors
* Common keybindings (ctrl-s, ctrl-c, ctrl-v, ctrl-z...)
* Keybindings can be rebound to your liking
* Sane defaults
* You shouldn't have to configure much out of the box (and it is extremely easy to configure)
* Splits and tabs
* Nano-like menu to help you remember the keybindings
* Extremely good mouse support
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
* Cross platform (It should work on all the platforms Go runs on)
* Note that while Windows is supported, there are still some bugs that need to be worked out
* 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)!)
* 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`
* 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...
- Easy to use and install.
- No dependencies or external files are needed just the binary you can download further down the page.
- Multiple cursors.
- Common keybindings (<kbd>Ctrl+S</kbd>, <kbd>Ctrl+C</kbd>, <kbd>Ctrl+V</kbd>, <kbd>Ctrl+Z</kbd>, …).
- Keybindings can be rebound to your liking.
- Sane defaults.
- You shouldn't have to configure much out of the box (and it is extremely easy to configure).
- Splits and tabs.
- nano-like menu to help you remember the keybindings.
- Extremely good mouse support.
- This means mouse dragging to create a selection, double click to select by word, and triple click to select by line.
- Cross-platform (it should work on all the platforms Go runs on).
- Note that while Windows is supported Mingw/Cygwin is not (see below)
- Plugin system (plugins are written in Lua).
- micro has a built-in plugin manager to automatically install, remove, and update plugins.
- Built-in diff gutter
- Simple autocompletion
- Persistent undo.
- Automatic linting and error notifications
- Syntax highlighting for over [130 languages](runtime/syntax).
- Color scheme support.
- By default, micro comes with 16, 256, and true color themes.
- True color support (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 features such as undo/redo, line numbers, Unicode support, soft wrapping, …
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
## Installation
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
@@ -84,19 +85,19 @@ Download the binary from the [releases](https://github.com/zyedidia/micro/releas
On that page you'll see the nightly release, which contains binaries for micro which are built every night,
and you'll see all the stable releases with the corresponding binaries.
If you'd like to see more information after installing micro, run `micro -version`.
Running `micro -version` will give you the version information.
### Installation script
There is a great script which can install micro for you by downloading the latest prebuilt binary. You can find it at https://getmic.ro (the github repo for it is [here](https://github.com/benweissmann/getmic.ro)).
There is a script which can install micro for you by downloading the latest prebuilt binary. You can find it at <https://getmic.ro>.
Then you can easily install micro:
$ curl https://getmic.ro | bash
```bash
curl https://getmic.ro | bash
```
The script will install the micro binary to the current directory.
See the [Github page](https://github.com/benweissmann/getmic.ro) for more information.
The script will install the micro binary to the current directory. See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
### Package managers
@@ -106,55 +107,70 @@ 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)
```
snap install micro --classic
```
On OpenBSD, micro is available in the ports tree. It is also available as a binary package.
On Debian `unstable | testing | buster-backports` and Ubuntu `focal` (20.04), micro is available
via `apt`:
```
pkg_add -v micro
sudo apt install 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 (necessary for the plugin manager),
and doesn't disable debug mode.
### MacOS terminal
### Fully static binary
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.
By default, the micro binary will dynamically link with core system libraries (this is generally
recommended for security and portability). However, there is a fully static prebuilt binary that
is provided for amd64 as `linux-static.tar.gz`, and to build a fully static binary from source, run
```
CGO_ENABLED=0 make build
```
### macOS terminal
If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->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.
On Linux, clipboard support requires the `xclip` or `xsel` commands to be installed.
For Ubuntu:
@@ -162,33 +178,40 @@ For Ubuntu:
sudo apt-get install xclip
```
If you don't have xclip or xsel, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
If you don't have `xclip` or `xsel`, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
### 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`
by pressing CtrlE in micro and typing `set colorscheme simple`.
you are using a terminal which does not support 256 color mode. Try changing the color scheme to `simple`
by pressing <kbd>Ctrl+E</kbd> in micro and typing `set colorscheme simple`.
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
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.
that micro's default color scheme won't look very good. You can either set
the color scheme to `simple`, or download and configure a better terminal emulator
than the Windows default.
### Plan9, Cygwin
### Cygwin, Mingw, Plan9
Please note that micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
Cygwin, Mingw, and Plan9 are unfortunately not officially supported. In Cygwin and Mingw, micro will often work when run using
the `winpty` utility:
```
winpty micro.exe ...
```
Micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (which is deprecated anyway).
# Usage
## Usage
Once you have built the editor, simply start it by running `micro path/to/file.txt` or simply `micro` to open an empty buffer.
Once you have built the editor, start it by running `micro path/to/file.txt` or `micro` to open an empty buffer.
Micro also supports creating buffers from `stdin`:
micro also supports creating buffers from `stdin`:
```sh
ifconfig | micro
@@ -200,22 +223,22 @@ You can also use the mouse to manipulate the text. Simply clicking and dragging
will select text. You can also double click to enable word selection, and triple
click to enable line selection.
# Documentation and Help
## Documentation and Help
Micro has a built-in help system which you can access by pressing `Ctrl-E` and typing `help`. Additionally, you can
micro has a built-in help system which you can access by pressing <kbd>Ctrl+E</kbd> and typing `help`. Additionally, you can
view the help files here:
* [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
* [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
* [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
* [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
* [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
* [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
- [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
- [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
- [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
- [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
- [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
- [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
I also recommend reading the [tutorial](https://github.com/zyedidia/micro/tree/master/runtime/help/tutorial.md) for
a brief introduction to the more powerful configuration features micro offers.
# Contributing
## Contributing
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

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

Before

Width:  |  Height:  |  Size: 3.6 KiB

70
assets/micro-logo.svg Normal file
View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 299.89999 103.2"
enable-background="new 0 0 960 560"
xml:space="preserve"
inkscape:version="0.91 r13725"
sodipodi:docname="micro-logo.svg"
width="299.89999"
height="103.2"><metadata
id="metadata21"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs19" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1237"
inkscape:window-height="867"
id="namedview17"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="1.1416667"
inkscape:cx="75.655934"
inkscape:cy="-4"
inkscape:window-x="1097"
inkscape:window-y="185"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" /><g
id="g3"
transform="translate(-178,-172.8)"><path
d="m 306.8,213.8 0,-2.6 c 1.6,-0.1 2.9,-0.4 4.1,-0.8 1.2,-0.4 2.5,-1 4,-1.8 l 2.3,0 0,5.2 c 2.4,-1.9 4.2,-3.1 5.5,-3.8 2,-1 4,-1.5 5.8,-1.5 1.3,0 2.5,0.2 3.7,0.7 1.2,0.5 2.2,1 2.9,1.7 0.7,0.7 1.4,1.6 1.9,2.8 2.2,-1.9 4.2,-3.3 6,-4 1.9,-0.8 3.7,-1.2 5.6,-1.2 1.8,0 3.4,0.4 4.8,1.1 1.4,0.8 2.4,1.7 3,2.8 0.6,1.1 0.9,2.8 0.9,5 l 0,14.4 c 0,1.5 0,2.4 0.1,2.6 0.1,0.4 0.3,0.8 0.7,1.1 0.3,0.4 0.7,0.6 1.2,0.7 0.4,0.1 1.2,0.2 2.4,0.2 l 1,0 0,2.6 -15.5,0 0,-2.6 c 1.8,0 2.9,-0.1 3.5,-0.4 0.5,-0.2 0.9,-0.6 1.2,-1.2 0.3,-0.6 0.4,-1.6 0.4,-3.2 l 0,-13.7 c 0,-1.7 -0.2,-2.9 -0.5,-3.6 -0.3,-0.7 -0.9,-1.2 -1.7,-1.7 -0.8,-0.4 -1.8,-0.7 -3,-0.7 -1.5,0 -3,0.4 -4.6,1.2 -2.2,1.1 -3.9,2.3 -5.1,3.6 l 0,14.8 c 0,1.4 0.1,2.4 0.2,2.8 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.5 1.1,0.6 0.4,0.1 1.5,0.2 3.1,0.2 l 0,2.6 -15.3,0 0,-2.6 0.9,0 c 1.2,0 2.1,-0.1 2.6,-0.4 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.2,-0.5 0.3,-1.5 0.3,-2.9 l 0,-13.2 c 0,-1.9 -0.2,-3.3 -0.5,-3.9 -0.3,-0.7 -0.9,-1.3 -1.7,-1.7 -0.8,-0.5 -1.8,-0.7 -3,-0.7 -1.3,0 -2.7,0.3 -4.1,1 -2,1 -3.9,2.2 -5.6,3.8 l 0,15.9 c 0,1 0.1,1.6 0.4,2.1 0.3,0.4 0.7,0.8 1.2,1.1 0.6,0.3 1.3,0.4 2.3,0.4 l 1.1,0 0,2.6 -15.6,0 0,-2.6 0.8,0 c 1.4,0 2.4,-0.1 2.8,-0.3 0.7,-0.3 1.1,-0.8 1.4,-1.5 0.2,-0.4 0.2,-1.3 0.2,-2.9 l 0,-18.1 -5.1,0 z"
id="path5"
inkscape:connector-curvature="0" /><path
d="m 366.4,213.7 0,-2.6 c 1.7,-0.2 3.2,-0.5 4.3,-0.9 1.2,-0.4 2.5,-1 4,-1.7 l 2.3,0 0,24.9 c 0,0.9 0.1,1.5 0.4,2 0.2,0.4 0.6,0.8 1,0.9 0.4,0.2 1.3,0.3 2.4,0.3 l 1.5,0 0,2.6 -15.9,0 0,-2.6 1.3,0 c 1.4,0 2.3,-0.1 2.8,-0.4 0.5,-0.2 0.8,-0.6 1,-1.1 0.2,-0.5 0.3,-1.5 0.3,-3.2 l 0,-18.3 -5.4,0 z m 7.9,-19.2 c 1,0 1.8,0.3 2.5,1 0.7,0.7 1.1,1.5 1.1,2.5 0,1 -0.4,1.8 -1.1,2.5 -0.7,0.7 -1.6,1.1 -2.5,1.1 -1,0 -1.8,-0.4 -2.5,-1.1 -0.7,-0.7 -1.1,-1.6 -1.1,-2.5 0,-1 0.4,-1.8 1.1,-2.5 0.6,-0.6 1.5,-1 2.5,-1 z"
id="path7"
inkscape:connector-curvature="0" /><path
d="m 413.1,230.6 2,1.6 c -3.9,5.2 -8.6,7.8 -14,7.8 -4.2,0 -7.8,-1.5 -10.7,-4.5 -2.9,-3 -4.4,-6.8 -4.4,-11.3 0,-3 0.7,-5.7 2,-8.1 1.3,-2.4 3.2,-4.2 5.6,-5.6 2.4,-1.3 5.2,-2 8.3,-2 3.6,0 6.5,0.9 8.9,2.6 2.4,1.7 3.6,3.5 3.6,5.3 0,1 -0.3,1.7 -0.8,2.2 -0.5,0.5 -1.2,0.8 -1.9,0.8 -0.4,0 -0.7,-0.1 -1.1,-0.3 -0.4,-0.2 -0.7,-0.5 -1.1,-0.9 -0.2,-0.2 -0.5,-0.8 -0.9,-1.7 -0.6,-1.2 -1,-2 -1.3,-2.4 -0.6,-0.8 -1.4,-1.5 -2.4,-2 -0.9,-0.5 -2,-0.7 -3.1,-0.7 -1.8,0 -3.4,0.5 -4.9,1.5 -1.5,1 -2.7,2.4 -3.6,4.3 -0.9,1.9 -1.3,4.2 -1.3,6.8 0,4.1 1.1,7.3 3.3,9.7 1.9,2.1 4.1,3.1 6.7,3.1 1.2,0 2.4,-0.2 3.6,-0.6 1.2,-0.4 2.4,-1 3.5,-1.8 0.9,-0.6 2.2,-1.8 4,-3.8 z"
id="path9"
inkscape:connector-curvature="0" /><path
d="m 418.7,213.7 0,-2.6 c 1.5,-0.1 2.8,-0.4 4,-0.8 1.2,-0.4 2.5,-1 4,-1.9 l 2.3,0 0,5.9 c 1.5,-1.8 3.2,-3.2 5.1,-4.3 1.9,-1.1 3.7,-1.6 5.2,-1.6 1.5,0 2.7,0.4 3.6,1.1 0.9,0.7 1.3,1.6 1.3,2.6 0,0.7 -0.3,1.4 -0.9,2 -0.6,0.6 -1.3,0.9 -2.1,0.9 -0.4,0 -0.7,-0.1 -1,-0.2 -0.3,-0.1 -0.7,-0.3 -1.2,-0.7 -1.1,-0.7 -2.1,-1.1 -2.9,-1.1 -1,0 -2.2,0.4 -3.4,1.3 -1.6,1.1 -2.8,2.2 -3.7,3.3 l 0,14.4 c 0,1.2 0.1,2.1 0.2,2.5 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.6 1.1,0.7 0.5,0.1 1.3,0.2 2.4,0.2 l 1,0 0,2.6 -16,0 0,-2.6 1.3,0 c 1.3,0 2.1,-0.1 2.5,-0.3 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.3,-0.5 0.4,-1.5 0.4,-3 l 0,-18.3 -5.1,0 z"
id="path11"
inkscape:connector-curvature="0" /><path
d="m 462.8,208.5 c 3,0 5.7,0.6 7.9,1.9 2.2,1.3 4,3.1 5.3,5.5 1.3,2.4 1.9,5.2 1.9,8.3 0,3.1 -0.7,5.9 -2,8.3 -1.3,2.4 -3.1,4.3 -5.4,5.5 -2.3,1.3 -5,1.9 -8.1,1.9 -5,0 -8.8,-1.6 -11.3,-4.7 -2.5,-3.1 -3.8,-6.8 -3.8,-11 0,-3.1 0.7,-5.8 2,-8.2 1.3,-2.4 3.1,-4.2 5.5,-5.6 2.4,-1.2 5.1,-1.9 8,-1.9 z m -0.2,3 c -2.4,0 -4.4,0.9 -6,2.8 -2.1,2.3 -3.1,5.7 -3.1,10.1 0,4.3 0.9,7.5 2.6,9.7 1.6,2 3.8,3 6.5,3 1.8,0 3.3,-0.5 4.7,-1.4 1.4,-0.9 2.5,-2.4 3.3,-4.5 0.8,-2 1.3,-4.4 1.3,-7.2 0,-2.7 -0.5,-5.1 -1.4,-7.2 -0.7,-1.7 -1.8,-3 -3.2,-4 -1.3,-0.8 -2.9,-1.3 -4.7,-1.3 z"
id="path13"
inkscape:connector-curvature="0" /></g><path
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
id="path15"
inkscape:connector-curvature="0"
style="fill:#2e3192" /></svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 253 KiB

View File

@@ -1,11 +1,4 @@
.\" micro manual page - micro(1)
.\"
.\" Copyright © 2017 Zachary Yedidia <zyedidia@gmail.com>
.\" Copyright © 2017 Collin Warren <anatoly@somethinghub.com>
.\"
.\" This document is provided under the same licensing as micro.
.\" See \usr\share\doc\micro\LICENSE for more information.
.TH micro 1 "2017-03-28"
.TH micro 1 "2020-02-10"
.SH NAME
micro \- A modern and intuitive terminal-based text editor
.SH SYNOPSIS
@@ -22,39 +15,96 @@ As the name indicates, micro aims to be somewhat of a successor to the nano edit
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
.SH OPTIONS
.PP
\-clean
.RS 4
Cleans the configuration directory
.RE
.PP
\-config-dir dir
.RS 4
Specify a custom location for the configuration directory
.RE
.PP
\-startpos LINE,COL
[FILE]:LINE:COL
.RS 4
Specify a line and column to start the cursor at when opening a buffer
.RE
.PP
\-options
.RS 4
Show all option help
.RE
.PP
\-debug
.RS 4
Enable debug mode (enables logging to ./log.txt)
.RE
.PP
\-version
.RS 4
Show the version number and information
.RE
Micro's plugin's can be managed at the command line with the following commands.
.RS 4
.PP
\-plugin remove [PLUGIN]...
.RS 4
Remove plugin(s)
.RE
.PP
\-plugin update [PLUGIN]...
.RS 4
Update plugin(s) (if no argument is given, updates all plugins)
.RE
.PP
\-plugin search [PLUGIN]...
.RS 4
Search for a plugin
.RE
.PP
\-plugin list
.RS 4
List installed plugins
.RE
.PP
\-plugin available
.RS 4
List available plugins
.RE
.RE
Micro's options can also be set via command line arguments for quick
adjustments. For real configuration, please use the settings.json
file (see 'help options').
.RS 4
.PP
\-option value
.RS 4
Set `option` to `value` for this session
For example: `micro -syntax off file.c`
.RE
.SH CONFIGURATION
Micro uses
\fI$XDG_CONFIG_HOME/micro\fR
for configuration by default. If it is not set, micro uses ~/.config/micro.
Two main configuration files are settings.json, containing the user's
settings, and bindings.json, containing the user's custom keybindings.
.SH ENVIRONMENT
Micro's behaviour can be changed by setting environment variables, of which there is currently only one:
\fIMICRO_TRUECOLOR\fR.
When MICRO_TRUECOLOR is set to 1, micro will attempt to treat your terminal as a true-color terminal and will be able to make full use of the true-color colorschemes that are included with micro. If MICRO_TRUECOLOR is not set or is set to 0, then micro will only make use of 256 color features and will internally map true-color colorschemes to the nearest colors available. For more information see micro's documentation.
Micro uses $MICRO_CONFIG_HOME as the configuration directory.
If this environment variable is not set, it uses $XDG_CONFIG_HOME/micro instead.
If that environment variable is not set, it uses ~/.config/micro as the configuration directory.
In the documentation, we use ~/.config/micro to refer to the configuration directory
(even if it may in fact be somewhere else if you have set either of the above environment variables).
.SH NOTICE
This manpage is intended only to serve as a quick guide to the invocation of
@@ -69,5 +119,5 @@ and to report any newly encountered bugs you may find. We strive to correct
bugs as swiftly as possible.
.SH COPYRIGHT
Copyright \(co 2017 Zachary Yedidia, Collin Warren, et al.
Copyright \(co 2020 Zachary Yedidia, et al.
See /usr/share/doc/micro/LICENSE and /usr/share/doc/micro/AUTHORS for more information.

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,249 +0,0 @@
package main
import (
"io/ioutil"
"os"
"strings"
)
var pluginCompletions []func(string) []string
// This file is meant (for now) for autocompletion in command mode, not
// while coding. This helps micro autocomplete commands and then filenames
// for example with `vsplit filename`.
// FileComplete autocompletes filenames
func FileComplete(input string) (string, []string) {
var sep string = string(os.PathSeparator)
dirs := strings.Split(input, sep)
var files []os.FileInfo
var err error
if len(dirs) > 1 {
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
directories = ReplaceHome(directories)
files, err = ioutil.ReadDir(directories)
} else {
files, err = ioutil.ReadDir(".")
}
var suggestions []string
if err != nil {
return "", suggestions
}
for _, f := range files {
name := f.Name()
if f.IsDir() {
name += sep
}
if strings.HasPrefix(name, dirs[len(dirs)-1]) {
suggestions = append(suggestions, name)
}
}
var chosen string
if len(suggestions) == 1 {
if len(dirs) > 1 {
chosen = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[0]
} else {
chosen = suggestions[0]
}
} else {
if len(dirs) > 1 {
chosen = strings.Join(dirs[:len(dirs)-1], sep) + sep
}
}
return chosen, suggestions
}
// CommandComplete autocompletes commands
func CommandComplete(input string) (string, []string) {
var suggestions []string
for cmd := range commands {
if strings.HasPrefix(cmd, input) {
suggestions = append(suggestions, cmd)
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
// HelpComplete autocompletes help topics
func HelpComplete(input string) (string, []string) {
var suggestions []string
for _, file := range ListRuntimeFiles(RTHelp) {
topic := file.Name()
if strings.HasPrefix(topic, input) {
suggestions = append(suggestions, topic)
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
// ColorschemeComplete tab-completes names of colorschemes.
func ColorschemeComplete(input string) (string, []string) {
var suggestions []string
files := ListRuntimeFiles(RTColorscheme)
for _, f := range files {
if strings.HasPrefix(f.Name(), input) {
suggestions = append(suggestions, f.Name())
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// OptionComplete autocompletes options
func OptionComplete(input string) (string, []string) {
var suggestions []string
localSettings := DefaultLocalSettings()
for option := range globalSettings {
if strings.HasPrefix(option, input) {
suggestions = append(suggestions, option)
}
}
for option := range localSettings {
if strings.HasPrefix(option, input) && !contains(suggestions, option) {
suggestions = append(suggestions, option)
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
// OptionValueComplete completes values for various options
func OptionValueComplete(inputOpt, input string) (string, []string) {
inputOpt = strings.TrimSpace(inputOpt)
var suggestions []string
localSettings := DefaultLocalSettings()
var optionVal interface{}
for k, option := range globalSettings {
if k == inputOpt {
optionVal = option
}
}
for k, option := range localSettings {
if k == inputOpt {
optionVal = option
}
}
switch optionVal.(type) {
case bool:
if strings.HasPrefix("on", input) {
suggestions = append(suggestions, "on")
} else if strings.HasPrefix("true", input) {
suggestions = append(suggestions, "true")
}
if strings.HasPrefix("off", input) {
suggestions = append(suggestions, "off")
} else if strings.HasPrefix("false", input) {
suggestions = append(suggestions, "false")
}
case string:
switch inputOpt {
case "colorscheme":
_, suggestions = ColorschemeComplete(input)
case "fileformat":
if strings.HasPrefix("unix", input) {
suggestions = append(suggestions, "unix")
}
if strings.HasPrefix("dos", input) {
suggestions = append(suggestions, "dos")
}
case "sucmd":
if strings.HasPrefix("sudo", input) {
suggestions = append(suggestions, "sudo")
}
if strings.HasPrefix("doas", input) {
suggestions = append(suggestions, "doas")
}
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
// MakeCompletion registers a function from a plugin for autocomplete commands
func MakeCompletion(function string) Completion {
pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
return Completion(-len(pluginCompletions))
}
// 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() {
if strings.HasPrefix(pp.Name, input) {
suggestions = append(suggestions, pp.Name)
}
}
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}

View File

@@ -1,613 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"unicode"
"github.com/flynn/json5"
"github.com/zyedidia/tcell"
)
var bindingsStr map[string]string
var bindings map[Key][]func(*View, bool) bool
var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
var helpBinding string
var kmenuBinding string
var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
"MousePress": (*View).MousePress,
"MouseMultiCursor": (*View).MouseMultiCursor,
}
var bindingActions = map[string]func(*View, bool) bool{
"CursorUp": (*View).CursorUp,
"CursorDown": (*View).CursorDown,
"CursorPageUp": (*View).CursorPageUp,
"CursorPageDown": (*View).CursorPageDown,
"CursorLeft": (*View).CursorLeft,
"CursorRight": (*View).CursorRight,
"CursorStart": (*View).CursorStart,
"CursorEnd": (*View).CursorEnd,
"SelectToStart": (*View).SelectToStart,
"SelectToEnd": (*View).SelectToEnd,
"SelectUp": (*View).SelectUp,
"SelectDown": (*View).SelectDown,
"SelectLeft": (*View).SelectLeft,
"SelectRight": (*View).SelectRight,
"WordRight": (*View).WordRight,
"WordLeft": (*View).WordLeft,
"SelectWordRight": (*View).SelectWordRight,
"SelectWordLeft": (*View).SelectWordLeft,
"DeleteWordRight": (*View).DeleteWordRight,
"DeleteWordLeft": (*View).DeleteWordLeft,
"SelectLine": (*View).SelectLine,
"SelectToStartOfLine": (*View).SelectToStartOfLine,
"SelectToEndOfLine": (*View).SelectToEndOfLine,
"ParagraphPrevious": (*View).ParagraphPrevious,
"ParagraphNext": (*View).ParagraphNext,
"InsertNewline": (*View).InsertNewline,
"InsertSpace": (*View).InsertSpace,
"Backspace": (*View).Backspace,
"Delete": (*View).Delete,
"InsertTab": (*View).InsertTab,
"Save": (*View).Save,
"SaveAll": (*View).SaveAll,
"SaveAs": (*View).SaveAs,
"Find": (*View).Find,
"FindNext": (*View).FindNext,
"FindPrevious": (*View).FindPrevious,
"Center": (*View).Center,
"Undo": (*View).Undo,
"Redo": (*View).Redo,
"Copy": (*View).Copy,
"Cut": (*View).Cut,
"CutLine": (*View).CutLine,
"DuplicateLine": (*View).DuplicateLine,
"DeleteLine": (*View).DeleteLine,
"MoveLinesUp": (*View).MoveLinesUp,
"MoveLinesDown": (*View).MoveLinesDown,
"IndentSelection": (*View).IndentSelection,
"OutdentSelection": (*View).OutdentSelection,
"OutdentLine": (*View).OutdentLine,
"Paste": (*View).Paste,
"PastePrimary": (*View).PastePrimary,
"SelectAll": (*View).SelectAll,
"OpenFile": (*View).OpenFile,
"Start": (*View).Start,
"End": (*View).End,
"PageUp": (*View).PageUp,
"PageDown": (*View).PageDown,
"HalfPageUp": (*View).HalfPageUp,
"HalfPageDown": (*View).HalfPageDown,
"StartOfLine": (*View).StartOfLine,
"EndOfLine": (*View).EndOfLine,
"ToggleHelp": (*View).ToggleHelp,
"ToggleKeyMenu": (*View).ToggleKeyMenu,
"ToggleRuler": (*View).ToggleRuler,
"JumpLine": (*View).JumpLine,
"ClearStatus": (*View).ClearStatus,
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"ToggleOverwriteMode": (*View).ToggleOverwriteMode,
"Escape": (*View).Escape,
"Quit": (*View).Quit,
"QuitAll": (*View).QuitAll,
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
"Unsplit": (*View).Unsplit,
"VSplit": (*View).VSplitBinding,
"HSplit": (*View).HSplitBinding,
"ToggleMacro": (*View).ToggleMacro,
"PlayMacro": (*View).PlayMacro,
"Suspend": (*View).Suspend,
"ScrollUp": (*View).ScrollUpAction,
"ScrollDown": (*View).ScrollDownAction,
"SpawnMultiCursor": (*View).SpawnMultiCursor,
"SpawnMultiCursorSelect": (*View).SpawnMultiCursorSelect,
"RemoveMultiCursor": (*View).RemoveMultiCursor,
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
"SkipMultiCursor": (*View).SkipMultiCursor,
"JumpToMatchingBrace": (*View).JumpToMatchingBrace,
// This was changed to InsertNewline but I don't want to break backwards compatibility
"InsertEnter": (*View).InsertNewline,
}
var bindingMouse = map[string]tcell.ButtonMask{
"MouseLeft": tcell.Button1,
"MouseMiddle": tcell.Button2,
"MouseRight": tcell.Button3,
"MouseWheelUp": tcell.WheelUp,
"MouseWheelDown": tcell.WheelDown,
"MouseWheelLeft": tcell.WheelLeft,
"MouseWheelRight": tcell.WheelRight,
}
var bindingKeys = map[string]tcell.Key{
"Up": tcell.KeyUp,
"Down": tcell.KeyDown,
"Right": tcell.KeyRight,
"Left": tcell.KeyLeft,
"UpLeft": tcell.KeyUpLeft,
"UpRight": tcell.KeyUpRight,
"DownLeft": tcell.KeyDownLeft,
"DownRight": tcell.KeyDownRight,
"Center": tcell.KeyCenter,
"PageUp": tcell.KeyPgUp,
"PageDown": tcell.KeyPgDn,
"Home": tcell.KeyHome,
"End": tcell.KeyEnd,
"Insert": tcell.KeyInsert,
"Delete": tcell.KeyDelete,
"Help": tcell.KeyHelp,
"Exit": tcell.KeyExit,
"Clear": tcell.KeyClear,
"Cancel": tcell.KeyCancel,
"Print": tcell.KeyPrint,
"Pause": tcell.KeyPause,
"Backtab": tcell.KeyBacktab,
"F1": tcell.KeyF1,
"F2": tcell.KeyF2,
"F3": tcell.KeyF3,
"F4": tcell.KeyF4,
"F5": tcell.KeyF5,
"F6": tcell.KeyF6,
"F7": tcell.KeyF7,
"F8": tcell.KeyF8,
"F9": tcell.KeyF9,
"F10": tcell.KeyF10,
"F11": tcell.KeyF11,
"F12": tcell.KeyF12,
"F13": tcell.KeyF13,
"F14": tcell.KeyF14,
"F15": tcell.KeyF15,
"F16": tcell.KeyF16,
"F17": tcell.KeyF17,
"F18": tcell.KeyF18,
"F19": tcell.KeyF19,
"F20": tcell.KeyF20,
"F21": tcell.KeyF21,
"F22": tcell.KeyF22,
"F23": tcell.KeyF23,
"F24": tcell.KeyF24,
"F25": tcell.KeyF25,
"F26": tcell.KeyF26,
"F27": tcell.KeyF27,
"F28": tcell.KeyF28,
"F29": tcell.KeyF29,
"F30": tcell.KeyF30,
"F31": tcell.KeyF31,
"F32": tcell.KeyF32,
"F33": tcell.KeyF33,
"F34": tcell.KeyF34,
"F35": tcell.KeyF35,
"F36": tcell.KeyF36,
"F37": tcell.KeyF37,
"F38": tcell.KeyF38,
"F39": tcell.KeyF39,
"F40": tcell.KeyF40,
"F41": tcell.KeyF41,
"F42": tcell.KeyF42,
"F43": tcell.KeyF43,
"F44": tcell.KeyF44,
"F45": tcell.KeyF45,
"F46": tcell.KeyF46,
"F47": tcell.KeyF47,
"F48": tcell.KeyF48,
"F49": tcell.KeyF49,
"F50": tcell.KeyF50,
"F51": tcell.KeyF51,
"F52": tcell.KeyF52,
"F53": tcell.KeyF53,
"F54": tcell.KeyF54,
"F55": tcell.KeyF55,
"F56": tcell.KeyF56,
"F57": tcell.KeyF57,
"F58": tcell.KeyF58,
"F59": tcell.KeyF59,
"F60": tcell.KeyF60,
"F61": tcell.KeyF61,
"F62": tcell.KeyF62,
"F63": tcell.KeyF63,
"F64": tcell.KeyF64,
"CtrlSpace": tcell.KeyCtrlSpace,
"CtrlA": tcell.KeyCtrlA,
"CtrlB": tcell.KeyCtrlB,
"CtrlC": tcell.KeyCtrlC,
"CtrlD": tcell.KeyCtrlD,
"CtrlE": tcell.KeyCtrlE,
"CtrlF": tcell.KeyCtrlF,
"CtrlG": tcell.KeyCtrlG,
"CtrlH": tcell.KeyCtrlH,
"CtrlI": tcell.KeyCtrlI,
"CtrlJ": tcell.KeyCtrlJ,
"CtrlK": tcell.KeyCtrlK,
"CtrlL": tcell.KeyCtrlL,
"CtrlM": tcell.KeyCtrlM,
"CtrlN": tcell.KeyCtrlN,
"CtrlO": tcell.KeyCtrlO,
"CtrlP": tcell.KeyCtrlP,
"CtrlQ": tcell.KeyCtrlQ,
"CtrlR": tcell.KeyCtrlR,
"CtrlS": tcell.KeyCtrlS,
"CtrlT": tcell.KeyCtrlT,
"CtrlU": tcell.KeyCtrlU,
"CtrlV": tcell.KeyCtrlV,
"CtrlW": tcell.KeyCtrlW,
"CtrlX": tcell.KeyCtrlX,
"CtrlY": tcell.KeyCtrlY,
"CtrlZ": tcell.KeyCtrlZ,
"CtrlLeftSq": tcell.KeyCtrlLeftSq,
"CtrlBackslash": tcell.KeyCtrlBackslash,
"CtrlRightSq": tcell.KeyCtrlRightSq,
"CtrlCarat": tcell.KeyCtrlCarat,
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
"CtrlPageUp": tcell.KeyCtrlPgUp,
"CtrlPageDown": tcell.KeyCtrlPgDn,
"Tab": tcell.KeyTab,
"Esc": tcell.KeyEsc,
"Escape": tcell.KeyEscape,
"Enter": tcell.KeyEnter,
"Backspace": tcell.KeyBackspace2,
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
"PgUp": tcell.KeyPgUp,
"PgDown": tcell.KeyPgDn,
}
// The Key struct holds the data for a keypress (keycode + modifiers)
type Key struct {
keyCode tcell.Key
modifiers tcell.ModMask
buttons tcell.ButtonMask
r rune
escape string
}
// InitBindings initializes the keybindings for micro
func InitBindings() {
bindings = make(map[Key][]func(*View, bool) bool)
bindingsStr = make(map[string]string)
mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
var parsed map[string]string
defaults := DefaultBindings()
filename := configDir + "/bindings.json"
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
TermMessage("Error reading bindings.json file: " + err.Error())
return
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading bindings.json:", err.Error())
}
}
parseBindings(defaults)
parseBindings(parsed)
}
func parseBindings(userBindings map[string]string) {
for k, v := range userBindings {
BindKey(k, v)
}
}
// findKey will find binding Key 'b' using string 'k'
func findKey(k string) (b Key, ok bool) {
modifiers := tcell.ModNone
// First, we'll strip off all the modifiers in the name and add them to the
// ModMask
modSearch:
for {
switch {
case strings.HasPrefix(k, "-"):
// We optionally support dashes between modifiers
k = k[1:]
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
k = k[4:]
modifiers |= tcell.ModCtrl
case strings.HasPrefix(k, "Alt"):
k = k[3:]
modifiers |= tcell.ModAlt
case strings.HasPrefix(k, "Shift"):
k = k[5:]
modifiers |= tcell.ModShift
case strings.HasPrefix(k, "\x1b"):
return Key{
keyCode: -1,
modifiers: modifiers,
buttons: -1,
r: 0,
escape: k,
}, true
default:
break modSearch
}
}
if len(k) == 0 {
return Key{buttons: -1}, false
}
// Control is handled specially, since some character codes in bindingKeys
// are different when Control is depressed. We should check for Control keys
// first.
if modifiers&tcell.ModCtrl != 0 {
// see if the key is in bindingKeys with the Ctrl prefix.
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
if code, ok := bindingKeys["Ctrl"+k]; ok {
// It is, we're done.
return Key{
keyCode: code,
modifiers: modifiers,
buttons: -1,
r: 0,
}, true
}
}
// See if we can find the key in bindingKeys
if code, ok := bindingKeys[k]; ok {
return Key{
keyCode: code,
modifiers: modifiers,
buttons: -1,
r: 0,
}, true
}
// See if we can find the key in bindingMouse
if code, ok := bindingMouse[k]; ok {
return Key{
modifiers: modifiers,
buttons: code,
r: 0,
}, true
}
// If we were given one character, then we've got a rune.
if len(k) == 1 {
return Key{
keyCode: tcell.KeyRune,
modifiers: modifiers,
buttons: -1,
r: rune(k[0]),
}, true
}
// We don't know what happened.
return Key{buttons: -1}, false
}
// findAction will find 'action' using string 'v'
func findAction(v string) (action func(*View, bool) bool) {
action, ok := bindingActions[v]
if !ok {
// If the user seems to be binding a function that doesn't exist
// We hope that it's a lua function that exists and bind it to that
action = LuaFunctionBinding(v)
}
return action
}
func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
action, ok := mouseBindingActions[v]
if !ok {
// If the user seems to be binding a function that doesn't exist
// We hope that it's a lua function that exists and bind it to that
action = LuaFunctionMouseBinding(v)
}
return action
}
// TryBindKey tries to bind a key by writing to configDir/bindings.json
// This function is unused for now
func TryBindKey(k, v string) {
filename := configDir + "/bindings.json"
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
TermMessage("Error reading bindings.json file: " + err.Error())
return
}
conflict := -1
lines := strings.Split(string(input), "\n")
for i, l := range lines {
parts := strings.Split(l, ":")
if len(parts) >= 2 {
if strings.Contains(parts[0], k) {
conflict = i
TermMessage("Warning: Keybinding conflict:", k, " has been overwritten")
}
}
}
binding := fmt.Sprintf(" \"%s\": \"%s\",", k, v)
if conflict == -1 {
lines = append([]string{lines[0], binding}, lines[conflict:]...)
} else {
lines = append(append(lines[:conflict], binding), lines[conflict+1:]...)
}
txt := strings.Join(lines, "\n")
err = ioutil.WriteFile(filename, []byte(txt), 0644)
if err != nil {
TermMessage("Error")
}
}
}
// BindKey takes a key and an action and binds the two together
func BindKey(k, v string) {
key, ok := findKey(k)
if !ok {
TermMessage("Unknown keybinding: " + k)
return
}
if v == "ToggleHelp" {
helpBinding = k
}
if v == "ToggleKeyMenu" {
kmenuBinding = k
}
if helpBinding == k && v != "ToggleHelp" {
helpBinding = ""
}
if kmenuBinding == k && v != "ToggleKeyMenu" {
kmenuBinding = ""
}
actionNames := strings.Split(v, ",")
if actionNames[0] == "UnbindKey" {
delete(bindings, key)
delete(mouseBindings, key)
delete(bindingsStr, k)
if len(actionNames) == 1 {
return
}
actionNames = append(actionNames[:0], actionNames[1:]...)
}
actions := make([]func(*View, bool) bool, 0, len(actionNames))
mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
for _, actionName := range actionNames {
if strings.HasPrefix(actionName, "Mouse") {
mouseActions = append(mouseActions, findMouseAction(actionName))
} else if strings.HasPrefix(actionName, "command:") {
cmd := strings.SplitN(actionName, ":", 2)[1]
actions = append(actions, CommandAction(cmd))
} else if strings.HasPrefix(actionName, "command-edit:") {
cmd := strings.SplitN(actionName, ":", 2)[1]
actions = append(actions, CommandEditAction(cmd))
} else {
actions = append(actions, findAction(actionName))
}
}
if len(actions) > 0 {
// Can't have a binding be both mouse and normal
delete(mouseBindings, key)
bindings[key] = actions
bindingsStr[k] = v
} else if len(mouseActions) > 0 {
// Can't have a binding be both mouse and normal
delete(bindings, key)
mouseBindings[key] = mouseActions
}
}
// 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": "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": "JumpLine",
"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

@@ -1,793 +0,0 @@
package main
import (
"bytes"
"crypto/md5"
"encoding/gob"
"errors"
"io"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/zyedidia/micro/cmd/micro/highlight"
)
var (
// 0 - no line type detected
// 1 - lf detected
// 2 - crlf detected
fileformat = 0
)
// Buffer stores the text for files that are loaded into the text editor
// It uses a rope to efficiently store the string and contains some
// simple functions for saving and wrapper functions for modifying the rope
type Buffer struct {
// The eventhandler for undo/redo
*EventHandler
// This stores all the text in the buffer as an array of lines
*LineArray
Cursor Cursor
cursors []*Cursor // for multiple cursors
curCursor int // the current cursor
// Path to the file on disk
Path string
// Absolute path to the file on disk
AbsPath string
// Name of the buffer on the status line
name string
// Whether or not the buffer has been modified since it was opened
IsModified bool
// Stores the last modification time of the file the buffer is pointing to
ModTime time.Time
// NumLines is the number of lines in the buffer
NumLines int
syntaxDef *highlight.Def
highlighter *highlight.Highlighter
// Hash of the original buffer -- empty if fastdirty is on
origHash [16]byte
// Buffer local settings
Settings map[string]interface{}
}
// The SerializedBuffer holds the types that get serialized when a buffer is saved
// These are used for the savecursor and saveundo options
type SerializedBuffer struct {
EventHandler *EventHandler
Cursor Cursor
ModTime time.Time
}
// NewBufferFromFile opens a new buffer using the given filepath
// It will also automatically handle `~`, and line/column with filename:l:c
// It will return an empty buffer if the filepath does not exist
// and an error if the file is a directory
func NewBufferFromFile(path string) (*Buffer, error) {
filename := GetPath(path)
filename = ReplaceHome(filename)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
if err == nil && fileInfo.IsDir() {
return nil, errors.New(filename + " is a directory")
}
defer file.Close()
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBufferFromString("", path)
} else {
buf = NewBuffer(file, FSize(file), path)
}
return buf, nil
}
// NewBufferFromString creates a new buffer containing the given
// string
func NewBufferFromString(text, path string) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path)
}
// NewBuffer creates a new buffer from a given reader with a given path
func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
startpos := Loc{0, 0}
startposErr := true
if strings.Contains(path, ":") {
var err error
split := strings.Split(path, ":")
path = split[0]
startpos.Y, err = strconv.Atoi(split[1])
if err != nil {
messenger.Error("Error opening file: ", err)
} else {
startposErr = false
if len(split) > 2 {
startpos.X, err = strconv.Atoi(split[2])
if err != nil {
messenger.Error("Error opening file: ", err)
}
}
}
}
if path != "" {
for _, tab := range tabs {
for _, view := range tab.Views {
if view.Buf.Path == path {
return view.Buf
}
}
}
}
b := new(Buffer)
b.LineArray = NewLineArray(size, reader)
b.Settings = DefaultLocalSettings()
for k, v := range globalSettings {
if _, ok := b.Settings[k]; ok {
b.Settings[k] = v
}
}
if fileformat == 1 {
b.Settings["fileformat"] = "unix"
} else if fileformat == 2 {
b.Settings["fileformat"] = "dos"
}
absPath, _ := filepath.Abs(path)
b.Path = path
b.AbsPath = absPath
// The last time this file was modified
b.ModTime, _ = GetModTime(b.Path)
b.EventHandler = NewEventHandler(b)
b.Update()
b.UpdateRules()
if _, err := os.Stat(configDir + "/buffers/"); os.IsNotExist(err) {
os.Mkdir(configDir+"/buffers/", os.ModePerm)
}
// Put the cursor at the first spot
cursorStartX := 0
cursorStartY := 0
// If -startpos LINE,COL was passed, use start position LINE,COL
if len(*flagStartPos) > 0 || !startposErr {
positions := strings.Split(*flagStartPos, ",")
if len(positions) == 2 || !startposErr {
var lineNum, colNum int
var errPos1, errPos2 error
if !startposErr {
lineNum = startpos.Y
colNum = startpos.X
} else {
lineNum, errPos1 = strconv.Atoi(positions[0])
colNum, errPos2 = strconv.Atoi(positions[1])
}
if errPos1 == nil && errPos2 == nil {
cursorStartX = colNum
cursorStartY = lineNum - 1
// Check to avoid line overflow
if cursorStartY > b.NumLines {
cursorStartY = b.NumLines - 1
} else if cursorStartY < 0 {
cursorStartY = 0
}
// Check to avoid column overflow
if cursorStartX > len(b.Line(cursorStartY)) {
cursorStartX = len(b.Line(cursorStartY))
} else if cursorStartX < 0 {
cursorStartX = 0
}
}
}
}
b.Cursor = Cursor{
Loc: Loc{
X: cursorStartX,
Y: cursorStartY,
},
buf: b,
}
InitLocalSettings(b)
if startposErr && len(*flagStartPos) == 0 && (b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool)) {
// If either savecursor or saveundo is turned on, we need to load the serialized information
// from ~/.config/micro/buffers
file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
defer file.Close()
if err == nil {
var buffer SerializedBuffer
decoder := gob.NewDecoder(file)
gob.Register(TextEvent{})
err = decoder.Decode(&buffer)
if err != nil {
TermMessage(err.Error(), "\n", "You 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.")
}
if b.Settings["savecursor"].(bool) {
b.Cursor = buffer.Cursor
b.Cursor.buf = b
b.Cursor.Relocate()
}
if b.Settings["saveundo"].(bool) {
// We should only use last time's eventhandler if the file wasn't modified by someone else in the meantime
if b.ModTime == buffer.ModTime {
b.EventHandler = buffer.EventHandler
b.EventHandler.buf = b
}
}
}
}
if !b.Settings["fastdirty"].(bool) {
if size > 50000 {
// If the file is larger than a megabyte fastdirty needs to be on
b.Settings["fastdirty"] = true
} else {
b.origHash = md5.Sum([]byte(b.String()))
}
}
b.cursors = []*Cursor{&b.Cursor}
return b
}
// GetName returns the name that should be displayed in the statusline
// for this buffer
func (b *Buffer) GetName() string {
if b.name == "" {
if b.Path == "" {
return "No name"
}
return b.Path
}
return b.name
}
// UpdateRules updates the syntax rules and filetype for this buffer
// This is called when the colorscheme changes
func (b *Buffer) UpdateRules() {
rehighlight := false
var files []*highlight.File
for _, f := range ListRuntimeFiles(RTSyntax) {
data, err := f.Data()
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
} else {
file, err := highlight.ParseFile(data)
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
ftdetect, err := highlight.ParseFtDetect(file)
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
ft := b.Settings["filetype"].(string)
if (ft == "Unknown" || ft == "") && !rehighlight {
if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
header := new(highlight.Header)
header.FileType = file.FileType
header.FtDetect = ftdetect
b.syntaxDef, err = highlight.ParseDef(file, header)
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
rehighlight = true
}
} else {
if file.FileType == ft && !rehighlight {
header := new(highlight.Header)
header.FileType = file.FileType
header.FtDetect = ftdetect
b.syntaxDef, err = highlight.ParseDef(file, header)
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
rehighlight = true
}
}
files = append(files, file)
}
}
if b.syntaxDef != nil {
highlight.ResolveIncludes(b.syntaxDef, files)
}
if b.highlighter == nil || rehighlight {
if b.syntaxDef != nil {
b.Settings["filetype"] = b.syntaxDef.FileType
b.highlighter = highlight.NewHighlighter(b.syntaxDef)
if b.Settings["syntax"].(bool) {
b.highlighter.HighlightStates(b)
}
}
}
}
// FileType returns the buffer's filetype
func (b *Buffer) FileType() string {
return b.Settings["filetype"].(string)
}
// IndentString returns a string representing one level of indentation
func (b *Buffer) IndentString() string {
if b.Settings["tabstospaces"].(bool) {
return Spaces(int(b.Settings["tabsize"].(float64)))
}
return "\t"
}
// CheckModTime makes sure that the file this buffer points to hasn't been updated
// by an external program since it was last read
// If it has, we ask the user if they would like to reload the file
func (b *Buffer) CheckModTime() {
modTime, ok := GetModTime(b.Path)
if ok {
if modTime != b.ModTime {
choice, canceled := messenger.YesNoPrompt("The file has changed since it was last read. Reload file? (y,n)")
messenger.Reset()
messenger.Clear()
if !choice || canceled {
// Don't load new changes -- do nothing
b.ModTime, _ = GetModTime(b.Path)
} else {
// Load new changes
b.ReOpen()
}
}
}
}
// ReOpen reloads the current buffer from disk
func (b *Buffer) ReOpen() {
data, err := ioutil.ReadFile(b.Path)
txt := string(data)
if err != nil {
messenger.Error(err.Error())
return
}
b.EventHandler.ApplyDiff(txt)
b.ModTime, _ = GetModTime(b.Path)
b.IsModified = false
b.Update()
b.Cursor.Relocate()
}
// Update fetches the string from the rope and updates the `text` and `lines` in the buffer
func (b *Buffer) Update() {
b.NumLines = len(b.lines)
}
// MergeCursors merges any cursors that are at the same position
// into one cursor
func (b *Buffer) MergeCursors() {
var cursors []*Cursor
for i := 0; i < len(b.cursors); i++ {
c1 := b.cursors[i]
if c1 != nil {
for j := 0; j < len(b.cursors); j++ {
c2 := b.cursors[j]
if c2 != nil && i != j && c1.Loc == c2.Loc {
b.cursors[j] = nil
}
}
cursors = append(cursors, c1)
}
}
b.cursors = cursors
for i := range b.cursors {
b.cursors[i].Num = i
}
if b.curCursor >= len(b.cursors) {
b.curCursor = len(b.cursors) - 1
}
}
// UpdateCursors updates all the cursors indicies
func (b *Buffer) UpdateCursors() {
for i, c := range b.cursors {
c.Num = i
}
}
// Save saves the buffer to its default path
func (b *Buffer) Save() error {
return b.SaveAs(b.Path)
}
// SaveWithSudo saves the buffer to the default path with sudo
func (b *Buffer) SaveWithSudo() error {
return b.SaveAsWithSudo(b.Path)
}
// Serialize serializes the buffer to configDir/buffers
func (b *Buffer) Serialize() error {
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
file, err := os.Create(configDir + "/buffers/" + EscapePath(b.AbsPath))
if err == nil {
enc := gob.NewEncoder(file)
gob.Register(TextEvent{})
err = enc.Encode(SerializedBuffer{
b.EventHandler,
b.Cursor,
b.ModTime,
})
}
err = file.Close()
return err
}
return nil
}
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
func (b *Buffer) SaveAs(filename string) error {
b.UpdateRules()
if b.Settings["rmtrailingws"].(bool) {
r, _ := regexp.Compile(`[ \t]+$`)
for lineNum, line := range b.Lines(0, b.NumLines) {
indices := r.FindStringIndex(line)
if indices == nil {
continue
}
startLoc := Loc{indices[0], lineNum}
b.deleteToEnd(startLoc)
}
b.Cursor.Relocate()
}
if b.Settings["eofnewline"].(bool) {
end := b.End()
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
b.Insert(end, "\n")
}
}
defer func() {
b.ModTime, _ = GetModTime(filename)
}()
// Removes any tilde and replaces with the absolute path to home
var absFilename string = ReplaceHome(filename)
// Get the leading path to the file | "." is returned if there's no leading path provided
if dirname := filepath.Dir(absFilename); dirname != "." {
// Check if the parent dirs don't exist
if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
// Prompt to make sure they want to create the dirs that are missing
if yes, canceled := messenger.YesNoPrompt("Parent folders \"" + dirname + "\" do not exist. Create them? (y,n)"); yes && !canceled {
// Create all leading dir(s) since they don't exist
if mkdirallErr := os.MkdirAll(dirname, os.ModePerm); mkdirallErr != nil {
// If there was an error creating the dirs
return mkdirallErr
}
} else {
// If they canceled the creation of leading dirs
return errors.New("Save aborted")
}
}
}
f, err := os.OpenFile(absFilename, os.O_WRONLY|os.O_CREATE, 0644)
defer f.Close()
if err != nil {
return err
}
if err := f.Truncate(0); err != nil {
return err
}
useCrlf := b.Settings["fileformat"] == "dos"
size := 0
for i, l := range b.lines {
size += len(l.data)
if _, err := f.Write(l.data); err != nil {
return err
}
if i != len(b.lines)-1 {
if useCrlf {
size += 2
if _, err := f.Write([]byte{'\r', '\n'}); err != nil {
return err
}
} else {
size++
if _, err := f.Write([]byte{'\n'}); err != nil {
return err
}
}
}
}
if !b.Settings["fastdirty"].(bool) {
if size > 50000 {
// If the file is larger than a megabyte fastdirty needs to be on
b.Settings["fastdirty"] = true
} else {
b.origHash = md5.Sum([]byte(b.String()))
}
}
b.Path = filename
b.IsModified = false
return b.Serialize()
}
// SaveAsWithSudo is the same as SaveAs except it uses a neat trick
// with tee to use sudo so the user doesn't have to reopen micro with sudo
func (b *Buffer) SaveAsWithSudo(filename string) error {
b.UpdateRules()
b.Path = filename
// Shut down the screen because we're going to interact directly with the shell
screen.Fini()
screen = nil
// Set up everything for the command
cmd := exec.Command(globalSettings["sucmd"].(string), "tee", filename)
cmd.Stdin = bytes.NewBufferString(b.SaveString(b.Settings["fileformat"] == "dos"))
// This is a trap for Ctrl-C so that it doesn't kill micro
// Instead we trap Ctrl-C to kill the program we're running
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for range c {
cmd.Process.Kill()
}
}()
// Start the command
cmd.Start()
err := cmd.Wait()
// Start the screen back up
InitScreen()
if err == nil {
b.IsModified = false
b.ModTime, _ = GetModTime(filename)
b.Serialize()
}
return err
}
// Modified returns if this buffer has been modified since
// being opened
func (b *Buffer) Modified() bool {
if b.Settings["fastdirty"].(bool) {
return b.IsModified
}
return b.origHash != md5.Sum([]byte(b.String()))
}
func (b *Buffer) insert(pos Loc, value []byte) {
b.IsModified = true
b.LineArray.insert(pos, value)
b.Update()
}
func (b *Buffer) remove(start, end Loc) string {
b.IsModified = true
sub := b.LineArray.remove(start, end)
b.Update()
return sub
}
func (b *Buffer) deleteToEnd(start Loc) {
b.IsModified = true
b.LineArray.DeleteToEnd(start)
b.Update()
}
// Start returns the location of the first character in the buffer
func (b *Buffer) Start() Loc {
return Loc{0, 0}
}
// End returns the location of the last character in the buffer
func (b *Buffer) End() Loc {
return Loc{utf8.RuneCount(b.lines[b.NumLines-1].data), b.NumLines - 1}
}
// RuneAt returns the rune at a given location in the buffer
func (b *Buffer) RuneAt(loc Loc) rune {
line := b.LineRunes(loc.Y)
if len(line) > 0 {
return line[loc.X]
}
return '\n'
}
// Line returns a single line as an array of runes
func (b *Buffer) LineBytes(n int) []byte {
if n >= len(b.lines) {
return []byte{}
}
return b.lines[n].data
}
// Line returns a single line as an array of runes
func (b *Buffer) LineRunes(n int) []rune {
if n >= len(b.lines) {
return []rune{}
}
return toRunes(b.lines[n].data)
}
// Line returns a single line
func (b *Buffer) Line(n int) string {
if n >= len(b.lines) {
return ""
}
return string(b.lines[n].data)
}
// LinesNum returns the number of lines in the buffer
func (b *Buffer) LinesNum() int {
return len(b.lines)
}
// Lines returns an array of strings containing the lines from start to end
func (b *Buffer) Lines(start, end int) []string {
lines := b.lines[start:end]
var slice []string
for _, line := range lines {
slice = append(slice, string(line.data))
}
return slice
}
// Len gives the length of the buffer
func (b *Buffer) Len() int {
return Count(b.String())
}
// MoveLinesUp moves the range of lines up one row
func (b *Buffer) MoveLinesUp(start int, end int) {
// 0 < start < end <= len(b.lines)
if start < 1 || start >= end || end > len(b.lines) {
return // what to do? FIXME
}
if end == len(b.lines) {
b.Insert(
Loc{
utf8.RuneCount(b.lines[end-1].data),
end - 1,
},
"\n"+b.Line(start-1),
)
} else {
b.Insert(
Loc{0, end},
b.Line(start-1)+"\n",
)
}
b.Remove(
Loc{0, start - 1},
Loc{0, start},
)
}
// MoveLinesDown moves the range of lines down one row
func (b *Buffer) MoveLinesDown(start int, end int) {
// 0 <= start < end < len(b.lines)
// if end == len(b.lines), we can't do anything here because the
// last line is unaccessible, FIXME
if start < 0 || start >= end || end >= len(b.lines)-1 {
return // what to do? FIXME
}
b.Insert(
Loc{0, start},
b.Line(end)+"\n",
)
end++
b.Remove(
Loc{0, end},
Loc{0, end + 1},
)
}
// ClearMatches clears all of the syntax highlighting for this buffer
func (b *Buffer) ClearMatches() {
for i := range b.lines {
b.SetMatch(i, nil)
b.SetState(i, nil)
}
}
func (b *Buffer) clearCursors() {
for i := 1; i < len(b.cursors); i++ {
b.cursors[i] = nil
}
b.cursors = b.cursors[:1]
b.UpdateCursors()
b.Cursor.ResetSelection()
}
var bracePairs = [][2]rune{
{'(', ')'},
{'{', '}'},
{'[', ']'},
}
// FindMatchingBrace returns the location in the buffer of the matching bracket
// It is given a brace type containing the open and closing character, (for example
// '{' and '}') as well as the location to match from
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) Loc {
curLine := b.LineRunes(start.Y)
startChar := curLine[start.X]
var i int
if startChar == braceType[0] {
for y := start.Y; y < b.NumLines; y++ {
l := b.LineRunes(y)
xInit := 0
if y == start.Y {
xInit = start.X
}
for x := xInit; x < len(l); x++ {
r := l[x]
if r == braceType[0] {
i++
} else if r == braceType[1] {
i--
if i == 0 {
return Loc{x, y}
}
}
}
}
} else if startChar == braceType[1] {
for y := start.Y; y >= 0; y-- {
l := []rune(string(b.lines[y].data))
xInit := len(l) - 1
if y == start.Y {
xInit = start.X
}
for x := xInit; x >= 0; x-- {
r := l[x]
if r == braceType[0] {
i--
if i == 0 {
return Loc{x, y}
}
} else if r == braceType[1] {
i++
}
}
}
}
return start
}

View File

@@ -1,238 +0,0 @@
package main
import (
"github.com/mattn/go-runewidth"
"github.com/zyedidia/tcell"
)
func min(a, b int) int {
if a <= b {
return a
}
return b
}
func visualToCharPos(visualIndex int, lineN int, str string, buf *Buffer, tabsize int) (int, int, *tcell.Style) {
charPos := 0
var lineIdx int
var lastWidth int
var style *tcell.Style
var width int
var rw int
for i, c := range str {
// width := StringWidth(str[:i], tabsize)
if group, ok := buf.Match(lineN)[charPos]; ok {
s := GetColor(group.String())
style = &s
}
if width >= visualIndex {
return charPos, visualIndex - lastWidth, style
}
if i != 0 {
charPos++
lineIdx += rw
}
lastWidth = width
rw = 0
if c == '\t' {
rw = tabsize - (lineIdx % tabsize)
width += rw
} else {
rw = runewidth.RuneWidth(c)
width += rw
}
}
return -1, -1, style
}
type Char struct {
visualLoc Loc
realLoc Loc
char rune
// The actual character that is drawn
// This is only different from char if it's for example hidden character
drawChar rune
style tcell.Style
width int
}
type CellView struct {
lines [][]*Char
}
func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
if width <= 0 {
return
}
matchingBrace := Loc{-1, -1}
// bracePairs is defined in buffer.go
if buf.Settings["matchbrace"].(bool) {
for _, bp := range bracePairs {
curX := buf.Cursor.X
curLoc := buf.Cursor.Loc
if buf.Settings["matchbraceleft"].(bool) {
curX--
if curX > 0 {
curLoc = curLoc.Move(-1, buf)
}
}
r := buf.Cursor.RuneUnder(curX)
if r == bp[0] || r == bp[1] {
matchingBrace = buf.FindMatchingBrace(bp, curLoc)
}
}
}
tabsize := int(buf.Settings["tabsize"].(float64))
softwrap := buf.Settings["softwrap"].(bool)
indentrunes := []rune(buf.Settings["indentchar"].(string))
// if empty indentchar settings, use space
if indentrunes == nil || len(indentrunes) == 0 {
indentrunes = []rune{' '}
}
indentchar := indentrunes[0]
start := buf.Cursor.Y
if buf.Settings["syntax"].(bool) && buf.syntaxDef != nil {
if start > 0 && buf.lines[start-1].rehighlight {
buf.highlighter.ReHighlightLine(buf, start-1)
buf.lines[start-1].rehighlight = false
}
buf.highlighter.ReHighlightStates(buf, start)
buf.highlighter.HighlightMatches(buf, top, top+height)
}
c.lines = make([][]*Char, 0)
viewLine := 0
lineN := top
curStyle := defStyle
for viewLine < height {
if lineN >= len(buf.lines) {
break
}
lineStr := buf.Line(lineN)
line := []rune(lineStr)
colN, startOffset, startStyle := visualToCharPos(left, lineN, lineStr, buf, tabsize)
if colN < 0 {
colN = len(line)
}
viewCol := -startOffset
if startStyle != nil {
curStyle = *startStyle
}
// We'll either draw the length of the line, or the width of the screen
// whichever is smaller
lineLength := min(StringWidth(lineStr, tabsize), width)
c.lines = append(c.lines, make([]*Char, lineLength))
wrap := false
// We only need to wrap if the length of the line is greater than the width of the terminal screen
if softwrap && StringWidth(lineStr, tabsize) > width {
wrap = true
// We're going to draw the entire line now
lineLength = StringWidth(lineStr, tabsize)
}
for viewCol < lineLength {
if colN >= len(line) {
break
}
if group, ok := buf.Match(lineN)[colN]; ok {
curStyle = GetColor(group.String())
}
char := line[colN]
if viewCol >= 0 {
st := curStyle
if colN == matchingBrace.X && lineN == matchingBrace.Y && !buf.Cursor.HasSelection() {
st = curStyle.Reverse(true)
}
if viewCol < len(c.lines[viewLine]) {
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, char, st, 1}
}
}
if char == '\t' {
charWidth := tabsize - (viewCol+left)%tabsize
if viewCol >= 0 {
c.lines[viewLine][viewCol].drawChar = indentchar
c.lines[viewLine][viewCol].width = charWidth
indentStyle := curStyle
ch := buf.Settings["indentchar"].(string)
if group, ok := colorscheme["indent-char"]; ok && !IsStrWhitespace(ch) && ch != "" {
indentStyle = group
}
c.lines[viewLine][viewCol].style = indentStyle
}
for i := 1; i < charWidth; i++ {
viewCol++
if viewCol >= 0 && viewCol < lineLength && viewCol < len(c.lines[viewLine]) {
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
}
}
viewCol++
} else if runewidth.RuneWidth(char) > 1 {
charWidth := runewidth.RuneWidth(char)
if viewCol >= 0 {
c.lines[viewLine][viewCol].width = charWidth
}
for i := 1; i < charWidth; i++ {
viewCol++
if viewCol >= 0 && viewCol < lineLength && viewCol < len(c.lines[viewLine]) {
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
}
}
viewCol++
} else {
viewCol++
}
colN++
if wrap && viewCol >= width {
viewLine++
// If we go too far soft wrapping we have to cut off
if viewLine >= height {
break
}
nextLine := line[colN:]
lineLength := min(StringWidth(string(nextLine), tabsize), width)
c.lines = append(c.lines, make([]*Char, lineLength))
viewCol = 0
}
}
if group, ok := buf.Match(lineN)[len(line)]; ok {
curStyle = GetColor(group.String())
}
// newline
viewLine++
lineN++
}
for i := top; i < top+height; i++ {
if i >= buf.NumLines {
break
}
buf.SetMatch(i, nil)
}
}

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

@@ -1,692 +0,0 @@
package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"unicode/utf8"
humanize "github.com/dustin/go-humanize"
"github.com/zyedidia/micro/cmd/micro/shellwords"
)
// A Command contains a action (a function to call) as well as information about how to autocomplete the command
type Command struct {
action func([]string)
completions []Completion
}
// A StrCommand is similar to a command but keeps the name of the action
type StrCommand struct {
action string
completions []Completion
}
var commands map[string]Command
var commandActions map[string]func([]string)
func init() {
commandActions = map[string]func([]string){
"Set": Set,
"SetLocal": SetLocal,
"Show": Show,
"ShowKey": ShowKey,
"Run": Run,
"Bind": Bind,
"Quit": Quit,
"Save": Save,
"Replace": Replace,
"ReplaceAll": ReplaceAll,
"VSplit": VSplit,
"HSplit": HSplit,
"Tab": NewTab,
"Help": Help,
"Eval": Eval,
"ToggleLog": ToggleLog,
"Plugin": PluginCmd,
"Reload": Reload,
"Cd": Cd,
"Pwd": Pwd,
"Open": Open,
"TabSwitch": TabSwitch,
"Term": Term,
"MemUsage": MemUsage,
"Retab": Retab,
"Raw": Raw,
}
}
// InitCommands initializes the default commands
func InitCommands() {
commands = make(map[string]Command)
defaults := DefaultCommands()
parseCommands(defaults)
}
func parseCommands(userCommands map[string]StrCommand) {
for k, v := range userCommands {
MakeCommand(k, v.action, v.completions...)
}
}
// 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 MakeCommand(name, function string, completions ...Completion) {
action := commandActions[function]
if _, ok := commandActions[function]; !ok {
// If the user seems to be binding a function that doesn't exist
// We hope that it's a lua function that exists and bind it to that
action = LuaFunctionCommand(function)
}
commands[name] = Command{action, completions}
}
// DefaultCommands returns a map containing micro's default commands
func DefaultCommands() map[string]StrCommand {
return map[string]StrCommand{
"set": {"Set", []Completion{OptionCompletion, OptionValueCompletion}},
"setlocal": {"SetLocal", []Completion{OptionCompletion, OptionValueCompletion}},
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
"showkey": {"ShowKey", []Completion{NoCompletion}},
"bind": {"Bind", []Completion{NoCompletion}},
"run": {"Run", []Completion{NoCompletion}},
"quit": {"Quit", []Completion{NoCompletion}},
"save": {"Save", []Completion{NoCompletion}},
"replace": {"Replace", []Completion{NoCompletion}},
"replaceall": {"ReplaceAll", []Completion{NoCompletion}},
"vsplit": {"VSplit", []Completion{FileCompletion, NoCompletion}},
"hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}},
"tab": {"Tab", []Completion{FileCompletion, NoCompletion}},
"help": {"Help", []Completion{HelpCompletion, NoCompletion}},
"eval": {"Eval", []Completion{NoCompletion}},
"log": {"ToggleLog", []Completion{NoCompletion}},
"plugin": {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
"reload": {"Reload", []Completion{NoCompletion}},
"cd": {"Cd", []Completion{FileCompletion}},
"pwd": {"Pwd", []Completion{NoCompletion}},
"open": {"Open", []Completion{FileCompletion}},
"tabswitch": {"TabSwitch", []Completion{NoCompletion}},
"term": {"Term", []Completion{NoCompletion}},
"memusage": {"MemUsage", []Completion{NoCompletion}},
"retab": {"Retab", []Completion{NoCompletion}},
"raw": {"Raw", []Completion{NoCompletion}},
}
}
// CommandEditAction returns a bindable function that opens a prompt with
// the given string and executes the command when the user presses
// enter
func CommandEditAction(prompt string) func(*View, bool) bool {
return func(v *View, usePlugin bool) bool {
input, canceled := messenger.Prompt("> ", prompt, "Command", CommandCompletion)
if !canceled {
HandleCommand(input)
}
return false
}
}
// CommandAction returns a bindable function which executes the
// given command
func CommandAction(cmd string) func(*View, bool) bool {
return func(v *View, usePlugin bool) bool {
HandleCommand(cmd)
return false
}
}
// PluginCmd installs, removes, updates, lists, or searches for given plugins
func PluginCmd(args []string) {
if len(args) >= 1 {
switch args[0] {
case "install":
installedVersions := GetInstalledVersions(false)
for _, plugin := range args[1:] {
pp := GetAllPluginPackages().Get(plugin)
if pp == nil {
messenger.Error("Unknown plugin \"" + plugin + "\"")
} else if err := pp.IsInstallable(); err != nil {
messenger.Error("Error installing ", plugin, ": ", err)
} else {
for _, installed := range installedVersions {
if pp.Name == installed.pack.Name {
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
messenger.Error(pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
} else {
messenger.Error(pp.Name, " is already installed")
}
}
}
pp.Install()
}
}
case "remove":
removed := ""
for _, plugin := range args[1:] {
// check if the plugin exists.
if _, ok := loadedPlugins[plugin]; ok {
UninstallPlugin(plugin)
removed += plugin + " "
continue
}
}
if !IsSpaces([]byte(removed)) {
messenger.Message("Removed ", removed)
} else {
messenger.Error("The requested plugins do not exist")
}
case "update":
UpdatePlugins(args[1:])
case "list":
plugins := GetInstalledVersions(false)
messenger.AddLog("----------------")
messenger.AddLog("The following plugins are currently installed:\n")
for _, p := range plugins {
messenger.AddLog(fmt.Sprintf("%s (%s)", p.pack.Name, p.Version))
}
messenger.AddLog("----------------")
if len(plugins) > 0 {
if CurView().Type != vtLog {
ToggleLog([]string{})
}
}
case "search":
plugins := SearchPlugin(args[1:])
messenger.Message(len(plugins), " plugins found")
for _, p := range plugins {
messenger.AddLog("----------------")
messenger.AddLog(p.String())
}
messenger.AddLog("----------------")
if len(plugins) > 0 {
if CurView().Type != vtLog {
ToggleLog([]string{})
}
}
case "available":
packages := GetAllPluginPackages()
messenger.AddLog("Available Plugins:")
for _, pkg := range packages {
messenger.AddLog(pkg.Name)
}
if CurView().Type != vtLog {
ToggleLog([]string{})
}
}
} else {
messenger.Error("Not enough arguments")
}
}
// Retab changes all spaces to tabs or all tabs to spaces
// depending on the user's settings
func Retab(args []string) {
CurView().Retab(true)
}
// Raw opens a new raw view which displays the escape sequences micro
// is receiving in real-time
func Raw(args []string) {
buf := NewBufferFromString("", "Raw events")
view := NewView(buf)
view.Buf.Insert(view.Cursor.Loc, "Warning: Showing raw event escape codes\n")
view.Buf.Insert(view.Cursor.Loc, "Use CtrlQ to exit\n")
view.Type = vtRaw
tab := NewTabFromView(view)
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
curTab = len(tabs) - 1
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.Views {
v.ToggleTabbar()
}
}
}
}
// TabSwitch switches to a given tab either by name or by number
func TabSwitch(args []string) {
if len(args) > 0 {
num, err := strconv.Atoi(args[0])
if err != nil {
// Check for tab with this name
found := false
for _, t := range tabs {
v := t.Views[t.CurView]
if v.Buf.GetName() == args[0] {
curTab = v.TabNum
found = true
}
}
if !found {
messenger.Error("Could not find tab: ", err)
}
} else {
num--
if num >= 0 && num < len(tabs) {
curTab = num
} else {
messenger.Error("Invalid tab index")
}
}
}
}
// Cd changes the current working directory
func Cd(args []string) {
if len(args) > 0 {
path := ReplaceHome(args[0])
err := os.Chdir(path)
if err != nil {
messenger.Error("Error with cd: ", err)
return
}
wd, _ := os.Getwd()
for _, tab := range tabs {
for _, view := range tab.Views {
if len(view.Buf.name) == 0 {
continue
}
view.Buf.Path, _ = MakeRelative(view.Buf.AbsPath, wd)
if p, _ := filepath.Abs(view.Buf.Path); !strings.Contains(p, wd) {
view.Buf.Path = view.Buf.AbsPath
}
}
}
}
}
// MemUsage prints micro's memory usage
// Alloc shows how many bytes are currently in use
// Sys shows how many bytes have been requested from the operating system
// NumGC shows how many times the GC has been run
// Note that Go commonly reserves more memory from the OS than is currently in-use/required
// Additionally, even if Go returns memory to the OS, the OS does not always claim it because
// there may be plenty of memory to spare
func MemUsage(args []string) {
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
messenger.Message(fmt.Sprintf("Alloc: %v, Sys: %v, NumGC: %v", humanize.Bytes(mem.Alloc), humanize.Bytes(mem.Sys), mem.NumGC))
}
// Pwd prints the current working directory
func Pwd(args []string) {
wd, err := os.Getwd()
if err != nil {
messenger.Message(err.Error())
} else {
messenger.Message(wd)
}
}
// Open opens a new buffer with a given filename
func Open(args []string) {
if len(args) > 0 {
filename := args[0]
// the filename might or might not be quoted, so unquote first then join the strings.
args, err := shellwords.Split(filename)
if err != nil {
messenger.Error("Error parsing args ", err)
return
}
filename = strings.Join(args, " ")
CurView().Open(filename)
} else {
messenger.Error("No filename")
}
}
// ToggleLog toggles the log view
func ToggleLog(args []string) {
buffer := messenger.getBuffer()
if CurView().Type != vtLog {
CurView().HSplit(buffer)
CurView().Type = vtLog
RedrawAll()
buffer.Cursor.Loc = buffer.Start()
CurView().Relocate()
buffer.Cursor.Loc = buffer.End()
CurView().Relocate()
} else {
CurView().Quit(true)
}
}
// Reload reloads all files (syntax files, colorschemes...)
func Reload(args []string) {
LoadAll()
}
// Help tries to open the given help page in a horizontal split
func Help(args []string) {
if len(args) < 1 {
// Open the default help if the user just typed "> help"
CurView().openHelp("help")
} else {
helpPage := args[0]
if FindRuntimeFile(RTHelp, helpPage) != nil {
CurView().openHelp(helpPage)
} else {
messenger.Error("Sorry, no help for ", helpPage)
}
}
}
// VSplit opens a vertical split with file given in the first argument
// If no file is given, it opens an empty buffer in a new split
func VSplit(args []string) {
if len(args) == 0 {
CurView().VSplit(NewBufferFromString("", ""))
} else {
buf, err := NewBufferFromFile(args[0])
if err != nil {
messenger.Error(err)
return
}
CurView().VSplit(buf)
}
}
// HSplit opens a horizontal split with file given in the first argument
// If no file is given, it opens an empty buffer in a new split
func HSplit(args []string) {
if len(args) == 0 {
CurView().HSplit(NewBufferFromString("", ""))
} else {
buf, err := NewBufferFromFile(args[0])
if err != nil {
messenger.Error(err)
return
}
CurView().HSplit(buf)
}
}
// Eval evaluates a lua expression
func Eval(args []string) {
if len(args) >= 1 {
err := L.DoString(args[0])
if err != nil {
messenger.Error(err)
}
} else {
messenger.Error("Not enough arguments")
}
}
// NewTab opens the given file in a new tab
func NewTab(args []string) {
if len(args) == 0 {
CurView().AddTab(true)
} else {
buf, err := NewBufferFromFile(args[0])
if err != nil {
messenger.Error(err)
return
}
tab := NewTabFromView(NewView(buf))
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
curTab = len(tabs) - 1
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.Views {
v.ToggleTabbar()
}
}
}
}
}
// Set sets an option
func Set(args []string) {
if len(args) < 2 {
messenger.Error("Not enough arguments")
return
}
option := args[0]
value := args[1]
SetOptionAndSettings(option, value)
}
// SetLocal sets an option local to the buffer
func SetLocal(args []string) {
if len(args) < 2 {
messenger.Error("Not enough arguments")
return
}
option := args[0]
value := args[1]
err := SetLocalOption(option, value, CurView())
if err != nil {
messenger.Error(err.Error())
}
}
// Show shows the value of the given option
func Show(args []string) {
if len(args) < 1 {
messenger.Error("Please provide an option to show")
return
}
option := GetOption(args[0])
if option == nil {
messenger.Error(args[0], " is not a valid option")
return
}
messenger.Message(option)
}
// ShowKey displays the action that a key is bound to
func ShowKey(args []string) {
if len(args) < 1 {
messenger.Error("Please provide a key to show")
return
}
if action, ok := bindingsStr[args[0]]; ok {
messenger.Message(action)
} else {
messenger.Message(args[0], " has no binding")
}
}
// Bind creates a new keybinding
func Bind(args []string) {
if len(args) < 2 {
messenger.Error("Not enough arguments")
return
}
BindKey(args[0], args[1])
}
// Run runs a shell command in the background
func Run(args []string) {
// Run a shell command in the background (openTerm is false)
HandleShellCommand(shellwords.Join(args...), false, true)
}
// Quit closes the main view
func Quit(args []string) {
// Close the main view
CurView().Quit(true)
}
// Save saves the buffer in the main view
func Save(args []string) {
if len(args) == 0 {
// Save the main view
CurView().Save(true)
} else {
CurView().Buf.SaveAs(args[0])
}
}
// Replace runs search and replace
func Replace(args []string) {
if len(args) < 2 || len(args) > 4 {
// We need to find both a search and replace expression
messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
return
}
all := false
noRegex := false
if len(args) > 2 {
for _, arg := range args[2:] {
switch arg {
case "-a":
all = true
case "-l":
noRegex = true
default:
messenger.Error("Invalid flag: " + arg)
return
}
}
}
search := string(args[0])
if noRegex {
search = regexp.QuoteMeta(search)
}
replace := string(args[1])
replaceBytes := []byte(replace)
regex, err := regexp.Compile("(?m)" + search)
if err != nil {
// There was an error with the user's regex
messenger.Error(err.Error())
return
}
view := CurView()
found := 0
replaceAll := func() {
var deltas []Delta
for i := 0; i < view.Buf.LinesNum(); i++ {
newText := regex.ReplaceAllFunc(view.Buf.lines[i].data, func(in []byte) []byte {
found++
return replaceBytes
})
from := Loc{0, i}
to := Loc{utf8.RuneCount(view.Buf.lines[i].data), i}
deltas = append(deltas, Delta{string(newText), from, to})
}
view.Buf.MultipleReplace(deltas)
}
if all {
replaceAll()
} else {
for {
// The 'check' flag was used
Search(search, view, true)
if !view.Cursor.HasSelection() {
break
}
view.Relocate()
RedrawAll()
choice, canceled := messenger.LetterPrompt("Perform replacement? (y,n,a)", 'y', 'n', 'a')
if canceled {
if view.Cursor.HasSelection() {
view.Cursor.Loc = view.Cursor.CurSelection[0]
view.Cursor.ResetSelection()
}
messenger.Reset()
break
} else if choice == 'a' {
if view.Cursor.HasSelection() {
view.Cursor.Loc = view.Cursor.CurSelection[0]
view.Cursor.ResetSelection()
}
messenger.Reset()
replaceAll()
break
} else if choice == 'y' {
view.Cursor.DeleteSelection()
view.Buf.Insert(view.Cursor.Loc, replace)
view.Cursor.ResetSelection()
messenger.Reset()
found++
}
if view.Cursor.HasSelection() {
searchStart = view.Cursor.CurSelection[1]
} else {
searchStart = view.Cursor.Loc
}
}
}
view.Cursor.Relocate()
if found > 1 {
messenger.Message("Replaced ", found, " occurrences of ", search)
} else if found == 1 {
messenger.Message("Replaced ", found, " occurrence of ", search)
} else {
messenger.Message("Nothing matched ", search)
}
}
// ReplaceAll replaces search term all at once
func ReplaceAll(args []string) {
// aliased to Replace command
Replace(append(args, "-a"))
}
// Term opens a terminal in the current view
func Term(args []string) {
var err error
if len(args) == 0 {
err = CurView().StartTerminal([]string{os.Getenv("SHELL"), "-i"}, true, false, "")
} else {
err = CurView().StartTerminal(args, true, false, "")
}
if err != nil {
messenger.Error(err)
}
}
// HandleCommand handles input from the user
func HandleCommand(input string) {
args, err := shellwords.Split(input)
if err != nil {
messenger.Error("Error parsing args ", err)
return
}
inputCmd := args[0]
if _, ok := commands[inputCmd]; !ok {
messenger.Error("Unknown command ", inputCmd)
} else {
commands[inputCmd].action(args[1:])
}
}

31
cmd/micro/debug.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"log"
"os"
"github.com/zyedidia/micro/internal/util"
)
// NullWriter simply sends writes into the void
type NullWriter struct{}
// Write is empty
func (NullWriter) Write(data []byte) (n int, err error) {
return 0, nil
}
// InitLog sets up the debug log system for micro if it has been enabled by compile-time variables
func InitLog() {
if util.Debug == "ON" {
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
log.SetOutput(f)
log.Println("Micro started")
} else {
log.SetOutput(NullWriter{})
}
}

View File

@@ -1,28 +0,0 @@
package main
import "github.com/zyedidia/micro/cmd/micro/highlight"
var syntaxFiles []*highlight.File
func LoadSyntaxFiles() {
InitColorscheme()
for _, f := range ListRuntimeFiles(RTSyntax) {
data, err := f.Data()
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
} else {
LoadSyntaxFile(data, f.Name())
}
}
}
func LoadSyntaxFile(text []byte, filename string) {
f, err := highlight.ParseFile(text)
if err != nil {
TermMessage("Syntax file error: " + filename + ": " + err.Error())
return
}
syntaxFiles = append(syntaxFiles, f)
}

149
cmd/micro/initlua.go Normal file
View File

@@ -0,0 +1,149 @@
package main
import (
"log"
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"
"github.com/zyedidia/micro/internal/action"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/display"
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"
)
func init() {
ulua.L = lua.NewState()
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":
return luaImportMicro()
case "micro/shell":
return luaImportMicroShell()
case "micro/buffer":
return luaImportMicroBuffer()
case "micro/config":
return luaImportMicroConfig()
case "micro/util":
return luaImportMicroUtil()
default:
return ulua.Import(pkg)
}
}
func luaImportMicro() *lua.LTable {
pkg := ulua.L.NewTable()
ulua.L.SetField(pkg, "TermMessage", luar.New(ulua.L, screen.TermMessage))
ulua.L.SetField(pkg, "TermError", luar.New(ulua.L, screen.TermError))
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
}
func luaImportMicroConfig() *lua.LTable {
pkg := ulua.L.NewTable()
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))
ulua.L.SetField(pkg, "OptionValueComplete", luar.New(ulua.L, action.OptionValueComplete))
ulua.L.SetField(pkg, "NoComplete", luar.New(ulua.L, nil))
ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKey))
ulua.L.SetField(pkg, "Reload", luar.New(ulua.L, action.ReloadConfig))
ulua.L.SetField(pkg, "AddRuntimeFileFromMemory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFilesFromDirectory))
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.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))
return pkg
}
func luaImportMicroShell() *lua.LTable {
pkg := ulua.L.NewTable()
ulua.L.SetField(pkg, "ExecCommand", luar.New(ulua.L, shell.ExecCommand))
ulua.L.SetField(pkg, "RunCommand", luar.New(ulua.L, shell.RunCommand))
ulua.L.SetField(pkg, "RunBackgroundShell", luar.New(ulua.L, shell.RunBackgroundShell))
ulua.L.SetField(pkg, "RunInteractiveShell", luar.New(ulua.L, shell.RunInteractiveShell))
ulua.L.SetField(pkg, "JobStart", luar.New(ulua.L, shell.JobStart))
ulua.L.SetField(pkg, "JobSpawn", luar.New(ulua.L, shell.JobSpawn))
ulua.L.SetField(pkg, "JobStop", luar.New(ulua.L, shell.JobStop))
ulua.L.SetField(pkg, "JobSend", luar.New(ulua.L, shell.JobSend))
ulua.L.SetField(pkg, "RunTermEmulator", luar.New(ulua.L, action.RunTermEmulator))
ulua.L.SetField(pkg, "TermEmuSupported", luar.New(ulua.L, action.TermEmuSupported))
return pkg
}
func luaImportMicroBuffer() *lua.LTable {
pkg := ulua.L.NewTable()
ulua.L.SetField(pkg, "NewMessage", luar.New(ulua.L, buffer.NewMessage))
ulua.L.SetField(pkg, "NewMessageAtLine", luar.New(ulua.L, buffer.NewMessageAtLine))
ulua.L.SetField(pkg, "MTInfo", luar.New(ulua.L, buffer.MTInfo))
ulua.L.SetField(pkg, "MTWarning", luar.New(ulua.L, buffer.MTWarning))
ulua.L.SetField(pkg, "MTError", luar.New(ulua.L, buffer.MTError))
ulua.L.SetField(pkg, "Loc", luar.New(ulua.L, func(x, y int) buffer.Loc {
return buffer.Loc{x, y}
}))
ulua.L.SetField(pkg, "BTDefault", luar.New(ulua.L, buffer.BTDefault.Kind))
ulua.L.SetField(pkg, "BTHelp", luar.New(ulua.L, buffer.BTHelp.Kind))
ulua.L.SetField(pkg, "BTLog", luar.New(ulua.L, buffer.BTLog.Kind))
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
}
func luaImportMicroUtil() *lua.LTable {
pkg := ulua.L.NewTable()
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

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

View File

@@ -1,267 +0,0 @@
package main
import (
"bufio"
"io"
"unicode/utf8"
"github.com/zyedidia/micro/cmd/micro/highlight"
)
func runeToByteIndex(n int, txt []byte) int {
if n == 0 {
return 0
}
count := 0
i := 0
for len(txt) > 0 {
_, size := utf8.DecodeRune(txt)
txt = txt[size:]
count += size
i++
if i == n {
break
}
}
return count
}
// A Line contains the data in bytes as well as a highlight state, match
// and a flag for whether the highlighting needs to be updated
type Line struct {
data []byte
state highlight.State
match highlight.LineMatch
rehighlight bool
}
// A LineArray simply stores and array of lines and makes it easy to insert
// and delete in it
type LineArray struct {
lines []Line
}
// Append efficiently appends lines together
// It allocates an additional 10000 lines if the original estimate
// is incorrect
func Append(slice []Line, data ...Line) []Line {
l := len(slice)
if l+len(data) > cap(slice) { // reallocate
newSlice := make([]Line, (l+len(data))+10000)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0 : l+len(data)]
for i, c := range data {
slice[l+i] = c
}
return slice
}
// NewLineArray returns a new line array from an array of bytes
func NewLineArray(size int64, reader io.Reader) *LineArray {
la := new(LineArray)
la.lines = make([]Line, 0, 1000)
br := bufio.NewReader(reader)
var loaded int
n := 0
for {
data, err := br.ReadBytes('\n')
if len(data) > 1 && data[len(data)-2] == '\r' {
data = append(data[:len(data)-2], '\n')
if fileformat == 0 {
fileformat = 2
}
} else if len(data) > 0 {
if fileformat == 0 {
fileformat = 1
}
}
if n >= 1000 && loaded >= 0 {
totalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))
newSlice := make([]Line, len(la.lines), totalLinesNum+10000)
copy(newSlice, la.lines)
la.lines = newSlice
loaded = -1
}
if loaded >= 0 {
loaded += len(data)
}
if err != nil {
if err == io.EOF {
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
// la.lines = Append(la.lines, Line{data[:len(data)]})
}
// Last line was read
break
} else {
// la.lines = Append(la.lines, Line{data[:len(data)-1]})
la.lines = Append(la.lines, Line{data[:len(data)-1], nil, nil, false})
}
n++
}
return la
}
// Returns the String representation of the LineArray
func (la *LineArray) String() string {
str := ""
for i, l := range la.lines {
str += string(l.data)
if i != len(la.lines)-1 {
str += "\n"
}
}
return str
}
// SaveString returns the string that should be written to disk when
// the line array is saved
// It is the same as string but uses crlf or lf line endings depending
func (la *LineArray) SaveString(useCrlf bool) string {
str := ""
for i, l := range la.lines {
str += string(l.data)
if i != len(la.lines)-1 {
if useCrlf {
str += "\r"
}
str += "\n"
}
}
return str
}
// 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})
copy(la.lines[y+2:], la.lines[y+1:])
la.lines[y+1] = Line{[]byte{}, la.lines[y].state, nil, false}
}
// inserts a byte array at a given location
func (la *LineArray) insert(pos Loc, value []byte) {
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
// x, y := pos.x, pos.y
for i := 0; i < len(value); i++ {
if value[i] == '\n' {
la.Split(Loc{x, y})
x = 0
y++
continue
}
la.insertByte(Loc{x, y}, value[i])
x++
}
}
// inserts a byte at a given location
func (la *LineArray) insertByte(pos Loc, value byte) {
la.lines[pos.Y].data = append(la.lines[pos.Y].data, 0)
copy(la.lines[pos.Y].data[pos.X+1:], la.lines[pos.Y].data[pos.X:])
la.lines[pos.Y].data[pos.X] = value
}
// JoinLines joins the two lines a and b
func (la *LineArray) JoinLines(a, b int) {
la.insert(Loc{len(la.lines[a].data), a}, la.lines[b].data)
la.DeleteLine(b)
}
// Split splits a line at a given position
func (la *LineArray) Split(pos Loc) {
la.NewlineBelow(pos.Y)
la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y].data[pos.X:])
la.lines[pos.Y+1].state = la.lines[pos.Y].state
la.lines[pos.Y].state = nil
la.lines[pos.Y].match = nil
la.lines[pos.Y+1].match = nil
la.lines[pos.Y].rehighlight = true
la.DeleteToEnd(Loc{pos.X, pos.Y})
}
// removes from start to end
func (la *LineArray) remove(start, end Loc) string {
sub := la.Substr(start, end)
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
if start.Y == end.Y {
la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
} else {
for i := start.Y + 1; i <= end.Y-1; i++ {
la.DeleteLine(start.Y + 1)
}
la.DeleteToEnd(Loc{startX, start.Y})
la.DeleteFromStart(Loc{endX - 1, start.Y + 1})
la.JoinLines(start.Y, start.Y+1)
}
return sub
}
// DeleteToEnd deletes from the end of a line to the position
func (la *LineArray) DeleteToEnd(pos Loc) {
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X]
}
// DeleteFromStart deletes from the start of a line to the position
func (la *LineArray) DeleteFromStart(pos Loc) {
la.lines[pos.Y].data = la.lines[pos.Y].data[pos.X+1:]
}
// DeleteLine deletes the line number
func (la *LineArray) DeleteLine(y int) {
la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])]
}
// DeleteByte deletes the byte at a position
func (la *LineArray) DeleteByte(pos Loc) {
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
}
// Substr returns the string representation between two locations
func (la *LineArray) Substr(start, end Loc) string {
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
if start.Y == end.Y {
return string(la.lines[start.Y].data[startX:endX])
}
var str string
str += string(la.lines[start.Y].data[startX:]) + "\n"
for i := start.Y + 1; i <= end.Y-1; i++ {
str += string(la.lines[i].data) + "\n"
}
str += string(la.lines[end.Y].data[:endX])
return str
}
// State gets the highlight state for the given line number
func (la *LineArray) State(lineN int) highlight.State {
return la.lines[lineN].state
}
// SetState sets the highlight state at the given line number
func (la *LineArray) SetState(lineN int, s highlight.State) {
la.lines[lineN].state = s
}
// SetMatch sets the match at the given line number
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
la.lines[lineN].match = m
}
// Match retrieves the match for the given line number
func (la *LineArray) Match(lineN int) highlight.LineMatch {
return la.lines[lineN].match
}

View File

@@ -1,669 +0,0 @@
package main
import (
"bufio"
"bytes"
"encoding/gob"
"fmt"
"os"
"strconv"
"github.com/mattn/go-runewidth"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/cmd/micro/shellwords"
"github.com/zyedidia/tcell"
)
// TermMessage sends a message to the user in the terminal. This usually occurs before
// micro has been fully initialized -- ie if there is an error in the syntax highlighting
// regular expressions
// The function must be called when the screen is not initialized
// This will write the message, and wait for the user
// to press and key to continue
func TermMessage(msg ...interface{}) {
screenWasNil := screen == nil
if !screenWasNil {
screen.Fini()
screen = nil
}
fmt.Println(msg...)
fmt.Print("\nPress enter to continue")
reader := bufio.NewReader(os.Stdin)
reader.ReadString('\n')
if !screenWasNil {
InitScreen()
}
}
// TermError sends an error to the user in the terminal. Like TermMessage except formatted
// as an error
func TermError(filename string, lineNum int, err string) {
TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
}
// Messenger is an object that makes it easy to send messages to the user
// and get input from the user
type Messenger struct {
log *Buffer
// Are we currently prompting the user?
hasPrompt bool
// Is there a message to print
hasMessage bool
// Message to print
message string
// The user's response to a prompt
response string
// style to use when drawing the message
style tcell.Style
// We have to keep track of the cursor for prompting
cursorx int
// This map stores the history for all the different kinds of uses Prompt has
// It's a map of history type -> history array
history map[string][]string
historyNum int
// Is the current message a message from the gutter
gutterMessage bool
}
// AddLog sends a message to the log view
func (m *Messenger) AddLog(msg ...interface{}) {
logMessage := fmt.Sprint(msg...)
buffer := m.getBuffer()
buffer.insert(buffer.End(), []byte(logMessage+"\n"))
buffer.Cursor.Loc = buffer.End()
buffer.Cursor.Relocate()
}
func (m *Messenger) getBuffer() *Buffer {
if m.log == nil {
m.log = NewBufferFromString("", "")
m.log.name = "Log"
}
return m.log
}
// Message sends a message to the user
func (m *Messenger) Message(msg ...interface{}) {
displayMessage := fmt.Sprint(msg...)
// only display a new message if there isn't an active prompt
// this is to prevent overwriting an existing prompt to the user
if m.hasPrompt == false {
// if there is no active prompt then style and display the message as normal
m.message = displayMessage
m.style = defStyle
if _, ok := colorscheme["message"]; ok {
m.style = colorscheme["message"]
}
m.hasMessage = true
}
// add the message to the log regardless of active prompts
m.AddLog(displayMessage)
}
// Error sends an error message to the user
func (m *Messenger) Error(msg ...interface{}) {
buf := new(bytes.Buffer)
fmt.Fprint(buf, msg...)
// only display a new message if there isn't an active prompt
// this is to prevent overwriting an existing prompt to the user
if m.hasPrompt == false {
// if there is no active prompt then style and display the message as normal
m.message = buf.String()
m.style = defStyle.
Foreground(tcell.ColorBlack).
Background(tcell.ColorMaroon)
if _, ok := colorscheme["error-message"]; ok {
m.style = colorscheme["error-message"]
}
m.hasMessage = true
}
// add the message to the log regardless of active prompts
m.AddLog(buf.String())
}
func (m *Messenger) PromptText(msg ...interface{}) {
displayMessage := fmt.Sprint(msg...)
// if there is no active prompt then style and display the message as normal
m.message = displayMessage
m.style = defStyle
if _, ok := colorscheme["message"]; ok {
m.style = colorscheme["message"]
}
m.hasMessage = true
// add the message to the log regardless of active prompts
m.AddLog(displayMessage)
}
// YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
m.hasPrompt = true
m.PromptText(prompt)
_, h := screen.Size()
for {
m.Clear()
m.Display()
screen.ShowCursor(Count(m.message), h-1)
screen.Show()
event := <-events
switch e := event.(type) {
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyRune:
if e.Rune() == 'y' || e.Rune() == 'Y' {
m.AddLog("\t--> y")
m.hasPrompt = false
return true, false
} else if e.Rune() == 'n' || e.Rune() == 'N' {
m.AddLog("\t--> n")
m.hasPrompt = false
return false, false
}
case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
m.AddLog("\t--> (cancel)")
m.Clear()
m.Reset()
m.hasPrompt = false
return false, true
}
}
}
}
// LetterPrompt gives the user a prompt and waits for a one letter response
func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
m.hasPrompt = true
m.PromptText(prompt)
_, h := screen.Size()
for {
m.Clear()
m.Display()
screen.ShowCursor(Count(m.message), h-1)
screen.Show()
event := <-events
switch e := event.(type) {
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyRune:
for _, r := range responses {
if e.Rune() == r {
m.AddLog("\t--> " + string(r))
m.Clear()
m.Reset()
m.hasPrompt = false
return r, false
}
}
case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
m.AddLog("\t--> (cancel)")
m.Clear()
m.Reset()
m.hasPrompt = false
return ' ', true
}
}
}
}
// Completion represents a type of completion
type Completion int
const (
NoCompletion Completion = iota
FileCompletion
CommandCompletion
HelpCompletion
OptionCompletion
PluginCmdCompletion
PluginNameCompletion
OptionValueCompletion
)
// Prompt sends the user a message and waits for a response to be typed in
// This function blocks the main loop while waiting for input
func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTypes ...Completion) (string, bool) {
m.hasPrompt = true
m.PromptText(prompt)
if _, ok := m.history[historyType]; !ok {
m.history[historyType] = []string{""}
} else {
m.history[historyType] = append(m.history[historyType], "")
}
m.historyNum = len(m.history[historyType]) - 1
response, canceled := placeholder, true
m.response = response
m.cursorx = Count(placeholder)
RedrawAll()
for m.hasPrompt {
var suggestions []string
m.Clear()
event := <-events
switch e := event.(type) {
case *tcell.EventResize:
for _, t := range tabs {
t.Resize()
}
RedrawAll()
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
// Cancel
m.AddLog("\t--> (cancel)")
m.hasPrompt = false
case tcell.KeyEnter:
// User is done entering their response
m.AddLog("\t--> " + m.response)
m.hasPrompt = false
response, canceled = m.response, false
m.history[historyType][len(m.history[historyType])-1] = response
case tcell.KeyTab:
args, err := shellwords.Split(m.response)
if err != nil {
break
}
currentArg := ""
currentArgNum := 0
if len(args) > 0 {
currentArgNum = len(args) - 1
currentArg = args[currentArgNum]
}
var completionType Completion
if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
if command, ok := commands[args[0]]; ok {
completionTypes = append([]Completion{CommandCompletion}, command.completions...)
}
}
if currentArgNum >= len(completionTypes) {
completionType = completionTypes[len(completionTypes)-1]
} else {
completionType = completionTypes[currentArgNum]
}
var chosen string
if completionType == FileCompletion {
chosen, suggestions = FileComplete(currentArg)
} else if completionType == CommandCompletion {
chosen, suggestions = CommandComplete(currentArg)
} else if completionType == HelpCompletion {
chosen, suggestions = HelpComplete(currentArg)
} else if completionType == OptionCompletion {
chosen, suggestions = OptionComplete(currentArg)
} else if completionType == OptionValueCompletion {
if currentArgNum-1 > 0 {
chosen, suggestions = OptionValueComplete(args[currentArgNum-1], currentArg)
}
} else if completionType == PluginCmdCompletion {
chosen, suggestions = PluginCmdComplete(currentArg)
} else if completionType == PluginNameCompletion {
chosen, suggestions = PluginNameComplete(currentArg)
} else if completionType < NoCompletion {
chosen, suggestions = PluginComplete(completionType, currentArg)
}
if len(suggestions) > 1 {
chosen = chosen + CommonSubstring(suggestions...)
}
if len(suggestions) != 0 && chosen != "" {
m.response = shellwords.Join(append(args[:len(args)-1], chosen)...)
m.cursorx = Count(m.response)
}
}
}
m.HandleEvent(event, m.history[historyType])
m.Clear()
for _, v := range tabs[curTab].Views {
v.Display()
}
DisplayTabs()
m.Display()
if len(suggestions) > 1 {
m.DisplaySuggestions(suggestions)
}
screen.Show()
}
m.Clear()
m.Reset()
return response, canceled
}
// UpHistory fetches the previous item in the history
func (m *Messenger) UpHistory(history []string) {
if m.historyNum > 0 {
m.historyNum--
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
}
// DownHistory fetches the next item in the history
func (m *Messenger) DownHistory(history []string) {
if m.historyNum < len(history)-1 {
m.historyNum++
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
}
// CursorLeft moves the cursor one character left
func (m *Messenger) CursorLeft() {
if m.cursorx > 0 {
m.cursorx--
}
}
// CursorRight moves the cursor one character right
func (m *Messenger) CursorRight() {
if m.cursorx < Count(m.response) {
m.cursorx++
}
}
// Start moves the cursor to the start of the line
func (m *Messenger) Start() {
m.cursorx = 0
}
// End moves the cursor to the end of the line
func (m *Messenger) End() {
m.cursorx = Count(m.response)
}
// Backspace deletes one character
func (m *Messenger) Backspace() {
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
m.cursorx--
}
}
// Paste pastes the clipboard
func (m *Messenger) Paste() {
clip, _ := clipboard.ReadAll("clipboard")
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
}
// WordLeft moves the cursor one word to the left
func (m *Messenger) WordLeft() {
response := []rune(m.response)
m.CursorLeft()
if m.cursorx <= 0 {
return
}
for IsWhitespace(response[m.cursorx]) {
if m.cursorx <= 0 {
return
}
m.CursorLeft()
}
m.CursorLeft()
for IsWordChar(string(response[m.cursorx])) {
if m.cursorx <= 0 {
return
}
m.CursorLeft()
}
m.CursorRight()
}
// WordRight moves the cursor one word to the right
func (m *Messenger) WordRight() {
response := []rune(m.response)
if m.cursorx >= len(response) {
return
}
for IsWhitespace(response[m.cursorx]) {
m.CursorRight()
if m.cursorx >= len(response) {
m.CursorRight()
return
}
}
m.CursorRight()
if m.cursorx >= len(response) {
return
}
for IsWordChar(string(response[m.cursorx])) {
m.CursorRight()
if m.cursorx >= len(response) {
return
}
}
}
// DeleteWordLeft deletes one word to the left
func (m *Messenger) DeleteWordLeft() {
m.WordLeft()
m.response = string([]rune(m.response)[:m.cursorx])
}
// HandleEvent handles an event for the prompter
func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
switch e := event.(type) {
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyCtrlA:
m.Start()
case tcell.KeyCtrlE:
m.End()
case tcell.KeyUp:
m.UpHistory(history)
case tcell.KeyDown:
m.DownHistory(history)
case tcell.KeyLeft:
if e.Modifiers() == tcell.ModCtrl {
m.Start()
} else if e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
m.WordLeft()
} else {
m.CursorLeft()
}
case tcell.KeyRight:
if e.Modifiers() == tcell.ModCtrl {
m.End()
} else if e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
m.WordRight()
} else {
m.CursorRight()
}
case tcell.KeyBackspace2, tcell.KeyBackspace:
if e.Modifiers() == tcell.ModCtrl || e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
m.DeleteWordLeft()
} else {
m.Backspace()
}
case tcell.KeyCtrlW:
m.DeleteWordLeft()
case tcell.KeyCtrlV:
m.Paste()
case tcell.KeyCtrlF:
m.WordRight()
case tcell.KeyCtrlB:
m.WordLeft()
case tcell.KeyRune:
m.response = Insert(m.response, m.cursorx, string(e.Rune()))
m.cursorx++
}
history[m.historyNum] = m.response
case *tcell.EventPaste:
clip := e.Text()
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
case *tcell.EventMouse:
x, y := e.Position()
x -= Count(m.message)
button := e.Buttons()
_, screenH := screen.Size()
if y == screenH-1 {
switch button {
case tcell.Button1:
m.cursorx = x
if m.cursorx < 0 {
m.cursorx = 0
} else if m.cursorx > Count(m.response) {
m.cursorx = Count(m.response)
}
}
}
}
}
// Reset resets the messenger's cursor, message and response
func (m *Messenger) Reset() {
m.cursorx = 0
m.message = ""
m.response = ""
}
// Clear clears the line at the bottom of the editor
func (m *Messenger) Clear() {
w, h := screen.Size()
for x := 0; x < w; x++ {
screen.SetContent(x, h-1, ' ', nil, defStyle)
}
}
func (m *Messenger) DisplaySuggestions(suggestions []string) {
w, screenH := screen.Size()
y := screenH - 2
statusLineStyle := defStyle.Reverse(true)
if style, ok := colorscheme["statusline"]; ok {
statusLineStyle = style
}
for x := 0; x < w; x++ {
screen.SetContent(x, y, ' ', nil, statusLineStyle)
}
x := 0
for _, suggestion := range suggestions {
for _, c := range suggestion {
screen.SetContent(x, y, c, nil, statusLineStyle)
x++
}
screen.SetContent(x, y, ' ', nil, statusLineStyle)
x++
}
}
// Display displays messages or prompts
func (m *Messenger) Display() {
_, h := screen.Size()
if m.hasMessage {
if m.hasPrompt || globalSettings["infobar"].(bool) {
runes := []rune(m.message + m.response)
posx := 0
for x := 0; x < len(runes); x++ {
screen.SetContent(posx, h-1, runes[x], nil, m.style)
posx += runewidth.RuneWidth(runes[x])
}
}
}
if m.hasPrompt {
screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
screen.Show()
}
}
// LoadHistory attempts to load user history from configDir/buffers/history
// into the history map
// The savehistory option must be on
func (m *Messenger) LoadHistory() {
if GetGlobalOption("savehistory").(bool) {
file, err := os.Open(configDir + "/buffers/history")
defer file.Close()
var decodedMap map[string][]string
if err == nil {
decoder := gob.NewDecoder(file)
err = decoder.Decode(&decodedMap)
if err != nil {
m.Error("Error loading history:", err)
return
}
}
if decodedMap != nil {
m.history = decodedMap
} else {
m.history = make(map[string][]string)
}
} else {
m.history = make(map[string][]string)
}
}
// SaveHistory saves the user's command history to configDir/buffers/history
// only if the savehistory option is on
func (m *Messenger) SaveHistory() {
if GetGlobalOption("savehistory").(bool) {
// Don't save history past 100
for k, v := range m.history {
if len(v) > 100 {
m.history[k] = v[len(m.history[k])-100:]
}
}
file, err := os.Create(configDir + "/buffers/history")
defer file.Close()
if err == nil {
encoder := gob.NewEncoder(file)
err = encoder.Encode(m.history)
if err != nil {
m.Error("Error saving history:", err)
return
}
}
}
}
// A GutterMessage is a message displayed on the side of the editor
type GutterMessage struct {
lineNum int
msg string
kind int
}
// These are the different types of messages
const (
// GutterInfo represents a simple info message
GutterInfo = iota
// GutterWarning represents a compiler warning
GutterWarning
// GutterError represents a compiler error
GutterError
)

View File

@@ -5,76 +5,131 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"sort"
"time"
"github.com/go-errors/errors"
"github.com/mattn/go-isatty"
"github.com/mitchellh/go-homedir"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/cmd/micro/terminfo"
isatty "github.com/mattn/go-isatty"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/action"
"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/tcell"
"github.com/zyedidia/tcell/encoding"
"layeh.com/gopher-luar"
)
const (
doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
autosaveTime = 8 // Number of seconds to wait before autosaving
)
var (
// The main screen
screen tcell.Screen
// Object to send messages and prompts to the user
messenger *Messenger
// The default highlighting style
// This simply defines the default foreground and background colors
defStyle tcell.Style
// Where the user's configuration is
// This should be $XDG_CONFIG_HOME/micro
// If $XDG_CONFIG_HOME is not set, it is ~/.config/micro
configDir string
// Version is the version number or commit hash
// These variables should be set by the linker when compiling
Version = "0.0.0-unknown"
// CommitHash is the commit this version was built on
CommitHash = "Unknown"
// CompileDate is the date this binary was compiled on
CompileDate = "Unknown"
// The list of views
tabs []*Tab
// This is the currently open tab
// It's just an index to the tab in the tabs array
curTab int
// Channel of jobs running in the background
jobs chan JobFunction
// Event channel
events chan tcell.Event
autosave chan bool
// Channels for the terminal emulator
updateterm chan bool
closeterm chan int
// How many redraws have happened
numRedraw uint
// Command line flags
flagVersion = flag.Bool("version", false, "Show the version number and information")
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
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("-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")
fmt.Println(" \tFor example: `micro -syntax off file.c`")
fmt.Println("\nUse `micro -options` to see the full list of configuration options")
}
optionFlags = make(map[string]*string)
for k, v := range config.DefaultAllSettings() {
optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'.", k, v))
}
flag.Parse()
if *flagVersion {
// If -version was passed
fmt.Println("Version:", util.Version)
fmt.Println("Commit hash:", util.CommitHash)
fmt.Println("Compiled on", util.CompileDate)
os.Exit(0)
}
if *flagOptions {
// If -options was passed
var keys []string
m := config.DefaultAllSettings()
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := m[k]
fmt.Printf("-%s value\n", k)
fmt.Printf(" \tDefault value: '%v'\n", v)
}
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
// based on the input stored in flag.Args()
func LoadInput() []*Buffer {
func LoadInput() []*buffer.Buffer {
// There are a number of ways micro should start given its input
// 1. If it is given a files in flag.Args(), it should open those
@@ -90,25 +145,15 @@ func LoadInput() []*Buffer {
var input []byte
var err error
args := flag.Args()
buffers := make([]*Buffer, 0, len(args))
buffers := make([]*buffer.Buffer, 0, len(args))
if len(args) > 0 {
// Option 1
// We go through each file and load it
for i := 0; i < len(args); i++ {
if strings.HasPrefix(args[i], "+") {
if strings.Contains(args[i], ":") {
split := strings.Split(args[i], ":")
*flagStartPos = split[0][1:] + "," + split[1]
} else {
*flagStartPos = args[i][1:] + ",0"
}
continue
}
buf, err := NewBufferFromFile(args[i])
buf, err := buffer.NewBufferFromFile(args[i], buffer.BTDefault)
if err != nil {
TermMessage(err)
screen.TermMessage(err)
continue
}
// If the file didn't exist, input will be empty, and we'll open an empty buffer
@@ -120,466 +165,181 @@ func LoadInput() []*Buffer {
// and we should read from stdin
input, err = ioutil.ReadAll(os.Stdin)
if err != nil {
TermMessage("Error reading from stdin: ", err)
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, NewBufferFromString(string(input), filename))
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, NewBufferFromString(string(input), filename))
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
}
return buffers
}
// InitConfigDir finds the configuration directory for micro according to the XDG spec.
// If no directory is found, it creates one.
func InitConfigDir() {
xdgHome := os.Getenv("XDG_CONFIG_HOME")
if xdgHome == "" {
// The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
home, err := homedir.Dir()
if err != nil {
TermMessage("Error finding your home directory\nCan't load config files")
return
}
xdgHome = home + "/.config"
}
configDir = xdgHome + "/micro"
if len(*flagConfigDir) > 0 {
if _, err := os.Stat(*flagConfigDir); os.IsNotExist(err) {
TermMessage("Error: " + *flagConfigDir + " does not exist. Defaulting to " + configDir + ".")
} else {
configDir = *flagConfigDir
return
}
}
if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
// If the xdgHome doesn't exist we should create it
err = os.Mkdir(xdgHome, os.ModePerm)
if err != nil {
TermMessage("Error creating XDG_CONFIG_HOME directory: " + err.Error())
}
}
if _, err := os.Stat(configDir); os.IsNotExist(err) {
// If the micro specific config directory doesn't exist we should create that too
err = os.Mkdir(configDir, os.ModePerm)
if err != nil {
TermMessage("Error creating configuration directory: " + err.Error())
}
}
}
// InitScreen creates and initializes the tcell screen
func InitScreen() {
// Should we enable true color?
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
tcelldb := os.Getenv("TCELLDB")
os.Setenv("TCELLDB", configDir+"/.tcelldb")
// In order to enable true color, we have to set the TERM to `xterm-truecolor` when
// initializing tcell, but after that, we can set the TERM back to whatever it was
oldTerm := os.Getenv("TERM")
if truecolor {
os.Setenv("TERM", "xterm-truecolor")
}
// Initilize tcell
var err error
screen, err = tcell.NewScreen()
if err != nil {
if err == tcell.ErrTermNotFound {
terminfo.WriteDB(configDir + "/.tcelldb")
screen, err = tcell.NewScreen()
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a screen.")
os.Exit(1)
}
} else {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a screen.")
os.Exit(1)
}
}
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 GetGlobalOption("mouse").(bool) {
screen.EnableMouse()
}
os.Setenv("TCELLDB", tcelldb)
// screen.SetStyle(defStyle)
}
// RedrawAll redraws everything -- all the views and the messenger
func RedrawAll() {
messenger.Clear()
w, h := screen.Size()
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
screen.SetContent(x, y, ' ', nil, defStyle)
}
}
for _, v := range tabs[curTab].Views {
v.Display()
}
DisplayTabs()
messenger.Display()
if globalSettings["keymenu"].(bool) {
DisplayKeyMenu()
}
screen.Show()
if numRedraw%50 == 0 {
runtime.GC()
}
numRedraw++
}
func LoadAll() {
// Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
InitConfigDir()
// Build a list of available Extensions (Syntax, Colorscheme etc.)
InitRuntimeFiles()
// Load the user's settings
InitGlobalSettings()
InitCommands()
InitBindings()
InitColorscheme()
for _, tab := range tabs {
for _, v := range tab.Views {
v.Buf.UpdateRules()
}
}
}
// Command line flags
var flagVersion = flag.Bool("version", false, "Show the version number and information")
var flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.")
var flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
var flagOptions = flag.Bool("options", false, "Show all option help")
func main() {
flag.Usage = func() {
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
fmt.Println("-config-dir dir")
fmt.Println(" \tSpecify a custom location for the configuration directory")
fmt.Println("-startpos LINE,COL")
fmt.Println("+LINE:COL")
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
fmt.Println(" \tThis can also be done by opening file:LINE:COL")
fmt.Println("-options")
fmt.Println(" \tShow all option help")
fmt.Println("-version")
fmt.Println(" \tShow the version number and information")
defer os.Exit(0)
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
fmt.Println("-option value")
fmt.Println(" \tSet `option` to `value` for this session")
fmt.Println(" \tFor example: `micro -syntax off file.c`")
fmt.Println("\nUse `micro -options` to see the full list of configuration options")
// runtime.SetCPUProfileRate(400)
// f, _ := os.Create("micro.prof")
// pprof.StartCPUProfile(f)
// defer pprof.StopCPUProfile()
var err error
InitFlags()
InitLog()
err = config.InitConfigDir(*flagConfigDir)
if err != nil {
screen.TermMessage(err)
}
optionFlags := make(map[string]*string)
for k, v := range DefaultGlobalSettings() {
optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'", k, v))
config.InitRuntimeFiles()
err = config.ReadSettings()
if err != nil {
screen.TermMessage(err)
}
config.InitGlobalSettings()
flag.Parse()
if *flagVersion {
// If -version was passed
fmt.Println("Version:", Version)
fmt.Println("Commit hash:", CommitHash)
fmt.Println("Compiled on", CompileDate)
os.Exit(0)
}
if *flagOptions {
// If -options was passed
for k, v := range DefaultGlobalSettings() {
fmt.Printf("-%s value\n", k)
fmt.Printf(" \tThe %s option. Default value: '%v'\n", k, v)
// flag options
for k, v := range optionFlags {
if *v != "" {
nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
if err != nil {
screen.TermMessage(err)
continue
}
config.GlobalSettings[k] = nativeValue
}
os.Exit(0)
}
// Start the Lua VM for running plugins
L = lua.NewState()
defer L.Close()
DoPluginFlags()
// Some encoding stuff in case the user isn't using UTF-8
encoding.Register()
tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
screen.Init()
// Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
InitConfigDir()
// Build a list of available Extensions (Syntax, Colorscheme etc.)
InitRuntimeFiles()
// Load the user's settings
InitGlobalSettings()
InitCommands()
InitBindings()
// Start the screen
InitScreen()
// This is just so 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
defer func() {
if err := recover(); err != nil {
screen.Fini()
screen.Screen.Fini()
fmt.Println("Micro encountered an error:", err)
// backup all open buffers
for _, b := range buffer.OpenBuffers {
b.Backup(false)
}
// Print the stack trace too
fmt.Print(errors.Wrap(err, 2).ErrorStack())
os.Exit(1)
}
}()
// Create a new messenger
// This is used for sending the user messages in the bottom of the editor
messenger = new(Messenger)
messenger.LoadHistory()
// Now we load the input
buffers := LoadInput()
if len(buffers) == 0 {
screen.Fini()
os.Exit(1)
err = config.LoadAllPlugins()
if err != nil {
screen.TermMessage(err)
}
for _, buf := range buffers {
// For each buffer we create a new tab and place the view in that tab
tab := NewTabFromView(NewView(buf))
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
for _, t := range tabs {
for _, v := range t.Views {
v.Center(false)
}
action.InitBindings()
action.InitCommands()
t.Resize()
}
err = config.InitColorscheme()
if err != nil {
screen.TermMessage(err)
}
for k, v := range optionFlags {
if *v != "" {
SetOption(k, *v)
}
b := LoadInput()
if len(b) == 0 {
// No buffers to open
screen.Screen.Fini()
runtime.Goexit()
}
// Load all the plugin stuff
// We give plugins access to a bunch of variables here which could be useful to them
L.SetGlobal("OS", luar.New(L, runtime.GOOS))
L.SetGlobal("tabs", luar.New(L, tabs))
L.SetGlobal("curTab", luar.New(L, curTab))
L.SetGlobal("messenger", luar.New(L, messenger))
L.SetGlobal("GetOption", luar.New(L, GetOption))
L.SetGlobal("AddOption", luar.New(L, AddOption))
L.SetGlobal("SetOption", luar.New(L, SetOption))
L.SetGlobal("SetLocalOption", luar.New(L, SetLocalOption))
L.SetGlobal("BindKey", luar.New(L, BindKey))
L.SetGlobal("MakeCommand", luar.New(L, MakeCommand))
L.SetGlobal("CurView", luar.New(L, CurView))
L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
L.SetGlobal("HandleCommand", luar.New(L, HandleCommand))
L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
L.SetGlobal("ExecCommand", luar.New(L, ExecCommand))
L.SetGlobal("RunShellCommand", luar.New(L, RunShellCommand))
L.SetGlobal("RunBackgroundShell", luar.New(L, RunBackgroundShell))
L.SetGlobal("RunInteractiveShell", luar.New(L, RunInteractiveShell))
L.SetGlobal("TermEmuSupported", luar.New(L, TermEmuSupported))
L.SetGlobal("RunTermEmulator", luar.New(L, RunTermEmulator))
L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion))
L.SetGlobal("NewBuffer", luar.New(L, NewBufferFromString))
L.SetGlobal("NewBufferFromFile", luar.New(L, NewBufferFromFile))
L.SetGlobal("RuneStr", luar.New(L, func(r rune) string {
return string(r)
}))
L.SetGlobal("Loc", luar.New(L, func(x, y int) Loc {
return Loc{x, y}
}))
L.SetGlobal("WorkingDirectory", luar.New(L, os.Getwd))
L.SetGlobal("JoinPaths", luar.New(L, filepath.Join))
L.SetGlobal("DirectoryName", luar.New(L, filepath.Dir))
L.SetGlobal("configDir", luar.New(L, configDir))
L.SetGlobal("Reload", luar.New(L, LoadAll))
L.SetGlobal("ByteOffset", luar.New(L, ByteOffset))
L.SetGlobal("ToCharPos", luar.New(L, ToCharPos))
action.InitTabs(b)
action.InitGlobals()
// Used for asynchronous jobs
L.SetGlobal("JobStart", luar.New(L, JobStart))
L.SetGlobal("JobSpawn", luar.New(L, JobSpawn))
L.SetGlobal("JobSend", luar.New(L, JobSend))
L.SetGlobal("JobStop", luar.New(L, JobStop))
// Extension Files
L.SetGlobal("ReadRuntimeFile", luar.New(L, PluginReadRuntimeFile))
L.SetGlobal("ListRuntimeFiles", luar.New(L, PluginListRuntimeFiles))
L.SetGlobal("AddRuntimeFile", luar.New(L, PluginAddRuntimeFile))
L.SetGlobal("AddRuntimeFilesFromDirectory", luar.New(L, PluginAddRuntimeFilesFromDirectory))
L.SetGlobal("AddRuntimeFileFromMemory", luar.New(L, PluginAddRuntimeFileFromMemory))
// Access to Go stdlib
L.SetGlobal("import", luar.New(L, Import))
jobs = make(chan JobFunction, 100)
events = make(chan tcell.Event, 100)
autosave = make(chan bool)
updateterm = make(chan bool)
closeterm = make(chan int)
LoadPlugins()
for _, t := range tabs {
for _, v := range t.Views {
GlobalPluginCall("onViewOpen", v)
GlobalPluginCall("onBufferOpen", v.Buf)
}
err = config.RunPluginFn("init")
if err != nil {
screen.TermMessage(err)
}
InitColorscheme()
messenger.style = defStyle
events = make(chan tcell.Event)
// Here is the event loop which runs in a separate thread
go func() {
for {
if screen != nil {
events <- screen.PollEvent()
screen.Lock()
e := screen.Screen.PollEvent()
screen.Unlock()
if e != nil {
events <- e
}
}
}()
go func() {
for {
time.Sleep(autosaveTime * time.Second)
if globalSettings["autosave"].(bool) {
autosave <- true
}
}
}()
// 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()
}
// wait for initial resize event
select {
case event := <-events:
action.Tabs.HandleEvent(event)
case <-time.After(10 * time.Millisecond):
// time out after 10ms
}
// Since this loop is very slow (waits for user input every time) it's
// okay to be inefficient and run it via a function every time
// We do this so we can recover from panics without crashing the editor
for {
// Display everything
RedrawAll()
var event tcell.Event
// Check for new events
select {
case f := <-jobs:
// If a new job has finished while running in the background we should execute the callback
f.function(f.output, f.args...)
continue
case <-autosave:
if CurView().Buf.Path != "" {
CurView().Save(true)
}
case <-updateterm:
continue
case vnum := <-closeterm:
tabs[curTab].Views[vnum].CloseTerminal()
case event = <-events:
}
for event != nil {
didAction := false
switch e := event.(type) {
case *tcell.EventResize:
for _, t := range tabs {
t.Resize()
}
case *tcell.EventMouse:
if !searching {
if e.Buttons() == tcell.Button1 {
// If the user left clicked we check a couple things
_, h := screen.Size()
x, y := e.Position()
if y == h-1 && messenger.message != "" && globalSettings["infobar"].(bool) {
// If the user clicked in the bottom bar, and there is a message down there
// we copy it to the clipboard.
// Often error messages are displayed down there so it can be useful to easily
// copy the message
clipboard.WriteAll(messenger.message, "primary")
break
}
if CurView().mouseReleased {
// We loop through each view in the current tab and make sure the current view
// is the one being clicked in
for _, v := range tabs[curTab].Views {
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
tabs[curTab].CurView = v.Num
}
}
}
} else if e.Buttons() == tcell.WheelUp || e.Buttons() == tcell.WheelDown {
var view *View
x, y := e.Position()
for _, v := range tabs[curTab].Views {
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
view = tabs[curTab].Views[v.Num]
}
}
if view != nil {
view.HandleEvent(e)
didAction = true
}
}
}
}
if !didAction {
// This function checks the mouse event for the possibility of changing the current tab
// If the tab was changed it returns true
if TabbarHandleMouseEvent(event) {
break
}
if searching {
// Since searching is done in real time, we need to redraw every time
// there is a new event in the search bar so we need a special function
// to run instead of the standard HandleEvent.
HandleSearchEvent(event, CurView())
} else {
// Send it to the view
CurView().HandleEvent(event)
}
}
select {
case event = <-events:
default:
event = nil
}
}
DoEvent()
}
}
// DoEvent runs the main action loop of the editor
func DoEvent() {
var event tcell.Event
// recover from errors without crashing the editor
defer func() {
if err := recover(); err != nil {
if e, ok := err.(*lua.ApiError); ok {
screen.TermMessage("Lua API error:", e)
} else {
screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
}
}
}()
// Display everything
screen.Screen.Fill(' ', config.DefStyle)
screen.Screen.HideCursor()
action.Tabs.Display()
for _, ep := range action.MainTab().Panes {
ep.Display()
}
action.MainTab().Display()
action.InfoBar.Display()
screen.Screen.Show()
// 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)
case <-config.Autosave:
for _, b := range buffer.OpenBuffers {
b.Save()
}
case <-shell.CloseTerms:
case event = <-events:
case <-screen.DrawChan():
}
if action.InfoBar.HasPrompt {
action.InfoBar.HandleEvent(event)
} else {
action.Tabs.HandleEvent(event)
}
}

View File

@@ -1,184 +0,0 @@
package main
import (
"errors"
"io/ioutil"
"os"
"strings"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/tcell"
"layeh.com/gopher-luar"
)
var loadedPlugins map[string]string
// Call calls the lua function 'function'
// If it does not exist nothing happens, if there is an error,
// the error is returned
func Call(function string, args ...interface{}) (lua.LValue, error) {
var luaFunc lua.LValue
if strings.Contains(function, ".") {
plugin := L.GetGlobal(strings.Split(function, ".")[0])
if plugin.String() == "nil" {
return nil, errors.New("function does not exist: " + function)
}
luaFunc = L.GetField(plugin, strings.Split(function, ".")[1])
} else {
luaFunc = L.GetGlobal(function)
}
if luaFunc.String() == "nil" {
return nil, errors.New("function does not exist: " + function)
}
var luaArgs []lua.LValue
for _, v := range args {
luaArgs = append(luaArgs, luar.New(L, v))
}
err := L.CallByParam(lua.P{
Fn: luaFunc,
NRet: 1,
Protect: true,
}, luaArgs...)
ret := L.Get(-1) // returned value
if ret.String() != "nil" {
L.Pop(1) // remove received value
}
return ret, err
}
// LuaFunctionBinding is a function generator which takes the name of a lua function
// and creates a function that will call that lua function
// Specifically it creates a function that can be called as a binding because this is used
// to bind keys to lua functions
func LuaFunctionBinding(function string) func(*View, bool) bool {
return func(v *View, _ bool) bool {
_, err := Call(function, nil)
if err != nil {
TermMessage(err)
}
return false
}
}
// LuaFunctionMouseBinding is a function generator which takes the name of a lua function
// and creates a function that will call that lua function
// Specifically it creates a function that can be called as a mouse binding because this is used
// to bind mouse actions to lua functions
func LuaFunctionMouseBinding(function string) func(*View, bool, *tcell.EventMouse) bool {
return func(v *View, _ bool, e *tcell.EventMouse) bool {
_, err := Call(function, e)
if err != nil {
TermMessage(err)
}
return false
}
}
func unpack(old []string) []interface{} {
new := make([]interface{}, len(old))
for i, v := range old {
new[i] = v
}
return new
}
// LuaFunctionCommand is the same as LuaFunctionBinding except it returns a normal function
// so that a command can be bound to a lua function
func LuaFunctionCommand(function string) func([]string) {
return func(args []string) {
_, err := Call(function, unpack(args)...)
if err != nil {
TermMessage(err)
}
}
}
// LuaFunctionComplete returns a function which can be used for autocomplete in plugins
func LuaFunctionComplete(function string) func(string) []string {
return func(input string) (result []string) {
res, err := Call(function, input)
if err != nil {
TermMessage(err)
}
if tbl, ok := res.(*lua.LTable); !ok {
TermMessage(function, "should return a table of strings")
} else {
for i := 1; i <= tbl.Len(); i++ {
val := tbl.RawGetInt(i)
if v, ok := val.(lua.LString); !ok {
TermMessage(function, "should return a table of strings")
} else {
result = append(result, string(v))
}
}
}
return result
}
}
// LuaFunctionJob returns a function that will call the given lua function
// structured as a job call i.e. the job output and arguments are provided
// to the lua function
func LuaFunctionJob(function string) func(string, ...string) {
return func(output string, args ...string) {
_, err := Call(function, unpack(append([]string{output}, args...))...)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
}
}
}
// luaPluginName convert a human-friendly plugin name into a valid lua variable name.
func luaPluginName(name string) string {
return strings.Replace(name, "-", "_", -1)
}
// LoadPlugins loads the pre-installed plugins and the plugins located in ~/.config/micro/plugins
func LoadPlugins() {
loadedPlugins = make(map[string]string)
for _, plugin := range ListRuntimeFiles(RTPlugin) {
pluginName := plugin.Name()
if _, ok := loadedPlugins[pluginName]; ok {
continue
}
data, err := plugin.Data()
if err != nil {
TermMessage("Error loading plugin: " + pluginName)
continue
}
pluginLuaName := luaPluginName(pluginName)
if err := LoadFile(pluginLuaName, pluginLuaName, string(data)); err != nil {
TermMessage(err)
continue
}
loadedPlugins[pluginName] = pluginLuaName
}
if _, err := os.Stat(configDir + "/init.lua"); err == nil {
data, _ := ioutil.ReadFile(configDir + "/init.lua")
if err := LoadFile("init", configDir+"init.lua", string(data)); err != nil {
TermMessage(err)
}
loadedPlugins["init"] = "init"
}
}
// GlobalCall makes a call to a function in every plugin that is currently
// loaded
func GlobalPluginCall(function string, args ...interface{}) {
for pl := range loadedPlugins {
_, err := Call(pl+"."+function, args...)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
continue
}
}
}

View File

@@ -1,207 +0,0 @@
package main
import (
"io/ioutil"
"os"
"path"
"path/filepath"
)
const (
RTColorscheme = "colorscheme"
RTSyntax = "syntax"
RTHelp = "help"
RTPlugin = "plugin"
)
// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
type RuntimeFile interface {
// Name returns a name of the file without paths or extensions
Name() string
// Data returns the content of the file.
Data() ([]byte, error)
}
// allFiles contains all available files, mapped by filetype
var allFiles map[string][]RuntimeFile
// some file on filesystem
type realFile string
// some asset file
type assetFile string
// some file on filesystem but with a different name
type namedFile struct {
realFile
name string
}
// a file with the data stored in memory
type memoryFile struct {
name string
data []byte
}
func (mf memoryFile) Name() string {
return mf.name
}
func (mf memoryFile) Data() ([]byte, error) {
return mf.data, nil
}
func (rf realFile) Name() string {
fn := filepath.Base(string(rf))
return fn[:len(fn)-len(filepath.Ext(fn))]
}
func (rf realFile) Data() ([]byte, error) {
return ioutil.ReadFile(string(rf))
}
func (af assetFile) Name() string {
fn := path.Base(string(af))
return fn[:len(fn)-len(path.Ext(fn))]
}
func (af assetFile) Data() ([]byte, error) {
return Asset(string(af))
}
func (nf namedFile) Name() string {
return nf.name
}
// AddRuntimeFile registers a file for the given filetype
func AddRuntimeFile(fileType string, file RuntimeFile) {
if allFiles == nil {
allFiles = make(map[string][]RuntimeFile)
}
allFiles[fileType] = append(allFiles[fileType], file)
}
// AddRuntimeFilesFromDirectory registers each file from the given directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromDirectory(fileType, directory, pattern string) {
files, _ := ioutil.ReadDir(directory)
for _, f := range files {
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
fullPath := filepath.Join(directory, f.Name())
AddRuntimeFile(fileType, realFile(fullPath))
}
}
}
// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromAssets(fileType, directory, pattern string) {
files, err := AssetDir(directory)
if err != nil {
return
}
for _, f := range files {
if ok, _ := path.Match(pattern, f); ok {
AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
}
}
}
// FindRuntimeFile finds a runtime file of the given filetype and name
// will return nil if no file was found
func FindRuntimeFile(fileType, name string) RuntimeFile {
for _, f := range ListRuntimeFiles(fileType) {
if f.Name() == name {
return f
}
}
return nil
}
// ListRuntimeFiles lists all known runtime files for the given filetype
func ListRuntimeFiles(fileType string) []RuntimeFile {
if files, ok := allFiles[fileType]; ok {
return files
}
return []RuntimeFile{}
}
// InitRuntimeFiles initializes all assets file and the config directory
func InitRuntimeFiles() {
add := func(fileType, dir, pattern string) {
AddRuntimeFilesFromDirectory(fileType, filepath.Join(configDir, dir), pattern)
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
}
add(RTColorscheme, "colorschemes", "*.micro")
add(RTSyntax, "syntax", "*.yaml")
add(RTHelp, "help", "*.md")
// Search configDir for plugin-scripts
files, _ := ioutil.ReadDir(filepath.Join(configDir, "plugins"))
for _, f := range files {
realpath, _ := filepath.EvalSymlinks(filepath.Join(configDir, "plugins", f.Name()))
realpathStat, _ := os.Stat(realpath)
if realpathStat.IsDir() {
scriptPath := filepath.Join(configDir, "plugins", f.Name(), f.Name()+".lua")
if _, err := os.Stat(scriptPath); err == nil {
AddRuntimeFile(RTPlugin, realFile(scriptPath))
}
}
}
if files, err := AssetDir("runtime/plugins"); err == nil {
for _, f := range files {
scriptPath := path.Join("runtime/plugins", f, f+".lua")
if _, err := AssetInfo(scriptPath); err == nil {
AddRuntimeFile(RTPlugin, assetFile(scriptPath))
}
}
}
}
// PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file
func PluginReadRuntimeFile(fileType, name string) string {
if file := FindRuntimeFile(fileType, name); file != nil {
if data, err := file.Data(); err == nil {
return string(data)
}
}
return ""
}
// PluginListRuntimeFiles allows plugins to lists all runtime files of the given type
func PluginListRuntimeFiles(fileType string) []string {
files := ListRuntimeFiles(fileType)
result := make([]string, len(files))
for i, f := range files {
result[i] = f.Name()
}
return result
}
// PluginAddRuntimeFile adds a file to the runtime files for a plugin
func PluginAddRuntimeFile(plugin, filetype, filePath string) {
fullpath := filepath.Join(configDir, "plugins", plugin, filePath)
if _, err := os.Stat(fullpath); err == nil {
AddRuntimeFile(filetype, realFile(fullpath))
} else {
fullpath = path.Join("runtime", "plugins", plugin, filePath)
AddRuntimeFile(filetype, assetFile(fullpath))
}
}
// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
func PluginAddRuntimeFilesFromDirectory(plugin, filetype, directory, pattern string) {
fullpath := filepath.Join(configDir, "plugins", plugin, directory)
if _, err := os.Stat(fullpath); err == nil {
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
} else {
fullpath = path.Join("runtime", "plugins", plugin, directory)
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
}
}
// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
func PluginAddRuntimeFileFromMemory(plugin, filetype, filename, data string) {
AddRuntimeFile(filetype, memoryFile{filename, []byte(data)})
}

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,190 +0,0 @@
package main
import (
"regexp"
"strings"
"github.com/zyedidia/tcell"
)
var (
// What was the last search
lastSearch string
// Where should we start the search down from (or up from)
searchStart Loc
// Is there currently a search in progress
searching bool
// Stores the history for searching
searchHistory []string
)
// BeginSearch starts a search
func BeginSearch(searchStr string) {
searchHistory = append(searchHistory, "")
messenger.historyNum = len(searchHistory) - 1
searching = true
messenger.response = searchStr
messenger.cursorx = Count(searchStr)
messenger.Message("Find: ")
messenger.hasPrompt = true
}
// EndSearch stops the current search
func EndSearch() {
searchHistory[len(searchHistory)-1] = messenger.response
searching = false
messenger.hasPrompt = false
messenger.Clear()
messenger.Reset()
if lastSearch != "" {
messenger.Message("^P Previous ^N Next")
}
}
// ExitSearch exits the search mode, reset active search phrase, and clear status bar
func ExitSearch(v *View) {
lastSearch = ""
searching = false
messenger.hasPrompt = false
messenger.Clear()
messenger.Reset()
v.Cursor.ResetSelection()
}
// HandleSearchEvent takes an event and a view and will do a real time match from the messenger's output
// to the current buffer. It searches down the buffer.
func HandleSearchEvent(event tcell.Event, v *View) {
switch e := event.(type) {
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyEscape:
// Exit the search mode
ExitSearch(v)
return
case tcell.KeyEnter:
// If the user has pressed Enter, they want this to be the lastSearch
lastSearch = messenger.response
EndSearch()
return
case tcell.KeyCtrlQ, tcell.KeyCtrlC:
// Done
EndSearch()
return
}
}
messenger.HandleEvent(event, searchHistory)
if messenger.cursorx < 0 {
// Done
EndSearch()
return
}
if messenger.response == "" {
v.Cursor.ResetSelection()
// We don't end the search though
return
}
Search(messenger.response, v, true)
v.Relocate()
return
}
func searchDown(r *regexp.Regexp, v *View, start, end Loc) bool {
for i := start.Y; i <= end.Y; i++ {
var l []byte
var charPos int
if i == start.Y {
runes := []rune(string(v.Buf.lines[i].data))
l = []byte(string(runes[start.X:]))
charPos = start.X
if strings.Contains(r.String(), "^") && start.X != 0 {
continue
}
} else {
l = v.Buf.lines[i].data
}
match := r.FindIndex(l)
if match != nil {
v.Cursor.SetSelectionStart(Loc{charPos + runePos(match[0], string(l)), i})
v.Cursor.SetSelectionEnd(Loc{charPos + runePos(match[1], string(l)), i})
v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
v.Cursor.Loc = v.Cursor.CurSelection[1]
return true
}
}
return false
}
func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
for i := start.Y; i >= end.Y; i-- {
var l []byte
if i == start.Y {
runes := []rune(string(v.Buf.lines[i].data))
l = []byte(string(runes[:start.X]))
if strings.Contains(r.String(), "$") && start.X != Count(string(l)) {
continue
}
} else {
l = v.Buf.lines[i].data
}
match := r.FindIndex(l)
if match != nil {
v.Cursor.SetSelectionStart(Loc{runePos(match[0], string(l)), i})
v.Cursor.SetSelectionEnd(Loc{runePos(match[1], string(l)), i})
v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
v.Cursor.Loc = v.Cursor.CurSelection[1]
return true
}
}
return false
}
// Search searches in the view for the given regex. The down bool
// specifies whether it should search down from the searchStart position
// or up from there
func Search(searchStr string, v *View, down bool) {
if searchStr == "" {
return
}
r, err := regexp.Compile(searchStr)
if v.Buf.Settings["ignorecase"].(bool) {
r, err = regexp.Compile("(?i)" + searchStr)
}
if err != nil {
return
}
var found bool
if down {
found = searchDown(r, v, searchStart, v.Buf.End())
if !found {
found = searchDown(r, v, v.Buf.Start(), searchStart)
}
} else {
found = searchUp(r, v, searchStart, v.Buf.Start())
if !found {
found = searchUp(r, v, v.Buf.End(), searchStart)
}
}
if !found {
v.Cursor.ResetSelection()
}
}

View File

@@ -1,523 +0,0 @@
package main
import (
"crypto/md5"
"encoding/json"
"errors"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
"github.com/flynn/json5"
"github.com/zyedidia/glob"
)
type optionValidator func(string, interface{}) error
// The options that the user can set
var globalSettings map[string]interface{}
var invalidSettings bool
// Options with validators
var optionValidators = map[string]optionValidator{
"tabsize": validatePositiveValue,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
"colorscheme": validateColorscheme,
"colorcolumn": validateNonNegativeValue,
"fileformat": validateLineEnding,
}
// InitGlobalSettings initializes the options map and sets all options to their default values
func InitGlobalSettings() {
invalidSettings = false
defaults := DefaultGlobalSettings()
var parsed map[string]interface{}
filename := configDir + "/settings.json"
writeSettings := false
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if !strings.HasPrefix(string(input), "null") {
if err != nil {
TermMessage("Error reading settings.json file: " + err.Error())
invalidSettings = true
return
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
invalidSettings = true
}
} else {
writeSettings = true
}
}
globalSettings = make(map[string]interface{})
for k, v := range defaults {
globalSettings[k] = v
}
for k, v := range parsed {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
globalSettings[k] = v
}
}
if _, err := os.Stat(filename); os.IsNotExist(err) || writeSettings {
err := WriteSettings(filename)
if err != nil {
TermMessage("Error writing settings.json file: " + err.Error())
}
}
}
// InitLocalSettings scans the json in settings.json and sets the options locally based
// on whether the buffer matches the glob
func InitLocalSettings(buf *Buffer) {
invalidSettings = false
var parsed map[string]interface{}
filename := configDir + "/settings.json"
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
TermMessage("Error reading settings.json file: " + err.Error())
invalidSettings = true
return
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
invalidSettings = true
}
}
for k, v := range parsed {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if strings.HasPrefix(k, "ft:") {
if buf.Settings["filetype"].(string) == k[3:] {
for k1, v1 := range v.(map[string]interface{}) {
buf.Settings[k1] = v1
}
}
} else {
g, err := glob.Compile(k)
if err != nil {
TermMessage("Error with glob setting ", k, ": ", err)
continue
}
if g.MatchString(buf.Path) {
for k1, v1 := range v.(map[string]interface{}) {
buf.Settings[k1] = v1
}
}
}
}
}
}
// WriteSettings writes the settings to the specified filename as JSON
func WriteSettings(filename string) error {
if invalidSettings {
// Do not write the settings if there was an error when reading them
return nil
}
var err error
if _, e := os.Stat(configDir); e == nil {
parsed := make(map[string]interface{})
filename := configDir + "/settings.json"
for k, v := range globalSettings {
parsed[k] = v
}
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if string(input) != "null" {
if err != nil {
return err
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
invalidSettings = true
}
for k, v := range parsed {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if _, ok := globalSettings[k]; ok {
parsed[k] = globalSettings[k]
}
}
}
}
}
txt, _ := json.MarshalIndent(parsed, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return err
}
// AddOption creates a new option. This is meant to be called by plugins to add options.
func AddOption(name string, value interface{}) {
globalSettings[name] = value
err := WriteSettings(configDir + "/settings.json")
if err != nil {
TermMessage("Error writing settings.json file: " + err.Error())
}
}
// GetGlobalOption returns the global value of the given option
func GetGlobalOption(name string) interface{} {
return globalSettings[name]
}
// GetLocalOption returns the local value of the given option
func GetLocalOption(name string, buf *Buffer) interface{} {
return buf.Settings[name]
}
// GetOption returns the value of the given option
// If there is a local version of the option, it returns that
// otherwise it will return the global version
func GetOption(name string) interface{} {
if GetLocalOption(name, CurView().Buf) != nil {
return GetLocalOption(name, CurView().Buf)
}
return GetGlobalOption(name)
}
// DefaultGlobalSettings returns the default global settings for micro
// Note that colorscheme is a global only option
func DefaultGlobalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"autosave": false,
"basename": false,
"colorcolumn": float64(0),
"colorscheme": "default",
"cursorline": true,
"eofnewline": false,
"fastdirty": true,
"fileformat": "unix",
"hidehelp": false,
"ignorecase": false,
"indentchar": " ",
"infobar": true,
"keepautoindent": false,
"keymenu": false,
"matchbrace": false,
"matchbraceleft": false,
"mouse": true,
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
"pluginrepos": []string{},
"rmtrailingws": false,
"ruler": true,
"savecursor": false,
"savehistory": true,
"saveundo": false,
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"softwrap": false,
"splitbottom": true,
"splitright": true,
"statusline": true,
"sucmd": "sudo",
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"termtitle": false,
"useprimary": true,
}
}
// DefaultLocalSettings returns the default local settings
// Note that filetype is a local only option
func DefaultLocalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"autosave": false,
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"eofnewline": false,
"fastdirty": true,
"fileformat": "unix",
"filetype": "Unknown",
"hidehelp": false,
"ignorecase": false,
"indentchar": " ",
"keepautoindent": false,
"matchbrace": false,
"matchbraceleft": false,
"rmtrailingws": false,
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"softwrap": false,
"splitbottom": true,
"splitright": true,
"statusline": true,
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"useprimary": true,
}
}
// SetOption attempts to set the given option to the value
// By default it will set the option as global, but if the option
// is local only it will set the local version
// Use setlocal to force an option to be set locally
func SetOption(option, value string) error {
if _, ok := globalSettings[option]; !ok {
if _, ok := CurView().Buf.Settings[option]; !ok {
return errors.New("Invalid option")
}
SetLocalOption(option, value, CurView())
return nil
}
var nativeValue interface{}
kind := reflect.TypeOf(globalSettings[option]).Kind()
if kind == reflect.Bool {
b, err := ParseBool(value)
if err != nil {
return errors.New("Invalid value")
}
nativeValue = b
} else if kind == reflect.String {
nativeValue = value
} else if kind == reflect.Float64 {
i, err := strconv.Atoi(value)
if err != nil {
return errors.New("Invalid value")
}
nativeValue = float64(i)
} else {
return errors.New("Option has unsupported value type")
}
if err := optionIsValid(option, nativeValue); err != nil {
return err
}
globalSettings[option] = nativeValue
if option == "colorscheme" {
// LoadSyntaxFiles()
InitColorscheme()
for _, tab := range tabs {
for _, view := range tab.Views {
view.Buf.UpdateRules()
}
}
}
if option == "infobar" || option == "keymenu" {
for _, tab := range tabs {
tab.Resize()
}
}
if option == "mouse" {
if !nativeValue.(bool) {
screen.DisableMouse()
} else {
screen.EnableMouse()
}
}
if len(tabs) != 0 {
if _, ok := CurView().Buf.Settings[option]; ok {
for _, tab := range tabs {
for _, view := range tab.Views {
SetLocalOption(option, value, view)
}
}
}
}
return nil
}
// SetLocalOption sets the local version of this option
func SetLocalOption(option, value string, view *View) error {
buf := view.Buf
if _, ok := buf.Settings[option]; !ok {
return errors.New("Invalid option")
}
var nativeValue interface{}
kind := reflect.TypeOf(buf.Settings[option]).Kind()
if kind == reflect.Bool {
b, err := ParseBool(value)
if err != nil {
return errors.New("Invalid value")
}
nativeValue = b
} else if kind == reflect.String {
nativeValue = value
} else if kind == reflect.Float64 {
i, err := strconv.Atoi(value)
if err != nil {
return errors.New("Invalid value")
}
nativeValue = float64(i)
} else {
return errors.New("Option has unsupported value type")
}
if err := optionIsValid(option, nativeValue); err != nil {
return err
}
if option == "fastdirty" {
// If it is being turned off, we have to hash every open buffer
var empty [16]byte
for _, tab := range tabs {
for _, v := range tab.Views {
if !nativeValue.(bool) {
if v.Buf.origHash == empty {
data, err := ioutil.ReadFile(v.Buf.AbsPath)
if err != nil {
data = []byte{}
}
v.Buf.origHash = md5.Sum(data)
}
} else {
v.Buf.IsModified = v.Buf.Modified()
}
}
}
}
buf.Settings[option] = nativeValue
if option == "statusline" {
view.ToggleStatusLine()
}
if option == "filetype" {
// LoadSyntaxFiles()
InitColorscheme()
buf.UpdateRules()
}
if option == "fileformat" {
buf.IsModified = true
}
if option == "syntax" {
if !nativeValue.(bool) {
buf.ClearMatches()
} else {
if buf.highlighter != nil {
buf.highlighter.HighlightStates(buf)
}
}
}
return nil
}
// SetOptionAndSettings sets the given option and saves the option setting to the settings config file
func SetOptionAndSettings(option, value string) {
filename := configDir + "/settings.json"
err := SetOption(option, value)
if err != nil {
messenger.Error(err.Error())
return
}
err = WriteSettings(filename)
if err != nil {
messenger.Error("Error writing to settings.json: " + err.Error())
return
}
}
func optionIsValid(option string, value interface{}) error {
if validator, ok := optionValidators[option]; ok {
return validator(option, value)
}
return nil
}
// Option validators
func validatePositiveValue(option string, value interface{}) error {
tabsize, ok := value.(float64)
if !ok {
return errors.New("Expected numeric type for " + option)
}
if tabsize < 1 {
return errors.New(option + " must be greater than 0")
}
return nil
}
func validateNonNegativeValue(option string, value interface{}) error {
nativeValue, ok := value.(float64)
if !ok {
return errors.New("Expected numeric type for " + option)
}
if nativeValue < 0 {
return errors.New(option + " must be non-negative")
}
return nil
}
func validateColorscheme(option string, value interface{}) error {
colorscheme, ok := value.(string)
if !ok {
return errors.New("Expected string type for colorscheme")
}
if !ColorschemeExists(colorscheme) {
return errors.New(colorscheme + " is not a valid colorscheme")
}
return nil
}
func validateLineEnding(option string, value interface{}) error {
endingType, ok := value.(string)
if !ok {
return errors.New("Expected string type for file format")
}
if endingType != "unix" && endingType != "dos" {
return errors.New("File format must be either 'unix' or 'dos'")
}
return nil
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,317 +0,0 @@
package main
// SplitType specifies whether a split is horizontal or vertical
type SplitType bool
const (
// VerticalSplit type
VerticalSplit = false
// HorizontalSplit type
HorizontalSplit = true
)
// A Node on the split tree
type Node interface {
VSplit(buf *Buffer, splitIndex int)
HSplit(buf *Buffer, splitIndex int)
String() string
}
// A LeafNode is an actual split so it contains a view
type LeafNode struct {
view *View
parent *SplitTree
}
// NewLeafNode returns a new leaf node containing the given view
func NewLeafNode(v *View, parent *SplitTree) *LeafNode {
n := new(LeafNode)
n.view = v
n.view.splitNode = n
n.parent = parent
return n
}
// A SplitTree is a Node itself and it contains other nodes
type SplitTree struct {
kind SplitType
parent *SplitTree
children []Node
x int
y int
width int
height int
lockWidth bool
lockHeight bool
tabNum int
}
// VSplit creates a vertical split
func (l *LeafNode) VSplit(buf *Buffer, splitIndex int) {
if splitIndex < 0 {
splitIndex = 0
}
tab := tabs[l.parent.tabNum]
if l.parent.kind == VerticalSplit {
if splitIndex > len(l.parent.children) {
splitIndex = len(l.parent.children)
}
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
l.parent.children = append(l.parent.children, nil)
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
tab.Views = append(tab.Views, nil)
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
tab.Views[splitIndex] = newView
tab.CurView = splitIndex
} else {
if splitIndex > 1 {
splitIndex = 1
}
s := new(SplitTree)
s.kind = VerticalSplit
s.parent = l.parent
s.tabNum = l.parent.tabNum
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
if splitIndex == 1 {
s.children = []Node{l, NewLeafNode(newView, s)}
} else {
s.children = []Node{NewLeafNode(newView, s), l}
}
l.parent.children[search(l.parent.children, l)] = s
l.parent = s
tab.Views = append(tab.Views, nil)
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
tab.Views[splitIndex] = newView
tab.CurView = splitIndex
}
tab.Resize()
}
// HSplit creates a horizontal split
func (l *LeafNode) HSplit(buf *Buffer, splitIndex int) {
if splitIndex < 0 {
splitIndex = 0
}
tab := tabs[l.parent.tabNum]
if l.parent.kind == HorizontalSplit {
if splitIndex > len(l.parent.children) {
splitIndex = len(l.parent.children)
}
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
l.parent.children = append(l.parent.children, nil)
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
tab.Views = append(tab.Views, nil)
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
tab.Views[splitIndex] = newView
tab.CurView = splitIndex
} else {
if splitIndex > 1 {
splitIndex = 1
}
s := new(SplitTree)
s.kind = HorizontalSplit
s.tabNum = l.parent.tabNum
s.parent = l.parent
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
newView.Num = len(tab.Views)
if splitIndex == 1 {
s.children = []Node{l, NewLeafNode(newView, s)}
} else {
s.children = []Node{NewLeafNode(newView, s), l}
}
l.parent.children[search(l.parent.children, l)] = s
l.parent = s
tab.Views = append(tab.Views, nil)
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
tab.Views[splitIndex] = newView
tab.CurView = splitIndex
}
tab.Resize()
}
// Delete deletes a split
func (l *LeafNode) Delete() {
i := search(l.parent.children, l)
copy(l.parent.children[i:], l.parent.children[i+1:])
l.parent.children[len(l.parent.children)-1] = nil
l.parent.children = l.parent.children[:len(l.parent.children)-1]
tab := tabs[l.parent.tabNum]
j := findView(tab.Views, l.view)
copy(tab.Views[j:], tab.Views[j+1:])
tab.Views[len(tab.Views)-1] = nil // or the zero value of T
tab.Views = tab.Views[:len(tab.Views)-1]
for i, v := range tab.Views {
v.Num = i
}
if tab.CurView > 0 {
tab.CurView--
}
}
// Cleanup rearranges all the parents after a split has been deleted
func (s *SplitTree) Cleanup() {
for i, node := range s.children {
if n, ok := node.(*SplitTree); ok {
if len(n.children) == 1 {
if child, ok := n.children[0].(*LeafNode); ok {
s.children[i] = child
child.parent = s
continue
}
}
n.Cleanup()
}
}
}
// ResizeSplits resizes all the splits correctly
func (s *SplitTree) ResizeSplits() {
lockedWidth := 0
lockedHeight := 0
lockedChildren := 0
for _, node := range s.children {
if n, ok := node.(*LeafNode); ok {
if s.kind == VerticalSplit {
if n.view.LockWidth {
lockedWidth += n.view.Width
lockedChildren++
}
} else {
if n.view.LockHeight {
lockedHeight += n.view.Height
lockedChildren++
}
}
} else if n, ok := node.(*SplitTree); ok {
if s.kind == VerticalSplit {
if n.lockWidth {
lockedWidth += n.width
lockedChildren++
}
} else {
if n.lockHeight {
lockedHeight += n.height
lockedChildren++
}
}
}
}
x, y := 0, 0
for _, node := range s.children {
if n, ok := node.(*LeafNode); ok {
if s.kind == VerticalSplit {
if !n.view.LockWidth {
n.view.Width = (s.width - lockedWidth) / (len(s.children) - lockedChildren)
}
n.view.Height = s.height
n.view.x = s.x + x
n.view.y = s.y
x += n.view.Width
} else {
if !n.view.LockHeight {
n.view.Height = (s.height - lockedHeight) / (len(s.children) - lockedChildren)
}
n.view.Width = s.width
n.view.y = s.y + y
n.view.x = s.x
y += n.view.Height
}
if n.view.Buf.Settings["statusline"].(bool) {
n.view.Height--
}
n.view.ToggleTabbar()
} else if n, ok := node.(*SplitTree); ok {
if s.kind == VerticalSplit {
if !n.lockWidth {
n.width = (s.width - lockedWidth) / (len(s.children) - lockedChildren)
}
n.height = s.height
n.x = s.x + x
n.y = s.y
x += n.width
} else {
if !n.lockHeight {
n.height = (s.height - lockedHeight) / (len(s.children) - lockedChildren)
}
n.width = s.width
n.y = s.y + y
n.x = s.x
y += n.height
}
n.ResizeSplits()
}
}
}
func (l *LeafNode) String() string {
return l.view.Buf.GetName()
}
func search(haystack []Node, needle Node) int {
for i, x := range haystack {
if x == needle {
return i
}
}
return 0
}
func findView(haystack []*View, needle *View) int {
for i, x := range haystack {
if x == needle {
return i
}
}
return 0
}
// VSplit is here just to make SplitTree fit the Node interface
func (s *SplitTree) VSplit(buf *Buffer, splitIndex int) {}
// HSplit is here just to make SplitTree fit the Node interface
func (s *SplitTree) HSplit(buf *Buffer, splitIndex int) {}
func (s *SplitTree) String() string {
str := "["
for _, child := range s.children {
str += child.String() + ", "
}
return str + "]"
}

View File

@@ -1,98 +0,0 @@
package main
import (
"path"
"strconv"
)
// Statusline represents the information line at the bottom
// of each view
// It gives information such as filename, whether the file has been
// modified, filetype, cursor location
type Statusline struct {
view *View
}
// Display draws the statusline to the screen
func (sline *Statusline) Display() {
if messenger.hasPrompt && !GetGlobalOption("infobar").(bool) {
return
}
// We'll draw the line at the lowest line in the view
y := sline.view.Height + sline.view.y
file := sline.view.Buf.GetName()
if sline.view.Buf.Settings["basename"].(bool) {
file = path.Base(file)
}
// If the buffer is dirty (has been modified) write a little '+'
if sline.view.Buf.Modified() {
file += " +"
}
// Add one to cursor.x and cursor.y because (0,0) is the top left,
// but users will be used to (1,1) (first line,first column)
// We use GetVisualX() here because otherwise we get the column number in runes
// so a '\t' is only 1, when it should be tabSize
columnNum := strconv.Itoa(sline.view.Cursor.GetVisualX() + 1)
lineNum := strconv.Itoa(sline.view.Cursor.Y + 1)
file += " (" + lineNum + "," + columnNum + ")"
// Add the filetype
file += " " + sline.view.Buf.FileType()
file += " " + sline.view.Buf.Settings["fileformat"].(string)
rightText := ""
if !sline.view.Buf.Settings["hidehelp"].(bool) {
if len(kmenuBinding) > 0 {
if globalSettings["keymenu"].(bool) {
rightText += kmenuBinding + ": hide bindings"
} else {
rightText += kmenuBinding + ": show bindings"
}
}
if len(helpBinding) > 0 {
if len(kmenuBinding) > 0 {
rightText += ", "
}
if sline.view.Type == vtHelp {
rightText += helpBinding + ": close help"
} else {
rightText += helpBinding + ": open help"
}
}
rightText += " "
}
statusLineStyle := defStyle.Reverse(true)
if style, ok := colorscheme["statusline"]; ok {
statusLineStyle = style
}
// Maybe there is a unicode filename?
fileRunes := []rune(file)
if sline.view.Type == vtTerm {
fileRunes = []rune(sline.view.term.title)
rightText = ""
}
viewX := sline.view.x
if viewX != 0 {
screen.SetContent(viewX, y, ' ', nil, statusLineStyle)
viewX++
}
for x := 0; x < sline.view.Width; x++ {
if x < len(fileRunes) {
screen.SetContent(viewX+x, y, fileRunes[x], nil, statusLineStyle)
} else if x >= sline.view.Width-len(rightText) && x < len(rightText)+sline.view.Width-len(rightText) {
screen.SetContent(viewX+x, y, []rune(rightText)[x-sline.view.Width+len(rightText)], nil, statusLineStyle)
} else {
screen.SetContent(viewX+x, y, ' ', nil, statusLineStyle)
}
}
}

View File

@@ -1,279 +0,0 @@
package main
import (
"sort"
"github.com/zyedidia/tcell"
)
var tabBarOffset int
// A Tab holds an array of views and a splitTree to determine how the
// views should be arranged
type Tab struct {
// This contains all the views in this tab
// There is generally only one view per tab, but you can have
// multiple views with splits
Views []*View
// This is the current view for this tab
CurView int
tree *SplitTree
}
// NewTabFromView creates a new tab and puts the given view in the tab
func NewTabFromView(v *View) *Tab {
t := new(Tab)
t.Views = append(t.Views, v)
t.Views[0].Num = 0
t.tree = new(SplitTree)
t.tree.kind = VerticalSplit
t.tree.children = []Node{NewLeafNode(t.Views[0], t.tree)}
w, h := screen.Size()
t.tree.width = w
t.tree.height = h
if globalSettings["infobar"].(bool) {
t.tree.height--
}
if globalSettings["keymenu"].(bool) {
t.tree.height -= 2
}
t.Resize()
return t
}
// SetNum sets all this tab's views to have the correct tab number
func (t *Tab) SetNum(num int) {
t.tree.tabNum = num
for _, v := range t.Views {
v.TabNum = num
}
}
// Cleanup cleans up the tree (for example if views have closed)
func (t *Tab) Cleanup() {
t.tree.Cleanup()
}
// Resize handles a resize event from the terminal and resizes
// all child views correctly
func (t *Tab) Resize() {
w, h := screen.Size()
t.tree.width = w
t.tree.height = h
if globalSettings["infobar"].(bool) {
t.tree.height--
}
if globalSettings["keymenu"].(bool) {
t.tree.height -= 2
}
t.tree.ResizeSplits()
for i, v := range t.Views {
v.Num = i
if v.Type == vtTerm {
v.term.Resize(v.Width, v.Height)
}
}
}
// CurView returns the current view
func CurView() *View {
curTab := tabs[curTab]
return curTab.Views[curTab.CurView]
}
// TabbarString returns the string that should be displayed in the tabbar
// It also returns a map containing which indicies correspond to which tab number
// This is useful when we know that the mouse click has occurred at an x location
// but need to know which tab that corresponds to to accurately change the tab
func TabbarString() (string, map[int]int) {
str := ""
indicies := make(map[int]int)
for i, t := range tabs {
if i == curTab {
str += "["
} else {
str += " "
}
buf := t.Views[t.CurView].Buf
str += buf.GetName()
if buf.Modified() {
str += " +"
}
if i == curTab {
str += "]"
} else {
str += " "
}
indicies[Count(str)-1] = i + 1
str += " "
}
return str, indicies
}
// TabbarHandleMouseEvent checks the given mouse event if it is clicking on the tabbar
// If it is it changes the current tab accordingly
// This function returns true if the tab is changed
func TabbarHandleMouseEvent(event tcell.Event) bool {
// There is no tabbar displayed if there are less than 2 tabs
if len(tabs) <= 1 {
return false
}
switch e := event.(type) {
case *tcell.EventMouse:
button := e.Buttons()
// Must be a left click
if button == tcell.Button1 {
x, y := e.Position()
if y != 0 {
return false
}
str, indicies := TabbarString()
if x+tabBarOffset >= len(str) {
return false
}
var tabnum int
var keys []int
for k := range indicies {
keys = append(keys, k)
}
sort.Ints(keys)
for _, k := range keys {
if x+tabBarOffset <= k {
tabnum = indicies[k] - 1
break
}
}
curTab = tabnum
return true
}
}
return false
}
// DisplayTabs displays the tabbar at the top of the editor if there are multiple tabs
func DisplayTabs() {
if len(tabs) <= 1 {
return
}
str, indicies := TabbarString()
tabBarStyle := defStyle.Reverse(true)
if style, ok := colorscheme["tabbar"]; ok {
tabBarStyle = style
}
// Maybe there is a unicode filename?
fileRunes := []rune(str)
w, _ := screen.Size()
tooWide := (w < len(fileRunes))
// if the entire tab-bar is longer than the screen is wide,
// then it should be truncated appropriately to keep the
// active tab visible on the UI.
if tooWide == true {
// first we have to work out where the selected tab is
// out of the total length of the tab bar. this is done
// by extracting the hit-areas from the indicies map
// that was constructed by `TabbarString()`
var keys []int
for offset := range indicies {
keys = append(keys, offset)
}
// sort them to be in ascending order so that values will
// correctly reflect the displayed ordering of the tabs
sort.Ints(keys)
// record the offset of each tab and the previous tab so
// we can find the position of the tab's hit-box.
previousTabOffset := 0
currentTabOffset := 0
for _, k := range keys {
tabIndex := indicies[k] - 1
if tabIndex == curTab {
currentTabOffset = k
break
}
// this is +2 because there are two padding spaces that aren't accounted
// for in the display. please note that this is for cosmetic purposes only.
previousTabOffset = k + 2
}
// get the width of the hitbox of the active tab, from there calculate the offsets
// to the left and right of it to approximately center it on the tab bar display.
centeringOffset := (w - (currentTabOffset - previousTabOffset))
leftBuffer := previousTabOffset - (centeringOffset / 2)
rightBuffer := currentTabOffset + (centeringOffset / 2)
// check to make sure we haven't overshot the bounds of the string,
// if we have, then take that remainder and put it on the left side
overshotRight := rightBuffer - len(fileRunes)
if overshotRight > 0 {
leftBuffer = leftBuffer + overshotRight
}
overshotLeft := leftBuffer - 0
if overshotLeft < 0 {
leftBuffer = 0
rightBuffer = leftBuffer + (w - 1)
} else {
rightBuffer = leftBuffer + (w - 2)
}
if rightBuffer > len(fileRunes)-1 {
rightBuffer = len(fileRunes) - 1
}
// construct a new buffer of text to put the
// newly formatted tab bar text into.
var displayText []rune
// if the left-side of the tab bar isn't at the start
// of the constructed tab bar text, then show that are
// more tabs to the left by displaying a "+"
if leftBuffer != 0 {
displayText = append(displayText, '+')
}
// copy the runes in from the original tab bar text string
// into the new display buffer
for x := leftBuffer; x < rightBuffer; x++ {
displayText = append(displayText, fileRunes[x])
}
// if there is more text to the right of the right-most
// column in the tab bar text, then indicate there are more
// tabs to the right by displaying a "+"
if rightBuffer < len(fileRunes)-1 {
displayText = append(displayText, '+')
}
// now store the offset from zero of the left-most text
// that is being displayed. This is to ensure that when
// clicking on the tab bar, the correct tab gets selected.
tabBarOffset = leftBuffer
// use the constructed buffer as the display buffer to print
// onscreen.
fileRunes = displayText
} else {
tabBarOffset = 0
}
// iterate over the width of the terminal display and for each column,
// write a character into the tab display area with the appropriate style.
for x := 0; x < w; x++ {
if x < len(fileRunes) {
screen.SetContent(x, 0, fileRunes[x], nil, tabBarStyle)
} else {
screen.SetContent(x, 0, ' ', nil, tabBarStyle)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,366 +0,0 @@
package main
import (
"os"
"os/user"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/mattn/go-runewidth"
)
// Util.go is a collection of utility functions that are used throughout
// the program
// Count returns the length of a string in runes
// This is exactly equivalent to utf8.RuneCountInString(), just less characters
func Count(s string) int {
return utf8.RuneCountInString(s)
}
// Convert byte array to rune array
func toRunes(b []byte) []rune {
runes := make([]rune, 0, utf8.RuneCount(b))
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
runes = append(runes, r)
b = b[size:]
}
return runes
}
func sliceStart(slc []byte, index int) []byte {
len := len(slc)
i := 0
totalSize := 0
for totalSize < len {
if i >= index {
return slc[totalSize:]
}
_, size := utf8.DecodeRune(slc[totalSize:])
totalSize += size
i++
}
return slc[totalSize:]
}
func sliceEnd(slc []byte, index int) []byte {
len := len(slc)
i := 0
totalSize := 0
for totalSize < len {
if i >= index {
return slc[:totalSize]
}
_, size := utf8.DecodeRune(slc[totalSize:])
totalSize += size
i++
}
return slc[:totalSize]
}
// NumOccurrences counts the number of occurrences of a byte in a string
func NumOccurrences(s string, c byte) int {
var n int
for i := 0; i < len(s); i++ {
if s[i] == c {
n++
}
}
return n
}
// Spaces returns a string with n spaces
func Spaces(n int) string {
return strings.Repeat(" ", n)
}
// Min takes the min of two ints
func Min(a, b int) int {
if a > b {
return b
}
return a
}
// Max takes the max of two ints
func Max(a, b int) int {
if a > b {
return a
}
return b
}
// FSize gets the size of a file
func FSize(f *os.File) int64 {
fi, _ := f.Stat()
// get the size
return fi.Size()
}
// IsWordChar returns whether or not the string is a 'word character'
// If it is a unicode character, then it does not match
// Word characters are defined as [A-Za-z0-9_]
func IsWordChar(str string) bool {
if len(str) > 1 {
// Unicode
return true
}
c := str[0]
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
}
// IsWhitespace returns true if the given rune is a space, tab, or newline
func IsWhitespace(c rune) bool {
return c == ' ' || c == '\t' || c == '\n'
}
// IsStrWhitespace returns true if the given string is all whitespace
func IsStrWhitespace(str string) bool {
for _, c := range str {
if !IsWhitespace(c) {
return false
}
}
return true
}
// Contains returns whether or not a string array contains a given string
func Contains(list []string, a string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
// Insert makes a simple insert into a string at the given position
func Insert(str string, pos int, value string) string {
return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
}
// MakeRelative will attempt to make a relative path between path and base
func MakeRelative(path, base string) (string, error) {
if len(path) > 0 {
rel, err := filepath.Rel(base, path)
if err != nil {
return path, err
}
return rel, nil
}
return path, nil
}
// GetLeadingWhitespace returns the leading whitespace of the given string
func GetLeadingWhitespace(str string) string {
ws := ""
for _, c := range str {
if c == ' ' || c == '\t' {
ws += string(c)
} else {
break
}
}
return ws
}
// IsSpaces checks if a given string is only spaces
func IsSpaces(str []byte) bool {
for _, c := range str {
if c != ' ' {
return false
}
}
return true
}
// IsSpacesOrTabs checks if a given string contains only spaces and tabs
func IsSpacesOrTabs(str string) bool {
for _, c := range str {
if c != ' ' && c != '\t' {
return false
}
}
return true
}
// ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
// as 'true' and 'false' respectively
func ParseBool(str string) (bool, error) {
if str == "on" {
return true, nil
}
if str == "off" {
return false, nil
}
return strconv.ParseBool(str)
}
// EscapePath replaces every path separator in a given path with a %
func EscapePath(path string) string {
path = filepath.ToSlash(path)
return strings.Replace(path, "/", "%", -1)
}
// GetModTime returns the last modification time for a given file
// It also returns a boolean if there was a problem accessing the file
func GetModTime(path string) (time.Time, bool) {
info, err := os.Stat(path)
if err != nil {
return time.Now(), false
}
return info.ModTime(), true
}
// StringWidth returns the width of a string where tabs count as `tabsize` width
func StringWidth(str string, tabsize int) int {
sw := runewidth.StringWidth(str)
lineIdx := 0
for _, ch := range str {
switch ch {
case '\t':
ts := tabsize - (lineIdx % tabsize)
sw += ts
lineIdx += ts
case '\n':
lineIdx = 0
default:
lineIdx++
}
}
return sw
}
// WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
// that have a width larger than 1 (this also counts tabs as `tabsize` width)
func WidthOfLargeRunes(str string, tabsize int) int {
count := 0
lineIdx := 0
for _, ch := range str {
var w int
if ch == '\t' {
w = tabsize - (lineIdx % tabsize)
} else {
w = runewidth.RuneWidth(ch)
}
if w > 1 {
count += (w - 1)
}
if ch == '\n' {
lineIdx = 0
} else {
lineIdx += w
}
}
return count
}
// RunePos returns the rune index of a given byte index
// This could cause problems if the byte index is between code points
func runePos(p int, str string) int {
return utf8.RuneCountInString(str[:p])
}
func lcs(a, b string) string {
arunes := []rune(a)
brunes := []rune(b)
lcs := ""
for i, r := range arunes {
if i >= len(brunes) {
break
}
if r == brunes[i] {
lcs += string(r)
} else {
break
}
}
return lcs
}
// CommonSubstring gets a common substring among the inputs
func CommonSubstring(arr ...string) string {
commonStr := arr[0]
for _, str := range arr[1:] {
commonStr = lcs(commonStr, str)
}
return commonStr
}
// Abs is a simple absolute value function for ints
func Abs(n int) int {
if n < 0 {
return -n
}
return n
}
// FuncName returns the full name of a given function object
func FuncName(i interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}
// ShortFuncName returns the name only of a given function object
func ShortFuncName(i interface{}) string {
return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
}
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
// home directory. Does nothing if the path does not start with '~'.
func ReplaceHome(path string) string {
if !strings.HasPrefix(path, "~") {
return path
}
var userData *user.User
var err error
homeString := strings.Split(path, "/")[0]
if homeString == "~" {
userData, err = user.Current()
if err != nil {
messenger.Error("Could not find user: ", err)
}
} else {
userData, err = user.Lookup(homeString[1:])
if err != nil {
if messenger != nil {
messenger.Error("Could not find user: ", err)
} else {
TermMessage("Could not find user: ", err)
}
return ""
}
}
home := userData.HomeDir
return strings.Replace(path, homeString, home, 1)
}
// GetPath returns a filename without everything following a `:`
// This is used for opening files like util.go:10:5 to specify a line and column
func GetPath(path string) string {
if strings.Contains(path, ":") {
path = strings.Split(path, ":")[0]
}
return path
}

View File

@@ -1,105 +0,0 @@
package main
import (
"testing"
)
func TestNumOccurences(t *testing.T) {
var tests = []struct {
inputStr string
inputChar byte
want int
}{
{"aaaa", 'a', 4},
{"\trfd\ta", '\t', 2},
{"∆ƒ\tø ® \t\t", '\t', 3},
}
for _, test := range tests {
if got := NumOccurrences(test.inputStr, test.inputChar); got != test.want {
t.Errorf("NumOccurences(%s, %c) = %d", test.inputStr, test.inputChar, got)
}
}
}
func TestSpaces(t *testing.T) {
var tests = []struct {
input int
want string
}{
{4, " "},
{0, ""},
}
for _, test := range tests {
if got := Spaces(test.input); got != test.want {
t.Errorf("Spaces(%d) = \"%s\"", test.input, got)
}
}
}
func TestIsWordChar(t *testing.T) {
if IsWordChar("t") == false {
t.Errorf("IsWordChar(t) = false")
}
if IsWordChar("T") == false {
t.Errorf("IsWordChar(T) = false")
}
if IsWordChar("5") == false {
t.Errorf("IsWordChar(5) = false")
}
if IsWordChar("_") == false {
t.Errorf("IsWordChar(_) = false")
}
if IsWordChar("ß") == false {
t.Errorf("IsWordChar(ß) = false")
}
if IsWordChar("~") == true {
t.Errorf("IsWordChar(~) = true")
}
if IsWordChar(" ") == true {
t.Errorf("IsWordChar( ) = true")
}
if IsWordChar(")") == true {
t.Errorf("IsWordChar()) = true")
}
if IsWordChar("\n") == true {
t.Errorf("IsWordChar(\n)) = true")
}
}
func TestStringWidth(t *testing.T) {
tabsize := 4
if w := StringWidth("1\t2", tabsize); w != 5 {
t.Error("StringWidth 1 Failed. Got", w)
}
if w := StringWidth("\t", tabsize); w != 4 {
t.Error("StringWidth 2 Failed. Got", w)
}
if w := StringWidth("1\t", tabsize); w != 4 {
t.Error("StringWidth 3 Failed. Got", w)
}
if w := StringWidth("\t\t", tabsize); w != 8 {
t.Error("StringWidth 4 Failed. Got", w)
}
if w := StringWidth("12\t2\t", tabsize); w != 8 {
t.Error("StringWidth 5 Failed. Got", w)
}
}
func TestWidthOfLargeRunes(t *testing.T) {
tabsize := 4
if w := WidthOfLargeRunes("1\t2", tabsize); w != 2 {
t.Error("WidthOfLargeRunes 1 Failed. Got", w)
}
if w := WidthOfLargeRunes("\t", tabsize); w != 3 {
t.Error("WidthOfLargeRunes 2 Failed. Got", w)
}
if w := WidthOfLargeRunes("1\t", tabsize); w != 2 {
t.Error("WidthOfLargeRunes 3 Failed. Got", w)
}
if w := WidthOfLargeRunes("\t\t", tabsize); w != 6 {
t.Error("WidthOfLargeRunes 4 Failed. Got", w)
}
if w := WidthOfLargeRunes("12\t2\t", tabsize); w != 3 {
t.Error("WidthOfLargeRunes 5 Failed. Got", w)
}
}

File diff suppressed because it is too large Load Diff

26
go.mod Normal file
View File

@@ -0,0 +1,26 @@
module github.com/zyedidia/micro
require (
github.com/blang/semver v3.5.1+incompatible
github.com/dustin/go-humanize v1.0.0
github.com/go-errors/errors v1.0.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-isatty v0.0.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/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 v1.4.4
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

75
go.sum Normal file
View File

@@ -0,0 +1,75 @@
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/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
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.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.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=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197 h1:gYTNnAW6azuB3BbA6QYWO/H4F2ABSOjjw3Z03tlXd2c=
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/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 v1.4.3 h1:s0nmxj22Jj+vyt4nVmnB6kbnwRxEay1zfS0j8IsbtfI=
github.com/zyedidia/tcell v1.4.3/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/tcell v1.4.4 h1:o34LXujNuSueuyTy+5eoQW+rQr8g0UbY8k1NczZyskQ=
github.com/zyedidia/tcell v1.4.4/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=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.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=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
layeh.com/gopher-luar v1.0.7 h1:53iv6CCkRs5wyofZ+qVXcyAYQOIG52s6pt4xkqZdq7k=
layeh.com/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=

1689
internal/action/actions.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
// +build plan9 nacl windows
package action
func (*BufPane) Suspend() bool {
InfoBar.Error("Suspend is only supported on BSD/Linux")
return false
}

View File

@@ -1,37 +1,27 @@
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
package main
package action
import "syscall"
import (
"syscall"
"github.com/zyedidia/micro/internal/screen"
)
// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.
// This only works on linux and has no default binding.
// This code was adapted from the suspend code in nsf/godit
func (v *View) Suspend(usePlugin bool) bool {
if usePlugin && !PreActionCall("Suspend", v) {
return false
}
screenWasNil := screen == nil
if !screenWasNil {
screen.Fini()
screen = nil
}
func (*BufPane) Suspend() bool {
screenb := screen.TempFini()
// suspend the process
pid := syscall.Getpid()
err := syscall.Kill(pid, syscall.SIGSTOP)
if err != nil {
TermMessage(err)
screen.TermMessage(err)
}
if !screenWasNil {
InitScreen()
}
screen.TempStart(screenb)
if usePlugin {
return PostActionCall("Suspend", v)
}
return true
return false
}

400
internal/action/bindings.go Normal file
View File

@@ -0,0 +1,400 @@
package action
import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"path/filepath"
"strings"
"unicode"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/tcell"
)
func createBindingsIfNotExist(fname string) {
if _, e := os.Stat(fname); os.IsNotExist(e) {
ioutil.WriteFile(fname, []byte("{}"), 0644)
}
}
// InitBindings intializes the bindings map by reading from bindings.json
func InitBindings() {
config.Bindings = DefaultBindings()
var parsed map[string]string
defaults := DefaultBindings()
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
screen.TermMessage("Error reading bindings.json file: " + err.Error())
return
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
screen.TermMessage("Error reading bindings.json:", err.Error())
}
}
for k, v := range defaults {
BindKey(k, v)
}
for k, v := range parsed {
BindKey(k, v)
}
}
func BindKey(k, v string) {
event, ok := findEvent(k)
if !ok {
screen.TermMessage(k, "is not a bindable event")
}
switch e := event.(type) {
case KeyEvent:
BufMapKey(e, v)
case MouseEvent:
BufMapMouse(e, v)
case RawEvent:
BufMapKey(e, v)
}
config.Bindings[k] = v
}
// findEvent will find binding Key 'b' using string 'k'
func findEvent(k string) (b Event, ok bool) {
modifiers := tcell.ModNone
// First, we'll strip off all the modifiers in the name and add them to the
// ModMask
modSearch:
for {
switch {
case strings.HasPrefix(k, "-"):
// We optionally support dashes between modifiers
k = k[1:]
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
k = k[4:]
modifiers |= tcell.ModCtrl
case strings.HasPrefix(k, "Alt"):
k = k[3:]
modifiers |= tcell.ModAlt
case strings.HasPrefix(k, "Shift"):
k = k[5:]
modifiers |= tcell.ModShift
case strings.HasPrefix(k, "\x1b"):
screen.Screen.RegisterRawSeq(k)
return RawEvent{
esc: k,
}, true
default:
break modSearch
}
}
if len(k) == 0 {
return KeyEvent{}, false
}
// Control is handled in a special way, since the terminal sends explicitly
// marked escape sequences for control keys
// We should check for Control keys first
if modifiers&tcell.ModCtrl != 0 {
// see if the key is in bindingKeys with the Ctrl prefix.
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
if code, ok := keyEvents["Ctrl"+k]; ok {
var r tcell.Key
// Special case for escape, for some reason tcell doesn't send it with the esc character
if code < 256 && code != 27 {
r = code
}
// It is, we're done.
return KeyEvent{
code: code,
mod: modifiers,
r: rune(r),
}, true
}
}
// See if we can find the key in bindingKeys
if code, ok := keyEvents[k]; ok {
var r tcell.Key
// Special case for escape, for some reason tcell doesn't send it with the esc character
if code < 256 && code != 27 {
r = code
}
return KeyEvent{
code: code,
mod: modifiers,
r: rune(r),
}, true
}
// See if we can find the key in bindingMouse
if code, ok := mouseEvents[k]; ok {
return MouseEvent{
btn: code,
mod: modifiers,
}, true
}
// If we were given one character, then we've got a rune.
if len(k) == 1 {
return KeyEvent{
code: tcell.KeyRune,
mod: modifiers,
r: rune(k[0]),
}, true
}
// We don't know what happened.
return KeyEvent{}, false
}
// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
// Returns true if the keybinding already existed and a possible error
func TryBindKey(k, v string, overwrite bool) (bool, error) {
var e error
var parsed map[string]string
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
return false, errors.New("Error reading bindings.json file: " + err.Error())
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
return false, errors.New("Error reading bindings.json: " + err.Error())
}
key, ok := findEvent(k)
if !ok {
return false, errors.New("Invalid event " + k)
}
found := false
for ev := range parsed {
if e, ok := findEvent(ev); ok {
if e == key {
if overwrite {
parsed[ev] = v
}
found = true
break
}
}
}
if found && !overwrite {
return true, nil
} else if !found {
parsed[k] = v
}
BindKey(k, v)
txt, _ := json.MarshalIndent(parsed, "", " ")
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return false, e
}
// UnbindKey removes the binding for a key from the bindings.json file
func UnbindKey(k string) error {
var e error
var parsed map[string]string
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
return errors.New("Error reading bindings.json file: " + err.Error())
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
return errors.New("Error reading bindings.json: " + err.Error())
}
key, ok := findEvent(k)
if !ok {
return errors.New("Invalid event " + k)
}
for ev := range parsed {
if e, ok := findEvent(ev); ok {
if e == key {
delete(parsed, ev)
break
}
}
}
defaults := DefaultBindings()
if a, ok := defaults[k]; ok {
BindKey(k, a)
} else if _, ok := config.Bindings[k]; ok {
delete(config.Bindings, k)
}
txt, _ := json.MarshalIndent(parsed, "", " ")
return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return e
}
var mouseEvents = map[string]tcell.ButtonMask{
"MouseLeft": tcell.Button1,
"MouseMiddle": tcell.Button2,
"MouseRight": tcell.Button3,
"MouseWheelUp": tcell.WheelUp,
"MouseWheelDown": tcell.WheelDown,
"MouseWheelLeft": tcell.WheelLeft,
"MouseWheelRight": tcell.WheelRight,
}
var keyEvents = map[string]tcell.Key{
"Up": tcell.KeyUp,
"Down": tcell.KeyDown,
"Right": tcell.KeyRight,
"Left": tcell.KeyLeft,
"UpLeft": tcell.KeyUpLeft,
"UpRight": tcell.KeyUpRight,
"DownLeft": tcell.KeyDownLeft,
"DownRight": tcell.KeyDownRight,
"Center": tcell.KeyCenter,
"PageUp": tcell.KeyPgUp,
"PageDown": tcell.KeyPgDn,
"Home": tcell.KeyHome,
"End": tcell.KeyEnd,
"Insert": tcell.KeyInsert,
"Delete": tcell.KeyDelete,
"Help": tcell.KeyHelp,
"Exit": tcell.KeyExit,
"Clear": tcell.KeyClear,
"Cancel": tcell.KeyCancel,
"Print": tcell.KeyPrint,
"Pause": tcell.KeyPause,
"Backtab": tcell.KeyBacktab,
"F1": tcell.KeyF1,
"F2": tcell.KeyF2,
"F3": tcell.KeyF3,
"F4": tcell.KeyF4,
"F5": tcell.KeyF5,
"F6": tcell.KeyF6,
"F7": tcell.KeyF7,
"F8": tcell.KeyF8,
"F9": tcell.KeyF9,
"F10": tcell.KeyF10,
"F11": tcell.KeyF11,
"F12": tcell.KeyF12,
"F13": tcell.KeyF13,
"F14": tcell.KeyF14,
"F15": tcell.KeyF15,
"F16": tcell.KeyF16,
"F17": tcell.KeyF17,
"F18": tcell.KeyF18,
"F19": tcell.KeyF19,
"F20": tcell.KeyF20,
"F21": tcell.KeyF21,
"F22": tcell.KeyF22,
"F23": tcell.KeyF23,
"F24": tcell.KeyF24,
"F25": tcell.KeyF25,
"F26": tcell.KeyF26,
"F27": tcell.KeyF27,
"F28": tcell.KeyF28,
"F29": tcell.KeyF29,
"F30": tcell.KeyF30,
"F31": tcell.KeyF31,
"F32": tcell.KeyF32,
"F33": tcell.KeyF33,
"F34": tcell.KeyF34,
"F35": tcell.KeyF35,
"F36": tcell.KeyF36,
"F37": tcell.KeyF37,
"F38": tcell.KeyF38,
"F39": tcell.KeyF39,
"F40": tcell.KeyF40,
"F41": tcell.KeyF41,
"F42": tcell.KeyF42,
"F43": tcell.KeyF43,
"F44": tcell.KeyF44,
"F45": tcell.KeyF45,
"F46": tcell.KeyF46,
"F47": tcell.KeyF47,
"F48": tcell.KeyF48,
"F49": tcell.KeyF49,
"F50": tcell.KeyF50,
"F51": tcell.KeyF51,
"F52": tcell.KeyF52,
"F53": tcell.KeyF53,
"F54": tcell.KeyF54,
"F55": tcell.KeyF55,
"F56": tcell.KeyF56,
"F57": tcell.KeyF57,
"F58": tcell.KeyF58,
"F59": tcell.KeyF59,
"F60": tcell.KeyF60,
"F61": tcell.KeyF61,
"F62": tcell.KeyF62,
"F63": tcell.KeyF63,
"F64": tcell.KeyF64,
"CtrlSpace": tcell.KeyCtrlSpace,
"CtrlA": tcell.KeyCtrlA,
"CtrlB": tcell.KeyCtrlB,
"CtrlC": tcell.KeyCtrlC,
"CtrlD": tcell.KeyCtrlD,
"CtrlE": tcell.KeyCtrlE,
"CtrlF": tcell.KeyCtrlF,
"CtrlG": tcell.KeyCtrlG,
"CtrlH": tcell.KeyCtrlH,
"CtrlI": tcell.KeyCtrlI,
"CtrlJ": tcell.KeyCtrlJ,
"CtrlK": tcell.KeyCtrlK,
"CtrlL": tcell.KeyCtrlL,
"CtrlM": tcell.KeyCtrlM,
"CtrlN": tcell.KeyCtrlN,
"CtrlO": tcell.KeyCtrlO,
"CtrlP": tcell.KeyCtrlP,
"CtrlQ": tcell.KeyCtrlQ,
"CtrlR": tcell.KeyCtrlR,
"CtrlS": tcell.KeyCtrlS,
"CtrlT": tcell.KeyCtrlT,
"CtrlU": tcell.KeyCtrlU,
"CtrlV": tcell.KeyCtrlV,
"CtrlW": tcell.KeyCtrlW,
"CtrlX": tcell.KeyCtrlX,
"CtrlY": tcell.KeyCtrlY,
"CtrlZ": tcell.KeyCtrlZ,
"CtrlLeftSq": tcell.KeyCtrlLeftSq,
"CtrlBackslash": tcell.KeyCtrlBackslash,
"CtrlRightSq": tcell.KeyCtrlRightSq,
"CtrlCarat": tcell.KeyCtrlCarat,
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
"Tab": tcell.KeyTab,
"Esc": tcell.KeyEsc,
"Escape": tcell.KeyEscape,
"Enter": tcell.KeyEnter,
"Backspace": tcell.KeyBackspace2,
"OldBackspace": tcell.KeyBackspace,
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
"PgUp": tcell.KeyPgUp,
"PgDown": tcell.KeyPgDn,
}

680
internal/action/bufpane.go Normal file
View File

@@ -0,0 +1,680 @@
package action
import (
"strings"
"time"
luar "layeh.com/gopher-luar"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/display"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/tcell"
)
type BufKeyAction func(*BufPane) bool
type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
var BufKeyBindings map[Event]BufKeyAction
var BufKeyStrings map[Event]string
var BufMouseBindings map[MouseEvent]BufMouseAction
func init() {
BufKeyBindings = make(map[Event]BufKeyAction)
BufKeyStrings = make(map[Event]string)
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
}
func LuaAction(fn string) func(*BufPane) bool {
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(h *BufPane) bool {
val, err := pl.Call(plFn, luar.New(ulua.L, h))
if err != nil {
screen.TermMessage(err)
}
if v, ok := val.(lua.LBool); !ok {
return false
} else {
return bool(v)
}
}
}
// BufMapKey maps a key event to an action
func BufMapKey(k Event, action string) {
BufKeyStrings[k] = action
var actionfns []func(*BufPane) bool
var names []string
var types []byte
for i := 0; ; i++ {
if action == "" {
break
}
// TODO: fix problem when complex bindings have these
// characters (escape them?)
idx := strings.IndexAny(action, "&|,")
a := action
if idx >= 0 {
a = action[:idx]
types = append(types, action[idx])
action = action[idx+1:]
} else {
types = append(types, ' ')
action = ""
}
var afn func(*BufPane) bool
if strings.HasPrefix(a, "command:") {
a = strings.SplitN(a, ":", 2)[1]
afn = CommandAction(a)
names = append(names, "")
} else if strings.HasPrefix(a, "command-edit:") {
a = strings.SplitN(a, ":", 2)[1]
afn = CommandEditAction(a)
names = append(names, "")
} else if strings.HasPrefix(a, "lua:") {
a = strings.SplitN(a, ":", 2)[1]
afn = LuaAction(a)
if afn == nil {
screen.TermMessage("Lua Error:", a, "does not exist")
continue
}
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)
} else {
screen.TermMessage("Error:", a, "does not exist")
continue
}
actionfns = append(actionfns, afn)
}
BufKeyBindings[k] = func(h *BufPane) bool {
cursors := h.Buf.GetCursors()
success := true
for i, a := range actionfns {
for j, c := range cursors {
if c == nil {
continue
}
h.Buf.SetCurCursor(c.Num)
h.Cursor = c
if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
success = h.execAction(a, names[i], j)
} else {
break
}
}
}
return true
}
}
// BufMapMouse maps a mouse event to an action
func BufMapMouse(k MouseEvent, action string) {
if f, ok := BufMouseActions[action]; ok {
BufMouseBindings[k] = f
} else {
delete(BufMouseBindings, k)
BufMapKey(k, action)
}
}
// The BufPane connects the buffer and the window
// It provides a cursor (or multiple) and defines a set of actions
// that can be taken on the buffer
// The ActionHandler can access the window for necessary info about
// visual positions for mouse clicks and scrolling
type BufPane struct {
display.BWindow
Buf *buffer.Buffer
Cursor *buffer.Cursor // the active cursor
// Since tcell doesn't differentiate between a mouse release event
// and a mouse move event with no keys pressed, we need to keep
// track of whether or not the mouse was pressed (or not released) last event to determine
// mouse release events
mouseReleased bool
// We need to keep track of insert key press toggle
isOverwriteMode bool
// This stores when the last click was
// This is useful for detecting double and triple clicks
lastClickTime time.Time
lastLoc buffer.Loc
// lastCutTime stores when the last ctrl+k was issued.
// It is used for clearing the clipboard to replace it with fresh cut lines.
lastCutTime time.Time
// freshClip returns true if the clipboard has never been pasted.
freshClip bool
// Was the last mouse event actually a double click?
// Useful for detecting triple clicks -- if a double click is detected
// but the last mouse event was actually a double click, it's a triple click
doubleClick bool
// Same here, just to keep track for mouse move events
tripleClick bool
// Last search stores the last successful search for FindNext and FindPrev
lastSearch string
// Should the current multiple cursor selection search based on word or
// based on selection (false for selection, true for word)
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, tab *Tab) *BufPane {
h := new(BufPane)
h.Buf = buf
h.BWindow = win
h.tab = tab
h.Cursor = h.Buf.GetActiveCursor()
h.mouseReleased = true
config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
return h
}
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
w := display.NewBufWindow(0, 0, 0, 0, buf)
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
// displays an error if there is one and returns the aggregrate
// boolean response
func (h *BufPane) PluginCB(cb string) bool {
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
if err != nil {
screen.TermMessage(err)
}
return b
}
// PluginCBRune is the same as PluginCB but also passes a rune to
// the plugins
func (h *BufPane) PluginCBRune(cb string, r rune) bool {
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
if err != nil {
screen.TermMessage(err)
}
return b
}
func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
h.Buf.Close()
h.Buf = b
h.BWindow.SetBuffer(b)
h.Cursor = b.GetActiveCursor()
h.Resize(h.GetView().Width, h.GetView().Height)
h.Relocate()
// Set mouseReleased to true because we assume the mouse is not being pressed when
// the editor is opened
h.mouseReleased = true
// Set isOverwriteMode to false, because we assume we are in the default mode when editor
// is opened
h.isOverwriteMode = false
h.lastClickTime = time.Time{}
}
func (h *BufPane) ID() uint64 {
return h.splitID
}
func (h *BufPane) SetID(i uint64) {
h.splitID = i
}
func (h *BufPane) Name() string {
return h.Buf.GetName()
}
// HandleEvent executes the tcell event properly
func (h *BufPane) HandleEvent(event tcell.Event) {
if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
if canceled {
h.Buf.DisableReload()
}
if !yes || canceled {
h.Buf.UpdateModTime()
} else {
h.Buf.ReOpen()
}
})
}
switch e := event.(type) {
case *tcell.EventRaw:
re := RawEvent{
esc: e.EscSeq(),
}
h.DoKeyEvent(re)
case *tcell.EventPaste:
h.paste(e.Text())
h.Relocate()
case *tcell.EventKey:
ke := KeyEvent{
code: e.Key(),
mod: e.Modifiers(),
r: e.Rune(),
}
done := h.DoKeyEvent(ke)
if !done && e.Key() == tcell.KeyRune {
h.DoRuneInsert(e.Rune())
}
case *tcell.EventMouse:
cancel := false
switch e.Buttons() {
case tcell.Button1:
_, my := e.Position()
if h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
cancel = true
}
case tcell.ButtonNone:
// Mouse event with no click
if !h.mouseReleased {
// Mouse was just released
// mx, my := e.Position()
// mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
// we could finish the selection based on the release location as described
// below but when the mouse click is within the scroll margin this will
// cause a scroll and selection even for a simple mouse click which is
// not good
// for terminals that don't support mouse motion events, selection via
// the mouse won't work but this is ok
// Relocating here isn't really necessary because the cursor will
// be in the right place from the last mouse event
// However, if we are running in a terminal that doesn't support mouse motion
// events, this still allows the user to make selections, except only after they
// release the mouse
// if !h.doubleClick && !h.tripleClick {
// h.Cursor.Loc = mouseLoc
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
// h.Cursor.CopySelection("primary")
// }
h.mouseReleased = true
}
}
if !cancel {
me := MouseEvent{
btn: e.Buttons(),
mod: e.Modifiers(),
}
h.DoMouseEvent(me, e)
}
}
h.Buf.MergeCursors()
if h.IsActive() {
// Display any gutter messages for this line
c := h.Buf.GetActiveCursor()
none := true
for _, m := range h.Buf.Messages {
if c.Y == m.Start.Y || c.Y == m.End.Y {
InfoBar.GutterMessage(m.Msg)
none = false
break
}
}
if none && InfoBar.HasGutter {
InfoBar.ClearGutter()
}
}
}
// DoKeyEvent executes a key event by finding the action it is bound
// to and executing it (possibly multiple times for multiple cursors)
func (h *BufPane) DoKeyEvent(e Event) bool {
if action, ok := BufKeyBindings[e]; ok {
return action(h)
}
return false
}
func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
if name != "Autocomplete" && name != "CycleAutocompleteBack" {
h.Buf.HasSuggestions = false
}
_, isMulti := MultiActions[name]
if (!isMulti && cursor == 0) || isMulti {
if h.PluginCB("pre" + name) {
success := action(h)
success = success && h.PluginCB("on"+name)
if isMulti {
if recording_macro {
if name != "ToggleMacro" && name != "PlayMacro" {
curmacro = append(curmacro, action)
}
}
}
return success
}
}
return false
}
func (h *BufPane) completeAction(action string) {
h.PluginCB("on" + action)
}
func (h *BufPane) HasKeyEvent(e Event) bool {
_, ok := BufKeyBindings[e]
return ok
}
// DoMouseEvent executes a mouse event by finding the action it is bound
// to and executing it
func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
if action, ok := BufMouseBindings[e]; ok {
if action(h, te) {
h.Relocate()
}
return true
} else if h.HasKeyEvent(e) {
return h.DoKeyEvent(e)
}
return false
}
// DoRuneInsert inserts a given rune into the current buffer
// (possibly multiple times for multiple cursors)
func (h *BufPane) DoRuneInsert(r rune) {
cursors := h.Buf.GetCursors()
for _, c := range cursors {
// Insert a character
h.Buf.SetCurCursor(c.Num)
h.Cursor = c
if !h.PluginCBRune("preRune", r) {
continue
}
if c.HasSelection() {
c.DeleteSelection()
c.ResetSelection()
}
if h.isOverwriteMode {
next := c.Loc
next.X++
h.Buf.Replace(c.Loc, next, string(r))
} else {
h.Buf.Insert(c.Loc, string(r))
}
if recording_macro {
curmacro = append(curmacro, r)
}
h.PluginCBRune("onRune", r)
}
}
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) 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()
}
func (h *BufPane) SetActive(b bool) {
h.BWindow.SetActive(b)
if b {
// Display any gutter messages for this line
c := h.Buf.GetActiveCursor()
none := true
for _, m := range h.Buf.Messages {
if c.Y == m.Start.Y || c.Y == m.End.Y {
InfoBar.GutterMessage(m.Msg)
none = false
break
}
}
if none && InfoBar.HasGutter {
InfoBar.ClearGutter()
}
}
}
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
var BufKeyActions = map[string]BufKeyAction{
"CursorUp": (*BufPane).CursorUp,
"CursorDown": (*BufPane).CursorDown,
"CursorPageUp": (*BufPane).CursorPageUp,
"CursorPageDown": (*BufPane).CursorPageDown,
"CursorLeft": (*BufPane).CursorLeft,
"CursorRight": (*BufPane).CursorRight,
"CursorStart": (*BufPane).CursorStart,
"CursorEnd": (*BufPane).CursorEnd,
"SelectToStart": (*BufPane).SelectToStart,
"SelectToEnd": (*BufPane).SelectToEnd,
"SelectUp": (*BufPane).SelectUp,
"SelectDown": (*BufPane).SelectDown,
"SelectLeft": (*BufPane).SelectLeft,
"SelectRight": (*BufPane).SelectRight,
"WordRight": (*BufPane).WordRight,
"WordLeft": (*BufPane).WordLeft,
"SelectWordRight": (*BufPane).SelectWordRight,
"SelectWordLeft": (*BufPane).SelectWordLeft,
"DeleteWordRight": (*BufPane).DeleteWordRight,
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
"SelectLine": (*BufPane).SelectLine,
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
"ParagraphNext": (*BufPane).ParagraphNext,
"InsertNewline": (*BufPane).InsertNewline,
"Backspace": (*BufPane).Backspace,
"Delete": (*BufPane).Delete,
"InsertTab": (*BufPane).InsertTab,
"Save": (*BufPane).Save,
"SaveAll": (*BufPane).SaveAll,
"SaveAs": (*BufPane).SaveAs,
"Find": (*BufPane).Find,
"FindNext": (*BufPane).FindNext,
"FindPrevious": (*BufPane).FindPrevious,
"Center": (*BufPane).Center,
"Undo": (*BufPane).Undo,
"Redo": (*BufPane).Redo,
"Copy": (*BufPane).Copy,
"Cut": (*BufPane).Cut,
"CutLine": (*BufPane).CutLine,
"DuplicateLine": (*BufPane).DuplicateLine,
"DeleteLine": (*BufPane).DeleteLine,
"MoveLinesUp": (*BufPane).MoveLinesUp,
"MoveLinesDown": (*BufPane).MoveLinesDown,
"IndentSelection": (*BufPane).IndentSelection,
"OutdentSelection": (*BufPane).OutdentSelection,
"Autocomplete": (*BufPane).Autocomplete,
"CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
"OutdentLine": (*BufPane).OutdentLine,
"IndentLine": (*BufPane).IndentLine,
"Paste": (*BufPane).Paste,
"PastePrimary": (*BufPane).PastePrimary,
"SelectAll": (*BufPane).SelectAll,
"OpenFile": (*BufPane).OpenFile,
"Start": (*BufPane).Start,
"End": (*BufPane).End,
"PageUp": (*BufPane).PageUp,
"PageDown": (*BufPane).PageDown,
"SelectPageUp": (*BufPane).SelectPageUp,
"SelectPageDown": (*BufPane).SelectPageDown,
"HalfPageUp": (*BufPane).HalfPageUp,
"HalfPageDown": (*BufPane).HalfPageDown,
"StartOfText": (*BufPane).StartOfText,
"StartOfLine": (*BufPane).StartOfLine,
"EndOfLine": (*BufPane).EndOfLine,
"ToggleHelp": (*BufPane).ToggleHelp,
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
"ToggleRuler": (*BufPane).ToggleRuler,
"ClearStatus": (*BufPane).ClearStatus,
"ShellMode": (*BufPane).ShellMode,
"CommandMode": (*BufPane).CommandMode,
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
"Escape": (*BufPane).Escape,
"Quit": (*BufPane).Quit,
"QuitAll": (*BufPane).QuitAll,
"AddTab": (*BufPane).AddTab,
"PreviousTab": (*BufPane).PreviousTab,
"NextTab": (*BufPane).NextTab,
"NextSplit": (*BufPane).NextSplit,
"PreviousSplit": (*BufPane).PreviousSplit,
"Unsplit": (*BufPane).Unsplit,
"VSplit": (*BufPane).VSplitAction,
"HSplit": (*BufPane).HSplitAction,
"ToggleMacro": (*BufPane).ToggleMacro,
"PlayMacro": (*BufPane).PlayMacro,
"Suspend": (*BufPane).Suspend,
"ScrollUp": (*BufPane).ScrollUpAction,
"ScrollDown": (*BufPane).ScrollDownAction,
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
"SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
"SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
"None": (*BufPane).None,
// This was changed to InsertNewline but I don't want to break backwards compatibility
"InsertEnter": (*BufPane).InsertNewline,
}
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
var BufMouseActions = map[string]BufMouseAction{
"MousePress": (*BufPane).MousePress,
"MouseMultiCursor": (*BufPane).MouseMultiCursor,
}
// MultiActions is a list of actions that should be executed multiple
// times if there are multiple cursors (one per cursor)
// Generally actions that modify global editor state like quitting or
// saving should not be included in this list
var MultiActions = map[string]bool{
"CursorUp": true,
"CursorDown": true,
"CursorPageUp": true,
"CursorPageDown": true,
"CursorLeft": true,
"CursorRight": true,
"CursorStart": true,
"CursorEnd": true,
"SelectToStart": true,
"SelectToEnd": true,
"SelectUp": true,
"SelectDown": true,
"SelectLeft": true,
"SelectRight": true,
"WordRight": true,
"WordLeft": true,
"SelectWordRight": true,
"SelectWordLeft": true,
"DeleteWordRight": true,
"DeleteWordLeft": true,
"SelectLine": true,
"SelectToStartOfLine": true,
"SelectToStartOfText": true,
"SelectToEndOfLine": true,
"ParagraphPrevious": true,
"ParagraphNext": true,
"InsertNewline": true,
"Backspace": true,
"Delete": true,
"InsertTab": true,
"FindNext": true,
"FindPrevious": true,
"Cut": true,
"CutLine": true,
"DuplicateLine": true,
"DeleteLine": true,
"MoveLinesUp": true,
"MoveLinesDown": true,
"IndentSelection": true,
"OutdentSelection": true,
"OutdentLine": true,
"IndentLine": true,
"Paste": true,
"PastePrimary": true,
"SelectPageUp": true,
"SelectPageDown": true,
"StartOfLine": true,
"StartOfText": true,
"EndOfLine": true,
"JumpToMatchingBrace": true,
}

894
internal/action/command.go Normal file
View File

@@ -0,0 +1,894 @@
package action
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"unicode/utf8"
shellquote "github.com/kballard/go-shellquote"
"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"
)
// A Command contains information about how to execute a command
// It has the action for that command as well as a completer function
type Command struct {
action func(*BufPane, []string)
completer buffer.Completer
}
var commands map[string]Command
func InitCommands() {
commands = map[string]Command{
"set": {(*BufPane).SetCmd, OptionValueComplete},
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
"show": {(*BufPane).ShowCmd, OptionComplete},
"showkey": {(*BufPane).ShowKeyCmd, nil},
"run": {(*BufPane).RunCmd, nil},
"bind": {(*BufPane).BindCmd, nil},
"unbind": {(*BufPane).UnbindCmd, nil},
"quit": {(*BufPane).QuitCmd, nil},
"goto": {(*BufPane).GotoCmd, nil},
"save": {(*BufPane).SaveCmd, nil},
"replace": {(*BufPane).ReplaceCmd, nil},
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
"help": {(*BufPane).HelpCmd, HelpComplete},
"eval": {(*BufPane).EvalCmd, nil},
"log": {(*BufPane).ToggleLogCmd, nil},
"plugin": {(*BufPane).PluginCmd, PluginComplete},
"reload": {(*BufPane).ReloadCmd, nil},
"reopen": {(*BufPane).ReopenCmd, nil},
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
"pwd": {(*BufPane).PwdCmd, nil},
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
"term": {(*BufPane).TermCmd, nil},
"memusage": {(*BufPane).MemUsageCmd, nil},
"retab": {(*BufPane).RetabCmd, nil},
"raw": {(*BufPane).RawCmd, nil},
"textfilter": {(*BufPane).TextFilterCmd, nil},
}
}
// 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 MakeCommand(name string, action func(bp *BufPane, args []string), completer buffer.Completer) {
if action != nil {
commands[name] = Command{action, completer}
}
}
// CommandEditAction returns a bindable function that opens a prompt with
// the given string and executes the command when the user presses
// enter
func CommandEditAction(prompt string) BufKeyAction {
return func(h *BufPane) bool {
InfoBar.Prompt("> ", prompt, "Command", nil, func(resp string, canceled bool) {
if !canceled {
MainTab().CurPane().HandleCommand(resp)
}
})
return false
}
}
// CommandAction returns a bindable function which executes the
// given command
func CommandAction(cmd string) BufKeyAction {
return func(h *BufPane) bool {
MainTab().CurPane().HandleCommand(cmd)
return false
}
}
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) < 1 {
InfoBar.Error("Not enough arguments")
return
}
if h.Buf.Type != buffer.BTLog {
h.OpenLogBuf()
}
config.PluginCommand(buffer.LogBuf, args[0], args[1:])
}
// RetabCmd changes all spaces to tabs or all tabs to spaces
// depending on the user's settings
func (h *BufPane) RetabCmd(args []string) {
h.Buf.Retab()
}
// RawCmd opens a new raw view which displays the escape sequences micro
// is receiving in real-time
func (h *BufPane) RawCmd(args []string) {
width, height := screen.Screen.Size()
iOffset := config.GetInfoBarOffset()
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane(nil))
Tabs.AddTab(tp)
Tabs.SetActive(len(Tabs.List) - 1)
}
// TextFilterCmd filters the selection through the command.
// Selection goes to the command input.
// On successful run command output replaces the current selection.
func (h *BufPane) TextFilterCmd(args []string) {
if len(args) == 0 {
InfoBar.Error("usage: textfilter arguments")
return
}
sel := h.Cursor.GetSelection()
if len(sel) == 0 {
h.Cursor.SelectWord()
sel = h.Cursor.GetSelection()
}
var bout, berr bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = strings.NewReader(string(sel))
cmd.Stderr = &berr
cmd.Stdout = &bout
err := cmd.Run()
if err != nil {
InfoBar.Error(err.Error() + " " + berr.String())
return
}
h.Cursor.DeleteSelection()
h.Buf.Insert(h.Cursor.Loc, bout.String())
}
// TabSwitchCmd switches to a given tab either by name or by number
func (h *BufPane) TabSwitchCmd(args []string) {
if len(args) > 0 {
num, err := strconv.Atoi(args[0])
if err != nil {
// Check for tab with this name
found := false
for i, t := range Tabs.List {
if t.Panes[t.active].Name() == args[0] {
Tabs.SetActive(i)
found = true
}
}
if !found {
InfoBar.Error("Could not find tab: ", err)
}
} else {
num--
if num >= 0 && num < len(Tabs.List) {
Tabs.SetActive(num)
} else {
InfoBar.Error("Invalid tab index")
}
}
}
}
// CdCmd changes the current working directory
func (h *BufPane) CdCmd(args []string) {
if len(args) > 0 {
path, err := util.ReplaceHome(args[0])
if err != nil {
InfoBar.Error(err)
return
}
err = os.Chdir(path)
if err != nil {
InfoBar.Error(err)
return
}
wd, _ := os.Getwd()
for _, b := range buffer.OpenBuffers {
if len(b.Path) > 0 {
b.Path, _ = util.MakeRelative(b.AbsPath, wd)
if p, _ := filepath.Abs(b.Path); !strings.Contains(p, wd) {
b.Path = b.AbsPath
}
}
}
}
}
// MemUsageCmd prints micro's memory usage
// Alloc shows how many bytes are currently in use
// Sys shows how many bytes have been requested from the operating system
// NumGC shows how many times the GC has been run
// Note that Go commonly reserves more memory from the OS than is currently in-use/required
// Additionally, even if Go returns memory to the OS, the OS does not always claim it because
// there may be plenty of memory to spare
func (h *BufPane) MemUsageCmd(args []string) {
InfoBar.Message(util.GetMemStats())
}
// PwdCmd prints the current working directory
func (h *BufPane) PwdCmd(args []string) {
wd, err := os.Getwd()
if err != nil {
InfoBar.Message(err.Error())
} else {
InfoBar.Message(wd)
}
}
// OpenCmd opens a new buffer with a given filename
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 := shellquote.Split(filename)
if err != nil {
InfoBar.Error("Error parsing args ", err)
return
}
if len(args) == 0 {
return
}
filename = strings.Join(args, " ")
open := func() {
b, err := buffer.NewBufferFromFile(filename, buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
h.OpenBuffer(b)
}
if h.Buf.Modified() {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
open()
} else if !canceled && yes {
h.Save()
open()
}
})
} else {
open()
}
} else {
InfoBar.Error("No filename")
}
}
// ToggleLogCmd toggles the log view
func (h *BufPane) ToggleLogCmd(args []string) {
if h.Buf.Type != buffer.BTLog {
h.OpenLogBuf()
} else {
h.Quit()
}
}
// ReloadCmd reloads all files (syntax files, colorschemes...)
func (h *BufPane) ReloadCmd(args []string) {
ReloadConfig()
}
func ReloadConfig() {
config.InitRuntimeFiles()
err := config.ReadSettings()
if err != nil {
screen.TermMessage(err)
}
config.InitGlobalSettings()
InitBindings()
InitCommands()
err = config.InitColorscheme()
if err != nil {
screen.TermMessage(err)
}
for _, b := range buffer.OpenBuffers {
b.UpdateRules()
}
}
// ReopenCmd reopens the buffer (reload from disk)
func (h *BufPane) ReopenCmd(args []string) {
if h.Buf.Modified() {
InfoBar.YNPrompt("Save file before reopen?", func(yes, canceled bool) {
if !canceled && yes {
h.Save()
h.Buf.ReOpen()
} else if !canceled {
h.Buf.ReOpen()
}
})
} else {
h.Buf.ReOpen()
}
}
func (h *BufPane) openHelp(page string) error {
if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {
return errors.New(fmt.Sprint("Unable to load help text", page, "\n", err))
} else {
helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp)
helpBuffer.SetName("Help " + page)
if h.Buf.Type == buffer.BTHelp {
h.OpenBuffer(helpBuffer)
} else {
h.HSplitBuf(helpBuffer)
}
}
return nil
}
// HelpCmd tries to open the given help page in a horizontal split
func (h *BufPane) HelpCmd(args []string) {
if len(args) < 1 {
// Open the default help if the user just typed "> help"
h.openHelp("help")
} else {
if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
err := h.openHelp(args[0])
if err != nil {
InfoBar.Error(err)
}
} else {
InfoBar.Error("Sorry, no help for ", args[0])
}
}
}
// VSplitCmd opens a vertical split with file given in the first argument
// If no file is given, it opens an empty buffer in a new split
func (h *BufPane) VSplitCmd(args []string) {
if len(args) == 0 {
// Open an empty vertical split
h.VSplitAction()
return
}
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
h.VSplitBuf(buf)
}
// HSplitCmd opens a horizontal split with file given in the first argument
// If no file is given, it opens an empty buffer in a new split
func (h *BufPane) HSplitCmd(args []string) {
if len(args) == 0 {
// Open an empty horizontal split
h.HSplitAction()
return
}
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
h.HSplitBuf(buf)
}
// EvalCmd evaluates a lua expression
func (h *BufPane) EvalCmd(args []string) {
InfoBar.Error("Eval unsupported")
}
// NewTabCmd opens the given file in a new tab
func (h *BufPane) NewTabCmd(args []string) {
width, height := screen.Screen.Size()
iOffset := config.GetInfoBarOffset()
if len(args) > 0 {
for _, a := range args {
b, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
tp := NewTabFromBuffer(0, 0, width, height-1-iOffset, b)
Tabs.AddTab(tp)
Tabs.SetActive(len(Tabs.List) - 1)
}
} else {
b := buffer.NewBufferFromString("", "", buffer.BTDefault)
tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
Tabs.AddTab(tp)
Tabs.SetActive(len(Tabs.List) - 1)
}
}
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
local := false
for _, s := range config.LocalSettings {
if s == option {
local = true
break
}
}
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 {
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)
}
}
}
}
}
}
for _, b := range buffer.OpenBuffers {
b.SetOptionNative(option, nativeValue)
}
return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
}
func SetGlobalOption(option, value string) error {
if _, ok := config.GlobalSettings[option]; !ok {
return config.ErrInvalidOption
}
nativeValue, err := config.GetNativeValue(option, config.GlobalSettings[option], value)
if err != nil {
return err
}
return SetGlobalOptionNative(option, nativeValue)
}
// ResetCmd resets a setting to its default value
func (h *BufPane) ResetCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Not enough arguments")
return
}
option := args[0]
defaultGlobals := config.DefaultGlobalSettings()
defaultLocals := config.DefaultCommonSettings()
if _, ok := defaultGlobals[option]; ok {
SetGlobalOptionNative(option, defaultGlobals[option])
return
}
if _, ok := defaultLocals[option]; ok {
h.Buf.SetOptionNative(option, defaultLocals[option])
return
}
InfoBar.Error(config.ErrInvalidOption)
}
// SetCmd sets an option
func (h *BufPane) SetCmd(args []string) {
if len(args) < 2 {
InfoBar.Error("Not enough arguments")
return
}
option := args[0]
value := args[1]
err := SetGlobalOption(option, value)
if err == config.ErrInvalidOption {
err := h.Buf.SetOption(option, value)
if err != nil {
InfoBar.Error(err)
}
} else if err != nil {
InfoBar.Error(err)
}
}
// SetLocalCmd sets an option local to the buffer
func (h *BufPane) SetLocalCmd(args []string) {
if len(args) < 2 {
InfoBar.Error("Not enough arguments")
return
}
option := args[0]
value := args[1]
err := h.Buf.SetOption(option, value)
if err != nil {
InfoBar.Error(err)
}
}
// ShowCmd shows the value of the given option
func (h *BufPane) ShowCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Please provide an option to show")
return
}
var option interface{}
if opt, ok := h.Buf.Settings[args[0]]; ok {
option = opt
} else if opt, ok := config.GlobalSettings[args[0]]; ok {
option = opt
}
if option == nil {
InfoBar.Error(args[0], " is not a valid option")
return
}
InfoBar.Message(option)
}
// ShowKeyCmd displays the action that a key is bound to
func (h *BufPane) ShowKeyCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Please provide a key to show")
return
}
if action, ok := config.Bindings[args[0]]; ok {
InfoBar.Message(action)
} else {
InfoBar.Message(args[0], " has no binding")
}
}
// BindCmd creates a new keybinding
func (h *BufPane) BindCmd(args []string) {
if len(args) < 2 {
InfoBar.Error("Not enough arguments")
return
}
_, err := TryBindKey(args[0], args[1], true)
if err != nil {
InfoBar.Error(err)
}
}
// UnbindCmd binds a key to its default action
func (h *BufPane) UnbindCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Not enough arguments")
return
}
err := UnbindKey(args[0])
if err != nil {
InfoBar.Error(err)
}
}
// RunCmd runs a shell command in the background
func (h *BufPane) RunCmd(args []string) {
runf, err := shell.RunBackgroundShell(shellquote.Join(args...))
if err != nil {
InfoBar.Error(err)
} else {
go func() {
InfoBar.Message(runf())
screen.Redraw()
}()
}
}
// QuitCmd closes the main view
func (h *BufPane) QuitCmd(args []string) {
h.Quit()
}
// GotoCmd is a command that will send the cursor to a certain
// position in the buffer
// For example: `goto line`, or `goto line:col`
func (h *BufPane) GotoCmd(args []string) {
if len(args) <= 0 {
InfoBar.Error("Not enough arguments")
} else {
h.RemoveAllMultiCursors()
if strings.Contains(args[0], ":") {
parts := strings.SplitN(args[0], ":", 2)
line, err := strconv.Atoi(parts[0])
if err != nil {
InfoBar.Error(err)
return
}
col, err := strconv.Atoi(parts[1])
if err != nil {
InfoBar.Error(err)
return
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
col = util.Clamp(col-1, 0, utf8.RuneCount(h.Buf.LineBytes(line)))
h.Cursor.GotoLoc(buffer.Loc{col, line})
} else {
line, err := strconv.Atoi(args[0])
if err != nil {
InfoBar.Error(err)
return
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
h.Cursor.GotoLoc(buffer.Loc{0, line})
}
h.Relocate()
}
}
// SaveCmd saves the buffer optionally with an argument file name
func (h *BufPane) SaveCmd(args []string) {
if len(args) == 0 {
h.Save()
} else {
h.Buf.SaveAs(args[0])
}
}
// ReplaceCmd runs search and replace
func (h *BufPane) ReplaceCmd(args []string) {
if len(args) < 2 || len(args) > 4 {
// We need to find both a search and replace expression
InfoBar.Error("Invalid replace statement: " + strings.Join(args, " "))
return
}
all := false
noRegex := false
foundSearch := false
foundReplace := false
var search string
var replaceStr string
for _, arg := range args {
switch arg {
case "-a":
all = true
case "-l":
noRegex = true
default:
if !foundSearch {
foundSearch = true
search = arg
} else if !foundReplace {
foundReplace = true
replaceStr = arg
} else {
InfoBar.Error("Invalid flag: " + arg)
return
}
}
}
if noRegex {
search = regexp.QuoteMeta(search)
}
replace := []byte(replaceStr)
var regex *regexp.Regexp
var err error
if h.Buf.Settings["ignorecase"].(bool) {
regex, err = regexp.Compile("(?im)" + search)
} else {
regex, err = regexp.Compile("(?m)" + search)
}
if err != nil {
// There was an error with the user's regex
InfoBar.Error(err)
return
}
nreplaced := 0
start := h.Buf.Start()
// end := h.Buf.End()
// if h.Cursor.HasSelection() {
// start = h.Cursor.CurSelection[0]
// end = h.Cursor.CurSelection[1]
// }
if all {
nreplaced = h.Buf.ReplaceRegex(start, h.Buf.End(), regex, replace)
} else {
inRange := func(l buffer.Loc) bool {
return l.GreaterEqual(start) && l.LessEqual(h.Buf.End())
}
searchLoc := start
searching := true
var doReplacement func()
doReplacement = func() {
locs, found, err := h.Buf.FindNext(search, start, h.Buf.End(), searchLoc, true, !noRegex)
if err != nil {
InfoBar.Error(err)
return
}
if !found || !inRange(locs[0]) || !inRange(locs[1]) {
h.Cursor.ResetSelection()
h.Buf.RelocateCursors()
return
}
h.Cursor.SetSelectionStart(locs[0])
h.Cursor.SetSelectionEnd(locs[1])
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
if !canceled && yes {
h.Buf.Replace(locs[0], locs[1], replaceStr)
searchLoc = locs[0]
searchLoc.X += utf8.RuneCount(replace)
h.Cursor.Loc = searchLoc
nreplaced++
} else if !canceled && !yes {
searchLoc = locs[0]
searchLoc.X += utf8.RuneCount(replace)
} else if canceled {
h.Cursor.ResetSelection()
h.Buf.RelocateCursors()
return
}
if searching {
doReplacement()
}
})
}
doReplacement()
}
h.Buf.RelocateCursors()
if nreplaced > 1 {
InfoBar.Message("Replaced ", nreplaced, " occurrences of ", search)
} else if nreplaced == 1 {
InfoBar.Message("Replaced ", nreplaced, " occurrence of ", search)
} else {
InfoBar.Message("Nothing matched ", search)
}
}
// ReplaceAllCmd replaces search term all at once
func (h *BufPane) ReplaceAllCmd(args []string) {
// aliased to Replace command
h.ReplaceCmd(append(args, "-a"))
}
// TermCmd opens a terminal in the current view
func (h *BufPane) TermCmd(args []string) {
ps := h.tab.Panes
if !TermEmuSupported {
InfoBar.Error("Terminal emulator not supported on this system")
return
}
if len(args) == 0 {
sh := os.Getenv("SHELL")
if sh == "" {
InfoBar.Error("Shell environment not found")
return
}
args = []string{sh}
}
term := func(i int, newtab bool) {
t := new(shell.Terminal)
t.Start(args, false, true, nil, nil)
id := h.ID()
if newtab {
h.AddTab()
i = 0
id = MainTab().Panes[0].ID()
} else {
MainTab().Panes[i].Close()
}
v := h.GetView()
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
if err != nil {
InfoBar.Error(err)
return
}
MainTab().Panes[i] = tp
MainTab().SetActive(i)
}
// If there is only one open file we make a new tab instead of overwriting it
newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1
if newtab {
term(0, true)
return
}
for i, p := range ps {
if p.ID() == h.ID() {
if h.Buf.Modified() {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
term(i, false)
} else if !canceled && yes {
h.Save()
term(i, false)
}
})
} else {
term(i, false)
}
}
}
}
// HandleCommand handles input from the user
func (h *BufPane) HandleCommand(input string) {
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 {
InfoBar.Error("Unknown command ", inputCmd)
} else {
WriteLog("> " + input + "\n")
commands[inputCmd].action(h, args[1:])
WriteLog("\n")
}
}

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",
}
}

42
internal/action/events.go Normal file
View File

@@ -0,0 +1,42 @@
package action
import (
"github.com/zyedidia/tcell"
)
type Event interface{}
// RawEvent is simply an escape code
// We allow users to directly bind escape codes
// to get around some of a limitations of terminals
type RawEvent struct {
esc string
}
// KeyEvent is a key event containing a key code,
// some possible modifiers (alt, ctrl, etc...) and
// a rune if it was simply a character press
// Note: to be compatible with tcell events,
// for ctrl keys r=code
type KeyEvent struct {
code tcell.Key
mod tcell.ModMask
r rune
}
// MouseEvent is a mouse event with a mouse button and
// any possible key modifiers
type MouseEvent struct {
btn tcell.ButtonMask
mod tcell.ModMask
}
type KeyAction func(Handler) bool
type MouseAction func(Handler, tcell.EventMouse) bool
// A Handler will take a tcell event and execute it
// appropriately
type Handler interface {
HandleEvent(tcell.Event)
HandleCommand(string)
}

View File

@@ -0,0 +1,48 @@
package action
import "github.com/zyedidia/micro/internal/buffer"
var InfoBar *InfoPane
var LogBufPane *BufPane
// InitGlobals initializes the log buffer and the info bar
func InitGlobals() {
InfoBar = NewInfoBar()
buffer.LogBuf = buffer.NewBufferFromString("", "Log", buffer.BTLog)
}
// GetInfoBar returns the infobar pane
func GetInfoBar() *InfoPane {
return InfoBar
}
// WriteLog writes a string to the log buffer
func WriteLog(s string) {
buffer.WriteLog(s)
if LogBufPane != nil {
LogBufPane.CursorEnd()
v := LogBufPane.GetView()
endY := buffer.LogBuf.End().Y
if endY > v.StartLine+v.Height {
v.StartLine = buffer.LogBuf.End().Y - v.Height + 2
LogBufPane.SetView(v)
}
}
}
// OpenLogBuf opens the log buffer from the current bufpane
// If the current bufpane is a log buffer nothing happens,
// otherwise the log buffer is opened in a horizontal split
func (h *BufPane) OpenLogBuf() {
LogBufPane = h.HSplitBuf(buffer.LogBuf)
LogBufPane.CursorEnd()
v := LogBufPane.GetView()
endY := buffer.LogBuf.End().Y
if endY > v.StartLine+v.Height {
v.StartLine = buffer.LogBuf.End().Y - v.Height + 2
LogBufPane.SetView(v)
}
}

View File

@@ -0,0 +1,280 @@
package action
import (
"bytes"
"sort"
"strings"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/util"
)
// This file is meant (for now) for autocompletion in command mode, not
// while coding. This helps micro autocomplete commands and then filenames
// for example with `vsplit filename`.
// CommandComplete autocompletes commands
func CommandComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := buffer.GetArg(b)
var suggestions []string
for cmd := range commands {
if strings.HasPrefix(cmd, input) {
suggestions = append(suggestions, cmd)
}
}
sort.Strings(suggestions)
completions := make([]string, len(suggestions))
for i := range suggestions {
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
}
return completions, suggestions
}
// HelpComplete autocompletes help topics
func HelpComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := buffer.GetArg(b)
var suggestions []string
for _, file := range config.ListRuntimeFiles(config.RTHelp) {
topic := file.Name()
if strings.HasPrefix(topic, input) {
suggestions = append(suggestions, topic)
}
}
sort.Strings(suggestions)
completions := make([]string, len(suggestions))
for i := range suggestions {
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
}
return completions, suggestions
}
// colorschemeComplete tab-completes names of colorschemes.
// This is just a heper value for OptionValueComplete
func colorschemeComplete(input string) (string, []string) {
var suggestions []string
files := config.ListRuntimeFiles(config.RTColorscheme)
for _, f := range files {
if strings.HasPrefix(f.Name(), input) {
suggestions = append(suggestions, f.Name())
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// OptionComplete autocompletes options
func OptionComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := buffer.GetArg(b)
var suggestions []string
for option := range config.GlobalSettings {
if strings.HasPrefix(option, input) {
suggestions = append(suggestions, option)
}
}
// for option := range localSettings {
// if strings.HasPrefix(option, input) && !contains(suggestions, option) {
// suggestions = append(suggestions, option)
// }
// }
sort.Strings(suggestions)
completions := make([]string, len(suggestions))
for i := range suggestions {
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
}
return completions, suggestions
}
// OptionValueComplete completes values for various options
func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X)
input, argstart := buffer.GetArg(b)
completeValue := false
args := bytes.Split(l, []byte{' '})
if len(args) >= 2 {
// localSettings := config.DefaultLocalSettings()
for option := range config.GlobalSettings {
if option == string(args[len(args)-2]) {
completeValue = true
break
}
}
// for option := range localSettings {
// if option == string(args[len(args)-2]) {
// completeValue = true
// break
// }
// }
}
if !completeValue {
return OptionComplete(b)
}
inputOpt := string(args[len(args)-2])
inputOpt = strings.TrimSpace(inputOpt)
var suggestions []string
// localSettings := config.DefaultLocalSettings()
var optionVal interface{}
for k, option := range config.GlobalSettings {
if k == inputOpt {
optionVal = option
}
}
// for k, option := range localSettings {
// if k == inputOpt {
// optionVal = option
// }
// }
switch optionVal.(type) {
case bool:
if strings.HasPrefix("on", input) {
suggestions = append(suggestions, "on")
} else if strings.HasPrefix("true", input) {
suggestions = append(suggestions, "true")
}
if strings.HasPrefix("off", input) {
suggestions = append(suggestions, "off")
} else if strings.HasPrefix("false", input) {
suggestions = append(suggestions, "false")
}
case string:
switch inputOpt {
case "colorscheme":
_, suggestions = colorschemeComplete(input)
case "fileformat":
if strings.HasPrefix("unix", input) {
suggestions = append(suggestions, "unix")
}
if strings.HasPrefix("dos", input) {
suggestions = append(suggestions, "dos")
}
case "sucmd":
if strings.HasPrefix("sudo", input) {
suggestions = append(suggestions, "sudo")
}
if strings.HasPrefix("doas", input) {
suggestions = append(suggestions, "doas")
}
}
}
sort.Strings(suggestions)
completions := make([]string, len(suggestions))
for i := range suggestions {
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
}
return completions, suggestions
}
// PluginCmdComplete autocompletes the plugin command
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := buffer.GetArg(b)
var suggestions []string
for _, cmd := range PluginCmds {
if strings.HasPrefix(cmd, input) {
suggestions = append(suggestions, cmd)
}
}
sort.Strings(suggestions)
completions := make([]string, len(suggestions))
for i := range suggestions {
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
}
return completions, suggestions
}
// PluginComplete completes values for the plugin command
func PluginComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X)
input, argstart := buffer.GetArg(b)
completeValue := false
args := bytes.Split(l, []byte{' '})
if len(args) >= 2 {
for _, cmd := range PluginCmds {
if cmd == string(args[len(args)-2]) {
completeValue = true
break
}
}
}
if !completeValue {
return PluginCmdComplete(b)
}
var suggestions []string
for _, pl := range config.Plugins {
if strings.HasPrefix(pl.Name, input) {
suggestions = append(suggestions, pl.Name)
}
}
sort.Strings(suggestions)
completions := make([]string, len(suggestions))
for i := range suggestions {
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
}
return completions, suggestions
}
// PluginNameComplete completes with the names of loaded plugins
// func PluginNameComplete(b *buffer.Buffer) ([]string, []string) {
// c := b.GetActiveCursor()
// input, argstart := buffer.GetArg(b)
//
// var suggestions []string
// for _, pp := range config.GetAllPluginPackages(nil) {
// if strings.HasPrefix(pp.Name, input) {
// suggestions = append(suggestions, pp.Name)
// }
// }
//
// sort.Strings(suggestions)
// completions := make([]string, len(suggestions))
// for i := range suggestions {
// completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
// }
// 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))
// }

220
internal/action/infopane.go Normal file
View File

@@ -0,0 +1,220 @@
package action
import (
"bytes"
"strings"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/internal/info"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/tcell"
)
type InfoKeyAction func(*InfoPane)
type InfoPane struct {
*BufPane
*info.InfoBuf
}
func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
ip := new(InfoPane)
ip.InfoBuf = ib
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
return ip
}
func NewInfoBar() *InfoPane {
ib := info.NewBuffer()
w := display.NewInfoWindow(ib)
return NewInfoPane(ib, w, nil)
}
func (h *InfoPane) Close() {
h.InfoBuf.Close()
h.BufPane.Close()
}
func (h *InfoPane) HandleEvent(event tcell.Event) {
switch e := event.(type) {
case *tcell.EventKey:
ke := KeyEvent{
code: e.Key(),
mod: e.Modifiers(),
r: e.Rune(),
}
done := h.DoKeyEvent(ke)
hasYN := h.HasYN
if e.Key() == tcell.KeyRune && hasYN {
if e.Rune() == 'y' && hasYN {
h.YNResp = true
h.DonePrompt(false)
} else if e.Rune() == 'n' && hasYN {
h.YNResp = false
h.DonePrompt(false)
}
}
if e.Key() == tcell.KeyRune && !done && !hasYN {
h.DoRuneInsert(e.Rune())
done = true
}
if done && h.HasPrompt && !hasYN {
resp := string(h.LineBytes(0))
hist := h.History[h.PromptType]
hist[h.HistoryNum] = resp
if h.EventCallback != nil {
h.EventCallback(resp)
}
}
case *tcell.EventMouse:
h.BufPane.HandleEvent(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 {
estr := BufKeyStrings[e]
for _, s := range InfoNones {
if s == estr {
return false
}
}
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.HasPrefix(estr, s) {
done = true
a(h)
break
}
}
if !done {
done = action(h.BufPane)
}
}
return done
}
// InfoNones is a list of actions that should have no effect when executed
// by an infohandler
var InfoNones = []string{
"Save",
"SaveAll",
"SaveAs",
"Find",
"FindNext",
"FindPrevious",
"Center",
"DuplicateLine",
"MoveLinesUp",
"MoveLinesDown",
"OpenFile",
"Start",
"End",
"PageUp",
"PageDown",
"SelectPageUp",
"SelectPageDown",
"HalfPageUp",
"HalfPageDown",
"ToggleHelp",
"ToggleKeyMenu",
"ToggleDiffGutter",
"ToggleRuler",
"JumpLine",
"ClearStatus",
"ShellMode",
"CommandMode",
"AddTab",
"PreviousTab",
"NextTab",
"NextSplit",
"PreviousSplit",
"Unsplit",
"VSplit",
"HSplit",
"ToggleMacro",
"PlayMacro",
"Suspend",
"ScrollUp",
"ScrollDown",
"SpawnMultiCursor",
"SpawnMultiCursorSelect",
"RemoveMultiCursor",
"RemoveAllMultiCursors",
"SkipMultiCursor",
}
// InfoOverrides is the list of actions which have been overridden
// by the infohandler
var InfoOverrides = map[string]InfoKeyAction{
"CursorUp": (*InfoPane).CursorUp,
"CursorDown": (*InfoPane).CursorDown,
"InsertNewline": (*InfoPane).InsertNewline,
"Autocomplete": (*InfoPane).Autocomplete,
"Escape": (*InfoPane).Escape,
"Quit": (*InfoPane).Quit,
"QuitAll": (*InfoPane).QuitAll,
}
// CursorUp cycles history up
func (h *InfoPane) CursorUp() {
h.UpHistory(h.History[h.PromptType])
}
// CursorDown cycles history down
func (h *InfoPane) CursorDown() {
h.DownHistory(h.History[h.PromptType])
}
// Autocomplete begins autocompletion
func (h *InfoPane) Autocomplete() {
b := h.Buf
if b.HasSuggestions {
b.CycleAutocomplete(true)
return
}
c := b.GetActiveCursor()
l := b.LineBytes(0)
l = util.SliceStart(l, c.X)
args := bytes.Split(l, []byte{' '})
cmd := string(args[0])
if len(args) == 1 {
b.Autocomplete(CommandComplete)
} else {
if action, ok := commands[cmd]; ok {
if action.completer != nil {
b.Autocomplete(action.completer)
}
}
}
}
// InsertNewline completes the prompt
func (h *InfoPane) InsertNewline() {
if !h.HasYN {
h.DonePrompt(false)
}
}
// Quit cancels the prompt
func (h *InfoPane) Quit() {
h.DonePrompt(true)
}
// QuitAll cancels the prompt
func (h *InfoPane) QuitAll() {
h.DonePrompt(true)
}
// Escape cancels the prompt
func (h *InfoPane) Escape() {
h.DonePrompt(true)
}

16
internal/action/pane.go Normal file
View File

@@ -0,0 +1,16 @@
package action
import (
"github.com/zyedidia/micro/internal/display"
)
type Pane interface {
Handler
display.Window
ID() uint64
SetID(i uint64)
Name() string
Close()
SetTab(t *Tab)
Tab() *Tab
}

View File

@@ -0,0 +1,40 @@
package action
import (
"fmt"
"reflect"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/tcell"
)
type RawPane struct {
*BufPane
}
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane {
rh := new(RawPane)
rh.BufPane = NewBufPane(b, win, tab)
return rh
}
func NewRawPane(tab *Tab) *RawPane {
b := buffer.NewBufferFromString("", "", buffer.BTRaw)
w := display.NewBufWindow(0, 0, 0, 0, b)
return NewRawPaneFromWin(b, w, tab)
}
func (h *RawPane) HandleEvent(event tcell.Event) {
switch e := event.(type) {
case *tcell.EventKey:
if e.Key() == tcell.KeyCtrlQ {
h.Quit()
}
}
h.Buf.Insert(h.Cursor.Loc, reflect.TypeOf(event).String()[7:])
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
h.Relocate()
}

300
internal/action/tab.go Normal file
View File

@@ -0,0 +1,300 @@
package action
import (
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/views"
"github.com/zyedidia/tcell"
)
// The TabList is a list of tabs and a window to display the tab bar
// at the top of the screen
type TabList struct {
*display.TabWindow
List []*Tab
}
// NewTabList creates a TabList from a list of buffers by creating a Tab
// for each buffer
func NewTabList(bufs []*buffer.Buffer) *TabList {
w, h := screen.Screen.Size()
iOffset := config.GetInfoBarOffset()
tl := new(TabList)
tl.List = make([]*Tab, len(bufs))
if len(bufs) > 1 {
for i, b := range bufs {
tl.List[i] = NewTabFromBuffer(0, 1, w, h-1-iOffset, b)
}
} else {
tl.List[0] = NewTabFromBuffer(0, 0, w, h-iOffset, bufs[0])
}
tl.TabWindow = display.NewTabWindow(w, 0)
tl.Names = make([]string, len(bufs))
return tl
}
// UpdateNames makes sure that the list of names the tab window has access to is
// correct
func (t *TabList) UpdateNames() {
t.Names = t.Names[:0]
for _, p := range t.List {
t.Names = append(t.Names, p.Panes[p.active].Name())
}
}
// AddTab adds a new tab to this TabList
func (t *TabList) AddTab(p *Tab) {
t.List = append(t.List, p)
t.Resize()
t.UpdateNames()
}
// RemoveTab removes a tab with the given id from the TabList
func (t *TabList) RemoveTab(id uint64) {
for i, p := range t.List {
if len(p.Panes) == 0 {
continue
}
if p.Panes[0].ID() == id {
copy(t.List[i:], t.List[i+1:])
t.List[len(t.List)-1] = nil
t.List = t.List[:len(t.List)-1]
if t.Active() >= len(t.List) {
t.SetActive(len(t.List) - 1)
}
t.Resize()
t.UpdateNames()
return
}
}
}
// Resize resizes all elements within the tab list
// One thing to note is that when there is only 1 tab
// the tab bar should not be drawn so resizing must take
// that into account
func (t *TabList) Resize() {
w, h := screen.Screen.Size()
iOffset := config.GetInfoBarOffset()
InfoBar.Resize(w, h-1)
if len(t.List) > 1 {
for _, p := range t.List {
p.Y = 1
p.Node.Resize(w, h-1-iOffset)
p.Resize()
}
} else if len(t.List) == 1 {
t.List[0].Y = 0
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
// otherwise it will forward the event to the currently active tab
func (t *TabList) HandleEvent(event tcell.Event) {
switch e := event.(type) {
case *tcell.EventResize:
t.Resize()
case *tcell.EventMouse:
mx, my := e.Position()
switch e.Buttons() {
case tcell.Button1:
if my == t.Y && mx == 0 {
t.Scroll(-4)
return
} else if my == t.Y && mx == t.Width-1 {
t.Scroll(4)
return
}
if len(t.List) > 1 {
ind := t.LocFromVisual(buffer.Loc{mx, my})
if ind != -1 {
t.SetActive(ind)
return
}
if my == 0 {
return
}
}
case tcell.WheelUp:
if my == t.Y {
t.Scroll(4)
return
}
case tcell.WheelDown:
if my == t.Y {
t.Scroll(-4)
return
}
}
}
t.List[t.Active()].HandleEvent(event)
}
// Display updates the names and then displays the tab bar
func (t *TabList) Display() {
t.UpdateNames()
if len(t.List) > 1 {
t.TabWindow.Display()
}
}
// Tabs is the global tab list
var Tabs *TabList
func InitTabs(bufs []*buffer.Buffer) {
Tabs = NewTabList(bufs)
}
func MainTab() *Tab {
return Tabs.List[Tabs.Active()]
}
// A Tab represents a single tab
// It consists of a list of edit panes (the open buffers),
// a split tree (stored as just the root node), and a uiwindow
// to display the UI elements like the borders between splits
type Tab struct {
*views.Node
*display.UIWindow
Panes []Pane
active int
resizing *views.Node // node currently being resized
}
// NewTabFromBuffer creates a new tab from the given buffer
func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
t := new(Tab)
t.Node = views.NewRoot(x, y, width, height)
t.UIWindow = display.NewUIWindow(t.Node)
e := NewBufPaneFromBuf(b, t)
e.SetID(t.ID())
t.Panes = append(t.Panes, e)
return t
}
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)
return t
}
// HandleEvent takes a tcell event and usually dispatches it to the current
// active pane. However if the event is a resize or a mouse event where the user
// is interacting with the UI (resizing splits) then the event is consumed here
// If the event is a mouse event in a pane, that pane will become active and get
// the event
func (t *Tab) HandleEvent(event tcell.Event) {
switch e := event.(type) {
case *tcell.EventMouse:
mx, my := e.Position()
switch e.Buttons() {
case tcell.Button1:
if t.resizing != nil {
var size int
if t.resizing.Kind == views.STVert {
size = mx - t.resizing.X
} else {
size = my - t.resizing.Y + 1
}
t.resizing.ResizeSplit(size)
t.Resize()
return
}
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
if resizeID != 0 {
t.resizing = t.GetNode(uint64(resizeID))
return
}
for i, p := range t.Panes {
v := p.GetView()
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
if inpane {
t.SetActive(i)
break
}
}
case tcell.ButtonNone:
t.resizing = nil
default:
for _, p := range t.Panes {
v := p.GetView()
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
if inpane {
p.HandleEvent(event)
return
}
}
}
}
t.Panes[t.active].HandleEvent(event)
}
// SetActive changes the currently active pane to the specified index
func (t *Tab) SetActive(i int) {
t.active = i
for j, p := range t.Panes {
if j == i {
p.SetActive(true)
} else {
p.SetActive(false)
}
}
}
// GetPane returns the pane with the given split index
func (t *Tab) GetPane(splitid uint64) int {
for i, p := range t.Panes {
if p.ID() == splitid {
return i
}
}
return 0
}
// Remove pane removes the pane with the given index
func (t *Tab) RemovePane(i int) {
copy(t.Panes[i:], t.Panes[i+1:])
t.Panes[len(t.Panes)-1] = nil
t.Panes = t.Panes[:len(t.Panes)-1]
}
// Resize resizes all panes according to their corresponding split nodes
func (t *Tab) Resize() {
for _, p := range t.Panes {
n := t.GetNode(p.ID())
pv := p.GetView()
offset := 0
if n.X != 0 {
offset = 1
}
pv.X, pv.Y = n.X+offset, n.Y
p.SetView(pv)
p.Resize(n.W-offset, n.H)
}
}
// CurPane returns the currently active pane
func (t *Tab) CurPane() *BufPane {
p, ok := t.Panes[t.active].(*BufPane)
if !ok {
return nil
}
return p
}

View File

@@ -0,0 +1,42 @@
// +build linux darwin dragonfly openbsd_amd64 freebsd
package action
import (
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/internal/shell"
)
// TermEmuSupported is a constant that marks if the terminal emulator is supported
const TermEmuSupported = true
// 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)
h.AddTab()
id := MainTab().Panes[0].ID()
v := h.GetView()
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
if err != nil {
return err
}
MainTab().Panes[0] = tp
MainTab().SetActive(0)
return nil
}

View File

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

139
internal/action/termpane.go Normal file
View File

@@ -0,0 +1,139 @@
package action
import (
"errors"
"runtime"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/tcell"
"github.com/zyedidia/terminal"
)
type TermPane struct {
*shell.Terminal
display.Window
mouseReleased bool
id uint64
tab *Tab
}
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64, tab *Tab) (*TermPane, error) {
if !TermEmuSupported {
return nil, errors.New("Terminal emulator is not supported on this system")
}
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, nil
}
func (t *TermPane) ID() uint64 {
return t.id
}
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() {
t.Close()
if len(MainTab().Panes) > 1 {
t.Unsplit()
} else if len(Tabs.List) > 1 {
Tabs.RemoveTab(t.id)
} else {
screen.Screen.Fini()
InfoBar.Close()
runtime.Goexit()
}
}
func (t *TermPane) Unsplit() {
n := MainTab().GetNode(t.id)
n.Unsplit()
MainTab().RemovePane(MainTab().GetPane(t.id))
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
}
// HandleEvent handles a tcell event by forwarding it to the terminal emulator
// If the event is a mouse event and the program running in the emulator
// does not have mouse support, the emulator will support selections and
// copy-paste
func (t *TermPane) HandleEvent(event tcell.Event) {
if e, ok := event.(*tcell.EventKey); ok {
if t.Status == shell.TTDone {
switch e.Key() {
case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter:
t.Close()
t.Quit()
default:
}
}
if e.Key() == tcell.KeyCtrlC && t.HasSelection() {
clipboard.WriteAll(t.GetSelection(t.GetView().Width), "clipboard")
InfoBar.Message("Copied selection to clipboard")
} 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())
} else if e != nil {
x, y := e.Position()
v := t.GetView()
x -= v.X
y -= v.Y
if e.Buttons() == tcell.Button1 {
if !t.mouseReleased {
// drag
t.Selection[1].X = x
t.Selection[1].Y = y
} else {
t.Selection[0].X = x
t.Selection[0].Y = y
t.Selection[1].X = x
t.Selection[1].Y = y
}
t.mouseReleased = false
} else if e.Buttons() == tcell.ButtonNone {
if !t.mouseReleased {
t.Selection[1].X = x
t.Selection[1].Y = y
}
t.mouseReleased = true
}
}
if t.Status == shell.TTClose {
t.Quit()
}
}
func (t *TermPane) HandleCommand(input string) {
InfoBar.Error("Commands are unsupported in term for now")
}

View File

@@ -0,0 +1,206 @@
package buffer
import (
"bytes"
"io/ioutil"
"os"
"sort"
"strings"
"unicode/utf8"
"github.com/zyedidia/micro/internal/util"
)
// A Completer is a function that takes a buffer and returns info
// describing what autocompletions should be inserted at the current
// cursor location
// It returns a list of string suggestions which will be inserted at
// the current cursor location if selected as well as a list of
// suggestion names which can be displayed in an autocomplete box or
// other UI element
type Completer func(*Buffer) ([]string, []string)
func (b *Buffer) GetSuggestions() {
}
// Autocomplete starts the autocomplete process
func (b *Buffer) Autocomplete(c Completer) bool {
b.Completions, b.Suggestions = c(b)
if len(b.Completions) != len(b.Suggestions) || len(b.Completions) == 0 {
return false
}
b.CurSuggestion = -1
b.CycleAutocomplete(true)
return true
}
// CycleAutocomplete moves to the next suggestion
func (b *Buffer) CycleAutocomplete(forward bool) {
prevSuggestion := b.CurSuggestion
if forward {
b.CurSuggestion++
} else {
b.CurSuggestion--
}
if b.CurSuggestion >= len(b.Suggestions) {
b.CurSuggestion = 0
} else if b.CurSuggestion < 0 {
b.CurSuggestion = len(b.Suggestions) - 1
}
c := b.GetActiveCursor()
start := c.Loc
end := c.Loc
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
start = end.Move(-utf8.RuneCountInString(b.Completions[prevSuggestion]), b)
} else {
// end = start.Move(1, b)
}
b.Replace(start, end, b.Completions[b.CurSuggestion])
if len(b.Suggestions) > 1 {
b.HasSuggestions = true
}
}
// GetWord gets the most recent word separated by any separator
// (whitespace, punctuation, any non alphanumeric character)
func GetWord(b *Buffer) ([]byte, int) {
c := b.GetActiveCursor()
l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X)
if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc)) {
return []byte{}, -1
}
if util.IsNonAlphaNumeric(b.RuneAt(c.Loc)) {
return []byte{}, c.X
}
args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
input := args[len(args)-1]
return input, c.X - utf8.RuneCount(input)
}
// GetArg gets the most recent word (separated by ' ' only)
func GetArg(b *Buffer) (string, int) {
c := b.GetActiveCursor()
l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X)
args := bytes.Split(l, []byte{' '})
input := string(args[len(args)-1])
argstart := 0
for i, a := range args {
if i == len(args)-1 {
break
}
argstart += utf8.RuneCount(a) + 1
}
return input, argstart
}
// FileComplete autocompletes filenames
func FileComplete(b *Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := GetArg(b)
sep := string(os.PathSeparator)
dirs := strings.Split(input, sep)
var files []os.FileInfo
var err error
if len(dirs) > 1 {
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
directories, _ = util.ReplaceHome(directories)
files, err = ioutil.ReadDir(directories)
} else {
files, err = ioutil.ReadDir(".")
}
if err != nil {
return nil, nil
}
var suggestions []string
for _, f := range files {
name := f.Name()
if f.IsDir() {
name += sep
}
if strings.HasPrefix(name, dirs[len(dirs)-1]) {
suggestions = append(suggestions, name)
}
}
sort.Strings(suggestions)
completions := make([]string, len(suggestions))
for i := range suggestions {
var complete string
if len(dirs) > 1 {
complete = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[i]
} else {
complete = suggestions[i]
}
completions[i] = util.SliceEndStr(complete, c.X-argstart)
}
return completions, suggestions
}
// BufferComplete autocompletes based on previous words in the buffer
func BufferComplete(b *Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := GetWord(b)
if argstart == -1 {
return []string{}, []string{}
}
inputLen := utf8.RuneCount(input)
suggestionsSet := make(map[string]struct{})
var suggestions []string
for i := c.Y; i >= 0; i-- {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
strw := string(w)
if _, ok := suggestionsSet[strw]; !ok {
suggestionsSet[strw] = struct{}{}
suggestions = append(suggestions, strw)
}
}
}
}
for i := c.Y + 1; i < b.LinesNum(); i++ {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
strw := string(w)
if _, ok := suggestionsSet[strw]; !ok {
suggestionsSet[strw] = struct{}{}
suggestions = append(suggestions, strw)
}
}
}
}
if len(suggestions) > 1 {
suggestions = append(suggestions, string(input))
}
completions := make([]string, len(suggestions))
for i := range suggestions {
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
}
return completions, suggestions
}

116
internal/buffer/backup.go Normal file
View File

@@ -0,0 +1,116 @@
package buffer
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"golang.org/x/text/encoding"
)
const backupMsg = `A backup was detected for this file. This likely means that micro
crashed while editing this file, or another instance of micro is currently
editing this file.
The backup was created on %s, and the file is
%s
* 'recover' will apply the backup as unsaved changes to the current buffer.
When the buffer is closed, the backup will be removed.
* 'ignore' will ignore the backup, discarding its changes. The backup file
will be removed.
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 == "" || b.Type != BTDefault {
return nil
}
if checkTime {
sub := time.Now().Sub(b.lastbackup)
if sub < time.Duration(backupTime)*time.Millisecond {
return nil
}
}
b.lastbackup = time.Now()
backupdir := filepath.Join(config.ConfigDir, "backups")
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
os.Mkdir(backupdir, os.ModePerm)
}
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
err := overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
}
// end of line
eol := []byte{'\n'}
// write lines
if _, e = file.Write(b.lines[0].data); e != nil {
return
}
for _, l := range b.lines[1:] {
if _, e = file.Write(eol); e != nil {
return
}
if _, e = file.Write(l.data); e != nil {
return
}
}
return
}, false)
return err
}
// RemoveBackup removes any backup file associated with this buffer
func (b *Buffer) RemoveBackup() {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
return
}
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
os.Remove(f)
}
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
// Returns true if a backup was applied
func (b *Buffer) ApplyBackup(fsize int64) bool {
if b.Settings["backup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
if info, err := os.Stat(backupfile); err == nil {
backup, err := os.Open(backupfile)
if err == nil {
defer backup.Close()
t := info.ModTime()
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
choice := screen.TermPrompt(msg, []string{"r", "i", "recover", "ignore"}, true)
if choice%2 == 0 {
// recover
b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
b.isModified = true
return true
} else if choice%2 == 1 {
// delete
os.Remove(backupfile)
}
}
}
}
return false
}

1070
internal/buffer/buffer.go Normal file

File diff suppressed because it is too large Load Diff

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