Compare commits

...

330 Commits

Author SHA1 Message Date
Zachary Yedidia
56aa289f81 Add support for COLORTERM 2018-06-03 17:07:44 -04:00
Zachary Yedidia
3c01947cb3 Fix ini comment highlighting
Fixes #1094
2018-05-12 21:29:02 -04:00
Zachary Yedidia
53e142fb88 Fix matchbraceleft option
Fixes #1101
2018-04-28 17:42:17 -04:00
Zachary Yedidia
2e64499f96 Fix possible crash in findkey
Fixes #1103
2018-04-28 17:16:22 -04:00
Zachary Yedidia
11cb702d7f Merge 2018-04-28 17:04:47 -04:00
Zachary Yedidia
7a2820cbc0 Add hidehelp option
Fixes #1080
2018-04-28 17:04:33 -04:00
Mark Weston
b181342ff1 Make ^X act like ^K when nothing is selected (#1092)
* Make ^X act like ^K when nothing is selected

^K is hard to reach with your left hand or requires to use both hands
Also with this you could remove ^K whatsoever and make room for a different command
This is how I configured nano by the way
Line duplication also becomes nearly instantaneous with a flash-quick ^X+^V+^V combo (nano doesn't have a dedicated shortcut)
Small block (5-10 lines) cuts/copies/duplicates can also be made this way

* Remove unnecessary lines

* Call CutLine the right way
2018-04-23 15:34:45 -04:00
Zachary Yedidia
f0e2f3cc96 Merge pull request #1085 from jtolds/themes
darcula: fix highlighted line and color column
2018-04-21 16:57:53 -04:00
Zachary Yedidia
6ef273accd Merge pull request #1084 from jtolds/master
home toggles between start of line and start of text
2018-04-07 20:03:57 -04:00
JT Olio
0eadf283a5 darcula: fix highlighted line and color column 2018-04-05 19:45:28 -06:00
JT Olio
f8a171379a home toggles between start of line and start of text
by default home sends the cursor to the beginning of the line.
if the cursor is at the beginning of the line already though, home
will send the cursor to the first non-whitespace rune. tapping home
will toggle between these two line starts.
2018-04-05 15:25:34 -06:00
Zachary Yedidia
1a62ede320 Build snap using up-to-date golang 2018-04-03 00:53:24 -04:00
Zachary Yedidia
1bb1da4765 Update snapcraft.yaml Go plugin 2018-04-02 21:16:19 -07:00
Zachary Yedidia
987d48038a Update snapcraft.yaml 2018-04-02 21:07:12 -07:00
Zachary Yedidia
abc04ec521 fix typo 2018-03-31 02:32:48 +00:00
Zachary Yedidia
b7706d775c Add docs for SpawnMultiCursorSelect 2018-03-30 16:42:28 -04:00
dwwmmn
ac0b89366b Implement SpawnMultiCursorSelect (#1046)
Add function to actions.go which adds a new cursor to the beginning of each line of a selection. Bind to Ctrl-M by default.
2018-03-30 16:40:45 -04:00
Zachary Yedidia
3293160dcb Fix ReplaceHome implementation 2018-03-30 16:21:39 -04:00
DanielPower
804943a1e8 Add support for ~username syntax (fix #1033) (#1035)
* Add support for ~username syntax (fix #1033)

* Fixed return string

Also removed non-descriptive variable name `foo`

* moved err declarations outside of if statement
2018-03-30 16:20:51 -04:00
Zachary Yedidia
89f50638d7 Merge 2018-03-30 15:59:45 -04:00
Zachary Yedidia
c606c51c8b Close fd properly in save
Fixes #1057
2018-03-30 15:59:26 -04:00
Zachary Yedidia
4bde88d126 Merge pull request #1076 from Velocet/patch-1
Create PowerShell.yaml - PowerShell Syntax Highlighting
2018-03-20 23:22:51 -04:00
Velocet
41bae11c1e Create PowerShell.yaml 2018-03-21 03:58:04 +01:00
Zachary Yedidia
f43a1b5ced Merge pull request #1054 from jtolds/master
allow optional brace matching with the closing brace to the left of the cursor
2018-03-19 00:32:26 -04:00
Zachary Yedidia
219f934656 Merge pull request #1067 from sum01/issue-1066
Fix #1066 php syntax
2018-03-19 00:32:07 -04:00
Zachary Yedidia
26da85dcb1 Fix test string formatting
Fixes #1068
2018-03-09 00:39:59 -05:00
Zachary Yedidia
2885b42c62 Update fastdirty hash during save
Fixes #1064
2018-03-08 15:07:14 -05:00
sum01
b12eca0a98 Fix #1066 php syntax 2018-03-08 11:28:38 -05:00
Zachary Yedidia
3e612d2597 Merge pull request #1045 from emilyaviva/master
Organize colorscheme setting documentation
2018-03-02 20:12:46 -05:00
Zachary Yedidia
ade5efef5d Merge pull request #1050 from mathieu-aubin/master
raster compression
2018-03-02 20:11:50 -05:00
Zachary Yedidia
cb45481526 Make tab views array public
Ref #1024
2018-03-02 19:50:33 -05:00
Zachary Yedidia
88d8b0b181 Count replacements in replaceall correctly
Fixes #1055
2018-03-02 19:32:23 -05:00
JT Olds
ea6a87d41a allow optionally brace matching with the closing brace to the left of the cursor
this behavior, while slightly less obvious, allows for observing what brace you
just closed. as you write closing braces, the brace you closed gets highlighted
2018-02-27 18:53:04 -07:00
Mathieu
1c2fd30cab raster compression 2018-02-23 19:30:28 +01:00
Emily Aviva Kapor-Mater
69ed07cc62 Move setting instructions to top; add some minor organization 2018-02-20 12:11:31 -08:00
Zachary Yedidia
6d2cbb6cce Use regexp replaceall
Fixes #1038
2018-02-19 17:04:09 -05:00
Zachary Yedidia
397c29443a Fix SaveAs Lua callback
Fixes #1029
2018-02-12 00:06:31 -05:00
Zachary Yedidia
5b26702d5e Merge pull request #1028 from filalex77/patch-1
Fix relative URL for terminfo
2018-02-09 11:07:14 -05:00
Oleksii Filonenko
b9e77eee6a Fix relative URL for terminfo 2018-02-09 17:36:12 +02:00
Zachary Yedidia
8e5fd674cc Merge 2018-02-08 14:18:04 -05:00
Zachary Yedidia
5038167650 Update clipboard 2018-02-08 14:17:58 -05:00
Zachary Yedidia
6787db9eb3 Merge pull request #1026 from mbesancon/patch-2
Update julia.yaml
2018-02-07 19:43:28 -05:00
mbesancon
75b9c8c1ec Update julia.yaml
added "import" keyword
2018-02-07 17:43:43 -05:00
Zachary Yedidia
a37c30b889 Fix resize when prompt is active
Fixes #1020
2018-02-04 22:58:20 -05:00
Zachary Yedidia
f17b42bcd2 Update licenses 2018-02-04 14:04:42 -05:00
Zachary Yedidia
7bfc90d080 Update license info 2018-02-04 11:33:03 -05:00
Zachary Yedidia
1d24609ed1 Add goconvey dependency to vendor
Ref #1
2018-02-03 22:33:32 -05:00
Zachary Yedidia
aa81cf5cf6 Support nano syntax for open at line
Ref #887
2018-02-02 16:53:08 -05:00
Zachary Yedidia
4790c39dfc Open at line syntax with filename:line:col
Ref #1010
Ref #887
Ref #836
2018-02-02 13:57:30 -05:00
Zachary Yedidia
35a9245c5d Use current view for every action
Fixes #1015
2018-02-02 12:33:13 -05:00
Zachary Yedidia
3e3cdfc5b5 Fix minor issue with autoscroll
Fixes #1012
2018-02-01 20:20:57 -05:00
Zachary Yedidia
f0e453b4f9 Improve ocaml syntax highlighting 2018-01-30 22:34:44 -05:00
Zachary Yedidia
3325b98063 Exit with error on screen initialization 2018-01-30 13:04:26 -05:00
Zachary Yedidia
4632c3594f Fix bad import path 2018-01-29 23:42:45 -05:00
Zachary Yedidia
96c7b1d07b Update to use new mkinfo from tcell
This update incorporates the new terminfo updates in tcell into micro
essentially merging zyedidia/mkinfo into micro. The zyedidia/mkinfo
program should no longer be necessary and micro should automatically
generate a tcell database on its own if it cannot find a terminal
entry. The tcell database will be located in `configDir/.tcelldb`.

Ref #20
Ref #922
2018-01-29 23:36:39 -05:00
Zachary Yedidia
f48116801b Improve man page 2018-01-29 20:36:18 -05:00
Zachary Yedidia
aaf098bb47 Update tex syntax file 2018-01-29 18:02:43 -05:00
Zachary Yedidia
6d4134a178 Optimization to lots of redraws on large files 2018-01-29 16:47:55 -05:00
Zachary Yedidia
015fcf5fec Minor optimizations 2018-01-29 16:02:15 -05:00
Zachary Yedidia
fddf1690e3 Large syntax highlighting memory optimization
Ref #634
2018-01-29 15:21:00 -05:00
Zachary Yedidia
0913a1aeb3 Fix syntax highlighting on empty buffer 2018-01-28 22:35:43 -05:00
Zachary Yedidia
a19a6d28a7 Small simplification 2018-01-28 15:15:23 -05:00
Zachary Yedidia
af520cf047 Fix terminal emulator support 2018-01-25 20:10:49 -05:00
Zachary Yedidia
db75e11e32 Update tcell 2018-01-24 16:11:48 -05:00
Zachary Yedidia
797e5cc27f Update tcell 2018-01-22 23:40:42 -05:00
Zachary Yedidia
36dc6647dd Add new shell command documentation
Ref #979
2018-01-22 21:03:52 -05:00
Zachary Yedidia
44b64f7129 Fix compile error 2018-01-22 17:32:30 -05:00
Zachary Yedidia
0a49ea0a0d Improve shell commands 2018-01-22 17:20:03 -05:00
Zachary Yedidia
4f41881c10 Make onViewOpen and onBufferOpen the same
Ref #948
2018-01-22 15:27:56 -05:00
Zachary Yedidia
63299df4b9 Don't throw error if job callback doesn't exist
Closes #953
2018-01-21 16:31:13 -05:00
Zachary Yedidia
10b8fb7b26 Expose emulator functions and support output
Ref #979
2018-01-20 23:34:16 -05:00
Zachary Yedidia
0a7e4c8f06 Use zyedidia/pty instead of kr/pty 2018-01-20 22:28:17 -05:00
Zachary Yedidia
83190a578e Change HandleShellCommand backend
I'm trying to add more options for plugins that want to run shell
commands. Also trying to add support for running shell commands in the
terminal emulator from a plugin and return the output.

More to come soon.

Ref #979
2018-01-20 22:23:52 -05:00
Zachary Yedidia
79349562b2 Improve unicode softwrap drawing
Ref #1002
Ref #909
2018-01-20 12:36:22 -05:00
Zachary Yedidia
0cb1ad09cd Merge 2018-01-19 00:28:58 -05:00
Zachary Yedidia
6ef00c4c3b Clean up terminal emulator a bit 2018-01-19 00:28:51 -05:00
Zachary Yedidia
bb598ae566 Merge pull request #999 from sum01/create_parents
Create parent folders (if none) when saving
2018-01-18 00:49:45 -05:00
Zachary Yedidia
13c63a9951 Merge pull request #1001 from sum01/makefile_syntax
Fix Makefile equals highlighting
2018-01-17 23:59:36 -05:00
sum01
cf06d06fb3 Fix Makefile = highlighting
I think they weren't being highlighted at all, leading to a weird looking default white box around them.
2018-01-17 23:44:53 -05:00
sum01
808e3a7c9f Prompt to create parent folders (if none) when saving
Fixes #995
2018-01-17 20:59:19 -05:00
Zachary Yedidia
16e9068cb9 Support line:col in JumpLine
Closes #1000
2018-01-17 19:09:50 -05:00
Zachary Yedidia
3924e363d1 Fix minor autoindent issue
Fixes #985
2018-01-17 17:37:17 -05:00
Zachary Yedidia
a274daeaaf Merge pull request #998 from JoshuaRLi/select-line-action
Implemented SelectLine as an Action
2018-01-17 17:25:37 -05:00
Zachary Yedidia
e26417fd14 Fix shebang js highlighting and js division
Closes #901
Closes #994
2018-01-17 17:19:03 -05:00
Joshua Li
d7ba2f600e implemented select line as an Action 2018-01-16 17:27:15 -05:00
Zachary Yedidia
1cf4baa743 Don't use indentchar style if disabled
Fixes #990
2018-01-14 11:23:30 -08:00
Zachary Yedidia
7e3aa337f6 Fix autocomplete on empty prompt 2018-01-10 15:41:49 -05:00
Zachary Yedidia
3f01101da4 Add onBufferOpen plugin callback
Closes #948
2018-01-08 17:08:11 -05:00
Zachary Yedidia
9a6054fc43 Add GetMouseClickLocation to view 2018-01-08 16:54:27 -05:00
Zachary Yedidia
b2a0745747 Update third-party licenses 2018-01-08 16:41:26 -05:00
Zachary Yedidia
7911ce1f16 Remove duplicate utf8 code 2018-01-08 16:38:59 -05:00
Zachary Yedidia
8bff7f00d0 Change docs to use true/false instead of on/off
Closes #976
2018-01-08 15:21:32 -05:00
Zachary Yedidia
957273fc92 Add railscast colorscheme
From https://github.com/pbsds/micro-railscast-theme
2018-01-07 21:02:24 -05:00
Zachary Yedidia
805d6ccaf7 Don't brace highlight with selection 2018-01-07 20:58:01 -05:00
Zachary Yedidia
fc2566a0de Add JumpToMatchingBrace action
This commit adds the JumpToMatchingBrace action which lets the cursor
jump to a matching brace if it is on one.

Closes #853
2018-01-07 16:17:22 -05:00
Zachary Yedidia
86c08bd747 Add brace highlighting
Use the 'matchbrace' option which is off by default.

Ref #853
2018-01-07 15:50:08 -05:00
Zachary Yedidia
0b47502e62 Fix minor issue with indent/outdent selection
Fixes #984
2018-01-06 16:04:18 -05:00
Zachary Yedidia
2afbcef825 Update readme 2018-01-05 23:05:20 -05:00
Zachary Yedidia
0a500be3ba Merge pull request #877 from IOAyman/readme
Added ToC in README
2018-01-05 23:04:52 -05:00
Zachary Yedidia
3b36316b00 Add support for selection and copy in terminal
This commit adds mouse and copy support in the terminal emulator
in micro.
2018-01-05 22:44:36 -05:00
Zachary Yedidia
d668050ebe Merge 2018-01-05 21:39:03 -05:00
Zachary Yedidia
dd47f167f1 Clean up terminal a bit and wait before closing 2018-01-05 21:38:40 -05:00
Zachary Yedidia
2ebeb9d5a5 Merge pull request #982 from sum01/syntax-touchups
Remove weird ignore on git-commit
2018-01-05 14:06:38 -05:00
sum01
8629357c70 Remove weird ignore on git-commit
It was needlessly highlighting everything that wasn't a comment.

Adds keyword detection for Github-esque issue-closing syntax.
Adds missing 'd' and 'drop' highlighting in git-rebase-todo
2018-01-05 03:41:50 -05:00
Zachary Yedidia
c8ff764467 Merge pull request #981 from sum01/fix_import
Fix #980 duplicate import
2018-01-04 22:29:27 -05:00
sum01
8e741599dc Fix #980 duplicate import 2018-01-04 22:27:09 -05:00
Zachary Yedidia
770cb87f7a Fix windows errors 2018-01-04 21:46:44 -05:00
Zachary Yedidia
d82867ee53 Add more comments 2018-01-04 17:14:51 -05:00
Zachary Yedidia
275bce7d69 Add new dependencies 2018-01-04 17:05:49 -05:00
Zachary Yedidia
9094c174cc Initial support for terminal within micro
This commit adds beta support for running a shell or other program
within a micro view.

Use the `> term` command. With no arguments, `term` will open your
shell in interactive mode. You can also run an arbitrary command
with `> term cmd` and the command with be executed and output
shown. One issue at the moment is the terminal window will close
immediately after the process dies.

No mouse events are sent to programs running within micro.

Ref #243
2018-01-04 17:03:08 -05:00
Zachary Yedidia
a814677b51 Improve command bar completion 2018-01-03 21:35:03 -05:00
Zachary Yedidia
8b60e4f3b1 Update colorscheme list in docs
Closes #956
2018-01-02 22:46:24 -05:00
Zachary Yedidia
c32f5a4859 Add basename option
Closes #903
2018-01-02 22:25:55 -05:00
Zachary Yedidia
df44f538fd Improve file save speed for large files 2018-01-02 18:36:29 -05:00
Zachary Yedidia
a4ae7a1e11 More command binding
Now can bind editable commands with `command-edit:`

Ref #974
2018-01-02 15:15:28 -05:00
Zachary Yedidia
70616b335e Merge 2018-01-02 15:03:10 -05:00
Zachary Yedidia
f6e9a16724 Allow binding commands
Bind commands with `command:...`

Ref #974
2018-01-02 15:02:46 -05:00
sum01
ac41e186a0 Add some Lua syntax (#962)
* Add some missing Lua string syntax
All Lua strings have the string functions inside of them.

'...you can use the string functions in object-oriented style'
See '6.4 – String Manipulation' in https://www.lua.org/manual/5.3/manual.html

* Lua - Highlight self and TODO/NOTE/FIXME

* Add Lua 'arg' and triple-dot syntax
2017-12-31 00:37:11 -05:00
Zachary Yedidia
a90cb64265 Merge pull request #971 from mbesancon/patch-1
added const for julia
2017-12-31 00:36:49 -05:00
Zachary Yedidia
5124dd04b3 Merge pull request #973 from sum01/micro_syntax
Fix micro (color) file syntax
2017-12-31 00:36:38 -05:00
sum01
7867d50d67 Fix micro file syntax
Some of the words were missing, so this adds those.
2017-12-30 12:13:52 -05:00
mbesancon
0ba60728e8 added const for julia
const is a base keyword
2017-12-29 13:08:21 -05:00
Zachary Yedidia
981263eb81 Merge 2017-12-28 16:05:53 -05:00
Zachary Yedidia
79deabbbd6 Fix options cmdline message
Ref #969
2017-12-28 16:05:35 -05:00
Zachary Yedidia
ba4b028076 Merge pull request #942 from motet-a/javascript-syntax
Improve JavaScript syntax highlighting
2017-12-28 14:57:49 -05:00
Zachary Yedidia
649e5799c2 Merge pull request #960 from nitsakh/insert-issue
Changes to add support for Insert Key Press
2017-12-28 14:54:31 -05:00
Zachary Yedidia
7339a88d68 Merge pull request #965 from tommyshem/ada
#964 add ada syntax highlighting file
2017-12-28 14:52:09 -05:00
tommy
b0cfb2e691 #964 add ada syntax 2017-12-27 14:27:42 +00:00
Zachary Yedidia
4e0d402cea Merge pull request #961 from sum01/fix_commit_syntax
Fix git-commit comment syntax
2017-12-22 23:06:37 -05:00
sum01
f882248f41 Fix git-commit comment syntax
A comment in a git-commit must have the hash at the start of the line, instead of just anywhere in the line.
2017-12-22 20:02:43 -05:00
Nitish Sakhawalkar
f58c5412a8 Updating to make overwrite mode as an action 2017-12-18 17:11:00 -08:00
Nitish Sakhawalkar
b0e4043513 Changes to add support for Insert Key Press 2017-12-18 13:28:21 -08:00
Antoine Motet
47dd65d4e5 Improve JavaScript syntax highlighting
- Sort keywords alphabetically
- Use `symbol.operator` for operators instead of `statement`
- Add a basic support for back-tick strings
- Mark unassigned keywords as errors
2017-12-17 23:53:58 +01:00
Tommy
fa84f6ddc3 create plugin folders work on windows fix #931 (#951) 2017-12-13 21:53:30 -05:00
Zachary Yedidia
2bf40f096e Don't autosave buffers with no path
Closes #955
2017-12-13 12:43:00 -05:00
Zachary Yedidia
4802403308 Remove android from actions_other build tag
Ref #949
2017-12-10 16:15:16 -05:00
Zachary Yedidia
e443adef31 Merge pull request #946 from tommyshem/luafix
fix lua comment block #929
2017-12-04 15:41:48 -05:00
tommy
cdb057dfc3 fix lua comment block 2017-12-04 20:30:35 +00:00
Zachary Yedidia
9da1ef178e Add support for setting local settings via filetype 2017-12-03 23:38:09 -05:00
Zachary Yedidia
bf33ab532c Store string keys for bindings 2017-12-03 23:15:32 -05:00
Zachary Yedidia
46c7437270 Fix ViewType refactor 2017-12-03 17:19:51 -05:00
Zachary Yedidia
09cab07352 Merge 2017-12-03 16:49:27 -05:00
Zachary Yedidia
b7214da4ea Make ViewType fields public
Ref #904
2017-12-03 16:49:05 -05:00
Bastien Traverse
5138ae2436 Fix typo in tutorial.md (#940)
Delete extraneous "plugins" word in line 12.
2017-12-03 15:16:50 -05:00
Zachary Yedidia
98778a80c2 Allow plugins to create view types
Closes #904
2017-12-03 15:15:07 -05:00
Zachary Yedidia
e0a8e90ad9 Merge 2017-12-03 13:05:50 -05:00
Zachary Yedidia
2ae9f88eaa Add showkey command 2017-12-03 13:05:46 -05:00
Tommy
ee8e022ccf stop version error when updating and option to disable builtin plugin. (#939) 2017-12-03 12:41:22 -05:00
Zachary Yedidia
3ca55f77a6 Merge 2017-12-01 20:39:30 -05:00
Zachary Yedidia
5f304db4a1 Update readme 2017-12-01 20:39:25 -05:00
Petr Shevtsov
93b8f10b02 Typo (#934) 2017-11-30 11:39:44 -05:00
Zachary Yedidia
bdb699211a Add raw command to view raw terminal esc codes 2017-11-29 01:06:16 -05:00
Zachary Yedidia
acd42df13c Fix panic on scroll
Fixes #932
2017-11-27 21:44:29 -05:00
Zachary Yedidia
5fc8f847a6 Improve command bar keybindings
The command bar now supports better keybindings:

CtrlA, CtrlLeft: start of line
CtrlE, CtrlRight: end of line
CtrlF, AltRight: next word
CtrlB, AltLeft: previous word
CtrlW, AltBackspace: delete previous word
CtrlV: Paste
Arrow keys as usual

These keybindings are not rebindable (maybe support will be added
in the future).
2017-11-24 14:43:26 -05:00
Zachary Yedidia
af6ef4f87f Minor comment improvement 2017-11-24 13:35:11 -05:00
Zachary Yedidia
7f287b62fb Fix autocomplete behavior for empty args
This also adds a modified version of go-shellwords as a dependency
and removes the dependency on the original go-shellwords.
2017-11-23 23:04:32 -05:00
Zachary Yedidia
36d72c4cab Move incomplete colorschemes and improve cd
The default colorschemes should ideally use 256 colors instead
of just 16 colors. The `simple` colorscheme should cover most
16 color use cases. I went through the colorschemes and put the themes
that didn't look good or looked incomplete in an in_progress directory.

This commit also improves the `cd` command behavior when using an
unnamed buffer.
2017-11-23 15:57:17 -05:00
Zachary Yedidia
71ee185b80 Check width before drawing cellview
Fixes #927
2017-11-23 14:44:07 -05:00
Zachary Yedidia
0360a2fcb5 Improve cmdbar parsing and add -l replace flag
The -l flag to the replace command means "literal" and will treat
the search term literally instead of as a regular expression.

The command bar also now supports expanding environment variables
and running expressions through the shell and using the result
in the command.
2017-11-22 13:54:39 -05:00
Zachary Yedidia
2ee7adb196 Support either io/ioutil or ioutil for lua import
Closes #923
2017-11-21 16:24:39 -05:00
Zachary Yedidia
d247db3e9d Implement retab command
Ref #919
2017-11-21 00:51:07 -05:00
Zachary Yedidia
e4c2f5d259 Merge pull request #891 from pranavraja/master
search: Only update lastSearch on ENTER
2017-11-19 15:47:05 -05:00
Zachary Yedidia
cc15df9307 Remove unnecessary authors file 2017-11-19 15:40:21 -05:00
Zachary Yedidia
812b547679 Merge pull request #613 from GeigerCounter/build_tools
Build tools
2017-11-19 15:39:32 -05:00
Zachary Yedidia
1c43bb572a Merge pull request #847 from sotpapathe/octave_support
Initial support for Octave/Matlab syntax highlighting
2017-11-18 16:56:31 -05:00
Zachary Yedidia
f96e9e9c1d Update lua go stdlib access documentation
Ref #912
2017-11-16 14:29:36 -05:00
Zachary Yedidia
7dfeda1ae5 Support .cljs and .cljc as clojure files
Fixes #911
2017-11-14 13:58:28 -05:00
Zachary Yedidia
d6ccaf0e41 Merge pull request #908 from FujiHaruka/patch-1
Update javascript.yaml
2017-11-08 00:28:09 -05:00
Zachary Yedidia
6b6fcc8ba0 Minor documentation update 2017-11-08 00:23:18 -05:00
Fuji Haruka
07bfcc9747 Update javascript.yaml
Add statements `async` and `await`.

Its status is stage 3 Draft.
https://tc39.github.io/ecmascript-asyncawait/#async-function-definitions
But I think it's usefull to add, because Node.js >= v7.6 support it.
2017-11-06 20:52:28 +09:00
Zachary Yedidia
423f4675d2 Add a scroll bar option
The option is `scrollbar` and is off by default. The scroll bar is
not interactive (you can't click and drag it) but this will likely
be fixed in the future.

Ref #869
2017-11-05 20:07:14 -05:00
Zachary Yedidia
c01ba97215 Add installation script instructions to readme 2017-10-31 16:16:57 -04:00
Zachary Yedidia
288717451f Fix typo in readme 2017-10-23 22:26:45 -04:00
Zachary Yedidia
a1f3499825 Fix issue with multicursor IDs
Fixes #899
2017-10-22 19:51:16 -04:00
Zachary Yedidia
63fa8fec41 Merge 2017-10-22 18:02:18 -04:00
Zachary Yedidia
b9e916999f Don't print error message if history file doesn't exist 2017-10-22 18:00:47 -04:00
Zachary Yedidia
afedad9977 Merge pull request #898 from TedSinger/master
savehistory bugfix
2017-10-22 11:59:43 -04:00
Ted Singer
d82ea2279d If the history file is unreadable or unparseable, Messenger.history remained nil, causing a panic on read.
Now in that case, we temporarily disable saving history and initialize history to empty, instead of nil
2017-10-21 18:59:11 -04:00
Zachary Yedidia
5b5998cf14 Merge 2017-10-21 15:32:34 -04:00
Zachary Yedidia
7b6430af1c Add savehistory option
When savehistory is enabled, micro will save your command history across
sessions. This includes command-mode, shell-mode, open, jump-to-line...
Anything that uses up-arrow for history in the infobar.

This option is on by default.

Closes #874
2017-10-21 15:31:04 -04:00
Zachary Yedidia
a0d475bebf Merge pull request #782 from i-amdroid/master
Added Twilight color scheme
2017-10-21 00:12:45 -04:00
therainingmonkey
31cd4b5795 Update Lua syntax (#893)
* Edited Lua syntax ('hash' is not a comment in Lua).

* Edited Lua syntax - hash (#) is a symbol in Lua (the length operator).
2017-10-21 00:10:46 -04:00
Zachary Yedidia
19ee4b281e Fix comment regex for shell filetype
Fixes #895
2017-10-20 23:57:49 -04:00
Zachary Yedidia
a171795654 Merge pull request #882 from onodera-punpun/ft
Add fish to ftoptions
2017-10-17 00:04:04 -04:00
Zachary Yedidia
98d8bfa879 Merge branch 'master' into ft 2017-10-17 00:03:57 -04:00
Pranav Raja
7bc2d870cd search: Only update lastSearch on ENTER
This has a few effects:

- `lastSearch` doesn't get overriden with partial searches
unnecessarily, which matches the behaviour of vim/emacs etc.

- Selecting a word, then pressing C-c C-f ENTER works better as you can
now use C-n and C-p to jump to more occurrences of what you just
searched for. Without this C-n would jump to what you searched for
*previously*.

- `lastSearch` will now be updated even if the search did not match -
again, this matches the behaviour of vim/emacs.
2017-10-16 17:44:44 +11:00
Zachary Yedidia
678819683a Merge 2017-10-15 15:35:54 -04:00
Zachary Yedidia
08e46f9112 Don't draw statusline if infobar is off and in use
Fixes #873
2017-10-15 15:35:19 -04:00
Zachary Yedidia
e071209add Merge pull request #890 from Jipok/patch-1
Use spaces for nim
2017-10-15 15:32:50 -04:00
Zachary Yedidia
74e79dc8f2 Merge pull request #880 from onodera-punpun/consistent
Alphabetically order options, format *.md files
2017-10-15 15:32:35 -04:00
Zachary Yedidia
955e8ffb08 Merge pull request #883 from onodera-punpun/lint
alphabetically order linters, add shell linter
2017-10-15 15:30:52 -04:00
Zachary Yedidia
b87a74711e Merge pull request #888 from matthewgraybosch/master
Update README.md
2017-10-15 15:30:35 -04:00
Jipok
ade0e9dd39 Use spaces for nim
From manual:
Nim's standard grammar describes an indentation sensitive language. This means that all the control structures are recognized by indentation. Indentation consists only of spaces; tabulators are not allowed.
2017-10-14 20:21:41 +05:00
Matthew Graybosch
f05f0b06ac Update README.md
Added information for OpenBSD. It works great there. 🤘
2017-10-12 13:52:47 -04:00
Camille Scholtz
f2006f592a alphabetically order linters, add shell linter 2017-10-11 17:47:23 +02:00
Camille Scholtz
5e66489836 Add fish to ftoptions 2017-10-11 17:02:37 +02:00
Camille Scholtz
9daa05d696 Use more consisten syntax in md files, format tp 80 collumns, fix some typos 2017-10-11 15:16:53 +02:00
Camille Scholtz
d76704839a alphabetically order options 2017-10-11 14:43:38 +02:00
Camille Scholtz
329669ce79 Make settings capitalization consistent 2017-10-11 14:22:23 +02:00
Ayman Nedjmeddine
4365b66398 Add a table of contents in the README 2017-10-10 19:37:28 +01:00
Zachary Yedidia
5af5140362 Merge pull request #876 from yannicka/setlocal-optionvaluecompletion
Add option value completion on setlocal
2017-10-08 14:52:50 -04:00
Yannick Armand
bf6ce3a17e Add option value completion on setlocal 2017-10-08 18:42:09 +02:00
Zachary Yedidia
e99fd1337e Update readme 2017-10-07 16:27:55 -04:00
Zachary Yedidia
17dac164ea Don't store cmd stdout in string
Storing the stdout confuses isatty causing programs running within
ShellMode to not format properly.

Fixes #862
2017-10-06 21:09:53 -04:00
Zachary Yedidia
b7c99c52d2 Update runtime 2017-10-06 20:43:14 -04:00
Zachary Yedidia
278aa6b050 Add docs for binding esc sequences 2017-10-06 20:42:58 -04:00
Zachary Yedidia
773c54a40d Support binding raw escapes codes 2017-10-06 14:03:35 -04:00
Zachary Yedidia
74589af1fc Revert "Update tcell to use gdamore's fix for idle wakeup"
This reverts commit f01ad3f726.
2017-10-06 13:21:53 -04:00
Zachary Yedidia
f01ad3f726 Update tcell to use gdamore's fix for idle wakeup
Note that you may encounter merge conflicts if you try to update. If you
do, remove the directory `cmd/micro/vendor/github.com/zyedidia/tcell`
and it will be recloned.
2017-10-06 13:03:43 -04:00
Zachary Yedidia
a0f3ec805d Merge 2017-10-06 11:00:31 -04:00
Zachary Yedidia
ea6012922f Add paren highlighting for js and update runtime 2017-10-06 10:59:43 -04:00
Zachary Yedidia
da33b59858 Merge pull request #868 from nicolasbd/patch-1
Support .es files and fix js parenthesis highlighting
2017-10-06 10:58:28 -04:00
Nicolas
9703d4f52f support es files and fix parenthesis highlighting
* This allows `micro` to use javascript syntax highlighting on `.es`, `.es6|7|8` files
* Fix parenthesis highlighting with @is73 regex, see #864
2017-10-06 16:29:49 +02:00
Zachary Yedidia
f3a30412f4 Merge pull request #858 from andreaTP/scalaSyntax
a couple more keywords to scala syntax
2017-10-04 15:15:18 -04:00
Zachary Yedidia
3116b082d8 Fix save and quit prompt 2017-10-04 12:11:20 -04:00
andrea
3e0a1b4517 a couple more keywords to scala syntax 2017-10-04 10:17:50 +01:00
Zachary Yedidia
ac3de065d9 Merge pull request #850 from nitsakh/feat-809
Implementation of Paragraph Feature
2017-10-03 23:49:57 -04:00
Zachary Yedidia
3e63ec74b9 Merge pull request #852 from popey/patch-3
Ensure snap is built with git version/tag info
2017-10-03 10:48:46 -04:00
Zachary Yedidia
c7334eb3b7 Fix sucmd option
Fixes #854
2017-10-03 10:48:07 -04:00
Alan Pope
dfbddd4b86 Ensure snap is built with git version/tag info
Changing from version: master to version: git will prevent the snap being built with the text 'master' as the version, but instead use the latest git tag or version info. This makes it easier to figure out which build is which in the store.
2017-10-03 12:50:47 +01:00
Zachary Yedidia
299416062f Merge 2017-10-02 23:44:58 -04:00
Zachary Yedidia
8b8fffb98d Add nano-style key menu option
Use the `keymenu` option (default `off`) to enable. ToggleKeyMenu is
also bound to `Alt-g` and this info is now displayed in the status line.

Closes #829
2017-10-02 23:44:11 -04:00
Nitish Sakhawalkar
ec221c0bc4 Implementation of Paragraph Feature
Changes to support moving cursor to next and previous paragraph
and updates to corresponding documentation
2017-10-02 19:54:57 -07:00
Zachary Yedidia
d27f8f9802 Merge pull request #846 from sotpapathe/patch_yaml_ftoptions
Added automatic tabs to spaces for yaml and updated readme
2017-10-02 14:05:14 -04:00
sotpapathe
c40c79427a Added initial support for Octave/Matlab syntax highlighting 2017-10-02 14:08:22 +04:00
sotpapathe
8a4f2193d8 Added automatic tabs to spaces for yaml and updated readme 2017-10-02 13:36:28 +04:00
Zachary Yedidia
aa667f6ba9 Merge pull request #845 from paykroyd/nit
grammar nit
2017-10-01 23:13:11 -04:00
Pete Aykroyd
d067de8150 grammar nit 2017-10-01 22:33:03 -04:00
Zachary Yedidia
b3559df543 Merge 2017-10-01 21:56:10 -04:00
Zachary Yedidia
f4e94d6d34 Add sucmd to customize "sudo" command
Fixes #833
2017-10-01 21:55:43 -04:00
Zachary Yedidia
13daa4e715 Merge pull request #842 from marius92mc/add-editorconfig-and-set-indent-size-to-4
Add .editorconfig and set indent_size to 4
2017-10-01 14:37:05 -04:00
marius92mc
75be4f5f61 Add .editorconfig and set indent_size to 4 2017-10-01 20:51:33 +03:00
Zachary Yedidia
46ced988eb Fix some golint warnings 2017-10-01 12:42:23 -04:00
Zachary Yedidia
28acfc6d3f Fix support for user-friendly plugin names
Fixes #840
2017-09-30 17:47:19 -04:00
Zachary Yedidia
660c7d3be5 Merge pull request #838 from yursan9/appstream
Update Appstream
2017-09-29 23:54:28 -04:00
Yurizal Susanto
52617bd5a8 Update Appstream 2017-09-30 10:12:53 +07:00
Zachary Yedidia
9db181037f Merge 2017-09-29 13:46:54 -04:00
Zachary Yedidia
861ea5aabc Update readme 2017-09-29 13:46:51 -04:00
Zachary Yedidia
e4125c0c6a Merge pull request #835 from andreaTP/jsSyntax
few more keywords for js syntax
2017-09-29 13:39:04 -04:00
andrea
ff9a8a1247 few more keywords for js syntax 2017-09-29 16:20:38 +01:00
Zachary Yedidia
ac29e30f54 Update readme 2017-09-28 19:38:24 -04:00
Zachary Yedidia
a02ae3ceed Replace home directory before performing SaveAs
Fixes #820
2017-09-26 22:55:06 -04:00
MrSndmn
54c02f4781 Perl syntax highlighting fix (#818)
* Perl syntax highlighting fix

* Useless escapes removed
2017-09-24 11:59:57 -04:00
Zachary Yedidia
a5e721b107 Set fastdirty on for files larger than 50kb 2017-09-23 21:18:37 -04:00
Zachary Yedidia
12a4dd58f3 Only replace '~' with home if at start of path
Ref #757
2017-09-23 20:56:08 -04:00
Zachary Yedidia
5a7ddb8330 Add autocompletion for option values
Closes #555
2017-09-23 20:47:19 -04:00
Zachary Yedidia
cb75531818 Make mouse option global option
Fixes #816
2017-09-21 17:10:53 -04:00
Zachary Yedidia
6229a0579f Update tcell
The latest commit to tcell should fix behavior for large pastes.

Fixes #815
2017-09-19 13:21:09 -04:00
Zachary Yedidia
fb980bb695 Add option for very accurate dirty flag
Set the `fastdirty` option flag to off if you really want accurate
reporting on whether the buffer is modified. This is more resource
intensive but it can be useful for people who don't mind.

Closes #787
Closes #467
2017-09-17 23:33:18 -04:00
Zachary Yedidia
19dc9d7bbc Fix options and make usage text much more readable
Now micro -h will just show you the important information and if you
want to see each individual option's help text use micro -options.
2017-09-17 22:11:26 -04:00
Zachary Yedidia
1e55b6f6b3 Only register double click on equal mouse location 2017-09-17 18:31:32 -04:00
Zachary Yedidia
0a35bfe2f5 Update readme 2017-09-15 16:25:01 -04:00
Zachary Yedidia
2f587c6d48 Fix moving to end of line on cursor down 2017-09-15 16:09:33 -04:00
Zachary Yedidia
5b426aee86 Update tcell 2017-09-15 14:15:21 -04:00
Zachary Yedidia
f700769b27 Update tcell 2017-09-15 13:33:06 -04:00
Zachary Yedidia
04b672eebe Update tcell 2017-09-15 10:50:56 -04:00
Zachary Yedidia
f7238e8e53 Update tcell 2017-09-14 17:29:25 -04:00
Zachary Yedidia
33cb39d318 Use type.keyword instead of keyword
Some syntax files used keyword from an old version when they should have
been using type.keyword.

Fixes #811
2017-09-13 18:00:47 -04:00
Zachary Yedidia
612658d9c4 Add documentation for new lua functions 2017-09-11 12:23:19 -04:00
Zachary Yedidia
c31613b2c7 Add --config-dir option 2017-09-10 23:20:21 -04:00
Zachary Yedidia
d7419d213a Merge branch 'better-lua' 2017-09-10 22:22:31 -04:00
Zachary Yedidia
67a3f86cc9 Update tcell 2017-09-10 17:21:37 -04:00
Zachary Yedidia
b0c0747a09 Merge 2017-09-10 14:16:35 -04:00
Zachary Yedidia
64d574c35c Give Lua access to most of the Go stdlib 2017-09-10 14:16:28 -04:00
Zachary Yedidia
4e531c2d1e Update tcell 2017-09-10 12:47:24 -04:00
Zachary Yedidia
ee8fa60bc5 Give Lua access to most of the Go stdlib 2017-09-09 17:37:08 -04:00
Zachary Yedidia
e40ff56e07 Merge pull request #805 from techtonik/patch-2
Allow GitHub to detect license
2017-09-08 23:19:32 -04:00
Zachary Yedidia
0c1db1e813 Escape regex chars in selections for multicursors
Fixes #808
2017-09-08 19:16:14 -04:00
Zachary Yedidia
a9b14d4c1b Add note about NaCl deprecation 2017-09-08 19:00:02 -04:00
Zachary Yedidia
69f77ee2f1 Set OrigSelection during search
Fixes #807
2017-09-08 18:57:24 -04:00
anatoly techtonik
3b5b9bbb21 Allow GitHub to detect license
It will appear in commits/releases line
2017-09-08 16:23:07 +03:00
Zachary Yedidia
65b5d6c5a9 Handle zip files that do not list directories
Some zip files do not mark the subdirectories in them as "files"
but they still need to be created.

Fixes #803
2017-09-07 17:02:26 -04:00
Zachary Yedidia
1a575bc9ae Fix UpN if proposedY < 0
Fixes #804
2017-09-07 16:44:05 -04:00
Zachary Yedidia
ab242c5b17 Update tcell 2017-09-06 16:41:09 -04:00
Zachary Yedidia
90977fb4e1 Add mouse option to allow disabling mouse support 2017-09-06 15:50:50 -04:00
Zachary Yedidia
404e5d206d Fix autoclose plugin cursor position 2017-09-05 18:36:42 -04:00
Zachary Yedidia
d41a255361 Fix undo of MultipleReplace
Fixes #799
2017-09-04 16:21:08 -04:00
Zachary Yedidia
efff850e54 Clear cursors before performing undo or redo
This fix isn't ideal because it removes the cursors but it does work

Fixes #798
2017-09-04 15:47:24 -04:00
Zachary Yedidia
4fcdde4149 Merge pull request #796 from tommyshem/remove-offset
missing line number offset to current line highlighting for loop. Fix #795
2017-09-02 10:30:39 -04:00
Tommy
c4d8b9e7fb add missing new lua command WorkingDirectory to docs (#797)
* add missing new lua command WorkingDirectory to docs

* added messenger.AddLog to doc and changed lua commands from . to :
2017-09-02 10:30:19 -04:00
tommy
68526dc119 add missing offset to current highlighting line. Fix #795 2017-09-02 09:40:35 +01:00
Tommy
4f2fc096e5 add comment and let multi values to the log to make it easy to log. (#788)
* add comment and let multi values to the log to make it easy to log.

* added WorkingDirectory to lua bindings (returns the current directory)
2017-08-27 12:18:56 -04:00
Zachary Yedidia
0f62ef687c Proper bounds check
Closes #791
2017-08-24 14:56:46 -04:00
Zachary Yedidia
32eb1135ed Update runtime 2017-08-24 13:15:17 -04:00
Zachary Yedidia
f88b4a6d57 Merge 2017-08-24 13:15:01 -04:00
Zachary Yedidia
9628b73525 Add support for switching between crlf and lf
Dos and Unix line endings are now both supported (previously on unix
line endings were supported) and can be accessed via the `fileformat`
option. The file format will be automatically detected and displayed in
the statusline but can be overriden.

Possible values for the `fileformat` option are `dos` and `unix`.

Closes #443
Closes #755
2017-08-24 13:13:14 -04:00
Zachary Yedidia
d70a48bd13 Improve julia syntax file
Ref #781
2017-08-14 15:18:48 -04:00
Zachary Yedidia
660f1e181a Add julia rules
Fixes #781
2017-08-14 11:59:29 -04:00
Zachary Yedidia
773284369b Improve yaml string highlighting
Fixes #783
2017-08-14 11:28:10 -04:00
Zachary Yedidia
f199c15269 Merge pull request #780 from evandandrea/snap-badge
Add a snap build badge
2017-08-13 11:23:33 -04:00
Andrey Yurtaev
e7facd74ba Added Twilight color scheme 2017-08-13 14:47:43 +03:00
Evan
47e612afef Add a snap build badge 2017-08-10 11:16:08 +01:00
Yannick A
921f88b95d Cursor move at the first char when Up on the first line (and at the last char when Down on last the line) (#773)
* Cursor move at the beginning of the line when Up on the first line (and at the last character when Down on last the line)

* Fix an issue when Up to a shorter line than current line
2017-08-09 11:58:37 -04:00
Yannick A
7fe8d73473 Fix somes selections issues (#771)
* SelectLeft on the last character is now possible

* SelectLeft on the last character is now possible

* CursorRight on selection places the cursor after the last selected character

* SelectRight on the last character do not select the last character and do nothing
2017-08-09 11:52:38 -04:00
Zachary Yedidia
fcb09556b1 Merge pull request #776 from tommyshem/syntax-checker
make syntax_checker work with error messages
2017-08-09 11:51:09 -04:00
Zachary Yedidia
69c6d8a099 Fix some lint problems 2017-08-08 11:30:09 -04:00
Zachary Yedidia
dd5afc0560 Update readme 2017-08-08 11:08:57 -04:00
Zachary Yedidia
471486b531 Add newline after build tags 2017-08-07 09:47:28 -04:00
Zachary Yedidia
202cfb574c Only write settings if no error when reading
This commit also switches from yosuke-furukawa/json5 to flynn/json5
because yosuke-furukawa/json5 does not provide a license.

Ref zyedidia/json5#1

Closes #775
2017-08-06 12:02:17 -04:00
tommy
ebb0976866 make syntax_checker work with error messages 2017-08-06 08:25:19 +01:00
Zachary Yedidia
5c785ab1ac Fix MoveLines when user has a selection
Ref #767
2017-08-03 12:42:40 -04:00
Zachary Yedidia
2024b8b2c2 Merge pull request #767 from yannicka/master
Move current line down/up: cursor stay with moved line
2017-08-03 12:40:50 -04:00
Zachary Yedidia
86c695ca52 Merge pull request #758 from r15ch13/scoop
Add Scoop package manager list in readme
2017-08-03 12:17:36 -04:00
Yannick Armand
9dd1df36d5 Move current line down/up: cursor stay with moved line 2017-08-03 12:02:07 +02:00
Zachary Yedidia
27f99b6309 Merge pull request #764 from yannicka/master
OutdentSelection works on all cases (whatever tabsize or tabstospace values)
2017-08-01 21:15:16 -04:00
Yannick Armand
ed9bc66060 OutdentSelection works on all cases (whatever tabsize or tabstospace values) 2017-08-01 22:42:42 +02:00
Zachary Yedidia
d1598bb754 Merge pull request #762 from yannicka/master
Display number of lines in the JumpLine prompt
2017-08-01 12:03:51 -04:00
Yannick Armand
cc5855d07b Display number of lines in the JumpLine prompt 2017-08-01 16:41:57 +02:00
Zachary Yedidia
487b36f48f Update tcell
Ref #761
2017-08-01 10:12:10 -04:00
Zachary Yedidia
c0b00c9a4c Update tcell 2017-07-31 22:47:08 -04:00
Zachary Yedidia
86c2ac95bb Merge cursors after spawning with mouse
Fixes #760
2017-07-31 22:30:57 -04:00
Zachary Yedidia
edb79f2972 Merge 2017-07-31 22:24:46 -04:00
Zachary Yedidia
305cefe461 Update tcell 2017-07-31 22:24:42 -04:00
Zachary Yedidia
315391b0aa Expand support for Suspend to OSX, BSD, Solaris 2017-07-31 20:05:34 -04:00
Zachary Yedidia
fd45acc910 Remove unused variables 2017-07-31 18:46:52 -04:00
Richard Kuhnt
a574ae6b6a Add Scoop package manager list in readme 2017-07-30 13:51:17 +02:00
Zachary Yedidia
fa56a477c2 Merge 2017-07-29 14:45:53 -04:00
Zachary Yedidia
0db4556efb Update snap installation instructions 2017-07-29 14:45:49 -04:00
GeigerCounter
e6797e0303 Packaging scripts passed install test. 2017-03-31 09:26:12 -04:00
GeigerCounter
6041e063e2 Added script for rpm and template rpmspec 2017-03-31 05:22:32 -04:00
GeigerCounter
c3861955e0 Added arm packaging to the scripts 2017-03-29 11:31:58 -04:00
GeigerCounter
4a45e69eb1 Properly include the man page 2017-03-29 08:56:25 -04:00
GeigerCounter
e52d05113e Added AUTHORS file for documentation 2017-03-29 08:42:28 -04:00
GeigerCounter
45992a0e0a Fixed mistake in desktop file 2017-03-29 08:39:50 -04:00
GeigerCounter
1fb405afd3 Tweaked build-deb script 2017-03-29 07:28:14 -04:00
GeigerCounter
e23d4d8fa1 Added scalable logo for packaging. 2017-03-29 05:36:09 -05:00
GeigerCounter
d8aab386f1 Stuff and things and stuff. 2017-03-28 14:05:17 -04:00
GeigerCounter
7d422bfae2 Merge branch 'master' of https://github.com/zyedidia/micro into build_tools 2017-03-28 13:40:26 -04:00
GeigerCounter
7bc870e72f Added a desktop specification. ( micro.desktop ) 2017-03-28 13:39:47 -04:00
GeigerCounter
edee53f6f2 Added rpm build script ( Untested. ) 2017-03-28 13:37:41 -04:00
135 changed files with 7950 additions and 3124 deletions

7
.editorconfig Normal file
View File

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

21
.gitmodules vendored
View File

@@ -31,9 +31,6 @@
[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/json5"]
path = cmd/micro/vendor/github.com/zyedidia/json5
url = https://github.com/zyedidia/json5
[submodule "cmd/micro/vendor/github.com/zyedidia/tcell"]
path = cmd/micro/vendor/github.com/zyedidia/tcell
url = https://github.com/zyedidia/tcell
@@ -55,3 +52,21 @@
[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

View File

@@ -1,6 +1,6 @@
Micro is licensed under the MIT "Expat" License:
MIT License
Copyright (c) 2016: Zachary Yedidia, et al.
Copyright (c) 2016-2017: 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

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

View File

@@ -4,6 +4,7 @@
[![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)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
[![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.
@@ -19,6 +20,22 @@ To see more screenshots of micro, showcasing all of the default colorschemes, se
You can also check out the website for Micro at https://micro-editor.github.io.
# Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Prebuilt binaries](#prebuilt-binaries)
- [Package Managers](#package-managers)
- [Building from source](#building-from-source)
- [MacOS terminal](#macos-terminal)
- [Linux clipboard support](#linux-clipboard-support)
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
- [Plan9, Cygwin](#plan9-cygwin)
- [Usage](#usage)
- [Documentation and Help](#documentation-and-help)
- [Contributing](#contributing)
- - -
# Features
* Easy to use and to install
@@ -29,6 +46,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
* Sane defaults
* You shouldn't have to configure much out of the box (and it is extremely easy to configure)
* Splits and tabs
* Nano-like menu to help you remember the keybindings
* Extremely good mouse support
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
* Cross platform (It should work on all the platforms Go runs on)
@@ -49,7 +67,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
* Macros
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)), and multiple cursors ([#5](https://github.com/zyedidia/micro/issues/5)) in the future.
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
@@ -68,7 +86,19 @@ 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`.
### Package Managers
### Installation script
There is a great script which can install micro for you by downloading the latest prebuilt binary. You can find it at https://getmic.ro (the github repo for it is [here](https://github.com/benweissmann/getmic.ro)).
Then you can easily install micro:
$ curl https://getmic.ro | bash
The script will install the micro binary to the current directory.
See the [Github page](https://github.com/benweissmann/getmic.ro) for more information.
### Package managers
You can install micro using Homebrew on Mac:
@@ -76,23 +106,35 @@ You can install micro using Homebrew on Mac:
brew install micro
```
On Windows, you can install micro through Chocolatey:
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 --beta
snap install micro --classic
```
On OpenBSD, micro is available in the ports tree. It is also available as a binary package.
```
pkg_add -v micro
```
### 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 recommand setting it to `~/go` if you don't have one).
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommend setting it to `~/go` if you don't have one).
```
go get -d github.com/zyedidia/micro/cmd/micro
@@ -108,7 +150,7 @@ You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd
### MacOS terminal
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. The newest versions also support true color.
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Load Preset`. The newest versions also support true color.
### Linux clipboard support
@@ -136,11 +178,11 @@ 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.
### Plan9, NaCl, Cygwin
### Plan9, Cygwin
Please note that 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, NaCl, and Cygwin (although this may change in the future).
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).
# Usage

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

63
assets/logo.svg Normal file
View File

@@ -0,0 +1,63 @@
<?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>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 KiB

After

Width:  |  Height:  |  Size: 68 KiB

73
assets/packaging/micro.1 Normal file
View File

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

View File

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

View File

@@ -1,13 +1,17 @@
package main
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/cmd/micro/shellwords"
"github.com/zyedidia/tcell"
)
@@ -76,7 +80,7 @@ func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
v.Cursor.ResetSelection()
v.Relocate()
}
if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold && (x == v.lastLoc.X && y == v.lastLoc.Y) {
if v.doubleClick {
// Triple click
v.lastClickTime = time.Now()
@@ -117,6 +121,8 @@ func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
}
}
v.lastLoc = Loc{x, y}
if usePlugin {
PostActionCall("MousePress", v, e)
}
@@ -248,7 +254,7 @@ func (v *View) CursorRight(usePlugin bool) bool {
}
if v.Cursor.HasSelection() {
v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
v.Cursor.Loc = v.Cursor.CurSelection[1]
v.Cursor.ResetSelection()
v.Cursor.StoreVisualX()
} else {
@@ -346,7 +352,7 @@ func (v *View) SelectLeft(usePlugin bool) bool {
}
loc := v.Cursor.Loc
count := v.Buf.End().Move(-1, v.Buf)
count := v.Buf.End()
if loc.GreaterThan(count) {
loc = count
}
@@ -369,7 +375,7 @@ func (v *View) SelectRight(usePlugin bool) bool {
}
loc := v.Cursor.Loc
count := v.Buf.End().Move(-1, v.Buf)
count := v.Buf.End()
if loc.GreaterThan(count) {
loc = count
}
@@ -429,7 +435,11 @@ func (v *View) StartOfLine(usePlugin bool) bool {
v.deselect(0)
v.Cursor.Start()
if v.Cursor.X != 0 {
v.Cursor.Start()
} else {
v.Cursor.StartOfText()
}
if usePlugin {
return PostActionCall("StartOfLine", v)
@@ -453,6 +463,20 @@ func (v *View) EndOfLine(usePlugin bool) bool {
return true
}
// SelectLine selects the entire current line
func (v *View) SelectLine(usePlugin bool) bool {
if usePlugin && !PreActionCall("SelectLine", v) {
return false
}
v.Cursor.SelectLine()
if usePlugin {
return PostActionCall("SelectLine", v)
}
return true
}
// SelectToStartOfLine selects to the start of the current line
func (v *View) SelectToStartOfLine(usePlugin bool) bool {
if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
@@ -489,6 +513,91 @@ func (v *View) SelectToEndOfLine(usePlugin bool) bool {
return true
}
// ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
func (v *View) ParagraphPrevious(usePlugin bool) bool {
if usePlugin && !PreActionCall("ParagraphPrevious", v) {
return false
}
var line int
for line = v.Cursor.Y; line > 0; line-- {
if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
v.Cursor.X = 0
v.Cursor.Y = line
break
}
}
// If no empty line found. move cursor to end of buffer
if line == 0 {
v.Cursor.Loc = v.Buf.Start()
}
if usePlugin {
return PostActionCall("ParagraphPrevious", v)
}
return true
}
// ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
func (v *View) ParagraphNext(usePlugin bool) bool {
if usePlugin && !PreActionCall("ParagraphNext", v) {
return false
}
var line int
for line = v.Cursor.Y; line < len(v.Buf.lines); line++ {
if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
v.Cursor.X = 0
v.Cursor.Y = line
break
}
}
// If no empty line found. move cursor to end of buffer
if line == len(v.Buf.lines) {
v.Cursor.Loc = v.Buf.End()
}
if usePlugin {
return PostActionCall("ParagraphNext", v)
}
return true
}
// Retab changes all tabs to spaces or all spaces to tabs depending
// on the user's settings
func (v *View) Retab(usePlugin bool) bool {
if usePlugin && !PreActionCall("Retab", v) {
return false
}
toSpaces := v.Buf.Settings["tabstospaces"].(bool)
tabsize := int(v.Buf.Settings["tabsize"].(float64))
dirty := false
for i := 0; i < v.Buf.NumLines; i++ {
l := v.Buf.Line(i)
ws := GetLeadingWhitespace(l)
if ws != "" {
if toSpaces {
ws = strings.Replace(ws, "\t", Spaces(tabsize), -1)
} else {
ws = strings.Replace(ws, Spaces(tabsize), "\t", -1)
}
}
l = strings.TrimLeft(l, " \t")
v.Buf.lines[i].data = []byte(ws + l)
dirty = true
}
v.Buf.IsModified = dirty
if usePlugin {
return PostActionCall("Retab", v)
}
return true
}
// CursorStart moves the cursor to the start of the buffer
func (v *View) CursorStart(usePlugin bool) bool {
if usePlugin && !PreActionCall("CursorStart", v) {
@@ -591,10 +700,14 @@ func (v *View) InsertNewline(usePlugin bool) bool {
}
ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
cx := v.Cursor.X
v.Buf.Insert(v.Cursor.Loc, "\n")
// v.Cursor.Right()
if v.Buf.Settings["autoindent"].(bool) {
if cx < len(ws) {
ws = ws[0:cx]
}
v.Buf.Insert(v.Cursor.Loc, ws)
// for i := 0; i < len(ws); i++ {
// v.Cursor.Right()
@@ -634,9 +747,9 @@ func (v *View) Backspace(usePlugin bool) bool {
// If the user is using spaces instead of tabs and they are deleting
// whitespace at the start of the line, we should delete as if it's a
// tab (tabSize number of spaces)
lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
lineStart := sliceEnd(v.Buf.LineBytes(v.Cursor.Y), v.Cursor.X)
tabSize := int(v.Buf.Settings["tabsize"].(float64))
if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && utf8.RuneCount(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
loc := v.Cursor.Loc
v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
} else {
@@ -721,13 +834,15 @@ func (v *View) IndentSelection(usePlugin bool) bool {
end := v.Cursor.CurSelection[1]
if end.Y < start.Y {
start, end = end, start
v.Cursor.SetSelectionStart(start)
v.Cursor.SetSelectionEnd(end)
}
startY := start.Y
endY := end.Move(-1, v.Buf).Y
endX := end.Move(-1, v.Buf).X
tabsize := len(v.Buf.IndentString())
for y := startY; y <= endY; y++ {
tabsize := len(v.Buf.IndentString())
v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
if y == startY && start.X > 0 {
v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
@@ -781,23 +896,18 @@ func (v *View) OutdentSelection(usePlugin bool) bool {
end := v.Cursor.CurSelection[1]
if end.Y < start.Y {
start, end = end, start
v.Cursor.SetSelectionStart(start)
v.Cursor.SetSelectionEnd(end)
}
startY := start.Y
endY := end.Move(-1, v.Buf).Y
endX := end.Move(-1, v.Buf).X
for y := startY; y <= endY; y++ {
for x := 0; x < len(v.Buf.IndentString()); x++ {
if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
break
}
v.Buf.Remove(Loc{0, y}, Loc{1, y})
if y == startY && start.X > 0 {
v.Cursor.SetSelectionStart(start.Move(-1, v.Buf))
}
if y == endY {
v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
}
}
}
v.Cursor.Relocate()
@@ -841,7 +951,7 @@ func (v *View) SaveAll(usePlugin bool) bool {
}
for _, t := range tabs {
for _, v := range t.views {
for _, v := range t.Views {
v.Save(false)
}
}
@@ -860,7 +970,7 @@ func (v *View) Save(usePlugin bool) bool {
return false
}
if v.Type.scratch == true {
if v.Type.Scratch == true {
// We can't save any view type with scratch set. eg help and log text
return false
}
@@ -910,19 +1020,24 @@ func (v *View) saveToFile(filename string) {
// SaveAs saves the buffer to disk with the given name
func (v *View) SaveAs(usePlugin bool) bool {
if v.mainCursor() {
if usePlugin && !PreActionCall("Find", v) {
if usePlugin && !PreActionCall("SaveAs", v) {
return false
}
filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
if !canceled {
// the filename might or might not be quoted, so unquote first then join the strings.
filename = strings.Join(SplitCommandArgs(filename), " ")
args, err := shellwords.Split(filename)
filename = strings.Join(args, " ")
if err != nil {
messenger.Error("Error parsing arguments: ", err)
return false
}
v.saveToFile(filename)
}
if usePlugin {
PostActionCall("Find", v)
PostActionCall("SaveAs", v)
}
}
return false
@@ -1002,6 +1117,10 @@ func (v *View) Undo(usePlugin bool) bool {
return false
}
if v.Buf.curCursor == 0 {
v.Buf.clearCursors()
}
v.Buf.Undo()
messenger.Message("Undid action")
@@ -1017,6 +1136,10 @@ func (v *View) Redo(usePlugin bool) bool {
return false
}
if v.Buf.curCursor == 0 {
v.Buf.clearCursors()
}
v.Buf.Redo()
messenger.Message("Redid action")
@@ -1096,9 +1219,9 @@ func (v *View) Cut(usePlugin bool) bool {
return PostActionCall("Cut", v)
}
return true
} else {
return v.CutLine(usePlugin)
}
return false
}
// DuplicateLine duplicates the current line or selection
@@ -1154,12 +1277,16 @@ func (v *View) MoveLinesUp(usePlugin bool) bool {
messenger.Message("Can not move further up")
return true
}
start := v.Cursor.CurSelection[0].Y
end := v.Cursor.CurSelection[1].Y
if start > end {
end, start = start, end
}
v.Buf.MoveLinesUp(
v.Cursor.CurSelection[0].Y,
v.Cursor.CurSelection[1].Y,
start,
end,
)
v.Cursor.UpN(1)
v.Cursor.CurSelection[0].Y -= 1
v.Cursor.CurSelection[1].Y -= 1
messenger.Message("Moved up selected line(s)")
} else {
@@ -1171,7 +1298,6 @@ func (v *View) MoveLinesUp(usePlugin bool) bool {
v.Cursor.Loc.Y,
v.Cursor.Loc.Y+1,
)
v.Cursor.UpN(1)
messenger.Message("Moved up current line")
}
v.Buf.IsModified = true
@@ -1193,13 +1319,16 @@ func (v *View) MoveLinesDown(usePlugin bool) bool {
messenger.Message("Can not move further down")
return true
}
start := v.Cursor.CurSelection[0].Y
end := v.Cursor.CurSelection[1].Y
if start > end {
end, start = start, end
}
v.Buf.MoveLinesDown(
v.Cursor.CurSelection[0].Y,
v.Cursor.CurSelection[1].Y,
start,
end,
)
v.Cursor.DownN(1)
v.Cursor.CurSelection[0].Y += 1
v.Cursor.CurSelection[1].Y += 1
messenger.Message("Moved down selected line(s)")
} else {
if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
@@ -1210,7 +1339,6 @@ func (v *View) MoveLinesDown(usePlugin bool) bool {
v.Cursor.Loc.Y,
v.Cursor.Loc.Y+1,
)
v.Cursor.DownN(1)
messenger.Message("Moved down current line")
}
v.Buf.IsModified = true
@@ -1252,6 +1380,27 @@ func (v *View) PastePrimary(usePlugin bool) bool {
return true
}
// JumpToMatchingBrace moves the cursor to the matching brace if it is
// currently on a brace
func (v *View) JumpToMatchingBrace(usePlugin bool) bool {
if usePlugin && !PreActionCall("JumpToMatchingBrace", v) {
return false
}
for _, bp := range bracePairs {
r := v.Cursor.RuneUnder(v.Cursor.X)
if r == bp[0] || r == bp[1] {
matchingBrace := v.Buf.FindMatchingBrace(bp, v.Cursor.Loc)
v.Cursor.GotoLoc(matchingBrace)
}
}
if usePlugin {
return PostActionCall("JumpToMatchingBrace", v)
}
return true
}
// SelectAll selects the entire buffer
func (v *View) SelectAll(usePlugin bool) bool {
if usePlugin && !PreActionCall("SelectAll", v) {
@@ -1479,20 +1628,38 @@ func (v *View) JumpLine(usePlugin bool) bool {
}
// Prompt for line number
linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
message := fmt.Sprintf("Jump to line:col (1 - %v) # ", v.Buf.NumLines)
input, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
if canceled {
return false
}
lineint, err := strconv.Atoi(linestring)
lineint = lineint - 1 // fix offset
if err != nil {
messenger.Error(err) // return errors
return false
var lineInt int
var colInt int
var err error
if strings.Contains(input, ":") {
split := strings.Split(input, ":")
lineInt, err = strconv.Atoi(split[0])
if err != nil {
messenger.Message("Invalid line number")
return false
}
colInt, err = strconv.Atoi(split[1])
if err != nil {
messenger.Message("Invalid column number")
return false
}
} else {
lineInt, err = strconv.Atoi(input)
if err != nil {
messenger.Message("Invalid line number")
return false
}
}
lineInt--
// Move cursor and view if possible.
if lineint < v.Buf.NumLines && lineint >= 0 {
v.Cursor.X = 0
v.Cursor.Y = lineint
if lineInt < v.Buf.NumLines && lineInt >= 0 {
v.Cursor.X = colInt
v.Cursor.Y = lineInt
if usePlugin {
return PostActionCall("JumpLine", v)
@@ -1540,6 +1707,25 @@ func (v *View) ToggleHelp(usePlugin bool) bool {
return true
}
// ToggleKeyMenu toggles the keymenu option and resizes all tabs
func (v *View) ToggleKeyMenu(usePlugin bool) bool {
if v.mainCursor() {
if usePlugin && !PreActionCall("ToggleBindings", v) {
return false
}
globalSettings["keymenu"] = !globalSettings["keymenu"].(bool)
for _, tab := range tabs {
tab.Resize()
}
if usePlugin {
return PostActionCall("ToggleBindings", v)
}
}
return true
}
// ShellMode opens a terminal to run a shell command
func (v *View) ShellMode(usePlugin bool) bool {
if v.mainCursor() {
@@ -1578,6 +1764,22 @@ func (v *View) CommandMode(usePlugin bool) bool {
return false
}
// ToggleOverwriteMode lets the user toggle the text overwrite mode
func (v *View) ToggleOverwriteMode(usePlugin bool) bool {
if v.mainCursor() {
if usePlugin && !PreActionCall("ToggleOverwriteMode", v) {
return false
}
v.isOverwriteMode = !v.isOverwriteMode
if usePlugin {
return PostActionCall("ToggleOverwriteMode", v)
}
}
return false
}
// Escape leaves current mode
func (v *View) Escape(usePlugin bool) bool {
if v.mainCursor() {
@@ -1606,12 +1808,12 @@ func (v *View) Quit(usePlugin bool) bool {
// Make sure not to quit if there are unsaved changes
if v.CanClose() {
v.CloseBuffer()
if len(tabs[curTab].views) > 1 {
if len(tabs[curTab].Views) > 1 {
v.splitNode.Delete()
tabs[v.TabNum].Cleanup()
tabs[v.TabNum].Resize()
} else if len(tabs) > 1 {
if len(tabs[v.TabNum].views) == 1 {
if len(tabs[v.TabNum].Views) == 1 {
tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
for i, t := range tabs {
t.SetNum(i)
@@ -1629,6 +1831,7 @@ func (v *View) Quit(usePlugin bool) bool {
}
screen.Fini()
messenger.SaveHistory()
os.Exit(0)
}
}
@@ -1649,7 +1852,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
closeAll := true
for _, tab := range tabs {
for _, v := range tab.views {
for _, v := range tab.Views {
if !v.CanClose() {
closeAll = false
}
@@ -1662,7 +1865,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
if shouldQuit {
for _, tab := range tabs {
for _, v := range tab.views {
for _, v := range tab.Views {
v.CloseBuffer()
}
}
@@ -1672,6 +1875,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
}
screen.Fini()
messenger.SaveHistory()
os.Exit(0)
}
}
@@ -1693,7 +1897,7 @@ func (v *View) AddTab(usePlugin bool) bool {
curTab = len(tabs) - 1
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.views {
for _, v := range t.Views {
v.ToggleTabbar()
}
}
@@ -1786,8 +1990,8 @@ func (v *View) Unsplit(usePlugin bool) bool {
}
curView := tabs[curTab].CurView
for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
view := tabs[curTab].views[i]
for i := len(tabs[curTab].Views) - 1; i >= 0; i-- {
view := tabs[curTab].Views[i]
if view != nil && view.Num != curView {
view.Quit(true)
// messenger.Message("Quit ", view.Buf.Path)
@@ -1809,7 +2013,7 @@ func (v *View) NextSplit(usePlugin bool) bool {
}
tab := tabs[curTab]
if tab.CurView < len(tab.views)-1 {
if tab.CurView < len(tab.Views)-1 {
tab.CurView++
} else {
tab.CurView = 0
@@ -1833,7 +2037,7 @@ func (v *View) PreviousSplit(usePlugin bool) bool {
if tab.CurView > 0 {
tab.CurView--
} else {
tab.CurView = len(tab.views) - 1
tab.CurView = len(tab.Views) - 1
}
if usePlugin {
@@ -1903,7 +2107,7 @@ func (v *View) PlayMacro(usePlugin bool) bool {
return true
}
// SpawnMultiCursor creates a new multiple cursor at the next occurence of the current selection or current word
// SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
func (v *View) SpawnMultiCursor(usePlugin bool) bool {
spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
// You can only spawn a cursor from the main cursor
@@ -1923,7 +2127,7 @@ func (v *View) SpawnMultiCursor(usePlugin bool) bool {
searchStart = spawner.CurSelection[1]
v.Cursor = c
Search(sel, v, true)
Search(regexp.QuoteMeta(sel), v, true)
for _, cur := range v.Buf.cursors {
if c.Loc == cur.Loc {
@@ -1944,6 +2148,54 @@ func (v *View) SpawnMultiCursor(usePlugin bool) bool {
return false
}
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
func (v *View) SpawnMultiCursorSelect(usePlugin bool) bool {
if v.Cursor == &v.Buf.Cursor {
if usePlugin && !PreActionCall("SpawnMultiCursorSelect", v) {
return false
}
// Avoid cases where multiple cursors already exist, that would create problems
if len(v.Buf.cursors) > 1 {
return false
}
var startLine int
var endLine int
a, b := v.Cursor.CurSelection[0].Y, v.Cursor.CurSelection[1].Y
if a > b {
startLine, endLine = b, a
} else {
startLine, endLine = a, b
}
if v.Cursor.HasSelection() {
v.Cursor.ResetSelection()
v.Cursor.GotoLoc(Loc{0, startLine})
for i := startLine; i <= endLine; i++ {
c := &Cursor{
buf: v.Buf,
}
c.GotoLoc(Loc{0, i})
v.Buf.cursors = append(v.Buf.cursors, c)
}
v.Buf.MergeCursors()
v.Buf.UpdateCursors()
} else {
return false
}
if usePlugin {
PostActionCall("SpawnMultiCursorSelect", v)
}
messenger.Message("Added cursors from selection")
}
return false
}
// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
if v.Cursor == &v.Buf.Cursor {
@@ -1963,6 +2215,7 @@ func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
v.Cursor = &v.Buf.Cursor
v.Buf.cursors = append(v.Buf.cursors, c)
v.Buf.MergeCursors()
v.Buf.UpdateCursors()
if usePlugin {
@@ -1984,7 +2237,7 @@ func (v *View) SkipMultiCursor(usePlugin bool) bool {
searchStart = cursor.CurSelection[1]
v.Cursor = cursor
Search(sel, v, true)
Search(regexp.QuoteMeta(sel), v, true)
v.Relocate()
v.Cursor = cursor
@@ -2027,12 +2280,7 @@ func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
return false
}
for i := 1; i < len(v.Buf.cursors); i++ {
v.Buf.cursors[i] = nil
}
v.Buf.cursors = v.Buf.cursors[:1]
v.Buf.UpdateCursors()
v.Cursor.ResetSelection()
v.Buf.clearCursors()
v.Relocate()
if usePlugin {

View File

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

View File

@@ -1,3 +1,5 @@
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
package main
import "syscall"
@@ -19,8 +21,7 @@ func (v *View) Suspend(usePlugin bool) bool {
// suspend the process
pid := syscall.Getpid()
tid := syscall.Gettid()
err := syscall.Tgkill(pid, tid, syscall.SIGSTOP)
err := syscall.Kill(pid, syscall.SIGSTOP)
if err != nil {
TermMessage(err)
}

View File

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

View File

@@ -1,17 +1,21 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"unicode"
"github.com/zyedidia/json5/encoding/json5"
"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,
@@ -19,90 +23,97 @@ var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
}
var bindingActions = map[string]func(*View, bool) bool{
"CursorUp": (*View).CursorUp,
"CursorDown": (*View).CursorDown,
"CursorPageUp": (*View).CursorPageUp,
"CursorPageDown": (*View).CursorPageDown,
"CursorLeft": (*View).CursorLeft,
"CursorRight": (*View).CursorRight,
"CursorStart": (*View).CursorStart,
"CursorEnd": (*View).CursorEnd,
"SelectToStart": (*View).SelectToStart,
"SelectToEnd": (*View).SelectToEnd,
"SelectUp": (*View).SelectUp,
"SelectDown": (*View).SelectDown,
"SelectLeft": (*View).SelectLeft,
"SelectRight": (*View).SelectRight,
"WordRight": (*View).WordRight,
"WordLeft": (*View).WordLeft,
"SelectWordRight": (*View).SelectWordRight,
"SelectWordLeft": (*View).SelectWordLeft,
"DeleteWordRight": (*View).DeleteWordRight,
"DeleteWordLeft": (*View).DeleteWordLeft,
"SelectToStartOfLine": (*View).SelectToStartOfLine,
"SelectToEndOfLine": (*View).SelectToEndOfLine,
"InsertNewline": (*View).InsertNewline,
"InsertSpace": (*View).InsertSpace,
"Backspace": (*View).Backspace,
"Delete": (*View).Delete,
"InsertTab": (*View).InsertTab,
"Save": (*View).Save,
"SaveAll": (*View).SaveAll,
"SaveAs": (*View).SaveAs,
"Find": (*View).Find,
"FindNext": (*View).FindNext,
"FindPrevious": (*View).FindPrevious,
"Center": (*View).Center,
"Undo": (*View).Undo,
"Redo": (*View).Redo,
"Copy": (*View).Copy,
"Cut": (*View).Cut,
"CutLine": (*View).CutLine,
"DuplicateLine": (*View).DuplicateLine,
"DeleteLine": (*View).DeleteLine,
"MoveLinesUp": (*View).MoveLinesUp,
"MoveLinesDown": (*View).MoveLinesDown,
"IndentSelection": (*View).IndentSelection,
"OutdentSelection": (*View).OutdentSelection,
"OutdentLine": (*View).OutdentLine,
"Paste": (*View).Paste,
"PastePrimary": (*View).PastePrimary,
"SelectAll": (*View).SelectAll,
"OpenFile": (*View).OpenFile,
"Start": (*View).Start,
"End": (*View).End,
"PageUp": (*View).PageUp,
"PageDown": (*View).PageDown,
"HalfPageUp": (*View).HalfPageUp,
"HalfPageDown": (*View).HalfPageDown,
"StartOfLine": (*View).StartOfLine,
"EndOfLine": (*View).EndOfLine,
"ToggleHelp": (*View).ToggleHelp,
"ToggleRuler": (*View).ToggleRuler,
"JumpLine": (*View).JumpLine,
"ClearStatus": (*View).ClearStatus,
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"Escape": (*View).Escape,
"Quit": (*View).Quit,
"QuitAll": (*View).QuitAll,
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
"Unsplit": (*View).Unsplit,
"VSplit": (*View).VSplitBinding,
"HSplit": (*View).HSplitBinding,
"ToggleMacro": (*View).ToggleMacro,
"PlayMacro": (*View).PlayMacro,
"Suspend": (*View).Suspend,
"ScrollUp": (*View).ScrollUpAction,
"ScrollDown": (*View).ScrollDownAction,
"SpawnMultiCursor": (*View).SpawnMultiCursor,
"RemoveMultiCursor": (*View).RemoveMultiCursor,
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
"SkipMultiCursor": (*View).SkipMultiCursor,
"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,
@@ -256,11 +267,13 @@ type Key struct {
modifiers tcell.ModMask
buttons tcell.ButtonMask
r rune
escape string
}
// InitBindings initializes the keybindings for micro
func InitBindings() {
bindings = make(map[Key][]func(*View, bool) bool)
bindingsStr = make(map[string]string)
mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
var parsed map[string]string
@@ -312,16 +325,29 @@ modSearch:
case strings.HasPrefix(k, "Shift"):
k = k[5:]
modifiers |= tcell.ModShift
case strings.HasPrefix(k, "\x1b"):
return Key{
keyCode: -1,
modifiers: modifiers,
buttons: -1,
r: 0,
escape: k,
}, true
default:
break modSearch
}
}
if len(k) == 0 {
return Key{buttons: -1}, false
}
// Control is handled specially, since some character codes in bindingKeys
// are different when Control is depressed. We should check for Control keys
// first.
if modifiers&tcell.ModCtrl != 0 {
// see if the key is in bindingKeys with the Ctrl prefix.
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
if code, ok := bindingKeys["Ctrl"+k]; ok {
// It is, we're done.
return Key{
@@ -387,6 +413,43 @@ func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
return action
}
// TryBindKey tries to bind a key by writing to configDir/bindings.json
// This function is unused for now
func TryBindKey(k, v string) {
filename := configDir + "/bindings.json"
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
TermMessage("Error reading bindings.json file: " + err.Error())
return
}
conflict := -1
lines := strings.Split(string(input), "\n")
for i, l := range lines {
parts := strings.Split(l, ":")
if len(parts) >= 2 {
if strings.Contains(parts[0], k) {
conflict = i
TermMessage("Warning: Keybinding conflict:", k, " has been overwritten")
}
}
}
binding := fmt.Sprintf(" \"%s\": \"%s\",", k, v)
if conflict == -1 {
lines = append([]string{lines[0], binding}, lines[conflict:]...)
} else {
lines = append(append(lines[:conflict], binding), lines[conflict+1:]...)
}
txt := strings.Join(lines, "\n")
err = ioutil.WriteFile(filename, []byte(txt), 0644)
if err != nil {
TermMessage("Error")
}
}
}
// BindKey takes a key and an action and binds the two together
func BindKey(k, v string) {
key, ok := findKey(k)
@@ -397,14 +460,21 @@ func BindKey(k, v string) {
if v == "ToggleHelp" {
helpBinding = k
}
if v == "ToggleKeyMenu" {
kmenuBinding = k
}
if helpBinding == k && v != "ToggleHelp" {
helpBinding = ""
}
if kmenuBinding == k && v != "ToggleKeyMenu" {
kmenuBinding = ""
}
actionNames := strings.Split(v, ",")
if actionNames[0] == "UnbindKey" {
delete(bindings, key)
delete(mouseBindings, key)
delete(bindingsStr, k)
if len(actionNames) == 1 {
return
}
@@ -415,6 +485,12 @@ func BindKey(k, v string) {
for _, actionName := range actionNames {
if strings.HasPrefix(actionName, "Mouse") {
mouseActions = append(mouseActions, findMouseAction(actionName))
} else if strings.HasPrefix(actionName, "command:") {
cmd := strings.SplitN(actionName, ":", 2)[1]
actions = append(actions, CommandAction(cmd))
} else if strings.HasPrefix(actionName, "command-edit:") {
cmd := strings.SplitN(actionName, ":", 2)[1]
actions = append(actions, CommandEditAction(cmd))
} else {
actions = append(actions, findAction(actionName))
}
@@ -424,6 +500,7 @@ func BindKey(k, v string) {
// Can't have a binding be both mouse and normal
delete(mouseBindings, key)
bindings[key] = actions
bindingsStr[k] = v
} else if len(mouseActions) > 0 {
// Can't have a binding be both mouse and normal
delete(bindings, key)
@@ -458,6 +535,8 @@ func DefaultBindings() map[string]string {
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
@@ -490,6 +569,7 @@ func DefaultBindings() map[string]string {
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"CtrlR": "ToggleRuler",
"CtrlL": "JumpLine",
"Delete": "Delete",
@@ -499,6 +579,7 @@ func DefaultBindings() map[string]string {
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
@@ -509,7 +590,6 @@ func DefaultBindings() map[string]string {
// "Alt-n": "CursorDown",
// Integration with file managers
"F1": "ToggleHelp",
"F2": "Save",
"F3": "Find",
"F4": "Quit",
@@ -525,6 +605,7 @@ func DefaultBindings() map[string]string {
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",

View File

@@ -2,7 +2,9 @@ package main
import (
"bytes"
"crypto/md5"
"encoding/gob"
"errors"
"io"
"io/ioutil"
"os"
@@ -15,10 +17,16 @@ import (
"time"
"unicode/utf8"
"github.com/mitchellh/go-homedir"
"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
@@ -45,11 +53,15 @@ type Buffer struct {
// Stores the last modification time of the file the buffer is pointing to
ModTime time.Time
// NumLines is the number of lines in the buffer
NumLines int
syntaxDef *highlight.Def
highlighter *highlight.Highlighter
// Hash of the original buffer -- empty if fastdirty is on
origHash [16]byte
// Buffer local settings
Settings map[string]interface{}
}
@@ -62,15 +74,64 @@ type SerializedBuffer struct {
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 {
for _, view := range tab.Views {
if view.Buf.Path == path {
return view.Buf
}
@@ -88,6 +149,12 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
}
}
if fileformat == 1 {
b.Settings["fileformat"] = "unix"
} else if fileformat == 2 {
b.Settings["fileformat"] = "dos"
}
absPath, _ := filepath.Abs(path)
b.Path = path
@@ -109,11 +176,18 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
cursorStartX := 0
cursorStartY := 0
// If -startpos LINE,COL was passed, use start position LINE,COL
if len(*flagStartPos) > 0 {
if len(*flagStartPos) > 0 || !startposErr {
positions := strings.Split(*flagStartPos, ",")
if len(positions) == 2 {
lineNum, errPos1 := strconv.Atoi(positions[0])
colNum, errPos2 := strconv.Atoi(positions[1])
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
@@ -142,10 +216,11 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
InitLocalSettings(b)
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
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)
@@ -161,14 +236,22 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
}
if b.Settings["saveundo"].(bool) {
// We should only use last time's eventhandler if the file wasn't by someone else in the meantime
// 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
}
}
}
file.Close()
}
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}
@@ -176,6 +259,8 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
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 == "" {
@@ -240,7 +325,6 @@ func (b *Buffer) UpdateRules() {
if b.syntaxDef != nil {
highlight.ResolveIncludes(b.syntaxDef, files)
}
files = nil
if b.highlighter == nil || rehighlight {
if b.syntaxDef != nil {
@@ -309,6 +393,8 @@ 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++ {
@@ -325,8 +411,17 @@ func (b *Buffer) MergeCursors() {
}
b.cursors = cursors
for i := range b.cursors {
b.cursors[i].Num = i
}
if b.curCursor >= len(b.cursors) {
b.curCursor = len(b.cursors) - 1
}
}
// UpdateCursors updates all the cursors indicies
func (b *Buffer) UpdateCursors() {
for i, c := range b.cursors {
c.Num = i
@@ -356,7 +451,7 @@ func (b *Buffer) Serialize() error {
b.ModTime,
})
}
file.Close()
err = file.Close()
return err
}
return nil
@@ -365,7 +460,6 @@ func (b *Buffer) Serialize() error {
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
func (b *Buffer) SaveAs(filename string) error {
b.UpdateRules()
dir, _ := homedir.Dir()
if b.Settings["rmtrailingws"].(bool) {
r, _ := regexp.Compile(`[ \t]+$`)
for lineNum, line := range b.Lines(0, b.NumLines) {
@@ -384,17 +478,74 @@ func (b *Buffer) SaveAs(filename string) error {
b.Insert(end, "\n")
}
}
str := b.String()
data := []byte(str)
err := ioutil.WriteFile(filename, data, 0644)
if err == nil {
b.Path = strings.Replace(filename, "~", dir, 1)
b.IsModified = false
defer func() {
b.ModTime, _ = GetModTime(filename)
return b.Serialize()
}()
// 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")
}
}
}
b.ModTime, _ = GetModTime(filename)
return err
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
@@ -408,8 +559,8 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
screen = nil
// Set up everything for the command
cmd := exec.Command("sudo", "tee", filename)
cmd.Stdin = bytes.NewBufferString(b.String())
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
@@ -435,6 +586,15 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
return err
}
// Modified returns if this buffer has been modified since
// being opened
func (b *Buffer) Modified() bool {
if b.Settings["fastdirty"].(bool) {
return b.IsModified
}
return b.origHash != md5.Sum([]byte(b.String()))
}
func (b *Buffer) insert(pos Loc, value []byte) {
b.IsModified = true
b.LineArray.insert(pos, value)
@@ -464,13 +624,29 @@ func (b *Buffer) End() Loc {
// RuneAt returns the rune at a given location in the buffer
func (b *Buffer) RuneAt(loc Loc) rune {
line := []rune(b.Line(loc.Y))
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) {
@@ -479,6 +655,7 @@ func (b *Buffer) Line(n int) string {
return string(b.lines[n].data)
}
// LinesNum returns the number of lines in the buffer
func (b *Buffer) LinesNum() int {
return len(b.lines)
}
@@ -550,3 +727,67 @@ func (b *Buffer) ClearMatches() {
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

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

View File

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

View File

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

View File

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

View File

@@ -28,6 +28,7 @@ type TextEvent struct {
Time time.Time
}
// A Delta is a change to the buffer
type Delta struct {
Text string
Start Loc
@@ -48,6 +49,11 @@ func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
for i, d := range t.Deltas {
t.Deltas[i].Text = buf.remove(d.Start, d.End)
buf.insert(d.Start, []byte(d.Text))
t.Deltas[i].Start = d.Start
t.Deltas[i].End = Loc{d.Start.X + Count(d.Text), d.Start.Y}
}
for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
}
}
}
@@ -99,7 +105,7 @@ func (eh *EventHandler) Insert(start Loc, text string) {
e := &TextEvent{
C: *eh.buf.cursors[eh.buf.curCursor],
EventType: TextEventInsert,
Deltas: []Delta{Delta{text, start, Loc{0, 0}}},
Deltas: []Delta{{text, start, Loc{0, 0}}},
Time: time.Now(),
}
eh.Execute(e)
@@ -129,7 +135,7 @@ func (eh *EventHandler) Remove(start, end Loc) {
e := &TextEvent{
C: *eh.buf.cursors[eh.buf.curCursor],
EventType: TextEventRemove,
Deltas: []Delta{Delta{"", start, end}},
Deltas: []Delta{{"", start, end}},
Time: time.Now(),
}
eh.Execute(e)

View File

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

View File

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

20
cmd/micro/keymenu.go Normal file
View File

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

View File

@@ -29,6 +29,8 @@ func runeToByteIndex(n int, txt []byte) int {
return count
}
// A Line contains the data in bytes as well as a highlight state, match
// and a flag for whether the highlighting needs to be updated
type Line struct {
data []byte
@@ -43,10 +45,12 @@ type LineArray struct {
lines []Line
}
// Append efficiently appends lines together
// It allocates an additional 10000 lines if the original estimate
// is incorrect
func Append(slice []Line, data ...Line) []Line {
l := len(slice)
if l+len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]Line, (l+len(data))+10000)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
@@ -71,11 +75,20 @@ func NewLineArray(size int64, reader io.Reader) *LineArray {
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)
// The copy function is predeclared and works for any slice type.
copy(newSlice, la.lines)
la.lines = newSlice
loaded = -1
@@ -87,7 +100,7 @@ func NewLineArray(size int64, reader io.Reader) *LineArray {
if err != nil {
if err == io.EOF {
la.lines = Append(la.lines, Line{data[:len(data)], nil, nil, false})
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
// la.lines = Append(la.lines, Line{data[:len(data)]})
}
// Last line was read
@@ -114,11 +127,28 @@ func (la *LineArray) String() string {
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})
la.lines = append(la.lines, Line{[]byte{' '}, nil, nil, false})
copy(la.lines[y+2:], la.lines[y+1:])
la.lines[y+1] = Line{[]byte(""), la.lines[y].state, nil, false}
la.lines[y+1] = Line{[]byte{}, la.lines[y].state, nil, false}
}
// inserts a byte array at a given location
@@ -216,18 +246,22 @@ func (la *LineArray) Substr(start, end Loc) string {
return str
}
// State gets the highlight state for the given line number
func (la *LineArray) State(lineN int) highlight.State {
return la.lines[lineN].state
}
// SetState sets the highlight state at the given line number
func (la *LineArray) SetState(lineN int, s highlight.State) {
la.lines[lineN].state = s
}
// SetMatch sets the match at the given line number
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
la.lines[lineN].match = m
}
// Match retrieves the match for the given line number
func (la *LineArray) Match(lineN int) highlight.LineMatch {
return la.lines[lineN].match
}

View File

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

539
cmd/micro/lua.go Normal file
View File

@@ -0,0 +1,539 @@
package main
import (
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"math/rand"
"net"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
luar "layeh.com/gopher-luar"
lua "github.com/yuin/gopher-lua"
)
var L *lua.LState
func init() {
L = lua.NewState()
L.SetGlobal("import", luar.New(L, Import))
}
// LoadFile loads a lua file
func LoadFile(module string, file string, data string) error {
pluginDef := "local P = {};" + module + " = P;setmetatable(" + module + ", {__index = _G});setfenv(1, P);"
if fn, err := L.Load(strings.NewReader(pluginDef+data), file); err != nil {
return err
} else {
L.Push(fn)
return L.PCall(0, lua.MultRet, nil)
}
}
// Import allows a lua plugin to import a package
func Import(pkg string) *lua.LTable {
switch pkg {
case "fmt":
return importFmt()
case "io":
return importIo()
case "io/ioutil", "ioutil":
return importIoUtil()
case "net":
return importNet()
case "math":
return importMath()
case "math/rand":
return importMathRand()
case "os":
return importOs()
case "runtime":
return importRuntime()
case "path":
return importPath()
case "filepath":
return importFilePath()
case "strings":
return importStrings()
case "regexp":
return importRegexp()
case "errors":
return importErrors()
case "time":
return importTime()
default:
return nil
}
}
func importFmt() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "tErrorf", luar.New(L, fmt.Errorf))
L.SetField(pkg, "Fprint", luar.New(L, fmt.Fprint))
L.SetField(pkg, "Fprintf", luar.New(L, fmt.Fprintf))
L.SetField(pkg, "Fprintln", luar.New(L, fmt.Fprintln))
L.SetField(pkg, "Fscan", luar.New(L, fmt.Fscan))
L.SetField(pkg, "Fscanf", luar.New(L, fmt.Fscanf))
L.SetField(pkg, "Fscanln", luar.New(L, fmt.Fscanln))
L.SetField(pkg, "Print", luar.New(L, fmt.Print))
L.SetField(pkg, "Printf", luar.New(L, fmt.Printf))
L.SetField(pkg, "Println", luar.New(L, fmt.Println))
L.SetField(pkg, "Scan", luar.New(L, fmt.Scan))
L.SetField(pkg, "Scanf", luar.New(L, fmt.Scanf))
L.SetField(pkg, "Scanln", luar.New(L, fmt.Scanln))
L.SetField(pkg, "Sprint", luar.New(L, fmt.Sprint))
L.SetField(pkg, "Sprintf", luar.New(L, fmt.Sprintf))
L.SetField(pkg, "Sprintln", luar.New(L, fmt.Sprintln))
L.SetField(pkg, "Sscan", luar.New(L, fmt.Sscan))
L.SetField(pkg, "Sscanf", luar.New(L, fmt.Sscanf))
L.SetField(pkg, "Sscanln", luar.New(L, fmt.Sscanln))
return pkg
}
func importIo() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Copy", luar.New(L, io.Copy))
L.SetField(pkg, "CopyN", luar.New(L, io.CopyN))
L.SetField(pkg, "EOF", luar.New(L, io.EOF))
L.SetField(pkg, "ErrClosedPipe", luar.New(L, io.ErrClosedPipe))
L.SetField(pkg, "ErrNoProgress", luar.New(L, io.ErrNoProgress))
L.SetField(pkg, "ErrShortBuffer", luar.New(L, io.ErrShortBuffer))
L.SetField(pkg, "ErrShortWrite", luar.New(L, io.ErrShortWrite))
L.SetField(pkg, "ErrUnexpectedEOF", luar.New(L, io.ErrUnexpectedEOF))
L.SetField(pkg, "LimitReader", luar.New(L, io.LimitReader))
L.SetField(pkg, "MultiReader", luar.New(L, io.MultiReader))
L.SetField(pkg, "MultiWriter", luar.New(L, io.MultiWriter))
L.SetField(pkg, "NewSectionReader", luar.New(L, io.NewSectionReader))
L.SetField(pkg, "Pipe", luar.New(L, io.Pipe))
L.SetField(pkg, "ReadAtLeast", luar.New(L, io.ReadAtLeast))
L.SetField(pkg, "ReadFull", luar.New(L, io.ReadFull))
L.SetField(pkg, "TeeReader", luar.New(L, io.TeeReader))
L.SetField(pkg, "WriteString", luar.New(L, io.WriteString))
return pkg
}
func importIoUtil() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "ReadAll", luar.New(L, ioutil.ReadAll))
L.SetField(pkg, "ReadDir", luar.New(L, ioutil.ReadDir))
L.SetField(pkg, "ReadFile", luar.New(L, ioutil.ReadFile))
L.SetField(pkg, "WriteFile", luar.New(L, ioutil.WriteFile))
return pkg
}
func importNet() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "CIDRMask", luar.New(L, net.CIDRMask))
L.SetField(pkg, "Dial", luar.New(L, net.Dial))
L.SetField(pkg, "DialIP", luar.New(L, net.DialIP))
L.SetField(pkg, "DialTCP", luar.New(L, net.DialTCP))
L.SetField(pkg, "DialTimeout", luar.New(L, net.DialTimeout))
L.SetField(pkg, "DialUDP", luar.New(L, net.DialUDP))
L.SetField(pkg, "DialUnix", luar.New(L, net.DialUnix))
L.SetField(pkg, "ErrWriteToConnected", luar.New(L, net.ErrWriteToConnected))
L.SetField(pkg, "FileConn", luar.New(L, net.FileConn))
L.SetField(pkg, "FileListener", luar.New(L, net.FileListener))
L.SetField(pkg, "FilePacketConn", luar.New(L, net.FilePacketConn))
L.SetField(pkg, "FlagBroadcast", luar.New(L, net.FlagBroadcast))
L.SetField(pkg, "FlagLoopback", luar.New(L, net.FlagLoopback))
L.SetField(pkg, "FlagMulticast", luar.New(L, net.FlagMulticast))
L.SetField(pkg, "FlagPointToPoint", luar.New(L, net.FlagPointToPoint))
L.SetField(pkg, "FlagUp", luar.New(L, net.FlagUp))
L.SetField(pkg, "IPv4", luar.New(L, net.IPv4))
L.SetField(pkg, "IPv4Mask", luar.New(L, net.IPv4Mask))
L.SetField(pkg, "IPv4allrouter", luar.New(L, net.IPv4allrouter))
L.SetField(pkg, "IPv4allsys", luar.New(L, net.IPv4allsys))
L.SetField(pkg, "IPv4bcast", luar.New(L, net.IPv4bcast))
L.SetField(pkg, "IPv4len", luar.New(L, net.IPv4len))
L.SetField(pkg, "IPv4zero", luar.New(L, net.IPv4zero))
L.SetField(pkg, "IPv6interfacelocalallnodes", luar.New(L, net.IPv6interfacelocalallnodes))
L.SetField(pkg, "IPv6len", luar.New(L, net.IPv6len))
L.SetField(pkg, "IPv6linklocalallnodes", luar.New(L, net.IPv6linklocalallnodes))
L.SetField(pkg, "IPv6linklocalallrouters", luar.New(L, net.IPv6linklocalallrouters))
L.SetField(pkg, "IPv6loopback", luar.New(L, net.IPv6loopback))
L.SetField(pkg, "IPv6unspecified", luar.New(L, net.IPv6unspecified))
L.SetField(pkg, "IPv6zero", luar.New(L, net.IPv6zero))
L.SetField(pkg, "InterfaceAddrs", luar.New(L, net.InterfaceAddrs))
L.SetField(pkg, "InterfaceByIndex", luar.New(L, net.InterfaceByIndex))
L.SetField(pkg, "InterfaceByName", luar.New(L, net.InterfaceByName))
L.SetField(pkg, "Interfaces", luar.New(L, net.Interfaces))
L.SetField(pkg, "JoinHostPort", luar.New(L, net.JoinHostPort))
L.SetField(pkg, "Listen", luar.New(L, net.Listen))
L.SetField(pkg, "ListenIP", luar.New(L, net.ListenIP))
L.SetField(pkg, "ListenMulticastUDP", luar.New(L, net.ListenMulticastUDP))
L.SetField(pkg, "ListenPacket", luar.New(L, net.ListenPacket))
L.SetField(pkg, "ListenTCP", luar.New(L, net.ListenTCP))
L.SetField(pkg, "ListenUDP", luar.New(L, net.ListenUDP))
L.SetField(pkg, "ListenUnix", luar.New(L, net.ListenUnix))
L.SetField(pkg, "ListenUnixgram", luar.New(L, net.ListenUnixgram))
L.SetField(pkg, "LookupAddr", luar.New(L, net.LookupAddr))
L.SetField(pkg, "LookupCNAME", luar.New(L, net.LookupCNAME))
L.SetField(pkg, "LookupHost", luar.New(L, net.LookupHost))
L.SetField(pkg, "LookupIP", luar.New(L, net.LookupIP))
L.SetField(pkg, "LookupMX", luar.New(L, net.LookupMX))
L.SetField(pkg, "LookupNS", luar.New(L, net.LookupNS))
L.SetField(pkg, "LookupPort", luar.New(L, net.LookupPort))
L.SetField(pkg, "LookupSRV", luar.New(L, net.LookupSRV))
L.SetField(pkg, "LookupTXT", luar.New(L, net.LookupTXT))
L.SetField(pkg, "ParseCIDR", luar.New(L, net.ParseCIDR))
L.SetField(pkg, "ParseIP", luar.New(L, net.ParseIP))
L.SetField(pkg, "ParseMAC", luar.New(L, net.ParseMAC))
L.SetField(pkg, "Pipe", luar.New(L, net.Pipe))
L.SetField(pkg, "ResolveIPAddr", luar.New(L, net.ResolveIPAddr))
L.SetField(pkg, "ResolveTCPAddr", luar.New(L, net.ResolveTCPAddr))
L.SetField(pkg, "ResolveUDPAddr", luar.New(L, net.ResolveUDPAddr))
L.SetField(pkg, "ResolveUnixAddr", luar.New(L, net.ResolveUnixAddr))
L.SetField(pkg, "SplitHostPort", luar.New(L, net.SplitHostPort))
return pkg
}
func importMath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Abs", luar.New(L, math.Abs))
L.SetField(pkg, "Acos", luar.New(L, math.Acos))
L.SetField(pkg, "Acosh", luar.New(L, math.Acosh))
L.SetField(pkg, "Asin", luar.New(L, math.Asin))
L.SetField(pkg, "Asinh", luar.New(L, math.Asinh))
L.SetField(pkg, "Atan", luar.New(L, math.Atan))
L.SetField(pkg, "Atan2", luar.New(L, math.Atan2))
L.SetField(pkg, "Atanh", luar.New(L, math.Atanh))
L.SetField(pkg, "Cbrt", luar.New(L, math.Cbrt))
L.SetField(pkg, "Ceil", luar.New(L, math.Ceil))
L.SetField(pkg, "Copysign", luar.New(L, math.Copysign))
L.SetField(pkg, "Cos", luar.New(L, math.Cos))
L.SetField(pkg, "Cosh", luar.New(L, math.Cosh))
L.SetField(pkg, "Dim", luar.New(L, math.Dim))
L.SetField(pkg, "Erf", luar.New(L, math.Erf))
L.SetField(pkg, "Erfc", luar.New(L, math.Erfc))
L.SetField(pkg, "Exp", luar.New(L, math.Exp))
L.SetField(pkg, "Exp2", luar.New(L, math.Exp2))
L.SetField(pkg, "Expm1", luar.New(L, math.Expm1))
L.SetField(pkg, "Float32bits", luar.New(L, math.Float32bits))
L.SetField(pkg, "Float32frombits", luar.New(L, math.Float32frombits))
L.SetField(pkg, "Float64bits", luar.New(L, math.Float64bits))
L.SetField(pkg, "Float64frombits", luar.New(L, math.Float64frombits))
L.SetField(pkg, "Floor", luar.New(L, math.Floor))
L.SetField(pkg, "Frexp", luar.New(L, math.Frexp))
L.SetField(pkg, "Gamma", luar.New(L, math.Gamma))
L.SetField(pkg, "Hypot", luar.New(L, math.Hypot))
L.SetField(pkg, "Ilogb", luar.New(L, math.Ilogb))
L.SetField(pkg, "Inf", luar.New(L, math.Inf))
L.SetField(pkg, "IsInf", luar.New(L, math.IsInf))
L.SetField(pkg, "IsNaN", luar.New(L, math.IsNaN))
L.SetField(pkg, "J0", luar.New(L, math.J0))
L.SetField(pkg, "J1", luar.New(L, math.J1))
L.SetField(pkg, "Jn", luar.New(L, math.Jn))
L.SetField(pkg, "Ldexp", luar.New(L, math.Ldexp))
L.SetField(pkg, "Lgamma", luar.New(L, math.Lgamma))
L.SetField(pkg, "Log", luar.New(L, math.Log))
L.SetField(pkg, "Log10", luar.New(L, math.Log10))
L.SetField(pkg, "Log1p", luar.New(L, math.Log1p))
L.SetField(pkg, "Log2", luar.New(L, math.Log2))
L.SetField(pkg, "Logb", luar.New(L, math.Logb))
L.SetField(pkg, "Max", luar.New(L, math.Max))
L.SetField(pkg, "Min", luar.New(L, math.Min))
L.SetField(pkg, "Mod", luar.New(L, math.Mod))
L.SetField(pkg, "Modf", luar.New(L, math.Modf))
L.SetField(pkg, "NaN", luar.New(L, math.NaN))
L.SetField(pkg, "Nextafter", luar.New(L, math.Nextafter))
L.SetField(pkg, "Pow", luar.New(L, math.Pow))
L.SetField(pkg, "Pow10", luar.New(L, math.Pow10))
L.SetField(pkg, "Remainder", luar.New(L, math.Remainder))
L.SetField(pkg, "Signbit", luar.New(L, math.Signbit))
L.SetField(pkg, "Sin", luar.New(L, math.Sin))
L.SetField(pkg, "Sincos", luar.New(L, math.Sincos))
L.SetField(pkg, "Sinh", luar.New(L, math.Sinh))
L.SetField(pkg, "Sqrt", luar.New(L, math.Sqrt))
L.SetField(pkg, "Tan", luar.New(L, math.Tan))
L.SetField(pkg, "Tanh", luar.New(L, math.Tanh))
L.SetField(pkg, "Trunc", luar.New(L, math.Trunc))
L.SetField(pkg, "Y0", luar.New(L, math.Y0))
L.SetField(pkg, "Y1", luar.New(L, math.Y1))
L.SetField(pkg, "Yn", luar.New(L, math.Yn))
return pkg
}
func importMathRand() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "ExpFloat64", luar.New(L, rand.ExpFloat64))
L.SetField(pkg, "Float32", luar.New(L, rand.Float32))
L.SetField(pkg, "Float64", luar.New(L, rand.Float64))
L.SetField(pkg, "Int", luar.New(L, rand.Int))
L.SetField(pkg, "Int31", luar.New(L, rand.Int31))
L.SetField(pkg, "Int31n", luar.New(L, rand.Int31n))
L.SetField(pkg, "Int63", luar.New(L, rand.Int63))
L.SetField(pkg, "Int63n", luar.New(L, rand.Int63n))
L.SetField(pkg, "Intn", luar.New(L, rand.Intn))
L.SetField(pkg, "NormFloat64", luar.New(L, rand.NormFloat64))
L.SetField(pkg, "Perm", luar.New(L, rand.Perm))
L.SetField(pkg, "Seed", luar.New(L, rand.Seed))
L.SetField(pkg, "Uint32", luar.New(L, rand.Uint32))
return pkg
}
func importOs() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Args", luar.New(L, os.Args))
L.SetField(pkg, "Chdir", luar.New(L, os.Chdir))
L.SetField(pkg, "Chmod", luar.New(L, os.Chmod))
L.SetField(pkg, "Chown", luar.New(L, os.Chown))
L.SetField(pkg, "Chtimes", luar.New(L, os.Chtimes))
L.SetField(pkg, "Clearenv", luar.New(L, os.Clearenv))
L.SetField(pkg, "Create", luar.New(L, os.Create))
L.SetField(pkg, "DevNull", luar.New(L, os.DevNull))
L.SetField(pkg, "Environ", luar.New(L, os.Environ))
L.SetField(pkg, "ErrExist", luar.New(L, os.ErrExist))
L.SetField(pkg, "ErrInvalid", luar.New(L, os.ErrInvalid))
L.SetField(pkg, "ErrNotExist", luar.New(L, os.ErrNotExist))
L.SetField(pkg, "ErrPermission", luar.New(L, os.ErrPermission))
L.SetField(pkg, "Exit", luar.New(L, os.Exit))
L.SetField(pkg, "Expand", luar.New(L, os.Expand))
L.SetField(pkg, "ExpandEnv", luar.New(L, os.ExpandEnv))
L.SetField(pkg, "FindProcess", luar.New(L, os.FindProcess))
L.SetField(pkg, "Getegid", luar.New(L, os.Getegid))
L.SetField(pkg, "Getenv", luar.New(L, os.Getenv))
L.SetField(pkg, "Geteuid", luar.New(L, os.Geteuid))
L.SetField(pkg, "Getgid", luar.New(L, os.Getgid))
L.SetField(pkg, "Getgroups", luar.New(L, os.Getgroups))
L.SetField(pkg, "Getpagesize", luar.New(L, os.Getpagesize))
L.SetField(pkg, "Getpid", luar.New(L, os.Getpid))
L.SetField(pkg, "Getuid", luar.New(L, os.Getuid))
L.SetField(pkg, "Getwd", luar.New(L, os.Getwd))
L.SetField(pkg, "Hostname", luar.New(L, os.Hostname))
L.SetField(pkg, "Interrupt", luar.New(L, os.Interrupt))
L.SetField(pkg, "IsExist", luar.New(L, os.IsExist))
L.SetField(pkg, "IsNotExist", luar.New(L, os.IsNotExist))
L.SetField(pkg, "IsPathSeparator", luar.New(L, os.IsPathSeparator))
L.SetField(pkg, "IsPermission", luar.New(L, os.IsPermission))
L.SetField(pkg, "Kill", luar.New(L, os.Kill))
L.SetField(pkg, "Lchown", luar.New(L, os.Lchown))
L.SetField(pkg, "Link", luar.New(L, os.Link))
L.SetField(pkg, "Lstat", luar.New(L, os.Lstat))
L.SetField(pkg, "Mkdir", luar.New(L, os.Mkdir))
L.SetField(pkg, "MkdirAll", luar.New(L, os.MkdirAll))
L.SetField(pkg, "ModeAppend", luar.New(L, os.ModeAppend))
L.SetField(pkg, "ModeCharDevice", luar.New(L, os.ModeCharDevice))
L.SetField(pkg, "ModeDevice", luar.New(L, os.ModeDevice))
L.SetField(pkg, "ModeDir", luar.New(L, os.ModeDir))
L.SetField(pkg, "ModeExclusive", luar.New(L, os.ModeExclusive))
L.SetField(pkg, "ModeNamedPipe", luar.New(L, os.ModeNamedPipe))
L.SetField(pkg, "ModePerm", luar.New(L, os.ModePerm))
L.SetField(pkg, "ModeSetgid", luar.New(L, os.ModeSetgid))
L.SetField(pkg, "ModeSetuid", luar.New(L, os.ModeSetuid))
L.SetField(pkg, "ModeSocket", luar.New(L, os.ModeSocket))
L.SetField(pkg, "ModeSticky", luar.New(L, os.ModeSticky))
L.SetField(pkg, "ModeSymlink", luar.New(L, os.ModeSymlink))
L.SetField(pkg, "ModeTemporary", luar.New(L, os.ModeTemporary))
L.SetField(pkg, "ModeType", luar.New(L, os.ModeType))
L.SetField(pkg, "NewFile", luar.New(L, os.NewFile))
L.SetField(pkg, "NewSyscallError", luar.New(L, os.NewSyscallError))
L.SetField(pkg, "O_APPEND", luar.New(L, os.O_APPEND))
L.SetField(pkg, "O_CREATE", luar.New(L, os.O_CREATE))
L.SetField(pkg, "O_EXCL", luar.New(L, os.O_EXCL))
L.SetField(pkg, "O_RDONLY", luar.New(L, os.O_RDONLY))
L.SetField(pkg, "O_RDWR", luar.New(L, os.O_RDWR))
L.SetField(pkg, "O_SYNC", luar.New(L, os.O_SYNC))
L.SetField(pkg, "O_TRUNC", luar.New(L, os.O_TRUNC))
L.SetField(pkg, "O_WRONLY", luar.New(L, os.O_WRONLY))
L.SetField(pkg, "Open", luar.New(L, os.Open))
L.SetField(pkg, "OpenFile", luar.New(L, os.OpenFile))
L.SetField(pkg, "PathListSeparator", luar.New(L, os.PathListSeparator))
L.SetField(pkg, "PathSeparator", luar.New(L, os.PathSeparator))
L.SetField(pkg, "Pipe", luar.New(L, os.Pipe))
L.SetField(pkg, "Readlink", luar.New(L, os.Readlink))
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
L.SetField(pkg, "Rename", luar.New(L, os.Rename))
L.SetField(pkg, "SEEK_CUR", luar.New(L, os.SEEK_CUR))
L.SetField(pkg, "SEEK_END", luar.New(L, os.SEEK_END))
L.SetField(pkg, "SEEK_SET", luar.New(L, os.SEEK_SET))
L.SetField(pkg, "SameFile", luar.New(L, os.SameFile))
L.SetField(pkg, "Setenv", luar.New(L, os.Setenv))
L.SetField(pkg, "StartProcess", luar.New(L, os.StartProcess))
L.SetField(pkg, "Stat", luar.New(L, os.Stat))
L.SetField(pkg, "Stderr", luar.New(L, os.Stderr))
L.SetField(pkg, "Stdin", luar.New(L, os.Stdin))
L.SetField(pkg, "Stdout", luar.New(L, os.Stdout))
L.SetField(pkg, "Symlink", luar.New(L, os.Symlink))
L.SetField(pkg, "TempDir", luar.New(L, os.TempDir))
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
return pkg
}
func importRuntime() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "GC", luar.New(L, runtime.GC))
L.SetField(pkg, "GOARCH", luar.New(L, runtime.GOARCH))
L.SetField(pkg, "GOMAXPROCS", luar.New(L, runtime.GOMAXPROCS))
L.SetField(pkg, "GOOS", luar.New(L, runtime.GOOS))
L.SetField(pkg, "GOROOT", luar.New(L, runtime.GOROOT))
return pkg
}
func importPath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Base", luar.New(L, path.Base))
L.SetField(pkg, "Clean", luar.New(L, path.Clean))
L.SetField(pkg, "Dir", luar.New(L, path.Dir))
L.SetField(pkg, "ErrBadPattern", luar.New(L, path.ErrBadPattern))
L.SetField(pkg, "Ext", luar.New(L, path.Ext))
L.SetField(pkg, "IsAbs", luar.New(L, path.IsAbs))
L.SetField(pkg, "Join", luar.New(L, path.Join))
L.SetField(pkg, "Match", luar.New(L, path.Match))
L.SetField(pkg, "Split", luar.New(L, path.Split))
return pkg
}
func importFilePath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))
L.SetField(pkg, "Dir", luar.New(L, filepath.Dir))
L.SetField(pkg, "EvalSymlinks", luar.New(L, filepath.EvalSymlinks))
L.SetField(pkg, "Ext", luar.New(L, filepath.Ext))
L.SetField(pkg, "FromSlash", luar.New(L, filepath.FromSlash))
L.SetField(pkg, "Glob", luar.New(L, filepath.Glob))
L.SetField(pkg, "HasPrefix", luar.New(L, filepath.HasPrefix))
L.SetField(pkg, "IsAbs", luar.New(L, filepath.IsAbs))
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
L.SetField(pkg, "Match", luar.New(L, filepath.Match))
L.SetField(pkg, "Rel", luar.New(L, filepath.Rel))
L.SetField(pkg, "Split", luar.New(L, filepath.Split))
L.SetField(pkg, "SplitList", luar.New(L, filepath.SplitList))
L.SetField(pkg, "ToSlash", luar.New(L, filepath.ToSlash))
L.SetField(pkg, "VolumeName", luar.New(L, filepath.VolumeName))
return pkg
}
func importStrings() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Contains", luar.New(L, strings.Contains))
L.SetField(pkg, "ContainsAny", luar.New(L, strings.ContainsAny))
L.SetField(pkg, "ContainsRune", luar.New(L, strings.ContainsRune))
L.SetField(pkg, "Count", luar.New(L, strings.Count))
L.SetField(pkg, "EqualFold", luar.New(L, strings.EqualFold))
L.SetField(pkg, "Fields", luar.New(L, strings.Fields))
L.SetField(pkg, "FieldsFunc", luar.New(L, strings.FieldsFunc))
L.SetField(pkg, "HasPrefix", luar.New(L, strings.HasPrefix))
L.SetField(pkg, "HasSuffix", luar.New(L, strings.HasSuffix))
L.SetField(pkg, "Index", luar.New(L, strings.Index))
L.SetField(pkg, "IndexAny", luar.New(L, strings.IndexAny))
L.SetField(pkg, "IndexByte", luar.New(L, strings.IndexByte))
L.SetField(pkg, "IndexFunc", luar.New(L, strings.IndexFunc))
L.SetField(pkg, "IndexRune", luar.New(L, strings.IndexRune))
L.SetField(pkg, "Join", luar.New(L, strings.Join))
L.SetField(pkg, "LastIndex", luar.New(L, strings.LastIndex))
L.SetField(pkg, "LastIndexAny", luar.New(L, strings.LastIndexAny))
L.SetField(pkg, "LastIndexFunc", luar.New(L, strings.LastIndexFunc))
L.SetField(pkg, "Map", luar.New(L, strings.Map))
L.SetField(pkg, "NewReader", luar.New(L, strings.NewReader))
L.SetField(pkg, "NewReplacer", luar.New(L, strings.NewReplacer))
L.SetField(pkg, "Repeat", luar.New(L, strings.Repeat))
L.SetField(pkg, "Replace", luar.New(L, strings.Replace))
L.SetField(pkg, "Split", luar.New(L, strings.Split))
L.SetField(pkg, "SplitAfter", luar.New(L, strings.SplitAfter))
L.SetField(pkg, "SplitAfterN", luar.New(L, strings.SplitAfterN))
L.SetField(pkg, "SplitN", luar.New(L, strings.SplitN))
L.SetField(pkg, "Title", luar.New(L, strings.Title))
L.SetField(pkg, "ToLower", luar.New(L, strings.ToLower))
L.SetField(pkg, "ToLowerSpecial", luar.New(L, strings.ToLowerSpecial))
L.SetField(pkg, "ToTitle", luar.New(L, strings.ToTitle))
L.SetField(pkg, "ToTitleSpecial", luar.New(L, strings.ToTitleSpecial))
L.SetField(pkg, "ToUpper", luar.New(L, strings.ToUpper))
L.SetField(pkg, "ToUpperSpecial", luar.New(L, strings.ToUpperSpecial))
L.SetField(pkg, "Trim", luar.New(L, strings.Trim))
L.SetField(pkg, "TrimFunc", luar.New(L, strings.TrimFunc))
L.SetField(pkg, "TrimLeft", luar.New(L, strings.TrimLeft))
L.SetField(pkg, "TrimLeftFunc", luar.New(L, strings.TrimLeftFunc))
L.SetField(pkg, "TrimPrefix", luar.New(L, strings.TrimPrefix))
L.SetField(pkg, "TrimRight", luar.New(L, strings.TrimRight))
L.SetField(pkg, "TrimRightFunc", luar.New(L, strings.TrimRightFunc))
L.SetField(pkg, "TrimSpace", luar.New(L, strings.TrimSpace))
L.SetField(pkg, "TrimSuffix", luar.New(L, strings.TrimSuffix))
return pkg
}
func importRegexp() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Match", luar.New(L, regexp.Match))
L.SetField(pkg, "MatchReader", luar.New(L, regexp.MatchReader))
L.SetField(pkg, "MatchString", luar.New(L, regexp.MatchString))
L.SetField(pkg, "QuoteMeta", luar.New(L, regexp.QuoteMeta))
L.SetField(pkg, "Compile", luar.New(L, regexp.Compile))
L.SetField(pkg, "CompilePOSIX", luar.New(L, regexp.CompilePOSIX))
L.SetField(pkg, "MustCompile", luar.New(L, regexp.MustCompile))
L.SetField(pkg, "MustCompilePOSIX", luar.New(L, regexp.MustCompilePOSIX))
return pkg
}
func importErrors() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "New", luar.New(L, errors.New))
return pkg
}
func importTime() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "After", luar.New(L, time.After))
L.SetField(pkg, "Sleep", luar.New(L, time.Sleep))
L.SetField(pkg, "Tick", luar.New(L, time.Tick))
L.SetField(pkg, "Since", luar.New(L, time.Since))
L.SetField(pkg, "FixedZone", luar.New(L, time.FixedZone))
L.SetField(pkg, "LoadLocation", luar.New(L, time.LoadLocation))
L.SetField(pkg, "NewTicker", luar.New(L, time.NewTicker))
L.SetField(pkg, "Date", luar.New(L, time.Date))
L.SetField(pkg, "Now", luar.New(L, time.Now))
L.SetField(pkg, "Parse", luar.New(L, time.Parse))
L.SetField(pkg, "ParseDuration", luar.New(L, time.ParseDuration))
L.SetField(pkg, "ParseInLocation", luar.New(L, time.ParseInLocation))
L.SetField(pkg, "Unix", luar.New(L, time.Unix))
L.SetField(pkg, "AfterFunc", luar.New(L, time.AfterFunc))
L.SetField(pkg, "NewTimer", luar.New(L, time.NewTimer))
L.SetField(pkg, "Nanosecond", luar.New(L, time.Nanosecond))
L.SetField(pkg, "Microsecond", luar.New(L, time.Microsecond))
L.SetField(pkg, "Millisecond", luar.New(L, time.Millisecond))
L.SetField(pkg, "Second", luar.New(L, time.Second))
L.SetField(pkg, "Minute", luar.New(L, time.Minute))
L.SetField(pkg, "Hour", luar.New(L, time.Hour))
return pkg
}

View File

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

View File

@@ -15,14 +15,13 @@ import (
"github.com/mitchellh/go-homedir"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/cmd/micro/terminfo"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/encoding"
"layeh.com/gopher-luar"
)
const (
synLinesUp = 75 // How many lines up to look to do syntax highlighting
synLinesDown = 75 // How many lines down to look to do syntax highlighting
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
@@ -46,14 +45,12 @@ var (
// Version is the version number or commit hash
// These variables should be set by the linker when compiling
Version = "0.0.0-unknown"
CommitHash = "Unknown"
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"
// L is the lua state
// This is the VM that runs the plugins
L *lua.LState
// The list of views
tabs []*Tab
// This is the currently open tab
@@ -62,9 +59,17 @@ var (
// Channel of jobs running in the background
jobs chan JobFunction
// Event channel
events chan tcell.Event
autosave chan bool
// Channels for the terminal emulator
updateterm chan bool
closeterm chan int
// How many redraws have happened
numRedraw uint
)
// LoadInput determines which files should be loaded into buffers
@@ -91,30 +96,23 @@ func LoadInput() []*Buffer {
// Option 1
// We go through each file and load it
for i := 0; i < len(args); i++ {
filename = 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
}
// Check that the file exists
var input *os.File
if _, e := os.Stat(filename); e == nil {
// If it exists we load it into a buffer
input, err = os.Open(filename)
stat, _ := input.Stat()
defer input.Close()
if err != nil {
TermMessage(err)
continue
}
if stat.IsDir() {
TermMessage("Cannot read", filename, "because it is a directory")
continue
}
buf, err := NewBufferFromFile(args[i])
if err != nil {
TermMessage(err)
continue
}
// If the file didn't exist, input will be empty, and we'll open an empty buffer
if input != nil {
buffers = append(buffers, NewBuffer(input, FSize(input), filename))
} else {
buffers = append(buffers, NewBufferFromString("", filename))
}
buffers = append(buffers, buf)
}
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
// Option 2
@@ -149,6 +147,15 @@ func InitConfigDir() {
}
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)
@@ -169,7 +176,20 @@ func InitConfigDir() {
// InitScreen creates and initializes the tcell screen
func InitScreen() {
// Should we enable true color?
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
truecolor := false
colorterm := os.Getenv("COLORTERM")
if colorterm == "24bit" || colorterm == "truecolor" {
truecolor = true
}
microtc := os.Getenv("MICRO_TRUECOLOR")
if microtc == "1" {
truecolor = true
} else if microtc == "0" {
truecolor = false
}
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
@@ -182,12 +202,19 @@ func InitScreen() {
var err error
screen, err = tcell.NewScreen()
if err != nil {
fmt.Println(err)
if err == tcell.ErrTermNotFound {
fmt.Println("Micro does not recognize your terminal:", oldTerm)
fmt.Println("Please go to https://github.com/zyedidia/mkinfo to read about how to fix this problem (it should be easy to fix).")
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)
}
os.Exit(1)
}
if err = screen.Init(); err != nil {
fmt.Println(err)
@@ -199,8 +226,13 @@ func InitScreen() {
os.Setenv("TERM", oldTerm)
}
screen.SetStyle(defStyle)
screen.EnableMouse()
if GetGlobalOption("mouse").(bool) {
screen.EnableMouse()
}
os.Setenv("TCELLDB", tcelldb)
// screen.SetStyle(defStyle)
}
// RedrawAll redraws everything -- all the views and the messenger
@@ -214,12 +246,20 @@ func RedrawAll() {
}
}
for _, v := range tabs[curTab].views {
for _, v := range tabs[curTab].Views {
v.Display()
}
DisplayTabs()
messenger.Display()
if globalSettings["keymenu"].(bool) {
DisplayKeyMenu()
}
screen.Show()
if numRedraw%50 == 0 {
runtime.GC()
}
numRedraw++
}
func LoadAll() {
@@ -238,22 +278,37 @@ func LoadAll() {
InitColorscheme()
for _, tab := range tabs {
for _, v := range tab.views {
for _, v := range tab.Views {
v.Buf.UpdateRules()
}
}
}
// Passing -version as a flag will have micro print out the version number
// 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.Print("Micro's options can be set via command line arguments for quick adjustments. For real configuration, please use the bindings.json file (see 'help options').\n\n")
flag.CommandLine.SetOutput(os.Stdout)
flag.PrintDefaults()
fmt.Println("-config-dir dir")
fmt.Println(" \tSpecify a custom location for the configuration directory")
fmt.Println("-startpos LINE,COL")
fmt.Println("+LINE:COL")
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
fmt.Println(" \tThis can also be done by opening file:LINE:COL")
fmt.Println("-options")
fmt.Println(" \tShow all option help")
fmt.Println("-version")
fmt.Println(" \tShow the version number and information")
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
fmt.Println("-option value")
fmt.Println(" \tSet `option` to `value` for this session")
fmt.Println(" \tFor example: `micro -syntax off file.c`")
fmt.Println("\nUse `micro -options` to see the full list of configuration options")
}
optionFlags := make(map[string]*string)
@@ -272,6 +327,15 @@ func main() {
os.Exit(0)
}
if *flagOptions {
// If -options was passed
for k, v := range DefaultGlobalSettings() {
fmt.Printf("-%s value\n", k)
fmt.Printf(" \tThe %s option. Default value: '%v'\n", k, v)
}
os.Exit(0)
}
// Start the Lua VM for running plugins
L = lua.NewState()
defer L.Close()
@@ -311,7 +375,7 @@ func main() {
// Create a new messenger
// This is used for sending the user messages in the bottom of the editor
messenger = new(Messenger)
messenger.history = make(map[string][]string)
messenger.LoadHistory()
// Now we load the input
buffers := LoadInput()
@@ -326,7 +390,7 @@ func main() {
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
for _, t := range tabs {
for _, v := range t.views {
for _, v := range t.Views {
v.Center(false)
}
@@ -356,15 +420,23 @@ func main() {
L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
L.SetGlobal("HandleCommand", luar.New(L, HandleCommand))
L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
L.SetGlobal("ExecCommand", luar.New(L, ExecCommand))
L.SetGlobal("RunShellCommand", luar.New(L, RunShellCommand))
L.SetGlobal("RunBackgroundShell", luar.New(L, RunBackgroundShell))
L.SetGlobal("RunInteractiveShell", luar.New(L, RunInteractiveShell))
L.SetGlobal("TermEmuSupported", luar.New(L, TermEmuSupported))
L.SetGlobal("RunTermEmulator", luar.New(L, RunTermEmulator))
L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion))
L.SetGlobal("NewBuffer", luar.New(L, NewBufferFromString))
L.SetGlobal("NewBufferFromFile", luar.New(L, NewBufferFromFile))
L.SetGlobal("RuneStr", luar.New(L, func(r rune) string {
return string(r)
}))
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))
@@ -385,25 +457,26 @@ func main() {
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 {
for pl := range loadedPlugins {
_, err := Call(pl+".onViewOpen", v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
continue
}
}
for _, v := range t.Views {
GlobalPluginCall("onViewOpen", v)
GlobalPluginCall("onBufferOpen", v.Buf)
}
}
InitColorscheme()
messenger.style = defStyle
// Here is the event loop which runs in a separate thread
go func() {
@@ -436,11 +509,19 @@ func main() {
f.function(f.output, f.args...)
continue
case <-autosave:
CurView().Save(true)
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 {
@@ -464,30 +545,44 @@ func main() {
if CurView().mouseReleased {
// We loop through each view in the current tab and make sure the current view
// is the one being clicked in
for _, v := range tabs[curTab].views {
for _, v := range tabs[curTab].Views {
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
tabs[curTab].CurView = v.Num
}
}
}
} else if e.Buttons() == tcell.WheelUp || e.Buttons() == tcell.WheelDown {
var view *View
x, y := e.Position()
for _, v := range tabs[curTab].Views {
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
view = tabs[curTab].Views[v.Num]
}
}
if view != nil {
view.HandleEvent(e)
didAction = true
}
}
}
}
// 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 !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)
if searching {
// Since searching is done in real time, we need to redraw every time
// there is a new event in the search bar so we need a special function
// to run instead of the standard HandleEvent.
HandleSearchEvent(event, CurView())
} else {
// Send it to the view
CurView().HandleEvent(event)
}
}
select {

View File

@@ -61,6 +61,10 @@ func LuaFunctionBinding(function string) func(*View, bool) bool {
}
}
// LuaFunctionMouseBinding is a function generator which takes the name of a lua function
// and creates a function that will call that lua function
// Specifically it creates a function that can be called as a mouse binding because this is used
// to bind mouse actions to lua functions
func LuaFunctionMouseBinding(function string) func(*View, bool, *tcell.EventMouse) bool {
return func(v *View, _ bool, e *tcell.EventMouse) bool {
_, err := Call(function, e)
@@ -114,10 +118,13 @@ func LuaFunctionComplete(function string) func(string) []string {
}
}
// LuaFunctionJob returns a function that will call the given lua function
// structured as a job call i.e. the job output and arguments are provided
// to the lua function
func LuaFunctionJob(function string) func(string, ...string) {
return func(output string, args ...string) {
_, err := Call(function, unpack(append([]string{output}, args...))...)
if err != nil {
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
}
}
@@ -130,11 +137,9 @@ func luaPluginName(name string) string {
// 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
@@ -147,9 +152,8 @@ func LoadPlugins() {
}
pluginLuaName := luaPluginName(pluginName)
pluginDef := "\nlocal P = {}\n" + pluginLuaName + " = P\nsetmetatable(" + pluginLuaName + ", {__index = _G})\nsetfenv(1, P)\n"
if err := L.DoString(pluginDef + string(data)); err != nil {
if err := LoadFile(pluginLuaName, pluginLuaName, string(data)); err != nil {
TermMessage(err)
continue
}
@@ -159,11 +163,22 @@ func LoadPlugins() {
}
if _, err := os.Stat(configDir + "/init.lua"); err == nil {
pluginDef := "\nlocal P = {}\n" + "init" + " = P\nsetmetatable(" + "init" + ", {__index = _G})\nsetfenv(1, P)\n"
data, _ := ioutil.ReadFile(configDir + "/init.lua")
if err := L.DoString(pluginDef + string(data)); err != nil {
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

@@ -14,8 +14,8 @@ import (
"sync"
"github.com/blang/semver"
"github.com/flynn/json5"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/json5/encoding/json5"
)
var (
@@ -422,6 +422,7 @@ func (pv *PluginVersion) DownloadAndInstall() error {
}
}
// Install files and directory's
for _, f := range z.File {
parts := strings.Split(f.Name, "/")
if allPrefixed {
@@ -434,6 +435,12 @@ func (pv *PluginVersion) DownloadAndInstall() error {
return err
}
} else {
basepath := filepath.Dir(targetName)
if err := os.MkdirAll(basepath, dirPerm); err != nil {
return err
}
content, err := f.Open()
if err != nil {
return err
@@ -534,7 +541,7 @@ func (pv PluginVersions) install() {
shouldInstall := true
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
if pv.Version.NE(sel.Version) {
messenger.AddLog(fmt.Sprint("Uninstalling %q", sel.pack.Name))
messenger.AddLog("Uninstalling", sel.pack.Name)
UninstallPlugin(sel.pack.Name)
} else {
shouldInstall = false

View File

@@ -1,10 +1,11 @@
package main
import (
"github.com/blang/semver"
"testing"
"github.com/zyedidia/json5/encoding/json5"
"github.com/blang/semver"
"github.com/flynn/json5"
)
func TestDependencyResolving(t *testing.T) {
@@ -35,7 +36,7 @@ func TestDependencyResolving(t *testing.T) {
if v == nil {
t.Errorf("Failed to resolve %s", name)
} else if expected.NE(v.Version) {
t.Errorf("%s resolved in wrong version got %s", name, v)
t.Errorf("%s resolved in wrong version %v", name, v)
}
}

File diff suppressed because one or more lines are too long

20
cmd/micro/scrollbar.go Normal file
View File

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

View File

@@ -64,7 +64,12 @@ func HandleSearchEvent(event tcell.Event, v *View) {
// Exit the search mode
ExitSearch(v)
return
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEnter:
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
@@ -113,6 +118,8 @@ func searchDown(r *regexp.Regexp, v *View, start, end Loc) bool {
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
@@ -140,6 +147,8 @@ func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
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
@@ -175,9 +184,7 @@ func Search(searchStr string, v *View, down bool) {
found = searchUp(r, v, v.Buf.End(), searchStart)
}
}
if found {
lastSearch = searchStr
} else {
if !found {
v.Cursor.ResetSelection()
}
}

View File

@@ -1,6 +1,8 @@
package main
import (
"crypto/md5"
"encoding/json"
"errors"
"io/ioutil"
"os"
@@ -8,8 +10,8 @@ import (
"strconv"
"strings"
"github.com/flynn/json5"
"github.com/zyedidia/glob"
"github.com/zyedidia/json5/encoding/json5"
)
type optionValidator func(string, interface{}) error
@@ -17,6 +19,8 @@ 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,
@@ -24,10 +28,12 @@ var optionValidators = map[string]optionValidator{
"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{}
@@ -38,12 +44,14 @@ func InitGlobalSettings() {
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
@@ -71,6 +79,7 @@ func InitGlobalSettings() {
// 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"
@@ -78,26 +87,36 @@ func InitLocalSettings(buf *Buffer) {
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") {
g, err := glob.Compile(k)
if err != nil {
TermMessage("Error with glob setting ", k, ": ", err)
continue
}
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
if g.MatchString(buf.Path) {
for k1, v1 := range v.(map[string]interface{}) {
buf.Settings[k1] = v1
}
}
}
}
@@ -106,6 +125,11 @@ func InitLocalSettings(buf *Buffer) {
// 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{})
@@ -124,6 +148,7 @@ func WriteSettings(filename string) error {
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
invalidSettings = true
}
for k, v := range parsed {
@@ -136,7 +161,7 @@ func WriteSettings(filename string) error {
}
}
txt, _ := json5.MarshalIndent(parsed, "", " ")
txt, _ := json.MarshalIndent(parsed, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return err
@@ -176,35 +201,44 @@ func GetOption(name string) interface{} {
func DefaultGlobalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"keepautoindent": false,
"autosave": false,
"basename": false,
"colorcolumn": float64(0),
"colorscheme": "default",
"cursorline": true,
"eofnewline": false,
"rmtrailingws": 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,
"scrollspeed": float64(2),
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"softwrap": false,
"splitRight": true,
"splitBottom": true,
"splitbottom": true,
"splitright": true,
"statusline": true,
"sucmd": "sudo",
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"termtitle": false,
"pluginchannels": []string{
"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
},
"pluginrepos": []string{},
"useprimary": true,
"useprimary": true,
}
}
@@ -213,23 +247,30 @@ func DefaultGlobalSettings() map[string]interface{} {
func DefaultLocalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"keepautoindent": false,
"autosave": false,
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"eofnewline": false,
"rmtrailingws": 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,
"scrollspeed": float64(2),
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"softwrap": false,
"splitRight": true,
"splitBottom": true,
"splitbottom": true,
"splitright": true,
"statusline": true,
"syntax": true,
"tabmovement": false,
@@ -283,22 +324,32 @@ func SetOption(option, value string) error {
// LoadSyntaxFiles()
InitColorscheme()
for _, tab := range tabs {
for _, view := range tab.views {
for _, view := range tab.Views {
view.Buf.UpdateRules()
}
}
}
if option == "infobar" {
if option == "infobar" || option == "keymenu" {
for _, tab := range tabs {
tab.Resize()
}
}
if _, ok := CurView().Buf.Settings[option]; ok {
for _, tab := range tabs {
for _, view := range tab.views {
SetLocalOption(option, value, view)
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)
}
}
}
}
@@ -338,6 +389,26 @@ func SetLocalOption(option, value string, view *View) error {
return err
}
if option == "fastdirty" {
// If it is being turned off, we have to hash every open buffer
var empty [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" {
@@ -350,11 +421,17 @@ func SetLocalOption(option, value string, view *View) error {
buf.UpdateRules()
}
if option == "fileformat" {
buf.IsModified = true
}
if option == "syntax" {
if !nativeValue.(bool) {
buf.ClearMatches()
} else {
buf.highlighter.HighlightStates(buf)
if buf.highlighter != nil {
buf.highlighter.HighlightStates(buf)
}
}
}
@@ -430,3 +507,17 @@ func validateColorscheme(option string, value interface{}) error {
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
}

129
cmd/micro/shell.go Normal file
View File

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

View File

@@ -0,0 +1,18 @@
// +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

@@ -0,0 +1,11 @@
// +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,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016 Stepets
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

View File

@@ -0,0 +1,49 @@
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

@@ -0,0 +1,180 @@
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

@@ -0,0 +1,229 @@
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

@@ -0,0 +1,22 @@
// +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

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

View File

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

View File

@@ -1,6 +1,7 @@
package main
import (
"path"
"strconv"
)
@@ -14,13 +15,20 @@ type Statusline struct {
// Display draws the statusline to the screen
func (sline *Statusline) Display() {
if messenger.hasPrompt && !GetGlobalOption("infobar").(bool) {
return
}
// We'll draw the line at the lowest line in the view
y := sline.view.Height + sline.view.y
file := sline.view.Buf.GetName()
if sline.view.Buf.Settings["basename"].(bool) {
file = path.Base(file)
}
// If the buffer is dirty (has been modified) write a little '+'
if sline.view.Buf.IsModified {
if sline.view.Buf.Modified() {
file += " +"
}
@@ -36,12 +44,28 @@ func (sline *Statusline) Display() {
// Add the filetype
file += " " + sline.view.Buf.FileType()
file += " " + sline.view.Buf.Settings["fileformat"].(string)
rightText := ""
if len(helpBinding) > 0 {
rightText = helpBinding + " for help "
if sline.view.Type == vtHelp {
rightText = helpBinding + " to close help "
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)
@@ -51,6 +75,12 @@ func (sline *Statusline) Display() {
// Maybe there is a unicode filename?
fileRunes := []rune(file)
if sline.view.Type == vtTerm {
fileRunes = []rune(sline.view.term.title)
rightText = ""
}
viewX := sline.view.x
if viewX != 0 {
screen.SetContent(viewX, y, ' ', nil, statusLineStyle)

View File

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

228
cmd/micro/terminal.go Normal file
View File

@@ -0,0 +1,228 @@
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

@@ -0,0 +1,203 @@
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

@@ -0,0 +1,6 @@
# 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

@@ -0,0 +1,512 @@
// 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

@@ -0,0 +1,837 @@
// 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

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

View File

@@ -1,8 +1,8 @@
package main
import (
"bytes"
"os"
"os/user"
"path/filepath"
"reflect"
"runtime"
@@ -23,6 +23,54 @@ 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
@@ -55,6 +103,7 @@ func Max(a, b int) int {
return b
}
// FSize gets the size of a file
func FSize(f *os.File) int64 {
fi, _ := f.Stat()
// get the size
@@ -129,7 +178,7 @@ func GetLeadingWhitespace(str string) string {
}
// IsSpaces checks if a given string is only spaces
func IsSpaces(str string) bool {
func IsSpaces(str []byte) bool {
for _, c := range str {
if c != ' ' {
return false
@@ -245,6 +294,7 @@ func lcs(a, b string) string {
return lcs
}
// CommonSubstring gets a common substring among the inputs
func CommonSubstring(arr ...string) string {
commonStr := arr[0]
@@ -273,93 +323,44 @@ func ShortFuncName(i interface{}) string {
return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
}
// SplitCommandArgs separates multiple command arguments which may be quoted.
// The returned slice contains at least one string
func SplitCommandArgs(input string) []string {
var result []string
var curQuote *bytes.Buffer
curArg := new(bytes.Buffer)
escape := false
finishQuote := func() {
if curQuote == nil {
return
}
str := curQuote.String()
if unquoted, err := strconv.Unquote(str); err == nil {
str = unquoted
}
curArg.WriteString(str)
curQuote = nil
// 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
}
appendResult := func() {
finishQuote()
escape = false
var userData *user.User
var err error
str := curArg.String()
result = append(result, str)
curArg.Reset()
}
for _, r := range input {
if r == ' ' && curQuote == nil {
appendResult()
} else {
runeHandled := false
appendRuneToBuff := func() {
if curQuote != nil {
curQuote.WriteRune(r)
} else {
curArg.WriteRune(r)
}
runeHandled = true
}
if r == '"' && curQuote == nil {
curQuote = new(bytes.Buffer)
appendRuneToBuff()
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 {
if curQuote != nil && !escape {
if r == '"' {
appendRuneToBuff()
finishQuote()
} else if r == '\\' {
appendRuneToBuff()
escape = true
continue
}
}
TermMessage("Could not find user: ", err)
}
if !runeHandled {
appendRuneToBuff()
}
}
escape = false
}
appendResult()
return result
}
// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
func JoinCommandArgs(args ...string) string {
buf := new(bytes.Buffer)
first := true
for _, arg := range args {
if first {
first = false
} else {
buf.WriteRune(' ')
}
quoted := strconv.Quote(arg)
if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
buf.WriteString(quoted)
} else {
buf.WriteString(arg)
return ""
}
}
return buf.String()
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,7 +1,6 @@
package main
import (
"reflect"
"testing"
)
@@ -67,56 +66,6 @@ func TestIsWordChar(t *testing.T) {
}
}
func TestJoinAndSplitCommandArgs(t *testing.T) {
tests := []struct {
Query []string
Wanted string
}{
{[]string{`test case`}, `"test case"`},
{[]string{`quote "test"`}, `"quote \"test\""`},
{[]string{`slash\\\ test`}, `"slash\\\\\\ test"`},
{[]string{`path 1`, `path\" 2`}, `"path 1" "path\\\" 2"`},
{[]string{`foo`}, `foo`},
{[]string{`foo\"bar`}, `"foo\\\"bar"`},
{[]string{``}, ``},
{[]string{`"`}, `"\""`},
{[]string{`a`, ``}, `a `},
{[]string{``, ``, ``, ``}, ` `},
{[]string{"\n"}, `"\n"`},
{[]string{"foo\tbar"}, `"foo\tbar"`},
}
for i, test := range tests {
if result := JoinCommandArgs(test.Query...); test.Wanted != result {
t.Errorf("JoinCommandArgs failed at Test %d\nGot: %q", i, result)
}
if result := SplitCommandArgs(test.Wanted); !reflect.DeepEqual(test.Query, result) {
t.Errorf("SplitCommandArgs failed at Test %d\nGot: `%q`", i, result)
}
}
splitTests := []struct {
Query string
Wanted []string
}{
{`"hallo""Welt"`, []string{`halloWelt`}},
{`"hallo" "Welt"`, []string{`hallo`, `Welt`}},
{`\"`, []string{`\"`}},
{`"foo`, []string{`"foo`}},
{`"foo"`, []string{`foo`}},
{`"\"`, []string{`"\"`}},
{`"C:\\"foo.txt`, []string{`C:\foo.txt`}},
{`"\n"new"\n"line`, []string{"\nnew\nline"}},
}
for i, test := range splitTests {
if result := SplitCommandArgs(test.Query); !reflect.DeepEqual(test.Wanted, result) {
t.Errorf("SplitCommandArgs failed at Split-Test %d\nGot: `%q`", i, result)
}
}
}
func TestStringWidth(t *testing.T) {
tabsize := 4
if w := StringWidth("1\t2", tabsize); w != 5 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1108,6 +1108,7 @@ function preInsertNewline(v)
v:InsertNewline(false)
v:InsertTab(false)
v.Buf:Insert(-v.Cursor.Loc, "\n" .. ws)
v:StartOfLine(false)
v:CursorLeft(false)
return false
end

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

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