Compare commits

..

708 Commits

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

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

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

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

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

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

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

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

* SelectLeft on the last character is now possible

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

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

Ref zyedidia/json5#1

Closes #775
2017-08-06 12:02:17 -04:00
tommy
ebb0976866 make syntax_checker work with error messages 2017-08-06 08:25:19 +01:00
Zachary Yedidia
5c785ab1ac Fix MoveLines when user has a selection
Ref #767
2017-08-03 12:42:40 -04:00
Zachary Yedidia
2024b8b2c2 Merge pull request #767 from yannicka/master
Move current line down/up: cursor stay with moved line
2017-08-03 12:40:50 -04:00
Zachary Yedidia
86c695ca52 Merge pull request #758 from r15ch13/scoop
Add Scoop package manager list in readme
2017-08-03 12:17:36 -04:00
Yannick Armand
9dd1df36d5 Move current line down/up: cursor stay with moved line 2017-08-03 12:02:07 +02:00
Zachary Yedidia
27f99b6309 Merge pull request #764 from yannicka/master
OutdentSelection works on all cases (whatever tabsize or tabstospace values)
2017-08-01 21:15:16 -04:00
Yannick Armand
ed9bc66060 OutdentSelection works on all cases (whatever tabsize or tabstospace values) 2017-08-01 22:42:42 +02:00
Zachary Yedidia
d1598bb754 Merge pull request #762 from yannicka/master
Display number of lines in the JumpLine prompt
2017-08-01 12:03:51 -04:00
Yannick Armand
cc5855d07b Display number of lines in the JumpLine prompt 2017-08-01 16:41:57 +02:00
Zachary Yedidia
487b36f48f Update tcell
Ref #761
2017-08-01 10:12:10 -04:00
Zachary Yedidia
c0b00c9a4c Update tcell 2017-07-31 22:47:08 -04:00
Zachary Yedidia
86c2ac95bb Merge cursors after spawning with mouse
Fixes #760
2017-07-31 22:30:57 -04:00
Zachary Yedidia
edb79f2972 Merge 2017-07-31 22:24:46 -04:00
Zachary Yedidia
305cefe461 Update tcell 2017-07-31 22:24:42 -04:00
Zachary Yedidia
315391b0aa Expand support for Suspend to OSX, BSD, Solaris 2017-07-31 20:05:34 -04:00
Zachary Yedidia
fd45acc910 Remove unused variables 2017-07-31 18:46:52 -04:00
Richard Kuhnt
a574ae6b6a Add Scoop package manager list in readme 2017-07-30 13:51:17 +02:00
Zachary Yedidia
fa56a477c2 Merge 2017-07-29 14:45:53 -04:00
Zachary Yedidia
0db4556efb Update snap installation instructions 2017-07-29 14:45:49 -04:00
Leo Arias
fad95c028a install the snap with make (#747)
* install the snap with make

* set the source-type to git, remove get

* remove extra space
2017-07-26 18:35:16 -04:00
Zachary Yedidia
5c462f5600 Always use custom syntax files over default
The custom syntax files were first in the array but micro should stop
loading files into the filetype if it already found a match.

Fixes #750
2017-07-23 20:22:47 -04:00
Zachary Yedidia
5dcc486214 Initialize submodules during makefile build 2017-07-21 17:53:26 -04:00
Zachary Yedidia
9d2915c328 Fix local import for tools/semver 2017-07-21 08:35:51 -04:00
Tatsuro YOKOTA
4f8f6f1ca3 multi-byte characters are overlapped in prompt message (#745)
* modified messenger.Display

* modified messenger.Display
2017-07-20 22:18:15 -04:00
Zachary Yedidia
fbf58486fb Merge pull request #743 from mfxmfx/rebuild-runtime
Rebuild runtime.go.
2017-07-18 12:55:27 -04:00
Markus F.X.J. Oberhumer
97aae225da Rebuild runtime.go. 2017-07-17 07:21:38 +02:00
Zachary Yedidia
1ca2debd7c Merge pull request #741 from adrian5/patch-1
Add consistent spacing to help file
2017-07-16 10:19:43 -04:00
adrian5
c1584dd72f Add consistent spacing to help file 2017-07-16 02:00:30 +02:00
Zachary Yedidia
2bbd29998e Merge 2017-07-15 17:53:40 -04:00
Zachary Yedidia
3b2d7abe3d Store visual x when cursor loc moves in certain cases
Fixes #739
2017-07-15 17:52:56 -04:00
Zachary Yedidia
994d1acbfc Merge pull request #720 from bvaudour/master
Add twig syntax (https://twig.sensiolabs.org/)
2017-07-12 16:10:48 -04:00
Zachary Yedidia
1f4ae1e2d5 Add semver code to tools directory
Ref #736
2017-07-12 09:36:16 -04:00
Zachary Yedidia
18ad74a982 Merge pull request #734 from eyelash/vala-types
improved highlighting for Vala types
2017-07-11 10:46:39 -04:00
Elias Aebi
4cad06c7b3 improved highlighting for Vala types 2017-07-11 14:40:38 +02:00
Zachary Yedidia
ec77dccb1d Merge 2017-07-10 18:04:40 -04:00
Zachary Yedidia
63b4848bb0 Make sure screen is not nil before pollevent
Ref #728
Ref #727
2017-07-10 18:04:11 -04:00
Zachary Yedidia
e27802c41e Merge pull request #726 from DanielPower/patch-1
Remove `new` keyword from C syntax
2017-07-10 08:49:38 -04:00
DanielPower
75329830f9 Remove new keyword from C syntax
Fixes #725
2017-07-10 00:36:49 -02:30
Zachary Yedidia
030a05c103 Always restart the screen when saving with sudo
Fixes #723
2017-07-08 15:13:42 -04:00
Zachary Yedidia
e4751fd84c Optimize search
Fixes #722
2017-07-08 15:03:35 -04:00
Zachary Yedidia
252def5b95 Fix recursive search and replace in special case
Fixes #717
2017-07-01 16:50:25 -04:00
Zachary Yedidia
42f2af7956 Print help text to stdout instead of stderr
Fixes #719
2017-07-01 16:49:08 -04:00
Zachary Yedidia
91fb8225d1 Add 'a' option in replace prompt
Closes #718
2017-07-01 16:47:50 -04:00
Zachary Yedidia
bee60023ae Optimize search and replace 2017-07-01 16:40:28 -04:00
bvaudour
0ffae1896b Add twig syntax (https://twig.sensiolabs.org/) 2017-06-30 12:31:18 +02:00
Zachary Yedidia
8f4820ba28 Move cursor selections on Insert or Remove
Ref #715
2017-06-25 19:14:01 -04:00
Zachary Yedidia
3a02ad8664 Fix vendor-src script 2017-06-23 17:28:33 -04:00
Zachary Yedidia
244e0ded60 Fix ordering of tagging and compiling in release script 2017-06-23 17:26:06 -04:00
Zachary Yedidia
19926d95fe Throw error if there is no detect regex
Closes #712
2017-06-22 10:42:06 -04:00
Zachary Yedidia
f27ee60149 Fix compilation errors 2017-06-21 14:37:30 -04:00
Zachary Yedidia
3908813afe Recover errors in highlight
Ref #712
2017-06-21 14:26:35 -04:00
Zachary Yedidia
e6f24b0924 Add new tcell commits 2017-06-20 17:47:39 -04:00
Zachary Yedidia
b2c1c8f8db Merge pull request #710 from HeavyHorst/master
fixed a panic when a line is cut out(strg+k) and the next line is empty.
2017-06-20 16:52:48 -04:00
Zachary Yedidia
5a2f9a374b Merge pull request #709 from mfxmfx/xterm-ctrl-pgup
Add support for xterm CtrlPgUp and CtrlPgDn keys.
2017-06-20 10:53:31 -04:00
Zachary Yedidia
59ab5107bf Add new commits to tcell (ctrlpageup/down) 2017-06-20 10:53:07 -04:00
Rene Kaufmann
06c65d8404 fixed a panic when a line is cut out(strg+k) and the next line
is empty.
2017-06-20 11:53:59 +02:00
Markus F.X.J. Oberhumer
d38055f825 Add support for xterm CtrlPgUp and CtrlPgDn keys. 2017-06-19 19:37:55 +02:00
Zachary Yedidia
4aeb4c78ac Merge 2017-06-18 15:39:50 -04:00
Zachary Yedidia
3741a71cc5 Check bounds on LastVisualX
Fixes #708
2017-06-18 15:38:33 -04:00
Zachary Yedidia
fc9ddaf941 Merge pull request #679 from alialaee/master
Find and replace one at a time
2017-06-18 12:13:43 -04:00
Zachary Yedidia
1f6a9cfa46 Merge pull request #700 from Calinou/darcula-tc-colorscheme
Add a Darcula colorscheme
2017-06-18 12:12:21 -04:00
Hugo Locurcio
1a18fad1a4 Add a Darcula colorscheme
It is ideally used in truecolor mode, but it also approximates well to
a 256-color palette.
2017-06-18 17:59:03 +02:00
Zachary Yedidia
39b5c4746e Don't vendor src in cross compile script 2017-06-18 10:38:57 -04:00
Zachary Yedidia
5ec08d0a29 Use correct separator on windows
Ref #673
2017-06-18 09:56:36 -04:00
Zachary Yedidia
7ec222895c Update makefile 2017-06-18 09:54:07 -04:00
Zachary Yedidia
da1ec3132f Update tcell 2017-06-17 23:02:22 -04:00
Zachary Yedidia
167f1e5770 Use the idle-wakeup-fix-forked branch of tcell 2017-06-17 22:54:05 -04:00
Zachary Yedidia
8719b9d75c Update readme 2017-06-17 18:00:26 -04:00
Zachary Yedidia
af1f161b06 Update makefile
Since dependencies are now vendored, there is no need to update them in
the makefile.
2017-06-17 17:54:08 -04:00
Zachary Yedidia
7e4ff05c57 Use submodules for dependency management 2017-06-17 17:52:28 -04:00
Zachary Yedidia
f1ecd37578 Update readme 2017-06-17 17:42:43 -04:00
Zachary Yedidia
7ccec0e3f7 Merge pull request #704 from zyedidia/multiple-cursors
Multiple cursors
2017-06-17 17:39:31 -04:00
Zachary Yedidia
397361f23d Add multiple cursor docs + improve docs in general 2017-06-17 17:36:27 -04:00
Zachary Yedidia
681da2e90c Deselect with mouse
This commit also makes non editing actions (save, quit...) only execute
once even if there are multiple cursors.
2017-06-17 11:05:23 -04:00
Zachary Yedidia
118e6b1804 Merge cursors properly
Cursors will merge together if they are on top of each other.
2017-06-17 10:43:14 -04:00
Zachary Yedidia
f933b90c66 Get undo working properly with multiple cursors 2017-06-16 22:19:33 -04:00
Zachary Yedidia
21840d3ffe Make cursor movement automatic on insert + remove
This changes the behavior of cursor movement so that all cursors are
adjusted when a change is made to the buffer. Cursors don't have to be
manually moved after calling Insert or Remove, those functions will move
the cursor properly on their own.

This should fix issues 1-3 mentioned in the multiple cursors discussion.

Ref #5
2017-06-15 18:52:51 -04:00
Zachary Yedidia
5e80ab9362 Merge pull request #697 from frankbraun/spaces
Implement Spaces() with with strings.Repeat()
2017-06-14 15:58:32 -04:00
Frank Braun
43eb238b08 Implement Spaces() with with strings.Repeat()
Shorter and more efficient.
2017-06-14 19:47:40 +00:00
Zachary Yedidia
00718f99cf Add ability to add cursors with Ctrl-MouseLeft
With the new code that allows binding mouse buttons this was remarkably
easy to add.

The new binding is:

    "Ctrl-MouseLeft": "MouseMultiCursor"

Note: A number of terminals don't support Ctrl-MouseLeft (macOS
especially) so you might want to rebind to MouseRight or MouseMiddle.
2017-06-12 20:25:10 -04:00
Zachary Yedidia
c3a73d63b8 Add comments 2017-06-12 20:25:10 -04:00
Zachary Yedidia
bc3c8eaf74 Use terminal cursor for the base cursor
If all cursors fake then that breaks support for things like
inserting japanese characters nicely, so fake cursors are now only used
as extra cursors.
2017-06-12 20:25:10 -04:00
Zachary Yedidia
8d268ef021 Remove debug messages 2017-06-12 20:25:10 -04:00
Zachary Yedidia
c45ff4dd4f Add multiple cursor support
This commit creates new keybindings and actions to handle multiple
cursors.

Here are the defaults:

    "Alt-n": "SpawnMultiCursor",
    "Alt-p": "RemoveMultiCursor",
    "Alt-c": "RemoveAllMultiCursors",
    "Alt-x": "SkipMultiCursor",
2017-06-12 20:25:10 -04:00
Zachary Yedidia
37ad137012 Properly show end of line selection 2017-06-12 15:49:21 -04:00
Zachary Yedidia
0165c4a40a Allow binding runes to actions
This new functionality would make it possible to emulate vim keybindings
pretty easily, for example.
2017-06-11 20:40:11 -04:00
Zachary Yedidia
3270acdd00 Add functionality for binding mouse buttons
This commit enables users to bind the mouse buttons (left, middle,
right buttons and the scroll wheel).

The default bindings now include the mouse bindings:

    "MouseWheelUp":   "ScrollUp",
    "MouseWheelDown": "ScrollDown",
    "MouseLeft":      "MousePress",
    "MouseMiddle":    "PastePrimary",

Mouse buttons can now also be bound to normal actions. For example:

    "MouseLeft": "Backspace"

This also means that plugins can access mouse event callbacks in the
standard way ('onAction').

More documentation for this will be coming soon.

Fixes #542
2017-06-11 17:49:59 -04:00
Zachary Yedidia
ee84296dfe Merge pull request #695 from elopio/snapcraft-update
update the snapcraft.yaml
2017-06-11 12:25:17 -04:00
Leo Arias
42849e7104 update the snapcraft.yaml 2017-06-11 16:11:07 +00:00
Zachary Yedidia
a1f6dd6f4f Update default colorscheme 2017-06-10 18:52:08 -04:00
Tommy
47cdfb3de0 added crontab highlighting. (#689)
* added crontab highlighting.

* added day and month keywords. added label keywords.
2017-06-10 15:24:46 -04:00
Zachary Yedidia
ac362bf1db Merge pull request #694 from tommyshem/xresources
Correct the comment from # to ! as pointed out in the gitter chat on Xresources files high lighting
2017-06-10 11:20:55 -04:00
tommy
462f73f695 Correct the comment from # to ! as point out in the gitter chat. 2017-06-10 11:33:50 +01:00
Zachary Yedidia
cf92f91e1e Improve solarized-tc colorscheme 2017-06-07 12:41:20 -04:00
Yurizal Susanto
52d6ac6cda Appstream Metainfo change (#692)
* Remove strange characters in some line

* Move the xml to data folder
2017-06-05 14:03:39 -04:00
Zachary Yedidia
eeb2aaf9ae Merge 2017-06-04 19:17:08 -04:00
Zachary Yedidia
f84c9f3b5d More descriptive error for 'terminal entry not found' 2017-06-04 19:17:02 -04:00
Zachary Yedidia
be56918174 Merge pull request #683 from yursan9/appstream
Add AppStream metainfo
2017-06-02 20:24:03 -04:00
Zachary Yedidia
08daaf95e4 Merge pull request #688 from tommyshem/help-defaultkeys
Added missing defualt key bindings to help_defaultkeys.md
2017-06-02 20:23:33 -04:00
tommy
51d73c6618 Added missing defualt key bindings and changed micro section to same format. Moved tab key bindings to its own heading. 2017-06-01 06:35:08 +01:00
Tommy
4644a2b5cc Check for readonly on viewtype and if true do not let any edits or paste to buffer. eg help and plugins. Finished needs checking. (#674)
* corrected spelling error and missing public function comments

* check for readonly and if true do not insert character

* mouse middle  click checks for view type readonly and does not paste if view is readonly

* check for view readonly with binding keys and if readonly do not change the content.
2017-05-29 14:18:10 -04:00
Zachary Yedidia
89863660ba Fix boolean logic order for hiding cursor
Fixes #684
2017-05-28 19:52:56 -04:00
Zachary Yedidia
641d188997 Update readme 2017-05-28 10:52:54 -04:00
Yurizal Susanto
226932e631 Add AppStream metainfo 2017-05-28 12:11:09 +07:00
Zachary Yedidia
be8124154b Re-add literate supportg 2017-05-27 17:16:21 -04:00
Zachary Yedidia
f086cc8713 Temporarily remove literate folder 2017-05-27 17:14:22 -04:00
Zachary Yedidia
624daabc02 Add support for zyedidia/Literate 2017-05-27 16:55:59 -04:00
Zachary Yedidia
05a187e470 Update runtime 2017-05-27 16:38:03 -04:00
Zachary Yedidia
53da1ff1fe Merge pull request #680 from tommyshem/kotlin
Added kotlin highlighting and fixed swift line comment todo not highlighting
2017-05-27 16:32:32 -04:00
tommy
112c731c7a Added kotlin highlighting and fixed swift line comment todo not highlighting. 2017-05-26 01:59:51 +01:00
ali
480a220fda Change replace command default behaviour to confirm replacement for each occurrences 2017-05-26 03:43:57 +04:30
Zachary Yedidia
6cf6857602 Add comment 2017-05-22 11:21:17 -04:00
Zachary Yedidia
97e2fb1288 Use messenger error instead of termerror 2017-05-21 10:46:06 -04:00
Zachary Yedidia
d1e70b5abf Add suspend for linux only 2017-05-21 10:44:33 -04:00
Zachary Yedidia
a70fb9db7d Test suspend code 2017-05-21 10:30:26 -04:00
Zachary Yedidia
285503d009 Merge 2017-05-21 10:19:13 -04:00
Zachary Yedidia
f364965ac0 Create ftoptions option to disable auto settings
Closes #662
2017-05-19 18:17:38 -04:00
Zachary Yedidia
61cea4624e Merge pull request #675 from benjamreynolds/patch-1
Cleanup from merge conflicts in syntax/README
2017-05-19 16:25:59 -04:00
Benjamin Reynolds
2899e47591 Cleanup from merge conflicts in syntax/README 2017-05-19 12:33:41 -05:00
Zachary Yedidia
e7ee194acf Merge 2017-05-17 12:13:24 -04:00
Zachary Yedidia
6e5536eae9 Load colorscheme after loading plugins
Fixes #671
2017-05-17 12:12:58 -04:00
Zachary Yedidia
5514e53a0b Merge pull request #665 from tommyshem/viewtype-check
before saving check for view type and if scratch is true then do not save
2017-05-15 15:46:35 -04:00
tommy
d8dee90c10 check viewtype before saving and if scratch is true then do not save 2017-05-15 20:00:58 +01:00
Zachary Yedidia
dcee63771a Merge 2017-05-14 10:27:37 -04:00
Zachary Yedidia
b7133b302b Don't remember path on save if it is invalid
Fixes #656
2017-05-14 10:27:15 -04:00
Zachary Yedidia
061040f5d9 Merge pull request #661 from aerth/indentchar
use space for indentchar if empty, fixes #660
2017-05-12 21:15:19 -04:00
aerth
f6ccaadc0c use space for indentchar if empty, fixes #660 2017-05-12 16:32:24 -07:00
Tommy
7c80de7ee1 Caps lock issue#658 (#659)
* caps-lock-issue #658

* when esc key is pressed the message is cleared away
2017-05-10 10:02:17 -04:00
Zachary Yedidia
ef0f506b6f Allow upper case in 'Save changes' prompt
Fixes #658
2017-05-09 14:46:23 -04:00
Zachary Yedidia
3d63f0771a Merge pull request #654 from tommyshem/swift-linux-support
added swift linting to linux
2017-05-08 13:18:18 -04:00
tommy
20ad87611f added swift linting to linux 2017-05-08 17:14:33 +01:00
Zachary Yedidia
fa7839e287 Merge pull request #653 from Necklaces/master
Added ShiftHome + ShiftEnd to keybindings.md documentation
2017-05-07 11:20:49 -04:00
Nichlas Severinsen
2aec2c13b5 Added ShiftHome + ShiftEnd to keybindings.md documentation 2017-05-07 13:19:58 +02:00
Tommy
3eb0d71bd3 Updated swift syntax yaml file as missing keywords and some highlighting not working correctly. (#644)
* Updated the keywords and micros

* updated keywords  and  interpolation and unicode added to string

* add test swift file

* Delete swift-test.swift

delete swift test file
2017-05-05 15:14:50 -04:00
Zachary Yedidia
18f9b6f34e Add tabmovement option
This option makes micro treat spaces at the beginning of lines as if
they are tabs. This option only does anything if tabstospaces is already
on. E.g. micro will move over 4 spaces at once when at the start of a
line.

Closes #616
2017-05-05 12:04:18 -04:00
Zachary Yedidia
57110c98e4 Fix rare problem with tabs and horizontal scrolling 2017-05-05 11:34:14 -04:00
Zachary Yedidia
a6ee75a9cf Add SaveAll command and display + for modified tabs
Closes #651
2017-05-05 11:05:23 -04:00
Zachary Yedidia
8d1618692e Disallow view switching during a search
Closes #642

This commit also removes some unnecessary functions that weren't being
used.
2017-05-05 10:52:09 -04:00
Zachary Yedidia
960c6cae62 Properly draw split divider 2017-05-05 10:40:49 -04:00
Zachary Yedidia
67ec0d3c80 Correctly detect synatx ft from header 2017-05-03 11:04:56 -04:00
Zachary Yedidia
d3f32b5bc3 If softwrap is on the leftcol must be 0
Fixes #645
2017-05-02 20:12:37 -04:00
Zachary Yedidia
84e350aa6f Optimize memory usage for loading syntax files 2017-05-02 10:30:27 -04:00
Zachary Yedidia
80242f0e08 Update runtime 2017-04-29 16:01:51 -04:00
Zachary Yedidia
2a3ce12bd4 Add termtitle option and move autoclose utf8 code 2017-04-29 16:01:09 -04:00
Collin Warren
aed8ba105a Readded title setting. ( As per the tcell pull. ) (#600) 2017-04-29 15:51:31 -04:00
Zachary Yedidia
0e9bc0ed87 Update snap yaml 2017-04-29 15:50:38 -04:00
Zachary Yedidia
9a798fe220 Merge pull request #638 from popey/patch-1
Update micro to be classic confined snap
2017-04-29 15:48:45 -04:00
Zachary Yedidia
5c3d9db5c9 Merge pull request #639 from popey/patch-2
Add micro snap install instructions
2017-04-29 15:48:36 -04:00
timon999
5ee774892a Make autoclose plugin work with Non-Ascii Unicode characters (#641)
* Make autoclose plugin work with Non-Ascii Unicode characters

* Removed lines that I forgot to remove
2017-04-29 15:44:36 -04:00
Zachary Yedidia
b4dda8bad8 Use less memory when opening very large files 2017-04-29 14:12:00 -04:00
Zachary Yedidia
47324aea97 Merge 2017-04-28 11:07:28 -04:00
Zachary Yedidia
def2b28d4e Differentiate between help buffers and log buffers
Closes #636
2017-04-28 11:07:05 -04:00
Alan Pope
b7bc34906d Add micro snap install instructions
Adds the simple command line install instructions for snap enabled systems.
2017-04-28 11:11:37 +01:00
Alan Pope
bb08d5241e Update micro to be classicly confined snap
As a text editor, micro will need to be able to edit files outside the confined world snap gives it. So this PR changes the confinement model of micro from strict to classic, making it way more useful on snap enabled systems.
2017-04-28 11:04:08 +01:00
Zachary Yedidia
ab24523bff Fix problem with search and replace indices
Closes #637
2017-04-27 20:10:51 -04:00
Zachary Yedidia
b8debb5404 Merge pull request #585 from samdmarshall/add-yaml-linter
Add yaml linter
2017-04-22 18:53:11 -04:00
Zachary Yedidia
d0e39853c6 Fix issue with cursorline 2017-04-22 18:48:05 -04:00
Samantha Marshall
a0bfd99a5d Updating changes to be in-line with master
Merging in master
2017-04-22 14:22:38 -04:00
Zachary Yedidia
471a8b7c2b Fix crash if tabs extend past view width
Ref #631
2017-04-21 12:48:03 -04:00
Zachary Yedidia
591e5e3145 Fix colorcolumn being ignored
Closes #627
2017-04-18 13:33:19 -04:00
Zachary Yedidia
282e7b1828 Merge pull request #626 from Necklaces/master
Added more default bindings:
2017-04-16 11:39:49 -04:00
Zachary Yedidia
007b060cbd Speed up search and replace
Ref #625
2017-04-16 11:11:04 -04:00
Zachary Yedidia
8168a75bde Slightly speed up search and replace
This commit also adds a linter for literate
2017-04-16 10:43:13 -04:00
Nichlas Severinsen
5afda4e76c Added more default bindings:
- Shift+Home for selecting to start of line
- Shift+End for selecting to end of line
These already exist as Ctrl+Shift+Left/Right-arrow
2017-04-16 15:34:44 +02:00
Zachary Yedidia
88c712b848 Allow plugins to create runtime files from strings 2017-04-15 14:45:44 -04:00
Zachary Yedidia
fca63d02f9 Update runtime 2017-04-15 12:02:01 -04:00
Zachary Yedidia
330888cb3b Merge pull request #624 from Necklaces/master
Improved R syntax rules
2017-04-15 12:01:30 -04:00
Zachary Yedidia
23c24c776e Merge pull request #621 from aerth/patch-1
Use GOHOSTARCH/GOHOSTOS to get version, date, Resolves #620
2017-04-15 12:01:12 -04:00
Nichlas Severinsen
233fa9b25c Improved R syntax rules 2017-04-15 02:06:19 +02:00
Zachary Yedidia
9530d6ad20 Merge 2017-04-13 21:39:20 -04:00
Zachary Yedidia
6458d3cac4 Don't draw cursorline at cursor position
Fixes #622

If the cursorline is drawn at the cursor's position, then the cursor
doesn't display. I'm not really sure why this is a problem with the
gnome terminal, it didn't seem to happen on earlier versions.
2017-04-13 21:37:13 -04:00
aerth
6945aa34eb Use GOHOSTARCH/GOHOSTOS to get version, date, Resolves #620
For "install" target, get GOBIN using 'go env GOPATH'
instead of $GOPATH because new Go doesn't require $GOPATH be set
2017-04-12 10:42:39 -07:00
Zachary Yedidia
47c9cc2fea Merge pull request #619 from nicqrocks/patch-1
Add other Perl6 extensions
2017-04-11 09:45:33 -04:00
Nic
1e90cec6f3 Add other Perl6 extensions
Perl6 files will occasionally have the extensions `.pl6` and `.pm6` instead of just `.p6`.
2017-04-11 09:32:46 -04:00
Zachary Yedidia
843867717c Merge 2017-04-02 09:27:25 -04:00
Zachary Yedidia
dd87769090 Fix TabSwitch to work by tab name as well 2017-04-02 09:27:04 -04:00
Zachary Yedidia
398370424b Relocate cursor on search
This commit also fixes some true color colorscheme issues

Fixes #614
2017-03-31 10:39:10 -04:00
Zachary Yedidia
be2d3c9c1e Read extra new line
Ref #603
2017-03-29 12:01:01 -04:00
Zachary Yedidia
05aa30d1be Improve html syntax rules 2017-03-29 10:08:37 -04:00
Zachary Yedidia
1c2b57dfe8 Add support for limit-group to highlights region limits 2017-03-29 09:54:54 -04:00
Zachary Yedidia
47ef864295 Add skip statements to all strings 2017-03-29 09:45:16 -04:00
Zachary Yedidia
a517ea45bd Slight improvements to included region highlighting 2017-03-29 09:25:08 -04:00
Zachary Yedidia
342f3c223d Don't skip included rules in end 2017-03-29 09:21:54 -04:00
Zachary Yedidia
079cbe11f4 Fix incorrect crystal filetype detection
Closes #608
2017-03-28 19:26:14 -04:00
Zachary Yedidia
3e61bd4d49 Improve some more region highlighting 2017-03-28 17:18:06 -04:00
Zachary Yedidia
b517ed28c0 Fix region highlighting with empty rules 2017-03-28 15:18:38 -04:00
Zachary Yedidia
299712ead3 Add support for skipping and remove need for lookbehind 2017-03-27 20:53:08 -04:00
Zachary Yedidia
c24f75999a Better unicode support in highlight 2017-03-27 19:35:28 -04:00
Zachary Yedidia
bde48c051a Fix out of bounds error on syntax highlighting
Fixes #606
2017-03-27 16:26:32 -04:00
Zachary Yedidia
d087a890ba Fix some issues with unicode syntax highlighting
Closes #604
2017-03-27 14:40:42 -04:00
Zachary Yedidia
75d4e70560 Document the keepautoindent option
Closes #602
2017-03-27 13:22:34 -04:00
Zachary Yedidia
73ab25d008 Properly handle files that don't end with newlines
Closes #603
2017-03-27 13:15:00 -04:00
Zachary Yedidia
790ccd429c Merge 2017-03-27 11:45:48 -04:00
Zachary Yedidia
47fd1475b5 Fix indent char styling
Closes #598
2017-03-27 11:45:24 -04:00
Zachary Yedidia
251a2b7455 Merge pull request #601 from NicolaiSoeborg/patch-3
Fix merge conflict / syntax of colors.md
2017-03-27 11:30:42 -04:00
Nicolai Søborg
3c85d31c15 Fix merge conflict / syntax of colors.md
I've changed "can provide" to "must provide" for detection of filetypes (i.e. `detect:` as micro crashes if omitted)
2017-03-27 17:11:51 +02:00
Zachary Yedidia
2e6cbcb362 Merge branch 'view-refactor' 2017-03-26 20:40:53 -04:00
Zachary Yedidia
12d74b99e8 Merge branch 'master' into view-refactor 2017-03-26 20:40:03 -04:00
Zachary Yedidia
4cda7e2d92 Update syntax readme and docs 2017-03-26 18:58:08 -04:00
Zachary Yedidia
1350deae56 Fix small softwrap bug 2017-03-26 18:01:02 -04:00
Zachary Yedidia
df564e1b8b Update yaml syntax file 2017-03-26 17:45:58 -04:00
Zachary Yedidia
bb7ce4cbb3 Add memusage option to view memory usage 2017-03-26 17:37:04 -04:00
Zachary Yedidia
1655fde09b Fix precedence for python multi-comments 2017-03-26 17:24:02 -04:00
Zachary Yedidia
89d1f1c202 Proper unicode support 2017-03-26 17:20:53 -04:00
Zachary Yedidia
b23c507af5 Read one line for empty strings 2017-03-26 15:27:53 -04:00
Zachary Yedidia
15055440da Optimize how files are read into the buffer 2017-03-26 15:23:32 -04:00
Zachary Yedidia
e2b7c85955 Small optimization to state only highlighting 2017-03-26 13:03:43 -04:00
Zachary Yedidia
9c5ab2afbd Fix possible overflow error 2017-03-25 17:31:46 -04:00
Zachary Yedidia
d413562145 Add more performance and memory optimizations 2017-03-24 14:11:21 -04:00
Zachary Yedidia
87f54be13a Add support for lookbehind in region regexes
Use the 'regexp2' library for lookahead and lookbehind in region
start and end regular expressions to support things like closing quotes
that aren't preceded by backslashes.
2017-03-22 19:03:06 -04:00
Zachary Yedidia
bea1c5dc28 Fix another issue with horizontal scrolling 2017-03-22 12:28:02 -04:00
Zachary Yedidia
04b4dbbfee Improve performance on long lines 2017-03-22 11:58:43 -04:00
Zachary Yedidia
d55e7319da Fix small bug 2017-03-21 15:46:06 -04:00
Zachary Yedidia
54bb99d758 Improve new syntax files and fix a region glitch 2017-03-21 15:07:39 -04:00
Zachary Yedidia
b977bf5cca Add converted syntax files 2017-03-21 14:55:22 -04:00
Zachary Yedidia
fa7f89a400 Properly escape start and end regexes 2017-03-21 14:48:24 -04:00
Zachary Yedidia
523f75654d No tabs in yaml 2017-03-21 14:44:54 -04:00
Zachary Yedidia
e85ae907a0 Add syntax converter from old file format to new 2017-03-21 14:35:58 -04:00
Zachary Yedidia
b0e287498e Update runtime 2017-03-21 13:16:08 -04:00
Zachary Yedidia
8a33c98bc6 Support rule precedence just like the old format 2017-03-21 13:15:46 -04:00
Zachary Yedidia
59bf1a2260 Optimize the memory usage for syntax highlighting 2017-03-21 12:45:27 -04:00
Zachary Yedidia
214adcf611 Properly clear syntax highlighting when it is disabled 2017-03-20 17:40:33 -04:00
Zachary Yedidia
23152f0c50 Use bytes for highlight groups 2017-03-20 15:14:04 -04:00
Zachary Yedidia
2a4abbee24 Fix larger multiline region highlighting 2017-03-16 16:38:44 -04:00
Zachary Yedidia
f637268fa7 Fix small issue with regions 2017-03-16 13:15:12 -04:00
Zachary Yedidia
ea7f90713c Fix some small glitches with cursor positioning 2017-03-13 19:23:47 -04:00
Zachary Yedidia
53a19afe52 Fix error when opening directory
Fixes #587
2017-03-11 13:08:32 -05:00
Zachary Yedidia
ed6951a653 Check if file is dir before opening
Fixes #587
2017-03-09 12:56:24 -05:00
Zachary Yedidia
2e99f52133 Fix split divider style 2017-03-09 12:40:53 -05:00
Zachary Yedidia
da5542a557 Fix findNext and findPrevious with certain regexes
Fixes #576
2017-03-05 11:20:42 -05:00
Zachary Yedidia
1cd4b2c4dc Update go yaml file 2017-03-05 11:12:50 -05:00
Zachary Yedidia
253e86230c Build runtime 2017-03-03 11:49:53 -05:00
CaptainMcClellan
9f9b5def41 Added theming to the Vsplit divider. (#578)
* Added title setting (Requires tcell pull!) and optimized tab display.

* Changed tcell call to screen.

* Fixed bad reference for getting Buffer name.

* Themeable vsplit divider.

* Bugfix.
2017-03-03 11:48:51 -05:00
CaptainMcClellan
d949b58fc0 Legacy syntax overhaul 2 (#577)
* Added title setting (Requires tcell pull!) and optimized tab display.

* Added Clojure highlighting.

* Changed tcell call to screen.

* Fixed bad reference for getting Buffer name.

* Yet another massive syntax overhaul

* Tweaking and testing web syntax

* More webdev goodies

* Added html5 highlighting and strict html4 highlighting.

* Documentation update
2017-03-03 11:47:03 -05:00
Zachary Yedidia
ab74e56a40 Revert "Added title setting (Requires tcell pull!) and optimized tab display." (#581) 2017-03-03 11:43:44 -05:00
Zachary Yedidia
a537c584d0 Update README.md 2017-03-03 10:28:29 -05:00
Zachary Yedidia
98365b6bfb Fix block indent selection
Fixes #572
2017-03-02 19:46:23 -05:00
CaptainMcClellan
57c030d3b9 Added title setting (Requires tcell pull!) and optimized tab display. (#575)
* Added title setting (Requires tcell pull!) and optimized tab display.

* Changed tcell call to screen.

* Fixed bad reference for getting Buffer name.
2017-03-02 12:27:49 -05:00
Marius Messerschmidt
89acc703f5 Added a script to build debian packages (#563)
Signed-off-by: Marius Messerschmidt <marius.messerschmidt@googlemail.com>
2017-03-02 12:26:01 -05:00
Zachary Yedidia
6df2d7d822 Update runtime 2017-03-01 17:09:09 -05:00
Zachary Yedidia
3c192c2fb5 Use Go regex word boundaries 2017-03-01 17:08:56 -05:00
Zachary Yedidia
995a910f6a Update runtime
Also made a minor addition to ruby.micro which fixes #574.
2017-03-01 09:36:41 -05:00
CaptainMcClellan
c29e58e3d4 Syntax Highlight Overhaul (#568)
* Adds new syntax groups and docs
* Large refactor of syntax highlighting files

* Changed keybindings for changing tabs.

* Improved the documentation.

* Added F3 for find to default bindings.

* Massive overhaul of the syntax files

* Phase 1 color-scheme updates.

* The new colorschemes.

* Colorscheme and syntax updates.

* Tiny fix to the cmc theme.

* Another phase of colorschemes and testing gitconfig.

* Fixed haskell error

* Fortran fix

* Delete test.txt

Heh, sorry about forgetting to do this again.
2017-03-01 09:30:35 -05:00
Zachary Yedidia
924809b19b Merge pull request #570 from yursan9/yaml
Improve YAML highlighting
2017-02-28 19:05:12 -05:00
Yurizal Susanto
85e7055505 Small fix for space detection 2017-02-28 13:49:48 +07:00
Yurizal Susanto
fb6d554df6 Improve YAML highlighting 2017-02-28 07:16:41 +07:00
Zachary Yedidia
bd0c5c655e Add more syntax files and include syntax highlighter in the repo 2017-02-26 11:14:35 -05:00
Zachary Yedidia
e6e190942c Minor fixes 2017-02-26 11:14:35 -05:00
Zachary Yedidia
25ad139675 Remove old view code 2017-02-26 11:14:35 -05:00
Zachary Yedidia
a095644731 Minor fix to tab size 2017-02-26 11:14:35 -05:00
Zachary Yedidia
f197eca320 Improve horizontal scrolling 2017-02-26 11:14:35 -05:00
Zachary Yedidia
d602cb68ca Support include statements in syntax files 2017-02-26 11:14:35 -05:00
Zachary Yedidia
56e98ea5f4 Highlight selections 2017-02-26 11:14:35 -05:00
Zachary Yedidia
16d8a560bf Don't highlight if syntax is off 2017-02-26 11:14:35 -05:00
Zachary Yedidia
32325f99ad Support multiple splits 2017-02-26 11:14:35 -05:00
Zachary Yedidia
9b33a1058a Add cursorline support 2017-02-26 11:14:35 -05:00
Zachary Yedidia
ff5c8d7451 Draw tab characters correctly 2017-02-26 11:14:35 -05:00
Zachary Yedidia
1ba51e4f59 Fix newline state 2017-02-26 11:14:35 -05:00
Zachary Yedidia
7fe2b8ef2f Store highlighting matches in each line 2017-02-26 11:14:35 -05:00
Zachary Yedidia
7bb61307e0 Fix tab size 2017-02-26 11:14:35 -05:00
Zachary Yedidia
d0057121ef Start implementing syntax highlighting optimizations 2017-02-26 11:14:35 -05:00
Zachary Yedidia
18c4196354 Store states in linearray 2017-02-26 11:14:35 -05:00
Zachary Yedidia
2fcb40d5a9 Use new syntax highlighting engine from zyedidia/highlight
This changes all the syntax files in the runtime directory and also
changes how syntax highlighting is done from inside micro.
2017-02-26 11:14:35 -05:00
Zachary Yedidia
0adb601f3c Improve drawing 2017-02-26 11:14:35 -05:00
Zachary Yedidia
b669437296 Add gutter message drawing and better cursor locating 2017-02-26 11:14:35 -05:00
Zachary Yedidia
9ef27203f0 Add support for line numbers and cursor locations in the new view 2017-02-26 11:14:35 -05:00
Zachary Yedidia
d2a1d849c9 Calculate line number offset 2017-02-26 11:14:35 -05:00
Zachary Yedidia
712b383e2c Use the new cellview for displaying
Syntax highlighting is still not supported when using the new cellview.
2017-02-26 11:14:35 -05:00
Zachary Yedidia
94175d1aa6 Add beginning of cellview as well as improved ViewTypes
This is the beginning of the view refactor (#515). It's just
the start and is untested for now.
2017-02-26 11:14:35 -05:00
Zachary Yedidia
9b51069041 Merge pull request #564 from DanielPower/master
Replaced tmp directory with file directory
2017-02-26 10:59:33 -05:00
Zachary Yedidia
80ab81fefc Add tabswitch command
Fixes #566
2017-02-26 10:52:14 -05:00
Daniel Power
d00562d37a Replaced tmp directory with file directory 2017-02-26 02:21:19 -03:30
Zachary Yedidia
75a344ef56 Merge 2017-02-23 22:20:36 -05:00
Zachary Yedidia
ffebb58d92 Update autoclose 2017-02-23 22:20:33 -05:00
Zachary Yedidia
c9199ba1bd Merge 2017-02-23 16:32:57 -05:00
Zachary Yedidia
5024ecd640 Add colorscheme complete function
This function is not used at the moment.
2017-02-23 16:32:33 -05:00
Zachary Yedidia
a185d6f9a0 Merge pull request #556 from AndydeCleyre/feature/zsh-theme-syntax
recognize zsh-themes as zsh filetypes
2017-02-21 15:31:56 -05:00
Andy Kluger
690610d4b1 recognize zsh-themes as zsh filetypes 2017-02-21 14:56:57 -05:00
Zachary Yedidia
043f7cdc47 Fix various linter and vet warnings 2017-02-21 13:07:37 -05:00
Zachary Yedidia
4d1ad52405 Fix find and replace counter 2017-02-20 18:33:44 -05:00
Zachary Yedidia
7294424c3e Merge pull request #553 from samdmarshall/master
fixing bug in closing tabs and not being able to select tabs
2017-02-20 18:06:41 -05:00
Samantha Marshall
bb55fc4150 fixing bug in closing tabs and not being able to select tabs 2017-02-20 18:01:29 -05:00
Zachary Yedidia
263eec7368 Resize all tabs on EventResize
Fixes #552
2017-02-20 13:08:14 -05:00
Zachary Yedidia
7b03f5bab2 Fix bold highlighting not working right 2017-02-20 09:28:37 -05:00
Zachary Yedidia
0fd042dce6 Merge pull request #551 from samdmarshall/master
fixing hex number highlighting for nim
2017-02-19 16:15:56 -05:00
Samantha Marshall
e379239140 fixing hex number highlighting for nim 2017-02-19 16:04:42 -05:00
Zachary Yedidia
c1db99a5a5 Merge pull request #522 from samdmarshall/master
Redo of #516 -- Making micro a bit more user-friendly around the quitting behavior
2017-02-16 15:57:04 -05:00
Zachary Yedidia
fb2bf7a377 Merge pull request #520 from samdmarshall/tab-scrolling
adding tab scrolling and additional tab indicators
2017-02-16 15:51:19 -05:00
Zachary Yedidia
9404b731ec Don't trim arguments for 'set' 2017-02-15 12:56:40 -05:00
Zachary Yedidia
e682c0355b Update README.md 2017-02-14 20:45:06 -05:00
Zachary Yedidia
d8e7291cb2 Include third party license in releases 2017-02-11 11:44:23 -05:00
Zachary Yedidia
556a3eb18f Merge pull request #547 from ta2gch/third-party-licenses
Add third party licenses
2017-02-11 11:43:15 -05:00
TANIGUCHI Masaya
f9fcdb2e8b Add third party licenses 2017-02-12 01:23:36 +09:00
Zachary Yedidia
d695d12872 Update go highlighting 2017-02-10 21:00:31 -05:00
Zachary Yedidia
ced7164912 Merge pull request #545 from ta2gch/cpp
lambda expression in c++
2017-02-10 12:43:27 -05:00
Zachary Yedidia
ce3bdf63c0 Bump 2017-02-10 11:07:17 -05:00
TANIGUCHI Masaya
4c678c4936 add c++ linter 2017-02-08 15:11:23 +09:00
TANIGUCHI Masaya
18d128eb3d create c++ syntax 2017-02-08 15:06:22 +09:00
TANIGUCHI Masaya
97632e5573 add keywords 2017-02-08 14:36:39 +09:00
Zachary Yedidia
5dc8fe40ca Add option to disable use of a the primary clipboard
Closes #544
2017-02-07 19:21:25 -05:00
Zachary Yedidia
28af256be0 Fix stringwidth build 2017-02-03 07:32:48 -05:00
Zachary Yedidia
c3a165e61d Merge pull request #539 from samdmarshall/nim
Adding syntax and linter support for nim-lang
2017-02-02 23:00:12 -05:00
Zachary Yedidia
33e5dd4aed Update runtime 2017-01-27 10:32:58 -05:00
Samantha Marshall
9122f152d1 fixing thestart/end syntax 2017-01-27 08:40:41 -05:00
Samantha Marshall
2202b32f31 finishing up syntax file 2017-01-26 15:50:54 -05:00
Samantha Marshall
15ab0b2fed adding nim linter and syntax 2017-01-26 14:19:07 -05:00
Zachary Yedidia
a8b0f6d679 Merge pull request #531 from bazzilic/patch-1
Update README.md to mention chocolatey package
2017-01-25 14:32:14 -05:00
Zachary Yedidia
40411ea627 Merge pull request #533 from onodera-punpun/toml
Add toml syntax file
2017-01-25 14:32:04 -05:00
Zachary Yedidia
8a6a3127c6 Merge pull request #534 from onodera-punpun/go2
update go syntax file
2017-01-25 14:31:53 -05:00
Zachary Yedidia
f951c6f489 Merge pull request #535 from yursan9/patch-1
Correct the splitBottom option
2017-01-25 14:31:41 -05:00
Yurizal Susanto
82a3b8bb39 Correct the splitBottom option 2017-01-24 10:40:36 +07:00
onodera-punpun
c29ccfe011 update go syntax file 2017-01-23 20:06:39 +01:00
onodera-punpun
d48efbf442 Add toml syntax file 2017-01-23 20:01:26 +01:00
bazzilic
69ef6def38 Update README.md to mention chocolatey package 2017-01-23 14:41:52 +08:00
DanielPower
f7560c3311 Add gruvbox truecolor colorscheme (#530)
* Add gruvbox truecolor colorscheme

* Fixed typo in gruvbox-tv, added operator type in colorschemes

* Added operator type to all default themes

* Changed operator to symbol

* changed operator to symbol due to name conflict

* Removed unused 'operator' field. Fixed gutter-error color

* Restored the statement group and removed operator
2017-01-20 14:32:34 -05:00
Samantha Marshall
ea57d8b883 Adding additional Python and Objective-C linting (#524)
* adding mypy and pylint to the linter plugin

* adding objective-c linting

* updating runtime file
2017-01-17 16:34:11 -05:00
Samantha Marshall
a7e5a5b26c only prompt the user if they can close all buffers, otherwise it would be misleading 2017-01-10 10:18:49 -05:00
Samantha Marshall
ea0dda98ce adding different functionality for closing a tab versus quitting micro 2017-01-10 10:13:21 -05:00
Nicolai Søborg
41fb57e449 Fix: incorrect clipboard w/ CutLine, DeleteLine and Search (#508)
* Fix: incorrect clipboard w/ CutLine, DeleteLine and Search

* Refactor: Add Cursor.CopySelection(clipboard)
2017-01-09 18:28:45 -05:00
Samantha Marshall
3783f0a9f0 adding tab scrolling and additional tab indicators 2017-01-04 14:24:13 -05:00
Zachary Yedidia
ae566920b6 Merge pull request #518 from samdmarshall/key-unbinding
Key unbinding
2017-01-04 13:56:34 -05:00
Zachary Yedidia
036ed7b9ed Add SaveAs action
Fixes #509
2017-01-04 13:51:17 -05:00
Zachary Yedidia
5775b4c05d Fix gutter coloring for soft wrap
Fixes #511
2017-01-04 13:29:57 -05:00
Samantha Marshall
29502e7f41 simplify the code around unbinding keys 2017-01-02 11:28:47 -05:00
Samantha Marshall
362d8eabae Adding functionality to unbind keys
* adds new special-case keybinding to remove an existing default key binding.
* hides the show/close help text in the status line when no key is assigned to "ToggleHelp"
* updating documentation
2017-01-02 10:56:55 -05:00
Zachary Yedidia
a3c58e52fc Merge pull request #513 from samdmarshall/master
Adding embedded Info.plist to OS X builds
2016-12-29 18:55:28 -05:00
Zachary Yedidia
1edd161684 Merge pull request #510 from legionus/email-syntax
Add mail syntax highlighting
2016-12-28 17:30:47 -05:00
Samantha Marshall
7f95891a9a Adding embedded Info.plist to OS X builds. 2016-12-28 13:17:54 -05:00
Alexey Gladkov
80c6c8ef9f Add mail syntax highlighting 2016-12-28 18:18:19 +01:00
Zachary Yedidia
143339dd67 Merge 2016-12-26 10:34:41 -05:00
Zachary Yedidia
7611c13d12 Better fix for mouse clicking 2016-12-26 10:33:41 -05:00
Zachary Yedidia
d49e366413 Merge pull request #507 from NicolaiSoeborg/master
Fix travis build (new imports for gopher-luar)
2016-12-25 10:56:53 -05:00
Nicolai
ac5fbd9515 Fix travis build (new imports for gopher-luar)
See
24588ee686
2016-12-25 15:42:18 +01:00
Zachary Yedidia
aef75f9b83 Fix bug with mouse clicks
Fixes #504
2016-12-24 15:34:01 -05:00
Zachary Yedidia
faec861081 Update php rules 2016-12-23 18:25:47 -05:00
Nicolai
5a9a7a3835 Merge remote-tracking branch 'refs/remotes/zyedidia/master' 2016-12-24 00:24:12 +01:00
Zachary Yedidia
2649b673f7 Merge 2016-12-22 18:34:15 -05:00
Zachary Yedidia
7958dc0592 Update php syntax rules 2016-12-22 18:34:10 -05:00
Zachary Yedidia
379a49f944 Merge pull request #488 from McSwaggens/removewhitespaces-setting
Added removewhitespaces setting
2016-12-22 17:49:49 -05:00
Daniel Jones
a311e07106 Changed removewhitespaces setting to keepautoindent 2016-12-23 09:44:57 +11:00
Zachary Yedidia
496fab031c Merge 2016-12-22 16:38:24 -05:00
Zachary Yedidia
1a95f34b0e Fix prompts not displaying 2016-12-22 16:38:20 -05:00
Zachary Yedidia
d560de4b40 Merge pull request #499 from 10sr/addrmtrailingws
[Proposal] Add feature to trim trailing whitespaces on save
2016-12-22 15:45:55 -05:00
Zachary Yedidia
0d9fc601ac Merge pull request #489 from november-eleven/refactor/plugin-name
Enable human-friendly plugin name
2016-12-22 15:44:09 -05:00
Zachary Yedidia
325c9111eb Merge pull request #502 from samdmarshall/master
preventing messages from over-writing the current prompt text
2016-12-18 18:38:03 -05:00
Samantha Marshall
968d5be74e fixing test, was using incorrect assignment syntax 2016-12-18 15:29:22 -05:00
Samantha Marshall
71ee042218 preventing messages from over-writing the current prompt message 2016-12-18 15:14:18 -05:00
Zachary Yedidia
d826db89d6 Merge pull request #501 from samdmarshall/master
updating micro syntax to better work with micro syntax files
2016-12-17 20:56:01 -05:00
Samantha Marshall
7db856d39d updating micro syntax to better work with micro syntax files 2016-12-17 20:47:25 -05:00
Zachary Yedidia
f90054cf25 Merge pull request #500 from NicolaiSoeborg/patch-1
Fix: mouse clicking with softwrap
2016-12-17 19:50:13 -05:00
Nicolai Søborg
37ae99ccd9 Fix: mouse clicking with softwrap
When clicking on a long line with softwrap turned on, the cursor will show up in the wrong location (`screenY` will be wrong in `GetSoftWrapLocation`). This seems to fix it.
2016-12-18 00:27:06 +01:00
10sr
e71b49481b Update help for rmtrailingws 2016-12-17 11:33:48 +09:00
10sr
701d0dfe3d Add rmtrailingws feature 2016-12-17 11:33:48 +09:00
Zachary Yedidia
3f02e12539 Merge pull request #497 from 10sr/fixeolnewline
Fix bug that eofnewline does not work on save
2016-12-16 14:41:06 -05:00
10sr
5b689a5592 Fix bug that eofnewline does not work on save 2016-12-15 16:29:50 +09:00
Zachary Yedidia
2bc70890f0 Merge pull request #495 from ColinRioux/master
Minor fix to documentation
2016-12-14 15:13:18 -05:00
Colin Rioux
4e5aa4ecc8 Minor fix to documentation 2016-12-14 15:11:57 -05:00
Zachary Yedidia
5f50d79efa Update docs
Fixes #486
2016-12-14 10:30:03 -05:00
Zachary Yedidia
000197fd28 Merge pull request #493 from Theodus/tabs
Move to new tab when created
2016-12-13 12:30:42 -05:00
theodus
4cb26d2e8e move to new tab 2016-12-13 12:12:20 -05:00
Zachary Yedidia
1d41634272 Add missing word boundary to python regex
Fixes #490
2016-12-13 09:27:54 -05:00
Zachary Yedidia
32e8284505 Expand '~' in SaveAs
Fixes #491
2016-12-13 08:58:08 -05:00
Thomas LE ROUX
651cb89948 refactor(plugin): Enable human-friendly plugin name 2016-12-12 16:37:48 +01:00
Zachary Yedidia
63f18f033c Update runtime 2016-12-11 16:43:07 -05:00
Daniel Jones
0558de12c6 Added removewhitespaces setting 2016-12-11 23:01:10 +11:00
Zachary Yedidia
95293457fb Merge 2016-12-10 20:36:08 -05:00
Zachary Yedidia
d71ad04d98 Display colorscheme error message using TermMessage 2016-12-10 20:36:03 -05:00
Zachary Yedidia
7134cc8e1c Merge pull request #487 from NicolaiSoeborg/patch-1
Update plugin documentation
2016-12-10 20:16:28 -05:00
Nicolai Søborg
3de440338d Update plugin documentation 2016-12-11 01:24:49 +01:00
Zachary Yedidia
73d14f5d37 Merge pull request #485 from sirikid/syntax-fix
Initial OCaml support (integer and real literals, comments)
2016-12-09 10:41:38 -05:00
Zachary Yedidia
291b1d1efc Use shell to parse command when using JobStart
Also changed all occurrences of JobStart to JobSpawn in the linter
plugin.
2016-12-09 10:34:39 -05:00
Ivan Sokolov
57960bdc81 Initial OCaml support (integers and real literals, comments) 2016-12-08 18:11:56 +03:00
Zachary Yedidia
e1d231baa3 Merge pull request #478 from NicolaiSoeborg/master
Add syntax highlighting for Solidity
2016-12-07 21:30:54 -05:00
Zachary Yedidia
8436e2866f Merge pull request #481 from sirikid/syntax-fix
Simple fix for Pascal syntax highlighting
2016-12-07 21:30:05 -05:00
Zachary Yedidia
3ee87e8767 Merge pull request #480 from Theodus/master
improve pony syntax
2016-12-07 21:30:00 -05:00
Ivan Sokolov
11e9419258 Simple fix for Pascal syntax highlighting 2016-12-08 05:03:00 +03:00
theodus
cb7fe94b04 improve pony syntax 2016-12-07 17:09:24 -05:00
Zachary Yedidia
3f01f73ea9 Give error message if input is a directory
Fixes #479
2016-12-07 10:28:03 -05:00
Nicolai
c35650e51a Add syntax highlighting for Solidity 2016-12-06 21:52:37 +01:00
Nicolai
b0813f12e6 Merge remote-tracking branch 'refs/remotes/zyedidia/master' 2016-12-06 21:51:33 +01:00
Zachary Yedidia
67ac3f1a24 Fix string width for different sized tabs
Fixes #475
2016-12-06 09:09:24 -05:00
Rohan Allison
44fa0d77ff Add Crystal language support (#473)
* ignore micro binary

* Add crystal syntax

* Add more crystal keywords; add character style

* Default character style to string
2016-12-05 18:07:27 -05:00
Zachary Yedidia
d00b9f3b7a Merge pull request #474 from NicolaiSoeborg/patch-1
Log erroneous keybindings
2016-12-05 15:01:50 -05:00
Nicolai Søborg
128dc9fea1 Log erroneous keybindings 2016-12-05 17:02:46 +01:00
Zachary Yedidia
ccff712d83 Merge pull request #471 from clemenscorny/master
Update tex.micro and vhdl.micro
2016-12-01 16:28:18 -05:00
Clemens Korner
069df5ef0b convert tex.micro to use micro's colorscheme feature 2016-12-01 21:00:02 +01:00
Clemens Korner
0357ec88d5 replace space and tab characters in vhdl.micro with [:space:] 2016-12-01 18:46:24 +01:00
Zachary Yedidia
ccc68bcf03 Merge pull request #469 from 10sr/allowSymlinkedPlugin
Enable plugins even when they are symlinks
2016-11-30 16:32:48 -05:00
10sr
92362093ab Allow symlink plugin directory 2016-11-30 14:28:13 +09:00
Zachary Yedidia
6fbff048f0 Fix bug with opening empty files 2016-11-29 16:25:16 -05:00
Zachary Yedidia
370e667e91 Remove debug statements 2016-11-29 13:57:26 -05:00
Zachary Yedidia
eeaac76f5f Use io.Readers to read files more efficiently 2016-11-29 13:44:30 -05:00
Zachary Yedidia
d13f9602ff Merge pull request #449 from 10sr/jobSpawn
Add `JobSpawn()` function for plugin interface
2016-11-29 08:57:41 -05:00
Zachary Yedidia
400ac56651 Make tab.CurView public 2016-11-28 20:50:11 -05:00
Zachary Yedidia
5481a834bf Actually rename width, height, lockWidth, lockHeight 2016-11-28 20:28:40 -05:00
Zachary Yedidia
e53229ec00 Make some view vars public 2016-11-28 20:23:22 -05:00
Zachary Yedidia
cee5a88341 Allow creating splits at arbitrary indices 2016-11-28 20:20:30 -05:00
Zachary Yedidia
1b92700990 Allow splits to be created in either direction
This commit adds the `splitRight` and `splitBottom` options to allow
the user to pick which direction to split in.

This also means that a new split is no longer just appended to the list
of splits.
2016-11-28 19:16:49 -05:00
Zachary Yedidia
78b2a99f2e Display 'No name' for empty files 2016-11-28 12:52:45 -05:00
Zachary Yedidia
4e4b4bfe68 Don't open buffer if there was an error loading the file 2016-11-28 10:51:09 -05:00
Zachary Yedidia
a60d348274 Update to cross compile script 2016-11-27 21:29:29 -05:00
Zachary Yedidia
d1402b6502 Fix for nightly release 2016-11-27 21:19:15 -05:00
Zachary Yedidia
92e44aa6af Provide vendored tarball and zip with future releases 2016-11-27 20:15:33 -05:00
Zachary Yedidia
5311a35f5a Merge pull request #462 from Theodus/pony-syntax
Add pony syntax
2016-11-24 14:15:51 -05:00
theodus
d2e59b525d add pony syntax 2016-11-23 22:38:44 -05:00
Zachary Yedidia
543f840912 Order syntax files, with custom syntax files first
Fixes #460
2016-11-23 14:18:20 -05:00
Zachary Yedidia
ea31c662c5 Optimize startup 2016-11-23 11:56:12 -05:00
Zachary Yedidia
c9b9b3d27f Update runtime 2016-11-20 11:07:04 -05:00
Zachary Yedidia
59251ee5d0 Merge pull request #459 from clemenscorny/master
vhdl syntax file
2016-11-20 11:06:44 -05:00
Clemens Korner
6fd117c5f8 vhdl syntax file 2016-11-20 16:43:48 +01:00
Zachary Yedidia
0fbae7610c Fix buffer name problem
Fixes #458
2016-11-19 19:07:51 -05:00
Zachary Yedidia
c692570212 Replace CtrlO with open command
This comit also makes it possible for a binding to auto-type the
beginning of a command into command mode.

Closes #450
2016-11-19 12:57:54 -05:00
Zachary Yedidia
3ecdd96931 Add cd and pwd commands to change the working dir
Closes #451
2016-11-18 16:48:08 -05:00
10sr
7bc8d77387 Add Buffer.AbsPath and a plugin function DirectoryName (#455)
* Add Buffer.AbsPath

* Add a plugin function DirectoryName

* Update plugins.md
2016-11-18 11:53:48 -05:00
Zachary Yedidia
4ce02e4c85 Add foundation for resizing splits arbitrarily
This commit adds the ability to lock a split's width or height so
you can have splits that aren't equally sized. It isn't yet possible
for users to resize splits but the functionality has been implemented.
2016-11-16 12:36:48 -05:00
10sr
856acf4a51 Update plugins.md 2016-11-16 14:14:04 +09:00
10sr
d70a2fe63d Add plugin function JobSpawn 2016-11-16 14:06:12 +09:00
Zachary Yedidia
855c5283e4 Fix minor issue with makefile
Ref #448
2016-11-14 20:10:10 -05:00
Zachary Yedidia
60f2c1e4cf Merge pull request #447 from samdmarshall/objective-c-syntax
Objective-C syntax rules
2016-11-13 18:53:09 -05:00
Samantha Marshall
935d390911 updating types 2016-11-13 15:05:07 -08:00
Samantha Marshall
0d09aabad6 adding objective-c syntax rules 2016-11-13 15:02:20 -08:00
Zachary Yedidia
89c468924e Fix rare out of bounds error with selections
Fixes #446
2016-11-11 20:12:21 -05:00
Zachary Yedidia
d0d167b663 Put linted classfiles in temp directory
Fixes #445
2016-11-09 17:30:25 -05:00
Zachary Yedidia
e721ef8d46 Merge 2016-11-08 09:35:06 -05:00
Zachary Yedidia
7c2baa6086 Add default ftoptions plugin to override settings
The ftoptions plugin will override values in settings.json based
on language requirements (e.g. using tabs in makefiles).
2016-11-08 09:34:12 -05:00
Zachary Yedidia
36ecf226a9 Merge pull request #444 from samdmarshall/c-hex-numbers
adding hexidecimal numbers to the existing C syntax rules
2016-11-08 09:24:18 -05:00
Samantha Marshall
87b5903f6a adding hexidecimal numbers to the existing C syntax rules 2016-11-07 11:42:43 -08:00
Zachary Yedidia
4c0b00bf2b Reset ModTime even if WriteFile fails
Ref #440
2016-11-03 10:55:44 -04:00
Zachary Yedidia
b4b0eda7d9 Merge pull request #433 from ilius/pr03.python_syntax
Fixes in Python 2.7 syntax, add Python 3.x syntax
2016-10-29 10:20:41 -04:00
Zachary Yedidia
a83ecd477e Merge pull request #436 from jncraton/paste-cleanup
Removed duplicate paste code for OS-level paste
2016-10-29 10:20:16 -04:00
Jon Craton
55add69fa0 Removed duplicate paste code for OS-level paste 2016-10-28 23:15:55 -04:00
Zachary Yedidia
199c295f1f Merge 2016-10-28 20:34:38 -04:00
Zachary Yedidia
ad0e098a25 Add ByteOffset and ToCharPos to plugin API 2016-10-28 20:34:28 -04:00
Zachary Yedidia
eee9c54a27 Merge pull request #435 from jncraton/bottomline-selection-fix
Search entire file for soft wrap location instead of line 1 to the number of lines in view
2016-10-28 20:00:59 -04:00
Jon Craton
9719e6caa7 Search entire file instead of line 1 to the number of lines in view 2016-10-28 19:42:17 -04:00
Saeed Rasooli
418720f6df add python3 syntax highlighting (detect from header) 2016-10-28 21:01:44 +03:30
Saeed Rasooli
80bd2694d6 fixes in python (2.7) syntax file, and rename to python2.micro
constants: sort by name, and __file__, remove __import__
functions: add next, help, __import__
separate types (like int and str) from builtin functions
separate and comment out methods/attrs of standard library (why hightlight?)
comment out NonSenseTypes like IntType, they are never used in code
magic methods: add __dict__
exec and map are functions, not keyword
remove trailing spaces
2016-10-28 21:01:43 +03:30
Saeed Rasooli
f6b7aaebbd Improvement: FindFileType: header regex should be prior to file extention 2016-10-28 21:01:43 +03:30
Zachary Yedidia
74610b8cd7 Fix problem with calculation Bottomline
Fixes #432
2016-10-26 12:29:23 -04:00
Zachary Yedidia
7492ab4de2 Add 'plugin available' command
Closes #413
2016-10-24 19:02:13 -04:00
Zachary Yedidia
c04a4ba604 Minor update 2016-10-24 08:03:00 -04:00
Zachary Yedidia
63ccbc1ebd Add eofnewline option
Closes #429

Enable with '> set eofnewline on'
2016-10-23 18:37:29 -04:00
Zachary Yedidia
ee553b7830 Add reload command
Closes #427
2016-10-21 11:51:36 -04:00
Zachary Yedidia
97fc52093f Add website to readme 2016-10-21 10:57:37 -04:00
Zachary Yedidia
49397039e0 Update runtime 2016-10-19 10:34:09 -04:00
Zachary Yedidia
efe1ab5db6 Merge pull request #425 from adrianvoica/master
Updated TypeScript with all the reserved words and new types
2016-10-19 07:26:49 -04:00
Zachary Yedidia
daeffdc81b Merge pull request #423 from ulrichSchreiner/master
add additional Dockerfile keywords
2016-10-19 07:26:36 -04:00
Zachary Yedidia
30083c4d0f Merge pull request #424 from ulrichSchreiner/yaml-highlighter
highlight yaml dicts as types
2016-10-19 07:26:23 -04:00
Adrian Voica
56e616d5bf Updated TypeScript with all the reserved words and new types 2016-10-19 11:54:49 +03:00
Ulrich Schreiner
112da0b8c6 highlight yaml dicts as types 2016-10-19 09:35:03 +02:00
Ulrich Schreiner
163a3993bd add additional Dockerfile keywords 2016-10-19 06:34:50 +02:00
Zachary Yedidia
1b9bb31dd6 Cleanup and add more comments 2016-10-18 11:12:28 -04:00
Zachary Yedidia
8db3b22411 Merge 2016-10-18 08:58:31 -04:00
Zachary Yedidia
4aae5ca451 Fix dockerfile syntax file
Fixes #421
2016-10-18 08:58:09 -04:00
Zachary Yedidia
d3a3b7a8cd Merge pull request #417 from jncraton/outdent-line
Added OutdentLine action
2016-10-16 09:55:24 -04:00
Jon Craton
cc9342df9d Added OutdentLine action 2016-10-15 12:47:15 -04:00
Jon Craton
fe0dce0960 Added IndentString method on Buffer (#415)
* Added IndentString function to retrun the string used for indentation (n-spaces or a tab) based on buffer settings

* Combined redundant  statements

* Removed duplicate leading whitespace check

* Better IndentString description

* Fixed remainder logic that I broke
2016-10-15 10:09:20 -04:00
Zachary Yedidia
766f836952 Merge pull request #416 from jncraton/duplicate-selection
DuplicateLine duplicates current selection if there is text selected
2016-10-15 10:09:08 -04:00
Jon Craton
78b0aac5ec DuplicateLine now duplicates the current selection if there is text selected 2016-10-14 22:22:48 -04:00
Jon Craton
690627a338 Refactored IndentSelection and OutdentSelection to remove duplicate code (#414)
* Refactored indent selection

* Refactored OutdentSelection

* Refactored to use x and y instead of line and j
2016-10-14 16:52:55 -04:00
Zachary Yedidia
25ced4c075 Merge pull request #412 from ilius/pr04.keybindings_help_fixes
Fixes in keybindings.md
2016-10-14 07:37:45 -04:00
Zachary Yedidia
771b5333aa Merge pull request #411 from zenlc2000/master
Reworded first sentence to make it clearer.
2016-10-14 07:35:19 -04:00
Saeed Rasooli
ae72608c5d Bugfix: keybindings.md: fix bad json syntax, due to #407 2016-10-14 14:29:58 +03:30
Saeed Rasooli
2e778a2a8e update keybindings.md due to PR #409 2016-10-14 14:29:58 +03:30
zenlc2000
bc9e811797 Reworded first sentence to make it clearer. 2016-10-13 23:10:37 -06:00
Zachary Yedidia
4db7f33eaf More fixes to search and replace 2016-10-13 20:47:33 -04:00
Zachary Yedidia
d3c5e3ab47 Improvements for softwrap mouse support 2016-10-13 17:09:15 -04:00
Zachary Yedidia
b13c6c4892 Fix problem with regexes in search and replace
Fixes #410
2016-10-13 14:59:57 -04:00
Zachary Yedidia
c50dda244b Fix mouse support with soft wrap 2016-10-13 14:26:45 -04:00
Zachary Yedidia
3fdc2ca0da Always use the selection as search term when using quick search 2016-10-13 12:12:55 -04:00
Zachary Yedidia
6b7ca3c559 Merge pull request #409 from ilius/pr02.improve_search_escape
Improve Search behaviour, and Escape key behaviour
2016-10-13 12:10:11 -04:00
Zachary Yedidia
5c2a2b1b7e Fix problem with horizontal scrolling 2016-10-12 22:05:24 -04:00
Zachary Yedidia
69e45f9a4f Fix problem causing hsplits not to display 2016-10-12 22:03:16 -04:00
Saeed Rasooli
127ebc15b9 Improvement: improve Search behaviour, and Escape key behaviour 2016-10-13 00:49:43 +03:30
Zachary Yedidia
ea1de18326 Add docs 2016-10-12 16:34:34 -04:00
Zachary Yedidia
edd25c68ee Fix glitch with bottomline when softwrap is disabled 2016-10-12 16:30:32 -04:00
Zachary Yedidia
e30a4139e6 Add softwrap 2016-10-12 16:24:00 -04:00
Saeed Rasooli
546acfd21d Fixes in last PR: MoveLinesUp and MoveLinesDown (#408)
* Bugfix: fix panic in MoveLinesUp when moving up the *last* line

* Bugfix: don't panic in Buffer.Line if index is out or range

* clean MoveLinesDown since it won't work for the last line anyway, add comment

* Cleanup: replace spaces with tabs in MoveLinesUp and MoveLinesDown
2016-10-12 11:38:44 -04:00
Zachary Yedidia
d27690b8c6 Merge 2016-10-12 14:47:40 +00:00
Zachary Yedidia
adc56e60fc Use build-date.go in cross compilation script 2016-10-12 14:47:29 +00:00
Zachary Yedidia
266ce5c43b Merge pull request #407 from ilius/pr01.move_up_down
Feature: add MoveLinesUp (Alt + Up) and MoveLinesDown (Alt + Down) actions
2016-10-12 09:51:30 -04:00
Saeed Rasooli
0bf07eadcc Improvement: move MoveLinesUp and MoveLinesDown to Buffer
enables Undo/Redo with EventHandler, #407
2016-10-12 08:15:46 +03:30
Saeed Rasooli
e4386d9398 add help for MoveLinesUp and MoveLinesDown 2016-10-12 08:15:46 +03:30
Saeed Rasooli
c1dd403ab9 Feature: add MoveLinesUp (Alt + Up) and MoveLinesDown (Alt + Down) actions 2016-10-12 08:15:46 +03:30
Zachary Yedidia
0e4f700527 Update installation instructions 2016-10-11 18:21:06 -04:00
Zachary Yedidia
a48c991958 Return 0.0.0-unknown version if building without a git repo 2016-10-11 15:25:39 -04:00
Zachary Yedidia
cbc250b7d0 Improve Makefile
Now you can use 'make update' which will update micro and all the
dependencies (but won't rebuild). The makefile also now supports
having a $GOBIN variable and having multiple directories in your
$GOPATH.
2016-10-11 11:07:53 -04:00
Zachary Yedidia
b27ef219a0 Update readme installation instructions 2016-10-11 09:34:49 -04:00
Zachary Yedidia
d163637fa8 Update docs 2016-10-11 09:13:03 -04:00
Zachary Yedidia
905d4d7020 Make monokai the default colorscheme
Monokai is a better default colorscheme because it has a better 16
color approximation than zenburn. On 16 color terminals, it looks like
zenburn is not syntax highlighting anything.
2016-10-11 09:09:56 -04:00
Zachary Yedidia
f85dd77036 Merge 2016-10-10 21:44:48 -04:00
Zachary Yedidia
8f5f8ffdd6 Fix tabstop sizing with mix of tabs and spaces
Fixes #404
2016-10-10 21:44:16 -04:00
Zachary Yedidia
b09093f78c Merge 2016-10-10 18:40:48 -04:00
Zachary Yedidia
104699e500 Use default foreground for empty indent chars
Fixes #403
2016-10-10 18:40:21 -04:00
Zachary Yedidia
38bf8c0225 Temporary fix for plugin panic
Fixes #402
2016-10-07 20:34:03 -04:00
Zachary Yedidia
6acda994e4 Update docs and readme 2016-10-06 20:36:37 -04:00
Zachary Yedidia
e563211790 Make linter a default plugin once again 2016-10-06 20:28:10 -04:00
Zachary Yedidia
6a5879cc15 Improve binary size by stripping more aggressively 2016-10-06 17:45:28 -04:00
Zachary Yedidia
aa624d86e6 Move linter and go plugins to their own repos
The linter and go plugins are no longer 'default'. Their installation
should be handled by the plugin manager: `> plugin install go` and
`> plugin install linter`.

The autoclose plugin will remain a default plugin because it provides
a more essential feature.

Closes #397
2016-10-06 17:18:53 -04:00
Zachary Yedidia
c410b7b2ce Improve plugin manager error feedback 2016-10-06 13:39:57 -04:00
Zachary Yedidia
79f1539486 Merge pull request #396 from boombuler/help
updated plugin help
2016-10-06 07:22:25 -04:00
Florian Sundermann
d7b7cc954a updated plugin help 2016-10-06 08:24:39 +02:00
Zachary Yedidia
1914a5b5ff Update readme 2016-10-05 18:28:08 -04:00
Zachary Yedidia
921b828afb Add some documentation about plugin manager 2016-10-05 18:26:41 -04:00
Zachary Yedidia
d3d35bd9ff Add more descriptive error messages for plugin installation failures
Ref #378
2016-10-05 18:00:05 -04:00
Zachary Yedidia
76a328a062 Use official plugin channel
Use the channel for official plugins from
https://github.com/micro-editor/plugin-channel

Ref #378
2016-10-05 17:57:03 -04:00
Zachary Yedidia
fb90e169cb Only allow one package per repository
This may be temporary.

Ref #378
2016-10-05 17:52:39 -04:00
Zachary Yedidia
a1a307d858 Merge pull request #378 from boombuler/pm
Plugin-Manager
2016-10-05 17:51:22 -04:00
Zachary Yedidia
3733e7e223 Add 'Unsplit' action and VSplit and HSplit actions
This commit adds the 'Unsplit' action used to close all splits except
the current one.

It also adds the 'VSplit' and 'HSplit' actions which open empty
vertical/horizontal splits so you can bind them to keys.

Closes #228
2016-10-04 11:08:32 -04:00
boombuler
3e8a587aa3 changed json5 repo 2016-10-02 07:57:39 +02:00
boombuler
8f2f1f8c1d skip core dependencies if micro was build with an unknown version. 2016-10-01 09:28:48 +02:00
boombuler
a940ce3036 allow user to set plugin channels / repos in settings.json 2016-10-01 08:37:04 +02:00
boombuler
d7da72a720 fix plugin zips which contain a root directory 2016-10-01 08:05:05 +02:00
boombuler
b54853140a new command plugin list
this command shows all currently installed plugins and their verion
2016-10-01 07:37:20 +02:00
boombuler
8ad2179423 Merge remote-tracking branch 'zyedidia/master' into pm 2016-10-01 07:20:21 +02:00
Zachary Yedidia
3037d72bcb Fix more tabnum issues
Fixes #395
2016-09-30 07:29:24 -04:00
Zachary Yedidia
7d16e97b95 Switch to my fork of json5
This should reduce go get download times for micro considerably
because the original json5 committed a bunch of binaries which
cause the repository to be very large and slow to download.

My fork fixes that.
2016-09-29 18:43:10 -04:00
Zachary Yedidia
0293b774f3 Fix SplitTree tab index
Fixes #392
2016-09-29 14:23:25 -04:00
Zachary Yedidia
32cd94b88f Minor optimization to tabsize fix 2016-09-28 18:08:06 -04:00
Zachary Yedidia
5e5dd78b7c Merge pull request #387 from boombuler/bug379
fixes #379 (second try)
2016-09-28 18:06:18 -04:00
Zachary Yedidia
1c5c741e87 Make sure /Users/zachary/gocode/bin exists before putting binary there 2016-09-28 17:59:40 -04:00
Zachary Yedidia
095e6993a8 Merge pull request #389 from dsnet/master
fix offset calculation for column ruler
2016-09-28 17:57:43 -04:00
Joe Tsai
7c3425a012 fix offset calculation for column ruler
The calculation for the column ruler index should:
* include the offset for the line numbers gutter
* not include the leftmost column since ruler should scroll with the pane

Fixes #379
2016-09-28 13:40:48 -07:00
boombuler
bc724bf781 fixes #379 (second try) 2016-09-28 21:54:34 +02:00
Zachary Yedidia
13144d4b57 Merge pull request #386 from zyedidia/revert-382-bug379
Revert "fixes #379"
2016-09-28 14:07:29 -04:00
Zachary Yedidia
97bdb15bd6 Revert "fixes #379" 2016-09-28 14:07:17 -04:00
Zachary Yedidia
fb69ecdc9b Add 'autosave' option
Closes #278
2016-09-28 13:07:05 -04:00
boombuler
1fe1c3eabb improved plugin search 2016-09-28 18:31:05 +02:00
Zachary Yedidia
191fd5e495 Merge pull request #382 from boombuler/bug379
fixes #379
2016-09-28 12:30:01 -04:00
boombuler
8aa017bfda autocomplete plugin commands 2016-09-28 18:15:39 +02:00
boombuler
9ea947c808 improved logging 2016-09-28 18:00:12 +02:00
boombuler
2a7a55eca4 better plugin search 2016-09-28 17:55:44 +02:00
boombuler
759c00098b Merge remote-tracking branch 'zyedidia/master' into pm 2016-09-28 17:36:37 +02:00
Florian Sundermann
cce36624dc PM should not install already installed plugins. 2016-09-28 16:34:28 +02:00
Zachary Yedidia
4664850186 Merge pull request #384 from boombuler/logview
don't use undo / redo history for log buffer.
2016-09-28 10:24:30 -04:00
Florian Sundermann
d9c666f6df don't use undo / redo history for log buffer. 2016-09-28 15:47:31 +02:00
Florian Sundermann
d7e38a52ea fixes #379
when tabstospaces is off tabs were always treated as
as a number of spaces not as tabs with tabstops.
2016-09-28 08:12:19 +02:00
boombuler
f3f4790103 simple plugin search 2016-09-27 21:25:57 +02:00
boombuler
83c1136ac5 Merge remote-tracking branch 'zyedidia/master' into pm 2016-09-27 20:57:49 +02:00
Zachary Yedidia
0ae5ae5d9a HSplit log, and update docs 2016-09-27 14:29:55 -04:00
Zachary Yedidia
c070e3e8f7 Merge pull request #381 from boombuler/logview
Log View
2016-09-27 14:28:06 -04:00
Zachary Yedidia
0de167b07b Add new plugin runtime function 2016-09-27 14:24:52 -04:00
boombuler
f904e2fe99 always scroll log to the cursor befor drawing and don't ask for save changes for help and log views 2016-09-27 17:52:40 +02:00
boombuler
b195ebad46 AddLog should be "public" accessible 2016-09-27 17:52:05 +02:00
Florian Sundermann
23ef69b935 change pluginmanager json to json5 2016-09-27 13:28:32 +02:00
Florian Sundermann
55c790f069 more tolerant version parsing 2016-09-27 13:26:11 +02:00
Florian Sundermann
4bcb13efc0 try to set a more matching version number 2016-09-27 13:25:17 +02:00
boombuler
50c7441533 also add TermMessage output to log 2016-09-26 19:28:42 +02:00
boombuler
c1a3ee1706 possibility to show a log view 2016-09-26 19:08:37 +02:00
boombuler
357fc09e69 Merge remote-tracking branch 'zyedidia/master' into pm 2016-09-26 18:24:43 +02:00
Zachary Yedidia
c1d08a6dc0 Fix typo 2016-09-26 12:08:35 -04:00
boombuler
f689143670 fixed tests 2016-09-26 17:51:50 +02:00
boombuler
56b3b79c50 removed testing code 2016-09-26 17:37:53 +02:00
Florian Sundermann
f351c251e4 first few pm commands 2016-09-26 16:53:39 +02:00
Zachary Yedidia
5cc66cef42 Fix problems recognizing CtrlH
Fixes #368

The 'Backspace2' key has been renamed to 'Backspace'.
2016-09-26 09:34:55 -04:00
Florian Sundermann
6791759440 Merge remote-tracking branch 'zyedidia/master' into pm 2016-09-26 12:49:57 +02:00
Zachary Yedidia
ac98f21199 Merge pull request #377 from onodera-punpun/patch-2
Add ` to autoclose
2016-09-25 13:32:37 -04:00
Camille
22ebbcfd89 Add ` to autoclose 2016-09-25 19:05:58 +02:00
Zachary Yedidia
292df7a9f7 Add mouse support and binding support to prompts
Closes #244
2016-09-24 15:26:19 -04:00
Zachary Yedidia
64fd96611c Check buffer filetype after loading plugins 2016-09-24 14:30:35 -04:00
Zachary Yedidia
de4a007bdf Merge pull request #371 from boombuler/plugins
Handle Plugins via RT-Files
2016-09-24 14:09:02 -04:00
boombuler
567faeb07e initial commit of pluginmanager 2016-09-23 10:03:42 +02:00
Zachary Yedidia
3afb3d0b22 Merge pull request #370 from boombuler/snippets_core
Snippets core
2016-09-20 08:58:22 -04:00
Florian Sundermann
8172ebf62b fixed loading order
plugins were not able to provide colorschemes
2016-09-19 16:04:59 +02:00
Florian Sundermann
1720d4023f load plugins as rt-files 2016-09-19 14:40:56 +02:00
Florian Sundermann
da6ab78384 fixed build 2016-09-19 13:28:14 +02:00
Florian Sundermann
6fe20fb305 some additions to the plugin API
Those changes were originally used for the snippet plugin which
may not be part of the core.
2016-09-19 13:23:47 +02:00
Zachary Yedidia
d41f0bb324 Merge 2016-09-18 09:30:28 -04:00
Zachary Yedidia
8e555e60f7 Inherit background color from default
Fixes #366
2016-09-18 09:29:58 -04:00
Zachary Yedidia
243f99aeb1 Add function to load runtime files from a directory for a plugin 2016-09-16 16:15:44 -04:00
Zachary Yedidia
ab36db7646 Update yaml header 2016-09-16 15:22:38 -04:00
Zachary Yedidia
2e3c87b67d Add quick start guide to help.md 2016-09-16 12:14:08 -04:00
Zachary Yedidia
a549d12808 Merge pull request #334 from techtonik/filemanagers
Usability integration with file managers
2016-09-16 12:09:17 -04:00
Zachary Yedidia
149fea8b76 Allow plugins to add their own runtime files 2016-09-16 11:02:10 -04:00
Zachary Yedidia
f7295a25d8 Merge pull request #363 from boombuler/rtfiles
Runtime files
2016-09-16 10:33:28 -04:00
Florian Sundermann
9eeb14956c allow plugins to list / read runtime files
also renamed most of the new functions to be
more specific about what kind of files this is for.
2016-09-15 16:42:45 +02:00
Florian Sundermann
796638d095 simplified file handling for runtime files 2016-09-15 15:50:26 +02:00
Zachary Yedidia
79621505f1 Merge pull request #359 from boombuler/params
Params
2016-09-14 17:14:01 -04:00
Zachary Yedidia
e484445b1e Merge pull request #354 from boombuler/help
Help
2016-09-14 17:13:46 -04:00
Zachary Yedidia
5eddba5516 Merge pull request #360 from rgburke/validate-options
Added ability to validate options values when being set
2016-09-14 15:34:18 -04:00
Richard Burke
bdc857952a Added ability to validate options values when being set 2016-09-14 20:06:48 +01:00
Zachary Yedidia
2bcc59faea Fix pyflakes linter pattern
Fixes #358
2016-09-14 12:54:31 -04:00
Florian Sundermann
6cc12b871c include trailing path delimiter
"C:" is not valid on windows but "C:\" is.
"foo" is as valid as "foo/" on other OS...
2016-09-14 16:28:25 +02:00
Florian Sundermann
d201e7c503 fixed directory completion on windows 2016-09-14 16:15:49 +02:00
Zachary Yedidia
c695df0adf Merge pull request #356 from boombuler/bug355
fixed bug 355
2016-09-14 07:29:44 -04:00
Zachary Yedidia
a04e3080fb Merge pull request #357 from boombuler/emptyfiles
don't reuse unsaved file buffers.
2016-09-14 07:28:52 -04:00
boombuler
7d395a29a7 don't clone unsaved file buffers.
if I open multiple empty tabs, I don't want the same "new file buffers"
2016-09-14 09:37:12 +02:00
boombuler
4046bb977e fixed bug 355 2016-09-14 09:24:38 +02:00
boombuler
d250b9d7b0 allow plugins to have a help file 2016-09-13 09:06:06 +02:00
boombuler
a7f159bddc Load help files when needed 2016-09-13 08:53:20 +02:00
Zachary Yedidia
d0fa467a3c Revert "Improve performance for very long lines"
This reverts commit d5694c0f35.

Fixes #351
See #348
2016-09-11 16:05:15 -04:00
anatoly techtonik
f4e0a3c0f8 Fix comma and tabs in markdown help 2016-09-11 22:40:02 +03:00
Zachary Yedidia
0bc80adc28 Fix strange selection for long lines
See #351
2016-09-11 15:00:44 -04:00
Zachary Yedidia
cfdaf0e3f6 Merge pull request #316 from elopio/snapcraft
Add the packaging metadata to build the micro snap
2016-09-10 15:43:39 -04:00
Zachary Yedidia
b5160c5d2c Optimize search and replace a lot 2016-09-10 11:32:54 -04:00
Zachary Yedidia
210a538cdd Improve performance for xml and html files 2016-09-10 10:32:21 -04:00
Zachary Yedidia
fd786b3020 Allow a buffer to be opened simultaneously 2016-09-10 10:30:15 -04:00
Zachary Yedidia
ba9560079c Merge 2016-09-10 10:03:55 -04:00
Zachary Yedidia
d5694c0f35 Improve performance for very long lines 2016-09-10 10:03:51 -04:00
Nickolay
1522b24803 Okay. Bugfix with syntax :P (#339)
* Now it can install micro into /usr/bin (make-build only, unix-only)

* Fixed Syntax Bugging

* Update xml.micro

* Revert Makefile change

* Update xml.micro
2016-09-10 09:18:14 -04:00
Zachary Yedidia
922baa930d Add eval command 2016-09-09 15:54:32 -04:00
Zachary Yedidia
faafda6b21 Remove pkgbuild from pre installed syntax files 2016-09-09 14:48:18 -04:00
Zachary Yedidia
9efc4fb5e9 Remove duplicate PKGBUILD filetype 2016-09-09 12:41:56 -04:00
Zachary Yedidia
37d83a280f Merge 2016-09-09 12:32:05 -04:00
Zachary Yedidia
8c0544c264 Use shell.micro for PKGBUILD files
Fixes #345
2016-09-09 12:31:44 -04:00
Zachary Yedidia
5e6a26a6ea Merge pull request #344 from apjanke/doco-tweaks
doco: grammar and formatting tweaks
2016-09-09 07:43:15 -04:00
Andrew Janke
9a09647330 doco: grammar and formatting tweaks 2016-09-09 00:07:58 -04:00
Zachary Yedidia
0c00e8da0e Merge pull request #341 from boombuler/bug297
fixes #297
2016-09-08 17:31:45 -04:00
Zachary Yedidia
af47cce86b Resize tabs more often
Fixes #343
2016-09-08 17:30:41 -04:00
Zachary Yedidia
301e86a46e Add SaveAs action and command
Fixes #340

You can bind the action `SaveAs` and if you provide an argument to
the `save` command it will save as. For example `> save test.txt`.
2016-09-08 14:13:46 -04:00
Florian Sundermann
0b1afe7f6c fixes #297
use a buffered channel to queue events.
otherwise those events might get lost.
2016-09-08 13:47:13 +02:00
Zachary Yedidia
1739f0631a Merge pull request #338 from akkatracker/patch-1
its --> it's in actions.go comment
2016-09-08 07:22:03 -04:00
Matthew Brener
4f4e87a82c its --> it's in actions.go comment 2016-09-08 16:14:04 +10:00
Zachary Yedidia
49ec611c8f Update readme 2016-09-07 20:28:18 -04:00
Zachary Yedidia
8f06e51170 Add colorcolumn option
Fixes #333

For example: `> set colorcolumn 80`.
2016-09-07 17:17:51 -04:00
anatoly techtonik
a853164302 Usability integration with file managers 2016-09-08 00:11:51 +03:00
Zachary Yedidia
dc8207149b Merge branch 'to-miz-master' 2016-09-07 16:57:58 -04:00
Zachary Yedidia
cc73efc0bd Merge branch 'master' of https://github.com/to-miz/micro into to-miz-master 2016-09-07 16:57:42 -04:00
Zachary Yedidia
4992efd172 Merge pull request #332 from nueh/master
Consider all multi-byte characters a "wordchar", fixes #329
2016-09-07 12:10:24 -04:00
Niklas Hennigs
956b1bdb3a Missed the test for the last commit, now fixed 2016-09-07 17:52:16 +02:00
Niklas Hennigs
cd52aaba13 This fixes zyedidia/micro#329
Now all multi-byte characters are considered a wordchar
2016-09-07 17:29:38 +02:00
Zachary Yedidia
cc6189b7ce Check colorschemes from ~/.config/micro/colorschemes
Fixes #331
2016-09-07 07:25:42 -04:00
Zachary Yedidia
5e82fc4673 Update readme 2016-09-06 20:33:39 -04:00
Zachary Yedidia
107a6f877b Update readme 2016-09-06 20:32:21 -04:00
Zachary Yedidia
e643860e3d Add Open command for view 2016-09-06 19:58:34 -04:00
Zachary Yedidia
0373589ab8 Merge 2016-09-06 19:30:28 -04:00
Zachary Yedidia
dce56a2b85 Have HandleShellCommand return the stdout
HandleShellCommand will now return the stdout as a string and
it also takes an additional flag indicating whether it should
wait before closing the shell and returning to the editor.
2016-09-06 19:27:57 -04:00
Zachary Yedidia
27d2ebfb45 Merge pull request #325 from techtonik/patch-1
Fix CanClose comment after API change
2016-09-06 16:02:45 -04:00
anatoly techtonik
1f457f9d9e Fix CanClose comment after API change
Follow up to 966dac97f8
2016-09-06 22:51:13 +03:00
Zachary Yedidia
c0282c4a3c Add issue template 2016-09-06 11:24:55 -04:00
Zachary Yedidia
0a6e1de404 Merge branch 'boombuler-params' 2016-09-06 10:59:46 -04:00
Zachary Yedidia
131524e670 Merge branch 'params' of https://github.com/boombuler/micro into boombuler-params 2016-09-06 10:59:30 -04:00
Zachary Yedidia
539495d2f7 Add support for macros
Closes #270

CtrlU to toggle recording and CtrlJ to playback.
You can also rebind using the "ToggleMacro" and "PlayMacro"
actions.

Note that recursive macros are not yet supported.
2016-09-06 10:44:15 -04:00
Zachary Yedidia
966dac97f8 Make unsaved changes prompt more clear
Fixes #301
2016-09-06 10:06:36 -04:00
Zachary Yedidia
bf6e596808 Merge pull request #323 from zonuexe/php-keywords
Add PHP Keywords
2016-09-06 07:24:22 -04:00
USAMI Kenta
efa24f5a8d Add PHP Keywords
- trait (PHP 5.5)
- iterable (PHP 7.1)
- void (PHP 7.1)

see https://github.com/ejmr/php-mode/pull/317
2016-09-06 18:25:08 +09:00
Zachary Yedidia
432146b068 Merge pull request #318 from elopio/update_runtime
Update runtime
2016-09-05 16:12:26 -04:00
Leo Arias
02b6eaaff0 Update runtime
closes #317
2016-09-05 18:31:36 +00:00
Leo Arias
e7ee18e421 Add the packaging metadata to build the micro snap 2016-09-05 17:17:59 +00:00
Zachary Yedidia
dc532e337b Merge pull request #315 from boombuler/pascal
added pascal syntax
2016-09-05 12:10:56 -04:00
boombuler
a952e249b4 added pascal syntax 2016-09-05 17:53:49 +02:00
Zachary Yedidia
94465ef1ae Merge 2016-09-05 11:38:16 -04:00
Zachary Yedidia
0a534767f0 Merge branch 'primary-clipboard' 2016-09-05 11:37:49 -04:00
Zachary Yedidia
7937f7038b Merge pull request #314 from mame98/master
Added some SCSS keywords for the css/scss highlighting
2016-09-05 10:50:00 -04:00
Marius Messerschmidt
d6a01ad29f Added some SCSS keywords for the css/scss highlighting
Signed-off-by: Marius Messerschmidt <marius.messerschmidt@googlemail.com>
2016-09-05 16:41:32 +02:00
Zachary Yedidia
b1cb583e8c Update runtime 2016-09-05 10:30:08 -04:00
Zachary Yedidia
ea4d822923 Merge pull request #283 from boombuler/autocompleteplugin
Autocomplete for plugins
2016-09-05 10:29:51 -04:00
Zachary Yedidia
583177feff Update readme 2016-09-05 10:29:15 -04:00
Zachary Yedidia
3bec1b8c1b Merge pull request #313 from adrianvoica/master
Updates to TypeScript dictionary.
2016-09-05 10:28:36 -04:00
Adrian Voica
b992669f5b Updates to TypeScript dictionary. 2016-09-05 17:18:41 +03:00
Zachary Yedidia
086aa61e5a Merge pull request #311 from adrianvoica/master
Added TypeScript syntax highlighting file.
2016-09-05 10:09:23 -04:00
to-miz
725533d991 fixed inserting runes that require ctrl+alt
we check wheter an input is a binding first, only if it is not a binding
do we insert the rune regardless of modifiers
2016-09-05 16:03:05 +02:00
Adrian Voica
c310053777 Added TypeScript syntax highlighting file. 2016-09-05 17:01:37 +03:00
Zachary Yedidia
2041e12eba Fix some issues with mouse selection copying 2016-09-05 08:36:30 -04:00
Zachary Yedidia
5d00522d4e Deselect for CursorStart
Fixes #308
2016-09-05 08:25:49 -04:00
Zachary Yedidia
c71e816e37 Fix recursive function 2016-09-04 21:28:40 -04:00
Zachary Yedidia
6721ec8e7d Copy to primary clipboard for any change in selection 2016-09-04 21:19:14 -04:00
Zachary Yedidia
93eadfb9dc Merge 2016-09-04 18:27:16 -04:00
Zachary Yedidia
cf3ce29a08 Fix YesNoPrompt bug 2016-09-04 18:27:11 -04:00
Zachary Yedidia
cd1117c08c Merge pull request #304 from techtonik/windate
Windows-compatible Makefile
2016-09-04 15:17:27 -04:00
anatoly techtonik
f247823936 Get build date on Windows without Python 2016-09-04 22:07:07 +03:00
anatoly techtonik
4b350d02e0 Let Go choose binary name and extension
This creates micro.exe on Windows
2016-09-04 19:38:50 +03:00
anatoly techtonik
ae3696e82d Use Python to get date in cross-platform way
I was able to build micro with Mozilla's pymake on Windows
2016-09-04 19:31:16 +03:00
boombuler
403a99d2ea removed obsolete replace command preparations 2016-09-03 12:13:25 +02:00
boombuler
b2735d7b5b use build-in functions to quote / unquote 2016-09-03 12:02:49 +02:00
boombuler
699ad316e5 removed old code 2016-09-03 08:18:47 +02:00
boombuler
8617ae5c1f keep trailing space at commandline 2016-09-03 08:16:18 +02:00
boombuler
c5ac5be764 updated plugin help 2016-09-02 19:50:19 +02:00
boombuler
881f57b047 allow plugins to register autocomplete functions 2016-09-02 19:42:33 +02:00
Zachary Yedidia
89c34ed8b3 Copy to primary clipboard on mouse selection 2016-09-02 10:44:32 -04:00
Zachary Yedidia
d9b8a04841 Add support for primary clipboard 2016-09-02 09:40:08 -04:00
Florian Sundermann
ccfe08bc60 allow command parameters to be quoted
this way FileCompletions could contain space runes without
breaking the parameter.
2016-09-02 13:44:22 +02:00
332 changed files with 19236 additions and 5414 deletions

9
.github/ISSUE_TEMPLATE vendored Normal file
View File

@@ -0,0 +1,9 @@
## Description of the problem or steps to reproduce
## Specifications
You can use `micro -version` to get the commit hash.
Commit hash:
OS:
Terminal:

6
.gitignore vendored
View File

@@ -1,5 +1,9 @@
./micro
.DS_Store
micro
!cmd/micro
binaries/
tmp.sh
test/
.idea/
packages/

57
.gitmodules vendored Normal file
View File

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

View File

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

1166
LICENSE-THIRD-PARTY Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +1,48 @@
.PHONY: runtime
VERSION = $(shell git describe --tags --abbrev=0)
HASH = $(shell git rev-parse --short HEAD)
VERSION := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
go run tools/build-version.go)
HASH := $(shell git rev-parse --short HEAD)
DATE := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
go run tools/build-date.go)
ADDITIONAL_GO_LINKER_FLAGS := $(shell GOOS=$(shell go env GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH) \
go run tools/info-plist.go "$(VERSION)")
GOBIN ?= $(shell go env GOPATH)/bin
# Builds micro after checking dependencies but without updating the runtime
build: deps tcell
go build -ldflags "-X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(shell date -u '+%B %d, %Y')'" -o micro ./cmd/micro
build: update
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Builds micro after building the runtime and checking dependencies
build-all: runtime build
# Builds micro without checking for dependencies
build-quick:
go build -ldflags "-X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(shell date -u '+%B %d, %Y')'" -o micro ./cmd/micro
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Same as 'build' but installs to $GOPATH/bin afterward
install: build
mv micro $(GOPATH)/bin
# Same as 'build' but installs to $GOBIN afterward
install: update
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Same as 'build-all' but installs to $GOPATH/bin afterward
# Same as 'build-all' but installs to $GOBIN afterward
install-all: runtime install
# Same as 'build-quick' but installs to $GOPATH/bin afterward
install-quick: build-quick
mv micro $(GOPATH)/bin
# Same as 'build-quick' but installs to $GOBIN afterward
install-quick:
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Updates tcell
tcell:
git -C $(GOPATH)/src/github.com/zyedidia/tcell pull
# Checks for dependencies
deps:
go get -d ./cmd/micro
update:
git pull
git submodule update --init
# Builds the runtime
runtime:
go get -u github.com/jteeuwen/go-bindata/...
$(GOPATH)/bin/go-bindata -nometadata -o runtime.go runtime/...
$(GOBIN)/go-bindata -nometadata -o runtime.go runtime/...
mv runtime.go cmd/micro
test:
go get -d ./cmd/micro
go test ./cmd/micro
clean:

View File

@@ -4,6 +4,7 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/zyedidia/micro)](https://goreportcard.com/report/github.com/zyedidia/micro)
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
[![Snap Status](https://build.snapcraft.io/badge/zyedidia/micro.svg)](https://build.snapcraft.io/user/zyedidia/micro)
Micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities
of modern terminals. It comes as one single, batteries-included, static binary with no dependencies, and you can download and use it right now.
@@ -17,10 +18,13 @@ Here is a picture of micro editing its source code.
To see more screenshots of micro, showcasing all of the default colorschemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
You can also check out the website for Micro at https://micro-editor.github.io.
# Features
* Easy to use and to install
* No dependencies or external files are needed -- just the binary you can download further down the page
* Multiple cursors
* Common keybindings (ctrl-s, ctrl-c, ctrl-v, ctrl-z...)
* Keybindings can be rebound to your liking
* Sane defaults
@@ -31,16 +35,20 @@ To see more screenshots of micro, showcasing all of the default colorschemes, se
* Cross platform (It should work on all the platforms Go runs on)
* Note that while Windows is supported, there are still some bugs that need to be worked out
* Plugin system (plugins are written in Lua)
* Micro has a built-in plugin manager to automatically install, remove, and update all your plugins
* Persistent undo
* Automatic linting and error notifications
* Syntax highlighting (for over [75 languages](runtime/syntax)!)
* Syntax highlighting (for over [90 languages](runtime/syntax)!)
* Colorscheme support
* By default, micro comes with 16, 256, and true color themes.
* True color support (set the `MICRO_TRUECOLOR` env variable to 1 to enable it)
* Snippets
* The snippet plugin can be installed with `> plugin install snippets`
* Copy and paste with the system clipboard
* Small and simple
* Easily configurable
* Common editor things such as undo/redo, line numbers, unicode support...
* Macros
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)), and multiple cursors ([#5](https://github.com/zyedidia/micro/issues/5)) in the future.
@@ -48,7 +56,7 @@ Although not yet implemented, I hope to add more features such as autocompletion
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro)
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro).
### Prebuilt binaries
@@ -61,19 +69,57 @@ and you'll see all the stable releases with the corresponding binaries.
If you'd like to see more information after installing micro, run `micro -version`.
### Package Managers
You can install micro using Homebrew on Mac:
```
brew install micro
```
On Windows, you can install micro through [Chocolatey](https://chocolatey.org/) or [Scoop](https://github.com/lukesampson/scoop):
```
choco install micro
```
or
```
scoop install micro
```
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
```
snap install micro --edge --classic
```
### Building from source
If your operating system does not have binary, but does run Go, you can build from source.
If your operating system does not have a binary release, but does run Go, you can build from source.
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO).
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommand setting it to `~/go` if you don't have one).
```sh
go get -u github.com/zyedidia/micro/...
```
go get -d github.com/zyedidia/micro/cmd/micro
cd $GOPATH/src/github.com/zyedidia/micro
make install
```
The binary will then be installed to `$GOPATH/bin` (or your `$GOBIN`).
Please make sure that when you are working with micro's code, you are working on your `GOPATH`.
You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd/micro`) but this isn't recommended because it doesn't build micro with version information which is useful for the plugin manager.
### MacOS terminal
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. The newest versions also support true color.
### Linux clipboard support
On Linux, clipboard support requires 'xclip' or 'xsel' command to be installed.
On Linux, clipboard support requires the 'xclip' or 'xsel' commands to be installed.
For Ubuntu:
@@ -87,9 +133,9 @@ If you don't have xclip or xsel, micro will use an internal clipboard for copy a
If you open micro and it doesn't seem like syntax highlighting is working, this is probably because
you are using a terminal which does not support 256 color. Try changing the colorscheme to `simple`
by running `> set colorscheme simple`.
by pressing CtrlE in micro and typing `set colorscheme simple`.
If you are using the default ubuntu terminal, to enable 256 make sure your `TERM` variable is set
If you are using the default Ubuntu terminal, to enable 256 make sure your `TERM` variable is set
to `xterm-256color`.
Many of the Windows terminals don't support more than 16 colors, which means
@@ -97,11 +143,11 @@ that micro's default colorscheme won't look very good. You can either set
the colorscheme to `simple`, or download a better terminal emulator, like
mintty.
### Plan9, NaCl, Cygwin
### Plan9, Cygwin
Please note that micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
Plan9, NaCl, and Cygwin (although this may change in the future).
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).
# Usage
@@ -121,7 +167,7 @@ click to enable line selection.
# Documentation and Help
Micro has a built-in help system which you can access by pressing `CtrlE` and typing `help`. Additionally, you can
Micro has a built-in help system which you can access by pressing `Ctrl-E` and typing `help`. Additionally, you can
view the help files here:
* [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
@@ -138,4 +184,7 @@ a brief introduction to the more powerful configuration features micro offers.
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
You can use the Github issue tracker to report bugs, ask questions, or suggest new features.
You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues)
to report bugs, ask questions, or suggest new features.
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro).

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -8,6 +8,8 @@ import (
"github.com/mitchellh/go-homedir"
)
var pluginCompletions []func(string) []string
// This file is meant (for now) for autocompletion in command mode, not
// while coding. This helps micro autocomplete commands and then filenames
// for example with `vsplit filename`.
@@ -16,12 +18,14 @@ import (
func FileComplete(input string) (string, []string) {
var sep string = string(os.PathSeparator)
dirs := strings.Split(input, sep)
var files []os.FileInfo
var err error
if len(dirs) > 1 {
home, _ := homedir.Dir()
directories := strings.Join(dirs[:len(dirs)-1], sep)
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
if strings.HasPrefix(directories, "~") {
directories = strings.Replace(directories, "~", home, 1)
}
@@ -29,6 +33,7 @@ func FileComplete(input string) (string, []string) {
} else {
files, err = ioutil.ReadDir(".")
}
var suggestions []string
if err != nil {
return "", suggestions
@@ -79,9 +84,9 @@ func CommandComplete(input string) (string, []string) {
func HelpComplete(input string) (string, []string) {
var suggestions []string
for _, topic := range helpFiles {
for _, file := range ListRuntimeFiles(RTHelp) {
topic := file.Name()
if strings.HasPrefix(topic, input) {
suggestions = append(suggestions, topic)
}
}
@@ -93,6 +98,25 @@ func HelpComplete(input string) (string, []string) {
return chosen, suggestions
}
// ColorschemeComplete tab-completes names of colorschemes.
func ColorschemeComplete(input string) (string, []string) {
var suggestions []string
files := ListRuntimeFiles(RTColorscheme)
for _, f := range files {
if strings.HasPrefix(f.Name(), input) {
suggestions = append(suggestions, f.Name())
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
@@ -123,3 +147,50 @@ func OptionComplete(input string) (string, []string) {
}
return chosen, suggestions
}
// MakeCompletion registers a function from a plugin for autocomplete commands
func MakeCompletion(function string) Completion {
pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
return Completion(-len(pluginCompletions))
}
// PluginComplete autocompletes from plugin function
func PluginComplete(complete Completion, input string) (chosen string, suggestions []string) {
idx := int(-complete) - 1
if len(pluginCompletions) <= idx {
return "", nil
}
suggestions = pluginCompletions[idx](input)
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return
}
func PluginCmdComplete(input string) (chosen string, suggestions []string) {
for _, cmd := range []string{"install", "remove", "search", "update", "list"} {
if strings.HasPrefix(cmd, input) {
suggestions = append(suggestions, cmd)
}
}
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
func PluginNameComplete(input string) (chosen string, suggestions []string) {
for _, pp := range GetAllPluginPackages() {
if strings.HasPrefix(pp.Name, input) {
suggestions = append(suggestions, pp.Name)
}
}
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}

View File

@@ -5,84 +5,119 @@ import (
"os"
"strings"
"github.com/yosuke-furukawa/json5/encoding/json5"
"github.com/flynn/json5"
"github.com/zyedidia/tcell"
)
var bindings map[Key][]func(*View, bool) bool
var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
var helpBinding string
var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
"MousePress": (*View).MousePress,
"MouseMultiCursor": (*View).MouseMultiCursor,
}
var bindingActions = map[string]func(*View, bool) bool{
"CursorUp": (*View).CursorUp,
"CursorDown": (*View).CursorDown,
"CursorPageUp": (*View).CursorPageUp,
"CursorPageDown": (*View).CursorPageDown,
"CursorLeft": (*View).CursorLeft,
"CursorRight": (*View).CursorRight,
"CursorStart": (*View).CursorStart,
"CursorEnd": (*View).CursorEnd,
"SelectToStart": (*View).SelectToStart,
"SelectToEnd": (*View).SelectToEnd,
"SelectUp": (*View).SelectUp,
"SelectDown": (*View).SelectDown,
"SelectLeft": (*View).SelectLeft,
"SelectRight": (*View).SelectRight,
"WordRight": (*View).WordRight,
"WordLeft": (*View).WordLeft,
"SelectWordRight": (*View).SelectWordRight,
"SelectWordLeft": (*View).SelectWordLeft,
"DeleteWordRight": (*View).DeleteWordRight,
"DeleteWordLeft": (*View).DeleteWordLeft,
"SelectToStartOfLine": (*View).SelectToStartOfLine,
"SelectToEndOfLine": (*View).SelectToEndOfLine,
"InsertNewline": (*View).InsertNewline,
"InsertSpace": (*View).InsertSpace,
"Backspace": (*View).Backspace,
"Delete": (*View).Delete,
"InsertTab": (*View).InsertTab,
"Save": (*View).Save,
"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,
"IndentSelection": (*View).IndentSelection,
"OutdentSelection": (*View).OutdentSelection,
"Paste": (*View).Paste,
"SelectAll": (*View).SelectAll,
"OpenFile": (*View).OpenFile,
"Start": (*View).Start,
"End": (*View).End,
"PageUp": (*View).PageUp,
"PageDown": (*View).PageDown,
"HalfPageUp": (*View).HalfPageUp,
"HalfPageDown": (*View).HalfPageDown,
"StartOfLine": (*View).StartOfLine,
"EndOfLine": (*View).EndOfLine,
"ToggleHelp": (*View).ToggleHelp,
"ToggleRuler": (*View).ToggleRuler,
"JumpLine": (*View).JumpLine,
"ClearStatus": (*View).ClearStatus,
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"Quit": (*View).Quit,
"QuitAll": (*View).QuitAll,
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
"CursorUp": (*View).CursorUp,
"CursorDown": (*View).CursorDown,
"CursorPageUp": (*View).CursorPageUp,
"CursorPageDown": (*View).CursorPageDown,
"CursorLeft": (*View).CursorLeft,
"CursorRight": (*View).CursorRight,
"CursorStart": (*View).CursorStart,
"CursorEnd": (*View).CursorEnd,
"SelectToStart": (*View).SelectToStart,
"SelectToEnd": (*View).SelectToEnd,
"SelectUp": (*View).SelectUp,
"SelectDown": (*View).SelectDown,
"SelectLeft": (*View).SelectLeft,
"SelectRight": (*View).SelectRight,
"WordRight": (*View).WordRight,
"WordLeft": (*View).WordLeft,
"SelectWordRight": (*View).SelectWordRight,
"SelectWordLeft": (*View).SelectWordLeft,
"DeleteWordRight": (*View).DeleteWordRight,
"DeleteWordLeft": (*View).DeleteWordLeft,
"SelectToStartOfLine": (*View).SelectToStartOfLine,
"SelectToEndOfLine": (*View).SelectToEndOfLine,
"InsertNewline": (*View).InsertNewline,
"InsertSpace": (*View).InsertSpace,
"Backspace": (*View).Backspace,
"Delete": (*View).Delete,
"InsertTab": (*View).InsertTab,
"Save": (*View).Save,
"SaveAll": (*View).SaveAll,
"SaveAs": (*View).SaveAs,
"Find": (*View).Find,
"FindNext": (*View).FindNext,
"FindPrevious": (*View).FindPrevious,
"Center": (*View).Center,
"Undo": (*View).Undo,
"Redo": (*View).Redo,
"Copy": (*View).Copy,
"Cut": (*View).Cut,
"CutLine": (*View).CutLine,
"DuplicateLine": (*View).DuplicateLine,
"DeleteLine": (*View).DeleteLine,
"MoveLinesUp": (*View).MoveLinesUp,
"MoveLinesDown": (*View).MoveLinesDown,
"IndentSelection": (*View).IndentSelection,
"OutdentSelection": (*View).OutdentSelection,
"OutdentLine": (*View).OutdentLine,
"Paste": (*View).Paste,
"PastePrimary": (*View).PastePrimary,
"SelectAll": (*View).SelectAll,
"OpenFile": (*View).OpenFile,
"Start": (*View).Start,
"End": (*View).End,
"PageUp": (*View).PageUp,
"PageDown": (*View).PageDown,
"HalfPageUp": (*View).HalfPageUp,
"HalfPageDown": (*View).HalfPageDown,
"StartOfLine": (*View).StartOfLine,
"EndOfLine": (*View).EndOfLine,
"ToggleHelp": (*View).ToggleHelp,
"ToggleRuler": (*View).ToggleRuler,
"JumpLine": (*View).JumpLine,
"ClearStatus": (*View).ClearStatus,
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"Escape": (*View).Escape,
"Quit": (*View).Quit,
"QuitAll": (*View).QuitAll,
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
"Unsplit": (*View).Unsplit,
"VSplit": (*View).VSplitBinding,
"HSplit": (*View).HSplitBinding,
"ToggleMacro": (*View).ToggleMacro,
"PlayMacro": (*View).PlayMacro,
"Suspend": (*View).Suspend,
"ScrollUp": (*View).ScrollUpAction,
"ScrollDown": (*View).ScrollDownAction,
"SpawnMultiCursor": (*View).SpawnMultiCursor,
"RemoveMultiCursor": (*View).RemoveMultiCursor,
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
"SkipMultiCursor": (*View).SkipMultiCursor,
// This was changed to InsertNewline but I don't want to break backwards compatibility
"InsertEnter": (*View).InsertNewline,
}
var bindingMouse = map[string]tcell.ButtonMask{
"MouseLeft": tcell.Button1,
"MouseMiddle": tcell.Button2,
"MouseRight": tcell.Button3,
"MouseWheelUp": tcell.WheelUp,
"MouseWheelDown": tcell.WheelDown,
"MouseWheelLeft": tcell.WheelLeft,
"MouseWheelRight": tcell.WheelRight,
}
var bindingKeys = map[string]tcell.Key{
"Up": tcell.KeyUp,
"Down": tcell.KeyDown,
@@ -202,12 +237,13 @@ var bindingKeys = map[string]tcell.Key{
"CtrlRightSq": tcell.KeyCtrlRightSq,
"CtrlCarat": tcell.KeyCtrlCarat,
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
"Backspace": tcell.KeyBackspace,
"CtrlPageUp": tcell.KeyCtrlPgUp,
"CtrlPageDown": tcell.KeyCtrlPgDn,
"Tab": tcell.KeyTab,
"Esc": tcell.KeyEsc,
"Escape": tcell.KeyEscape,
"Enter": tcell.KeyEnter,
"Backspace2": tcell.KeyBackspace2,
"Backspace": tcell.KeyBackspace2,
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
"PgUp": tcell.KeyPgUp,
@@ -218,12 +254,14 @@ var bindingKeys = map[string]tcell.Key{
type Key struct {
keyCode tcell.Key
modifiers tcell.ModMask
buttons tcell.ButtonMask
r rune
}
// InitBindings initializes the keybindings for micro
func InitBindings() {
bindings = make(map[Key][]func(*View, bool) bool)
mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
var parsed map[string]string
defaults := DefaultBindings()
@@ -264,7 +302,8 @@ modSearch:
case strings.HasPrefix(k, "-"):
// We optionally support dashes between modifiers
k = k[1:]
case strings.HasPrefix(k, "Ctrl"):
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
k = k[4:]
modifiers |= tcell.ModCtrl
case strings.HasPrefix(k, "Alt"):
@@ -288,6 +327,7 @@ modSearch:
return Key{
keyCode: code,
modifiers: modifiers,
buttons: -1,
r: 0,
}, true
}
@@ -298,6 +338,16 @@ modSearch:
return Key{
keyCode: code,
modifiers: modifiers,
buttons: -1,
r: 0,
}, true
}
// See if we can find the key in bindingMouse
if code, ok := bindingMouse[k]; ok {
return Key{
modifiers: modifiers,
buttons: code,
r: 0,
}, true
}
@@ -307,12 +357,13 @@ modSearch:
return Key{
keyCode: tcell.KeyRune,
modifiers: modifiers,
buttons: -1,
r: rune(k[0]),
}, true
}
// We don't know what happened.
return Key{}, false
return Key{buttons: -1}, false
}
// findAction will find 'action' using string 'v'
@@ -326,23 +377,58 @@ func findAction(v string) (action func(*View, bool) bool) {
return action
}
func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
action, ok := mouseBindingActions[v]
if !ok {
// If the user seems to be binding a function that doesn't exist
// We hope that it's a lua function that exists and bind it to that
action = LuaFunctionMouseBinding(v)
}
return action
}
// BindKey takes a key and an action and binds the two together
func BindKey(k, v string) {
key, ok := findKey(k)
if !ok {
TermMessage("Unknown keybinding: " + k)
return
}
if v == "ToggleHelp" {
helpBinding = k
}
actionNames := strings.Split(v, ",")
actions := make([]func(*View, bool) bool, 0, len(actionNames))
for _, actionName := range actionNames {
actions = append(actions, findAction(actionName))
if helpBinding == k && v != "ToggleHelp" {
helpBinding = ""
}
bindings[key] = actions
actionNames := strings.Split(v, ",")
if actionNames[0] == "UnbindKey" {
delete(bindings, key)
delete(mouseBindings, key)
if len(actionNames) == 1 {
return
}
actionNames = append(actionNames[:0], actionNames[1:]...)
}
actions := make([]func(*View, bool) bool, 0, len(actionNames))
mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
for _, actionName := range actionNames {
if strings.HasPrefix(actionName, "Mouse") {
mouseActions = append(mouseActions, findMouseAction(actionName))
} else {
actions = append(actions, findAction(actionName))
}
}
if len(actions) > 0 {
// Can't have a binding be both mouse and normal
delete(mouseBindings, key)
bindings[key] = actions
} else if len(mouseActions) > 0 {
// Can't have a binding be both mouse and normal
delete(bindings, key)
mouseBindings[key] = mouseActions
}
}
// DefaultBindings returns a map containing micro's default keybindings
@@ -358,24 +444,27 @@ func DefaultBindings() map[string]string {
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfLine",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfLine",
"ShiftHome": "SelectToStartOfLine",
"CtrlShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Enter": "InsertNewline",
"Space": "InsertSpace",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"Backspace2": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Alt-Backspace2": "DeleteWordLeft",
"Tab": "IndentSelection,InsertTab",
"Backtab": "OutdentSelection",
"Backtab": "OutdentSelection,OutdentLine",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
@@ -390,30 +479,54 @@ func DefaultBindings() map[string]string {
"CtrlV": "Paste",
"CtrlA": "SelectAll",
"CtrlT": "AddTab",
"CtrlRightSq": "PreviousTab",
"CtrlBackslash": "NextTab",
"Alt,": "PreviousTab",
"Alt.": "NextTab",
"Home": "StartOfLine",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
"CtrlR": "ToggleRuler",
"CtrlL": "JumpLine",
"Delete": "Delete",
"Esc": "ClearStatus",
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfLine",
"Alt-e": "EndOfLine",
"Alt-p": "CursorUp",
"Alt-n": "CursorDown",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Integration with file managers
"F1": "ToggleHelp",
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
}

View File

@@ -3,15 +3,27 @@ package main
import (
"bytes"
"encoding/gob"
"io"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/mitchellh/go-homedir"
"github.com/zyedidia/micro/cmd/micro/highlight"
)
var (
// 0 - no line type detected
// 1 - lf detected
// 2 - crlf detected
fileformat = 0
)
// Buffer stores the text for files that are loaded into the text editor
@@ -23,12 +35,16 @@ type Buffer struct {
// This stores all the text in the buffer as an array of lines
*LineArray
Cursor Cursor
Cursor Cursor
cursors []*Cursor // for multiple cursors
curCursor int // the current cursor
// Path to the file on disk
Path string
// Absolute path to the file on disk
AbsPath string
// Name of the buffer on the status line
Name string
name string
// Whether or not the buffer has been modified since it was opened
IsModified bool
@@ -38,8 +54,8 @@ type Buffer struct {
NumLines int
// Syntax highlighting rules
rules []SyntaxRule
syntaxDef *highlight.Def
highlighter *highlight.Highlighter
// Buffer local settings
Settings map[string]interface{}
@@ -53,10 +69,24 @@ type SerializedBuffer struct {
ModTime time.Time
}
// NewBuffer creates a new buffer from `txt` with path and name `path`
func NewBuffer(txt []byte, path string) *Buffer {
func NewBufferFromString(text, path string) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path)
}
// NewBuffer creates a new buffer from a given reader with a given path
func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
if path != "" {
for _, tab := range tabs {
for _, view := range tab.views {
if view.Buf.Path == path {
return view.Buf
}
}
}
}
b := new(Buffer)
b.LineArray = NewLineArray(txt)
b.LineArray = NewLineArray(size, reader)
b.Settings = DefaultLocalSettings()
for k, v := range globalSettings {
@@ -65,21 +95,23 @@ func NewBuffer(txt []byte, path string) *Buffer {
}
}
b.Path = path
b.Name = path
// If the file doesn't have a path to disk then we give it no name
if path == "" {
b.Name = "No name"
if fileformat == 1 {
b.Settings["fileformat"] = "unix"
} else if fileformat == 2 {
b.Settings["fileformat"] = "dos"
}
absPath, _ := filepath.Abs(path)
b.Path = path
b.AbsPath = absPath
// The last time this file was modified
b.ModTime, _ = GetModTime(b.Path)
b.EventHandler = NewEventHandler(b)
b.Update()
b.FindFileType()
b.UpdateRules()
if _, err := os.Stat(configDir + "/buffers/"); os.IsNotExist(err) {
@@ -126,8 +158,7 @@ func NewBuffer(txt []byte, path string) *Buffer {
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
// If either savecursor or saveundo is turned on, we need to load the serialized information
// from ~/.config/micro/buffers
absPath, _ := filepath.Abs(b.Path)
file, err := os.Open(configDir + "/buffers/" + EscapePath(absPath))
file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
if err == nil {
var buffer SerializedBuffer
decoder := gob.NewDecoder(file)
@@ -153,18 +184,89 @@ func NewBuffer(txt []byte, path string) *Buffer {
file.Close()
}
if b.Settings["mouse"].(bool) {
screen.EnableMouse()
}
b.cursors = []*Cursor{&b.Cursor}
return b
}
func (b *Buffer) GetName() string {
if b.name == "" {
if b.Path == "" {
return "No name"
}
return b.Path
}
return b.name
}
// UpdateRules updates the syntax rules and filetype for this buffer
// This is called when the colorscheme changes
func (b *Buffer) UpdateRules() {
b.rules = GetRules(b)
}
rehighlight := false
var files []*highlight.File
for _, f := range ListRuntimeFiles(RTSyntax) {
data, err := f.Data()
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
} else {
file, err := highlight.ParseFile(data)
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
ftdetect, err := highlight.ParseFtDetect(file)
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
// FindFileType identifies this buffer's filetype based on the extension or header
func (b *Buffer) FindFileType() {
b.Settings["filetype"] = FindFileType(b)
ft := b.Settings["filetype"].(string)
if (ft == "Unknown" || ft == "") && !rehighlight {
if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
header := new(highlight.Header)
header.FileType = file.FileType
header.FtDetect = ftdetect
b.syntaxDef, err = highlight.ParseDef(file, header)
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
rehighlight = true
}
} else {
if file.FileType == ft && !rehighlight {
header := new(highlight.Header)
header.FileType = file.FileType
header.FtDetect = ftdetect
b.syntaxDef, err = highlight.ParseDef(file, header)
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
rehighlight = true
}
}
files = append(files, file)
}
}
if b.syntaxDef != nil {
highlight.ResolveIncludes(b.syntaxDef, files)
}
if b.highlighter == nil || rehighlight {
if b.syntaxDef != nil {
b.Settings["filetype"] = b.syntaxDef.FileType
b.highlighter = highlight.NewHighlighter(b.syntaxDef)
if b.Settings["syntax"].(bool) {
b.highlighter.HighlightStates(b)
}
}
}
}
// FileType returns the buffer's filetype
@@ -172,6 +274,14 @@ func (b *Buffer) FileType() string {
return b.Settings["filetype"].(string)
}
// IndentString returns a string representing one level of indentation
func (b *Buffer) IndentString() string {
if b.Settings["tabstospaces"].(bool) {
return Spaces(int(b.Settings["tabsize"].(float64)))
}
return "\t"
}
// CheckModTime makes sure that the file this buffer points to hasn't been updated
// by an external program since it was last read
// If it has, we ask the user if they would like to reload the file
@@ -215,6 +325,30 @@ func (b *Buffer) Update() {
b.NumLines = len(b.lines)
}
func (b *Buffer) MergeCursors() {
var cursors []*Cursor
for i := 0; i < len(b.cursors); i++ {
c1 := b.cursors[i]
if c1 != nil {
for j := 0; j < len(b.cursors); j++ {
c2 := b.cursors[j]
if c2 != nil && i != j && c1.Loc == c2.Loc {
b.cursors[j] = nil
}
}
cursors = append(cursors, c1)
}
}
b.cursors = cursors
}
func (b *Buffer) UpdateCursors() {
for i, c := range b.cursors {
c.Num = i
}
}
// Save saves the buffer to its default path
func (b *Buffer) Save() error {
return b.SaveAs(b.Path)
@@ -228,8 +362,7 @@ 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) {
absPath, _ := filepath.Abs(b.Path)
file, err := os.Create(configDir + "/buffers/" + EscapePath(absPath))
file, err := os.Create(configDir + "/buffers/" + EscapePath(b.AbsPath))
if err == nil {
enc := gob.NewEncoder(file)
gob.Register(TextEvent{})
@@ -239,7 +372,7 @@ func (b *Buffer) Serialize() error {
b.ModTime,
})
}
file.Close()
err = file.Close()
return err
}
return nil
@@ -247,43 +380,52 @@ func (b *Buffer) Serialize() error {
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
func (b *Buffer) SaveAs(filename string) error {
b.FindFileType()
b.UpdateRules()
b.Name = filename
b.Path = filename
data := []byte(b.String())
dir, _ := homedir.Dir()
if b.Settings["rmtrailingws"].(bool) {
r, _ := regexp.Compile(`[ \t]+$`)
for lineNum, line := range b.Lines(0, b.NumLines) {
indices := r.FindStringIndex(line)
if indices == nil {
continue
}
startLoc := Loc{indices[0], lineNum}
b.deleteToEnd(startLoc)
}
b.Cursor.Relocate()
}
if b.Settings["eofnewline"].(bool) {
end := b.End()
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
b.Insert(end, "\n")
}
}
str := b.SaveString(b.Settings["fileformat"] == "dos")
data := []byte(str)
err := ioutil.WriteFile(filename, data, 0644)
if err == nil {
b.Path = strings.Replace(filename, "~", dir, 1)
b.IsModified = false
b.ModTime, _ = GetModTime(filename)
return b.Serialize()
}
b.ModTime, _ = GetModTime(filename)
return err
}
// SaveAsWithSudo is the same as SaveAs except it uses a neat trick
// with tee to use sudo so the user doesn't have to reopen micro with sudo
func (b *Buffer) SaveAsWithSudo(filename string) error {
b.FindFileType()
b.UpdateRules()
b.Name = filename
b.Path = filename
// The user may have already used sudo in which case we won't need the password
// It's a bit nicer for them if they don't have to enter the password every time
_, err := RunShellCommand("sudo -v")
needPassword := err != nil
// If we need the password, we have to close the screen and ask using the shell
if needPassword {
// Shut down the screen because we're going to interact directly with the shell
screen.Fini()
screen = nil
}
// Shut down the screen because we're going to interact directly with the shell
screen.Fini()
screen = nil
// Set up everything for the command
cmd := exec.Command("sudo", "tee", filename)
cmd.Stdin = bytes.NewBufferString(b.String())
cmd.Stdin = bytes.NewBufferString(b.SaveString(b.Settings["fileformat"] == "dos"))
// This is a trap for Ctrl-C so that it doesn't kill micro
// Instead we trap Ctrl-C to kill the program we're running
@@ -297,13 +439,10 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
// Start the command
cmd.Start()
err = cmd.Wait()
err := cmd.Wait()
// If we needed the password, we closed the screen, so we have to initialize it again
if needPassword {
// Start the screen back up
InitScreen()
}
// Start the screen back up
InitScreen()
if err == nil {
b.IsModified = false
b.ModTime, _ = GetModTime(filename)
@@ -323,6 +462,11 @@ func (b *Buffer) remove(start, end Loc) string {
b.Update()
return sub
}
func (b *Buffer) deleteToEnd(start Loc) {
b.IsModified = true
b.LineArray.DeleteToEnd(start)
b.Update()
}
// Start returns the location of the first character in the buffer
func (b *Buffer) Start() Loc {
@@ -331,12 +475,28 @@ func (b *Buffer) Start() Loc {
// End returns the location of the last character in the buffer
func (b *Buffer) End() Loc {
return Loc{utf8.RuneCount(b.lines[b.NumLines-1]), b.NumLines - 1}
return Loc{utf8.RuneCount(b.lines[b.NumLines-1].data), b.NumLines - 1}
}
// RuneAt returns the rune at a given location in the buffer
func (b *Buffer) RuneAt(loc Loc) rune {
line := []rune(b.Line(loc.Y))
if len(line) > 0 {
return line[loc.X]
}
return '\n'
}
// Line returns a single line
func (b *Buffer) Line(n int) string {
return string(b.lines[n])
if n >= len(b.lines) {
return ""
}
return string(b.lines[n].data)
}
func (b *Buffer) LinesNum() int {
return len(b.lines)
}
// Lines returns an array of strings containing the lines from start to end
@@ -344,7 +504,7 @@ func (b *Buffer) Lines(start, end int) []string {
lines := b.lines[start:end]
var slice []string
for _, line := range lines {
slice = append(slice, string(line))
slice = append(slice, string(line.data))
}
return slice
}
@@ -353,3 +513,65 @@ func (b *Buffer) Lines(start, end int) []string {
func (b *Buffer) Len() int {
return Count(b.String())
}
// MoveLinesUp moves the range of lines up one row
func (b *Buffer) MoveLinesUp(start int, end int) {
// 0 < start < end <= len(b.lines)
if start < 1 || start >= end || end > len(b.lines) {
return // what to do? FIXME
}
if end == len(b.lines) {
b.Insert(
Loc{
utf8.RuneCount(b.lines[end-1].data),
end - 1,
},
"\n"+b.Line(start-1),
)
} else {
b.Insert(
Loc{0, end},
b.Line(start-1)+"\n",
)
}
b.Remove(
Loc{0, start - 1},
Loc{0, start},
)
}
// MoveLinesDown moves the range of lines down one row
func (b *Buffer) MoveLinesDown(start int, end int) {
// 0 <= start < end < len(b.lines)
// if end == len(b.lines), we can't do anything here because the
// last line is unaccessible, FIXME
if start < 0 || start >= end || end >= len(b.lines)-1 {
return // what to do? FIXME
}
b.Insert(
Loc{0, start},
b.Line(end)+"\n",
)
end++
b.Remove(
Loc{0, end},
Loc{0, end + 1},
)
}
// ClearMatches clears all of the syntax highlighting for this buffer
func (b *Buffer) ClearMatches() {
for i := range b.lines {
b.SetMatch(i, nil)
b.SetState(i, nil)
}
}
func (b *Buffer) clearCursors() {
for i := 1; i < len(b.cursors); i++ {
b.cursors[i] = nil
}
b.cursors = b.cursors[:1]
b.UpdateCursors()
b.Cursor.ResetSelection()
}

207
cmd/micro/cellview.go Normal file
View File

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

View File

@@ -2,7 +2,6 @@ package main
import (
"fmt"
"io/ioutil"
"regexp"
"strconv"
"strings"
@@ -16,66 +15,67 @@ type Colorscheme map[string]tcell.Style
// The current colorscheme
var colorscheme Colorscheme
var preInstalledColors = []string{"default", "simple", "solarized", "solarized-tc", "atom-dark-tc", "monokai", "gruvbox", "zenburn", "bubblegum"}
// This takes in a syntax group and returns the colorscheme's style for that group
func GetColor(color string) tcell.Style {
st := defStyle
if color == "" {
return st
}
groups := strings.Split(color, ".")
if len(groups) > 1 {
curGroup := ""
for i, g := range groups {
if i != 0 {
curGroup += "."
}
curGroup += g
if style, ok := colorscheme[curGroup]; ok {
st = style
}
}
} else if style, ok := colorscheme[color]; ok {
st = style
} else {
st = StringToStyle(color)
}
return st
}
// ColorschemeExists checks if a given colorscheme exists
func ColorschemeExists(colorschemeName string) bool {
files, _ := ioutil.ReadDir(configDir)
for _, f := range files {
if f.Name() == colorschemeName+".micro" {
return true
}
}
for _, name := range preInstalledColors {
if name == colorschemeName {
return true
}
}
return false
return FindRuntimeFile(RTColorscheme, colorschemeName) != nil
}
// InitColorscheme picks and initializes the colorscheme when micro starts
func InitColorscheme() {
colorscheme = make(Colorscheme)
defStyle = tcell.StyleDefault.
Foreground(tcell.ColorDefault).
Background(tcell.ColorDefault)
if screen != nil {
screen.SetStyle(defStyle)
}
LoadDefaultColorscheme()
}
// LoadDefaultColorscheme loads the default colorscheme from $(configDir)/colorschemes
func LoadDefaultColorscheme() {
LoadColorscheme(globalSettings["colorscheme"].(string), configDir+"/colorschemes")
LoadColorscheme(globalSettings["colorscheme"].(string))
}
// LoadColorscheme loads the given colorscheme from a directory
func LoadColorscheme(colorschemeName, dir string) {
files, _ := ioutil.ReadDir(dir)
found := false
for _, f := range files {
if f.Name() == colorschemeName+".micro" {
text, err := ioutil.ReadFile(dir + "/" + f.Name())
if err != nil {
fmt.Println("Error loading colorscheme:", err)
continue
}
colorscheme = ParseColorscheme(string(text))
found = true
}
}
for _, name := range preInstalledColors {
if name == colorschemeName {
data, err := Asset("runtime/colorschemes/" + name + ".micro")
if err != nil {
TermMessage("Unable to load pre-installed colorscheme " + name)
continue
}
colorscheme = ParseColorscheme(string(data))
found = true
}
}
if !found {
func LoadColorscheme(colorschemeName string) {
file := FindRuntimeFile(RTColorscheme, colorschemeName)
if file == nil {
TermMessage(colorschemeName, "is not a valid colorscheme")
} else {
if data, err := file.Data(); err != nil {
TermMessage("Error loading colorscheme:", err)
} else {
colorscheme = ParseColorscheme(string(data))
}
}
}
@@ -102,7 +102,15 @@ func ParseColorscheme(text string) Colorscheme {
link := string(matches[1])
colors := string(matches[2])
c[link] = StringToStyle(colors)
style := StringToStyle(colors)
c[link] = style
if link == "default" {
defStyle = style
}
if screen != nil {
screen.SetStyle(defStyle)
}
} else {
fmt.Println("Color-link statement is not valid:", line)
}
@@ -115,9 +123,14 @@ func ParseColorscheme(text string) Colorscheme {
// The strings must be in the format "extra foregroundcolor,backgroundcolor"
// The 'extra' can be bold, reverse, or underline
func StringToStyle(str string) tcell.Style {
var fg string
bg := "default"
split := strings.Split(str, ",")
var fg, bg string
spaceSplit := strings.Split(str, " ")
var split []string
if len(spaceSplit) > 1 {
split = strings.Split(spaceSplit[1], ",")
} else {
split = strings.Split(str, ",")
}
if len(split) > 1 {
fg, bg = split[0], split[1]
} else {
@@ -126,7 +139,19 @@ func StringToStyle(str string) tcell.Style {
fg = strings.TrimSpace(fg)
bg = strings.TrimSpace(bg)
style := defStyle.Foreground(StringToColor(fg)).Background(StringToColor(bg))
var fgColor, bgColor tcell.Color
if fg == "" {
fgColor, _, _ = defStyle.Decompose()
} else {
fgColor = StringToColor(fg)
}
if bg == "" {
_, bgColor, _ = defStyle.Decompose()
} else {
bgColor = StringToColor(bg)
}
style := defStyle.Foreground(fgColor).Background(bgColor)
if strings.Contains(str, "bold") {
style = style.Bold(true)
}

View File

@@ -2,21 +2,28 @@ package main
import (
"bytes"
"io/ioutil"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
humanize "github.com/dustin/go-humanize"
"github.com/mitchellh/go-homedir"
)
// A Command contains a action (a function to call) as well as information about how to autocomplete the command
type Command struct {
action func([]string)
completions []Completion
}
// A StrCommand is similar to a command but keeps the name of the action
type StrCommand struct {
action string
completions []Completion
@@ -24,19 +31,33 @@ type StrCommand struct {
var commands map[string]Command
var commandActions = map[string]func([]string){
"Set": Set,
"SetLocal": SetLocal,
"Show": Show,
"Run": Run,
"Bind": Bind,
"Quit": Quit,
"Save": Save,
"Replace": Replace,
"VSplit": VSplit,
"HSplit": HSplit,
"Tab": NewTab,
"Help": Help,
var commandActions map[string]func([]string)
func init() {
commandActions = map[string]func([]string){
"Set": Set,
"SetLocal": SetLocal,
"Show": Show,
"Run": Run,
"Bind": Bind,
"Quit": Quit,
"Save": Save,
"Replace": Replace,
"ReplaceAll": ReplaceAll,
"VSplit": VSplit,
"HSplit": HSplit,
"Tab": NewTab,
"Help": Help,
"Eval": Eval,
"ToggleLog": ToggleLog,
"Plugin": PluginCmd,
"Reload": Reload,
"Cd": Cd,
"Pwd": Pwd,
"Open": Open,
"TabSwitch": TabSwitch,
"MemUsage": MemUsage,
}
}
// InitCommands initializes the default commands
@@ -69,21 +90,219 @@ func MakeCommand(name, function string, completions ...Completion) {
// DefaultCommands returns a map containing micro's default commands
func DefaultCommands() map[string]StrCommand {
return map[string]StrCommand{
"set": {"Set", []Completion{OptionCompletion, NoCompletion}},
"setlocal": {"SetLocal", []Completion{OptionCompletion, NoCompletion}},
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
"bind": {"Bind", []Completion{NoCompletion}},
"run": {"Run", []Completion{NoCompletion}},
"quit": {"Quit", []Completion{NoCompletion}},
"save": {"Save", []Completion{NoCompletion}},
"replace": {"Replace", []Completion{NoCompletion}},
"vsplit": {"VSplit", []Completion{FileCompletion, NoCompletion}},
"hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}},
"tab": {"Tab", []Completion{FileCompletion, NoCompletion}},
"help": {"Help", []Completion{HelpCompletion, NoCompletion}},
"set": {"Set", []Completion{OptionCompletion, NoCompletion}},
"setlocal": {"SetLocal", []Completion{OptionCompletion, NoCompletion}},
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
"bind": {"Bind", []Completion{NoCompletion}},
"run": {"Run", []Completion{NoCompletion}},
"quit": {"Quit", []Completion{NoCompletion}},
"save": {"Save", []Completion{NoCompletion}},
"replace": {"Replace", []Completion{NoCompletion}},
"replaceall": {"ReplaceAll", []Completion{NoCompletion}},
"vsplit": {"VSplit", []Completion{FileCompletion, NoCompletion}},
"hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}},
"tab": {"Tab", []Completion{FileCompletion, NoCompletion}},
"help": {"Help", []Completion{HelpCompletion, NoCompletion}},
"eval": {"Eval", []Completion{NoCompletion}},
"log": {"ToggleLog", []Completion{NoCompletion}},
"plugin": {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
"reload": {"Reload", []Completion{NoCompletion}},
"cd": {"Cd", []Completion{FileCompletion}},
"pwd": {"Pwd", []Completion{NoCompletion}},
"open": {"Open", []Completion{FileCompletion}},
"tabswitch": {"TabSwitch", []Completion{NoCompletion}},
"memusage": {"MemUsage", []Completion{NoCompletion}},
}
}
// PluginCmd installs, removes, updates, lists, or searches for given plugins
func PluginCmd(args []string) {
if len(args) >= 1 {
switch args[0] {
case "install":
installedVersions := GetInstalledVersions(false)
for _, plugin := range args[1:] {
pp := GetAllPluginPackages().Get(plugin)
if pp == nil {
messenger.Error("Unknown plugin \"" + plugin + "\"")
} else if err := pp.IsInstallable(); err != nil {
messenger.Error("Error installing ", plugin, ": ", err)
} else {
for _, installed := range installedVersions {
if pp.Name == installed.pack.Name {
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
messenger.Error(pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
} else {
messenger.Error(pp.Name, " is already installed")
}
}
}
pp.Install()
}
}
case "remove":
removed := ""
for _, plugin := range args[1:] {
// check if the plugin exists.
if _, ok := loadedPlugins[plugin]; ok {
UninstallPlugin(plugin)
removed += plugin + " "
continue
}
}
if !IsSpaces(removed) {
messenger.Message("Removed ", removed)
} else {
messenger.Error("The requested plugins do not exist")
}
case "update":
UpdatePlugins(args[1:])
case "list":
plugins := GetInstalledVersions(false)
messenger.AddLog("----------------")
messenger.AddLog("The following plugins are currently installed:\n")
for _, p := range plugins {
messenger.AddLog(fmt.Sprintf("%s (%s)", p.pack.Name, p.Version))
}
messenger.AddLog("----------------")
if len(plugins) > 0 {
if CurView().Type != vtLog {
ToggleLog([]string{})
}
}
case "search":
plugins := SearchPlugin(args[1:])
messenger.Message(len(plugins), " plugins found")
for _, p := range plugins {
messenger.AddLog("----------------")
messenger.AddLog(p.String())
}
messenger.AddLog("----------------")
if len(plugins) > 0 {
if CurView().Type != vtLog {
ToggleLog([]string{})
}
}
case "available":
packages := GetAllPluginPackages()
messenger.AddLog("Available Plugins:")
for _, pkg := range packages {
messenger.AddLog(pkg.Name)
}
if CurView().Type != vtLog {
ToggleLog([]string{})
}
}
} else {
messenger.Error("Not enough arguments")
}
}
// TabSwitch switches to a given tab either by name or by number
func TabSwitch(args []string) {
if len(args) > 0 {
num, err := strconv.Atoi(args[0])
if err != nil {
// Check for tab with this name
found := false
for _, t := range tabs {
v := t.views[t.CurView]
if v.Buf.GetName() == args[0] {
curTab = v.TabNum
found = true
}
}
if !found {
messenger.Error("Could not find tab: ", err)
}
} else {
num--
if num >= 0 && num < len(tabs) {
curTab = num
} else {
messenger.Error("Invalid tab index")
}
}
}
}
// Cd changes the current working directory
func Cd(args []string) {
if len(args) > 0 {
home, _ := homedir.Dir()
path := strings.Replace(args[0], "~", home, 1)
os.Chdir(path)
for _, tab := range tabs {
for _, view := range tab.views {
wd, _ := os.Getwd()
view.Buf.Path, _ = MakeRelative(view.Buf.AbsPath, wd)
if p, _ := filepath.Abs(view.Buf.Path); !strings.Contains(p, wd) {
view.Buf.Path = view.Buf.AbsPath
}
}
}
}
}
// MemUsage prints micro's memory usage
// Alloc shows how many bytes are currently in use
// Sys shows how many bytes have been requested from the operating system
// NumGC shows how many times the GC has been run
// Note that Go commonly reserves more memory from the OS than is currently in-use/required
// Additionally, even if Go returns memory to the OS, the OS does not always claim it because
// there may be plenty of memory to spare
func MemUsage(args []string) {
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
messenger.Message(fmt.Sprintf("Alloc: %v, Sys: %v, NumGC: %v", humanize.Bytes(mem.Alloc), humanize.Bytes(mem.Sys), mem.NumGC))
}
// Pwd prints the current working directory
func Pwd(args []string) {
wd, err := os.Getwd()
if err != nil {
messenger.Message(err.Error())
} else {
messenger.Message(wd)
}
}
// Open opens a new buffer with a given filename
func Open(args []string) {
if len(args) > 0 {
filename := args[0]
// the filename might or might not be quoted, so unquote first then join the strings.
filename = strings.Join(SplitCommandArgs(filename), " ")
CurView().Open(filename)
} else {
messenger.Error("No filename")
}
}
// ToggleLog toggles the log view
func ToggleLog(args []string) {
buffer := messenger.getBuffer()
if CurView().Type != vtLog {
CurView().HSplit(buffer)
CurView().Type = vtLog
RedrawAll()
buffer.Cursor.Loc = buffer.Start()
CurView().Relocate()
buffer.Cursor.Loc = buffer.End()
CurView().Relocate()
} else {
CurView().Quit(true)
}
}
// Reload reloads all files (syntax files, colorschemes...)
func Reload(args []string) {
LoadAll()
}
// Help tries to open the given help page in a horizontal split
func Help(args []string) {
if len(args) < 1 {
@@ -91,7 +310,7 @@ func Help(args []string) {
CurView().openHelp("help")
} else {
helpPage := args[0]
if _, ok := helpPages[helpPage]; ok {
if FindRuntimeFile(RTHelp, helpPage) != nil {
CurView().openHelp(helpPage)
} else {
messenger.Error("Sorry, no help for ", helpPage)
@@ -103,19 +322,27 @@ func Help(args []string) {
// If no file is given, it opens an empty buffer in a new split
func VSplit(args []string) {
if len(args) == 0 {
CurView().VSplit(NewBuffer([]byte{}, ""))
CurView().VSplit(NewBufferFromString("", ""))
} else {
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := ioutil.ReadFile(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
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBuffer([]byte{}, filename)
buf = NewBufferFromString("", filename)
} else {
buf = NewBuffer(file, filename)
buf = NewBuffer(file, FSize(file), filename)
}
CurView().VSplit(buf)
}
@@ -125,24 +352,44 @@ func VSplit(args []string) {
// If no file is given, it opens an empty buffer in a new split
func HSplit(args []string) {
if len(args) == 0 {
CurView().HSplit(NewBuffer([]byte{}, ""))
CurView().HSplit(NewBufferFromString("", ""))
} else {
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := ioutil.ReadFile(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
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBuffer([]byte{}, filename)
buf = NewBufferFromString("", filename)
} else {
buf = NewBuffer(file, filename)
buf = NewBuffer(file, FSize(file), filename)
}
CurView().HSplit(buf)
}
}
// Eval evaluates a lua expression
func Eval(args []string) {
if len(args) >= 1 {
err := L.DoString(args[0])
if err != nil {
messenger.Error(err)
}
} else {
messenger.Error("Not enough arguments")
}
}
// NewTab opens the given file in a new tab
func NewTab(args []string) {
if len(args) == 0 {
@@ -151,12 +398,27 @@ func NewTab(args []string) {
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, _ := ioutil.ReadFile(filename)
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
tab := NewTabFromView(NewView(NewBuffer(file, filename)))
if err == nil && fileInfo.IsDir() {
messenger.Error(filename, " is a directory")
return
}
defer file.Close()
var buf *Buffer
if err != nil {
buf = NewBufferFromString("", filename)
} else {
buf = NewBuffer(file, FSize(file), filename)
}
tab := NewTabFromView(NewView(buf))
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
curTab++
curTab = len(tabs) - 1
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.views {
@@ -174,8 +436,8 @@ func Set(args []string) {
return
}
option := strings.TrimSpace(args[0])
value := strings.TrimSpace(args[1])
option := args[0]
value := args[1]
SetOptionAndSettings(option, value)
}
@@ -187,8 +449,8 @@ func SetLocal(args []string) {
return
}
option := strings.TrimSpace(args[0])
value := strings.TrimSpace(args[1])
option := args[0]
value := args[1]
err := SetLocalOption(option, value, CurView())
if err != nil {
@@ -225,7 +487,7 @@ func Bind(args []string) {
// Run runs a shell command in the background
func Run(args []string) {
// Run a shell command in the background (openTerm is false)
HandleShellCommand(strings.Join(args, " "), false)
HandleShellCommand(JoinCommandArgs(args...), false, true)
}
// Quit closes the main view
@@ -236,48 +498,37 @@ func Quit(args []string) {
// Save saves the buffer in the main view
func Save(args []string) {
// Save the main view
CurView().Save(true)
if len(args) == 0 {
// Save the main view
CurView().Save(true)
} else {
CurView().Buf.SaveAs(args[0])
}
}
// Replace runs search and replace
func Replace(args []string) {
// This is a regex to parse the replace expression
// We allow no quotes if there are no spaces, but if you want to search
// for or replace an expression with spaces, you can add double quotes
r := regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"|[^\s]*`)
replaceCmd := r.FindAllString(strings.Join(args, " "), -1)
if len(replaceCmd) < 2 {
if len(args) < 2 || len(args) > 3 {
// We need to find both a search and replace expression
messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
return
}
var flags string
if len(replaceCmd) == 3 {
// The user included some flags
flags = replaceCmd[2]
allAtOnce := false
if len(args) == 3 {
// user added -a flag
if args[2] == "-a" {
allAtOnce = true
} else {
messenger.Error("Invalid replace flag: " + args[2])
return
}
}
search := string(replaceCmd[0])
replace := string(replaceCmd[1])
search := string(args[0])
replace := string(args[1])
// If the search and replace expressions have quotes, we need to remove those
if strings.HasPrefix(search, `"`) && strings.HasSuffix(search, `"`) {
search = search[1 : len(search)-1]
}
if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
replace = replace[1 : len(replace)-1]
}
// We replace all escaped double quotes to real double quotes
search = strings.Replace(search, `\"`, `"`, -1)
replace = strings.Replace(replace, `\"`, `"`, -1)
// Replace some things so users can actually insert newlines and tabs in replacements
replace = strings.Replace(replace, "\\n", "\n", -1)
replace = strings.Replace(replace, "\\t", "\t", -1)
regex, err := regexp.Compile(search)
regex, err := regexp.Compile("(?m)" + search)
if err != nil {
// There was an error with the user's regex
messenger.Error(err.Error())
@@ -287,7 +538,32 @@ func Replace(args []string) {
view := CurView()
found := 0
if strings.Contains(flags, "c") {
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)
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}
xOffset += deltaXOffset
deltas = append(deltas, Delta{replace, from, to})
found++
}
}
}
view.Buf.MultipleReplace(deltas)
}
if allAtOnce {
replaceAll()
} else {
for {
// The 'check' flag was used
Search(search, view, true)
@@ -295,11 +571,8 @@ func Replace(args []string) {
break
}
view.Relocate()
if view.Buf.Settings["syntax"].(bool) {
view.matches = Match(view)
}
RedrawAll()
choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
choice, canceled := messenger.LetterPrompt("Perform replacement? (y,n,a)", 'y', 'n', 'a')
if canceled {
if view.Cursor.HasSelection() {
view.Cursor.Loc = view.Cursor.CurSelection[0]
@@ -307,31 +580,27 @@ func Replace(args []string) {
}
messenger.Reset()
break
}
if choice {
} else if choice == 'a' {
if view.Cursor.HasSelection() {
view.Cursor.Loc = view.Cursor.CurSelection[0]
view.Cursor.ResetSelection()
}
messenger.Reset()
replaceAll()
break
} else if choice == 'y' {
view.Cursor.DeleteSelection()
view.Buf.Insert(view.Cursor.Loc, replace)
view.Cursor.ResetSelection()
messenger.Reset()
found++
}
if view.Cursor.HasSelection() {
searchStart = view.Cursor.CurSelection[1]
} else {
if view.Cursor.HasSelection() {
searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf)
} else {
searchStart = ToCharPos(view.Cursor.Loc, view.Buf)
}
continue
searchStart = view.Cursor.Loc
}
}
} else {
for {
match := regex.FindStringIndex(view.Buf.String())
if match == nil {
break
}
found++
view.Buf.Replace(FromCharPos(match[0], view.Buf), FromCharPos(match[1], view.Buf), replace)
}
}
view.Cursor.Relocate()
@@ -344,10 +613,16 @@ func Replace(args []string) {
}
}
// ReplaceAll replaces search term all at once
func ReplaceAll(args []string) {
// aliased to Replace command
Replace(append(args, "-a"))
}
// RunShellCommand executes a shell command and returns the output/error
func RunShellCommand(input string) (string, error) {
inputCmd := strings.Split(input, " ")[0]
args := strings.Split(input, " ")[1:]
inputCmd := SplitCommandArgs(input)[0]
args := SplitCommandArgs(input)[1:]
cmd := exec.Command(inputCmd, args...)
outputBytes := &bytes.Buffer{}
@@ -362,8 +637,8 @@ func RunShellCommand(input string) (string, error) {
// 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) {
inputCmd := strings.Split(input, " ")[0]
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
inputCmd := SplitCommandArgs(input)[0]
if !openTerm {
// Simply run the command in the background and notify the user when it's done
messenger.Message("Running...")
@@ -388,12 +663,13 @@ func HandleShellCommand(input string, openTerm bool) {
screen.Fini()
screen = nil
args := strings.Split(input, " ")[1:]
args := SplitCommandArgs(input)[1:]
// Set up everything for the command
var outputBuf bytes.Buffer
cmd := exec.Command(inputCmd, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stdout = io.MultiWriter(os.Stdout, &outputBuf)
cmd.Stderr = os.Stderr
// This is a trap for Ctrl-C so that it doesn't kill micro
@@ -406,26 +682,35 @@ func HandleShellCommand(input string, openTerm bool) {
}
}()
// Start the command
cmd.Start()
cmd.Wait()
err := cmd.Wait()
// This is just so we don't return right away and let the user press enter to return
TermMessage("")
output := outputBuf.String()
if err != nil {
output = err.Error()
}
if waitToFinish {
// This is just so we don't return right away and let the user press enter to return
TermMessage("")
}
// Start the screen back up
InitScreen()
return output
}
return ""
}
// HandleCommand handles input from the user
func HandleCommand(input string) {
inputCmd := strings.Split(input, " ")[0]
args := strings.Split(input, " ")[1:]
args := SplitCommandArgs(input)
inputCmd := args[0]
if _, ok := commands[inputCmd]; !ok {
messenger.Error("Unknown command ", inputCmd)
} else {
commands[inputCmd].action(args)
commands[inputCmd].action(args[1:])
}
}

View File

@@ -1,5 +1,9 @@
package main
import (
"github.com/zyedidia/clipboard"
)
// The Cursor struct stores the location of the cursor in the view
// The complicated part about the cursor is storing its location.
// The cursor must be displayed at an x, y location, but since the buffer
@@ -19,6 +23,9 @@ type Cursor struct {
// This is used for line and word selection where it is necessary
// to know what the original selection was
OrigSelection [2]Loc
// Which cursor index is this (for multiple cursors)
Num int
}
// Goto puts the cursor at the given cursor's location and gives the current cursor its selection too
@@ -27,12 +34,31 @@ func (c *Cursor) Goto(b Cursor) {
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
}
// CopySelection copies the user's selection to either "primary" or "clipboard"
func (c *Cursor) CopySelection(target string) {
if c.HasSelection() {
if target != "primary" || c.buf.Settings["useprimary"].(bool) {
clipboard.WriteAll(c.GetSelection(), target)
}
}
}
// ResetSelection resets the user's selection
func (c *Cursor) ResetSelection() {
c.CurSelection[0] = c.buf.Start()
c.CurSelection[1] = c.buf.Start()
}
// SetSelectionStart sets the start of the selection
func (c *Cursor) SetSelectionStart(pos Loc) {
c.CurSelection[0] = pos
}
// SetSelectionEnd sets the end of the selection
func (c *Cursor) SetSelectionEnd(pos Loc) {
c.CurSelection[1] = pos
}
// HasSelection returns whether or not the user has selected anything
func (c *Cursor) HasSelection() bool {
return c.CurSelection[0] != c.CurSelection[1]
@@ -43,7 +69,7 @@ func (c *Cursor) DeleteSelection() {
if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
c.buf.Remove(c.CurSelection[1], c.CurSelection[0])
c.Loc = c.CurSelection[1]
} else if c.GetSelection() == "" {
} else if !c.HasSelection() {
return
} else {
c.buf.Remove(c.CurSelection[0], c.CurSelection[1])
@@ -53,21 +79,24 @@ func (c *Cursor) DeleteSelection() {
// GetSelection returns the cursor's selection
func (c *Cursor) GetSelection() string {
if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
return c.buf.Substr(c.CurSelection[1], c.CurSelection[0])
if InBounds(c.CurSelection[0], c.buf) && InBounds(c.CurSelection[1], c.buf) {
if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
return c.buf.Substr(c.CurSelection[1], c.CurSelection[0])
}
return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
}
return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
return ""
}
// SelectLine selects the current line
func (c *Cursor) SelectLine() {
c.Start()
c.CurSelection[0] = c.Loc
c.SetSelectionStart(c.Loc)
c.End()
if c.buf.NumLines-1 > c.Y {
c.CurSelection[1] = c.Loc.Move(1, c.buf)
c.SetSelectionEnd(c.Loc.Move(1, c.buf))
} else {
c.CurSelection[1] = c.Loc
c.SetSelectionEnd(c.Loc)
}
c.OrigSelection = c.CurSelection
@@ -77,13 +106,13 @@ func (c *Cursor) SelectLine() {
func (c *Cursor) AddLineToSelection() {
if c.Loc.LessThan(c.OrigSelection[0]) {
c.Start()
c.CurSelection[0] = c.Loc
c.CurSelection[1] = c.OrigSelection[1]
c.SetSelectionStart(c.Loc)
c.SetSelectionEnd(c.OrigSelection[1])
}
if c.Loc.GreaterThan(c.OrigSelection[1]) {
c.End()
c.CurSelection[1] = c.Loc.Move(1, c.buf)
c.CurSelection[0] = c.OrigSelection[0]
c.SetSelectionEnd(c.Loc.Move(1, c.buf))
c.SetSelectionStart(c.OrigSelection[0])
}
if c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) {
@@ -98,8 +127,8 @@ func (c *Cursor) SelectWord() {
}
if !IsWordChar(string(c.RuneUnder(c.X))) {
c.CurSelection[0] = c.Loc
c.CurSelection[1] = c.Loc.Move(1, c.buf)
c.SetSelectionStart(c.Loc)
c.SetSelectionEnd(c.Loc.Move(1, c.buf))
c.OrigSelection = c.CurSelection
return
}
@@ -110,14 +139,14 @@ func (c *Cursor) SelectWord() {
backward--
}
c.CurSelection[0] = Loc{backward, c.Y}
c.SetSelectionStart(Loc{backward, c.Y})
c.OrigSelection[0] = c.CurSelection[0]
for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
forward++
}
c.CurSelection[1] = Loc{forward, c.Y}.Move(1, c.buf)
c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
c.OrigSelection[1] = c.CurSelection[1]
c.Loc = c.CurSelection[1]
}
@@ -136,8 +165,8 @@ func (c *Cursor) AddWordToSelection() {
backward--
}
c.CurSelection[0] = Loc{backward, c.Y}
c.CurSelection[1] = c.OrigSelection[1]
c.SetSelectionStart(Loc{backward, c.Y})
c.SetSelectionEnd(c.OrigSelection[1])
}
if c.Loc.GreaterThan(c.OrigSelection[1]) {
@@ -147,8 +176,8 @@ func (c *Cursor) AddWordToSelection() {
forward++
}
c.CurSelection[1] = Loc{forward, c.Y}.Move(1, c.buf)
c.CurSelection[0] = c.OrigSelection[0]
c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
c.SetSelectionStart(c.OrigSelection[0])
}
c.Loc = c.CurSelection[1]
@@ -157,11 +186,11 @@ func (c *Cursor) AddWordToSelection() {
// SelectTo selects from the current cursor location to the given location
func (c *Cursor) SelectTo(loc Loc) {
if loc.GreaterThan(c.OrigSelection[0]) {
c.CurSelection[0] = c.OrigSelection[0]
c.CurSelection[1] = loc
c.SetSelectionStart(c.OrigSelection[0])
c.SetSelectionEnd(loc)
} else {
c.CurSelection[0] = loc
c.CurSelection[1] = c.OrigSelection[0]
c.SetSelectionStart(loc)
c.SetSelectionEnd(c.OrigSelection[0])
}
}
@@ -221,19 +250,19 @@ func (c *Cursor) UpN(amount int) {
proposedY := c.Y - amount
if proposedY < 0 {
proposedY = 0
c.LastVisualX = 0
} else if proposedY >= c.buf.NumLines {
proposedY = c.buf.NumLines - 1
}
if proposedY == c.Y {
return
}
c.Y = proposedY
runes := []rune(c.buf.Line(c.Y))
c.X = c.GetCharPosInLine(c.Y, c.LastVisualX)
c.X = c.GetCharPosInLine(proposedY, c.LastVisualX)
if c.X > len(runes) {
c.X = len(runes)
}
c.Y = proposedY
}
// DownN moves the cursor down N lines (if possible)
@@ -310,9 +339,22 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
func (c *Cursor) GetVisualX() int {
runes := []rune(c.buf.Line(c.Y))
tabSize := int(c.buf.Settings["tabsize"].(float64))
if c.X > len(runes) {
c.X = len(runes) - 1
}
if c.X < 0 {
c.X = 0
}
return StringWidth(string(runes[:c.X]), tabSize)
}
// StoreVisualX stores the current visual x value in the cursor
func (c *Cursor) StoreVisualX() {
c.LastVisualX = c.GetVisualX()
}
// Relocate makes sure that the cursor is inside the bounds of the buffer
// If it isn't, it moves it to be within the buffer's lines
func (c *Cursor) Relocate() {

View File

@@ -1,18 +1,22 @@
package main
import (
"strings"
"time"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/yuin/gopher-lua"
)
const (
// Opposite and undoing events must have opposite values
// TextEventInsert repreasents an insertion event
// TextEventInsert represents an insertion event
TextEventInsert = 1
// TextEventRemove represents a deletion event
TextEventRemove = -1
// TextEventReplace represents a replace event
TextEventReplace = 0
)
// TextEvent holds data for a manipulation on some text that can be undone
@@ -20,18 +24,36 @@ type TextEvent struct {
C Cursor
EventType int
Text string
Start Loc
End Loc
Deltas []Delta
Time time.Time
}
type Delta struct {
Text string
Start Loc
End Loc
}
// ExecuteTextEvent runs a text event
func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
if t.EventType == TextEventInsert {
buf.insert(t.Start, []byte(t.Text))
for _, d := range t.Deltas {
buf.insert(d.Start, []byte(d.Text))
}
} else if t.EventType == TextEventRemove {
t.Text = buf.remove(t.Start, t.End)
for i, d := range t.Deltas {
t.Deltas[i].Text = buf.remove(d.Start, d.End)
}
} else if t.EventType == TextEventReplace {
for i, d := range t.Deltas {
t.Deltas[i].Text = buf.remove(d.Start, d.End)
buf.insert(d.Start, []byte(d.Text))
t.Deltas[i].Start = d.Start
t.Deltas[i].End = Loc{d.Start.X + Count(d.Text), d.Start.Y}
}
for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
}
}
}
@@ -80,23 +102,67 @@ func (eh *EventHandler) ApplyDiff(new string) {
// Insert creates an insert text event and executes it
func (eh *EventHandler) Insert(start Loc, text string) {
e := &TextEvent{
C: eh.buf.Cursor,
C: *eh.buf.cursors[eh.buf.curCursor],
EventType: TextEventInsert,
Text: text,
Start: start,
Deltas: []Delta{{text, start, Loc{0, 0}}},
Time: time.Now(),
}
eh.Execute(e)
e.End = start.Move(Count(text), eh.buf)
e.Deltas[0].End = start.Move(Count(text), eh.buf)
end := e.Deltas[0].End
for _, c := range eh.buf.cursors {
move := func(loc Loc) Loc {
if start.Y != end.Y && loc.GreaterThan(start) {
loc.Y += end.Y - start.Y
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
loc = loc.Move(Count(text), eh.buf)
}
return loc
}
c.Loc = move(c.Loc)
c.CurSelection[0] = move(c.CurSelection[0])
c.CurSelection[1] = move(c.CurSelection[1])
c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1])
c.LastVisualX = c.GetVisualX()
}
}
// Remove creates a remove text event and executes it
func (eh *EventHandler) Remove(start, end Loc) {
e := &TextEvent{
C: eh.buf.Cursor,
C: *eh.buf.cursors[eh.buf.curCursor],
EventType: TextEventRemove,
Start: start,
End: end,
Deltas: []Delta{{"", start, end}},
Time: time.Now(),
}
eh.Execute(e)
for _, c := range eh.buf.cursors {
move := func(loc Loc) Loc {
if start.Y != end.Y && loc.GreaterThan(end) {
loc.Y -= end.Y - start.Y
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
loc = loc.Move(-Diff(start, end, eh.buf), eh.buf)
}
return loc
}
c.Loc = move(c.Loc)
c.CurSelection[0] = move(c.CurSelection[0])
c.CurSelection[1] = move(c.CurSelection[1])
c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1])
c.LastVisualX = c.GetVisualX()
}
}
// MultipleReplace creates an multiple insertions executes them
func (eh *EventHandler) MultipleReplace(deltas []Delta) {
e := &TextEvent{
C: *eh.buf.cursors[eh.buf.curCursor],
EventType: TextEventReplace,
Deltas: deltas,
Time: time.Now(),
}
eh.Execute(e)
@@ -114,6 +180,17 @@ func (eh *EventHandler) Execute(t *TextEvent) {
eh.RedoStack = new(Stack)
}
eh.UndoStack.Push(t)
for pl := range loadedPlugins {
ret, err := Call(pl+".onBeforeTextEvent", t)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
}
if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
return
}
}
ExecuteTextEvent(t, eh.buf)
}
@@ -158,8 +235,12 @@ func (eh *EventHandler) UndoOneEvent() {
// Set the cursor in the right place
teCursor := t.C
t.C = eh.buf.Cursor
eh.buf.Cursor.Goto(teCursor)
if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
t.C = *eh.buf.cursors[teCursor.Num]
eh.buf.cursors[teCursor.Num].Goto(teCursor)
} else {
teCursor.Num = -1
}
// Push it to the redo stack
eh.RedoStack.Push(t)
@@ -201,8 +282,12 @@ func (eh *EventHandler) RedoOneEvent() {
UndoTextEvent(t, eh.buf)
teCursor := t.C
t.C = eh.buf.Cursor
eh.buf.Cursor.Goto(teCursor)
if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
t.C = *eh.buf.cursors[teCursor.Num]
eh.buf.cursors[teCursor.Num].Goto(teCursor)
} else {
teCursor.Num = -1
}
eh.UndoStack.Push(t)
}

View File

@@ -1,25 +0,0 @@
package main
var helpPages map[string]string
var helpFiles = []string{
"help",
"keybindings",
"plugins",
"colors",
"options",
"commands",
"tutorial",
}
// LoadHelp loads the help text from inside the binary
func LoadHelp() {
helpPages = make(map[string]string)
for _, file := range helpFiles {
data, err := Asset("runtime/help/" + file + ".md")
if err != nil {
TermMessage("Unable to load help text", file)
}
helpPages[file] = string(data)
}
}

View File

@@ -0,0 +1,18 @@
package highlight
import "regexp"
// DetectFiletype will use the list of syntax definitions provided and the filename and first line of the file
// to determine the filetype of the file
// It will return the corresponding syntax definition for the filetype
func MatchFiletype(ftdetect [2]*regexp.Regexp, filename string, firstLine []byte) bool {
if ftdetect[0].MatchString(filename) {
return true
}
if ftdetect[1] != nil {
return ftdetect[1].Match(firstLine)
}
return false
}

View File

@@ -0,0 +1,374 @@
package highlight
import (
"regexp"
"strings"
"unicode/utf8"
)
// 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 {
if p < 0 {
return 0
}
if p >= len(str) {
return utf8.RuneCountInString(str)
}
return utf8.RuneCountInString(str[:p])
}
func combineLineMatch(src, dst LineMatch) LineMatch {
for k, v := range src {
if g, ok := dst[k]; ok {
if g == 0 {
dst[k] = v
}
} else {
dst[k] = v
}
}
return dst
}
// A State represents the region at the end of a line
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
LinesNum() int
State(lineN int) State
SetState(lineN int, s State)
SetMatch(lineN int, m LineMatch)
}
// A Highlighter contains the information needed to highlight a string
type Highlighter struct {
lastRegion *region
Def *Def
}
// NewHighlighter returns a new highlighter from the given syntax definition
func NewHighlighter(def *Def) *Highlighter {
h := new(Highlighter)
h.Def = def
return h
}
// LineMatch represents the syntax highlighting matches for one line. Each index where the coloring is changed is marked with that
// 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 {
regexStr := regex.String()
if strings.Contains(regexStr, "^") {
if !canMatchStart {
return nil
}
}
if strings.Contains(regexStr, "$") {
if !canMatchEnd {
return nil
}
}
var strbytes []byte
if skip != nil {
strbytes = skip.ReplaceAllFunc([]byte(string(str)), func(match []byte) []byte {
res := make([]byte, utf8.RuneCount(match))
return res
})
} else {
strbytes = []byte(string(str))
}
match := regex.FindIndex(strbytes)
if match == nil {
return nil
}
// return []int{match.Index, match.Index + match.Length}
return []int{runePos(match[0], string(str)), runePos(match[1], string(str))}
}
func findAllIndex(regex *regexp.Regexp, str []rune, canMatchStart, canMatchEnd bool) [][]int {
regexStr := regex.String()
if strings.Contains(regexStr, "^") {
if !canMatchStart {
return nil
}
}
if strings.Contains(regexStr, "$") {
if !canMatchEnd {
return nil
}
}
matches := regex.FindAllIndex([]byte(string(str)), -1)
for i, m := range matches {
matches[i][0] = runePos(m[0], string(str))
matches[i][1] = runePos(m[1], string(str))
}
return matches
}
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []rune, curRegion *region, statesOnly bool) LineMatch {
// highlights := make(LineMatch)
if start == 0 {
if !statesOnly {
if _, ok := highlights[0]; !ok {
highlights[0] = curRegion.group
}
}
}
loc := findIndex(curRegion.end, curRegion.skip, line, start == 0, canMatchEnd)
if loc != nil {
if !statesOnly {
highlights[start+loc[0]] = curRegion.limitGroup
}
if curRegion.parent == nil {
if !statesOnly {
highlights[start+loc[1]] = 0
h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], curRegion, statesOnly)
}
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, line[loc[1]:], statesOnly)
return highlights
}
if !statesOnly {
highlights[start+loc[1]] = curRegion.parent.group
h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], curRegion, statesOnly)
}
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, line[loc[1]:], curRegion.parent, statesOnly)
return highlights
}
if len(line) == 0 || statesOnly {
if canMatchEnd {
h.lastRegion = curRegion
}
return highlights
}
firstLoc := []int{len(line), 0}
var firstRegion *region
for _, r := range curRegion.rules.regions {
loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
if loc != nil {
if loc[0] < firstLoc[0] {
firstLoc = loc
firstRegion = r
}
}
}
if firstLoc[0] != len(line) {
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)
return highlights
}
fullHighlights := make([]Group, len([]rune(string(line))))
for i := 0; i < len(fullHighlights); i++ {
fullHighlights[i] = curRegion.group
}
for _, p := range curRegion.rules.patterns {
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
for _, m := range matches {
for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
}
}
}
for i, h := range fullHighlights {
if i == 0 || h != fullHighlights[i-1] {
// if _, ok := highlights[start+i]; !ok {
highlights[start+i] = h
// }
}
}
if canMatchEnd {
h.lastRegion = curRegion
}
return highlights
}
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []rune, statesOnly bool) LineMatch {
if len(line) == 0 {
if canMatchEnd {
h.lastRegion = nil
}
return highlights
}
firstLoc := []int{len(line), 0}
var firstRegion *region
for _, r := range h.Def.rules.regions {
loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
if loc != nil {
if loc[0] < firstLoc[0] {
firstLoc = loc
firstRegion = r
}
}
}
if firstLoc[0] != len(line) {
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)
return highlights
}
if statesOnly {
if canMatchEnd {
h.lastRegion = nil
}
return highlights
}
fullHighlights := make([]Group, len(line))
for _, p := range h.Def.rules.patterns {
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
for _, m := range matches {
for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
}
}
}
for i, h := range fullHighlights {
if i == 0 || h != fullHighlights[i-1] {
// if _, ok := highlights[start+i]; !ok {
highlights[start+i] = h
// }
}
}
if canMatchEnd {
h.lastRegion = nil
}
return highlights
}
// HighlightString syntax highlights a string
// Use this function for simple syntax highlighting and use the other functions for
// more advanced syntax highlighting. They are optimized for quick rehighlighting of the same
// text with minor changes made
func (h *Highlighter) HighlightString(input string) []LineMatch {
lines := strings.Split(input, "\n")
var lineMatches []LineMatch
for i := 0; i < len(lines); i++ {
line := []rune(lines[i])
highlights := make(LineMatch)
if i == 0 || h.lastRegion == nil {
lineMatches = append(lineMatches, h.highlightEmptyRegion(highlights, 0, true, i, line, false))
} else {
lineMatches = append(lineMatches, h.highlightRegion(highlights, 0, true, i, line, h.lastRegion, false))
}
}
return lineMatches
}
// 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))
// highlights := make(LineMatch)
if i == 0 || h.lastRegion == nil {
h.highlightEmptyRegion(nil, 0, true, i, line, true)
} else {
h.highlightRegion(nil, 0, true, i, line, h.lastRegion, true)
}
curState := h.lastRegion
input.SetState(i, curState)
}
}
// HighlightMatches sets the matches for each line in between startline and endline
// It sets all other matches in the buffer to nil to conserve memory
// This assumes that all the states are set correctly
func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
for i := startline; i < endline; i++ {
if i >= input.LinesNum() {
break
}
line := []rune(input.Line(i))
highlights := make(LineMatch)
var match LineMatch
if i == 0 || input.State(i-1) == nil {
match = h.highlightEmptyRegion(highlights, 0, true, i, line, false)
} else {
match = h.highlightRegion(highlights, 0, true, i, line, input.State(i-1), false)
}
input.SetMatch(i, match)
}
}
// ReHighlightStates will scan down from `startline` and set the appropriate end of line state
// for each line until it comes across the same state in two consecutive lines
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
// lines := input.LineData()
h.lastRegion = nil
if startline > 0 {
h.lastRegion = input.State(startline - 1)
}
for i := startline; i < input.LinesNum(); i++ {
line := []rune(input.Line(i))
// highlights := make(LineMatch)
// var match LineMatch
if i == 0 || h.lastRegion == nil {
h.highlightEmptyRegion(nil, 0, true, i, line, true)
} else {
h.highlightRegion(nil, 0, true, i, line, h.lastRegion, true)
}
curState := h.lastRegion
lastState := input.State(i)
input.SetState(i, curState)
if curState == lastState {
break
}
}
}
// ReHighlightLine will rehighlight the state and match for a single line
func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
line := []rune(input.Line(lineN))
highlights := make(LineMatch)
h.lastRegion = nil
if lineN > 0 {
h.lastRegion = input.State(lineN - 1)
}
var match LineMatch
if lineN == 0 || h.lastRegion == nil {
match = h.highlightEmptyRegion(highlights, 0, true, lineN, line, false)
} else {
match = h.highlightRegion(highlights, 0, true, lineN, line, h.lastRegion, false)
}
curState := h.lastRegion
input.SetMatch(lineN, match)
input.SetState(lineN, curState)
}

View File

@@ -0,0 +1,354 @@
package highlight
import (
"errors"
"fmt"
"regexp"
"gopkg.in/yaml.v2"
)
// A Group represents a syntax group
type Group uint8
// Groups contains all of the groups that are defined
// You can access them in the map via their string name
var Groups map[string]Group
var numGroups Group
// String returns the group name attached to the specific group
func (g Group) String() string {
for k, v := range Groups {
if v == g {
return k
}
}
return ""
}
// A Def is a full syntax definition for a language
// It has a filetype, information about how to detect the filetype based
// on filename or header (the first line of the file)
// Then it has the rules which define how to highlight the file
type Def struct {
*Header
rules *rules
}
type Header struct {
FileType string
FtDetect [2]*regexp.Regexp
}
type File struct {
FileType string
yamlSrc map[interface{}]interface{}
}
// A Pattern is one simple syntax rule
// It has a group that the rule belongs to, as well as
// the regular expression to match the pattern
type pattern struct {
group Group
regex *regexp.Regexp
}
// rules defines which patterns and regions can be used to highlight
// a filetype
type rules struct {
regions []*region
patterns []*pattern
includes []string
}
// A region is a highlighted region (such as a multiline comment, or a string)
// It belongs to a group, and has start and end regular expressions
// A region also has rules of its own that only apply when matching inside the
// region and also rules from the above region do not match inside this region
// Note that a region may contain more regions
type region struct {
group Group
limitGroup Group
parent *region
start *regexp.Regexp
end *regexp.Regexp
skip *regexp.Regexp
rules *rules
}
func init() {
Groups = make(map[string]Group)
}
func ParseFtDetect(file *File) (r [2]*regexp.Regexp, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
rules := file.yamlSrc
loaded := 0
for k, v := range rules {
if k == "detect" {
ftdetect := v.(map[interface{}]interface{})
if len(ftdetect) >= 1 {
syntax, err := regexp.Compile(ftdetect["filename"].(string))
if err != nil {
return r, err
}
r[0] = syntax
}
if len(ftdetect) >= 2 {
header, err := regexp.Compile(ftdetect["header"].(string))
if err != nil {
return r, err
}
r[1] = header
}
loaded++
}
if loaded >= 2 {
break
}
}
if loaded == 0 {
return r, errors.New("No detect regexes found")
}
return r, err
}
func ParseFile(input []byte) (f *File, err error) {
// This is just so if we have an error, we can exit cleanly and return the parse error to the user
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
var rules map[interface{}]interface{}
if err = yaml.Unmarshal(input, &rules); err != nil {
return nil, err
}
f = new(File)
f.yamlSrc = rules
for k, v := range rules {
if k == "filetype" {
filetype := v.(string)
f.FileType = filetype
break
}
}
return f, err
}
// ParseDef parses an input syntax file into a highlight Def
func ParseDef(f *File, header *Header) (s *Def, err error) {
// This is just so if we have an error, we can exit cleanly and return the parse error to the user
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
rules := f.yamlSrc
s = new(Def)
s.Header = header
for k, v := range rules {
if k == "rules" {
inputRules := v.([]interface{})
rules, err := parseRules(inputRules, nil)
if err != nil {
return nil, err
}
s.rules = rules
}
}
return s, err
}
// ResolveIncludes will sort out the rules for including other filetypes
// You should call this after parsing all the Defs
func ResolveIncludes(def *Def, files []*File) {
resolveIncludesInDef(files, def)
}
func resolveIncludesInDef(files []*File, d *Def) {
for _, lang := range d.rules.includes {
for _, searchFile := range files {
if lang == searchFile.FileType {
searchDef, _ := ParseDef(searchFile, nil)
d.rules.patterns = append(d.rules.patterns, searchDef.rules.patterns...)
d.rules.regions = append(d.rules.regions, searchDef.rules.regions...)
}
}
}
for _, r := range d.rules.regions {
resolveIncludesInRegion(files, r)
r.parent = nil
}
}
func resolveIncludesInRegion(files []*File, region *region) {
for _, lang := range region.rules.includes {
for _, searchFile := range files {
if lang == searchFile.FileType {
searchDef, _ := ParseDef(searchFile, nil)
region.rules.patterns = append(region.rules.patterns, searchDef.rules.patterns...)
region.rules.regions = append(region.rules.regions, searchDef.rules.regions...)
}
}
}
for _, r := range region.rules.regions {
resolveIncludesInRegion(files, r)
r.parent = region
}
}
func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
ru = new(rules)
for _, v := range input {
rule := v.(map[interface{}]interface{})
for k, val := range rule {
group := k
switch object := val.(type) {
case string:
if k == "include" {
ru.includes = append(ru.includes, object)
} else {
// Pattern
r, err := regexp.Compile(object)
if err != nil {
return nil, err
}
groupStr := group.(string)
if _, ok := Groups[groupStr]; !ok {
numGroups++
Groups[groupStr] = numGroups
}
groupNum := Groups[groupStr]
ru.patterns = append(ru.patterns, &pattern{groupNum, r})
}
case map[interface{}]interface{}:
// region
region, err := parseRegion(group.(string), object, curRegion)
if err != nil {
return nil, err
}
ru.regions = append(ru.regions, region)
default:
return nil, fmt.Errorf("Bad type %T", object)
}
}
}
return ru, nil
}
func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegion *region) (r *region, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
r = new(region)
if _, ok := Groups[group]; !ok {
numGroups++
Groups[group] = numGroups
}
groupNum := Groups[group]
r.group = groupNum
r.parent = prevRegion
r.start, err = regexp.Compile(regionInfo["start"].(string))
if err != nil {
return nil, err
}
r.end, err = regexp.Compile(regionInfo["end"].(string))
if err != nil {
return nil, err
}
// skip is optional
if _, ok := regionInfo["skip"]; ok {
r.skip, err = regexp.Compile(regionInfo["skip"].(string))
if err != nil {
return nil, err
}
}
// limit-color is optional
if _, ok := regionInfo["limit-group"]; ok {
groupStr := regionInfo["limit-group"].(string)
if _, ok := Groups[groupStr]; !ok {
numGroups++
Groups[groupStr] = numGroups
}
groupNum := Groups[groupStr]
r.limitGroup = groupNum
if err != nil {
return nil, err
}
} else {
r.limitGroup = r.group
}
r.rules, err = parseRules(regionInfo["rules"].([]interface{}), r)
if err != nil {
return nil, err
}
return r, nil
}

View File

@@ -1,498 +1,28 @@
package main
import (
"github.com/zyedidia/tcell"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
)
import "github.com/zyedidia/micro/cmd/micro/highlight"
// FileTypeRules represents a complete set of syntax rules for a filetype
type FileTypeRules struct {
filetype string
filename string
text string
}
var syntaxFiles []*highlight.File
// SyntaxRule represents a regex to highlight in a certain style
type SyntaxRule struct {
// What to highlight
regex *regexp.Regexp
// Any flags
flags string
// Whether this regex is a start=... end=... regex
startend bool
// How to highlight it
style tcell.Style
}
var syntaxFiles map[[2]*regexp.Regexp]FileTypeRules
// These syntax files are pre installed and embedded in the resulting binary by go-bindata
var preInstalledSynFiles = []string{
"Dockerfile",
"apacheconf",
"arduino",
"asciidoc",
"asm",
"awk",
"c",
"caddyfile",
"cmake",
"coffeescript",
"colortest",
"conf",
"conky",
"csharp",
"css",
"cython",
"d",
"dart",
"dot",
"erb",
"fish",
"fortran",
"gdscript",
"gentoo-ebuild",
"gentoo-etc-portage",
"git-commit",
"git-config",
"git-rebase-todo",
"glsl",
"go",
"golo",
"groff",
"haml",
"haskell",
"html",
"ini",
"inputrc",
"java",
"javascript",
"json",
"keymap",
"kickstart",
"ledger",
"lilypond",
"lisp",
"lua",
"makefile",
"man",
"markdown",
"mpdconf",
"micro",
"nanorc",
"nginx",
"ocaml",
"patch",
"peg",
"perl",
"perl6",
"php",
"pkg-config",
"pkgbuild",
"po",
"pov",
"privoxy-action",
"privoxy-config",
"privoxy-filter",
"puppet",
"python",
"r",
"reST",
"rpmspec",
"ruby",
"rust",
"scala",
"sed",
"sh",
"sls",
"sql",
"swift",
"systemd",
"tcl",
"tex",
"vala",
"vi",
"xml",
"xresources",
"yaml",
"yum",
"zsh",
}
// LoadSyntaxFiles loads the syntax files from the default directory (configDir)
func LoadSyntaxFiles() {
// Load the user's custom syntax files, if there are any
LoadSyntaxFilesFromDir(configDir + "/syntax")
// Load the pre-installed syntax files from inside the binary
for _, filetype := range preInstalledSynFiles {
data, err := Asset("runtime/syntax/" + filetype + ".micro")
if err != nil {
TermMessage("Unable to load pre-installed syntax file " + filetype)
continue
}
LoadSyntaxFile(string(data), filetype+".micro")
}
}
// LoadSyntaxFilesFromDir loads the syntax files from a specified directory
// To load the syntax files, we must fill the `syntaxFiles` map
// This involves finding the regex for syntax and if it exists, the regex
// for the header. Then we must get the text for the file and the filetype.
func LoadSyntaxFilesFromDir(dir string) {
colorscheme = make(Colorscheme)
InitColorscheme()
// Default style
defStyle = tcell.StyleDefault.
Foreground(tcell.ColorDefault).
Background(tcell.ColorDefault)
// There may be another default style defined in the colorscheme
// In that case we should use that one
if style, ok := colorscheme["default"]; ok {
defStyle = style
}
if screen != nil {
screen.SetStyle(defStyle)
}
syntaxFiles = make(map[[2]*regexp.Regexp]FileTypeRules)
files, _ := ioutil.ReadDir(dir)
for _, f := range files {
if filepath.Ext(f.Name()) == ".micro" {
filename := dir + "/" + f.Name()
text, err := ioutil.ReadFile(filename)
if err != nil {
TermMessage("Error loading syntax file " + filename + ": " + err.Error())
return
}
LoadSyntaxFile(string(text), filename)
}
}
}
// JoinRule takes a syntax rule (which can be multiple regular expressions)
// and joins it into one regular expression by ORing everything together
func JoinRule(rule string) string {
split := strings.Split(rule, `" "`)
joined := strings.Join(split, ")|(")
joined = "(" + joined + ")"
return joined
}
// LoadSyntaxFile simply gets the filetype of a the syntax file and the source for the
// file and creates FileTypeRules out of it. If this filetype is the one opened by the user
// the rules will be loaded and compiled later
// In this function we are only concerned with loading the syntax and header regexes
func LoadSyntaxFile(text, filename string) {
var err error
lines := strings.Split(string(text), "\n")
// Regex for parsing syntax statements
syntaxParser := regexp.MustCompile(`syntax "(.*?)"\s+"(.*)"+`)
// Regex for parsing header statements
headerParser := regexp.MustCompile(`header "(.*)"`)
// Is there a syntax definition in this file?
hasSyntax := syntaxParser.MatchString(text)
// Is there a header definition in this file?
hasHeader := headerParser.MatchString(text)
var syntaxRegex *regexp.Regexp
var headerRegex *regexp.Regexp
var filetype string
for lineNum, line := range lines {
if (hasSyntax == (syntaxRegex != nil)) && (hasHeader == (headerRegex != nil)) {
// We found what we we're supposed to find
break
}
if strings.TrimSpace(line) == "" ||
strings.TrimSpace(line)[0] == '#' {
// Ignore this line
continue
}
if strings.HasPrefix(line, "syntax") {
// Syntax statement
syntaxMatches := syntaxParser.FindSubmatch([]byte(line))
if len(syntaxMatches) == 3 {
if syntaxRegex != nil {
TermError(filename, lineNum, "Syntax statement redeclaration")
}
filetype = string(syntaxMatches[1])
extensions := JoinRule(string(syntaxMatches[2]))
syntaxRegex, err = regexp.Compile(extensions)
if err != nil {
TermError(filename, lineNum, err.Error())
continue
}
} else {
TermError(filename, lineNum, "Syntax statement is not valid: "+line)
continue
}
} else if strings.HasPrefix(line, "header") {
// Header statement
headerMatches := headerParser.FindSubmatch([]byte(line))
if len(headerMatches) == 2 {
header := JoinRule(string(headerMatches[1]))
headerRegex, err = regexp.Compile(header)
if err != nil {
TermError(filename, lineNum, "Regex error: "+err.Error())
continue
}
} else {
TermError(filename, lineNum, "Header statement is not valid: "+line)
continue
}
}
}
if syntaxRegex != nil {
// Add the current rules to the syntaxFiles variable
regexes := [2]*regexp.Regexp{syntaxRegex, headerRegex}
syntaxFiles[regexes] = FileTypeRules{filetype, filename, text}
}
}
// LoadRulesFromFile loads just the syntax rules from a given file
// Only the necessary rules are loaded when the buffer is opened.
// If we load all the rules for every filetype when micro starts, there's a bit of lag
// A rule just explains how to color certain regular expressions
// Example: color comment "//.*"
// This would color all strings that match the regex "//.*" in the comment color defined
// by the colorscheme
func LoadRulesFromFile(text, filename string) []SyntaxRule {
lines := strings.Split(string(text), "\n")
// Regex for parsing standard syntax rules
ruleParser := regexp.MustCompile(`color (.*?)\s+(?:\((.*?)\)\s+)?"(.*)"`)
// Regex for parsing syntax rules with start="..." end="..."
ruleStartEndParser := regexp.MustCompile(`color (.*?)\s+(?:\((.*?)\)\s+)?start="(.*)"\s+end="(.*)"`)
var rules []SyntaxRule
for lineNum, line := range lines {
if strings.TrimSpace(line) == "" ||
strings.TrimSpace(line)[0] == '#' ||
strings.HasPrefix(line, "syntax") ||
strings.HasPrefix(line, "header") {
// Ignore this line
continue
}
// Syntax rule, but it could be standard or start-end
if ruleParser.MatchString(line) {
// Standard syntax rule
// Parse the line
submatch := ruleParser.FindSubmatch([]byte(line))
var color string
var regexStr string
var flags string
if len(submatch) == 4 {
// If len is 4 then the user specified some additional flags to use
color = string(submatch[1])
flags = string(submatch[2])
regexStr = "(?" + flags + ")" + JoinRule(string(submatch[3]))
} else if len(submatch) == 3 {
// If len is 3, no additional flags were given
color = string(submatch[1])
regexStr = JoinRule(string(submatch[2]))
} else {
// If len is not 3 or 4 there is a problem
TermError(filename, lineNum, "Invalid statement: "+line)
continue
}
// Compile the regex
regex, err := regexp.Compile(regexStr)
if err != nil {
TermError(filename, lineNum, err.Error())
continue
}
// Get the style
// The user could give us a "color" that is really a part of the colorscheme
// in which case we should look that up in the colorscheme
// They can also just give us a straight up color
st := defStyle
groups := strings.Split(color, ".")
if len(groups) > 1 {
curGroup := ""
for i, g := range groups {
if i != 0 {
curGroup += "."
}
curGroup += g
if style, ok := colorscheme[curGroup]; ok {
st = style
}
}
} else if style, ok := colorscheme[color]; ok {
st = style
} else {
st = StringToStyle(color)
}
// Add the regex, flags, and style
// False because this is not start-end
rules = append(rules, SyntaxRule{regex, flags, false, st})
} else if ruleStartEndParser.MatchString(line) {
// Start-end syntax rule
submatch := ruleStartEndParser.FindSubmatch([]byte(line))
var color string
var start string
var end string
// Use m and s flags by default
flags := "ms"
if len(submatch) == 5 {
// If len is 5 the user provided some additional flags
color = string(submatch[1])
flags += string(submatch[2])
start = string(submatch[3])
end = string(submatch[4])
} else if len(submatch) == 4 {
// If len is 4 the user did not provide additional flags
color = string(submatch[1])
start = string(submatch[2])
end = string(submatch[3])
} else {
// If len is not 4 or 5 there is a problem
TermError(filename, lineNum, "Invalid statement: "+line)
continue
}
// Compile the regex
regex, err := regexp.Compile("(?" + flags + ")" + "(" + start + ").*?(" + end + ")")
if err != nil {
TermError(filename, lineNum, err.Error())
continue
}
// Get the style
// The user could give us a "color" that is really a part of the colorscheme
// in which case we should look that up in the colorscheme
// They can also just give us a straight up color
st := defStyle
if _, ok := colorscheme[color]; ok {
st = colorscheme[color]
} else {
st = StringToStyle(color)
}
// Add the regex, flags, and style
// True because this is start-end
rules = append(rules, SyntaxRule{regex, flags, true, st})
}
}
return rules
}
// FindFileType finds the filetype for the given buffer
func FindFileType(buf *Buffer) string {
for r := range syntaxFiles {
if r[0] != nil && r[0].MatchString(buf.Path) {
// The syntax statement matches the extension
return syntaxFiles[r].filetype
} else if r[1] != nil && r[1].MatchString(buf.Line(0)) {
// The header statement matches the first line
return syntaxFiles[r].filetype
}
}
return "Unknown"
}
// GetRules finds the syntax rules that should be used for the buffer
// and returns them. It also returns the filetype of the file
func GetRules(buf *Buffer) []SyntaxRule {
for r := range syntaxFiles {
if syntaxFiles[r].filetype == buf.FileType() {
return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename)
}
}
return nil
}
// SyntaxMatches is an alias to a map from character numbers to styles,
// so map[3] represents the style of the third character
type SyntaxMatches [][]tcell.Style
// Match takes a buffer and returns the syntax matches: a 2d array specifying how it should be syntax highlighted
// We match the rules from up `synLinesUp` lines and down `synLinesDown` lines
func Match(v *View) SyntaxMatches {
buf := v.Buf
rules := v.Buf.rules
viewStart := v.Topline
viewEnd := v.Topline + v.height
if viewEnd > buf.NumLines {
viewEnd = buf.NumLines
}
lines := buf.Lines(viewStart, viewEnd)
matches := make(SyntaxMatches, len(lines))
for i, line := range lines {
matches[i] = make([]tcell.Style, len(line)+1)
for j := range matches[i] {
matches[i][j] = defStyle
}
}
// We don't actually check the entire buffer, just from synLinesUp to synLinesDown
totalStart := v.Topline - synLinesUp
totalEnd := v.Topline + v.height + synLinesDown
if totalStart < 0 {
totalStart = 0
}
if totalEnd > buf.NumLines {
totalEnd = buf.NumLines
}
str := strings.Join(buf.Lines(totalStart, totalEnd), "\n")
startNum := ToCharPos(Loc{0, totalStart}, v.Buf)
for _, rule := range rules {
if rule.startend {
if indicies := rule.regex.FindAllStringIndex(str, -1); indicies != nil {
for _, value := range indicies {
value[0] = runePos(value[0], str) + startNum
value[1] = runePos(value[1], str) + startNum
startLoc := FromCharPos(value[0], buf)
endLoc := FromCharPos(value[1], buf)
for curLoc := startLoc; curLoc.LessThan(endLoc); curLoc = curLoc.Move(1, buf) {
if curLoc.Y < v.Topline {
continue
}
colNum, lineNum := curLoc.X, curLoc.Y
if lineNum == -1 || colNum == -1 {
continue
}
lineNum -= viewStart
if lineNum >= 0 && lineNum < v.height {
matches[lineNum][colNum] = rule.style
}
}
}
}
for _, f := range ListRuntimeFiles(RTSyntax) {
data, err := f.Data()
if err != nil {
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
} else {
for lineN, line := range lines {
if indicies := rule.regex.FindAllStringIndex(line, -1); indicies != nil {
for _, value := range indicies {
start := runePos(value[0], line)
end := runePos(value[1], line)
for i := start; i < end; i++ {
matches[lineN][i] = rule.style
}
}
}
}
LoadSyntaxFile(data, f.Name())
}
}
return matches
}
func LoadSyntaxFile(text []byte, filename string) {
f, err := highlight.ParseFile(text)
if err != nil {
TermMessage("Syntax file error: " + filename + ": " + err.Error())
return
}
syntaxFiles = append(syntaxFiles, f)
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"io"
"os/exec"
"strings"
)
// Jobs are the way plugins can run processes in the background
@@ -40,15 +39,17 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
return f.Writer.Write(data)
}
// JobStart starts a process in the background with the given callbacks
// JobStart starts a shell command in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
split := strings.Split(cmd, " ")
args := split[1:]
cmdName := split[0]
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
}
// JobSpawn starts a process with args in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
// Set up everything correctly if the functions have been provided
proc := exec.Command(cmdName, args...)
proc := exec.Command(cmdName, cmdArgs...)
var outbuf bytes.Buffer
if onStdout != "" {
proc.Stdout = &CallbackFile{&outbuf, LuaFunctionJob(onStdout), userargs}

View File

@@ -1,8 +1,11 @@
package main
import (
"bytes"
"bufio"
"io"
"unicode/utf8"
"github.com/zyedidia/micro/cmd/micro/highlight"
)
func runeToByteIndex(n int, txt []byte) int {
@@ -26,21 +29,84 @@ func runeToByteIndex(n int, txt []byte) int {
return count
}
type Line struct {
data []byte
state highlight.State
match highlight.LineMatch
rehighlight bool
}
// A LineArray simply stores and array of lines and makes it easy to insert
// and delete in it
type LineArray struct {
lines [][]byte
lines []Line
}
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)
slice = newSlice
}
slice = slice[0 : l+len(data)]
for i, c := range data {
slice[l+i] = c
}
return slice
}
// NewLineArray returns a new line array from an array of bytes
func NewLineArray(text []byte) *LineArray {
func NewLineArray(size int64, reader io.Reader) *LineArray {
la := new(LineArray)
// Split the bytes into lines
split := bytes.Split(text, []byte("\n"))
la.lines = make([][]byte, len(split))
for i := range split {
la.lines[i] = make([]byte, len(split[i]))
copy(la.lines[i], split[i])
la.lines = make([]Line, 0, 1000)
br := bufio.NewReader(reader)
var loaded int
n := 0
for {
data, err := br.ReadBytes('\n')
if len(data) > 1 && data[len(data)-2] == '\r' {
data = append(data[:len(data)-2], '\n')
if fileformat == 0 {
fileformat = 2
}
} else if len(data) > 0 {
if fileformat == 0 {
fileformat = 1
}
}
if n >= 1000 && loaded >= 0 {
totalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))
newSlice := make([]Line, len(la.lines), totalLinesNum+10000)
// The copy function is predeclared and works for any slice type.
copy(newSlice, la.lines)
la.lines = newSlice
loaded = -1
}
if loaded >= 0 {
loaded += len(data)
}
if err != nil {
if err == io.EOF {
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
// la.lines = Append(la.lines, Line{data[:len(data)]})
}
// Last line was read
break
} else {
// la.lines = Append(la.lines, Line{data[:len(data)-1]})
la.lines = Append(la.lines, Line{data[:len(data)-1], nil, nil, false})
}
n++
}
return la
@@ -48,19 +114,43 @@ func NewLineArray(text []byte) *LineArray {
// Returns the String representation of the LineArray
func (la *LineArray) String() string {
return string(bytes.Join(la.lines, []byte("\n")))
str := ""
for i, l := range la.lines {
str += string(l.data)
if i != len(la.lines)-1 {
str += "\n"
}
}
return str
}
// SaveString returns the string that should be written to disk when
// the line array is saved
// It is the same as string but uses crlf or lf line endings depending
func (la *LineArray) SaveString(useCrlf bool) string {
str := ""
for i, l := range la.lines {
str += string(l.data)
if i != len(la.lines)-1 {
if useCrlf {
str += "\r"
}
str += "\n"
}
}
return str
}
// NewlineBelow adds a newline below the given line number
func (la *LineArray) NewlineBelow(y int) {
la.lines = append(la.lines, []byte(" "))
la.lines = append(la.lines, Line{[]byte(" "), nil, nil, false})
copy(la.lines[y+2:], la.lines[y+1:])
la.lines[y+1] = []byte("")
la.lines[y+1] = Line{[]byte(""), la.lines[y].state, nil, false}
}
// inserts a byte array at a given location
func (la *LineArray) insert(pos Loc, value []byte) {
x, y := runeToByteIndex(pos.X, la.lines[pos.Y]), pos.Y
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
// x, y := pos.x, pos.y
for i := 0; i < len(value); i++ {
if value[i] == '\n' {
@@ -76,31 +166,36 @@ func (la *LineArray) insert(pos Loc, value []byte) {
// inserts a byte at a given location
func (la *LineArray) insertByte(pos Loc, value byte) {
la.lines[pos.Y] = append(la.lines[pos.Y], 0)
copy(la.lines[pos.Y][pos.X+1:], la.lines[pos.Y][pos.X:])
la.lines[pos.Y][pos.X] = value
la.lines[pos.Y].data = append(la.lines[pos.Y].data, 0)
copy(la.lines[pos.Y].data[pos.X+1:], la.lines[pos.Y].data[pos.X:])
la.lines[pos.Y].data[pos.X] = value
}
// JoinLines joins the two lines a and b
func (la *LineArray) JoinLines(a, b int) {
la.insert(Loc{len(la.lines[a]), a}, la.lines[b])
la.insert(Loc{len(la.lines[a].data), a}, la.lines[b].data)
la.DeleteLine(b)
}
// Split splits a line at a given position
func (la *LineArray) Split(pos Loc) {
la.NewlineBelow(pos.Y)
la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y][pos.X:])
la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y].data[pos.X:])
la.lines[pos.Y+1].state = la.lines[pos.Y].state
la.lines[pos.Y].state = nil
la.lines[pos.Y].match = nil
la.lines[pos.Y+1].match = nil
la.lines[pos.Y].rehighlight = true
la.DeleteToEnd(Loc{pos.X, pos.Y})
}
// removes from start to end
func (la *LineArray) remove(start, end Loc) string {
sub := la.Substr(start, end)
startX := runeToByteIndex(start.X, la.lines[start.Y])
endX := runeToByteIndex(end.X, la.lines[end.Y])
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
if start.Y == end.Y {
la.lines[start.Y] = append(la.lines[start.Y][:startX], la.lines[start.Y][endX:]...)
la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
} else {
for i := start.Y + 1; i <= end.Y-1; i++ {
la.DeleteLine(start.Y + 1)
@@ -114,12 +209,12 @@ func (la *LineArray) remove(start, end Loc) string {
// DeleteToEnd deletes from the end of a line to the position
func (la *LineArray) DeleteToEnd(pos Loc) {
la.lines[pos.Y] = la.lines[pos.Y][:pos.X]
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X]
}
// DeleteFromStart deletes from the start of a line to the position
func (la *LineArray) DeleteFromStart(pos Loc) {
la.lines[pos.Y] = la.lines[pos.Y][pos.X+1:]
la.lines[pos.Y].data = la.lines[pos.Y].data[pos.X+1:]
}
// DeleteLine deletes the line number
@@ -129,21 +224,37 @@ func (la *LineArray) DeleteLine(y int) {
// DeleteByte deletes the byte at a position
func (la *LineArray) DeleteByte(pos Loc) {
la.lines[pos.Y] = la.lines[pos.Y][:pos.X+copy(la.lines[pos.Y][pos.X:], la.lines[pos.Y][pos.X+1:])]
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
}
// Substr returns the string representation between two locations
func (la *LineArray) Substr(start, end Loc) string {
startX := runeToByteIndex(start.X, la.lines[start.Y])
endX := runeToByteIndex(end.X, la.lines[end.Y])
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
if start.Y == end.Y {
return string(la.lines[start.Y][startX:endX])
return string(la.lines[start.Y].data[startX:endX])
}
var str string
str += string(la.lines[start.Y][startX:]) + "\n"
str += string(la.lines[start.Y].data[startX:]) + "\n"
for i := start.Y + 1; i <= end.Y-1; i++ {
str += string(la.lines[i]) + "\n"
str += string(la.lines[i].data) + "\n"
}
str += string(la.lines[end.Y][:endX])
str += string(la.lines[end.Y].data[:endX])
return str
}
func (la *LineArray) State(lineN int) highlight.State {
return la.lines[lineN].state
}
func (la *LineArray) SetState(lineN int, s highlight.State) {
la.lines[lineN].state = s
}
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
la.lines[lineN].match = m
}
func (la *LineArray) Match(lineN int) highlight.LineMatch {
return la.lines[lineN].match
}

View File

@@ -28,11 +28,54 @@ func ToCharPos(start Loc, buf *Buffer) int {
return loc
}
// InBounds returns whether the given location is a valid character position in the given buffer
func InBounds(pos Loc, buf *Buffer) bool {
if pos.Y < 0 || pos.Y >= buf.NumLines || pos.X < 0 || pos.X > Count(buf.Line(pos.Y)) {
return false
}
return true
}
// ByteOffset is just like ToCharPos except it counts bytes instead of runes
func ByteOffset(pos Loc, buf *Buffer) int {
x, y := pos.X, pos.Y
loc := 0
for i := 0; i < y; i++ {
// + 1 for the newline
loc += len(buf.Line(i)) + 1
}
loc += len(buf.Line(y)[:x])
return loc
}
// Loc stores a location
type Loc struct {
X, Y int
}
func Diff(a, b Loc, buf *Buffer) int {
if a.Y == b.Y {
if a.X > b.X {
return a.X - b.X
}
return b.X - a.X
}
// Make sure a is guaranteed to be less than b
if b.LessThan(a) {
a, b = b, a
}
loc := 0
for i := a.Y + 1; i < b.Y; i++ {
// + 1 for the newline
loc += Count(buf.Line(i)) + 1
}
loc += Count(buf.Line(a.Y)) - a.X + b.X + 1
return loc
}
// LessThan returns true if b is smaller
func (l Loc) LessThan(b Loc) bool {
if l.Y < b.Y {

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

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

View File

@@ -6,8 +6,8 @@ import (
"fmt"
"os"
"strconv"
"strings"
"github.com/mattn/go-runewidth"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/tcell"
)
@@ -22,6 +22,7 @@ func TermMessage(msg ...interface{}) {
screenWasNil := screen == nil
if !screenWasNil {
screen.Fini()
screen = nil
}
fmt.Println(msg...)
@@ -44,6 +45,7 @@ func TermError(filename string, lineNum int, err string) {
// Messenger is an object that makes it easy to send messages to the user
// and get input from the user
type Messenger struct {
log *Buffer
// Are we currently prompting the user?
hasPrompt bool
// Is there a message to print
@@ -68,38 +70,87 @@ type Messenger struct {
gutterMessage bool
}
// AddLog sends a message to the log view
func (m *Messenger) AddLog(msg ...interface{}) {
logMessage := fmt.Sprint(msg...)
buffer := m.getBuffer()
buffer.insert(buffer.End(), []byte(logMessage+"\n"))
buffer.Cursor.Loc = buffer.End()
buffer.Cursor.Relocate()
}
func (m *Messenger) getBuffer() *Buffer {
if m.log == nil {
m.log = NewBufferFromString("", "")
m.log.name = "Log"
}
return m.log
}
// Message sends a message to the user
func (m *Messenger) Message(msg ...interface{}) {
buf := new(bytes.Buffer)
fmt.Fprint(buf, msg...)
m.message = buf.String()
m.style = defStyle
displayMessage := fmt.Sprint(msg...)
// only display a new message if there isn't an active prompt
// this is to prevent overwriting an existing prompt to the user
if m.hasPrompt == false {
// if there is no active prompt then style and display the message as normal
m.message = displayMessage
if _, ok := colorscheme["message"]; ok {
m.style = colorscheme["message"]
m.style = defStyle
if _, ok := colorscheme["message"]; ok {
m.style = colorscheme["message"]
}
m.hasMessage = true
}
m.hasMessage = true
// add the message to the log regardless of active prompts
m.AddLog(displayMessage)
}
// Error sends an error message to the user
func (m *Messenger) Error(msg ...interface{}) {
buf := new(bytes.Buffer)
fmt.Fprint(buf, msg...)
m.message = buf.String()
m.style = defStyle.
Foreground(tcell.ColorBlack).
Background(tcell.ColorMaroon)
if _, ok := colorscheme["error-message"]; ok {
m.style = colorscheme["error-message"]
// only display a new message if there isn't an active prompt
// this is to prevent overwriting an existing prompt to the user
if m.hasPrompt == false {
// if there is no active prompt then style and display the message as normal
m.message = buf.String()
m.style = defStyle.
Foreground(tcell.ColorBlack).
Background(tcell.ColorMaroon)
if _, ok := colorscheme["error-message"]; ok {
m.style = colorscheme["error-message"]
}
m.hasMessage = true
}
// add the message to the log regardless of active prompts
m.AddLog(buf.String())
}
func (m *Messenger) PromptText(msg ...interface{}) {
displayMessage := fmt.Sprint(msg...)
// if there is no active prompt then style and display the message as normal
m.message = displayMessage
m.style = defStyle
if _, ok := colorscheme["message"]; ok {
m.style = colorscheme["message"]
}
m.hasMessage = true
// add the message to the log regardless of active prompts
m.AddLog(displayMessage)
}
// YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
m.hasPrompt = true
m.Message(prompt)
m.PromptText(prompt)
_, h := screen.Size()
for {
@@ -113,12 +164,20 @@ func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyRune:
if e.Rune() == 'y' {
if e.Rune() == 'y' || e.Rune() == 'Y' {
m.AddLog("\t--> y")
m.hasPrompt = false
return true, false
} else if e.Rune() == 'n' {
} else if e.Rune() == 'n' || e.Rune() == 'N' {
m.AddLog("\t--> n")
m.hasPrompt = false
return false, false
}
case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
m.AddLog("\t--> (cancel)")
m.Clear()
m.Reset()
m.hasPrompt = false
return false, true
}
}
@@ -128,7 +187,7 @@ func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
// LetterPrompt gives the user a prompt and waits for a one letter response
func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
m.hasPrompt = true
m.Message(prompt)
m.PromptText(prompt)
_, h := screen.Size()
for {
@@ -144,6 +203,7 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
case tcell.KeyRune:
for _, r := range responses {
if e.Rune() == r {
m.AddLog("\t--> " + string(r))
m.Clear()
m.Reset()
m.hasPrompt = false
@@ -151,6 +211,7 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
}
}
case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
m.AddLog("\t--> (cancel)")
m.Clear()
m.Reset()
m.hasPrompt = false
@@ -168,13 +229,15 @@ const (
CommandCompletion
HelpCompletion
OptionCompletion
PluginCmdCompletion
PluginNameCompletion
)
// Prompt sends the user a message and waits for a response to be typed in
// This function blocks the main loop while waiting for input
func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Completion) (string, bool) {
func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTypes ...Completion) (string, bool) {
m.hasPrompt = true
m.Message(prompt)
m.PromptText(prompt)
if _, ok := m.history[historyType]; !ok {
m.history[historyType] = []string{""}
} else {
@@ -182,7 +245,9 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
}
m.historyNum = len(m.history[historyType]) - 1
response, canceled := "", true
response, canceled := placeholder, true
m.response = response
m.cursorx = Count(placeholder)
RedrawAll()
for m.hasPrompt {
@@ -196,14 +261,16 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
switch e.Key() {
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
// Cancel
m.AddLog("\t--> (cancel)")
m.hasPrompt = false
case tcell.KeyEnter:
// User is done entering their response
m.AddLog("\t--> " + m.response)
m.hasPrompt = false
response, canceled = m.response, false
m.history[historyType][len(m.history[historyType])-1] = response
case tcell.KeyTab:
args := strings.Split(m.response, " ")
args := SplitCommandArgs(m.response)
currentArgNum := len(args) - 1
currentArg := args[currentArgNum]
var completionType Completion
@@ -229,6 +296,12 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
chosen, suggestions = HelpComplete(currentArg)
} else if completionType == OptionCompletion {
chosen, suggestions = OptionComplete(currentArg)
} else if completionType == PluginCmdCompletion {
chosen, suggestions = PluginCmdComplete(currentArg)
} else if completionType == PluginNameCompletion {
chosen, suggestions = PluginNameComplete(currentArg)
} else if completionType < NoCompletion {
chosen, suggestions = PluginComplete(completionType, currentArg)
}
if len(suggestions) > 1 {
@@ -236,10 +309,7 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
}
if chosen != "" {
if len(args) > 1 {
chosen = " " + chosen
}
m.response = strings.Join(args[:len(args)-1], " ") + chosen
m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
m.cursorx = Count(m.response)
}
}
@@ -268,36 +338,58 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
switch e := event.(type) {
case *tcell.EventKey:
if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
for key, actions := range bindings {
if e.Key() == key.keyCode {
if e.Key() == tcell.KeyRune {
if e.Rune() != key.r {
continue
}
}
if e.Modifiers() == key.modifiers {
for _, action := range actions {
funcName := FuncName(action)
switch funcName {
case "main.(*View).CursorUp":
if m.historyNum > 0 {
m.historyNum--
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case "main.(*View).CursorDown":
if m.historyNum < len(history)-1 {
m.historyNum++
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case "main.(*View).CursorLeft":
if m.cursorx > 0 {
m.cursorx--
}
case "main.(*View).CursorRight":
if m.cursorx < Count(m.response) {
m.cursorx++
}
case "main.(*View).CursorStart", "main.(*View).StartOfLine":
m.cursorx = 0
case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
m.cursorx = Count(m.response)
case "main.(*View).Backspace":
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
m.cursorx--
}
case "main.(*View).Paste":
clip, _ := clipboard.ReadAll("clipboard")
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
}
}
}
}
}
}
switch e.Key() {
case tcell.KeyUp:
if m.historyNum > 0 {
m.historyNum--
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case tcell.KeyDown:
if m.historyNum < len(history)-1 {
m.historyNum++
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case tcell.KeyLeft:
if m.cursorx > 0 {
m.cursorx--
}
case tcell.KeyRight:
if m.cursorx < Count(m.response) {
m.cursorx++
}
case tcell.KeyBackspace2, tcell.KeyBackspace:
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
m.cursorx--
}
case tcell.KeyCtrlV:
clip, _ := clipboard.ReadAll()
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
case tcell.KeyRune:
m.response = Insert(m.response, m.cursorx, string(e.Rune()))
m.cursorx++
@@ -308,6 +400,23 @@ func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
clip := e.Text()
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
case *tcell.EventMouse:
x, y := e.Position()
x -= Count(m.message)
button := e.Buttons()
_, screenH := screen.Size()
if y == screenH-1 {
switch button {
case tcell.Button1:
m.cursorx = x
if m.cursorx < 0 {
m.cursorx = 0
} else if m.cursorx > Count(m.response) {
m.cursorx = Count(m.response)
}
}
}
}
}
@@ -357,8 +466,10 @@ func (m *Messenger) Display() {
if m.hasMessage {
if m.hasPrompt || globalSettings["infobar"].(bool) {
runes := []rune(m.message + m.response)
posx := 0
for x := 0; x < len(runes); x++ {
screen.SetContent(x, h-1, runes[x], nil, m.style)
screen.SetContent(posx, h-1, runes[x], nil, m.style)
posx += runewidth.RuneWidth(runes[x])
}
}
}

View File

@@ -5,24 +5,25 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/go-errors/errors"
"github.com/layeh/gopher-luar"
"github.com/mattn/go-isatty"
"github.com/mitchellh/go-homedir"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/encoding"
"layeh.com/gopher-luar"
)
const (
synLinesUp = 75 // How many lines up to look to do syntax highlighting
synLinesDown = 75 // How many lines down to look to do syntax highlighting
doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
autosaveTime = 8 // Number of seconds to wait before autosaving
)
var (
@@ -43,14 +44,10 @@ var (
// Version is the version number or commit hash
// These variables should be set by the linker when compiling
Version = "Unknown"
Version = "0.0.0-unknown"
CommitHash = "Unknown"
CompileDate = "Unknown"
// L is the lua state
// This is the VM that runs the plugins
L *lua.LState
// The list of views
tabs []*Tab
// This is the currently open tab
@@ -60,7 +57,8 @@ var (
// Channel of jobs running in the background
jobs chan JobFunction
// Event channel
events chan tcell.Event
events chan tcell.Event
autosave chan bool
)
// LoadInput determines which files should be loaded into buffers
@@ -80,26 +78,37 @@ func LoadInput() []*Buffer {
var filename string
var input []byte
var err error
var buffers []*Buffer
args := flag.Args()
buffers := make([]*Buffer, 0, len(args))
if len(flag.Args()) > 0 {
if len(args) > 0 {
// Option 1
// We go through each file and load it
for i := 0; i < len(flag.Args()); i++ {
filename = flag.Args()[i]
for i := 0; i < len(args); i++ {
filename = args[i]
// 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 = ioutil.ReadFile(filename)
input, err = os.Open(filename)
stat, _ := input.Stat()
defer input.Close()
if err != nil {
TermMessage(err)
input = []byte{}
filename = ""
continue
}
if stat.IsDir() {
TermMessage("Cannot read", filename, "because it is a directory")
continue
}
}
// If the file didn't exist, input will be empty, and we'll open an empty buffer
buffers = append(buffers, NewBuffer(input, filename))
if input != nil {
buffers = append(buffers, NewBuffer(input, FSize(input), filename))
} else {
buffers = append(buffers, NewBufferFromString("", filename))
}
}
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
// Option 2
@@ -110,10 +119,10 @@ func LoadInput() []*Buffer {
TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, NewBuffer(input, filename))
buffers = append(buffers, NewBufferFromString(string(input), filename))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, NewBuffer(input, filename))
buffers = append(buffers, NewBufferFromString(string(input), filename))
}
return buffers
@@ -168,6 +177,10 @@ func InitScreen() {
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).")
}
os.Exit(1)
}
if err = screen.Init(); err != nil {
@@ -181,12 +194,19 @@ func InitScreen() {
}
screen.SetStyle(defStyle)
screen.EnableMouse()
}
// RedrawAll redraws everything -- all the views and the messenger
func RedrawAll() {
messenger.Clear()
w, h := screen.Size()
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
screen.SetContent(x, y, ' ', nil, defStyle)
}
}
for _, v := range tabs[curTab].views {
v.Display()
}
@@ -195,6 +215,28 @@ func RedrawAll() {
screen.Show()
}
func LoadAll() {
// Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
InitConfigDir()
// Build a list of available Extensions (Syntax, Colorscheme etc.)
InitRuntimeFiles()
// Load the user's settings
InitGlobalSettings()
InitCommands()
InitBindings()
InitColorscheme()
for _, tab := range tabs {
for _, v := range tab.views {
v.Buf.UpdateRules()
}
}
}
// Passing -version as a flag will have micro print out the version number
var flagVersion = flag.Bool("version", false, "Show the version number and information")
var flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.")
@@ -202,7 +244,8 @@ var flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at
func main() {
flag.Usage = func() {
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
fmt.Println("Micro's options can be set via command line arguments for quick adjustments. For real configuration, please use the bindings.json file (see 'help options').\n")
fmt.Print("Micro's options can be set via command line arguments for quick adjustments. For real configuration, please use the bindings.json file (see 'help options').\n\n")
flag.CommandLine.SetOutput(os.Stdout)
flag.PrintDefaults()
}
@@ -233,18 +276,15 @@ func main() {
// Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
InitConfigDir()
// Build a list of available Extensions (Syntax, Colorscheme etc.)
InitRuntimeFiles()
// Load the user's settings
InitGlobalSettings()
InitCommands()
InitBindings()
// Load the syntax files, including the colorscheme
LoadSyntaxFiles()
// Load the help files
LoadHelp()
// Start the screen
InitScreen()
@@ -268,6 +308,11 @@ func main() {
// Now we load the input
buffers := LoadInput()
if len(buffers) == 0 {
screen.Fini()
os.Exit(1)
}
for _, buf := range buffers {
// For each buffer we create a new tab and place the view in that tab
tab := NewTabFromView(NewView(buf))
@@ -276,10 +321,9 @@ func main() {
for _, t := range tabs {
for _, v := range t.views {
v.Center(false)
if globalSettings["syntax"].(bool) {
v.matches = Match(v)
}
}
t.Resize()
}
}
@@ -306,36 +350,73 @@ func main() {
L.SetGlobal("HandleCommand", luar.New(L, HandleCommand))
L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion))
L.SetGlobal("NewBuffer", luar.New(L, NewBufferFromString))
L.SetGlobal("RuneStr", luar.New(L, func(r rune) string {
return string(r)
}))
L.SetGlobal("Loc", luar.New(L, func(x, y int) Loc {
return Loc{x, y}
}))
L.SetGlobal("WorkingDirectory", luar.New(L, os.Getwd))
L.SetGlobal("JoinPaths", luar.New(L, filepath.Join))
L.SetGlobal("DirectoryName", luar.New(L, filepath.Dir))
L.SetGlobal("configDir", luar.New(L, configDir))
L.SetGlobal("Reload", luar.New(L, LoadAll))
L.SetGlobal("ByteOffset", luar.New(L, ByteOffset))
L.SetGlobal("ToCharPos", luar.New(L, ToCharPos))
// Used for asynchronous jobs
L.SetGlobal("JobStart", luar.New(L, JobStart))
L.SetGlobal("JobSpawn", luar.New(L, JobSpawn))
L.SetGlobal("JobSend", luar.New(L, JobSend))
L.SetGlobal("JobStop", luar.New(L, JobStop))
LoadPlugins()
// Extension Files
L.SetGlobal("ReadRuntimeFile", luar.New(L, PluginReadRuntimeFile))
L.SetGlobal("ListRuntimeFiles", luar.New(L, PluginListRuntimeFiles))
L.SetGlobal("AddRuntimeFile", luar.New(L, PluginAddRuntimeFile))
L.SetGlobal("AddRuntimeFilesFromDirectory", luar.New(L, PluginAddRuntimeFilesFromDirectory))
L.SetGlobal("AddRuntimeFileFromMemory", luar.New(L, PluginAddRuntimeFileFromMemory))
// Access to Go stdlib
L.SetGlobal("import", luar.New(L, Import))
jobs = make(chan JobFunction, 100)
events = make(chan tcell.Event)
events = make(chan tcell.Event, 100)
autosave = make(chan bool)
LoadPlugins()
for _, t := range tabs {
for _, v := range t.views {
for _, pl := range loadedPlugins {
for pl := range loadedPlugins {
_, err := Call(pl+".onViewOpen", v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
continue
}
}
if v.Buf.Settings["syntax"].(bool) {
v.matches = Match(v)
}
}
}
InitColorscheme()
// Here is the event loop which runs in a separate thread
go func() {
for {
events <- screen.PollEvent()
if screen != nil {
events <- screen.PollEvent()
}
}
}()
go func() {
for {
time.Sleep(autosaveTime * time.Second)
if globalSettings["autosave"].(bool) {
autosave <- true
}
}
}()
@@ -351,50 +432,66 @@ func main() {
// If a new job has finished while running in the background we should execute the callback
f.function(f.output, f.args...)
continue
case <-autosave:
CurView().Save(true)
case event = <-events:
}
switch e := event.(type) {
case *tcell.EventMouse:
if e.Buttons() == tcell.Button1 {
// If the user left clicked we check a couple things
_, h := screen.Size()
x, y := e.Position()
if y == h-1 && messenger.message != "" && globalSettings["infobar"].(bool) {
// If the user clicked in the bottom bar, and there is a message down there
// we copy it to the clipboard.
// Often error messages are displayed down there so it can be useful to easily
// copy the message
clipboard.WriteAll(messenger.message)
continue
for event != nil {
switch e := event.(type) {
case *tcell.EventResize:
for _, t := range tabs {
t.Resize()
}
case *tcell.EventMouse:
if !searching {
if e.Buttons() == tcell.Button1 {
// If the user left clicked we check a couple things
_, h := screen.Size()
x, y := e.Position()
if y == h-1 && messenger.message != "" && globalSettings["infobar"].(bool) {
// If the user clicked in the bottom bar, and there is a message down there
// we copy it to the clipboard.
// Often error messages are displayed down there so it can be useful to easily
// copy the message
clipboard.WriteAll(messenger.message, "primary")
break
}
if CurView().mouseReleased {
// We loop through each view in the current tab and make sure the current view
// is the one being clicked in
for _, v := range tabs[curTab].views {
if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
tabs[curTab].curView = v.Num
if CurView().mouseReleased {
// We loop through each view in the current tab and make sure the current view
// is the one being clicked in
for _, v := range tabs[curTab].views {
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
tabs[curTab].CurView = v.Num
}
}
}
}
}
}
}
// This function checks the mouse event for the possibility of changing the current tab
// If the tab was changed it returns true
if TabbarHandleMouseEvent(event) {
continue
}
// This function checks the mouse event for the possibility of changing the current tab
// If the tab was changed it returns true
if TabbarHandleMouseEvent(event) {
break
}
if searching {
// Since searching is done in real time, we need to redraw every time
// there is a new event in the search bar so we need a special function
// to run instead of the standard HandleEvent.
HandleSearchEvent(event, CurView())
} else {
// Send it to the view
CurView().HandleEvent(event)
if searching {
// Since searching is done in real time, we need to redraw every time
// there is a new event in the search bar so we need a special function
// to run instead of the standard HandleEvent.
HandleSearchEvent(event, CurView())
} else {
// Send it to the view
CurView().HandleEvent(event)
}
select {
case event = <-events:
default:
event = nil
}
}
}
}

View File

@@ -6,17 +6,12 @@ import (
"os"
"strings"
"github.com/layeh/gopher-luar"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/tcell"
"layeh.com/gopher-luar"
)
var loadedPlugins []string
var preInstalledPlugins = []string{
"go",
"linter",
"autoclose",
}
var loadedPlugins map[string]string
// Call calls the lua function 'function'
// If it does not exist nothing happens, if there is an error,
@@ -66,6 +61,16 @@ func LuaFunctionBinding(function string) func(*View, bool) bool {
}
}
func LuaFunctionMouseBinding(function string) func(*View, bool, *tcell.EventMouse) bool {
return func(v *View, _ bool, e *tcell.EventMouse) bool {
_, err := Call(function, e)
if err != nil {
TermMessage(err)
}
return false
}
}
func unpack(old []string) []interface{} {
new := make([]interface{}, len(old))
for i, v := range old {
@@ -85,6 +90,30 @@ func LuaFunctionCommand(function string) func([]string) {
}
}
// LuaFunctionComplete returns a function which can be used for autocomplete in plugins
func LuaFunctionComplete(function string) func(string) []string {
return func(input string) (result []string) {
res, err := Call(function, input)
if err != nil {
TermMessage(err)
}
if tbl, ok := res.(*lua.LTable); !ok {
TermMessage(function, "should return a table of strings")
} else {
for i := 1; i <= tbl.Len(); i++ {
val := tbl.RawGetInt(i)
if v, ok := val.(lua.LString); !ok {
TermMessage(function, "should return a table of strings")
} else {
result = append(result, string(v))
}
}
}
return result
}
}
func LuaFunctionJob(function string) func(string, ...string) {
return func(output string, args ...string) {
_, err := Call(function, unpack(append([]string{output}, args...))...)
@@ -94,59 +123,43 @@ func LuaFunctionJob(function string) func(string, ...string) {
}
}
// luaPluginName convert a human-friendly plugin name into a valid lua variable name.
func luaPluginName(name string) string {
return strings.Replace(name, "-", "_", -1)
}
// LoadPlugins loads the pre-installed plugins and the plugins located in ~/.config/micro/plugins
func LoadPlugins() {
files, _ := ioutil.ReadDir(configDir + "/plugins")
for _, plugin := range files {
if plugin.IsDir() {
pluginName := plugin.Name()
files, _ := ioutil.ReadDir(configDir + "/plugins/" + pluginName)
for _, f := range files {
if f.Name() == pluginName+".lua" {
data, _ := ioutil.ReadFile(configDir + "/plugins/" + pluginName + "/" + f.Name())
pluginDef := "\nlocal P = {}\n" + pluginName + " = P\nsetmetatable(" + pluginName + ", {__index = _G})\nsetfenv(1, P)\n"
loadedPlugins = make(map[string]string)
if err := L.DoString(pluginDef + string(data)); err != nil {
TermMessage(err)
continue
}
loadedPlugins = append(loadedPlugins, pluginName)
}
}
for _, plugin := range ListRuntimeFiles(RTPlugin) {
pluginName := plugin.Name()
if _, ok := loadedPlugins[pluginName]; ok {
continue
}
}
for _, pluginName := range preInstalledPlugins {
alreadyExists := false
for _, pl := range loadedPlugins {
if pl == pluginName {
alreadyExists = true
break
}
data, err := plugin.Data()
if err != nil {
TermMessage("Error loading plugin: " + pluginName)
continue
}
if !alreadyExists {
plugin := "runtime/plugins/" + pluginName + "/" + pluginName + ".lua"
data, err := Asset(plugin)
if err != nil {
TermMessage("Error loading pre-installed plugin: " + pluginName)
continue
}
pluginDef := "\nlocal P = {}\n" + pluginName + " = P\nsetmetatable(" + pluginName + ", {__index = _G})\nsetfenv(1, P)\n"
if err := L.DoString(pluginDef + string(data)); err != nil {
TermMessage(err)
continue
}
loadedPlugins = append(loadedPlugins, pluginName)
pluginLuaName := luaPluginName(pluginName)
if err := LoadFile(pluginName, pluginName, string(data)); err != nil {
TermMessage(err)
continue
}
loadedPlugins[pluginName] = pluginLuaName
}
if _, err := os.Stat(configDir + "/init.lua"); err == nil {
pluginDef := "\nlocal P = {}\n" + "init" + " = P\nsetmetatable(" + "init" + ", {__index = _G})\nsetfenv(1, P)\n"
data, _ := ioutil.ReadFile(configDir + "/init.lua")
if err := L.DoString(pluginDef + string(data)); err != nil {
if err := LoadFile("init", configDir+"init.lua", string(data)); err != nil {
TermMessage(err)
}
loadedPlugins = append(loadedPlugins, "init")
loadedPlugins["init"] = "init"
}
}

622
cmd/micro/pluginmanager.go Normal file
View File

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

View File

@@ -0,0 +1,56 @@
package main
import (
"testing"
"github.com/blang/semver"
"github.com/flynn/json5"
)
func TestDependencyResolving(t *testing.T) {
js := `
[{
"Name": "Foo",
"Versions": [{ "Version": "1.0.0" }, { "Version": "1.5.0" },{ "Version": "2.0.0" }]
}, {
"Name": "Bar",
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": ">1.0.0 <2.0.0"} }]
}, {
"Name": "Unresolvable",
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": "<=1.0.0", "Bar": ">0.0.0"} }]
}]
`
var all PluginPackages
err := json5.Unmarshal([]byte(js), &all)
if err != nil {
t.Error(err)
}
selected, err := all.Resolve(PluginVersions{}, PluginDependencies{
&PluginDependency{"Bar", semver.MustParseRange(">=1.0.0")},
})
check := func(name, version string) {
v := selected.find(name)
expected := semver.MustParse(version)
if v == nil {
t.Errorf("Failed to resolve %s", name)
} else if expected.NE(v.Version) {
t.Errorf("%s resolved in wrong version got %s", name, v)
}
}
if err != nil {
t.Error(err)
} else {
check("Foo", "1.5.0")
check("Bar", "1.0.0")
}
selected, err = all.Resolve(PluginVersions{}, PluginDependencies{
&PluginDependency{"Unresolvable", semver.MustParseRange(">0.0.0")},
})
if err == nil {
t.Error("Unresolvable package resolved:", selected)
}
}

207
cmd/micro/rtfiles.go Normal file
View File

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

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,7 @@ package main
import (
"regexp"
"strings"
"github.com/zyedidia/tcell"
)
@@ -11,7 +12,7 @@ var (
lastSearch string
// Where should we start the search down from (or up from)
searchStart int
searchStart Loc
// Is there currently a search in progress
searching bool
@@ -21,12 +22,14 @@ var (
)
// BeginSearch starts a search
func BeginSearch() {
func BeginSearch(searchStr string) {
searchHistory = append(searchHistory, "")
messenger.historyNum = len(searchHistory) - 1
searching = true
messenger.hasPrompt = true
messenger.response = searchStr
messenger.cursorx = Count(searchStr)
messenger.Message("Find: ")
messenger.hasPrompt = true
}
// EndSearch stops the current search
@@ -41,13 +44,27 @@ func EndSearch() {
}
}
// ExitSearch exits the search mode, reset active search phrase, and clear status bar
func ExitSearch(v *View) {
lastSearch = ""
searching = false
messenger.hasPrompt = false
messenger.Clear()
messenger.Reset()
v.Cursor.ResetSelection()
}
// HandleSearchEvent takes an event and a view and will do a real time match from the messenger's output
// to the current buffer. It searches down the buffer.
func HandleSearchEvent(event tcell.Event, v *View) {
switch e := event.(type) {
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape, tcell.KeyEnter:
case tcell.KeyEscape:
// Exit the search mode
ExitSearch(v)
return
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEnter:
// Done
EndSearch()
return
@@ -70,9 +87,71 @@ func HandleSearchEvent(event tcell.Event, v *View) {
Search(messenger.response, v, true)
v.Relocate()
return
}
func searchDown(r *regexp.Regexp, v *View, start, end Loc) bool {
for i := start.Y; i <= end.Y; i++ {
var l []byte
var charPos int
if i == start.Y {
runes := []rune(string(v.Buf.lines[i].data))
l = []byte(string(runes[start.X:]))
charPos = start.X
if strings.Contains(r.String(), "^") && start.X != 0 {
continue
}
} else {
l = v.Buf.lines[i].data
}
match := r.FindIndex(l)
if match != nil {
v.Cursor.SetSelectionStart(Loc{charPos + runePos(match[0], string(l)), i})
v.Cursor.SetSelectionEnd(Loc{charPos + runePos(match[1], string(l)), i})
v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
v.Cursor.Loc = v.Cursor.CurSelection[1]
return true
}
}
return false
}
func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
for i := start.Y; i >= end.Y; i-- {
var l []byte
if i == start.Y {
runes := []rune(string(v.Buf.lines[i].data))
l = []byte(string(runes[:start.X]))
if strings.Contains(r.String(), "$") && start.X != Count(string(l)) {
continue
}
} else {
l = v.Buf.lines[i].data
}
match := r.FindIndex(l)
if match != nil {
v.Cursor.SetSelectionStart(Loc{runePos(match[0], string(l)), i})
v.Cursor.SetSelectionEnd(Loc{runePos(match[1], string(l)), i})
v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
v.Cursor.Loc = v.Cursor.CurSelection[1]
return true
}
}
return false
}
// Search searches in the view for the given regex. The down bool
// specifies whether it should search down from the searchStart position
// or up from there
@@ -80,15 +159,6 @@ func Search(searchStr string, v *View, down bool) {
if searchStr == "" {
return
}
var str string
var charPos int
text := v.Buf.String()
if down {
str = string([]rune(text)[searchStart:])
charPos = searchStart
} else {
str = string([]rune(text)[:searchStart])
}
r, err := regexp.Compile(searchStr)
if v.Buf.Settings["ignorecase"].(bool) {
r, err = regexp.Compile("(?i)" + searchStr)
@@ -96,40 +166,22 @@ func Search(searchStr string, v *View, down bool) {
if err != nil {
return
}
matches := r.FindAllStringIndex(str, -1)
var match []int
if matches == nil {
// Search the entire buffer now
matches = r.FindAllStringIndex(text, -1)
charPos = 0
if matches == nil {
v.Cursor.ResetSelection()
return
}
if !down {
match = matches[len(matches)-1]
} else {
match = matches[0]
var found bool
if down {
found = searchDown(r, v, searchStart, v.Buf.End())
if !found {
found = searchDown(r, v, v.Buf.Start(), searchStart)
}
str = text
}
if !down {
match = matches[len(matches)-1]
} else {
match = matches[0]
found = searchUp(r, v, searchStart, v.Buf.Start())
if !found {
found = searchUp(r, v, v.Buf.End(), searchStart)
}
}
if match[0] == match[1] {
return
if found {
lastSearch = searchStr
} else {
v.Cursor.ResetSelection()
}
v.Cursor.CurSelection[0] = FromCharPos(charPos+runePos(match[0], str), v.Buf)
v.Cursor.CurSelection[1] = FromCharPos(charPos+runePos(match[1], str), v.Buf)
v.Cursor.Loc = v.Cursor.CurSelection[1]
if v.Relocate() {
v.matches = Match(v)
}
lastSearch = searchStr
}

View File

@@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"errors"
"io/ioutil"
"os"
@@ -8,15 +9,30 @@ import (
"strconv"
"strings"
"github.com/yosuke-furukawa/json5/encoding/json5"
"github.com/flynn/json5"
"github.com/zyedidia/glob"
)
type optionValidator func(string, interface{}) error
// The options that the user can set
var globalSettings map[string]interface{}
var invalidSettings bool
// Options with validators
var optionValidators = map[string]optionValidator{
"tabsize": validatePositiveValue,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
"colorscheme": validateColorscheme,
"colorcolumn": validateNonNegativeValue,
"fileformat": validateLineEnding,
}
// InitGlobalSettings initializes the options map and sets all options to their default values
func InitGlobalSettings() {
invalidSettings = false
defaults := DefaultGlobalSettings()
var parsed map[string]interface{}
@@ -27,12 +43,14 @@ func InitGlobalSettings() {
if !strings.HasPrefix(string(input), "null") {
if err != nil {
TermMessage("Error reading settings.json file: " + err.Error())
invalidSettings = true
return
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
invalidSettings = true
}
} else {
writeSettings = true
@@ -60,6 +78,7 @@ func InitGlobalSettings() {
// InitLocalSettings scans the json in settings.json and sets the options locally based
// on whether the buffer matches the glob
func InitLocalSettings(buf *Buffer) {
invalidSettings = false
var parsed map[string]interface{}
filename := configDir + "/settings.json"
@@ -67,12 +86,14 @@ func InitLocalSettings(buf *Buffer) {
input, err := ioutil.ReadFile(filename)
if err != nil {
TermMessage("Error reading settings.json file: " + err.Error())
invalidSettings = true
return
}
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
invalidSettings = true
}
}
@@ -95,6 +116,11 @@ func InitLocalSettings(buf *Buffer) {
// WriteSettings writes the settings to the specified filename as JSON
func WriteSettings(filename string) error {
if invalidSettings {
// Do not write the settings if there was an error when reading them
return nil
}
var err error
if _, e := os.Stat(configDir); e == nil {
parsed := make(map[string]interface{})
@@ -113,6 +139,7 @@ func WriteSettings(filename string) error {
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
invalidSettings = true
}
for k, v := range parsed {
@@ -125,7 +152,7 @@ func WriteSettings(filename string) error {
}
}
txt, _ := json5.MarshalIndent(parsed, "", " ")
txt, _ := json.MarshalIndent(parsed, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return err
@@ -164,21 +191,38 @@ func GetOption(name string) interface{} {
// Note that colorscheme is a global only option
func DefaultGlobalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"colorscheme": "zenburn",
"cursorline": true,
"ignorecase": false,
"indentchar": " ",
"infobar": true,
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"statusline": true,
"syntax": true,
"tabsize": float64(4),
"tabstospaces": false,
"autoindent": true,
"keepautoindent": false,
"autosave": false,
"colorcolumn": float64(0),
"colorscheme": "default",
"cursorline": true,
"eofnewline": false,
"rmtrailingws": false,
"ignorecase": false,
"indentchar": " ",
"infobar": true,
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"softwrap": false,
"splitRight": true,
"splitBottom": true,
"statusline": true,
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"termtitle": false,
"pluginchannels": []string{
"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
},
"pluginrepos": []string{},
"useprimary": true,
"fileformat": "unix",
"mouse": true,
}
}
@@ -186,20 +230,32 @@ func DefaultGlobalSettings() map[string]interface{} {
// Note that filetype is a local only option
func DefaultLocalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"cursorline": true,
"filetype": "Unknown",
"ignorecase": false,
"indentchar": " ",
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"statusline": true,
"syntax": true,
"tabsize": float64(4),
"tabstospaces": false,
"autoindent": true,
"keepautoindent": false,
"autosave": false,
"colorcolumn": float64(0),
"cursorline": true,
"eofnewline": false,
"rmtrailingws": false,
"filetype": "Unknown",
"ignorecase": false,
"indentchar": " ",
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"softwrap": false,
"splitRight": true,
"splitBottom": true,
"statusline": true,
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"useprimary": true,
"fileformat": "unix",
"mouse": true,
}
}
@@ -208,12 +264,6 @@ func DefaultLocalSettings() map[string]interface{} {
// is local only it will set the local version
// Use setlocal to force an option to be set locally
func SetOption(option, value string) error {
if option == "colorscheme" {
if !ColorschemeExists(value) {
return errors.New(value + " is not a valid colorscheme")
}
}
if _, ok := globalSettings[option]; !ok {
if _, ok := CurView().Buf.Settings[option]; !ok {
return errors.New("Invalid option")
@@ -222,31 +272,39 @@ func SetOption(option, value string) error {
return nil
}
var nativeValue interface{}
kind := reflect.TypeOf(globalSettings[option]).Kind()
if kind == reflect.Bool {
b, err := ParseBool(value)
if err != nil {
return errors.New("Invalid value")
}
globalSettings[option] = b
nativeValue = b
} else if kind == reflect.String {
globalSettings[option] = value
nativeValue = value
} else if kind == reflect.Float64 {
i, err := strconv.Atoi(value)
if err != nil {
return errors.New("Invalid value")
}
globalSettings[option] = float64(i)
nativeValue = float64(i)
} else {
return errors.New("Option has unsupported value type")
}
if err := optionIsValid(option, nativeValue); err != nil {
return err
}
globalSettings[option] = nativeValue
if option == "colorscheme" {
LoadSyntaxFiles()
// LoadSyntaxFiles()
InitColorscheme()
for _, tab := range tabs {
for _, view := range tab.views {
view.Buf.UpdateRules()
if view.Buf.Settings["syntax"].(bool) {
view.matches = Match(view)
}
}
}
}
@@ -275,35 +333,60 @@ func SetLocalOption(option, value string, view *View) error {
return errors.New("Invalid option")
}
var nativeValue interface{}
kind := reflect.TypeOf(buf.Settings[option]).Kind()
if kind == reflect.Bool {
b, err := ParseBool(value)
if err != nil {
return errors.New("Invalid value")
}
buf.Settings[option] = b
nativeValue = b
} else if kind == reflect.String {
buf.Settings[option] = value
nativeValue = value
} else if kind == reflect.Float64 {
i, err := strconv.Atoi(value)
if err != nil {
return errors.New("Invalid value")
}
buf.Settings[option] = float64(i)
nativeValue = float64(i)
} else {
return errors.New("Option has unsupported value type")
}
if err := optionIsValid(option, nativeValue); err != nil {
return err
}
buf.Settings[option] = nativeValue
if option == "statusline" {
view.ToggleStatusLine()
if buf.Settings["syntax"].(bool) {
view.matches = Match(view)
}
}
if option == "filetype" {
LoadSyntaxFiles()
// LoadSyntaxFiles()
InitColorscheme()
buf.UpdateRules()
if buf.Settings["syntax"].(bool) {
view.matches = Match(view)
}
if option == "fileformat" {
buf.IsModified = true
}
if option == "syntax" {
if !nativeValue.(bool) {
buf.ClearMatches()
} else {
buf.highlighter.HighlightStates(buf)
}
}
if option == "mouse" {
if !nativeValue.(bool) {
screen.DisableMouse()
} else {
screen.EnableMouse()
}
}
@@ -327,3 +410,69 @@ func SetOptionAndSettings(option, value string) {
return
}
}
func optionIsValid(option string, value interface{}) error {
if validator, ok := optionValidators[option]; ok {
return validator(option, value)
}
return nil
}
// Option validators
func validatePositiveValue(option string, value interface{}) error {
tabsize, ok := value.(float64)
if !ok {
return errors.New("Expected numeric type for " + option)
}
if tabsize < 1 {
return errors.New(option + " must be greater than 0")
}
return nil
}
func validateNonNegativeValue(option string, value interface{}) error {
nativeValue, ok := value.(float64)
if !ok {
return errors.New("Expected numeric type for " + option)
}
if nativeValue < 0 {
return errors.New(option + " must be non-negative")
}
return nil
}
func validateColorscheme(option string, value interface{}) error {
colorscheme, ok := value.(string)
if !ok {
return errors.New("Expected string type for colorscheme")
}
if !ColorschemeExists(colorscheme) {
return errors.New(colorscheme + " is not a valid colorscheme")
}
return nil
}
func validateLineEnding(option string, value interface{}) error {
endingType, ok := value.(string)
if !ok {
return errors.New("Expected string type for file format")
}
if endingType != "unix" && endingType != "dos" {
return errors.New("File format must be either 'unix' or 'dos'")
}
return nil
}

View File

@@ -1,24 +1,30 @@
package main
// SplitType specifies whether a split is horizontal or vertical
type SplitType bool
const (
VerticalSplit = false
// VerticalSplit type
VerticalSplit = false
// HorizontalSplit type
HorizontalSplit = true
)
// A Node on the split tree
type Node interface {
VSplit(buf *Buffer)
HSplit(buf *Buffer)
VSplit(buf *Buffer, splitIndex int)
HSplit(buf *Buffer, splitIndex int)
String() string
}
// A LeafNode is an actual split so it contains a view
type LeafNode struct {
view *View
parent *SplitTree
}
// NewLeafNode returns a new leaf node containing the given view
func NewLeafNode(v *View, parent *SplitTree) *LeafNode {
n := new(LeafNode)
n.view = v
@@ -27,6 +33,7 @@ func NewLeafNode(v *View, parent *SplitTree) *LeafNode {
return n
}
// A SplitTree is a Node itself and it contains other nodes
type SplitTree struct {
kind SplitType
@@ -36,64 +43,122 @@ type SplitTree struct {
x int
y int
width int
height int
width int
height int
lockWidth bool
lockHeight bool
tabNum int
}
func (l *LeafNode) VSplit(buf *Buffer) {
// VSplit creates a vertical split
func (l *LeafNode) VSplit(buf *Buffer, splitIndex int) {
if splitIndex < 0 {
splitIndex = 0
}
tab := tabs[l.parent.tabNum]
if l.parent.kind == VerticalSplit {
if splitIndex > len(l.parent.children) {
splitIndex = len(l.parent.children)
}
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
newView.Num = len(tab.views)
l.parent.children = append(l.parent.children, NewLeafNode(newView, l.parent))
tab.curView++
tab.views = append(tab.views, newView)
l.parent.children = append(l.parent.children, nil)
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
} else {
if splitIndex > 1 {
splitIndex = 1
}
s := new(SplitTree)
s.kind = VerticalSplit
s.parent = l.parent
s.tabNum = l.parent.tabNum
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
newView.Num = len(tab.views)
s.children = []Node{l, NewLeafNode(newView, s)}
if splitIndex == 1 {
s.children = []Node{l, NewLeafNode(newView, s)}
} else {
s.children = []Node{NewLeafNode(newView, s), l}
}
l.parent.children[search(l.parent.children, l)] = s
l.parent = s
tab.curView++
tab.views = append(tab.views, newView)
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
}
tab.Resize()
}
func (l *LeafNode) HSplit(buf *Buffer) {
// HSplit creates a horizontal split
func (l *LeafNode) HSplit(buf *Buffer, splitIndex int) {
if splitIndex < 0 {
splitIndex = 0
}
tab := tabs[l.parent.tabNum]
if l.parent.kind == HorizontalSplit {
if splitIndex > len(l.parent.children) {
splitIndex = len(l.parent.children)
}
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
newView.Num = len(tab.views)
l.parent.children = append(l.parent.children, NewLeafNode(newView, l.parent))
tab.curView++
tab.views = append(tab.views, newView)
l.parent.children = append(l.parent.children, nil)
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
} else {
if splitIndex > 1 {
splitIndex = 1
}
s := new(SplitTree)
s.kind = HorizontalSplit
s.tabNum = l.parent.tabNum
s.parent = l.parent
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
newView.Num = len(tab.views)
s.children = []Node{l, NewLeafNode(newView, s)}
if splitIndex == 1 {
s.children = []Node{l, NewLeafNode(newView, s)}
} else {
s.children = []Node{NewLeafNode(newView, s), l}
}
l.parent.children[search(l.parent.children, l)] = s
l.parent = s
tab.curView++
tab.views = append(tab.views, newView)
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
}
tab.Resize()
}
// Delete deletes a split
func (l *LeafNode) Delete() {
i := search(l.parent.children, l)
@@ -110,11 +175,12 @@ func (l *LeafNode) Delete() {
for i, v := range tab.views {
v.Num = i
}
if tab.curView > 0 {
tab.curView--
if tab.CurView > 0 {
tab.CurView--
}
}
// Cleanup rearranges all the parents after a split has been deleted
func (s *SplitTree) Cleanup() {
for i, node := range s.children {
if n, ok := node.(*SplitTree); ok {
@@ -130,41 +196,84 @@ func (s *SplitTree) Cleanup() {
}
}
// ResizeSplits resizes all the splits correctly
func (s *SplitTree) ResizeSplits() {
for i, node := range s.children {
lockedWidth := 0
lockedHeight := 0
lockedChildren := 0
for _, node := range s.children {
if n, ok := node.(*LeafNode); ok {
if s.kind == VerticalSplit {
n.view.width = s.width / len(s.children)
n.view.height = s.height
n.view.x = s.x + n.view.width*i
n.view.y = s.y
if n.view.LockWidth {
lockedWidth += n.view.Width
lockedChildren++
}
} else {
n.view.height = s.height / len(s.children)
n.view.width = s.width
if n.view.LockHeight {
lockedHeight += n.view.Height
lockedChildren++
}
}
} else if n, ok := node.(*SplitTree); ok {
if s.kind == VerticalSplit {
if n.lockWidth {
lockedWidth += n.width
lockedChildren++
}
} else {
if n.lockHeight {
lockedHeight += n.height
lockedChildren++
}
}
}
}
x, y := 0, 0
for _, node := range s.children {
if n, ok := node.(*LeafNode); ok {
if s.kind == VerticalSplit {
if !n.view.LockWidth {
n.view.Width = (s.width - lockedWidth) / (len(s.children) - lockedChildren)
}
n.view.Height = s.height
n.view.y = s.y + n.view.height*i
n.view.x = s.x + x
n.view.y = s.y
x += n.view.Width
} else {
if !n.view.LockHeight {
n.view.Height = (s.height - lockedHeight) / (len(s.children) - lockedChildren)
}
n.view.Width = s.width
n.view.y = s.y + y
n.view.x = s.x
y += n.view.Height
}
if n.view.Buf.Settings["statusline"].(bool) {
n.view.height--
n.view.Height--
}
n.view.ToggleTabbar()
n.view.matches = Match(n.view)
} else if n, ok := node.(*SplitTree); ok {
if s.kind == VerticalSplit {
n.width = s.width / len(s.children)
if !n.lockWidth {
n.width = (s.width - lockedWidth) / (len(s.children) - lockedChildren)
}
n.height = s.height
n.x = s.x + n.width*i
n.x = s.x + x
n.y = s.y
x += n.width
} else {
n.height = s.height / len(s.children)
if !n.lockHeight {
n.height = (s.height - lockedHeight) / (len(s.children) - lockedChildren)
}
n.width = s.width
n.y = s.y + n.height*i
n.y = s.y + y
n.x = s.x
y += n.height
}
n.ResizeSplits()
}
@@ -172,7 +281,7 @@ func (s *SplitTree) ResizeSplits() {
}
func (l *LeafNode) String() string {
return l.view.Buf.Name
return l.view.Buf.GetName()
}
func search(haystack []Node, needle Node) int {
@@ -193,8 +302,11 @@ func findView(haystack []*View, needle *View) int {
return 0
}
func (s *SplitTree) VSplit(buf *Buffer) {}
func (s *SplitTree) HSplit(buf *Buffer) {}
// VSplit is here just to make SplitTree fit the Node interface
func (s *SplitTree) VSplit(buf *Buffer, splitIndex int) {}
// HSplit is here just to make SplitTree fit the Node interface
func (s *SplitTree) HSplit(buf *Buffer, splitIndex int) {}
func (s *SplitTree) String() string {
str := "["

View File

@@ -15,9 +15,9 @@ type Statusline struct {
// Display draws the statusline to the screen
func (sline *Statusline) Display() {
// We'll draw the line at the lowest line in the view
y := sline.view.height + sline.view.y
y := sline.view.Height + sline.view.y
file := sline.view.Buf.Name
file := sline.view.Buf.GetName()
// If the buffer is dirty (has been modified) write a little '+'
if sline.view.Buf.IsModified {
@@ -36,9 +36,14 @@ func (sline *Statusline) Display() {
// Add the filetype
file += " " + sline.view.Buf.FileType()
rightText := helpBinding + " for help "
if sline.view.Help {
rightText = helpBinding + " to close help "
file += " " + sline.view.Buf.Settings["fileformat"].(string)
rightText := ""
if len(helpBinding) > 0 {
rightText = helpBinding + " for help "
if sline.view.Type == vtHelp {
rightText = helpBinding + " to close help "
}
}
statusLineStyle := defStyle.Reverse(true)
@@ -53,11 +58,11 @@ func (sline *Statusline) Display() {
screen.SetContent(viewX, y, ' ', nil, statusLineStyle)
viewX++
}
for x := 0; x < sline.view.width; x++ {
for x := 0; x < sline.view.Width; x++ {
if x < len(fileRunes) {
screen.SetContent(viewX+x, y, fileRunes[x], nil, statusLineStyle)
} else if x >= sline.view.width-len(rightText) && x < len(rightText)+sline.view.width-len(rightText) {
screen.SetContent(viewX+x, y, []rune(rightText)[x-sline.view.width+len(rightText)], nil, statusLineStyle)
} else if x >= sline.view.Width-len(rightText) && x < len(rightText)+sline.view.Width-len(rightText) {
screen.SetContent(viewX+x, y, []rune(rightText)[x-sline.view.Width+len(rightText)], nil, statusLineStyle)
} else {
screen.SetContent(viewX+x, y, ' ', nil, statusLineStyle)
}

View File

@@ -6,15 +6,15 @@ import (
"github.com/zyedidia/tcell"
)
var tabBarOffset int
type Tab struct {
// This contains all the views in this tab
// There is generally only one view per tab, but you can have
// multiple views with splits
views []*View
// This is the current view for this tab
curView int
// Generally this is the name of the current view's buffer
name string
CurView int
tree *SplitTree
}
@@ -37,11 +37,14 @@ func NewTabFromView(v *View) *Tab {
t.tree.height--
}
t.Resize()
return t
}
// SetNum sets all this tab's views to have the correct tab number
func (t *Tab) SetNum(num int) {
t.tree.tabNum = num
for _, v := range t.views {
v.TabNum = num
}
@@ -61,12 +64,16 @@ func (t *Tab) Resize() {
}
t.tree.ResizeSplits()
for i, v := range t.views {
v.Num = i
}
}
// 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
@@ -82,13 +89,17 @@ func TabbarString() (string, map[int]int) {
} else {
str += " "
}
str += t.views[t.curView].Buf.Name
buf := t.views[t.CurView].Buf
str += buf.GetName()
if buf.IsModified {
str += " +"
}
if i == curTab {
str += "]"
} else {
str += " "
}
indicies[len(str)-1] = i + 1
indicies[Count(str)-1] = i + 1
str += " "
}
return str, indicies
@@ -113,7 +124,7 @@ func TabbarHandleMouseEvent(event tcell.Event) bool {
return false
}
str, indicies := TabbarString()
if x >= len(str) {
if x+tabBarOffset >= len(str) {
return false
}
var tabnum int
@@ -123,7 +134,7 @@ func TabbarHandleMouseEvent(event tcell.Event) bool {
}
sort.Ints(keys)
for _, k := range keys {
if x <= k {
if x+tabBarOffset <= k {
tabnum = indicies[k] - 1
break
}
@@ -142,7 +153,7 @@ func DisplayTabs() {
return
}
str, _ := TabbarString()
str, indicies := TabbarString()
tabBarStyle := defStyle.Reverse(true)
if style, ok := colorscheme["tabbar"]; ok {
@@ -152,6 +163,98 @@ func DisplayTabs() {
// Maybe there is a unicode filename?
fileRunes := []rune(str)
w, _ := screen.Size()
tooWide := (w < len(fileRunes))
// if the entire tab-bar is longer than the screen is wide,
// then it should be truncated appropriately to keep the
// active tab visible on the UI.
if tooWide == true {
// first we have to work out where the selected tab is
// out of the total length of the tab bar. this is done
// by extracting the hit-areas from the indicies map
// that was constructed by `TabbarString()`
var keys []int
for offset := range indicies {
keys = append(keys, offset)
}
// sort them to be in ascending order so that values will
// correctly reflect the displayed ordering of the tabs
sort.Ints(keys)
// record the offset of each tab and the previous tab so
// we can find the position of the tab's hit-box.
previousTabOffset := 0
currentTabOffset := 0
for _, k := range keys {
tabIndex := indicies[k] - 1
if tabIndex == curTab {
currentTabOffset = k
break
}
// this is +2 because there are two padding spaces that aren't accounted
// for in the display. please note that this is for cosmetic purposes only.
previousTabOffset = k + 2
}
// get the width of the hitbox of the active tab, from there calculate the offsets
// to the left and right of it to approximately center it on the tab bar display.
centeringOffset := (w - (currentTabOffset - previousTabOffset))
leftBuffer := previousTabOffset - (centeringOffset / 2)
rightBuffer := currentTabOffset + (centeringOffset / 2)
// check to make sure we haven't overshot the bounds of the string,
// if we have, then take that remainder and put it on the left side
overshotRight := rightBuffer - len(fileRunes)
if overshotRight > 0 {
leftBuffer = leftBuffer + overshotRight
}
overshotLeft := leftBuffer - 0
if overshotLeft < 0 {
leftBuffer = 0
rightBuffer = leftBuffer + (w - 1)
} else {
rightBuffer = leftBuffer + (w - 2)
}
if rightBuffer > len(fileRunes)-1 {
rightBuffer = len(fileRunes) - 1
}
// construct a new buffer of text to put the
// newly formatted tab bar text into.
var displayText []rune
// if the left-side of the tab bar isn't at the start
// of the constructed tab bar text, then show that are
// more tabs to the left by displaying a "+"
if leftBuffer != 0 {
displayText = append(displayText, '+')
}
// copy the runes in from the original tab bar text string
// into the new display buffer
for x := leftBuffer; x < rightBuffer; x++ {
displayText = append(displayText, fileRunes[x])
}
// if there is more text to the right of the right-most
// column in the tab bar text, then indicate there are more
// tabs to the right by displaying a "+"
if rightBuffer < len(fileRunes)-1 {
displayText = append(displayText, '+')
}
// now store the offset from zero of the left-most text
// that is being displayed. This is to ensure that when
// clicking on the tab bar, the correct tab gets selected.
tabBarOffset = leftBuffer
// use the constructed buffer as the display buffer to print
// onscreen.
fileRunes = displayText
} else {
tabBarOffset = 0
}
// iterate over the width of the terminal display and for each column,
// write a character into the tab display area with the appropriate style.
for x := 0; x < w; x++ {
if x < len(fileRunes) {
screen.SetContent(x, 0, fileRunes[x], nil, tabBarStyle)

View File

@@ -1,8 +1,11 @@
package main
import (
"bytes"
"os"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"time"
@@ -20,7 +23,7 @@ func Count(s string) int {
return utf8.RuneCountInString(s)
}
// NumOccurrences counts the number of occurences of a byte in a string
// NumOccurrences counts the number of occurrences of a byte in a string
func NumOccurrences(s string, c byte) int {
var n int
for i := 0; i < len(s); i++ {
@@ -33,11 +36,7 @@ func NumOccurrences(s string, c byte) int {
// Spaces returns a string with n spaces
func Spaces(n int) string {
var str string
for i := 0; i < n; i++ {
str += " "
}
return str
return strings.Repeat(" ", n)
}
// Min takes the min of two ints
@@ -56,13 +55,19 @@ func Max(a, b int) int {
return b
}
func FSize(f *os.File) int64 {
fi, _ := f.Stat()
// get the size
return fi.Size()
}
// IsWordChar returns whether or not the string is a 'word character'
// If it is a unicode character, then it does not match
// Word characters are defined as [A-Za-z0-9_]
func IsWordChar(str string) bool {
if len(str) > 1 {
// Unicode
return false
return true
}
c := str[0]
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
@@ -73,6 +78,16 @@ func IsWhitespace(c rune) bool {
return c == ' ' || c == '\t' || c == '\n'
}
// IsStrWhitespace returns true if the given string is all whitespace
func IsStrWhitespace(str string) bool {
for _, c := range str {
if !IsWhitespace(c) {
return false
}
}
return true
}
// Contains returns whether or not a string array contains a given string
func Contains(list []string, a string) bool {
for _, b := range list {
@@ -88,6 +103,18 @@ func Insert(str string, pos int, value string) string {
return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
}
// MakeRelative will attempt to make a relative path between path and base
func MakeRelative(path, base string) (string, error) {
if len(path) > 0 {
rel, err := filepath.Rel(base, path)
if err != nil {
return path, err
}
return rel, nil
}
return path, nil
}
// GetLeadingWhitespace returns the leading whitespace of the given string
func GetLeadingWhitespace(str string) string {
ws := ""
@@ -154,7 +181,19 @@ func GetModTime(path string) (time.Time, bool) {
// StringWidth returns the width of a string where tabs count as `tabsize` width
func StringWidth(str string, tabsize int) int {
sw := runewidth.StringWidth(str)
sw += NumOccurrences(str, '\t') * (tabsize - 1)
lineIdx := 0
for _, ch := range str {
switch ch {
case '\t':
ts := tabsize - (lineIdx % tabsize)
sw += ts
lineIdx += ts
case '\n':
lineIdx = 0
default:
lineIdx++
}
}
return sw
}
@@ -162,16 +201,22 @@ func StringWidth(str string, tabsize int) int {
// that have a width larger than 1 (this also counts tabs as `tabsize` width)
func WidthOfLargeRunes(str string, tabsize int) int {
count := 0
lineIdx := 0
for _, ch := range str {
var w int
if ch == '\t' {
w = tabsize
w = tabsize - (lineIdx % tabsize)
} else {
w = runewidth.RuneWidth(ch)
}
if w > 1 {
count += (w - 1)
}
if ch == '\n' {
lineIdx = 0
} else {
lineIdx += w
}
}
return count
}
@@ -217,3 +262,104 @@ func Abs(n int) int {
}
return n
}
// FuncName returns the full name of a given function object
func FuncName(i interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}
// ShortFuncName returns the name only of a given function object
func ShortFuncName(i interface{}) string {
return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
}
// SplitCommandArgs separates multiple command arguments which may be quoted.
// The returned slice contains at least one string
func SplitCommandArgs(input string) []string {
var result []string
var curQuote *bytes.Buffer
curArg := new(bytes.Buffer)
escape := false
finishQuote := func() {
if curQuote == nil {
return
}
str := curQuote.String()
if unquoted, err := strconv.Unquote(str); err == nil {
str = unquoted
}
curArg.WriteString(str)
curQuote = nil
}
appendResult := func() {
finishQuote()
escape = false
str := curArg.String()
result = append(result, str)
curArg.Reset()
}
for _, r := range input {
if r == ' ' && curQuote == nil {
appendResult()
} else {
runeHandled := false
appendRuneToBuff := func() {
if curQuote != nil {
curQuote.WriteRune(r)
} else {
curArg.WriteRune(r)
}
runeHandled = true
}
if r == '"' && curQuote == nil {
curQuote = new(bytes.Buffer)
appendRuneToBuff()
} else {
if curQuote != nil && !escape {
if r == '"' {
appendRuneToBuff()
finishQuote()
} else if r == '\\' {
appendRuneToBuff()
escape = true
continue
}
}
}
if !runeHandled {
appendRuneToBuff()
}
}
escape = false
}
appendResult()
return result
}
// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
func JoinCommandArgs(args ...string) string {
buf := new(bytes.Buffer)
first := true
for _, arg := range args {
if first {
first = false
} else {
buf.WriteRune(' ')
}
quoted := strconv.Quote(arg)
if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
buf.WriteString(quoted)
} else {
buf.WriteString(arg)
}
}
return buf.String()
}

View File

@@ -1,6 +1,9 @@
package main
import "testing"
import (
"reflect"
"testing"
)
func TestNumOccurences(t *testing.T) {
var tests = []struct {
@@ -47,15 +50,15 @@ func TestIsWordChar(t *testing.T) {
if IsWordChar("_") == false {
t.Errorf("IsWordChar(_) = false")
}
if IsWordChar("ß") == false {
t.Errorf("IsWordChar(ß) = false")
}
if IsWordChar("~") == true {
t.Errorf("IsWordChar(~) = true")
}
if IsWordChar(" ") == true {
t.Errorf("IsWordChar( ) = true")
}
if IsWordChar("ß") == true {
t.Errorf("IsWordChar(ß) = true")
}
if IsWordChar(")") == true {
t.Errorf("IsWordChar()) = true")
}
@@ -63,3 +66,91 @@ func TestIsWordChar(t *testing.T) {
t.Errorf("IsWordChar(\n)) = true")
}
}
func TestJoinAndSplitCommandArgs(t *testing.T) {
tests := []struct {
Query []string
Wanted string
}{
{[]string{`test case`}, `"test case"`},
{[]string{`quote "test"`}, `"quote \"test\""`},
{[]string{`slash\\\ test`}, `"slash\\\\\\ test"`},
{[]string{`path 1`, `path\" 2`}, `"path 1" "path\\\" 2"`},
{[]string{`foo`}, `foo`},
{[]string{`foo\"bar`}, `"foo\\\"bar"`},
{[]string{``}, ``},
{[]string{`"`}, `"\""`},
{[]string{`a`, ``}, `a `},
{[]string{``, ``, ``, ``}, ` `},
{[]string{"\n"}, `"\n"`},
{[]string{"foo\tbar"}, `"foo\tbar"`},
}
for i, test := range tests {
if result := JoinCommandArgs(test.Query...); test.Wanted != result {
t.Errorf("JoinCommandArgs failed at Test %d\nGot: %q", i, result)
}
if result := SplitCommandArgs(test.Wanted); !reflect.DeepEqual(test.Query, result) {
t.Errorf("SplitCommandArgs failed at Test %d\nGot: `%q`", i, result)
}
}
splitTests := []struct {
Query string
Wanted []string
}{
{`"hallo""Welt"`, []string{`halloWelt`}},
{`"hallo" "Welt"`, []string{`hallo`, `Welt`}},
{`\"`, []string{`\"`}},
{`"foo`, []string{`"foo`}},
{`"foo"`, []string{`foo`}},
{`"\"`, []string{`"\"`}},
{`"C:\\"foo.txt`, []string{`C:\foo.txt`}},
{`"\n"new"\n"line`, []string{"\nnew\nline"}},
}
for i, test := range splitTests {
if result := SplitCommandArgs(test.Query); !reflect.DeepEqual(test.Wanted, result) {
t.Errorf("SplitCommandArgs failed at Split-Test %d\nGot: `%q`", i, result)
}
}
}
func TestStringWidth(t *testing.T) {
tabsize := 4
if w := StringWidth("1\t2", tabsize); w != 5 {
t.Error("StringWidth 1 Failed. Got", w)
}
if w := StringWidth("\t", tabsize); w != 4 {
t.Error("StringWidth 2 Failed. Got", w)
}
if w := StringWidth("1\t", tabsize); w != 4 {
t.Error("StringWidth 3 Failed. Got", w)
}
if w := StringWidth("\t\t", tabsize); w != 8 {
t.Error("StringWidth 4 Failed. Got", w)
}
if w := StringWidth("12\t2\t", tabsize); w != 8 {
t.Error("StringWidth 5 Failed. Got", w)
}
}
func TestWidthOfLargeRunes(t *testing.T) {
tabsize := 4
if w := WidthOfLargeRunes("1\t2", tabsize); w != 2 {
t.Error("WidthOfLargeRunes 1 Failed. Got", w)
}
if w := WidthOfLargeRunes("\t", tabsize); w != 3 {
t.Error("WidthOfLargeRunes 2 Failed. Got", w)
}
if w := WidthOfLargeRunes("1\t", tabsize); w != 2 {
t.Error("WidthOfLargeRunes 3 Failed. Got", w)
}
if w := WidthOfLargeRunes("\t\t", tabsize); w != 6 {
t.Error("WidthOfLargeRunes 4 Failed. Got", w)
}
if w := WidthOfLargeRunes("12\t2\t", tabsize); w != 3 {
t.Error("WidthOfLargeRunes 5 Failed. Got", w)
}
}

1
cmd/micro/vendor/golang.org/x/net generated vendored Submodule

1
cmd/micro/vendor/golang.org/x/text generated vendored Submodule

1
cmd/micro/vendor/gopkg.in/yaml.v2 generated vendored Submodule

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,5 +1,5 @@
# Runtime files for Micro
This directory will be embedded in the Go binary for portability, but it may just as well be put in `~/.config/micro`. If you would like to make your own colorschemes
and syntax files, you can put in in `~/.config/micro/colorschemes` and `~/.config/micro/syntax` respectively.
and syntax files, you can put them in `~/.config/micro/colorschemes` and `~/.config/micro/syntax` respectively.

View File

@@ -4,6 +4,7 @@ color-link identifier "#F9EE98,#1D1F21"
color-link constant "#FF73FD,#1D1F21"
color-link constant.string "#A8FF60,#1D1F21"
color-link statement "#96CBFE,#1D1F21"
color-link symbol "#96CBFE,#1D1F21"
color-link preproc "#62B1FE,#1D1F21"
color-link type "#C6C5FE,#1D1F21"
color-link special "#A6E22E,#1D1F21"
@@ -11,9 +12,15 @@ color-link underlined "#D33682,#1D1F21"
color-link error "bold #FF4444,#1D1F21"
color-link todo "bold #FF8844,#1D1F21"
color-link statusline "#1D1F21,#C5C8C6"
color-link tabbar "#1D1F21,#C5C8C6"
color-link indent-char "#505050,#1D1F21"
color-link line-number "#656866,#232526"
color-link current-line-number "#656866,#1D1F21"
color-link gutter-error "#FF4444,#1D1F21"
color-link gutter-warning "#EEEE77,#1D1F21"
color-link cursor-line "#2D2F31"
color-link color-column "#2D2F31"
#color-link symbol.brackets "#96CBFE,#1D1F21"
#No extended types (bool in C, etc.)
#color-link type.extended "default"
#Plain brackets

View File

@@ -5,15 +5,20 @@ color-link constant.string "136,231"
color-link constant.number "131,231"
color-link identifier "133,231"
color-link statement "32,231"
color-link symbol "32,231"
color-link preproc "28,231"
color-link type "61,231"
color-link special "167,231"
color-link error "231, 160"
color-link underlined "underline 241,231"
color-link todo "246,231"
color-link statusline "241,254"
color-link tabbar "241,254"
color-link gutter-error "197,231"
color-link gutter-warning "134,231"
color-link line-number "246,254"
color-link cursor-line "254"
color-link color-column "254"
#No extended types (bool in C, &c.) and plain brackets
color-link type.extended "default"
color-link symbol.brackets "default"

View File

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

View File

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

View File

@@ -0,0 +1,36 @@
#CaptainMcClellan's personal colour scheme.
#Full colour edition.
color-link default "#aaaaaa,#1e2124"
color-link comment "bold #555555"
color-link constant "#008888"
#color-link constant.string "#888800"
color-link constant.string "#a85700"
color-link constant.specialChar "bold #ccccff"
color-link identifier "bold #e34234"
color-link identifier.macro "bold #e34234"
color-link identifier.var "bold #5757ff"
color-link identifier.class "bold #ffffff"
color-link statement "bold #ffff55"
color-link symbol "#722f37"
color-link symbol.brackets "#4169e1"
color-link symbol.tag "#5757ff"
color-link preproc "bold #55ffff"
color-link type "#3eb489"
color-link type.keyword "bold #bdecb6"
color-link special "#b57edc"
color-link ignore "default"
color-link error "bold ,#e34234"
color-link todo "bold underline #888888,#f26522"
color-link indent-char ",#bdecb6"
color-link line-number "#bdecb6,#36393e"
color-link line-number.scrollbar "#3eb489"
color-link statusline "#aaaaaa,#8a496b"
color-link tabbar "#aaaaaa,#8a496b"
color-link current-line-number "bold #e34234,#424549"
color-link current-line-number.scroller "red"
color-link gutter-error ",#e34234"
color-link gutter-warning "#e34234"
color-link color-column "#f26522"
color-link constant.bool "bold #55ffff"
color-link constant.bool.true "bold #85ff85"
color-link constant.bool.false "bold #ff8585"

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
color-link default "#CCCCCC,#242424"
color-link comment "#707070,#242424"
color-link identifier "#FFC66D,#242424"
color-link constant "#7A9EC2,#242424"
color-link constant.string "#6A8759,#242424"
color-link constant.string.char "#6A8759,#242424"
color-link statement "#CC8242,#242424"
color-link symbol "#CCCCCC,#242424"
color-link preproc "#CC8242,#242424"
color-link type "#CC8242,#242424"
color-link special "#CC8242,#242424"
color-link underlined "#D33682,#242424"
color-link error "bold #CB4B16,#242424"
color-link todo "bold #D33682,#242424"
color-link statusline "#242424,#CCCCCC"
color-link tabbar "#242424,#CCCCCC"
color-link indent-char "#4F4F4F,#242424"
color-link line-number "#666666,#242424"
color-link 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"
#No extended types; Plain brackets.
color-link type.extended "default"
#color-link symbol.brackets "default"
color-link symbol.tag "#AE81FF,#242424"

View File

@@ -1,20 +1,27 @@
color-link default "188,237"
color-link comment "108,237"
color-link constant.string "174,237"
color-link constant.number "116,237"
color-link constant "181,237"
color-link identifier "223,237"
color-link statement "223,237"
color-link preproc "223,237"
color-link type "187,237"
color-link special "181,237"
color-link underlined "188,237"
color-link error "115,236"
color-link todo "bold 254,237"
color-link statusline "186,236"
color-link indent-char "238,237"
color-link line-number "188,238"
color-link gutter-error "237,174"
color-link gutter-warning "174,237"
color-link cursor-line "238"
color-link current-line-number "188,237"
color-link default "#F8F8F2,#282828"
color-link comment "#75715E,#282828"
color-link identifier "#66D9EF,#282828"
color-link constant "#AE81FF,#282828"
color-link constant.string "#E6DB74,#282828"
color-link constant.string.char "#BDE6AD,#282828"
color-link statement "#F92672,#282828"
color-link symbol "#F92672,#282828"
color-link preproc "#CB4B16,#282828"
color-link type "#66D9EF,#282828"
color-link special "#A6E22E,#282828"
color-link underlined "#D33682,#282828"
color-link error "bold #CB4B16,#282828"
color-link todo "bold #D33682,#282828"
color-link statusline "#282828,#F8F8F2"
color-link tabbar "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"
color-link line-number "#AAAAAA,#323232"
color-link current-line-number "#AAAAAA,#282828"
color-link gutter-error "#CB4B16,#282828"
color-link gutter-warning "#E6DB74,#282828"
color-link cursor-line "#323232"
color-link color-column "#323232"
#No extended types; Plain brackets.
color-link type.extended "default"
#color-link symbol.brackets "default"
color-link symbol.tag "#AE81FF,#282828"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
#Geany
color-link comment "red"
color-link constant "default"
color-link constant.number
color-link constant.string "bold yellow"
color-link identifier "default"
color-link preproc "cyan"
color-link special "blue"
color-link statement "blue"
color-link symbol "default"
color-link symbol.tag "bold blue"
color-link type "blue"
color-link type.extended "default"
color-link error "red"
color-link todo "bold cyan"
color-link indent-char "bold black"
color-link line-number ""
color-link current-line-number ""
color-link statusline "black,white"
color-link tabbar "black,white"
color-link color-column "bold geren"
color-link gutter-error ",red"
color-link gutter-warning "red"

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
color-link default "#ebdbb2,#282828"
color-link comment "#928374,#282828"
color-link symbol "#d79921,#282828"
color-link constant "#d3869b,#282828"
color-link constant.string "#b8bb26,#282828"
color-link constant.string.char "#b8bb26,#282828"
color-link identifier "#8ec07c,#282828"
color-link statement "#fb4934,#282828"
color-link preproc "#fb4934,235"
color-link type "#fb4934,#282828"
color-link special "#d79921,#282828"
color-link underlined "underline #282828"
color-link error "#9d0006,#282828"
color-link gutter-error "#fb4934,#282828"
color-link gutter-warning "#d79921,#282828"
color-link line-number "#665c54,#282828"
color-link current-line-number "#665c54,#3c3836"
color-link cursor-line "#3c3836"
color-link color-column "#79740e"
color-link statusline "#ebdbb2,#665c54"
color-link tabbar "#ebdbb2,#665c54"

View File

@@ -4,6 +4,7 @@ color-link constant "175,235"
color-link constant.string "142,235"
color-link identifier "109,235"
color-link statement "124,235"
color-link symbol "124,235"
color-link preproc "72,235"
color-link type "214,235"
color-link special "172,235"
@@ -13,3 +14,6 @@ color-link todo "bold 223,235"
color-link line-number "243,237"
color-link current-line-number "172,237"
color-link cursor-line "237"
color-link color-column "237"
color-link statusline "223,237"
color-link tabbar "223,237"

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,9 @@ color-link comment "#75715E,#282828"
color-link identifier "#66D9EF,#282828"
color-link constant "#AE81FF,#282828"
color-link constant.string "#E6DB74,#282828"
color-link constant.string.char "#BDE6AD,#282828"
color-link statement "#F92672,#282828"
color-link symbol "#F92672,#282828"
color-link preproc "#CB4B16,#282828"
color-link type "#66D9EF,#282828"
color-link special "#A6E22E,#282828"
@@ -11,9 +13,15 @@ color-link underlined "#D33682,#282828"
color-link error "bold #CB4B16,#282828"
color-link todo "bold #D33682,#282828"
color-link statusline "#282828,#F8F8F2"
color-link tabbar "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"
color-link line-number "#AAAAAA,#323232"
color-link current-line-number "#AAAAAA,#282828"
color-link gutter-error "#CB4B16,#282828"
color-link gutter-warning "#E6DB74,#282828"
color-link cursor-line "#323232"
color-link color-column "#323232"
#No extended types; Plain brackets.
color-link type.extended "default"
#color-link symbol.brackets "default"
color-link symbol.tag "#AE81FF,#282828"

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ color-link comment "blue"
color-link constant "red"
color-link identifier "cyan"
color-link statement "yellow"
color-link symbol "yellow"
color-link preproc "magenta"
color-link type "green"
color-link special "magenta"
@@ -13,4 +14,12 @@ color-link line-number "yellow"
color-link current-line-number "red"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link cursor-line "white"
#Cursor line causes readability issues. Disabled for now.
#color-link cursor-line "white,black"
color-link color-column "white"
#No extended types. (bool in C)
color-link type.extended "default"
#No bracket highlighting.
color-link symbol.brackets "default"
#Color shebangs the comment color
color-link preproc.shebang "comment"

View File

@@ -2,17 +2,23 @@ color-link default "#839496,#002833"
color-link comment "#586E75,#002833"
color-link identifier "#268BD2,#002833"
color-link constant "#2AA198,#002833"
color-link constant.specialChar "#DC322F,#002833"
color-link statement "#859900,#002833"
color-link symbol "#859900,#002833"
color-link preproc "#CB4B16,#002833"
color-link type "#B58900,#002833"
color-link special "#DC322F,#002833"
color-link special "#268BD2,#002833"
color-link underlined "#D33682,#002833"
color-link error "bold #CB4B16,#002833"
color-link todo "bold #D33682,#002833"
color-link statusline "#003541,#839496"
color-link indent-char "#586E75,#002833"
color-link tabbar "#003541,#839496"
color-link indent-char "#003541,#002833"
color-link line-number "#586E75,#003541"
color-link current-line-number "#586E75,#002833"
color-link gutter-error "#003541,#CB4B16"
color-link gutter-warning "#CB4B16,#002833"
color-link cursor-line "#003541"
color-link color-column "#003541"
color-link type.extended "#839496,#002833"
color-link symbol.brackets "#839496,#002833"

View File

@@ -1,17 +1,23 @@
color-link comment "brightgreen"
color-link comment "bold brightgreen"
color-link constant "cyan"
color-link constant.specialChar "red"
color-link identifier "blue"
color-link statement "green"
color-link symbol "green"
color-link preproc "brightred"
color-link type "yellow"
color-link special "red"
color-link special "blue"
color-link underlined "magenta"
color-link error "bold brightred"
color-link todo "bold magenta"
color-link statusline "black,brightblue"
color-link tabbar "black,brightblue"
color-link indent-char "black"
color-link line-number "brightgreen,black"
color-link current-line-number "brightgreen,default"
color-link line-number "bold brightgreen,black"
color-link current-line-number "bold brightgreen,default"
color-link gutter-error "black,brightred"
color-link gutter-warning "brightred,default"
color-link cursor-line "black"
color-link color-column "black"
color-link type.extended "default"
color-link symbol.brackets "default"

View File

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

View File

@@ -5,6 +5,7 @@ color-link constant.number "116,237"
color-link constant "181,237"
color-link identifier "223,237"
color-link statement "223,237"
color-link symbol "223,237"
color-link preproc "223,237"
color-link type "187,237"
color-link special "181,237"
@@ -12,9 +13,11 @@ color-link underlined "188,237"
color-link error "115,236"
color-link todo "bold 254,237"
color-link statusline "186,236"
color-link tabbar "186,236"
color-link indent-char "238,237"
color-link line-number "248,238"
color-link gutter-error "237,174"
color-link gutter-warning "174,237"
color-link cursor-line "238"
color-link color-column "238"
color-link current-line-number "188,237"

View File

@@ -5,40 +5,76 @@ This help page aims to cover two aspects of micro's syntax highlighting engine:
- How to create colorschemes and use them
- How to create syntax files to add to the list of languages micro can highlight
### Colorschemes
## Colorschemes
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
* zenburn: this is micro's default colorscheme because it looks very good
and works in 256 color terminals.
this colorscheme also has the name 'default'
* mc: A 16-color theme based on the look and feel of GNU Midnight Commander.
This will look great used in conjunction with Midnight Commander.
* nano: A 16-color theme loosely based on GNU nano's syntax highlighting.
* monokai: this is the monokai colorscheme; you may recognize it as
Sublime Text's default colorscheme. It requires true color to
look perfect, but the 256 color approximation looks very good as well.
It's also the default colorscheme.
* solarized: this is the solarized colorscheme.
* zenburn: The 'zenburn' colorscheme and works well with 256 color terminals
* solarized: this is the solarized colorscheme.
You should have the solarized color palette in your terminal to use it.
* solarized-tc: this is the solarized colorscheme for true color, just
make sure your terminal supports true color before using it and that the
* 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.
* 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.
* atom-dark-tc: this colorscheme is based off of Atom's "dark" colorscheme.
It requires true color to look good.
To enable one of these colorschemes just run the command `set colorscheme solarized`.
(or whichever one you choose).
* 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 on most
ANSI (16-color) terminals.)
* 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.
* codeblocks: A colorscheme based on the Code::Blocks IDE's default syntax highlighting.
* 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 foreground and background colors.
Note: This provides no syntax highlighting!
See `help gimmickcolors` for a list of some true colour themes that are more
just for fun than for serious use. ( Though feel free if you want! )
---
### Creating a Colorscheme
Micro's colorschemes are also extremely simple to create. The default ones can be found
[here](https://github.com/zyedidia/micro/tree/master/runtime/colorschemes).
They are only about 18 lines in total.
They are only about 18-30 lines in total.
Basically to create the colorscheme you need to link highlight groups with actual colors.
This is done using the `color-link` command.
@@ -85,7 +121,8 @@ If the user's terminal supports true color, then you can also specify colors exa
their hex codes. If the terminal is not true color but micro is told to use a true color colorscheme
it will attempt to map the colors to the available 256 colors.
Generally colorschemes which require true color terminals to look good are marked with a `-tc` suffix.
Generally colorschemes which require true color terminals to look good are marked with a `-tc` suffix
and colorschemes which supply a white background are marked with a `-paper` suffix.
---
@@ -96,61 +133,172 @@ Here is a list of the colorscheme groups that you can use:
* identifier
* constant
* statement
* symbol
* preproc
* type
* special
* underlined
* error
* todo
* statusline (color of the statusline)
* indent-char (color of the character which indicates tabs if the option is enabled)
* statusline ( Color of the statusline)
* tabbar ( Color of the tabbar that lists open files.)
* indent-char ( Color of the character which indicates tabs if the option is enabled)
* line-number
* gutter-error
* gutter-warning
* cursor-line
* current-line-number
* color-column
* ignore
* divider ( Color of the divider between vertical splits. )
Colorschemes can be placed in the `~/.config/micro/colorschemes` directory to be used.
### Syntax files
The syntax files specify how to highlight certain languages.
The first statement in a syntax file will probably the syntax statement. This tells micro
what language the syntax file is for and how to detect a file in that language.
Essentially, it's just
```
syntax "Name of language" "\.extension$"
```
For the extension, micro will just compare that regex to the filename and if it matches then it
will use the syntax rules defined in the remainder of the file.
There is also a possibility to use a header statement which is a regex that micro will compare
with the first line of the file. This is almost only used for shebangs at the top of shell scripts
which don't have any extension (see sh.micro for an example).
Colorschemes must be placed in the `~/.config/micro/colorschemes` directory to be used.
---
The rest of a syntax file is very simple and is essentially a list of regexes specifying how to highlight
different expressions.
In addition to the main colorscheme groups, there are subgroups that you can
specify by adding `.subgroup` to the group. If you're creating your own
custom syntax files, you can make use of your own subgroups.
It is recommended that when creating a syntax file you use the colorscheme groups (see above) to
highlight different expressions. You may also hard code colors, but that may not look good depending
on what terminal colorscheme the user has installed.
If micro can't match the subgroup, it'll default to the root group, so
it's safe and recommended to use subgroups in your custom syntax files.
Here is an example to highlight comments (expressions starting with `//`):
For example if `constant.string` is found in your colorscheme, micro will
use that for highlighting strings. If it's not found, it will use constant
instead. Micro tries to match the largest set of groups it can find in the
colorscheme definitions, so if, for examle `constant.bool.true` is found then
micro will use that. If `constant.bool.true` is not found but `constant.bool`
is found micro will use `constant.bool`. If not, it uses `constant`.
Here's a list of subgroups used in micro's built-in syntax files.
* comment.bright ( Some filetypes have distinctions between types of comments.)
* constant.bool
* constant.bool.true
* constant.bool.false
* constant.number
* constant.specialChar
* constant.string
* constant.string.url
* identifier.class ( Also used for functions. )
* identifier.macro
* identifier.var
* preproc.shebang ( The #! at the beginning of a file that tells the os what script interpreter to use. )
* symbol.brackets ( {}()[] and sometimes <> )
* symbol.operator ( Color operator symbols differently. )
* symbol.tag ( For html tags, among other things.)
* type.keyword ( If you want a special highlight for keywords like `private` )
In the future, plugins may also be able to use color groups for styling.
## Syntax files
The syntax files is written in yaml-format and specify how to highlight languages.
Micro's builtin syntax highlighting tries very hard to be sane, sensible
and provide ample coverage of the meaningful elements of a language. Micro has
syntax files built int for over 100 languages now. However, there may be
situations where you find Micro's highlighting to be insufficient or not to
your liking. Good news is you can create syntax files (.micro extension), place them in
`~/.config/micro/syntax` and Micro will use those instead.
### Filetype defintion
You must start the syntax file by declaring the filetype:
```
color comment "//.*"
filetype: go
```
This will highlight the regex `//.*` in the color that the user's colorscheme has linked to the comment
group.
#### Detect definition
Note that this regex only matches the current line. Here is an example for multiline comments (`/* comment */`):
Then you must provide information about how to detect the filetype:
```
color comment start="/\*" end="\*/"
detect:
filename: "\\.go$"
```
Micro will match this regex against a given filename to detect the filetype. You may also
provide an optional `header` regex that will check the first line of the file. For example:
```
detect:
filename: "\\.ya?ml$"
header: "%YAML"
```
#### Syntax rules
Next you must provide the syntax highlighting rules. There are two types of rules: patterns and regions.
A pattern is matched on a single line and usually a single word as well. A region highlights between two
patterns over multiple lines and may have rules of its own inside the region.
Here are some example patterns in Go:
```
rules:
- special: "\\b(break|case|continue|default|go|goto|range|return)\\b"
- statement: "\\b(else|for|if|switch)\\b"
- preproc: "\\b(package|import|const|var|type|struct|func|go|defer|iota)\\b"
```
The order of patterns does matter as patterns lower in the file will overwrite the ones defined above them.
And here are some example regions for Go:
```
- constant.string:
start: "\""
end: "\""
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:
- todo: "(TODO|XXX|FIXME):?"
- comment:
start: "/\\*"
end: "\\*/"
rules:
- todo: "(TODO|XXX|FIXME):?"
```
Notice how the regions may contain rules inside of them. Any inner rules that are matched are then skipped when searching
for the end of the region. For example, when highlighting `"foo \" bar"`, since `\"` is matched by an inner rule in the
region, it is skipped. Likewise for `"foo \\" bar`, since `\\` is matched by an inner rule, it is skipped, and then the `"`
is found and the string ends at the correct place.
You may also explicitly mark skip regexes if you don't want them to be highlighted. For example:
```
- constant.string:
start: "\""
end: "\""
skip: "\\."
rules: []
```
#### Includes
You may also include rules from other syntax files as embedded languages. For example, the following is possible
for html:
```
- default:
start: "<script.*?>"
end: "</script.*?>"
rules:
- include: "javascript"
- default:
start: "<style.*?>"
end: "</style.*?>"
rules:
- include: "css"
```

View File

@@ -5,16 +5,22 @@ Here are the possible commands that you can use.
* `quit`: Quits micro.
* `save`: Saves the current buffer.
* `save filename?`: Saves the current buffer. If the filename is provided it will
'save as' the filename.
* `replace "search" "value" flags`: This will replace `search` with `value`.
The `flags` are optional.
At this point, there is only one flag: `c`, which enables `check` mode
which asks if you'd like to perform the replacement each time
At this point, there is only one flag: `-a`, which replaces all occurrences
at once.
Note that `search` must be a valid regex. If one of the arguments
does not have any spaces in it, you may omit the quotes.
* `replaceall "search" "value"`: This will replace `search` with `value` without
user confirmation.
See `replace` command for more information.
* `set option value`: sets the option to value. See the `options` help topic
for a list of options you can set.
@@ -23,6 +29,10 @@ Here are the possible commands that you can use.
* `show option`: shows the current value of the given option.
* `eval "expression"`: Evaluates a Lua expression. Note that micro will not
print anything so you should use `messenger:Message(...)` to display a
value.
* `run sh-command`: runs the given shell command in the background. The
command's output will be displayed in one line when it finishes running.
@@ -30,19 +40,47 @@ Here are the possible commands that you can use.
keybindings above for more info about what keys and actions are available.
* `vsplit filename`: opens a vertical split with `filename`. If no filename is
provided, a vertical split is opened with an empty buffer
provided, a vertical split is opened with an empty buffer.
* `hsplit filename`: same as `vsplit` but opens a horizontal split instead of
a vertical split
a vertical split.
* `tab filename`: opens the given file in a new tab.
* `tabswitch tab`: This command will switch to the specified tab.
The `tab` can either be a tab number, or a name of a tab.
* `log`: opens a log of all messages and debug statements.
* `plugin install plugin_name`: installs the given plugin.
* `plugin remove plugin_name`: removes the given plugin.
* `plugin list`: lists all installed plugins.
* `plugin update`: updates all installed plugins.
* `plugin search plugin_name`: searches for the given plugin.
Note that you can find a list of all available plugins at
github.com/micro-editor/plugin-channel.
You can also see more information about the plugin manager
in the `Plugin Manager` section of the `plugins` help topic.
* `plugin available`: list plugins available for download (this includes
any plugins that may be already installed).
* `reload`: reloads all runtime files.
* `cd path`: Change the working directory to the given `path`.
* `pwd`: Print the current working directory.
* `open filename`: Open a file in the current buffer.
---
The following commands are provided by the default plugins:
* `lint`: Lint the current file for errors.
* `gofmt`: Run gofmt on the current file.
* `goimports`: Run goimports on the current file.

126
runtime/help/defaultkeys.md Normal file
View File

@@ -0,0 +1,126 @@
# Default Keys
Below are simple charts of the default hotkeys and their functions.
For more information about binding custom hotkeys or changing
default bindings, please run `> help keybindings`
Please remember that *all* keys here are rebindable!
If you don't like it, you can change it!
# Power user
| Key | Description of function |
|-------- |-------------------------------------------------------------------------------------------------- |
| Ctrl+E | Open a command prompt for running commands (see `> help commands` for a list of valid commands). |
| Tab | In command prompt, it will autocomplete if possible. |
| Ctrl+B | Run a shell command (this will close micro while your command executes). |
# Navigation
| Key | Description of function |
|-------------------------- |------------------------------------------------------------------------------------------ |
| Arrows | Move the cursor around |
| Shift+arrows | Move and select text |
| Home or CtrlLeftArrow | Move to the beginning of the current line |
| End or CtrlRightArrow | Move to the end of the current line |
| AltLeftArrow | Move cursor one word left |
| AltRightArrow | Move cursor one word right |
| PageUp | Move cursor up one page |
| PageDown | Move cursor down one page |
| CtrlHome or CtrlUpArrow | Move cursor to start of document |
| CtrlEnd or CtrlDownArrow | Move cursor to end of document |
| Ctrl+L | Jump to a line in the file (prompts with #) |
| Ctrl+W | Cycle between splits in the current tab (use `> vsplit` or `> hsplit` to create a split) |
# Tabs
| Key | Description of function |
|-------- |------------------------- |
| Ctrl+T | Open a new tab |
| Alt+, | Previous tab |
| Alt+. | Next tab |
# Find Operations
| Key | Description of function |
|-------- |------------------------------------------ |
| Ctrl+F | Find (opens prompt) |
| Ctrl+N | Find next instance of current search |
| Ctrl+P | Find previous instance of current search |
# File Operations
| Key | Description of function |
|-------- |---------------------------------------------------------------- |
| Ctrl+Q | Close current file (quits micro if this is the last file open) |
| Ctrl+O | Open a file (prompts for filename) |
| Ctrl+S | Save current file |
# Text operations
| Key | Description of function |
|--------------------------------- |------------------------------------------ |
| AltShiftRightArrow | Select word right |
| AltShiftLeftArrow | Select word left |
| ShiftHome or CtrlShiftLeftArrow | Select to start of current line |
| ShiftEnd or CtrlShiftRightArrow | Select to end of current line |
| CtrlShiftUpArrow | Select to start of file |
| CtrlShiftDownArrow | Select to end of file |
| Ctrl+X | Cut selected text |
| Ctrl+C | Copy selected text |
| Ctrl+V | Paste |
| Ctrl+K | Cut current line |
| Ctrl+D | Duplicate current line |
| Ctrl+Z | Undo |
| Ctrl+Y | Redo |
| AltUpArrow | Move current line or selected lines up |
| AltDownArrow | Move current line of selected lines down |
| AltBackspace or AltCtrl+H | Delete word left |
| Ctrl+A | Select all |
# Macros
| Key | Description of function |
|-------- |---------------------------------------------------------------------------------- |
| Ctrl+U | Toggle macro recording (press Ctrl+U to start recording and press again to stop) |
| Ctrl+J | Run latest recorded macro |
# Multiple cursors
| Key | Description of function |
|---------------- |---------------------------------------------------------------------------------------------- |
| Alt+N | Create new multiple cursor from selection (will select current word if no current selection) |
| Alt+P | Remove latest multiple cursor |
| Alt+C | Remove all multiple cursors (cancel) |
| Alt+X | Skip multiple cursor selection |
| Ctrl-MouseLeft | Place a multiple cursor at any location |
# Other
| Key | Description of function |
|-------- |----------------------------------------------------------------------------------- |
| Ctrl+G | Open help file |
| Ctrl+H | Backspace (old terminals do not support the backspace key and use Ctrl+H instead) |
| Ctrl+R | Toggle the line number ruler |
# Emacs style actions
| Key | Description of function |
|------- |------------------------- |
| Alt+F | Next word |
| Alt+B | Previous word |
| Alt+A | Move to start of line |
| Alt+E | Move to end of line |
# Function keys.
Warning! The function keys may not work in all terminals!
| Key | Description of function |
|----- |------------------------- |
| F1 | Open help |
| F2 | Save |
| F3 | Find |
| F4 | Quit |
| F7 | Find |
| F10 | Quit |

View File

@@ -0,0 +1,14 @@
# Gimmick colors
We have included a few colorschemes that are for fun:
* funky-cactus: I don't know why I made this. (Written by CaptainMcClellan)
* gameboy-tc: Colorscheme based on the olive green original Gameboy!
* nes-tc: A colorscheme and syntax highlighting using only colors in the
Nintendo Entertainment System color palette.
* symbian-tc: Colorscheme based on SymbOS's GUI.
* matrix: Pretend it's 1981 with a colorscheme based on a monochrome
IBM 5151. ( Does not include the ghosting and trailing. )
Check the plugin repo periodically for gimmick-color extension packs
and genuine additional themes.

View File

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

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