Compare commits

..

169 Commits

Author SHA1 Message Date
Zachary Yedidia
1856891622 Update nightly release script to not duplicate nightlies 2018-07-20 00:24:02 +00:00
djmnzp
8a250f7d95 Update ats syntax (#1141)
* Multiple changes
 - Fixed overlapping between the macros and some statements.
 - Added "t" and "abs" as types.
 - Removed "fun0", "fun1", "clo0", "clo1", ..., "prf" from types and added them to the special block as effects.
 - Added "lin", "lincloptr0" and "lincloptr1" as effects.
 - Added "do" and "static" as statements.
 - Added "tupz!" and "prerr!" to the special block.
 - Fixed some typos.

* Updated regex for exhaustive types

* Final touches

* Removed "t" from types

* Minor fix

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

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

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

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

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

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

- go version go1.10.3 darwin/amd64

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

* Fixed crash when -startpos has an invalid argument

* Adapted tests to new interface

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

* Changed Fatalf format back to digits

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

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

* Fixed off-by-one line error

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

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

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

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

* Fixed path matching with regex by @Pariador

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

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

* added exunit support

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

* Parallel hash calculation.

* Minor changes.

* Removed unnecessary memory allocations while trimming trailing whitespace.

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

* support selecting page up and page down

* fix matchbraceleft for braces that start on x=0

* fix multiline copy-paste indenting

let's say you have two lines like

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

so you start from cursor x=0 and select both lines, then paste.
we don't want any leading whitespace in this case, because the
cursor is already at x=0 and the selection already includes
whitespace.
2018-05-12 21:31:57 -04:00
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
Ayman Nedjmeddine
4365b66398 Add a table of contents in the README 2017-10-10 19:37:28 +01:00
85 changed files with 5380 additions and 2369 deletions

15
.gitmodules vendored
View File

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

View File

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

View File

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

View File

@@ -20,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
@@ -30,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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -7,16 +7,13 @@
.\" See \usr\share\doc\micro\LICENSE for more information.
.TH micro 1 "2017-03-28"
.SH NAME
micro \- An intuitive and modern terminal text editor
.
micro \- A modern and intuitive terminal-based text editor
.SH SYNOPSIS
.B micro
.RB []
[
.I "filename \&..."
]
.RB [OPTIONS]
[FILE]\&...
.SH DESCRIPTION
( Copied from the README file. )
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.
@@ -25,19 +22,39 @@ As the name indicates, micro aims to be somewhat of a successor to the nano edit
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
.SH OPTIONS
.B \-v --version
Displays the current version of micro and the git commit hash.
.TP
.SH ENVIRONMENT
Micro's behaviour can be changed by setting environment variables, of which
there is currently only one:
.I MICRO_TRUE_COLOR
.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
When MICRO_TRUE_COLOR 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_TRUE_COLOR 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 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

View File

@@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/clipboard"
@@ -434,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)
@@ -458,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) {
@@ -543,6 +562,8 @@ func (v *View) ParagraphNext(usePlugin bool) bool {
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
@@ -679,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()
@@ -722,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 {
@@ -809,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))
@@ -869,6 +896,8 @@ 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
@@ -922,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)
}
}
@@ -941,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
}
@@ -991,7 +1020,7 @@ 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
}
@@ -1008,7 +1037,7 @@ func (v *View) SaveAs(usePlugin bool) bool {
}
if usePlugin {
PostActionCall("Find", v)
PostActionCall("SaveAs", v)
}
}
return false
@@ -1190,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
@@ -1351,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) {
@@ -1465,6 +1515,42 @@ func (v *View) PageDown(usePlugin bool) bool {
return false
}
// SelectPageUp selects up one page
func (v *View) SelectPageUp(usePlugin bool) bool {
if usePlugin && !PreActionCall("SelectPageUp", v) {
return false
}
if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = v.Cursor.Loc
}
v.Cursor.UpN(v.Height)
v.Cursor.SelectTo(v.Cursor.Loc)
if usePlugin {
return PostActionCall("SelectPageUp", v)
}
return true
}
// SelectPageDown selects down one page
func (v *View) SelectPageDown(usePlugin bool) bool {
if usePlugin && !PreActionCall("SelectPageDown", v) {
return false
}
if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = v.Cursor.Loc
}
v.Cursor.DownN(v.Height)
v.Cursor.SelectTo(v.Cursor.Loc)
if usePlugin {
return PostActionCall("SelectPageDown", v)
}
return true
}
// CursorPageUp places the cursor a page up
func (v *View) CursorPageUp(usePlugin bool) bool {
if usePlugin && !PreActionCall("CursorPageUp", v) {
@@ -1578,21 +1664,38 @@ func (v *View) JumpLine(usePlugin bool) bool {
}
// Prompt for line number
message := fmt.Sprintf("Jump to line (1 - %v) # ", v.Buf.NumLines)
linestring, canceled := messenger.Prompt(message, "", "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)
@@ -1697,6 +1800,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() {
@@ -1725,12 +1844,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)
@@ -1769,7 +1888,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
}
@@ -1782,7 +1901,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()
}
}
@@ -1814,7 +1933,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()
}
}
@@ -1907,8 +2026,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)
@@ -1930,7 +2049,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
@@ -1954,7 +2073,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 {
@@ -2065,6 +2184,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 {

View File

@@ -1,9 +1,9 @@
// +build android plan9 nacl windows
// +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

@@ -142,6 +142,7 @@ 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
@@ -219,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) {
@@ -232,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,14 +1,17 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"unicode"
"github.com/flynn/json5"
"github.com/zyedidia/tcell"
)
var bindingsStr map[string]string
var bindings map[Key][]func(*View, bool) bool
var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
var helpBinding string
@@ -20,93 +23,99 @@ 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,
"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,
"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,
"SelectPageUp": (*View).SelectPageUp,
"SelectPageDown": (*View).SelectPageDown,
"HalfPageUp": (*View).HalfPageUp,
"HalfPageDown": (*View).HalfPageDown,
"StartOfLine": (*View).StartOfLine,
"EndOfLine": (*View).EndOfLine,
"ToggleHelp": (*View).ToggleHelp,
"ToggleKeyMenu": (*View).ToggleKeyMenu,
"ToggleRuler": (*View).ToggleRuler,
"JumpLine": (*View).JumpLine,
"ClearStatus": (*View).ClearStatus,
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"ToggleOverwriteMode": (*View).ToggleOverwriteMode,
"Escape": (*View).Escape,
"Quit": (*View).Quit,
"QuitAll": (*View).QuitAll,
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
"Unsplit": (*View).Unsplit,
"VSplit": (*View).VSplitBinding,
"HSplit": (*View).HSplitBinding,
"ToggleMacro": (*View).ToggleMacro,
"PlayMacro": (*View).PlayMacro,
"Suspend": (*View).Suspend,
"ScrollUp": (*View).ScrollUpAction,
"ScrollDown": (*View).ScrollDownAction,
"SpawnMultiCursor": (*View).SpawnMultiCursor,
"SpawnMultiCursorSelect": (*View).SpawnMultiCursorSelect,
"RemoveMultiCursor": (*View).RemoveMultiCursor,
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
"SkipMultiCursor": (*View).SkipMultiCursor,
"JumpToMatchingBrace": (*View).JumpToMatchingBrace,
// This was changed to InsertNewline but I don't want to break backwards compatibility
"InsertEnter": (*View).InsertNewline,
@@ -248,6 +257,7 @@ var bindingKeys = map[string]tcell.Key{
"Escape": tcell.KeyEscape,
"Enter": tcell.KeyEnter,
"Backspace": tcell.KeyBackspace2,
"OldBackspace": tcell.KeyBackspace,
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
"PgUp": tcell.KeyPgUp,
@@ -266,6 +276,7 @@ type Key struct {
// 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
@@ -330,11 +341,16 @@ 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{
@@ -400,6 +416,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)
@@ -424,6 +477,7 @@ func BindKey(k, v string) {
if actionNames[0] == "UnbindKey" {
delete(bindings, key)
delete(mouseBindings, key)
delete(bindingsStr, k)
if len(actionNames) == 1 {
return
}
@@ -434,6 +488,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))
}
@@ -443,6 +503,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)
@@ -521,6 +582,7 @@ func DefaultBindings() map[string]string {
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
@@ -546,6 +608,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

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

117
cmd/micro/buffer_test.go Normal file
View File

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

View File

@@ -69,12 +69,32 @@ func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
return
}
matchingBrace := Loc{-1, -1}
// bracePairs is defined in buffer.go
if buf.Settings["matchbrace"].(bool) {
for _, bp := range bracePairs {
curX := buf.Cursor.X
curLoc := buf.Cursor.Loc
if buf.Settings["matchbraceleft"].(bool) {
if curX > 0 {
curX--
curLoc = curLoc.Move(-1, buf)
}
}
r := buf.Cursor.RuneUnder(curX)
if r == bp[0] || r == bp[1] {
matchingBrace = buf.FindMatchingBrace(bp, curLoc)
}
}
}
tabsize := int(buf.Settings["tabsize"].(float64))
softwrap := buf.Settings["softwrap"].(bool)
indentrunes := []rune(buf.Settings["indentchar"].(string))
// if empty indentchar settings, use space
if indentrunes == nil || len(indentrunes) == 0 {
indentrunes = []rune(" ")
indentrunes = []rune{' '}
}
indentchar := indentrunes[0]
@@ -137,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
@@ -146,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
}
@@ -155,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}
}
}
@@ -167,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

@@ -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,16 +1,14 @@
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"unicode/utf8"
humanize "github.com/dustin/go-humanize"
"github.com/zyedidia/micro/cmd/micro/shellwords"
@@ -37,6 +35,7 @@ func init() {
"Set": Set,
"SetLocal": SetLocal,
"Show": Show,
"ShowKey": ShowKey,
"Run": Run,
"Bind": Bind,
"Quit": Quit,
@@ -55,8 +54,10 @@ func init() {
"Pwd": Pwd,
"Open": Open,
"TabSwitch": TabSwitch,
"Term": Term,
"MemUsage": MemUsage,
"Retab": Retab,
"Raw": Raw,
}
}
@@ -93,6 +94,7 @@ func DefaultCommands() map[string]StrCommand {
"set": {"Set", []Completion{OptionCompletion, OptionValueCompletion}},
"setlocal": {"SetLocal", []Completion{OptionCompletion, OptionValueCompletion}},
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
"showkey": {"ShowKey", []Completion{NoCompletion}},
"bind": {"Bind", []Completion{NoCompletion}},
"run": {"Run", []Completion{NoCompletion}},
"quit": {"Quit", []Completion{NoCompletion}},
@@ -111,8 +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
}
}
@@ -151,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")
@@ -199,10 +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 {
@@ -212,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
@@ -243,7 +293,7 @@ func Cd(args []string) {
}
wd, _ := os.Getwd()
for _, tab := range tabs {
for _, view := range tab.views {
for _, view := range tab.Views {
if len(view.Buf.name) == 0 {
continue
}
@@ -341,24 +391,10 @@ func VSplit(args []string) {
if len(args) == 0 {
CurView().VSplit(NewBufferFromString("", ""))
} else {
filename := args[0]
filename = ReplaceHome(filename)
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)
}
@@ -370,24 +406,10 @@ func HSplit(args []string) {
if len(args) == 0 {
CurView().HSplit(NewBufferFromString("", ""))
} else {
filename := args[0]
filename = ReplaceHome(filename)
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)
}
@@ -410,23 +432,10 @@ func NewTab(args []string) {
if len(args) == 0 {
CurView().AddTab(true)
} else {
filename := args[0]
filename = ReplaceHome(filename)
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))
@@ -435,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()
}
}
@@ -489,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 {
@@ -552,6 +575,7 @@ func Replace(args []string) {
}
replace := string(args[1])
replaceBytes := []byte(replace)
regex, err := regexp.Compile("(?m)" + search)
if err != nil {
@@ -565,23 +589,16 @@ 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)
}
@@ -644,94 +661,17 @@ func ReplaceAll(args []string) {
Replace(append(args, "-a"))
}
// 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]
cmd := exec.Command(inputCmd, args[1:]...)
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 {
args, err := shellwords.Split(input)
if err != nil {
return ""
}
inputCmd := args[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 := args[1:]
// Set up everything for the command
var output string
cmd := exec.Command(inputCmd, args...)
cmd.Stdin = os.Stdin
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()
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

View File

@@ -35,6 +35,13 @@ func (c *Cursor) Goto(b Cursor) {
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
}
// GotoLoc puts the cursor at the given cursor's location and gives
// the current cursor its selection too
func (c *Cursor) GotoLoc(l Loc) {
c.X, c.Y = l.X, l.Y
c.LastVisualX = c.GetVisualX()
}
// CopySelection copies the user's selection to either "primary"
// or "clipboard"
func (c *Cursor) CopySelection(target string) {
@@ -326,6 +333,18 @@ func (c *Cursor) Start() {
c.LastVisualX = c.GetVisualX()
}
// StartOfText moves the cursor to the first non-whitespace rune of
// the line it is on
func (c *Cursor) StartOfText() {
c.Start()
for IsWhitespace(c.RuneUnder(c.X)) {
if c.X == Count(c.buf.Line(c.Y)) {
break
}
c.Right()
}
}
// GetCharPosInLine gets the char position of a visual x y
// coordinate (this is necessary because tabs are 1 char but
// 4 visual spaces)

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

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

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)
@@ -85,7 +89,6 @@ func NewLineArray(size int64, reader io.Reader) *LineArray {
if n >= 1000 && loaded >= 0 {
totalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))
newSlice := make([]Line, len(la.lines), totalLinesNum+10000)
// The copy function is predeclared and works for any slice type.
copy(newSlice, la.lines)
la.lines = newSlice
loaded = -1
@@ -143,9 +146,9 @@ func (la *LineArray) SaveString(useCrlf bool) string {
// NewlineBelow adds a newline below the given line number
func (la *LineArray) NewlineBelow(y int) {
la.lines = append(la.lines, Line{[]byte(" "), nil, nil, false})
la.lines = append(la.lines, Line{[]byte{' '}, nil, nil, false})
copy(la.lines[y+2:], la.lines[y+1:])
la.lines[y+1] = Line{[]byte(""), la.lines[y].state, nil, false}
la.lines[y+1] = Line{[]byte{}, la.lines[y].state, nil, false}
}
// inserts a byte array at a given location
@@ -243,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

@@ -416,7 +416,6 @@ func importFilePath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
L.SetField(pkg, "Clean", luar.New(L, filepath.Join))
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))

View File

@@ -223,6 +223,7 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
}
}
// Completion represents a type of completion
type Completion int
const (
@@ -260,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:
@@ -322,7 +328,7 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
chosen = chosen + CommonSubstring(suggestions...)
}
if chosen != "" {
if len(suggestions) != 0 && chosen != "" {
m.response = shellwords.Join(append(args[:len(args)-1], chosen)...)
m.cursorx = Count(m.response)
}
@@ -332,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()
@@ -348,6 +354,7 @@ 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--
@@ -355,6 +362,8 @@ func (m *Messenger) UpHistory(history []string) {
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++
@@ -362,33 +371,47 @@ func (m *Messenger) DownHistory(history []string) {
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()
@@ -410,6 +433,8 @@ func (m *Messenger) WordLeft() {
}
m.CursorRight()
}
// WordRight moves the cursor one word to the right
func (m *Messenger) WordRight() {
response := []rune(m.response)
if m.cursorx >= len(response) {
@@ -433,6 +458,8 @@ func (m *Messenger) WordRight() {
}
}
}
// DeleteWordLeft deletes one word to the left
func (m *Messenger) DeleteWordLeft() {
m.WordLeft()
m.response = string([]rune(m.response)[:m.cursorx])
@@ -577,11 +604,11 @@ func (m *Messenger) Display() {
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)
file.Close()
if err != nil {
m.Error("Error loading history:", err)
@@ -606,11 +633,12 @@ func (m *Messenger) SaveHistory() {
// Don't save history past 100
for k, v := range m.history {
if len(v) > 100 {
m.history[k] = v[0: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)
@@ -619,7 +647,6 @@ func (m *Messenger) SaveHistory() {
m.Error("Error saving history:", err)
return
}
file.Close()
}
}
}

View File

@@ -15,6 +15,7 @@ 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"
@@ -44,8 +45,10 @@ 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"
// The list of views
@@ -56,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
@@ -85,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
@@ -174,6 +178,9 @@ func InitScreen() {
// Should we enable true color?
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
tcelldb := os.Getenv("TCELLDB")
os.Setenv("TCELLDB", configDir+"/.tcelldb")
// In order to enable true color, we have to set the TERM to `xterm-truecolor` when
// initializing tcell, but after that, we can set the TERM back to whatever it was
oldTerm := os.Getenv("TERM")
@@ -185,12 +192,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)
@@ -206,7 +220,9 @@ func InitScreen() {
screen.EnableMouse()
}
screen.SetStyle(defStyle)
os.Setenv("TCELLDB", tcelldb)
// screen.SetStyle(defStyle)
}
// RedrawAll redraws everything -- all the views and the messenger
@@ -220,7 +236,7 @@ func RedrawAll() {
}
}
for _, v := range tabs[curTab].views {
for _, v := range tabs[curTab].Views {
v.Display()
}
DisplayTabs()
@@ -229,6 +245,11 @@ func RedrawAll() {
DisplayKeyMenu()
}
screen.Show()
if numRedraw%50 == 0 {
runtime.GC()
}
numRedraw++
}
func LoadAll() {
@@ -247,13 +268,13 @@ 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")
@@ -265,13 +286,15 @@ func main() {
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 bindings.json\nfile (see 'help options').\n\n")
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`")
@@ -357,7 +380,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)
}
@@ -387,9 +410,16 @@ 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)
}))
@@ -423,22 +453,20 @@ func main() {
jobs = make(chan JobFunction, 100)
events = make(chan tcell.Event, 100)
autosave = make(chan bool)
updateterm = make(chan bool)
closeterm = make(chan int)
LoadPlugins()
for _, t := range tabs {
for _, v := range t.views {
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() {
@@ -471,7 +499,13 @@ 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:
}
@@ -501,7 +535,7 @@ 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
}
@@ -510,13 +544,15 @@ func main() {
} else if e.Buttons() == tcell.WheelUp || e.Buttons() == tcell.WheelDown {
var view *View
x, y := e.Position()
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 {
view = tabs[curTab].views[v.Num]
view = tabs[curTab].Views[v.Num]
}
}
view.HandleEvent(e)
didAction = true
if view != nil {
view.HandleEvent(e)
didAction = true
}
}
}
}

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)
}
}
@@ -163,3 +170,15 @@ func LoadPlugins() {
loadedPlugins["init"] = "init"
}
}
// GlobalCall makes a call to a function in every plugin that is currently
// loaded
func GlobalPluginCall(function string, args ...interface{}) {
for pl := range loadedPlugins {
_, err := Call(pl+"."+function, args...)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
continue
}
}
}

View File

@@ -8,7 +8,6 @@ import (
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"sort"
"strings"
@@ -423,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 {
@@ -435,7 +435,7 @@ func (pv *PluginVersion) DownloadAndInstall() error {
return err
}
} else {
basepath := path.Dir(targetName)
basepath := filepath.Dir(targetName)
if err := os.MkdirAll(basepath, dirPerm); err != nil {
return err
@@ -541,7 +541,7 @@ func (pv PluginVersions) install() {
shouldInstall := true
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
if pv.Version.NE(sel.Version) {
messenger.AddLog(fmt.Sprint("Uninstalling %q", sel.pack.Name))
messenger.AddLog("Uninstalling", sel.pack.Name)
UninstallPlugin(sel.pack.Name)
} else {
shouldInstall = false

View File

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

View File

@@ -1,15 +1,17 @@
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)
screen.SetContent(sb.view.x+sb.view.Width-1, sb.view.y+sb.pos(), ' ', nil, style)
}
func (sb *ScrollBar) Pos() int {
func (sb *ScrollBar) pos() int {
numlines := sb.view.Buf.NumLines
h := sb.view.Height
filepercent := float32(sb.view.Topline) / float32(numlines)

View File

@@ -4,11 +4,13 @@ import (
"crypto/md5"
"encoding/json"
"errors"
"io"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
"sync"
"github.com/flynn/json5"
"github.com/zyedidia/glob"
@@ -100,15 +102,23 @@ func InitLocalSettings(buf *Buffer) {
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
}
}
}
}
@@ -194,17 +204,21 @@ func DefaultGlobalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"autosave": false,
"basename": false,
"colorcolumn": float64(0),
"colorscheme": "default",
"cursorline": true,
"eofnewline": false,
"fastdirty": true,
"fileformat": "unix",
"hidehelp": false,
"ignorecase": false,
"indentchar": " ",
"infobar": true,
"keepautoindent": false,
"keymenu": false,
"matchbrace": false,
"matchbraceleft": false,
"mouse": true,
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
"pluginrepos": []string{},
@@ -236,15 +250,19 @@ func DefaultLocalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"autosave": false,
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"eofnewline": false,
"fastdirty": true,
"fileformat": "unix",
"filetype": "Unknown",
"hidehelp": false,
"ignorecase": false,
"indentchar": " ",
"keepautoindent": false,
"matchbrace": false,
"matchbraceleft": false,
"rmtrailingws": false,
"ruler": true,
"savecursor": false,
@@ -308,7 +326,7 @@ 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()
}
}
@@ -331,7 +349,7 @@ func SetOption(option, value string) error {
if len(tabs) != 0 {
if _, ok := CurView().Buf.Settings[option]; ok {
for _, tab := range tabs {
for _, view := range tab.views {
for _, view := range tab.Views {
SetLocalOption(option, value, view)
}
}
@@ -375,22 +393,36 @@ func SetLocalOption(option, value string, view *View) error {
if option == "fastdirty" {
// If it is being turned off, we have to hash every open buffer
var empty [16]byte
var empty [md5.Size]byte
var wg sync.WaitGroup
for _, tab := range tabs {
for _, v := range tab.views {
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)
wg.Add(1)
go func(b *Buffer) { // calculate md5 hash of the file
defer wg.Done()
if file, e := os.Open(b.AbsPath); e == nil {
defer file.Close()
h := md5.New()
if _, e = io.Copy(h, file); e == nil {
h.Sum(b.origHash[:0])
}
}
}(v.Buf)
}
} else {
v.Buf.IsModified = v.Buf.Modified()
}
}
}
wg.Wait()
}
buf.Settings[option] = nativeValue
@@ -413,7 +445,9 @@ func SetLocalOption(option, value string, view *View) error {
if !nativeValue.(bool) {
buf.ClearMatches()
} else {
buf.highlighter.HighlightStates(buf)
if buf.highlighter != nil {
buf.highlighter.HighlightStates(buf)
}
}
}

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

@@ -168,19 +168,10 @@ func Join(args ...string) string {
for _, b := range w {
switch b {
case 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u',
'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'_', '-', '.', ',', ':', '/', '@':
case ' ', '\t', '\r', '\n':
buf.WriteByte('\\')
buf.WriteString(string(b))
default:
buf.WriteByte('\\')
buf.WriteString(string(b))
}
}

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"
)
@@ -22,6 +23,9 @@ func (sline *Statusline) Display() {
y := sline.view.Height + sline.view.y
file := sline.view.Buf.GetName()
if sline.view.Buf.Settings["basename"].(bool) {
file = path.Base(file)
}
// If the buffer is dirty (has been modified) write a little '+'
if sline.view.Buf.Modified() {
@@ -43,24 +47,26 @@ func (sline *Statusline) Display() {
file += " " + sline.view.Buf.Settings["fileformat"].(string)
rightText := ""
if len(kmenuBinding) > 0 {
if globalSettings["keymenu"].(bool) {
rightText += kmenuBinding + ": hide bindings"
} else {
rightText += kmenuBinding + ": show bindings"
}
}
if len(helpBinding) > 0 {
if !sline.view.Buf.Settings["hidehelp"].(bool) {
if len(kmenuBinding) > 0 {
rightText += ", "
if globalSettings["keymenu"].(bool) {
rightText += kmenuBinding + ": hide bindings"
} else {
rightText += kmenuBinding + ": show bindings"
}
}
if sline.view.Type == vtHelp {
rightText += helpBinding + ": close help"
} else {
rightText += helpBinding + ": open help"
if len(helpBinding) > 0 {
if len(kmenuBinding) > 0 {
rightText += ", "
}
if sline.view.Type == vtHelp {
rightText += helpBinding + ": close help"
} else {
rightText += helpBinding + ": open help"
}
}
rightText += " "
}
rightText += " "
statusLineStyle := defStyle.Reverse(true)
if style, ok := colorscheme["statusline"]; ok {
@@ -69,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
@@ -48,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
@@ -71,15 +76,18 @@ func (t *Tab) Resize() {
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
@@ -95,7 +103,7 @@ func TabbarString() (string, map[int]int) {
} else {
str += " "
}
buf := t.views[t.CurView].Buf
buf := t.Views[t.CurView].Buf
str += buf.GetName()
if buf.Modified() {
str += " +"

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

@@ -2,6 +2,7 @@ package main
import (
"os"
"os/user"
"path/filepath"
"reflect"
"runtime"
@@ -10,8 +11,9 @@ import (
"time"
"unicode/utf8"
"github.com/go-errors/errors"
"github.com/mattn/go-runewidth"
homedir "github.com/mitchellh/go-homedir"
"regexp"
)
// Util.go is a collection of utility functions that are used throughout
@@ -23,6 +25,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
@@ -130,7 +180,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
@@ -282,10 +332,69 @@ func ReplaceHome(path string) string {
return path
}
home, err := homedir.Dir()
if err != nil {
messenger.Error("Could not find home directory: ", err)
return path
var userData *user.User
var err error
homeString := strings.Split(path, "/")[0]
if homeString == "~" {
userData, err = user.Current()
if err != nil {
messenger.Error("Could not find user: ", err)
}
} else {
userData, err = user.Lookup(homeString[1:])
if err != nil {
if messenger != nil {
messenger.Error("Could not find user: ", err)
} else {
TermMessage("Could not find user: ", err)
}
return ""
}
}
return strings.Replace(path, "~", home, 1)
home := userData.HomeDir
return strings.Replace(path, homeString, home, 1)
}
// GetPathAndCursorPosition returns a filename without everything following a `:`
// This is used for opening files like util.go:10:5 to specify a line and column
// Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
func GetPathAndCursorPosition(path string) (string, []string) {
re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
match := re.FindStringSubmatch(path)
// no lines/columns were specified in the path, return just the path with no cursor location
if len(match) == 0 {
return path, nil
} else if match[len(match)-1] != "" {
// if the last capture group match isn't empty then both line and column were provided
return match[1], match[2:]
}
// if it was empty, then only a line was provided, so default to column 0
return match[1], []string{match[2], "0"}
}
func ParseCursorLocation(cursorPositions []string) (Loc, error) {
startpos := Loc{0, 0}
var err error
// if no positions are available exit early
if cursorPositions == nil {
return startpos, errors.New("No cursor positions were provided.")
}
startpos.Y, err = strconv.Atoi(cursorPositions[0])
if err != nil {
messenger.Error("Error parsing cursor position: ", err)
} else {
if len(cursorPositions) > 1 {
startpos.X, err = strconv.Atoi(cursorPositions[1])
if err != nil {
messenger.Error("Error parsing cursor position: ", err)
}
}
}
return startpos, err
}

View File

@@ -103,3 +103,229 @@ func TestWidthOfLargeRunes(t *testing.T) {
t.Error("WidthOfLargeRunes 5 Failed. Got", w)
}
}
func assertEqual(t *testing.T, expected interface{}, result interface{}) {
if expected != result {
t.Fatalf("Expected: %d != Got: %d", expected, result)
}
}
func assertTrue(t *testing.T, condition bool) {
if !condition {
t.Fatalf("Condition was not true. Got false")
}
}
func TestGetPathRelativeWithDot(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("./myfile:10:5")
assertEqual(t, path, "./myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "5", cursorPosition[1])
}
func TestGetPathRelativeWithDotWindows(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition(".\\myfile:10:5")
assertEqual(t, path, ".\\myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, cursorPosition[1], "5")
}
func TestGetPathRelativeNoDot(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("myfile:10:5")
assertEqual(t, path, "myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, cursorPosition[1], "5")
}
func TestGetPathAbsoluteWindows(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("C:\\myfile:10:5")
assertEqual(t, path, "C:\\myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, cursorPosition[1], "5")
path, cursorPosition = GetPathAndCursorPosition("C:/myfile:10:5")
assertEqual(t, path, "C:/myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "5", cursorPosition[1])
}
func TestGetPathAbsoluteUnix(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile:10:5")
assertEqual(t, path, "/home/user/myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "5", cursorPosition[1])
}
func TestGetPathRelativeWithDotWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("./myfile")
assertEqual(t, path, "./myfile")
// no cursor position in filename, nil should be returned
assertTrue(t, cursorPosition == nil)
}
func TestGetPathRelativeWithDotWindowsWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition(".\\myfile")
assertEqual(t, path, ".\\myfile")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathRelativeNoDotWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("myfile")
assertEqual(t, path, "myfile")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathAbsoluteWindowsWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("C:\\myfile")
assertEqual(t, path, "C:\\myfile")
assertTrue(t, cursorPosition == nil)
path, cursorPosition = GetPathAndCursorPosition("C:/myfile")
assertEqual(t, path, "C:/myfile")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathAbsoluteUnixWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile")
assertEqual(t, path, "/home/user/myfile")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathSingleLetterFileRelativePath(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("a:5:6")
assertEqual(t, path, "a")
assertEqual(t, "5", cursorPosition[0])
assertEqual(t, "6", cursorPosition[1])
}
func TestGetPathSingleLetterFileAbsolutePathWindows(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("C:\\a:5:6")
assertEqual(t, path, "C:\\a")
assertEqual(t, "5", cursorPosition[0])
assertEqual(t, "6", cursorPosition[1])
path, cursorPosition = GetPathAndCursorPosition("C:/a:5:6")
assertEqual(t, path, "C:/a")
assertEqual(t, "5", cursorPosition[0])
assertEqual(t, "6", cursorPosition[1])
}
func TestGetPathSingleLetterFileAbsolutePathUnix(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("/home/user/a:5:6")
assertEqual(t, path, "/home/user/a")
assertEqual(t, "5", cursorPosition[0])
assertEqual(t, "6", cursorPosition[1])
}
func TestGetPathSingleLetterFileAbsolutePathWindowsWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("C:\\a")
assertEqual(t, path, "C:\\a")
assertTrue(t, cursorPosition == nil)
path, cursorPosition = GetPathAndCursorPosition("C:/a")
assertEqual(t, path, "C:/a")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathSingleLetterFileAbsolutePathUnixWithoutLineAndColumn(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("/home/user/a")
assertEqual(t, path, "/home/user/a")
assertTrue(t, cursorPosition == nil)
}
func TestGetPathRelativeWithDotOnlyLine(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("./myfile:10")
assertEqual(t, path, "./myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
}
func TestGetPathRelativeWithDotWindowsOnlyLine(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition(".\\myfile:10")
assertEqual(t, path, ".\\myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
}
func TestGetPathRelativeNoDotOnlyLine(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("myfile:10")
assertEqual(t, path, "myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
}
func TestGetPathAbsoluteWindowsOnlyLine(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("C:\\myfile:10")
assertEqual(t, path, "C:\\myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
path, cursorPosition = GetPathAndCursorPosition("C:/myfile:10")
assertEqual(t, path, "C:/myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
}
func TestGetPathAbsoluteUnixOnlyLine(t *testing.T) {
path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile:10")
assertEqual(t, path, "/home/user/myfile")
assertEqual(t, "10", cursorPosition[0])
assertEqual(t, "0", cursorPosition[1])
}
func TestParseCursorLocationOneArg(t *testing.T) {
location, err := ParseCursorLocation([]string{"3"})
assertEqual(t, 3, location.Y)
assertEqual(t, 0, location.X)
assertEqual(t, nil, err)
}
func TestParseCursorLocationTwoArgs(t *testing.T) {
location, err := ParseCursorLocation([]string{"3", "15"})
assertEqual(t, 3, location.Y)
assertEqual(t, 15, location.X)
assertEqual(t, nil, err)
}
func TestParseCursorLocationNoArgs(t *testing.T) {
location, err := ParseCursorLocation(nil)
// the expected result is the start position - 0, 0
assertEqual(t, 0, location.Y)
assertEqual(t, 0, location.X)
// an error will be present here as the positions we're parsing are a nil
assertTrue(t, err != nil)
}
func TestParseCursorLocationFirstArgNotValidNumber(t *testing.T) {
// the messenger is necessary as ParseCursorLocation
// puts a message in it on error
messenger = new(Messenger)
_, err := ParseCursorLocation([]string{"apples", "1"})
// the expected result is the start position - 0, 0
assertTrue(t, messenger.hasMessage)
assertTrue(t, err != nil)
}
func TestParseCursorLocationSecondArgNotValidNumber(t *testing.T) {
// the messenger is necessary as ParseCursorLocation
// puts a message in it on error
messenger = new(Messenger)
_, err := ParseCursorLocation([]string{"1", "apples"})
// the expected result is the start position - 0, 0
assertTrue(t, messenger.hasMessage)
assertTrue(t, err != nil)
}

View File

@@ -1,7 +1,8 @@
package main
import (
"os"
"fmt"
"reflect"
"strconv"
"strings"
"time"
@@ -11,9 +12,9 @@ import (
// 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.
@@ -70,6 +73,8 @@ 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
@@ -89,11 +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
@@ -131,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") {
@@ -151,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 {
@@ -168,7 +198,10 @@ func (v *View) ToggleTabbar() {
}
func (v *View) paste(clip string) {
leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
leadingWS := ""
if v.Cursor.X > 0 {
leadingWS = GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
}
if v.Cursor.HasSelection() {
v.Cursor.DeleteSelection()
@@ -242,29 +275,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) {
filename = ReplaceHome(filename)
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)
}
@@ -358,6 +383,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
@@ -410,7 +439,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
}
@@ -429,6 +458,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) {
@@ -444,7 +498,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))
}
@@ -460,7 +513,8 @@ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
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) {
@@ -470,7 +524,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 {
@@ -496,6 +550,25 @@ func (v *View) SetCursor(c *Cursor) bool {
// HandleEvent handles an event passed by the main loop
func (v *View) HandleEvent(event tcell.Event) {
if v.Type == vtTerm {
v.term.HandleEvent(event)
return
}
if v.Type == vtRaw {
v.Buf.Insert(v.Cursor.Loc, reflect.TypeOf(event).String()[7:])
v.Buf.Insert(v.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
switch e := event.(type) {
case *tcell.EventKey:
if e.Key() == tcell.KeyCtrlQ {
v.Quit(true)
}
}
return
}
// This bool determines whether the view is relocated at the end of the function
// By default it's true because most events should cause a relocate
relocate := true
@@ -506,7 +579,7 @@ func (v *View) HandleEvent(event tcell.Event) {
case *tcell.EventRaw:
for key, actions := range bindings {
if key.keyCode == -1 {
if e.EscapeCode() == key.escape {
if e.EscSeq() == key.escape {
for _, c := range v.Buf.cursors {
ok := v.SetCursor(c)
if !ok {
@@ -547,9 +620,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)
@@ -558,7 +632,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)
@@ -576,7 +657,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
}
@@ -708,12 +789,17 @@ 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()
}
@@ -777,11 +863,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

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

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

@@ -8,75 +8,68 @@ This help page aims to cover two aspects of micro's syntax highlighting engine:
## 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
These should work and look nice in most terminals. I recommend these
themes the most.
* `monokai`: this is the monokai colorscheme; you may recognize it as Sublime
Text's default colorscheme. It requires true color to look perfect, but the
256 color approximation looks very good as well. It's also the default
colorscheme.
* `zenburn`
* `gruvbox`
* `darcula`
* `twilight`
* `railscast`
* `bubblegum`: a light colorscheme
* zenburn: The 'zenburn' colorscheme and works well with 256 color terminals
### 16 color
* solarized: this is the solarized colorscheme. You should have the solarized
color palette in your terminal to use it.
These may vary widely based on the 16 colors selected for your terminal.
* solarized-tc: this is the solarized colorscheme for true color; just make sure
your terminal supports true color before using it and that the MICRO_TRUECOLOR
environment variable is set to 1 before starting micro.
* atom-dark-tc: this colorscheme is based off of Atom's "dark" colorscheme. It
requires true color to look good.
* 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.
* cmc-paper: Basically cmc-16, but on a white background. (Actually light grey
* `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-tc: A true colour variant of the cmc theme. It requires true color to
### True color
These require terminals that support true color and require `MICRO_TRUECOLOR=1` (this is an environment variable).
* `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)
* 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
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!
### 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!)
## Creating a Colorscheme
Micro's colorschemes are also extremely simple to create. The default ones ca
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).

View File

@@ -80,6 +80,16 @@ Here are the possible commands that you can use.
* `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:

View File

@@ -95,6 +95,7 @@ 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

View File

@@ -5,16 +5,18 @@ 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
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 Ctrl0E and then type
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

View File

@@ -47,6 +47,33 @@ save and quit you can bind it like so:
}
```
## Binding commands
You can also bind a key to execute a command in command mode (see
`help commands`). Simply prepend the binding with `command:`. For example:
```json
{
"Alt-p": "command:pwd"
}
```
Now when you press `Alt-p` the `pwd` command will be executed which will show
your working directory in the infobar.
You can also bind an "editable" command with `command-edit:`. This means that
micro won't immediately execute the command when you press the binding, but
instead just place the string in the infobar in command mode. For example,
you could rebind `CtrlG` to `> help`:
```json
{
"CtrlG": "command-edit:help "
}
```
Now when you press `CtrlG`, `help` will appear in the command bar and your cursor will
be placed after it (note the space in the json that controls the cursor placement).
## Binding raw escape sequences
@@ -129,6 +156,7 @@ MoveLinesUp
MoveLinesDown
DeleteWordRight
DeleteWordLeft
SelectLine
SelectToStartOfLine
SelectToEndOfLine
InsertNewline
@@ -159,6 +187,8 @@ Start
End
PageUp
PageDown
SelectPageUp
SelectPageDown
HalfPageUp
HalfPageDown
StartOfLine
@@ -183,14 +213,16 @@ 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)
@@ -322,6 +354,7 @@ CtrlRightSq
CtrlCarat
CtrlUnderscore
Backspace
OldBackspace
Tab
Esc
Escape
@@ -435,6 +468,7 @@ MouseWheelRight
// Multiple cursors bindings
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",

View File

@@ -11,14 +11,19 @@ Here are the options that you can set:
* `autoindent`: when creating a new line use the same indentation as the
previous line.
default value: `on`
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: `off`
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
@@ -44,7 +49,7 @@ Here are the options that you can set:
* `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`
default value: `true`
* `eofnewline`: micro will automatically add a newline to the file.
@@ -59,7 +64,7 @@ Here are the options that you can set:
intensive. This option is only for people who really care about having
accurate modified status.
default value: `on`
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
@@ -78,7 +83,7 @@ Here are the options that you can set:
* `ignorecase`: perform case-insensitive searches.
default value: `off`
default value: `false`
* `indentchar`: sets the indentation character.
@@ -87,20 +92,20 @@ Here are the options that you can set:
* `infobar`: enables the line at the bottom of the editor where messages are
printed. This option is `global only`.
default value: `on`
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: `off`
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: `off`
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
@@ -108,7 +113,7 @@ Here are the options that you can set:
example, because the terminal has access to the local clipboard and micro
does not).
default value: `on`
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
@@ -129,26 +134,26 @@ Here are the options that you can set:
* `ruler`: display line numbers.
default value: `on`
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: `off`
default value: `false`
* `savehistory`: remember command history between closing and re-opening
micro.
default value: `on`
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: `off`
default value: `false`
* `scrollbar`: display a scroll bar
default value: `off`
default value: `false`
* `scrollmargin`: amount of lines you would like to see above and below the
cursor.
@@ -161,25 +166,35 @@ Here are the options that you can set:
* `softwrap`: should micro wrap lines that are too long to fit on the screen.
default value: `off`
default value: `false`
* `splitbottom`: when a horizontal split is created, should it be created below
the current split?
default value: `on`
default value: `true`
* `splitright`: when a vertical split is created, should it be created to the
right of the current split?
default value: `on`
default value: `true`
* `statusline`: display the status line at the bottom of the screen.
default value: `on`
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: `on`
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
@@ -191,7 +206,7 @@ Here are the options that you can set:
(e.g. move over 4 spaces at once). This option only does anything if
`tabstospaces` is on.
default value: `off`
default value: `false`
* `tabsize`: sets the tab size to `option`
@@ -199,18 +214,18 @@ Here are the options that you can set:
* `tabstospaces`: use spaces instead of tabs
default value: `off`
default value: `false`
* `termtitle`: defines whether or not your terminal's title will be set by micro
when opened.
default value: `off`
default value: `false`
* `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`
default value: `true`
---
@@ -219,19 +234,19 @@ Default plugin options:
* `autoclose`: automatically close `{}` `()` `[]` `""` `''`. Provided by the
`autoclose` plugin
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 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`
* `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
@@ -249,10 +264,25 @@ will set the option in all buffers.
The `colorscheme` option is global only, and the `filetype` option is local
only. To set an option locally, use `setlocal` instead of `set`.
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
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
{
"ft:go": {
"tabstospaces": false
},
"ft:ruby": {
"tabsize": 2
},
"tabstospaces": true,
"tabsize": 4
}
```
Or similarly you can match with globs:
```json
{
"*.go": {
@@ -265,6 +295,3 @@ files, and `tabsize` 4 for all files except Ruby files:
"tabsize": 4
}
```
As you can see it is quite easy to set options locally using the `settings.json`
file.

View File

@@ -65,6 +65,9 @@ functions are given using Go's type system):
* `NewBuffer(text, path string) *Buffer`: creates a new buffer from a given
reader with a given path
* `NewBufferFromFile(path string) *Buffer`: creates a new buffer from a given
path
* `GetLeadingWhitespace() bool`: returns the leading whitespace of the given
string
@@ -108,11 +111,33 @@ functions are given using Go's type system):
* `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.
* `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

View File

@@ -6,10 +6,12 @@ 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.
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`.

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 Stepets
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

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

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

View File

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

44
runtime/syntax/ada.yaml Normal file
View File

@@ -0,0 +1,44 @@
filetype: ada
detect:
filename: "(\\.ads$|\\.adb$|\\.ada$)"
rules:
# Operators
- symbol.operator: ([.:;,+*|=!?\\%]|<|>|/|-|&)
- symbol.brackets: "[(){}]|\\[|\\]"
# keyword.reserved
- statement.reserved: \b(abort|abs|abstract|accept|access|aliased|all|and|array|at|begin|body|case)\b
- statement.reserved: \b(constant|declare|delay|delta|digits|do|else|elsif|end|entry|exception|exit|for|function)\b
- statement.reserved: \b(generic|goto|if|in|interface|is|limited|loop|mod|new|not|null|of|or|others|out|overriding)\b
- statement.reserved: \b(package|pragma|private|procedure|protected|raise|range|record|rem|renames|return|requeue)\b
- statement.reserved: \b(reverse|select|separate|some|subtype|synchronized|tagged|task|terminate|then|type|until)\b
- statement.reserved: \b(use|when|while|with|xor)\b
# Constant
- constant.bool: \b(TRUE|FALSE)
- constant.number: ([0-9]+)
# Storage Types
- type.storage: \b(INTEGER|NATURAL|POSITIVE|FLOAT|CHARACTER|STRING)\b
- type.storage: \b(LONG_INTEGER|SHORT_INTEGER|LONG_FLOAT|SHORT_FLOAT)\b
#Character
- constant.string.char: \'.\'
# String
- constant.string:
start: \"
end: \"
skip: \\.
rules:
- constant.specialChar: (\\0|\\\\|\\t|\\n|\\r|\\"|\\')
- constant.interpolation: \\\([[:graph:]]*\)
- constant.unicode: \\u\{[[:xdigit:]]+}
# Line Comment
- comment.line: "--.*"
# Todo
- todo: "(TODO|XXX|FIXME):?"

99
runtime/syntax/ats.yaml Normal file
View File

@@ -0,0 +1,99 @@
filetype: ATS
detect:
filename: "\\.(d|h|s)ats$"
rules:
- default: \b[[:alnum:]]+[^0-9A-Za-z]
# Operators
- symbol.operator: "[.:;+`|~<>?='\\&]|/|%|-|,|!|\\*|@|\\#"
- symbol.operator: \^
# Types, abstract types and some predefined sorts
- type: \b(addr|age?z|bool|char|cls|schar|uchar|double|ldouble|eff|exn|float|int(ptr)?|lincloptr|uint)\b
- type: \b(lint|ulint|llint|ullint|nat|ptr|real|ref|size_t|ssize_t|sint|usint|string|tkind|viewt|v?t0p|vt|void)\b
- type: \b(prop|t[@0]ype|type|viewt[@0]ype|viewtype|vt[@0]ype|vtype|view)\b
- type: \b(prop[+-]|t[@0]ype[+-]|type[+-]|viewt[@0]ype[+-]|viewtype[+-]|vt[@0]ype[+-]|vtype[+-]|view[+-])
# Statements
- statement: \b(abstype|abst|absprop|absviewt|absvt(ype)?|absview|and|andalso|as|(re)?assume|begin|(pr)?case|s?case)\b
- statement: \b(classdec|dataprop|data(v|view)?type|dataview|datasort|do|dynload|else|end|exception|extype|extva(r|l)|s?if)\b
- statement: \b(ifcase|import|for|in|infix(l|r)?|let|local|macrodef|macdef|not|of|op|or|orelse|overload|(pre|post|non)fix)\b
- statement: \b(propdef|rec|sortdef|stacst|stadef|staload|stavar|sta(tic)?|symelim|symintr|tkindef|then|try|viewdef|v?typedef)\b
- statement: \b(viewtypedef|(pr)?va(l|r)|when|where|while|with|withtype|withprop|withv(iew)?type|withview)\b
- statement: \b(abst[@0]ype|absviewt[@0]?ype|absvt[@0]ype|abstbox|abstflat|absvtbox|absvtflat|absimpl|absreimpl|abs)\b
- statement: \b(case[+-]|(pr)?va(l|r)[+-]|for\*|while\*)
# Numbers
- constant.number: \b[0-9.]+([eE][+-]?[0-9]+)?[fFlL]?\b
- constant.number: \b0[xX][0-9A-Fa-f]*(\.[0-9A-Fa-f]*)?[pP][+-]?[0-9]+[fFlL]?\b
- constant.number: \b([0-9]+|0[xX][0-9A-Fa-f]+)[lLuU]*\b
# Function-related keywords, special functions and namespaces. Not really identifiers
- identifier: \b(fix|(pr)?fu?n|fnx|castfn|praxi|extern|lam|llam|(pr)?implement|(pr)?implmnt)\b
- identifier: \b(fix@|fold@|free@|lam@|llam@|addr@|view@|ref@|fn\*)
- identifier: \$\w*\b
# Other keywords, function effects...
- special: (\$(arrpsz|arrptrsize|break|continue|d2ctype|delay|effmask_(ntm|exn|ref|wrt|all)))\b
- special: (\$(effmask|extern|extype_struct|extype|extkind|extval|extfcall|extmcall|ldelay|literal))\b
- special: (\$(li?st_vt|li?st_t|li?st|myfilename|myfunction|mylocation|raise|rec(ord)?_vt))\b
- special: (\$(rec(ord)?_t|rec(ord)?|showtype|solver_assert|solver_verify|tempenver))\b
- special: (\$(tup(le)?_vt|tup(le)?_t|tup(le)?|tyrep|vararg|vcopyenv_vt|vcopyenv_v))\b
- special: \!(wrt|exnref|exnwrt|exn|refwrt|ref|all|ntm|laz)\b
- special: \b(fun(0|1)|(lin)?cloptr(0|1)?|cloref(0|1)?|clo(0|1)?|lin|prf)\b
- special: \b(f?print(ln)?!|prerr(ln)?!|tupz!)
# Some C macros and other ATS macros
- preproc: ^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error|assert)\b
- preproc: ^[[:space:]]*#[[:space:]]*(codegen2|codegen3|elifdef|elifndef|prerr|print|require|then|staload|dynload)\b
# Boolean values
- constant.bool: \b(true|false|null)\b
# The "%{ ... %}" block inserts foreign code into ATS at compile-time
# Code inside of it is generally C or JavaScript
- default:
start: "%{[#^$]?"
end: "%}"
skip: "\\."
limit-group: symbol.operator
rules:
- include: "c"
- include: "javascript"
# Strings and chars
- constant.string: \"[^"]*\"
- constant.string: \'[^']*\'
# Comments
# "////" comments everything until it reaches EOF
- comment.block:
start: ////
end: $a
rules:
- todo: (TODO|XXX|FIXME)
- comment.line:
start: //
end: $
rules:
- todo: (TODO|XXX|FIXME)
# ML-like block comment
- comment.block:
start: \(\*
end: \*\)
rules:
- todo: (TODO|XXX|FIXME)
# C-like block comment
- comment.block:
start: /\*
end: \*\/
rules:
- todo: (TODO|XXX|FIXME)

View File

@@ -0,0 +1,30 @@
filetype: elixir
detect:
filename: "\\.ex$|\\.exs$"
rules:
- statement: "\\b(abs|trunc|rem|div|round|max|min|and|or|not|throw|raise|reraise|hd|tl|in|length|elem|put_elem|destructure|to_(string|charlist)|is_(atom|binary|bitstring|boolean|float|function|integer|list|map|nil|number|pid|port|reference|tuple)|(bit|byte|map|tuple)_size|binary_part|def(delegate|exception|guard|guardp|impl|macro|macrop|module|overridable|p|protocol|struct)?|sigil_[crswCRSWDNT]|if|else|unless|cond|binding|node|self|spawn|spawn_link|spawn_monitor|send|exit|struct|get_and_update_in|get_in|put_in|pop_in|update_in|apply|inspect|make_ref|use|do|end)\\b"
- statement: "\\b(alias|import|require|case|fn|receive|after|try|catch|rescue|super|quote|unquote|unquote_splicing|for|with)\\b"
- constant: "\\b\\[A-Z]+\\b"
- constant.number: "\\b[0-9]+\\b"
- constant.string: "`[^`]*`|%x\\{[^}]*\\}"
- constant.string: "\"([^\"]|(\\\\\"))*\"|%[QW]?\\{[^}]*\\}|%[QW]?\\([^)]*\\)|%[QW]?<[^>]*>|%[QW]?\\[[^]]*\\]|%[QW]?\\$[^$]*\\$|%[QW]?\\^[^^]*\\^|%[QW]?![^!]*!"
- constant.string: "'([^']|(\\\\'))*'|%[qw]\\{[^}]*\\}|%[qw]\\([^)]*\\)|%[qw]<[^>]*>|%[qw]\\[[^]]*\\]|%[qw]\\$[^$]*\\$|%[qw]\\^[^^]*\\^|%[qw]![^!]*!"
- symbol.brackets: "\\{|\\}|\\[|\\]|\\(|\\)"
- comment: "#[^{].*$|#$"
- comment.bright: "##[^{].*$|##$"
- type.keyword: "\\:[a-zA-Z][a-zA-Z0-9_]*"
- type.keyword: "\\b(describe|test)"
- statement: "\\b(expected|assert|assert_raise|assert_in_delta|assert_received|catch_error|catch_throw|flunk|refute|refute_in_delta|refute_received)\\b"
- symbol.tag: "^\\s*\\@[a-zA-Z][a-zA-Z0-9_]*\\b"
- identifier.macro: "\\b(__CALLER__|__DIR__|__ENV__|__MODULE__|__aliases__|__block__|defmacro)\\b"
- todo: "(XXX|TODO|FIXME|\\?\\?\\?)"
- preproc.shebang: "\\W*#!.+?( |$)"

View File

@@ -0,0 +1,48 @@
filetype: fsharp
detect:
filename: "\\.fs?$"
rules:
- identifier: "\\b[A-Z][0-9a-z_]{2,}\\b"
#declarations
- statement: "\\b(let|val|method|in|and|rec|private|virtual|constraint)\\b"
#structure items
- type: "\\b(type|open|class|module|exception|external)\\b"
#patterns
- statement: "\\b(fun|function|functor|match|try|with)\\b"
#patterns-modifiers
- statement: "\\b(as|when|of)\\b"
#conditions
- statement: "\\b(if|then|else)\\b"
#blocs
- type: "\\b(begin|end|object|struct|sig|for|while|do|done|to|downto)\\b"
#constantes
- constant.bool: "\\b(true|false)\\b"
#modules/classes
- special: "\\b(include|inherit|initializer)\\b"
#expr modifiers
- special: "\\b(new|ref|mutable|lazy|assert|raise)\\b"
#keywords which don't exist in ocaml
- type: "\\b(base|delegate|downcast|extern|finally|fixed|global|inline|interface|internal|let!|member|namespace|null|override|private|public)\\b"
- type: "\\b(return|return!|select|static|upcast|use|use!|void|yield|yield!)\\b"
- constant.string:
start: "'"
end: "'"
skip: "\\\\."
rules:
- constant.specialChar: "%."
- constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
- constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
- constant.string:
start: "\""
end: "\""
skip: "\\\\."
rules:
- constant.specialChar: "%."
- constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
- constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
- comment:
start: "\\(\\*"
end: "\\*\\)"
rules: []

View File

@@ -1,29 +1,25 @@
filetype: git-commit
detect:
filename: "COMMIT_EDITMSG|TAG_EDITMSG"
filename: "^(.*[\\/])?(COMMIT_EDITMSG|TAG_EDITMSG)$"
rules:
# Commit message
- ignore: ".*"
# Comments
- comment:
start: "#"
end: "$"
rules: []
# File changes
- type.keyword: "#[[:space:]](deleted|modified|new file|renamed):[[:space:]].*"
- type.keyword: "#[[:space:]]deleted:"
- type.keyword: "#[[:space:]]modified:"
- type.keyword: "#[[:space:]]new file:"
- type.keyword: "#[[:space:]]renamed:"
# Untracked filenames
- error: "^# [^/?*:;{}\\\\]+\\.[^/?*:;{}\\\\]+$"
- type.keyword: "^#[[:space:]]Changes.*[:]"
- type.keyword: "^#[[:space:]]Your branch and '[^']+"
- type.keyword: "^#[[:space:]]Your branch and '"
- type.keyword: "^#[[:space:]]On branch [^ ]+"
- type.keyword: "^#[[:space:]]On branch"
# Recolor hash symbols
- special: "#"
# Color keywords for closing issues (such as on Github)
- type.keyword: "\\b(?i)((fix(es|ed)?|close(s|d)?) #[0-9]+)\\b"
# Comments
- comment.line:
start: "^#"
end: "$"
rules: []

View File

@@ -1,28 +1,19 @@
filetype: git-rebase-todo
detect:
filename: "git-rebase-todo"
filename: "^(.*[\\/])?git\\-rebase\\-todo$"
rules:
# Rebase commands
- statement: "^(p(ick)?|r(eword)?|e(dit)?|s(quash)?|f(ixup)?|x|exec|d(rop)?)\\b"
# Commit IDs
- identifier: "\\b([0-9a-f]{7,40})\\b"
# Color keywords for Github (and others)
- type.keyword: "\\b(?i)((fix(es|ed)?|close(s|d)?) #[0-9]+)\\b"
# Comments
- comment:
start: "#"
- comment.line:
start: "^#"
end: "$"
rules: []
# Rebase commands
- statement: "^(e|edit) [0-9a-f]{7,40}"
- statement: "^# (e, edit)"
- statement: "^(f|fixup) [0-9a-f]{7,40}"
- statement: "^# (f, fixup)"
- statement: "^(p|pick) [0-9a-f]{7,40}"
- statement: "^# (p, pick)"
- statement: "^(r|reword) [0-9a-f]{7,40}"
- statement: "^# (r, reword)"
- statement: "^(s|squash) [0-9a-f]{7,40}"
- statement: "^# (s, squash)"
- statement: "^(x|exec) [^ ]+ [0-9a-f]{7,40}"
- statement: "^# (x, exec)"
# Recolor hash symbols
- special: "#"
# Commit IDs
- identifier: "[0-9a-f]{7,40}"

View File

@@ -10,5 +10,15 @@ rules:
- identifier: "^[[:space:]]*[^=]*="
- special: "^[[:space:]]*\\[.*\\]$"
- statement: "[=;]"
- comment: "(^|[[:space:]])#([^{].*)?$"
- constant.string: "\"(\\\\.|[^\"])*\"|'(\\\\.|[^'])*'"
- comment:
start: "#"
end: "$"
rules:
- todo: "(TODO|XXX|FIXME):?"
- comment:
start: ";"
end: "$"
rules:
- todo: "(TODO|XXX|FIXME):?"

View File

@@ -15,16 +15,20 @@ rules:
- symbol.brackets: "(\\{|\\})"
- symbol.brackets: "(\\(|\\))"
- symbol.brackets: "(\\[|\\])"
- statement: "\\b(break|case|catch|continue|default|delete|do|else|finally)\\b"
- statement: "\\b(for|function|class|extends|get|if|in|instanceof|new|return|set|switch|async|await)\\b"
- statement: "\\b(switch|this|throw|try|typeof|var|const|let|void|while|with)\\b"
- symbol.operator: "[-+/*=<>!~%?:&|]"
- statement: "\\b(async|await|break|case|catch|const|continue|debugger|default|delete|do|else|export|finally)\\b"
- statement: "\\b(for|function|class|extends|get|if|import|in|instanceof|let|new|return|set)\\b"
- statement: "\\b(super|switch|this|throw|try|typeof|var|void|while|with|yield)\\b"
# reserved but unassigned
- error: "\\b(enum|implements|interface|package|private|protected|public)"
- constant: "\\b(null|undefined|NaN)\\b"
- constant: "\\b(true|false)\\b"
- type: "\\b(Array|Boolean|Date|Enumerator|Error|Function|Math)\\b"
- type: "\\b(Number|Object|RegExp|String)\\b"
- statement: "[-+/*=<>!~%?:&|]"
- constant: "/[^*]([^/]|(\\\\/))*[^\\\\]/[gim]*"
# - constant: "/[^*]([^/]|(\\\\/))*[^\\\\]/[gim]*"
- constant: "\\\\[0-7][0-7]?[0-7]?|\\\\x[0-9a-fA-F]+|\\\\[bfnrt'\"\\?\\\\]"
- comment: "^#!.*/(env +)?node( |$)"
- constant.string:
start: "\""
@@ -40,6 +44,12 @@ rules:
rules:
- constant.specialChar: "\\\\."
- constant.string:
start: "`"
end: "`"
rules:
- constant.specialChar: "\\\\."
- comment:
start: "//"
end: "$"

View File

@@ -13,7 +13,7 @@ rules:
# definitions
- identifier: "[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[(]"
# keywords
- statement: "\\b(begin|break|catch|continue|function|elseif|else|end|finally|for|global|local|if|include|using|require|macro|println|return|try|type|while|module)\\b"
- statement: "\\b(begin|break|catch|continue|function|elseif|struct|else|end|finally|for|global|local|const|if|include|import|using|require|macro|println|return|try|type|while|module)\\b"
# decorators
- identifier.macro: "@[A-Za-z0-9_]+"
# operators

View File

@@ -18,11 +18,12 @@ rules:
- identifier: "coroutine\\.\\b(create|isyieldable|resume|running|status|wrap|yield)\\b"
- identifier: "debug\\.\\b(debug|getfenv|gethook|getinfo|getlocal|getmetatable|getregistry|getupvalue|getuservalue|setfenv|sethook|setlocal|setmetatable|setupvalue|setuservalue|traceback|upvalueid|upvaluejoin)\\b"
- identifier: "bit32\\.\\b(arshift|band|bnot|bor|btest|bxor|extract|replace|lrotate|lshift|rrotate|rshift)\\b"
- identifier: "\\:\\b(close|flush|lines|read|seek|setvbuf|write)\\b"
- identifier: "\\:\\b(close|flush|lines|read|seek|setvbuf|write|byte|char|dump|find|format|gmatch|gsub|len|lower|match|pack|packsize|rep|reverse|sub|unpack|upper)\\b"
- identifier: "\\b(self|arg)\\b"
- constant: "\\b(false|nil|true)\\b"
- statement: "(\\b(dofile|require|include)|%q|%!|%Q|%r|%x)\\b"
- constant.number: "\\b([0-9]+)\\b"
- symbol: "(\\(|\\)|\\[|\\]|\\{|\\}|\\*\\*|\\*|/|%|\\+|-|\\^|>|>=|<|<=|~=|=|\\.\\.|#)"
- symbol: "(\\(|\\)|\\[|\\]|\\{|\\}|\\*\\*|\\*|/|%|\\+|-|\\^|>|>=|<|<=|~=|=|[\\.]{2,3}|#)"
- constant.string:
start: "\""
@@ -46,13 +47,16 @@ rules:
- special: "\\\\[0-7][0-7][0-7]|\\\\x[0-9a-fA-F][0-9a-fA-F]|\\\\[abefnrs]|(\\\\c|\\\\C-|\\\\M-|\\\\M-\\\\C-)."
- comment.block:
start: "\\-\\-\\[(\\=*|\\#*)\\["
end: "\\-\\-\\](\\=*|\\#*)\\]"
rules:
- todo: "(TODO|NOTE|FIXME):?"
# this has to go after block comment or block comment does not work
- comment:
start: "\\-\\-"
end: "$"
rules: []
- comment:
start: "\\-\\-\\[\\["
end: "\\]\\]"
rules: []
rules:
- todo: "(TODO|NOTE|FIXME):?"

View File

@@ -7,8 +7,8 @@ detect:
rules:
- preproc: "\\<(ifeq|ifdef|ifneq|ifndef|else|endif)\\>"
- statement: "^(export|include|override)\\>"
- operator: "^[^:= ]+:"
- operator: "([=,%]|\\+=|\\?=|:=|&&|\\|\\|)"
- symbol.operator: "^[^:= ]+:"
- symbol.operator: "([=,%]|\\+=|\\?=|:=|&&|\\|\\|)"
- statement: "\\$\\((abspath|addprefix|addsuffix|and|basename|call|dir)[[:space:]]"
- statement: "\\$\\((error|eval|filter|filter-out|findstring|firstword)[[:space:]]"
- statement: "\\$\\((flavor|foreach|if|info|join|lastword|notdir|or)[[:space:]]"

View File

@@ -6,13 +6,25 @@ detect:
rules:
- statement: "\\b(syntax|color(-link)?)\\b"
- statement: "\\b(start=|end=)\\b"
- identifier: "\\b(default|comment|symbol|identifier|constant(.string(.char)?|.number)?|statement|preproc|type|special|underlined|error|todo|statusline|indent-char|(current-)?line-number|gutter-error|gutter-warning|cursor-line|color-column)\\b"
# Simple one-liners
- identifier: "\\b(default|number|statement|underlined|error|todo|statusline|indent-char|cursor\\-line|color\\-column|ignore|divider|tabbar)\\b"
# Separate identifiers to keep "complex" regex clean
- identifier: "\\b(special(Char)?)\\b"
- identifier: "\\b((current\\-)?line\\-number)\\b"
- identifier: "\\b(gutter\\-(info|error|warning){1})\\b"
- identifier: "\\b(comment(\\.bright)?)\\b"
- identifier: "\\b(symbol(\\.(brackets|operator|tag))?)\\b"
- identifier: "\\b(identifier(\\.(class|macro|var))?)\\b"
- identifier: "\\b(constant(\\.(bool(\\.(true|false){1})?|number|specialChar|string(\\.url)?){1})?)\\b"
- identifier: "\\b(preproc(\\.shebang)?)\\b"
- identifier: "\\b(type(\\.keyword)?)\\b"
- constant.number: "\\b(|h|A|0x)+[0-9]+(|h|A)+\\b"
- constant.number: "\\b0x[0-9 a-f A-F]+\\b"
- comment:
start: "#"
end: "$"
rules: []
rules:
- todo: "(FIXME|TODO|NOTE):?"
- constant.string:
start: "\""
end: "\""
@@ -20,4 +32,3 @@ rules:
rules:
- constant.specialChar: "\\\\."
- constant.number: "#[0-9 A-F a-f]+"

View File

@@ -4,23 +4,42 @@ detect:
filename: "\\.mli?$"
rules:
# Numbers
## Integers
### Binary
- constant.number: "-?0[bB][01][01_]*"
### Octal
- constant.number: "-?0[oO][0-7][0-7_]*"
### Decimal
- constant.number: "-?\\d[\\d_]*"
### Hexadecimal
- constant.number: "-?0[xX][0-9a-fA-F][0-9a-fA-F_]*"
## Real
### Decimal
- constant.number: "-?\\d[\\d_]*.\\d[\\d_]*([eE][+-]\\d[\\d_]*.\\d[\\d_]*)?"
### Hexadecimal
- constant.number: "-?0[xX][0-9a-fA-F][0-9a-fA-F_]*.[0-9a-fA-F][0-9a-fA-F_]*([pP][+-][0-9a-fA-F][0-9a-fA-F_]*.[0-9a-fA-F][0-9a-fA-F_]*)?"
# Comments
- identifier: "\\b[A-Z][0-9a-z_]{2,}\\b"
#declarations
- statement: "\\b(let|val|method|in|and|rec|private|virtual|constraint)\\b"
#structure items
- type: "\\b(type|open|class|module|exception|external)\\b"
#patterns
- statement: "\\b(fun|function|functor|match|try|with)\\b"
#patterns-modifiers
- statement: "\\b(as|when|of)\\b"
#conditions
- statement: "\\b(if|then|else)\\b"
#blocs
- type: "\\b(begin|end|object|struct|sig|for|while|do|done|to|downto)\\b"
#constantes
- constant.bool: "\\b(true|false)\\b"
#modules/classes
- special: "\\b(include|inherit|initializer)\\b"
#expr modifiers
- special: "\\b(new|ref|mutable|lazy|assert|raise)\\b"
- constant.string:
start: "'"
end: "'"
skip: "\\\\."
rules:
- constant.specialChar: "%."
- constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
- constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
- constant.string:
start: "\""
end: "\""
skip: "\\\\."
rules:
- constant.specialChar: "%."
- constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
- constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
- comment:
start: "\\(\\*"
end: "\\*\\)"
rules: []
rules: []

View File

@@ -18,10 +18,10 @@ rules:
- comment: "<!--.+?-->"
- default: "<\\?(php|=)\" end=\"\\?>"
- identifier.class: "([a-zA-Z0-9_-]+)\\("
- preproc: "(require|include|require_once|include_once)"
- preproc: "\\b(require|include)(_once)?\\b"
- type: "\\b(var|class|extends|function|echo|case|default|exit|switch|extends|as|define|do|declare|in|trait|interface|[E|e]xception|array|int|string|bool|iterable|void)\\b"
- identifier.class: "[a-zA-Z\\\\]+::"
- identifier: "([A-Z][a-zA-Z0-9_]+)\\s"
- identifier: "\\b([A-Z][a-zA-Z0-9_]+)\\b"
- identifier: "([A-Z0-9_]+)[;|\\s|\\)|,]"
- type.keyword: "(global|public|private|protected|static|const)"
- statement: "(implements|abstract|instanceof|if|else(if)?|endif|namespace|use|as|new|throw|catch|try|while|print|(end)?(foreach)?)\\b"

View File

@@ -24,7 +24,6 @@ type MultiRule struct {
func JoinRule(rule string) string {
split := strings.Split(rule, `" "`)
joined := strings.Join(split, "|")
joined = joined
return joined
}

View File

@@ -1,7 +1,7 @@
filetype: tex
detect:
filename: "\\.tex$|bib|\\.bib$|cls|\\.cls$"
filename: "\\.tex$|\\.bib$|\\.cls$"
rules:
# colorize the identifiers of {<identifier>} and [<identifier>]

View File

@@ -12,11 +12,14 @@ apps:
command: bin/micro
parts:
go:
source-tag: go1.10
micro:
after: [go]
source: .
source-type: git
plugin: nil
build-packages: [golang-go, make]
build-packages: [make]
prepare: |
mkdir -p ../go/src/github.com/zyedidia/micro
cp -R . ../go/src/github.com/zyedidia/micro

View File

@@ -1,14 +1,20 @@
# This script creates the nightly release on Github for micro
# You must have the correct Github access token to run this script
commitID=$(git rev-parse HEAD)
info=$(github-release info -u zyedidia -r micro -t nightly)
if [[ $info = *$commitID* ]]; then
echo "No new commits since last nightly"
exit 1
fi
echo "Deleting old release"
github-release delete \
--user zyedidia \
--repo micro \
--tag nightly
commitID=$(git rev-parse HEAD)
echo "Moving tag"
git tag --force nightly $commitID
git push --force --tags