Compare commits

..

379 Commits

Author SHA1 Message Date
Zachary Yedidia
3d845eefe3 Increase totalwidth as well for correct tab alignment 2023-02-13 14:28:07 -08:00
Zachary Yedidia
d8576d0fe6 Display a tilde before every uppercase letter 2023-02-13 14:24:49 -08:00
Sertonix
7ee77d56a6 Delete .gitmodules (#2743) 2023-02-06 17:00:09 -08:00
Rein F
eb6daab169 Make the commenttype option useful (#2735) 2023-02-05 18:30:12 -08:00
Sertonix
b0cd92b70f add restrict to C syntax (#2728) 2023-02-04 14:33:25 -08:00
Sertonix
a929437490 fix unsigned int not highlighted (#2725) 2023-02-01 10:27:33 -08:00
Zachary Yedidia
cb66d22b94 Update readme build badge for github actions 2023-01-30 17:42:34 -08:00
Zachary Yedidia
d1af21f626 Convert ci to github actions 2023-01-30 17:34:37 -08:00
Alan Hamlett
0500cc234d util.HttpRequest helper to make requests with headers (#2678) 2023-01-30 01:29:06 -08:00
Zachary Yedidia
f7677549ea Fix build
Fixes #2716
2023-01-29 19:23:21 -08:00
Preston Thorpe
d5caf84788 Prompt on save/ existing file (#2714)
* Update for fixing bug issue

Adds YNprompt when user tries save new file as existing file name in current directory.

https://github.com/zyedidia/micro/issues/2709

* Update actions.go

error handled. gonna have to be tested on permission errors, etc
2023-01-29 18:11:34 -08:00
Dmitry Maluka
e31f5ed26e Add -profile flag for CPU profiling (#2715)
There is already a commented-out code in main() for enabling CPU
profiling of micro, which is often useful. Adjust it to allow enabling
it just by providing -profile flag on the command line, without
modifying and recompiling the code.
2023-01-29 18:10:57 -08:00
Zachary Yedidia
3ef0267f0a Reload settings when filetype is changed
Fixes #2712
2023-01-26 18:21:35 -08:00
Zachary Yedidia
d5ff785559 Add fakecursor option
When 'fakecursor' is enabled micro will use a "fake" block to display
the cursor instead of the terminal's cursor.

Closes #2698
Closes #2703
2023-01-26 18:02:54 -08:00
Zachary Yedidia
e222ed73b8 Rename multimode to multiopen
Ref #2689
2023-01-24 17:06:52 -08:00
Andrew Geng
432fc7ed58 Add -multimode to open multiple files into split. (#2689)
Adds config option `multimode`, which takes values `tab`, `vsplit`,
or `hsplit` (corresponding to the file-opening commands). I mean to
use it with a command line like

    micro -multimode vsplit foo.h foo.c

to open files in a side-by-side split, but if one really wanted to
one could set it in the config file to change the default behavior of
opening multiple files in tabs.
2023-01-23 11:13:42 -08:00
Zachary Yedidia
43b512fd77 Include mac arm binary in release scripts 2023-01-12 19:19:20 -08:00
Zachary Yedidia
980112c071 Add darwin arm64 prebuilt binary
Fixes #2456
2023-01-12 19:16:07 -08:00
Alan Christian
b0e7efe6a5 description different from JSON real default (#2680)
* description different from JSON real default

* fix wrong line
2023-01-12 12:40:04 -08:00
Steven Xu
64638845e9 fix: allow binding the minus key, fixes #2471 (#2617) 2023-01-11 21:56:54 -08:00
oplexius
97f362b465 Doc changes (#2646)
Try to make things a little more concise to start with, remove a couple of repetitions.

This seems like a good editor for people starting out, and I like it as a good alternative to nano for explaining to beginners how to edit config files. If you appreciate the suggestions, I can go over the rest of the documentation, in the hope of making things even easier to get going with.

If the line breaks are off, I can redo this in a text editor, rather than on the web on github. I've limited the changes, but I could make a few more changes to style...

Btw, the tutorial is just about settings. I'm guessing this is just because things aren't finished, but I'm asking just in case this is an error and there may exist a tutorial somewhere.
2023-01-11 21:56:23 -08:00
Dmitry Maluka
f1801f1958 Comment plugin: improve commenting multi-line selection (#2668)
When commenting a block of multiple lines, the comment symbol is added
right before the first non-whitespace character in each line, e.g.:

void somefunc(int a)
{
    // if (a) {
        // a += 2;
        // printf("a = %d\n", a);
    // } else {
        // printf("none");
    // }
}

which isn't quite nice.

Change it to add the comment at the same position on each line, which is
the position of the leftmost non-whitespace in the entire block, e.g.:

void somefunc(int a)
{
    // if (a) {
    //     a += 2;
    //     printf("a = %d\n", a);
    // } else {
    //     printf("none");
    // }
}

Ref #2282
2023-01-11 21:54:59 -08:00
Zachary Yedidia
443ede470d Fix perl syntax file
Fixes #2656
2022-12-09 01:19:19 -08:00
Mark Shachkov
c4d4d5fb7d Add CUDA syntax support (#2652)
Reusing cpp syntax with minor additions.
2022-12-03 22:17:35 -08:00
Dmitry Maluka
28e0e20651 Improve buffer view relocation after jumping to a far-away location (#2628)
* Improve buffer view relocation after jumping to a far-away location

When the cursor is moved to a location which is far away from the
current location (e.g. after a search or a goto line), the buffer view
is always relocated in such a way that the cursor is at the bottom or
at the top (minus scrollmargin), i.e. as if we just scrolled to this
location. It's not like in other editors, and IMHO it's annoying. When
we jump to a new location far away, we usually want to see more of its
context, so the cursor should be placed closer to the center of the
view, not near its edges.

This change implements the behavior similar to other editors:

- If the distance between the new and the old location is less than one
  frame (i.e. the view either doesn't change or just slightly "shifts")
  then the current behavior remains unchanged.

- Otherwise the current line is placed at 25% of the window height.

* Postpone calling onBufPaneOpen until the initial resize

It is currently not possible to find out the geometry of a newly created
bufpane in onBufPaneOpen lua callback: bp:GetView() returns {0,0,0,0}
instead of the actual window. The reason is that the bufpane view is not
properly initialized yet when the bufpane is created and the callback is
triggered. It is initialized a bit later, at the initial resize.

So postpone calling onBufPaneOpen until after the initial resize.

* Improve buffer view relocation when opening a file at a far-away location

When a file is opened with the initial cursor location at a given line
which is far away from the beginning of the file, the buffer view is
relocated so that the cursor is at the bottom (minus scrollmargin)
as if we just scrolled to this line, which is annoying since we'd rather
like to see more of the context of this initial location.

So implement the behavior similar to the earlier commit (which addresses
a similar issue about jumping far away after a search or goto):

- If the initial cursor location is less than one frame away from the
  beginning of the buffer, keep the existing behavior i.e. just display
  the beginning of the buffer.

- Otherwise place the cursor location at 25% of the window height.
2022-12-02 19:38:09 -08:00
Jakob Nybo Nissen
9a10cac598 Improve Julia syntax highlighting (#2643)
Specifically, do not allow multiline single-quote strings, which are not a
thing in Julia. The existing rule broke when adjoints were used, such as
`b = a'`.
The syntax rules have been copied from Rust, which also uses single ticks for
character literals, and also uses the ' symbol for things unrelated to chars.
2022-12-02 17:49:50 -08:00
Aaron Clark
0a080ba03c Syntax: added support for Smalltalk (#2626) 2022-12-02 17:49:25 -08:00
Dmitry Maluka
3f0cd019d7 Fix incorrect LastVisualX after changing bufWidth w/o resize (#2629)
* Fix incorrect LastVisualX after changing bufWidth w/o resize

When we resize a buffer pane with softwrap enabled, we update the
cursors LastVisualX values to ensure that moving cursor up or down
within a wrapped line will move the cursor to the correct location.
The problem is that we need to do it also in cases when the visual
buffer width within the buffer window is changing without resizing
the window itself, e.g. when toggling the ruler on/off.

So update LastVisualX whenever the buffer width changes, not neccesarily
as a result of resizing the buffer window.

* Update LastVisualX and relocate when toggling wordwrap on/off

Visual location of a cursor may change not only when softwrap is toggled
on or off but also when wordwrap is toggled on or off without changing
the softwrap setting. So need to update cursor LastVisualX values and
relocate the view if needed, just like when softwrap is toggled, to make
sure that moving the cursor up and down will work correctly and that the
cursor will not be left out of the view.
2022-12-02 17:49:06 -08:00
zehkira
5d3dbde698 Fix "no" when replacing with empty string (#2641)
* advance to end of found string when not replacing

* simplify search location change
2022-12-02 17:48:50 -08:00
Zachary Yedidia
87ad67ada7 Update readme 2022-11-25 16:48:06 -08:00
Zachary Yedidia
fba5e2bf36 Bump clipper 2022-11-05 18:30:52 -07:00
Zachary Yedidia
2e9dabd434 Fix save with sudo for large file sizes
Fixes #2200
2022-11-02 16:00:12 -07:00
d!key
c8c7ad57bd Added openSUSE installation command (#2587) 2022-10-12 19:06:01 -07:00
Zachary Yedidia
986faa783b Add better explanation about find 2022-10-12 19:05:42 -07:00
Zachary Yedidia
f86f56a628 Bump tcell 2022-10-07 11:20:31 -07:00
Zachary Yedidia
957650c3ee Document FindLiteral default binding 2022-10-04 16:30:06 -07:00
Dmitry Maluka
8ff7ec50ef Fix relocate at the end of buffer when scrollmargin is 0 (#2578)
* Add LessEqual and GreaterEqual for SLoc

* Fix relocate at the end of buffer when scrollmargin is 0

Fix the following issue: when scrollmargin is set to 0 and we move the
cursor to the end of buffer (e.g. via Ctrl-End), the buffer view doesn't
move.

The cause is that the condition c.LessThan(w.Scroll(bEnd, -scrollmargin+1))
doesn't hold, since Scroll() takes care not to return a location beyond
the end of buffer, so in this case Scroll() just returns bEnd.
2022-10-01 13:03:40 -07:00
Valiant-Wolf
48645907ec Revert adding 'git-rebase-todo' to the main git syntax highlighting file (#2564) 2022-09-16 11:27:38 -07:00
Tomo
3cbbba534c Add justfile syntax (#2523)
Just is 'just a command runner'. It has syntax inspired by make,
 so `makefile.yaml` was used as a template.
2022-08-21 23:53:04 -07:00
Mishanya
3dcd01f8b8 Add template literal string highlighting to typescript (#2525) 2022-08-21 23:52:47 -07:00
trolzen
e7bdcb093b Restore installation instructions for Debian and Ubuntu. (#2530) 2022-08-20 19:08:57 -07:00
Zachary Yedidia
810133d5a8 Use shell job for terminal callback
Fixes #2529
2022-08-19 15:46:01 -07:00
Zachary Yedidia
3d6b0c6dd6 Ensure screen cannot draw during a term prompt
Fixes #2528
2022-08-16 22:07:41 -07:00
cat-master21
225927b9a2 add: add Pacstall to README.md (#2367) 2022-07-24 14:14:52 -07:00
Daniel Lee Harple
88e76b367c plugins: load directories that are symlinks (#2214)
Fix issue where symlinked plugin directories were ignored. For example

    $ file ~/.config/micro/plug/example
    example: symbolic link to <target directory>

This allows plugins to be managed in a user's "dotfiles" repository, and
be symlinked into micro's plugin directory.
2022-07-24 14:13:46 -07:00
silvershade1337
208a778387 Add new python 3.10 keywords (#2243)
Keywords from the new python 3.10.0 feature "Structural Pattern Matching" - match and case
2022-07-24 14:11:48 -07:00
Martin Kühl
738f131269 Fix gruvbox-tc colorscheme (#2240)
The underline style is missing a color and accidentally using the
background color for its foreground.  This makes links essentially
invisible.  It's also missing the todo style.

This change adds the missing style and color.  Following the gruvbox
colorscheme it uses the gruvbox shade of blue for links, and makes
todos bold.
2022-07-24 14:11:16 -07:00
Matthias Thym
639d8a0b08 Add Terraform syntax support (#2279) 2022-07-24 14:10:29 -07:00
Lincoln Júnior
ce2d186543 Fix cursor position change after CopyLine command (#2353) 2022-07-24 14:09:14 -07:00
Max Grinberg
091fa9091d Added installation instruction for Gentoo distro (#2209)
Co-authored-by: Max Grinberg <codeswhite@protonmail.com>
2022-07-24 14:07:57 -07:00
worldmaker
7efec130dc Fix weird behavior of JumpToMatchingBrace in some ill cases (#1966)
It should not return false immediately when no matching brace is found. This makes the jump fails in certain case: `[  )I]` =/=> `[I  )]`.
When there is no brace near the cursor, the last statement is also executed. This may cause problems when chaining commands.
2022-07-24 14:06:59 -07:00
Mario
cf98b7f824 Update README.md (#2109)
Change over 10 years deprecated ifconfig
2022-07-24 14:04:59 -07:00
Zachary Yedidia
dde4001170 Merge branch 'john-batch-master' 2022-07-24 14:04:00 -07:00
john-batch
c226779aca Case-insensitive highlighting of hexadecimal constants 2022-07-24 14:03:53 -07:00
Max Grinberg
02ef99a3a6 Linux clipboard notes reformatted and reordered (#2210)
Co-authored-by: Max Grinberg <codeswhite@protonmail.com>
2022-07-24 14:00:41 -07:00
Zachary Yedidia
bd9bd3a215 Merge branch 'mardukbp-patch-1' 2022-07-24 13:59:18 -07:00
Zachary Yedidia
ce98970c06 Merge branch 'patch-1' of https://github.com/mardukbp/micro into mardukbp-patch-1 2022-07-24 13:59:12 -07:00
Sizhe Zhao
7cc74491d0 runtime/help/defaultkeys.md: Fix table (#2376) 2022-07-24 13:56:30 -07:00
Waldir Pimenta
63cb6ce9fd help/options.md: reword hlsearch help text (#2502)
Also adjust text wrapping in some lines that had become too long with recent edits.
2022-07-24 13:56:11 -07:00
Abirdcfly
ee6688eb74 delete minor unreachable code caused by log.Fatal (#2507)
Signed-off-by: Abirdcfly <fp544037857@gmail.com>
2022-07-24 13:56:02 -07:00
Zachary Yedidia
ad70480de7 Only run info plist on darwin 2022-07-21 17:54:01 -07:00
Zachary Yedidia
490ee93796 Fix info-plist script 2022-07-21 17:46:23 -07:00
Zachary Yedidia
ba11d98fef Merge 2022-07-20 10:16:46 -07:00
Zachary Yedidia
d629008688 Add livemd to markdown extensions 2022-07-20 10:16:42 -07:00
Naftoli Gugenheim
6aa3ea70dc scala.yaml: add support for .sc extension (#2452)
It's used for Ammonite scripts and Scala worksheets
2022-07-17 12:19:07 -07:00
Mikko
3d4012850a fix javascript syntax recognizing parts of words as keywords (#2462) 2022-07-17 12:18:56 -07:00
Aaron Clark
5e035efbcb added support for HolyC (#2473) 2022-07-17 12:18:39 -07:00
yeti
515ec57b77 improve fortran syntax highlighting (#2479)
* support integer highlighting

* add missing keywords and move some to where they fit better

* add missing operators

* fix previous commit

* add and

* add import
2022-07-17 12:18:30 -07:00
Ben Hammond
585dcc7d19 Adds options for tab bar and tab color reversing (#2480)
* Adds options for tab bar and tab color reversing

* Fixes small bug with tabreverse, options now work fully as expected
2022-07-17 12:18:11 -07:00
raidenXR
fc21fc9816 Gnuplot Syntax highlighting (#2483)
* gnuplot syntax support

* Update gnuplot.yaml

* Add files via upload

* Update gnuplot.yaml
2022-07-17 12:17:11 -07:00
Matthias Thym
77c784def4 Add nushell syntax highlighting (#2486) 2022-07-17 12:16:57 -07:00
Peder Bergebakken Sundt
80bfaf1c54 runtime/syntax/nix: Add support for block comments (#2488) 2022-07-17 12:16:47 -07:00
Waldir Pimenta
d89db64829 syntax/git-rebase-todo.yaml: support more commands (#2495)
For reference, see the list of supported commands in the help text of git's interactive rebase:
https://github.com/git/git/blob/v2.37.1/rebase-interactive.c#L43-L59
2022-07-17 12:16:35 -07:00
Vilém Zouhar
191438b481 add new logo with white shadow, change readme title logo (#2497) 2022-07-17 12:16:27 -07:00
USAMI Kenta
37ed9dfe1e PHP: Add enum and keyword, and modify types (#2204)
* Add enum keyword to PHP (8.1) syntax

* Specify only keywords that are valid as type declarations as PHP types

boolean, integer and resource are not valid type name.

* Add match keyword to PHP (8.2) syntax
2022-07-17 12:16:17 -07:00
Zachary Yedidia
0ac7193c4d Fix cross compilation from macOS 2022-07-15 11:40:32 -07:00
Zachary Yedidia
9ce469f372 Update zyedidia/pty for openbsd support
Fixes #2335
2022-06-24 23:31:13 -07:00
Zachary Yedidia
03ae049c0f Use zyedidia/clipper for external clipboard
Micro will now also search for a program called micro-clip for handling
the clipboard. This allows the user to make a program called micro-clip
that micro will call out to for performing copy/paste. For copy it will
be called with `micro-clip -i <reg>` and the text will be provided on
stdin. For paste it will be called with `micro-clip -o <reg>` and micro
expects the text to be provided on stdout.
2022-06-14 08:40:57 -04:00
Zachary Yedidia
4194c502ae Update clipboard for WSL support 2022-06-12 15:39:33 +01:00
Zachary Yedidia
f32da00667 Add discussions note to readme 2022-06-10 11:19:05 +01:00
Zachary Yedidia
a285e814c1 Update tcell for OSC 52 fix
Ref #2444
2022-06-08 23:47:56 +01:00
Ethan Kinnear
6948cc88ef Register Brewfiles as Ruby files (#2432)
A `Brewfile` (sometimes named `.Brewfile`) is a bundler for Homebrew packages.
Brewfiles are written in Ruby and are functionally similar to Gemfiles.

`homebrew-bundle` on GitHub: <https://github.com/Homebrew/homebrew-bundle>.
`brew bundle` Manpage: <https://docs.brew.sh/Manpage#bundle-subcommand>.
2022-06-08 00:07:59 +01:00
Ruzie
25a19e2f21 fix: add "unknown" type (#2445) 2022-06-08 00:07:38 +01:00
Mikko
c57c5c04e5 Julia syntax improvements (#2415)
* fix D syntax highlighting for integer literals with underscores

* improve julia syntax highlighting for strings, chars and operators
2022-05-15 13:00:59 -07:00
Lars Müller
656e0a8a7b Lua syntax highlighting: Various fixes (#2426) 2022-05-15 13:00:44 -07:00
Lars Müller
176d1aa17a Fix syntax highlighting of single-quoted strings (#2425) 2022-05-15 13:00:29 -07:00
Lars Müller
db3afc1c0d Fix Lua number syntax highlighting (#2409)
* Fix Lua number syntax highlighting

* Number RegEx: Fix hex exponent

The hex exponent doesn't support hex digits, only decimals.
2022-04-28 15:28:28 -07:00
esdnm
dd69599f37 Update README.md (#2400) 2022-04-10 16:36:19 -07:00
Zachary Yedidia
2f4675eb93 Merge 2022-02-26 18:33:35 -08:00
Zachary Yedidia
56c825c44c Update minimum required Go version to 1.16
Fixes #2361
2022-02-26 18:33:12 -08:00
0x5c
987e409071 Added lines and percentage statusbar directives (#2055)
- "lines" for the number of lines in the buffer
- "percentage" for the percentage of the file at the current line

Fixes zyedidia#2049
2022-02-22 01:31:32 -08:00
Philippe Eberli
fe59e18e69 Error in documentation of +LINE:COL (#2205)
Its +LINE:COL not +LINE,COL
2022-02-14 13:32:46 -08:00
ftphikari
d3b9b37b07 runtime/syntax: add rudimentary Odin support (#2296)
Odin is a general-purpose programming language with distinct typing,
built for high performance, modern systems, and built-in data-oriented
data types. The Odin Programming Language, the C alternative for
the joy of programming. The Data-Oriented Language for Sane Software
Development.

https://odin-lang.org/
https://odin-lang.org/docs/overview/
https://github.com/odin-lang/Odin
2022-02-11 14:15:34 -08:00
Evan Shimoniak
9ca89ad300 Made apparent the functionality of the ftoptions plugin (#2321)
* Clarified meaning of indentchar setting

The description "sets the indentation character" combined with the default value of a space led me to believe that this was a way to set a preference for tabs/spaces and choose a number of spaces per indentation all at once. I've updated the description to try to make its true function clearer.

* Added note on rmtrailingws

This behavior was unexpected for me, so it's probably good to let other users know which option has precedence.

* Added details to help command

Initially I kept trying to use `help <command-name>` rather than `help commands`

* Added warning about ftoptions and tabstospaces

The current description for ftoptions states that it "alters some default options depending on the filetype", which hints at this behavior, but does not explicitly state it.

* Clarified specific functionality of ftoptions
2022-02-11 14:15:26 -08:00
Andrey Bienkowski
031d953ed5 Help: mention (un)indent in defaultkeys (#2358) 2022-02-11 14:13:35 -08:00
Shura
9ece5c8a3f Perl syntax improvement (#2359)
* Perl syntax improvement

Fixed \" inside strings
Fixed comment coloring
Improved integer, float, strings variable coloring
Added regex coloring

* Update perl.yaml
2022-02-11 14:13:27 -08:00
Andrew
f20179519f Detect more file types for git syntax highlighting (#2330)
* Update git-commit.yaml

This will enable syntax highlighting for merge commit messages

* Update git-commit.yaml
2022-01-08 19:51:15 -08:00
dependabot[bot]
80d0654847 Bump gopkg.in/yaml.v2 from 2.2.7 to 2.2.8 (#2329)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.7 to 2.2.8.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.7...v2.2.8)

---
updated-dependencies:
- dependency-name: gopkg.in/yaml.v2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-06 18:14:50 -08:00
Zachary Yedidia
a40d171d9c Merge 2022-01-06 18:11:15 -08:00
Zachary Yedidia
1cf8cbe546 Update luar import path
Fixes #2306
2022-01-06 18:11:02 -08:00
Zachary Yedidia
409ac54605 Add unofficial discord to readme 2022-01-01 00:33:05 -05:00
Juan Francisco Cantero Hurtado
3a1ec088e2 Raku syntax: fix comments (#2318)
Code:
 # sub xyz(Str is encoded("utf8")) returns int32 is native("asdf") { * }
2021-12-31 18:51:53 -05:00
Juan Francisco Cantero Hurtado
a2d83132ca Raku syntax: Fix strings and comments (#2311)
* Raku syntax: Fix strings and comments

Problematic code:
my @array1 = [
   "'", "a", "b"
];
my @array2 = [
   '"', 'a', 'b'
];
my @array3 = [
   "#", "a", "b"
];

I deleted "default" because it was breaking comments with urls after
of my changes.

Some parts were taken from:
https://github.com/hankache/raku.nanorc/blob/master/raku.nanorc

* Raku syntax: fix strings

Code:
sub xyz(Str is encoded('utf8')) returns int32 is native('asdf') { * }
sub xyz(Str is encoded("utf8")) returns int32 is native("asdf") { * }

From python3.yaml
2021-12-31 17:42:21 -05:00
Reilly Wood
580c32bcef Fix repo.json info in plugin docs (#2313) 2021-12-31 02:06:53 -05:00
Owen Valentine
cc0af275c1 Adjust default keybinding text (#2293)
Fixes #2287
2021-12-24 22:32:15 -05:00
Juan Francisco Cantero Hurtado
fd20fc8837 Fix the syntax for Raku variables. (#2309)
The current version has problems with the last argument and the
brackets in this code:

sub myfunction(Int $x1, Int $x2){

}

Taken from:
https://github.com/hankache/raku.nanorc/blob/master/raku.nanorc
2021-12-21 23:47:31 -05:00
Jake Leahy
c442d7030d Fix edge case when comment is at start of line (#2237) 2021-12-05 17:45:10 -08:00
antonl05
6998cb5602 add more types for nim (#2284) 2021-11-22 22:41:04 -08:00
Zachary Yedidia
d2dca2b6c3 Fix 2021-11-22 18:46:36 -08:00
Zachary Yedidia
0e63224dea Use abspath for local glob settings
Ref #2276
2021-11-22 18:45:19 -08:00
Hugo Hromic
0bbc3e7e3d Add support for alternatives system in Debian package (#1935)
* Allows for micro to be selectable in the `editor` group
* Use same priority as in the nano package

Ref: https://wiki.debian.org/DebianAlternatives
2021-11-17 15:51:40 -08:00
Matthias Thym
dd8e341de2 Fix linter help formatting (#2280) 2021-11-17 12:59:16 -08:00
abaldota
d023aef6b5 Python syntax: multiline string should be constant.string, not comment (#2268)
* Python syntax: multiline string should be comment.string, not comment 

''' delimits multiline strings, not comments

* Python syntax: multiline string should be comment.string, not comment 

''' delimits multiline strings, not comments

* Update python3.yaml for python3.10 keywords
2021-11-14 21:54:56 -08:00
Herby Gillot
26c24c25c0 README: add MacPorts install info (#2265) 2021-11-06 20:18:24 -07:00
Evan Shimoniak
58e3cc1be0 Clarified some documentation (#2259)
* Clarified meaning of indentchar setting

The description "sets the indentation character" combined with the default value of a space led me to believe that this was a way to set a preference for tabs/spaces and choose a number of spaces per indentation all at once. I've updated the description to try to make its true function clearer.

* Added note on rmtrailingws

This behavior was unexpected for me, so it's probably good to let other users know which option has precedence.

* Added details to help command

Initially I kept trying to use `help <command-name>` rather than `help commands`
2021-11-02 13:26:14 -07:00
Zachary Yedidia
7df91eb038 Fix makefile for cross compilation 2021-10-29 04:47:23 +00:00
Dmitry Maluka
728d87ceba Fix regression: non-working direct colors in syntax files (#2252)
After 9ad4437, directly specifying color names (instead of syntax groups)
in syntax files no longer works. In particular *.patch and *.diff files
are not highlighted, since in patch.yaml direct colors names are used.

Restore the previous behavior of GetColor (fallback to direct colors if
no syntax group found) to fix this regression, but also make some changes
in StringToStyle and StringToColor to still fix the issue which was fixed
by 9ad4437. In other words, ensure that there is no confusion between
direct colors ("red", "green" etc) and syntax groups omitted in the
colorscheme file.
2021-10-27 15:12:55 -07:00
Zachary Yedidia
a6796fcbd9 Update makefile generation rules
Ref #2229
2021-09-29 16:30:20 -07:00
Dmitry Maluka
ffbb257434 Support for highlighting all search matches (hlsearch) (#1762)
* Support for highlighting all search matches (hlsearch)

hlsearch is implemented efficiently using the buffer's line array,
somewhat similarly to the syntax highlighting.
Unlike the syntax highlighter which highlights the entire file,
hlsearch searches for matches for the displayed lines only.
Matches are searched when the given line is displayed first time
or after it was modified. Otherwise the previously found matches
are used.

* Add UnhighlightSearch action

and add it to the list of actions triggered by Esc key by default.

* Add comment explaining the purpose of search map

* Add hlsearch colors to colorschemes

Mostly just copied from the corresponding original (mostly vim) colorschemes.

* Highlight matches during/after replace as well

As a side effect it also changes the last search value, i.e. affects FindNext
and FindPrevious, but it's probably fine. In vim it works the same way.

* Improve hlsearch option description
2021-09-28 13:39:03 -07:00
Dmitry Maluka
9ad4437a98 Fix some issues with default colors in colorschemes (#2225)
* Fix default colors for unconfigured syntax groups

When GetColor is called for a syntax group not specified in the
colorscheme, it should fallback not to the terminal's default colors
(tcell.DefaultColor) but to the colorscheme's defaults (DefStyle)
which may be different from tcell.DefaultColor.

For example, if we are using micro's default colorscheme in a terminal
which uses a black-on-white theme, then dots and commas in Go files
("symbol" syntax group in go.yaml) are displayed black on a dark
background, i.e. barely visible.

* Avoid using terminal's default colors directly

If a syntax group color is set to "default" (which we have for some
syntax groups in some colorschemes), it defaults to the terminal's
default colors (tcell.DefaultColor), which is fine for 16-color
colorschemes but not quite fine for truecolor and 256-color
colorschemes which should not depend on the terminal colors.
It should default to the colorscheme's default (DefStyle) instead.

For example, if we are using micro's default colorscheme in a terminal
which uses a black-on-white theme, then "bool" type in C files
("type.extended" syntax group in c.yaml) is displayed black on a dark
background, i.e. barely visible.
2021-09-28 13:30:29 -07:00
Dmitry Maluka
a21a720941 Make 'make' do the same as 'make build' (#2217)
Fix a slight regression after ec3292: when 'make' is run without specifying
a target, it counter-intuitively runs fetch-tags instead of building micro.
2021-09-24 14:40:26 -07:00
Ryan Westlund
c2d7b62e8f Fix #2190: Document goto command in commands.md (#2218) 2021-09-24 14:40:11 -07:00
Ryan Westlund
9270f17378 Fix #1943: 'Duplicated line' message being wrong (#2219) 2021-09-24 14:39:58 -07:00
TogoorooDev
395bfc2307 Syntax Highlighting Support for FreeBSD Kernel Configuration Files (#2220)
* added FreeBSD kernel configuration file format

* updated format, notably to highlight the word 'include'
2021-09-24 14:39:46 -07:00
Ali Kefia
a417ec4dcb normalize path - force slash separator to access embed FS (#2197) 2021-08-25 16:26:54 -04:00
Ali Kefia
ec3292e8c4 Build : using go:generate and go:embed (#2195)
* using go:generate and go:embed

* fix import
2021-08-24 22:02:29 -04:00
Zachary Yedidia
fe3186ba9d Ignore tool files 2021-08-21 18:07:43 -04:00
Zachary Yedidia
3a97ce820c More style improvements 2021-08-21 18:04:08 -04:00
Zachary Yedidia
c44ccb8cc7 Merge 2021-08-21 17:58:35 -04:00
Zachary Yedidia
0914f158c2 Improve comments 2021-08-21 17:58:30 -04:00
Andrew Clarke
dcf94816fb remove carriage return from -clean prompt and fix broken logic (#2186) 2021-08-21 00:30:16 -04:00
Zachary Yedidia
bb609467dd Update comment filetype when commenting 2021-08-20 14:42:38 -04:00
Zachary Yedidia
a4c3f7dad9 Merge branch 'master' of https://github.com/zyedidia/micro 2021-08-20 13:56:22 -04:00
Zachary Yedidia
dceddcfd83 Fix save with sudo auto-detection and sudo/doas message 2021-08-20 13:55:59 -04:00
Zachary Yedidia
0c2e139672 Fix formatting 2021-08-18 16:55:51 -04:00
Zachary Yedidia
fb1e7eabab Update install instructions 2021-08-18 16:55:22 -04:00
Zachary Yedidia
272f3adcc4 Add eget to install instructions 2021-08-18 16:13:17 -04:00
Dmitry Maluka
87ad6fc0bb plugins.md: update link to the internal documentation (#2191)
godoc.org now redirects to pkg.go.dev and it's not obvious how to locate
the internal packages documentation at https://pkg.go.dev/github.com/zyedidia/micro
2021-08-18 15:35:49 -04:00
Zachary Yedidia
2eeeb0f2e4 Update runtime 2021-08-18 01:09:53 -04:00
Zachary Yedidia
aa541d6d6f Merge 2021-08-18 01:08:58 -04:00
Zachary Yedidia
403a9eb11d Add rust clippy linter and go vet linter 2021-08-18 01:08:54 -04:00
john-batch
77f93bfd93 Make yes/no prompts case-insensitive (#2182) 2021-08-13 23:56:26 -04:00
Zachary Yedidia
b97638566e Merge branch 'pyfisch-patch-3' 2021-08-06 20:45:12 -04:00
Zachary Yedidia
7a1ba01621 Merge branch 'patch-3' of https://github.com/pyfisch/micro into pyfisch-patch-3 2021-08-06 20:44:57 -04:00
riley
2b8cd6b758 Highlight racket files as lisp (#1931)
Add syntax highlighting for [racket](racket-lang.org), a (variant of scheme which is a) variant of lisp which uses the .rkt extension.
2021-08-06 20:38:22 -04:00
AAAA
cbe339da07 Update v.yaml (#1925)
Improvements:
 - Use proper scope names for better colorization
 - Better regex to detect binary, octal, decimal and hexadecimal numbers
 - Extend some definitions based on the Vlang docs

Co-authored-by: AAAA <dev@onerbs.com>
2021-08-06 20:37:42 -04:00
Zachary Yedidia
e290ce2de5 Fixes for syntax and docs
Fixes #2163
Ref #2173
2021-08-03 00:07:14 -04:00
Andrey Nering
c7fd4ba5f1 Document that "bubblegum" is a light theme (#2153) 2021-08-02 21:13:05 -04:00
Zachary Yedidia
0efc919f24 Merge 2021-08-02 21:06:28 -04:00
Zachary Yedidia
c315a91fc6 Allow aborting while opening a file with backup
Also fixes an issue where the abort prompt consumes interrupt signals.

Fixes #2151
2021-08-02 21:05:22 -04:00
pyfisch
6e1fe5b301 More precise filename detection for shell scripts
Make the regular expression much more precise:

* match literal dots instead of any char (match rc.conf but not rcXconf)
* match special filenames exactly (match PKGBUILD but not myPKGBUILD.something)

Run build-all to update internal/config/runtime.go

closes #2163
2021-07-21 22:09:46 +02:00
pyfisch
84a490f14c Update rust syntax: don't highlight lifetimes (#2164)
Work-around rust lifetimes and character literals both using single quotes.
2021-07-21 15:02:28 -04:00
pyfisch
42a9302636 Update rust syntax: char literal (#2162)
Highlight character literals started with a single quote (').
Importantly this ensures correct highlighting for the character literal '"'.
Limitation: rust char literals contain exactly one character, however this isn't checked by the highlighter.

Closes #2160
2021-07-21 12:37:41 -04:00
Ali Kefia
ae0c28a03d Fix name collision on linter name (swiftc) (#2158) 2021-07-18 18:30:52 -04:00
Ali Kefia
1a5518ebbb Shellcheck as a new shell linter + runtime.go out of git control (#2157)
* shellcheck as a new shell linter + runtime.go out of git control

* keep runtime.go and keep both shfmt and shellcheck since we can remove from custom conf
2021-07-16 15:01:50 -04:00
Rylee
160a81c572 Add alcritty and foot to the list of OSC 52 supporting terminals (#2154) 2021-07-13 23:37:48 -04:00
Ali Kefia
4a2a72983f Search the last match on line when search back (#2156) 2021-07-13 23:37:22 -04:00
Zachary Yedidia
33e064b3b9 Add default binding for FindLiteral 2021-07-04 20:00:49 -04:00
Zachary Yedidia
005442a4d0 Merge 2021-06-17 17:11:35 -04:00
Zachary Yedidia
6c666190e2 Update zyedidia/pty from upstream
Fixes #2138
2021-06-17 17:10:59 -04:00
vandervoortj
9ceb69921a Add nix language syntax (#2024)
* Create nix.yaml

Add nix language syntax

* Add nix-linter
2021-06-15 17:56:31 -04:00
Kethan
4b0db74770 Fix typo in tutorial.md (#2130) 2021-06-12 23:52:08 -04:00
Dmitry Maluka
3bfd367d87 find: select prefilled text (#2127)
The new feature of prefilling the search bar with the selected text (added
in 3d0b5db) has an annoying side effect: if we do have some text selected
but we want to search for some other pattern, not the selected text,
then we have to manually delete the prefilled text before we can start
entering our wanted search pattern.

A simple solution is to select the prefilled text, so that we can replace it
with our pattern right away just by typing, without any additional keystrokes.
2021-06-09 17:04:11 -04:00
Balló György
6d5beb50ba Add desktop-id to Appstream Metainfo (#2122)
It's needed for tools like appstream-generator to detect the associated desktop file.
2021-06-06 17:53:33 -04:00
Ali Kefia
3d0b5db2e4 find: prefill with selection (#2115)
* find: prefill with selection

* keep search func - could be used in plugins
2021-06-02 16:04:31 -04:00
Jeff Zhao
ee9c78dc86 Improve linter performance (#2083)
Only compute args if we are actually going to use it
2021-06-01 19:34:10 -04:00
Rosetta H&S
b66ec2bf7a Add highlighting for user-defined types (#2107)
* Added highlighting for user-defined types

Provides automatic highlighting of user-defined types ending with either "_t" or "_T", as is seen in editors such as Nano, or within GitHub itself.

* Update cpp.yaml
2021-06-01 19:33:40 -04:00
Héctor M. Monacci
2dc2cabe0e Dont take # as comment when preceded by backslash (#2112)
When escaped with a backslash (e.g., inside a regex) the numeral char, ```#```, shouldn't be interpreted as a beginning of comment.
2021-06-01 19:33:06 -04:00
Zachary Yedidia
56c7744e23 Fix erlang comment syntax 2021-05-31 20:26:32 -04:00
Zachary Yedidia
9bc32d4be9 Update tcell
Fixes #2108
2021-05-24 01:43:11 -04:00
Zachary Yedidia
0b0c99f1f5 Warn for readonly instead of setting option
Fixes #2106
2021-05-19 14:58:00 -04:00
Zachary Yedidia
6bc498e625 Update tcell
Fixes #2081
2021-05-16 16:35:47 -04:00
Zachary Yedidia
346c10f20e Merge pull request #2076 from dmaluka/softwrap-improvement2
Softwrap improvements
2021-05-11 20:36:23 -04:00
Zachary Yedidia
86df9ad3b0 Merge 2021-04-20 21:28:37 -04:00
Zachary Yedidia
0851499130 Handle SIGHUP properly
Fixes #2085

Not the nicest solution but it will do for now.
2021-04-20 21:27:59 -04:00
Dmitry Maluka
88c95c8fae Fix up X,Y values in BufView
Let's return absolute X, Y values, rather than relative to the bufwindow.
2021-04-09 01:48:58 +02:00
Dmitry Maluka
aaac60a78d Replace BufWidth & BufHeight with BufView
BufView returns not only the buffer's width and height but also its
x,y position. It may be useful e.g. for checking if a mouse click was
on the actual buffer or ourside it, e.g. on the gutter.
2021-04-08 23:54:18 +02:00
Dmitry Maluka
ab6ce444a7 Don't highlight padding spaces in word wrapping
Don't highlight space characters at the right edge which are used just
for padding after line break in word wrapping, i.e. don't correspond to
any real characters in the buffer.

This makes it look nicer e.g. when selecting word-wrapped text.
2021-04-08 23:54:14 +02:00
Dmitry Maluka
965e43ebf1 Implement word wrapping
Fixes #264
Fixes #1644
2021-04-08 23:54:10 +02:00
Dmitry Maluka
f2613eeb3b Simplify LocFromVisual implementation
Now that we have LocFromVLoc, we can radically simplify the code of LocFromVisual.
Less duplication, less potential bugs.
2021-04-08 23:54:06 +02:00
Dmitry Maluka
6d13710d93 Implement moving cursor up/down within a wrapped line
Modified behavior of CursorUp, CursorDown, CursorPageUp etc:
if softwrap is enabled, cursor moves by visual lines, not logical lines.

TODO: implement it also for Home and End keys: move cursor to the
visual start or end of a line. I haven't implemented it for now, because
I'm not sure what should be the behavior of StartOfTextToggle then
(considering that Home key is bound to StartOfTextToggle by default).

Fixes #1598
2021-04-08 23:54:02 +02:00
Dmitry Maluka
7a3d1e6e30 Add VLoc, VLocFromLoc and LocFromVLoc
VLoc allows any location in the buffer to be represented as a visual
location in the linewrapped buffer. In particular, this is useful
for implementing moving cursor up and down within a wrapped line.
2021-04-08 23:53:57 +02:00
Dmitry Maluka
0487db8b99 Fix horizontal scrolling with a wide rune at the right edge of window 2021-04-08 23:53:53 +02:00
Dmitry Maluka
cd7ab640c5 Fix displaying incomplete tab or wide rune at the right edge of window
Fix displaying tabs and wide runes which don't fit in the window.
Don't overwrite the vertical divider and the adjacent window.

- For tabs: display only as many of the tab's spaces as fit in the window.

- For wide runes: if a rune doesn't fit, don't display it in this line at all.
  If softwrap is on, display this rune in the next line.

Fixes #1979
2021-04-08 23:53:49 +02:00
Dmitry Maluka
a1651aec2f Fix horizontal scrolling issue after toggling softwrap on/off
Fixes #645
2021-04-08 23:53:44 +02:00
Dmitry Maluka
c960c93a83 Add BufWidth and BufHeight
Fixes issue with the usage of a slightly incorrect buffer height value
(v.Height should be v.Height-1 if statusline is displayed).

Also, to avoid too many duplications, the code reorganized a little:
buffer display params (width, height, gutter offset and others) are
calculated in a single place.
2021-04-08 23:53:40 +02:00
Alex Tsantilis
f1aaa30743 Update and rename perl6.yaml to raku.yaml (#1927)
The language has been renamed but still aims to support the old file extensions for a time.
2021-04-07 16:21:19 -04:00
Laszlo Gombos
9ed3525076 Improve patch file detection by adding a header rule. (#1942) 2021-04-07 16:20:57 -04:00
Dmitry Maluka
1f73f8587c Add buffer.WordAt (#2070)
Add buffer.WordAt function returning the word around a given location
in the buffer. Useful for plugins.
2021-04-07 16:20:39 -04:00
Dmitry Maluka
c5798b5b8c Fix softwrap scrolling issues (#1981)
Softwrap implementation enhanced to fix various issues with scrolling,
centering, relocating etc.

The main idea is simple: work not with simple line numbers but
with (Line, Row) pairs, where Line is a line number in the buffer
and Row is a visual line (a row) number within this line.
The logic remains mostly the same, but simple arithmetic operations
on line numbers are replaced with corresponding operations on
(Line, Row) pairs.

Fixes #632, #1657
2021-04-07 16:18:51 -04:00
Zachary Yedidia
6f949fe985 Merge 2021-03-08 13:11:08 -05:00
Zachary Yedidia
2d5fc15990 Update runewidth version
Fixes #1873
2021-03-08 13:10:52 -05:00
Zachary Yedidia
3c00d20ccc Update snap badge 2021-03-05 20:57:31 -05:00
Zachary Yedidia
ae114a766c Merge 2021-03-02 17:16:54 -05:00
Zachary Yedidia
6761bdf8aa Fix noregex interactive replace
Fixes #2052
2021-03-02 17:16:19 -05:00
Zachary Yedidia
c014af9280 Add ForceQuit action
Closes #1039
2021-03-01 21:55:49 -05:00
Dmitry Maluka
de8f4bf72f Fix regressions in buffer settings initialization (#2035)
Fix regressions after ba98b55:

- Unable to override filetype autodetection by setting a specific filetype
  for specific files, i.e. this doesn't work:

    "*.h": {
        "filetype": "c++"
    },

- Unable to enable/disable syntax highlighting for specific files,
  i.e. this doesn't work:

    "*.c": {
        "syntax": false
    },

- "readonly" setting doesn't work (neither global nor per-filetype).
2021-02-22 18:18:37 -05:00
Zachary Yedidia
975e78d9c0 Remove conf highlighting (too many conflicts)
Fixes #2031

The conf highlighter interferes with many more specific highlighters and
doesn't provide much value on its own.
2021-02-20 14:27:58 -05:00
Zachary Yedidia
8aadf6af65 Fix #2030: warn for invalid pane type 2021-02-18 19:02:23 -05:00
relrelb
6c694a1db4 Improve C syntax highlighting (#2015) 2021-02-17 22:28:19 -05:00
Nikolay Korotkiy
261eb41912 Add Gemini syntax file (#2016) 2021-02-17 22:27:56 -05:00
Sebastian Kolind Sørensen
d7abc8f100 Add .tsx support for Typescript syntax (#2021)
* Update to look for tsx files also

* Shorten filename detection
2021-02-17 22:27:45 -05:00
ejose19
a36ab0188a docs: update Arch Linux installation method (#2028) 2021-02-17 22:27:13 -05:00
Zachary Yedidia
ba98b558d9 Only initialize buffer settings once
Ref #2009
2021-02-07 13:14:40 -05:00
Zachary Yedidia
c21b85929f gofmt 2021-01-27 22:52:40 -05:00
Zachary Yedidia
c1e0e1d3b6 Merge branch 'ilius-PR-find-on-type' 2021-01-27 13:49:47 -05:00
Zachary Yedidia
c3a17a71be Rename to incsearch 2021-01-27 13:49:38 -05:00
Zachary Yedidia
120cd02b30 Merge branch 'PR-find-on-type' of https://github.com/ilius/micro into ilius-PR-find-on-type 2021-01-27 13:48:01 -05:00
Siddhant N Trivedi
cf35b8021c Fix some quality issues (#1914)
* Add .deepsource.toml

* Fix unnecessary typecasting on `bytes.Buffer`

* Fix check for empty string

* Replace nested if block with else-if

* Replace nested if block with else-if

* Replaced string.Replace() with string.ReplaceAll where n<0

* Remove deepsource toml file

Signed-off-by: siddhant-deepsource <siddhant@deepsource.io>

Co-authored-by: DeepSource Bot <bot@deepsource.io>
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2021-01-09 13:39:21 -05:00
Peter Kramarik
fcc2fea684 fix vue syntax highlight (#1982) 2021-01-08 17:06:49 -05:00
Marduk Bolaños
84f7248943 Added multiline strings to the Scala syntax highlighter (#1969)
In Scala multiline strings are constructed just like in Python

```
val message = """
This
is
a
message
"""
2021-01-05 19:59:28 -05:00
Dmitry Maluka
e80f899480 Fix non-working TryBindKey and UnbindKey (#1970)
Fixed regression: since merging keybindings branch, TryBindKey and
UnbindKey and accordingly "bind" and "unbind" commands don't work
(fail to unmarshal bindings.json).

This is just a quick fixup to make TryBindKey and UnbindKey work again.
They still work with "buffer" bindings only.
2021-01-05 19:37:49 -05:00
Saeed Rasooli
dd37ad5ce4 add settings option "findontype" to allow disabling search-on-type 2021-01-04 10:30:47 +03:30
Zachary Yedidia
c2b58fe861 Update readme 2020-12-29 14:13:54 -05:00
Zachary Yedidia
ef5b21c861 Merge branch 'a11ce-python-highlight-zero' 2020-12-27 18:43:07 -05:00
Zachary Yedidia
54c23cae72 Merge branch 'python-highlight-zero' of https://github.com/a11ce/micro into a11ce-python-highlight-zero 2020-12-27 18:42:53 -05:00
worldmaker
06a5f1a62d fix the missing break in JumpToMatchingBrace (#1960)
In JumpToMatchingBrace, the loop should stop immediately after finding the matching bracket.
It causes multiple jumps in certain situations:
`(I  [  ]{  }) => (  I[  ]{  })`
2020-12-27 18:38:16 -05:00
Zachary Yedidia
3321e844fc Merge 2020-12-26 14:45:27 -05:00
Zachary Yedidia
b5ce418201 Only use internal clipboard on error 2020-12-26 14:45:22 -05:00
Dmitry Maluka
57a3927f02 Don't automatically disable readonly option (#1957)
Fix the regression after 3b34a02: setting readonly option to true
in onBufferOpen lua callback doesn't work, since it is automatically
reset to false if write permission is not denied.
2020-12-23 15:21:20 -05:00
Dmitry Maluka
e4f7f80862 Fix potential file leaks (#1958) 2020-12-23 15:21:01 -05:00
Zachary Yedidia
2fbeb40bf0 Update hlint format 2020-12-20 16:53:18 -05:00
Zachary Yedidia
ecde9a53d7 Update runtime 2020-12-20 14:53:04 -05:00
Zachary Yedidia
299ba2fe97 Fix stat error detection
Fixes #1955
2020-12-20 13:05:10 -05:00
Zachary Yedidia
6b7c04b421 Add Dracula colorscheme to defaults
See https://draculatheme.com/micro.
2020-12-20 01:08:41 -05:00
Dmitry Maluka
83b3efc9de Document undocumented colorscheme groups (#1939) 2020-12-20 00:35:57 -05:00
Zachary Yedidia
c13acf6b19 Merge 2020-12-17 21:55:59 -05:00
Zachary Yedidia
3b34a021e3 Improve file permission detection
Mark files as readonly automatically if write permission is denied.
Display errors when opening files (except for non-existence errors).

Fixes #1224
2020-12-17 21:54:18 -05:00
Zachary Yedidia
4c21808c6c Remove clipboard error message 2020-12-16 21:35:07 -05:00
a11ce
1e5f8c020e Highlight 0 as a constant number in python3 2020-12-15 18:57:28 -05:00
Alekhine51
3fb5a7053f Added a sentence to colors.md clarifying that the truecolor environment variable has to be created by the user. (#1928) 2020-12-08 22:43:37 -05:00
Carlos Henrique Guardão Gandarez
7a5f7e443a Make more libraries available (#1917)
* Make more libraries available to plugin dvelopment

* Add Unzip function to util
2020-11-21 01:46:17 -05:00
Zachary Yedidia
7df04a58eb Clear prompt before callback
Ref #1913
2020-11-16 14:07:22 -05:00
Marduk Bolaños
3d683b27f7 .sbt files also contain Scala code
The Scala Build Tool sbt uses Scala as configuration language.
2020-11-11 09:58:10 +01:00
Zachary Yedidia
f3b21362f3 Disable fake cursor for Windows Terminal
Ref #1900
2020-11-06 13:45:34 -05:00
Zachary Yedidia
95fea064b0 Fix internal string binding representation 2020-11-05 15:52:25 -05:00
Zachary Yedidia
5d230754a8 Merge 2020-11-05 15:39:29 -05:00
Zachary Yedidia
19067a9bf0 Enable ignorecase by default
Closes #1908
2020-11-05 15:39:05 -05:00
Dmitry Maluka
298fa40f90 Fix buffer.RuneAt (#1895)
Fix buffer.RuneAt returning the rune not at the given location (as the
documentation claims) but just before it.
2020-10-19 20:36:14 -04:00
Dmitry Maluka
23162f7a34 Add tabbar.active color group (#1831)
Added tabbar.active color group for displaying the name of the active
tab in the tabbar with different colors.

If tabbar.active is not defined in the colorscheme, the active tab name
is displayed with the same colors as inactive ones.

Ref #1646
2020-10-17 20:53:08 -04:00
Dmitry Maluka
92e9060bcd Fix suggestions display (#1825)
Fix the following bugs:

- If a split pane is not at the left edge of the screen, the statusline
with suggestions for it is displayed at wrong place.

- When keymenu is enabled, the statusline with suggestions is not
displayed at all.
2020-10-17 20:48:39 -04:00
XeroOl
b2620eb68c update lua.yaml (#1892)
added `break` as a keyword
2020-10-16 01:44:48 -04:00
Zachary Yedidia
a424a0dca1 Fix autosave not running by default 2020-10-08 23:33:34 -04:00
Zachary Yedidia
cfcb2e4577 Update runtime 2020-10-06 17:39:20 -04:00
Zachary Yedidia
95eb8eb9dd Merge 2020-10-06 17:32:09 -04:00
Zachary Yedidia
882bd8ad1f Update tcell for alacritty and konsole 2020-10-06 17:32:06 -04:00
ThatXliner
c0907bb58e ✏️ : Added more Code tags (#1875)
I added more syntax highlighting for python comment code tags. Though this should be implemented for all languages...
2020-10-06 16:56:08 -04:00
Marduk Bolaños
225b24f356 Fixed help topic name (#1876)
The help topic is called `commands` not `command`
2020-10-06 16:55:47 -04:00
Zachary Yedidia
8742674197 Update tcell to 2.0.5 2020-10-06 16:54:46 -04:00
Zachary Yedidia
530041ac70 Fix typo
Closes #1869
2020-09-23 21:54:25 -04:00
Zachary Yedidia
49786cf8c3 Fix palette colors with tcell v2 2020-09-21 01:21:59 -04:00
Ryan Westlund
e8ba143144 Fix Crystal syntax highlighting (#1844)
Don't highlight things that don't exist, add some missing keywords,
highlight true/false/nil as constants instead of keywords, and
highlight types as types instead of constants.
2020-09-17 23:20:28 -04:00
MasFlam
e0dc018f66 Fix some left-over details in C++ syntax highlighting (#1865)
- move type cast keywords into operators, since that's their syntactic function
- fix a single dot being matched as a constant.number
- add the missing caret operator
2020-09-17 23:19:41 -04:00
MasFlam
cf63a68c8b groovy highlight (#1866) 2020-09-17 23:19:32 -04:00
Sourya Vatsyayan
fc3dd9a62f Fix quality issues (#1856)
* Add .deepsource.toml

* Remove unnecessary comparison with bool

* Remove unnecessary use of slice

* Replace multiple `append`s with one

* Remove unnecessary wrapping of function call

* Fix check for empty string

* Simplify error creation with `fmt.Errorf`

* Fix defers before error check

Signed-off-by: sourya_deepsource <sourya@deepsource.io>

* Remove untrappable `os.Kill` signal

Signed-off-by: sourya_deepsource <sourya@deepsource.io>

* Remove empty else branch

Signed-off-by: sourya_deepsource <sourya@deepsource.io>

* Add missing error check

Signed-off-by: sourya_deepsource <sourya@deepsource.io>

* Merge variable declaration and assignment

Signed-off-by: sourya_deepsource <sourya@deepsource.io>

* Remove unnecessary `nil` check

Signed-off-by: sourya_deepsource <sourya@deepsource.io>

* Revert changes to generated files

Signed-off-by: sourya_deepsource <sourya@deepsource.io>

* Remove .deepsource.toml

Signed-off-by: sourya_deepsource <sourya@deepsource.io>

Co-authored-by: DeepSource Bot <bot@deepsource.io>
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2020-09-16 00:08:01 -04:00
Ertu (Er2, Err)
6dd92faf1a coffeescript syntax fix (#1861) 2020-09-16 00:06:40 -04:00
Zachary Yedidia
c26a365f5c Enable xterm automatically if screen init fails 2020-09-15 01:11:59 -04:00
Zachary Yedidia
08f0345d48 Update tcell 2020-09-14 11:30:20 -04:00
Zachary Yedidia
7ba484519e Merge 2020-09-14 01:14:31 -04:00
Zachary Yedidia
4341d536af Update tcell 2020-09-14 01:14:28 -04:00
MasFlam
8df921cf96 Overall syntax highlighting improvements for C++ (#1858)
* Overall syntax highlighting improvements for C++
Most of these changes are based on the information on cppreference.com;
specifically from here: https://en.cppreference.com/w/cpp/keyword
- made `identifier` actually match any identifier
- add ~ as an operator
- add `static_assert` as a keyword (statement)
- add keywords that are interchangeable with operators as operators
- add keywords `sizeof`, `alignof` and `typeid` as operators
- add the quasi-keywords `asm`, `fortran` and `final`, `override`
- add the keyword `nullptr`
- add `_Pragma` as a preprocessor keyword
- add C++20 (concepts and modules) -related keywords
- add casting keywords
- add the keyword (specifier) `noexcept`
- remove `nothrow` (because it's not any more special than `vector` is)
- add `wchar_t` and `charXX_t` types
- add cv type keywords as `type.keyword`s
- move some fitting keywords into `type.keywords`
(mostly because they appear in/near type signatures etc.)
I didn't include coroutine-related language features,
primarily because there is no good source of information
about them other than the ISO C++ standard.

* Further changes to C++ syntax highlighting
- reverted the changes to the `identifier` regex, since most
colorschemes color it the same as `type`s and/or `statement`s
- fix the 2nd `type` regex (the word boundaries were in only two pipe-options)
- move `nullptr` back into `constant.bool`,
since it looks better in-editor this way (imo)
- add `?` as an operator
- add regexes that match all the correct number literals, and nothing else
(see https://en.cppreference.com/w/cpp/language/floating_literal)
(that is, if I haven't made a mistake)
2020-09-14 00:06:47 -04:00
Zachary Yedidia
067f08c56e Merge 2020-09-14 00:05:55 -04:00
Zachary Yedidia
ad16f1756b Remove test option
Fix #1857
2020-09-14 00:05:35 -04:00
Zachary Yedidia
fcfec28d79 Don't highlight parens in default theme 2020-09-13 20:25:02 -04:00
Zachary Yedidia
5044ccf6bb Update keybinding docs
Also updates the pane type of `info` to `command` which is a more
descriptive name.
2020-09-06 17:38:23 -04:00
Zachary Yedidia
4ac511b597 Update tcell 2020-09-06 11:40:34 -04:00
Zachary Yedidia
96601a915d Replace meta with alt automatically, update tcell 2020-09-05 21:59:19 -04:00
Zachary Yedidia
11104fd093 Update to tcell v2 2020-09-05 14:52:35 -04:00
Zachary Yedidia
f35f507832 Never backup closed buffers 2020-09-04 13:36:23 -04:00
Zachary Yedidia
04c1430747 Merge 2020-08-30 15:46:14 -04:00
Zachary Yedidia
548bb98641 Properly close unmodified buffers on sigterm 2020-08-30 15:46:11 -04:00
Zachary Yedidia
c9e49fa1a4 Update tcell 2020-08-25 15:21:18 -04:00
Dmitry Maluka
c9b0451a33 AddToHistory function for plugins (#1830)
Add InfoBuf's method AddToHistory function which adds a new item
to the history for the prompt type `ptype`.
This function is not used by micro itself. It is useful for plugins
which add their own items to the history, bypassing the infobar
command line.
2020-08-23 15:47:14 -04:00
Peder B. Sundt
5bee7272e9 Add improvements to the python3 syntax definitions (#1833)
* Python3: Add built-in object 'cls'

* Python3: Add the bitwise negation operator ~

* Python3: Add support for hexadeximal and binary numerical literals

* Python3: Rewrite '(__foo__|__bar__)' as '__(foo|bar)__', add known '__i.*__' methods

* Python3: Add __iter__ as a magic method, sort the list of magic methods

* Python3: Numerical literals: Add support for '_', disallow leading 0 for decimals

* fixup! Python3: Numerical literals: Add support for '_', disallow leading 0 for decimals

hex oct and bin have different sets of allowed digits

* Python3: Add support for floating point numbers with optional scientific notation

* Python3: stop single-line strings at EOL

* Python3: Add support for TODO and FIXME in comments

* Python3: Add support for the ^ bitwise xor operator
2020-08-23 15:46:27 -04:00
Peder B. Sundt
11291e1406 Minor tweak to the railscast color scheme (#1834)
* Railscast: Change the color of operators to match keywords instead of identifiers

* Railscast: Add a color for `constant.specialChar`
2020-08-23 15:46:05 -04:00
Dmitry Maluka
c7e72220dd Add scrollbar color group (#1840)
Ref #1837
2020-08-23 15:45:43 -04:00
Bartek Pacia
3ba03cca15 fix spelling (#1828) 2020-08-17 13:04:44 -04:00
Ryan Westlund
c6d04220be Highlight static as keyword in Javascript files (#1824) 2020-08-14 16:56:55 -04:00
Dmitry Maluka
7e19b68426 Avoid duplicate entries in history (#1822) 2020-08-13 01:38:50 -04:00
Zachary Yedidia
c5bafbc1c5 Merge 2020-08-12 01:18:18 -04:00
Zachary Yedidia
6b80870dfd Don't auto-relocate mouse events 2020-08-12 01:18:15 -04:00
Zachary Yedidia
5cb618c466 Improve showkey command 2020-08-11 22:18:10 -04:00
Ryan Westlund
a87370b111 Improve Rust syntax highlighting (#1820) 2020-08-11 21:39:57 -04:00
Zachary Yedidia
352f57cf11 Enable registering raw events
Fixes #1821
2020-08-11 14:36:58 -04:00
Zachary Yedidia
1e83e666fb Don't overwrite user bindings
This fix still needs more work.

Ref #1821
2020-08-11 01:43:41 -04:00
Zachary Yedidia
c837a7d0b7 Ref #1819 2020-08-10 20:34:10 -04:00
Zachary Yedidia
63d45bc9c5 Fix JobSend stdin 2020-08-10 12:24:29 -04:00
Zachary Yedidia
0283c01432 Record events in cursor 2020-08-09 16:42:03 -04:00
Zachary Yedidia
bbd6f559ab Allow configuration for info/term bindings
This commit exposes the separate infopane bindings to configuration
from the user. This also adds support for separate bindings in the
terminal emulator view. Default bindings are provided, but can also
be rebound in bindings.json.
2020-08-09 16:42:03 -04:00
Zachary Yedidia
2363a4019b Separate bindings for buffers and command bar
This commit separates actions in the command bar from actions in
a normal buffer, and implements what is needed to allow rebinding,
although an interface for command bar keybindings is not yet exposed
to the user.
2020-08-09 16:42:03 -04:00
Zachary Yedidia
d33c28eeb8 Preliminary support for key sequences
This commit adds support for binding key sequences such as
"<Ctrl-x><Ctrl-c>". This commit does not solve the problem
of global bindings yet, and therefore the command bar doesn't
work properly in this commit.
2020-08-09 16:42:03 -04:00
Zachary Yedidia
5ff8b3791d Basic implementation of KeyTree 2020-08-09 16:42:03 -04:00
Zachary Yedidia
6c53407e6d Improve internal keyevent names 2020-08-09 16:42:03 -04:00
Robin Voetter
626b08e991 Improve Zig syntax definitions (#1814)
* Improve Zig syntax definitions

* Remove duplicate definition of constant.number

* Make undefined a constant
2020-08-05 19:39:03 -04:00
Jakob Nybo Nissen
697751d5f6 Allow Julia multiline strings and comments (#1813) 2020-08-05 19:38:29 -04:00
Zachary Yedidia
dd54a64746 Initialize t.release to true 2020-08-04 18:41:14 -04:00
Dmitry Maluka
6e43af31cb Fix non-working split resize with mouse drag (#1811)
Fix the 2nd part of #1773: resize via mouse drag doesn't work if the
split on the left contains other splits, i.e. is not a leaf node.

The problem is that only leaf nodes have unique id. For non-leaf nodes
ID() returns 0. So we shouldn't search the node by id.
So replace GetMouseSplitID() with GetMouseSplitNode().
2020-08-04 18:37:19 -04:00
Dmitry Maluka
a4cc5a4146 Fix erased vertical dividing line (#1810)
Fix the 1st part of #1773: the dividing line between vertical splits
is not displayed if the split on the left contains other splits, i.e.
is not a leaf node.
2020-08-04 18:33:16 -04:00
Morten Linderud
9a3fb52b42 Support reproducible builds (#1802)
* Makefile: Ensure we strip out embedded paths

To reproduce binaries undeterministic values needs to be removed. By
default Go embeds several module paths into the binaries, which prevents
people from reproducing said distributed binary.

The distributed binary from micro contains the full home path of the
current builder of the binary. -trimpath removes these paths from the
binary.

    $ strings micro | grep "/home/zyedidia" | wc -l
    868

This also helps other distributions providing reproducible versions of
micro down the line.

Signed-off-by: Morten Linderud <morten@linderud.pw>

* build-date: Ensure build time adheres to SOURCE_DATE_EPOCH

Embedding undeterministic values into binaries prevents reproduction of
the binaries. The reproducible builds projects defines
`SOURCE_DATE_EPOCH` to allow deterministic insertion of build times.

This patch ensures `build-date` checks the environment variable before
building with the local time.

    $ SOURCE_DATE_EPOCH=123123 go run tools/build-date.go
    January 02, 1970
    $ go run tools/build-date.go
    July 31, 2020

    $ make build-quick && ./micro --version
    [...]
    Compiled on July 31, 2020
    $ SOURCE_DATE_EPOCH=123123 make build-quick && ./micro --version
    [...]
    Compiled on January 02, 1970

https://reproducible-builds.org/specs/source-date-epoch/

Signed-off-by: Morten Linderud <morten@linderud.pw>
2020-08-01 20:26:39 -04:00
Zachary Yedidia
cfb8d4a6b5 Merge 2020-08-01 20:18:41 -04:00
Zachary Yedidia
b507cd26f4 Exit gracefully when SIGTERM is received
Ref #779
2020-08-01 20:18:07 -04:00
Zachary Yedidia
ce46b8e9a1 Solus install instruction 2020-07-30 21:53:30 -04:00
Zachary Yedidia
95ec55fbbf Check error in terminal emulator 2020-07-27 17:43:55 -04:00
Zachary Yedidia
015e7c7b83 Don't update internal plugins
Ref #1792
2020-07-22 15:40:40 -04:00
Ryan Westlund
1f27f51f9a Add syntax support for Renpy (#1789) 2020-07-22 15:26:37 -04:00
Zachary Yedidia
ab1d74dc79 Tidy go mod 2020-07-22 15:23:13 -04:00
Zachary Yedidia
2b1ebd5fb7 Merge 2020-07-19 19:08:00 -04:00
Zachary Yedidia
b521e47d0b Update tcell 2020-07-19 19:07:52 -04:00
Ryan Westlund
1343955b05 Add comment support for OCaml (#1776) 2020-07-15 14:13:44 -04:00
franekjel
1a89d2095d Support for multiple modifiers in colorschemes (#1772)
* Support for multiple modifiers (eg. "bold italic")

* Test for multiple modifiers (bold + italic + underline)
2020-07-14 17:58:03 -04:00
Zachary Yedidia
781a2dd826 Add flake8 linter, postinit and preinit
Closes #1768
2020-07-13 13:28:26 -04:00
Zachary Yedidia
a45591a24d Read paste option in screen init
Fixes #1767
2020-07-10 12:26:15 -04:00
Zachary Yedidia
a52dbb2142 Fix swift linting problem 2020-07-09 18:08:14 -04:00
Zachary Yedidia
41a27cc58a Update linter to include eslint
Ref #1766
2020-07-09 18:04:40 -04:00
Zachary Yedidia
3d387732c4 Update linter documentation
Ref #1766
2020-07-09 17:58:42 -04:00
Zachary Yedidia
04f281bf1d Name svg micro.svg in tarballs
Ref #1765
2020-07-09 13:29:48 -04:00
Zachary Yedidia
2caff00ce1 Add micro icon and desktop file to release zips
Closes #1765
2020-07-09 13:26:12 -04:00
Ján Priner
ddc887f6e4 Highlight multicharacter escape sequences in C and C++ string literals (#1761) 2020-07-08 00:17:48 -04:00
Ján Priner
51444765f4 Update micro.desktop (#1759) 2020-07-06 17:55:13 -04:00
Zachary Yedidia
767918d2d1 fix 2020-07-06 17:32:41 +00:00
Zachary Yedidia
984b6d4e6a Rename deb file in scripts 2020-07-06 17:30:49 +00:00
Zachary Yedidia
4184bc19e0 Improve deb package 2020-07-06 17:25:30 +00:00
Zachary Yedidia
0d81e68f86 Chmod 2020-07-06 03:21:13 +00:00
Zachary Yedidia
8316bce6eb Update script 2020-07-06 03:19:47 +00:00
Zachary Yedidia
f1318f28ea Merge 2020-07-06 03:14:06 +00:00
Zachary Yedidia
0283155305 Improve packaging
Slight improvements to the man page, and the man page is now
provided in prebuilt binary tarballs. Also a .deb file is now
provided as an asset along with prebuilt binary tarballs.
2020-07-06 03:13:02 +00:00
Zachary Yedidia
cd0a9b6a60 Update nightly release scripts 2020-07-05 17:12:06 -04:00
Zachary Yedidia
621e4e9e4d Slight performance improvement 2020-07-05 15:54:00 -04:00
Zachary Yedidia
806525c3da Improve comment plugin
When commenting a selection, the plugin won't just toggle each
line individually but will only uncomment the block if it is all
comments.

The comment plugin also now takes into account any number of spaces
between the comment character and the text. For example '//comment' will
be uncommented properly, as well as '//      comment'.

Fixes #1758
2020-07-05 15:48:49 -04:00
Zachary Yedidia
102ae04a16 Improve multicursor clipboard
Ref #1721
2020-07-05 01:12:35 -04:00
Zachary Yedidia
037c3c993f Add clipboard support for multicursors
Fixes #1721
2020-07-04 21:26:36 -04:00
Zachary Yedidia
d8596919a6 Fix reading clipboard internally for OSC52 2020-07-04 20:54:27 -04:00
Zachary Yedidia
cf86f6848f Don't set fastdirty base if modified 2020-07-04 20:09:44 -04:00
Zachary Yedidia
aeb5563df0 Update runtime 2020-07-04 20:06:37 -04:00
Zachary Yedidia
a3eacb785f Merge 2020-07-04 20:01:02 -04:00
Zachary Yedidia
f143418267 Add support for copy-paste via OSC 52
Ref #1754
2020-07-04 20:00:39 -04:00
Jim Tittsler
9003462e76 Fix README ToC link (#1757) 2020-07-04 19:47:10 -04:00
Zachary Yedidia
67355337b3 Fix escape not exiting prompt 2020-07-03 22:12:58 -04:00
Zachary Yedidia
32c8517a90 Rebind escape to clear info and deselect 2020-07-03 21:02:16 -04:00
Zachary Yedidia
b793e9bb92 Use tcell's CanDisplay instead of Go's IsPrint
Fixes #1755
2020-07-02 17:57:50 -04:00
Zachary Yedidia
977290d77b Improve php string highlighting
Fixes #1753
2020-07-01 23:38:47 -04:00
Zachary Yedidia
2c4035aa65 Update clipboard verification
Ref #1752
2020-07-01 01:24:06 -04:00
Utkarsh Gupta
b748d0c383 Drop unnecessary Ftoa and FtoaWithDigits function (#1751)
Fixes: #1749

Signed-off-by: Utkarsh Gupta <utkarsh@debian.org>
2020-06-30 13:19:27 -04:00
Zachary Yedidia
b8fbbf5c83 Only lock event handling 2020-06-28 16:34:01 -04:00
Zachary Yedidia
253281ae5e Add a lock for plugins to use if using async code
Ref #1539
2020-06-28 16:29:32 -04:00
Zachary Yedidia
f5c6f66c8f Fix path escaping on Windows
Windows does not allow ':' in a path, but for some reason previous
versions still worked, except the file for storing buffer info
(which had a ':' in the name) was not viewable except by opening
it with micro.

Ref #1736
2020-06-27 17:59:28 -04:00
Zachary Yedidia
3da0415ef1 Merge 2020-06-27 17:55:39 -04:00
Zachary Yedidia
be4d186a46 Close file properly in clean and update makefile 2020-06-27 17:55:01 -04:00
Ryan Westlund
e946d9eddf Improve Haskell syntax highlighting (#1745) 2020-06-26 17:53:43 -04:00
Zachary Yedidia
60846f549c Update plugin documentation 2020-06-24 17:24:45 -04:00
Zachary Yedidia
5f62f550f3 Add more functions to customize status bar
Adds `status.lines`, `status.vcol`, `status.bytes`, `status.size`,
and exposes some functions from go-humanize to plugins.

Ref #1727
2020-06-24 17:19:42 -04:00
Zachary Yedidia
db1df05017 Support month and day names in crontab syntax
Ref #1739
2020-06-24 16:33:22 -04:00
Zachary Yedidia
05cbc310f3 Use boundaries in cron syntax rules
Fixes #1739
2020-06-24 16:00:56 -04:00
Zachary Yedidia
3ddb2ee316 Add Search function to BufPane 2020-06-23 18:47:42 -04:00
Zachary Yedidia
a749786830 Update readme
Ref #1741
2020-06-23 17:37:58 -04:00
Zachary Yedidia
687e4bdc25 Don't delete user settings if a parse error occurs 2020-06-23 17:29:20 -04:00
Zachary Yedidia
37c754c7c7 Treat CRLF as LF when inserting text
In effect, pasting text with \r\n will remove the \r character and
delegate whether or not the file will be saved with CRLF or LF line
endings to the `fileformat` option.

Ref #1742
2020-06-23 17:17:22 -04:00
Zachary Yedidia
9cc7c9be2d Fix backup call in test 2020-06-22 18:20:01 -04:00
Zachary Yedidia
a8332fd316 Improve backup system
This commit introduces several improvements to the backup system.

* Backups are made every 8 seconds for buffers that have been modified
  since the last backup.

* The `permbackup` option allows users to specify that backups should
  be kept permanently.

* `The backupdir` option allows users to store backups in a custom
   directory.

Fixes #1641
Fixes #1536
Ref #1539 (removes possibility of race condition for backups)
2020-06-22 17:54:56 -04:00
Zachary Yedidia
c5136820c4 Don't use make in travis script 2020-06-20 20:39:20 -04:00
Zachary Yedidia
349fbb698c Upgrade travis to go 1.13.x 2020-06-20 20:29:58 -04:00
Zachary Yedidia
a1d863251f Upgrade Travis Go to 1.12.x
Fixes Travis Windows build problem because go modules are enabled
by default in 1.12.x and don't need an environment variable to be
set.
2020-06-20 20:22:31 -04:00
Zachary Yedidia
42cc50106e Include windows for travis 2020-06-20 20:12:15 -04:00
Zachary Yedidia
4d13308624 Persist plugin options correctly 2020-06-20 20:07:33 -04:00
Zachary Yedidia
d0b75bc09f Add simulation screen tests 2020-06-20 18:24:12 -04:00
Zachary Yedidia
a9ca57af6e Improve message in micro -clean
Ref #1736
2020-06-20 13:44:52 -04:00
Zachary Yedidia
bcc35c9f8c Fix backspace on Windows
Fixes #1735
2020-06-20 13:22:01 -04:00
Zachary Yedidia
fb258dd57a Clean default settings in micro -clean 2020-06-18 17:22:21 -04:00
Zachary Yedidia
891b117a33 Bind Ctrl-/ (CtrlUnderscore) in comment plugin 2020-06-18 17:18:34 -04:00
Zachary Yedidia
f5dc0a51ba Fix issue with search and replace at the end of a range 2020-06-18 16:38:10 -04:00
Zachary Yedidia
8cbe7fa92b Update tcell version 2020-06-17 23:24:53 -04:00
Zachary Yedidia
a584ff36de Merge 2020-06-17 23:14:03 -04:00
Zachary Yedidia
f5405cee18 Improve keybinding label consistency
The old notation (for example `CtrlG`) causes confusion when combined
with new notation needed for alt (`Alt-g`) due to Alt being case
sensitive. Previously both formats were supported, but the documentation
and defaults used a combination. This commit only uses the new notation
for consistency.

Ref #1470
2020-06-17 23:11:50 -04:00
Zachary Yedidia
3516c8a9a6 Start replacement search at cursor location
Fixes #1731
2020-06-17 22:43:22 -04:00
Matthias
c19dce87e4 Fix typo in defaultkeys (#1730) 2020-06-17 13:35:59 -04:00
Zachary Yedidia
2adba18159 Don't move nightly tag 2020-06-17 00:48:17 +00:00
Zachary Yedidia
f9f2ef02ac Edit nightly release instead of replacing 2020-06-16 20:33:59 -04:00
Zachary Yedidia
0976eb3e51 Cross compile binaries in release scripts 2020-06-16 19:55:12 -04:00
Zachary Yedidia
ac2d1491ff Use hub for creating releases 2020-06-16 19:55:12 -04:00
181 changed files with 6294 additions and 2683 deletions

22
.github/workflows/test.yaml vendored Normal file
View File

@@ -0,0 +1,22 @@
on: [push, pull_request]
name: Build and Test
jobs:
test:
strategy:
matrix:
go-version: [1.19.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- uses: actions/checkout@v3
- name: Build
run: |
make build
- name: Test
run: |
make test

2
.gitignore vendored
View File

@@ -15,7 +15,5 @@ benchmark_results*
tools/build-version
tools/build-date
tools/info-plist
tools/bindata
tools/vscode-tests/
*.hdr
assets_vfsdata.go

0
.gitmodules vendored
View File

View File

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

View File

@@ -1,4 +1,4 @@
.PHONY: runtime
.PHONY: runtime build generate build-quick
VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
go run tools/build-version.go)
@@ -6,46 +6,36 @@ HASH = $(shell git rev-parse --short HEAD)
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
go run tools/build-date.go)
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH))
GOARCH=$(shell go env GOHOSTARCH) \
go run tools/info-plist.go "$(shell go env GOOS)" "$(VERSION)")
GOBIN ?= $(shell go env GOPATH)/bin
GOVARS = -X github.com/zyedidia/micro/v2/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/v2/internal/util.CompileDate=$(DATE)'
DEBUGVAR = -X github.com/zyedidia/micro/v2/internal/util.Debug=ON
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
# Builds micro after checking dependencies but without updating the runtime
build:
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build: generate build-quick
build-quick:
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-dbg:
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
go build -trimpath -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
build-tags: fetch-tags
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-tags: fetch-tags generate
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Builds micro after building the runtime and checking dependencies
build-all: runtime build
build-all: build
# Builds micro without checking for dependencies
build-quick:
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Same as 'build' but installs to $GOBIN afterward
install:
install: generate
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Same as 'build-all' but installs to $GOBIN afterward
install-all: runtime install
# Same as 'build-quick' but installs to $GOBIN afterward
install-quick:
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
install-all: install
fetch-tags:
git fetch --tags
# Builds the runtime
runtime:
go generate ./internal/config
generate:
GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime
testgen:
mkdir -p tools/vscode-tests
@@ -58,6 +48,7 @@ testgen:
test:
go test ./internal/...
go test ./cmd/...
bench:
for i in 1 2 3; do \

View File

@@ -1,11 +1,11 @@
<img alt="micro logo" src="./assets/micro-logo.svg" width="500px"/>
<img alt="micro logo" src="./assets/micro-logo-drop.svg" width="500px"/>
[![Build Status](https://travis-ci.org/zyedidia/micro.svg?branch=master)](https://travis-ci.org/zyedidia/micro)
![Test Workflow](https://github.com/zyedidia/micro/actions/workflows/test.yaml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/zyedidia/micro)](https://goreportcard.com/report/github.com/zyedidia/micro)
[![Release](https://img.shields.io/github/release/zyedidia/micro.svg?label=Release)](https://github.com/zyedidia/micro/releases)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Snap Status](https://build.snapcraft.io/badge/zyedidia/micro.svg)](https://build.snapcraft.io/user/zyedidia/micro)
[![Snap Status](https://snapcraft.io/micro/badge.svg)](https://snapcraft.io/micro)
**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!
@@ -17,7 +17,7 @@ Here is a picture of micro editing its source code.
![Screenshot](./assets/micro-solarized.png)
To see more screenshots of micro, showcasing some of the default color schemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
To see more screenshots of micro, showcasing some of the default color schemes, see [here](https://micro-editor.github.io).
You can also check out the website for Micro at https://micro-editor.github.io.
@@ -25,14 +25,14 @@ You can also check out the website for Micro at https://micro-editor.github.io.
- [Features](#features)
- [Installation](#installation)
- [Prebuilt binaries](#prebuilt-binaries)
- [Prebuilt binaries](#pre-built-binaries)
- [Package Managers](#package-managers)
- [Building from source](#building-from-source)
- [Fully static binary](#fully-static-binary)
- [macOS terminal](#macos-terminal)
- [Linux clipboard support](#linux-clipboard-support)
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
- [Plan9, Cygwin, Mingw](#plan9-cygwin-mingw)
- [Cygwin, Mingw, Plan9](#cygwin-mingw-plan9)
- [Usage](#usage)
- [Documentation and Help](#documentation-and-help)
- [Contributing](#contributing)
@@ -79,23 +79,40 @@ If you want more information about ways to install micro, see this [wiki page](h
Use `micro -version` to get the version information after installing. It is only guaranteed that you are installing the most recent
stable version if you install from the prebuilt binaries, Homebrew, or Snap.
### Prebuilt binaries
A desktop entry file and man page can be found in the [assets/packaging](https://github.com/zyedidia/micro/tree/master/assets/packaging) directory.
All you need to install micro is one file, the binary itself. It's as simple as that!
### Pre-built binaries
Download the binary from the [releases](https://github.com/zyedidia/micro/releases) page.
Pre-built binaries are distributed with [releases](https://github.com/zyedidia/micro/releases).
### Installation script
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
There is a script which can install micro for you by downloading the latest prebuilt binary. You can find it at <https://getmic.ro>.
You can easily install micro by running
#### Quick-install script
```bash
curl https://getmic.ro | bash
```
The script will place the micro binary in the current directory. See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
The script will place the micro binary in the current directory. From there, you can move it to a directory on your path of your choosing (e.g. `sudo mv micro /usr/bin`). See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
#### Eget
With [Eget](https://github.com/zyedidia/eget) installed, you can easily get a pre-built binary:
```
eget zyedidia/micro
```
Use `--tag VERSION` to download a specific tagged version.
```
eget --tag nightly zyedidia/micro # download the nightly version (compiled every day at midnight UTC)
eget --tag v2.0.8 zyedidia/micro # download version 2.0.8 rather than the latest release
```
You can install `micro` by adding `--to /usr/local/bin` to the `eget` command, or move the binary manually to a directory on your `$PATH` after the download completes.
See [Eget](https://github.com/zyedidia/eget) for more information.
### Package managers
@@ -115,17 +132,19 @@ On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/in
snap install micro --classic
```
**Note for Linux:** for interfacing with the local system clipboard, `xclip` or `xsel`
must be installed. Please see the section on [Linux clipboard support](https://github.com/zyedidia/micro#linux-clipboard-support)
further below.
Micro is also available through other package managers on Linux such as apt, dnf, AUR, Nix, and package managers
Micro is also available through other package managers on Linux such dnf, AUR, Nix, and package managers
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. -->
* Linux: Available in distro-specific package managers.
* `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`).
* `dnf install micro` (Fedora).
* `yay -S micro` (Arch Linux).
* `apt install micro` (Ubuntu and Debian).
* `pacman -S micro` (Arch Linux).
* `emerge app-editors/micro` (Gentoo).
* `zypper install micro-editor` (SUSE)
* `eopkg install micro` (Solus).
* `pacstall -I micro` (Pacstall).
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop).
* `choco install micro`.
@@ -134,12 +153,22 @@ for other operating systems. These packages are not guaranteed to be up-to-date.
* `pkd_add -v micro`.
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](http://www.pkgsrc.org/)-current:
* `pkg_add micro`
* macOS with [MacPorts](https://www.macports.org):
* `sudo port install micro`
**Note for Linux desktop environments:**
For interfacing with the local system clipboard, the following tools need to be installed:
* For X11 `xclip` or `xsel`
* 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.
### Building from source
If your operating system does not have a binary release, but does run Go, you can build from source.
Make sure that you have Go version 1.11 or greater and Go modules are enabled.
Make sure that you have Go version 1.16 or greater and Go modules are enabled.
```
git clone https://github.com/zyedidia/micro
@@ -174,14 +203,17 @@ If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) i
If you still insist on using the default Mac terminal, be sure to set `Use Option key as Meta key` under
`Preferences->Profiles->Keyboard` to use <kbd>option</kbd> as <kbd>alt</kbd>.
### Linux clipboard support
### WSL and Windows Console
On Linux, clipboard support requires:
If you use micro within WSL, it is highly recommended that you use the [Windows
Terminal](https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701?hl=en-us&gl=us)
instead of the default Windows Console.
- On X11, the `xclip` or `xsel` commands (for Ubuntu: `sudo apt install xclip`)
- On Wayland, the `wl-clipboard` command
If you don't have these commands, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
If you must use Windows Console for some reason, note that there is a bug in
Windows Console WSL that causes a font change whenever micro tries to access
the external clipboard via powershell. To fix this, use an internal clipboard
with `set clipboard internal` (though your system clipboard will no longer be
available in micro).
### Colors and syntax highlighting
@@ -217,7 +249,7 @@ Once you have built the editor, start it by running `micro path/to/file.txt` or
micro also supports creating buffers from `stdin`:
```sh
ifconfig | micro
ip a | micro
```
You can move the cursor around with the arrow keys and mouse.
@@ -241,6 +273,8 @@ view the help files here:
I also recommend reading the [tutorial](https://github.com/zyedidia/micro/tree/master/runtime/help/tutorial.md) for
a brief introduction to the more powerful configuration features micro offers.
There is also an unofficial Discord, which you can join at https://discord.gg/nhWR6armnR.
## Contributing
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
@@ -248,6 +282,6 @@ If you find any bugs, please report them! I am also happy to accept pull request
You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues)
to report bugs, ask questions, or suggest new features.
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro).
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro) or the [Discord](https://discord.gg/nhWR6armnR). You can also use the [Discussions](https://github.com/zyedidia/micro/discussions) section on Github for a forum-like setting or for Q&A.
Sometimes I am unresponsive, and I apologize! If that happens, please ping me.

104
assets/micro-logo-drop.svg Normal file
View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 304.70001 103.2"
enable-background="new 0 0 960 560"
xml:space="preserve"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="micro-logo-drop.svg"
width="304.70001"
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
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs19"><filter
style="color-interpolation-filters:sRGB"
inkscape:label="Blur"
id="filter1040"
x="-0.028037383"
y="-0.10549451"
width="1.0560748"
height="1.210989"><feGaussianBlur
stdDeviation="2 2"
result="blur"
id="feGaussianBlur1038" /></filter></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1043"
id="namedview17"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="2.5161345"
inkscape:cx="158.97401"
inkscape:cy="109.69207"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1"
inkscape:pagecheckerboard="0" /><g
id="g838"
transform="translate(-178,-172.8)"
style="fill:#ffffff;fill-opacity:1;filter:url(#filter1040)"><path
d="m 306.8,213.8 v -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 h 2.3 v 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 v 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 h 1 v 2.6 h -15.5 v -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 v -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 v 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 v 2.6 h -15.3 v -2.6 h 0.9 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 v -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 v 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 h 1.1 v 2.6 h -15.6 v -2.6 h 0.8 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 v -18.1 h -5.1 z"
id="path828"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" /><path
d="m 366.4,213.7 v -2.6 c 1.7,-0.2 3.2,-0.5 4.3,-0.9 1.2,-0.4 2.5,-1 4,-1.7 h 2.3 v 24.9 c 0,0.9 0.1,1.5 0.4,2 0.2,0.4 0.6,0.8 1,0.9 0.4,0.2 1.3,0.3 2.4,0.3 h 1.5 v 2.6 h -15.9 v -2.6 h 1.3 c 1.4,0 2.3,-0.1 2.8,-0.4 0.5,-0.2 0.8,-0.6 1,-1.1 0.2,-0.5 0.3,-1.5 0.3,-3.2 v -18.3 h -5.4 z m 7.9,-19.2 c 1,0 1.8,0.3 2.5,1 0.7,0.7 1.1,1.5 1.1,2.5 0,1 -0.4,1.8 -1.1,2.5 -0.7,0.7 -1.6,1.1 -2.5,1.1 -1,0 -1.8,-0.4 -2.5,-1.1 -0.7,-0.7 -1.1,-1.6 -1.1,-2.5 0,-1 0.4,-1.8 1.1,-2.5 0.6,-0.6 1.5,-1 2.5,-1 z"
id="path830"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" /><path
d="m 413.1,230.6 2,1.6 c -3.9,5.2 -8.6,7.8 -14,7.8 -4.2,0 -7.8,-1.5 -10.7,-4.5 -2.9,-3 -4.4,-6.8 -4.4,-11.3 0,-3 0.7,-5.7 2,-8.1 1.3,-2.4 3.2,-4.2 5.6,-5.6 2.4,-1.3 5.2,-2 8.3,-2 3.6,0 6.5,0.9 8.9,2.6 2.4,1.7 3.6,3.5 3.6,5.3 0,1 -0.3,1.7 -0.8,2.2 -0.5,0.5 -1.2,0.8 -1.9,0.8 -0.4,0 -0.7,-0.1 -1.1,-0.3 -0.4,-0.2 -0.7,-0.5 -1.1,-0.9 -0.2,-0.2 -0.5,-0.8 -0.9,-1.7 -0.6,-1.2 -1,-2 -1.3,-2.4 -0.6,-0.8 -1.4,-1.5 -2.4,-2 -0.9,-0.5 -2,-0.7 -3.1,-0.7 -1.8,0 -3.4,0.5 -4.9,1.5 -1.5,1 -2.7,2.4 -3.6,4.3 -0.9,1.9 -1.3,4.2 -1.3,6.8 0,4.1 1.1,7.3 3.3,9.7 1.9,2.1 4.1,3.1 6.7,3.1 1.2,0 2.4,-0.2 3.6,-0.6 1.2,-0.4 2.4,-1 3.5,-1.8 0.9,-0.6 2.2,-1.8 4,-3.8 z"
id="path832"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" /><path
d="m 418.7,213.7 v -2.6 c 1.5,-0.1 2.8,-0.4 4,-0.8 1.2,-0.4 2.5,-1 4,-1.9 h 2.3 v 5.9 c 1.5,-1.8 3.2,-3.2 5.1,-4.3 1.9,-1.1 3.7,-1.6 5.2,-1.6 1.5,0 2.7,0.4 3.6,1.1 0.9,0.7 1.3,1.6 1.3,2.6 0,0.7 -0.3,1.4 -0.9,2 -0.6,0.6 -1.3,0.9 -2.1,0.9 -0.4,0 -0.7,-0.1 -1,-0.2 -0.3,-0.1 -0.7,-0.3 -1.2,-0.7 -1.1,-0.7 -2.1,-1.1 -2.9,-1.1 -1,0 -2.2,0.4 -3.4,1.3 -1.6,1.1 -2.8,2.2 -3.7,3.3 V 232 c 0,1.2 0.1,2.1 0.2,2.5 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.6 1.1,0.7 0.5,0.1 1.3,0.2 2.4,0.2 h 1 v 2.6 h -16 v -2.6 h 1.3 c 1.3,0 2.1,-0.1 2.5,-0.3 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.3,-0.5 0.4,-1.5 0.4,-3 v -18.3 h -5.1 z"
id="path834"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" /><path
d="m 462.8,208.5 c 3,0 5.7,0.6 7.9,1.9 2.2,1.3 4,3.1 5.3,5.5 1.3,2.4 1.9,5.2 1.9,8.3 0,3.1 -0.7,5.9 -2,8.3 -1.3,2.4 -3.1,4.3 -5.4,5.5 -2.3,1.3 -5,1.9 -8.1,1.9 -5,0 -8.8,-1.6 -11.3,-4.7 -2.5,-3.1 -3.8,-6.8 -3.8,-11 0,-3.1 0.7,-5.8 2,-8.2 1.3,-2.4 3.1,-4.2 5.5,-5.6 2.4,-1.2 5.1,-1.9 8,-1.9 z m -0.2,3 c -2.4,0 -4.4,0.9 -6,2.8 -2.1,2.3 -3.1,5.7 -3.1,10.1 0,4.3 0.9,7.5 2.6,9.7 1.6,2 3.8,3 6.5,3 1.8,0 3.3,-0.5 4.7,-1.4 1.4,-0.9 2.5,-2.4 3.3,-4.5 0.8,-2 1.3,-4.4 1.3,-7.2 0,-2.7 -0.5,-5.1 -1.4,-7.2 -0.7,-1.7 -1.8,-3 -3.2,-4 -1.3,-0.8 -2.9,-1.3 -4.7,-1.3 z"
id="path836"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" /></g><g
id="g3"
transform="translate(-178,-172.8)"><path
d="m 306.8,213.8 v -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 h 2.3 v 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 v 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 h 1 v 2.6 h -15.5 v -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 v -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 v 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 v 2.6 h -15.3 v -2.6 h 0.9 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 v -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 v 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 h 1.1 v 2.6 h -15.6 v -2.6 h 0.8 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 v -18.1 h -5.1 z"
id="path5"
inkscape:connector-curvature="0" /><path
d="m 366.4,213.7 v -2.6 c 1.7,-0.2 3.2,-0.5 4.3,-0.9 1.2,-0.4 2.5,-1 4,-1.7 h 2.3 v 24.9 c 0,0.9 0.1,1.5 0.4,2 0.2,0.4 0.6,0.8 1,0.9 0.4,0.2 1.3,0.3 2.4,0.3 h 1.5 v 2.6 h -15.9 v -2.6 h 1.3 c 1.4,0 2.3,-0.1 2.8,-0.4 0.5,-0.2 0.8,-0.6 1,-1.1 0.2,-0.5 0.3,-1.5 0.3,-3.2 v -18.3 h -5.4 z m 7.9,-19.2 c 1,0 1.8,0.3 2.5,1 0.7,0.7 1.1,1.5 1.1,2.5 0,1 -0.4,1.8 -1.1,2.5 -0.7,0.7 -1.6,1.1 -2.5,1.1 -1,0 -1.8,-0.4 -2.5,-1.1 -0.7,-0.7 -1.1,-1.6 -1.1,-2.5 0,-1 0.4,-1.8 1.1,-2.5 0.6,-0.6 1.5,-1 2.5,-1 z"
id="path7"
inkscape:connector-curvature="0" /><path
d="m 413.1,230.6 2,1.6 c -3.9,5.2 -8.6,7.8 -14,7.8 -4.2,0 -7.8,-1.5 -10.7,-4.5 -2.9,-3 -4.4,-6.8 -4.4,-11.3 0,-3 0.7,-5.7 2,-8.1 1.3,-2.4 3.2,-4.2 5.6,-5.6 2.4,-1.3 5.2,-2 8.3,-2 3.6,0 6.5,0.9 8.9,2.6 2.4,1.7 3.6,3.5 3.6,5.3 0,1 -0.3,1.7 -0.8,2.2 -0.5,0.5 -1.2,0.8 -1.9,0.8 -0.4,0 -0.7,-0.1 -1.1,-0.3 -0.4,-0.2 -0.7,-0.5 -1.1,-0.9 -0.2,-0.2 -0.5,-0.8 -0.9,-1.7 -0.6,-1.2 -1,-2 -1.3,-2.4 -0.6,-0.8 -1.4,-1.5 -2.4,-2 -0.9,-0.5 -2,-0.7 -3.1,-0.7 -1.8,0 -3.4,0.5 -4.9,1.5 -1.5,1 -2.7,2.4 -3.6,4.3 -0.9,1.9 -1.3,4.2 -1.3,6.8 0,4.1 1.1,7.3 3.3,9.7 1.9,2.1 4.1,3.1 6.7,3.1 1.2,0 2.4,-0.2 3.6,-0.6 1.2,-0.4 2.4,-1 3.5,-1.8 0.9,-0.6 2.2,-1.8 4,-3.8 z"
id="path9"
inkscape:connector-curvature="0" /><path
d="m 418.7,213.7 v -2.6 c 1.5,-0.1 2.8,-0.4 4,-0.8 1.2,-0.4 2.5,-1 4,-1.9 h 2.3 v 5.9 c 1.5,-1.8 3.2,-3.2 5.1,-4.3 1.9,-1.1 3.7,-1.6 5.2,-1.6 1.5,0 2.7,0.4 3.6,1.1 0.9,0.7 1.3,1.6 1.3,2.6 0,0.7 -0.3,1.4 -0.9,2 -0.6,0.6 -1.3,0.9 -2.1,0.9 -0.4,0 -0.7,-0.1 -1,-0.2 -0.3,-0.1 -0.7,-0.3 -1.2,-0.7 -1.1,-0.7 -2.1,-1.1 -2.9,-1.1 -1,0 -2.2,0.4 -3.4,1.3 -1.6,1.1 -2.8,2.2 -3.7,3.3 V 232 c 0,1.2 0.1,2.1 0.2,2.5 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.6 1.1,0.7 0.5,0.1 1.3,0.2 2.4,0.2 h 1 v 2.6 h -16 v -2.6 h 1.3 c 1.3,0 2.1,-0.1 2.5,-0.3 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.3,-0.5 0.4,-1.5 0.4,-3 v -18.3 h -5.1 z"
id="path11"
inkscape:connector-curvature="0" /><path
d="m 462.8,208.5 c 3,0 5.7,0.6 7.9,1.9 2.2,1.3 4,3.1 5.3,5.5 1.3,2.4 1.9,5.2 1.9,8.3 0,3.1 -0.7,5.9 -2,8.3 -1.3,2.4 -3.1,4.3 -5.4,5.5 -2.3,1.3 -5,1.9 -8.1,1.9 -5,0 -8.8,-1.6 -11.3,-4.7 -2.5,-3.1 -3.8,-6.8 -3.8,-11 0,-3.1 0.7,-5.8 2,-8.2 1.3,-2.4 3.1,-4.2 5.5,-5.6 2.4,-1.2 5.1,-1.9 8,-1.9 z m -0.2,3 c -2.4,0 -4.4,0.9 -6,2.8 -2.1,2.3 -3.1,5.7 -3.1,10.1 0,4.3 0.9,7.5 2.6,9.7 1.6,2 3.8,3 6.5,3 1.8,0 3.3,-0.5 4.7,-1.4 1.4,-0.9 2.5,-2.4 3.3,-4.5 0.8,-2 1.3,-4.4 1.3,-7.2 0,-2.7 -0.5,-5.1 -1.4,-7.2 -0.7,-1.7 -1.8,-3 -3.2,-4 -1.3,-0.8 -2.9,-1.3 -4.7,-1.3 z"
id="path13"
inkscape:connector-curvature="0" /></g><path
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 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"
inkscape:connector-curvature="0"
style="fill:#2e3192" /></svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 103.2 103.2"
enable-background="new 0 0 960 560"
xml:space="preserve"
inkscape:version="0.91 r13725"
sodipodi:docname="micro-logo-notext.svg"
width="103.2"
height="103.2"><metadata
id="metadata9"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs7" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="733"
inkscape:window-height="480"
id="namedview5"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="0.28541667"
inkscape:cx="302"
inkscape:cy="-4"
inkscape:window-x="1699"
inkscape:window-y="277"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" /><path
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
id="path3"
inkscape:connector-curvature="0"
style="fill:#2e3192" /></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,9 @@
#!/bin/sh
set -e
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ]; then
update-alternatives --install /usr/bin/editor editor /usr/bin/micro 40 \
--slave /usr/share/man/man1/editor.1 editor.1 \
/usr/share/man/man1/micro.1
fi

View File

@@ -0,0 +1,7 @@
#!/bin/sh
set -e
if [ "$1" != "upgrade" ]; then
update-alternatives --remove editor /usr/bin/micro
fi

View File

@@ -14,6 +14,8 @@ of modern terminals. It comes as one single, batteries-included, static binary w
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
Use Ctrl-q to quit, Ctrl-s to save, and Ctrl-g to open the in-editor help menu.
.SH OPTIONS
.PP
\-clean
@@ -51,7 +53,7 @@ Enable debug mode (enables logging to ./log.txt)
Show the version number and information
.RE
Micro's plugin's can be managed at the command line with the following commands.
Micro's plugins can be managed at the command line with the following commands.
.RS 4
.PP
@@ -119,5 +121,5 @@ and to report any newly encountered bugs you may find. We strive to correct
bugs as swiftly as possible.
.SH COPYRIGHT
Copyright \(co 2020 Zachary Yedidia, et al.
See /usr/share/doc/micro/LICENSE and /usr/share/doc/micro/AUTHORS for more information.
Copyright \(co 2020 Zachary Yedidia, et al. MIT license.
See \fBhttps://github.com/zyedidia/micro\fP for details.

View File

@@ -6,10 +6,10 @@ Comment=Edit text files in a terminal
Icon=micro
Type=Application
Categories=terminal;TextEditor;
Categories=Utility;TextEditor;Development;
Keywords=text;editor;syntax;terminal;
Exec=micro %U
Exec=micro %F
StartupNotify=false
Terminal=true
MimeType=text/plain;text/x-chdr;text/x-csrc;text/x-c++hdr;text/x-c++src;text/x-java;text/x-dsrc;text/x-pascal;text/x-perl;text/x-python;application/x-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/xml;text/html;text/css;text/x-sql;text/x-diff;

View File

@@ -23,12 +23,9 @@ func shouldContinue() bool {
return false
}
if len(text) <= 1 {
// default continue
return true
}
text = strings.TrimRight(text, "\r\n")
return strings.ToLower(text)[0] == 'y'
return len(text) == 0 || strings.ToLower(text)[0] == 'y'
}
// CleanConfig performs cleanup in the user's configuration directory
@@ -41,6 +38,9 @@ func CleanConfig() {
return
}
fmt.Println("Cleaning default settings")
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
// detect unused options
var unusedOptions []string
defaultSettings := config.DefaultAllSettings()
@@ -94,14 +94,13 @@ func CleanConfig() {
file, e := os.Open(fname)
if e == nil {
defer file.Close()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&buffer)
if err != nil && f.Name() != "history" {
badFiles = append(badFiles, fname)
}
file.Close()
}
}
@@ -111,15 +110,21 @@ func CleanConfig() {
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
if shouldContinue() {
removed := 0
for _, f := range badFiles {
err := os.Remove(f)
if err != nil {
fmt.Println(err)
continue
}
removed++
}
fmt.Println("Removed badly formatted files")
if removed == 0 {
fmt.Println("Failed to remove files")
} else {
fmt.Printf("Removed %d badly formatted files\n", removed)
}
fmt.Print("\n\n")
}
}

View File

@@ -50,12 +50,11 @@ func luaImportMicro() *lua.LTable {
ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() action.Pane {
return action.MainTab().CurPane()
}))
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, func() *action.Tab {
return action.MainTab()
}))
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, action.MainTab))
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
return action.Tabs
}))
ulua.L.SetField(pkg, "Lock", luar.New(ulua.L, ulua.Lock))
return pkg
}
@@ -119,6 +118,9 @@ func luaImportMicroBuffer() *lua.LTable {
ulua.L.SetField(pkg, "Loc", luar.New(ulua.L, func(x, y int) buffer.Loc {
return buffer.Loc{x, y}
}))
ulua.L.SetField(pkg, "SLoc", luar.New(ulua.L, func(line, row int) display.SLoc {
return display.SLoc{line, row}
}))
ulua.L.SetField(pkg, "BTDefault", luar.New(ulua.L, buffer.BTDefault.Kind))
ulua.L.SetField(pkg, "BTHelp", luar.New(ulua.L, buffer.BTHelp.Kind))
ulua.L.SetField(pkg, "BTLog", luar.New(ulua.L, buffer.BTLog.Kind))
@@ -145,6 +147,10 @@ func luaImportMicroUtil() *lua.LTable {
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
ulua.L.SetField(pkg, "String", luar.New(ulua.L, util.String))
ulua.L.SetField(pkg, "Unzip", luar.New(ulua.L, util.Unzip))
ulua.L.SetField(pkg, "Version", luar.New(ulua.L, util.Version))
ulua.L.SetField(pkg, "SemVersion", luar.New(ulua.L, util.SemVersion))
ulua.L.SetField(pkg, "HttpRequest", luar.New(ulua.L, util.HttpRequest))
ulua.L.SetField(pkg, "CharacterCountInString", luar.New(ulua.L, util.CharacterCountInString))
ulua.L.SetField(pkg, "RuneStr", luar.New(ulua.L, func(r rune) string {
return string(r)

View File

@@ -4,11 +4,15 @@ import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/signal"
"regexp"
"runtime"
"runtime/pprof"
"sort"
"strconv"
"syscall"
"time"
"github.com/go-errors/errors"
@@ -16,16 +20,17 @@ import (
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/clipboard"
"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/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
var (
// Event channel
events chan tcell.Event
autosave chan bool
// Command line flags
@@ -33,9 +38,13 @@ var (
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
flagOptions = flag.Bool("options", false, "Show all option help")
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
flagProfile = flag.Bool("profile", false, "Enable CPU profiling (writes profile info to ./micro.prof)")
flagPlugin = flag.String("plugin", "", "Plugin command")
flagClean = flag.Bool("clean", false, "Clean configuration directory")
optionFlags map[string]*string
sigterm chan os.Signal
sighup chan os.Signal
)
func InitFlags() {
@@ -52,6 +61,9 @@ func InitFlags() {
fmt.Println(" \tShow all option help")
fmt.Println("-debug")
fmt.Println(" \tEnable debug mode (enables logging to ./log.txt)")
fmt.Println("-profile")
fmt.Println(" \tEnable CPU profiling (writes profile info to ./micro.prof")
fmt.Println(" \tso it can be analyzed later with \"go tool pprof micro.prof\")")
fmt.Println("-version")
fmt.Println(" \tShow the version number and information")
@@ -132,7 +144,7 @@ func DoPluginFlags() {
// LoadInput determines which files should be loaded into buffers
// based on the input stored in flag.Args()
func LoadInput() []*buffer.Buffer {
func LoadInput(args []string) []*buffer.Buffer {
// There are a number of ways micro should start given its input
// 1. If it is given a files in flag.Args(), it should open those
@@ -147,7 +159,6 @@ func LoadInput() []*buffer.Buffer {
var filename string
var input []byte
var err error
args := flag.Args()
buffers := make([]*buffer.Buffer, 0, len(args))
btype := buffer.BTDefault
@@ -222,15 +233,21 @@ func main() {
os.Exit(0)
}()
// runtime.SetCPUProfileRate(400)
// f, _ := os.Create("micro.prof")
// pprof.StartCPUProfile(f)
// defer pprof.StopCPUProfile()
var err error
InitFlags()
if *flagProfile {
f, err := os.Create("micro.prof")
if err != nil {
log.Fatal("error creating CPU profile: ", err)
}
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("error starting CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
InitLog()
err = config.InitConfigDir(*flagConfigDir)
@@ -262,18 +279,29 @@ func main() {
DoPluginFlags()
screen.Init()
err = screen.Init()
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
os.Exit(1)
}
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
clipErr := clipboard.Initialize(m)
defer func() {
if err := recover(); err != nil {
screen.Screen.Fini()
fmt.Println("Micro encountered an error:", err)
if screen.Screen != nil {
screen.Screen.Fini()
}
if e, ok := err.(*lua.ApiError); ok {
fmt.Println("Lua API error:", e)
} else {
fmt.Println("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
}
// backup all open buffers
for _, b := range buffer.OpenBuffers {
b.Backup(false)
b.Backup()
}
// Print the stack trace too
fmt.Print(errors.Wrap(err, 2).ErrorStack())
os.Exit(1)
}
}()
@@ -291,7 +319,15 @@ func main() {
screen.TermMessage(err)
}
b := LoadInput()
err = config.RunPluginFn("preinit")
if err != nil {
screen.TermMessage(err)
}
action.InitGlobals()
buffer.SetMessager(action.InfoBar)
args := flag.Args()
b := LoadInput(args)
if len(b) == 0 {
// No buffers to open
@@ -300,14 +336,32 @@ func main() {
}
action.InitTabs(b)
action.InitGlobals()
err = config.RunPluginFn("init")
if err != nil {
screen.TermMessage(err)
}
events = make(chan tcell.Event)
err = config.RunPluginFn("postinit")
if err != nil {
screen.TermMessage(err)
}
if clipErr != nil {
log.Println(clipErr, " or change 'clipboard' option")
}
if a := config.GetGlobalOption("autosave").(float64); a > 0 {
config.SetAutoTime(int(a))
config.StartAutoSave()
}
screen.Events = make(chan tcell.Event)
sigterm = make(chan os.Signal, 1)
sighup = make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
signal.Notify(sighup, syscall.SIGHUP)
// Here is the event loop which runs in a separate thread
go func() {
@@ -316,7 +370,7 @@ func main() {
e := screen.Screen.PollEvent()
screen.Unlock()
if e != nil {
events <- e
screen.Events <- e
}
}
}()
@@ -329,15 +383,12 @@ func main() {
// wait for initial resize event
select {
case event := <-events:
case event := <-screen.Events:
action.Tabs.HandleEvent(event)
case <-time.After(10 * time.Millisecond):
// time out after 10ms
}
// Since this loop is very slow (waits for user input every time) it's
// okay to be inefficient and run it via a function every time
// We do this so we can recover from panics without crashing the editor
for {
DoEvent()
}
@@ -347,16 +398,6 @@ func main() {
func DoEvent() {
var event tcell.Event
// recover from errors without crashing the editor
defer func() {
if err := recover(); err != nil {
if e, ok := err.(*lua.ApiError); ok {
screen.TermMessage("Lua API error:", e)
} else {
screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
}
}
}()
// Display everything
screen.Screen.Fill(' ', config.DefStyle)
screen.Screen.HideCursor()
@@ -372,22 +413,48 @@ func DoEvent() {
select {
case f := <-shell.Jobs:
// 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)
ulua.Lock.Unlock()
case <-config.Autosave:
ulua.Lock.Lock()
for _, b := range buffer.OpenBuffers {
b.Save()
}
ulua.Lock.Unlock()
case <-shell.CloseTerms:
case event = <-events:
case event = <-screen.Events:
case <-screen.DrawChan():
for len(screen.DrawChan()) > 0 {
<-screen.DrawChan()
}
case <-sighup:
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
os.Exit(0)
case <-sigterm:
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
if screen.Screen != nil {
screen.Screen.Fini()
}
os.Exit(0)
}
ulua.Lock.Lock()
// if event != nil {
if action.InfoBar.HasPrompt {
action.InfoBar.HandleEvent(event)
} else {
action.Tabs.HandleEvent(event)
}
// }
ulua.Lock.Unlock()
}

339
cmd/micro/micro_test.go Normal file
View File

@@ -0,0 +1,339 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"testing"
"github.com/go-errors/errors"
"github.com/stretchr/testify/assert"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell/v2"
)
var tempDir string
var sim tcell.SimulationScreen
func init() {
screen.Events = make(chan tcell.Event, 8)
}
func startup(args []string) (tcell.SimulationScreen, error) {
var err error
tempDir, err = ioutil.TempDir("", "micro_test")
if err != nil {
return nil, err
}
err = config.InitConfigDir(tempDir)
if err != nil {
return nil, err
}
config.InitRuntimeFiles()
err = config.ReadSettings()
if err != nil {
return nil, err
}
err = config.InitGlobalSettings()
if err != nil {
return nil, err
}
s, err := screen.InitSimScreen()
if err != nil {
return nil, err
}
defer func() {
if err := recover(); err != nil {
screen.Screen.Fini()
fmt.Println("Micro encountered an error:", err)
// backup all open buffers
for _, b := range buffer.OpenBuffers {
b.Backup()
}
// Print the stack trace too
log.Fatalf(errors.Wrap(err, 2).ErrorStack())
}
}()
err = config.LoadAllPlugins()
if err != nil {
screen.TermMessage(err)
}
action.InitBindings()
action.InitCommands()
err = config.InitColorscheme()
if err != nil {
return nil, err
}
b := LoadInput(args)
if len(b) == 0 {
return nil, errors.New("No buffers opened")
}
action.InitTabs(b)
action.InitGlobals()
err = config.RunPluginFn("init")
if err != nil {
return nil, err
}
s.InjectResize()
handleEvent()
return s, nil
}
func cleanup() {
os.RemoveAll(tempDir)
}
func handleEvent() {
screen.Lock()
e := screen.Screen.PollEvent()
screen.Unlock()
if e != nil {
screen.Events <- e
}
DoEvent()
}
func injectKey(key tcell.Key, r rune, mod tcell.ModMask) {
sim.InjectKey(key, r, mod)
handleEvent()
}
func injectMouse(x, y int, buttons tcell.ButtonMask, mod tcell.ModMask) {
sim.InjectMouse(x, y, buttons, mod)
handleEvent()
}
func injectString(str string) {
// the tcell simulation screen event channel can only handle
// 10 events at once, so we need to divide up the key events
// into chunks of 10 and handle the 10 events before sending
// another chunk of events
iters := len(str) / 10
extra := len(str) % 10
for i := 0; i < iters; i++ {
s := i * 10
e := i*10 + 10
sim.InjectKeyBytes([]byte(str[s:e]))
for i := 0; i < 10; i++ {
handleEvent()
}
}
sim.InjectKeyBytes([]byte(str[len(str)-extra:]))
for i := 0; i < extra; i++ {
handleEvent()
}
}
func openFile(file string) {
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("open %s", file))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
}
func createTestFile(name string, content string) (string, error) {
testf, err := ioutil.TempFile("", name)
if err != nil {
return "", err
}
if _, err := testf.Write([]byte(content)); err != nil {
return "", err
}
if err := testf.Close(); err != nil {
return "", err
}
return testf.Name(), nil
}
func TestMain(m *testing.M) {
var err error
sim, err = startup([]string{})
if err != nil {
log.Fatalln(err)
}
retval := m.Run()
cleanup()
os.Exit(retval)
}
func TestSimpleEdit(t *testing.T) {
file, err := createTestFile("micro_simple_edit_test", "base content")
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file)
var buf *buffer.Buffer
for _, b := range buffer.OpenBuffers {
if b.Path == file {
buf = b
}
}
if buf == nil {
t.Errorf("Could not find buffer %s", file)
return
}
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectKey(tcell.KeyUp, 0, tcell.ModNone)
injectString("first line")
// test both kinds of backspace
for i := 0; i < len("ne"); i++ {
injectKey(tcell.KeyBackspace, rune(tcell.KeyBackspace), tcell.ModNone)
}
for i := 0; i < len(" li"); i++ {
injectKey(tcell.KeyBackspace2, rune(tcell.KeyBackspace2), tcell.ModNone)
}
injectString("foobar")
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, "firstfoobar\nbase content\n", string(data))
}
func TestMouse(t *testing.T) {
file, err := createTestFile("micro_mouse_test", "base content")
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file)
// buffer:
// base content
// the selections need to happen at different locations to avoid a double click
injectMouse(3, 0, tcell.Button1, tcell.ModNone)
injectKey(tcell.KeyLeft, 0, tcell.ModNone)
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
injectString("secondline")
// buffer:
// secondlinebase content
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
// buffer:
// secondline
// base content
injectMouse(2, 0, tcell.Button1, tcell.ModNone)
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
// buffer:
//
// secondline
// base content
injectKey(tcell.KeyUp, 0, tcell.ModNone)
injectString("firstline")
// buffer:
// firstline
// secondline
// base content
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
}
var srTestStart = `foo
foo
foofoofoo
Ernleȝe foo æðelen
`
var srTest2 = `test_string
test_string
test_stringtest_stringtest_string
Ernleȝe test_string æðelen
`
var srTest3 = `test_foo
test_string
test_footest_stringtest_foo
Ernleȝe test_string æðelen
`
func TestSearchAndReplace(t *testing.T) {
file, err := createTestFile("micro_search_replace_test", srTestStart)
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file)
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string"))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, srTest2, string(data))
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("replace %s %s", "string", "foo"))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectString("ynyny")
injectKey(tcell.KeyEscape, 0, tcell.ModNone)
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err = ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, srTest3, string(data))
}
func TestMultiCursor(t *testing.T) {
// TODO
}
func TestSettingsPersistence(t *testing.T) {
// TODO
}
// more tests (rendering, tabs, plugins)?

View File

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

20
go.mod
View File

@@ -8,28 +8,24 @@ require (
github.com/mattn/go-isatty v0.0.11
github.com/mattn/go-runewidth v0.0.7
github.com/mitchellh/go-homedir v1.1.0
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff
github.com/sergi/go-diff v1.1.0
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect
github.com/stretchr/testify v1.4.0
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834
github.com/zyedidia/clipper v0.1.1
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/pty v2.0.0+incompatible // indirect
github.com/zyedidia/tcell v1.4.6
github.com/zyedidia/pty v1.1.20 // indirect
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8 // indirect
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.2.7
gopkg.in/yaml.v2 v2.2.8
layeh.com/gopher-luar v1.0.7
)
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
replace github.com/mattn/go-runewidth => github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059
replace github.com/mattn/go-runewidth => github.com/zyedidia/go-runewidth v0.0.12
go 1.11
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.7
go 1.16

50
go.sum
View File

@@ -17,47 +17,54 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/layeh/gopher-luar v1.0.7 h1:wnfZhYiJM748y1A4qYBfcFeMY9HWbdERny+ZL0f/jWc=
github.com/layeh/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059 h1:/+h2b6i15wh4EWsFkfdNdBE1jjGA872tpXEyhPM5aYg=
github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0=
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/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/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834 h1:0nOfq3JwYRiY3+nwfWVQYEaXDmGCQgj3RKoqTifLzP4=
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
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/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/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
github.com/zyedidia/go-runewidth v0.0.12 h1:aHWj8qL3aH7caRzoPBJXe1pEaZBXHpKtfTuiBo5p74Q=
github.com/zyedidia/go-runewidth v0.0.12/go.mod h1:vF8djYdLmG8BJaUZ4CznFYCJ3pFR8m4B4VinTvTTarU=
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655 h1:Z3RhH6hvcSx7eX6Q/pP6YVsgea/1eMDG99vtWwi3nK4=
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655/go.mod h1:1sTqqO+kcYzZp43M5VsJe1tns9IzlSeC9jB6c2+o/5Y=
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5 h1:Zs6mpwXvlqpF9zHl5XaN0p5V4J9XvP+WBuiuXyIgqvc=
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5/go.mod h1:c1r+Ob9tUTPB0FKWO1+x+Hsc/zNa45WdGq7Y38Ybip0=
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d h1:zmDMkh22zXOB7gz8jFaI4GpI7llsPgzm38/jG0UgxjE=
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I=
github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
github.com/zyedidia/tcell v1.4.6 h1:8OYvZpUyqYQ3nigenBwOtnY3fXWEHekbm6QYchBeOxs=
github.com/zyedidia/tcell v1.4.6/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
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/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/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -67,16 +74,11 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
layeh.com/gopher-luar v1.0.7 h1:53iv6CCkRs5wyofZ+qVXcyAYQOIG52s6pt4xkqZdq7k=
layeh.com/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -1,39 +1,49 @@
package action
import (
"errors"
"fmt"
"io/fs"
"os"
"regexp"
"runtime"
"strings"
"time"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/clipboard"
"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/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
// ScrollUp is not an action
func (h *BufPane) ScrollUp(n int) {
v := h.GetView()
if v.StartLine >= n {
v.StartLine -= n
h.SetView(v)
} else {
v.StartLine = 0
}
v.StartLine = h.Scroll(v.StartLine, -n)
h.SetView(v)
}
// ScrollDown is not an action
func (h *BufPane) ScrollDown(n int) {
v := h.GetView()
if v.StartLine <= h.Buf.LinesNum()-1-n {
v.StartLine += n
h.SetView(v)
v.StartLine = h.Scroll(v.StartLine, n)
h.SetView(v)
}
// ScrollAdjust can be used to shift the view so that the last line is at the
// bottom if the user has scrolled past the last line.
func (h *BufPane) ScrollAdjust() {
v := h.GetView()
end := h.SLocFromLoc(h.Buf.End())
if h.Diff(v.StartLine, end) < h.BufView().Height-1 {
v.StartLine = h.Scroll(end, -h.BufView().Height+1)
}
h.SetView(v)
}
// MousePress is the event that should happen when a normal click happens
@@ -59,7 +69,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
h.doubleClick = false
h.Cursor.SelectLine()
h.Cursor.CopySelection("primary")
h.Cursor.CopySelection(clipboard.PrimaryReg)
} else {
// Double click
h.lastClickTime = time.Now()
@@ -68,7 +78,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
h.tripleClick = false
h.Cursor.SelectWord()
h.Cursor.CopySelection("primary")
h.Cursor.CopySelection(clipboard.PrimaryReg)
}
} else {
h.doubleClick = false
@@ -92,6 +102,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
h.Cursor.StoreVisualX()
h.lastLoc = mouseLoc
h.Relocate()
return true
}
@@ -110,22 +121,55 @@ func (h *BufPane) ScrollDownAction() bool {
// Center centers the view on the cursor
func (h *BufPane) Center() bool {
v := h.GetView()
v.StartLine = h.Cursor.Y - v.Height/2
if v.StartLine+v.Height > h.Buf.LinesNum() {
v.StartLine = h.Buf.LinesNum() - v.Height
}
if v.StartLine < 0 {
v.StartLine = 0
}
v.StartLine = h.Scroll(h.SLocFromLoc(h.Cursor.Loc), -h.BufView().Height/2)
h.SetView(v)
h.Relocate()
h.ScrollAdjust()
return true
}
// MoveCursorUp is not an action
func (h *BufPane) MoveCursorUp(n int) {
if !h.Buf.Settings["softwrap"].(bool) {
h.Cursor.UpN(n)
} else {
vloc := h.VLocFromLoc(h.Cursor.Loc)
sloc := h.Scroll(vloc.SLoc, -n)
if sloc == vloc.SLoc {
// we are at the beginning of buffer
h.Cursor.Loc = h.Buf.Start()
h.Cursor.LastVisualX = 0
} else {
vloc.SLoc = sloc
vloc.VisualX = h.Cursor.LastVisualX
h.Cursor.Loc = h.LocFromVLoc(vloc)
}
}
}
// MoveCursorDown is not an action
func (h *BufPane) MoveCursorDown(n int) {
if !h.Buf.Settings["softwrap"].(bool) {
h.Cursor.DownN(n)
} else {
vloc := h.VLocFromLoc(h.Cursor.Loc)
sloc := h.Scroll(vloc.SLoc, n)
if sloc == vloc.SLoc {
// we are at the end of buffer
h.Cursor.Loc = h.Buf.End()
vloc = h.VLocFromLoc(h.Cursor.Loc)
h.Cursor.LastVisualX = vloc.VisualX
} else {
vloc.SLoc = sloc
vloc.VisualX = h.Cursor.LastVisualX
h.Cursor.Loc = h.LocFromVLoc(vloc)
}
}
}
// CursorUp moves the cursor up
func (h *BufPane) CursorUp() bool {
h.Cursor.Deselect(true)
h.Cursor.Up()
h.MoveCursorUp(1)
h.Relocate()
return true
}
@@ -133,7 +177,7 @@ func (h *BufPane) CursorUp() bool {
// CursorDown moves the cursor down
func (h *BufPane) CursorDown() bool {
h.Cursor.Deselect(true)
h.Cursor.Down()
h.MoveCursorDown(1)
h.Relocate()
return true
}
@@ -211,7 +255,7 @@ func (h *BufPane) SelectUp() bool {
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
h.Cursor.Up()
h.MoveCursorUp(1)
h.Cursor.SelectTo(h.Cursor.Loc)
h.Relocate()
return true
@@ -222,7 +266,7 @@ func (h *BufPane) SelectDown() bool {
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
h.Cursor.Down()
h.MoveCursorDown(1)
h.Cursor.SelectTo(h.Cursor.Loc)
h.Relocate()
return true
@@ -747,10 +791,27 @@ func (h *BufPane) SaveAsCB(action string, callback func()) bool {
return
}
filename := strings.Join(args, " ")
noPrompt := h.saveBufToFile(filename, action, callback)
if noPrompt {
h.completeAction(action)
fileinfo, err := os.Stat(filename)
if err != nil {
if os.IsNotExist(err) {
noPrompt := h.saveBufToFile(filename, action, callback)
if noPrompt {
h.completeAction(action)
return
}
}
}
InfoBar.YNPrompt(
fmt.Sprintf("the file %s already exists in the directory, would you like to overwrite? Y/n", fileinfo.Name()),
func(yes, canceled bool) {
if yes && !canceled {
noPrompt := h.saveBufToFile(filename, action, callback)
if noPrompt {
h.completeAction(action)
}
}
},
)
}
})
return false
@@ -767,7 +828,7 @@ func (h *BufPane) SaveAs() bool {
func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
err := h.Buf.SaveAs(filename)
if err != nil {
if strings.HasSuffix(err.Error(), "permission denied") {
if errors.Is(err, fs.ErrPermission) {
saveWithSudo := func() {
err = h.Buf.SaveAsWithSudo(filename)
if err != nil {
@@ -784,12 +845,15 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
if h.Buf.Settings["autosu"].(bool) {
saveWithSudo()
} else {
InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
if yes && !canceled {
saveWithSudo()
h.completeAction(action)
}
})
InfoBar.YNPrompt(
fmt.Sprintf("Permission denied. Do you want to save this file using %s? (y,n)", config.GlobalSettings["sucmd"].(string)),
func(yes, canceled bool) {
if yes && !canceled {
saveWithSudo()
h.completeAction(action)
}
},
)
return false
}
} else {
@@ -816,27 +880,55 @@ func (h *BufPane) FindLiteral() bool {
return h.find(false)
}
// Search searches for a given string/regex in the buffer and selects the next
// match if a match is found
// This function behaves the same way as Find and FindLiteral actions:
// it affects the buffer's LastSearch and LastSearchRegex (saved searches)
// for use with FindNext and FindPrevious, and turns HighlightSearch on or off
// according to hlsearch setting
func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
if err != nil {
return err
}
if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.GotoLoc(h.Cursor.CurSelection[1])
h.Buf.LastSearch = str
h.Buf.LastSearchRegex = useRegex
h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
} else {
h.Cursor.ResetSelection()
}
return nil
}
func (h *BufPane) find(useRegex bool) bool {
h.searchOrig = h.Cursor.Loc
prompt := "Find: "
if useRegex {
prompt = "Find (regex): "
}
InfoBar.Prompt(prompt, "", "Find", func(resp string) {
// Event callback
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.GotoLoc(match[1])
} else {
h.Cursor.GotoLoc(h.searchOrig)
h.Cursor.ResetSelection()
var eventCallback func(resp string)
if h.Buf.Settings["incsearch"].(bool) {
eventCallback = func(resp string) {
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.GotoLoc(match[1])
} else {
h.GotoLoc(h.searchOrig)
h.Cursor.ResetSelection()
}
}
h.Relocate()
}, func(resp string, canceled bool) {
}
findCallback := func(resp string, canceled bool) {
// Finished callback
if !canceled {
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
@@ -848,9 +940,10 @@ func (h *BufPane) find(useRegex bool) bool {
h.Cursor.SetSelectionEnd(match[1])
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
h.lastSearch = resp
h.lastSearchRegex = useRegex
h.GotoLoc(h.Cursor.CurSelection[1])
h.Buf.LastSearch = resp
h.Buf.LastSearchRegex = useRegex
h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
} else {
h.Cursor.ResetSelection()
InfoBar.Message("No matches found")
@@ -858,9 +951,27 @@ func (h *BufPane) find(useRegex bool) bool {
} else {
h.Cursor.ResetSelection()
}
h.Relocate()
})
}
pattern := string(h.Cursor.GetSelection())
if eventCallback != nil && pattern != "" {
eventCallback(pattern)
}
InfoBar.Prompt(prompt, pattern, "Find", eventCallback, findCallback)
if pattern != "" {
InfoBar.SelectAll()
}
return true
}
// ToggleHighlightSearch toggles highlighting all instances of the last used search term
func (h *BufPane) ToggleHighlightSearch() bool {
h.Buf.HighlightSearch = !h.Buf.HighlightSearch
return true
}
// UnhighlightSearch unhighlights all instances of the last used search term
func (h *BufPane) UnhighlightSearch() bool {
h.Buf.HighlightSearch = false
return true
}
@@ -874,7 +985,7 @@ func (h *BufPane) FindNext() bool {
if h.Cursor.HasSelection() {
searchLoc = h.Cursor.CurSelection[1]
}
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -883,11 +994,10 @@ func (h *BufPane) FindNext() bool {
h.Cursor.SetSelectionEnd(match[1])
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.Loc = h.Cursor.CurSelection[1]
h.GotoLoc(h.Cursor.CurSelection[1])
} else {
h.Cursor.ResetSelection()
}
h.Relocate()
return true
}
@@ -901,7 +1011,7 @@ func (h *BufPane) FindPrevious() bool {
if h.Cursor.HasSelection() {
searchLoc = h.Cursor.CurSelection[0]
}
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -910,11 +1020,10 @@ func (h *BufPane) FindPrevious() bool {
h.Cursor.SetSelectionEnd(match[1])
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.Loc = h.Cursor.CurSelection[1]
h.GotoLoc(h.Cursor.CurSelection[1])
} else {
h.Cursor.ResetSelection()
}
h.Relocate()
return true
}
@@ -937,33 +1046,27 @@ func (h *BufPane) Redo() bool {
// Copy the selection to the system clipboard
func (h *BufPane) Copy() bool {
if h.Cursor.HasSelection() {
h.Cursor.CopySelection("clipboard")
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = true
if clipboard.Unsupported {
InfoBar.Message("Copied selection (install xclip for external clipboard)")
} else {
InfoBar.Message("Copied selection")
}
InfoBar.Message("Copied selection")
}
h.Relocate()
return true
}
// Copy the current line to the clipboard
// CopyLine copies the current line to the clipboard
func (h *BufPane) CopyLine() bool {
if h.Cursor.HasSelection() {
return false
} else {
h.Cursor.SelectLine()
h.Cursor.CopySelection("clipboard")
h.freshClip = true
if clipboard.Unsupported {
InfoBar.Message("Copied line (install xclip for external clipboard)")
} else {
InfoBar.Message("Copied line")
}
}
origLoc := h.Cursor.Loc
h.Cursor.SelectLine()
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = true
InfoBar.Message("Copied line")
h.Cursor.Deselect(true)
h.Cursor.Loc = origLoc
h.Relocate()
return true
}
@@ -974,15 +1077,15 @@ func (h *BufPane) CutLine() bool {
if !h.Cursor.HasSelection() {
return false
}
if h.freshClip == true {
if h.freshClip {
if h.Cursor.HasSelection() {
if clip, err := clipboard.ReadAll("clipboard"); err != nil {
// messenger.Error(err)
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
InfoBar.Error(err)
} else {
clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
}
}
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
h.Copy()
}
h.freshClip = true
@@ -997,7 +1100,7 @@ func (h *BufPane) CutLine() bool {
// Cut the selection to the system clipboard
func (h *BufPane) Cut() bool {
if h.Cursor.HasSelection() {
h.Cursor.CopySelection("clipboard")
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
h.freshClip = true
@@ -1005,14 +1108,15 @@ func (h *BufPane) Cut() bool {
h.Relocate()
return true
} else {
return h.CutLine()
}
return h.CutLine()
}
// DuplicateLine duplicates the current line or selection
func (h *BufPane) DuplicateLine() bool {
var infoMessage = "Duplicated line"
if h.Cursor.HasSelection() {
infoMessage = "Duplicated selection"
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
} else {
h.Cursor.End()
@@ -1020,7 +1124,7 @@ func (h *BufPane) DuplicateLine() bool {
// h.Cursor.Right()
}
InfoBar.Message("Duplicated line")
InfoBar.Message(infoMessage)
h.Relocate()
return true
}
@@ -1123,16 +1227,24 @@ func (h *BufPane) MoveLinesDown() bool {
// Paste whatever is in the system clipboard into the buffer
// Delete and paste if the user has a selection
func (h *BufPane) Paste() bool {
clip, _ := clipboard.ReadAll("clipboard")
h.paste(clip)
clip, err := clipboard.ReadMulti(clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
if err != nil {
InfoBar.Error(err)
} else {
h.paste(clip)
}
h.Relocate()
return true
}
// PastePrimary pastes from the primary clipboard (only use on linux)
func (h *BufPane) PastePrimary() bool {
clip, _ := clipboard.ReadAll("primary")
h.paste(clip)
clip, err := clipboard.ReadMulti(clipboard.PrimaryReg, h.Cursor.Num, h.Buf.NumCursors())
if err != nil {
InfoBar.Error(err)
} else {
h.paste(clip)
}
h.Relocate()
return true
}
@@ -1141,7 +1253,7 @@ func (h *BufPane) paste(clip string) {
if h.Buf.Settings["smartpaste"].(bool) {
if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
clip = strings.ReplaceAll(clip, "\n", "\n"+string(leadingWS))
}
}
@@ -1153,11 +1265,7 @@ func (h *BufPane) paste(clip string) {
h.Buf.Insert(h.Cursor.Loc, clip)
// h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
h.freshClip = false
if clipboard.Unsupported {
InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
} else {
InfoBar.Message("Pasted clipboard")
}
InfoBar.Message("Pasted clipboard")
}
// JumpToMatchingBrace moves the cursor to the matching brace if it is
@@ -1174,14 +1282,12 @@ func (h *BufPane) JumpToMatchingBrace() bool {
} else {
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
}
} else {
return false
h.Relocate()
return true
}
}
}
h.Relocate()
return true
return false
}
// SelectAll selects the entire buffer
@@ -1218,45 +1324,29 @@ func (h *BufPane) JumpLine() bool {
// Start moves the viewport to the start of the buffer
func (h *BufPane) Start() bool {
v := h.GetView()
v.StartLine = 0
v.StartLine = display.SLoc{0, 0}
h.SetView(v)
return true
}
// End moves the viewport to the end of the buffer
func (h *BufPane) End() bool {
// TODO: softwrap problems?
v := h.GetView()
if v.Height > h.Buf.LinesNum() {
v.StartLine = 0
h.SetView(v)
} else {
v.StartLine = h.Buf.LinesNum() - v.Height
h.SetView(v)
}
v.StartLine = h.Scroll(h.SLocFromLoc(h.Buf.End()), -h.BufView().Height+1)
h.SetView(v)
return true
}
// PageUp scrolls the view up a page
func (h *BufPane) PageUp() bool {
v := h.GetView()
if v.StartLine > v.Height {
h.ScrollUp(v.Height)
} else {
v.StartLine = 0
}
h.SetView(v)
h.ScrollUp(h.BufView().Height)
return true
}
// PageDown scrolls the view down a page
func (h *BufPane) PageDown() bool {
v := h.GetView()
if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
h.ScrollDown(v.Height)
} else if h.Buf.LinesNum() >= v.Height {
v.StartLine = h.Buf.LinesNum() - v.Height
}
h.ScrollDown(h.BufView().Height)
h.ScrollAdjust()
return true
}
@@ -1265,7 +1355,7 @@ func (h *BufPane) SelectPageUp() bool {
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
h.Cursor.UpN(h.GetView().Height)
h.MoveCursorUp(h.BufView().Height)
h.Cursor.SelectTo(h.Cursor.Loc)
h.Relocate()
return true
@@ -1276,7 +1366,7 @@ func (h *BufPane) SelectPageDown() bool {
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
h.Cursor.DownN(h.GetView().Height)
h.MoveCursorDown(h.BufView().Height)
h.Cursor.SelectTo(h.Cursor.Loc)
h.Relocate()
return true
@@ -1291,7 +1381,7 @@ func (h *BufPane) CursorPageUp() bool {
h.Cursor.ResetSelection()
h.Cursor.StoreVisualX()
}
h.Cursor.UpN(h.GetView().Height)
h.MoveCursorUp(h.BufView().Height)
h.Relocate()
return true
}
@@ -1305,34 +1395,21 @@ func (h *BufPane) CursorPageDown() bool {
h.Cursor.ResetSelection()
h.Cursor.StoreVisualX()
}
h.Cursor.DownN(h.GetView().Height)
h.MoveCursorDown(h.BufView().Height)
h.Relocate()
return true
}
// HalfPageUp scrolls the view up half a page
func (h *BufPane) HalfPageUp() bool {
v := h.GetView()
if v.StartLine > v.Height/2 {
h.ScrollUp(v.Height / 2)
} else {
v.StartLine = 0
}
h.SetView(v)
h.ScrollUp(h.BufView().Height / 2)
return true
}
// HalfPageDown scrolls the view down half a page
func (h *BufPane) HalfPageDown() bool {
v := h.GetView()
if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
h.ScrollDown(v.Height / 2)
} else {
if h.Buf.LinesNum() >= v.Height {
v.StartLine = h.Buf.LinesNum() - v.Height
}
}
h.SetView(v)
h.ScrollDown(h.BufView().Height / 2)
h.ScrollAdjust()
return true
}
@@ -1419,39 +1496,55 @@ func (h *BufPane) Escape() bool {
return true
}
// Deselect deselects on the current cursor
func (h *BufPane) Deselect() bool {
h.Cursor.Deselect(true)
return true
}
// ClearInfo clears the infobar
func (h *BufPane) ClearInfo() bool {
InfoBar.Message("")
return true
}
// ForceQuit closes the current tab or view even if there are unsaved changes
// (no prompt)
func (h *BufPane) ForceQuit() bool {
h.Buf.Close()
if len(MainTab().Panes) > 1 {
h.Unsplit()
} else if len(Tabs.List) > 1 {
Tabs.RemoveTab(h.splitID)
} else {
screen.Screen.Fini()
InfoBar.Close()
runtime.Goexit()
}
return true
}
// Quit this will close the current tab or view that is open
func (h *BufPane) Quit() bool {
quit := func() {
h.Buf.Close()
if len(MainTab().Panes) > 1 {
h.Unsplit()
} else if len(Tabs.List) > 1 {
Tabs.RemoveTab(h.splitID)
} else {
screen.Screen.Fini()
InfoBar.Close()
runtime.Goexit()
}
}
if h.Buf.Modified() {
if config.GlobalSettings["autosave"].(float64) > 0 {
// autosave on means we automatically save when quitting
h.SaveCB("Quit", func() {
quit()
h.ForceQuit()
})
} else {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
quit()
h.ForceQuit()
} else if !canceled && yes {
h.SaveCB("Quit", func() {
quit()
h.ForceQuit()
})
}
})
}
} else {
quit()
h.ForceQuit()
}
return true
}
@@ -1574,12 +1667,12 @@ func (h *BufPane) PreviousSplit() bool {
}
var curmacro []interface{}
var recording_macro bool
var recordingMacro bool
// ToggleMacro toggles recording of a macro
func (h *BufPane) ToggleMacro() bool {
recording_macro = !recording_macro
if recording_macro {
recordingMacro = !recordingMacro
if recordingMacro {
curmacro = []interface{}{}
InfoBar.Message("Recording")
} else {
@@ -1591,7 +1684,7 @@ func (h *BufPane) ToggleMacro() bool {
// PlayMacro plays back the most recently recorded macro
func (h *BufPane) PlayMacro() bool {
if recording_macro {
if recordingMacro {
return false
}
for _, action := range curmacro {
@@ -1651,10 +1744,9 @@ func (h *BufPane) SpawnMultiCursor() bool {
func (h *BufPane) SpawnMultiCursorUp() bool {
if h.Cursor.Y == 0 {
return false
} else {
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
h.Cursor.Relocate()
}
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)
@@ -1669,10 +1761,9 @@ func (h *BufPane) SpawnMultiCursorUp() bool {
func (h *BufPane) SpawnMultiCursorDown() bool {
if h.Cursor.Y+1 == h.Buf.LinesNum() {
return false
} else {
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
h.Cursor.Relocate()
}
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)

View File

@@ -3,18 +3,26 @@ package action
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"unicode"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
var Binder = map[string]func(e Event, action string){
"command": InfoMapEvent,
"buffer": BufMapEvent,
"terminal": TermMapEvent,
}
func createBindingsIfNotExist(fname string) {
if _, e := os.Stat(fname); os.IsNotExist(e) {
ioutil.WriteFile(fname, []byte("{}"), 0644)
@@ -23,10 +31,7 @@ func createBindingsIfNotExist(fname string) {
// InitBindings intializes the bindings map by reading from bindings.json
func InitBindings() {
config.Bindings = DefaultBindings()
var parsed map[string]string
defaults := DefaultBindings()
var parsed map[string]interface{}
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
@@ -44,34 +49,89 @@ func InitBindings() {
}
}
for k, v := range defaults {
BindKey(k, v)
for p, bind := range Binder {
defaults := DefaultBindings(p)
for k, v := range defaults {
BindKey(k, v, bind)
}
}
for k, v := range parsed {
BindKey(k, v)
switch val := v.(type) {
case string:
BindKey(k, val, Binder["buffer"])
case map[string]interface{}:
bind, ok := Binder[k]
if !ok || bind == nil {
screen.TermMessage(fmt.Sprintf("%s is not a valid pane type", k))
continue
}
for e, a := range val {
s, ok := a.(string)
if !ok {
screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
} else {
BindKey(e, s, bind)
}
}
default:
screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
}
}
}
func BindKey(k, v string) {
event, ok := findEvent(k)
if !ok {
screen.TermMessage(k, "is not a bindable event")
func BindKey(k, v string, bind func(e Event, a string)) {
event, err := findEvent(k)
if err != nil {
screen.TermMessage(err)
return
}
switch e := event.(type) {
case KeyEvent:
BufMapKey(e, v)
case MouseEvent:
BufMapMouse(e, v)
case RawEvent:
BufMapKey(e, v)
}
bind(event, v)
config.Bindings[k] = v
// switch e := event.(type) {
// case KeyEvent:
// InfoMapKey(e, v)
// case KeySequenceEvent:
// InfoMapKey(e, v)
// case MouseEvent:
// InfoMapMouse(e, v)
// case RawEvent:
// InfoMapKey(e, v)
// }
}
// findEvent will find binding Key 'b' using string 'k'
func findEvent(k string) (b Event, ok bool) {
var r = regexp.MustCompile("<(.+?)>")
func findEvents(k string) (b KeySequenceEvent, ok bool, err error) {
var events []Event = nil
for len(k) > 0 {
groups := r.FindStringSubmatchIndex(k)
if len(groups) > 3 {
if events == nil {
events = make([]Event, 0, 3)
}
e, ok := findSingleEvent(k[groups[2]:groups[3]])
if !ok {
return KeySequenceEvent{}, false, errors.New("Invalid event " + k[groups[2]:groups[3]])
}
events = append(events, e)
k = k[groups[3]+1:]
} else {
return KeySequenceEvent{}, false, nil
}
}
return KeySequenceEvent{events}, true, nil
}
// findSingleEvent will find binding Key 'b' using string 'k'
func findSingleEvent(k string) (b Event, ok bool) {
modifiers := tcell.ModNone
// First, we'll strip off all the modifiers in the name and add them to the
@@ -79,7 +139,7 @@ func findEvent(k string) (b Event, ok bool) {
modSearch:
for {
switch {
case strings.HasPrefix(k, "-"):
case strings.HasPrefix(k, "-") && k != "-":
// We optionally support dashes between modifiers
k = k[1:]
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
@@ -102,7 +162,7 @@ modSearch:
}
}
if len(k) == 0 {
if k == "" {
return KeyEvent{}, false
}
@@ -162,11 +222,28 @@ modSearch:
return KeyEvent{}, false
}
func findEvent(k string) (Event, error) {
var event Event
event, ok, err := findEvents(k)
if err != nil {
return nil, err
}
if !ok {
event, ok = findSingleEvent(k)
if !ok {
return nil, errors.New(k + " is not a bindable event")
}
}
return event, nil
}
// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
// Returns true if the keybinding already existed and a possible error
func TryBindKey(k, v string, overwrite bool) (bool, error) {
var e error
var parsed map[string]string
var parsed map[string]interface{}
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
@@ -181,14 +258,14 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
return false, errors.New("Error reading bindings.json: " + err.Error())
}
key, ok := findEvent(k)
if !ok {
return false, errors.New("Invalid event " + k)
key, err := findEvent(k)
if err != nil {
return false, err
}
found := false
for ev := range parsed {
if e, ok := findEvent(ev); ok {
if e, err := findEvent(ev); err == nil {
if e == key {
if overwrite {
parsed[ev] = v
@@ -205,7 +282,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
parsed[k] = v
}
BindKey(k, v)
BindKey(k, v, Binder["buffer"])
txt, _ := json.MarshalIndent(parsed, "", " ")
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
@@ -216,7 +293,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
// UnbindKey removes the binding for a key from the bindings.json file
func UnbindKey(k string) error {
var e error
var parsed map[string]string
var parsed map[string]interface{}
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
@@ -231,13 +308,13 @@ func UnbindKey(k string) error {
return errors.New("Error reading bindings.json: " + err.Error())
}
key, ok := findEvent(k)
if !ok {
return errors.New("Invalid event " + k)
key, err := findEvent(k)
if err != nil {
return err
}
for ev := range parsed {
if e, ok := findEvent(ev); ok {
if e, err := findEvent(ev); err == nil {
if e == key {
delete(parsed, ev)
break
@@ -245,12 +322,12 @@ func UnbindKey(k string) error {
}
}
defaults := DefaultBindings()
defaults := DefaultBindings("buffer")
if a, ok := defaults[k]; ok {
BindKey(k, a)
} else if _, ok := config.Bindings[k]; ok {
BindKey(k, a, Binder["buffer"])
} else if _, ok := config.Bindings["buffer"][k]; ok {
BufUnmap(key)
delete(config.Bindings, k)
delete(config.Bindings["buffer"], k)
}
txt, _ := json.MarshalIndent(parsed, "", " ")
@@ -260,9 +337,9 @@ func UnbindKey(k string) error {
}
var mouseEvents = map[string]tcell.ButtonMask{
"MouseLeft": tcell.Button1,
"MouseMiddle": tcell.Button2,
"MouseRight": tcell.Button3,
"MouseLeft": tcell.ButtonPrimary,
"MouseMiddle": tcell.ButtonMiddle,
"MouseRight": tcell.ButtonSecondary,
"MouseWheelUp": tcell.WheelUp,
"MouseWheelDown": tcell.WheelDown,
"MouseWheelLeft": tcell.WheelLeft,

View File

@@ -8,26 +8,43 @@ import (
lua "github.com/yuin/gopher-lua"
"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/display"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)
// BufKeyAction represents an action bound to a key.
type BufKeyAction func(*BufPane) bool
// BufMouseAction is an action that must be bound to a mouse event.
type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
var BufKeyBindings map[Event]BufKeyAction
var BufKeyStrings map[Event]string
var BufMouseBindings map[MouseEvent]BufMouseAction
// BufBindings stores the bindings for the buffer pane type.
var BufBindings *KeyTree
func init() {
BufKeyBindings = make(map[Event]BufKeyAction)
BufKeyStrings = make(map[Event]string)
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
// BufKeyActionGeneral makes a general pane action from a BufKeyAction.
func BufKeyActionGeneral(a BufKeyAction) PaneKeyAction {
return func(p Pane) bool {
return a(p.(*BufPane))
}
}
// BufMouseActionGeneral makes a general pane mouse action from a BufKeyAction.
func BufMouseActionGeneral(a BufMouseAction) PaneMouseAction {
return func(p Pane, me *tcell.EventMouse) bool {
return a(p.(*BufPane), me)
}
}
func init() {
BufBindings = NewKeyTree()
}
// LuaAction makes a BufKeyAction from a lua function.
func LuaAction(fn string) func(*BufPane) bool {
luaFn := strings.Split(fn, ".")
if len(luaFn) <= 1 {
@@ -51,9 +68,19 @@ func LuaAction(fn string) func(*BufPane) bool {
}
}
// BufMapKey maps a key event to an action
func BufMapKey(k Event, action string) {
BufKeyStrings[k] = action
// BufMapKey maps an event to an action
func BufMapEvent(k Event, action string) {
config.Bindings["buffer"][k.Name()] = action
switch e := k.(type) {
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 types []byte
@@ -108,10 +135,11 @@ func BufMapKey(k Event, action string) {
}
actionfns = append(actionfns, afn)
}
BufKeyBindings[k] = func(h *BufPane) bool {
bufAction := func(h *BufPane) bool {
cursors := h.Buf.GetCursors()
success := true
for i, a := range actionfns {
innerSuccess := true
for j, c := range cursors {
if c == nil {
continue
@@ -119,37 +147,41 @@ func BufMapKey(k Event, action string) {
h.Buf.SetCurCursor(c.Num)
h.Cursor = c
if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
success = h.execAction(a, names[i], j)
innerSuccess = innerSuccess && h.execAction(a, names[i], j)
} else {
break
}
}
// if the action changed the current pane, update the reference
h = MainTab().CurPane()
success = innerSuccess
}
return true
}
BufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(bufAction))
}
// BufMapMouse maps a mouse event to an action
func BufMapMouse(k MouseEvent, action string) {
func bufMapMouse(k MouseEvent, action string) {
if f, ok := BufMouseActions[action]; ok {
BufMouseBindings[k] = f
BufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
} else {
delete(BufMouseBindings, k)
BufMapKey(k, action)
// TODO
// delete(BufMouseBindings, k)
bufMapKey(k, action)
}
}
// BufUnmap unmaps a key or mouse event from any action
func BufUnmap(k Event) {
delete(BufKeyBindings, k)
delete(BufKeyStrings, k)
switch e := k.(type) {
case MouseEvent:
delete(BufMouseBindings, e)
}
// TODO
// delete(BufKeyBindings, k)
//
// switch e := k.(type) {
// case MouseEvent:
// delete(BufMouseBindings, e)
// }
}
// The BufPane connects the buffer and the window
@@ -160,9 +192,13 @@ func BufUnmap(k Event) {
type BufPane struct {
display.BWindow
// Buf is the buffer this BufPane views
Buf *buffer.Buffer
// Bindings stores the association of key events and actions
bindings *KeyTree
Cursor *buffer.Cursor // the active cursor
// Cursor is the currently active buffer cursor
Cursor *buffer.Cursor
// Since tcell doesn't differentiate between a mouse release event
// and a mouse move event with no keys pressed, we need to keep
@@ -191,9 +227,6 @@ type BufPane struct {
// Same here, just to keep track for mouse move events
tripleClick bool
// Last search stores the last successful search for FindNext and FindPrev
lastSearch string
lastSearchRegex bool
// Should the current multiple cursor selection search based on word or
// based on selection (false for selection, true for word)
multiWord bool
@@ -203,9 +236,14 @@ type BufPane struct {
// remember original location of a search in case the search is canceled
searchOrig buffer.Loc
// The pane may not yet be fully initialized after its creation
// since we may not know the window geometry yet. In such case we finish
// its initialization a bit later, after the initial resize.
initialized bool
}
func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
func newBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
h := new(BufPane)
h.Buf = buf
h.BWindow = win
@@ -214,20 +252,47 @@ func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
h.Cursor = h.Buf.GetActiveCursor()
h.mouseReleased = true
config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
return h
}
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
w := display.NewBufWindow(0, 0, 0, 0, buf)
return NewBufPane(buf, w, tab)
// NewBufPane creates a new buffer pane with the given window.
func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
h := newBufPane(buf, win, tab)
h.finishInitialize()
return h
}
// NewBufPaneFromBuf constructs a new pane from the given buffer and automatically
// creates a buf window.
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
w := display.NewBufWindow(0, 0, 0, 0, buf)
h := newBufPane(buf, w, tab)
// Postpone finishing initializing the pane until we know the actual geometry
// of the buf window.
return h
}
// TODO: make sure splitID and tab are set before finishInitialize is called
func (h *BufPane) finishInitialize() {
h.initialRelocate()
h.initialized = true
config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
}
// Resize resizes the pane
func (h *BufPane) Resize(width, height int) {
h.BWindow.Resize(width, height)
if !h.initialized {
h.finishInitialize()
}
}
// SetTab sets this pane's tab.
func (h *BufPane) SetTab(t *Tab) {
h.tab = t
}
// Tab returns this pane's tab.
func (h *BufPane) Tab() *Tab {
return h.tab
}
@@ -238,9 +303,8 @@ func (h *BufPane) ResizePane(size int) {
h.tab.Resize()
}
// PluginCB calls all plugin callbacks with a certain name and
// displays an error if there is one and returns the aggregrate
// boolean response
// PluginCB calls all plugin callbacks with a certain name and displays an
// error if there is one and returns the aggregrate boolean response
func (h *BufPane) PluginCB(cb string) bool {
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
if err != nil {
@@ -249,8 +313,7 @@ func (h *BufPane) PluginCB(cb string) bool {
return b
}
// 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 {
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
if err != nil {
@@ -259,30 +322,71 @@ func (h *BufPane) PluginCBRune(cb string, r rune) bool {
return b
}
// OpenBuffer opens the given buffer in this pane.
func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
h.Buf.Close()
h.Buf = b
h.BWindow.SetBuffer(b)
h.Cursor = b.GetActiveCursor()
h.Resize(h.GetView().Width, h.GetView().Height)
h.Relocate()
// Set mouseReleased to true because we assume the mouse is not being pressed when
// the editor is opened
h.initialRelocate()
// Set mouseReleased to true because we assume the mouse is not being
// pressed when the editor is opened
h.mouseReleased = true
// Set isOverwriteMode to false, because we assume we are in the default mode when editor
// is opened
// Set isOverwriteMode to false, because we assume we are in the default
// mode when editor is opened
h.isOverwriteMode = false
h.lastClickTime = time.Time{}
}
// GotoLoc moves the cursor to a new location and adjusts the view accordingly.
// Use GotoLoc when the new location may be far away from the current location.
func (h *BufPane) GotoLoc(loc buffer.Loc) {
sloc := h.SLocFromLoc(loc)
d := h.Diff(h.SLocFromLoc(h.Cursor.Loc), sloc)
h.Cursor.GotoLoc(loc)
// If the new location is far away from the previous one,
// ensure the cursor is at 25% of the window height
height := h.BufView().Height
if util.Abs(d) >= height {
v := h.GetView()
v.StartLine = h.Scroll(sloc, -height/4)
h.ScrollAdjust()
v.StartCol = 0
}
h.Relocate()
}
func (h *BufPane) initialRelocate() {
sloc := h.SLocFromLoc(h.Cursor.Loc)
height := h.BufView().Height
// If the initial cursor location is far away from the beginning
// of the buffer, ensure the cursor is at 25% of the window height
v := h.GetView()
if h.Diff(display.SLoc{0, 0}, sloc) < height {
v.StartLine = display.SLoc{0, 0}
} else {
v.StartLine = h.Scroll(sloc, -height/4)
h.ScrollAdjust()
}
v.StartCol = 0
h.Relocate()
}
// ID returns this pane's split id.
func (h *BufPane) ID() uint64 {
return h.splitID
}
// SetID sets the split ID of this pane.
func (h *BufPane) SetID(i uint64) {
h.splitID = i
}
// Name returns the BufPane's name.
func (h *BufPane) Name() string {
n := h.Buf.GetName()
if h.Buf.Modified() {
@@ -319,7 +423,7 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
case *tcell.EventKey:
ke := KeyEvent{
code: e.Key(),
mod: e.Modifiers(),
mod: metaToAlt(e.Modifiers()),
r: e.Rune(),
}
@@ -360,7 +464,7 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
// }
if h.Cursor.HasSelection() {
h.Cursor.CopySelection("primary")
h.Cursor.CopySelection(clipboard.PrimaryReg)
}
h.mouseReleased = true
}
@@ -369,7 +473,7 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
if !cancel {
me := MouseEvent{
btn: e.Buttons(),
mod: e.Modifiers(),
mod: metaToAlt(e.Modifiers()),
}
h.DoMouseEvent(me, e)
}
@@ -393,13 +497,27 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
}
}
// Bindings returns the current bindings tree for this buffer.
func (h *BufPane) Bindings() *KeyTree {
if h.bindings != nil {
return h.bindings
}
return BufBindings
}
// DoKeyEvent executes a key event by finding the action it is bound
// to and executing it (possibly multiple times for multiple cursors)
func (h *BufPane) DoKeyEvent(e Event) bool {
if action, ok := BufKeyBindings[e]; ok {
return action(h)
binds := h.Bindings()
action, more := binds.NextEvent(e, nil)
if action != nil && !more {
action(h)
binds.ResetEvents()
return true
} else if action == nil && !more {
binds.ResetEvents()
}
return false
return more
}
func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
@@ -414,7 +532,7 @@ func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int
success = success && h.PluginCB("on"+name)
if isMulti {
if recording_macro {
if recordingMacro {
if name != "ToggleMacro" && name != "PlayMacro" {
curmacro = append(curmacro, action)
}
@@ -433,22 +551,34 @@ func (h *BufPane) completeAction(action string) {
}
func (h *BufPane) HasKeyEvent(e Event) bool {
_, ok := BufKeyBindings[e]
return ok
// TODO
return true
// _, ok := BufKeyBindings[e]
// return ok
}
// DoMouseEvent executes a mouse event by finding the action it is bound
// to and executing it
func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
if action, ok := BufMouseBindings[e]; ok {
if action(h, te) {
h.Relocate()
}
binds := h.Bindings()
action, _ := binds.NextEvent(e, te)
if action != nil {
action(h)
binds.ResetEvents()
return true
} else if h.HasKeyEvent(e) {
return h.DoKeyEvent(e)
}
// TODO
return false
// if action, ok := BufMouseBindings[e]; ok {
// if action(h, te) {
// h.Relocate()
// }
// return true
// } else if h.HasKeyEvent(e) {
// return h.DoKeyEvent(e)
// }
// return false
}
// DoRuneInsert inserts a given rune into the current buffer
@@ -474,7 +604,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
} else {
h.Buf.Insert(c.Loc, string(r))
}
if recording_macro {
if recordingMacro {
curmacro = append(curmacro, r)
}
h.Relocate()
@@ -482,6 +612,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
}
}
// VSplitIndex opens the given buffer in a vertical split on the given side.
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
e := NewBufPaneFromBuf(buf, h.tab)
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
@@ -490,6 +621,8 @@ func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
MainTab().SetActive(len(MainTab().Panes) - 1)
return e
}
// HSplitIndex opens the given buffer in a horizontal split on the given side.
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
e := NewBufPaneFromBuf(buf, h.tab)
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
@@ -499,16 +632,22 @@ func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
return e
}
// VSplitBuf opens the given buffer in a new vertical split.
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
}
// HSplitBuf opens the given buffer in a new horizontal split.
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
}
// Close this pane.
func (h *BufPane) Close() {
h.Buf.Close()
}
// SetActive marks this pane as active.
func (h *BufPane) SetActive(b bool) {
h.BWindow.SetActive(b)
if b {
@@ -606,6 +745,8 @@ var BufKeyActions = map[string]BufKeyAction{
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
"ToggleRuler": (*BufPane).ToggleRuler,
"ToggleHighlightSearch": (*BufPane).ToggleHighlightSearch,
"UnhighlightSearch": (*BufPane).UnhighlightSearch,
"ClearStatus": (*BufPane).ClearStatus,
"ShellMode": (*BufPane).ShellMode,
"CommandMode": (*BufPane).CommandMode,
@@ -613,6 +754,7 @@ var BufKeyActions = map[string]BufKeyAction{
"Escape": (*BufPane).Escape,
"Quit": (*BufPane).Quit,
"QuitAll": (*BufPane).QuitAll,
"ForceQuit": (*BufPane).ForceQuit,
"AddTab": (*BufPane).AddTab,
"PreviousTab": (*BufPane).PreviousTab,
"NextTab": (*BufPane).NextTab,
@@ -635,6 +777,8 @@ var BufKeyActions = map[string]BufKeyAction{
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
"JumpLine": (*BufPane).JumpLine,
"Deselect": (*BufPane).Deselect,
"ClearInfo": (*BufPane).ClearInfo,
"None": (*BufPane).None,
// This was changed to InsertNewline but I don't want to break backwards compatibility
@@ -686,6 +830,7 @@ var MultiActions = map[string]bool{
"FindNext": true,
"FindPrevious": true,
"CopyLine": true,
"Copy": true,
"Cut": true,
"CutLine": true,
"DuplicateLine": true,

View File

@@ -13,6 +13,7 @@ import (
shellquote "github.com/kballard/go-shellquote"
"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/screen"
"github.com/zyedidia/micro/v2/internal/shell"
@@ -505,6 +506,12 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
}
} 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 {
for _, pl := range config.Plugins {
if option == pl.Name {
@@ -634,7 +641,12 @@ func (h *BufPane) ShowKeyCmd(args []string) {
return
}
if action, ok := config.Bindings[args[0]]; ok {
event, err := findEvent(args[0])
if err != nil {
InfoBar.Error(err)
return
}
if action, ok := config.Bindings["buffer"][event.Name()]; ok {
InfoBar.Message(action)
} else {
InfoBar.Message(args[0], " has no binding")
@@ -710,7 +722,7 @@ func (h *BufPane) GotoCmd(args []string) {
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
h.Cursor.GotoLoc(buffer.Loc{col, line})
h.GotoLoc(buffer.Loc{col, line})
} else {
line, err := strconv.Atoi(args[0])
if err != nil {
@@ -721,9 +733,8 @@ func (h *BufPane) GotoCmd(args []string) {
line = h.Buf.LinesNum() + 1 + line
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
h.Cursor.GotoLoc(buffer.Loc{0, line})
h.GotoLoc(buffer.Loc{0, line})
}
h.Relocate()
}
}
@@ -805,10 +816,10 @@ func (h *BufPane) ReplaceCmd(args []string) {
return l.GreaterEqual(start) && l.LessEqual(end)
}
searchLoc := start
searchLoc := h.Cursor.Loc
var doReplacement func()
doReplacement = func() {
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, !noRegex)
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, true)
if err != nil {
InfoBar.Error(err)
return
@@ -816,14 +827,16 @@ func (h *BufPane) ReplaceCmd(args []string) {
if !found || !inRange(locs[0]) || !inRange(locs[1]) {
h.Cursor.ResetSelection()
h.Buf.RelocateCursors()
return
}
h.Cursor.SetSelectionStart(locs[0])
h.Cursor.SetSelectionEnd(locs[1])
h.Cursor.GotoLoc(locs[0])
h.Relocate()
h.GotoLoc(locs[0])
h.Buf.LastSearch = search
h.Buf.LastSearchRegex = true
h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
if !canceled && yes {
@@ -831,12 +844,13 @@ func (h *BufPane) ReplaceCmd(args []string) {
searchLoc = locs[0]
searchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf)
end.Move(nrunes, h.Buf)
if end.Y == locs[1].Y {
end = end.Move(nrunes, h.Buf)
}
h.Cursor.Loc = searchLoc
nreplaced++
} else if !canceled && !yes {
searchLoc = locs[0]
searchLoc.X += util.CharacterCount(replace)
searchLoc = locs[1]
} else if canceled {
h.Cursor.ResetSelection()
h.Buf.RelocateCursors()

View File

@@ -0,0 +1,21 @@
package action
var termdefaults = map[string]string{
"<Ctrl-q><Ctrl-q>": "Exit",
"<Ctrl-e><Ctrl-e>": "CommandMode",
"<Ctrl-w><Ctrl-w>": "NextSplit",
}
// DefaultBindings returns a map containing micro's default keybindings
func DefaultBindings(pane string) map[string]string {
switch pane {
case "command":
return infodefaults
case "buffer":
return bufdefaults
case "terminal":
return termdefaults
default:
return map[string]string{}
}
}

View File

@@ -1,107 +1,180 @@
package action
// DefaultBindings returns a map containing micro's default keybindings
func DefaultBindings() map[string]string {
return map[string]string{
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfTextToggle",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfTextToggle",
"ShiftHome": "SelectToStartOfTextToggle",
"CtrlShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
"CtrlN": "FindNext",
"CtrlP": "FindPrevious",
"CtrlZ": "Undo",
"CtrlY": "Redo",
"CtrlC": "CopyLine|Copy",
"CtrlX": "Cut",
"CtrlK": "CutLine",
"CtrlD": "DuplicateLine",
"CtrlV": "Paste",
"CtrlA": "SelectAll",
"CtrlT": "AddTab",
"Alt,": "PreviousTab",
"Alt.": "NextTab",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"CtrlR": "ToggleRuler",
"CtrlL": "command-edit:goto ",
"Delete": "Delete",
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
"Insert": "ToggleOverwriteMode",
var bufdefaults = map[string]string{
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfTextToggle",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfTextToggle",
"ShiftHome": "SelectToStartOfTextToggle",
"CtrlShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"OldBackspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
"Ctrl-o": "OpenFile",
"Ctrl-s": "Save",
"Ctrl-f": "Find",
"Alt-F": "FindLiteral",
"Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
"Alt-,": "PreviousTab",
"Alt-.": "NextTab",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"Ctrl-g": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"Ctrl-r": "ToggleRuler",
"Ctrl-l": "command-edit:goto ",
"Delete": "Delete",
"Ctrl-b": "ShellMode",
"Ctrl-q": "Quit",
"Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit",
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfText",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfText",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Integration with file managers
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Integration with file managers
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"AltShiftUp": "SpawnMultiCursorUp",
"AltShiftDown": "SpawnMultiCursorDown",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
"Alt-n": "SpawnMultiCursor",
"AltShiftUp": "SpawnMultiCursorUp",
"AltShiftDown": "SpawnMultiCursorDown",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
var infodefaults = map[string]string{
"Up": "HistoryUp",
"Down": "HistoryDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltUp": "CursorStart",
"AltDown": "CursorEnd",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfTextToggle",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfTextToggle",
"ShiftHome": "SelectToStartOfTextToggle",
"CtrlShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Enter": "ExecuteCommand",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"OldBackspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "CommandComplete",
"Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-v": "Paste",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"Delete": "Delete",
"Ctrl-q": "AbortCommand",
"Ctrl-e": "EndOfLine",
"Ctrl-a": "StartOfLine",
"Ctrl-w": "DeleteWordLeft",
"Insert": "ToggleOverwriteMode",
"Ctrl-b": "WordLeft",
"Ctrl-f": "WordRight",
"Ctrl-d": "DeleteWordLeft",
"Ctrl-m": "ExecuteCommand",
"Ctrl-n": "HistoryDown",
"Ctrl-p": "HistoryUp",
"Ctrl-u": "SelectToStart",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfText",
"Alt-e": "EndOfLine",
// Integration with file managers
"F10": "AbortCommand",
"Esc": "AbortCommand",
// Mouse bindings
"MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
}

View File

@@ -2,108 +2,181 @@
package action
// DefaultBindings returns a map containing micro's default keybindings
func DefaultBindings() map[string]string {
return map[string]string{
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"CtrlLeft": "WordLeft",
"CtrlRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"CtrlShiftRight": "SelectWordRight",
"CtrlShiftLeft": "SelectWordLeft",
"AltLeft": "StartOfTextToggle",
"AltRight": "EndOfLine",
"AltShiftLeft": "SelectToStartOfTextToggle",
"ShiftHome": "SelectToStartOfTextToggle",
"AltShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
"CtrlN": "FindNext",
"CtrlP": "FindPrevious",
"CtrlZ": "Undo",
"CtrlY": "Redo",
"CtrlC": "CopyLine|Copy",
"CtrlX": "Cut",
"CtrlK": "CutLine",
"CtrlD": "DuplicateLine",
"CtrlV": "Paste",
"CtrlA": "SelectAll",
"CtrlT": "AddTab",
"Alt,": "PreviousTab",
"Alt.": "NextTab",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"CtrlR": "ToggleRuler",
"CtrlL": "command-edit:goto ",
"Delete": "Delete",
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
"Insert": "ToggleOverwriteMode",
var bufdefaults = map[string]string{
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"CtrlLeft": "WordLeft",
"CtrlRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"CtrlShiftRight": "SelectWordRight",
"CtrlShiftLeft": "SelectWordLeft",
"AltLeft": "StartOfTextToggle",
"AltRight": "EndOfLine",
"AltShiftLeft": "SelectToStartOfTextToggle",
"ShiftHome": "SelectToStartOfTextToggle",
"AltShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"OldBackspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
"Ctrl-o": "OpenFile",
"Ctrl-s": "Save",
"Ctrl-f": "Find",
"Alt-F": "FindLiteral",
"Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
"Alt-,": "PreviousTab",
"Alt-.": "NextTab",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"Ctrl-g": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"Ctrl-r": "ToggleRuler",
"Ctrl-l": "command-edit:goto ",
"Delete": "Delete",
"Ctrl-b": "ShellMode",
"Ctrl-q": "Quit",
"Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit",
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfText",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfText",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Integration with file managers
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Integration with file managers
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
"AltShiftUp": "SpawnMultiCursorUp",
"AltShiftDown": "SpawnMultiCursorDown",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
"AltShiftUp": "SpawnMultiCursorUp",
"AltShiftDown": "SpawnMultiCursorDown",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
var infodefaults = map[string]string{
"Up": "HistoryUp",
"Down": "HistoryDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"AltLeft": "StartOfTextToggle",
"AltRight": "EndOfLine",
"AltUp": "CursorStart",
"AltDown": "CursorEnd",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "WordLeft",
"CtrlRight": "WordRight",
"CtrlShiftLeft": "SelectToStartOfTextToggle",
"ShiftHome": "SelectToStartOfTextToggle",
"CtrlShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Enter": "ExecuteCommand",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"OldBackspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "CommandComplete",
"Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-v": "Paste",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"Delete": "Delete",
"Ctrl-q": "AbortCommand",
"Ctrl-e": "EndOfLine",
"Ctrl-a": "StartOfLine",
"Ctrl-w": "DeleteWordLeft",
"Insert": "ToggleOverwriteMode",
"Ctrl-b": "WordLeft",
"Ctrl-f": "WordRight",
"Ctrl-d": "DeleteWordLeft",
"Ctrl-m": "ExecuteCommand",
"Ctrl-n": "HistoryDown",
"Ctrl-p": "HistoryUp",
"Ctrl-u": "SelectToStart",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfText",
"Alt-e": "EndOfLine",
// Integration with file managers
"F10": "AbortCommand",
"Esc": "AbortCommand",
// Mouse bindings
"MouseWheelUp": "HistoryUp",
"MouseWheelDown": "HistoryDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
}

View File

@@ -1,10 +1,17 @@
package action
import (
"github.com/zyedidia/tcell"
"bytes"
"errors"
"fmt"
"strings"
"github.com/zyedidia/tcell/v2"
)
type Event interface{}
type Event interface {
Name() string
}
// RawEvent is simply an escape code
// We allow users to directly bind escape codes
@@ -13,6 +20,10 @@ type RawEvent struct {
esc string
}
func (r RawEvent) Name() string {
return r.esc
}
// KeyEvent is a key event containing a key code,
// some possible modifiers (alt, ctrl, etc...) and
// a rune if it was simply a character press
@@ -22,6 +33,71 @@ type KeyEvent struct {
code tcell.Key
mod tcell.ModMask
r rune
any bool
}
func metaToAlt(mod tcell.ModMask) tcell.ModMask {
if mod&tcell.ModMeta != 0 {
mod &= ^tcell.ModMeta
mod |= tcell.ModAlt
}
return mod
}
func (k KeyEvent) Name() string {
if k.any {
return "<any>"
}
s := ""
m := []string{}
if k.mod&tcell.ModShift != 0 {
m = append(m, "Shift")
}
if k.mod&tcell.ModAlt != 0 {
m = append(m, "Alt")
}
if k.mod&tcell.ModMeta != 0 {
m = append(m, "Meta")
}
if k.mod&tcell.ModCtrl != 0 {
m = append(m, "Ctrl")
}
ok := false
if s, ok = tcell.KeyNames[k.code]; !ok {
if k.code == tcell.KeyRune {
s = string(k.r)
} else {
s = fmt.Sprintf("Key[%d,%d]", k.code, int(k.r))
}
}
if len(m) != 0 {
if k.mod&tcell.ModCtrl != 0 && strings.HasPrefix(s, "Ctrl-") {
s = s[5:]
if len(s) == 1 {
s = strings.ToLower(s)
}
}
return fmt.Sprintf("%s-%s", strings.Join(m, "-"), s)
}
return s
}
// A KeySequence defines a list of consecutive
// events. All events in the sequence must be KeyEvents
// or MouseEvents.
type KeySequenceEvent struct {
keys []Event
}
func (k KeySequenceEvent) Name() string {
buf := bytes.Buffer{}
for _, e := range k.keys {
buf.WriteByte('<')
buf.WriteString(e.Name())
buf.WriteByte('>')
}
return buf.String()
}
// MouseEvent is a mouse event with a mouse button and
@@ -31,8 +107,54 @@ type MouseEvent struct {
mod tcell.ModMask
}
type KeyAction func(Handler) bool
type MouseAction func(Handler, tcell.EventMouse) bool
func (m MouseEvent) Name() string {
mod := ""
if m.mod&tcell.ModShift != 0 {
mod = "Shift-"
}
if m.mod&tcell.ModAlt != 0 {
mod = "Alt-"
}
if m.mod&tcell.ModMeta != 0 {
mod = "Meta-"
}
if m.mod&tcell.ModCtrl != 0 {
mod = "Ctrl-"
}
for k, v := range mouseEvents {
if v == m.btn {
return fmt.Sprintf("%s%s", mod, k)
}
}
return ""
}
// ConstructEvent takes a tcell event and returns a micro
// event. Note that tcell events can't express certain
// micro events such as key sequences. This function is
// mostly used for debugging/raw panes or constructing
// intermediate micro events while parsing a sequence.
func ConstructEvent(event tcell.Event) (Event, error) {
switch e := event.(type) {
case *tcell.EventKey:
return KeyEvent{
code: e.Key(),
mod: metaToAlt(e.Modifiers()),
r: e.Rune(),
}, nil
case *tcell.EventRaw:
return RawEvent{
esc: e.EscSeq(),
}, nil
case *tcell.EventMouse:
return MouseEvent{
btn: e.Buttons(),
mod: metaToAlt(e.Modifiers()),
}, nil
}
return nil, errors.New("No micro event equivalent")
}
// A Handler will take a tcell event and execute it
// appropriately

View File

@@ -2,7 +2,10 @@ package action
import "github.com/zyedidia/micro/v2/internal/buffer"
// InfoBar is the global info bar.
var InfoBar *InfoPane
// LogBufPane is a global log buffer.
var LogBufPane *BufPane
// InitGlobals initializes the log buffer and the info bar
@@ -21,13 +24,6 @@ func WriteLog(s string) {
buffer.WriteLog(s)
if LogBufPane != nil {
LogBufPane.CursorEnd()
v := LogBufPane.GetView()
endY := buffer.LogBuf.End().Y
if endY > v.StartLine+v.Height {
v.StartLine = buffer.LogBuf.End().Y - v.Height + 2
LogBufPane.SetView(v)
}
}
}
@@ -37,12 +33,4 @@ func WriteLog(s string) {
func (h *BufPane) OpenLogBuf() {
LogBufPane = h.HSplitBuf(buffer.LogBuf)
LogBufPane.CursorEnd()
v := LogBufPane.GetView()
endY := buffer.LogBuf.End().Y
if endY > v.StartLine+v.Height {
v.StartLine = buffer.LogBuf.End().Y - v.Height + 2
LogBufPane.SetView(v)
}
}

View File

@@ -186,6 +186,16 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
if strings.HasPrefix("doas", input) {
suggestions = append(suggestions, "doas")
}
case "clipboard":
if strings.HasPrefix("external", input) {
suggestions = append(suggestions, "external")
}
if strings.HasPrefix("internal", input) {
suggestions = append(suggestions, "internal")
}
if strings.HasPrefix("terminal", input) {
suggestions = append(suggestions, "terminal")
}
}
}
sort.Strings(suggestions)

View File

@@ -2,17 +2,60 @@ package action
import (
"bytes"
"strings"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/info"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
type InfoKeyAction func(*InfoPane)
var InfoBindings *KeyTree
var InfoBufBindings *KeyTree
func init() {
InfoBindings = NewKeyTree()
InfoBufBindings = NewKeyTree()
}
func InfoMapEvent(k Event, action string) {
config.Bindings["command"][k.Name()] = action
switch e := k.(type) {
case KeyEvent, KeySequenceEvent, RawEvent:
infoMapKey(e, action)
case MouseEvent:
infoMapMouse(e, action)
}
}
func infoMapKey(k Event, action string) {
if f, ok := InfoKeyActions[action]; ok {
InfoBindings.RegisterKeyBinding(k, InfoKeyActionGeneral(f))
} else if f, ok := BufKeyActions[action]; ok {
InfoBufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(f))
}
}
func infoMapMouse(k MouseEvent, action string) {
// TODO: map mouse
if f, ok := BufMouseActions[action]; ok {
InfoBufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
} else {
infoMapKey(k, action)
}
}
func InfoKeyActionGeneral(a InfoKeyAction) PaneKeyAction {
return func(p Pane) bool {
a(p.(*InfoPane))
return true
}
}
type InfoPane struct {
*BufPane
*info.InfoBuf
@@ -22,6 +65,7 @@ func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
ip := new(InfoPane)
ip.InfoBuf = ib
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
ip.BufPane.bindings = InfoBufBindings
return ip
}
@@ -42,17 +86,17 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
case *tcell.EventKey:
ke := KeyEvent{
code: e.Key(),
mod: e.Modifiers(),
mod: metaToAlt(e.Modifiers()),
r: e.Rune(),
}
done := h.DoKeyEvent(ke)
hasYN := h.HasYN
if e.Key() == tcell.KeyRune && hasYN {
if e.Rune() == 'y' && hasYN {
if (e.Rune() == 'y' || e.Rune() == 'Y') && hasYN {
h.YNResp = true
h.DonePrompt(false)
} else if e.Rune() == 'n' && hasYN {
} else if (e.Rune() == 'n' || e.Rune() == 'N') && hasYN {
h.YNResp = false
h.DonePrompt(false)
}
@@ -76,104 +120,43 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
// DoKeyEvent executes a key event for the command bar, doing any overridden actions
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
done := false
if action, ok := BufKeyBindings[e]; ok {
estr := BufKeyStrings[e]
for _, s := range InfoNones {
if s == estr {
return false
}
}
for s, a := range InfoOverrides {
// TODO this is a hack and really we should have support
// for having binding overrides for different buffers
if strings.HasPrefix(estr, s) {
done = true
a(h)
break
}
}
if !done {
done = action(h.BufPane)
action, more := InfoBindings.NextEvent(e, nil)
if action != nil && !more {
action(h)
InfoBindings.ResetEvents()
return true
} else if action == nil && !more {
InfoBindings.ResetEvents()
// return false //TODO:?
}
if !more {
action, more = InfoBufBindings.NextEvent(e, nil)
if action != nil && !more {
done := action(h.BufPane)
InfoBufBindings.ResetEvents()
return done
} else if action == nil && !more {
InfoBufBindings.ResetEvents()
}
}
return done
return more
}
// InfoNones is a list of actions that should have no effect when executed
// by an infohandler
var InfoNones = []string{
"Save",
"SaveAll",
"SaveAs",
"Find",
"FindNext",
"FindPrevious",
"Center",
"DuplicateLine",
"MoveLinesUp",
"MoveLinesDown",
"OpenFile",
"Start",
"End",
"PageUp",
"PageDown",
"SelectPageUp",
"SelectPageDown",
"HalfPageUp",
"HalfPageDown",
"ToggleHelp",
"ToggleKeyMenu",
"ToggleDiffGutter",
"ToggleRuler",
"JumpLine",
"ClearStatus",
"ShellMode",
"CommandMode",
"AddTab",
"PreviousTab",
"NextTab",
"NextSplit",
"PreviousSplit",
"Unsplit",
"VSplit",
"HSplit",
"ToggleMacro",
"PlayMacro",
"Suspend",
"ScrollUp",
"ScrollDown",
"SpawnMultiCursor",
"SpawnMultiCursorSelect",
"RemoveMultiCursor",
"RemoveAllMultiCursors",
"SkipMultiCursor",
}
// InfoOverrides is the list of actions which have been overridden
// by the infohandler
var InfoOverrides = map[string]InfoKeyAction{
"CursorUp": (*InfoPane).CursorUp,
"CursorDown": (*InfoPane).CursorDown,
"InsertNewline": (*InfoPane).InsertNewline,
"Autocomplete": (*InfoPane).Autocomplete,
"Escape": (*InfoPane).Escape,
"Quit": (*InfoPane).Quit,
"QuitAll": (*InfoPane).QuitAll,
}
// CursorUp cycles history up
func (h *InfoPane) CursorUp() {
// HistoryUp cycles history up
func (h *InfoPane) HistoryUp() {
h.UpHistory(h.History[h.PromptType])
}
// CursorDown cycles history down
func (h *InfoPane) CursorDown() {
// HistoryDown cycles history down
func (h *InfoPane) HistoryDown() {
h.DownHistory(h.History[h.PromptType])
}
// Autocomplete begins autocompletion
func (h *InfoPane) Autocomplete() {
func (h *InfoPane) CommandComplete() {
b := h.Buf
if b.HasSuggestions {
b.CycleAutocomplete(true)
@@ -201,24 +184,23 @@ func (h *InfoPane) Autocomplete() {
}
}
// InsertNewline completes the prompt
func (h *InfoPane) InsertNewline() {
// ExecuteCommand completes the prompt
func (h *InfoPane) ExecuteCommand() {
if !h.HasYN {
h.DonePrompt(false)
}
}
// Quit cancels the prompt
func (h *InfoPane) Quit() {
// AbortCommand cancels the prompt
func (h *InfoPane) AbortCommand() {
h.DonePrompt(true)
}
// QuitAll cancels the prompt
func (h *InfoPane) QuitAll() {
h.DonePrompt(true)
}
// Escape cancels the prompt
func (h *InfoPane) Escape() {
h.DonePrompt(true)
// InfoKeyActions contains the list of all possible key actions the infopane could execute
var InfoKeyActions = map[string]InfoKeyAction{
"HistoryUp": (*InfoPane).HistoryUp,
"HistoryDown": (*InfoPane).HistoryDown,
"CommandComplete": (*InfoPane).CommandComplete,
"ExecuteCommand": (*InfoPane).ExecuteCommand,
"AbortCommand": (*InfoPane).AbortCommand,
}

261
internal/action/keytree.go Normal file
View File

@@ -0,0 +1,261 @@
package action
import (
"bytes"
"github.com/zyedidia/tcell/v2"
)
type PaneKeyAction func(Pane) bool
type PaneMouseAction func(Pane, *tcell.EventMouse) bool
type PaneKeyAnyAction func(Pane, []KeyEvent) bool
// A KeyTreeNode stores a single node in the KeyTree (trie). The
// children are stored as a map, and any node may store a list of
// actions (the list will be nil if no actions correspond to a certain
// node)
type KeyTreeNode struct {
children map[Event]*KeyTreeNode
// Only one of these actions may be active in the current
// mode, and only one will be returned. If multiple actions
// are active, it is undefined which one will be the one
// returned.
actions []TreeAction
}
func NewKeyTreeNode() *KeyTreeNode {
n := new(KeyTreeNode)
n.children = make(map[Event]*KeyTreeNode)
n.actions = []TreeAction{}
return n
}
// A TreeAction stores an action, and a set of mode constraints for
// the action to be active.
type TreeAction struct {
// only one of these can be non-nil
action PaneKeyAction
any PaneKeyAnyAction
mouse PaneMouseAction
modes []ModeConstraint
}
// A KeyTree is a data structure for storing keybindings. It maps
// key events to actions, and maintains a set of currently enabled
// modes, which affects the action that is returned for a key event.
// The tree acts like a Trie for Events to handle sequence events.
type KeyTree struct {
root *KeyTreeNode
modes map[string]bool
cursor KeyTreeCursor
}
// A KeyTreeCursor keeps track of the current location within the
// tree, and stores any information from previous events that may
// be needed to execute the action (values of wildcard events or
// mouse events)
type KeyTreeCursor struct {
node *KeyTreeNode
recordedEvents []Event
wildcards []KeyEvent
mouseInfo *tcell.EventMouse
}
// MakeClosure uses the information stored in a key tree cursor to construct
// a PaneKeyAction from a TreeAction (which may have a PaneKeyAction, PaneMouseAction,
// or AnyAction)
func (k *KeyTreeCursor) MakeClosure(a TreeAction) PaneKeyAction {
if a.action != nil {
return a.action
} else if a.any != nil {
return func(p Pane) bool {
return a.any(p, k.wildcards)
}
} else if a.mouse != nil {
return func(p Pane) bool {
return a.mouse(p, k.mouseInfo)
}
}
return nil
}
// NewKeyTree allocates and returns an empty key tree
func NewKeyTree() *KeyTree {
root := NewKeyTreeNode()
tree := new(KeyTree)
tree.root = root
tree.modes = make(map[string]bool)
tree.cursor = KeyTreeCursor{
node: root,
wildcards: []KeyEvent{},
mouseInfo: nil,
}
return tree
}
// A ModeConstraint specifies that an action can only be executed
// while a certain mode is enabled or disabled.
type ModeConstraint struct {
mode string
disabled bool
}
// RegisterKeyBinding registers a PaneKeyAction with an Event.
func (k *KeyTree) RegisterKeyBinding(e Event, a PaneKeyAction) {
k.registerBinding(e, TreeAction{
action: a,
any: nil,
mouse: nil,
modes: nil,
})
}
// RegisterKeyAnyBinding registers a PaneKeyAnyAction with an Event.
// The event should contain an "any" event.
func (k *KeyTree) RegisterKeyAnyBinding(e Event, a PaneKeyAnyAction) {
k.registerBinding(e, TreeAction{
action: nil,
any: a,
mouse: nil,
modes: nil,
})
}
// RegisterMouseBinding registers a PaneMouseAction with an Event.
// The event should contain a mouse event.
func (k *KeyTree) RegisterMouseBinding(e Event, a PaneMouseAction) {
k.registerBinding(e, TreeAction{
action: nil,
any: nil,
mouse: a,
modes: nil,
})
}
func (k *KeyTree) registerBinding(e Event, a TreeAction) {
switch ev := e.(type) {
case KeyEvent, MouseEvent, RawEvent:
newNode, ok := k.root.children[e]
if !ok {
newNode = NewKeyTreeNode()
k.root.children[e] = newNode
}
// newNode.actions = append(newNode.actions, a)
newNode.actions = []TreeAction{a}
case KeySequenceEvent:
n := k.root
for _, key := range ev.keys {
newNode, ok := n.children[key]
if !ok {
newNode = NewKeyTreeNode()
n.children[key] = newNode
}
n = newNode
}
// n.actions = append(n.actions, a)
n.actions = []TreeAction{a}
}
}
// NextEvent returns the action for the current sequence where e is the next
// event. Even if the action was registered as a PaneKeyAnyAction or PaneMouseAction,
// it will be returned as a PaneKeyAction closure where the appropriate arguments
// have been provided.
// If no action is associated with the given Event, or mode constraints are not
// met for that action, nil is returned.
// A boolean is returned to indicate if there is a conflict with this action. A
// conflict occurs when there is an active action for this event but there are
// bindings associated with further sequences starting with this event. The
// calling function can decide what to do about the conflict (e.g. use a
// timeout).
func (k *KeyTree) NextEvent(e Event, mouse *tcell.EventMouse) (PaneKeyAction, bool) {
n := k.cursor.node
c, ok := n.children[e]
if !ok {
return nil, false
}
more := len(c.children) > 0
k.cursor.node = c
k.cursor.recordedEvents = append(k.cursor.recordedEvents, e)
switch ev := e.(type) {
case KeyEvent:
if ev.any {
k.cursor.wildcards = append(k.cursor.wildcards, ev)
}
case MouseEvent:
k.cursor.mouseInfo = mouse
}
if len(c.actions) > 0 {
// check if actions are active
for _, a := range c.actions {
active := true
for _, mc := range a.modes {
// if any mode constraint is not met, the action is not active
hasMode := k.modes[mc.mode]
if hasMode != mc.disabled {
active = false
}
}
if active {
// the first active action to be found is returned
return k.cursor.MakeClosure(a), more
}
}
}
return nil, more
}
// ResetEvents sets the current sequence back to the initial value.
func (k *KeyTree) ResetEvents() {
k.cursor.node = k.root
k.cursor.wildcards = []KeyEvent{}
k.cursor.recordedEvents = []Event{}
k.cursor.mouseInfo = nil
}
// RecordedEventsStr returns the list of recorded events as a string
func (k *KeyTree) RecordedEventsStr() string {
buf := &bytes.Buffer{}
for _, e := range k.cursor.recordedEvents {
buf.WriteString(e.Name())
}
return buf.String()
}
// DeleteBinding removes any currently active actions associated with the
// given event.
func (k *KeyTree) DeleteBinding(e Event) {
}
// DeleteAllBindings removes all actions associated with the given event,
// regardless of whether they are active or not.
func (k *KeyTree) DeleteAllBindings(e Event) {
}
// SetMode enables or disabled a given mode
func (k *KeyTree) SetMode(mode string, en bool) {
k.modes[mode] = en
}
// HasMode returns if the given mode is currently active
func (k *KeyTree) HasMode(mode string) bool {
return k.modes[mode]
}

View File

@@ -4,6 +4,7 @@ import (
"github.com/zyedidia/micro/v2/internal/display"
)
// A Pane is a general interface for a window in the editor.
type Pane interface {
Handler
display.Window

View File

@@ -6,7 +6,7 @@ import (
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
type RawPane struct {
@@ -36,8 +36,8 @@ func (h *RawPane) HandleEvent(event tcell.Event) {
h.Buf.Insert(h.Cursor.Loc, reflect.TypeOf(event).String()[7:])
switch e := event.(type) {
case *tcell.EventKey:
e, err := ConstructEvent(event)
if err == nil {
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %s", e.Name()))
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/views"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
// The TabList is a list of tabs and a window to display the tab bar
@@ -148,7 +148,19 @@ func (t *TabList) Display() {
var Tabs *TabList
func InitTabs(bufs []*buffer.Buffer) {
Tabs = NewTabList(bufs)
multiopen := config.GetGlobalOption("multiopen").(string)
if multiopen == "tab" {
Tabs = NewTabList(bufs)
} else {
Tabs = NewTabList(bufs[:1])
for _, b := range bufs[1:] {
if multiopen == "vsplit" {
MainTab().CurPane().VSplitBuf(b)
} else { // default hsplit
MainTab().CurPane().HSplitBuf(b)
}
}
}
}
func MainTab() *Tab {
@@ -175,6 +187,7 @@ func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
t := new(Tab)
t.Node = views.NewRoot(x, y, width, height)
t.UIWindow = display.NewUIWindow(t.Node)
t.release = true
e := NewBufPaneFromBuf(b, t)
e.SetID(t.ID())
@@ -187,6 +200,7 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
t := new(Tab)
t.Node = views.NewRoot(x, y, width, height)
t.UIWindow = display.NewUIWindow(t.Node)
t.release = true
pane.SetTab(t)
pane.SetID(t.ID())
@@ -220,9 +234,8 @@ func (t *Tab) HandleEvent(event tcell.Event) {
}
if wasReleased {
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
if resizeID != 0 {
t.resizing = t.GetNode(uint64(resizeID))
t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my})
if t.resizing != nil {
return
}

View File

@@ -24,7 +24,10 @@ func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callba
}
t := new(shell.Terminal)
t.Start(args, getOutput, wait, callback, userargs)
err = t.Start(args, getOutput, wait, callback, userargs)
if err != nil {
return err
}
h.AddTab()
id := MainTab().Panes[0].ID()

View File

@@ -4,14 +4,52 @@ import (
"errors"
"runtime"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
"github.com/zyedidia/terminal"
)
type TermKeyAction func(*TermPane)
var TermBindings *KeyTree
func init() {
TermBindings = NewKeyTree()
}
func TermKeyActionGeneral(a TermKeyAction) PaneKeyAction {
return func(p Pane) bool {
a(p.(*TermPane))
return true
}
}
func TermMapEvent(k Event, action string) {
config.Bindings["terminal"][k.Name()] = action
switch e := k.(type) {
case KeyEvent, KeySequenceEvent, RawEvent:
termMapKey(e, action)
case MouseEvent:
termMapMouse(e, action)
}
}
func termMapKey(k Event, action string) {
if f, ok := TermKeyActions[action]; ok {
TermBindings.RegisterKeyBinding(k, TermKeyActionGeneral(f))
}
}
func termMapMouse(k MouseEvent, action string) {
// TODO: map mouse
termMapKey(k, action)
}
type TermPane struct {
*shell.Terminal
display.Window
@@ -53,6 +91,7 @@ func (t *TermPane) Tab() *Tab {
func (t *TermPane) Close() {}
// Quit closes this termpane
func (t *TermPane) Quit() {
t.Close()
if len(MainTab().Panes) > 1 {
@@ -66,6 +105,7 @@ func (t *TermPane) Quit() {
}
}
// Unsplit removes this split
func (t *TermPane) Unsplit() {
n := MainTab().GetNode(t.id)
n.Unsplit()
@@ -81,6 +121,26 @@ func (t *TermPane) Unsplit() {
// copy-paste
func (t *TermPane) HandleEvent(event tcell.Event) {
if e, ok := event.(*tcell.EventKey); ok {
ke := KeyEvent{
code: e.Key(),
mod: metaToAlt(e.Modifiers()),
r: e.Rune(),
}
action, more := TermBindings.NextEvent(ke, nil)
if !more {
if action != nil {
action(t)
TermBindings.ResetEvents()
return
}
TermBindings.ResetEvents()
}
if more {
return
}
if t.Status == shell.TTDone {
switch e.Key() {
case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter:
@@ -90,7 +150,7 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
}
}
if e.Key() == tcell.KeyCtrlC && t.HasSelection() {
clipboard.WriteAll(t.GetSelection(t.GetView().Width), "clipboard")
clipboard.Write(t.GetSelection(t.GetView().Width), clipboard.ClipboardReg)
InfoBar.Message("Copied selection to clipboard")
} else if t.Status != shell.TTDone {
t.WriteString(event.EscSeq())
@@ -134,6 +194,41 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
}
}
// Exit closes the termpane
func (t *TermPane) Exit() {
t.Terminal.Close()
t.Quit()
}
// CommandMode opens the termpane's command mode
func (t *TermPane) CommandMode() {
InfoBar.Prompt("> ", "", "TerminalCommand", nil, func(resp string, canceled bool) {
if !canceled {
t.HandleCommand(resp)
}
})
}
// NextSplit moves to the next split
func (t *TermPane) NextSplit() {
a := t.tab.active
if a < len(t.tab.Panes)-1 {
a++
} else {
a = 0
}
t.tab.SetActive(a)
}
// HandleCommand handles a command for the term pane
func (t *TermPane) HandleCommand(input string) {
InfoBar.Error("Commands are unsupported in term for now")
}
// TermKeyActions contains the list of all possible key actions the termpane could execute
var TermKeyActions = map[string]TermKeyAction{
"Exit": (*TermPane).Exit,
"CommandMode": (*TermPane).CommandMode,
"NextSplit": (*TermPane).NextSplit,
}

View File

@@ -54,8 +54,6 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
end := c.Loc
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
} else {
// end = start.Move(1, b)
}
b.Replace(start, end, b.Completions[b.CurSuggestion])
@@ -71,11 +69,11 @@ func GetWord(b *Buffer) ([]byte, int) {
l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X)
if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc)) {
if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc.Move(-1, b))) {
return []byte{}, -1
}
if util.IsNonAlphaNumeric(b.RuneAt(c.Loc)) {
if util.IsNonAlphaNumeric(b.RuneAt(c.Loc.Move(-1, b))) {
return []byte{}, c.X
}

View File

@@ -5,6 +5,7 @@ import (
"io"
"os"
"path/filepath"
"sync/atomic"
"time"
"github.com/zyedidia/micro/v2/internal/config"
@@ -25,32 +26,60 @@ The backup was created on %s, and the file is
When the buffer is closed, the backup will be removed.
* 'ignore' will ignore the backup, discarding its changes. The backup file
will be removed.
* 'abort' will abort the open operation, and instead open an empty buffer.
Options: [r]ecover, [i]gnore: `
Options: [r]ecover, [i]gnore, [a]bort: `
var backupRequestChan chan *Buffer
func backupThread() {
for {
time.Sleep(time.Second * 8)
for len(backupRequestChan) > 0 {
b := <-backupRequestChan
bfini := atomic.LoadInt32(&(b.fini)) != 0
if !bfini {
b.Backup()
}
}
}
}
func init() {
backupRequestChan = make(chan *Buffer, 10)
go backupThread()
}
func (b *Buffer) RequestBackup() {
if !b.requestedBackup {
select {
case backupRequestChan <- b:
default:
// channel is full
}
b.requestedBackup = true
}
}
// Backup saves the current buffer to ConfigDir/backups
func (b *Buffer) Backup(checkTime bool) error {
func (b *Buffer) Backup() error {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
return nil
}
if checkTime {
sub := time.Now().Sub(b.lastbackup)
if sub < time.Duration(backupTime)*time.Millisecond {
return nil
}
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
if backupdir == "" || err != nil {
backupdir = filepath.Join(config.ConfigDir, "backups")
}
b.lastbackup = time.Now()
backupdir := filepath.Join(config.ConfigDir, "backups")
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
os.Mkdir(backupdir, os.ModePerm)
}
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
err := overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
}
@@ -74,12 +103,14 @@ func (b *Buffer) Backup(checkTime bool) error {
return
}, false)
b.requestedBackup = false
return err
}
// RemoveBackup removes any backup file associated with this buffer
func (b *Buffer) RemoveBackup() {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
return
}
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
@@ -88,8 +119,8 @@ func (b *Buffer) RemoveBackup() {
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
// Returns true if a backup was applied
func (b *Buffer) ApplyBackup(fsize int64) bool {
if b.Settings["backup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
if info, err := os.Stat(backupfile); err == nil {
backup, err := os.Open(backupfile)
@@ -97,20 +128,22 @@ func (b *Buffer) ApplyBackup(fsize int64) bool {
defer backup.Close()
t := info.ModTime()
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
choice := screen.TermPrompt(msg, []string{"r", "i", "recover", "ignore"}, true)
choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)
if choice%2 == 0 {
if choice%3 == 0 {
// recover
b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
b.isModified = true
return true
} else if choice%2 == 1 {
return true, true
} else if choice%3 == 1 {
// delete
os.Remove(backupfile)
} else if choice%3 == 2 {
return false, false
}
}
}
}
return false
return false, true
}

View File

@@ -14,6 +14,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
luar "layeh.com/gopher-luar"
@@ -102,9 +103,7 @@ type SharedBuffer struct {
diffLock sync.RWMutex
diff map[int]DiffStatus
// counts the number of edits
// resets every backupTime edits
lastbackup time.Time
requestedBackup bool
// ReloadDisabled allows the user to disable reloads if they
// are viewing a file that is constantly changing
@@ -147,18 +146,20 @@ func (b *SharedBuffer) remove(start, end Loc) []byte {
func (b *SharedBuffer) MarkModified(start, end int) {
b.ModifiedThisFrame = true
if !b.Settings["syntax"].(bool) || b.SyntaxDef == nil {
return
}
start = util.Clamp(start, 0, len(b.lines)-1)
end = util.Clamp(end, 0, len(b.lines)-1)
l := -1
for i := start; i <= end; i++ {
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
l := -1
for i := start; i <= end; i++ {
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
}
b.Highlighter.HighlightMatches(b, start, l)
}
for i := start; i <= end; i++ {
b.LineArray.invalidateSearchMatches(i)
}
b.Highlighter.HighlightMatches(b, start, l)
}
// DisableReload disables future reloads of this sharedbuffer
@@ -182,13 +183,34 @@ type DiffStatus byte
// The syntax highlighting info must be stored with the buffer because the syntax
// highlighter attaches information to each line of the buffer for optimization
// purposes so it doesn't have to rehighlight everything on every update.
// Likewise for the search highlighting.
type Buffer struct {
*EventHandler
*SharedBuffer
fini int32
cursors []*Cursor
curCursor int
StartCursor Loc
// OptionCallback is called after a buffer option value is changed.
// The display module registers its OptionCallback to ensure the buffer window
// is properly updated when needed. This is a workaround for the fact that
// the buffer module cannot directly call the display's API (it would mean
// a circular dependency between packages).
OptionCallback func(option string, nativeValue interface{})
// The display module registers its own GetVisualX function for getting
// the correct visual x location of a cursor when softwrap is used.
// This is hacky. Maybe it would be better to move all the visual x logic
// from buffer to display, but it would require rewriting a lot of code.
GetVisualX func(loc Loc) int
// Last search stores the last successful search
LastSearch string
LastSearchRegex bool
// HighlightSearch enables highlighting all instances of the last successful search
HighlightSearch bool
}
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
@@ -212,21 +234,39 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
return nil, err
}
file, err := os.Open(filename)
fileInfo, _ := os.Stat(filename)
f, err := os.OpenFile(filename, os.O_WRONLY, 0)
readonly := os.IsPermission(err)
f.Close()
if err == nil && fileInfo.IsDir() {
fileInfo, serr := os.Stat(filename)
if serr != nil && !os.IsNotExist(serr) {
return nil, serr
}
if serr == nil && fileInfo.IsDir() {
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
}
defer file.Close()
file, err := os.Open(filename)
if err == nil {
defer file.Close()
}
var buf *Buffer
if err != nil {
if os.IsNotExist(err) {
// File does not exist -- create an empty buffer with that name
buf = NewBufferFromString("", filename, btype)
} else if err != nil {
return nil, err
} else {
buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
if buf == nil {
return nil, errors.New("could not open file")
}
}
if readonly && prompt != nil {
prompt.Message(fmt.Sprintf("Warning: file is readonly - %s will be attempted when saving", config.GlobalSettings["sucmd"].(string)))
// buf.SetOptionNative("readonly", true)
}
return buf, nil
@@ -256,7 +296,10 @@ func NewBufferFromString(text, path string, btype BufType) *Buffer {
// Places the cursor at startcursor. If startcursor is -1, -1 places the
// cursor at an autodetected location (based on savecursor or :LINE:COL)
func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufType) *Buffer {
absPath, _ := filepath.Abs(path)
absPath, err := filepath.Abs(path)
if err != nil {
absPath = path
}
b := new(Buffer)
@@ -271,6 +314,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
}
}
hasBackup := false
if !found {
b.SharedBuffer = new(SharedBuffer)
b.Type = btype
@@ -278,23 +322,38 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
b.AbsPath = absPath
b.Path = path
// this is a little messy since we need to know some settings to read
// the file properly, but some settings depend on the filetype, which
// we don't know until reading the file. We first read the settings
// into a local variable and then use that to determine the encoding,
// readonly, and fileformat necessary for reading the file and
// assigning the filetype.
settings := config.DefaultCommonSettings()
b.Settings = config.DefaultCommonSettings()
for k, v := range config.GlobalSettings {
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
// make sure setting is not global-only
settings[k] = v
b.Settings[k] = v
}
}
config.InitLocalSettings(b.Settings, path)
config.InitLocalSettings(settings, absPath)
b.Settings["readonly"] = settings["readonly"]
b.Settings["filetype"] = settings["filetype"]
b.Settings["syntax"] = settings["syntax"]
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
enc, err := htmlindex.Get(settings["encoding"].(string))
if err != nil {
enc = unicode.UTF8
b.Settings["encoding"] = "utf-8"
}
hasBackup := b.ApplyBackup(size)
var ok bool
hasBackup, ok = b.ApplyBackup(size)
if !ok {
return NewBufferFromString("", "", btype)
}
if !hasBackup {
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
@@ -303,7 +362,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
if size == 0 {
// for empty files, use the fileformat setting instead of
// autodetection
switch b.Settings["fileformat"] {
switch settings["fileformat"] {
case "unix":
ff = FFUnix
case "dos":
@@ -340,12 +399,10 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
if startcursor.X != -1 && startcursor.Y != -1 {
b.StartCursor = startcursor
} else {
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
err := b.Unserialize()
if err != nil {
screen.TermMessage(err)
}
} else if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
err := b.Unserialize()
if err != nil {
screen.TermMessage(err)
}
}
@@ -356,12 +413,14 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
if size > LargeFileThreshold {
// If the file is larger than LargeFileThreshold fastdirty needs to be on
b.Settings["fastdirty"] = true
} else {
} else if !hasBackup {
// since applying a backup does not save the applied backup to disk, we should
// not calculate the original hash based on the backup data
calcHash(b, &b.origHash)
}
}
err := config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
if err != nil {
screen.TermMessage(err)
}
@@ -395,6 +454,8 @@ func (b *Buffer) Fini() {
if b.Type == BTStdout {
fmt.Fprint(util.Stdout, string(b.Bytes()))
}
atomic.StoreInt32(&(b.fini), int32(1))
}
// GetName returns the name that should be displayed in the statusline
@@ -425,7 +486,7 @@ func (b *Buffer) Insert(start Loc, text string) {
b.EventHandler.active = b.curCursor
b.EventHandler.Insert(start, text)
go b.Backup(true)
b.RequestBackup()
}
}
@@ -436,7 +497,7 @@ func (b *Buffer) Remove(start, end Loc) {
b.EventHandler.active = b.curCursor
b.EventHandler.Remove(start, end)
go b.Backup(true)
b.RequestBackup()
}
}
@@ -506,16 +567,38 @@ func (b *Buffer) RuneAt(loc Loc) rune {
for len(line) > 0 {
r, _, size := util.DecodeCharacter(line)
line = line[size:]
i++
if i == loc.X {
return r
}
i++
}
}
return '\n'
}
// WordAt returns the word around a given location in the buffer
func (b *Buffer) WordAt(loc Loc) []byte {
if len(b.LineBytes(loc.Y)) == 0 || !util.IsWordChar(b.RuneAt(loc)) {
return []byte{}
}
start := loc
end := loc.Move(1, b)
for start.X > 0 && util.IsWordChar(b.RuneAt(start.Move(-1, b))) {
start.X--
}
lineLen := util.CharacterCount(b.LineBytes(loc.Y))
for end.X < lineLen && util.IsWordChar(b.RuneAt(end)) {
end.X++
}
return b.Substr(start, end)
}
// Modified returns if this buffer has been modified since
// being opened
func (b *Buffer) Modified() bool {
@@ -533,6 +616,22 @@ func (b *Buffer) Modified() bool {
return buff != b.origHash
}
// Size returns the number of bytes in the current buffer
func (b *Buffer) Size() int {
nb := 0
for i := 0; i < b.LinesNum(); i++ {
nb += len(b.LineBytes(i))
if i != b.LinesNum()-1 {
if b.Endings == FFDos {
nb++ // carriage return
}
nb++ // newline
}
}
return nb
}
// calcHash calculates md5 hash of all lines in the buffer
func calcHash(b *Buffer, out *[md5.Size]byte) error {
h := md5.New()
@@ -589,6 +688,9 @@ func (b *Buffer) UpdateRules() {
}
header, err = highlight.MakeHeaderYaml(data)
if err != nil {
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())
@@ -964,9 +1066,9 @@ func (b *Buffer) Retab() {
ws := util.GetLeadingWhitespace(l)
if len(ws) != 0 {
if toSpaces {
ws = bytes.Replace(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize), -1)
ws = bytes.ReplaceAll(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize))
} else {
ws = bytes.Replace(ws, bytes.Repeat([]byte{' '}, tabsize), []byte{'\t'}, -1)
ws = bytes.ReplaceAll(ws, bytes.Repeat([]byte{' '}, tabsize), []byte{'\t'})
}
}
@@ -1109,6 +1211,12 @@ func (b *Buffer) DiffStatus(lineN int) DiffStatus {
return b.diff[lineN]
}
// SearchMatch returns true if the given location is within a match of the last search.
// It is used for search highlighting
func (b *Buffer) SearchMatch(pos Loc) bool {
return b.LineArray.SearchMatch(b, pos)
}
// WriteLog writes a string to the log buffer
func WriteLog(s string) {
LogBuf.EventHandler.Insert(LogBuf.End(), s)

View File

@@ -1,7 +1,7 @@
package buffer
import (
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/util"
)
@@ -67,6 +67,10 @@ func (c *Cursor) GotoLoc(l Loc) {
// GetVisualX returns the x value of the cursor in visual spaces
func (c *Cursor) GetVisualX() int {
if c.buf.GetVisualX != nil {
return c.buf.GetVisualX(c.Loc)
}
if c.X <= 0 {
c.X = 0
return 0
@@ -125,10 +129,10 @@ func (c *Cursor) End() {
// CopySelection copies the user's selection to either "primary"
// or "clipboard"
func (c *Cursor) CopySelection(target string) {
func (c *Cursor) CopySelection(target clipboard.Register) {
if c.HasSelection() {
if target != "primary" || c.buf.Settings["useprimary"].(bool) {
clipboard.WriteAll(string(c.GetSelection()), target)
if target != clipboard.PrimaryReg || c.buf.Settings["useprimary"].(bool) {
clipboard.WriteMulti(string(c.GetSelection()), target, c.Num, c.buf.NumCursors())
}
}
}

View File

@@ -32,6 +32,15 @@ func runeToByteIndex(n int, txt []byte) int {
return count
}
// A searchState contains the search match info for a single line
type searchState struct {
search string
useRegex bool
ignorecase bool
match [][2]int
done bool
}
// A Line contains the data in bytes as well as a highlight state, match
// and a flag for whether the highlighting needs to be updated
type Line struct {
@@ -41,6 +50,14 @@ type Line struct {
match highlight.LineMatch
rehighlight bool
lock sync.Mutex
// The search states for the line, used for highlighting of search matches,
// separately from the syntax highlighting.
// A map is used because the line array may be shared between multiple buffers
// (multiple instances of the same file opened in different edit panes)
// which have distinct searches, so in the general case there are multiple
// searches per a line, one search per a Buffer containing this line.
search map[*Buffer]*searchState
}
const (
@@ -130,7 +147,7 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
if err != nil {
if err == io.EOF {
la.lines = Append(la.lines, Line{
data: data[:],
data: data,
state: nil,
match: nil,
rehighlight: false,
@@ -191,10 +208,15 @@ func (la *LineArray) newlineBelow(y int) {
func (la *LineArray) insert(pos Loc, value []byte) {
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
for i := 0; i < len(value); i++ {
if value[i] == '\n' {
if value[i] == '\n' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') {
la.split(Loc{x, y})
x = 0
y++
if value[i] == '\r' {
i++
}
continue
}
la.insertByte(Loc{x, y}, value[i])
@@ -351,3 +373,79 @@ func (la *LineArray) SetRehighlight(lineN int, on bool) {
defer la.lines[lineN].lock.Unlock()
la.lines[lineN].rehighlight = on
}
// SearchMatch returns true if the location `pos` is within a match
// of the last search for the buffer `b`.
// It is used for efficient highlighting of search matches (separately
// from the syntax highlighting).
// SearchMatch searches for the matches if it is called first time
// for the given line or if the line was modified. Otherwise the
// previously found matches are used.
//
// The buffer `b` needs to be passed because the line array may be shared
// between multiple buffers (multiple instances of the same file opened
// in different edit panes) which have distinct searches, so SearchMatch
// needs to know which search to match against.
func (la *LineArray) SearchMatch(b *Buffer, pos Loc) bool {
if b.LastSearch == "" {
return false
}
lineN := pos.Y
if la.lines[lineN].search == nil {
la.lines[lineN].search = make(map[*Buffer]*searchState)
}
s, ok := la.lines[lineN].search[b]
if !ok {
// Note: here is a small harmless leak: when the buffer `b` is closed,
// `s` is not deleted from the map. It means that the buffer
// will not be garbage-collected until the line array is garbage-collected,
// i.e. until all the buffers sharing this file are closed.
s = new(searchState)
la.lines[lineN].search[b] = s
}
if !ok || s.search != b.LastSearch || s.useRegex != b.LastSearchRegex ||
s.ignorecase != b.Settings["ignorecase"].(bool) {
s.search = b.LastSearch
s.useRegex = b.LastSearchRegex
s.ignorecase = b.Settings["ignorecase"].(bool)
s.done = false
}
if !s.done {
s.match = nil
start := Loc{0, lineN}
end := Loc{util.CharacterCount(la.lines[lineN].data), lineN}
for start.X < end.X {
m, found, _ := b.FindNext(b.LastSearch, start, end, start, true, b.LastSearchRegex)
if !found {
break
}
s.match = append(s.match, [2]int{m[0].X, m[1].X})
start.X = m[1].X
if m[1].X == m[0].X {
start.X = m[1].X + 1
}
}
s.done = true
}
for _, m := range s.match {
if pos.X >= m[0] && pos.X < m[1] {
return true
}
}
return false
}
// invalidateSearchMatches marks search matches for the given line as outdated.
// It is called when the line is modified.
func (la *LineArray) invalidateSearchMatches(lineN int) {
if la.lines[lineN].search != nil {
for _, s := range la.lines[lineN].search {
s.done = false
}
}
}

View File

@@ -2,7 +2,7 @@ package buffer
import (
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
type MsgType int
@@ -82,3 +82,13 @@ func (b *Buffer) ClearMessages(owner string) {
func (b *Buffer) ClearAllMessages() {
b.Messages = make([]*Message, 0)
}
type Messager interface {
Message(msg ...interface{})
}
var prompt Messager
func SetMessager(m Messager) {
prompt = m
}

View File

@@ -29,9 +29,11 @@ const LargeFileThreshold = 50000
// closed afterwards.
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
var writeCloser io.WriteCloser
var screenb bool
var cmd *exec.Cmd
if withSudo {
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
if writeCloser, err = cmd.StdinPipe(); err != nil {
return
@@ -44,13 +46,13 @@ func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error,
cmd.Process.Kill()
}()
defer func() {
screenb := screen.TempFini()
if e := cmd.Run(); e != nil && err == nil {
err = e
}
screen.TempStart(screenb)
}()
screenb = screen.TempFini()
// need to start the process now, otherwise when we flush the file
// 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
if e := cmd.Start(); e != nil && err == nil {
return err
}
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return
}
@@ -63,6 +65,15 @@ func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error,
err = e
}
if withSudo {
// wait for dd to finish and restart the screen if we used sudo
err := cmd.Wait()
if err != nil {
return err
}
screen.TempStart(screenb)
}
return
}
@@ -109,7 +120,7 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
if b.Settings["eofnewline"].(bool) {
end := b.End()
if b.RuneAt(Loc{end.X, end.Y}) != '\n' {
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
b.insert(end, []byte{'\n'})
}
}

View File

@@ -7,6 +7,13 @@ import (
)
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
if start.Y > b.LinesNum()-1 {
start.X = lastcn - 1
}
if end.Y > b.LinesNum()-1 {
end.X = lastcn
}
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
@@ -48,6 +55,13 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
}
func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
if start.Y > b.LinesNum()-1 {
start.X = lastcn - 1
}
if end.Y > b.LinesNum()-1 {
end.X = lastcn
}
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
@@ -77,9 +91,10 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
l = util.SliceStart(l, end.X)
}
match := r.FindIndex(l)
allMatches := r.FindAllIndex(l, -1)
if match != nil {
if allMatches != nil {
match := allMatches[len(allMatches)-1]
start := Loc{charpos + util.RunePos(l, match[0]), i}
end := Loc{charpos + util.RunePos(l, match[1]), i}
return [2]Loc{start, end}, true
@@ -132,7 +147,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
// and returns the number of replacements made and the number of runes
// added or removed
// added or removed on the last line of the range
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) {
if start.GreaterThan(end) {
start, end = end, start
@@ -162,7 +177,9 @@ func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []b
result = search.Expand(result, replace, in, submatches)
}
found++
netrunes += util.CharacterCount(in) - util.CharacterCount(result)
if i == end.Y {
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
}
return result
})

View File

@@ -51,8 +51,8 @@ func (b *Buffer) Unserialize() error {
return nil
}
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
defer file.Close()
if err == nil {
defer file.Close()
var buffer SerializedBuffer
decoder := gob.NewDecoder(file)
err = decoder.Decode(&buffer)

View File

@@ -10,14 +10,26 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
if option == "fastdirty" {
if !nativeValue.(bool) {
e := calcHash(b, &b.origHash)
if e == ErrFileTooLarge {
b.Settings["fastdirty"] = false
if !b.Modified() {
e := calcHash(b, &b.origHash)
if e == ErrFileTooLarge {
b.Settings["fastdirty"] = false
}
}
}
} else if option == "statusline" {
screen.Redraw()
} else if option == "filetype" {
config.InitRuntimeFiles()
err := config.ReadSettings()
if err != nil {
screen.TermMessage(err)
}
err = config.InitGlobalSettings()
if err != nil {
screen.TermMessage(err)
}
config.InitLocalSettings(b.Settings, b.Path)
b.UpdateRules()
} else if option == "fileformat" {
switch b.Settings["fileformat"].(string) {
@@ -37,6 +49,16 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
b.isModified = true
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
b.Type.Readonly = nativeValue.(bool)
} else if option == "hlsearch" {
for _, buf := range OpenBuffers {
if b.SharedBuffer == buf.SharedBuffer {
buf.HighlightSearch = nativeValue.(bool)
}
}
}
if b.OptionCallback != nil {
b.OptionCallback(option, nativeValue)
}
return nil

View File

@@ -0,0 +1,163 @@
package clipboard
import (
"errors"
"github.com/zyedidia/clipper"
)
type Method int
const (
// External relies on external tools for accessing the clipboard
// These include xclip, xsel, wl-clipboard for linux, pbcopy/pbpaste on Mac,
// and Syscalls on Windows.
External Method = iota
// Terminal uses the terminal to manage the clipboard via OSC 52. Many
// terminals do not support OSC 52, in which case this method won't work.
Terminal
// Internal just manages the clipboard with an internal buffer and doesn't
// attempt to interface with the system clipboard
Internal
)
// CurrentMethod is the method used to store clipboard information
var CurrentMethod Method = Internal
// A Register is a buffer used to store text. The system clipboard has the 'clipboard'
// and 'primary' (linux-only) registers, but other registers may be used internal to micro.
type Register int
const (
// ClipboardReg is the main system clipboard
ClipboardReg Register = -1
// PrimaryReg is the system primary clipboard (linux only)
PrimaryReg = -2
)
var clipboard clipper.Clipboard
// Initialize attempts to initialize the clipboard using the given method
func Initialize(m Method) error {
var err error
switch m {
case External:
clips := make([]clipper.Clipboard, 0, len(clipper.Clipboards)+1)
clips = append(clips, &clipper.Custom{
Name: "micro-clip",
})
clips = append(clips, clipper.Clipboards...)
clipboard, err = clipper.GetClipboard(clips...)
}
if err != nil {
CurrentMethod = Internal
}
return err
}
// SetMethod changes the clipboard access method
func SetMethod(m string) Method {
switch m {
case "internal":
CurrentMethod = Internal
case "external":
CurrentMethod = External
case "terminal":
CurrentMethod = Terminal
}
return CurrentMethod
}
// Read reads from a clipboard register
func Read(r Register) (string, error) {
return read(r, CurrentMethod)
}
// Write writes text to a clipboard register
func Write(text string, r Register) error {
return write(text, r, CurrentMethod)
}
// ReadMulti reads text from a clipboard register for a certain multi-cursor
func ReadMulti(r Register, num, ncursors int) (string, error) {
clip, err := Read(r)
if err != nil {
return "", err
}
if ValidMulti(r, clip, ncursors) {
return multi.getText(r, num), nil
}
return clip, nil
}
// WriteMulti writes text to a clipboard register for a certain multi-cursor
func WriteMulti(text string, r Register, num int, ncursors int) error {
return writeMulti(text, r, num, ncursors, CurrentMethod)
}
// ValidMulti checks if the internal multi-clipboard is valid and up-to-date
// with the system clipboard
func ValidMulti(r Register, clip string, ncursors int) bool {
return multi.isValid(r, clip, ncursors)
}
func writeMulti(text string, r Register, num int, ncursors int, m Method) error {
multi.writeText(text, r, num, ncursors)
return write(multi.getAllText(r), r, m)
}
func read(r Register, m Method) (string, error) {
switch m {
case External:
switch r {
case ClipboardReg:
b, e := clipboard.ReadAll(clipper.RegClipboard)
return string(b), e
case PrimaryReg:
b, e := clipboard.ReadAll(clipper.RegPrimary)
return string(b), e
default:
return internal.read(r), nil
}
case Internal:
return internal.read(r), nil
case Terminal:
switch r {
case ClipboardReg:
// terminal paste works by sending an esc sequence to the
// terminal to trigger a paste event
return terminal.read("clipboard")
case PrimaryReg:
return terminal.read("primary")
default:
return internal.read(r), nil
}
}
return "", errors.New("Invalid clipboard method")
}
func write(text string, r Register, m Method) error {
switch m {
case External:
switch r {
case ClipboardReg:
return clipboard.WriteAll(clipper.RegClipboard, []byte(text))
case PrimaryReg:
return clipboard.WriteAll(clipper.RegPrimary, []byte(text))
default:
internal.write(text, r)
}
case Internal:
internal.write(text, r)
case Terminal:
switch r {
case ClipboardReg:
return terminal.write(text, "c")
case PrimaryReg:
return terminal.write(text, "p")
default:
internal.write(text, r)
}
}
return nil
}

View File

@@ -0,0 +1,17 @@
package clipboard
type internalClipboard map[Register]string
var internal internalClipboard
func init() {
internal = make(internalClipboard)
}
func (c internalClipboard) read(r Register) string {
return c[r]
}
func (c internalClipboard) write(text string, r Register) {
c[r] = text
}

View File

@@ -0,0 +1,63 @@
package clipboard
import (
"bytes"
)
// For storing multi cursor clipboard contents
type multiClipboard map[Register][]string
var multi multiClipboard
func (c multiClipboard) getAllText(r Register) string {
content := c[r]
if content == nil {
return ""
}
buf := &bytes.Buffer{}
for _, s := range content {
buf.WriteString(s)
}
return buf.String()
}
func (c multiClipboard) getText(r Register, num int) string {
content := c[r]
if content == nil || len(content) <= num {
return ""
}
return content[num]
}
// isValid checks if the text stored in this multi-clipboard is the same as the
// text stored in the system clipboard (provided as an argument), and therefore
// if it is safe to use the multi-clipboard for pasting instead of the system
// clipboard.
func (c multiClipboard) isValid(r Register, clipboard string, ncursors int) bool {
content := c[r]
if content == nil || len(content) != ncursors {
return false
}
return clipboard == c.getAllText(r)
}
func (c multiClipboard) writeText(text string, r Register, num int, ncursors int) {
content := c[r]
if content == nil || len(content) != ncursors {
content = make([]string, ncursors, ncursors)
c[r] = content
}
if num >= ncursors {
return
}
content[num] = text
}
func init() {
multi = make(multiClipboard)
}

View File

@@ -0,0 +1,33 @@
package clipboard
import (
"errors"
"time"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell/v2"
)
type terminalClipboard struct{}
var terminal terminalClipboard
func (t terminalClipboard) read(reg string) (string, error) {
screen.Screen.GetClipboard(reg)
// wait at most 200ms for response
for {
select {
case event := <-screen.Events:
e, ok := event.(*tcell.EventPaste)
if ok {
return e.Text(), nil
}
case <-time.After(200 * time.Millisecond):
return "", errors.New("No clipboard received from terminal")
}
}
}
func (t terminalClipboard) write(text, reg string) error {
return screen.Screen.SetClipboard(text, reg)
}

View File

@@ -31,14 +31,13 @@ func GetAutoTime() int {
func StartAutoSave() {
go func() {
for {
if autotime < 1 {
break
}
time.Sleep(time.Duration(autotime) * time.Second)
// it's possible autotime was changed while sleeping
if autotime < 1 {
autolock.Lock()
a := autotime
autolock.Unlock()
if a < 1 {
break
}
time.Sleep(time.Duration(a) * time.Second)
Autosave <- true
}
}()

View File

@@ -6,13 +6,13 @@ import (
"strconv"
"strings"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
// Micro's default style
// DefStyle is Micro's default style
var DefStyle tcell.Style = tcell.StyleDefault
// The current colorscheme
// Colorscheme is the current colorscheme
var Colorscheme map[string]tcell.Style
// GetColor takes in a syntax group and returns the colorscheme's style for that group
@@ -117,16 +117,11 @@ func ParseColorscheme(text string) (map[string]tcell.Style, error) {
// StringToStyle returns a style from a string
// The strings must be in the format "extra foregroundcolor,backgroundcolor"
// The 'extra' can be bold, reverse, or underline
// The 'extra' can be bold, reverse, italic or underline
func StringToStyle(str string) tcell.Style {
var fg, bg string
spaceSplit := strings.Split(str, " ")
var split []string
if len(spaceSplit) > 1 {
split = strings.Split(spaceSplit[1], ",")
} else {
split = strings.Split(str, ",")
}
split := strings.Split(spaceSplit[len(spaceSplit)-1], ",")
if len(split) > 1 {
fg, bg = split[0], split[1]
} else {
@@ -136,15 +131,22 @@ func StringToStyle(str string) tcell.Style {
bg = strings.TrimSpace(bg)
var fgColor, bgColor tcell.Color
if fg == "" {
var ok bool
if fg == "" || fg == "default" {
fgColor, _, _ = DefStyle.Decompose()
} else {
fgColor = StringToColor(fg)
fgColor, ok = StringToColor(fg)
if !ok {
fgColor, _, _ = DefStyle.Decompose()
}
}
if bg == "" {
if bg == "" || bg == "default" {
_, bgColor, _ = DefStyle.Decompose()
} else {
bgColor = StringToColor(bg)
bgColor, ok = StringToColor(bg)
if !ok {
_, bgColor, _ = DefStyle.Decompose()
}
}
style := DefStyle.Foreground(fgColor).Background(bgColor)
@@ -165,95 +167,59 @@ func StringToStyle(str string) tcell.Style {
// StringToColor returns a tcell color from a string representation of a color
// We accept either bright... or light... to mean the brighter version of a color
func StringToColor(str string) tcell.Color {
func StringToColor(str string) (tcell.Color, bool) {
switch str {
case "black":
return tcell.ColorBlack
return tcell.ColorBlack, true
case "red":
return tcell.ColorMaroon
return tcell.ColorMaroon, true
case "green":
return tcell.ColorGreen
return tcell.ColorGreen, true
case "yellow":
return tcell.ColorOlive
return tcell.ColorOlive, true
case "blue":
return tcell.ColorNavy
return tcell.ColorNavy, true
case "magenta":
return tcell.ColorPurple
return tcell.ColorPurple, true
case "cyan":
return tcell.ColorTeal
return tcell.ColorTeal, true
case "white":
return tcell.ColorSilver
return tcell.ColorSilver, true
case "brightblack", "lightblack":
return tcell.ColorGray
return tcell.ColorGray, true
case "brightred", "lightred":
return tcell.ColorRed
return tcell.ColorRed, true
case "brightgreen", "lightgreen":
return tcell.ColorLime
return tcell.ColorLime, true
case "brightyellow", "lightyellow":
return tcell.ColorYellow
return tcell.ColorYellow, true
case "brightblue", "lightblue":
return tcell.ColorBlue
return tcell.ColorBlue, true
case "brightmagenta", "lightmagenta":
return tcell.ColorFuchsia
return tcell.ColorFuchsia, true
case "brightcyan", "lightcyan":
return tcell.ColorAqua
return tcell.ColorAqua, true
case "brightwhite", "lightwhite":
return tcell.ColorWhite
return tcell.ColorWhite, true
case "default":
return tcell.ColorDefault
return tcell.ColorDefault, true
default:
// Check if this is a 256 color
if num, err := strconv.Atoi(str); err == nil {
return GetColor256(num)
return GetColor256(num), true
}
// Probably a truecolor hex value
return tcell.GetColor(str)
// Check if this is a truecolor hex value
if len(str) == 7 && str[0] == '#' {
return tcell.GetColor(str), true
}
return tcell.ColorDefault, false
}
}
// GetColor256 returns the tcell color for a number between 0 and 255
func GetColor256(color int) tcell.Color {
colors := []tcell.Color{tcell.ColorBlack, tcell.ColorMaroon, tcell.ColorGreen,
tcell.ColorOlive, tcell.ColorNavy, tcell.ColorPurple,
tcell.ColorTeal, tcell.ColorSilver, tcell.ColorGray,
tcell.ColorRed, tcell.ColorLime, tcell.ColorYellow,
tcell.ColorBlue, tcell.ColorFuchsia, tcell.ColorAqua,
tcell.ColorWhite, tcell.Color16, tcell.Color17, tcell.Color18, tcell.Color19, tcell.Color20,
tcell.Color21, tcell.Color22, tcell.Color23, tcell.Color24, tcell.Color25, tcell.Color26, tcell.Color27, tcell.Color28,
tcell.Color29, tcell.Color30, tcell.Color31, tcell.Color32, tcell.Color33, tcell.Color34, tcell.Color35, tcell.Color36,
tcell.Color37, tcell.Color38, tcell.Color39, tcell.Color40, tcell.Color41, tcell.Color42, tcell.Color43, tcell.Color44,
tcell.Color45, tcell.Color46, tcell.Color47, tcell.Color48, tcell.Color49, tcell.Color50, tcell.Color51, tcell.Color52,
tcell.Color53, tcell.Color54, tcell.Color55, tcell.Color56, tcell.Color57, tcell.Color58, tcell.Color59, tcell.Color60,
tcell.Color61, tcell.Color62, tcell.Color63, tcell.Color64, tcell.Color65, tcell.Color66, tcell.Color67, tcell.Color68,
tcell.Color69, tcell.Color70, tcell.Color71, tcell.Color72, tcell.Color73, tcell.Color74, tcell.Color75, tcell.Color76,
tcell.Color77, tcell.Color78, tcell.Color79, tcell.Color80, tcell.Color81, tcell.Color82, tcell.Color83, tcell.Color84,
tcell.Color85, tcell.Color86, tcell.Color87, tcell.Color88, tcell.Color89, tcell.Color90, tcell.Color91, tcell.Color92,
tcell.Color93, tcell.Color94, tcell.Color95, tcell.Color96, tcell.Color97, tcell.Color98, tcell.Color99, tcell.Color100,
tcell.Color101, tcell.Color102, tcell.Color103, tcell.Color104, tcell.Color105, tcell.Color106, tcell.Color107, tcell.Color108,
tcell.Color109, tcell.Color110, tcell.Color111, tcell.Color112, tcell.Color113, tcell.Color114, tcell.Color115, tcell.Color116,
tcell.Color117, tcell.Color118, tcell.Color119, tcell.Color120, tcell.Color121, tcell.Color122, tcell.Color123, tcell.Color124,
tcell.Color125, tcell.Color126, tcell.Color127, tcell.Color128, tcell.Color129, tcell.Color130, tcell.Color131, tcell.Color132,
tcell.Color133, tcell.Color134, tcell.Color135, tcell.Color136, tcell.Color137, tcell.Color138, tcell.Color139, tcell.Color140,
tcell.Color141, tcell.Color142, tcell.Color143, tcell.Color144, tcell.Color145, tcell.Color146, tcell.Color147, tcell.Color148,
tcell.Color149, tcell.Color150, tcell.Color151, tcell.Color152, tcell.Color153, tcell.Color154, tcell.Color155, tcell.Color156,
tcell.Color157, tcell.Color158, tcell.Color159, tcell.Color160, tcell.Color161, tcell.Color162, tcell.Color163, tcell.Color164,
tcell.Color165, tcell.Color166, tcell.Color167, tcell.Color168, tcell.Color169, tcell.Color170, tcell.Color171, tcell.Color172,
tcell.Color173, tcell.Color174, tcell.Color175, tcell.Color176, tcell.Color177, tcell.Color178, tcell.Color179, tcell.Color180,
tcell.Color181, tcell.Color182, tcell.Color183, tcell.Color184, tcell.Color185, tcell.Color186, tcell.Color187, tcell.Color188,
tcell.Color189, tcell.Color190, tcell.Color191, tcell.Color192, tcell.Color193, tcell.Color194, tcell.Color195, tcell.Color196,
tcell.Color197, tcell.Color198, tcell.Color199, tcell.Color200, tcell.Color201, tcell.Color202, tcell.Color203, tcell.Color204,
tcell.Color205, tcell.Color206, tcell.Color207, tcell.Color208, tcell.Color209, tcell.Color210, tcell.Color211, tcell.Color212,
tcell.Color213, tcell.Color214, tcell.Color215, tcell.Color216, tcell.Color217, tcell.Color218, tcell.Color219, tcell.Color220,
tcell.Color221, tcell.Color222, tcell.Color223, tcell.Color224, tcell.Color225, tcell.Color226, tcell.Color227, tcell.Color228,
tcell.Color229, tcell.Color230, tcell.Color231, tcell.Color232, tcell.Color233, tcell.Color234, tcell.Color235, tcell.Color236,
tcell.Color237, tcell.Color238, tcell.Color239, tcell.Color240, tcell.Color241, tcell.Color242, tcell.Color243, tcell.Color244,
tcell.Color245, tcell.Color246, tcell.Color247, tcell.Color248, tcell.Color249, tcell.Color250, tcell.Color251, tcell.Color252,
tcell.Color253, tcell.Color254, tcell.Color255,
if color == 0 {
return tcell.ColorDefault
}
if color >= 0 && color < len(colors) {
return colors[color]
}
return tcell.ColorDefault
return tcell.PaletteColor(color)
}

View File

@@ -4,7 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
func TestSimpleStringToStyle(t *testing.T) {
@@ -26,6 +26,18 @@ func TestAttributeStringToStyle(t *testing.T) {
assert.NotEqual(t, 0, attr&tcell.AttrBold)
}
func TestMultiAttributesStringToStyle(t *testing.T) {
s := StringToStyle("bold italic underline cyan,brightcyan")
fg, bg, attr := s.Decompose()
assert.Equal(t, tcell.ColorTeal, fg)
assert.Equal(t, tcell.ColorAqua, bg)
assert.NotEqual(t, 0, attr&tcell.AttrBold)
assert.NotEqual(t, 0, attr&tcell.AttrItalic)
assert.NotEqual(t, 0, attr&tcell.AttrUnderline)
}
func TestColor256StringToStyle(t *testing.T) {
s := StringToStyle("128,60")

View File

@@ -4,4 +4,12 @@ const (
DoubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
)
var Bindings map[string]string
var Bindings map[string]map[string]string
func init() {
Bindings = map[string]map[string]string{
"command": make(map[string]string),
"buffer": make(map[string]string),
"terminal": make(map[string]string),
}
}

View File

@@ -479,9 +479,7 @@ func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
result := make(PluginVersions, 0)
p := pl.Get(name)
if p != nil {
for _, v := range p.Versions {
result = append(result, v)
}
result = append(result, p.Versions...)
}
return result
}
@@ -607,7 +605,7 @@ func UpdatePlugins(out io.Writer, plugins []string) {
// if no plugins are specified, update all installed plugins.
if len(plugins) == 0 {
for _, p := range Plugins {
if !p.IsEnabled() {
if !p.IsEnabled() || p.Default {
continue
}
plugins = append(plugins, p.Name)

View File

@@ -1,4 +1,3 @@
//go:generate go run runtime_generate.go ../../runtime/syntax ../../runtime
package config
import (
@@ -11,7 +10,7 @@ import (
"regexp"
"strings"
"github.com/zyedidia/micro/v2/internal/vfsutil"
rt "github.com/zyedidia/micro/v2/runtime"
)
const (
@@ -93,7 +92,7 @@ func (af assetFile) Name() string {
}
func (af assetFile) Data() ([]byte, error) {
return vfsutil.ReadFile(Assets, string(af))
return rt.Asset(string(af))
}
func (nf namedFile) Name() string {
@@ -114,10 +113,7 @@ func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
// AddRuntimeFilesFromDirectory registers each file from the given directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
files, err := ioutil.ReadDir(directory)
if err != nil {
return
}
files, _ := ioutil.ReadDir(directory)
for _, f := range files {
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
fullPath := filepath.Join(directory, f.Name())
@@ -129,13 +125,13 @@ func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string
// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
files, err := vfsutil.ReadDir(Assets, directory)
files, err := rt.AssetDir(directory)
if err != nil {
return
}
for _, f := range files {
if ok, _ := path.Match(pattern, f.Name()); ok {
AddRuntimeFile(fileType, assetFile(path.Join(directory, f.Name())))
if ok, _ := path.Match(pattern, f); ok {
AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
}
}
}
@@ -162,60 +158,11 @@ func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
return realFiles[fileType]
}
func addPlugin(dirname string, plugdir string, isID func(string) bool, vfs bool) {
var srcs []os.FileInfo
var err error
if vfs {
srcs, err = vfsutil.ReadDir(Assets, filepath.Join(plugdir, dirname))
} else {
srcs, err = ioutil.ReadDir(filepath.Join(plugdir, dirname))
}
if err != nil {
return
}
p := new(Plugin)
p.Name = dirname
p.DirName = dirname
p.Default = vfs
for _, f := range srcs {
if strings.HasSuffix(f.Name(), ".lua") {
if vfs {
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, dirname, f.Name())))
} else {
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, dirname, f.Name())))
}
} else if strings.HasSuffix(f.Name(), ".json") {
var data []byte
var err error
if vfs {
data, err = vfsutil.ReadFile(Assets, filepath.Join(plugdir, dirname, f.Name()))
} else {
data, err = ioutil.ReadFile(filepath.Join(plugdir, dirname, f.Name()))
}
if err != nil {
continue
}
p.Info, err = NewPluginInfo(data)
if err != nil {
continue
}
p.Name = p.Info.Name
}
}
if !isID(p.Name) || len(p.Srcs) <= 0 {
log.Println(p.Name, "is not a plugin")
return
}
Plugins = append(Plugins, p)
}
// InitRuntimeFiles initializes all assets file and the config directory
func InitRuntimeFiles() {
add := func(fileType RTFiletype, dir, pattern string) {
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
AddRuntimeFilesFromAssets(fileType, dir, pattern)
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
}
add(RTColorscheme, "colorschemes", "*.micro")
@@ -234,24 +181,69 @@ func InitRuntimeFiles() {
// Search ConfigDir for plugin-scripts
plugdir := filepath.Join(ConfigDir, "plug")
files, err := ioutil.ReadDir(plugdir)
files, _ := ioutil.ReadDir(plugdir)
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
if err == nil {
for _, d := range files {
if d.IsDir() {
addPlugin(d.Name(), plugdir, isID, false)
for _, d := range files {
plugpath := filepath.Join(plugdir, d.Name())
if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
srcs, _ := ioutil.ReadDir(plugpath)
p := new(Plugin)
p.Name = d.Name()
p.DirName = d.Name()
for _, f := range srcs {
if strings.HasSuffix(f.Name(), ".lua") {
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
} else if strings.HasSuffix(f.Name(), ".json") {
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
if err != nil {
continue
}
p.Info, err = NewPluginInfo(data)
if err != nil {
continue
}
p.Name = p.Info.Name
}
}
if !isID(p.Name) || len(p.Srcs) <= 0 {
log.Println(p.Name, "is not a plugin")
continue
}
Plugins = append(Plugins, p)
}
}
plugdir = "plugins"
files, err = vfsutil.ReadDir(Assets, plugdir)
if err == nil {
plugdir = filepath.Join("runtime", "plugins")
if files, err := rt.AssetDir(plugdir); err == nil {
for _, d := range files {
if d.IsDir() {
addPlugin(d.Name(), plugdir, isID, true)
if srcs, err := rt.AssetDir(filepath.Join(plugdir, d)); err == nil {
p := new(Plugin)
p.Name = d
p.DirName = d
p.Default = true
for _, f := range srcs {
if strings.HasSuffix(f, ".lua") {
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
} else if strings.HasSuffix(f, ".json") {
data, err := rt.Asset(filepath.Join(plugdir, d, f))
if err != nil {
continue
}
p.Info, err = NewPluginInfo(data)
if err != nil {
continue
}
p.Name = p.Info.Name
}
}
if !isID(p.Name) || len(p.Srcs) <= 0 {
log.Println(p.Name, "is not a plugin")
continue
}
Plugins = append(Plugins, p)
}
}
}
@@ -288,7 +280,7 @@ func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) e
if _, err := os.Stat(fullpath); err == nil {
AddRealRuntimeFile(filetype, realFile(fullpath))
} else {
fullpath = path.Join("plugins", pldir, filePath)
fullpath = path.Join("runtime", "plugins", pldir, filePath)
AddRuntimeFile(filetype, assetFile(fullpath))
}
return nil
@@ -305,7 +297,7 @@ func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, dire
if _, err := os.Stat(fullpath); err == nil {
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
} else {
fullpath = path.Join("plugins", pldir, directory)
fullpath = path.Join("runtime", "plugins", pldir, directory)
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
}
return nil

View File

@@ -27,7 +27,8 @@ var (
GlobalSettings map[string]interface{}
// This is the raw parsed json
parsedSettings map[string]interface{}
parsedSettings map[string]interface{}
settingsParseError bool
// ModifiedSettings is a map of settings which should be written to disk
// because they have been modified by the user in this session
@@ -42,6 +43,7 @@ func init() {
// Options with validators
var optionValidators = map[string]optionValidator{
"autosave": validateNonNegativeValue,
"clipboard": validateClipboard,
"tabsize": validatePositiveValue,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
@@ -49,6 +51,7 @@ var optionValidators = map[string]optionValidator{
"colorcolumn": validateNonNegativeValue,
"fileformat": validateLineEnding,
"encoding": validateEncoding,
"multiopen": validateMultiOpen,
}
func ReadSettings() error {
@@ -56,12 +59,14 @@ func ReadSettings() error {
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
settingsParseError = true
return errors.New("Error reading settings.json file: " + err.Error())
}
if !strings.HasPrefix(string(input), "null") {
// Unmarshal the input into the parsed map
err = json5.Unmarshal(input, &parsedSettings)
if err != nil {
settingsParseError = true
return errors.New("Error reading settings.json: " + err.Error())
}
@@ -100,7 +105,7 @@ func InitGlobalSettings() error {
for k, v := range parsedSettings {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if _, ok := GlobalSettings[k]; ok && !verifySetting(k, reflect.TypeOf(v), reflect.TypeOf(GlobalSettings[k])) {
err = errors.New(fmt.Sprintf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k])))
err = fmt.Errorf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k]))
continue
}
@@ -121,7 +126,7 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
if settings["filetype"].(string) == k[3:] {
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
parseError = fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1]))
continue
}
settings[k1] = v1
@@ -137,7 +142,7 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
if g.MatchString(path) {
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
parseError = fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1]))
continue
}
settings[k1] = v1
@@ -151,6 +156,14 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
// WriteSettings writes the settings to the specified filename as JSON
func WriteSettings(filename string) error {
if settingsParseError {
// Don't write settings if there was a parse error
// because this will delete the settings.json if it
// is invalid. Instead we should allow the user to fix
// it manually.
return nil
}
var err error
if _, e := os.Stat(ConfigDir); e == nil {
defaults := DefaultGlobalSettings()
@@ -205,7 +218,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.
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
name = pl + "." + name
if v, ok := GlobalSettings[name]; !ok {
if _, ok := GlobalSettings[name]; !ok {
defaultCommonSettings[name] = defaultvalue
GlobalSettings[name] = defaultvalue
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
@@ -213,7 +226,7 @@ func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{})
return errors.New("Error writing settings.json file: " + err.Error())
}
} else {
defaultCommonSettings[name] = v
defaultCommonSettings[name] = defaultvalue
}
return nil
}
@@ -247,6 +260,7 @@ var defaultCommonSettings = map[string]interface{}{
"autoindent": true,
"autosu": false,
"backup": true,
"backupdir": "",
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
@@ -256,11 +270,14 @@ var defaultCommonSettings = map[string]interface{}{
"fastdirty": false,
"fileformat": "unix",
"filetype": "unknown",
"ignorecase": false,
"hlsearch": false,
"incsearch": true,
"ignorecase": true,
"indentchar": " ",
"keepautoindent": false,
"matchbrace": true,
"mkparents": false,
"permbackup": false,
"readonly": false,
"rmtrailingws": false,
"ruler": true,
@@ -282,6 +299,7 @@ var defaultCommonSettings = map[string]interface{}{
"tabsize": float64(4),
"tabstospaces": false,
"useprimary": true,
"wordwrap": false,
}
func GetInfoBarOffset() int {
@@ -309,18 +327,23 @@ func DefaultCommonSettings() map[string]interface{} {
// 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,
"savehistory": true,
"sucmd": "sudo",
"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,
}
@@ -437,6 +460,22 @@ func validateColorscheme(option string, value interface{}) error {
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)
@@ -455,3 +494,19 @@ func validateEncoding(option string, value interface{}) error {
_, err := htmlindex.Get(value.(string))
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

@@ -2,17 +2,17 @@ package display
import (
"strconv"
"unicode"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
// The BufWindow provides a way of displaying a certain section
// of a buffer
// The BufWindow provides a way of displaying a certain section of a buffer.
type BufWindow struct {
*View
@@ -23,15 +23,20 @@ type BufWindow struct {
sline *StatusLine
gutterOffset int
drawStatus bool
bufWidth int
bufHeight int
gutterOffset int
hasMessage bool
maxLineNumLength int
drawDivider bool
}
// NewBufWindow creates a new window at a location in the screen with a width and height
func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
w := new(BufWindow)
w.View = new(View)
w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf
w.X, w.Y, w.Width, w.Height = x, y, width, height
w.SetBuffer(buf)
w.active = true
w.sline = NewStatusLine(w)
@@ -39,31 +44,123 @@ func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
return w
}
// SetBuffer sets this window's buffer.
func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
w.Buf = b
b.OptionCallback = func(option string, nativeValue interface{}) {
if option == "softwrap" {
if nativeValue.(bool) {
w.StartCol = 0
} else {
w.StartLine.Row = 0
}
}
if option == "softwrap" || option == "wordwrap" {
w.Relocate()
for _, c := range w.Buf.GetCursors() {
c.LastVisualX = c.GetVisualX()
}
}
}
b.GetVisualX = func(loc buffer.Loc) int {
return w.VLocFromLoc(loc).VisualX
}
}
// GetView gets the view.
func (w *BufWindow) GetView() *View {
return w.View
}
// GetView sets the view.
func (w *BufWindow) SetView(view *View) {
w.View = view
}
// Resize resizes this window.
func (w *BufWindow) Resize(width, height int) {
w.Width, w.Height = width, height
w.updateDisplayInfo()
w.Relocate()
}
// SetActive marks the window as active.
func (w *BufWindow) SetActive(b bool) {
w.active = b
}
// IsActive returns true if this window is active.
func (w *BufWindow) IsActive() bool {
return w.active
}
// BufView returns the width, height and x,y location of the actual buffer.
// It is not exactly the same as the whole window which also contains gutter,
// ruler, scrollbar and statusline.
func (w *BufWindow) BufView() View {
return View{
X: w.X + w.gutterOffset,
Y: w.Y,
Width: w.bufWidth,
Height: w.bufHeight,
StartLine: w.StartLine,
StartCol: w.StartCol,
}
}
func (w *BufWindow) updateDisplayInfo() {
b := w.Buf
w.drawDivider = false
if !b.Settings["statusline"].(bool) {
_, h := screen.Screen.Size()
infoY := h
if config.GetGlobalOption("infobar").(bool) {
infoY--
}
if w.Y+w.Height != infoY {
w.drawDivider = true
}
}
w.bufHeight = w.Height
if b.Settings["statusline"].(bool) || w.drawDivider {
w.bufHeight--
}
w.hasMessage = len(b.Messages) > 0
// We need to know the string length of the largest line number
// so we can pad appropriately when displaying line numbers
w.maxLineNumLength = len(strconv.Itoa(b.LinesNum()))
w.gutterOffset = 0
if w.hasMessage {
w.gutterOffset += 2
}
if b.Settings["diffgutter"].(bool) {
w.gutterOffset++
}
if b.Settings["ruler"].(bool) {
w.gutterOffset += w.maxLineNumLength + 1
}
prevBufWidth := w.bufWidth
w.bufWidth = w.Width - w.gutterOffset
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
w.bufWidth--
}
if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) {
for _, c := range w.Buf.GetCursors() {
c.LastVisualX = c.GetVisualX()
}
}
}
func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) {
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
width := 0
@@ -106,63 +203,49 @@ func (w *BufWindow) Clear() {
}
}
// Bottomline returns the line number of the lowest line in the view
// You might think that this is obviously just v.StartLine + v.Height
// but if softwrap is enabled things get complicated since one buffer
// line can take up multiple lines in the view
func (w *BufWindow) Bottomline() int {
if !w.Buf.Settings["softwrap"].(bool) {
h := w.StartLine + w.Height - 1
if w.drawStatus {
h--
}
return h
}
l := w.LocFromVisual(buffer.Loc{0, w.Y + w.Height})
return l.Y
}
// Relocate moves the view window so that the cursor is in view
// This is useful if the user has scrolled far away, and then starts typing
// Returns true if the window location is moved
func (w *BufWindow) Relocate() bool {
b := w.Buf
// how many buffer lines are in the view
height := w.Bottomline() + 1 - w.StartLine
h := w.Height
if w.drawStatus {
h--
}
height := w.bufHeight
ret := false
activeC := w.Buf.GetActiveCursor()
cy := activeC.Y
scrollmargin := int(b.Settings["scrollmargin"].(float64))
if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 {
w.StartLine = cy - scrollmargin
c := w.SLocFromLoc(activeC.Loc)
bStart := SLoc{0, 0}
bEnd := w.SLocFromLoc(b.End())
if c.LessThan(w.Scroll(w.StartLine, scrollmargin)) && c.GreaterThan(w.Scroll(bStart, scrollmargin-1)) {
w.StartLine = w.Scroll(c, -scrollmargin)
ret = true
} else if cy < w.StartLine {
w.StartLine = cy
} else if c.LessThan(w.StartLine) {
w.StartLine = c
ret = true
}
if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin {
w.StartLine = cy - height + 1 + scrollmargin
if c.GreaterThan(w.Scroll(w.StartLine, height-1-scrollmargin)) && c.LessEqual(w.Scroll(bEnd, -scrollmargin)) {
w.StartLine = w.Scroll(c, -height+1+scrollmargin)
ret = true
} else if cy >= b.LinesNum()-scrollmargin && cy >= height {
w.StartLine = b.LinesNum() - height
} else if c.GreaterThan(w.Scroll(bEnd, -scrollmargin)) && c.GreaterThan(w.Scroll(w.StartLine, height-1)) {
w.StartLine = w.Scroll(bEnd, -height+1)
ret = true
}
// horizontal relocation (scrolling)
if !b.Settings["softwrap"].(bool) {
cx := activeC.GetVisualX()
rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
if rw == 0 {
rw = 1 // tab or newline
}
if cx < w.StartCol {
w.StartCol = cx
ret = true
}
if cx+w.gutterOffset+1 > w.StartCol+w.Width {
w.StartCol = cx - w.Width + w.gutterOffset + 1
if cx+w.gutterOffset+rw > w.StartCol+w.Width {
w.StartCol = cx - w.Width + w.gutterOffset + rw
ret = true
}
}
@@ -171,123 +254,18 @@ func (w *BufWindow) Relocate() bool {
// LocFromVisual takes a visual location (x and y position) and returns the
// position in the buffer corresponding to the visual location
// Computing the buffer location requires essentially drawing the entire screen
// to account for complications like softwrap, wide characters, and horizontal scrolling
// If the requested position does not correspond to a buffer location it returns
// the nearest position
func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
b := w.Buf
hasMessage := len(b.Messages) > 0
bufHeight := w.Height
if w.drawStatus {
bufHeight--
vx := svloc.X - w.X - w.gutterOffset
if vx < 0 {
vx = 0
}
bufWidth := w.Width
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
bufWidth--
vloc := VLoc{
SLoc: w.Scroll(w.StartLine, svloc.Y-w.Y),
VisualX: vx + w.StartCol,
}
// We need to know the string length of the largest line number
// so we can pad appropriately when displaying line numbers
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
tabsize := int(b.Settings["tabsize"].(float64))
softwrap := b.Settings["softwrap"].(bool)
// this represents the current draw position
// within the current window
vloc := buffer.Loc{X: 0, Y: 0}
// this represents the current draw position in the buffer (char positions)
bloc := buffer.Loc{X: -1, Y: w.StartLine}
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
vloc.X = 0
if hasMessage {
vloc.X += 2
}
if b.Settings["diffgutter"].(bool) {
vloc.X++
}
if b.Settings["ruler"].(bool) {
vloc.X += maxLineNumLength + 1
}
line := b.LineBytes(bloc.Y)
line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize)
bloc.X = bslice
draw := func() {
if nColsBeforeStart <= 0 {
vloc.X++
}
nColsBeforeStart--
}
totalwidth := w.StartCol - nColsBeforeStart
if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
return bloc
}
for len(line) > 0 {
if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
return bloc
}
r, _, size := util.DecodeCharacter(line)
draw()
width := 0
switch r {
case '\t':
ts := tabsize - (totalwidth % tabsize)
width = ts
default:
width = runewidth.RuneWidth(r)
}
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for i := 1; i < width; i++ {
if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
return bloc
}
draw()
}
}
bloc.X++
line = line[size:]
totalwidth += width
// If we reach the end of the window then we either stop or we wrap for softwrap
if vloc.X >= bufWidth {
if !softwrap {
break
} else {
vloc.Y++
if vloc.Y >= bufHeight {
break
}
vloc.X = w.gutterOffset
}
}
}
if vloc.Y+w.Y == svloc.Y {
return bloc
}
if bloc.Y+1 >= b.LinesNum() || vloc.Y+1 >= bufHeight {
return bloc
}
bloc.X = w.StartCol
bloc.Y++
}
return buffer.Loc{}
return w.LocFromVLoc(vloc)
}
func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
@@ -334,7 +312,7 @@ func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool
vloc.X++
}
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
cursorLine := w.Buf.GetActiveCursor().Loc.Y
var lineInt int
if w.Buf.Settings["relativeruler"] == false || cursorLine == bloc.Y {
@@ -345,7 +323,7 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxL
lineNum := strconv.Itoa(util.Abs(lineInt))
// Write the spaces before the line number if necessary
for i := 0; i < maxLineNumLength-len(lineNum); i++ {
for i := 0; i < w.maxLineNumLength-len(lineNum); i++ {
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
vloc.X++
}
@@ -392,16 +370,7 @@ func (w *BufWindow) displayBuffer() {
return
}
hasMessage := len(b.Messages) > 0
bufHeight := w.Height
if w.drawStatus {
bufHeight--
}
bufWidth := w.Width
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
bufWidth--
}
maxWidth := w.gutterOffset + w.bufWidth
if b.ModifiedThisFrame {
if b.Settings["diffgutter"].(bool) {
@@ -462,25 +431,27 @@ func (w *BufWindow) displayBuffer() {
}
}
// We need to know the string length of the largest line number
// so we can pad appropriately when displaying line numbers
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
softwrap := b.Settings["softwrap"].(bool)
wordwrap := softwrap && b.Settings["wordwrap"].(bool)
tabsize := util.IntOpt(b.Settings["tabsize"])
colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
// this represents the current draw position
// within the current window
vloc := buffer.Loc{X: 0, Y: 0}
if softwrap {
// the start line may be partially out of the current window
vloc.Y = -w.StartLine.Row
}
// this represents the current draw position in the buffer (char positions)
bloc := buffer.Loc{X: -1, Y: w.StartLine}
bloc := buffer.Loc{X: -1, Y: w.StartLine.Line}
cursors := b.GetCursors()
curStyle := config.DefStyle
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
for ; vloc.Y < w.bufHeight; vloc.Y++ {
vloc.X = 0
currentLine := false
@@ -496,88 +467,99 @@ func (w *BufWindow) displayBuffer() {
s = curNumStyle
}
if hasMessage {
w.drawGutter(&vloc, &bloc)
}
if vloc.Y >= 0 {
if w.hasMessage {
w.drawGutter(&vloc, &bloc)
}
if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(s, false, &vloc, &bloc)
}
if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(s, false, &vloc, &bloc)
}
if b.Settings["ruler"].(bool) {
w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
if b.Settings["ruler"].(bool) {
w.drawLineNum(s, false, &vloc, &bloc)
}
} else {
vloc.X = w.gutterOffset
}
w.gutterOffset = vloc.X
line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
if startStyle != nil {
curStyle = *startStyle
}
bloc.X = bslice
draw := func(r rune, combc []rune, style tcell.Style, showcursor bool) {
if nColsBeforeStart <= 0 {
_, origBg, _ := style.Decompose()
_, defBg, _ := config.DefStyle.Decompose()
// syntax highlighting with non-default background takes precedence
// over cursor-line and color-column
dontOverrideBackground := origBg != defBg
for _, c := range cursors {
if c.HasSelection() &&
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
// The current character is selected
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool) {
if nColsBeforeStart <= 0 && vloc.Y >= 0 {
if highlight {
if w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {
style = config.DefStyle.Reverse(true)
if s, ok := config.Colorscheme["selection"]; ok {
if s, ok := config.Colorscheme["hlsearch"]; ok {
style = s
}
}
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
!c.HasSelection() && c.Y == bloc.Y {
if s, ok := config.Colorscheme["cursor-line"]; ok {
_, origBg, _ := style.Decompose()
_, defBg, _ := config.DefStyle.Decompose()
// syntax or hlsearch highlighting with non-default background takes precedence
// over cursor-line and color-column
dontOverrideBackground := origBg != defBg
for _, c := range cursors {
if c.HasSelection() &&
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
// The current character is selected
style = config.DefStyle.Reverse(true)
if s, ok := config.Colorscheme["selection"]; ok {
style = s
}
}
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
!c.HasSelection() && c.Y == bloc.Y {
if s, ok := config.Colorscheme["cursor-line"]; ok {
fg, _, _ := s.Decompose()
style = style.Background(fg)
}
}
}
for _, m := range b.Messages {
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
style = style.Underline(true)
break
}
}
if r == '\t' {
indentrunes := []rune(b.Settings["indentchar"].(string))
// if empty indentchar settings, use space
if len(indentrunes) == 0 {
indentrunes = []rune{' '}
}
r = indentrunes[0]
if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
fg, _, _ := s.Decompose()
style = style.Foreground(fg)
}
}
if s, ok := config.Colorscheme["color-column"]; ok {
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
fg, _, _ := s.Decompose()
style = style.Background(fg)
}
}
}
for _, m := range b.Messages {
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
style = style.Underline(true)
break
}
}
if r == '\t' {
indentrunes := []rune(b.Settings["indentchar"].(string))
// if empty indentchar settings, use space
if indentrunes == nil || len(indentrunes) == 0 {
indentrunes = []rune{' '}
}
r = indentrunes[0]
if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
fg, _, _ := s.Decompose()
style = style.Foreground(fg)
}
}
if s, ok := config.Colorscheme["color-column"]; ok {
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
fg, _, _ := s.Decompose()
style = style.Background(fg)
}
}
for _, mb := range matchingBraces {
if mb.X == bloc.X && mb.Y == bloc.Y {
style = style.Underline(true)
for _, mb := range matchingBraces {
if mb.X == bloc.X && mb.Y == bloc.Y {
style = style.Underline(true)
}
}
}
@@ -590,64 +572,135 @@ func (w *BufWindow) displayBuffer() {
}
}
}
}
if nColsBeforeStart <= 0 {
vloc.X++
}
nColsBeforeStart--
}
wrap := func() {
vloc.X = 0
if w.hasMessage {
w.drawGutter(&vloc, &bloc)
}
if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
}
// This will draw an empty line number because the current line is wrapped
if b.Settings["ruler"].(bool) {
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
}
}
type glyph struct {
r rune
combc []rune
style tcell.Style
width int
}
var word []glyph
if wordwrap {
word = make([]glyph, 0, w.bufWidth)
} else {
word = make([]glyph, 0, 1)
}
wordwidth := 0
totalwidth := w.StartCol - nColsBeforeStart
for len(line) > 0 {
r, combc, size := util.DecodeCharacter(line)
line = line[size:]
curStyle, _ = w.getStyle(curStyle, bloc)
draw(r, combc, curStyle, true)
loc := buffer.Loc{X: bloc.X + len(word), Y: bloc.Y}
curStyle, _ = w.getStyle(curStyle, loc)
width := 0
char := ' '
switch r {
case '\t':
ts := tabsize - (totalwidth % tabsize)
width = ts
width = util.Min(ts, maxWidth-vloc.X)
totalwidth += ts
default:
if unicode.IsUpper(r) {
width = 2
totalwidth += 2
break
}
width = runewidth.RuneWidth(r)
char = '@'
totalwidth += width
}
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for i := 1; i < width; i++ {
draw(char, nil, curStyle, false)
word = append(word, glyph{r, combc, curStyle, width})
wordwidth += width
// Collect a complete word to know its width.
// If wordwrap is off, every single character is a complete "word".
if wordwrap {
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
continue
}
}
bloc.X++
line = line[size:]
totalwidth += width
// If a word (or just a wide rune) does not fit in the window
if vloc.X+wordwidth > maxWidth && vloc.X > w.gutterOffset {
for vloc.X < maxWidth {
draw(' ', nil, config.DefStyle, false, false)
}
// If we reach the end of the window then we either stop or we wrap for softwrap
if vloc.X >= bufWidth {
// We either stop or we wrap to draw the word in the next line
if !softwrap {
break
} else {
vloc.Y++
if vloc.Y >= bufHeight {
if vloc.Y >= w.bufHeight {
break
}
vloc.X = 0
if hasMessage {
w.drawGutter(&vloc, &bloc)
}
if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
wrap()
}
}
for _, r := range word {
if unicode.IsUpper(r.r) {
draw('~', nil, r.style, true, true)
draw(r.r, r.combc, r.style, true, false)
bloc.X++
continue
}
draw(r.r, r.combc, r.style, true, true)
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if r.width > 1 {
char := ' '
if r.r != '\t' {
char = '@'
}
// This will draw an empty line number because the current line is wrapped
if b.Settings["ruler"].(bool) {
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
for i := 1; i < r.width; i++ {
draw(char, nil, r.style, true, false)
}
}
bloc.X++
}
word = word[:0]
wordwidth = 0
// If we reach the end of the window then we either stop or we wrap for softwrap
if vloc.X >= maxWidth {
if !softwrap {
break
} else {
vloc.Y++
if vloc.Y >= w.bufHeight {
break
}
wrap()
}
}
}
@@ -661,7 +714,7 @@ func (w *BufWindow) displayBuffer() {
}
}
}
for i := vloc.X; i < bufWidth; i++ {
for i := vloc.X; i < maxWidth; i++ {
curStyle := style
if s, ok := config.Colorscheme["color-column"]; ok {
if colorcolumn != 0 && i-w.gutterOffset+w.StartCol == colorcolumn {
@@ -672,9 +725,9 @@ func (w *BufWindow) displayBuffer() {
screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
}
if vloc.X != bufWidth {
if vloc.X != maxWidth {
// Display newline within a selection
draw(' ', nil, config.DefStyle, true)
draw(' ', nil, config.DefStyle, true, true)
}
bloc.X = w.StartCol
@@ -686,18 +739,9 @@ func (w *BufWindow) displayBuffer() {
}
func (w *BufWindow) displayStatusLine() {
_, h := screen.Screen.Size()
infoY := h
if config.GetGlobalOption("infobar").(bool) {
infoY--
}
if w.Buf.Settings["statusline"].(bool) {
w.drawStatus = true
w.sline.Display()
} else if w.Y+w.Height != infoY {
w.drawStatus = true
} else if w.drawDivider {
divchars := config.GetGlobalOption("divchars").(string)
if util.CharacterCountInString(divchars) != 2 {
divchars = "|-"
@@ -719,31 +763,33 @@ func (w *BufWindow) displayStatusLine() {
for x := w.X; x < w.X+w.Width; x++ {
screen.SetContent(x, w.Y+w.Height-1, divchar, combc, dividerStyle)
}
} else {
w.drawStatus = false
}
}
func (w *BufWindow) displayScrollBar() {
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
scrollX := w.X + w.Width - 1
bufHeight := w.Height
if w.drawStatus {
bufHeight--
}
barsize := int(float64(w.Height) / float64(w.Buf.LinesNum()) * float64(w.Height))
if barsize < 1 {
barsize = 1
}
barstart := w.Y + int(float64(w.StartLine)/float64(w.Buf.LinesNum())*float64(w.Height))
for y := barstart; y < util.Min(barstart+barsize, w.Y+bufHeight); y++ {
screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
barstart := w.Y + int(float64(w.StartLine.Line)/float64(w.Buf.LinesNum())*float64(w.Height))
scrollBarStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["scrollbar"]; ok {
scrollBarStyle = style
}
for y := barstart; y < util.Min(barstart+barsize, w.Y+w.bufHeight); y++ {
screen.SetContent(scrollX, y, '|', nil, scrollBarStyle)
}
}
}
// Display displays the buffer and the statusline
func (w *BufWindow) Display() {
w.updateDisplayInfo()
w.displayStatusLine()
w.displayScrollBar()
w.displayBuffer()

View File

@@ -7,7 +7,7 @@ import (
"github.com/zyedidia/micro/v2/internal/info"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
type InfoWindow struct {
@@ -72,6 +72,23 @@ func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
}
func (i *InfoWindow) BufView() View {
return View{
X: 0,
Y: i.Y,
Width: i.Width,
Height: 1,
StartLine: SLoc{0, 0},
StartCol: 0,
}
}
func (i *InfoWindow) Scroll(s SLoc, n int) SLoc { return s }
func (i *InfoWindow) Diff(s1, s2 SLoc) int { return 0 }
func (i *InfoWindow) SLocFromLoc(loc buffer.Loc) SLoc { return SLoc{0, 0} }
func (i *InfoWindow) VLocFromLoc(loc buffer.Loc) VLoc { return VLoc{SLoc{0, 0}, loc.X} }
func (i *InfoWindow) LocFromVLoc(vloc VLoc) buffer.Loc { return buffer.Loc{vloc.VisualX, 0} }
func (i *InfoWindow) Clear() {
for x := 0; x < i.Width; x++ {
screen.SetContent(x, i.Y, ' ', nil, i.defStyle())

View File

@@ -0,0 +1,348 @@
package display
import (
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/util"
)
// SLoc represents a vertical scrolling location, i.e. a location of a visual line
// in the buffer. When softwrap is enabled, a buffer line may be displayed as
// multiple visual lines (rows). So SLoc stores a number of a line in the buffer
// and a number of a row within this line.
type SLoc struct {
Line, Row int
}
// LessThan returns true if s is less b
func (s SLoc) LessThan(b SLoc) bool {
if s.Line < b.Line {
return true
}
return s.Line == b.Line && s.Row < b.Row
}
// GreaterThan returns true if s is bigger than b
func (s SLoc) GreaterThan(b SLoc) bool {
if s.Line > b.Line {
return true
}
return s.Line == b.Line && s.Row > b.Row
}
// LessEqual returns true if s is less than or equal to b
func (s SLoc) LessEqual(b SLoc) bool {
if s.Line < b.Line {
return true
}
if s.Line == b.Line && s.Row < b.Row {
return true
}
return s == b
}
// GreaterEqual returns true if s is bigger than or equal to b
func (s SLoc) GreaterEqual(b SLoc) bool {
if s.Line > b.Line {
return true
}
if s.Line == b.Line && s.Row > b.Row {
return true
}
return s == b
}
// VLoc represents a location in the buffer as a visual location in the
// linewrapped buffer.
type VLoc struct {
SLoc
VisualX int
}
type SoftWrap interface {
Scroll(s SLoc, n int) SLoc
Diff(s1, s2 SLoc) int
SLocFromLoc(loc buffer.Loc) SLoc
VLocFromLoc(loc buffer.Loc) VLoc
LocFromVLoc(vloc VLoc) buffer.Loc
}
func (w *BufWindow) getVLocFromLoc(loc buffer.Loc) VLoc {
vloc := VLoc{SLoc: SLoc{loc.Y, 0}, VisualX: 0}
if loc.X <= 0 {
return vloc
}
if w.bufWidth <= 0 {
return vloc
}
wordwrap := w.Buf.Settings["wordwrap"].(bool)
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
line := w.Buf.LineBytes(loc.Y)
x := 0
totalwidth := 0
wordwidth := 0
wordoffset := 0
for len(line) > 0 {
r, _, size := util.DecodeCharacter(line)
line = line[size:]
width := 0
switch r {
case '\t':
ts := tabsize - (totalwidth % tabsize)
width = util.Min(ts, w.bufWidth-vloc.VisualX)
totalwidth += ts
default:
width = runewidth.RuneWidth(r)
totalwidth += width
}
wordwidth += width
// Collect a complete word to know its width.
// If wordwrap is off, every single character is a complete "word".
if wordwrap {
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
if x < loc.X {
wordoffset += width
x++
}
continue
}
}
// If a word (or just a wide rune) does not fit in the window
if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
vloc.Row++
vloc.VisualX = 0
}
if x == loc.X {
vloc.VisualX += wordoffset
return vloc
}
x++
vloc.VisualX += wordwidth
wordwidth = 0
wordoffset = 0
if vloc.VisualX >= w.bufWidth {
vloc.Row++
vloc.VisualX = 0
}
}
return vloc
}
func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
loc := buffer.Loc{X: 0, Y: svloc.Line}
if w.bufWidth <= 0 {
return loc
}
wordwrap := w.Buf.Settings["wordwrap"].(bool)
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
line := w.Buf.LineBytes(svloc.Line)
vloc := VLoc{SLoc: SLoc{svloc.Line, 0}, VisualX: 0}
totalwidth := 0
var widths []int
if wordwrap {
widths = make([]int, 0, w.bufWidth)
} else {
widths = make([]int, 0, 1)
}
wordwidth := 0
for len(line) > 0 {
r, _, size := util.DecodeCharacter(line)
line = line[size:]
width := 0
switch r {
case '\t':
ts := tabsize - (totalwidth % tabsize)
width = util.Min(ts, w.bufWidth-vloc.VisualX)
totalwidth += ts
default:
width = runewidth.RuneWidth(r)
totalwidth += width
}
widths = append(widths, width)
wordwidth += width
// Collect a complete word to know its width.
// If wordwrap is off, every single character is a complete "word".
if wordwrap {
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
continue
}
}
// If a word (or just a wide rune) does not fit in the window
if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
if vloc.Row == svloc.Row {
if wordwrap {
// it's a word, not a wide rune
loc.X--
}
return loc
}
vloc.Row++
vloc.VisualX = 0
}
for i := range widths {
vloc.VisualX += widths[i]
if vloc.Row == svloc.Row && vloc.VisualX > svloc.VisualX {
return loc
}
loc.X++
}
widths = widths[:0]
wordwidth = 0
if vloc.VisualX >= w.bufWidth {
vloc.Row++
vloc.VisualX = 0
}
}
return loc
}
func (w *BufWindow) getRowCount(line int) int {
eol := buffer.Loc{X: util.CharacterCount(w.Buf.LineBytes(line)), Y: line}
return w.getVLocFromLoc(eol).Row + 1
}
func (w *BufWindow) scrollUp(s SLoc, n int) SLoc {
for n > 0 {
if n <= s.Row {
s.Row -= n
n = 0
} else if s.Line > 0 {
s.Line--
n -= s.Row + 1
s.Row = w.getRowCount(s.Line) - 1
} else {
s.Row = 0
break
}
}
return s
}
func (w *BufWindow) scrollDown(s SLoc, n int) SLoc {
for n > 0 {
rc := w.getRowCount(s.Line)
if n < rc-s.Row {
s.Row += n
n = 0
} else if s.Line < w.Buf.LinesNum()-1 {
s.Line++
n -= rc - s.Row
s.Row = 0
} else {
s.Row = rc - 1
break
}
}
return s
}
func (w *BufWindow) scroll(s SLoc, n int) SLoc {
if n < 0 {
return w.scrollUp(s, -n)
}
return w.scrollDown(s, n)
}
func (w *BufWindow) diff(s1, s2 SLoc) int {
n := 0
for s1.LessThan(s2) {
if s1.Line < s2.Line {
n += w.getRowCount(s1.Line) - s1.Row
s1.Line++
s1.Row = 0
} else {
n += s2.Row - s1.Row
s1.Row = s2.Row
}
}
return n
}
// Scroll returns the location which is n visual lines below the location s
// i.e. the result of scrolling n lines down. n can be negative,
// which means scrolling up. The returned location is guaranteed to be
// within the buffer boundaries.
func (w *BufWindow) Scroll(s SLoc, n int) SLoc {
if !w.Buf.Settings["softwrap"].(bool) {
s.Line += n
if s.Line < 0 {
s.Line = 0
}
if s.Line > w.Buf.LinesNum()-1 {
s.Line = w.Buf.LinesNum() - 1
}
return s
}
return w.scroll(s, n)
}
// Diff returns the difference (the vertical distance) between two SLocs.
func (w *BufWindow) Diff(s1, s2 SLoc) int {
if !w.Buf.Settings["softwrap"].(bool) {
return s2.Line - s1.Line
}
if s1.GreaterThan(s2) {
return -w.diff(s2, s1)
}
return w.diff(s1, s2)
}
// SLocFromLoc takes a position in the buffer and returns the location
// of the visual line containing this position.
func (w *BufWindow) SLocFromLoc(loc buffer.Loc) SLoc {
if !w.Buf.Settings["softwrap"].(bool) {
return SLoc{loc.Y, 0}
}
return w.getVLocFromLoc(loc).SLoc
}
// VLocFromLoc takes a position in the buffer and returns the corresponding
// visual location in the linewrapped buffer.
func (w *BufWindow) VLocFromLoc(loc buffer.Loc) VLoc {
if !w.Buf.Settings["softwrap"].(bool) {
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
visualx := util.StringWidth(w.Buf.LineBytes(loc.Y), loc.X, tabsize)
return VLoc{SLoc{loc.Y, 0}, visualx}
}
return w.getVLocFromLoc(loc)
}
// LocFromVLoc takes a visual location in the linewrapped buffer and returns
// the position in the buffer corresponding to this visual location.
func (w *BufWindow) LocFromVLoc(vloc VLoc) buffer.Loc {
if !w.Buf.Settings["softwrap"].(bool) {
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
x := util.GetCharPosInLine(w.Buf.LineBytes(vloc.Line), vloc.VisualX, tabsize)
return buffer.Loc{x, vloc.Line}
}
return w.getLocFromVLoc(vloc)
}

View File

@@ -47,6 +47,12 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
}
return ""
},
"lines": func(b *buffer.Buffer) string {
return strconv.Itoa(b.LinesNum())
},
"percentage": func(b *buffer.Buffer) string {
return strconv.Itoa((b.GetActiveCursor().Y + 1) * 100 / b.LinesNum())
},
}
func SetStatusInfoFnLua(fn string) {
@@ -98,6 +104,8 @@ func (s *StatusLine) Display() {
// We'll draw the line at the lowest line in the window
y := s.win.Height + s.win.Y - 1
winX := s.win.X
b := s.win.Buf
// autocomplete suggestions (for the buffer, not for the infowindow)
if b.HasSuggestions && len(b.Suggestions) > 1 {
@@ -105,10 +113,6 @@ func (s *StatusLine) Display() {
if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style
}
keymenuOffset := 0
if config.GetGlobalOption("keymenu").(bool) {
keymenuOffset = len(keydisplay)
}
x := 0
for j, sug := range b.Suggestions {
style := statusLineStyle
@@ -116,13 +120,13 @@ func (s *StatusLine) Display() {
style = style.Reverse(true)
}
for _, r := range sug {
screen.SetContent(x, y-keymenuOffset, r, nil, style)
screen.SetContent(winX+x, y, r, nil, style)
x++
if x >= s.win.Width {
return
}
}
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
x++
if x >= s.win.Width {
return
@@ -130,7 +134,7 @@ func (s *StatusLine) Display() {
}
for x < s.win.Width {
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
x++
}
return
@@ -143,7 +147,7 @@ func (s *StatusLine) Display() {
return []byte(fmt.Sprint(s.FindOpt(string(option))))
} else if bytes.HasPrefix(name, []byte("bind")) {
binding := string(name[5:])
for k, v := range config.Bindings {
for k, v := range config.Bindings["buffer"] {
if v == binding {
return []byte(k)
}
@@ -170,7 +174,6 @@ func (s *StatusLine) Display() {
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)
rightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)
winX := s.win.X
for x := 0; x < s.win.Width; x++ {
if x < leftLen {
r, combc, size := util.DecodeCharacter(leftText)

View File

@@ -2,6 +2,7 @@ package display
import (
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/tcell/v2"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
@@ -94,12 +95,31 @@ func (w *TabWindow) Display() {
x := -w.hscroll
done := false
tabBarStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["tabbar"]; ok {
tabBarStyle = style
}
globalTabReverse := config.GetGlobalOption("tabreverse").(bool)
globalTabHighlight := config.GetGlobalOption("tabhighlight").(bool)
draw := func(r rune, n int) {
// xor of reverse and tab highlight to get tab character (as in filename and surrounding characters) reverse state
tabCharHighlight := (globalTabReverse || globalTabHighlight) && !(globalTabReverse && globalTabHighlight)
reverseStyles := func(reverse bool) (tcell.Style, tcell.Style) {
tabBarStyle := config.DefStyle.Reverse(reverse)
if style, ok := config.Colorscheme["tabbar"]; ok {
tabBarStyle = style
}
tabBarActiveStyle := tabBarStyle
if style, ok := config.Colorscheme["tabbar.active"]; ok {
tabBarActiveStyle = style
}
return tabBarStyle, tabBarActiveStyle
}
draw := func(r rune, n int, active bool, reversed bool) {
tabBarStyle, tabBarActiveStyle := reverseStyles(reversed)
style := tabBarStyle
if active {
style = tabBarActiveStyle
}
for i := 0; i < n; i++ {
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
@@ -114,7 +134,7 @@ func (w *TabWindow) Display() {
} else if x == 0 && w.hscroll > 0 {
screen.SetContent(0, w.Y, '<', nil, tabBarStyle)
} else if x >= 0 && x < w.Width {
screen.SetContent(x, w.Y, c, nil, tabBarStyle)
screen.SetContent(x, w.Y, c, nil, style)
}
x++
}
@@ -123,28 +143,33 @@ func (w *TabWindow) Display() {
for i, n := range w.Names {
if i == w.active {
draw('[', 1)
draw('[', 1, true, tabCharHighlight)
} else {
draw(' ', 1)
draw(' ', 1, false, tabCharHighlight)
}
for _, c := range n {
draw(c, 1)
draw(c, 1, i == w.active, tabCharHighlight)
}
if i == len(w.Names)-1 {
done = true
}
if i == w.active {
draw(']', 1)
draw(' ', 2)
draw(']', 1, true, tabCharHighlight)
draw(' ', 2, true, globalTabReverse)
} else {
draw(' ', 3)
draw(' ', 1, false, tabCharHighlight)
draw(' ', 2, false, globalTabReverse)
}
if x >= w.Width {
break
}
}
if x < w.Width {
draw(' ', w.Width-x)
draw(' ', w.Width-x, false, globalTabReverse)
}
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
"github.com/zyedidia/terminal"
)
@@ -81,7 +81,7 @@ func (w *TermWindow) Display() {
if b == terminal.DefaultBG {
bg = int(tcell.ColorDefault)
}
st := tcell.StyleDefault.Foreground(config.GetColor256(int(fg))).Background(config.GetColor256(int(bg)))
st := tcell.StyleDefault.Foreground(config.GetColor256(fg)).Background(config.GetColor256(bg))
if l.LessThan(w.Selection[1]) && l.GreaterEqual(w.Selection[0]) || l.LessThan(w.Selection[0]) && l.GreaterEqual(w.Selection[1]) {
st = st.Reverse(true)

View File

@@ -38,15 +38,14 @@ func (w *UIWindow) drawNode(n *views.Node) {
}
for i, c := range cs {
if c.IsLeaf() && c.Kind == views.STVert {
if c.Kind == views.STVert {
if i != len(cs)-1 {
for h := 0; h < c.H; h++ {
screen.SetContent(c.X+c.W, c.Y+h, divchar, combc, dividerStyle)
}
}
} else {
w.drawNode(c)
}
w.drawNode(c)
}
}
@@ -54,32 +53,32 @@ func (w *UIWindow) Display() {
w.drawNode(w.root)
}
func (w *UIWindow) GetMouseSplitID(vloc buffer.Loc) uint64 {
var mouseLoc func(*views.Node) uint64
mouseLoc = func(n *views.Node) uint64 {
func (w *UIWindow) GetMouseSplitNode(vloc buffer.Loc) *views.Node {
var mouseLoc func(*views.Node) *views.Node
mouseLoc = func(n *views.Node) *views.Node {
cs := n.Children()
for i, c := range cs {
if c.Kind == views.STVert {
if i != len(cs)-1 {
if vloc.X == c.X+c.W && vloc.Y >= c.Y && vloc.Y < c.Y+c.H {
return c.ID()
return c
}
}
} else if c.Kind == views.STHoriz {
if i != len(cs)-1 {
if vloc.Y == c.Y+c.H-1 && vloc.X >= c.X && vloc.X < c.X+c.W {
return c.ID()
return c
}
}
}
}
for _, c := range cs {
m := mouseLoc(c)
if m != 0 {
if m != nil {
return m
}
}
return 0
return nil
}
return mouseLoc(w.root)
}

View File

@@ -8,10 +8,13 @@ type View struct {
X, Y int // X,Y location of the view
Width, Height int // Width and height of the view
// Start line and start column of the view (vertical/horizontal scroll)
// Start line of the view (for vertical scroll)
StartLine SLoc
// Start column of the view (for horizontal scroll)
// note that since the starting column of every line is different if the view
// is scrolled, StartCol is a visual index (will be the same for every line)
StartLine, StartCol int
StartCol int
}
type Window interface {
@@ -28,5 +31,7 @@ type Window interface {
type BWindow interface {
Window
SoftWrap
SetBuffer(b *buffer.Buffer)
BufView() View
}

View File

@@ -14,9 +14,9 @@ import (
func (i *InfoBuf) LoadHistory() {
if config.GetGlobalOption("savehistory").(bool) {
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
defer file.Close()
var decodedMap map[string][]string
if err == nil {
defer file.Close()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&decodedMap)
@@ -48,8 +48,8 @@ func (i *InfoBuf) SaveHistory() {
}
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
defer file.Close()
if err == nil {
defer file.Close()
encoder := gob.NewEncoder(file)
err = encoder.Encode(i.History)
@@ -61,6 +61,30 @@ func (i *InfoBuf) SaveHistory() {
}
}
// AddToHistory adds a new item to the history for the prompt type `ptype`.
// This function is not used by micro itself. It is useful for plugins
// which add their own items to the history, bypassing the infobar command line.
func (i *InfoBuf) AddToHistory(ptype string, item string) {
if i.HasPrompt && i.PromptType == ptype {
return
}
if _, ok := i.History[ptype]; !ok {
i.History[ptype] = []string{item}
} else {
i.History[ptype] = append(i.History[ptype], item)
// avoid duplicates
h := i.History[ptype]
for j := len(h) - 2; j >= 0; j-- {
if h[j] == h[len(h)-1] {
i.History[ptype] = append(h[:j], h[j+1:]...)
break
}
}
}
}
// UpHistory fetches the previous item in the history
func (i *InfoBuf) UpHistory(history []string) {
if i.HistoryNum > 0 && i.HasPrompt && !i.HasYN {

View File

@@ -54,7 +54,7 @@ func (i *InfoBuf) Close() {
func (i *InfoBuf) Message(msg ...interface{}) {
// only display a new message if there isn't an active prompt
// this is to prevent overwriting an existing prompt to the user
if i.HasPrompt == false {
if !i.HasPrompt {
displayMessage := fmt.Sprint(msg...)
// if there is no active prompt then style and display the message as normal
i.Msg = displayMessage
@@ -78,7 +78,7 @@ func (i *InfoBuf) ClearGutter() {
func (i *InfoBuf) Error(msg ...interface{}) {
// only display a new message if there isn't an active prompt
// this is to prevent overwriting an existing prompt to the user
if i.HasPrompt == false {
if !i.HasPrompt {
// if there is no active prompt then style and display the message as normal
i.Msg = fmt.Sprint(msg...)
i.HasMessage, i.HasError = false, true
@@ -137,18 +137,27 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
if !hadYN {
if i.PromptCallback != nil {
if canceled {
i.Replace(i.Start(), i.End(), "")
i.PromptCallback("", true)
h := i.History[i.PromptType]
i.History[i.PromptType] = h[:len(h)-1]
} else {
resp := string(i.LineBytes(0))
i.Replace(i.Start(), i.End(), "")
i.PromptCallback(resp, false)
h := i.History[i.PromptType]
h[len(h)-1] = resp
// avoid duplicates
for j := len(h) - 2; j >= 0; j-- {
if h[j] == h[len(h)-1] {
i.History[i.PromptType] = append(h[:j], h[j+1:]...)
break
}
}
}
// i.PromptCallback = nil
}
i.Replace(i.Start(), i.End(), "")
}
if i.YNCallback != nil && hadYN {
i.YNCallback(i.YNResp, canceled)

View File

@@ -1,6 +1,7 @@
package lua
import (
"archive/zip"
"bytes"
"errors"
"fmt"
@@ -9,20 +10,24 @@ import (
"math"
"math/rand"
"net"
"net/http"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"sync"
"time"
"unicode/utf8"
humanize "github.com/dustin/go-humanize"
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"
)
var L *lua.LState
var Lock sync.Mutex
// LoadFile loads a lua file
func LoadFile(module string, file string, data []byte) error {
@@ -69,6 +74,12 @@ func Import(pkg string) *lua.LTable {
return importTime()
case "unicode/utf8", "utf8":
return importUtf8()
case "humanize":
return importHumanize()
case "net/http", "http":
return importHTTP()
case "archive/zip":
return importArchiveZip()
default:
return nil
}
@@ -378,6 +389,7 @@ func importOs() *lua.LTable {
L.SetField(pkg, "Symlink", luar.New(L, os.Symlink))
L.SetField(pkg, "TempDir", luar.New(L, os.TempDir))
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
L.SetField(pkg, "UserHomeDir", luar.New(L, os.UserHomeDir))
return pkg
}
@@ -556,3 +568,31 @@ func importUtf8() *lua.LTable {
return pkg
}
func importHumanize() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Bytes", luar.New(L, humanize.Bytes))
L.SetField(pkg, "Ordinal", luar.New(L, humanize.Ordinal))
return pkg
}
func importHTTP() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Get", luar.New(L, http.Get))
L.SetField(pkg, "Post", luar.New(L, http.Post))
return pkg
}
func importArchiveZip() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "OpenReader", luar.New(L, zip.OpenReader))
L.SetField(pkg, "NewReader", luar.New(L, zip.NewReader))
L.SetField(pkg, "NewWriter", luar.New(L, zip.NewWriter))
return pkg
}

View File

@@ -1,14 +1,14 @@
package screen
import (
"fmt"
"errors"
"log"
"os"
"sync"
"unicode"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/v2"
)
// Screen is the tcell screen we use to draw to the terminal
@@ -19,6 +19,9 @@ import (
// same time too.
var Screen tcell.Screen
// Events is the channel of tcell events
var Events chan (tcell.Event)
// The lock is necessary since the screen is polled on a separate thread
var lock sync.Mutex
@@ -76,6 +79,10 @@ func ShowFakeCursor(x, y int) {
lastCursor.style = style
}
func UseFake() bool {
return util.FakeCursor || config.GetGlobalOption("fakecursor").(bool)
}
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
// reset previous locations of the cursor
// Fake cursors are also necessary to display multiple cursors
@@ -88,7 +95,7 @@ func ShowFakeCursorMulti(x, y int) {
// if enabled or using the terminal cursor otherwise
// By default only the windows console will use a fake cursor
func ShowCursor(x, y int) {
if util.FakeCursor {
if UseFake() {
ShowFakeCursor(x, y)
} else {
Screen.ShowCursor(x, y)
@@ -98,12 +105,12 @@ func ShowCursor(x, y int) {
// SetContent sets a cell at a point on the screen and makes sure that it is
// synced with the last cursor location
func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
if !unicode.IsPrint(mainc) {
if !Screen.CanDisplay(mainc, true) {
mainc = '<27>'
}
Screen.SetContent(x, y, mainc, combc, style)
if util.FakeCursor && lastCursor.x == x && lastCursor.y == y {
if UseFake() && lastCursor.x == x && lastCursor.y == y {
lastCursor.r = mainc
lastCursor.style = style
lastCursor.combc = combc
@@ -131,7 +138,7 @@ func TempStart(screenWasNil bool) {
}
// Init creates and initializes the tcell screen
func Init() {
func Init() error {
drawChan = make(chan bool, 8)
// Should we enable true color?
@@ -142,30 +149,67 @@ func Init() {
}
var oldTerm string
if config.GetGlobalOption("xterm").(bool) {
modifiedTerm := false
setXterm := func() {
oldTerm = os.Getenv("TERM")
os.Setenv("TERM", "xterm-256color")
modifiedTerm = true
}
if config.GetGlobalOption("xterm").(bool) {
setXterm()
}
// Initilize tcell
var err error
Screen, err = tcell.NewScreen()
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
os.Exit(1)
log.Println("Warning: during screen initialization:", err)
log.Println("Falling back to TERM=xterm-256color")
setXterm()
Screen, err = tcell.NewScreen()
if err != nil {
return err
}
}
if err = Screen.Init(); err != nil {
fmt.Println(err)
os.Exit(1)
return err
}
Screen.SetPaste(config.GetGlobalOption("paste").(bool))
// restore TERM
if config.GetGlobalOption("xterm").(bool) {
if modifiedTerm {
os.Setenv("TERM", oldTerm)
}
if config.GetGlobalOption("mouse").(bool) {
Screen.EnableMouse()
}
return nil
}
// InitSimScreen initializes a simulation screen for testing purposes
func InitSimScreen() (tcell.SimulationScreen, error) {
drawChan = make(chan bool, 8)
// Initilize tcell
var err error
s := tcell.NewSimulationScreen("")
if s == nil {
return nil, errors.New("Failed to get a simulation screen")
}
if err = s.Init(); err != nil {
return nil, err
}
s.SetSize(80, 24)
Screen = s
if config.GetGlobalOption("mouse").(bool) {
Screen.EnableMouse()
}
return s, nil
}

View File

@@ -37,6 +37,12 @@ type CallbackFile struct {
args []interface{}
}
// Job stores the executing command for the job, and the stdin pipe
type Job struct {
*exec.Cmd
Stdin io.WriteCloser
}
func (f *CallbackFile) Write(data []byte) (int, error) {
// This is either stderr or stdout
// In either case we create a new job function callback and put it in the jobs channel
@@ -47,13 +53,13 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
// JobStart starts a shell command in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *Job {
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
}
// JobSpawn starts a process with args in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *Job {
// Set up everything correctly if the functions have been provided
proc := exec.Command(cmdName, cmdArgs...)
var outbuf bytes.Buffer
@@ -67,28 +73,24 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(
} else {
proc.Stderr = &outbuf
}
stdin, _ := proc.StdinPipe()
go func() {
// Run the process in the background and create the onExit callback
proc.Run()
jobFunc := JobFunction{onExit, string(outbuf.Bytes()), userargs}
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
Jobs <- jobFunc
}()
return proc
return &Job{proc, stdin}
}
// JobStop kills a job
func JobStop(cmd *exec.Cmd) {
cmd.Process.Kill()
func JobStop(j *Job) {
j.Process.Kill()
}
// JobSend sends the given data into the job's stdin stream
func JobSend(cmd *exec.Cmd, data string) {
stdin, err := cmd.StdinPipe()
if err != nil {
return
}
stdin.Write([]byte(data))
func JobSend(j *Job, data string) {
j.Stdin.Write([]byte(data))
}

View File

@@ -128,7 +128,13 @@ func (t *Terminal) Close() {
// call the lua function that the user has given as a callback
if t.getOutput {
if t.callback != nil {
t.callback(t.output.String())
Jobs <- JobFunction{
Function: func(out string, args []interface{}) {
t.callback(out)
},
Output: t.output.String(),
Args: nil,
}
}
}
}

View File

@@ -1,9 +1,12 @@
package util
import (
"archive/zip"
"bytes"
"errors"
"fmt"
"io"
"net/http"
"os"
"os/user"
"path/filepath"
@@ -23,7 +26,7 @@ var (
// Version is the version number or commit hash
Version = "0.0.0-unknown"
// Semantic version
// SemVersion is the Semantic version
SemVersion semver.Version
// CommitHash is the commit this version was built on
CommitHash = "Unknown"
@@ -46,7 +49,8 @@ func init() {
fmt.Println("Invalid version: ", Version, err)
}
if runtime.GOOS == "windows" {
_, wt := os.LookupEnv("WT_SESSION")
if runtime.GOOS == "windows" && !wt {
FakeCursor = true
}
Stdout = new(bytes.Buffer)
@@ -336,7 +340,11 @@ func GetModTime(path string) (time.Time, error) {
// EscapePath replaces every path separator in a given path with a %
func EscapePath(path string) string {
path = filepath.ToSlash(path)
return strings.Replace(path, "/", "%", -1)
if runtime.GOOS == "windows" {
// ':' is not valid in a path name on Windows but is ok on Unix
path = strings.ReplaceAll(path, ":", "%")
}
return strings.ReplaceAll(path, "/", "%")
}
// GetLeadingWhitespace returns the leading whitespace of the given byte array
@@ -414,19 +422,88 @@ func Clamp(val, min, max int) int {
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.
func IsAutocomplete(c rune) bool {
return c == '.' || !IsNonAlphaNumeric(c)
}
// ParseSpecial replaces escaped ts with '\t'.
func ParseSpecial(s string) string {
return strings.Replace(s, "\\t", "\t", -1)
return strings.ReplaceAll(s, "\\t", "\t")
}
// String converts a byte array to a string (for lua plugins)
func String(s []byte) string {
return string(s)
}
// Unzip unzips a file to given folder
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
os.MkdirAll(dest, 0755)
// Closure to address file descriptors issue with all the deferred .Close() methods
extractAndWriteFile := func(f *zip.File) error {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
path := filepath.Join(dest, f.Name)
// Check for ZipSlip (Directory traversal)
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
return fmt.Errorf("illegal file path: %s", path)
}
if f.FileInfo().IsDir() {
os.MkdirAll(path, f.Mode())
} else {
os.MkdirAll(filepath.Dir(path), f.Mode())
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, rc)
if err != nil {
return err
}
}
return nil
}
for _, f := range r.File {
err := extractAndWriteFile(f)
if err != nil {
return err
}
}
return nil
}
// HttpRequest returns a new http.Client for making custom requests (for lua plugins)
func HttpRequest(method string, url string, headers []string) (resp *http.Response, err error) {
client := http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
for i := 0; i < len(headers); i += 2 {
req.Header.Add(headers[i], headers[i+1])
}
return client.Do(req)
}

View File

@@ -1,21 +0,0 @@
package vfsutil
import (
"net/http"
"os"
)
// File implements http.FileSystem using the native file system restricted to a
// specific file served at root.
//
// While the FileSystem.Open method takes '/'-separated paths, a File's string
// value is a filename on the native file system, not a URL, so it is separated
// by filepath.Separator, which isn't necessarily '/'.
type File string
func (f File) Open(name string) (http.File, error) {
if name != "/" {
return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
}
return os.Open(string(f))
}

View File

@@ -1,39 +0,0 @@
// Package vfsutil implements some I/O utility functions for http.FileSystem.
package vfsutil
import (
"io/ioutil"
"net/http"
"os"
)
// ReadDir reads the contents of the directory associated with file and
// returns a slice of FileInfo values in directory order.
func ReadDir(fs http.FileSystem, name string) ([]os.FileInfo, error) {
f, err := fs.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return f.Readdir(0)
}
// Stat returns the FileInfo structure describing file.
func Stat(fs http.FileSystem, name string) (os.FileInfo, error) {
f, err := fs.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return f.Stat()
}
// ReadFile reads the file named by path from fs and returns the contents.
func ReadFile(fs http.FileSystem, path string) ([]byte, error) {
rc, err := fs.Open(path)
if err != nil {
return nil, err
}
defer rc.Close()
return ioutil.ReadAll(rc)
}

View File

@@ -1,146 +0,0 @@
package vfsutil
import (
"io"
"net/http"
"os"
pathpkg "path"
"path/filepath"
"sort"
)
// Walk walks the filesystem rooted at root, calling walkFn for each file or
// directory in the filesystem, including root. All errors that arise visiting files
// and directories are filtered by walkFn. The files are walked in lexical
// order.
func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error {
info, err := Stat(fs, root)
if err != nil {
return walkFn(root, nil, err)
}
return walk(fs, root, info, walkFn)
}
// readDirNames reads the directory named by dirname and returns
// a sorted list of directory entries.
func readDirNames(fs http.FileSystem, dirname string) ([]string, error) {
fis, err := ReadDir(fs, dirname)
if err != nil {
return nil, err
}
names := make([]string, len(fis))
for i := range fis {
names[i] = fis[i].Name()
}
sort.Strings(names)
return names, nil
}
// walk recursively descends path, calling walkFn.
func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
err := walkFn(path, info, nil)
if err != nil {
if info.IsDir() && err == filepath.SkipDir {
return nil
}
return err
}
if !info.IsDir() {
return nil
}
names, err := readDirNames(fs, path)
if err != nil {
return walkFn(path, info, err)
}
for _, name := range names {
filename := pathpkg.Join(path, name)
fileInfo, err := Stat(fs, filename)
if err != nil {
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
return err
}
} else {
err = walk(fs, filename, fileInfo, walkFn)
if err != nil {
if !fileInfo.IsDir() || err != filepath.SkipDir {
return err
}
}
}
}
return nil
}
// WalkFilesFunc is the type of the function called for each file or directory visited by WalkFiles.
// It's like filepath.WalkFunc, except it provides an additional ReadSeeker parameter for file being visited.
type WalkFilesFunc func(path string, info os.FileInfo, rs io.ReadSeeker, err error) error
// WalkFiles walks the filesystem rooted at root, calling walkFn for each file or
// directory in the filesystem, including root. In addition to FileInfo, it passes an
// ReadSeeker to walkFn for each file it visits.
func WalkFiles(fs http.FileSystem, root string, walkFn WalkFilesFunc) error {
file, info, err := openStat(fs, root)
if err != nil {
return walkFn(root, nil, nil, err)
}
return walkFiles(fs, root, info, file, walkFn)
}
// walkFiles recursively descends path, calling walkFn.
// It closes the input file after it's done with it, so the caller shouldn't.
func walkFiles(fs http.FileSystem, path string, info os.FileInfo, file http.File, walkFn WalkFilesFunc) error {
err := walkFn(path, info, file, nil)
file.Close()
if err != nil {
if info.IsDir() && err == filepath.SkipDir {
return nil
}
return err
}
if !info.IsDir() {
return nil
}
names, err := readDirNames(fs, path)
if err != nil {
return walkFn(path, info, nil, err)
}
for _, name := range names {
filename := pathpkg.Join(path, name)
file, fileInfo, err := openStat(fs, filename)
if err != nil {
if err := walkFn(filename, nil, nil, err); err != nil && err != filepath.SkipDir {
return err
}
} else {
err = walkFiles(fs, filename, fileInfo, file, walkFn)
// file is closed by walkFiles, so we don't need to close it here.
if err != nil {
if !fileInfo.IsDir() || err != filepath.SkipDir {
return err
}
}
}
}
return nil
}
// openStat performs Open and Stat and returns results, or first error encountered.
// The caller is responsible for closing the returned file when done.
func openStat(fs http.FileSystem, name string) (http.File, os.FileInfo, error) {
f, err := fs.Open(name)
if err != nil {
return nil, nil, err
}
fi, err := f.Stat()
if err != nil {
f.Close()
return nil, nil, err
}
return f, fi, nil
}

View File

@@ -1,93 +0,0 @@
package vfsutil_test
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/shurcooL/httpfs/vfsutil"
"golang.org/x/tools/godoc/vfs/httpfs"
"golang.org/x/tools/godoc/vfs/mapfs"
)
func ExampleWalk() {
var fs http.FileSystem = httpfs.New(mapfs.New(map[string]string{
"zzz-last-file.txt": "It should be visited last.",
"a-file.txt": "It has stuff.",
"another-file.txt": "Also stuff.",
"folderA/entry-A.txt": "Alpha.",
"folderA/entry-B.txt": "Beta.",
}))
walkFn := func(path string, fi os.FileInfo, err error) error {
if err != nil {
log.Printf("can't stat file %s: %v\n", path, err)
return nil
}
fmt.Println(path)
return nil
}
err := vfsutil.Walk(fs, "/", walkFn)
if err != nil {
panic(err)
}
// Output:
// /
// /a-file.txt
// /another-file.txt
// /folderA
// /folderA/entry-A.txt
// /folderA/entry-B.txt
// /zzz-last-file.txt
}
func ExampleWalkFiles() {
var fs http.FileSystem = httpfs.New(mapfs.New(map[string]string{
"zzz-last-file.txt": "It should be visited last.",
"a-file.txt": "It has stuff.",
"another-file.txt": "Also stuff.",
"folderA/entry-A.txt": "Alpha.",
"folderA/entry-B.txt": "Beta.",
}))
walkFn := func(path string, fi os.FileInfo, r io.ReadSeeker, err error) error {
if err != nil {
log.Printf("can't stat file %s: %v\n", path, err)
return nil
}
fmt.Println(path)
if !fi.IsDir() {
b, err := ioutil.ReadAll(r)
if err != nil {
log.Printf("can't read file %s: %v\n", path, err)
return nil
}
fmt.Printf("%q\n", b)
}
return nil
}
err := vfsutil.WalkFiles(fs, "/", walkFn)
if err != nil {
panic(err)
}
// Output:
// /
// /a-file.txt
// "It has stuff."
// /another-file.txt
// "Also stuff."
// /folderA
// /folderA/entry-A.txt
// "Alpha."
// /folderA/entry-B.txt
// "Beta."
// /zzz-last-file.txt
// "It should be visited last."
}

View File

@@ -67,6 +67,7 @@ func combineLineMatch(src, dst LineMatch) LineMatch {
// A State represents the region at the end of a line
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

View File

@@ -11,6 +11,7 @@ color-link special "#A6E22E,#1D1F21"
color-link underlined "#D33682,#1D1F21"
color-link error "bold #FF4444,#1D1F21"
color-link todo "bold #FF8844,#1D1F21"
color-link hlsearch "#000000,#B4EC85"
color-link statusline "#1D1F21,#C5C8C6"
color-link tabbar "#1D1F21,#C5C8C6"
color-link indent-char "#505050,#1D1F21"

View File

@@ -12,6 +12,7 @@ color-link special "167,231"
color-link error "231, 160"
color-link underlined "underline 241,231"
color-link todo "246,231"
color-link hlsearch "231,136"
color-link statusline "241,254"
color-link tabbar "241,254"
color-link diff-added "34"

View File

@@ -26,6 +26,7 @@ color-link special "magenta"
color-link ignore "default"
color-link error "bold ,brightred"
color-link todo "underline black,brightyellow"
color-link hlsearch "white,darkgreen"
color-link indent-char ",brightgreen"
color-link line-number "green"
color-link line-number.scrollbar "green"

View File

@@ -21,6 +21,7 @@ color-link special "#b57edc"
color-link ignore "default"
color-link error "bold ,#e34234"
color-link todo "bold underline #888888,#f26522"
color-link hlsearch "#b7b7b7,#32593d"
color-link indent-char ",#bdecb6"
color-link line-number "#bdecb6,#36393e"
color-link line-number.scrollbar "#3eb489"

View File

@@ -12,6 +12,7 @@ color-link special "#CC8242,#242424"
color-link underlined "#D33682,#242424"
color-link error "bold #CB4B16,#242424"
color-link todo "bold #D33682,#242424"
color-link hlsearch "#CCCCCC,#32593D"
color-link statusline "#242424,#CCCCCC"
color-link tabbar "#242424,#CCCCCC"
color-link indent-char "#4F4F4F,#242424"

View File

@@ -5,13 +5,14 @@ color-link constant "#AE81FF,#282828"
color-link constant.string "#E6DB74,#282828"
color-link constant.string.char "#BDE6AD,#282828"
color-link statement "#F92672,#282828"
color-link symbol "#F92672,#282828"
color-link 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"

View File

@@ -0,0 +1,45 @@
color-link default "#F8F8F2,#282A36"
color-link comment "#6272A4"
color-link identifier "#50FA7B"
color-link identifier.class "#8BE9FD"
color-link identifier.var "#F8F8F2"
color-link constant "#BD93F9"
color-link constant.number "#F8F8F2"
color-link constant.string "#F1FA8C"
color-link symbol "#FF79C6"
color-link symbol.brackets "#F8F8F2"
color-link symbol.tag "#AE81FF"
color-link type "italic #8BE9FD"
color-link type.keyword "#FF79C6"
color-link special "#FF79C6"
color-link statement "#FF79C6"
color-link preproc "#FF79C6"
color-link underlined "#FF79C6"
color-link error "bold #FF5555"
color-link todo "bold #FF79C6"
color-link hlsearch "#282A36,#50FA7B"
color-link diff-added "#50FA7B"
color-link diff-modified "#FFB86C"
color-link diff-deleted "#FF5555"
color-link gutter-error "#FF5555"
color-link gutter-warning "#E6DB74"
color-link statusline "#282A36,#F8F8F2"
color-link tabbar "#282A36,#F8F8F2"
color-link indent-char "#6272A4"
color-link line-number "#6272A4"
color-link current-line-number "#F8F8F2"
color-link cursor-line "#44475A,#F8F8F2"
color-link color-column "#44475A"
color-link type.extended "default"

View File

@@ -15,6 +15,7 @@ color-link divider "#001e28,#d0d0d0"
color-link error "#cb4b16,#001e28"
color-link gutter-error "#cb4b16,#001e28"
color-link gutter-warning "#fce94f,#001e28"
color-link hlsearch "#ffffff,#005028"
color-link identifier "#00c8a0,#001e28"
color-link identifier.class "#00c8a0,#001e28"
color-link indent-char "#a0a0a0,#001e28"

View File

@@ -15,6 +15,7 @@ color-link divider "#f0f0f0,#004080"
color-link error "#500000,#f0f0f0"
color-link gutter-error "#500000,#f0f0f0"
color-link gutter-warning "#dcc800,#f0f0f0"
color-link hlsearch "#000000,#b8d8e8"
color-link identifier "bold #0078a0,#f0f0f0"
color-link identifier.class "bold #0078a0,#f0f0f0"
color-link indent-char "#404040,#f0f0f0"

View File

@@ -15,6 +15,7 @@ color-link divider "#2d0023,#d0d0d0"
color-link error "#cb4b16,#2d0023"
color-link gutter-error "#cb4b16,#2d0023"
color-link gutter-warning "#fce94f,#2d0023"
color-link hlsearch "#ffffff,#005028"
color-link identifier "#00c8a0,#2d0023"
color-link identifier.class "#00c8a0,#2d0023"
color-link indent-char "#a0a0a0,#2d0023"

View File

@@ -12,6 +12,7 @@ color-link type "blue"
color-link type.extended "default"
color-link error "red"
color-link todo "bold cyan"
color-link hlsearch "black,brightcyan"
color-link indent-char "bold black"
color-link line-number ""
color-link current-line-number ""

View File

@@ -11,6 +11,7 @@ color-link special "#D26937,#0C1014"
color-link underlined "#EDB443,#0C1014"
color-link error "bold #C23127,#0C1014"
color-link todo "bold #888CA6,#0C1014"
color-link hlsearch "#091F2E,#EDB443"
color-link statusline "#091F2E,#599CAB"
color-link indent-char "#505050,#0C1014"
color-link line-number "#245361,#11151C"

View File

@@ -9,8 +9,10 @@ color-link statement "#fb4934,#282828"
color-link preproc "#fb4934,235"
color-link type "#fb4934,#282828"
color-link special "#d79921,#282828"
color-link underlined "underline #282828"
color-link underlined "underline #458588,#282828"
color-link error "#9d0006,#282828"
color-link todo "bold #ebdbb2,#282828"
color-link hlsearch "#282828,#fabd2f"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"

View File

@@ -11,6 +11,7 @@ color-link special "172,235"
color-link underlined "underline 109,235"
color-link error "235,124"
color-link todo "bold 223,235"
color-link hlsearch "235,214"
color-link diff-added "34"
color-link diff-modified "214"
color-link diff-deleted "160"

View File

@@ -14,6 +14,7 @@ color-link divider "#263238,#80DEEA"
color-link error "bold #263238,#F07178"
color-link gutter-error "#EEFFFF,#F07178"
color-link gutter-warning "#EEFFFF,#FFF176"
color-link hlsearch "#FFFFFF,#546E7A"
color-link identifier "#82AAFF,#263238"
color-link identifier.macro "#FFCB6B,#263238"
color-link indent-char "#505050,#263238"

View File

@@ -11,6 +11,7 @@ color-link special "#A6E22E"
color-link underlined "#D33682"
color-link error "bold #CB4B16"
color-link todo "bold #D33682"
color-link hlsearch "#1D0000,#E6DB74"
color-link statusline "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"
color-link line-number "#AAAAAA,#282828"

View File

@@ -5,13 +5,14 @@ color-link constant "#AE81FF,#282828"
color-link constant.string "#E6DB74,#282828"
color-link constant.string.char "#BDE6AD,#282828"
color-link statement "#F92672,#282828"
color-link symbol "#F92672,#282828"
color-link symbol.operator "#F92672,#282828"
color-link preproc "#CB4B16,#282828"
color-link type "#66D9EF,#282828"
color-link special "#A6E22E,#282828"
color-link underlined "#D33682,#282828"
color-link error "bold #CB4B16,#282828"
color-link todo "bold #D33682,#282828"
color-link hlsearch "#282828,#E6DB74"
color-link statusline "#282828,#F8F8F2"
color-link tabbar "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"

View File

@@ -15,6 +15,7 @@ color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link gutter-error "#9B859D"
color-link gutter-warning "#9B859D"
color-link hlsearch "#21252C,#E5C07B"
color-link identifier "#61AFEF"
color-link identifier.class "#C678DD"
color-link identifier.var "#C678DD"

View File

@@ -3,6 +3,7 @@ color-link comment "#bc9458,#2b2b2b"
color-link statement "#cc7833,#2b2b2b"
color-link constant "#a5c261,#2b2b2b"
color-link constant.bool "#6d9cbe,#2b2b2b"
color-link constant.specialChar "#459231,#2b2b2b"
color-link type "#6d9cbe,#2b2b2b"
color-link preproc "#cc7833,#2b2b2b"
color-link special "#cc7833,#2b2b2b"
@@ -10,6 +11,7 @@ color-link underlined "#cc7833,#2b2b2b"
color-link todo "bold #cc7833,#2b2b2b"
color-link error "bold #cc7833,#2b2b2b"
color-link gutter-error "#cc7833,#11151C"
color-link hlsearch "#e6e1dc,#474d5c"
color-link indent-char "#414141,#2b2b2b"
color-link line-number "#a1a1a1,#232323"
color-link current-line-number "#e6e1dc,#2b2b2b"
@@ -18,6 +20,8 @@ color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link gutter-warning "#a5c261,#11151C"
color-link symbol "#edb753,#2b2b2b"
color-link symbol.operator "#cc7833,#2b2b2b"
color-link symbol.brackets "#cc7833,#2b2b2b"
color-link identifier "#edb753,#2b2b2b"
color-link statusline "#b1b1b1,#232323"
color-link tabbar "bold #b1b1b1,#232323"

View File

@@ -9,6 +9,7 @@ color-link special "magenta"
color-link ignore "default"
color-link error ",brightred"
color-link todo ",brightyellow"
color-link hlsearch "black,yellow"
color-link indent-char "black"
color-link line-number "yellow"
color-link current-line-number "red"

View File

@@ -11,6 +11,7 @@ color-link special "#268BD2,#002833"
color-link underlined "#D33682,#002833"
color-link error "bold #CB4B16,#002833"
color-link todo "bold #D33682,#002833"
color-link hlsearch "#002833,#B58900"
color-link statusline "#003541,#839496"
color-link tabbar "#003541,#839496"
color-link indent-char "#003541,#002833"

View File

@@ -10,6 +10,7 @@ color-link special "blue"
color-link underlined "magenta"
color-link error "bold brightred"
color-link todo "bold magenta"
color-link hlsearch "black,yellow"
color-link statusline "black,brightblue"
color-link tabbar "black,brightblue"
color-link indent-char "black"

View File

@@ -11,6 +11,7 @@ color-link special "22"
color-link underlined "61,230"
color-link error "88"
color-link todo "210"
color-link hlsearch "0,253"
color-link statusline "233,229"
color-link tabbar "233,229"
color-link indent-char "229"

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