Compare commits

...

250 commits

Author SHA1 Message Date
Dmytro Maluka
9eb8782ff2
Rework FindMatchingBrace() interface and implementation (#3319)
Instead of passing a single brace pair to FindMatchingBrace(), make it
traverse all brace pairs in buffer.BracePairs on its own.

This has the following advantages:

1. Makes FindMatchingBrace() easier to use, in particular much easier
   to use from Lua.

2. Lets FindMatchingBrace() ensure that we use just one matching brace -
   the higher-priority one. This fixes the following issues:

    ([foo]bar)
     ^

when the cursor is on `[`:

- Both `[]` and `()` pairs are highlighted, whereas the expected
  behavior is that only one pair is highlighted - the one that the
  JumpToMatchingBrace action would jump to.

- JumpToMatchingBrace action incorrectly jumps to `)` instead of
  `]` (which should take higher priority in this case).

In contrast, with `((foo)bar)` it works correctly.
2024-06-05 00:56:19 +02:00
Massimo Mund
46e55c8e91
Fixed trailing line spaces being ignored by word- or subword-jumps (#3321) 2024-06-04 21:10:09 +02:00
Neko Box Coder
dd913df9e9
Reordered prompt done callback to avoid accessing out of bound history (#3318)
* Reordered prompt done callback to avoid accessing out of bound history

* Formatting
2024-06-02 20:00:13 +02:00
Jöran Karl
e9bd1b35f4
Merge pull request #3270 from niten94/sh-break-continue
Add, move commands in shell syntax file
2024-05-22 22:21:06 +02:00
niten94
4911a56181 Add commands in shell syntax file
Add `break`, `command`, `continue`, `eval`, `exec`, `getopt`, `getopts`,
`trap` and `wait` command in shell syntax file.
2024-05-23 00:59:58 +08:00
niten94
343812bd2e Change color of commands in shell syntax file
Move `local`, `read`, `shift` and `time` to "Shell commands" in shell
syntax file.
2024-05-23 00:49:13 +08:00
Jöran Karl
35630aa736
Merge pull request #2665 from masmu/feature/sub-words
Implemented sub-word cursor movement
2024-05-22 06:24:19 +02:00
Massimo Mund
78fcf2fc31 Updated WordLeft() and WordRight() behavior to be in line with SubWordLeft() and SubWordRight() 2024-05-20 23:23:33 +02:00
Massimo Mund
5dbdf8c0e8 Implemented SubWordRight, SubWordLeft, SelectSubWordRight, SelectSubWordLeft and DeleteSubWordRight, DeleteSubWordLeft 2024-05-20 23:23:33 +02:00
Massimo Mund
889a841575 Replaced IsNonAlphaNumeric() with IsNonWordChar() 2024-05-20 23:23:33 +02:00
Dmytro Maluka
917650826a
Merge pull request #3291 from dmaluka/diffgutter-cleanup
Diffgutter: simplify + fix race
2024-05-14 18:03:07 +02:00
Dmytro Maluka
b70f0eb113
Add onAnyEvent callback (#3244)
Implement a radical approach to improving abilities of plugins to detect
and handle various changes of micro's state: add onAnyEvent callback
which is called, literally, after any event. A plugin can use this
callback to compare a state after the previous event and after the
current event, and thus is able to catch various events that cannot be
detected using other callbacks.

Some examples of such events:

- change of current working directory
- switching cursor focus between a bufpane and the command bar
- change of message text in the status bar
2024-05-14 18:01:15 +02:00
Dmytro Maluka
5a159ce444 updateDiffSync(): fix potential race
When updateDiffSync() is called asynchronously, it should lock the
line array when calling Bytes(), to prevent race if the line array is
being modified by the main goroutine in the meantime.
2024-05-12 21:07:12 +02:00
Dmytro Maluka
bca35a5939 Simplify UpdateDiff() interface
The callback passed to UpdateDiff() is superfluous: in the synchronous
case screen.Redraw() is not needed anyway (since the screen is redrawn
at every iteration of the main loop), and in the asynchronous case
UpdateDiff() can just call screen.Redraw() directly.
2024-05-12 20:05:14 +02:00
Jöran Karl
1f51d0b9e2
Merge pull request #3271 from JoeKar/fix/inactive-mouse-release
Fix lost mouse release events in case the pane becomes inactive
2024-04-27 23:22:57 +02:00
Jöran Karl
0a1447b688 action: tab: Stop resize in case of mouse release while not pressed 2024-04-27 21:38:02 +02:00
Jöran Karl
2ecdac8405 action: tab: Release mouse press in case of mouse release while not pressed 2024-04-27 21:37:59 +02:00
Jöran Karl
385437d400
Merge pull request #3266 from JoeKar/fix/keysequence-comparison
bindings: Correct `KeySequenceEvent` comparison (fix crash)
2024-04-26 17:37:19 +02:00
Jöran Karl
1c35f3dc39
Merge pull request #3261 from JoeKar/fix/command-term
action: Stop processing chained actions/commands in the moment the current `Pane` is not a `BufPane` (fix crash)
2024-04-26 17:36:12 +02:00
Jöran Karl
07cda68795 initlua: Correct return type of CurPane() to be of type *BufPane 2024-04-25 23:34:39 +02:00
Jöran Karl
3919cf399f action: Provide Name() to treat TermPane as Pane
This will add the capability to address the `TermPane` within the tabs, since
the tab list only stores panes.
2024-04-25 23:34:39 +02:00
Jöran Karl
b05df07df2 bindings: Small refactoring of TryBindKey() for better readability 2024-04-25 23:21:52 +02:00
Jöran Karl
8af890a0a3 bindings: Correct KeySequenceEvent comparison
We've to iterate over the included elements,
since slices can't be simply compared with the comparison operators.
2024-04-25 23:20:30 +02:00
Dmytro Maluka
ff5b147639
Merge pull request #3267 from dmaluka/dokeyevent-improvements
Small fixes and improvements for InfoPane's key event handling
2024-04-25 21:59:44 +02:00
Dmytro Maluka
3f810c24d2
Fix Deselect() after mouse selection (#3268)
Ensure that the selection start is always before the selection end,
regardless of the direction of a mouse selection, to make
h.Cursor.Deselect() handle its `start` argument correctly.

This makes the cursor behavior after mouse selections consistent with
the cursor behavior after keyboard selections.

Fixes #3055
2024-04-25 21:58:40 +02:00
Jöran Karl
26fa15c147 action: Stop action iteration in the moment the current pane isn't a BufPane 2024-04-25 18:07:03 +02:00
Yevhen Babiichuk (DustDFG)
147943837d
Fix cursor moving down when selection exist. Solves (#3087) (#3091)
Previously `CursorDown` function called `Deselect` with a wrong
argument which lead to the situation when cursor was moved to the
start instead of the end of the selection

Signed-off-by: Yevhen Babiichuk (DustDFG) <dfgdust@gmail.com>
2024-04-25 02:27:41 +02:00
Dmytro Maluka
24406a5ae8 Comment plugin: doc: fix incorrect keybinding 2024-04-25 01:28:34 +02:00
Dmytro Maluka
8632b82cbe infopane: DoKeyEvent: it is buggy, let's add a TODO for now 2024-04-25 00:30:41 +02:00
Dmytro Maluka
fade304667 infopane: HandleEvent: refactor y/n prompt handling 2024-04-25 00:13:37 +02:00
Dmytro Maluka
5b3737fb2a infopane: HandleEvent: reset key sequence when handling y/n prompt
Fix the following buggy behavior:

1. bind "<n><a>" to the Paste action in the command bar
2. open a split pane, type some text and press Ctrl-q to close it
3. answer "n" to the "Save changes before closing?" prompt
4. press Ctrl-e to open the command prompt and press "a"

-> result: instead of inserting the "a" letter, clipboard is pasted.
2024-04-25 00:05:27 +02:00
Dmytro Maluka
36bf3f6619 DoKeyEvent: document return value
The return value of DoKeyEvent() has a dual meaning, which makes the
code not obvious and confusing. So at least document it.
2024-04-24 23:21:28 +02:00
Dmytro Maluka
8c7f63ac15 infopane: DoKeyEvent: ignore action return value
It is not really defined what is the meaning of this return value.
Currently this value is always true. And even if this value actually
meant something (for example, the result of the last executed action
in the chain), we should not use this value in HandleEvent(). The key
event handling logic should behave the same regardless of whether the
action triggered by this key succeeded or not.
2024-04-24 22:51:27 +02:00
Dmytro Maluka
18f3e1bf89
Merge pull request #3245 from dmaluka/onsetactive-fix
Fix issues with `onSetActive` callback
2024-04-23 21:28:03 +02:00
Dmytro Maluka
e48575f349
Add onBufPaneOpen error checking (#3246)
If onBufPaneOpen callback execution fails (e.g. due to a Lua runtime
error), report this error to the user, like we do for all other Lua
callbacks, rather than silently continue working as if nothing
happened.
2024-04-23 21:23:25 +02:00
Dmytro Maluka
eec068a4fc
help/colors: syntax: document default.yaml (#3262) 2024-04-23 21:21:51 +02:00
Dmytro Maluka
5510317942
Relocate buffer view when reloading file (#3250)
After reloading a file that has been externally modified, the buffer
view may become invalid: the displayed subset of lines of the file may
no longer exist, since the file may have been truncated. So relocate the
buffer view in this case.

In particular, this fixes crashes caused by out of bounds accesses to
the line array by displayBuffer() trying to display no longer existing
lines.
2024-04-21 22:49:01 +02:00
Dmytro Maluka
169a9a65fa
Merge pull request #3259 from dmaluka/default-syntax-followup
Follow-ups after adding `default.yaml` support
2024-04-21 22:48:33 +02:00
Jöran Karl
c3052b491f
parser: Check and prompt for empty patterns and region properties (fix crash) (#3256)
* parser: Precise error message for missing `start` & `end` in region

* parser: Check and prompt for empty patterns and region properties

* syntax: Remove empty identifier pattern from log definition
2024-04-21 20:13:28 +02:00
Dmytro Maluka
b929c61228 help/colors: syntax: document that nested includes are not supported 2024-04-21 15:41:49 +02:00
Dmytro Maluka
08c516c730 UpdateRules: optimize out HasIncludes() usage 2024-04-21 15:14:21 +02:00
Dmytro Maluka
1bddc8d03e UpdateRules: move include logic to a helper function 2024-04-21 15:13:03 +02:00
Jöran Karl
f9cad2e448
action: Fix the duplication of the unknown filetype (#3258) 2024-04-19 06:01:27 +02:00
Dmytro Maluka
3aed20fde9 UpdateRules: correct the comments
The "runtime" term is ambiguous: it refers to both built-in and user's
custom ("real runtime") files.
2024-04-19 00:10:58 +02:00
Dmytro Maluka
a436dae587 UpdateRules: allow includes in default.yaml 2024-04-18 23:29:33 +02:00
Dmytro Maluka
5610d01e08 UpdateRules: fix set filetype unknown
Fix `set filetype unknown` not working as expected in the following
scenario:

1. open foo.txt (no filetype detected) -> ft is `unknown`, highlighted
   with default.yaml, as expected

2. `set filetype go` -> ft is `go`, highlighted with go.yaml as expected

3. `set filetype unknown` -> ft is still `go`, still highlighted with
   go.yaml (whereas expected behavior is: ft is `unknown`, highlighted
   with default.yaml)

Fix that by always updating b.SyntaxDef value, not reusing the old one.

This also makes the code simpler and easier to understand.
2024-04-18 22:39:16 +02:00
Jöran Karl
0806addbd7
Merge pull request #2933 from JoeKar/feature/default-syntax
syntax: Provide default.yaml as fallback definition
2024-04-18 19:38:35 +02:00
Jöran Karl
6cd39efddc buffer: Refactor UpdateRules() by creating further helper functions
- `findRealRuntimeSyntaxDef()`
- `findRuntimeSyntaxDef()`

This will reduce the length of this function again and thus improves the
readability.
2024-04-18 18:33:00 +02:00
Jöran Karl
089160a7e4 buffer: Refactor UpdateRules() by creating parseDefFromFile()
This will reduce the length of this function and thus improves the
readability.
2024-04-18 18:29:52 +02:00
Jöran Karl
ed993a4021 buffer: Precise comment about searching in the internal runtime files 2024-04-18 18:20:11 +02:00
Jöran Karl
4cafa601b5 syntax: Optimize the patterns and remove the comment region 2024-04-18 18:20:11 +02:00
Jöran Karl
87ee41ab27 buffer: Don't process the default syntax in the user's custom file lookup
It needs to be processed earliest in the moment no match could be determined.
2024-04-18 18:20:08 +02:00
Kevin Klement
8d8bc58f91
Update html.yaml by adding support for dialog tags (#3255)
Another relatively new tag but commonly used
2024-04-18 14:36:19 +02:00
Jöran Karl
6ffabd626f buffer: Let the user override the default.yaml 2024-04-17 18:10:15 +02:00
Jöran Karl
2c53d1fcab test: Perform DoEvent() as long as normal or draw events are present
This is necessary since DoEvent() isn't called in a loop like in the main
application, but as one-shot only and a async draw event can lead to ignore
the explicit injected events.
Additional checks have been added to check the presence of the expected buffers.
2024-04-14 16:55:59 +02:00
Jöran Karl
f265179def buffer: Correct error message in case of failed read 2024-04-14 16:55:59 +02:00
Jöran Karl
390794213e syntax: Provide default.yaml as fallback definition 2024-04-14 16:55:59 +02:00
Jöran Karl
430da61314 highlighter: Remove EmptyDef since it's superseeded by a nil check of SyntaxDef 2024-04-14 16:55:59 +02:00
matthias314
f386b29e16
add public keyword to Julia syntax file (#3247) 2024-04-13 14:47:42 +02:00
Dmytro Maluka
4283881591 onSetActive doc: move it
Cosmetic change: move onSetActive description to keep it together with
other callbacks that are associated with bufpane, not with buffer.
2024-04-12 02:33:16 +02:00
Dmytro Maluka
186817d0c4 onSetPane doc: s/panel/bufpane/
It is pane, not panel. Also, let's call it bufpane here, like we do in
other callbacks' descriptions.
2024-04-12 02:30:56 +02:00
Dmytro Maluka
2a1790d15a Don't call onSetActive for an already active pane
Currently onSetActive is called when the user clicks with the mouse on
a pane even if this pane is already active. We should avoid calling it
in this case.

Implementation detail: like with tabs in the previous commit, we cannot
check if the pane is already active just by checking the index passed
to the Tab's SetActive() (since the index may not change while the pane
itself changes), we need to check state of the pane itself. So we move
the onSetActive invocation from the Tab's SetActive() to the BufPane's
SetActive().
2024-04-12 02:21:03 +02:00
Dmytro Maluka
c6dc5a4b1f Call onSetActive when switching to another tab
We should call the onSetActive callback not only when switching to
another bufpane within the same tab but also when switching to another
tab.

Note on implementation details:

- In SetActive() we need to check if the tab is not already active, to
  avoid calling onSetActive for an already active bufpane.

- We cannot check that just by checking if the tab index passed to
  SetActive() is different from the current active tab index, since this
  index may remain the same even if the tab itself is different (in the
  case of removing a tab from the tablist). So we need to check the tab
  itself, not just the tab index. So we introduce the isActive field,
  to track the tab's active state in the Tab structure itself.
2024-04-12 02:07:29 +02:00
Jöran Karl
426aa9bb8b
command: Prevent re-writing settings in case of local option (#3178)
* command: Prevent re-writing settings in case of local option

* command: Refactor SetGlobalOptionNative()

Co-authored-by: Dmitry Maluka <dmitrymaluka@gmail.com>

---------

Co-authored-by: Dmitry Maluka <dmitrymaluka@gmail.com>
2024-04-11 18:35:13 +02:00
Dmytro Maluka
acb0d763df
ReHighlightStates: sanity-check startline value (#3237)
Check if startline value is valid before passing it to input.State(),
to prevent a theoretically possible race when the number of lines
changes in the meantime, causing an out of bounds access.

Actually this race cannot happen: ReHighlightStates() is only called
from the main goroutine, and the line array is modified, again, only by
the main goroutine. So for now this change is rather cosmetic: it is
just to make the highligher API implementation self-sufficiently safe
without assumptions about which goroutines are using which API functions
and how.
2024-04-09 00:31:01 +02:00
lvyaoting
d1d38d1ed7
chore: fix some typos (#3239)
Signed-off-by: lvyaoting <lvyaoting@outlook.com>
2024-04-08 12:04:38 +02:00
Jöran Karl
467c71dbb8
Merge pull request #3224 from JoeKar/fix/line-synchronization
buffer: Add proper lock mechanism to lock the full `LineArray` instead of single lines
2024-04-06 00:09:17 +02:00
Jöran Karl
a3ca054371 buffer: Uncomment InitRuntimeFiles(false) in the buffer_test.go
...since we fixed the race between the syntax highlighting and the buffer
editing.
2024-04-05 14:24:59 +02:00
Jöran Karl
b6dcbfa846 highlighter: Fix race between the async highlighter and the main routine
This is achieved by the usage of the new `LineArray` locking machanism,
which prevents the interruption in the moment of modifications like insertion
or removal of lines.

Co-authored-by: Dmytro Maluka <dmitrymaluka@gmail.com>
2024-04-05 14:24:39 +02:00
Jöran Karl
6e71e37568 buffer: Rename LineBytes parameter to "lineN" to fit to the rest 2024-04-05 14:24:39 +02:00
Jöran Karl
dd7134a762 buffer: Remove superfluous rehighlight from LineArray
...which isn't used so far and probably handled better in a different way.
2024-04-05 14:24:39 +02:00
Jöran Karl
2830c4878e buffer: Lock the LineArray in case of modifications and export this lock 2024-04-05 14:24:06 +02:00
Jöran Karl
53d56d032c buffer: Remove unneeded recursion of insert()
This is necessary as a preparation to introduce a lock for the whole LineArray.
The modification can then be done without trying to lock the same lock twice.

Co-authored-by: Dmytro Maluka <dmitrymaluka@gmail.com>
2024-04-05 14:19:37 +02:00
Jöran Karl
c493e14eb4
Merge pull request #3220 from dmaluka/tests-rtfiles-fix
Don't initialize plugins and user settings in tests
2024-04-05 11:24:40 +02:00
Dmytro Maluka
69dc54b407 Temporarily don't initialize runtime files in buffer test
Adding InitRuntimeFiles() to buffer_test.go has changed the behavior
of this test: now it tests not just buffer editing per se, but also
how well buffer editing works together with syntax highlighting (since
InitRuntimeFiles() loads syntax files, and many of the test buffers
match the json header pattern in the json.yaml syntax file, so they are
"highlighted" as json). This revealed long existing races between
buffer editing and syntax highlighting.

Until we fix those races, temporarily disable InitRuntimeFiles() in this
test.
2024-04-03 04:37:44 +02:00
Dmytro Maluka
c5d32f625b Ignore user-defined runtime files in buffer test and rtfiles test
When initializing runtime files (syntax files etc) in tests, initialize
built-in runtime files only, to ensure that the tests are not affected
by whatever is in ~/.config/micro/ on the test machine.

micro_test.go already ensures that, by using its own temporary directory
as an (empty) config directory. So we only need to fix buffer_test.go
and rtfiles_test.go. In those tests, don't repeat the same dance with
a temporary directory, instead just ignore the config directory.
2024-04-03 03:44:15 +02:00
Dmytro Maluka
baca0e5cb2 Add param to InitRuntimeFiles() to init built-in files only 2024-04-03 03:41:06 +02:00
Dmytro Maluka
d67ce731ed Don't initialize plugins in buffer test and rtfiles test
Adding InitPlugins() to tests has caused noisy error logs when running
the buffer_test.go test (although the test result is still PASS):

2024/03/23 15:14:30 Plugin does not exist: autoclose at autoclose : &{autoclose autoclose <nil> [runtime/plugins/autoclose/autoclose.lua] false true}
2024/03/23 15:14:30 Plugin does not exist: comment at comment : &{comment comment <nil> [runtime/plugins/comment/comment.lua] false true}
2024/03/23 15:14:30 Plugin does not exist: diff at diff : &{diff diff <nil> [runtime/plugins/diff/diff.lua] false true}
2024/03/23 15:14:30 Plugin does not exist: ftoptions at ftoptions : &{ftoptions ftoptions <nil> [runtime/plugins/ftoptions/ftoptions.lua] false true}
...

These errors are caused simply by the fact that plugins are initialized
but not loaded. Adding config.LoadAllPlugins() to buffer_test.go "fixes"
this problem.

However, at the moment it doesn't seem a good idea to load plugins in
buffer_test.go, since buffer_test.go doesn't properly initialize Lua. It
only does ulua.L = lua.NewState() but doesn't do the other stuff that
init() in cmd/micro/initlua.go does. As a result, plugins will not be
able to do anything correctly.

So in order to initialize Lua correctly we need to be inside cmd/micro/,
so we cannot do it in buffer_test.go or any other tests except
micro_test.go.
2024-04-03 03:04:42 +02:00
Dmytro Maluka
828871acdf
Improve crontab filetype detection (#3222)
Support crontab filetype detection in the case crontab is opened via
sudoedit. Also apparently this fixes crontab filetype detection when
it is opened normally via `crontab -e` but in MacOS.

Fixes #3172
2024-04-01 19:50:42 +02:00
Dmytro Maluka
dc833d3552 Check for missing or empty filetype in syntax files
To avoid surprises like with jsonnet.
2024-03-28 01:22:25 +01:00
Dmytro Maluka
08028cf415 s/filename/filetype/ in jsonnet syntax file
This typo causes a funny bug: the autodetected filetype for *.jsonnet
files is an empty string instead of "jsonnet".

Even funnier, when autocompleting "set filetype " (with the fix from
PR #3218), the first suggested filetype is this empty string.
2024-03-28 01:22:25 +01:00
Dmytro Maluka
93dd8ca729 help/colors: s/line/file/ 2024-03-28 01:20:16 +01:00
Jöran Karl
3d7024e059
infocomplete: Complete filetypes (follow-up) (#3218)
* infocomplete: Complete filetypes (follow-up)

The first shot of the feature unfortunately completed the *.yaml file
names instead of the included filetypes. This will be corrected with
this follow up.

* infocomplete: Correct comment of filetypeComplete according to review hint

Co-authored-by: Dmytro Maluka <dmitrymaluka@gmail.com>

---------

Co-authored-by: Dmytro Maluka <dmitrymaluka@gmail.com>
2024-03-27 18:58:12 +01:00
blt-r
b291f27c3f
Add missing <release> entries in metainfo file (#3170)
* Add missing <release> entries in metainfo file

* Fix date

* Fix release date
2024-03-26 19:11:56 +01:00
Jöran Karl
3903859970
command: Add jump to perform a relative goto (#3210)
* help: Precise `goto` command documentation

* command: Add `jump` to perform a relative `goto`

* command: Refactor GotoCmd() and JumpCmd()
2024-03-25 21:16:23 +01:00
Dmytro Maluka
839e86849e
Update color groups documentation (#3203) 2024-03-25 21:06:06 +01:00
Dmytro Maluka
20bf7096b8
Make set filetype off work as expected (#3216)
Disable syntax highlighting after setting filetype to `off`.
2024-03-25 19:38:33 +01:00
Dmytro Maluka
d96f060b4c
Merge pull request #3214 from dmaluka/filetype-autocomplete-unknown
Autocomplete `unknown` value in `set filetype ...`
2024-03-25 19:38:10 +01:00
Dmytro Maluka
08892b125f
Fix crash when syntax file has no rules (#3213)
If a syntax file aaa.yaml contains no `rules` directive, then after
`set filetype aaa` micro crashes with d.rules nil pointer dereference
in HasIncludes():

Micro encountered an error: runtime.errorString runtime error: invalid memory address or nil pointer dereference
runtime/panic.go:221 (0x44c527)
runtime/panic.go:220 (0x44c4f7)
github.com/zyedidia/micro/v2/pkg/highlight/parser.go:239 (0x820919)
github.com/zyedidia/micro/v2/internal/buffer/buffer.go:830 (0x82b818)
github.com/zyedidia/micro/v2/internal/buffer/settings.go:33 (0x83b665)
github.com/zyedidia/micro/v2/internal/action/command.go:578 (0x87d75f)
github.com/zyedidia/micro/v2/internal/action/command.go:598 (0x87da79)
github.com/zyedidia/micro/v2/internal/action/command.go:634 (0x87de54)
github.com/zyedidia/micro/v2/internal/action/command.go:1030 (0x880f68)
github.com/zyedidia/micro/v2/internal/action/actions.go:1545 (0x870d72)
github.com/zyedidia/micro/v2/internal/info/infobuffer.go:152 (0x8421b4)
github.com/zyedidia/micro/v2/internal/action/infopane.go:208 (0x8854cc)
github.com/zyedidia/micro/v2/internal/action/infopane.go:54 (0x8844d6)
github.com/zyedidia/micro/v2/internal/action/infopane.go:131 (0x884d42)
github.com/zyedidia/micro/v2/internal/action/infopane.go:95 (0x8849ff)
github.com/zyedidia/micro/v2/cmd/micro/micro.go:481 (0x8bfb86)
github.com/zyedidia/micro/v2/cmd/micro/micro.go:397 (0x8bf63e)
runtime/proc.go:255 (0x438867)
runtime/asm_amd64.s:1581 (0x467a81)
2024-03-25 19:35:57 +01:00
Dmytro Maluka
838f371486
Revert "Don't expose Go timers directly to lua" (#3211)
* Revert "Don't expose Go timers directly to lua"

This reverts commit 4ffc2206ee.

Reason for revert: some plugins happen to use raw Go timers via
time.AfterFunc(), in an unsafe way (without synchronizing their
async code with micro). Let them keep doing that for now, in an
unsafe way but at least without immediate crashes.

Fixes #3209

* Add TODO about Go timers deprecation
2024-03-25 17:11:12 +01:00
Dmytro Maluka
fc7efbdbe9
Merge pull request #3212 from dmaluka/help-misc-improvements
Misc documentation improvements
2024-03-25 03:31:02 +01:00
Dmytro Maluka
2ab1b3132e
Merge pull request #3208 from dmaluka/restore-header-matches
Reintroduce `header` patterns for filetype detection
2024-03-25 03:30:32 +01:00
Dmytro Maluka
d64c9443f5 Autocomplete off value as well
It is also a documented special value of the `filetype` option.
2024-03-25 03:25:13 +01:00
Dmytro Maluka
ee6519f5cb Autocomplete unknown value in set filetype ...
`unknown` is a valid value for the `filetype` option (and executing
`set filetype unknown` does what is expected: it forces filetype
autodetection). So let's add `unknown` to the autocomplete suggestions
for `filetype`, along with actual filetypes.
2024-03-24 22:35:17 +01:00
Dmytro Maluka
984c32b513 help/colors: add break before paragraph about colorscheme includes
Make it a well-visible subsection of the "Creating a Colorscheme"
section.
2024-03-24 19:56:26 +01:00
Dmytro Maluka
1595c5ddda help/colors: remove "Syntax file headers" section
The section says that users may use their own .hdr files for their own
custom syntax files, which is simply not true.

As a matter of fact, .hdr files are an implementation detail that
doesn't need to be mentioned in the user documentation.
2024-03-24 19:50:30 +01:00
Dmytro Maluka
1021f61a81 syntax: remove some commented out garbage 2024-03-24 15:22:43 +01:00
Dmytro Maluka
053949eac6 UpdateRules: de-densify code arouns signatureMatch
Purely cosmetic change: make the code a bit more readable by reducing
its visual "density".
2024-03-24 04:47:04 +01:00
Dmytro Maluka
9ee82a6cb3 UpdateRules: rename syntaxFileBuffer to syntaxFileInfo
To make it more clear. Why Buffer?
2024-03-24 04:47:04 +01:00
Dmytro Maluka
66a3839589 Update and clarify documentation on filetype detection patterns 2024-03-24 04:47:04 +01:00
Dmytro Maluka
b2a428f1cd Restore header instead of signature in most syntax files
Turning `header` patterns into `signature` patterns in all syntax files
was a mistake. The two are different things. In almost all syntax files
those patterns are things like shebangs or <?xml ... ?> or
<!DOCTYPE html5> i.e. things that:

1. can be (and should be) used for detecting the filetype when there is
   no `filename` match (and that is actually the purpose of those
   patterns, so it's a regression that it doesn't work anymore).

2. should only occur in the first line of the file, not in the first
   100 lines or so.

In other words, the old `header` semantics was exactly what was needed
for those filetypes, while the new `signature` semantics makes little
sense for them.

So replace `signature` back with `header` in most syntax files. Keep
`signature` only in C++ and Objective-C syntax files, for which it was
actually introduced.
2024-03-24 04:47:04 +01:00
Dmytro Maluka
5492d30953 UpdateRules: add comment about the reason for signature match 2024-03-24 04:47:04 +01:00
Dmytro Maluka
6c3b5ad17c UpdateRules: refactor "header.FileType == ft" case 2024-03-24 04:47:04 +01:00
Dmytro Maluka
39e410aa46 UpdateRules: reintroduce using header regex for filetype detection
Replacing header patterns with signature patterns was a mistake, since
both are quite different from each other, and both have their uses. In
fact, this caused a serious regression: for such files as shell scripts
without *.sh extension but with #!/bin/sh inside, filetype detection
does not work at all anymore.

Since both header and signature patterns are useful, reintroduce support
for header patterns while keeping support for signature patterns as well
and make both work nicely together.

Also, unlike in the old implementation (before signatures were
introduced), ensure that filename matches take precedence over header
matches, i.e. if there is at least one filename match found, all header
matches are ignored. This makes the behavior more deterministic and
prevents previously observed issues like #2894 and #3054: wrongly
detected filetypes caused by some overly general header patterns.

Precisely, the new behavior is:

1. if there is at least one filename match, use filename matches only
2. if there are no filename matches, use header matches
3. in both cases, try to use signatures to find the best match among
multiple filename or header matches
2024-03-24 04:47:04 +01:00
Dmytro Maluka
3f4942cedb syntax parser: reintroduce header regex in .hdr files
Replacing header patterns with signature patterns was a mistake, since
both have their own uses. So restore support for header regex, while
keeping support for signature regex as well.
2024-03-24 04:47:04 +01:00
Dmytro Maluka
2b8d925925 UpdateRules: rename syntaxFiles to fnameMatches
As a preparation for reintroducing header matches.
2024-03-24 04:47:04 +01:00
Dmytro Maluka
0c923aa156 UpdateRules: don't call highlight.ParseFile() needlessly
No need to parse a syntax YAML file if we are not going to use it,
it's a waste of CPU cycles.
2024-03-24 04:47:04 +01:00
Dmytro Maluka
13483602d5 UpdateRules: fix foundDef logic
The original meaning of foundDef was: "we already found the final syntax
definition in a user's custom syntax file". After introducing signatures
its meaning became: "we found some potential syntax definition in a
user's custom syntax file, but we don't know yet if it's the final one".
This makes the code confusing and actually buggy.

At least one bug is that if we found some potential filename matches in
the user's custom syntax files, we don't search for more matches in the
built-in syntax files. Which is wrong: we should keep searching for as
many potential matches as possible, in both user's and built-in syntax
files, to select the best one among them.

Fix that by restoring the original meaning of foundDef and updating the
logic accordingly.
2024-03-24 04:47:04 +01:00
Jöran Karl
c2c2b2addf
chore: remove repetitive words (follow-up) (#3207) 2024-03-23 20:40:15 +01:00
occupyhabit
8b4e9d2c5e
chore: remove repetitive words (#3205)
Signed-off-by: occupyhabit <wangmengjiao@outlook.com>
2024-03-23 17:02:41 +01:00
Jöran Karl
a57d29ada9
command: Fix reload command to correctly initialize and reload all runtime files (#3062)
* rtfiles: Initialize all-/realFiles and Plugins in InitRuntimeFiles

* command: Reload plugins at ReloadCmd too

* command: Don't reload plugins in case of ReloadConfig()

* rtfiles: Split InitRuntimeFiles() into one func for assets and one for plugins

* rtfiles: Remove the unnecessary init function

With this modification the InitRuntimeFiles() and InitPlugins() (if needed)
must be called first, otherwise uninitialized runtime file variables are most
likely.
2024-03-22 20:47:30 +01:00
Jöran Karl
bb1f4dad77
help: Exchange all indentations to spaces, remove trailing ws and generalize indentations (#3193)
* help: Exchange all indentations to spaces and remove trailing ws

* Add some missing `` marks

Co-authored-by: Jöran Karl <3951388+JoeKar@users.noreply.github.com>

* help: Generalize indentation levels

* help: Some small visual changes

- removed some superfluous whitespaces
- add a line break in before an link
- corrected one typo

---------

Co-authored-by: Yevhen Babiichuk (DustDFG) <dfgdust@gmail.com>
2024-03-22 17:58:44 +01:00
Yevhen Babiichuk (DustDFG)
426e6c600f
Fix trailing spaces/tabs in yaml syntax files (#3200)
Signed-off-by: Yevhen Babiichuk (DustDFG) <dfgdust@gmail.com>
2024-03-22 17:56:09 +01:00
Dmytro Maluka
9ab9f8bc1c
Forward resize event to both TabList and InfoBar (#3179)
InfoBar should really receive the resize event, to know the window width
in order to do horizontal scrolling of the command line when it doesn't
fit in the screen. Although currently it doesn't scroll the command line
at all (see issue #2527) and just ignores the resize event, but we
should fix that anyway, so let's forward the resize event to it.
2024-03-21 21:40:22 +01:00
Mikko
f15db6aa30
improve Rust raw string literal highlighting (#3192) 2024-03-21 21:34:54 +01:00
Jöran Karl
4895a29be2
colorscheme: Add capability to include schemes (#2844) 2024-03-21 18:37:51 +01:00
Yevhen Babiichuk (DustDFG)
b518bda50c
Dont highlight tab/space errors in the BTHelp buffers (#3189)
Signed-off-by: Yevhen Babiichuk (DustDFG) <dfgdust@gmail.com>
2024-03-19 16:22:28 +01:00
Jöran Karl
c64add289b
command: Fix replace to be able to insert '$' (#2954)
* command: Fix replace to be able to insert '$'

* help: commands: Precise the documentation of `replace`

* help: commands: Further improvement suggested within the review

Co-authored-by: Beni Cherniavsky-Paskin <cben@redhat.com>

* Fix replace with '$' in a more kosher way

On top of JoeKar's fix.

---------

Co-authored-by: Beni Cherniavsky-Paskin <cben@redhat.com>
Co-authored-by: Dmytro Maluka <dmitrymaluka@gmail.com>
2024-03-17 21:37:16 +01:00
Paulo S. Costa
5ae2799b70
Color material-tc scrollbar (#1838)
* Color material-tc scrollbar

* Update scrollbar color to correct name
2024-03-17 17:57:21 +01:00
Suhaas Joshi
16e38b988c
cmd: Fix typo in the plugin line of "micro --help" (#2594)
fixes #2582
2024-03-17 16:48:41 +01:00
Lizzy Fleckenstein
8a3d83f7c7
Add support for rust async/await keywords (#2556) 2024-03-17 16:47:46 +01:00
Dmytro Maluka
55b251ffee Revert "command: Add capability to use relative numbers in goto (#2985)"
This reverts commit ca3a9d0794.
2024-03-17 16:39:47 +01:00
Jöran Karl
8724709cf9
Reduce the available string option validators and add autocompletion for them (#3021)
* settings: Move all options to the start of the file

This will help with the overview of all available options and their optional
validators.

* settings: Add generic string option validator

* settings: Autocomplete string options
2024-03-15 22:20:39 +01:00
Jöran Karl
4a53419c62
option: Don't apply rmtrailingws in case of timed autosave (#2850) 2024-03-15 18:46:51 +01:00
cyqsimon
8af304cc21
Update OSC52 info for Alacritty (#3174) 2024-03-15 18:43:34 +01:00
Dmytro Maluka
399134fe5b Escape regex in pre-filled search pattern in Find prompt
Fixes #3177
2024-03-15 12:25:39 +01:00
Dmytro Maluka
db26b5fee5 Add TODO about mysterious behavior of ResizeSplit() 2024-03-14 05:26:34 +01:00
Dmytro Maluka
cb903f414c
Remove unused autosave channel leftover (#3024)
config.Autosave channel is used instead.
2024-03-14 04:58:45 +01:00
Dmytro Maluka
0a69cc68dc
Merge pull request #3023 from dmaluka/timerchan
Rework lua timers and remove lua.Lock
2024-03-14 04:58:19 +01:00
Dmytro Maluka
1d1b363fa7 Remove lua.Lock
Exposing locking primitives to lua plugins is tricky and may lead to
deadlocks. Instead, if possible, it's better to ensure all the needed
synchonization in micro itself, without leaving this burden to lua code.

Since we've added micro.After() timer API and removed exposing Go timers
directly to lua, now we (probably?) have no cases of lua code possibly
running asynchronously without micro controlling when it is running. So
now we can remove lua.Lock.

This means breaking compatibility, but, until recently lua.Lock wasn't
workable at all (see #2945), which suggests that it has never been
really used by anyone. So it should be safe to remove it.
2024-03-14 04:53:56 +01:00
Dmytro Maluka
4ffc2206ee Don't expose Go timers directly to lua
Since we now expose our own micro.After() API which is more convenient
and safer to use than directly using Go timers, we can remove exposing
Go timers to lua directly.
2024-03-14 04:52:59 +01:00
Dmytro Maluka
9089e9ec83 Add micro's own lua timer function micro.After()
Directly using Go's time.AfterFunc() from lua is tricky. First, it
requires the lua timer callback to explicitly lock ulua.Lock to prevent
races. Second, it requires the lua timer callback to explicitly redraw
the screen if the callback changes the screen contents (see #2923).

So instead provide micro's own timer API which ensures both
synchronization and redrawing on its own, instead of leaving this burden
to lua code. In fact, its implementation runs the lua timer callback in
the main micro's goroutine (i.e. from micro's perspective it is
synchronous, not asynchronous), so both redrawing and synchronization
are ensured automatically.

Fixes #2923
2024-03-14 04:52:59 +01:00
Dmytro Maluka
c24604d1ab
Fix overwriting persistent non-default settings with temporary default settings (#3010)
Passing options via micro -option=value in the command line should only
temporarily override the option value for the current micro session,
not change it permanently in settings.json. But currently it wrongly
writes it to settings.json in the case when the value passed via command
line is the default value of this option, while the current permanent
setting in settings.json is a non-default value.

Fixes #3005
2024-03-14 04:43:40 +01:00
Dmytro Maluka
606bcecf03
Merge pull request #3009 from dmaluka/fix-unneeded-settings-write
Fix unneeded rewriting of settings.json
2024-03-14 04:40:34 +01:00
Alan
94af5f13bd
Update commands.md (#2966)
* Update commands.md

removed question marks since they're not meant to be typed and their meaning is equivocal

* Update commands.md

added brackets around optional arguments, and added 'key' placeholder indicating a required argument for 'showkey'

* Update commands.md

added single quotes inside of bracketed optional params (though I feel they should be reserved for shell escaping, and italics should be used for replacing text of arguments. I also added brackets (and quotes) around the `exec` parameter for `term`, although I'm not really sure if that's right because this command doesn't work on my system.
2024-03-14 04:39:17 +01:00
Dmytro Maluka
80db98dc81
Merge pull request #2959 from JoeKar/fix/raw-esc-sequence
bindings: Allow raw escape sequence to be bound with `bind`
2024-03-14 04:38:05 +01:00
Dmytro Maluka
e424537ff8
Merge pull request #2819 from JoeKar/fix/file-detection
Improve file detection with signature check capabilities
2024-03-14 04:37:10 +01:00
Dmytro Maluka
5bfda7b5f6
Merge branch 'master' into fix/file-detection 2024-03-14 04:32:09 +01:00
Dmytro Maluka
3dba23a348
Minor: fix weird error message text when unable to load help (#2618)
If we add something like this to init.lua:

   config.AddRuntimeFile("status", config.RTHelp, "help/foo.md")

then start micro and run "help foo", the resulting error message looks
weird:

   Unable to load help textfooopen plugins/status/help/foo.md: file does not exist

Change it to:

   Unable to load help text for foo: open plugins/status/help/foo.md: file does not exist
2024-03-14 03:59:36 +01:00
Dmytro Maluka
00174bb376
Merge pull request #2606 from dmaluka/mouse-release-and-drag-events
Introduce mouse release and mouse drag events
2024-03-14 03:54:04 +01:00
Dmytro Maluka
c4c5b184c2
Improve support for mouse events handling (#2605)
- If a mouse event is bound to a Lua function, pass *tcell.EventMouse to
  this Lua function, so that it can find out the position where a button
  was clicked etc, just like the built-in MousePress and MouseMultiCursor
  actions.

- Make mouse actions more a first-class citizen: allow chaining them and
  running onAction and preAction callbacks for them, just like key actions.
2024-03-14 03:52:52 +01:00
Dmytro Maluka
7b718cb87c
Merge pull request #1897 from dmaluka/wserrors
New feature: Highlighting whitespace errors
2024-03-14 03:26:09 +01:00
Dmitry Maluka
6dc3df646b readme: Mention hltrailingws/hltaberrors feature 2024-03-14 03:18:11 +01:00
Dmitry Maluka
13d1407f60 hltrailingws: simpler and better undo/redo handling 2024-03-14 03:18:11 +01:00
Dmitry Maluka
53efce72fa hltrailingws: improve behavior with selection
Improve user experience: if we are at a line with a new (i.e.
not highlighted yet) trailingws and we begin selecting text,
don't highlight the trailingws until we are done with selection,
even if we moved the cursor to another line while selecting.
2024-03-14 03:18:11 +01:00
Dmitry Maluka
f108c90643 hltrailingws: improve updateTrailingWs logic
Handle the case when the cursor itself hasn't really moved to
another line, but its line number has changed due to insert
or remove of some lines above.
In this case, if the cursor is still at its new trailingws,
we should not reset NewTrailingWsY to -1 but update it to the
new line number.

A scenario exemplifying this issue:
Bind some key, e.g. Alt-r, to such a lua function:

function insertNewlineAbove(bp)
    bp.Buf:Insert(buffer.Loc(0, bp.Cursor.Y), "\n")
end

Then in a file containing these lines:

aaa
bbb
ccc

insert a space at the end of bbb line, and then press Alt-r.
bbb and ccc are moved one line down, but also the trailing space
after bbb becomes highlighted, which isn't what we expect.
This commit fixes that.
2024-03-14 03:18:11 +01:00
Dmitry Maluka
b824e767d6 Add tab-error and trailingws colors to colorschemes 2024-03-14 03:18:10 +01:00
Dmitry Maluka
c52ccad14b hltrailingws: adjust autoclose plugin implementation
Fix unwanted highlighting of whitespace in the new line when inserting
a newline after a bracket (when hltrailingws is on). To fix it, change
the order of operations: insert the new empty line after all other
things, to avoid moving the cursor between lines after that.
2024-03-14 03:10:33 +01:00
Dmitry Maluka
104caf08dd Highlighting trailing whitespaces
Added option `hltrailingws` for highlighting trailing whitespaces
at the end of lines. Note that it behaves in a "smart" way.
It doesn't highlight newly added (transient) trailing whitespaces
that naturally occur while typing text. It would be annoying to
see transient highlighting every time we enter a space at the end
of a line while typing.
So a newly added trailing whitespace starts being highlighting
only after the cursor moves to another line. Thus the highlighting
serves its purpose: it draws our attention to annoying sloppy
forgotten trailing whitespaces.
2024-03-14 03:10:31 +01:00
Dmitry Maluka
64370b70d6 Highlighting tab errors
Added option `hltaberrors` which helps to spot sloppy whitespace errors
with tabs used instead of spaces or vice versa.

It uses the value of `tabstospaces` option as a criterion whether a
tab or space character is an error or not.
If `tabstospaces` is on, we probably expect that the file should contain
no tab characters, so any tab character is highlighted as an error.
If `tabstospaces` is off, we probably expect that the file uses
indentation with tabs, so space characters in the initial indent part
of lines are highlighted as errors.
2024-03-14 03:09:30 +01:00
Jöran Karl
8368af3cc8
command: Fix set local-only options for the current buffer only (#3042) 2024-03-13 21:34:52 +01:00
Jöran Karl
ca3a9d0794
command: Add capability to use relative numbers in goto (#2985)
* command: Handle relative line numbers for goto

* help: Adapt goto command documentation
2024-03-13 21:32:12 +01:00
Mikko
bd306d67b4
Smarter smartpaste (#3001) (#3002)
* smarterpaste(?)

* make it more readable

* fix edge cases

* fix paste starting with a single space

* fix single line paste
2024-03-13 21:16:10 +01:00
dimaguy
a01ae92541
Add main tag to html syntax highlighting (#2999)
* Add main tag to html syntax highlighting

* Reorder main
2024-03-13 21:12:38 +01:00
Dmytro Maluka
dcdd3e749a
Fix ruler overwriting neighboring split pane + fix crash #3052 (#3069)
* Fix gutter overwriting other split pane

When we resize a split pane to a very small width, so that the gutter
does not fit in the pane, it overwrites the sibling split pane.

To fix it, clean up the calculation of gutter width, buffer width and
scrollbar width, so that they add up exactly to the window width, and
ensure that we don't draw the gutter beyond this calculated gutter
width (gutterOffset).

As a bonus, this also fixes the crash #3052 (observed when resizing a
split pane to a very small width, if wordwrap is enabled), by ensuring
that bufWidth is never negative.

[*] By the gutter we mean of course gutter + diffgutter + ruler.

* Don't display line numbers if buffer width is 0 and softwrap is on

If softwrap is enabled, the line numbers displayed in the ruler depend
on the heights of the displayed softwrapped lines, which depend on the
width of the displayed buffer. If this width is 0 (e.g. after resizing
buffer pane to a very small width), there is no displayed text at all,
so line numbers don't make sense. So don't display line numbers in this
case.

* Fix buffer text overwriting scrollbar when window width is 1 char
2024-03-13 21:11:04 +01:00
Dmytro Maluka
628d9bb37b
Fix split pane divider hovering over neighboring split pane (#3070)
Fix the following funny issue: if we open 3 vertical split panes (i.e.
with 2 vertical dividers between them) and drag the rightmost divider
to the left (for resizing the middle and the rightmost split panes), it
does not stop at the leftmost divider but jumps over it and then hovers
over the leftmost split pane. And likewise with horizontal split panes.
2024-03-13 21:02:11 +01:00
Jöran Karl
bfc4b1d195
termwindow: Show cursor only when his X and Y axis is smaller than the window (#3036) 2024-03-13 20:58:44 +01:00
Yevhen Babiichuk (DustDFG)
d2ee6107a3
Highlight autcompleted command in statusline for simple theme (#3057)
Signed-off-by: Yevhen Babiichuk (DustDFG) <dfgdust@gmail.com>
Co-authored-by: Avi Halachmi (:avih) <avihpit@yahoo.com>
2024-03-13 20:44:41 +01:00
toiletbril
69eaa9191a
options: add matchbracestyle (#2876)
* Update docs to include `matchbracestyle`

* Add `matchbracestyle` to infocomplete.go

* Add validator and default settings for `matchbracestyle`

* Highlight or underline braces based on `matchbracestyle`

* Add `match-brace` to default colorschemes

* Correct `FindMatchingBrace()` counting

Make brace under the cursor have priority over brace to the left in
ambiguous cases when matching braces

Co-authored-by: Dmitry Maluka <dmitrymaluka@gmail.com>

* Fix conflicts

---------

Co-authored-by: Jöran Karl <3951388+JoeKar@users.noreply.github.com>
Co-authored-by: Dmitry Maluka <dmitrymaluka@gmail.com>
2024-03-13 20:21:27 +01:00
taconi
14dca7d349
syntax: log: add syntax highlight code for log files (#3105) 2024-03-13 19:01:16 +01:00
taconi
fad4e449fb
syntax: kvlang: add syntax highlight code for .kv files (#3106) 2024-03-13 19:00:29 +01:00
にてん
f0bc6281d4
Add onRune parameter, utf8 package in plugins.md (#3100)
Add bufpane in parameters of onRune, preRune, and unicode/utf8
Go package in plugins.md.
2024-03-12 21:40:13 +01:00
taconi
fe4ade78df
feat: adds GetArg and GetWord methods to Buffer (#3112) 2024-03-12 21:23:08 +01:00
Alex Rønne Petersen
88b4498ce0
Some syntax highlighting updates for C and C#. (#3125)
* Update C syntax with keywords up to C23.

* Update C syntax with some GCC extensions.

* Update C# syntax with new keywords up to C# 12.

* Update C# syntax with preprocessor directives.

* Add Cake build script (C#) syntax.

* Add MSBuild (XML) syntax.
2024-03-12 21:20:03 +01:00
Jöran Karl
3fce03dfd0
syntax: sh: Fix command parameter highlighting (#3128) 2024-03-12 21:11:20 +01:00
blt-r
15b36ce0d6
Remove the NoDisplay=true from desktop file (#3171)
TUI apps usually have their desktop files visible in system menus.
Also, flathub no longer allows apps with invisible desktop files.
2024-03-12 20:58:09 +01:00
Jöran Karl
321322af31
micro: DoEvent: Don't forward the resize event into the InfoBar (#3035) 2024-03-12 18:49:24 +01:00
Jöran Karl
0de16334d3
micro: Don't forward nil events into the sub event handler (#2992) 2024-03-12 18:35:33 +01:00
Jöran Karl
c15abea64c
rtfiles: Give user defined runtime files precedence over asset files (#3066) 2024-03-04 13:24:40 -08:00
Dmytro Maluka
9fdea82542
Fix various issues with SpawnMultiCursor{Up,Down} (#3145)
* SpawnMultiCursorUp/Down: change order of adding cursors

SpawnMultiCursor{Up,Down} currently works in a tricky way: instead of
creating a new cursor above or below, it moves the current "primary"
cursor above or below, and then creates a new cursor below or above the
new position of the current cursor (i.e. at its previous position),
creating an illusion for the user that the current (top-most or
bottom-most) cursor is a newly spawned cursor.

This trick causes at least the following issues:

- When the line above or below, where we spawn a new cursor, is shorter
  than the current cursor position in the current line, the new cursor
  is placed at the end of this short line (which is expected), but also
  the current cursor unexpectedly changes its x position and moves
  below/above the new cursor.

- When removing a cursor in RemoveMultiCursor (default Alt-p key), it
  non-intuitively removes the cursor which, from the user point of view,
  is not the last but the last-but-one cursor.

Fix these issues by replacing the trick with a straightforward logic:
just create the new cursor above or below the last one.

Note that this fix has a user-visible side effect: the last cursor is
no longer the "primary" one (since it is now the last in the list, not
the first), so e.g. when the user clears multicursors via Esc key, the
remaining cursor is the first one, not the last one. I assume it's ok.

* SpawnMultiCursorUp/Down: move common code to a helper fn

* SpawnMultiCursorUp/Down: honor visual width and LastVisualX

Make spawning multicursors up/down behave more similarly to cursor
movements up/down. This change fixes 2 issues at once:

- SpawnMultiCursorUp/Down doesn't take into account the visual width of
  the text before the cursor, which may be different from its character
  width (e.g. if it contains tabs). So e.g. if the number of tabs before
  the cursor in the current line is not the same as in the new line, the
  new cursor is placed at an unexpected location.

- SpawnMultiCursorUp/Down doesn't take into account the cursor's
  remembered x position (LastVisualX) when e.g. spawning a new cursor
  in the below line which is short than the current cursor position, and
  then spawning yet another cursor in the next below line which is
  longer than this short line.

* SpawnMultiCursorUp/Down: honor softwrap

When softwrap is enabled and the current line is wrapped, make
SpawnMultiCursor{Up,Down} spawn cursor in the next visual line within
this wrapped line, similarly to how we handle cursor movements up/down
within wrapped lines.

* SpawnMultiCursorUp/Down: deselect when spawning cursors

To avoid weird user experience (spawned cursors messing with selections
of existing cursors).
2024-03-04 13:23:50 -08:00
Jöran Karl
eedebd80d4
util: Fix opening filenames including colons with parsecursor (#3119)
The regex pattern shall search for the end of the filename first as it does
while opening with +LINE:COL.
2024-03-04 13:22:47 -08:00
Dmytro Maluka
e5026ef3fa
Make MouseMultiCursor toggle cursors (#3146)
It is useful to be able to use mouse not only for adding new cursors
but also for removing them. So let's modify MouseMultiCursor behavior:
if a cursor already exists at the mouse click location, remove it.
2024-03-04 13:21:50 -08:00
Dmytro Maluka
af2ec9d540
Make default fileformat value suited to the OS (#3141)
Set fileformat by default to `dos` on Windows.
2024-03-04 13:20:02 -08:00
Dmytro Maluka
59dda01cb7
Make plugins in ~/.config/micro/plug dir override built-in plugins (#3031)
If ~/.config/micro/plug directory contains a plugin with the same name
as a built-in plugin, the expected behavior is that the user-defined
plugin in ~/.config/micro/plug is loaded instead of the built-in one.

Whereas the existing behavior is that the built-in plugin is used
instead of the user-defined one. Even worse, it is buggy: in this case
the plugin is registered twice, so its callbacks are executed twice
(e.g. with the autoclose plugin, a bracket is autoclosed with two
closing brackets instead of one).

Fix this by ensuring that if a plugin with the same name exists in the
~/.config/micro/plug directory, the built-in one is ignored.

Fixes #3029
2024-01-17 00:09:33 -08:00
Yevhen Babiichuk (DustDFG)
fce8db80de
Add go.mod syntax support (#3061)
Signed-off-by: Yevhen Babiichuk (DustDFG) <dfgdust@gmail.com>
2024-01-17 00:07:51 -08:00
Jöran Karl
e5a9b906f3
infocomplete: Complete filetypes (#3090) 2024-01-17 00:06:45 -08:00
niten94
422305af99
Set bits in mode used when opening files (#3095)
Set write permission bits of group and other users in mode used when
opening files.
2024-01-17 00:06:14 -08:00
Yevhen Babiichuk (DustDFG)
4e383dd110
Do correct cursor right with storing visual X in CursorRight action (#3103)
Signed-off-by: Yevhen Babiichuk (DustDFG) <dfgdust@gmail.com>
2024-01-17 00:04:18 -08:00
Jöran Karl
2d82362a66
actions: saveas: Fix crash at access without permission (#3082) 2023-12-10 13:52:22 -08:00
Dmitry Maluka
359b58a89b Don't rewrite settings.json when registering options
It doesn't seem necessary to write settings to settings.json when
registering a new option. The option is set to its default value, which
means that it will not be written to settings.json (precisely because
it's the default value), so the contents of settings.json don't change
and thus don't need to be written again.

This unneeded writing, in particular, causes unexpected "The file on
disk has changed. Reload file? (y,n,esc)" each time when we open
settings.json via micro.

Fixes #2647
2023-11-03 01:11:05 +01:00
Dmitry Maluka
a373d22939 Refactor defaultvalue setting a bit 2023-11-03 01:07:29 +01:00
Dmitry Maluka
12398916c7 Fix code duplication in RegisterCommonOptionPlug
Avoid code duplication between RegisterCommonOption() and
RegisterCommonOptionPlug(), exactly the same way as it is done for
RegisterGlobalOption() and RegisterGlobalOptionPlug().
2023-11-03 00:58:30 +01:00
Dmitry Maluka
c791cef9c6 Fix default setting for global & common options
Apply the same fix as 4d13308 to all kinds of options, not just to
plugin options.
2023-11-03 00:51:30 +01:00
Jöran Karl
3c16df87ee options: Add capability to define the line count parsed for the signature check 2023-10-26 20:59:42 +02:00
Jöran Karl
2d0d0416e7 buffer: Prefer user defined over built-in file types 2023-10-26 20:59:42 +02:00
Jöran Karl
2aa386f455 syntax: Prepare a concrete signature example for C++ 2023-10-26 20:59:37 +02:00
Jöran Karl
93151f8109 syntax: Prepare a concrete signature example for objective C 2023-10-26 20:48:27 +02:00
Jöran Karl
433879046e Improve file detection with signature check capabilities
This allows more complex detection upon regex rules for a certain amount of
lines.
2023-10-26 20:20:02 +02:00
Zachary Yedidia
d8e9d61a95 Build arm32 binaries with GOARM=6
Ref #2986
2023-10-22 22:50:27 +02:00
Jöran Karl
6fa12743d6 bindings: Add capability to unregister user defined raw escape sequence 2023-10-22 13:59:59 +02:00
Jöran Karl
dcc7205699 bindings: Allow raw escape sequence to be bound with bind 2023-10-22 13:59:38 +02:00
Zachary Yedidia
68d88b571d Bump tcell 2023-10-22 00:35:11 +02:00
Jöran Karl
041ee002dd
syntax: sh: Add here document as string region (#2953) 2023-10-22 00:19:59 +02:00
Jöran Karl
f7244d09c6
Add reload setting - finalization of #2627 (#2845)
* Add reload setting

Can be set to:

* auto - Automatically reload files that changed
* disabled - Do not reload files
* prompt - Prompt the user about reloading the file.

* option: Add default value for reload option and documentation

---------

Co-authored-by: Wilberto Morales <wilbertomorales777@gmail.com>
2023-10-22 00:18:41 +02:00
Zachary Yedidia
9da6af91c8 Handle SIGABRT properly 2023-10-20 08:51:17 +02:00
Andrew Geng
2a9a5afbb2
Fix python decorator syntax. (#2827)
1. Python decorators begin a compound statement, so they only appear
   at the start of a line. So match at the line start to avoid giving
   decorator colors to matrix multiplication (@) expressions. Source:
   https://docs.python.org/3/reference/compound_stmts.html#function-definitions

2. Python decorators go to the end of the line and might not include
   parentheses (for example @functools.cache). So instead of matching
   everything until an `(`, just match as many non-`(` characters
   as possible---which both catches the @functools.cache example and
   allows decorator parameters to fall back to the default color.

3. Instead of hardcoding `brightgreen` (which railscast.micro also
   complains about), color decorators as `preproc` (otherwise unused
   by the python syntax files, and arguably the right colorscheme
   group to be using for syntactic sugars anyway). Note this will
   change decorator colors---for example from bright green to kinda
   brown on monokai, and from yellow to more of a light orange on
   railscast.
2023-10-16 11:08:37 +02:00
Jeffrey Smith
c55fb3329f
Fixed newline format detection for files not ending with a newline (#2875)
* Fixed newline format detection for files not ending with a newline

Files with Windows-style line endings were being converted to
Unix-style if the file did not end with a newline

* Updated file format detection fix for consistency
2023-10-16 11:03:03 +02:00
John Veness
c27593c163
Fix typos in README (#2919) 2023-10-16 10:48:22 +02:00
maddes8cht
fe89df1d4e
fix: recognize .cmd as batch files (#2922) 2023-10-16 10:47:44 +02:00
Mikko
9b81589fba
help: fix incorrect instructions for disabling key binding (#2943) 2023-10-16 10:46:01 +02:00
Dmitry Maluka
dc6a275e04
Fix non-working lua Lock (#2945)
The lock provided to lua as micro.Lock does not really work: an attempt
to use it via micro.Lock:Lock() results in an error:

Plugin initlua: init:260: attempt to call a non-function object
stack traceback:
	init:260: in main chunk
	[G]: ?

The reason is that the value that is provided to lua is a copy of the
mutex, not the mutex itself.

Ref #1539
2023-10-16 10:45:37 +02:00
Jöran Karl
db5fcf11a9
save: Restore the screen before overwriteFile() is left (#2967)
...otherwise there is no screen anymore to draw a possible error message.
2023-10-16 10:44:35 +02:00
Dmitry Maluka
db6d4f5461
save: Restore the screen if failed to start sudo (#2971)
Similarly to the crash fixed by #2967, which happens if sudo failed,
a crash also happens when sudo even fails to start. The reason for
the crash is also similar: nil dereference of screen.Screen caused by
the fact that we do not restore temporarily disabled screen.

To reproduce this crash, set the `sucmd` option to some non-existing
command, e.g. `aaa`, and try to save a file with root privileges.
2023-10-16 10:44:05 +02:00
Jöran Karl
1231d24279
syntax: Fix include of patch in git-commit (#2917) 2023-09-14 13:26:51 -07:00
Alexander Wilms
d8abfd3999
Improve metainfo file (#2910) 2023-09-09 19:17:37 -07:00
Dmitry Maluka
9fabffc880
Fix issues with handling invalid regex in syntax files (#2913)
* Fix panic due to invalid regex in a syntax file

When a user's custom syntax file has a malformed filename regex or
header regex, MakeHeaderYaml() returns error but we do not properly
handle it, which results in a panic due to a dereference of the `header`
pointer which is nil:

Micro encountered an error: runtime.errorString runtime error: invalid memory address or nil pointer dereference
runtime/panic.go:221 (0x44c367)
runtime/panic.go:220 (0x44c337)
github.com/zyedidia/micro/v2/internal/buffer/buffer.go:709 (0x82bc0f)
github.com/zyedidia/micro/v2/internal/buffer/buffer.go:392 (0x828292)
github.com/zyedidia/micro/v2/internal/buffer/buffer.go:261 (0x8278c8)
github.com/zyedidia/micro/v2/cmd/micro/micro.go:203 (0x8b9e7b)
github.com/zyedidia/micro/v2/cmd/micro/micro.go:331 (0x8ba9e5)
runtime/proc.go:255 (0x4386a7)
runtime/asm_amd64.s:1581 (0x467941)

* Do not ignore invalid filename regex error in a syntax file

When the filename regex in a syntax file is malformed but the subsequent
header regex is correct, the filename regex error gets silently ignored,
since the `err` value is overwritten by the subsequent successful header
regex result.
2023-09-09 19:17:23 -07:00
Zachary Yedidia
630b3229ee Fix term output capturing
Fixes #2912
2023-09-08 23:27:39 -07:00
Zachary Yedidia
fbce241753 Docs for fakecursor option
Fixes #2908
2023-09-07 08:17:12 -04:00
Zachary Yedidia
ffc7118af0 Reset snapcraft to standard config 2023-09-06 17:02:29 -04:00
Zachary Yedidia
1ab390e1a2 Set snap grade/version to allow release 2023-09-06 16:50:04 -04:00
Zachary Yedidia
909c1a2dda Update snapcraft 2023-09-06 16:31:21 -04:00
Zachary Yedidia
c2cebaa3d1 Bump tcell for windows update 2023-09-02 13:32:09 +01:00
taconi
75b9a6cefe
Add onSetActive callback (#2885)
Co-authored-by: taconi <igor.tacoi@protonmail.com>
2023-08-31 17:29:07 +01:00
Nobleman
1cbe1441aa
Update micro.desktop (#2864)
Add `NoDisplay=true` to avoid showing up in applications menu
2023-08-31 17:26:19 +01:00
Zachary Yedidia
d0f0b95e45 Fix for capturing internal terminal pty output
Fixes #2879
2023-08-31 07:11:00 -07:00
Jöran Karl
a78c2c3509
actions: Fix the iteration over a slice under modification in QuitAll() (#2898) 2023-08-31 12:53:33 +01:00
Jöran Karl
ceaa143c62
highlighter: Fix regions and patterns inside regions (#2840)
* highlighter: Fix regions and patterns inside regions

* highlighting: Remove 2nd recursive highlightRegion() call

...and add limitGroup checks to pattern search.

* yaml: Add TODO type highlighting

* highlighting: Don't stop in highlightRegion() at empty lines

...because possible region line end pattern must be detected.

* syntax/sh: Correct string handling due to additional pattern handling

* syntax/sh: Remove slash in variables

* highlighter: Accept nested region only in case it's within the current reagion

* highlighter: Accept nested patterns only in case it's within the current reagion

* highlighter: Don't search for nesting in case the region end was found at start
2023-07-11 13:49:12 -07:00
rilysh
cb260bf6bf
add new types for zig (#2861) 2023-07-10 23:11:07 -07:00
Mathias Lohne
0c28fbf7a5
Add support for Jenkinsfile syntax (#2750)
Build pipelines for the Jenkins build system are configured in Groovy,
however since their filename is always `Jenkinsfile`, micro doesn't
recognize them as Groovy, and doesn't add syntax highlighting.

This small commit simply adds `Jenkinsfile` and `jenkinsfile` as file
names recognized as Groovy.
2023-07-08 14:08:34 -07:00
Jan Katins
b02afb1116
Update copypaste.md (#2826)
Status page: https://wezfurlong.org/wezterm/escape-sequences.html#operating-system-command-sequences

Status at 20230521:

> Requests to query the clipboard are ignored. Allows setting or clearing the clipboard

Issue to support querying: https://github.com/wez/wezterm/issues/2050
2023-07-08 14:08:15 -07:00
Jöran Karl
e5bbeff8ac
plugins.md: Add (on|pre)Rune documentation (#2837) 2023-07-08 14:07:56 -07:00
William Etheredge
7e64a43af6
Treat Containerfiles as Dockerfiles (#2846)
Containerfiles should be fully compatible with Dockerfiles:
https://github.com/containers/buildah/discussions/3170
2023-07-08 14:07:19 -07:00
Shreyas A S
51022e6162
Fixed a typo in keybindings.md (#2852)
A 'to' was missing. I noticed it while reading through the documentation.
2023-07-08 14:07:06 -07:00
Zachary Yedidia
5cb9d5eaaf Resolve merge conflict with scrollbarchar 2023-07-08 14:06:41 -07:00
Christian Muehlhaeuser
ffa7f987b6
Add 'scrollbarchar' option (#2342)
This lets you specify a character that will get used for rendering the
scrollbar.

Co-authored-by: Zachary Yedidia <zyedidia@gmail.com>
2023-07-08 14:02:01 -07:00
Dmitry Maluka
9593c2a720
Add HistorySearchUp and HistorySearchDown actions (#1829)
Add HistorySearchUp and HistorySearchDown actions which are similar to
HistoryUp and HistoryDown but search for the prev/next history item
whose beginning matches the currently entered text in the infobuffer
(more precisely, the text before cursor).

Also fixed the following issue: if we scrolled to an older history item
and then edit the infobuffer, this older item gets modified.
We should not edit old history entries. So in this case set HistoryNum
to the last (newly added) item and modify the last item.
2023-07-08 14:00:22 -07:00
guangwu
d7c8daad0d
chore: os.SEEK_CUR os.SEEK_END os.SEEK_SET has been deprecated since Go 1.7 (#2856) 2023-07-04 12:13:41 -07:00
Jöran Karl
0859f4aa36
Fix: Syntax highlighting for various issues (#2810)
* highlighter: Fix region & pattern detection

* syntax/sh: Highlight upper case options too

* syntax/c(pp): Try to synchronize the rules to lower the maintenance effort

* syntax/ruby: Fix explicit filename detection in directories

* highlighter: Respect skip rules in regions

* syntax/sh: Fix parameter expansion, cond. flags and generalize filename via ""

* syntax/php|vi: Correct strings in comments to comments only

Additionally improve vimscript comment handling.

* highlighter: Remove problematic start|end check in find(all)Index()

...and additionally remove recursive region end detection
2023-06-05 17:39:12 -07:00
Jöran Karl
c46467b5b9
plugins: Add capability to dis-/enable them per buffer (#2836) 2023-06-05 17:38:33 -07:00
rfjakob
1b4f6ecb12
save: fsync data safely to disk (#2681)
On modern Linux systems, it can take 30 seconds for
the data to actually hit the disk (check
/proc/sys/vm/dirty_expire_centisecs).

If the computer crashes in those 30 seconds, the user
may end up with an empty file as seen here:
https://github.com/neovim/neovim/issues/9888

This is why editors like vim and nano call
the fsync syscall after they wrote the file.
This syscall is available as file.Sync() in Go.

Running strace against micro shows that fsync is
called as expected:

	$ strace -f -p $(pgrep micro) -e fsync
	strace: Process 3284344 attached with 9 threads
	[pid 3284351] fsync(8)                  = 0

Also, we now catch errors returned from w.Flush().
2023-05-25 22:21:19 -07:00
Emily Grace Seville
fa468cac5f
Add config JSON schema (#2697)
* Add config JSON schema:
- moved from SchemaStore here

* feat(schema): move to data/
2023-05-16 22:16:17 -07:00
Runar
ff1107bc5b
Filled µ with white for consistency (#2811) 2023-04-30 12:06:47 -07:00
Ilya Grigoriev
651a30105b
Goto next/previous diff commands + minor cleanups (#2759)
* Comment fix & gofmt fix

* Goto next/previous diff commands

These commands will work in `git` repositories or whenever `set diff on` is
working. They are bound to `Alt-[` and `Alt-]` by default. I would prefer
`Alt-Up` and `Alt-Down`, but that's already taken.

There are no tests at the moment; I'm looking into writing some since that will
be needed for the rest of the plan to make
https://github.com/zyedidia/micro/discussions/2753 a reality. I'm not sure how
difficult that will be.

* Realign JSON in keybindings.md
2023-04-20 15:23:35 -07:00
Therk
9cef21ecd6
fixed tex comment detection at start of line (#2764) 2023-04-20 15:21:59 -07:00
blt__
61c90b27ab
Add type="desktop-application" into appdata component (#2803)
* Add type="desktop-application" into appdata component

* Add tags necessary for the desktop-application component
2023-04-20 15:20:24 -07:00
blt__
27ed176a22
Fix the appdata (#2796)
It had a dot at the end of <caption> which angered 'upstram-util validate'
2023-04-13 14:21:09 -07:00
blt__
04d30ebced
Fix appdata file (#2793)
* Add <content_rating> to appdata file

* Add <project_license> to appdata file

* Change application_id to use github.io instead of github.com

Github actually gives username.github.io domains and not username.github.com,
therefore it is more correct to use .io top level domain.
We can use <provides><id>...</id></provides> for everyone who expected 
the old application_id
2023-04-08 00:10:43 -07:00
blt__
d5f6b626d2
Fix com.github.zyedidia.micro.metainfo.xml (#2792)
It had some invisible characters
2023-04-06 17:05:53 -07:00
Zachary Yedidia
5739daffc8 Exit application if input terminal has closed
Bump tcell to get access to event error information, and to propagate
input EOF errors as event errors.

Fixes #2569
Fixes #2148
2023-03-20 16:17:43 -04:00
Arnaud Vallette d'Osia
9c2ce486a5
Hare (harelang) syntax (#2776) 2023-03-16 13:26:33 -07:00
Zachary Yedidia
7bef54856c Replace zyedidia/pty with upstream creack/pty
Bump zyedidia/terminal, which is the actual dependency. We can get the
information we need from the Term's pty file rather than using a buffer
connected to stdout.

Fixes #2775
2023-03-15 13:14:33 -07:00
Dmitry Maluka
dda79ca70e
Add statusline.inactive and statusline.suggestions color groups (#1832)
Add color groups for displaying statuslines of inactive split panes
and the suggestions menu with colors different from the statusline
of the active pane.
2023-03-13 16:18:44 -07:00
Marcelina Hołub
b16af564a7
refactor(runtime): simplify AssetDir() (#2761)
* refactor(runtime): simplify AssetDir()

* test(runtime): remove cwd checks
2023-02-28 01:31:51 -08:00
dependabot[bot]
127b340a08
Bump golang.org/x/text from 0.3.2 to 0.3.8 (#2757)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.2 to 0.3.8.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.3.2...v0.3.8)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-22 22:47:48 -08:00
Zachary Yedidia
7dd88c2f23 Don't auto-detect commenttype if last_ft is empty
Also fixes commenttype for batch files.

Fixes #2752
2023-02-15 21:57:12 -08:00
Zachary Yedidia
c492466583 Always return full RunBackgroundShell output
Fixes #2459
2023-02-14 11:34:19 -08:00
Dmitry Maluka
2d95064ff6 Make a pane active whenever any mouse button is pressed on it
Since now bufpane handles mouse move and release events generically and
separately from mouse press events, that creates a mess when we dispatch
a mouse press event to an inactive pane without making it active.

For example:
1. Click the right button on an inactive pane. It remains inactive.
2. Then click the left button on it. It becomes active, and an
unexpected text selection appears.
The reason is that the release event for the first click was dispatched
to a wrong pane - the (then) active one, so the (then) inactive pane
didn't get the release event and treats the second click not as a mouse
press but as a mouse move.

The simplest way to fix it is to avoid this scenario entirely, i.e.
always activate the pane when clicking any mouse button on it, not just
the left button.

For mouse wheel motion events we keep the existing behavior: the pane
gets the event but doesn't become active. Mouse wheel motion events are
not affected by the described issue, as they have no paired "release"
events.
2023-01-29 18:27:22 +01:00
Dmitry Maluka
e5093892fd Reset mouse release state after restarting the screen
When we temporarily disable the screen (e.g. during TermMessage or
RunInteractiveShell), if the mouse is pressed when the screen is still
active and then released when the screen is already stopped, we aren't
able to catch this mouse release event, so we erroneously think that the
mouse is still pressed after the screen is restarted. This results in
wrong behavior due to a mouse press event treated as a mouse move event,
e.g. upon the left button click we see an unexpected text selection.

So need to reset the mouse release state to "released" after restarting
the screen, assuming it is always released when the screen is restarted.
2023-01-29 18:26:55 +01:00
Dmitry Maluka
124fa9e2e7 Fix up double release event after drag
If we press mouse, drag and then release, the release event is
generated twice, since both mouse press and mouse drag events have been
saved in mousePressed map. To fix that, ensure that we only store mouse
press events in it.
2023-01-29 18:22:45 +01:00
Dmitry Maluka
34ac83b594 Introduce mouse release and mouse drag events
Introduce separate mouse release and mouse drag (move while pressed)
events: MouseLeftRelease, MouseLeftDrag, MouseRightRelease etc,
to allow binding them to actions independently from mouse press events
(MouseLeft, MouseRight etc).

This change:

- Makes it possible to handle mouse release and drag for arbitrary mouse
  events and actions (including Lua actions), not just for MouseLeft as
  in the current code.

- Fixes issue #2599 with PastePrimary and MouseMultiCursor actions:
  selection is pasted not only when pressing MouseMiddle but also when
  moving mouse with MouseMiddle pressed; similarly, a new multicursor is
  added not only when pressing Ctrl-MouseLeft but also when moving mouse
  with Ctrl-MouseLeft pressed.

My initial approach was not to introduce new events for mouse release
and mouse drag but to pass "mouse released" info to action functions
in addition to *tcell.EventMouse to let the action functions do the
necessary checks (similarly to what MousePress is already doing). But
then I realized it was a bad idea, since we still want to be able also
to bind mouse events to regular key actions (such as PastePrimary)
which don't care about mouse event info.
2023-01-29 18:21:59 +01:00
188 changed files with 3918 additions and 1608 deletions

View file

@ -53,7 +53,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
- Extremely good mouse support. - Extremely good mouse support.
- This means mouse dragging to create a selection, double click to select by word, and triple click to select by line. - This means mouse dragging to create a selection, double click to select by word, and triple click to select by line.
- Cross-platform (it should work on all the platforms Go runs on). - Cross-platform (it should work on all the platforms Go runs on).
- Note that while Windows is supported Mingw/Cygwin is not (see below). - Note that while Windows is supported, Mingw/Cygwin is not (see below).
- Plugin system (plugins are written in Lua). - Plugin system (plugins are written in Lua).
- micro has a built-in plugin manager to automatically install, remove, and update plugins. - micro has a built-in plugin manager to automatically install, remove, and update plugins.
- Built-in diff gutter. - Built-in diff gutter.
@ -68,6 +68,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
- Small and simple. - Small and simple.
- Easily configurable. - Easily configurable.
- Macros. - Macros.
- Smart highlighting of trailing whitespace and tab vs space errors.
- Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, … - Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, …
## Installation ## Installation
@ -83,7 +84,7 @@ A desktop entry file and man page can be found in the [assets/packaging](https:/
### Pre-built binaries ### Pre-built binaries
Pre-built binaries are distributed with [releases](https://github.com/zyedidia/micro/releases). Pre-built binaries are distributed in [releases](https://github.com/zyedidia/micro/releases).
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`. To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
@ -132,7 +133,7 @@ On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/in
snap install micro --classic snap install micro --classic
``` ```
Micro is also available through other package managers on Linux such dnf, AUR, Nix, and package managers Micro is also available through other package managers on Linux such as dnf, AUR, Nix, and package managers
for other operating systems. These packages are not guaranteed to be up-to-date. for other operating systems. These packages are not guaranteed to be up-to-date.
<!-- * `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`). At the moment, this package (2.0.1-1) is outdated and has a known bug where debug mode is enabled. --> <!-- * `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`). At the moment, this package (2.0.1-1) is outdated and has a known bug where debug mode is enabled. -->
@ -159,8 +160,8 @@ for other operating systems. These packages are not guaranteed to be up-to-date.
**Note for Linux desktop environments:** **Note for Linux desktop environments:**
For interfacing with the local system clipboard, the following tools need to be installed: For interfacing with the local system clipboard, the following tools need to be installed:
* For X11 `xclip` or `xsel` * For X11, `xclip` or `xsel`
* For [Wayland](https://wayland.freedesktop.org/) `wl-clipboard` * For [Wayland](https://wayland.freedesktop.org/), `wl-clipboard`
Without these tools installed, micro will use an internal clipboard for copy and paste, but it won't be accessible to external applications. Without these tools installed, micro will use an internal clipboard for copy and paste, but it won't be accessible to external applications.
@ -221,7 +222,7 @@ If you open micro and it doesn't seem like syntax highlighting is working, this
you are using a terminal which does not support 256 color mode. Try changing the color scheme to `simple` you are using a terminal which does not support 256 color mode. Try changing the color scheme to `simple`
by pressing <kbd>Ctrl-e</kbd> in micro and typing `set colorscheme simple`. by pressing <kbd>Ctrl-e</kbd> in micro and typing `set colorscheme simple`.
If you are using the default Ubuntu terminal, to enable 256 make sure your `TERM` variable is set If you are using the default Ubuntu terminal, to enable 256 color mode make sure your `TERM` variable is set
to `xterm-256color`. to `xterm-256color`.
Many of the Windows terminals don't support more than 16 colors, which means Many of the Windows terminals don't support more than 16 colors, which means
@ -240,7 +241,7 @@ winpty micro.exe ...
Micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this 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 means that micro is restricted to the platforms tcell supports. As a result, micro does not support
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (which is deprecated anyway). Plan9 or Cygwin (although this may change in the future). Micro also doesn't support NaCl (which is deprecated anyway).
## Usage ## Usage

View file

@ -9,7 +9,7 @@
viewBox="0 0 304.70001 103.2" viewBox="0 0 304.70001 103.2"
enable-background="new 0 0 960 560" enable-background="new 0 0 960 560"
xml:space="preserve" xml:space="preserve"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="micro-logo-drop.svg" sodipodi:docname="micro-logo-drop.svg"
width="304.70001" width="304.70001"
height="103.2" height="103.2"
@ -43,21 +43,23 @@
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:window-width="1920" inkscape:window-width="1920"
inkscape:window-height="1043" inkscape:window-height="1080"
id="namedview17" id="namedview17"
showgrid="false" showgrid="false"
fit-margin-top="0" fit-margin-top="0"
fit-margin-left="0" fit-margin-left="0"
fit-margin-right="0" fit-margin-right="0"
fit-margin-bottom="0" fit-margin-bottom="0"
inkscape:zoom="2.5161345" inkscape:zoom="13.204388"
inkscape:cx="158.97401" inkscape:cx="71.832181"
inkscape:cy="109.69207" inkscape:cy="63.956011"
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="0" inkscape:window-y="0"
inkscape:window-maximized="1" inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" inkscape:current-layer="Layer_1"
inkscape:pagecheckerboard="0" /><g inkscape:pagecheckerboard="0"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" /><g
id="g838" id="g838"
transform="translate(-178,-172.8)" transform="translate(-178,-172.8)"
style="fill:#ffffff;fill-opacity:1;filter:url(#filter1040)"><path style="fill:#ffffff;fill-opacity:1;filter:url(#filter1040)"><path
@ -101,4 +103,7 @@
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 h -0.2 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 h -0.7 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 h 0.8 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z" d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 h -0.2 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 h -0.7 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 h 0.8 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
id="path15" id="path15"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
style="fill:#2e3192" /></svg> style="fill:#2e3192" /><path
style="fill:#ffffff;stroke-width:0.0757324"
d="m 30.026506,86.559353 c -1.017302,-0.241662 -1.787869,-0.887419 -2.143612,-1.796406 -0.545654,-1.394246 -0.158934,-4.812615 1.126179,-9.954732 1.255925,-5.025324 2.459082,-9.096362 5.109736,-17.289458 0.344312,-1.064257 1.654133,-5.2136 1.888607,-5.982859 0.296596,-0.97307 0.598551,-2.708021 0.79743,-4.581811 0.108312,-1.020494 0.246431,-2.186451 0.306932,-2.591018 0.0605,-0.404565 0.178758,-1.341754 0.262796,-2.082641 0.224837,-1.982189 0.649291,-5.218012 0.916787,-6.98913 0.444542,-2.943359 0.753682,-4.198397 1.354756,-5.499991 0.686842,-1.487323 1.771061,-2.655188 2.805126,-3.021538 0.542395,-0.19216 1.381388,-0.270583 1.982594,-0.185316 1.252526,0.17764 1.883508,0.754167 2.211742,2.020866 0.313761,1.21084 -0.05565,3.930951 -0.877141,6.458782 -1.290698,3.971623 -2.036395,5.990995 -2.986916,8.088674 -1.185138,2.61545 -2.712212,6.873258 -2.939609,8.196258 -0.49042,2.853282 0.04972,5.146283 1.578225,6.6999 0.913915,0.928929 2.023939,1.521458 3.413442,1.82209 0.903748,0.195534 2.608483,0.179674 3.407958,-0.03171 1.383427,-0.365777 2.763884,-1.250325 4.377299,-2.804821 3.163126,-3.047616 5.113532,-6.222841 6.797438,-11.066108 0.353971,-1.018094 0.493359,-1.574562 0.749316,-2.991429 0.271014,-1.500218 1.040858,-5.574621 1.51657,-8.026458 0.08082,-0.416528 0.218253,-1.149239 0.305416,-1.628246 0.472088,-2.594388 1.148516,-4.178722 2.330295,-5.458032 0.763841,-0.826879 1.674493,-1.206419 2.894632,-1.206419 1.24359,0 2.138991,0.401576 2.574266,1.154526 0.974305,1.685378 0.683954,4.053139 -1.163626,9.489195 -0.954432,2.808181 -2.572717,6.998752 -3.493593,9.046702 -0.971745,2.161077 -2.201912,5.041664 -2.441809,5.717796 l -0.268706,0.757324 0.09021,1.120423 c 0.212423,2.638199 0.889316,4.086035 2.469149,5.281365 0.932959,0.705895 1.786459,0.982601 3.026274,0.981126 2.426542,-0.0029 4.480731,-1.028876 5.685658,-2.839769 0.811784,-1.220036 1.58443,-3.158397 2.044887,-5.130071 l 0.207813,-0.889855 h 0.356374 0.356373 l 0.04799,0.892492 c 0.0554,1.030319 -0.04881,3.015268 -0.219241,4.175846 -0.345822,2.354993 -1.040859,4.427262 -1.983165,5.91286 -0.701565,1.106055 -1.958204,2.491062 -2.717404,2.994989 -1.555814,1.032691 -4.187858,1.499135 -6.161832,1.091984 -0.603718,-0.124523 -1.72865,-0.689523 -2.178956,-1.094387 -1.477985,-1.328835 -2.187139,-3.341642 -2.360358,-6.699454 -0.08196,-1.588814 0.0522,-3.504923 0.298559,-4.263967 0.05681,-0.175039 0.04587,-0.208265 -0.06857,-0.208265 -0.09667,0 -0.197671,0.148268 -0.348229,0.511194 -0.711765,1.715746 -1.965261,3.867832 -3.142896,5.395934 -0.680786,0.883388 -2.612844,2.822501 -3.483678,3.496397 -2.517073,1.947843 -5.073167,2.951502 -8.060525,3.164993 -1.592379,0.1138 -2.868371,-0.07567 -4.016971,-0.596469 -1.69649,-0.769225 -3.109446,-2.469115 -3.819014,-4.594555 -0.614034,-1.839276 -0.863382,-4.754214 -0.580679,-6.788275 0.05951,-0.428202 0.126068,-0.957467 0.147897,-1.176145 l 0.03969,-0.397595 H 37.651633 37.254872 L 36.96284,53.90253 c -0.705326,1.783387 -1.458627,4.293583 -2.085205,6.948448 -1.027173,4.352223 -1.56307,7.486558 -2.197428,12.852248 -0.310323,2.624858 -0.310577,2.629265 -0.189513,3.294359 0.13956,0.766706 0.417018,1.85334 0.68249,2.672894 0.306093,0.944956 0.565598,2.296449 0.565598,2.945615 0,1.819491 -0.751236,3.258298 -2.006909,3.84374 -0.402074,0.187462 -1.15114,0.231172 -1.705369,0.09951 z"
id="path218" /></svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -2,13 +2,6 @@
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1" version="1.1"
id="Layer_1" id="Layer_1"
x="0px" x="0px"
@ -16,13 +9,20 @@
viewBox="0 0 103.2 103.2" viewBox="0 0 103.2 103.2"
enable-background="new 0 0 960 560" enable-background="new 0 0 960 560"
xml:space="preserve" xml:space="preserve"
inkscape:version="0.91 r13725" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="micro-logo-notext.svg" sodipodi:docname="micro-logo-mark.svg"
width="103.2" width="103.2"
height="103.2"><metadata height="103.2"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata9"><rdf:RDF><cc:Work id="metadata9"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs7" /><sodipodi:namedview id="defs7" /><sodipodi:namedview
pagecolor="#ffffff" pagecolor="#ffffff"
bordercolor="#666666" bordercolor="#666666"
@ -32,22 +32,28 @@
guidetolerance="10" guidetolerance="10"
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:window-width="733" inkscape:window-width="1920"
inkscape:window-height="480" inkscape:window-height="1080"
id="namedview5" id="namedview5"
showgrid="false" showgrid="false"
fit-margin-top="0" fit-margin-top="0"
fit-margin-left="0" fit-margin-left="0"
fit-margin-right="0" fit-margin-right="0"
fit-margin-bottom="0" fit-margin-bottom="0"
inkscape:zoom="0.28541667" inkscape:zoom="5.405335"
inkscape:cx="302" inkscape:cx="75.573484"
inkscape:cy="-4" inkscape:cy="51.153166"
inkscape:window-x="1699" inkscape:window-x="0"
inkscape:window-y="277" inkscape:window-y="0"
inkscape:window-maximized="0" inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" /><path inkscape:current-layer="Layer_1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" /><path
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z" d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
id="path3" id="path3"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
style="fill:#2e3192" /></svg> style="fill:#2e3192" /><path
style="fill:#ffffff;stroke-width:0.185002"
d="m 29.320064,86.164872 c -1.277771,-0.647664 -1.573829,-1.327981 -1.549788,-3.561297 0.04016,-3.730697 1.622887,-10.030031 5.903272,-23.495306 2.770635,-8.715885 2.799071,-8.822813 3.148729,-11.840154 0.585284,-5.050637 1.565844,-12.45598 1.8369,-13.872547 0.43516,-2.274196 0.976755,-3.690519 1.880879,-4.918684 0.974445,-1.323691 1.896478,-1.826405 3.360953,-1.832474 3.009215,-0.01247 3.55713,2.574946 1.786201,8.434969 -0.742771,2.45784 -2.2493,6.487571 -3.407575,9.114735 -0.420971,0.954834 -1.151241,2.827983 -1.622823,4.162554 -0.839682,2.376289 -0.857669,2.47434 -0.869358,4.739023 -0.01095,2.122185 0.02796,2.3976 0.472736,3.346042 0.91751,1.956495 2.602228,3.131322 5.078862,3.541714 2.587757,0.428804 4.551892,-0.347899 7.187533,-2.842264 2.232774,-2.113092 3.746907,-4.117682 4.998184,-6.617188 1.816108,-3.627792 2.213624,-4.978174 3.527565,-11.983266 0.66466,-3.543546 1.376157,-6.951356 1.581104,-7.57291 0.970636,-2.943689 2.922262,-4.567831 5.096985,-4.241711 1.740397,0.260989 2.500104,1.361773 2.494406,3.614287 -0.0068,2.696563 -2.48184,9.966491 -6.424307,18.870246 l -1.269708,2.867537 0.02005,1.757523 c 0.01504,1.318294 0.119434,2.015481 0.417735,2.789716 1.028756,2.67011 3.517063,4.054736 6.342356,3.529224 3.19144,-0.593617 4.98902,-2.612828 6.217715,-6.984325 0.403553,-1.435775 0.552101,-1.739647 0.850428,-1.739647 0.34646,0 0.356492,0.101757 0.241656,2.451282 -0.238951,4.888854 -1.330826,7.853563 -3.80789,10.339358 -1.255532,1.259957 -1.547319,1.456015 -2.694109,1.81022 -1.395674,0.431082 -3.784736,0.537505 -4.865716,0.216749 -1.759682,-0.522141 -3.031085,-2.027386 -3.686869,-4.364972 -0.336042,-1.197843 -0.516218,-5.455318 -0.283812,-6.706338 0.266094,-1.432359 -0.105859,-1.235144 -0.879069,0.466093 -1.724383,3.794037 -4.750586,7.236231 -8.063683,9.172148 -2.368072,1.383716 -5.903865,2.143782 -8.230062,1.769159 -2.672688,-0.430424 -4.588062,-2.213422 -5.66376,-5.272324 -0.491128,-1.396592 -0.514658,-1.618704 -0.512739,-4.840059 0.0018,-3.093063 -0.02515,-3.376294 -0.321772,-3.376294 -0.414677,0 -0.706335,0.582138 -1.434591,2.863386 -1.443227,4.52088 -2.73082,10.895957 -3.516703,17.411762 l -0.381426,3.162426 0.469219,1.740138 c 0.927877,3.441104 1.066474,4.326417 0.841521,5.375336 -0.537458,2.506081 -2.272098,3.528416 -4.269226,2.516133 z"
id="path210" /></svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -2,13 +2,6 @@
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1" version="1.1"
id="Layer_1" id="Layer_1"
x="0px" x="0px"
@ -16,13 +9,20 @@
viewBox="0 0 299.89999 103.2" viewBox="0 0 299.89999 103.2"
enable-background="new 0 0 960 560" enable-background="new 0 0 960 560"
xml:space="preserve" xml:space="preserve"
inkscape:version="0.91 r13725" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="micro-logo.svg" sodipodi:docname="micro-logo.svg"
width="299.89999" width="299.89999"
height="103.2"><metadata height="103.2"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata21"><rdf:RDF><cc:Work id="metadata21"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs19" /><sodipodi:namedview id="defs19" /><sodipodi:namedview
pagecolor="#ffffff" pagecolor="#ffffff"
bordercolor="#666666" bordercolor="#666666"
@ -32,21 +32,24 @@
guidetolerance="10" guidetolerance="10"
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:window-width="1237" inkscape:window-width="1920"
inkscape:window-height="867" inkscape:window-height="1080"
id="namedview17" id="namedview17"
showgrid="false" showgrid="false"
fit-margin-top="0" fit-margin-top="0"
fit-margin-left="0" fit-margin-left="0"
fit-margin-right="0" fit-margin-right="0"
fit-margin-bottom="0" fit-margin-bottom="0"
inkscape:zoom="1.1416667" inkscape:zoom="16.645603"
inkscape:cx="75.655934" inkscape:cx="65.092264"
inkscape:cy="-4" inkscape:cy="49.051992"
inkscape:window-x="1097" inkscape:window-x="0"
inkscape:window-y="185" inkscape:window-y="0"
inkscape:window-maximized="0" inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" /><g inkscape:current-layer="Layer_1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" /><g
id="g3" id="g3"
transform="translate(-178,-172.8)"><path transform="translate(-178,-172.8)"><path
d="m 306.8,213.8 0,-2.6 c 1.6,-0.1 2.9,-0.4 4.1,-0.8 1.2,-0.4 2.5,-1 4,-1.8 l 2.3,0 0,5.2 c 2.4,-1.9 4.2,-3.1 5.5,-3.8 2,-1 4,-1.5 5.8,-1.5 1.3,0 2.5,0.2 3.7,0.7 1.2,0.5 2.2,1 2.9,1.7 0.7,0.7 1.4,1.6 1.9,2.8 2.2,-1.9 4.2,-3.3 6,-4 1.9,-0.8 3.7,-1.2 5.6,-1.2 1.8,0 3.4,0.4 4.8,1.1 1.4,0.8 2.4,1.7 3,2.8 0.6,1.1 0.9,2.8 0.9,5 l 0,14.4 c 0,1.5 0,2.4 0.1,2.6 0.1,0.4 0.3,0.8 0.7,1.1 0.3,0.4 0.7,0.6 1.2,0.7 0.4,0.1 1.2,0.2 2.4,0.2 l 1,0 0,2.6 -15.5,0 0,-2.6 c 1.8,0 2.9,-0.1 3.5,-0.4 0.5,-0.2 0.9,-0.6 1.2,-1.2 0.3,-0.6 0.4,-1.6 0.4,-3.2 l 0,-13.7 c 0,-1.7 -0.2,-2.9 -0.5,-3.6 -0.3,-0.7 -0.9,-1.2 -1.7,-1.7 -0.8,-0.4 -1.8,-0.7 -3,-0.7 -1.5,0 -3,0.4 -4.6,1.2 -2.2,1.1 -3.9,2.3 -5.1,3.6 l 0,14.8 c 0,1.4 0.1,2.4 0.2,2.8 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.5 1.1,0.6 0.4,0.1 1.5,0.2 3.1,0.2 l 0,2.6 -15.3,0 0,-2.6 0.9,0 c 1.2,0 2.1,-0.1 2.6,-0.4 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.2,-0.5 0.3,-1.5 0.3,-2.9 l 0,-13.2 c 0,-1.9 -0.2,-3.3 -0.5,-3.9 -0.3,-0.7 -0.9,-1.3 -1.7,-1.7 -0.8,-0.5 -1.8,-0.7 -3,-0.7 -1.3,0 -2.7,0.3 -4.1,1 -2,1 -3.9,2.2 -5.6,3.8 l 0,15.9 c 0,1 0.1,1.6 0.4,2.1 0.3,0.4 0.7,0.8 1.2,1.1 0.6,0.3 1.3,0.4 2.3,0.4 l 1.1,0 0,2.6 -15.6,0 0,-2.6 0.8,0 c 1.4,0 2.4,-0.1 2.8,-0.3 0.7,-0.3 1.1,-0.8 1.4,-1.5 0.2,-0.4 0.2,-1.3 0.2,-2.9 l 0,-18.1 -5.1,0 z" d="m 306.8,213.8 0,-2.6 c 1.6,-0.1 2.9,-0.4 4.1,-0.8 1.2,-0.4 2.5,-1 4,-1.8 l 2.3,0 0,5.2 c 2.4,-1.9 4.2,-3.1 5.5,-3.8 2,-1 4,-1.5 5.8,-1.5 1.3,0 2.5,0.2 3.7,0.7 1.2,0.5 2.2,1 2.9,1.7 0.7,0.7 1.4,1.6 1.9,2.8 2.2,-1.9 4.2,-3.3 6,-4 1.9,-0.8 3.7,-1.2 5.6,-1.2 1.8,0 3.4,0.4 4.8,1.1 1.4,0.8 2.4,1.7 3,2.8 0.6,1.1 0.9,2.8 0.9,5 l 0,14.4 c 0,1.5 0,2.4 0.1,2.6 0.1,0.4 0.3,0.8 0.7,1.1 0.3,0.4 0.7,0.6 1.2,0.7 0.4,0.1 1.2,0.2 2.4,0.2 l 1,0 0,2.6 -15.5,0 0,-2.6 c 1.8,0 2.9,-0.1 3.5,-0.4 0.5,-0.2 0.9,-0.6 1.2,-1.2 0.3,-0.6 0.4,-1.6 0.4,-3.2 l 0,-13.7 c 0,-1.7 -0.2,-2.9 -0.5,-3.6 -0.3,-0.7 -0.9,-1.2 -1.7,-1.7 -0.8,-0.4 -1.8,-0.7 -3,-0.7 -1.5,0 -3,0.4 -4.6,1.2 -2.2,1.1 -3.9,2.3 -5.1,3.6 l 0,14.8 c 0,1.4 0.1,2.4 0.2,2.8 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.5 1.1,0.6 0.4,0.1 1.5,0.2 3.1,0.2 l 0,2.6 -15.3,0 0,-2.6 0.9,0 c 1.2,0 2.1,-0.1 2.6,-0.4 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.2,-0.5 0.3,-1.5 0.3,-2.9 l 0,-13.2 c 0,-1.9 -0.2,-3.3 -0.5,-3.9 -0.3,-0.7 -0.9,-1.3 -1.7,-1.7 -0.8,-0.5 -1.8,-0.7 -3,-0.7 -1.3,0 -2.7,0.3 -4.1,1 -2,1 -3.9,2.2 -5.6,3.8 l 0,15.9 c 0,1 0.1,1.6 0.4,2.1 0.3,0.4 0.7,0.8 1.2,1.1 0.6,0.3 1.3,0.4 2.3,0.4 l 1.1,0 0,2.6 -15.6,0 0,-2.6 0.8,0 c 1.4,0 2.4,-0.1 2.8,-0.3 0.7,-0.3 1.1,-0.8 1.4,-1.5 0.2,-0.4 0.2,-1.3 0.2,-2.9 l 0,-18.1 -5.1,0 z"
@ -67,4 +70,7 @@
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z" d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
id="path15" id="path15"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
style="fill:#2e3192" /></svg> style="fill:#2e3192" /><path
style="fill:#ffffff;stroke-width:0.0600759"
d="m 30.192709,86.597991 c -0.530828,-0.09608 -1.19875,-0.411872 -1.578921,-0.746511 -0.792953,-0.697985 -1.054327,-1.680313 -0.947823,-3.562219 0.16271,-2.875042 0.852662,-6.034057 2.963728,-13.569713 0.66017,-2.356543 0.955814,-3.307037 3.762987,-12.097989 1.219825,-3.820007 1.435496,-4.505244 1.616654,-5.136492 0.306236,-1.067081 0.590331,-2.663175 0.753866,-4.235353 0.08592,-0.826044 0.236455,-2.096649 0.334514,-2.823568 0.09806,-0.726919 0.246246,-1.916422 0.329306,-2.643341 0.08306,-0.726918 0.231698,-1.902905 0.330307,-2.613302 0.09861,-0.710398 0.231242,-1.724179 0.294741,-2.252848 0.19473,-1.621264 0.604712,-4.037809 0.845956,-4.986301 0.495326,-1.947452 1.158621,-3.216325 2.26111,-4.325467 0.731983,-0.736399 1.547763,-1.051329 2.723316,-1.051329 1.344787,0 2.103359,0.409522 2.539237,1.370828 0.373167,0.823003 0.432731,1.702332 0.227502,3.358553 -0.206897,1.669687 -0.429401,2.498899 -1.62432,6.053417 -0.891865,2.653022 -1.418886,4.025585 -2.237847,5.828196 -0.890733,1.960586 -1.401439,3.281416 -2.291175,5.925621 -0.696894,2.071095 -0.858755,3.003396 -0.79649,4.587665 0.05016,1.276299 0.270881,2.168068 0.761945,3.078469 1.114561,2.066325 3.341124,3.259541 6.082361,3.259541 0.831865,0 1.52957,-0.113832 2.245267,-0.366322 1.037155,-0.365895 1.69838,-0.767468 2.829986,-1.718697 2.058613,-1.730473 4.031033,-4.098263 5.356083,-6.429706 1.132231,-1.992175 2.742129,-5.986041 2.978686,-7.389579 0.126006,-0.747618 0.37151,-2.073261 0.753923,-4.070941 0.459374,-2.399719 0.965049,-5.073707 1.26106,-6.668427 0.439666,-2.368642 0.948255,-3.731056 1.831386,-4.905927 1.000947,-1.33161 1.919678,-1.818989 3.424905,-1.816884 1.371199,0.0019 2.259901,0.453797 2.692584,1.369104 0.199937,0.42295 0.37898,1.160518 0.431897,1.779189 0.0423,0.494585 -0.08313,1.707742 -0.270194,2.613303 -0.520247,2.51845 -2.995194,9.527499 -4.836622,13.697311 -0.189691,0.429543 -0.709117,1.619046 -1.154281,2.64334 -0.445164,1.024295 -0.903857,2.078627 -1.019317,2.342962 -0.593057,1.357747 -0.644155,1.607255 -0.563046,2.7493 0.142046,2.000035 0.604952,3.420811 1.436759,4.409774 0.719848,0.85585 1.902762,1.62255 2.859809,1.853569 0.533147,0.128695 1.669602,0.128252 2.472607,-9.67e-4 1.437635,-0.231339 2.769133,-0.900566 3.72751,-1.873493 1.098243,-1.114915 2.227996,-3.662559 2.785802,-6.282105 l 0.13752,-0.645816 h 0.37414 0.37414 l 0.04419,0.94284 c 0.124949,2.666054 -0.382363,6.016009 -1.237138,8.16926 -0.848692,2.137927 -2.617365,4.354096 -4.156972,5.208738 -1.58257,0.878493 -4.420415,1.19721 -6.111929,0.68643 -0.649563,-0.196146 -1.47209,-0.685817 -1.961392,-1.167665 -1.354216,-1.333585 -1.999054,-3.254244 -2.18916,-6.52045 -0.03525,-0.60571 -0.04689,-1.38515 -0.02584,-1.732089 0.04435,-0.731258 0.257009,-2.357205 0.335205,-2.562875 0.04613,-0.121335 0.03516,-0.140427 -0.08025,-0.139702 -0.11259,7.09e-4 -0.171074,0.09313 -0.370649,0.58574 -0.571777,1.411317 -1.625409,3.288777 -2.58713,4.609988 -2.555402,3.510606 -5.935984,6.014779 -9.311242,6.897323 -1.386313,0.362485 -1.927076,0.42829 -3.514441,0.427668 -1.398071,-5.41e-4 -1.500695,-0.0084 -2.047014,-0.157216 -1.248806,-0.340101 -2.244463,-0.904197 -3.05944,-1.733346 -1.343156,-1.366511 -2.129105,-3.116872 -2.494126,-5.554581 -0.150028,-1.001927 -0.191427,-3.616227 -0.06949,-4.388291 0.05195,-0.328906 0.113311,-0.84947 0.136367,-1.156809 l 0.04192,-0.558799 -0.380315,0.01812 -0.380315,0.01812 -0.231805,0.570721 c -1.478913,3.641182 -3.072314,10.383891 -3.918324,16.580955 -0.190557,1.395837 -0.701916,5.676121 -0.706953,5.917479 -0.0093,0.446744 0.454257,2.427922 0.818884,3.499628 0.121802,0.358001 0.382754,1.549663 0.538684,2.459961 0.04595,0.268246 -0.06655,1.468043 -0.178759,1.906478 -0.165253,0.645686 -0.477741,1.20884 -0.915337,1.649588 -0.463951,0.467293 -0.819805,0.689321 -1.309045,0.816755 -0.410787,0.106995 -0.564727,0.106887 -1.159735,-7.81e-4 z"
id="path240" /></svg>

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -2,6 +2,7 @@ package main
import ( import (
"log" "log"
"time"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar" luar "layeh.com/gopher-luar"
@ -47,14 +48,18 @@ func luaImportMicro() *lua.LTable {
ulua.L.SetField(pkg, "InfoBar", luar.New(ulua.L, action.GetInfoBar)) ulua.L.SetField(pkg, "InfoBar", luar.New(ulua.L, action.GetInfoBar))
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, log.Println)) ulua.L.SetField(pkg, "Log", luar.New(ulua.L, log.Println))
ulua.L.SetField(pkg, "SetStatusInfoFn", luar.New(ulua.L, display.SetStatusInfoFnLua)) ulua.L.SetField(pkg, "SetStatusInfoFn", luar.New(ulua.L, display.SetStatusInfoFnLua))
ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() action.Pane { ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() *action.BufPane {
return action.MainTab().CurPane() return action.MainTab().CurPane()
})) }))
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, action.MainTab)) ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, action.MainTab))
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList { ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
return action.Tabs return action.Tabs
})) }))
ulua.L.SetField(pkg, "Lock", luar.New(ulua.L, ulua.Lock)) ulua.L.SetField(pkg, "After", luar.New(ulua.L, func(t time.Duration, f func()) {
time.AfterFunc(t, func() {
timerChan <- f
})
}))
return pkg return pkg
} }

View file

@ -3,6 +3,7 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
@ -22,7 +23,6 @@ import (
"github.com/zyedidia/micro/v2/internal/buffer" "github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/clipboard" "github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell" "github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/micro/v2/internal/util"
@ -30,9 +30,6 @@ import (
) )
var ( var (
// Event channel
autosave chan bool
// Command line flags // Command line flags
flagVersion = flag.Bool("version", false, "Show the version number and information") flagVersion = flag.Bool("version", false, "Show the version number and information")
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory") flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
@ -45,6 +42,8 @@ var (
sigterm chan os.Signal sigterm chan os.Signal
sighup chan os.Signal sighup chan os.Signal
timerChan chan func()
) )
func InitFlags() { func InitFlags() {
@ -67,7 +66,7 @@ func InitFlags() {
fmt.Println("-version") fmt.Println("-version")
fmt.Println(" \tShow the version number and information") fmt.Println(" \tShow the version number and information")
fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n") fmt.Print("\nMicro's plugins can be managed at the command line with the following commands.\n")
fmt.Println("-plugin install [PLUGIN]...") fmt.Println("-plugin install [PLUGIN]...")
fmt.Println(" \tInstall plugin(s)") fmt.Println(" \tInstall plugin(s)")
fmt.Println("-plugin remove [PLUGIN]...") fmt.Println("-plugin remove [PLUGIN]...")
@ -255,7 +254,9 @@ func main() {
screen.TermMessage(err) screen.TermMessage(err)
} }
config.InitRuntimeFiles() config.InitRuntimeFiles(true)
config.InitPlugins()
err = config.ReadSettings() err = config.ReadSettings()
if err != nil { if err != nil {
screen.TermMessage(err) screen.TermMessage(err)
@ -274,6 +275,7 @@ func main() {
continue continue
} }
config.GlobalSettings[k] = nativeValue config.GlobalSettings[k] = nativeValue
config.VolatileSettings[k] = true
} }
} }
@ -360,9 +362,11 @@ func main() {
sigterm = make(chan os.Signal, 1) sigterm = make(chan os.Signal, 1)
sighup = make(chan os.Signal, 1) sighup = make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) signal.Notify(sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGABRT)
signal.Notify(sighup, syscall.SIGHUP) signal.Notify(sighup, syscall.SIGHUP)
timerChan = make(chan func())
// Here is the event loop which runs in a separate thread // Here is the event loop which runs in a separate thread
go func() { go func() {
for { for {
@ -413,21 +417,19 @@ func DoEvent() {
select { select {
case f := <-shell.Jobs: case f := <-shell.Jobs:
// If a new job has finished while running in the background we should execute the callback // If a new job has finished while running in the background we should execute the callback
ulua.Lock.Lock()
f.Function(f.Output, f.Args) f.Function(f.Output, f.Args)
ulua.Lock.Unlock()
case <-config.Autosave: case <-config.Autosave:
ulua.Lock.Lock()
for _, b := range buffer.OpenBuffers { for _, b := range buffer.OpenBuffers {
b.Save() b.AutoSave()
} }
ulua.Lock.Unlock()
case <-shell.CloseTerms: case <-shell.CloseTerms:
case event = <-screen.Events: case event = <-screen.Events:
case <-screen.DrawChan(): case <-screen.DrawChan():
for len(screen.DrawChan()) > 0 { for len(screen.DrawChan()) > 0 {
<-screen.DrawChan() <-screen.DrawChan()
} }
case f := <-timerChan:
f()
case <-sighup: case <-sighup:
for _, b := range buffer.OpenBuffers { for _, b := range buffer.OpenBuffers {
if !b.Modified() { if !b.Modified() {
@ -448,13 +450,39 @@ func DoEvent() {
os.Exit(0) os.Exit(0)
} }
ulua.Lock.Lock() if e, ok := event.(*tcell.EventError); ok {
// if event != nil { log.Println("tcell event error: ", e.Error())
if action.InfoBar.HasPrompt {
action.InfoBar.HandleEvent(event) if e.Err() == io.EOF {
} else { // shutdown due to terminal closing/becoming inaccessible
action.Tabs.HandleEvent(event) for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
if screen.Screen != nil {
screen.Screen.Fini()
}
os.Exit(0)
}
return
}
if event != nil {
_, resize := event.(*tcell.EventResize)
if resize {
action.InfoBar.HandleEvent(event)
action.Tabs.HandleEvent(event)
} else if action.InfoBar.HasPrompt {
action.InfoBar.HandleEvent(event)
} else {
action.Tabs.HandleEvent(event)
}
}
err := config.RunPluginFn("onAnyEvent")
if err != nil {
screen.TermMessage(err)
} }
// }
ulua.Lock.Unlock()
} }

View file

@ -35,7 +35,9 @@ func startup(args []string) (tcell.SimulationScreen, error) {
return nil, err return nil, err
} }
config.InitRuntimeFiles() config.InitRuntimeFiles(true)
config.InitPlugins()
err = config.ReadSettings() err = config.ReadSettings()
if err != nil { if err != nil {
return nil, err return nil, err
@ -107,7 +109,10 @@ func handleEvent() {
if e != nil { if e != nil {
screen.Events <- e screen.Events <- e
} }
DoEvent()
for len(screen.DrawChan()) > 0 || len(screen.Events) > 0 {
DoEvent()
}
} }
func injectKey(key tcell.Key, r rune, mod tcell.ModMask) { func injectKey(key tcell.Key, r rune, mod tcell.ModMask) {
@ -149,6 +154,16 @@ func openFile(file string) {
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone) injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
} }
func findBuffer(file string) *buffer.Buffer {
var buf *buffer.Buffer
for _, b := range buffer.OpenBuffers {
if b.Path == file {
buf = b
}
}
return buf
}
func createTestFile(name string, content string) (string, error) { func createTestFile(name string, content string) (string, error) {
testf, err := ioutil.TempFile("", name) testf, err := ioutil.TempFile("", name)
if err != nil { if err != nil {
@ -188,14 +203,7 @@ func TestSimpleEdit(t *testing.T) {
openFile(file) openFile(file)
var buf *buffer.Buffer if findBuffer(file) == nil {
for _, b := range buffer.OpenBuffers {
if b.Path == file {
buf = b
}
}
if buf == nil {
t.Errorf("Could not find buffer %s", file) t.Errorf("Could not find buffer %s", file)
return return
} }
@ -234,6 +242,11 @@ func TestMouse(t *testing.T) {
openFile(file) openFile(file)
if findBuffer(file) == nil {
t.Errorf("Could not find buffer %s", file)
return
}
// buffer: // buffer:
// base content // base content
// the selections need to happen at different locations to avoid a double click // the selections need to happen at different locations to avoid a double click
@ -297,6 +310,11 @@ func TestSearchAndReplace(t *testing.T) {
openFile(file) openFile(file)
if findBuffer(file) == nil {
t.Errorf("Could not find buffer %s", file)
return
}
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl) injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string")) injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string"))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone) injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)

View file

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

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>io.github.zyedidia.micro</id>
<launchable type="desktop-id">micro.desktop</launchable>
<name>Micro Text Editor</name>
<summary>A modern and intuitive terminal-based text editor</summary>
<description>
<p>
micro is a terminal-based text editor that aims to be easy to use and
intuitive, while also taking advantage of the capabilities of modern terminals.
It comes as a single, batteries-included, static binary with no dependencies;
you can download and use it right now!
</p>
<p>
As its name indicates, micro aims to be somewhat of a successor to the nano
editor by being easy to install and use. It strives to be enjoyable as a full-time
editor for people who prefer to work in a terminal, or those who regularly
edit files over SSH.
</p>
</description>
<metadata_license>MIT</metadata_license>
<project_license>MIT</project_license>
<categories>
<category>Development</category>
<category>TextEditor</category>
</categories>
<releases>
<release version="2.0.13" date="2023-10-22"/>
<release version="2.0.12" date="2023-09-06"/>
<release version="2.0.11" date="2022-08-01"/>
</releases>
<provides>
<binary>micro</binary>
<id>com.github.zyedidia.micro</id>
</provides>
<developer_name>Zachary Yedidia</developer_name>
<screenshots>
<screenshot type="default">
<caption>Micro Text Editor editing its source code</caption>
<image type="source">https://raw.githubusercontent.com/zyedidia/micro/master/assets/micro-solarized.png</image>
</screenshot>
</screenshots>
<content_rating type="oars-1.1" />
<url type="homepage">https://micro-editor.github.io</url>
<url type="bugtracker">https://github.com/zyedidia/micro/issues</url>
<url type="faq">https://micro-editor.github.io/about.html</url>
<url type="help">https://micro-editor.github.io/about.html</url>
<url type="contact">https://github.com/zyedidia</url>
<url type="vcs-browser">https://github.com/zyedidia/micro</url>
<url type="contribute">https://github.com/zyedidia/micro#contributing</url>
</component>

367
data/micro.json Normal file
View file

@ -0,0 +1,367 @@
{
"$comment": "https://github.com/zyedidia/micro",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "options",
"description": "A micro editor config schema",
"type": "object",
"properties": {
"autoindent": {
"description": "Whether to use the same indentation as a previous line\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"autosave": {
"description": "A delay between automatic saves\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "integer",
"minimum": 0,
"default": 0
},
"autosu": {
"description": "Whether attempt to use super user privileges\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"backup": {
"description": "Whether to backup all open buffers\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"backupdir": {
"description": "A directory to store backups\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"default": ""
},
"basename": {
"description": "Whether to show a basename instead of a full path\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"clipboard": {
"description": "A way to access the system clipboard\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"enum": [
"external",
"terminal",
"internal"
],
"default": "external"
},
"colorcolumn": {
"description": "A position to display a column\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "integer",
"minimum": 0,
"default": 0
},
"colorscheme": {
"description": "A color scheme\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"enum": [
"atom-dark",
"bubblegum",
"cmc-16",
"cmc-tc",
"darcula",
"default",
"dracula-tc",
"dukedark-tc",
"dukelight-tc",
"dukeubuntu-tc",
"geany",
"gotham",
"gruvbox",
"gruvbox-tc",
"material-tc",
"monokai-dark",
"monokai",
"one-dark",
"railscast",
"simple",
"solarized",
"solarized-tc",
"sunny-day",
"twilight",
"zenburn"
],
"default": "default"
},
"cursorline": {
"description": "Whether to highlight a line with a cursor with a different color\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"diffgutter": {
"description": "Whether to display diff inticators before lines\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"divchars": {
"description": "Divider chars for vertical and horizontal splits\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"default": "|-"
},
"divreverse": {
"description": "Whether to use inversed color scheme colors for splits\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"encoding": {
"description": "An encoding used to open and save files\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"default": "utf-8"
},
"eofnewline": {
"description": "Whether to add a missing trailing new line\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"fastdirty": {
"description": "Whether to use a fast algorithm to determine whether a file is changed\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"fileformat": {
"description": "A line ending format\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"enum": [
"unix",
"dos"
],
"default": "unix"
},
"filetype": {
"description": "A filetype for the current buffer\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"default": "unknown"
},
"hlsearch": {
"description": "Whether to highlight all instances of a searched text after a successful search\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"incsearch": {
"description": "Whether to enable an incremental search in `Find` prompt\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"ignorecase": {
"description": "Whether to perform case-insensitive searches\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"indentchar": {
"description": "An indentation character\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"maxLength": 1,
"default": " "
},
"infobar": {
"description": "Whether to enable a line at the bottom where messages are printed\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"keepautoindent": {
"description": "Whether add a whitespace while using autoindent\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"keymenu": {
"description": "Whether to display nano-style key menu at the bottom\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"matchbrace": {
"description": "Whether to show matching braces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"matchbracestyle": {
"description": "Whether to underline or highlight matching braces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"enum": [
"underline",
"highlight"
],
"default": "underline"
},
"mkparents": {
"description": "Whether to create missing directories\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"mouse": {
"description": "Whether to enable mouse support\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"paste": {
"description": "Whether to treat characters sent from the terminal in a single chunk as a paste event\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"parsecursor": {
"description": "Whether to extract a line number and a column to open files with from file names\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"permbackup": {
"description": "Whether to permanently save backups\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"pluginchannels": {
"description": "A file with list of plugin channels\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"default": "https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"
},
"pluginrepos": {
"description": "Plugin repositories\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "array",
"uniqueItems": true,
"items": {
"description": "A pluging repository\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string"
},
"default": []
},
"readonly": {
"description": "Whether to forbid buffer editing\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"rmtrailingws": {
"description": "Whether to remove trailing whitespaces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"ruler": {
"description": "Whether to display line numbers\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"relativeruler": {
"description": "Whether to display relative line numbers\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"savecursor": {
"description": "Whether to save cursor position in files\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"savehistory": {
"description": "Whether to save command history between closing and re-opening editor\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"saveundo": {
"description": "Whether to save undo after closing file\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"scrollbar": {
"description": "Whether to save undo after closing file\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"scrollmargin": {
"description": "A margin at which a view starts scrolling when a cursor approaches an edge of a view\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "integer",
"default": 3
},
"scrollspeed": {
"description": "Line count to scroll for one scroll event\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "integer",
"default": 2
},
"smartpaste": {
"description": "Whether to add a leading whitespace while pasting multiple lines\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"softwrap": {
"description": "Whether to wrap long lines\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"splitbottom": {
"description": "Whether to create a new horizontal split below the current one\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"splitright": {
"description": "Whether to create a new vertical split right of the current one\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"statusformatl": {
"description": "Format string of left-justified part of the statusline\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"default": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)"
},
"statusformatr": {
"description": "Format string of right-justified part of the statusline\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"default": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help"
},
"statusline": {
"description": "Whether to display a status line\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"default": "sudo"
},
"sucmd": {
"description": "A super user command\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "string",
"default": "sudo",
"examples": [
"sudo",
"doas"
]
},
"syntax": {
"description": "Whether to enable a syntax highlighting\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"tabmovement": {
"description": "Whether to navigate spaces at the beginning of lines as if they are tabs\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"tabhighlight": {
"description": "Whether to invert tab character colors\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"tabreverse": {
"description": "Whether to reverse tab bar colors\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"tabsize": {
"description": "A tab size\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "integer",
"default": 4
},
"tabstospaces": {
"description": "Whether to use spaces instead of tabs\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"useprimary": {
"description": "Whether to use primary clipboard to copy selections in the background\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": true
},
"wordwrap": {
"description": "Whether to wrap long lines by words\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
},
"xterm": {
"description": "Whether to assume that the current terminal is `xterm`\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
"type": "boolean",
"default": false
}
},
"additionalProperties": false
}

7
go.mod
View file

@ -14,10 +14,9 @@ require (
github.com/zyedidia/clipper v0.1.1 github.com/zyedidia/clipper v0.1.1
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/pty v1.1.20 // indirect github.com/zyedidia/tcell/v2 v2.0.10 // indirect
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8 // indirect github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 golang.org/x/text v0.3.8
golang.org/x/text v0.3.2
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
layeh.com/gopher-luar v1.0.7 layeh.com/gopher-luar v1.0.7
) )

49
go.sum
View file

@ -3,6 +3,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -36,13 +38,10 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8 h1:woqigIZtZUZxws1zZA99nAvuz2mQrxtWsuZSR9c8I/A= github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8 h1:woqigIZtZUZxws1zZA99nAvuz2mQrxtWsuZSR9c8I/A=
github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8/go.mod h1:6Yhx5ZJl5942QrNRWLwITArVT9okUXc5c3brgWJMoDc= github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8/go.mod h1:6Yhx5ZJl5942QrNRWLwITArVT9okUXc5c3brgWJMoDc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/zyedidia/clipper v0.0.0-20220613212750-517cd4a6c524 h1:sWYUMHs1EAlsPERKLkaLCxLM0misLylZMEc9Ip5Csjw=
github.com/zyedidia/clipper v0.0.0-20220613212750-517cd4a6c524/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=
github.com/zyedidia/clipper v0.1.0 h1:e16nhM1RgL3HYcugcHRUpMya1K830TS5uo6LlPJHySg=
github.com/zyedidia/clipper v0.1.0/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=
github.com/zyedidia/clipper v0.1.1 h1:HBgguFNDq/QmSQKBnhy4sMKzILINr139VEgAhftOUTw= github.com/zyedidia/clipper v0.1.1 h1:HBgguFNDq/QmSQKBnhy4sMKzILINr139VEgAhftOUTw=
github.com/zyedidia/clipper v0.1.1/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ= github.com/zyedidia/clipper v0.1.1/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew= github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
@ -55,26 +54,46 @@ github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d h1:zmDMkh22zXOB7gz8
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I= github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I=
github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s= github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE= github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
github.com/zyedidia/pty v1.1.15 h1:XlxMFph7HDvTn4sDG8Klgmb/g4ATGiSj4655vAETp1U=
github.com/zyedidia/pty v1.1.15/go.mod h1:HWbpfrLoVM9FmU+/9NV+PzVQV8jSxgnQLk8fvx0q/i8=
github.com/zyedidia/pty v1.1.19 h1:GouvvD/u+uml5EPFUAt5N3rFQKPBmZuuUXHvzAJhVA0=
github.com/zyedidia/pty v1.1.19/go.mod h1:HWbpfrLoVM9FmU+/9NV+PzVQV8jSxgnQLk8fvx0q/i8=
github.com/zyedidia/pty v1.1.20 h1:mkZ5/UiEjZVMFzoXp8oyJAlbn3b380m5lvFrbx/NL/g=
github.com/zyedidia/pty v1.1.20/go.mod h1:HWbpfrLoVM9FmU+/9NV+PzVQV8jSxgnQLk8fvx0q/i8=
github.com/zyedidia/tcell/v2 v2.0.9 h1:FxXRkE62N0GPHES7EMLtp2rteYqC9r1kVid8vJN1kOE= github.com/zyedidia/tcell/v2 v2.0.9 h1:FxXRkE62N0GPHES7EMLtp2rteYqC9r1kVid8vJN1kOE=
github.com/zyedidia/tcell/v2 v2.0.9/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws= github.com/zyedidia/tcell/v2 v2.0.9/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8 h1:53ULv4mmLyQDnqbjVxanckP57WSreWHwTmlLJrJEutY= github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8 h1:53ULv4mmLyQDnqbjVxanckP57WSreWHwTmlLJrJEutY=
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws= github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc= github.com/zyedidia/tcell/v2 v2.0.10-0.20230320201625-54f6acdada4a h1:W4TWa++Wk6uRGxZoxr2nPX1TpIEl+Wxv0mTtocG4TYc=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E= github.com/zyedidia/tcell/v2 v2.0.10-0.20230320201625-54f6acdada4a/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/tcell/v2 v2.0.10-0.20230831153116-061c5b2c7260 h1:SCAmAacT5BxZsmOFdFy5zwwi6nj1MjA60gydjKdTgXo=
github.com/zyedidia/tcell/v2 v2.0.10-0.20230831153116-061c5b2c7260/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/tcell/v2 v2.0.10 h1:6fbbYAx/DYc9A//4jU1OeBrxtc9qJxYCZXCtGQbtTWU=
github.com/zyedidia/tcell/v2 v2.0.10/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef h1:LeB4Qs0Tss4r/Qh8pfsTTqagDYHysfKJLYzAH3MVfu0=
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef/go.mod h1:zeb8MJdcCObFKVvur3n2B4BANIPuo2Q8r4iiNs9Enx0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -51,53 +51,47 @@ func (h *BufPane) ScrollAdjust() {
func (h *BufPane) MousePress(e *tcell.EventMouse) bool { func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
b := h.Buf b := h.Buf
mx, my := e.Position() mx, my := e.Position()
// ignore click on the status line
if my >= h.BufView().Y+h.BufView().Height {
return false
}
mouseLoc := h.LocFromVisual(buffer.Loc{mx, my}) mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
h.Cursor.Loc = mouseLoc h.Cursor.Loc = mouseLoc
if h.mouseReleased {
if b.NumCursors() > 1 {
b.ClearCursors()
h.Relocate()
h.Cursor = h.Buf.GetActiveCursor()
h.Cursor.Loc = mouseLoc
}
if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
if h.doubleClick {
// Triple click
h.lastClickTime = time.Now()
h.tripleClick = true if b.NumCursors() > 1 {
h.doubleClick = false b.ClearCursors()
h.Relocate()
h.Cursor.SelectLine() h.Cursor = h.Buf.GetActiveCursor()
h.Cursor.CopySelection(clipboard.PrimaryReg) h.Cursor.Loc = mouseLoc
} else { }
// Double click if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
h.lastClickTime = time.Now() if h.doubleClick {
// Triple click
h.doubleClick = true
h.tripleClick = false
h.Cursor.SelectWord()
h.Cursor.CopySelection(clipboard.PrimaryReg)
}
} else {
h.doubleClick = false
h.tripleClick = false
h.lastClickTime = time.Now() h.lastClickTime = time.Now()
h.Cursor.OrigSelection[0] = h.Cursor.Loc h.tripleClick = true
h.Cursor.CurSelection[0] = h.Cursor.Loc h.doubleClick = false
h.Cursor.CurSelection[1] = h.Cursor.Loc
} h.Cursor.SelectLine()
h.mouseReleased = false h.Cursor.CopySelection(clipboard.PrimaryReg)
} else if !h.mouseReleased {
if h.tripleClick {
h.Cursor.AddLineToSelection()
} else if h.doubleClick {
h.Cursor.AddWordToSelection()
} else { } else {
h.Cursor.SetSelectionEnd(h.Cursor.Loc) // Double click
h.lastClickTime = time.Now()
h.doubleClick = true
h.tripleClick = false
h.Cursor.SelectWord()
h.Cursor.CopySelection(clipboard.PrimaryReg)
} }
} else {
h.doubleClick = false
h.tripleClick = false
h.lastClickTime = time.Now()
h.Cursor.OrigSelection[0] = h.Cursor.Loc
h.Cursor.CurSelection[0] = h.Cursor.Loc
h.Cursor.CurSelection[1] = h.Cursor.Loc
} }
h.Cursor.StoreVisualX() h.Cursor.StoreVisualX()
@ -106,6 +100,45 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
return true return true
} }
func (h *BufPane) MouseDrag(e *tcell.EventMouse) bool {
mx, my := e.Position()
// ignore drag on the status line
if my >= h.BufView().Y+h.BufView().Height {
return false
}
h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
if h.tripleClick {
h.Cursor.AddLineToSelection()
} else if h.doubleClick {
h.Cursor.AddWordToSelection()
} else {
h.Cursor.SelectTo(h.Cursor.Loc)
}
h.Cursor.StoreVisualX()
h.Relocate()
return true
}
func (h *BufPane) MouseRelease(e *tcell.EventMouse) bool {
// We could finish the selection based on the release location as in the
// commented out code below, to allow text selections even in a terminal
// that doesn't support mouse motion events. But when the mouse click is
// within the scroll margin, that would cause a scroll and selection
// even for a simple mouse click, which is not good.
// if !h.doubleClick && !h.tripleClick {
// mx, my := e.Position()
// h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
// }
if h.Cursor.HasSelection() {
h.Cursor.CopySelection(clipboard.PrimaryReg)
}
return true
}
// ScrollUpAction scrolls the view up // ScrollUpAction scrolls the view up
func (h *BufPane) ScrollUpAction() bool { func (h *BufPane) ScrollUpAction() bool {
h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"])) h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
@ -176,7 +209,7 @@ func (h *BufPane) CursorUp() bool {
// CursorDown moves the cursor down // CursorDown moves the cursor down
func (h *BufPane) CursorDown() bool { func (h *BufPane) CursorDown() bool {
h.Cursor.Deselect(true) h.Cursor.Deselect(false)
h.MoveCursorDown(1) h.MoveCursorDown(1)
h.Relocate() h.Relocate()
return true return true
@ -211,7 +244,7 @@ func (h *BufPane) CursorLeft() bool {
func (h *BufPane) CursorRight() bool { func (h *BufPane) CursorRight() bool {
if h.Cursor.HasSelection() { if h.Cursor.HasSelection() {
h.Cursor.Deselect(false) h.Cursor.Deselect(false)
h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf) h.Cursor.Right()
} else { } else {
tabstospaces := h.Buf.Settings["tabstospaces"].(bool) tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
tabmovement := h.Buf.Settings["tabmovement"].(bool) tabmovement := h.Buf.Settings["tabmovement"].(bool)
@ -250,6 +283,22 @@ func (h *BufPane) WordLeft() bool {
return true return true
} }
// SubWordRight moves the cursor one sub-word to the right
func (h *BufPane) SubWordRight() bool {
h.Cursor.Deselect(false)
h.Cursor.SubWordRight()
h.Relocate()
return true
}
// SubWordLeft moves the cursor one sub-word to the left
func (h *BufPane) SubWordLeft() bool {
h.Cursor.Deselect(true)
h.Cursor.SubWordLeft()
h.Relocate()
return true
}
// SelectUp selects up one line // SelectUp selects up one line
func (h *BufPane) SelectUp() bool { func (h *BufPane) SelectUp() bool {
if !h.Cursor.HasSelection() { if !h.Cursor.HasSelection() {
@ -326,6 +375,28 @@ func (h *BufPane) SelectWordLeft() bool {
return true return true
} }
// SelectSubWordRight selects the sub-word to the right of the cursor
func (h *BufPane) SelectSubWordRight() bool {
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
h.Cursor.SubWordRight()
h.Cursor.SelectTo(h.Cursor.Loc)
h.Relocate()
return true
}
// SelectSubWordLeft selects the sub-word to the left of the cursor
func (h *BufPane) SelectSubWordLeft() bool {
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
h.Cursor.SubWordLeft()
h.Cursor.SelectTo(h.Cursor.Loc)
h.Relocate()
return true
}
// StartOfText moves the cursor to the start of the text of the line // StartOfText moves the cursor to the start of the text of the line
func (h *BufPane) StartOfText() bool { func (h *BufPane) StartOfText() bool {
h.Cursor.Deselect(true) h.Cursor.Deselect(true)
@ -589,6 +660,28 @@ func (h *BufPane) DeleteWordLeft() bool {
return true return true
} }
// DeleteSubWordRight deletes the sub-word to the right of the cursor
func (h *BufPane) DeleteSubWordRight() bool {
h.SelectSubWordRight()
if h.Cursor.HasSelection() {
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
}
h.Relocate()
return true
}
// DeleteSubWordLeft deletes the sub-word to the left of the cursor
func (h *BufPane) DeleteSubWordLeft() bool {
h.SelectSubWordLeft()
if h.Cursor.HasSelection() {
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
}
h.Relocate()
return true
}
// Delete deletes the next character // Delete deletes the next character
func (h *BufPane) Delete() bool { func (h *BufPane) Delete() bool {
if h.Cursor.HasSelection() { if h.Cursor.HasSelection() {
@ -712,8 +805,8 @@ func (h *BufPane) Autocomplete() bool {
} }
r := h.Cursor.RuneUnder(h.Cursor.X) r := h.Cursor.RuneUnder(h.Cursor.X)
prev := h.Cursor.RuneUnder(h.Cursor.X - 1) prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) { if !util.IsAutocomplete(prev) || util.IsWordChar(r) {
// don't autocomplete if cursor is on alpha numeric character (middle of a word) // don't autocomplete if cursor is within a word
return false return false
} }
@ -793,25 +886,26 @@ func (h *BufPane) SaveAsCB(action string, callback func()) bool {
filename := strings.Join(args, " ") filename := strings.Join(args, " ")
fileinfo, err := os.Stat(filename) fileinfo, err := os.Stat(filename)
if err != nil { if err != nil {
if os.IsNotExist(err) { if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrPermission) {
noPrompt := h.saveBufToFile(filename, action, callback) noPrompt := h.saveBufToFile(filename, action, callback)
if noPrompt { if noPrompt {
h.completeAction(action) h.completeAction(action)
return return
} }
} }
} } else {
InfoBar.YNPrompt( InfoBar.YNPrompt(
fmt.Sprintf("the file %s already exists in the directory, would you like to overwrite? Y/n", fileinfo.Name()), fmt.Sprintf("The file %s already exists in the directory, would you like to overwrite? Y/n", fileinfo.Name()),
func(yes, canceled bool) { func(yes, canceled bool) {
if yes && !canceled { if yes && !canceled {
noPrompt := h.saveBufToFile(filename, action, callback) noPrompt := h.saveBufToFile(filename, action, callback)
if noPrompt { if noPrompt {
h.completeAction(action) h.completeAction(action)
}
} }
} },
}, )
) }
} }
}) })
return false return false
@ -953,6 +1047,9 @@ func (h *BufPane) find(useRegex bool) bool {
} }
} }
pattern := string(h.Cursor.GetSelection()) pattern := string(h.Cursor.GetSelection())
if useRegex && pattern != "" {
pattern = regexp.QuoteMeta(pattern)
}
if eventCallback != nil && pattern != "" { if eventCallback != nil && pattern != "" {
eventCallback(pattern) eventCallback(pattern)
} }
@ -1027,6 +1124,28 @@ func (h *BufPane) FindPrevious() bool {
return true return true
} }
// DiffNext searches forward until the beginning of the next block of diffs
func (h *BufPane) DiffNext() bool {
cur := h.Cursor.Loc.Y
dl, err := h.Buf.FindNextDiffLine(cur, true)
if err != nil {
return false
}
h.GotoLoc(buffer.Loc{0, dl})
return true
}
// DiffPrevious searches forward until the end of the previous block of diffs
func (h *BufPane) DiffPrevious() bool {
cur := h.Cursor.Loc.Y
dl, err := h.Buf.FindNextDiffLine(cur, false)
if err != nil {
return false
}
h.GotoLoc(buffer.Loc{0, dl})
return true
}
// Undo undoes the last action // Undo undoes the last action
func (h *BufPane) Undo() bool { func (h *BufPane) Undo() bool {
h.Buf.Undo() h.Buf.Undo()
@ -1251,9 +1370,13 @@ func (h *BufPane) PastePrimary() bool {
func (h *BufPane) paste(clip string) { func (h *BufPane) paste(clip string) {
if h.Buf.Settings["smartpaste"].(bool) { if h.Buf.Settings["smartpaste"].(bool) {
if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 { if h.Cursor.X > 0 {
leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y)) leadingPasteWS := string(util.GetLeadingWhitespace([]byte(clip)))
clip = strings.ReplaceAll(clip, "\n", "\n"+string(leadingWS)) if leadingPasteWS != " " && strings.Contains(clip, "\n"+leadingPasteWS) {
leadingWS := string(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y)))
clip = strings.TrimPrefix(clip, leadingPasteWS)
clip = strings.ReplaceAll(clip, "\n"+leadingPasteWS, "\n"+leadingWS)
}
} }
} }
@ -1271,21 +1394,15 @@ func (h *BufPane) paste(clip string) {
// JumpToMatchingBrace moves the cursor to the matching brace if it is // JumpToMatchingBrace moves the cursor to the matching brace if it is
// currently on a brace // currently on a brace
func (h *BufPane) JumpToMatchingBrace() bool { func (h *BufPane) JumpToMatchingBrace() bool {
for _, bp := range buffer.BracePairs { matchingBrace, left, found := h.Buf.FindMatchingBrace(h.Cursor.Loc)
r := h.Cursor.RuneUnder(h.Cursor.X) if found {
rl := h.Cursor.RuneUnder(h.Cursor.X - 1) if left {
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] { h.Cursor.GotoLoc(matchingBrace)
matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc) } else {
if found { h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
if left {
h.Cursor.GotoLoc(matchingBrace)
} else {
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
}
h.Relocate()
return true
}
} }
h.Relocate()
return true
} }
return false return false
} }
@ -1311,7 +1428,7 @@ func (h *BufPane) OpenFile() bool {
return true return true
} }
// OpenFile opens a new file in the buffer // JumpLine asks the user to enter a line number to jump to
func (h *BufPane) JumpLine() bool { func (h *BufPane) JumpLine() bool {
InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) { InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
if !canceled { if !canceled {
@ -1417,9 +1534,7 @@ func (h *BufPane) HalfPageDown() bool {
func (h *BufPane) ToggleDiffGutter() bool { func (h *BufPane) ToggleDiffGutter() bool {
if !h.Buf.Settings["diffgutter"].(bool) { if !h.Buf.Settings["diffgutter"].(bool) {
h.Buf.Settings["diffgutter"] = true h.Buf.Settings["diffgutter"] = true
h.Buf.UpdateDiff(func(synchronous bool) { h.Buf.UpdateDiff()
screen.Redraw()
})
InfoBar.Message("Enabled diff gutter") InfoBar.Message("Enabled diff gutter")
} else { } else {
h.Buf.Settings["diffgutter"] = false h.Buf.Settings["diffgutter"] = false
@ -1560,9 +1675,7 @@ func (h *BufPane) QuitAll() bool {
} }
quit := func() { quit := func() {
for _, b := range buffer.OpenBuffers { buffer.CloseOpenBuffers()
b.Close()
}
screen.Screen.Fini() screen.Screen.Fini()
InfoBar.Close() InfoBar.Close()
runtime.Goexit() runtime.Goexit()
@ -1691,7 +1804,7 @@ func (h *BufPane) PlayMacro() bool {
switch t := action.(type) { switch t := action.(type) {
case rune: case rune:
h.DoRuneInsert(t) h.DoRuneInsert(t)
case func(*BufPane) bool: case BufKeyAction:
t(h) t(h)
} }
} }
@ -1740,15 +1853,39 @@ func (h *BufPane) SpawnMultiCursor() bool {
return true return true
} }
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less. // SpawnMultiCursorUpN is not an action
func (h *BufPane) SpawnMultiCursorUp() bool { func (h *BufPane) SpawnMultiCursorUpN(n int) bool {
if h.Cursor.Y == 0 { lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
return false var c *buffer.Cursor
} if !h.Buf.Settings["softwrap"].(bool) {
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1}) if n > 0 && lastC.Y == 0 {
h.Cursor.Relocate() return false
}
if n < 0 && lastC.Y+1 == h.Buf.LinesNum() {
return false
}
h.Buf.DeselectCursors()
c = buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
c.LastVisualX = lastC.LastVisualX
c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)
c.Relocate()
} else {
vloc := h.VLocFromLoc(lastC.Loc)
sloc := h.Scroll(vloc.SLoc, -n)
if sloc == vloc.SLoc {
return false
}
h.Buf.DeselectCursors()
vloc.SLoc = sloc
vloc.VisualX = lastC.LastVisualX
c = buffer.NewCursor(h.Buf, h.LocFromVLoc(vloc))
c.LastVisualX = lastC.LastVisualX
}
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
h.Buf.AddCursor(c) h.Buf.AddCursor(c)
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1) h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
h.Buf.MergeCursors() h.Buf.MergeCursors()
@ -1757,20 +1894,14 @@ func (h *BufPane) SpawnMultiCursorUp() bool {
return true return true
} }
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
func (h *BufPane) SpawnMultiCursorUp() bool {
return h.SpawnMultiCursorUpN(1)
}
// SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more. // SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
func (h *BufPane) SpawnMultiCursorDown() bool { func (h *BufPane) SpawnMultiCursorDown() bool {
if h.Cursor.Y+1 == h.Buf.LinesNum() { return h.SpawnMultiCursorUpN(-1)
return false
}
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
h.Cursor.Relocate()
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
h.Buf.AddCursor(c)
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
h.Buf.MergeCursors()
h.Relocate()
return true
} }
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
@ -1807,11 +1938,27 @@ func (h *BufPane) SpawnMultiCursorSelect() bool {
return true return true
} }
// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position,
// or removes a cursor if it is already there
func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool { func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
b := h.Buf b := h.Buf
mx, my := e.Position() mx, my := e.Position()
// ignore click on the status line
if my >= h.BufView().Y+h.BufView().Height {
return false
}
mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my}) mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
if h.Buf.NumCursors() > 1 {
cursors := h.Buf.GetCursors()
for _, c := range cursors {
if c.Loc == mouseLoc {
h.Buf.RemoveCursor(c.Num)
return true
}
}
}
c := buffer.NewCursor(b, mouseLoc) c := buffer.NewCursor(b, mouseLoc)
b.AddCursor(c) b.AddCursor(c)
b.MergeCursors() b.MergeCursors()

View file

@ -88,6 +88,10 @@ func BindKey(k, v string, bind func(e Event, a string)) {
return return
} }
if strings.HasPrefix(k, "\x1b") {
screen.Screen.RegisterRawSeq(k)
}
bind(event, v) bind(event, v)
// switch e := event.(type) { // switch e := event.(type) {
@ -153,7 +157,6 @@ modSearch:
k = k[5:] k = k[5:]
modifiers |= tcell.ModShift modifiers |= tcell.ModShift
case strings.HasPrefix(k, "\x1b"): case strings.HasPrefix(k, "\x1b"):
screen.Screen.RegisterRawSeq(k)
return RawEvent{ return RawEvent{
esc: k, esc: k,
}, true }, true
@ -201,11 +204,20 @@ modSearch:
}, true }, true
} }
var mstate MouseState = MousePress
if strings.HasSuffix(k, "Drag") {
k = k[:len(k)-4]
mstate = MouseDrag
} else if strings.HasSuffix(k, "Release") {
k = k[:len(k)-7]
mstate = MouseRelease
}
// See if we can find the key in bindingMouse // See if we can find the key in bindingMouse
if code, ok := mouseEvents[k]; ok { if code, ok := mouseEvents[k]; ok {
return MouseEvent{ return MouseEvent{
btn: code, btn: code,
mod: modifiers, mod: modifiers,
state: mstate,
}, true }, true
} }
@ -239,6 +251,24 @@ func findEvent(k string) (Event, error) {
return event, nil return event, nil
} }
func eventsEqual(e1 Event, e2 Event) bool {
seq1, ok1 := e1.(KeySequenceEvent)
seq2, ok2 := e2.(KeySequenceEvent)
if ok1 && ok2 {
if len(seq1.keys) != len(seq2.keys) {
return false
}
for i := 0; i < len(seq1.keys); i++ {
if seq1.keys[i] != seq2.keys[i] {
return false
}
}
return true
}
return e1 == e2
}
// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json // TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
// Returns true if the keybinding already existed and a possible error // Returns true if the keybinding already existed and a possible error
func TryBindKey(k, v string, overwrite bool) (bool, error) { func TryBindKey(k, v string, overwrite bool) (bool, error) {
@ -264,21 +294,23 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
} }
found := false found := false
for ev := range parsed { var ev string
for ev = range parsed {
if e, err := findEvent(ev); err == nil { if e, err := findEvent(ev); err == nil {
if e == key { if eventsEqual(e, key) {
if overwrite {
parsed[ev] = v
}
found = true found = true
break break
} }
} }
} }
if found && !overwrite { if found {
return true, nil if overwrite {
} else if !found { parsed[ev] = v
} else {
return true, nil
}
} else {
parsed[k] = v parsed[k] = v
} }
@ -315,13 +347,17 @@ func UnbindKey(k string) error {
for ev := range parsed { for ev := range parsed {
if e, err := findEvent(ev); err == nil { if e, err := findEvent(ev); err == nil {
if e == key { if eventsEqual(e, key) {
delete(parsed, ev) delete(parsed, ev)
break break
} }
} }
} }
if strings.HasPrefix(k, "\x1b") {
screen.Screen.UnregisterRawSeq(k)
}
defaults := DefaultBindings("buffer") defaults := DefaultBindings("buffer")
if a, ok := defaults[k]; ok { if a, ok := defaults[k]; ok {
BindKey(k, a, Binder["buffer"]) BindKey(k, a, Binder["buffer"])

View file

@ -8,7 +8,6 @@ import (
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/buffer" "github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display" "github.com/zyedidia/micro/v2/internal/display"
ulua "github.com/zyedidia/micro/v2/internal/lua" ulua "github.com/zyedidia/micro/v2/internal/lua"
@ -17,6 +16,8 @@ import (
"github.com/zyedidia/tcell/v2" "github.com/zyedidia/tcell/v2"
) )
type BufAction interface{}
// BufKeyAction represents an action bound to a key. // BufKeyAction represents an action bound to a key.
type BufKeyAction func(*BufPane) bool type BufKeyAction func(*BufPane) bool
@ -44,8 +45,9 @@ func init() {
BufBindings = NewKeyTree() BufBindings = NewKeyTree()
} }
// LuaAction makes a BufKeyAction from a lua function. // LuaAction makes an action from a lua function. It returns either a BufKeyAction
func LuaAction(fn string) func(*BufPane) bool { // or a BufMouseAction depending on the event type.
func LuaAction(fn string, k Event) BufAction {
luaFn := strings.Split(fn, ".") luaFn := strings.Split(fn, ".")
if len(luaFn) <= 1 { if len(luaFn) <= 1 {
return nil return nil
@ -55,33 +57,42 @@ func LuaAction(fn string) func(*BufPane) bool {
if pl == nil { if pl == nil {
return nil return nil
} }
return func(h *BufPane) bool {
val, err := pl.Call(plFn, luar.New(ulua.L, h)) var action BufAction
if err != nil { switch k.(type) {
screen.TermMessage(err) case KeyEvent, KeySequenceEvent, RawEvent:
} action = BufKeyAction(func(h *BufPane) bool {
if v, ok := val.(lua.LBool); !ok { val, err := pl.Call(plFn, luar.New(ulua.L, h))
return false if err != nil {
} else { screen.TermMessage(err)
return bool(v) }
} if v, ok := val.(lua.LBool); !ok {
return false
} else {
return bool(v)
}
})
case MouseEvent:
action = BufMouseAction(func(h *BufPane, te *tcell.EventMouse) bool {
val, err := pl.Call(plFn, luar.New(ulua.L, h), luar.New(ulua.L, te))
if err != nil {
screen.TermMessage(err)
}
if v, ok := val.(lua.LBool); !ok {
return false
} else {
return bool(v)
}
})
} }
return action
} }
// BufMapKey maps an event to an action // BufMapEvent maps an event to an action
func BufMapEvent(k Event, action string) { func BufMapEvent(k Event, action string) {
config.Bindings["buffer"][k.Name()] = action config.Bindings["buffer"][k.Name()] = action
switch e := k.(type) { var actionfns []BufAction
case KeyEvent, KeySequenceEvent, RawEvent:
bufMapKey(e, action)
case MouseEvent:
bufMapMouse(e, action)
}
}
func bufMapKey(k Event, action string) {
var actionfns []func(*BufPane) bool
var names []string var names []string
var types []byte var types []byte
for i := 0; ; i++ { for i := 0; ; i++ {
@ -102,7 +113,7 @@ func bufMapKey(k Event, action string) {
action = "" action = ""
} }
var afn func(*BufPane) bool var afn BufAction
if strings.HasPrefix(a, "command:") { if strings.HasPrefix(a, "command:") {
a = strings.SplitN(a, ":", 2)[1] a = strings.SplitN(a, ":", 2)[1]
afn = CommandAction(a) afn = CommandAction(a)
@ -113,7 +124,7 @@ func bufMapKey(k Event, action string) {
names = append(names, "") names = append(names, "")
} else if strings.HasPrefix(a, "lua:") { } else if strings.HasPrefix(a, "lua:") {
a = strings.SplitN(a, ":", 2)[1] a = strings.SplitN(a, ":", 2)[1]
afn = LuaAction(a) afn = LuaAction(a, k)
if afn == nil { if afn == nil {
screen.TermMessage("Lua Error:", a, "does not exist") screen.TermMessage("Lua Error:", a, "does not exist")
continue continue
@ -129,13 +140,16 @@ func bufMapKey(k Event, action string) {
} else if f, ok := BufKeyActions[a]; ok { } else if f, ok := BufKeyActions[a]; ok {
afn = f afn = f
names = append(names, a) names = append(names, a)
} else if f, ok := BufMouseActions[a]; ok {
afn = f
names = append(names, a)
} else { } else {
screen.TermMessage("Error in bindings: action", a, "does not exist") screen.TermMessage("Error in bindings: action", a, "does not exist")
continue continue
} }
actionfns = append(actionfns, afn) actionfns = append(actionfns, afn)
} }
bufAction := func(h *BufPane) bool { bufAction := func(h *BufPane, te *tcell.EventMouse) bool {
cursors := h.Buf.GetCursors() cursors := h.Buf.GetCursors()
success := true success := true
for i, a := range actionfns { for i, a := range actionfns {
@ -147,7 +161,7 @@ func bufMapKey(k Event, action string) {
h.Buf.SetCurCursor(c.Num) h.Buf.SetCurCursor(c.Num)
h.Cursor = c h.Cursor = c
if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') { if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
innerSuccess = innerSuccess && h.execAction(a, names[i], j) innerSuccess = innerSuccess && h.execAction(a, names[i], j, te)
} else { } else {
break break
} }
@ -155,21 +169,21 @@ func bufMapKey(k Event, action string) {
// if the action changed the current pane, update the reference // if the action changed the current pane, update the reference
h = MainTab().CurPane() h = MainTab().CurPane()
success = innerSuccess success = innerSuccess
if h == nil {
// stop, in case the current pane is not a BufPane
break
}
} }
return true return true
} }
BufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(bufAction)) switch e := k.(type) {
} case KeyEvent, KeySequenceEvent, RawEvent:
BufBindings.RegisterKeyBinding(e, BufKeyActionGeneral(func(h *BufPane) bool {
// BufMapMouse maps a mouse event to an action return bufAction(h, nil)
func bufMapMouse(k MouseEvent, action string) { }))
if f, ok := BufMouseActions[action]; ok { case MouseEvent:
BufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f)) BufBindings.RegisterMouseBinding(e, BufMouseActionGeneral(bufAction))
} else {
// TODO
// delete(BufMouseBindings, k)
bufMapKey(k, action)
} }
} }
@ -200,11 +214,15 @@ type BufPane struct {
// Cursor is the currently active buffer cursor // Cursor is the currently active buffer cursor
Cursor *buffer.Cursor Cursor *buffer.Cursor
// Since tcell doesn't differentiate between a mouse release event // Since tcell doesn't differentiate between a mouse press event
// and a mouse move event with no keys pressed, we need to keep // and a mouse move event with button pressed (nor between a mouse
// track of whether or not the mouse was pressed (or not released) last event to determine // release event and a mouse move event with no buttons pressed),
// mouse release events // we need to keep track of whether or not the mouse was previously
mouseReleased bool // pressed, to determine mouse release and mouse drag events.
// Moreover, since in case of a release event tcell doesn't tell us
// which button was released, we need to keep track of which
// (possibly multiple) buttons were pressed previously.
mousePressed map[MouseEvent]bool
// We need to keep track of insert key press toggle // We need to keep track of insert key press toggle
isOverwriteMode bool isOverwriteMode bool
@ -250,7 +268,7 @@ func newBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
h.tab = tab h.tab = tab
h.Cursor = h.Buf.GetActiveCursor() h.Cursor = h.Buf.GetActiveCursor()
h.mouseReleased = true h.mousePressed = make(map[MouseEvent]bool)
return h return h
} }
@ -276,7 +294,11 @@ func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
func (h *BufPane) finishInitialize() { func (h *BufPane) finishInitialize() {
h.initialRelocate() h.initialRelocate()
h.initialized = true h.initialized = true
config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
err := config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
if err != nil {
screen.TermMessage(err)
}
} }
// Resize resizes the pane // Resize resizes the pane
@ -304,9 +326,9 @@ func (h *BufPane) ResizePane(size int) {
} }
// PluginCB calls all plugin callbacks with a certain name and displays an // PluginCB calls all plugin callbacks with a certain name and displays an
// error if there is one and returns the aggregrate boolean response // error if there is one and returns the aggregate boolean response
func (h *BufPane) PluginCB(cb string) bool { func (h *BufPane) PluginCB(cb string) bool {
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h)) b, err := config.RunPluginFnBool(h.Buf.Settings, cb, luar.New(ulua.L, h))
if err != nil { if err != nil {
screen.TermMessage(err) screen.TermMessage(err)
} }
@ -315,13 +337,19 @@ func (h *BufPane) PluginCB(cb string) bool {
// PluginCBRune is the same as PluginCB but also passes a rune to the plugins // PluginCBRune is the same as PluginCB but also passes a rune to the plugins
func (h *BufPane) PluginCBRune(cb string, r rune) bool { func (h *BufPane) PluginCBRune(cb string, r rune) bool {
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r))) b, err := config.RunPluginFnBool(h.Buf.Settings, cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
if err != nil { if err != nil {
screen.TermMessage(err) screen.TermMessage(err)
} }
return b return b
} }
func (h *BufPane) resetMouse() {
for me := range h.mousePressed {
delete(h.mousePressed, me)
}
}
// OpenBuffer opens the given buffer in this pane. // OpenBuffer opens the given buffer in this pane.
func (h *BufPane) OpenBuffer(b *buffer.Buffer) { func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
h.Buf.Close() h.Buf.Close()
@ -332,7 +360,7 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
h.initialRelocate() h.initialRelocate()
// Set mouseReleased to true because we assume the mouse is not being // Set mouseReleased to true because we assume the mouse is not being
// pressed when the editor is opened // pressed when the editor is opened
h.mouseReleased = true h.resetMouse()
// Set isOverwriteMode to false, because we assume we are in the default // Set isOverwriteMode to false, because we assume we are in the default
// mode when editor is opened // mode when editor is opened
h.isOverwriteMode = false h.isOverwriteMode = false
@ -395,20 +423,40 @@ func (h *BufPane) Name() string {
return n return n
} }
// ReOpen reloads the file opened in the bufpane from disk
func (h *BufPane) ReOpen() {
h.Buf.ReOpen()
h.Relocate()
}
func (h *BufPane) getReloadSetting() string {
reloadSetting := h.Buf.Settings["reload"]
return reloadSetting.(string)
}
// HandleEvent executes the tcell event properly // HandleEvent executes the tcell event properly
func (h *BufPane) HandleEvent(event tcell.Event) { func (h *BufPane) HandleEvent(event tcell.Event) {
if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled { if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) { reload := h.getReloadSetting()
if canceled {
h.Buf.DisableReload()
}
if !yes || canceled {
h.Buf.UpdateModTime()
} else {
h.Buf.ReOpen()
}
})
if reload == "prompt" {
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
if canceled {
h.Buf.DisableReload()
}
if !yes || canceled {
h.Buf.UpdateModTime()
} else {
h.ReOpen()
}
})
} else if reload == "auto" {
h.ReOpen()
} else if reload == "disabled" {
h.Buf.DisableReload()
} else {
InfoBar.Message("Invalid reload setting")
}
} }
switch e := event.(type) { switch e := event.(type) {
@ -432,50 +480,37 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
h.DoRuneInsert(e.Rune()) h.DoRuneInsert(e.Rune())
} }
case *tcell.EventMouse: case *tcell.EventMouse:
cancel := false if e.Buttons() != tcell.ButtonNone {
switch e.Buttons() {
case tcell.Button1:
_, my := e.Position()
if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
cancel = true
}
case tcell.ButtonNone:
// Mouse event with no click
if !h.mouseReleased {
// Mouse was just released
// mx, my := e.Position()
// mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
// we could finish the selection based on the release location as described
// below but when the mouse click is within the scroll margin this will
// cause a scroll and selection even for a simple mouse click which is
// not good
// for terminals that don't support mouse motion events, selection via
// the mouse won't work but this is ok
// Relocating here isn't really necessary because the cursor will
// be in the right place from the last mouse event
// However, if we are running in a terminal that doesn't support mouse motion
// events, this still allows the user to make selections, except only after they
// release the mouse
// if !h.doubleClick && !h.tripleClick {
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
// }
if h.Cursor.HasSelection() {
h.Cursor.CopySelection(clipboard.PrimaryReg)
}
h.mouseReleased = true
}
}
if !cancel {
me := MouseEvent{ me := MouseEvent{
btn: e.Buttons(), btn: e.Buttons(),
mod: metaToAlt(e.Modifiers()), mod: metaToAlt(e.Modifiers()),
state: MousePress,
}
isDrag := len(h.mousePressed) > 0
if e.Buttons() & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone {
h.mousePressed[me] = true
}
if isDrag {
me.state = MouseDrag
} }
h.DoMouseEvent(me, e) h.DoMouseEvent(me, e)
} else {
// Mouse event with no click - mouse was just released.
// If there were multiple mouse buttons pressed, we don't know which one
// was actually released, so we assume they all were released.
pressed := len(h.mousePressed) > 0
for me := range h.mousePressed {
delete(h.mousePressed, me)
me.state = MouseRelease
h.DoMouseEvent(me, e)
}
if !pressed {
// Propagate the mouse release in case the press wasn't for this BufPane
Tabs.ResetMouse()
}
} }
} }
h.Buf.MergeCursors() h.Buf.MergeCursors()
@ -495,6 +530,14 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
InfoBar.ClearGutter() InfoBar.ClearGutter()
} }
} }
cursors := h.Buf.GetCursors()
for _, c := range cursors {
if c.NewTrailingWsY != c.Y && (!c.HasSelection() ||
(c.NewTrailingWsY != c.CurSelection[0].Y && c.NewTrailingWsY != c.CurSelection[1].Y)) {
c.NewTrailingWsY = -1
}
}
} }
// Bindings returns the current bindings tree for this buffer. // Bindings returns the current bindings tree for this buffer.
@ -506,7 +549,10 @@ func (h *BufPane) Bindings() *KeyTree {
} }
// DoKeyEvent executes a key event by finding the action it is bound // DoKeyEvent executes a key event by finding the action it is bound
// to and executing it (possibly multiple times for multiple cursors) // to and executing it (possibly multiple times for multiple cursors).
// Returns true if the action was executed OR if there are more keys
// remaining to process before executing an action (if this is a key
// sequence event). Returns false if no action found.
func (h *BufPane) DoKeyEvent(e Event) bool { func (h *BufPane) DoKeyEvent(e Event) bool {
binds := h.Bindings() binds := h.Bindings()
action, more := binds.NextEvent(e, nil) action, more := binds.NextEvent(e, nil)
@ -520,7 +566,7 @@ func (h *BufPane) DoKeyEvent(e Event) bool {
return more return more
} }
func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool { func (h *BufPane) execAction(action BufAction, name string, cursor int, te *tcell.EventMouse) bool {
if name != "Autocomplete" && name != "CycleAutocompleteBack" { if name != "Autocomplete" && name != "CycleAutocompleteBack" {
h.Buf.HasSuggestions = false h.Buf.HasSuggestions = false
} }
@ -528,7 +574,13 @@ func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int
_, isMulti := MultiActions[name] _, isMulti := MultiActions[name]
if (!isMulti && cursor == 0) || isMulti { if (!isMulti && cursor == 0) || isMulti {
if h.PluginCB("pre" + name) { if h.PluginCB("pre" + name) {
success := action(h) var success bool
switch a := action.(type) {
case BufKeyAction:
success = a(h)
case BufMouseAction:
success = a(h, te)
}
success = success && h.PluginCB("on"+name) success = success && h.PluginCB("on"+name)
if isMulti { if isMulti {
@ -649,6 +701,10 @@ func (h *BufPane) Close() {
// SetActive marks this pane as active. // SetActive marks this pane as active.
func (h *BufPane) SetActive(b bool) { func (h *BufPane) SetActive(b bool) {
if h.IsActive() == b {
return
}
h.BWindow.SetActive(b) h.BWindow.SetActive(b)
if b { if b {
// Display any gutter messages for this line // Display any gutter messages for this line
@ -664,8 +720,12 @@ func (h *BufPane) SetActive(b bool) {
if none && InfoBar.HasGutter { if none && InfoBar.HasGutter {
InfoBar.ClearGutter() InfoBar.ClearGutter()
} }
}
err := config.RunPluginFn("onSetActive", luar.New(ulua.L, h))
if err != nil {
screen.TermMessage(err)
}
}
} }
// BufKeyActions contains the list of all possible key actions the bufhandler could execute // BufKeyActions contains the list of all possible key actions the bufhandler could execute
@ -686,10 +746,16 @@ var BufKeyActions = map[string]BufKeyAction{
"SelectRight": (*BufPane).SelectRight, "SelectRight": (*BufPane).SelectRight,
"WordRight": (*BufPane).WordRight, "WordRight": (*BufPane).WordRight,
"WordLeft": (*BufPane).WordLeft, "WordLeft": (*BufPane).WordLeft,
"SubWordRight": (*BufPane).SubWordRight,
"SubWordLeft": (*BufPane).SubWordLeft,
"SelectWordRight": (*BufPane).SelectWordRight, "SelectWordRight": (*BufPane).SelectWordRight,
"SelectWordLeft": (*BufPane).SelectWordLeft, "SelectWordLeft": (*BufPane).SelectWordLeft,
"SelectSubWordRight": (*BufPane).SelectSubWordRight,
"SelectSubWordLeft": (*BufPane).SelectSubWordLeft,
"DeleteWordRight": (*BufPane).DeleteWordRight, "DeleteWordRight": (*BufPane).DeleteWordRight,
"DeleteWordLeft": (*BufPane).DeleteWordLeft, "DeleteWordLeft": (*BufPane).DeleteWordLeft,
"DeleteSubWordRight": (*BufPane).DeleteSubWordRight,
"DeleteSubWordLeft": (*BufPane).DeleteSubWordLeft,
"SelectLine": (*BufPane).SelectLine, "SelectLine": (*BufPane).SelectLine,
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine, "SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
"SelectToStartOfText": (*BufPane).SelectToStartOfText, "SelectToStartOfText": (*BufPane).SelectToStartOfText,
@ -708,6 +774,8 @@ var BufKeyActions = map[string]BufKeyAction{
"FindLiteral": (*BufPane).FindLiteral, "FindLiteral": (*BufPane).FindLiteral,
"FindNext": (*BufPane).FindNext, "FindNext": (*BufPane).FindNext,
"FindPrevious": (*BufPane).FindPrevious, "FindPrevious": (*BufPane).FindPrevious,
"DiffNext": (*BufPane).DiffNext,
"DiffPrevious": (*BufPane).DiffPrevious,
"Center": (*BufPane).Center, "Center": (*BufPane).Center,
"Undo": (*BufPane).Undo, "Undo": (*BufPane).Undo,
"Redo": (*BufPane).Redo, "Redo": (*BufPane).Redo,
@ -788,6 +856,8 @@ var BufKeyActions = map[string]BufKeyAction{
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
var BufMouseActions = map[string]BufMouseAction{ var BufMouseActions = map[string]BufMouseAction{
"MousePress": (*BufPane).MousePress, "MousePress": (*BufPane).MousePress,
"MouseDrag": (*BufPane).MouseDrag,
"MouseRelease": (*BufPane).MouseRelease,
"MouseMultiCursor": (*BufPane).MouseMultiCursor, "MouseMultiCursor": (*BufPane).MouseMultiCursor,
} }
@ -812,10 +882,16 @@ var MultiActions = map[string]bool{
"SelectRight": true, "SelectRight": true,
"WordRight": true, "WordRight": true,
"WordLeft": true, "WordLeft": true,
"SubWordRight": true,
"SubWordLeft": true,
"SelectWordRight": true, "SelectWordRight": true,
"SelectWordLeft": true, "SelectWordLeft": true,
"SelectSubWordRight": true,
"SelectSubWordLeft": true,
"DeleteWordRight": true, "DeleteWordRight": true,
"DeleteWordLeft": true, "DeleteWordLeft": true,
"DeleteSubWordRight": true,
"DeleteSubWordLeft": true,
"SelectLine": true, "SelectLine": true,
"SelectToStartOfLine": true, "SelectToStartOfLine": true,
"SelectToStartOfText": true, "SelectToStartOfText": true,

View file

@ -41,6 +41,7 @@ func InitCommands() {
"unbind": {(*BufPane).UnbindCmd, nil}, "unbind": {(*BufPane).UnbindCmd, nil},
"quit": {(*BufPane).QuitCmd, nil}, "quit": {(*BufPane).QuitCmd, nil},
"goto": {(*BufPane).GotoCmd, nil}, "goto": {(*BufPane).GotoCmd, nil},
"jump": {(*BufPane).JumpCmd, nil},
"save": {(*BufPane).SaveCmd, nil}, "save": {(*BufPane).SaveCmd, nil},
"replace": {(*BufPane).ReplaceCmd, nil}, "replace": {(*BufPane).ReplaceCmd, nil},
"replaceall": {(*BufPane).ReplaceAllCmd, nil}, "replaceall": {(*BufPane).ReplaceAllCmd, nil},
@ -329,13 +330,30 @@ func (h *BufPane) ToggleLogCmd(args []string) {
} }
} }
// ReloadCmd reloads all files (syntax files, colorschemes...) // ReloadCmd reloads all files (syntax files, colorschemes, plugins...)
func (h *BufPane) ReloadCmd(args []string) { func (h *BufPane) ReloadCmd(args []string) {
ReloadConfig() reloadRuntime(true)
} }
// ReloadConfig reloads only the configuration
func ReloadConfig() { func ReloadConfig() {
config.InitRuntimeFiles() reloadRuntime(false)
}
func reloadRuntime(reloadPlugins bool) {
if reloadPlugins {
err := config.RunPluginFn("deinit")
if err != nil {
screen.TermMessage(err)
}
}
config.InitRuntimeFiles(true)
if reloadPlugins {
config.InitPlugins()
}
err := config.ReadSettings() err := config.ReadSettings()
if err != nil { if err != nil {
screen.TermMessage(err) screen.TermMessage(err)
@ -344,14 +362,36 @@ func ReloadConfig() {
if err != nil { if err != nil {
screen.TermMessage(err) screen.TermMessage(err)
} }
if reloadPlugins {
err = config.LoadAllPlugins()
if err != nil {
screen.TermMessage(err)
}
}
InitBindings() InitBindings()
InitCommands() InitCommands()
if reloadPlugins {
err = config.RunPluginFn("preinit")
if err != nil {
screen.TermMessage(err)
}
err = config.RunPluginFn("init")
if err != nil {
screen.TermMessage(err)
}
err = config.RunPluginFn("postinit")
if err != nil {
screen.TermMessage(err)
}
}
err = config.InitColorscheme() err = config.InitColorscheme()
if err != nil { if err != nil {
screen.TermMessage(err) screen.TermMessage(err)
} }
for _, b := range buffer.OpenBuffers { for _, b := range buffer.OpenBuffers {
b.UpdateRules() b.UpdateRules()
} }
@ -363,22 +403,24 @@ func (h *BufPane) ReopenCmd(args []string) {
InfoBar.YNPrompt("Save file before reopen?", func(yes, canceled bool) { InfoBar.YNPrompt("Save file before reopen?", func(yes, canceled bool) {
if !canceled && yes { if !canceled && yes {
h.Save() h.Save()
h.Buf.ReOpen() h.ReOpen()
} else if !canceled { } else if !canceled {
h.Buf.ReOpen() h.ReOpen()
} }
}) })
} else { } else {
h.Buf.ReOpen() h.ReOpen()
} }
} }
func (h *BufPane) openHelp(page string) error { func (h *BufPane) openHelp(page string) error {
if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil { if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {
return errors.New(fmt.Sprint("Unable to load help text", page, "\n", err)) return errors.New(fmt.Sprintf("Unable to load help text for %s: %v", page, err))
} else { } else {
helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp) helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp)
helpBuffer.SetName("Help " + page) helpBuffer.SetName("Help " + page)
helpBuffer.SetOptionNative("hltaberrors", false)
helpBuffer.SetOptionNative("hltrailingws", false)
if h.Buf.Type == buffer.BTHelp { if h.Buf.Type == buffer.BTHelp {
h.OpenBuffer(helpBuffer) h.OpenBuffer(helpBuffer)
@ -471,61 +513,61 @@ func (h *BufPane) NewTabCmd(args []string) {
} }
func SetGlobalOptionNative(option string, nativeValue interface{}) error { func SetGlobalOptionNative(option string, nativeValue interface{}) error {
local := false // check for local option first...
for _, s := range config.LocalSettings { for _, s := range config.LocalSettings {
if s == option { if s == option {
local = true MainTab().CurPane().Buf.SetOptionNative(option, nativeValue)
break return nil
} }
} }
if !local { // ...if it's not local continue with the globals
config.GlobalSettings[option] = nativeValue config.GlobalSettings[option] = nativeValue
config.ModifiedSettings[option] = true config.ModifiedSettings[option] = true
delete(config.VolatileSettings, option)
if option == "colorscheme" { if option == "colorscheme" {
// LoadSyntaxFiles() // LoadSyntaxFiles()
config.InitColorscheme() config.InitColorscheme()
for _, b := range buffer.OpenBuffers { for _, b := range buffer.OpenBuffers {
b.UpdateRules() b.UpdateRules()
} }
} else if option == "infobar" || option == "keymenu" { } else if option == "infobar" || option == "keymenu" {
Tabs.Resize() Tabs.Resize()
} else if option == "mouse" { } else if option == "mouse" {
if !nativeValue.(bool) { if !nativeValue.(bool) {
screen.Screen.DisableMouse() screen.Screen.DisableMouse()
} else {
screen.Screen.EnableMouse()
}
} else if option == "autosave" {
if nativeValue.(float64) > 0 {
config.SetAutoTime(int(nativeValue.(float64)))
config.StartAutoSave()
} else {
config.SetAutoTime(0)
}
} else if option == "paste" {
screen.Screen.SetPaste(nativeValue.(bool))
} else if option == "clipboard" {
m := clipboard.SetMethod(nativeValue.(string))
err := clipboard.Initialize(m)
if err != nil {
return err
}
} else { } else {
for _, pl := range config.Plugins { screen.Screen.EnableMouse()
if option == pl.Name { }
if nativeValue.(bool) && !pl.Loaded { } else if option == "autosave" {
pl.Load() if nativeValue.(float64) > 0 {
_, err := pl.Call("init") config.SetAutoTime(int(nativeValue.(float64)))
if err != nil && err != config.ErrNoSuchFunction { config.StartAutoSave()
screen.TermMessage(err) } else {
} config.SetAutoTime(0)
} else if !nativeValue.(bool) && pl.Loaded { }
_, err := pl.Call("deinit") } else if option == "paste" {
if err != nil && err != config.ErrNoSuchFunction { screen.Screen.SetPaste(nativeValue.(bool))
screen.TermMessage(err) } else if option == "clipboard" {
} m := clipboard.SetMethod(nativeValue.(string))
err := clipboard.Initialize(m)
if err != nil {
return err
}
} else {
for _, pl := range config.Plugins {
if option == pl.Name {
if nativeValue.(bool) && !pl.Loaded {
pl.Load()
_, err := pl.Call("init")
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
} else if !nativeValue.(bool) && pl.Loaded {
_, err := pl.Call("deinit")
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
} }
} }
} }
@ -634,6 +676,11 @@ func (h *BufPane) ShowCmd(args []string) {
InfoBar.Message(option) InfoBar.Message(option)
} }
func parseKeyArg(arg string) string {
// If this is a raw escape sequence, convert it to its raw byte form
return strings.ReplaceAll(arg, "\\x1b", "\x1b")
}
// ShowKeyCmd displays the action that a key is bound to // ShowKeyCmd displays the action that a key is bound to
func (h *BufPane) ShowKeyCmd(args []string) { func (h *BufPane) ShowKeyCmd(args []string) {
if len(args) < 1 { if len(args) < 1 {
@ -641,7 +688,7 @@ func (h *BufPane) ShowKeyCmd(args []string) {
return return
} }
event, err := findEvent(args[0]) event, err := findEvent(parseKeyArg(args[0]))
if err != nil { if err != nil {
InfoBar.Error(err) InfoBar.Error(err)
return return
@ -660,7 +707,7 @@ func (h *BufPane) BindCmd(args []string) {
return return
} }
_, err := TryBindKey(args[0], args[1], true) _, err := TryBindKey(parseKeyArg(args[0]), args[1], true)
if err != nil { if err != nil {
InfoBar.Error(err) InfoBar.Error(err)
} }
@ -673,7 +720,7 @@ func (h *BufPane) UnbindCmd(args []string) {
return return
} }
err := UnbindKey(args[0]) err := UnbindKey(parseKeyArg(args[0]))
if err != nil { if err != nil {
InfoBar.Error(err) InfoBar.Error(err)
} }
@ -701,41 +748,65 @@ func (h *BufPane) QuitCmd(args []string) {
// position in the buffer // position in the buffer
// For example: `goto line`, or `goto line:col` // For example: `goto line`, or `goto line:col`
func (h *BufPane) GotoCmd(args []string) { func (h *BufPane) GotoCmd(args []string) {
line, col, err := h.parseLineCol(args)
if err != nil {
InfoBar.Error(err)
return
}
if line < 0 {
line = h.Buf.LinesNum() + 1 + line
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
h.RemoveAllMultiCursors()
h.GotoLoc(buffer.Loc{col, line})
}
// JumpCmd is a command that will send the cursor to a certain relative
// position in the buffer
// For example: `jump line`, `jump -line`, or `jump -line:col`
func (h *BufPane) JumpCmd(args []string) {
line, col, err := h.parseLineCol(args)
if err != nil {
InfoBar.Error(err)
return
}
line = h.Buf.GetActiveCursor().Y + 1 + line
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
h.RemoveAllMultiCursors()
h.GotoLoc(buffer.Loc{col, line})
}
// parseLineCol is a helper to parse the input of GotoCmd and JumpCmd
func (h *BufPane) parseLineCol(args []string) (line int, col int, err error) {
if len(args) <= 0 { if len(args) <= 0 {
InfoBar.Error("Not enough arguments") return 0, 0, errors.New("Not enough arguments")
}
line, col = 0, 0
if strings.Contains(args[0], ":") {
parts := strings.SplitN(args[0], ":", 2)
line, err = strconv.Atoi(parts[0])
if err != nil {
return 0, 0, err
}
col, err = strconv.Atoi(parts[1])
if err != nil {
return 0, 0, err
}
} else { } else {
h.RemoveAllMultiCursors() line, err = strconv.Atoi(args[0])
if strings.Contains(args[0], ":") { if err != nil {
parts := strings.SplitN(args[0], ":", 2) return 0, 0, err
line, err := strconv.Atoi(parts[0])
if err != nil {
InfoBar.Error(err)
return
}
col, err := strconv.Atoi(parts[1])
if err != nil {
InfoBar.Error(err)
return
}
if line < 0 {
line = h.Buf.LinesNum() + 1 + line
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
h.GotoLoc(buffer.Loc{col, line})
} else {
line, err := strconv.Atoi(args[0])
if err != nil {
InfoBar.Error(err)
return
}
if line < 0 {
line = h.Buf.LinesNum() + 1 + line
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
h.GotoLoc(buffer.Loc{0, line})
} }
} }
return line, col, nil
} }
// SaveCmd saves the buffer optionally with an argument file name // SaveCmd saves the buffer optionally with an argument file name
@ -810,7 +881,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
end = h.Cursor.CurSelection[1] end = h.Cursor.CurSelection[1]
} }
if all { if all {
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace) nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace, !noRegex)
} else { } else {
inRange := func(l buffer.Loc) bool { inRange := func(l buffer.Loc) bool {
return l.GreaterEqual(start) && l.LessEqual(end) return l.GreaterEqual(start) && l.LessEqual(end)
@ -840,7 +911,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) { InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
if !canceled && yes { if !canceled && yes {
_, nrunes := h.Buf.ReplaceRegex(locs[0], locs[1], regex, replace) _, nrunes := h.Buf.ReplaceRegex(locs[0], locs[1], regex, replace, !noRegex)
searchLoc = locs[0] searchLoc = locs[0]
searchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf) searchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf)

View file

@ -41,6 +41,8 @@ var bufdefaults = map[string]string{
"Alt-F": "FindLiteral", "Alt-F": "FindLiteral",
"Ctrl-n": "FindNext", "Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious", "Ctrl-p": "FindPrevious",
"Alt-[": "DiffPrevious|CursorStart",
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo", "Ctrl-z": "Undo",
"Ctrl-y": "Redo", "Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy", "Ctrl-c": "CopyLine|Copy",
@ -90,11 +92,13 @@ var bufdefaults = map[string]string{
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch", "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
// Mouse bindings // Mouse bindings
"MouseWheelUp": "ScrollUp", "MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown", "MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress", "MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary", "MouseLeftDrag": "MouseDrag",
"Ctrl-MouseLeft": "MouseMultiCursor", "MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor", "Alt-n": "SpawnMultiCursor",
"AltShiftUp": "SpawnMultiCursorUp", "AltShiftUp": "SpawnMultiCursorUp",
@ -173,8 +177,10 @@ var infodefaults = map[string]string{
"Esc": "AbortCommand", "Esc": "AbortCommand",
// Mouse bindings // Mouse bindings
"MouseWheelUp": "HistoryUp", "MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown", "MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress", "MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary", "MouseLeftDrag": "MouseDrag",
"MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
} }

View file

@ -1,3 +1,4 @@
//go:build !darwin
// +build !darwin // +build !darwin
package action package action
@ -43,6 +44,8 @@ var bufdefaults = map[string]string{
"Alt-F": "FindLiteral", "Alt-F": "FindLiteral",
"Ctrl-n": "FindNext", "Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious", "Ctrl-p": "FindPrevious",
"Alt-[": "DiffPrevious|CursorStart",
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo", "Ctrl-z": "Undo",
"Ctrl-y": "Redo", "Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy", "Ctrl-c": "CopyLine|Copy",
@ -92,11 +95,13 @@ var bufdefaults = map[string]string{
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch", "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
// Mouse bindings // Mouse bindings
"MouseWheelUp": "ScrollUp", "MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown", "MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress", "MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary", "MouseLeftDrag": "MouseDrag",
"Ctrl-MouseLeft": "MouseMultiCursor", "MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor", "Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect", "Alt-m": "SpawnMultiCursorSelect",
@ -175,8 +180,10 @@ var infodefaults = map[string]string{
"Esc": "AbortCommand", "Esc": "AbortCommand",
// Mouse bindings // Mouse bindings
"MouseWheelUp": "HistoryUp", "MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown", "MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress", "MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary", "MouseLeftDrag": "MouseDrag",
"MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
} }

View file

@ -100,11 +100,20 @@ func (k KeySequenceEvent) Name() string {
return buf.String() return buf.String()
} }
type MouseState int
const (
MousePress = iota
MouseDrag
MouseRelease
)
// MouseEvent is a mouse event with a mouse button and // MouseEvent is a mouse event with a mouse button and
// any possible key modifiers // any possible key modifiers
type MouseEvent struct { type MouseEvent struct {
btn tcell.ButtonMask btn tcell.ButtonMask
mod tcell.ModMask mod tcell.ModMask
state MouseState
} }
func (m MouseEvent) Name() string { func (m MouseEvent) Name() string {
@ -122,9 +131,17 @@ func (m MouseEvent) Name() string {
mod = "Ctrl-" mod = "Ctrl-"
} }
state := ""
switch m.state {
case MouseDrag:
state = "Drag"
case MouseRelease:
state = "Release"
}
for k, v := range mouseEvents { for k, v := range mouseEvents {
if v == m.btn { if v == m.btn {
return fmt.Sprintf("%s%s", mod, k) return fmt.Sprintf("%s%s%s", mod, k, state)
} }
} }
return "" return ""

View file

@ -8,6 +8,7 @@ import (
"github.com/zyedidia/micro/v2/internal/buffer" "github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/pkg/highlight"
) )
// This file is meant (for now) for autocompletion in command mode, not // This file is meant (for now) for autocompletion in command mode, not
@ -17,7 +18,7 @@ import (
// CommandComplete autocompletes commands // CommandComplete autocompletes commands
func CommandComplete(b *buffer.Buffer) ([]string, []string) { func CommandComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor() c := b.GetActiveCursor()
input, argstart := buffer.GetArg(b) input, argstart := b.GetArg()
var suggestions []string var suggestions []string
for cmd := range commands { for cmd := range commands {
@ -38,7 +39,7 @@ func CommandComplete(b *buffer.Buffer) ([]string, []string) {
// HelpComplete autocompletes help topics // HelpComplete autocompletes help topics
func HelpComplete(b *buffer.Buffer) ([]string, []string) { func HelpComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor() c := b.GetActiveCursor()
input, argstart := buffer.GetArg(b) input, argstart := b.GetArg()
var suggestions []string var suggestions []string
@ -77,6 +78,63 @@ func colorschemeComplete(input string) (string, []string) {
return chosen, suggestions return chosen, suggestions
} }
// filetypeComplete autocompletes filetype
func filetypeComplete(input string) (string, []string) {
var suggestions []string
// We cannot match filetypes just by names of syntax files,
// since those names may be different from the actual filetype values
// specified inside syntax files (e.g. "c++" filetype in cpp.yaml).
// So we need to parse filetype values out of those files.
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
data, err := f.Data()
if err != nil {
continue
}
header, err := highlight.MakeHeaderYaml(data)
if err != nil {
continue
}
// Prevent duplicated defaults
if header.FileType == "off" || header.FileType == "unknown" {
continue
}
if strings.HasPrefix(header.FileType, input) {
suggestions = append(suggestions, header.FileType)
}
}
headerLoop:
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
data, err := f.Data()
if err != nil {
continue
}
header, err := highlight.MakeHeader(data)
if err != nil {
continue
}
for _, v := range suggestions {
if v == header.FileType {
continue headerLoop
}
}
if strings.HasPrefix(header.FileType, input) {
suggestions = append(suggestions, header.FileType)
}
}
if strings.HasPrefix("off", input) {
suggestions = append(suggestions, "off")
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
func contains(s []string, e string) bool { func contains(s []string, e string) bool {
for _, a := range s { for _, a := range s {
if a == e { if a == e {
@ -89,7 +147,7 @@ func contains(s []string, e string) bool {
// OptionComplete autocompletes options // OptionComplete autocompletes options
func OptionComplete(b *buffer.Buffer) ([]string, []string) { func OptionComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor() c := b.GetActiveCursor()
input, argstart := buffer.GetArg(b) input, argstart := b.GetArg()
var suggestions []string var suggestions []string
for option := range config.GlobalSettings { for option := range config.GlobalSettings {
@ -116,7 +174,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor() c := b.GetActiveCursor()
l := b.LineBytes(c.Y) l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X) l = util.SliceStart(l, c.X)
input, argstart := buffer.GetArg(b) input, argstart := b.GetArg()
completeValue := false completeValue := false
args := bytes.Split(l, []byte{' '}) args := bytes.Split(l, []byte{' '})
@ -172,13 +230,8 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
switch inputOpt { switch inputOpt {
case "colorscheme": case "colorscheme":
_, suggestions = colorschemeComplete(input) _, suggestions = colorschemeComplete(input)
case "fileformat": case "filetype":
if strings.HasPrefix("unix", input) { _, suggestions = filetypeComplete(input)
suggestions = append(suggestions, "unix")
}
if strings.HasPrefix("dos", input) {
suggestions = append(suggestions, "dos")
}
case "sucmd": case "sucmd":
if strings.HasPrefix("sudo", input) { if strings.HasPrefix("sudo", input) {
suggestions = append(suggestions, "sudo") suggestions = append(suggestions, "sudo")
@ -186,15 +239,13 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
if strings.HasPrefix("doas", input) { if strings.HasPrefix("doas", input) {
suggestions = append(suggestions, "doas") suggestions = append(suggestions, "doas")
} }
case "clipboard": default:
if strings.HasPrefix("external", input) { if choices, ok := config.OptionChoices[inputOpt]; ok {
suggestions = append(suggestions, "external") for _, choice := range choices {
} if strings.HasPrefix(choice, input) {
if strings.HasPrefix("internal", input) { suggestions = append(suggestions, choice)
suggestions = append(suggestions, "internal") }
} }
if strings.HasPrefix("terminal", input) {
suggestions = append(suggestions, "terminal")
} }
} }
} }
@ -210,7 +261,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
// PluginCmdComplete autocompletes the plugin command // PluginCmdComplete autocompletes the plugin command
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) { func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor() c := b.GetActiveCursor()
input, argstart := buffer.GetArg(b) input, argstart := b.GetArg()
var suggestions []string var suggestions []string
for _, cmd := range PluginCmds { for _, cmd := range PluginCmds {
@ -232,7 +283,7 @@ func PluginComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor() c := b.GetActiveCursor()
l := b.LineBytes(c.Y) l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X) l = util.SliceStart(l, c.X)
input, argstart := buffer.GetArg(b) input, argstart := b.GetArg()
completeValue := false completeValue := false
args := bytes.Split(l, []byte{' '}) args := bytes.Split(l, []byte{' '})

View file

@ -83,6 +83,8 @@ func (h *InfoPane) Close() {
func (h *InfoPane) HandleEvent(event tcell.Event) { func (h *InfoPane) HandleEvent(event tcell.Event) {
switch e := event.(type) { switch e := event.(type) {
case *tcell.EventResize:
// TODO
case *tcell.EventKey: case *tcell.EventKey:
ke := KeyEvent{ ke := KeyEvent{
code: e.Key(), code: e.Key(),
@ -93,12 +95,14 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
done := h.DoKeyEvent(ke) done := h.DoKeyEvent(ke)
hasYN := h.HasYN hasYN := h.HasYN
if e.Key() == tcell.KeyRune && hasYN { if e.Key() == tcell.KeyRune && hasYN {
if (e.Rune() == 'y' || e.Rune() == 'Y') && hasYN { y := e.Rune() == 'y' || e.Rune() == 'Y'
h.YNResp = true n := e.Rune() == 'n' || e.Rune() == 'N'
h.DonePrompt(false) if y || n {
} else if (e.Rune() == 'n' || e.Rune() == 'N') && hasYN { h.YNResp = y
h.YNResp = false
h.DonePrompt(false) h.DonePrompt(false)
InfoBindings.ResetEvents()
InfoBufBindings.ResetEvents()
} }
} }
if e.Key() == tcell.KeyRune && !done && !hasYN { if e.Key() == tcell.KeyRune && !done && !hasYN {
@ -108,7 +112,11 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
if done && h.HasPrompt && !hasYN { if done && h.HasPrompt && !hasYN {
resp := string(h.LineBytes(0)) resp := string(h.LineBytes(0))
hist := h.History[h.PromptType] hist := h.History[h.PromptType]
hist[h.HistoryNum] = resp if resp != hist[h.HistoryNum] {
h.HistoryNum = len(hist) - 1
hist[h.HistoryNum] = resp
h.HistorySearch = false
}
if h.EventCallback != nil { if h.EventCallback != nil {
h.EventCallback(resp) h.EventCallback(resp)
} }
@ -118,7 +126,10 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
} }
} }
// DoKeyEvent executes a key event for the command bar, doing any overridden actions // DoKeyEvent executes a key event for the command bar, doing any overridden actions.
// Returns true if the action was executed OR if there are more keys remaining
// to process before executing an action (if this is a key sequence event).
// Returns false if no action found.
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool { func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
action, more := InfoBindings.NextEvent(e, nil) action, more := InfoBindings.NextEvent(e, nil)
if action != nil && !more { if action != nil && !more {
@ -132,11 +143,25 @@ func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
} }
if !more { if !more {
// If no infopane action found, try to find a bufpane action.
//
// TODO: this is buggy. For example, if the command bar has the following
// two bindings:
//
// "<Ctrl-x><Ctrl-p>": "HistoryUp",
// "<Ctrl-x><Ctrl-v>": "Paste",
//
// the 2nd binding (with a bufpane action) doesn't work, since <Ctrl-x>
// has been already consumed by the 1st binding (with an infopane action).
//
// We should either iterate both InfoBindings and InfoBufBindings keytrees
// together, or just use the same keytree for both infopane and bufpane
// bindings.
action, more = InfoBufBindings.NextEvent(e, nil) action, more = InfoBufBindings.NextEvent(e, nil)
if action != nil && !more { if action != nil && !more {
done := action(h.BufPane) action(h.BufPane)
InfoBufBindings.ResetEvents() InfoBufBindings.ResetEvents()
return done return true
} else if action == nil && !more { } else if action == nil && !more {
InfoBufBindings.ResetEvents() InfoBufBindings.ResetEvents()
} }
@ -155,6 +180,18 @@ func (h *InfoPane) HistoryDown() {
h.DownHistory(h.History[h.PromptType]) h.DownHistory(h.History[h.PromptType])
} }
// HistorySearchUp fetches the previous history item beginning with the text
// in the infobuffer before cursor
func (h *InfoPane) HistorySearchUp() {
h.SearchUpHistory(h.History[h.PromptType])
}
// HistorySearchDown fetches the next history item beginning with the text
// in the infobuffer before cursor
func (h *InfoPane) HistorySearchDown() {
h.SearchDownHistory(h.History[h.PromptType])
}
// Autocomplete begins autocompletion // Autocomplete begins autocompletion
func (h *InfoPane) CommandComplete() { func (h *InfoPane) CommandComplete() {
b := h.Buf b := h.Buf
@ -198,9 +235,11 @@ func (h *InfoPane) AbortCommand() {
// InfoKeyActions contains the list of all possible key actions the infopane could execute // InfoKeyActions contains the list of all possible key actions the infopane could execute
var InfoKeyActions = map[string]InfoKeyAction{ var InfoKeyActions = map[string]InfoKeyAction{
"HistoryUp": (*InfoPane).HistoryUp, "HistoryUp": (*InfoPane).HistoryUp,
"HistoryDown": (*InfoPane).HistoryDown, "HistoryDown": (*InfoPane).HistoryDown,
"CommandComplete": (*InfoPane).CommandComplete, "HistorySearchUp": (*InfoPane).HistorySearchUp,
"ExecuteCommand": (*InfoPane).ExecuteCommand, "HistorySearchDown": (*InfoPane).HistorySearchDown,
"AbortCommand": (*InfoPane).AbortCommand, "CommandComplete": (*InfoPane).CommandComplete,
"ExecuteCommand": (*InfoPane).ExecuteCommand,
"AbortCommand": (*InfoPane).AbortCommand,
} }

View file

@ -1,9 +1,12 @@
package action package action
import ( import (
luar "layeh.com/gopher-luar"
"github.com/zyedidia/micro/v2/internal/buffer" "github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display" "github.com/zyedidia/micro/v2/internal/display"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/views" "github.com/zyedidia/micro/v2/internal/views"
"github.com/zyedidia/tcell/v2" "github.com/zyedidia/tcell/v2"
@ -121,6 +124,12 @@ func (t *TabList) HandleEvent(event tcell.Event) {
return return
} }
} }
case tcell.ButtonNone:
if t.List[t.Active()].release {
// Mouse release received, while already released
t.ResetMouse()
return
}
case tcell.WheelUp: case tcell.WheelUp:
if my == t.Y { if my == t.Y {
t.Scroll(4) t.Scroll(4)
@ -144,6 +153,45 @@ func (t *TabList) Display() {
} }
} }
func (t *TabList) SetActive(a int) {
t.TabWindow.SetActive(a)
for i, p := range t.List {
if i == a {
if !p.isActive {
p.isActive = true
err := config.RunPluginFn("onSetActive", luar.New(ulua.L, p.CurPane()))
if err != nil {
screen.TermMessage(err)
}
}
} else {
p.isActive = false
}
}
}
// ResetMouse resets the mouse release state after the screen was stopped
// or the pane changed.
// This prevents situations in which mouse releases are received at the wrong place
// and the mouse state is still pressed.
func (t *TabList) ResetMouse() {
for _, tab := range t.List {
if !tab.release && tab.resizing != nil {
tab.resizing = nil
}
tab.release = true
for _, p := range tab.Panes {
if bp, ok := p.(*BufPane); ok {
bp.resetMouse()
}
}
}
}
// Tabs is the global tab list // Tabs is the global tab list
var Tabs *TabList var Tabs *TabList
@ -161,6 +209,8 @@ func InitTabs(bufs []*buffer.Buffer) {
} }
} }
} }
screen.RestartCallback = Tabs.ResetMouse
} }
func MainTab() *Tab { func MainTab() *Tab {
@ -174,6 +224,9 @@ func MainTab() *Tab {
type Tab struct { type Tab struct {
*views.Node *views.Node
*display.UIWindow *display.UIWindow
isActive bool
Panes []Pane Panes []Pane
active int active int
@ -211,34 +264,40 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
// HandleEvent takes a tcell event and usually dispatches it to the current // HandleEvent takes a tcell event and usually dispatches it to the current
// active pane. However if the event is a resize or a mouse event where the user // active pane. However if the event is a resize or a mouse event where the user
// is interacting with the UI (resizing splits) then the event is consumed here // is interacting with the UI (resizing splits) then the event is consumed here
// If the event is a mouse event in a pane, that pane will become active and get // If the event is a mouse press event in a pane, that pane will become active
// the event // and get the event
func (t *Tab) HandleEvent(event tcell.Event) { func (t *Tab) HandleEvent(event tcell.Event) {
switch e := event.(type) { switch e := event.(type) {
case *tcell.EventMouse: case *tcell.EventMouse:
mx, my := e.Position() mx, my := e.Position()
switch e.Buttons() { btn := e.Buttons()
case tcell.Button1: switch {
case btn & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone:
// button press or drag
wasReleased := t.release wasReleased := t.release
t.release = false t.release = false
if t.resizing != nil {
var size int if btn == tcell.Button1 {
if t.resizing.Kind == views.STVert { if t.resizing != nil {
size = mx - t.resizing.X var size int
} else { if t.resizing.Kind == views.STVert {
size = my - t.resizing.Y + 1 size = mx - t.resizing.X
} else {
size = my - t.resizing.Y + 1
}
t.resizing.ResizeSplit(size)
t.Resize()
return
}
if wasReleased {
t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my})
if t.resizing != nil {
return
}
} }
t.resizing.ResizeSplit(size)
t.Resize()
return
} }
if wasReleased { if wasReleased {
t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my})
if t.resizing != nil {
return
}
for i, p := range t.Panes { for i, p := range t.Panes {
v := p.GetView() v := p.GetView()
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
@ -248,10 +307,15 @@ func (t *Tab) HandleEvent(event tcell.Event) {
} }
} }
} }
case tcell.ButtonNone: case btn == tcell.ButtonNone:
t.resizing = nil // button release
t.release = true t.release = true
if t.resizing != nil {
t.resizing = nil
return
}
default: default:
// wheel move
for _, p := range t.Panes { for _, p := range t.Panes {
v := p.GetView() v := p.GetView()
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height

View file

@ -81,6 +81,10 @@ func (t *TermPane) SetID(i uint64) {
t.id = i t.id = i
} }
func (t *TermPane) Name() string {
return t.Terminal.Name()
}
func (t *TermPane) SetTab(tab *Tab) { func (t *TermPane) SetTab(tab *Tab) {
t.tab = tab t.tab = tab
} }

View file

@ -64,7 +64,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
// GetWord gets the most recent word separated by any separator // GetWord gets the most recent word separated by any separator
// (whitespace, punctuation, any non alphanumeric character) // (whitespace, punctuation, any non alphanumeric character)
func GetWord(b *Buffer) ([]byte, int) { func (b *Buffer) GetWord() ([]byte, int) {
c := b.GetActiveCursor() c := b.GetActiveCursor()
l := b.LineBytes(c.Y) l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X) l = util.SliceStart(l, c.X)
@ -73,17 +73,17 @@ func GetWord(b *Buffer) ([]byte, int) {
return []byte{}, -1 return []byte{}, -1
} }
if util.IsNonAlphaNumeric(b.RuneAt(c.Loc.Move(-1, b))) { if util.IsNonWordChar(b.RuneAt(c.Loc.Move(-1, b))) {
return []byte{}, c.X return []byte{}, c.X
} }
args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric) args := bytes.FieldsFunc(l, util.IsNonWordChar)
input := args[len(args)-1] input := args[len(args)-1]
return input, c.X - util.CharacterCount(input) return input, c.X - util.CharacterCount(input)
} }
// GetArg gets the most recent word (separated by ' ' only) // GetArg gets the most recent word (separated by ' ' only)
func GetArg(b *Buffer) (string, int) { func (b *Buffer) GetArg() (string, int) {
c := b.GetActiveCursor() c := b.GetActiveCursor()
l := b.LineBytes(c.Y) l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X) l = util.SliceStart(l, c.X)
@ -104,7 +104,7 @@ func GetArg(b *Buffer) (string, int) {
// FileComplete autocompletes filenames // FileComplete autocompletes filenames
func FileComplete(b *Buffer) ([]string, []string) { func FileComplete(b *Buffer) ([]string, []string) {
c := b.GetActiveCursor() c := b.GetActiveCursor()
input, argstart := GetArg(b) input, argstart := b.GetArg()
sep := string(os.PathSeparator) sep := string(os.PathSeparator)
dirs := strings.Split(input, sep) dirs := strings.Split(input, sep)
@ -153,7 +153,7 @@ func FileComplete(b *Buffer) ([]string, []string) {
// BufferComplete autocompletes based on previous words in the buffer // BufferComplete autocompletes based on previous words in the buffer
func BufferComplete(b *Buffer) ([]string, []string) { func BufferComplete(b *Buffer) ([]string, []string) {
c := b.GetActiveCursor() c := b.GetActiveCursor()
input, argstart := GetWord(b) input, argstart := b.GetWord()
if argstart == -1 { if argstart == -1 {
return []string{}, []string{} return []string{}, []string{}
@ -166,7 +166,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
var suggestions []string var suggestions []string
for i := c.Y; i >= 0; i-- { for i := c.Y; i >= 0; i-- {
l := b.LineBytes(i) l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric) words := bytes.FieldsFunc(l, util.IsNonWordChar)
for _, w := range words { for _, w := range words {
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen { if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
strw := string(w) strw := string(w)
@ -179,7 +179,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
} }
for i := c.Y + 1; i < b.LinesNum(); i++ { for i := c.Y + 1; i < b.LinesNum(); i++ {
l := b.LineBytes(i) l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric) words := bytes.FieldsFunc(l, util.IsNonWordChar)
for _, w := range words { for _, w := range words {
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen { if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
strw := string(w) strw := string(w)

View file

@ -57,7 +57,7 @@ var (
BTLog = BufType{2, true, true, false} BTLog = BufType{2, true, true, false}
// BTScratch is a buffer that cannot be saved (for scratch work) // BTScratch is a buffer that cannot be saved (for scratch work)
BTScratch = BufType{3, false, true, false} BTScratch = BufType{3, false, true, false}
// BTRaw is is a buffer that shows raw terminal events // BTRaw is a buffer that shows raw terminal events
BTRaw = BufType{4, false, true, false} BTRaw = BufType{4, false, true, false}
// BTInfo is a buffer for inputting information // BTInfo is a buffer for inputting information
BTInfo = BufType{5, false, true, false} BTInfo = BufType{5, false, true, false}
@ -430,6 +430,15 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
return b return b
} }
// CloseOpenBuffers removes all open buffers
func CloseOpenBuffers() {
for i, buf := range OpenBuffers {
buf.Fini()
OpenBuffers[i] = nil
}
OpenBuffers = OpenBuffers[:0]
}
// Close removes this buffer from the list of open buffers // Close removes this buffer from the list of open buffers
func (b *Buffer) Close() { func (b *Buffer) Close() {
for i, buf := range OpenBuffers { for i, buf := range OpenBuffers {
@ -474,7 +483,7 @@ func (b *Buffer) GetName() string {
return name return name
} }
//SetName changes the name for this buffer // SetName changes the name for this buffer
func (b *Buffer) SetName(s string) { func (b *Buffer) SetName(s string) {
b.name = s b.name = s
} }
@ -559,6 +568,13 @@ func (b *Buffer) RelocateCursors() {
} }
} }
// DeselectCursors removes selection from all cursors
func (b *Buffer) DeselectCursors() {
for _, c := range b.cursors {
c.Deselect(true)
}
}
// RuneAt returns the rune at a given location in the buffer // RuneAt returns the rune at a given location in the buffer
func (b *Buffer) RuneAt(loc Loc) rune { func (b *Buffer) RuneAt(loc Loc) rune {
line := b.LineBytes(loc.Y) line := b.LineBytes(loc.Y)
@ -666,6 +682,103 @@ func calcHash(b *Buffer, out *[md5.Size]byte) error {
return nil return nil
} }
func parseDefFromFile(f config.RuntimeFile, header *highlight.Header) *highlight.Def {
data, err := f.Data()
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
return nil
}
if header == nil {
header, err = highlight.MakeHeaderYaml(data)
if err != nil {
screen.TermMessage("Error parsing header for syntax file " + f.Name() + ": " + err.Error())
return nil
}
}
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
return nil
}
syndef, err := highlight.ParseDef(file, header)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
return nil
}
return syndef
}
// findRealRuntimeSyntaxDef finds a specific syntax definition
// in the user's custom syntax files
func findRealRuntimeSyntaxDef(name string, header *highlight.Header) *highlight.Def {
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
if f.Name() == name {
syndef := parseDefFromFile(f, header)
if syndef != nil {
return syndef
}
}
}
return nil
}
// findRuntimeSyntaxDef finds a specific syntax definition
// in the built-in syntax files
func findRuntimeSyntaxDef(name string, header *highlight.Header) *highlight.Def {
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
if f.Name() == name {
syndef := parseDefFromFile(f, header)
if syndef != nil {
return syndef
}
}
}
return nil
}
func resolveIncludes(syndef *highlight.Def) {
includes := highlight.GetIncludes(syndef)
if len(includes) == 0 {
return
}
var files []*highlight.File
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
data, err := f.Data()
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
header, err := highlight.MakeHeaderYaml(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
for _, i := range includes {
if header.FileType == i {
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
files = append(files, file)
break
}
}
if len(files) >= len(includes) {
break
}
}
highlight.ResolveIncludes(syndef, files)
}
// UpdateRules updates the syntax rules and filetype for this buffer // UpdateRules updates the syntax rules and filetype for this buffer
// This is called when the colorscheme changes // This is called when the colorscheme changes
func (b *Buffer) UpdateRules() { func (b *Buffer) UpdateRules() {
@ -674,13 +787,32 @@ func (b *Buffer) UpdateRules() {
} }
ft := b.Settings["filetype"].(string) ft := b.Settings["filetype"].(string)
if ft == "off" { if ft == "off" {
b.ClearMatches()
b.SyntaxDef = nil
return return
} }
b.SyntaxDef = nil
// syntaxFileInfo is an internal helper structure
// to store properties of one single syntax file
type syntaxFileInfo struct {
header *highlight.Header
fileName string
syntaxDef *highlight.Def
}
fnameMatches := []syntaxFileInfo{}
headerMatches := []syntaxFileInfo{}
syntaxFile := "" syntaxFile := ""
foundDef := false foundDef := false
var header *highlight.Header var header *highlight.Header
// search for the syntax file in the user's custom syntax files // search for the syntax file in the user's custom syntax files
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) { for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
if f.Name() == "default" {
continue
}
data, err := f.Data() data, err := f.Data()
if err != nil { if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error()) screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
@ -690,119 +822,147 @@ func (b *Buffer) UpdateRules() {
header, err = highlight.MakeHeaderYaml(data) header, err = highlight.MakeHeaderYaml(data)
if err != nil { if err != nil {
screen.TermMessage("Error parsing header for syntax file " + f.Name() + ": " + err.Error()) screen.TermMessage("Error parsing header for syntax file " + f.Name() + ": " + err.Error())
}
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue continue
} }
if ((ft == "unknown" || ft == "") && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft { matchedFileType := false
matchedFileName := false
matchedFileHeader := false
if ft == "unknown" || ft == "" {
if header.MatchFileName(b.Path) {
matchedFileName = true
}
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {
matchedFileHeader = true
}
} else if header.FileType == ft {
matchedFileType = true
}
if matchedFileType || matchedFileName || matchedFileHeader {
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
syndef, err := highlight.ParseDef(file, header) syndef, err := highlight.ParseDef(file, header)
if err != nil { if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error()) screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue continue
} }
b.SyntaxDef = syndef
syntaxFile = f.Name() if matchedFileType {
foundDef = true b.SyntaxDef = syndef
break syntaxFile = f.Name()
foundDef = true
break
}
if matchedFileName {
fnameMatches = append(fnameMatches, syntaxFileInfo{header, f.Name(), syndef})
} else if matchedFileHeader {
headerMatches = append(headerMatches, syntaxFileInfo{header, f.Name(), syndef})
}
} }
} }
// search in the default syntax files if !foundDef {
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) { // search for the syntax file in the built-in syntax files
data, err := f.Data() for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
if err != nil { data, err := f.Data()
screen.TermMessage("Error loading syntax header file " + f.Name() + ": " + err.Error()) if err != nil {
continue screen.TermMessage("Error loading syntax header file " + f.Name() + ": " + err.Error())
} continue
}
header, err = highlight.MakeHeader(data) header, err = highlight.MakeHeader(data)
if err != nil { if err != nil {
screen.TermMessage("Error reading syntax header file", f.Name(), err) screen.TermMessage("Error reading syntax header file", f.Name(), err)
continue continue
} }
if ft == "unknown" || ft == "" { if ft == "unknown" || ft == "" {
if highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data) { if header.MatchFileName(b.Path) {
fnameMatches = append(fnameMatches, syntaxFileInfo{header, f.Name(), nil})
}
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {
headerMatches = append(headerMatches, syntaxFileInfo{header, f.Name(), nil})
}
} else if header.FileType == ft {
syntaxFile = f.Name() syntaxFile = f.Name()
break break
} }
} else if header.FileType == ft { }
syntaxFile = f.Name() }
break
if syntaxFile == "" {
matches := fnameMatches
if len(matches) == 0 {
matches = headerMatches
}
length := len(matches)
if length > 0 {
signatureMatch := false
if length > 1 {
// multiple matching syntax files found, try to resolve the ambiguity
// using signatures
detectlimit := util.IntOpt(b.Settings["detectlimit"])
lineCount := len(b.lines)
limit := lineCount
if detectlimit > 0 && lineCount > detectlimit {
limit = detectlimit
}
matchLoop:
for _, m := range matches {
if m.header.HasFileSignature() {
for i := 0; i < limit; i++ {
if m.header.MatchFileSignature(b.lines[i].data) {
syntaxFile = m.fileName
if m.syntaxDef != nil {
b.SyntaxDef = m.syntaxDef
foundDef = true
}
header = m.header
signatureMatch = true
break matchLoop
}
}
}
}
}
if length == 1 || !signatureMatch {
syntaxFile = matches[0].fileName
if matches[0].syntaxDef != nil {
b.SyntaxDef = matches[0].syntaxDef
foundDef = true
}
header = matches[0].header
}
} }
} }
if syntaxFile != "" && !foundDef { if syntaxFile != "" && !foundDef {
// we found a syntax file using a syntax header file // we found a syntax file using a syntax header file
for _, f := range config.ListRuntimeFiles(config.RTSyntax) { b.SyntaxDef = findRuntimeSyntaxDef(syntaxFile, header)
if f.Name() == syntaxFile {
data, err := f.Data()
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
syndef, err := highlight.ParseDef(file, header)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
b.SyntaxDef = syndef
break
}
}
} }
if b.SyntaxDef != nil && highlight.HasIncludes(b.SyntaxDef) { if b.SyntaxDef != nil {
includes := highlight.GetIncludes(b.SyntaxDef) b.Settings["filetype"] = b.SyntaxDef.FileType
var files []*highlight.File
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
data, err := f.Data()
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
header, err := highlight.MakeHeaderYaml(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
for _, i := range includes {
if header.FileType == i {
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
files = append(files, file)
break
}
}
if len(files) >= len(includes) {
break
}
}
highlight.ResolveIncludes(b.SyntaxDef, files)
}
if b.Highlighter == nil || syntaxFile != "" {
if b.SyntaxDef != nil {
b.Settings["filetype"] = b.SyntaxDef.FileType
}
} else { } else {
b.SyntaxDef = &highlight.EmptyDef // search for the default file in the user's custom syntax files
b.SyntaxDef = findRealRuntimeSyntaxDef("default", nil)
if b.SyntaxDef == nil {
// search for the default file in the built-in syntax files
b.SyntaxDef = findRuntimeSyntaxDef("default", nil)
}
}
if b.SyntaxDef != nil {
resolveIncludes(b.SyntaxDef)
} }
if b.SyntaxDef != nil { if b.SyntaxDef != nil {
@ -904,7 +1064,7 @@ func (b *Buffer) MergeCursors() {
b.EventHandler.active = b.curCursor b.EventHandler.active = b.curCursor
} }
// UpdateCursors updates all the cursors indicies // UpdateCursors updates all the cursors indices
func (b *Buffer) UpdateCursors() { func (b *Buffer) UpdateCursors() {
b.EventHandler.cursors = b.cursors b.EventHandler.cursors = b.cursors
b.EventHandler.active = b.curCursor b.EventHandler.active = b.curCursor
@ -980,34 +1140,14 @@ var BracePairs = [][2]rune{
{'[', ']'}, {'[', ']'},
} }
// FindMatchingBrace returns the location in the buffer of the matching bracket func (b *Buffer) findMatchingBrace(braceType [2]rune, start Loc, char rune) (Loc, bool) {
// It is given a brace type containing the open and closing character, (for example
// '{' and '}') as well as the location to match from
// TODO: maybe can be more efficient with utf8 package
// returns the location of the matching brace
// if the boolean returned is true then the original matching brace is one character left
// of the starting location
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, bool) {
curLine := []rune(string(b.LineBytes(start.Y)))
startChar := ' '
if start.X >= 0 && start.X < len(curLine) {
startChar = curLine[start.X]
}
leftChar := ' '
if start.X-1 >= 0 && start.X-1 < len(curLine) {
leftChar = curLine[start.X-1]
}
var i int var i int
if startChar == braceType[0] || leftChar == braceType[0] { if char == braceType[0] {
for y := start.Y; y < b.LinesNum(); y++ { for y := start.Y; y < b.LinesNum(); y++ {
l := []rune(string(b.LineBytes(y))) l := []rune(string(b.LineBytes(y)))
xInit := 0 xInit := 0
if y == start.Y { if y == start.Y {
if startChar == braceType[0] { xInit = start.X
xInit = start.X
} else {
xInit = start.X - 1
}
} }
for x := xInit; x < len(l); x++ { for x := xInit; x < len(l); x++ {
r := l[x] r := l[x]
@ -1016,42 +1156,74 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, boo
} else if r == braceType[1] { } else if r == braceType[1] {
i-- i--
if i == 0 { if i == 0 {
if startChar == braceType[0] { return Loc{x, y}, true
return Loc{x, y}, false, true
}
return Loc{x, y}, true, true
} }
} }
} }
} }
} else if startChar == braceType[1] || leftChar == braceType[1] { } else if char == braceType[1] {
for y := start.Y; y >= 0; y-- { for y := start.Y; y >= 0; y-- {
l := []rune(string(b.lines[y].data)) l := []rune(string(b.lines[y].data))
xInit := len(l) - 1 xInit := len(l) - 1
if y == start.Y { if y == start.Y {
if leftChar == braceType[1] { xInit = start.X
xInit = start.X - 1
} else {
xInit = start.X
}
} }
for x := xInit; x >= 0; x-- { for x := xInit; x >= 0; x-- {
r := l[x] r := l[x]
if r == braceType[0] { if r == braceType[1] {
i++
} else if r == braceType[0] {
i-- i--
if i == 0 { if i == 0 {
if leftChar == braceType[1] { return Loc{x, y}, true
return Loc{x, y}, true, true
}
return Loc{x, y}, false, true
} }
} else if r == braceType[1] {
i++
} }
} }
} }
} }
return start, true, false return start, false
}
// If there is a brace character (for example '{' or ']') at the given start location,
// FindMatchingBrace returns the location of the matching brace for it (for example '}'
// or '['). The second returned value is true if there was no matching brace found
// for given starting location but it was found for the location one character left
// of it. The third returned value is true if the matching brace was found at all.
func (b *Buffer) FindMatchingBrace(start Loc) (Loc, bool, bool) {
// TODO: maybe can be more efficient with utf8 package
curLine := []rune(string(b.LineBytes(start.Y)))
// first try to find matching brace for the given location (it has higher priority)
if start.X >= 0 && start.X < len(curLine) {
startChar := curLine[start.X]
for _, bp := range BracePairs {
if startChar == bp[0] || startChar == bp[1] {
mb, found := b.findMatchingBrace(bp, start, startChar)
if found {
return mb, false, true
}
}
}
}
// failed to find matching brace for the given location, so try to find matching
// brace for the location one character left of it
if start.X-1 >= 0 && start.X-1 < len(curLine) {
leftChar := curLine[start.X-1]
left := Loc{start.X - 1, start.Y}
for _, bp := range BracePairs {
if leftChar == bp[0] || leftChar == bp[1] {
mb, found := b.findMatchingBrace(bp, left, leftChar)
if found {
return mb, true, true
}
}
}
}
return start, false, false
} }
// Retab changes all tabs to spaces or vice versa // Retab changes all tabs to spaces or vice versa
@ -1073,7 +1245,11 @@ func (b *Buffer) Retab() {
} }
l = bytes.TrimLeft(l, " \t") l = bytes.TrimLeft(l, " \t")
b.Lock()
b.lines[i].data = append(ws, l...) b.lines[i].data = append(ws, l...)
b.Unlock()
b.MarkModified(i, i) b.MarkModified(i, i)
dirty = true dirty = true
} }
@ -1116,7 +1292,7 @@ func (b *Buffer) Write(bytes []byte) (n int, err error) {
return len(bytes), nil return len(bytes), nil
} }
func (b *Buffer) updateDiffSync() { func (b *Buffer) updateDiff(synchronous bool) {
b.diffLock.Lock() b.diffLock.Lock()
defer b.diffLock.Unlock() defer b.diffLock.Unlock()
@ -1127,7 +1303,16 @@ func (b *Buffer) updateDiffSync() {
} }
differ := dmp.New() differ := dmp.New()
baseRunes, bufferRunes, _ := differ.DiffLinesToRunes(string(b.diffBase), string(b.Bytes()))
if !synchronous {
b.Lock()
}
bytes := b.Bytes()
if !synchronous {
b.Unlock()
}
baseRunes, bufferRunes, _ := differ.DiffLinesToRunes(string(b.diffBase), string(bytes))
diffs := differ.DiffMainRunes(baseRunes, bufferRunes, false) diffs := differ.DiffMainRunes(baseRunes, bufferRunes, false)
lineN := 0 lineN := 0
@ -1156,13 +1341,9 @@ func (b *Buffer) updateDiffSync() {
// UpdateDiff computes the diff between the diff base and the buffer content. // UpdateDiff computes the diff between the diff base and the buffer content.
// The update may be performed synchronously or asynchronously. // The update may be performed synchronously or asynchronously.
// UpdateDiff calls the supplied callback when the update is complete.
// The argument passed to the callback is set to true if and only if
// the update was performed synchronously.
// If an asynchronous update is already pending when UpdateDiff is called, // If an asynchronous update is already pending when UpdateDiff is called,
// UpdateDiff does not schedule another update, in which case the callback // UpdateDiff does not schedule another update.
// is not called. func (b *Buffer) UpdateDiff() {
func (b *Buffer) UpdateDiff(callback func(bool)) {
if b.updateDiffTimer != nil { if b.updateDiffTimer != nil {
return return
} }
@ -1173,20 +1354,18 @@ func (b *Buffer) UpdateDiff(callback func(bool)) {
} }
if lineCount < 1000 { if lineCount < 1000 {
b.updateDiffSync() b.updateDiff(true)
callback(true)
} else if lineCount < 30000 { } else if lineCount < 30000 {
b.updateDiffTimer = time.AfterFunc(500*time.Millisecond, func() { b.updateDiffTimer = time.AfterFunc(500*time.Millisecond, func() {
b.updateDiffTimer = nil b.updateDiffTimer = nil
b.updateDiffSync() b.updateDiff(false)
callback(false) screen.Redraw()
}) })
} else { } else {
// Don't compute diffs for very large files // Don't compute diffs for very large files
b.diffLock.Lock() b.diffLock.Lock()
b.diff = make(map[int]DiffStatus) b.diff = make(map[int]DiffStatus)
b.diffLock.Unlock() b.diffLock.Unlock()
callback(true)
} }
} }
@ -1198,9 +1377,7 @@ func (b *Buffer) SetDiffBase(diffBase []byte) {
} else { } else {
b.diffBaseLineCount = strings.Count(string(diffBase), "\n") b.diffBaseLineCount = strings.Count(string(diffBase), "\n")
} }
b.UpdateDiff(func(synchronous bool) { b.UpdateDiff()
screen.Redraw()
})
} }
// DiffStatus returns the diff status for a line in the buffer // DiffStatus returns the diff status for a line in the buffer
@ -1211,6 +1388,41 @@ func (b *Buffer) DiffStatus(lineN int) DiffStatus {
return b.diff[lineN] return b.diff[lineN]
} }
// FindNextDiffLine returns the line number of the next block of diffs.
// If `startLine` is already in a block of diffs, lines in that block are skipped.
func (b *Buffer) FindNextDiffLine(startLine int, forward bool) (int, error) {
if b.diff == nil {
return 0, errors.New("no diff data")
}
startStatus, ok := b.diff[startLine]
if !ok {
startStatus = DSUnchanged
}
curLine := startLine
for {
curStatus, ok := b.diff[curLine]
if !ok {
curStatus = DSUnchanged
}
if curLine < 0 || curLine > b.LinesNum() {
return 0, errors.New("no next diff hunk")
}
if curStatus != startStatus {
if startStatus != DSUnchanged && curStatus == DSUnchanged {
// Skip over the block of unchanged text
startStatus = DSUnchanged
} else {
return curLine, nil
}
}
if forward {
curLine++
} else {
curLine--
}
}
}
// SearchMatch returns true if the given location is within a match of the last search. // SearchMatch returns true if the given location is within a match of the last search.
// It is used for search highlighting // It is used for search highlighting
func (b *Buffer) SearchMatch(pos Loc) bool { func (b *Buffer) SearchMatch(pos Loc) bool {

View file

@ -20,6 +20,7 @@ type operation struct {
func init() { func init() {
ulua.L = lua.NewState() ulua.L = lua.NewState()
config.InitRuntimeFiles(false)
config.InitGlobalSettings() config.InitGlobalSettings()
config.GlobalSettings["backup"] = false config.GlobalSettings["backup"] = false
config.GlobalSettings["fastdirty"] = true config.GlobalSettings["fastdirty"] = true

View file

@ -30,6 +30,11 @@ type Cursor struct {
// to know what the original selection was // to know what the original selection was
OrigSelection [2]Loc OrigSelection [2]Loc
// The line number where a new trailing whitespace has been added
// or -1 if there is no new trailing whitespace at this cursor.
// This is used for checking if a trailing whitespace should be highlighted
NewTrailingWsY int
// Which cursor index is this (for multiple cursors) // Which cursor index is this (for multiple cursors)
Num int Num int
} }
@ -38,6 +43,8 @@ func NewCursor(b *Buffer, l Loc) *Cursor {
c := &Cursor{ c := &Cursor{
buf: b, buf: b,
Loc: l, Loc: l,
NewTrailingWsY: -1,
} }
c.StoreVisualX() c.StoreVisualX()
return c return c
@ -396,13 +403,26 @@ func (c *Cursor) SelectTo(loc Loc) {
// WordRight moves the cursor one word to the right // WordRight moves the cursor one word to the right
func (c *Cursor) WordRight() { func (c *Cursor) WordRight() {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.Right()
return
}
for util.IsWhitespace(c.RuneUnder(c.X)) { for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.Right()
return return
} }
c.Right() c.Right()
} }
if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) &&
util.IsNonWordChar(c.RuneUnder(c.X+1)) {
for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
return
}
c.Right()
}
return
}
c.Right() c.Right()
for util.IsWordChar(c.RuneUnder(c.X)) { for util.IsWordChar(c.RuneUnder(c.X)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
@ -414,6 +434,10 @@ func (c *Cursor) WordRight() {
// WordLeft moves the cursor one word to the left // WordLeft moves the cursor one word to the left
func (c *Cursor) WordLeft() { func (c *Cursor) WordLeft() {
if c.X == 0 {
c.Left()
return
}
c.Left() c.Left()
for util.IsWhitespace(c.RuneUnder(c.X)) { for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == 0 { if c.X == 0 {
@ -421,6 +445,17 @@ func (c *Cursor) WordLeft() {
} }
c.Left() c.Left()
} }
if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) &&
util.IsNonWordChar(c.RuneUnder(c.X-1)) {
for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == 0 {
return
}
c.Left()
}
c.Right()
return
}
c.Left() c.Left()
for util.IsWordChar(c.RuneUnder(c.X)) { for util.IsWordChar(c.RuneUnder(c.X)) {
if c.X == 0 { if c.X == 0 {
@ -431,6 +466,132 @@ func (c *Cursor) WordLeft() {
c.Right() c.Right()
} }
// SubWordRight moves the cursor one sub-word to the right
func (c *Cursor) SubWordRight() {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.Right()
return
}
if util.IsWhitespace(c.RuneUnder(c.X)) {
for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
return
}
c.Right()
}
return
}
if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
return
}
c.Right()
}
return
}
if util.IsSubwordDelimiter(c.RuneUnder(c.X)) {
for util.IsSubwordDelimiter(c.RuneUnder(c.X)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
return
}
c.Right()
}
if util.IsWhitespace(c.RuneUnder(c.X)) {
return
}
}
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
return
}
if util.IsUpperLetter(c.RuneUnder(c.X)) &&
util.IsUpperLetter(c.RuneUnder(c.X+1)) {
for util.IsUpperAlphanumeric(c.RuneUnder(c.X)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
return
}
c.Right()
}
if util.IsLowerAlphanumeric(c.RuneUnder(c.X)) {
c.Left()
}
} else {
c.Right()
for util.IsLowerAlphanumeric(c.RuneUnder(c.X)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
return
}
c.Right()
}
}
}
// SubWordLeft moves the cursor one sub-word to the left
func (c *Cursor) SubWordLeft() {
if c.X == 0 {
c.Left()
return
}
c.Left()
if util.IsWhitespace(c.RuneUnder(c.X)) {
for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == 0 {
return
}
c.Left()
}
c.Right()
return
}
if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == 0 {
return
}
c.Left()
}
c.Right()
return
}
if util.IsSubwordDelimiter(c.RuneUnder(c.X)) {
for util.IsSubwordDelimiter(c.RuneUnder(c.X)) {
if c.X == 0 {
return
}
c.Left()
}
if util.IsWhitespace(c.RuneUnder(c.X)) {
c.Right()
return
}
}
if c.X == 0 {
return
}
if util.IsUpperLetter(c.RuneUnder(c.X)) &&
util.IsUpperLetter(c.RuneUnder(c.X-1)) {
for util.IsUpperAlphanumeric(c.RuneUnder(c.X)) {
if c.X == 0 {
return
}
c.Left()
}
if !util.IsUpperAlphanumeric(c.RuneUnder(c.X)) {
c.Right()
}
} else {
for util.IsLowerAlphanumeric(c.RuneUnder(c.X)) {
if c.X == 0 {
return
}
c.Left()
}
if !util.IsAlphanumeric(c.RuneUnder(c.X)) {
c.Right()
}
}
}
// RuneUnder returns the rune under the given x position // RuneUnder returns the rune under the given x position
func (c *Cursor) RuneUnder(x int) rune { func (c *Cursor) RuneUnder(x int) rune {
line := c.buf.LineBytes(c.Y) line := c.buf.LineBytes(c.Y)

View file

@ -106,6 +106,10 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
c.Relocate() c.Relocate()
c.LastVisualX = c.GetVisualX() c.LastVisualX = c.GetVisualX()
} }
if useUndo {
eh.updateTrailingWs(t)
}
} }
// ExecuteTextEvent runs a text event // ExecuteTextEvent runs a text event
@ -237,7 +241,7 @@ func (eh *EventHandler) Execute(t *TextEvent) {
} }
eh.UndoStack.Push(t) eh.UndoStack.Push(t)
b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t)) b, err := config.RunPluginFnBool(nil, "onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
if err != nil { if err != nil {
screen.TermMessage(err) screen.TermMessage(err)
} }
@ -290,6 +294,7 @@ func (eh *EventHandler) UndoOneEvent() {
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) { if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
t.C = *eh.cursors[teCursor.Num] t.C = *eh.cursors[teCursor.Num]
eh.cursors[teCursor.Num].Goto(teCursor) eh.cursors[teCursor.Num].Goto(teCursor)
eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY
} else { } else {
teCursor.Num = -1 teCursor.Num = -1
} }
@ -333,6 +338,7 @@ func (eh *EventHandler) RedoOneEvent() {
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) { if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
t.C = *eh.cursors[teCursor.Num] t.C = *eh.cursors[teCursor.Num]
eh.cursors[teCursor.Num].Goto(teCursor) eh.cursors[teCursor.Num].Goto(teCursor)
eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY
} else { } else {
teCursor.Num = -1 teCursor.Num = -1
} }
@ -342,3 +348,58 @@ func (eh *EventHandler) RedoOneEvent() {
eh.UndoStack.Push(t) eh.UndoStack.Push(t)
} }
// updateTrailingWs updates the cursor's trailing whitespace status after a text event
func (eh *EventHandler) updateTrailingWs(t *TextEvent) {
if len(t.Deltas) != 1 {
return
}
text := t.Deltas[0].Text
start := t.Deltas[0].Start
end := t.Deltas[0].End
c := eh.cursors[eh.active]
isEol := func(loc Loc) bool {
return loc.X == util.CharacterCount(eh.buf.LineBytes(loc.Y))
}
if t.EventType == TextEventInsert && c.Loc == end && isEol(end) {
var addedTrailingWs bool
addedAfterWs := false
addedWsOnly := false
if start.Y == end.Y {
addedTrailingWs = util.HasTrailingWhitespace(text)
addedWsOnly = util.IsBytesWhitespace(text)
addedAfterWs = start.X > 0 && util.IsWhitespace(c.buf.RuneAt(Loc{start.X - 1, start.Y}))
} else {
lastnl := bytes.LastIndex(text, []byte{'\n'})
addedTrailingWs = util.HasTrailingWhitespace(text[lastnl+1:])
}
if addedTrailingWs && !(addedAfterWs && addedWsOnly) {
c.NewTrailingWsY = c.Y
} else if !addedTrailingWs {
c.NewTrailingWsY = -1
}
} else if t.EventType == TextEventRemove && c.Loc == start && isEol(start) {
removedAfterWs := util.HasTrailingWhitespace(eh.buf.LineBytes(start.Y))
var removedWsOnly bool
if start.Y == end.Y {
removedWsOnly = util.IsBytesWhitespace(text)
} else {
firstnl := bytes.Index(text, []byte{'\n'})
removedWsOnly = util.IsBytesWhitespace(text[:firstnl])
}
if removedAfterWs && !removedWsOnly {
c.NewTrailingWsY = c.Y
} else if !removedAfterWs {
c.NewTrailingWsY = -1
}
} else if c.NewTrailingWsY != -1 && start.Y != end.Y && c.Loc.GreaterThan(start) &&
((t.EventType == TextEventInsert && c.Y == c.NewTrailingWsY+(end.Y-start.Y)) ||
(t.EventType == TextEventRemove && c.Y == c.NewTrailingWsY-(end.Y-start.Y))) {
// The cursor still has its new trailingws
// but its line number was shifted by insert or remove of lines above
c.NewTrailingWsY = c.Y
}
}

View file

@ -46,10 +46,9 @@ type searchState struct {
type Line struct { type Line struct {
data []byte data []byte
state highlight.State state highlight.State
match highlight.LineMatch match highlight.LineMatch
rehighlight bool lock sync.Mutex
lock sync.Mutex
// The search states for the line, used for highlighting of search matches, // The search states for the line, used for highlighting of search matches,
// separately from the syntax highlighting. // separately from the syntax highlighting.
@ -75,6 +74,7 @@ type LineArray struct {
lines []Line lines []Line
Endings FileFormat Endings FileFormat
initsize uint64 initsize uint64
lock sync.Mutex
} }
// Append efficiently appends lines together // Append efficiently appends lines together
@ -116,12 +116,12 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
dlen := len(data) dlen := len(data)
if dlen > 1 && data[dlen-2] == '\r' { if dlen > 1 && data[dlen-2] == '\r' {
data = append(data[:dlen-2], '\n') data = append(data[:dlen-2], '\n')
if endings == FFAuto { if la.Endings == FFAuto {
la.Endings = FFDos la.Endings = FFDos
} }
dlen = len(data) dlen = len(data)
} else if dlen > 0 { } else if dlen > 0 {
if endings == FFAuto { if la.Endings == FFAuto {
la.Endings = FFUnix la.Endings = FFUnix
} }
} }
@ -147,20 +147,18 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
la.lines = Append(la.lines, Line{ la.lines = Append(la.lines, Line{
data: data, data: data,
state: nil, state: nil,
match: nil, match: nil,
rehighlight: false,
}) })
} }
// Last line was read // Last line was read
break break
} else { } else {
la.lines = Append(la.lines, Line{ la.lines = Append(la.lines, Line{
data: data[:dlen-1], data: data[:dlen-1],
state: nil, state: nil,
match: nil, match: nil,
rehighlight: false,
}) })
} }
n++ n++
@ -190,22 +188,23 @@ func (la *LineArray) Bytes() []byte {
// newlineBelow adds a newline below the given line number // newlineBelow adds a newline below the given line number
func (la *LineArray) newlineBelow(y int) { func (la *LineArray) newlineBelow(y int) {
la.lines = append(la.lines, Line{ la.lines = append(la.lines, Line{
data: []byte{' '}, data: []byte{' '},
state: nil, state: nil,
match: nil, match: nil,
rehighlight: false,
}) })
copy(la.lines[y+2:], la.lines[y+1:]) copy(la.lines[y+2:], la.lines[y+1:])
la.lines[y+1] = Line{ la.lines[y+1] = Line{
data: []byte{}, data: []byte{},
state: la.lines[y].state, state: la.lines[y].state,
match: nil, match: nil,
rehighlight: false,
} }
} }
// Inserts a byte array at a given location // Inserts a byte array at a given location
func (la *LineArray) insert(pos Loc, value []byte) { func (la *LineArray) insert(pos Loc, value []byte) {
la.lock.Lock()
defer la.lock.Unlock()
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
for i := 0; i < len(value); i++ { for i := 0; i < len(value); i++ {
if value[i] == '\n' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') { if value[i] == '\n' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') {
@ -233,24 +232,26 @@ func (la *LineArray) insertByte(pos Loc, value byte) {
// joinLines joins the two lines a and b // joinLines joins the two lines a and b
func (la *LineArray) joinLines(a, b int) { func (la *LineArray) joinLines(a, b int) {
la.insert(Loc{len(la.lines[a].data), a}, la.lines[b].data) la.lines[a].data = append(la.lines[a].data, la.lines[b].data...)
la.deleteLine(b) la.deleteLine(b)
} }
// split splits a line at a given position // split splits a line at a given position
func (la *LineArray) split(pos Loc) { func (la *LineArray) split(pos Loc) {
la.newlineBelow(pos.Y) la.newlineBelow(pos.Y)
la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y].data[pos.X:]) la.lines[pos.Y+1].data = append(la.lines[pos.Y+1].data, la.lines[pos.Y].data[pos.X:]...)
la.lines[pos.Y+1].state = la.lines[pos.Y].state la.lines[pos.Y+1].state = la.lines[pos.Y].state
la.lines[pos.Y].state = nil la.lines[pos.Y].state = nil
la.lines[pos.Y].match = nil la.lines[pos.Y].match = nil
la.lines[pos.Y+1].match = nil la.lines[pos.Y+1].match = nil
la.lines[pos.Y].rehighlight = true
la.deleteToEnd(Loc{pos.X, pos.Y}) la.deleteToEnd(Loc{pos.X, pos.Y})
} }
// removes from start to end // removes from start to end
func (la *LineArray) remove(start, end Loc) []byte { func (la *LineArray) remove(start, end Loc) []byte {
la.lock.Lock()
defer la.lock.Unlock()
sub := la.Substr(start, end) sub := la.Substr(start, end)
startX := runeToByteIndex(start.X, la.lines[start.Y].data) startX := runeToByteIndex(start.X, la.lines[start.Y].data)
endX := runeToByteIndex(end.X, la.lines[end.Y].data) endX := runeToByteIndex(end.X, la.lines[end.Y].data)
@ -327,11 +328,11 @@ func (la *LineArray) End() Loc {
} }
// LineBytes returns line n as an array of bytes // LineBytes returns line n as an array of bytes
func (la *LineArray) LineBytes(n int) []byte { func (la *LineArray) LineBytes(lineN int) []byte {
if n >= len(la.lines) || n < 0 { if lineN >= len(la.lines) || lineN < 0 {
return []byte{} return []byte{}
} }
return la.lines[n].data return la.lines[lineN].data
} }
// State gets the highlight state for the given line number // State gets the highlight state for the given line number
@ -362,16 +363,14 @@ func (la *LineArray) Match(lineN int) highlight.LineMatch {
return la.lines[lineN].match return la.lines[lineN].match
} }
func (la *LineArray) Rehighlight(lineN int) bool { // Locks the whole LineArray
la.lines[lineN].lock.Lock() func (la *LineArray) Lock() {
defer la.lines[lineN].lock.Unlock() la.lock.Lock()
return la.lines[lineN].rehighlight
} }
func (la *LineArray) SetRehighlight(lineN int, on bool) { // Unlocks the whole LineArray
la.lines[lineN].lock.Lock() func (la *LineArray) Unlock() {
defer la.lines[lineN].lock.Unlock() la.lock.Unlock()
la.lines[lineN].rehighlight = on
} }
// SearchMatch returns true if the location `pos` is within a match // SearchMatch returns true if the location `pos` is within a match

View file

@ -51,27 +51,38 @@ func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error,
// contents to its stdin it might hang because the kernel's pipe size // contents to its stdin it might hang because the kernel's pipe size
// is too small to handle the full file contents all at once // is too small to handle the full file contents all at once
if e := cmd.Start(); e != nil && err == nil { if e := cmd.Start(); e != nil && err == nil {
screen.TempStart(screenb)
return err return err
} }
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil { } else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666); err != nil {
return return
} }
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder())) w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
err = fn(w) err = fn(w)
w.Flush()
if e := writeCloser.Close(); e != nil && err == nil { if err2 := w.Flush(); err2 != nil && err == nil {
err = e err = err2
}
// Call Sync() on the file to make sure the content is safely on disk.
// Does not work with sudo as we don't have direct access to the file.
if !withSudo {
f := writeCloser.(*os.File)
if err2 := f.Sync(); err2 != nil && err == nil {
err = err2
}
}
if err2 := writeCloser.Close(); err2 != nil && err == nil {
err = err2
} }
if withSudo { if withSudo {
// wait for dd to finish and restart the screen if we used sudo // wait for dd to finish and restart the screen if we used sudo
err := cmd.Wait() err := cmd.Wait()
screen.TempStart(screenb)
if err != nil { if err != nil {
return err return err
} }
screen.TempStart(screenb)
} }
return return
@ -82,9 +93,14 @@ func (b *Buffer) Save() error {
return b.SaveAs(b.Path) return b.SaveAs(b.Path)
} }
// AutoSave saves the buffer to its default path
func (b *Buffer) AutoSave() error {
return b.saveToFile(b.Path, false, true)
}
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist // SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
func (b *Buffer) SaveAs(filename string) error { func (b *Buffer) SaveAs(filename string) error {
return b.saveToFile(filename, false) return b.saveToFile(filename, false, false)
} }
func (b *Buffer) SaveWithSudo() error { func (b *Buffer) SaveWithSudo() error {
@ -92,10 +108,10 @@ func (b *Buffer) SaveWithSudo() error {
} }
func (b *Buffer) SaveAsWithSudo(filename string) error { func (b *Buffer) SaveAsWithSudo(filename string) error {
return b.saveToFile(filename, true) return b.saveToFile(filename, true, false)
} }
func (b *Buffer) saveToFile(filename string, withSudo bool) error { func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error {
var err error var err error
if b.Type.Readonly { if b.Type.Readonly {
return errors.New("Cannot save readonly buffer") return errors.New("Cannot save readonly buffer")
@ -107,7 +123,7 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
return errors.New("Save with sudo not supported on Windows") return errors.New("Save with sudo not supported on Windows")
} }
if b.Settings["rmtrailingws"].(bool) { if !autoSave && b.Settings["rmtrailingws"].(bool) {
for i, l := range b.lines { for i, l := range b.lines {
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace)) leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))

View file

@ -148,7 +148,7 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area // ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
// and returns the number of replacements made and the number of runes // and returns the number of replacements made and the number of runes
// added or removed on the last line of the range // added or removed on the last line of the range
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) { func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte, captureGroups bool) (int, int) {
if start.GreaterThan(end) { if start.GreaterThan(end) {
start, end = end, start start, end = end, start
} }
@ -172,9 +172,13 @@ func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []b
l = util.SliceStart(l, end.X) l = util.SliceStart(l, end.X)
} }
newText := search.ReplaceAllFunc(l, func(in []byte) []byte { newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
result := []byte{} var result []byte
for _, submatches := range search.FindAllSubmatchIndex(in, -1) { if captureGroups {
result = search.Expand(result, replace, in, submatches) for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
result = search.Expand(result, replace, in, submatches)
}
} else {
result = replace
} }
found++ found++
if i == end.Y { if i == end.Y {

View file

@ -20,7 +20,7 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
} else if option == "statusline" { } else if option == "statusline" {
screen.Redraw() screen.Redraw()
} else if option == "filetype" { } else if option == "filetype" {
config.InitRuntimeFiles() config.InitRuntimeFiles(true)
err := config.ReadSettings() err := config.ReadSettings()
if err != nil { if err != nil {
screen.TermMessage(err) screen.TermMessage(err)
@ -55,6 +55,25 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
buf.HighlightSearch = nativeValue.(bool) buf.HighlightSearch = nativeValue.(bool)
} }
} }
} else {
for _, pl := range config.Plugins {
if option == pl.Name {
if nativeValue.(bool) {
if !pl.Loaded {
pl.Load()
}
_, err := pl.Call("init")
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
} else if !nativeValue.(bool) && pl.Loaded {
_, err := pl.Call("deinit")
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}
}
} }
if b.OptionCallback != nil { if b.OptionCallback != nil {

View file

@ -52,43 +52,55 @@ func InitColorscheme() error {
Colorscheme = make(map[string]tcell.Style) Colorscheme = make(map[string]tcell.Style)
DefStyle = tcell.StyleDefault DefStyle = tcell.StyleDefault
return LoadDefaultColorscheme() c, err := LoadDefaultColorscheme()
if err == nil {
Colorscheme = c
}
return err
} }
// LoadDefaultColorscheme loads the default colorscheme from $(ConfigDir)/colorschemes // LoadDefaultColorscheme loads the default colorscheme from $(ConfigDir)/colorschemes
func LoadDefaultColorscheme() error { func LoadDefaultColorscheme() (map[string]tcell.Style, error) {
return LoadColorscheme(GlobalSettings["colorscheme"].(string)) var parsedColorschemes []string
return LoadColorscheme(GlobalSettings["colorscheme"].(string), &parsedColorschemes)
} }
// LoadColorscheme loads the given colorscheme from a directory // LoadColorscheme loads the given colorscheme from a directory
func LoadColorscheme(colorschemeName string) error { func LoadColorscheme(colorschemeName string, parsedColorschemes *[]string) (map[string]tcell.Style, error) {
c := make(map[string]tcell.Style)
file := FindRuntimeFile(RTColorscheme, colorschemeName) file := FindRuntimeFile(RTColorscheme, colorschemeName)
if file == nil { if file == nil {
return errors.New(colorschemeName + " is not a valid colorscheme") return c, errors.New(colorschemeName + " is not a valid colorscheme")
} }
if data, err := file.Data(); err != nil { if data, err := file.Data(); err != nil {
return errors.New("Error loading colorscheme: " + err.Error()) return c, errors.New("Error loading colorscheme: " + err.Error())
} else { } else {
Colorscheme, err = ParseColorscheme(string(data)) var err error
c, err = ParseColorscheme(file.Name(), string(data), parsedColorschemes)
if err != nil { if err != nil {
return err return c, err
} }
} }
return nil return c, nil
} }
// ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object // ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
// Colorschemes are made up of color-link statements linking a color group to a list of colors // Colorschemes are made up of color-link statements linking a color group to a list of colors
// For example, color-link keyword (blue,red) makes all keywords have a blue foreground and // For example, color-link keyword (blue,red) makes all keywords have a blue foreground and
// red background // red background
func ParseColorscheme(text string) (map[string]tcell.Style, error) { func ParseColorscheme(name string, text string, parsedColorschemes *[]string) (map[string]tcell.Style, error) {
var err error var err error
parser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`) colorParser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
includeParser := regexp.MustCompile(`include\s+"(.*)"`)
lines := strings.Split(text, "\n") lines := strings.Split(text, "\n")
c := make(map[string]tcell.Style) c := make(map[string]tcell.Style)
if parsedColorschemes != nil {
*parsedColorschemes = append(*parsedColorschemes, name)
}
lineLoop:
for _, line := range lines { for _, line := range lines {
if strings.TrimSpace(line) == "" || if strings.TrimSpace(line) == "" ||
strings.TrimSpace(line)[0] == '#' { strings.TrimSpace(line)[0] == '#' {
@ -96,7 +108,30 @@ func ParseColorscheme(text string) (map[string]tcell.Style, error) {
continue continue
} }
matches := parser.FindSubmatch([]byte(line)) matches := includeParser.FindSubmatch([]byte(line))
if len(matches) == 2 {
// support includes only in case parsedColorschemes are given
if parsedColorschemes != nil {
include := string(matches[1])
for _, name := range *parsedColorschemes {
// check for circular includes...
if name == include {
// ...and prevent them
continue lineLoop
}
}
includeScheme, err := LoadColorscheme(include, parsedColorschemes)
if err != nil {
return c, err
}
for k, v := range includeScheme {
c[k] = v
}
}
continue
}
matches = colorParser.FindSubmatch([]byte(line))
if len(matches) == 3 { if len(matches) == 3 {
link := string(matches[1]) link := string(matches[1])
colors := string(matches[2]) colors := string(matches[2])

View file

@ -65,7 +65,7 @@ color-link constant "#AE81FF,#282828"
color-link constant.string "#E6DB74,#282828" color-link constant.string "#E6DB74,#282828"
color-link constant.string.char "#BDE6AD,#282828"` color-link constant.string.char "#BDE6AD,#282828"`
c, err := ParseColorscheme(testColorscheme) c, err := ParseColorscheme("testColorscheme", testColorscheme, nil)
assert.Nil(t, err) assert.Nil(t, err)
fg, bg, _ := c["comment"].Decompose() fg, bg, _ := c["comment"].Decompose()

View file

@ -28,7 +28,7 @@ func LoadAllPlugins() error {
func RunPluginFn(fn string, args ...lua.LValue) error { func RunPluginFn(fn string, args ...lua.LValue) error {
var reterr error var reterr error
for _, p := range Plugins { for _, p := range Plugins {
if !p.IsEnabled() { if !p.IsLoaded() {
continue continue
} }
_, err := p.Call(fn, args...) _, err := p.Call(fn, args...)
@ -42,11 +42,11 @@ func RunPluginFn(fn string, args ...lua.LValue) error {
// RunPluginFnBool runs a function in all plugins and returns // RunPluginFnBool runs a function in all plugins and returns
// false if any one of them returned false // false if any one of them returned false
// also returns an error if any of the plugins had an error // also returns an error if any of the plugins had an error
func RunPluginFnBool(fn string, args ...lua.LValue) (bool, error) { func RunPluginFnBool(settings map[string]interface{}, fn string, args ...lua.LValue) (bool, error) {
var reterr error var reterr error
retbool := true retbool := true
for _, p := range Plugins { for _, p := range Plugins {
if !p.IsEnabled() { if !p.IsLoaded() || (settings != nil && settings[p.Name] == false) {
continue continue
} }
val, err := p.Call(fn, args...) val, err := p.Call(fn, args...)
@ -71,11 +71,11 @@ type Plugin struct {
Info *PluginInfo // json file containing info Info *PluginInfo // json file containing info
Srcs []RuntimeFile // lua files Srcs []RuntimeFile // lua files
Loaded bool Loaded bool
Default bool // pre-installed plugin Default bool // pre-installed plugin
} }
// IsEnabled returns if a plugin is enabled // IsLoaded returns if a plugin is enabled
func (p *Plugin) IsEnabled() bool { func (p *Plugin) IsLoaded() bool {
if v, ok := GlobalSettings[p.Name]; ok { if v, ok := GlobalSettings[p.Name]; ok {
return v.(bool) && p.Loaded return v.(bool) && p.Loaded
} }
@ -101,7 +101,7 @@ func (p *Plugin) Load() error {
} }
} }
p.Loaded = true p.Loaded = true
RegisterGlobalOption(p.Name, true) RegisterCommonOption(p.Name, true)
return nil return nil
} }
@ -133,7 +133,7 @@ func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
func FindPlugin(name string) *Plugin { func FindPlugin(name string) *Plugin {
var pl *Plugin var pl *Plugin
for _, p := range Plugins { for _, p := range Plugins {
if !p.IsEnabled() { if !p.IsLoaded() {
continue continue
} }
if p.Name == name { if p.Name == name {

View file

@ -363,7 +363,7 @@ func GetInstalledVersions(withCore bool) PluginVersions {
} }
for _, p := range Plugins { for _, p := range Plugins {
if !p.IsEnabled() { if !p.IsLoaded() {
continue continue
} }
version := GetInstalledPluginVersion(p.Name) version := GetInstalledPluginVersion(p.Name)
@ -572,7 +572,7 @@ func (pv PluginVersions) install(out io.Writer) {
// UninstallPlugin deletes the plugin folder of the given plugin // UninstallPlugin deletes the plugin folder of the given plugin
func UninstallPlugin(out io.Writer, name string) { func UninstallPlugin(out io.Writer, name string) {
for _, p := range Plugins { for _, p := range Plugins {
if !p.IsEnabled() { if !p.IsLoaded() {
continue continue
} }
if p.Name == name { if p.Name == name {
@ -605,7 +605,7 @@ func UpdatePlugins(out io.Writer, plugins []string) {
// if no plugins are specified, update all installed plugins. // if no plugins are specified, update all installed plugins.
if len(plugins) == 0 { if len(plugins) == 0 {
for _, p := range Plugins { for _, p := range Plugins {
if !p.IsEnabled() || p.Default { if !p.IsLoaded() || p.Default {
continue continue
} }
plugins = append(plugins, p.Name) plugins = append(plugins, p.Name)

View file

@ -40,6 +40,10 @@ var allFiles [][]RuntimeFile
var realFiles [][]RuntimeFile var realFiles [][]RuntimeFile
func init() { func init() {
initRuntimeVars()
}
func initRuntimeVars() {
allFiles = make([][]RuntimeFile, NumTypes) allFiles = make([][]RuntimeFile, NumTypes)
realFiles = make([][]RuntimeFile, NumTypes) realFiles = make([][]RuntimeFile, NumTypes)
} }
@ -129,9 +133,17 @@ func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
if err != nil { if err != nil {
return return
} }
assetLoop:
for _, f := range files { for _, f := range files {
if ok, _ := path.Match(pattern, f); ok { if ok, _ := path.Match(pattern, f); ok {
AddRuntimeFile(fileType, assetFile(path.Join(directory, f))) af := assetFile(path.Join(directory, f))
for _, rf := range realFiles[fileType] {
if af.Name() == rf.Name() {
continue assetLoop
}
}
AddRuntimeFile(fileType, af)
} }
} }
} }
@ -158,19 +170,30 @@ func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
return realFiles[fileType] return realFiles[fileType]
} }
// InitRuntimeFiles initializes all assets file and the config directory // InitRuntimeFiles initializes all assets files and the config directory.
func InitRuntimeFiles() { // If `user` is false, InitRuntimeFiles ignores the config directory and
// initializes asset files only.
func InitRuntimeFiles(user bool) {
add := func(fileType RTFiletype, dir, pattern string) { add := func(fileType RTFiletype, dir, pattern string) {
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern) if user {
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
}
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern) AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
} }
initRuntimeVars()
add(RTColorscheme, "colorschemes", "*.micro") add(RTColorscheme, "colorschemes", "*.micro")
add(RTSyntax, "syntax", "*.yaml") add(RTSyntax, "syntax", "*.yaml")
add(RTSyntaxHeader, "syntax", "*.hdr") add(RTSyntaxHeader, "syntax", "*.hdr")
add(RTHelp, "help", "*.md") add(RTHelp, "help", "*.md")
}
// InitPlugins initializes the plugins
func InitPlugins() {
Plugins = Plugins[:0]
initlua := filepath.Join(ConfigDir, "init.lua") initlua := filepath.Join(ConfigDir, "init.lua")
if _, err := os.Stat(initlua); !os.IsNotExist(err) { if _, err := os.Stat(initlua); !os.IsNotExist(err) {
p := new(Plugin) p := new(Plugin)
p.Name = "initlua" p.Name = "initlua"
@ -218,7 +241,15 @@ func InitRuntimeFiles() {
plugdir = filepath.Join("runtime", "plugins") plugdir = filepath.Join("runtime", "plugins")
if files, err := rt.AssetDir(plugdir); err == nil { if files, err := rt.AssetDir(plugdir); err == nil {
outer:
for _, d := range files { for _, d := range files {
for _, p := range Plugins {
if p.Name == d {
log.Println(p.Name, "built-in plugin overridden by user-defined one")
continue outer
}
}
if srcs, err := rt.AssetDir(filepath.Join(plugdir, d)); err == nil { if srcs, err := rt.AssetDir(filepath.Join(plugdir, d)); err == nil {
p := new(Plugin) p := new(Plugin)
p.Name = d p.Name = d

View file

@ -7,7 +7,7 @@ import (
) )
func init() { func init() {
InitRuntimeFiles() InitRuntimeFiles(false)
} }
func TestAddFile(t *testing.T) { func TestAddFile(t *testing.T) {

View file

@ -8,6 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime"
"strconv" "strconv"
"strings" "strings"
@ -19,6 +20,116 @@ import (
type optionValidator func(string, interface{}) error type optionValidator func(string, interface{}) error
// a list of settings that need option validators
var optionValidators = map[string]optionValidator{
"autosave": validateNonNegativeValue,
"clipboard": validateChoice,
"colorcolumn": validateNonNegativeValue,
"colorscheme": validateColorscheme,
"detectlimit": validateNonNegativeValue,
"encoding": validateEncoding,
"fileformat": validateChoice,
"matchbracestyle": validateChoice,
"multiopen": validateChoice,
"reload": validateChoice,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
"tabsize": validatePositiveValue,
}
// a list of settings with pre-defined choices
var OptionChoices = map[string][]string{
"clipboard": {"internal", "external", "terminal"},
"fileformat": {"unix", "dos"},
"matchbracestyle": {"underline", "highlight"},
"multiopen": {"tab", "hsplit", "vsplit"},
"reload": {"prompt", "auto", "disabled"},
}
// a list of settings that can be globally and locally modified and their
// default values
var defaultCommonSettings = map[string]interface{}{
"autoindent": true,
"autosu": false,
"backup": true,
"backupdir": "",
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"detectlimit": float64(100),
"diffgutter": false,
"encoding": "utf-8",
"eofnewline": true,
"fastdirty": false,
"fileformat": defaultFileFormat(),
"filetype": "unknown",
"hlsearch": false,
"hltaberrors": false,
"hltrailingws": false,
"incsearch": true,
"ignorecase": true,
"indentchar": " ",
"keepautoindent": false,
"matchbrace": true,
"matchbracestyle": "underline",
"mkparents": false,
"permbackup": false,
"readonly": false,
"reload": "prompt",
"rmtrailingws": false,
"ruler": true,
"relativeruler": false,
"savecursor": false,
"saveundo": false,
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"smartpaste": true,
"softwrap": false,
"splitbottom": true,
"splitright": true,
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
"statusline": true,
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"useprimary": true,
"wordwrap": false,
}
// a list of settings that should only be globally modified and their
// default values
var DefaultGlobalOnlySettings = map[string]interface{}{
"autosave": float64(0),
"clipboard": "external",
"colorscheme": "default",
"divchars": "|-",
"divreverse": true,
"fakecursor": false,
"infobar": true,
"keymenu": false,
"mouse": true,
"multiopen": "tab",
"parsecursor": false,
"paste": false,
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
"pluginrepos": []string{},
"savehistory": true,
"scrollbarchar": "|",
"sucmd": "sudo",
"tabhighlight": false,
"tabreverse": true,
"xterm": false,
}
// a list of settings that should never be globally modified
var LocalSettings = []string{
"filetype",
"readonly",
}
var ( var (
ErrInvalidOption = errors.New("Invalid option") ErrInvalidOption = errors.New("Invalid option")
ErrInvalidValue = errors.New("Invalid value") ErrInvalidValue = errors.New("Invalid value")
@ -33,27 +144,18 @@ var (
// ModifiedSettings is a map of settings which should be written to disk // ModifiedSettings is a map of settings which should be written to disk
// because they have been modified by the user in this session // because they have been modified by the user in this session
ModifiedSettings map[string]bool ModifiedSettings map[string]bool
// VolatileSettings is a map of settings which should not be written to disk
// because they have been temporarily set for this session only
VolatileSettings map[string]bool
) )
func init() { func init() {
ModifiedSettings = make(map[string]bool) ModifiedSettings = make(map[string]bool)
VolatileSettings = make(map[string]bool)
parsedSettings = make(map[string]interface{}) parsedSettings = make(map[string]interface{})
} }
// Options with validators
var optionValidators = map[string]optionValidator{
"autosave": validateNonNegativeValue,
"clipboard": validateClipboard,
"tabsize": validatePositiveValue,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
"colorscheme": validateColorscheme,
"colorcolumn": validateNonNegativeValue,
"fileformat": validateLineEnding,
"encoding": validateEncoding,
"multiopen": validateMultiOpen,
}
func ReadSettings() error { func ReadSettings() error {
filename := filepath.Join(ConfigDir, "settings.json") filename := filepath.Join(ConfigDir, "settings.json")
if _, e := os.Stat(filename); e == nil { if _, e := os.Stat(filename); e == nil {
@ -172,7 +274,8 @@ func WriteSettings(filename string) error {
for k, v := range parsedSettings { for k, v := range parsedSettings {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") { if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
cur, okcur := GlobalSettings[k] cur, okcur := GlobalSettings[k]
if def, ok := defaults[k]; ok && okcur && reflect.DeepEqual(cur, def) { _, vol := VolatileSettings[k]
if def, ok := defaults[k]; ok && okcur && !vol && reflect.DeepEqual(cur, def) {
delete(parsedSettings, k) delete(parsedSettings, k)
} }
} }
@ -217,18 +320,7 @@ func OverwriteSettings(filename string) error {
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options. // RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error { func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
name = pl + "." + name return RegisterCommonOption(pl+"."+name, defaultvalue)
if _, ok := GlobalSettings[name]; !ok {
defaultCommonSettings[name] = defaultvalue
GlobalSettings[name] = defaultvalue
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
if err != nil {
return errors.New("Error writing settings.json file: " + err.Error())
}
} else {
defaultCommonSettings[name] = defaultvalue
}
return nil
} }
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name) // RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
@ -236,18 +328,21 @@ func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{})
return RegisterGlobalOption(pl+"."+name, defaultvalue) return RegisterGlobalOption(pl+"."+name, defaultvalue)
} }
// RegisterCommonOption creates a new option
func RegisterCommonOption(name string, defaultvalue interface{}) error {
if _, ok := GlobalSettings[name]; !ok {
GlobalSettings[name] = defaultvalue
}
defaultCommonSettings[name] = defaultvalue
return nil
}
// RegisterGlobalOption creates a new global-only option // RegisterGlobalOption creates a new global-only option
func RegisterGlobalOption(name string, defaultvalue interface{}) error { func RegisterGlobalOption(name string, defaultvalue interface{}) error {
if v, ok := GlobalSettings[name]; !ok { if _, ok := GlobalSettings[name]; !ok {
DefaultGlobalOnlySettings[name] = defaultvalue
GlobalSettings[name] = defaultvalue GlobalSettings[name] = defaultvalue
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
if err != nil {
return errors.New("Error writing settings.json file: " + err.Error())
}
} else {
DefaultGlobalOnlySettings[name] = v
} }
DefaultGlobalOnlySettings[name] = defaultvalue
return nil return nil
} }
@ -256,50 +351,11 @@ func GetGlobalOption(name string) interface{} {
return GlobalSettings[name] return GlobalSettings[name]
} }
var defaultCommonSettings = map[string]interface{}{ func defaultFileFormat() string {
"autoindent": true, if runtime.GOOS == "windows" {
"autosu": false, return "dos"
"backup": true, }
"backupdir": "", return "unix"
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"diffgutter": false,
"encoding": "utf-8",
"eofnewline": true,
"fastdirty": false,
"fileformat": "unix",
"filetype": "unknown",
"hlsearch": false,
"incsearch": true,
"ignorecase": true,
"indentchar": " ",
"keepautoindent": false,
"matchbrace": true,
"mkparents": false,
"permbackup": false,
"readonly": false,
"rmtrailingws": false,
"ruler": true,
"relativeruler": false,
"savecursor": false,
"saveundo": false,
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"smartpaste": true,
"softwrap": false,
"splitbottom": true,
"splitright": true,
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
"statusline": true,
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"useprimary": true,
"wordwrap": false,
} }
func GetInfoBarOffset() int { func GetInfoBarOffset() int {
@ -323,36 +379,6 @@ func DefaultCommonSettings() map[string]interface{} {
return commonsettings return commonsettings
} }
// a list of settings that should only be globally modified and their
// default values
var DefaultGlobalOnlySettings = map[string]interface{}{
"autosave": float64(0),
"clipboard": "external",
"colorscheme": "default",
"divchars": "|-",
"divreverse": true,
"fakecursor": false,
"infobar": true,
"keymenu": false,
"mouse": true,
"multiopen": "tab",
"parsecursor": false,
"paste": false,
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
"pluginrepos": []string{},
"savehistory": true,
"sucmd": "sudo",
"tabhighlight": false,
"tabreverse": true,
"xterm": false,
}
// a list of settings that should never be globally modified
var LocalSettings = []string{
"filetype",
"readonly",
}
// DefaultGlobalSettings returns the default global settings for micro // DefaultGlobalSettings returns the default global settings for micro
// Note that colorscheme is a global only option // Note that colorscheme is a global only option
func DefaultGlobalSettings() map[string]interface{} { func DefaultGlobalSettings() map[string]interface{} {
@ -446,6 +472,26 @@ func validateNonNegativeValue(option string, value interface{}) error {
return nil return nil
} }
func validateChoice(option string, value interface{}) error {
if choices, ok := OptionChoices[option]; ok {
val, ok := value.(string)
if !ok {
return errors.New("Expected string type for " + option)
}
for _, v := range choices {
if val == v {
return nil
}
}
choicesStr := strings.Join(choices, ", ")
return errors.New(option + " must be one of: " + choicesStr)
}
return errors.New("Option has no pre-defined choices")
}
func validateColorscheme(option string, value interface{}) error { func validateColorscheme(option string, value interface{}) error {
colorscheme, ok := value.(string) colorscheme, ok := value.(string)
@ -460,53 +506,7 @@ func validateColorscheme(option string, value interface{}) error {
return nil return nil
} }
func validateClipboard(option string, value interface{}) error {
val, ok := value.(string)
if !ok {
return errors.New("Expected string type for clipboard")
}
switch val {
case "internal", "external", "terminal":
default:
return errors.New(option + " must be 'internal', 'external', or 'terminal'")
}
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
}
func validateEncoding(option string, value interface{}) error { func validateEncoding(option string, value interface{}) error {
_, err := htmlindex.Get(value.(string)) _, err := htmlindex.Get(value.(string))
return err return err
} }
func validateMultiOpen(option string, value interface{}) error {
val, ok := value.(string)
if !ok {
return errors.New("Expected string type for multiopen")
}
switch val {
case "tab", "hsplit", "vsplit":
default:
return errors.New(option + " must be 'tab', 'hsplit', or 'vsplit'")
}
return nil
}

View file

@ -129,6 +129,11 @@ func (w *BufWindow) updateDisplayInfo() {
w.bufHeight-- w.bufHeight--
} }
scrollbarWidth := 0
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height && w.Width > 0 {
scrollbarWidth = 1
}
w.hasMessage = len(b.Messages) > 0 w.hasMessage = len(b.Messages) > 0
// We need to know the string length of the largest line number // We need to know the string length of the largest line number
@ -146,13 +151,13 @@ func (w *BufWindow) updateDisplayInfo() {
w.gutterOffset += w.maxLineNumLength + 1 w.gutterOffset += w.maxLineNumLength + 1
} }
prevBufWidth := w.bufWidth if w.gutterOffset > w.Width-scrollbarWidth {
w.gutterOffset = w.Width - scrollbarWidth
w.bufWidth = w.Width - w.gutterOffset
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
w.bufWidth--
} }
prevBufWidth := w.bufWidth
w.bufWidth = w.Width - w.gutterOffset - scrollbarWidth
if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) { if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) {
for _, c := range w.Buf.GetCursors() { for _, c := range w.Buf.GetCursors() {
c.LastVisualX = c.GetVisualX() c.LastVisualX = c.GetVisualX()
@ -277,13 +282,17 @@ func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
break break
} }
} }
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s) for i := 0; i < 2 && vloc.X < w.gutterOffset; i++ {
vloc.X++ screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s) vloc.X++
vloc.X++ }
} }
func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) { func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
if vloc.X >= w.gutterOffset {
return
}
symbol := ' ' symbol := ' '
styleName := "" styleName := ""
@ -319,26 +328,28 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, vloc
} else { } else {
lineInt = bloc.Y - cursorLine lineInt = bloc.Y - cursorLine
} }
lineNum := strconv.Itoa(util.Abs(lineInt)) lineNum := []rune(strconv.Itoa(util.Abs(lineInt)))
// Write the spaces before the line number if necessary // Write the spaces before the line number if necessary
for i := 0; i < w.maxLineNumLength-len(lineNum); i++ { for i := 0; i < w.maxLineNumLength-len(lineNum) && vloc.X < w.gutterOffset; i++ {
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
vloc.X++ vloc.X++
} }
// Write the actual line number // Write the actual line number
for _, ch := range lineNum { for i := 0; i < len(lineNum) && vloc.X < w.gutterOffset; i++ {
if softwrapped { if softwrapped || (w.bufWidth == 0 && w.Buf.Settings["softwrap"] == true) {
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
} else { } else {
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle) screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, lineNum[i], nil, lineNumStyle)
} }
vloc.X++ vloc.X++
} }
// Write the extra space // Write the extra space
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) if vloc.X < w.gutterOffset {
vloc.X++ screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
vloc.X++
}
} }
// getStyle returns the highlight style for the given character position // getStyle returns the highlight style for the given character position
@ -373,18 +384,7 @@ func (w *BufWindow) displayBuffer() {
if b.ModifiedThisFrame { if b.ModifiedThisFrame {
if b.Settings["diffgutter"].(bool) { if b.Settings["diffgutter"].(bool) {
b.UpdateDiff(func(synchronous bool) { b.UpdateDiff()
// If the diff was updated asynchronously, the outer call to
// displayBuffer might already be completed and we need to
// schedule a redraw in order to display the new diff.
// Note that this cannot lead to an infinite recursion
// because the modifications were cleared above so there won't
// be another call to UpdateDiff when displayBuffer is called
// during the redraw.
if !synchronous {
screen.Redraw()
}
})
} }
b.ModifiedThisFrame = false b.ModifiedThisFrame = false
} }
@ -392,26 +392,20 @@ func (w *BufWindow) displayBuffer() {
var matchingBraces []buffer.Loc var matchingBraces []buffer.Loc
// bracePairs is defined in buffer.go // bracePairs is defined in buffer.go
if b.Settings["matchbrace"].(bool) { if b.Settings["matchbrace"].(bool) {
for _, bp := range buffer.BracePairs { for _, c := range b.GetCursors() {
for _, c := range b.GetCursors() { if c.HasSelection() {
if c.HasSelection() { continue
continue }
}
curX := c.X
curLoc := c.Loc
r := c.RuneUnder(curX) mb, left, found := b.FindMatchingBrace(c.Loc)
rl := c.RuneUnder(curX - 1) if found {
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] { matchingBraces = append(matchingBraces, mb)
mb, left, found := b.FindMatchingBrace(bp, curLoc) if !left {
if found { if b.Settings["matchbracestyle"].(string) != "highlight" {
matchingBraces = append(matchingBraces, mb) matchingBraces = append(matchingBraces, c.Loc)
if !left {
matchingBraces = append(matchingBraces, curLoc)
} else {
matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
}
} }
} else {
matchingBraces = append(matchingBraces, c.Loc.Move(-1, b))
} }
} }
} }
@ -482,6 +476,12 @@ func (w *BufWindow) displayBuffer() {
vloc.X = w.gutterOffset vloc.X = w.gutterOffset
} }
bline := b.LineBytes(bloc.Y)
blineLen := util.CharacterCount(bline)
leadingwsEnd := len(util.GetLeadingWhitespace(bline))
trailingwsStart := blineLen - util.CharacterCount(util.GetTrailingWhitespace(bline))
line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y) line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
if startStyle != nil { if startStyle != nil {
curStyle = *startStyle curStyle = *startStyle
@ -505,6 +505,37 @@ func (w *BufWindow) displayBuffer() {
// over cursor-line and color-column // over cursor-line and color-column
dontOverrideBackground := origBg != defBg dontOverrideBackground := origBg != defBg
if b.Settings["hltaberrors"].(bool) {
if s, ok := config.Colorscheme["tab-error"]; ok {
isTab := (r == '\t') || (r == ' ' && !showcursor)
if (b.Settings["tabstospaces"].(bool) && isTab) ||
(!b.Settings["tabstospaces"].(bool) && bloc.X < leadingwsEnd && r == ' ' && !isTab) {
fg, _, _ := s.Decompose()
style = style.Background(fg)
dontOverrideBackground = true
}
}
}
if b.Settings["hltrailingws"].(bool) {
if s, ok := config.Colorscheme["trailingws"]; ok {
if bloc.X >= trailingwsStart && bloc.X < blineLen {
hl := true
for _, c := range cursors {
if c.NewTrailingWsY == bloc.Y {
hl = false
break
}
}
if hl {
fg, _, _ := s.Decompose()
style = style.Background(fg)
dontOverrideBackground = true
}
}
}
}
for _, c := range cursors { for _, c := range cursors {
if c.HasSelection() && if c.HasSelection() &&
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) || (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
@ -557,7 +588,15 @@ func (w *BufWindow) displayBuffer() {
for _, mb := range matchingBraces { for _, mb := range matchingBraces {
if mb.X == bloc.X && mb.Y == bloc.Y { if mb.X == bloc.X && mb.Y == bloc.Y {
style = style.Underline(true) if b.Settings["matchbracestyle"].(string) == "highlight" {
if s, ok := config.Colorscheme["match-brace"]; ok {
style = s
} else {
style = style.Reverse(true)
}
} else {
style = style.Underline(true)
}
} }
} }
} }
@ -609,7 +648,7 @@ func (w *BufWindow) displayBuffer() {
wordwidth := 0 wordwidth := 0
totalwidth := w.StartCol - nColsBeforeStart totalwidth := w.StartCol - nColsBeforeStart
for len(line) > 0 { for len(line) > 0 && vloc.X < maxWidth {
r, combc, size := util.DecodeCharacter(line) r, combc, size := util.DecodeCharacter(line)
line = line[size:] line = line[size:]
@ -767,8 +806,14 @@ func (w *BufWindow) displayScrollBar() {
scrollBarStyle = style scrollBarStyle = style
} }
scrollBarChar := config.GetGlobalOption("scrollbarchar").(string)
if util.CharacterCountInString(scrollBarChar) != 1 {
scrollBarChar = "|"
}
scrollBarRune := []rune(scrollBarChar)
for y := barstart; y < util.Min(barstart+barsize, w.Y+w.bufHeight); y++ { for y := barstart; y < util.Min(barstart+barsize, w.Y+w.bufHeight); y++ {
screen.SetContent(scrollX, y, '|', nil, scrollBarStyle) screen.SetContent(scrollX, y, scrollBarRune[0], nil, scrollBarStyle)
} }
} }
} }

View file

@ -260,7 +260,9 @@ func (i *InfoWindow) Display() {
done := false done := false
statusLineStyle := config.DefStyle.Reverse(true) statusLineStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["statusline"]; ok { if style, ok := config.Colorscheme["statusline.suggestions"]; ok {
statusLineStyle = style
} else if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style statusLineStyle = style
} }
keymenuOffset := 0 keymenuOffset := 0

View file

@ -66,7 +66,7 @@ func SetStatusInfoFnLua(fn string) {
return return
} }
statusInfo[fn] = func(b *buffer.Buffer) string { statusInfo[fn] = func(b *buffer.Buffer) string {
if pl == nil || !pl.IsEnabled() { if pl == nil || !pl.IsLoaded() {
return "" return ""
} }
val, err := pl.Call(plFn, luar.New(ulua.L, b)) val, err := pl.Call(plFn, luar.New(ulua.L, b))
@ -110,7 +110,9 @@ func (s *StatusLine) Display() {
// autocomplete suggestions (for the buffer, not for the infowindow) // autocomplete suggestions (for the buffer, not for the infowindow)
if b.HasSuggestions && len(b.Suggestions) > 1 { if b.HasSuggestions && len(b.Suggestions) > 1 {
statusLineStyle := config.DefStyle.Reverse(true) statusLineStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["statusline"]; ok { if style, ok := config.Colorscheme["statusline.suggestions"]; ok {
statusLineStyle = style
} else if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style statusLineStyle = style
} }
x := 0 x := 0
@ -167,8 +169,16 @@ func (s *StatusLine) Display() {
rightText = formatParser.ReplaceAllFunc(rightText, formatter) rightText = formatParser.ReplaceAllFunc(rightText, formatter)
statusLineStyle := config.DefStyle.Reverse(true) statusLineStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["statusline"]; ok { if s.win.IsActive() {
statusLineStyle = style if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style
}
} else {
if style, ok := config.Colorscheme["statusline.inactive"]; ok {
statusLineStyle = style
} else if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style
}
} }
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1) leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)

View file

@ -110,6 +110,8 @@ func (w *TermWindow) Display() {
} }
if w.State.CursorVisible() && w.active { if w.State.CursorVisible() && w.active {
curx, cury := w.State.Cursor() curx, cury := w.State.Cursor()
screen.ShowCursor(curx+w.X, cury+w.Y) if curx < w.Width && cury < w.Height {
screen.ShowCursor(curx+w.X, cury+w.Y)
}
} }
} }

View file

@ -4,8 +4,10 @@ import (
"encoding/gob" "encoding/gob"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
) )
// LoadHistory attempts to load user history from configDir/buffers/history // LoadHistory attempts to load user history from configDir/buffers/history
@ -102,3 +104,51 @@ func (i *InfoBuf) DownHistory(history []string) {
i.Buffer.GetActiveCursor().GotoLoc(i.End()) i.Buffer.GetActiveCursor().GotoLoc(i.End())
} }
} }
// SearchUpHistory fetches the previous item in the history
// beginning with the text in the infobuffer before cursor
func (i *InfoBuf) SearchUpHistory(history []string) {
if i.HistoryNum > 0 && i.HasPrompt && !i.HasYN {
i.searchHistory(history, false)
}
}
// SearchDownHistory fetches the next item in the history
// beginning with the text in the infobuffer before cursor
func (i *InfoBuf) SearchDownHistory(history []string) {
if i.HistoryNum < len(history)-1 && i.HasPrompt && !i.HasYN {
i.searchHistory(history, true)
}
}
func (i *InfoBuf) searchHistory(history []string, down bool) {
line := string(i.LineBytes(0))
c := i.Buffer.GetActiveCursor()
if !i.HistorySearch || !strings.HasPrefix(line, i.HistorySearchPrefix) {
i.HistorySearch = true
i.HistorySearchPrefix = util.SliceStartStr(line, c.X)
}
found := -1
if down {
for j := i.HistoryNum + 1; j < len(history); j++ {
if strings.HasPrefix(history[j], i.HistorySearchPrefix) {
found = j
break
}
}
} else {
for j := i.HistoryNum - 1; j >= 0; j-- {
if strings.HasPrefix(history[j], i.HistorySearchPrefix) {
found = j
break
}
}
}
if found != -1 {
i.HistoryNum = found
i.Replace(i.Start(), i.End(), history[found])
c.GotoLoc(i.End())
}
}

View file

@ -7,7 +7,7 @@ import (
) )
// The InfoBuf displays messages and other info at the bottom of the screen. // The InfoBuf displays messages and other info at the bottom of the screen.
// It is respresented as a buffer and a message with a style. // It is represented as a buffer and a message with a style.
type InfoBuf struct { type InfoBuf struct {
*buffer.Buffer *buffer.Buffer
@ -25,6 +25,10 @@ type InfoBuf struct {
// It's a map of history type -> history array // It's a map of history type -> history array
History map[string][]string History map[string][]string
HistoryNum int HistoryNum int
// HistorySearch indicates whether we are searching for history items
// beginning with HistorySearchPrefix
HistorySearch bool
HistorySearchPrefix string
// Is the current message a message from the gutter // Is the current message a message from the gutter
HasGutter bool HasGutter bool
@ -102,6 +106,7 @@ func (i *InfoBuf) Prompt(prompt string, msg string, ptype string, eventcb func(s
i.History[ptype] = append(i.History[ptype], "") i.History[ptype] = append(i.History[ptype], "")
} }
i.HistoryNum = len(i.History[ptype]) - 1 i.HistoryNum = len(i.History[ptype]) - 1
i.HistorySearch = false
i.PromptType = ptype i.PromptType = ptype
i.Msg = prompt i.Msg = prompt
@ -138,13 +143,12 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
if i.PromptCallback != nil { if i.PromptCallback != nil {
if canceled { if canceled {
i.Replace(i.Start(), i.End(), "") i.Replace(i.Start(), i.End(), "")
i.PromptCallback("", true)
h := i.History[i.PromptType] h := i.History[i.PromptType]
i.History[i.PromptType] = h[:len(h)-1] i.History[i.PromptType] = h[:len(h)-1]
i.PromptCallback("", true)
} else { } else {
resp := string(i.LineBytes(0)) resp := string(i.LineBytes(0))
i.Replace(i.Start(), i.End(), "") i.Replace(i.Start(), i.End(), "")
i.PromptCallback(resp, false)
h := i.History[i.PromptType] h := i.History[i.PromptType]
h[len(h)-1] = resp h[len(h)-1] = resp
@ -155,6 +159,8 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
break break
} }
} }
i.PromptCallback(resp, false)
} }
// i.PromptCallback = nil // i.PromptCallback = nil
} }

View file

@ -17,7 +17,6 @@ import (
"regexp" "regexp"
"runtime" "runtime"
"strings" "strings"
"sync"
"time" "time"
"unicode/utf8" "unicode/utf8"
@ -27,7 +26,6 @@ import (
) )
var L *lua.LState var L *lua.LState
var Lock sync.Mutex
// LoadFile loads a lua file // LoadFile loads a lua file
func LoadFile(module string, file string, data []byte) error { func LoadFile(module string, file string, data []byte) error {
@ -376,9 +374,9 @@ func importOs() *lua.LTable {
L.SetField(pkg, "Remove", luar.New(L, os.Remove)) L.SetField(pkg, "Remove", luar.New(L, os.Remove))
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll)) L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
L.SetField(pkg, "Rename", luar.New(L, os.Rename)) L.SetField(pkg, "Rename", luar.New(L, os.Rename))
L.SetField(pkg, "SEEK_CUR", luar.New(L, os.SEEK_CUR)) L.SetField(pkg, "SEEK_CUR", luar.New(L, io.SeekCurrent))
L.SetField(pkg, "SEEK_END", luar.New(L, os.SEEK_END)) L.SetField(pkg, "SEEK_END", luar.New(L, io.SeekEnd))
L.SetField(pkg, "SEEK_SET", luar.New(L, os.SEEK_SET)) L.SetField(pkg, "SEEK_SET", luar.New(L, io.SeekStart))
L.SetField(pkg, "SameFile", luar.New(L, os.SameFile)) L.SetField(pkg, "SameFile", luar.New(L, os.SameFile))
L.SetField(pkg, "Setenv", luar.New(L, os.Setenv)) L.SetField(pkg, "Setenv", luar.New(L, os.Setenv))
L.SetField(pkg, "StartProcess", luar.New(L, os.StartProcess)) L.SetField(pkg, "StartProcess", luar.New(L, os.StartProcess))
@ -523,21 +521,16 @@ func importErrors() *lua.LTable {
func importTime() *lua.LTable { func importTime() *lua.LTable {
pkg := L.NewTable() pkg := L.NewTable()
L.SetField(pkg, "After", luar.New(L, time.After))
L.SetField(pkg, "Sleep", luar.New(L, time.Sleep)) 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, "Since", luar.New(L, time.Since))
L.SetField(pkg, "FixedZone", luar.New(L, time.FixedZone)) L.SetField(pkg, "FixedZone", luar.New(L, time.FixedZone))
L.SetField(pkg, "LoadLocation", luar.New(L, time.LoadLocation)) 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, "Date", luar.New(L, time.Date))
L.SetField(pkg, "Now", luar.New(L, time.Now)) L.SetField(pkg, "Now", luar.New(L, time.Now))
L.SetField(pkg, "Parse", luar.New(L, time.Parse)) L.SetField(pkg, "Parse", luar.New(L, time.Parse))
L.SetField(pkg, "ParseDuration", luar.New(L, time.ParseDuration)) L.SetField(pkg, "ParseDuration", luar.New(L, time.ParseDuration))
L.SetField(pkg, "ParseInLocation", luar.New(L, time.ParseInLocation)) L.SetField(pkg, "ParseInLocation", luar.New(L, time.ParseInLocation))
L.SetField(pkg, "Unix", luar.New(L, time.Unix)) 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, "Nanosecond", luar.New(L, time.Nanosecond))
L.SetField(pkg, "Microsecond", luar.New(L, time.Microsecond)) L.SetField(pkg, "Microsecond", luar.New(L, time.Microsecond))
L.SetField(pkg, "Millisecond", luar.New(L, time.Millisecond)) L.SetField(pkg, "Millisecond", luar.New(L, time.Millisecond))
@ -545,6 +538,15 @@ func importTime() *lua.LTable {
L.SetField(pkg, "Minute", luar.New(L, time.Minute)) L.SetField(pkg, "Minute", luar.New(L, time.Minute))
L.SetField(pkg, "Hour", luar.New(L, time.Hour)) L.SetField(pkg, "Hour", luar.New(L, time.Hour))
// TODO: these raw Go timer APIs don't provide any synchronization
// with micro. Stop exposing them to lua once plugins switch to using
// the safer micro.After() interface instead. See issue #3209
L.SetField(pkg, "After", luar.New(L, time.After))
L.SetField(pkg, "AfterFunc", luar.New(L, time.AfterFunc))
L.SetField(pkg, "NewTicker", luar.New(L, time.NewTicker))
L.SetField(pkg, "NewTimer", luar.New(L, time.NewTimer))
L.SetField(pkg, "Tick", luar.New(L, time.Tick))
return pkg return pkg
} }

View file

@ -22,6 +22,10 @@ var Screen tcell.Screen
// Events is the channel of tcell events // Events is the channel of tcell events
var Events chan (tcell.Event) var Events chan (tcell.Event)
// RestartCallback is called when the screen is restarted after it was
// temporarily shut down
var RestartCallback func()
// The lock is necessary since the screen is polled on a separate thread // The lock is necessary since the screen is polled on a separate thread
var lock sync.Mutex var lock sync.Mutex
@ -134,6 +138,10 @@ func TempStart(screenWasNil bool) {
if !screenWasNil { if !screenWasNil {
Init() Init()
Unlock() Unlock()
if RestartCallback != nil {
RestartCallback()
}
} }
} }

View file

@ -8,7 +8,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"strings"
shellquote "github.com/kballard/go-shellquote" shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/screen"
@ -59,15 +58,10 @@ func RunBackgroundShell(input string) (func() string, error) {
inputCmd := args[0] inputCmd := args[0]
return func() string { return func() string {
output, err := RunCommand(input) output, err := RunCommand(input)
totalLines := strings.Split(output, "\n")
str := output str := output
if len(totalLines) < 3 { if err != nil {
if err == nil { str = fmt.Sprint(inputCmd, " exited with error: ", err, ": ", output)
str = fmt.Sprint(inputCmd, " exited without error")
} else {
str = fmt.Sprint(inputCmd, " exited with error: ", err, ": ", output)
}
} }
return str return str
}, nil }, nil

View file

@ -78,8 +78,9 @@ func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback f
t.output = nil t.output = nil
if getOutput { if getOutput {
t.output = bytes.NewBuffer([]byte{}) t.output = bytes.NewBuffer([]byte{})
cmd.Stdout = t.output
} }
Term, _, err := terminal.Start(&t.State, cmd, t.output) Term, _, err := terminal.Start(&t.State, cmd)
if err != nil { if err != nil {
return err return err
} }

View file

@ -16,6 +16,7 @@ import (
"strings" "strings"
"time" "time"
"unicode" "unicode"
"unicode/utf8"
"github.com/blang/semver" "github.com/blang/semver"
runewidth "github.com/mattn/go-runewidth" runewidth "github.com/mattn/go-runewidth"
@ -217,10 +218,68 @@ func FSize(f *os.File) int64 {
return fi.Size() return fi.Size()
} }
// IsWordChar returns whether or not the string is a 'word character' // IsWordChar returns whether or not a rune is a 'word character'
// Word characters are defined as numbers, letters, or '_' // Word characters are defined as numbers, letters or sub-word delimiters
func IsWordChar(r rune) bool { func IsWordChar(r rune) bool {
return unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' return IsAlphanumeric(r) || IsSubwordDelimiter(r)
}
// IsNonWordChar returns whether or not a rune is not a 'word character'
// Non word characters are defined as all characters not being numbers, letters or sub-word delimiters
// See IsWordChar()
func IsNonWordChar(r rune) bool {
return !IsWordChar(r)
}
// IsUpperWordChar returns whether or not a rune is an 'upper word character'
// Upper word characters are defined as numbers, upper-case letters or sub-word delimiters
func IsUpperWordChar(r rune) bool {
return IsUpperAlphanumeric(r) || IsSubwordDelimiter(r)
}
// IsLowerWordChar returns whether or not a rune is a 'lower word character'
// Lower word characters are defined as numbers, lower-case letters or sub-word delimiters
func IsLowerWordChar(r rune) bool {
return IsLowerAlphanumeric(r) || IsSubwordDelimiter(r)
}
// IsSubwordDelimiter returns whether or not a rune is a 'sub-word delimiter character'
// i.e. is considered a part of the word and is used as a delimiter between sub-words of the word.
// For now the only sub-word delimiter character is '_'.
func IsSubwordDelimiter(r rune) bool {
return r == '_'
}
// IsAlphanumeric returns whether or not a rune is an 'alphanumeric character'
// Alphanumeric characters are defined as numbers or letters
func IsAlphanumeric(r rune) bool {
return unicode.IsLetter(r) || unicode.IsNumber(r)
}
// IsUpperAlphanumeric returns whether or not a rune is an 'upper alphanumeric character'
// Upper alphanumeric characters are defined as numbers or upper-case letters
func IsUpperAlphanumeric(r rune) bool {
return IsUpperLetter(r) || unicode.IsNumber(r)
}
// IsLowerAlphanumeric returns whether or not a rune is a 'lower alphanumeric character'
// Lower alphanumeric characters are defined as numbers or lower-case letters
func IsLowerAlphanumeric(r rune) bool {
return IsLowerLetter(r) || unicode.IsNumber(r)
}
// IsUpperLetter returns whether or not a rune is an 'upper letter character'
// Upper letter characters are defined as upper-case letters
func IsUpperLetter(r rune) bool {
// unicode.IsUpper() returns true for letters only
return unicode.IsUpper(r)
}
// IsLowerLetter returns whether or not a rune is a 'lower letter character'
// Lower letter characters are defined as lower-case letters
func IsLowerLetter(r rune) bool {
// unicode.IsLower() returns true for letters only
return unicode.IsLower(r)
} }
// Spaces returns a string with n spaces // Spaces returns a string with n spaces
@ -315,7 +374,7 @@ func ReplaceHome(path string) (string, error) {
// This is used for opening files like util.go:10:5 to specify a line and column // This is used for opening files like util.go:10:5 to specify a line and column
// Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly. // Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
func GetPathAndCursorPosition(path string) (string, []string) { func GetPathAndCursorPosition(path string) (string, []string) {
re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`) re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?$`)
match := re.FindStringSubmatch(path) match := re.FindStringSubmatch(path)
// no lines/columns were specified in the path, return just the path with no cursor location // no lines/columns were specified in the path, return just the path with no cursor location
if len(match) == 0 { if len(match) == 0 {
@ -363,6 +422,28 @@ func GetLeadingWhitespace(b []byte) []byte {
return ws return ws
} }
// GetTrailingWhitespace returns the trailing whitespace of the given byte array
func GetTrailingWhitespace(b []byte) []byte {
ws := []byte{}
for len(b) > 0 {
r, size := utf8.DecodeLastRune(b)
if IsWhitespace(r) {
ws = append([]byte(string(r)), ws...)
} else {
break
}
b = b[:len(b)-size]
}
return ws
}
// HasTrailingWhitespace returns true if the given byte array ends with a whitespace
func HasTrailingWhitespace(b []byte) bool {
r, _ := utf8.DecodeLastRune(b)
return IsWhitespace(r)
}
// IntOpt turns a float64 setting to an int // IntOpt turns a float64 setting to an int
func IntOpt(opt interface{}) int { func IntOpt(opt interface{}) int {
return int(opt.(float64)) return int(opt.(float64))
@ -422,14 +503,9 @@ func Clamp(val, min, max int) int {
return val return val
} }
// IsNonAlphaNumeric returns if the rune is not a number of letter or underscore.
func IsNonAlphaNumeric(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
}
// IsAutocomplete returns whether a character should begin an autocompletion. // IsAutocomplete returns whether a character should begin an autocompletion.
func IsAutocomplete(c rune) bool { func IsAutocomplete(c rune) bool {
return c == '.' || !IsNonAlphaNumeric(c) return c == '.' || IsWordChar(c)
} }
// ParseSpecial replaces escaped ts with '\t'. // ParseSpecial replaces escaped ts with '\t'.

View file

@ -185,6 +185,10 @@ func (n *Node) hResizeSplit(i int, size int) bool {
// ResizeSplit resizes a certain split to a given size // ResizeSplit resizes a certain split to a given size
func (n *Node) ResizeSplit(size int) bool { func (n *Node) ResizeSplit(size int) bool {
// TODO: `size < 0` does not work for some reason
if size <= 0 {
return false
}
if len(n.parent.children) <= 1 { if len(n.parent.children) <= 1 {
// cannot resize a lone node // cannot resize a lone node
return false return false
@ -201,7 +205,7 @@ func (n *Node) ResizeSplit(size int) bool {
return n.parent.hResizeSplit(ind, size) return n.parent.hResizeSplit(ind, size)
} }
// Resize sets this node's size and resizes all children accordlingly // Resize sets this node's size and resizes all children accordingly
func (n *Node) Resize(w, h int) { func (n *Node) Resize(w, h int) {
n.W, n.H = w, h n.W, n.H = w, h

View file

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

View file

@ -67,9 +67,6 @@ func combineLineMatch(src, dst LineMatch) LineMatch {
// A State represents the region at the end of a line // A State represents the region at the end of a line
type State *region type State *region
// EmptyDef is an empty definition.
var EmptyDef = Def{nil, &rules{}}
// LineStates is an interface for a buffer-like object which can also store the states and matches for every line // LineStates is an interface for a buffer-like object which can also store the states and matches for every line
type LineStates interface { type LineStates interface {
LineBytes(n int) []byte LineBytes(n int) []byte
@ -77,6 +74,8 @@ type LineStates interface {
State(lineN int) State State(lineN int) State
SetState(lineN int, s State) SetState(lineN int, s State)
SetMatch(lineN int, m LineMatch) SetMatch(lineN int, m LineMatch)
Lock()
Unlock()
} }
// A Highlighter contains the information needed to highlight a string // A Highlighter contains the information needed to highlight a string
@ -96,19 +95,7 @@ func NewHighlighter(def *Def) *Highlighter {
// color's group (represented as one byte) // color's group (represented as one byte)
type LineMatch map[int]Group type LineMatch map[int]Group
func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) []int { func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte) []int {
regexStr := regex.String()
if strings.Contains(regexStr, "^") {
if !canMatchStart {
return nil
}
}
if strings.Contains(regexStr, "$") {
if !canMatchEnd {
return nil
}
}
var strbytes []byte var strbytes []byte
if skip != nil { if skip != nil {
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte { strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
@ -127,18 +114,7 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchSt
return []int{runePos(match[0], str), runePos(match[1], str)} return []int{runePos(match[0], str), runePos(match[1], str)}
} }
func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) [][]int { func findAllIndex(regex *regexp.Regexp, str []byte) [][]int {
regexStr := regex.String()
if strings.Contains(regexStr, "^") {
if !canMatchStart {
return nil
}
}
if strings.Contains(regexStr, "$") {
if !canMatchEnd {
return nil
}
}
matches := regex.FindAllIndex(str, -1) matches := regex.FindAllIndex(str, -1)
for i, m := range matches { for i, m := range matches {
matches[i][0] = runePos(m[0], str) matches[i][0] = runePos(m[0], str)
@ -157,52 +133,33 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
} }
} }
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, sliceEnd(line, loc[0]), curRegion, statesOnly)
}
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), statesOnly)
return highlights
}
if !statesOnly {
highlights[start+loc[1]] = curRegion.parent.group
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, loc[0]), curRegion, statesOnly)
}
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), curRegion.parent, statesOnly)
return highlights
}
if lineLen == 0 {
if canMatchEnd {
h.lastRegion = curRegion
}
return highlights
}
firstLoc := []int{lineLen, 0}
var firstRegion *region var firstRegion *region
for _, r := range curRegion.rules.regions { firstLoc := []int{lineLen, 0}
loc := findIndex(r.start, nil, line, start == 0, canMatchEnd) searchNesting := true
if loc != nil { endLoc := findIndex(curRegion.end, curRegion.skip, line)
if loc[0] < firstLoc[0] { if endLoc != nil {
firstLoc = loc if start == endLoc[0] {
firstRegion = r searchNesting = false
} else {
firstLoc = endLoc
}
}
if searchNesting {
for _, r := range curRegion.rules.regions {
loc := findIndex(r.start, r.skip, line)
if loc != nil {
if loc[0] < firstLoc[0] {
firstLoc = loc
firstRegion = r
}
} }
} }
} }
if firstLoc[0] != lineLen { if firstRegion != nil && firstLoc[0] != lineLen {
if !statesOnly { if !statesOnly {
highlights[start+firstLoc[0]] = firstRegion.limitGroup highlights[start+firstLoc[0]] = firstRegion.limitGroup
} }
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), curRegion, statesOnly) h.highlightEmptyRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), statesOnly)
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly) h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
return highlights return highlights
} }
@ -213,11 +170,17 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
fullHighlights[i] = curRegion.group fullHighlights[i] = curRegion.group
} }
for _, p := range curRegion.rules.patterns { if searchNesting {
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd) for _, p := range curRegion.rules.patterns {
for _, m := range matches { if curRegion.group == curRegion.limitGroup || p.group == curRegion.limitGroup {
for i := m[0]; i < m[1]; i++ { matches := findAllIndex(p.regex, line)
fullHighlights[i] = p.group for _, m := range matches {
if ((endLoc == nil) || (m[0] < endLoc[0])) {
for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
}
}
}
} }
} }
} }
@ -228,6 +191,25 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
} }
} }
loc := endLoc
if loc != nil {
if !statesOnly {
highlights[start+loc[0]] = curRegion.limitGroup
}
if curRegion.parent == nil {
if !statesOnly {
highlights[start+loc[1]] = 0
}
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), statesOnly)
return highlights
}
if !statesOnly {
highlights[start+loc[1]] = curRegion.parent.group
}
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), curRegion.parent, statesOnly)
return highlights
}
if canMatchEnd { if canMatchEnd {
h.lastRegion = curRegion h.lastRegion = curRegion
} }
@ -244,10 +226,10 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
return highlights return highlights
} }
firstLoc := []int{lineLen, 0}
var firstRegion *region var firstRegion *region
firstLoc := []int{lineLen, 0}
for _, r := range h.Def.rules.regions { for _, r := range h.Def.rules.regions {
loc := findIndex(r.start, nil, line, start == 0, canMatchEnd) loc := findIndex(r.start, r.skip, line)
if loc != nil { if loc != nil {
if loc[0] < firstLoc[0] { if loc[0] < firstLoc[0] {
firstLoc = loc firstLoc = loc
@ -255,7 +237,7 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
} }
} }
} }
if firstLoc[0] != lineLen { if firstRegion != nil && firstLoc[0] != lineLen {
if !statesOnly { if !statesOnly {
highlights[start+firstLoc[0]] = firstRegion.limitGroup highlights[start+firstLoc[0]] = firstRegion.limitGroup
} }
@ -274,7 +256,7 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
fullHighlights := make([]Group, len(line)) fullHighlights := make([]Group, len(line))
for _, p := range h.Def.rules.patterns { for _, p := range h.Def.rules.patterns {
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd) matches := findAllIndex(p.regex, line)
for _, m := range matches { for _, m := range matches {
for i := m[0]; i < m[1]; i++ { for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group fullHighlights[i] = p.group
@ -320,7 +302,13 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
// HighlightStates correctly sets all states for the buffer // HighlightStates correctly sets all states for the buffer
func (h *Highlighter) HighlightStates(input LineStates) { func (h *Highlighter) HighlightStates(input LineStates) {
for i := 0; i < input.LinesNum(); i++ { for i := 0; ; i++ {
input.Lock()
if i >= input.LinesNum() {
input.Unlock()
break
}
line := input.LineBytes(i) line := input.LineBytes(i)
// highlights := make(LineMatch) // highlights := make(LineMatch)
@ -333,6 +321,7 @@ func (h *Highlighter) HighlightStates(input LineStates) {
curState := h.lastRegion curState := h.lastRegion
input.SetState(i, curState) input.SetState(i, curState)
input.Unlock()
} }
} }
@ -341,7 +330,9 @@ func (h *Highlighter) HighlightStates(input LineStates) {
// This assumes that all the states are set correctly // This assumes that all the states are set correctly
func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) { func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
for i := startline; i <= endline; i++ { for i := startline; i <= endline; i++ {
input.Lock()
if i >= input.LinesNum() { if i >= input.LinesNum() {
input.Unlock()
break break
} }
@ -356,6 +347,7 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
} }
input.SetMatch(i, match) input.SetMatch(i, match)
input.Unlock()
} }
} }
@ -367,9 +359,19 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
h.lastRegion = nil h.lastRegion = nil
if startline > 0 { if startline > 0 {
h.lastRegion = input.State(startline - 1) input.Lock()
if startline-1 < input.LinesNum() {
h.lastRegion = input.State(startline - 1)
}
input.Unlock()
} }
for i := startline; i < input.LinesNum(); i++ { for i := startline; ; i++ {
input.Lock()
if i >= input.LinesNum() {
input.Unlock()
break
}
line := input.LineBytes(i) line := input.LineBytes(i)
// highlights := make(LineMatch) // highlights := make(LineMatch)
@ -383,6 +385,7 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
lastState := input.State(i) lastState := input.State(i)
input.SetState(i, curState) input.SetState(i, curState)
input.Unlock()
if curState == lastState { if curState == lastState {
return i return i
@ -394,6 +397,9 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
// ReHighlightLine will rehighlight the state and match for a single line // ReHighlightLine will rehighlight the state and match for a single line
func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) { func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
input.Lock()
defer input.Unlock()
line := input.LineBytes(lineN) line := input.LineBytes(lineN)
highlights := make(LineMatch) highlights := make(LineMatch)

View file

@ -33,27 +33,28 @@ func (g Group) String() string {
// Then it has the rules which define how to highlight the file // Then it has the rules which define how to highlight the file
type Def struct { type Def struct {
*Header *Header
rules *rules rules *rules
} }
type Header struct { type Header struct {
FileType string FileType string
FtDetect [2]*regexp.Regexp FileNameRegex *regexp.Regexp
HeaderRegex *regexp.Regexp
SignatureRegex *regexp.Regexp
} }
type HeaderYaml struct { type HeaderYaml struct {
FileType string `yaml:"filetype"` FileType string `yaml:"filetype"`
Detect struct { Detect struct {
FNameRgx string `yaml:"filename"` FNameRegexStr string `yaml:"filename"`
HeaderRgx string `yaml:"header"` HeaderRegexStr string `yaml:"header"`
SignatureRegexStr string `yaml:"signature"`
} `yaml:"detect"` } `yaml:"detect"`
} }
type File struct { type File struct {
FileType string FileType string
yamlSrc map[interface{}]interface{}
yamlSrc map[interface{}]interface{}
} }
// A Pattern is one simple syntax rule // A Pattern is one simple syntax rule
@ -97,20 +98,24 @@ func init() {
// A yaml file might take ~400us to parse while a header file only takes ~20us // A yaml file might take ~400us to parse while a header file only takes ~20us
func MakeHeader(data []byte) (*Header, error) { func MakeHeader(data []byte) (*Header, error) {
lines := bytes.Split(data, []byte{'\n'}) lines := bytes.Split(data, []byte{'\n'})
if len(lines) < 3 { if len(lines) < 4 {
return nil, errors.New("Header file has incorrect format") return nil, errors.New("Header file has incorrect format")
} }
header := new(Header) header := new(Header)
var err error var err error
header.FileType = string(lines[0]) header.FileType = string(lines[0])
fnameRgx := string(lines[1]) fnameRegexStr := string(lines[1])
headerRgx := string(lines[2]) headerRegexStr := string(lines[2])
signatureRegexStr := string(lines[3])
if fnameRgx != "" { if fnameRegexStr != "" {
header.FtDetect[0], err = regexp.Compile(fnameRgx) header.FileNameRegex, err = regexp.Compile(fnameRegexStr)
} }
if headerRgx != "" { if err == nil && headerRegexStr != "" {
header.FtDetect[1], err = regexp.Compile(headerRgx) header.HeaderRegex, err = regexp.Compile(headerRegexStr)
}
if err == nil && signatureRegexStr != "" {
header.SignatureRegex, err = regexp.Compile(signatureRegexStr)
} }
if err != nil { if err != nil {
@ -132,11 +137,14 @@ func MakeHeaderYaml(data []byte) (*Header, error) {
header := new(Header) header := new(Header)
header.FileType = hdrYaml.FileType header.FileType = hdrYaml.FileType
if hdrYaml.Detect.FNameRgx != "" { if hdrYaml.Detect.FNameRegexStr != "" {
header.FtDetect[0], err = regexp.Compile(hdrYaml.Detect.FNameRgx) header.FileNameRegex, err = regexp.Compile(hdrYaml.Detect.FNameRegexStr)
} }
if hdrYaml.Detect.HeaderRgx != "" { if err == nil && hdrYaml.Detect.HeaderRegexStr != "" {
header.FtDetect[1], err = regexp.Compile(hdrYaml.Detect.HeaderRgx) header.HeaderRegex, err = regexp.Compile(hdrYaml.Detect.HeaderRegexStr)
}
if err == nil && hdrYaml.Detect.SignatureRegexStr != "" {
header.SignatureRegex, err = regexp.Compile(hdrYaml.Detect.SignatureRegexStr)
} }
if err != nil { if err != nil {
@ -146,6 +154,37 @@ func MakeHeaderYaml(data []byte) (*Header, error) {
return header, nil return header, nil
} }
// MatchFileName will check the given file name with the stored regex
func (header *Header) MatchFileName(filename string) bool {
if header.FileNameRegex != nil {
return header.FileNameRegex.MatchString(filename)
}
return false
}
func (header *Header) MatchFileHeader(firstLine []byte) bool {
if header.HeaderRegex != nil {
return header.HeaderRegex.Match(firstLine)
}
return false
}
// HasFileSignature checks the presence of a stored signature
func (header *Header) HasFileSignature() bool {
return header.SignatureRegex != nil
}
// MatchFileSignature will check the given line with the stored regex
func (header *Header) MatchFileSignature(line []byte) bool {
if header.SignatureRegex != nil {
return header.SignatureRegex.Match(line)
}
return false
}
func ParseFile(input []byte) (f *File, err error) { 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 // This is just so if we have an error, we can exit cleanly and return the parse error to the user
defer func() { defer func() {
@ -170,11 +209,19 @@ func ParseFile(input []byte) (f *File, err error) {
if k == "filetype" { if k == "filetype" {
filetype := v.(string) filetype := v.(string)
if filetype == "" {
return nil, errors.New("empty filetype")
}
f.FileType = filetype f.FileType = filetype
break break
} }
} }
if f.FileType == "" {
return nil, errors.New("missing filetype")
}
return f, err return f, err
} }
@ -191,12 +238,12 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
} }
}() }()
rules := f.yamlSrc src := f.yamlSrc
s = new(Def) s = new(Def)
s.Header = header s.Header = header
for k, v := range rules { for k, v := range src {
if k == "rules" { if k == "rules" {
inputRules := v.([]interface{}) inputRules := v.([]interface{})
@ -209,6 +256,11 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
} }
} }
if s.rules == nil {
// allow empty rules
s.rules = new(rules)
}
return s, err return s, err
} }
@ -303,6 +355,10 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
switch object := val.(type) { switch object := val.(type) {
case string: case string:
if object == "" {
return nil, fmt.Errorf("Empty rule %s", k)
}
if k == "include" { if k == "include" {
ru.includes = append(ru.includes, object) ru.includes = append(ru.includes, object)
} else { } else {
@ -356,30 +412,56 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
r.group = groupNum r.group = groupNum
r.parent = prevRegion r.parent = prevRegion
r.start, err = regexp.Compile(regionInfo["start"].(string)) // start is mandatory
if start, ok := regionInfo["start"]; ok {
start := start.(string)
if start == "" {
return nil, fmt.Errorf("Empty start in %s", group)
}
if err != nil { r.start, err = regexp.Compile(start)
return nil, err if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("Missing start in %s", group)
} }
r.end, err = regexp.Compile(regionInfo["end"].(string)) // end is mandatory
if end, ok := regionInfo["end"]; ok {
end := end.(string)
if end == "" {
return nil, fmt.Errorf("Empty end in %s", group)
}
if err != nil { r.end, err = regexp.Compile(end)
return nil, err if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("Missing end in %s", group)
} }
// skip is optional // skip is optional
if _, ok := regionInfo["skip"]; ok { if skip, ok := regionInfo["skip"]; ok {
r.skip, err = regexp.Compile(regionInfo["skip"].(string)) skip := skip.(string)
if skip == "" {
return nil, fmt.Errorf("Empty skip in %s", group)
}
r.skip, err = regexp.Compile(skip)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
// limit-color is optional // limit-color is optional
if _, ok := regionInfo["limit-group"]; ok { if groupStr, ok := regionInfo["limit-group"]; ok {
groupStr := regionInfo["limit-group"].(string) groupStr := groupStr.(string)
if groupStr == "" {
return nil, fmt.Errorf("Empty limit-group in %s", group)
}
if _, ok := Groups[groupStr]; !ok { if _, ok := Groups[groupStr]; !ok {
numGroups++ numGroups++
Groups[groupStr] = numGroups Groups[groupStr] = numGroups

View file

@ -28,3 +28,6 @@ color-link color-column "#2D2F31"
#No extended types (bool in C, etc.) #No extended types (bool in C, etc.)
#color-link type.extended "default" #color-link type.extended "default"
#Plain brackets #Plain brackets
color-link match-brace "#1D1F21,#62B1FE"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View file

@ -26,3 +26,6 @@ color-link color-column "254"
#No extended types (bool in C, &c.) and plain brackets #No extended types (bool in C, &c.) and plain brackets
color-link type.extended "241,231" color-link type.extended "241,231"
color-link symbol.brackets "241,231" color-link symbol.brackets "241,231"
color-link match-brace "231,28"
color-link tab-error "210"
color-link trailingws "210"

View file

@ -42,3 +42,6 @@ color-link gutter-warning "red"
color-link color-column "cyan" color-link color-column "cyan"
color-link underlined.url "underline blue, white" color-link underlined.url "underline blue, white"
color-link divider "blue" color-link divider "blue"
color-link match-brace "black,cyan"
color-link tab-error "brightred"
color-link trailingws "brightred"

View file

@ -38,3 +38,6 @@ color-link color-column "#f26522"
color-link constant.bool "bold #55ffff" color-link constant.bool "bold #55ffff"
color-link constant.bool.true "bold #85ff85" color-link constant.bool.true "bold #85ff85"
color-link constant.bool.false "bold #ff8585" color-link constant.bool.false "bold #ff8585"
color-link match-brace "#1e2124,#55ffff"
color-link tab-error "#d75f5f"
color-link trailingws "#d75f5f"

View file

@ -29,3 +29,6 @@ color-link color-column "#2C2C2C"
color-link type.extended "default" color-link type.extended "default"
#color-link symbol.brackets "default" #color-link symbol.brackets "default"
color-link symbol.tag "#AE81FF,#242424" color-link symbol.tag "#AE81FF,#242424"
color-link match-brace "#242424,#7A9EC2"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View file

@ -1,31 +1 @@
color-link default "#F8F8F2,#282828" include "monokai"
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.operator "#F92671,#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 hlsearch "#282828,#E6DB74"
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 diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
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

@ -43,3 +43,7 @@ color-link cursor-line "#44475A,#F8F8F2"
color-link color-column "#44475A" color-link color-column "#44475A"
color-link type.extended "default" color-link type.extended "default"
color-link match-brace "#282A36,#FF79C6"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View file

@ -33,3 +33,6 @@ color-link type "bold #3cc83c,#001e28"
color-link type.keyword "bold #5aaae6,#001e28" color-link type.keyword "bold #5aaae6,#001e28"
color-link type.extended "#ffffff,#001e28" color-link type.extended "#ffffff,#001e28"
color-link underlined "#608b4e,#001e28" color-link underlined "#608b4e,#001e28"
color-link match-brace "#001e28,#5aaae6"
color-link tab-error "#d75f5f"
color-link trailingws "#d75f5f"

View file

@ -33,3 +33,6 @@ color-link type "bold #004080,#f0f0f0"
color-link type.keyword "bold #780050,#f0f0f0" color-link type.keyword "bold #780050,#f0f0f0"
color-link type.extended "#000000,#f0f0f0" color-link type.extended "#000000,#f0f0f0"
color-link underlined "#3f7f5f,#f0f0f0" color-link underlined "#3f7f5f,#f0f0f0"
color-link match-brace "#f0f0f0,#780050"
color-link tab-error "#ff8787"
color-link trailingws "#ff8787"

View file

@ -33,3 +33,6 @@ color-link type "bold #3cc83c,#2d0023"
color-link type.keyword "bold #5aaae6,#2d0023" color-link type.keyword "bold #5aaae6,#2d0023"
color-link type.extended "#ffffff,#2d0023" color-link type.extended "#ffffff,#2d0023"
color-link underlined "#886484,#2d0023" color-link underlined "#886484,#2d0023"
color-link match-brace "#2d0023,#5aaae6"
color-link tab-error "#d75f5f"
color-link trailingws "#d75f5f"

View file

@ -24,3 +24,6 @@ color-link diff-modified "yellow"
color-link diff-deleted "red" color-link diff-deleted "red"
color-link gutter-error ",red" color-link gutter-error ",red"
color-link gutter-warning "red" color-link gutter-warning "red"
color-link match-brace "black,cyan"
color-link tab-error "brightred"
color-link trailingws "brightred"

View file

@ -24,3 +24,6 @@ color-link gutter-warning "#EDB443,#11151C"
color-link cursor-line "#091F2E" color-link cursor-line "#091F2E"
color-link color-column "#11151C" color-link color-column "#11151C"
color-link symbol "#99D1CE,#0C1014" color-link symbol "#99D1CE,#0C1014"
color-link match-brace "#0C1014,#D26937"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View file

@ -24,3 +24,6 @@ color-link cursor-line "#3c3836"
color-link color-column "#79740e" color-link color-column "#79740e"
color-link statusline "#ebdbb2,#665c54" color-link statusline "#ebdbb2,#665c54"
color-link tabbar "#ebdbb2,#665c54" color-link tabbar "#ebdbb2,#665c54"
color-link match-brace "#282828,#d3869b"
color-link tab-error "#d75f5f"
color-link trailingws "#d75f5f"

View file

@ -21,3 +21,6 @@ color-link cursor-line "237"
color-link color-column "237" color-link color-column "237"
color-link statusline "223,237" color-link statusline "223,237"
color-link tabbar "223,237" color-link tabbar "223,237"
color-link match-brace "235,72"
color-link tab-error "167"
color-link trailingws "167"

View file

@ -20,6 +20,7 @@ color-link identifier.macro "#FFCB6B,#263238"
color-link indent-char "#505050,#263238" color-link indent-char "#505050,#263238"
color-link line-number "#656866,#283942" color-link line-number "#656866,#283942"
color-link preproc "#C792EA,#263238" color-link preproc "#C792EA,#263238"
color-link scrollbar "#80DEEA,#283942"
color-link special "#C792EA,#263238" color-link special "#C792EA,#263238"
color-link statement "#C792EA,#263238" color-link statement "#C792EA,#263238"
color-link statusline "#80DEEA,#3b4d56" color-link statusline "#80DEEA,#3b4d56"
@ -30,3 +31,6 @@ color-link tabbar "#80DEEA,#3b4d56"
color-link todo "bold #C792EA,#263238" color-link todo "bold #C792EA,#263238"
color-link type "#FFCB6B,#263238" color-link type "#FFCB6B,#263238"
color-link underlined "underline #EEFFFF,#263238" color-link underlined "underline #EEFFFF,#263238"
color-link match-brace "#263238,#C792EA"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View file

@ -23,3 +23,6 @@ color-link gutter-error "#CB4B16"
color-link gutter-warning "#E6DB74" color-link gutter-warning "#E6DB74"
color-link cursor-line "#323232" color-link cursor-line "#323232"
color-link color-column "#323232" color-link color-column "#323232"
color-link match-brace "#1D0000,#AE81FF"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View file

@ -29,3 +29,6 @@ color-link color-column "#323232"
color-link type.extended "default" color-link type.extended "default"
#color-link symbol.brackets "default" #color-link symbol.brackets "default"
color-link symbol.tag "#AE81FF,#282828" color-link symbol.tag "#AE81FF,#282828"
color-link match-brace "#282828,#AE81FF"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View file

@ -34,3 +34,6 @@ color-link todo "#8B98AB"
color-link type "#66D9EF" color-link type "#66D9EF"
color-link type.keyword "#C678DD" color-link type.keyword "#C678DD"
color-link underlined "#8996A8" color-link underlined "#8996A8"
color-link match-brace "#21252C,#C678DD"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View file

@ -28,6 +28,10 @@ color-link tabbar "bold #b1b1b1,#232323"
color-link cursor-line "#353535" color-link cursor-line "#353535"
color-link color-column "#353535" color-link color-column "#353535"
color-link space "underline #e6e1dc,#2b2b2b" color-link space "underline #e6e1dc,#2b2b2b"
color-link tab-error "#d75f5f"
color-link trailingws "#d75f5f"
#the Python syntax definition are wrong. This is not how you should do decorators! #the Python syntax definition are wrong. This is not how you should do decorators!
color-link brightgreen "#edb753,#2b2b2b" color-link brightgreen "#edb753,#2b2b2b"
color-link match-brace "#2b2b2b,#a5c261"

View file

@ -10,6 +10,7 @@ color-link ignore "default"
color-link error ",brightred" color-link error ",brightred"
color-link todo ",brightyellow" color-link todo ",brightyellow"
color-link hlsearch "black,yellow" color-link hlsearch "black,yellow"
color-link statusline "black,white"
color-link indent-char "black" color-link indent-char "black"
color-link line-number "yellow" color-link line-number "yellow"
color-link current-line-number "red" color-link current-line-number "red"
@ -27,3 +28,6 @@ color-link type.extended "default"
color-link symbol.brackets "default" color-link symbol.brackets "default"
#Color shebangs the comment color #Color shebangs the comment color
color-link preproc.shebang "comment" color-link preproc.shebang "comment"
color-link match-brace ",magenta"
color-link tab-error "brightred"
color-link trailingws "brightred"

View file

@ -26,3 +26,6 @@ color-link cursor-line "#003541"
color-link color-column "#003541" color-link color-column "#003541"
color-link type.extended "#839496,#002833" color-link type.extended "#839496,#002833"
color-link symbol.brackets "#839496,#002833" color-link symbol.brackets "#839496,#002833"
color-link match-brace "#002833,#268BD2"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View file

@ -25,3 +25,6 @@ color-link cursor-line "black"
color-link color-column "black" color-link color-column "black"
color-link type.extended "default" color-link type.extended "default"
color-link symbol.brackets "default" color-link symbol.brackets "default"
color-link match-brace ",blue"
color-link tab-error "brightred"
color-link trailingws "brightred"

View file

@ -24,3 +24,6 @@ color-link gutter-warning "88"
color-link cursor-line "229" color-link cursor-line "229"
#color-link color-column "196" #color-link color-column "196"
color-link current-line-number "246" color-link current-line-number "246"
color-line match-brace "230,22"
color-link tab-error "210"
color-link trailingws "210"

View file

@ -35,3 +35,6 @@ color-link todo "#8B98AB"
color-link type "#F9EE98" color-link type "#F9EE98"
color-link type.keyword "#CDA869" color-link type.keyword "#CDA869"
color-link underlined "#8996A8" color-link underlined "#8996A8"
color-link match-brace "#141414,#E0C589"
color-link tab-error "#D75F5F"
color-link trailingws "#D75F5F"

View file

@ -25,3 +25,6 @@ color-link gutter-warning "174,237"
color-link cursor-line "238" color-link cursor-line "238"
color-link color-column "238" color-link color-column "238"
color-link current-line-number "188,237" color-link current-line-number "188,237"
color-link match-brace "237,223"
color-link tab-error "167"
color-link trailingws "167"

View file

@ -8,7 +8,7 @@ This help page aims to cover two aspects of micro's syntax highlighting engine:
## Colorschemes ## Colorschemes
To change your colorscheme, press Ctrl-e in micro to bring up the command To change your colorscheme, press `Ctrl-e` in micro to bring up the command
prompt, and type: prompt, and type:
``` ```
@ -93,7 +93,7 @@ and set this variable yourself.
* `solarized-tc`: this is the solarized colorscheme for true color. * `solarized-tc`: this is the solarized colorscheme for true color.
* `atom-dark`: this colorscheme is based off of Atom's "dark" colorscheme. * `atom-dark`: this colorscheme is based off of Atom's "dark" colorscheme.
* `cmc-tc`: A true colour variant of the cmc theme. It requires true color to * `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. look its best. Use cmc-16 if your terminal doesn't support true color.
* `gruvbox-tc`: The true color version of the gruvbox colorscheme * `gruvbox-tc`: The true color version of the gruvbox colorscheme
* `material-tc`: Colorscheme based off of Google's Material Design palette * `material-tc`: Colorscheme based off of Google's Material Design palette
@ -106,7 +106,7 @@ be found
Custom colorschemes should be placed in the `~/.config/micro/colorschemes` Custom colorschemes should be placed in the `~/.config/micro/colorschemes`
directory. directory.
A number of custom directives are placed in a `.micro` file. Colorschemes are A number of custom directives are placed in a `.micro` file. Colorschemes are
typically only 18-30 lines in total. typically only 18-30 lines in total.
To create the colorscheme you need to link highlight groups with To create the colorscheme you need to link highlight groups with
@ -152,7 +152,7 @@ Then you can use the terminals 256 colors by using their numbers 1-256 (numbers
If the user's terminal supports true color, then you can also specify colors If the user's terminal supports true color, then you can also specify colors
exactly using their hex codes. If the terminal is not true color but micro is exactly using their hex codes. If the terminal is not true color but micro is
told to use a true color colorscheme it will attempt to map the colors to the told to use a true color colorscheme it will attempt to map the colors to the
available 256 colors. available 256 colors.
Generally colorschemes which require true color terminals to look good are Generally colorschemes which require true color terminals to look good are
@ -194,6 +194,10 @@ Here is a list of the colorscheme groups that you can use:
* divider (Color of the divider between vertical splits) * divider (Color of the divider between vertical splits)
* message (Color of messages in the bottom line of the screen) * message (Color of messages in the bottom line of the screen)
* error-message (Color of error messages in the bottom line of the screen) * error-message (Color of error messages in the bottom line of the screen)
* match-brace (Color of matching brackets when `matchbracestyle` is set to `highlight`)
* hlsearch (Color of highlighted search results when `hlsearch` is enabled)
* tab-error (Color of tab vs space errors when `hltaberrors` is enabled)
* trailingws (Color of trailing whitespaces when `hltrailingws` is enabled)
Colorschemes must be placed in the `~/.config/micro/colorschemes` directory to Colorschemes must be placed in the `~/.config/micro/colorschemes` directory to
be used. be used.
@ -210,9 +214,9 @@ safe and recommended to use subgroups in your custom syntax files.
For example if `constant.string` is found in your colorscheme, micro will us For example if `constant.string` is found in your colorscheme, micro will us
that for highlighting strings. If it's not found, it will use constant instead. 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 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 definitions, so if, for example `constant.bool.true` is found then micro will
use that. If `constant.bool.true` is not found but `constant.bool` is found use that. If `constant.bool.true` is not found but `constant.bool` is found
micro will use `constant.bool`. If not, it uses `constant`. micro will use `constant.bool`. If not, it uses `constant`.
Here's a list of subgroups used in micro's built-in syntax files. Here's a list of subgroups used in micro's built-in syntax files.
@ -220,10 +224,10 @@ Here's a list of subgroups used in micro's built-in syntax files.
* constant.bool * constant.bool
* constant.bool.true * constant.bool.true
* constant.bool.false * constant.bool.false
* constant.number * constant.number
* constant.specialChar * constant.specialChar
* constant.string * constant.string
* constant.string.url * constant.string.url
* identifier.class (Also used for functions) * identifier.class (Also used for functions)
* identifier.macro * identifier.macro
* identifier.var * identifier.var
@ -236,6 +240,12 @@ Here's a list of subgroups used in micro's built-in syntax files.
In the future, plugins may also be able to use color groups for styling. In the future, plugins may also be able to use color groups for styling.
---
Last but not least it's even possible to use `include` followed by the
colorscheme name as string to include a different colorscheme within a new one.
Additionally the groups can then be extended or overwritten. The `default.micro`
theme can be seen as an example, which links to the chosen default colorscheme.
## Syntax files ## Syntax files
@ -244,7 +254,7 @@ languages.
Micro's builtin syntax highlighting tries very hard to be sane, sensible and 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 provide ample coverage of the meaningful elements of a language. Micro has
syntax files built in for over 100 languages now! However, there may be syntax files built in for over 100 languages now! However, there may be
situations where you find Micro's highlighting to be insufficient or not to situations where you find Micro's highlighting to be insufficient or not to
your liking. The good news is that you can create your own syntax files, and your liking. The good news is that you can create your own syntax files, and
place them in `~/.config/micro/syntax` and Micro will use those instead. place them in `~/.config/micro/syntax` and Micro will use those instead.
@ -267,8 +277,9 @@ detect:
``` ```
Micro will match this regex against a given filename to detect the filetype. 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: In addition to the `filename` regex (or even instead of it) you can provide
a `header` regex that will check the first line of the file. For example:
``` ```
detect: detect:
@ -276,6 +287,32 @@ detect:
header: "%YAML" header: "%YAML"
``` ```
This is useful in cases when the given file name is not sufficient to determine
the filetype, e.g. with the above example, if a YAML file has no `.yaml`
extension but may contain a `%YAML` directive in its first line.
`filename` takes precedence over `header`, i.e. if there is a syntax file that
matches the file with a filetype by the `filename` and another syntax file that
matches the same file with another filetype by the `header`, the first filetype
will be used.
Finally, in addition to `filename` and/or `header` (but not instead of them)
you may also provide an optional `signature` regex which is useful for resolving
ambiguities when there are multiple syntax files matching the same file with
different filetypes. If a `signature` regex is given, micro will match a certain
amount of first lines in the file (this amount is determined by the `detectlimit`
option) against this regex, and if any of the lines match, this syntax file's
filetype will be preferred over other matching filetypes.
For example, to distinguish C++ header files from C and Objective-C header files
that have the same `.h` extension:
```
detect:
filename: "\\.c(c|pp|xx)$|\\.h(h|pp|xx)?$"
signature: "namespace|template|public|protected|private"
```
### Syntax rules ### Syntax rules
Next you must provide the syntax highlighting rules. There are two types of Next you must provide the syntax highlighting rules. There are two types of
@ -356,15 +393,28 @@ example, the following is possible for html:
- include: "css" - include: "css"
``` ```
## Syntax file headers Note that nested include (i.e. including syntax files that include other syntax
files) is not supported yet.
Syntax file headers are an optimization and it is likely you do not need to ### Default syntax highlighting
worry about them.
Syntax file headers are files that contain only the filetype and the detection If micro cannot detect the filetype of the file, it falls back to using the
regular expressions for a given syntax file. They have a `.hdr` suffix and are default syntax highlighting for it, which highlights just the bare minimum:
used by default only for the pre-installed syntax files. Header files allow email addresses, URLs etc.
micro to parse the syntax files much faster when checking the filetype of a
certain file. Custom syntax files may provide header files in Just like in other cases, you can override the default highlighting by adding
`~/.config/micro/syntax` as well but it is not necessary (only do this if you your own custom `default.yaml` file to `~/.config/micro/syntax`.
have many (100+) custom syntax files and want to improve performance).
For example, if you work with various config files that use the `#` sign to mark
the beginning of a comment, you can use the following custom `default.yaml` to
highlight those comments by default:
```
filetype: unknown
detect:
filename: ""
rules:
- comment: "(^|\\s)#.*$"
```

View file

@ -1,6 +1,6 @@
# Command bar # Command bar
The command bar is opened by pressing Ctrl-e. It is a single-line buffer, The command bar is opened by pressing `Ctrl-e`. It is a single-line buffer,
meaning that all keybindings from a normal buffer are supported (as well meaning that all keybindings from a normal buffer are supported (as well
as mouse and selection). as mouse and selection).
@ -21,32 +21,43 @@ quotes here but these are not necessary when entering the command in micro.
This command will modify `bindings.json` and overwrite any bindings to This command will modify `bindings.json` and overwrite any bindings to
`key` that already exist. `key` that already exist.
* `help 'topic'?`: opens the corresponding help topic. If no topic is provided * `help ['topic']`: opens the corresponding help topic. If no topic is provided
opens the default help screen. Help topics are stored as `.md` files in the opens the default help screen. Help topics are stored as `.md` files in the
`runtime/help` directory of the source tree, which is embedded in the final `runtime/help` directory of the source tree, which is embedded in the final
binary. binary.
* `save 'filename'?`: saves the current buffer. If the file is provided it * `save ['filename']`: saves the current buffer. If the file is provided it
will 'save as' the filename. will 'save as' the filename.
* `quit`: quits micro. * `quit`: quits micro.
* `goto 'line'`: jumps to the given line number. A negative number can be * `goto 'line[:col]'`: goes to the given absolute line (and optional column)
passed to jump inward from the end of the file; for example, -5 jumps number.
to the 5th-last line in the file. A negative number can be passed to go inward from the end of the file.
Example: -5 goes to the 5th-last line in the file.
* `replace 'search' 'value' 'flags'?`: This will replace `search` with `value`. * `jump 'line[:col]'`: goes to the given relative number from the current
line (and optional absolute column) number.
Example: -5 jumps 5 lines up in the file, while (+)3 jumps 3 lines down.
* `replace 'search' 'value' ['flags']`: This will replace `search` with `value`.
The `flags` are optional. Possible flags are: The `flags` are optional. Possible flags are:
* `-a`: Replace all occurrences at once * `-a`: Replace all occurrences at once
* `-l`: Do a literal search instead of a regex search * `-l`: Do a literal search instead of a regex search
Note that `search` must be a valid regex (unless `-l` is passed). If one Note that `search` must be a valid regex (unless `-l` is passed). If one
of the arguments does not have any spaces in it, you may omit the quotes. of the arguments does not have any spaces in it, you may omit the quotes.
In case the search is done non-literal (without `-l`), the 'value'
is interpreted as a template:
* `$3` or `${3}` substitutes the submatch of the 3rd (capturing group)
* `$foo` or `${foo}` substitutes the submatch of the (?P<foo>named group)
* You have to write `$$` to substitute a literal dollar.
* `replaceall 'search' 'value'`: this will replace all occurrences of `search` * `replaceall 'search' 'value'`: this will replace all occurrences of `search`
with `value` without user confirmation. with `value` without user confirmation.
See `replace` command for more information. See `replace` command for more information.
* `set 'option' 'value'`: sets the option to value. See the `options` help * `set 'option' 'value'`: sets the option to value. See the `options` help
topic for a list of options you can set. This will modify your topic for a list of options you can set. This will modify your
@ -57,18 +68,18 @@ quotes here but these are not necessary when entering the command in micro.
* `show 'option'`: shows the current value of the given option. * `show 'option'`: shows the current value of the given option.
* `run 'sh-command'`: runs the given shell command in the background. The * `run 'sh-command'`: runs the given shell command in the background. The
command's output will be displayed in one line when it finishes running. command's output will be displayed in one line when it finishes running.
* `vsplit 'filename'`: opens a vertical split with `filename`. If no filename * `vsplit ['filename']`: opens a vertical split with `filename`. If no filename
is provided, a vertical split is opened with an empty buffer. is provided, a vertical split is opened with an empty buffer.
* `hsplit 'filename'`: same as `vsplit` but opens a horizontal split instead * `hsplit ['filename']`: same as `vsplit` but opens a horizontal split instead
of a vertical split. of a vertical split.
* `tab 'filename'`: opens the given file in a new tab. * `tab ['filename']`: opens the given file in a new tab.
* `tabmove '[-+]?n'`: Moves the active tab to another slot. `n` is an integer. * `tabmove '[-+]n'`: Moves the active tab to another slot. `n` is an integer.
If `n` is prefixed with `-` or `+`, then it represents a relative position If `n` is prefixed with `-` or `+`, then it represents a relative position
(e.g. `tabmove +2` moves the tab to the right by `2`). If `n` has no prefix, (e.g. `tabmove +2` moves the tab to the right by `2`). If `n` has no prefix,
it represents an absolute position (e.g. `tabmove 2` moves the tab to slot `2`). it represents an absolute position (e.g. `tabmove 2` moves the tab to slot `2`).
@ -89,7 +100,7 @@ quotes here but these are not necessary when entering the command in micro.
* `plugin remove 'pl'`: remove a plugin. * `plugin remove 'pl'`: remove a plugin.
* `plugin update 'pl'`: update a plugin (if no arguments are provided * `plugin update ['pl']`: update a plugin (if no arguments are provided
updates all plugins). updates all plugins).
* `plugin search 'pl'`: search available plugins for a keyword. * `plugin search 'pl'`: search available plugins for a keyword.
@ -114,10 +125,10 @@ quotes here but these are not necessary when entering the command in micro.
the terminal and helps you see which bindings aren't possible and why. This the terminal and helps you see which bindings aren't possible and why. This
is most useful for debugging keybindings. is most useful for debugging keybindings.
* `showkey`: Show the action(s) bound to a given key. For example * `showkey 'key'`: Show the action(s) bound to a given key. For example
running `> showkey Ctrl-c` will display `Copy`. running `> showkey Ctrl-c` will display `Copy`.
* `term exec?`: Open a terminal emulator running the given executable. If no * `term ['exec']`: Open a terminal emulator running the given executable. If no
executable is given, this will open the default shell in the terminal executable is given, this will open the default shell in the terminal
emulator. emulator.

View file

@ -12,13 +12,13 @@ is limited support among terminal emulators for the terminal clipboard
(which uses the OSC 52 protocol to communicate clipboard contents). (which uses the OSC 52 protocol to communicate clipboard contents).
Here is a list of terminal emulators and their status: Here is a list of terminal emulators and their status:
* Kitty: supported, but only writing is enabled by default. To enable * `Kitty`: supported, but only writing is enabled by default. To enable
reading, add `read-primary` and `read-clipboard` to the reading, add `read-primary` and `read-clipboard` to the
`clipboard_control` option. `clipboard_control` option.
* iTerm2: only copying (writing to clipboard) is supported. Must be enabled in * `iTerm2`: only copying (writing to clipboard) is supported. Must be enabled in
`Preferences->General-> Selection->Applications in terminal may access clipboard`. `Preferences->General-> Selection->Applications in terminal may access clipboard`.
You can use Command-v to paste. You can use `Command-v` to paste.
* `st`: supported. * `st`: supported.
@ -31,10 +31,14 @@ Here is a list of terminal emulators and their status:
* `gnome-terminal`: does not support OSC 52. * `gnome-terminal`: does not support OSC 52.
* `alacritty`: supported. * `alacritty`: supported. Since 0.13.0, reading has been disabled by default.
To reenable it, set the `terminal.osc52` option to `CopyPaste`.
* `foot`: supported. * `foot`: supported.
* `wezterm`: only copying (writing to clipboard) is supported.
**Summary:** If you want copy and paste to work over SSH, then you **Summary:** If you want copy and paste to work over SSH, then you
should set `clipboard` to `terminal`, and make sure your terminal should set `clipboard` to `terminal`, and make sure your terminal
supports OSC 52. supports OSC 52.
@ -45,12 +49,12 @@ supports OSC 52.
The recommended method of pasting is the following: The recommended method of pasting is the following:
* If you are not working over SSH, use the micro keybinding (Ctrl-v * If you are not working over SSH, use the micro keybinding (`Ctrl-v`
by default) to perform pastes. If on Linux, install `xclip` or by default) to perform pastes. If on Linux, install `xclip` or
`xsel` beforehand. `xsel` beforehand.
* If you are working over SSH, use the terminal keybinding * If you are working over SSH, use the terminal keybinding
(Ctrl-Shift-v or Command-v) to perform pastes. If your terminal (`Ctrl-Shift-v` or `Command-v`) to perform pastes. If your terminal
does not support bracketed paste, when performing a paste first does not support bracketed paste, when performing a paste first
enable the `paste` option, and when finished disable the option. enable the `paste` option, and when finished disable the option.
@ -59,8 +63,8 @@ The recommended method of pasting is the following:
Micro is an application that runs within the terminal. This means Micro is an application that runs within the terminal. This means
that the terminal sends micro events, such as key events, mouse that the terminal sends micro events, such as key events, mouse
events, resize events, and paste events. Micro's default keybinding events, resize events, and paste events. Micro's default keybinding
for paste is Ctrl-v. This means that when micro receives the key for paste is `Ctrl-v`. This means that when micro receives the key
event saying Ctrl-v has been pressed from the terminal, it will event saying `Ctrl-v` has been pressed from the terminal, it will
attempt to access the system clipboard and effect a paste. The attempt to access the system clipboard and effect a paste. The
system clipboard will be accessed through `pbpaste` on MacOS system clipboard will be accessed through `pbpaste` on MacOS
(installed by default), `xclip` or `xsel` on Linux (these (installed by default), `xclip` or `xsel` on Linux (these
@ -73,8 +77,8 @@ For certain keypresses, the terminal will not send an event to
micro and will instead do something itself. In this document, micro and will instead do something itself. In this document,
such keypresses will be called "terminal keybindings." Often such keypresses will be called "terminal keybindings." Often
there will be a terminal keybinding for pasting and copying. On there will be a terminal keybinding for pasting and copying. On
MacOS these are Command-v and Command-c and on Linux Ctrl-Shift-v MacOS these are Command-v and Command-c and on Linux `Ctrl-Shift-v`
and Ctrl-Shift-c. When the terminal keybinding for paste is and `Ctrl-Shift-c`. When the terminal keybinding for paste is
executed, your terminal will access the system clipboard, and send executed, your terminal will access the system clipboard, and send
micro either a paste event or a list of key events (one key for each micro either a paste event or a list of key events (one key for each
character in the paste), depending on whether or not your terminal character in the paste), depending on whether or not your terminal
@ -86,7 +90,7 @@ sends a list of key events, this can cause issues because micro
will think you manually entered each character and may add closing will think you manually entered each character and may add closing
brackets or automatic indentation, which will mess up the pasted brackets or automatic indentation, which will mess up the pasted
text. To avoid this, you can temporarily enable the `paste` option text. To avoid this, you can temporarily enable the `paste` option
while you perform the paste. When paste option is on, micro will while you perform the paste. When paste option is on, micro will
aggregate lists of multiple key events into larger paste events. aggregate lists of multiple key events into larger paste events.
It is a good idea to disable the `paste` option during normal use It is a good idea to disable the `paste` option during normal use
as occasionally if you are typing quickly, the terminal will send as occasionally if you are typing quickly, the terminal will send
@ -97,7 +101,7 @@ entered.
When working over SSH, micro is running on the remote machine and When working over SSH, micro is running on the remote machine and
your terminal is running on your local machine. Therefore if you your terminal is running on your local machine. Therefore if you
would like to paste, using Ctrl-v (micro's keybinding) will not would like to paste, using `Ctrl-v` (micro's keybinding) will not
work because when micro attempts to access the system clipboard, work because when micro attempts to access the system clipboard,
it will access the remote machine's clipboard rather than the local it will access the remote machine's clipboard rather than the local
machine's clipboard. On the other hand, the terminal keybinding machine's clipboard. On the other hand, the terminal keybinding
@ -110,12 +114,12 @@ the network as a paste event, which is what you want.
The recommended method of copying is the following: The recommended method of copying is the following:
* If you are not working over SSH, use the micro keybinding (Ctrl-c by * If you are not working over SSH, use the micro keybinding (`Ctrl-c` by
default) to perform copies. If on Linux, install `xclip` or `xsel` default) to perform copies. If on Linux, install `xclip` or `xsel`
beforehand. beforehand.
* If you are working over SSH, use the terminal keybinding * If you are working over SSH, use the terminal keybinding
(Ctrl-Shift-c or Command-c) to perform copies. You must first disable (`Ctrl-Shift-c` or `Command-c`) to perform copies. You must first disable
the `mouse` option to perform a terminal selection, and you may wish the `mouse` option to perform a terminal selection, and you may wish
to disable line numbers and diff indicators (`ruler` and `diffgutter` to disable line numbers and diff indicators (`ruler` and `diffgutter`
options) and close other splits. This method will only be able to copy options) and close other splits. This method will only be able to copy
@ -126,14 +130,14 @@ Copying follows a similar discussion to the one above about pasting.
The primary difference is before performing a copy, the application The primary difference is before performing a copy, the application
doing the copy must be told what text needs to be copied. doing the copy must be told what text needs to be copied.
Micro has a keybinding (Ctrl-c) for copying and will access the system Micro has a keybinding (`Ctrl-c`) for copying and will access the system
clipboard to perform the copy. The text that micro will copy into is clipboard to perform the copy. The text that micro will copy into is
the text that is currently selected in micro (usually such text is the text that is currently selected in micro (usually such text is
displayed with a white background). When the `mouse` option is enabled, displayed with a white background). When the `mouse` option is enabled,
the mouse can be used to select text, as well as other keybindings, the mouse can be used to select text, as well as other keybindings,
such as ShiftLeft, etc... such as ShiftLeft, etc...
The terminal also has a keybinding (Ctrl-Shift-c or Command-c) to perform The terminal also has a keybinding (`Ctrl-Shift-c` or `Command-c`) to perform
a copy, and the text that it copies is the text selected by the terminal's a copy, and the text that it copies is the text selected by the terminal's
selection (*not* micro's selection). To select text with the terminal selection (*not* micro's selection). To select text with the terminal
selection, micro's mouse support must first be disabled by turning the selection, micro's mouse support must first be disabled by turning the

View file

@ -52,9 +52,9 @@ can change it!
| Ctrl-n | Find next instance of current search | | Ctrl-n | Find next instance of current search |
| Ctrl-p | Find previous instance of current search | | Ctrl-p | Find previous instance of current search |
Note: Ctrl-n and Ctrl-p should be used from the main buffer, not from inside Note: `Ctrl-n` and `Ctrl-p` should be used from the main buffer, not from inside
the search prompt. After Ctrl-f, press enter to complete the search and then the search prompt. After `Ctrl-f`, press enter to complete the search and then
you can use Ctrl-n and Ctrl-p to cycle through matches. you can use `Ctrl-n` and `Ctrl-p` to cycle through matches.
### File Operations ### File Operations
@ -129,7 +129,7 @@ you can use Ctrl-n and Ctrl-p to cycle through matches.
### Function keys. ### Function keys.
Warning! The function keys may not work in all terminals! Warning! The function keys may not work in all terminals!
| Key | Description of function | | Key | Description of function |
|------ |-------------------------- | |------ |-------------------------- |

View file

@ -4,9 +4,9 @@ Micro is an easy to use, intuitive, text editor that takes advantage of the
full capabilities of modern terminals. full capabilities of modern terminals.
Micro can be controlled by commands entered on the command bar, or with Micro can be controlled by commands entered on the command bar, or with
keybindings. To open the command bar, press Ctrl-e: the `>` prompt will keybindings. To open the command bar, press `Ctrl-e`: the `>` prompt will
display. From now on, when the documentation shows a command to run (such as display. From now on, when the documentation shows a command to run (such as
`> help`), press Ctrl-e and type the command followed by enter. `> help`), press `Ctrl-e` and type the command followed by enter.
For a list of the default keybindings, run `> help defaultkeys`. For a list of the default keybindings, run `> help defaultkeys`.
For more information on keybindings, see `> help keybindings`. For more information on keybindings, see `> help keybindings`.
@ -14,7 +14,7 @@ To toggle a short list of important keybindings, press Alt-g.
## Quick-start ## Quick-start
To quit, press Ctrl-q. Save by pressing Ctrl-s. Press Ctrl-e, as previously To quit, press `Ctrl-q`. Save by pressing `Ctrl-s`. Press `Ctrl-e`, as previously
mentioned, to start typing commands. To see which commands are available, at the mentioned, to start typing commands. To see which commands are available, at the
prompt, press tab, or view the help topic with `> help commands`. prompt, press tab, or view the help topic with `> help commands`.
@ -26,31 +26,31 @@ If the colorscheme doesn't look good, you can change it with
or see more information about colorschemes and syntax highlighting with `> help or see more information about colorschemes and syntax highlighting with `> help
colors`. colors`.
Press Ctrl-w to move between splits, and type `> vsplit filename` or Press `Ctrl-w` to move between splits, and type `> vsplit filename` or
`> hsplit filename` to open a new split. `> hsplit filename` to open a new split.
## Accessing more help ## Accessing more help
Micro has a built-in help system which can be accessed with the `> help` command. Micro has a built-in help system which can be accessed with the `> help` command.
To view help for the various available topics, press Ctrl-e to access command To view help for the various available topics, press `Ctrl-e` to access command
mode and type in `> help` followed by a topic. Typing just `> help` will open mode and type in `> help` followed by a topic. Typing just `> help` will open
this page. this page.
Here are the available help topics: Here are the available help topics:
* tutorial: A brief tutorial which gives an overview of all the other help * `tutorial`: A brief tutorial which gives an overview of all the other help
topics topics
* keybindings: Gives a full list of the default keybindings as well as how to * `keybindings`: Gives a full list of the default keybindings as well as how to
rebind them rebind them
* defaultkeys: Gives a more straight-forward list of the hotkey commands and * `defaultkeys`: Gives a more straight-forward list of the hotkey commands and
what they do what they do
* commands: Gives a list of all the 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 * `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`: Explains how micro's plugin system works and how to create your own
plugins plugins
* colors: Explains micro's colorscheme and syntax highlighting engine and how * `colors`: Explains micro's colorscheme and syntax highlighting engine and how
to create your own colorschemes or add new languages to the engine to create your own colorschemes or add new languages to the engine
For example, to open the help page on plugins you would run `> help plugins`. For example, to open the help page on plugins you would run `> help plugins`.

View file

@ -16,7 +16,7 @@ Micro will know what to do with it.
You can use Ctrl + arrows to move word by word (Alt + arrows for Mac). Alt + left and right You can use Ctrl + arrows to move word by word (Alt + arrows for Mac). Alt + left and right
move the cursor to the start and end of the line (Ctrl + left/right for Mac), and Ctrl + up and down move the move the cursor to the start and end of the line (Ctrl + left/right for Mac), and Ctrl + up and down move the
cursor the start and end of the buffer. cursor to the start and end of the buffer.
You can hold shift with all of these movement actions to select while moving. You can hold shift with all of these movement actions to select while moving.
@ -30,21 +30,21 @@ following in the `bindings.json` file.
```json ```json
{ {
"Ctrl-y": "Undo", "Ctrl-y": "Undo",
"Ctrl-z": "Redo" "Ctrl-z": "Redo"
} }
``` ```
**Note:** The syntax `<Modifier><key>` is equivalent to `<Modifier>-<key>`. In **Note:** The syntax `<Modifier><key>` is equivalent to `<Modifier>-<key>`. In
addition, Ctrl-Shift bindings are not supported by terminals, and are the same addition, `Ctrl-Shift` bindings are not supported by terminals, and are the same
as simply Ctrl bindings. This means that `CtrlG`, `Ctrl-G`, and `Ctrl-g` all as simply `Ctrl` bindings. This means that `CtrlG`, `Ctrl-G`, and `Ctrl-g` all
mean the same thing. However, for Alt this is not the case: `AltG` and `Alt-G` mean the same thing. However, for `Alt` this is not the case: `AltG` and `Alt-G`
mean `Alt-Shift-g`, while `Alt-g` does not require the Shift modifier. mean `Alt-Shift-g`, while `Alt-g` does not require the Shift modifier.
In addition to editing your `~/.config/micro/bindings.json`, you can run In addition to editing your `~/.config/micro/bindings.json`, you can run
`>bind <keycombo> <action>` For a list of bindable actions, see below. `>bind <keycombo> <action>` For a list of bindable actions, see below.
You can also chain commands when rebinding. For example, if you want Alt-s to You can also chain commands when rebinding. For example, if you want `Alt-s` to
save and quit you can bind it like so: save and quit you can bind it like so:
```json ```json
@ -178,12 +178,18 @@ SelectToStartOfText
SelectToStartOfTextToggle SelectToStartOfTextToggle
WordRight WordRight
WordLeft WordLeft
SubWordRight
SubWordLeft
SelectWordRight SelectWordRight
SelectWordLeft SelectWordLeft
SelectSubWordRight
SelectSubWordLeft
MoveLinesUp MoveLinesUp
MoveLinesDown MoveLinesDown
DeleteWordRight DeleteWordRight
DeleteWordLeft DeleteWordLeft
DeleteSubWordRight
DeleteSubWordLeft
SelectLine SelectLine
SelectToStartOfLine SelectToStartOfLine
SelectToEndOfLine SelectToEndOfLine
@ -200,6 +206,8 @@ Find
FindLiteral FindLiteral
FindNext FindNext
FindPrevious FindPrevious
DiffPrevious
DiffNext
Undo Undo
Redo Redo
Copy Copy
@ -407,8 +415,14 @@ mouse actions)
``` ```
MouseLeft MouseLeft
MouseLeftDrag
MouseLeftRelease
MouseMiddle MouseMiddle
MouseMiddleDrag
MouseMiddleRelease
MouseRight MouseRight
MouseRightDrag
MouseRightRelease
MouseWheelUp MouseWheelUp
MouseWheelDown MouseWheelDown
MouseWheelLeft MouseWheelLeft
@ -461,29 +475,31 @@ conventions for text editing defaults.
"Alt-{": "ParagraphPrevious", "Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext", "Alt-}": "ParagraphNext",
"Enter": "InsertNewline", "Enter": "InsertNewline",
"Ctrl-h": "Backspace", "Ctrl-h": "Backspace",
"Backspace": "Backspace", "Backspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft", "Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft", "Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab", "Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "OutdentSelection|OutdentLine", "Backtab": "OutdentSelection|OutdentLine",
"Ctrl-o": "OpenFile", "Ctrl-o": "OpenFile",
"Ctrl-s": "Save", "Ctrl-s": "Save",
"Ctrl-f": "Find", "Ctrl-f": "Find",
"Alt-F": "FindLiteral", "Alt-F": "FindLiteral",
"Ctrl-n": "FindNext", "Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious", "Ctrl-p": "FindPrevious",
"Ctrl-z": "Undo", "Alt-[": "DiffPrevious|CursorStart",
"Ctrl-y": "Redo", "Alt-]": "DiffNext|CursorEnd",
"Ctrl-c": "CopyLine|Copy", "Ctrl-z": "Undo",
"Ctrl-x": "Cut", "Ctrl-y": "Redo",
"Ctrl-k": "CutLine", "Ctrl-c": "CopyLine|Copy",
"Ctrl-d": "DuplicateLine", "Ctrl-x": "Cut",
"Ctrl-v": "Paste", "Ctrl-k": "CutLine",
"Ctrl-a": "SelectAll", "Ctrl-d": "DuplicateLine",
"Ctrl-t": "AddTab", "Ctrl-v": "Paste",
"Alt-,": "PreviousTab", "Ctrl-a": "SelectAll",
"Alt-.": "NextTab", "Ctrl-t": "AddTab",
"Alt-,": "PreviousTab",
"Alt-.": "NextTab",
"Home": "StartOfText", "Home": "StartOfText",
"End": "EndOfLine", "End": "EndOfLine",
"CtrlHome": "CursorStart", "CtrlHome": "CursorStart",
@ -492,17 +508,17 @@ conventions for text editing defaults.
"PageDown": "CursorPageDown", "PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab", "CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab", "CtrlPageDown": "NextTab",
"Ctrl-g": "ToggleHelp", "Ctrl-g": "ToggleHelp",
"Alt-g": "ToggleKeyMenu", "Alt-g": "ToggleKeyMenu",
"Ctrl-r": "ToggleRuler", "Ctrl-r": "ToggleRuler",
"Ctrl-l": "command-edit:goto ", "Ctrl-l": "command-edit:goto ",
"Delete": "Delete", "Delete": "Delete",
"Ctrl-b": "ShellMode", "Ctrl-b": "ShellMode",
"Ctrl-q": "Quit", "Ctrl-q": "Quit",
"Ctrl-e": "CommandMode", "Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit", "Ctrl-w": "NextSplit",
"Ctrl-u": "ToggleMacro", "Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro", "Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode", "Insert": "ToggleOverwriteMode",
// Emacs-style keybindings // Emacs-style keybindings
@ -520,12 +536,15 @@ conventions for text editing defaults.
"Esc": "Escape", "Esc": "Escape",
// Mouse bindings // Mouse bindings
"MouseWheelUp": "ScrollUp", "MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown", "MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress", "MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary", "MouseLeftDrag": "MouseDrag",
"Ctrl-MouseLeft": "MouseMultiCursor", "MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
// Multi-cursor bindings
"Alt-n": "SpawnMultiCursor", "Alt-n": "SpawnMultiCursor",
"AltShiftUp": "SpawnMultiCursorUp", "AltShiftUp": "SpawnMultiCursorUp",
"AltShiftDown": "SpawnMultiCursorDown", "AltShiftDown": "SpawnMultiCursorDown",
@ -629,10 +648,12 @@ are given below:
"Esc": "AbortCommand", "Esc": "AbortCommand",
// Mouse bindings // Mouse bindings
"MouseWheelUp": "HistoryUp", "MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown", "MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress", "MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary" "MouseLeftDrag": "MouseDrag",
"MouseLeftRelease": "MouseRelease",
"MouseMiddle": "PastePrimary"
} }
} }
``` ```

View file

@ -11,10 +11,10 @@ if you have set either of the above environment variables).
Here are the available options: Here are the available options:
* `autoindent`: when creating a new line, use the same indentation as the * `autoindent`: when creating a new line, use the same indentation as the
previous line. previous line.
default value: `true` default value: `true`
* `autosave`: automatically save the buffer every n seconds, where n is the * `autosave`: automatically save the buffer every n seconds, where n is the
value of the autosave option. Also when quitting on a modified buffer, micro value of the autosave option. Also when quitting on a modified buffer, micro
@ -77,32 +77,39 @@ Here are the available options:
specified column. This is useful if you want column 80 to be highlighted specified column. This is useful if you want column 80 to be highlighted
special for example. special for example.
default value: `0` default value: `0`
* `colorscheme`: loads the colorscheme stored in * `colorscheme`: loads the colorscheme stored in
$(configDir)/colorschemes/`option`.micro, This setting is `global only`. $(configDir)/colorschemes/`option`.micro, This setting is `global only`.
default value: `default` default value: `default`
Note that the default colorschemes (default, solarized, and solarized-tc) Note that the default colorschemes (default, solarized, and solarized-tc)
are not located in configDir, because they are embedded in the micro are not located in configDir, because they are embedded in the micro
binary. binary.
The colorscheme can be selected from all the files in the The colorscheme can be selected from all the files in the
~/.config/micro/colorschemes/ directory. Micro comes by default with ~/.config/micro/colorschemes/ directory. Micro comes by default with
three colorschemes: three colorschemes:
You can read more about micro's colorschemes in the `colors` help topic You can read more about micro's colorschemes in the `colors` help topic
(`help colors`). (`help colors`).
* `cursorline`: highlight the line that the cursor is on in a different color * `cursorline`: highlight the line that the cursor is on in a different color
(the color is defined by the colorscheme you are using). (the color is defined by the colorscheme you are using).
default value: `true` default value: `true`
* `detectlimit`: if this is not set to 0, it will limit the amount of first
lines in a file that are matched to determine the filetype.
A higher limit means better accuracy of guessing the filetype, but also
taking more time.
default value: `100`
* `diffgutter`: display diff indicators before lines. * `diffgutter`: display diff indicators before lines.
default value: `false` default value: `false`
* `divchars`: specifies the "divider" characters used for the dividing line * `divchars`: specifies the "divider" characters used for the dividing line
between vertical/horizontal splits. The first character is for vertical between vertical/horizontal splits. The first character is for vertical
@ -127,7 +134,13 @@ Here are the available options:
* `eofnewline`: micro will automatically add a newline to the end of the * `eofnewline`: micro will automatically add a newline to the end of the
file if one does not exist. file if one does not exist.
default value: `true` default value: `true`
* `fakecursor`: forces micro to render the cursor using terminal colors rather
than the actual terminal cursor. This is useful when the terminal's cursor is
slow or otherwise unavailable/undesirable to use.
default value: `false`
* `fastdirty`: this determines what kind of algorithm micro uses to determine * `fastdirty`: this determines what kind of algorithm micro uses to determine
if a buffer is modified or not. When `fastdirty` is on, micro just uses a if a buffer is modified or not. When `fastdirty` is on, micro just uses a
@ -138,7 +151,7 @@ Here are the available options:
intensive. This option will be automatically disabled if the file size intensive. This option will be automatically disabled if the file size
exceeds 50KB. exceeds 50KB.
default value: `false` default value: `false`
* `fileformat`: this determines what kind of line endings micro will use for * `fileformat`: this determines what kind of line endings micro will use for
the file. Unix line endings are just `\n` (linefeed) whereas dos line the file. Unix line endings are just `\n` (linefeed) whereas dos line
@ -151,12 +164,12 @@ Here are the available options:
an effect if the file is empty/newly created, because otherwise the fileformat an effect if the file is empty/newly created, because otherwise the fileformat
will be automatically detected from the existing line endings. will be automatically detected from the existing line endings.
default value: `unix` default value: `unix` on Unix systems, `dos` on Windows
* `filetype`: sets the filetype for the current buffer. Set this option to * `filetype`: sets the filetype for the current buffer. Set this option to
`off` to completely disable filetype detection. `off` to completely disable filetype detection.
default value: `unknown`. This will be automatically overridden depending default value: `unknown`. This will be automatically overridden depending
on the file you open. on the file you open.
* `hlsearch`: highlight all instances of the searched text after a successful * `hlsearch`: highlight all instances of the searched text after a successful
@ -166,49 +179,70 @@ Here are the available options:
change the `hlsearch` setting. As long as `hlsearch` is set to true, the next change the `hlsearch` setting. As long as `hlsearch` is set to true, the next
search will have the highlighting turned on again. search will have the highlighting turned on again.
default value: `false` default value: `false`
* `hltaberrors`: highlight tabs when spaces are expected, and spaces when tabs
are expected. More precisely: if `tabstospaces` option is on, highlight
all tab characters; if `tabstospaces` is off, highlight space characters
in the initial indent part of the line.
default value: `false`
* `hltrailingws`: highlight trailing whitespaces at ends of lines. Note that
it doesn't highlight newly added trailing whitespaces that naturally occur
while typing text. It highlights only nasty forgotten trailing whitespaces.
default value: `false`
* `incsearch`: enable incremental search in "Find" prompt (matching as you type). * `incsearch`: enable incremental search in "Find" prompt (matching as you type).
default value: `true` default value: `true`
* `ignorecase`: perform case-insensitive searches. * `ignorecase`: perform case-insensitive searches.
default value: `true` default value: `true`
* `indentchar`: sets the indentation character. This will not be inserted into * `indentchar`: sets the indentation character. This will not be inserted into
files; it is only a visual indicator that whitespace is present. If set to a files; it is only a visual indicator that whitespace is present. If set to a
printing character, it functions as a subset of the "show invisibles" printing character, it functions as a subset of the "show invisibles"
setting available in many other text editors. The color of this character is setting available in many other text editors. The color of this character is
determined by the `indent-char` field in the current theme rather than the determined by the `indent-char` field in the current theme rather than the
default text color. default text color.
default value: ` ` (space) default value: ` ` (space)
* `infobar`: enables the line at the bottom of the editor where messages are * `infobar`: enables the line at the bottom of the editor where messages are
printed. This option is `global only`. printed. This option is `global only`.
default value: `true` default value: `true`
* `keepautoindent`: when using autoindent, whitespace is added for you. This * `keepautoindent`: when using autoindent, whitespace is added for you. This
option determines if when you move to the next line without any insertions option determines if when you move to the next line without any insertions
the whitespace that was added should be deleted to remove trailing the whitespace that was added should be deleted to remove trailing
whitespace. By default, the autoindent whitespace is deleted if the line whitespace. By default, the autoindent whitespace is deleted if the line
was left empty. was left empty.
default value: `false` default value: `false`
* `keymenu`: display the nano-style key menu at the bottom of the screen. Note * `keymenu`: display the nano-style key menu at the bottom of the screen. Note
that ToggleKeyMenu is bound to `Alt-g` by default and this is displayed in that ToggleKeyMenu is bound to `Alt-g` by default and this is displayed in
the statusline. To disable this, simply by `Alt-g` to `UnbindKey`. the statusline. To disable the key binding, bind `Alt-g` to `None`.
default value: `false` default value: `false`
* `matchbrace`: underline matching braces for '()', '{}', '[]' when the cursor * `matchbrace`: show matching braces for '()', '{}', '[]' when the cursor
is on a brace character. is on a brace character or next to it.
default value: `true` default value: `true`
* `matchbracestyle`: whether to underline or highlight matching braces when
`matchbrace` is enabled. The color of highlight is determined by the `match-brace`
field in the current theme. Possible values:
* `underline`: underline matching braces.
* `highlight`: use `match-brace` style from the current theme.
default value: `underline`
* `mkparents`: if a file is opened on a path that does not exist, the file * `mkparents`: if a file is opened on a path that does not exist, the file
cannot be saved because the parent directories don't exist. This option lets cannot be saved because the parent directories don't exist. This option lets
micro automatically create the parent directories in such a situation. micro automatically create the parent directories in such a situation.
@ -221,7 +255,7 @@ Here are the available options:
example, because the terminal has access to the local clipboard and micro example, because the terminal has access to the local clipboard and micro
does not). does not).
default value: `true` default value: `true`
* `multiopen`: specifies how to layout multiple files opened at startup. * `multiopen`: specifies how to layout multiple files opened at startup.
Most useful as a command-line option, like `-multiopen vsplit`. Possible Most useful as a command-line option, like `-multiopen vsplit`. Possible
@ -230,11 +264,11 @@ Here are the available options:
* `vsplit`: open files side-by-side. * `vsplit`: open files side-by-side.
* `hsplit`: open files stacked top to bottom. * `hsplit`: open files stacked top to bottom.
default value: `tab` default value: `tab`
* `paste`: treat characters sent from the terminal in a single chunk as a paste * `paste`: treat characters sent from the terminal in a single chunk as a paste
event rather than a series of manual key presses. If you are pasting using event rather than a series of manual key presses. If you are pasting using
the terminal keybinding (not Ctrl-v, which is micro's default paste the terminal keybinding (not `Ctrl-v`, which is micro's default paste
keybinding) then it is a good idea to enable this option during the paste keybinding) then it is a good idea to enable this option during the paste
and disable once the paste is over. See `> help copypaste` for details about and disable once the paste is over. See `> help copypaste` for details about
copying and pasting in a terminal environment. copying and pasting in a terminal environment.
@ -275,26 +309,34 @@ Here are the available options:
default value: `false` default value: `false`
* `rmtrailingws`: micro will automatically trim trailing whitespaces at ends of * `reload`: controls the reload behavior of the current buffer in case the file
lines. Note: This setting overrides `keepautoindent` has changed. The available options are `prompt`, `auto` & `disabled`.
default value: `false` default value: `prompt`
* `rmtrailingws`: micro will automatically trim trailing whitespaces at ends of
lines.
Note: This setting overrides `keepautoindent` and isn't used at timed `autosave`
or forced `autosave` in case the buffer didn't change. A manual save will
involve the action regardless if the buffer has been changed or not.
default value: `false`
* `ruler`: display line numbers. * `ruler`: display line numbers.
default value: `true` default value: `true`
* `relativeruler`: make line numbers display relatively. If set to true, all * `relativeruler`: make line numbers display relatively. If set to true, all
lines except for the line that the cursor is located will display the distance lines except for the line that the cursor is located will display the distance
from the cursor's line. from the cursor's line.
default value: `false` default value: `false`
* `savecursor`: remember where the cursor was last time the file was opened and * `savecursor`: remember where the cursor was last time the file was opened and
put it there when you open the file again. Information is saved to put it there when you open the file again. Information is saved to
`~/.config/micro/buffers/` `~/.config/micro/buffers/`
default value: `false` default value: `false`
* `savehistory`: remember command history between closing and re-opening * `savehistory`: remember command history between closing and re-opening
micro. Information is saved to `~/.config/micro/buffers/history`. micro. Information is saved to `~/.config/micro/buffers/history`.
@ -305,40 +347,44 @@ Here are the available options:
so if you close and reopen a file, you can keep undoing. Information is so if you close and reopen a file, you can keep undoing. Information is
saved to `~/.config/micro/buffers/`. saved to `~/.config/micro/buffers/`.
default value: `false` default value: `false`
* `scrollbar`: display a scroll bar * `scrollbar`: display a scroll bar
default value: `false` default value: `false`
* `scrollbarchar`: specifies the character used for displaying the scrollbar
default value: `|`
* `scrollmargin`: margin at which the view starts scrolling when the cursor * `scrollmargin`: margin at which the view starts scrolling when the cursor
approaches the edge of the view. approaches the edge of the view.
default value: `3` default value: `3`
* `scrollspeed`: amount of lines to scroll for one scroll event. * `scrollspeed`: amount of lines to scroll for one scroll event.
default value: `2` default value: `2`
* `smartpaste`: add leading whitespace when pasting multiple lines. * `smartpaste`: add leading whitespace when pasting multiple lines.
This will attempt to preserve the current indentation level when pasting an This will attempt to preserve the current indentation level when pasting an
unindented block. unindented block.
default value: `true` default value: `true`
* `softwrap`: wrap lines that are too long to fit on the screen. * `softwrap`: wrap lines that are too long to fit on the screen.
default value: `false` default value: `false`
* `splitbottom`: when a horizontal split is created, create it below the * `splitbottom`: when a horizontal split is created, create it below the
current split. current split.
default value: `true` default value: `true`
* `splitright`: when a vertical split is created, create it to the right of the * `splitright`: when a vertical split is created, create it to the right of the
current split. current split.
default value: `true` default value: `true`
* `statusformatl`: format string definition for the left-justified part of the * `statusformatl`: format string definition for the left-justified part of the
statusline. Special directives should be placed inside `$()`. Special statusline. Special directives should be placed inside `$()`. Special
@ -357,36 +403,36 @@ Here are the available options:
* `statusline`: display the status line at the bottom of the screen. * `statusline`: display the status line at the bottom of the screen.
default value: `true` default value: `true`
* `sucmd`: specifies the super user command. On most systems this is "sudo" but * `sucmd`: specifies the super user command. On most systems this is "sudo" but
on BSD it can be "doas." This option can be customized and is only used when on BSD it can be "doas." This option can be customized and is only used when
saving with su. saving with su.
default value: `sudo` default value: `sudo`
* `syntax`: enables syntax highlighting. * `syntax`: enables syntax highlighting.
default value: `true` default value: `true`
* `tabmovement`: navigate spaces at the beginning of lines as if they are tabs * `tabmovement`: navigate spaces at the beginning of lines as if they are tabs
(e.g. move over 4 spaces at once). This option only does anything if (e.g. move over 4 spaces at once). This option only does anything if
`tabstospaces` is on. `tabstospaces` is on.
default value: `false` default value: `false`
* `tabhighlight`: inverts the tab characters' (filename, save indicator, etc) * `tabhighlight`: inverts the tab characters' (filename, save indicator, etc)
colors with respect to the tab bar. colors with respect to the tab bar.
default value: false default value: false
* `tabreverse`: reverses the tab bar colors when active. * `tabreverse`: reverses the tab bar colors when active.
default value: true default value: true
* `tabsize`: the size in spaces that a tab character should be displayed with. * `tabsize`: the size in spaces that a tab character should be displayed with.
default value: `4` default value: `4`
* `tabstospaces`: use spaces instead of tabs. Note: This option will be * `tabstospaces`: use spaces instead of tabs. Note: This option will be
overridden by [the `ftoptions` plugin](https://github.com/zyedidia/micro/blob/master/runtime/plugins/ftoptions/ftoptions.lua) overridden by [the `ftoptions` plugin](https://github.com/zyedidia/micro/blob/master/runtime/plugins/ftoptions/ftoptions.lua)
@ -394,25 +440,25 @@ Here are the available options:
your config. See [issue #2213](https://github.com/zyedidia/micro/issues/2213) your config. See [issue #2213](https://github.com/zyedidia/micro/issues/2213)
for more details. for more details.
default value: `false` default value: `false`
* `useprimary` (only useful on unix): defines whether or not micro will use the * `useprimary` (only useful on unix): defines whether or not micro will use the
primary clipboard to copy selections in the background. This does not affect primary clipboard to copy selections in the background. This does not affect
the normal clipboard using Ctrl-c and Ctrl-v. the normal clipboard using `Ctrl-c` and `Ctrl-v`.
default value: `true` default value: `true`
* `wordwrap`: wrap long lines by words, i.e. break at spaces. This option * `wordwrap`: wrap long lines by words, i.e. break at spaces. This option
only does anything if `softwrap` is on. only does anything if `softwrap` is on.
default value: `false` default value: `false`
* `xterm`: micro will assume that the terminal it is running in conforms to * `xterm`: micro will assume that the terminal it is running in conforms to
`xterm-256color` regardless of what the `$TERM` variable actually contains. `xterm-256color` regardless of what the `$TERM` variable actually contains.
Enabling this option may cause unwanted effects if your terminal in fact Enabling this option may cause unwanted effects if your terminal in fact
does not conform to the `xterm-256color` standard. does not conform to the `xterm-256color` standard.
Default value: `false` default value: `false`
--- ---
@ -435,7 +481,7 @@ or disable them:
recent Git commit rather than the diff since opening the file. recent Git commit rather than the diff since opening the file.
Any option you set in the editor will be saved to the file Any option you set in the editor will be saved to the file
~/.config/micro/settings.json so, in effect, your configuration file will be ~/.config/micro/settings.json so, in effect, your configuration file will be
created for you. If you'd like to take your configuration with you to another created for you. If you'd like to take your configuration with you to another
machine, simply copy the settings.json to the other machine. machine, simply copy the settings.json to the other machine.
@ -480,6 +526,7 @@ so that you can see what the formatting should look like.
"linter": true, "linter": true,
"literate": true, "literate": true,
"matchbrace": true, "matchbrace": true,
"matchbracestyle": "underline",
"mkparents": false, "mkparents": false,
"mouse": true, "mouse": true,
"parsecursor": false, "parsecursor": false,
@ -536,14 +583,14 @@ all files except Go files, and `tabsize` 4 for all files except Ruby files:
```json ```json
{ {
"ft:go": { "ft:go": {
"tabstospaces": false "tabstospaces": false
}, },
"ft:ruby": { "ft:ruby": {
"tabsize": 2 "tabsize": 2
}, },
"tabstospaces": true, "tabstospaces": true,
"tabsize": 4 "tabsize": 4
} }
``` ```
@ -551,13 +598,13 @@ Or similarly you can match with globs:
```json ```json
{ {
"*.go": { "*.go": {
"tabstospaces": false "tabstospaces": false
}, },
"*.rb": { "*.rb": {
"tabsize": 2 "tabsize": 2
}, },
"tabstospaces": true, "tabstospaces": true,
"tabsize": 4 "tabsize": 4
} }
``` ```

View file

@ -5,7 +5,8 @@ folders containing Lua files and possibly other source files placed
in `~/.config/micro/plug`. The plugin directory (within `plug`) should in `~/.config/micro/plug`. The plugin directory (within `plug`) should
contain at least one Lua file and a `repo.json` file. The `repo.json` file contain at least one Lua file and a `repo.json` file. The `repo.json` file
provides additional information such as the name of the plugin, the provides additional information such as the name of the plugin, the
plugin's website, dependencies, etc... [Here is an example `repo.json` file](https://github.com/micro-editor/updated-plugins/blob/master/go-plugin/repo.json) plugin's website, dependencies, etc...
[Here is an example `repo.json` file](https://github.com/micro-editor/updated-plugins/blob/master/go-plugin/repo.json)
from the go plugin, which has the following file structure: from the go plugin, which has the following file structure:
``` ```
@ -56,6 +57,8 @@ which micro defines:
* `onBufPaneOpen(bufpane)`: runs when a bufpane is opened. The input * `onBufPaneOpen(bufpane)`: runs when a bufpane is opened. The input
contains the bufpane object. contains the bufpane object.
* `onSetActive(bufpane)`: runs when changing the currently active bufpane.
* `onAction(bufpane)`: runs when `Action` is triggered by the user, where * `onAction(bufpane)`: runs when `Action` is triggered by the user, where
`Action` is a bindable action (see `> help keybindings`). A bufpane `Action` is a bindable action (see `> help keybindings`). A bufpane
is passed as input and the function should return a boolean defining is passed as input and the function should return a boolean defining
@ -65,6 +68,14 @@ which micro defines:
by the user. Returns a boolean which defines whether the action should by the user. Returns a boolean which defines whether the action should
be canceled. be canceled.
* `onRune(bufpane, rune)`: runs when the composed rune has been inserted
* `preRune(bufpane, rune)`: runs before the composed rune will be inserted
* `onAnyEvent()`: runs when literally anything happens. It is useful for
detecting various changes of micro's state that cannot be detected
using other callbacks.
For example a function which is run every time the user saves the buffer For example a function which is run every time the user saves the buffer
would be: would be:
@ -76,7 +87,7 @@ end
``` ```
The `bp` variable is a reference to the bufpane the action is being executed The `bp` variable is a reference to the bufpane the action is being executed
within. This is almost always the current bufpane. within. This is almost always the current bufpane.
All available actions are listed in the keybindings section of the help. All available actions are listed in the keybindings section of the help.
@ -115,99 +126,104 @@ The packages and functions are listed below (in Go type signatures):
current pane is not a BufPane. current pane is not a BufPane.
- `CurTab() *Tab`: returns the current tab. - `CurTab() *Tab`: returns the current tab.
- `After(t time.Duration, f func())`: run function `f` in the background
after time `t` elapses. See https://pkg.go.dev/time#Duration for the
usage of `time.Duration`.
* `micro/config` * `micro/config`
- `MakeCommand(name string, action func(bp *BufPane, args[]string), - `MakeCommand(name string, action func(bp *BufPane, args[]string),
completer buffer.Completer)`: completer buffer.Completer)`:
create a command with the given name, and lua callback function when create a command with the given name, and lua callback function when
the command is run. A completer may also be given to specify how the command is run. A completer may also be given to specify how
autocompletion should work with the custom command. autocompletion should work with the custom command.
- `FileComplete`: autocomplete using files in the current directory - `FileComplete`: autocomplete using files in the current directory
- `HelpComplete`: autocomplete using names of help documents - `HelpComplete`: autocomplete using names of help documents
- `OptionComplete`: autocomplete using names of options - `OptionComplete`: autocomplete using names of options
- `OptionValueComplete`: autocomplete using names of options, and valid - `OptionValueComplete`: autocomplete using names of options, and valid
values afterwards values afterwards
- `NoComplete`: no autocompletion suggestions - `NoComplete`: no autocompletion suggestions
- `TryBindKey(k, v string, overwrite bool) (bool, error)`: bind the key - `TryBindKey(k, v string, overwrite bool) (bool, error)`: bind the key
`k` to the string `v` in the `bindings.json` file. If `overwrite` is `k` to the string `v` in the `bindings.json` file. If `overwrite` is
true, this will overwrite any existing binding to key `k`. Returns true true, this will overwrite any existing binding to key `k`. Returns true
if the binding was made, and a possible error (for example writing to if the binding was made, and a possible error (for example writing to
`bindings.json` can cause an error). `bindings.json` can cause an error).
- `Reload()`: reload configuration files. - `Reload()`: reload configuration files.
- `AddRuntimeFileFromMemory(filetype RTFiletype, filename, data string)`: - `AddRuntimeFileFromMemory(filetype RTFiletype, filename, data string)`:
add a runtime file to the `filetype` runtime filetype, with name add a runtime file to the `filetype` runtime filetype, with name
`filename` and data `data`. `filename` and data `data`.
- `AddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, - `AddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype,
directory, pattern string)`: directory, pattern string)`:
add runtime files for the given plugin with the given RTFiletype from add runtime files for the given plugin with the given RTFiletype from
a directory within the plugin root. Only adds files that match the a directory within the plugin root. Only adds files that match the
pattern using Go's `filepath.Match` pattern using Go's `filepath.Match`
- `AddRuntimeFile(plugin string, filetype RTFiletype, filepath string)`: - `AddRuntimeFile(plugin string, filetype RTFiletype, filepath string)`:
add a given file inside the plugin root directory as a runtime file add a given file inside the plugin root directory as a runtime file
to the given RTFiletype category. to the given RTFiletype category.
- `ListRuntimeFiles(fileType RTFiletype) []string`: returns a list of - `ListRuntimeFiles(fileType RTFiletype) []string`: returns a list of
names of runtime files of the given type. names of runtime files of the given type.
- `ReadRuntimeFile(fileType RTFiletype, name string) string`: returns the - `ReadRuntimeFile(fileType RTFiletype, name string) string`: returns the
contents of a given runtime file. contents of a given runtime file.
- `NewRTFiletype() int`: creates a new RTFiletype, and returns its value. - `NewRTFiletype() int`: creates a new RTFiletype, and returns its value.
- `RTColorscheme`: runtime files for colorschemes. - `RTColorscheme`: runtime files for colorschemes.
- `RTSyntax`: runtime files for syntax files. - `RTSyntax`: runtime files for syntax files.
- `RTHelp`: runtime files for help documents. - `RTHelp`: runtime files for help documents.
- `RTPlugin`: runtime files for plugin source code. - `RTPlugin`: runtime files for plugin source code.
- `RegisterCommonOption(pl string, name string, defaultvalue interface{})`: - `RegisterCommonOption(pl string, name string, defaultvalue interface{})`:
registers a new option with for the given plugin. The name of the registers a new option with for the given plugin. The name of the
option will be `pl.name`, and will have the given default value. Since option will be `pl.name`, and will have the given default value. Since
this registers a common option, the option will be modifiable on a this registers a common option, the option will be modifiable on a
per-buffer basis, while also having a global value (in the per-buffer basis, while also having a global value (in the
GlobalSettings map). GlobalSettings map).
- `RegisterGlobalOption(pl string, name string, defaultvalue interface{})`: - `RegisterGlobalOption(pl string, name string, defaultvalue interface{})`:
same as `RegisterCommonOption` but the option cannot be modified same as `RegisterCommonOption` but the option cannot be modified
locally to each buffer. locally to each buffer.
- `GetGlobalOption(name string) interface{}`: returns the value of a - `GetGlobalOption(name string) interface{}`: returns the value of a
given plugin in the `GlobalSettings` map. given plugin in the `GlobalSettings` map.
- `SetGlobalOption(option, value string) error`: sets an option to a - `SetGlobalOption(option, value string) error`: sets an option to a
given value. Same as using the `> set` command. This will parse the given value. Same as using the `> set` command. This will parse the
value to the actual value type. value to the actual value type.
- `SetGlobalOptionNative(option string, value interface{}) error`: sets - `SetGlobalOptionNative(option string, value interface{}) error`: sets
an option to a given value, where the type of value is the actual an option to a given value, where the type of value is the actual
type of the value internally. type of the value internally.
* `micro/shell` * `micro/shell`
- `ExecCommand(name string, arg ...string) (string, error)`: runs an - `ExecCommand(name string, arg ...string) (string, error)`: runs an
executable with the given arguments, and pipes the output (stderr executable with the given arguments, and pipes the output (stderr
and stdout) of the executable to an internal buffer, which is and stdout) of the executable to an internal buffer, which is
returned as a string, along with a possible error. returned as a string, along with a possible error.
- `RunCommand(input string) (string, error)`: same as `ExecCommand`, - `RunCommand(input string) (string, error)`: same as `ExecCommand`,
except this uses micro's argument parser to parse the arguments from except this uses micro's argument parser to parse the arguments from
the input. For example `cat 'hello world.txt' file.txt`, will pass the input. For example `cat 'hello world.txt' file.txt`, will pass
two arguments in the `ExecCommand` argument list (quoting arguments two arguments in the `ExecCommand` argument list (quoting arguments
will preserve spaces). will preserve spaces).
- `RunBackgroundShell(input string) (func() string, error)`: returns a - `RunBackgroundShell(input string) (func() string, error)`: returns a
function that will run the given shell command and return its output. function that will run the given shell command and return its output.
- `RunInteractiveShell(input string, wait bool, getOutput bool) - `RunInteractiveShell(input string, wait bool, getOutput bool)
(string, error)`: (string, error)`:
temporarily closes micro and runs the given command in the terminal. temporarily closes micro and runs the given command in the terminal.
If `wait` is true, micro will wait for the user to press enter before If `wait` is true, micro will wait for the user to press enter before
returning to text editing. If `getOutput` is true, micro redirect returning to text editing. If `getOutput` is true, micro redirect
stdout from the command to the returned string. stdout from the command to the returned string.
- `JobStart(cmd string, onStdout, onStderr, - `JobStart(cmd string, onStdout, onStderr,
onExit func(string, []interface{}), userargs ...interface{}) onExit func(string, []interface{}), userargs ...interface{})
*exec.Cmd`: *exec.Cmd`:
Starts a background job by running the shell on the given command Starts a background job by running the shell on the given command
@ -216,16 +232,16 @@ The packages and functions are listed below (in Go type signatures):
be passed to the callbacks, along with the output as the first be passed to the callbacks, along with the output as the first
argument of the callback. argument of the callback.
- `JobSpawn(cmd string, cmdArgs []string, onStdout, onStderr, - `JobSpawn(cmd string, cmdArgs []string, onStdout, onStderr,
onExit func(string, []interface{}), userargs ...interface{}) onExit func(string, []interface{}), userargs ...interface{})
*exec.Cmd`: *exec.Cmd`:
same as `JobStart`, except doesn't run the command through the shell same as `JobStart`, except doesn't run the command through the shell
and instead takes as inputs the list of arguments. and instead takes as inputs the list of arguments.
- `JobStop(cmd *exec.Cmd)`: kills a job. - `JobStop(cmd *exec.Cmd)`: kills a job.
- `JobSend(cmd *exec.Cmd, data string)`: sends some data to a job's stdin. - `JobSend(cmd *exec.Cmd, data string)`: sends some data to a job's stdin.
- `RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, - `RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool,
callback func(out string, userargs []interface{}), callback func(out string, userargs []interface{}),
userargs []interface{}) error`: userargs []interface{}) error`:
starts a terminal emulator from a given BufPane with the input command. starts a terminal emulator from a given BufPane with the input command.
@ -236,7 +252,7 @@ The packages and functions are listed below (in Go type signatures):
optional user arguments. This function returns an error on systems optional user arguments. This function returns an error on systems
where the terminal emulator is not supported. where the terminal emulator is not supported.
- `TermEmuSupported`: true on systems where the terminal emulator is - `TermEmuSupported`: true on systems where the terminal emulator is
supported and false otherwise. Supported systems: supported and false otherwise. Supported systems:
* Linux * Linux
* MacOS * MacOS
@ -361,6 +377,7 @@ strings
regexp regexp
errors errors
time time
unicode/utf8
archive/zip archive/zip
net/http net/http
``` ```

View file

@ -50,11 +50,11 @@ function preInsertNewline(bp)
for i = 1, #autoNewlinePairs do for i = 1, #autoNewlinePairs do
if curRune == charAt(autoNewlinePairs[i], 1) then if curRune == charAt(autoNewlinePairs[i], 1) then
if nextRune == charAt(autoNewlinePairs[i], 2) then if nextRune == charAt(autoNewlinePairs[i], 2) then
bp:InsertNewline()
bp:InsertTab()
bp.Buf:Insert(-bp.Cursor.Loc, "\n" .. ws) bp.Buf:Insert(-bp.Cursor.Loc, "\n" .. ws)
bp:StartOfLine() bp:StartOfLine()
bp:CursorLeft() bp:CursorLeft()
bp:InsertNewline()
bp:InsertTab()
return false return false
end end
end end

View file

@ -7,7 +7,7 @@ local buffer = import("micro/buffer")
local ft = {} local ft = {}
ft["apacheconf"] = "# %s" ft["apacheconf"] = "# %s"
ft["bat"] = ":: %s" ft["batch"] = ":: %s"
ft["c"] = "// %s" ft["c"] = "// %s"
ft["c++"] = "// %s" ft["c++"] = "// %s"
ft["cmake"] = "# %s" ft["cmake"] = "# %s"
@ -63,7 +63,7 @@ ft["zsh"] = "# %s"
local last_ft local last_ft
function updateCommentType(buf) function updateCommentType(buf)
if buf.Settings["commenttype"] == nil or last_ft ~= buf.Settings["filetype"] then if buf.Settings["commenttype"] == nil or (last_ft ~= buf.Settings["filetype"] and last_ft ~= nil) then
if ft[buf.Settings["filetype"]] ~= nil then if ft[buf.Settings["filetype"]] ~= nil then
buf.Settings["commenttype"] = ft[buf.Settings["filetype"]] buf.Settings["commenttype"] = ft[buf.Settings["filetype"]]
else else

View file

@ -8,7 +8,7 @@ file:
```json ```json
{ {
"Alt-g": "comment.comment" "Alt-g": "lua:comment.comment"
} }
``` ```

View file

@ -22,7 +22,7 @@ func AssetDir(name string) ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
names := make([]string, len(entries), len(entries)) names := make([]string, len(entries))
for i, entry := range entries { for i, entry := range entries {
names[i] = entry.Name() names[i] = entry.Name()
} }

16
runtime/runtime_test.go Normal file
View file

@ -0,0 +1,16 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAssetDir(t *testing.T) {
t.Parallel()
// Test AssetDir
entries, err := AssetDir("syntax")
assert.NoError(t, err)
assert.Contains(t, entries, "go.yaml")
assert.True(t, len(entries) > 5)
}

View file

@ -5,7 +5,6 @@ filetype: powershell
detect: detect:
filename: "\\.ps(1|m1|d1)$" filename: "\\.ps(1|m1|d1)$"
#header: ""
rules: rules:
# - comment.block: # Block Comment # - comment.block: # Block Comment

View file

@ -2,7 +2,8 @@
Here are micro's syntax files. Here are micro's syntax files.
Each yaml file specifies how to detect the filetype based on file extension or headers (first line of the file). Each yaml file specifies how to detect the filetype based on file extension or header (first line of the line).
In addition, a signature can be provided to help resolving ambiguities when multiple matching filetypes are detected.
Then there are patterns and regions linked to highlight groups which tell micro how to highlight that filetype. Then there are patterns and regions linked to highlight groups which tell micro how to highlight that filetype.
Making your own syntax files is very simple. I recommend you check the file after you are finished with the Making your own syntax files is very simple. I recommend you check the file after you are finished with the
@ -21,7 +22,7 @@ syntax files that you would like to convert to the new filetype, you can use the
$ go run syntax_converter.go c.micro > c.yaml $ go run syntax_converter.go c.micro > c.yaml
``` ```
Most the the syntax files here have been converted using that tool. Most of the syntax files here have been converted using that tool.
Note that the tool isn't perfect and though it is unlikely, you may run into some small issues that you will have to fix manually Note that the tool isn't perfect and though it is unlikely, you may run into some small issues that you will have to fix manually
(about 4 files from this directory had issues after being converted). (about 4 files from this directory had issues after being converted).

View file

@ -3,7 +3,7 @@ filetype: ada
detect: detect:
filename: "(\\.ads$|\\.adb$|\\.ada$)" filename: "(\\.ads$|\\.adb$|\\.ada$)"
rules: rules:
# Operators # Operators
- symbol.operator: ([.:;,+*|=!?\\%]|<|>|/|-|&) - symbol.operator: ([.:;,+*|=!?\\%]|<|>|/|-|&)
- symbol.brackets: "[(){}]|\\[|\\]" - symbol.brackets: "[(){}]|\\[|\\]"
@ -18,11 +18,11 @@ rules:
# Constant # Constant
- constant.bool: \b(TRUE|FALSE) - constant.bool: \b(TRUE|FALSE)
- constant.number: ([0-9]+) - constant.number: ([0-9]+)
# Storage Types # Storage Types
- type.storage: \b(INTEGER|NATURAL|POSITIVE|FLOAT|CHARACTER|STRING)\b - type.storage: \b(INTEGER|NATURAL|POSITIVE|FLOAT|CHARACTER|STRING)\b
- type.storage: \b(LONG_INTEGER|SHORT_INTEGER|LONG_FLOAT|SHORT_FLOAT)\b - type.storage: \b(LONG_INTEGER|SHORT_INTEGER|LONG_FLOAT|SHORT_FLOAT)\b
#Character #Character
- constant.string.char: \'.\' - constant.string.char: \'.\'
@ -36,9 +36,8 @@ rules:
- constant.interpolation: \\\([[:graph:]]*\) - constant.interpolation: \\\([[:graph:]]*\)
- constant.unicode: \\u\{[[:xdigit:]]+} - constant.unicode: \\u\{[[:xdigit:]]+}
# Line Comment # Line Comment
- comment.line: "--.*" - comment.line: "--.*"
# Todo # Todo
- todo: "(TODO|XXX|FIXME):?" - todo: "(TODO|XXX|FIXME):?"

View file

@ -4,9 +4,9 @@ detect:
filename: "\\.?ino$" filename: "\\.?ino$"
rules: rules:
- identifier: "\\b[A-Z_][0-9A-Z_]+\\b" - identifier: "\\b[A-Z_][0-9A-Z_]+\\b"
## ## Sized (u)int types
- type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\\b" - type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\\b"
## Constants ## Constants
@ -33,7 +33,7 @@ rules:
- type: "\\b(boolean|byte|char|float|int|long|word)\\b" - type: "\\b(boolean|byte|char|float|int|long|word)\\b"
## Control Structions ## Control Structions
- statement: "\\b(case|class|default|do|double|else|false|for|if|new|null|private|protected|public|short|signed|static|String|switch|this|throw|try|true|unsigned|void|while)\\b" - statement: "\\b(case|class|default|do|double|else|false|for|if|new|null|private|protected|public|short|signed|static|String|switch|this|throw|try|true|unsigned|void|while)\\b"
- statement: "\\b(goto|continue|break|return)\\b" - statement: "\\b(goto|continue|break|return)\\b"
## Math ## Math
@ -66,7 +66,7 @@ rules:
## Structure ## Structure
- identifier: "\\b(setup|loop)\\b" - identifier: "\\b(setup|loop)\\b"
## ##
- statement: "^[[:space:]]*#[[:space:]]*(define|include(_next)?|(un|ifn?)def|endif|el(if|se)|if|warning|error|pragma)" - statement: "^[[:space:]]*#[[:space:]]*(define|include(_next)?|(un|ifn?)def|endif|el(if|se)|if|warning|error|pragma)"
## GCC builtins ## GCC builtins

View file

@ -28,7 +28,7 @@ rules:
# Paragraph Title # Paragraph Title
- statement: "^\\..*$" - statement: "^\\..*$"
# source # source
- identifier: "^\\[(source,.+|NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\]" - identifier: "^\\[(source,.+|NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\]"
# Other markup # Other markup

View file

@ -1,8 +1,7 @@
filetype: batch filetype: batch
detect: detect:
filename: "(\\.bat$)" filename: "(\\.bat$|\\.cmd$)"
# header: ""
rules: rules:
# Numbers # Numbers

View file

@ -5,19 +5,22 @@ detect:
rules: rules:
- identifier: "\\b[A-Z_][0-9A-Z_]+\\b" - identifier: "\\b[A-Z_][0-9A-Z_]+\\b"
- type: "\\b(auto|float|double|char|int|short|long|sizeof|enum|void|static|const|struct|union|typedef|extern|(un)?signed|inline)\\b" - type: "\\b(_Atomic|_BitInt|float|double|_Decimal32|_Decimal64|_Decimal128|_Complex|complex|_Imaginary|imaginary|_Bool|bool|char|int|short|long|enum|void|struct|union|typedef|typeof|typeof_unqual|(un)?signed|inline|_Noreturn)\\b"
- type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\\b" - type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr))|char(8|16|32)|wchar)_t\\b"
# GCC float/decimal/fixed types
- type: "\\b(_Float16|__fp16|_Float32|_Float32x|_Float64|_Float64x|__float80|_Float128|_Float128x|__float128|__ibm128|__int128|_Fract|_Sat|_Accum)\\b"
- type: "\\b[a-z_][0-9a-z_]+(_t|_T)\\b" - type: "\\b[a-z_][0-9a-z_]+(_t|_T)\\b"
- type.extended: "\\b(bool)\\b" - statement: "\\b(auto|volatile|register|restrict|_Alignas|alignas|_Alignof|alignof|static|const|constexpr|extern|_Thread_local|thread_local)\\b"
- statement: "\\b(volatile|register|restrict)\\b" - statement: "\\b(for|if|while|do|else|case|default|switch|_Generic|_Static_assert|static_assert)\\b"
- statement: "\\b(for|if|while|do|else|case|default|switch)\\b"
- statement: "\\b(goto|continue|break|return)\\b" - statement: "\\b(goto|continue|break|return)\\b"
- preproc: "^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error)" - statement: "\\b(asm|fortran)\\b"
- preproc: "^[[:space:]]*#[[:space:]]*(define|embed|pragma|include|(un|ifn?)def|endif|el(if|ifdef|ifndef|se)|if|line|warning|error|__has_include|__has_embed|__has_c_attribute)"
- preproc: "^[[:space:]]*_Pragma\\b"
# GCC builtins # GCC builtins
- statement: "__attribute__[[:space:]]*\\(\\([^)]*\\)\\)" - statement: "__attribute__[[:space:]]*\\(\\([^)]*\\)\\)"
- statement: "__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__" - statement: "__(aligned|asm|builtin|extension|hidden|inline|packed|restrict|section|typeof|weak)__"
# Operator Color # Operator Color
- symbol.operator: "([.:;,+*|=!\\%]|<|>|/|-|&)" - symbol.operator: "[-+*/%=<>.:;,~&|^!?]|\\b(offsetof|sizeof)\\b"
- symbol.brackets: "[(){}]|\\[|\\]" - symbol.brackets: "[(){}]|\\[|\\]"
# Integer Constants # Integer Constants
- constant.number: "(\\b([1-9][0-9]*|0[0-7]*|0[Xx][0-9A-Fa-f]+|0[Bb][01]+)([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\b)" - constant.number: "(\\b([1-9][0-9]*|0[0-7]*|0[Xx][0-9A-Fa-f]+|0[Bb][01]+)([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\b)"
@ -25,7 +28,7 @@ rules:
- constant.number: "(\\b(([0-9]*[.][0-9]+|[0-9]+[.][0-9]*)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+)[FfLl]?\\b)" - constant.number: "(\\b(([0-9]*[.][0-9]+|[0-9]+[.][0-9]*)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+)[FfLl]?\\b)"
# Hexadecimal Floating Constants # Hexadecimal Floating Constants
- constant.number: "(\\b0[Xx]([0-9A-Za-z]*[.][0-9A-Za-z]+|[0-9A-Za-z]+[.][0-9A-Za-z]*)[Pp][+-]?[0-9]+[FfLl]?\\b)" - constant.number: "(\\b0[Xx]([0-9A-Za-z]*[.][0-9A-Za-z]+|[0-9A-Za-z]+[.][0-9A-Za-z]*)[Pp][+-]?[0-9]+[FfLl]?\\b)"
- constant.number: "NULL" - constant.bool: "(\\b(true|false|NULL|nullptr|TRUE|FALSE)\\b)"
- constant.string: - constant.string:
start: "\"" start: "\""
@ -53,3 +56,4 @@ rules:
end: "\\*/" end: "\\*/"
rules: rules:
- todo: "(TODO|XXX|FIXME):?" - todo: "(TODO|XXX|FIXME):?"

7
runtime/syntax/cake.yaml Normal file
View file

@ -0,0 +1,7 @@
filetype: cake
detect:
filename: "\\.cake$"
rules:
- include: "csharp"
- preproc: "^[[:space:]]*#(addin|break|l|load|module|r|reference|tool)"

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