Compare commits

..

337 Commits

Author SHA1 Message Date
Jöran Karl
31b26da647 util: Let DecodeCharacter use DecodeCombinedCharacter 2026-01-25 16:30:31 +01:00
Jöran Karl
832cce7531 buffer: Improve cursor movement 2026-01-25 16:30:27 +01:00
Jöran Karl
187ba51fd6 buffer: Rework to retain support of combined characters 2026-01-25 16:24:14 +01:00
Jöran Karl
2e1249cc67 buffer: Remove data as structure element of Line 2026-01-25 16:24:10 +01:00
Jöran Karl
ceea2378f6 buffer: Build the lines with runes 2026-01-25 16:14:48 +01:00
Dmytro Maluka
dc2d70bfe1 Fix default keybindings for Ctrl-c and Ctrl-x in command mode (#3973)
Micro doesn't support chained actions for command mode keybindings yet,
it only supports them for regular buffer keybindings. Whereas Ctrl-c and
Ctrl-x are bound by default to the chained actions Copy|CopyLine and
Cut|CutLine in both buffer mode and command mode, so in command mode
Ctrl-c and Ctrl-x don't work at all (with default keybindings).

Luckily CopyLine and CutLine would not be not very useful in command
mode anyway. So fix the issue by changing the default keybindings in
command mode to the simple non-chained actions Copy and Cut.
2026-01-23 19:07:13 +01:00
Jöran Karl
3e95779cf0 templates: Fix empty titles introduced with #3971 2026-01-21 20:55:01 +01:00
Jöran Karl
28e1b020e4 Merge pull request #3971 from JoeKar/fix/repository-url
Change `zyedidia/micro` to `micro-editor/micro`
2026-01-21 20:52:05 +01:00
Jöran Karl
0a4e15b5a7 Merge pull request #3969 from JoeKar/fix/issue-template
Restore issue template functionality
2026-01-21 20:51:40 +01:00
Jöran Karl
ab8c242044 gofmt after renaming the URL 2026-01-21 20:29:57 +01:00
Jöran Karl
4ead0e453b Change zyedidia/micro to micro-editor/micro 2026-01-21 20:29:55 +01:00
Jöran Karl
20842c0d30 templates: Deny blank issues 2026-01-21 19:23:19 +01:00
Jöran Karl
5c98734f56 templates: Add feature template 2026-01-21 19:23:17 +01:00
Jöran Karl
2e278712ef templates: Convert markdown issue template into YAML form
.github/ISSUE_TEMPLATE isn't allowed as template file name any longer,
since it is used as the default search folder for issue templates.
2026-01-21 19:23:15 +01:00
Frank
d1426b6fb2 Add syntax highlighting rules for B language (#3965) 2026-01-18 10:45:45 +01:00
niten94
a544015a35 Use argument passed to OpenCmd without splitting (#3946)
"Split(args[0])" has been performed since "open" was added[1], but it
may had been left by accident. It's unlikely desired when using the
command prompt, and doesn't seem to have been added once to commands
such as "vsplit" [2] which were implemented days before.

[1]: 5825353f64
[2]: 541daf212e
2026-01-11 11:19:50 +01:00
Jöran Karl
adfc136506 command: Fix typo in documentation of HelpCmd() (#3956) 2026-01-11 11:19:09 +01:00
Jöran Karl
ee09a0354a tools/cross-compile: Drop creation of Linux 64 fully static archive (#3957)
Since https://github.com/benweissmann/getmic.ro/pull/40 was merged there is no
need to create this superfluous archive any longer.
2026-01-10 20:14:37 +01:00
Jöran Karl
9a6c827880 config: Don't hardcode the fakecursor under Windows console any longer (#3959)
* config: Don't hardcode the `fakecursor` under Windows console any longer

We just set the global default and allow the user to override it.

* help: Add a concrete note for the `fakecursor` option in the Windows Console
2026-01-10 20:13:29 +01:00
Jöran Karl
6a62575bcf metainfo: Release v2.0.15 2025-12-31 12:47:43 +01:00
Neko Box Coder
467eb88df0 Adding clarification regarding multiple characters for showchars (#3945)
Adding clarification regarding multiple characters for showchars.

Closes #3938
2025-12-18 18:16:51 +01:00
Jöran Karl
d1ceacad88 Merge pull request #3910 from AndydeCleyre/bugfix/2600
Only set buffer type to stdout when no file args are passed
2025-12-07 18:04:32 +01:00
Andy Kluger
331c43ebb5 Replace never-assigned var filename with literal empty strings 2025-12-03 15:10:36 -05:00
Dmytro Maluka
118f5a3e35 gofmt fix 2025-12-03 01:19:35 +01:00
Andy Kluger
70d9b64301 LoadInput: reduce variable scope (input, err)
Move input and err variable declarations to their usage point.
2025-12-02 14:59:45 -05:00
Andy Kluger
4debd29ccf Make the buffer creation when (no file args, terminal stdin) explicitly distinct
. . . but functionally equivalent.
2025-12-01 13:26:19 -05:00
Andy Kluger
c80e5cd97f Only set buffer type to stdout when no file args are passed
Fixes #2600
2025-12-01 13:15:28 -05:00
Mikko
70dfc7fcb4 fix drawing of wide characters in InfoWindow (#3919) 2025-11-27 19:45:30 +01:00
Jöran Karl
a2620d1c02 Merge pull request #3914 from matthias314/m3/fix-issue-3700
quick fix for #3700
2025-11-27 19:24:52 +01:00
matthias314
560bfcf749 ensure regexp error messages are displayed in findCallback 2025-11-25 17:19:26 -05:00
matthias314
a577fc95ff handle regexps with missing \E (quick fix for #3700) 2025-11-25 17:19:26 -05:00
Jöran Karl
bc5e59c670 Merge pull request #3618 from Neko-Box-Coder/LockConfig
Removing the ability for plugins to modify settings.json and bindings.json. Adding an option to reject plugins to bind keys.
2025-11-18 21:28:19 +01:00
Neko Box Coder
4f1d2bb543 Adding lockbindings option for disallowing lua to modify bindings at all 2025-11-18 19:12:20 +00:00
Neko Box Coder
7a250b7df4 Changing behavior for SetGlobalOption*() for lua to not write to file 2025-11-18 19:12:05 +00:00
Neko Box Coder
b39b5b5916 Changing behavior for TryBindKey() for lua to not write to bindings.json 2025-11-18 19:12:04 +00:00
Nabeel Sherazi
9183fbe640 Include --options-with-hyphens in statement regex (#3863) 2025-11-05 19:07:24 +01:00
niten94
fa68314123 Update Tcell to v2.0.13 (#3895) 2025-10-27 20:38:31 +01:00
kodesettings
ccf0a9f6d6 Added linting support for ldc2 and gdc compilers (#3892)
* Added linting support for ldc2 and gdc compilers

* Update runtime/plugins/linter/linter.lua

ldc2 arguments have been updated for linter

Co-authored-by: Mikko <Andriamanitra@users.noreply.github.com>

---------

Co-authored-by: Mikko <Andriamanitra@users.noreply.github.com>
2025-10-21 18:51:54 +02:00
Neko Box Coder
115e560ee2 Fixing regression introduced by #3310 with missing word boundary (#3891) 2025-10-19 13:13:30 +02:00
Jöran Karl
284942dffd Merge pull request #3806 from JoeKar/fix/backup-path
backup+util: Prevent too long backup file names with hashing + resolve file
2025-10-19 12:46:17 +02:00
Jöran Karl
bab39079b3 save: Remove a possible written backup in case the path has changed 2025-10-18 21:07:07 +02:00
Jöran Karl
02611f4ad2 backup: Keep path of stored & hashed backup files in a own $hash.path file
Since full escaped backup paths can become longer than the maximum filename size
and hashed filenames cannot be restored it is helpful to have a lookup file for
the user to resolve the hashed path.
2025-10-18 21:07:06 +02:00
Jöran Karl
1ce2202d9a util: Convert suffix added with AppendBackupSuffix() to simple constant 2025-10-18 21:06:47 +02:00
Jöran Karl
78d2c617ed util: Hash the path in DetermineEscapePath()
...in case the escaped path exceeds the file name length limit
2025-10-18 21:05:56 +02:00
Mikko
815ca0b6d8 fix c++ highlighting for binary literal with digit separator and suffix (#3870) 2025-10-11 21:59:02 +02:00
Jöran Karl
ec8bb7c11d Merge pull request #3866 from niten94/hide-sudo-prompt-win
- Disable sudo save prompt on Windows
- Add micro.exe to .gitignore
2025-09-21 14:33:23 +02:00
niten94
dcdddc191b Add micro.exe to .gitignore 2025-09-21 00:30:16 +08:00
niten94
85b4b2b788 Disable sudo save prompt on Windows
Display message stating that saving with sudo is unsupported on Windows,
immediately without a prompt when permission is denied.
2025-09-21 00:30:07 +08:00
Jöran Karl
ad24089e4e README: Use v2 for the Go Report Card (#3835) 2025-09-09 20:28:13 +02:00
Neko Box Coder
ed970eede3 Reordering triple quotes string to be evaluated first for groovy syntax (#3858) 2025-09-07 11:40:13 +02:00
Neko Box Coder
4d95f5f121 Adding comment for typescript (#3857) 2025-09-07 11:37:11 +02:00
Jöran Karl
45342bb0f1 Merge pull request #3760 from Neko-Box-Coder/MoreCharOptions
Adding indenttabchar, indentspacechar and spacechar options
2025-09-06 18:53:09 +02:00
Neko Box Coder
1ef6459846 Adding showchars option 2025-09-06 16:48:01 +01:00
Neko Box Coder
532c315f79 Simplifying draw to be less nested 2025-09-06 16:46:50 +01:00
Neko Box Coder
7b01fe4f56 Splitting draw out to getRuneStyle in bufwindow, removing @ for wide rune in bufwindow 2025-09-06 16:42:40 +01:00
cutelisp
0b9c7c0c4a Add toggle & togglelocal command (#3783) 2025-09-05 20:56:02 +02:00
Luca Stefani
e9f241af71 micro: Handle +/regex search from args (#3767)
This is a feature found in vim and commonly used
by Linux kernel test robots to give context about
warnings and/or failures.

e.g. vim +/imem_size +623 drivers/net/ipa/ipa_mem.c

The order in which the commands appear in the args
determines in which order the "goto line:column"
and search will be executed.
2025-09-05 20:53:37 +02:00
ZRZ
bbea2a3f28 Fix highlighting for auto in C++ (#3836) 2025-09-02 19:46:35 +02:00
Jöran Karl
b37fa2e34d Merge pull request #3846 from JoeKar/revert/syntax-rules
syntax: Revert removal of `rules: []`
2025-09-02 19:28:06 +02:00
Mikko
4b2f8aa828 make arduino filetype detection less aggressive (#3848) 2025-09-01 20:46:10 +02:00
Jöran Karl
52f629cee7 syntax: Fix invalid escape of ' in php 2025-08-31 18:58:15 +02:00
Jöran Karl
d9245d9659 syntax: Add the empty rules: [] to the files currently "missing" them 2025-08-31 18:53:45 +02:00
Jöran Karl
3fd2fe3cc7 Revert "doc: syntax: Add hint about incompatibilities to previous versions"
This reverts commit 02e69dddbe.
2025-08-31 13:53:34 +02:00
Jöran Karl
0277516eef Revert "syntax: Remove empty rules in regions"
This reverts commit a9b513a28a.
2025-08-31 13:47:53 +02:00
deepdring
b8057f28c6 refactor: use a more modern writing style to simplify code (#3834)
Signed-off-by: deepdring <deepdrink@icloud.com>
2025-08-26 02:00:51 +02:00
Dmytro Maluka
094b02da4c plugin installer: Remove extra spaces in log messages 2025-08-20 22:19:47 +02:00
Dmytro Maluka
7a252f4986 Merge pull request #3810 from Neko-Box-Coder/ShowBuiltinPlugins
Adding the ability to differentiate builtin plugins when listing
2025-08-20 22:05:07 +02:00
Mikko
6b15bf3b19 fix shell highlighting for variables with leading underscore (#3833) 2025-08-20 20:51:34 +02:00
Jöran Karl
421da6752e Merge pull request #3812 from dmaluka/update-manpage
Update man page + change `[FILE]:LINE:COL` to `FILE[:LINE:COL]` in `micro -help`
2025-08-19 17:36:41 +02:00
Dmytro Maluka
c7d65d113a man page: Remove empty lines
As pointed out by @niten94, empty lines in the man page are not just
unnecessary but also cause extra empty lines to be displayed with
alternative `man` implementations, e.g. `mandoc`. To test on Debian,
for example, mandoc's implementation is named `mman` and included in the
`mandoc` package.
2025-08-19 01:57:18 +02:00
Dmytro Maluka
2b5eb3eca2 man page: Improve formatting
Display option names in bold text and argument placeholders in italic
text, as recommended in `man man`.
2025-08-19 01:56:31 +02:00
Dmytro Maluka
20ebca9be4 Update man page
Align it with `micro -help`.
2025-08-19 01:55:57 +02:00
Dmytro Maluka
f735ff04b4 Improve micro -help output 2025-08-16 16:29:16 +02:00
remisalmon
86a9fac7ef Fix variable expansion regex in fish syntax (#3830) 2025-08-16 13:30:47 +02:00
Jöran Karl
1dbb058773 Merge pull request #3814 from JoeKar/fix/sudo-save
save: Use `dd` with the `notrunc` & `fsync` and postpone truncation
2025-08-12 19:37:16 +02:00
Aleksey Sakovets
bce573b6c9 Add syntax highlighting for meson build system (#3236)
* Add syntax highlighting for meson build system

It is basically a port of upstream syntax highlighting for vim,
but a bit less noisy (e.g numbers are not colored).

* meson.yaml: fix meson_options.txt detection

* meson.yaml: remove empty rules

* meson.yaml: add highlighting for operators and brackets

* meson.yaml: rearrange the keywords to be in alphabetical order

* meson.yaml: add highlighting for numbers
2025-08-12 19:36:09 +02:00
Dmytro Maluka
774a6d8036 Merge pull request #3822 from dmaluka/fix-spurious-backups
Fix spurious backups of unmodified files
2025-08-11 22:30:25 +02:00
Neko Box Coder
177f4d5b01 Updating remove plugin message to specify plugin name 2025-08-09 10:58:16 +01:00
Jöran Karl
165a5a4896 save: Use dd with the notrunc & fsync option
Using notrunc  will stop the overall truncation of the target file done by sudo.
We need to do this because dd, like other coreutils, already truncates the file
on open(). In case we can't store the backup file afterwards we would end up in
a truncated file for which the user has no write permission by default.
Instead we use a second call of `dd` to perform the necessary truncation
on the command line.
With the fsync option we force the dd process to synchronize the written file
to the underlying device.
2025-08-06 20:13:36 +02:00
Dmytro Maluka
0a9fa4f2ea Always use temporary file when writing backup
When writing a backup file, we should write it atomically (i.e. use a
temporary file + rename) in all cases, not only when replacing an
existing backup. Just like we do in util.SafeWrite(). Otherwise, if
micro crashes while writing this backup, even if that doesn't result
in corrupting an existing good backup, it still results in creating
an undesired backup with invalid contents.
2025-08-03 18:41:34 +02:00
Dmytro Maluka
a862c9709e Unify backup write logic
Use the same backup write helper function for both periodic
background backups and for temporary backups in safeWrite().

Besides just removing code duplication, this brings the advantages
of both together:

- Temporary backups in safeWrite() now use the same atomic mechanism
  when replacing an already existing backup. So that if micro crashes
  in the middle of writing the backup in safeWrite(), this corrupted
  backup will not overwrite a previous good backup.

- Better error handling for periodic backups.
2025-08-03 18:37:00 +02:00
Dmytro Maluka
04b878bc2d Ignore the backup option when removing backup
When we need to remove existing backup for whatever reason (e.g. because
we've just successfully saved the file), we should do that regardless of
whether backups are enabled or not, since a backup may exist regardless
(it could have been created before the `backup` option got disabled).
2025-08-03 16:32:25 +02:00
Dmytro Maluka
e127f08251 On panic, backup modified buffers only 2025-08-03 16:32:11 +02:00
Dmytro Maluka
7aa495fd3f Remove backup when buffer becomes unmodified
We should cancel previously requested periodic backup (and remove this
backup if it has already been created) not only when saving or closing
the buffer but also in other cases when the buffer's state changes from
modified to unmodified, i.e. when the user undoes all unsaved changes.

Otherwise, if micro terminates abnormally before the buffer is closed,
this backup will not get removed (so next time micro will suggest the
user to recover this file), even though all changes to this file were
successfully saved.
2025-08-03 16:17:53 +02:00
Dmytro Maluka
2c010afbe4 Fix races between removing backups and creating periodic backups
Micro's logic for periodic backup creation is racy and may cause
spurious backups of unmodified buffers, at least for the following
reasons:

1. When a buffer is closed, its backup is removed by the main goroutine,
   without any synchronization with the backup/save goroutine which
   creates periodic backups in the background.

   A part of the problem here is that the main goroutine removes the
   backup before setting b.fini to true, not after it, so the
   backup/save goroutine may start creating a new backup even after it
   has been removed by the main goroutine. But even if we move the
   b.RemoveBackup() call after setting b.fini, it will not solve the
   problem, since the backup/save goroutine may have already started
   creating a new periodic backup just before b.fini was set to true.

2. When a buffer is successfully saved and thus its backup is removed,
   if there was a periodic backup for this buffer requested by the main
   goroutine but not saved by the backup/save goroutine yet (i.e. this
   request is still pending in backupRequestChan), micro doesn't cancel
   this pending request, so a backup is unexpectedly saved a couple of
   seconds after the file itself was saved.

   Although usually this erroneous backup is removed later, when the
   buffer is closed. But if micro terminates abnormally and the buffer
   is not properly closed, this backup is not removed. Also if this
   issue occurs in combination with the race issue #1 described above,
   this backup may not be successfully removed either.

So, to fix these issues:

1. Do the backup removal in the backup/save goroutine (at requests from
   the main goroutine), not directly in the main goroutine.

2. Make the communication between these goroutines fully synchronous:

2a. Instead of using the buffered channel backupRequestChan as a storage
    for pending requests for periodic backups, let the backup/save
    goroutine itself store this information, in the requestesBackups
    map. Then, backupRequestChan can be made non-buffered.

2b. Make saveRequestChan a non-buffered channel as well. (There was no
    point in making it buffered in the first place, actually.) Once both
    channels are non-buffered, the backup/save goroutine receives both
    backup and save requests from the main goroutine in exactly the same
    order as the main goroutine sends them, so we can guarantee that
    saving the buffer will cancel the previous pending backup request
    for this buffer.
2025-08-03 16:17:03 +02:00
Dmytro Maluka
e84d44d451 Move backup & save related stuff from Buffer to SharedBuffer
Various methods of Buffer should be rather methods of SharedBuffer. This
commit doesn't move all of them to SharedBuffer yet, only those that
need to be moved to SharedBuffer in order to be able to request creating
or removing backups in other SharedBuffer methods.
2025-08-03 14:53:29 +02:00
Dmytro Maluka
f938f62e31 Make isModified reflect actual modified/unmodified state of buffer
Instead of calculating the hash of the buffer every time Modified() is
called, do that every time b.isModified is updated (i.e. every time the
buffer is modified) and set b.isModified value accordingly.

This change means that the hash will be recalculated every time the user
types or deletes a character. But that is what already happens anyway,
since inserting or deleting characters triggers redrawing the display,
in particular redrawing the status line, which triggers Modified() in
order to show the up-to-date modified/unmodified status in the status
line. And with this change, we will be able to check this status
more than once during a single "handle event & redraw" cycle, while
still recalculating the hash only once.
2025-08-03 14:48:26 +02:00
Dmytro Maluka
4ade5cdf24 Make calcHash() a method of SharedBuffer
This will make it easier to use calcHash() in other SharedBuffer
methods.
2025-08-03 14:47:27 +02:00
Neko Box Coder
21edb89693 Adding explicit checks for initlua for different plugin commands 2025-07-30 01:15:58 +01:00
Neko Box Coder
1096c4f3ba Adding the ability to differenitate builtin plugins when listing 2025-07-29 23:00:55 +01:00
cutelisp
c9f84cd2b7 Documentation Fix (#3818) 2025-07-26 11:39:12 +02:00
cutelisp
f97cba34d2 Small Documentation Improvement (#3786) 2025-07-25 21:02:33 +02:00
Jöran Karl
87ce5738e1 save: gofmt 2025-07-23 22:00:10 +02:00
Neko Box Coder
d7e43d4974 Adding missing file closes, rewriting safeWrite() to be more robust (#3807) 2025-07-22 22:58:18 +02:00
Dmytro Maluka
dbdf753f27 Merge pull request #3310 from Neko-Box-Coder/CppAndCLiteralSeparatorFix
Changing syntax behavior for single quote to allow binary and hex literal separator
2025-07-20 16:14:32 +02:00
Dmytro Maluka
7492195a5b Merge pull request #3799 from dmaluka/doc-update
Some documentation improvements
2025-07-19 14:19:11 +02:00
cutelisp
3a7705a090 Enhance GetNativeValue 2025-07-11 11:21:40 +02:00
blamedrop
55a553041b Update log buffer name as well
Using such fake path have some issues as mentioned in https://github.com/zyedidia/micro/pull/3791#discussion_r2176197355 comment
2025-07-09 00:47:15 +02:00
blamedrop
532a229c57 Add buffer name for raw pane 2025-07-09 00:47:15 +02:00
Jonathan
cf92f77fdc Add syntax highlighting for PRQL (#3313)
* Add syntax highlighting for PRQL

Adds a syntax highlighting mode for the PRQL query language.

PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement.
https://prql-lang.org/
https://github.com/PRQL/prql

* Update runtime/syntax/prql.yaml

Co-authored-by: Mikko <Andriamanitra@users.noreply.github.com>

* Update runtime/syntax/prql.yaml

Co-authored-by: Mikko <Andriamanitra@users.noreply.github.com>

* Update runtime/syntax/prql.yaml

Co-authored-by: Mikko <Andriamanitra@users.noreply.github.com>

* Update runtime/syntax/prql.yaml

Co-authored-by: Mikko <Andriamanitra@users.noreply.github.com>

* Update prql.yaml

* Update prql.yaml

---------

Co-authored-by: Mikko <Andriamanitra@users.noreply.github.com>
2025-07-08 11:45:29 +02:00
Dmytro Maluka
0694cd2c1b help: Document passing *tcell.EventMouse to mouse action callbacks 2025-07-05 17:23:19 +02:00
Dmytro Maluka
c267c7c9aa help: Update and correct documentation for onAction return value
The documentation says that the returned value of onAction callbacks
is used for determining whether the view should be relocated, which
has nothing to do with reality, this returned value is used for a
completely different thing. So update the docs accordingly.
2025-07-05 17:23:19 +02:00
Dmytro Maluka
ffdd4b43dd help: Update the list of mouse actions 2025-07-05 17:23:19 +02:00
Dmytro Maluka
61d7f68f9b help: Document binding keys to lua functions
This is still not properly documented (except for the example in
tutorial.md), so document it.
2025-07-05 17:23:19 +02:00
Dmytro Maluka
41b912b539 Merge pull request #3779 from dmaluka/on-pre-mouse-pass-te
Pass mouse info to {on,pre}MouseXXX callbacks
2025-06-29 22:46:10 +02:00
Jöran Karl
da02f836ff Merge pull request #3761 from dmaluka/colorscheme-plugins-regression-fix
Fix non-working colorscheme plugins
2025-06-25 20:57:49 +02:00
Neko Box Coder
4db233acf4 Minor fix for comment plugin and migrating to use "comment.type" option (#3424)
Fixing comment plugin not using user settings when overriding default
setting,
Migrating comment plugin to use "comment.type" option instead
2025-06-24 22:30:26 +02:00
Dmytro Maluka
e923640b37 Move loading colorschemes after initializing plugins
Micro "allows" plugins to register colorschemes via
config.AddRuntimeFile(). However, that has never really worked, since
InitColorscheme() is called earlier than plugins init() or even
preinit() callbacks are called.

To work around that, plugins that use it (e.g. nord-tc [1]) are using a
tricky hack: call config.AddRuntimeFile() not in init() or preinit() but
directly in Lua's global scope, so that it is called earlier, when the
plugin's Lua code is loaded. This hack is not guaranteed to work, and
works by chance. Furthermore, it only works when starting micro, and
doesn't work after the `reload` command. (The reason it doesn't work is
that PluginAddRuntimeFile() calls FindPlugin() which calls IsLoaded()
which returns false, since, well, the plugin is not loaded, it is only
being loaded. And the reason why it works when starting micro is that
in that case IsLoaded() confusingly returns true, since
GlobalSettings[p.Name] has not been set yet.)

So move InitColorscheme() call after calling plugins init/preinit/
postinit callbacks, to let plugins successfully register colorschemes
in any of those callbacks instead of using the aforementioned hack.

[1] https://github.com/KiranWells/micro-nord-tc-colors
2025-06-24 22:24:47 +02:00
Dmytro Maluka
73066fb69b Disable early validation of colorscheme option
Adding early validation of options in ReadSettings() caused a regression:
colorschemes registered by plugins via config.AddRuntimeFile() stopped
working, since ReadSettings() is called when plugins are not initialized
(or even loaded) yet, so a colorscheme is not registered yet and thus
its validation fails.

Fix that with an ad-hoc fix: treat the "colorscheme" option as a special
case and do not verify it early when reading settings.json, postponing
that until the moment when we try to load this colorscheme.
2025-06-24 22:24:47 +02:00
Dmytro Maluka
baa632e6cc Remove PluginCBRune()
Now that we can pass extra args directly to PluginCB(), we can remove
PluginCBRune() for simplicity.
2025-06-21 03:09:55 +02:00
Dmytro Maluka
7861b00cd1 Pass mouse info to {on,pre}MouseXXX callbacks
Pass *tcell.EventMouse to action callbacks for "mouse actions", i.e. to
onMousePress, preMouseDrag and so on, similarly to how we pass it to lua
functions bound to mouse events.
2025-06-21 03:06:48 +02:00
Dmytro Maluka
54ba3cdb4f Make PluginCB() a variadic function
So that we can pass extra arguments to bufpane callbacks easily, by
passing them directly to PluginCB().
2025-06-21 03:04:37 +02:00
theredcmdcraft
97b5e3506e Nftables improvements (#3517) 2025-06-20 21:00:25 +02:00
Mikko
29dc892009 Fix Ruby syntax highlighting for predefined variables (#3778) 2025-06-20 20:53:46 +02:00
cutelisp
5eddf5b85d Change MainTab calls (#3750)
Swaping `MainTab` calls to `h.tab` will not change the normal micro
behaviour.
This changes will make possible to call `ForceQuit`, `VSplitIndex` and
`HSplitIndex` for tabs that aren't main one.
2025-05-26 22:07:14 +02:00
Jöran Karl
cd0dc9a701 options: Add truecolor to control the usage (#2867)
- `auto`: enable usage of true color if it is supported, otherwise disable it
- `on`: force usage of true color
- `off`: disable true color usage

Co-authored-by: Dmytro Maluka <dmitrymaluka@gmail.com>
2025-05-26 18:25:07 +02:00
Codemanticism
bf255b6c35 rust.yaml: Add the keyword "union" (#3759) 2025-05-25 12:59:59 +02:00
Dmytro Maluka
98ff79dbca Relocate buffer view after setting options that affect it (#3743)
Whenever the user changes the value of an option that affects the
calculation of display params in updateDisplayInfo(), we should
immediately recalculate those params and relocate the buffer view
accordingly.

For example: after enabling ruler via `set ruler on`, if the cursor is
currently at the rightmost edge of the bufpane and softwrap is disabled,
then the cursor becomes invisible (since it is now outside the view,
since the buffer view width is decreased as a result of adding the ruler
but StartCol is not updated accordingly), it only becomes visible again
after the user types a character (or performs some other action that
triggers updateDisplayInfo() + relocate). Fix it.
2025-05-11 16:20:23 +02:00
cutelisp
44d0368747 FIX: ruler drawn on top of the tab bar (#3744)
Wrap function lacked a condition to avoid drawing below 0.
2025-05-11 15:32:35 +02:00
cutelisp
895d9d2c82 Fix Scrollbar covering cursor (#3741) 2025-05-11 15:22:59 +02:00
Jöran Karl
809db4ee24 Merge pull request #3738 from JoeKar/fix/termcmd
command: Fix crash caused by `TermCmd()`
2025-05-10 20:29:20 +02:00
Jöran Karl
58b6917526 command: Apply small cosmetics to openTerm() 2025-05-08 06:27:11 +02:00
Jöran Karl
63b6a1e6cf command: Extract term() as dedicated openTerm() function 2025-05-08 06:27:07 +02:00
Jöran Karl
4769a94fb1 command: Exit loop in TermCmd() after terminal call
Otherwise the last opened pane is closed instead of the active one.
2025-05-07 19:33:31 +02:00
Shinsuke Nashimoto
91832d0016 Fixed a broken colorscheme (sunny-day) due to a typo (#3735) 2025-05-06 20:38:45 +02:00
Jöran Karl
06fe85c8c9 Merge pull request #3708 from Neko-Box-Coder/ChainedParentsFix
Fix unable to perform proportional resize caused by chained parents after quiting a nested VSplit inside a HSplit
2025-04-29 21:06:19 +02:00
cutelisp
ca32ffbb4a Change variable visibility (#3720)
Changed DoubleClick and TripleClick to public so they can be accessed by
Lua plugins.
2025-04-29 20:55:01 +02:00
cutelisp
b61c8a4e1a Deleted duplicated line (#3728) 2025-04-28 19:55:03 +02:00
Jöran Karl
333770bbd0 Merge pull request #3727 from JoeKar/fix/path2filepath
Convert leftover usages of `path` to `filepath`
2025-04-27 13:16:00 +02:00
Jöran Karl
7e583fe6ff lua: Remove duplicated export of filepath.Join() 2025-04-26 20:37:51 +02:00
Jöran Karl
1eef4bb3e0 Convert leftover usages of path to filepath 2025-04-26 20:37:49 +02:00
Neko Box Coder
8e7089993d Simplifying unsplit logic 2025-04-26 18:11:19 +01:00
Neko Box Coder
080d216ffd Fixing chained parents by flattening the tree on unsplit 2025-04-26 18:11:19 +01:00
Jöran Karl
e5c3a3edc3 Merge pull request #3719 from niten94/sbuf-switch-skipsave
Skip save on `open` or `term` command if buffer is shared
2025-04-24 13:36:16 +02:00
niten94
c457ae421a Generalize save prompt on close code into method
This slightly changes the open and term command to be similar with the
Quit action, where the buffer or pane is replaced after the prompts are
completed if "n" wasn't pressed after the 1st prompt.
2025-04-18 19:21:27 +08:00
niten94
0d5b2b73e3 Skip save on open or term command if buffer is shared 2025-04-18 19:19:19 +08:00
Mikko
79fe4ae3e3 fix cycling through completion suggestions ending in non-word character (#3650) 2025-04-15 21:02:41 +02:00
cutelisp
b88300ef7f Fix comment (#3716) 2025-04-12 12:27:00 +02:00
Jöran Karl
d36e7f4057 Merge pull request #3714 from niten94/terminal-20250105
Update `micro-editor/terminal` and support terminal emulator in platforms
2025-04-09 20:08:08 +02:00
niten94
2bb3c9aa73 Add Solaris and Illumos targets in cross-compile.sh 2025-04-05 17:49:57 +08:00
niten94
f2454c9248 Specify tags where term emulation is unsupported
Copy build constraints in actions_other.go to terminal_unsupported.go,
to avoid maintaining separate build constraints that are similar with
terminal_supported.go.
2025-04-05 17:49:57 +08:00
niten94
a699cac3be Support term emulation on solaris, netbsd, openbsd/*
Support terminal emulation on platforms below:
- Solaris
- NetBSD: Supported in creack/pty since v1.1.12
- OpenBSD with GOOS != amd64
  - Other architectures are supported now in creack/pty
2025-04-05 17:49:57 +08:00
niten94
9fdf5f3a26 Update micro-editor/terminal to 2025-03-24
Syscall is replaced with IoctlSetWinsize in this version to fix a build
error that occurs with Solaris.
2025-04-05 17:49:36 +08:00
Jöran Karl
f4d62a498b Merge pull request #3704 from dmaluka/gofmt-cleanup
gofmt cleanup
2025-03-25 20:19:52 +01:00
Dmytro Maluka
948b05745f Fix remaining gofmt complaints 2025-03-24 23:04:06 +01:00
Dmytro Maluka
eadc402ae0 Import paths: fix non-alphabetic order
Make gofmt happy about that.
2025-03-24 23:01:48 +01:00
Dmytro Maluka
1bd86a8f79 Build constraints: switch to new syntax
Make gofmt happy about that.
2025-03-24 22:54:32 +01:00
Jöran Karl
219fb12482 Merge pull request #3697 from JoeKar/doc/syntax
doc: syntax: Add hint about incompatibilities to previous versions
2025-03-15 20:29:04 +01:00
Jöran Karl
02e69dddbe doc: syntax: Add hint about incompatibilities to previous versions 2025-03-15 17:45:45 +01:00
Sertonix
78f0a9cd30 doc: syntax: remove syntax_checker.go from README.md
The patch is taken from:
https://github.com/zyedidia/micro/pull/2738

The mentioned file was already removed with the following commit:
fe3186ba9d

The `micro` binary itself takes now care of validating the syntax definitions
and informs about possible issues.
2025-03-15 14:58:14 +01:00
Dmytro Maluka
0b75031ac5 syntax: asm: highlight C-like comments (#3696)
Different assemblers have different syntaxes for comments: ";", "#",
"!", "|", "@", "*" and finally C-like comments "//" and "/* ... */".

Micro currently highlights only ";". This is causing various problems
with broken highlighting with other types of comments (i.e. those not
recognized by micro as comments), when the text in those comments
contains special characters, causing wrong highlighting of text after
them.

On the other hand, highlighting comments like "#", "|" etc would cause
conflicts with other syntax elements, e.g. constants in ARM assembly,
preprocessor directives, arithmetic expressions etc.

So let's highlight at least C-like comments. They are quite commonly
used and they are not so likely to cause conflicts with other syntax
elements.
2025-03-15 14:29:45 +01:00
Dmytro Maluka
fa317456e9 Merge pull request #3689 from Neko-Box-Coder/BetterSaveCmd
Updating SaveCmd to use saveBufToFile instead
2025-03-12 22:11:51 +01:00
Neko Box Coder
82b700390d ReloadSettings only when we need to when saving a file (#3688) 2025-03-12 22:06:24 +01:00
Neko Box Coder
9003243178 Removing the use of SetName() for file buffers 2025-03-12 19:24:36 +00:00
Mikko
7d16dcdaa6 List more bindable actions in help keybindings (#3685) 2025-03-11 07:35:24 +01:00
Neko Box Coder
c9f12206e9 Updating SaveCmd to use saveBufToFile instead 2025-03-11 03:11:03 +00:00
Jöran Karl
98356765c1 Merge pull request #3273 from JoeKar/fix/save-atomically
save: Perform write process safe
2025-03-08 14:04:41 +01:00
Dmytro Maluka
2ae9812f47 Merge pull request #3673 from niten94/status-pass-repodir
`status.lua`: Display commit and branch of repository where file is located
2025-03-04 20:15:23 +01:00
niten94
85e2b3bd86 status.lua: Display hash and branch of file
Return current commit hash and branch of repository where file in buffer
is located instead of current directory.
2025-03-02 09:40:27 +08:00
edwloef
3c68655f33 add syntax highlighting for new rust keywords and types (#3677) 2025-03-01 13:58:30 +01:00
Jöran Karl
8b21724c6e buffer: Store the encoding inside the buffer 2025-02-28 19:02:16 +01:00
Jöran Karl
fe134b92d5 history: Perform write process safe 2025-02-28 18:57:53 +01:00
Jöran Karl
6164050425 save: Update the modification time of the buffer only in case of file changes 2025-02-28 18:57:53 +01:00
Jöran Karl
49aebe8aca save+util: Provide a meaningful error message for safe (over-)write fails 2025-02-28 18:57:53 +01:00
Jöran Karl
79ce93fb7d backup: Clear the requested backup upon completion notification
Now the main go routine takes care of the backup synchronization.
2025-02-28 18:57:53 +01:00
Jöran Karl
771aab251c save+backup: Process the save & backup with a sequential channel
As advantage we don't need to synchonize them any longer and
don't need further insufficient lock mechanisms.
2025-02-28 18:57:53 +01:00
Jöran Karl
35d295dd04 buffer: Remove superfluous backupTime 2025-02-28 18:57:53 +01:00
Jöran Karl
8c883c6210 backup: Rearrange and extend BackupMsg 2025-02-28 18:57:53 +01:00
Jöran Karl
c4dcef3e66 micro: Provide recovery of settings.json & bindings.json 2025-02-28 18:57:53 +01:00
Jöran Karl
e15bb88270 micro: Generalize exit behavior 2025-02-28 18:57:53 +01:00
Jöran Karl
9592bb1549 save: Further rework of overwriteFile()
- extract the open logic into `openFile()` and return a `wrappedFile`
- extract the closing logic into `Close()` and make a method of `wrappedFile`
- rename `writeFile()` into `Write()` and make a method of `wrappedFile`

This allows to use the split parts alone while keeping overwriteFile() as simple
interface to use all in a row.
2025-02-28 18:57:53 +01:00
Jöran Karl
f8d98558f0 save: Merge overwrite() into overwriteFile() and extract writeFile() 2025-02-28 18:57:53 +01:00
Jöran Karl
c926649496 settings: Perform write process safe 2025-02-28 18:57:53 +01:00
Jöran Karl
63d68ec441 bindings: Perform write process safe 2025-02-28 18:57:53 +01:00
Jöran Karl
c972360386 serialize: Perform write process safe 2025-02-28 18:57:53 +01:00
Jöran Karl
022ec0228a util: Provide SafeWrite() to generalize the internal file write process
SafeWrite() will create a temporary intermediate file.
2025-02-28 18:57:53 +01:00
Jöran Karl
4ac8c786f5 backup: Perform write process safe 2025-02-28 18:57:53 +01:00
Jöran Karl
21b7080935 util: Provide AppendBackupSuffix() for further transformations 2025-02-28 18:57:53 +01:00
Jöran Karl
1663a1a6e4 actions: Don't overwrite the buffers Path
This is fully handled within the buffers `save` domain.
2025-02-28 18:57:53 +01:00
Jöran Karl
9b53257e50 save: Perform write process safe 2025-02-28 18:57:53 +01:00
Jöran Karl
6e8daa117a ioutil: Remove deprecated functions where possible 2025-02-28 18:57:53 +01:00
Jöran Karl
18a81f043c util: Generalize the file mode of 0666 with util.FileMode 2025-02-28 18:57:53 +01:00
Jöran Karl
69064cf808 util: Improve and rename EscapePath() to DetermineEscapePath()
If the new URL encoded path is found then it has precedence over the '%' escaped
path. In case none of both is found the new URL approach is used.
2025-02-28 18:57:53 +01:00
Jöran Karl
e828027cc0 clean: Remove some unneeded filepath.Join() calls 2025-02-28 18:57:53 +01:00
Jöran Karl
c2bc4688dd clean: Inform about all failed write steps 2025-02-28 18:57:53 +01:00
Jöran Karl
5aac42dbe7 bindings: Convert os.IsNotExist() into errors.Is() 2025-02-28 18:57:53 +01:00
Jöran Karl
42ae05b082 backup: Lock the buffer lines in Backup() 2025-02-28 18:57:53 +01:00
Jöran Karl
0b871e174f backup: Store the file with the endings of the buffer 2025-02-28 18:57:53 +01:00
Jöran Karl
7c659d1820 backup: Convert os.IsNotExist() into errors.Is() 2025-02-28 18:57:53 +01:00
Jöran Karl
6066c1a10e buffer: Convert os.Is() into errors.Is() 2025-02-28 18:57:53 +01:00
Jöran Karl
6bcec2100c open & write: Process regular files only 2025-02-28 18:57:53 +01:00
Jöran Karl
edc5ff75e3 save: Convert os.IsNotExist() into errors.Is() 2025-02-28 18:57:53 +01:00
Jöran Karl
3fcaf16074 actions: SaveCmd: Print the error of SaveAs to the InfoBar 2025-02-28 18:57:53 +01:00
Jöran Karl
5c21241fc4 actions: SaveAs: Print the error of os.Stat() to the InfoBar 2025-02-28 18:57:53 +01:00
Jöran Karl
272a308275 Merge pull request #3663 from niten94/sh-separate-paramexp
`sh.yaml`: Match valid parameter expansions without braces
2025-02-27 19:03:30 +01:00
Jöran Karl
c93747926d Merge pull request #3662 from JoeKar/fix/reload-settings
buffer: Fix `ReloadSettings(true)` for volatile `filetype`
2025-02-24 18:01:02 +01:00
niten94
0985d2cadd sh.yaml: Match parameter expansions with braces using \w 2025-02-23 11:39:11 +08:00
Jöran Karl
ddc6051b33 actions: Use SetOptionNative() instead of setting options directly
Setting options directly in (h.)Buf.Settings without calling SetOption() or
SetOptionNative() is generally not the best idea, since it may not
trigger the needed side effects.
In particular, after https://github.com/zyedidia/micro/pull/3343,
directly setting `diffgutter` and `ruler` causes them not being tracked as
locally overridden per buffer, so if we run the `reload` command,
it unexpectedly replaces them with the default ones.
2025-02-20 20:24:07 +01:00
Jöran Karl
2e94235905 buffer: Perform filetype callbacks on ReloadSettings()
In `ReloadSettings()` the `filetype` can change upon globbed path given by
the `settings.json` or by identifying a different `filetype` based on the
file name given or pattern present inside the file.
To prevent further recursion caused by checking the `filetype` again, its
processing stops here and isn't considered in `DoSetOptionNative()`
once again where the callbacks are usually triggered.
2025-02-20 20:24:05 +01:00
Jöran Karl
4a9058c3bd buffer: Move UpdatePathGlobLocals() before updating the filetype
Like in NewBuffer(), we need to update glob-based local settings
before updating the filetype, since the filetype itself may be among those
glob-based local settings.
2025-02-20 20:20:38 +01:00
Jöran Karl
982a4fe065 config: Prevent the update of filetype by UpdateFileTypeLocals()
This shall prevent unpredictable results caused by such a user configuration:

```
"ft:go" {
	"filetype": "c"
}
```
2025-02-20 20:18:36 +01:00
Jöran Karl
930fbea74d config: Split up InitLocalSettings() into two dedicated functions
* `UpdatePathGlobLocals()`
	* to apply the settings provided within e.g. "/etc/*": {}
* `UpdateFileTypeLocals()`
	* to apply the settings provided within e.g. "ft:shell": {}

We don't need to call `InitLocalSettings()` twice any longer.
2025-02-20 20:18:30 +01:00
Jöran Karl
00e568640c buffer: Fix ReloadSettings(true) for volatile filetype
We shall not overwrite a volatile set `filetype` provided as argument for micro:
`micro -filetype shell foo`
2025-02-17 20:30:20 +01:00
niten94
d992c606c5 status.lua: Move import lines to beginning of file 2025-02-15 23:17:33 +08:00
niten94
4abd966a99 sh.yaml: Match valid parameter expansions without braces
Match parameter expansions with valid name only in shell syntax file
when there are no braces.
2025-02-15 07:54:22 +08:00
matthias314
5a62a8ead4 match beginning and end of line correctly in FindNext and ReplaceRegex (#3575) 2025-02-09 15:19:43 +01:00
Jöran Karl
bf4156c490 Merge pull request #3657 from Andriamanitra/PR3656-continuation
plugin: linter: add ruff to default configuration
2025-02-08 16:19:59 +01:00
Andriamanitra
b9f1fc8da2 add missing linters to help linter 2025-02-07 23:56:25 +02:00
magneticminou
728526682e plugin: linter: add ruff to documentation 2025-02-07 14:12:29 -03:00
magneticminou
c105c940fe plugin: linter: change in ruff configuration
Use `--output-format concise` as suggested to get exact column of error

Co-authored-by: Mikko <Andriamanitra@users.noreply.github.com>
2025-02-06 19:28:47 -03:00
magneticminou
cdc9ab17f2 plugin: linter: add ruff to default configuration. 2025-02-06 17:05:04 -03:00
usfbih8u
b432bb7cfa docs: remove duplicated line (#3647) 2025-02-01 09:41:25 +01:00
Jöran Karl
e4b0ad7107 Merge pull request #3620 from JoeKar/feature/cursor-overwrite-indicator
statusline: Provide `overwrite` mode indicator
2025-01-31 17:59:36 +01:00
Jöran Karl
57a6e81ddb statusline: Provide overwrite mode indicator 2025-01-30 20:19:37 +01:00
Jöran Karl
5ee7fb6014 Merge pull request #3576 from niten94/optmd-quote-sort
`options.md`: Add, sort entries and adjust formatting
2025-01-29 17:13:53 +01:00
niten94
7aa72b6a96 Sort options in settings.go 2025-01-29 16:06:35 +08:00
Neko Box Coder
dc18642985 Add missing resize in TabMove (#3619) 2025-01-28 21:04:05 +01:00
yz778
c02036e52f Prompt to save or discard new files even with autosave enabled (#3626) 2025-01-25 20:24:31 +01:00
Neko Box Coder
698511c5b6 Fixing settings not being applied when saving as a new file (#3625) 2025-01-24 18:44:27 +01:00
Jöran Karl
c61670e86f buffer: Store the overwrite mode 2025-01-22 17:12:50 +01:00
Dmytro Maluka
6309136322 Adjust formatting of colorscheme option description 2025-01-21 17:26:42 +08:00
niten94
9e46a38536 Insert few parts in options.md in backticks 2025-01-21 17:23:44 +08:00
matthias314
f5debdf8fe ignore quoted and escaped characters when splitting keybindings into actions (#3612) 2025-01-20 20:28:38 +01:00
niten94
4377e56e7e Add, sort options in list, default JSON in options.md 2025-01-19 21:34:38 +08:00
Antoine Beaubien
9b3f7ff240 Update options.md (#3615) 2025-01-19 13:07:48 +01:00
Jöran Karl
f49487dc3a import: Use micro-editor/terminal instead of zyedidia/terminal (#3600) 2025-01-14 18:20:37 +01:00
Jöran Karl
c77ed02778 Merge pull request #3601 from JoeKar/import/go-runewidth
import: Use `mattn/go-runewidth` instead of `zyedidia/go-runewidth`
2025-01-06 07:07:41 +01:00
Jöran Karl
d9956bde38 import: Bump mattn/go-runewidth to v0.0.16 2025-01-05 13:20:53 +01:00
Jöran Karl
ab6e170ec9 import: Use mattn/go-runewidth instead of zyedidia/go-runewidth 2025-01-05 13:19:39 +01:00
Jöran Karl
4d97076479 import: Use micro-editor/go-shellquote instead of zyedidia/go-shellquote (#3596) 2025-01-05 12:19:11 +01:00
Jöran Karl
a883c14c18 Merge pull request #3595 from JoeKar/import/json5
import: Use `micro-editor/json5` instead of `zyedidia/json5`
2025-01-05 12:13:21 +01:00
Jöran Karl
ce356c7957 Build: set 1.19 as minimum supported Go version 2025-01-04 16:01:21 +01:00
Jöran Karl
2ddf461ad8 import: Use micro-editor/json5 instead of zyedidia/json5 2025-01-04 16:01:20 +01:00
Jöran Karl
6600430e88 import: Use micro-editor/tcell (legacy) instead of zyedidia/tcell (#3593) 2025-01-04 15:55:46 +01:00
Dmytro Maluka
82467ba9f6 Merge pull request #3597 from matthias314/m3/gopher-bump
bump gopher-lua and gopher-luar to current versions
2025-01-03 13:09:22 +01:00
Jöran Karl
99a27db4f7 Merge pull request #2962 from JoeKar/fix/linter-ft-relation
plugin: Add new `onBufferOptionChanged` callback
2025-01-01 11:11:39 +01:00
niten94
58d38af8cd Update go-isatty to v0.0.20 (#3561) 2024-12-31 13:32:34 +01:00
Jöran Karl
d1f54ea2a4 plugin: linter: Remove the forced C++14 standard to keep GCCs default 2024-12-31 13:27:38 +01:00
Jöran Karl
6b21fc5f92 plugin: linter: Invoke g++ for c++ instead of gcc 2024-12-31 13:27:38 +01:00
Jöran Karl
de84da068d plugin: linter: Move file type check into a dedicated function 2024-12-31 13:27:38 +01:00
Jöran Karl
771b84141f plugin: linter: Use new onBufferOptionChanged callback 2024-12-31 13:27:38 +01:00
Jöran Karl
415ceee46b plugin: Add new onBufferOptionChanged callback
This can become handy in the moment a plugin needs to react on e.g. changed
file type.
2024-12-31 13:27:35 +01:00
matthias314
aa24590070 bump gopher-luar to v1.0.11 2024-12-28 17:46:59 -05:00
matthias314
505aad8ba0 bump gopher-lua to v1.1.1 2024-12-27 17:59:12 -05:00
matthias314
2898f1590d made FindNext and FindPrevious work with empty matches (#3572) 2024-12-17 18:52:44 +01:00
matthias314
aa0fefcaa1 skip empty match right after previous match in ReplaceCmd (#3566) 2024-12-17 18:44:48 +01:00
matthias314
8cdf68bbf6 skip save dialog on quit if buffer is shared (#3559) 2024-12-09 19:42:19 +01:00
Jöran Karl
fb20818042 Merge pull request #3540 from JoeKar/fix/cursor-down
actions: Perform `Cursor(Page)Down` with selection like GUI editors do
2024-12-04 21:23:40 +01:00
Dmytro Maluka
71a26381c0 Fix unwanted view adjustment after page down (#3555)
Fix regression introduced while implementing nano-like page up/down in
commit b2dbcb3e: if the view is already at the end of the buffer and
the last line is even above the bottom, i.e. there are some empty
lines displayed below the last line (e.g. if we have scrolled past the
last line via the mouse wheel), pressing PageDown not just moves the
cursor to the last line but also unexpectedly adjusts the view so that
the last line is exactly at the bottom.
2024-12-03 21:07:30 +01:00
Jöran Karl
2c4754d484 actions: Prevent additional cursor move down on Cursor(Page)Down
This is needed to not move two lines below the last visual selection when it
has end behind the new line character.
2024-12-03 20:38:34 +01:00
matthias314
831e31d483 avoid creating nil callback for JobSpawn (#3554) 2024-12-02 21:21:29 +01:00
Jöran Karl
50639015d7 cursor: Remove selection reduction by one character on Deselect() 2024-11-30 15:25:14 +01:00
Jöran Karl
aaf45a871f bufwindow: Don't highlight lines in ruler with active selection 2024-11-26 20:30:43 +01:00
Jöran Karl
3a16197da7 actions: On Cursor(Page)Down with selection of newline place cursor to start 2024-11-26 20:12:43 +01:00
med-ab
56c1f75bad Add .cjs (common javascript) to javascript syntax definition (#3539) 2024-11-20 14:51:51 +01:00
Oleksandr Redko
b881bf5606 Remove unused internal or unexported functions (#3481) 2024-11-16 21:19:37 +01:00
Owen McGrath
c8eeb788cb Update java.yaml (#3526) 2024-10-31 23:01:56 +01:00
Jöran Karl
aeabd5a7ba Merge pull request #3518 from nimishjha/nano-like-pageup-pagedown
implement nano-like page up/page down functionality
2024-10-29 21:23:05 +01:00
Nimish Jha
b2dbcb3eab implement nano-like page up/page down functionality 2024-10-29 10:22:35 +11:00
Nimish Jha
eb880d8841 simplify code 2024-10-27 16:22:53 +11:00
Dmytro Maluka
1ead9ce4fd Fix regression in CopyLine, CutLine, DeleteLine for last line (#3519)
Fix regression introduced in commit fdacb28962 ("CopyLine, CutLine,
DeleteLine: respect selection"): when CopyLine, CutLine or DeleteLine is
done in the last line of the buffer and this line is not empty, this
line gets selected but does not get copied/cut/deleted (and worse, it
remains selected).
2024-10-24 18:01:45 +02:00
Nimish Jha
b3227d6049 add actions: CursorToViewTop, CursorToViewCenter, CursorToViewBottom (#3506) 2024-10-23 07:25:33 +02:00
niten94
2c6dc32f5d Set version as release when there are no commits ahead (#3515)
Print release version tag in tools/build-version.go even if the commit
being checked has a tag that is not a version number if there are no
commits ahead.
2024-10-22 22:07:30 +02:00
Creeper Lv
3cb8069e4a Add target for Windows ARM64 in cross-compile.sh (#3512) 2024-10-21 19:33:14 +02:00
Dmytro Maluka
8c0e0fa2ed Make textfilter work with multicursors (#3511)
As requested in [1] and [2], change the `textfilter` command behavior to
apply the filter to the selections of all cursors, not just the 1st one.

[1] https://github.com/zyedidia/micro/discussions/3505
[2] https://github.com/zyedidia/micro/discussions/3510
2024-10-20 21:27:19 +02:00
Dmytro Maluka
f293f983bd Merge pull request #3503 from dmaluka/spawcursorup-logical-lines
Revert `SpawnMultiCursor{Up,Down}` honoring softwrap + overhaul `LastVisualX` usage
2024-10-20 21:26:59 +02:00
Jöran Karl
07f8cfbef1 Merge pull request #3502 from JoeKar/feature/help-split
action/command: Allow `-vsplit` & `-hsplit` as optional argument for `help`

Additionally the help, vsplit and hsplit command can now open multiple files like the tab command.
2024-10-20 20:17:53 +02:00
Jöran Karl
39b2b2639a action/command: Precise HelpCmd() documentation 2024-10-20 14:26:42 +02:00
Jöran Karl
47b84f75e1 config/settings: Add option helpsplit for permanent help split type
For downward compatibility the default split type for the `help` command
is set to be `hsplit`.
2024-10-20 14:26:42 +02:00
Jöran Karl
ff4c5c83f2 runtime/help: Align tab's documentation to vsplit 2024-10-20 14:26:42 +02:00
Jöran Karl
acabf2b492 action/command: Align vsplit & hsplit to tab's multiopen handling 2024-10-20 14:26:40 +02:00
Dmytro Maluka
1023c8d1be Document a few more undocumented colorscheme groups 2024-10-19 01:48:27 +02:00
Jöran Karl
2c62d4b70c action/command: Allow multiple help pages to be opened 2024-10-15 23:35:05 +02:00
Jöran Karl
26f0806915 action/command: Add optional flag -hsplit & -vsplit to help 2024-10-15 23:35:03 +02:00
Dmytro Maluka
e6ed161ca4 SpawnMultiCursorUp/Down: revert honoring softwrap
Commit 9fdea82542 ("Fix various issues with
`SpawnMultiCursor{Up,Down}`") changed SpawnMultiCursorUp/Down actions to
honor softwrap, i.e. spawn cursor in the next visual line within a
wrapped line, which may not be the next logical line in a buffer. That
was done for "consistency" with cursor movement actions CursorUp/Down
etc. But it seems there are no actual use cases for that, whereas at
least some users prefer spawning multicursor in the next logical line
regardless of the softwrap setting. So restore the old behavior.

Fixes #3499
2024-10-14 01:42:04 +02:00
Dmytro Maluka
134cd999c6 Reset LastVisualX on undo/redo
In cursor's Goto(), which is currently only used by undo and redo, we
restore remembered LastVisualX and LastWrappedVisualX values. But if
the window had been resized in the meantime, the LastWrappedVisualX
may not be valid anymore. So it may cause the cursor moving to
unexpected locations.

So for simplicity just reset these values on undo or redo, instead of
using remembered ones.
2024-10-14 01:42:04 +02:00
Dmytro Maluka
6214abba9a Overhaul LastVisualX and GetVisualX() usage
Restore the original meaning of LastVisualX before commit 6d13710d93
("Implement moving cursor up/down within a wrapped line"): last visual x
location of the cursor in a logical line in the buffer, not in a visual
line on the screen (in other words, taking tabs and wide characters into
account, but not taking softwrap into account). And add a separate
LastWrappedVisualX field, similar to LastVisualX but taking softwrap
into account as well.

This allows tracking last x position at the same time for both cases
when we care about softwrap and when we don't care about it. This can be
useful, for example, for implementing cursor up/down movement actions
that always move by logical lines, not by visual lines, even if softwrap
is enabled (in addition to our default CursorUp and CursorDown actions
that move by visual lines).

Also this fixes a minor bug: in InsertTab(), when `tabstospaces` is
enabled and we insert a tab, the amount of inserted spaces depends on
the visual line wrapping (i.e. on the window width), which is probably
not a good idea.
2024-10-14 01:41:35 +02:00
Dmytro Maluka
85afb6eb87 Use StoreVisualX() all over the code
Since we already have the StoreVisualX() helper, use it all over the
place instead of setting LastVisualX directly.

This will allow us to add more logic to StoreVisualX() add let this
extra logic apply everywhere automatically.
2024-10-13 17:46:34 +02:00
Jöran Karl
d60413f03c Merge pull request #3495 from dmaluka/sudo-sigint-fix
Fix SIGINT killing micro when saving with sudo
2024-10-12 14:02:09 +02:00
Dmytro Maluka
af88b4d2a8 Fix error reporting when saving with sudo failed
When saving a file with sudo fails (e.g. if we set `sucmd` to a
non-existent binary, e.g. `set sucmd aaa`), we erroneously return
success instead of the error, as a result we report to the user that
that the file has been successfully saved. Fix it.
2024-10-06 17:08:25 +02:00
Dmytro Maluka
4baac3d3fb Fix SIGINT killing micro when saving with sudo
When we are saving a file with sudo, if we interrupt sudo via Ctrl-c,
it doesn't just kill sudo, it kills micro itself.

The cause is the same as in the issue #2612 for RunInteractiveShell()
which was fixed by #3357. So fix it the same way as in #3357.
2024-10-06 17:08:25 +02:00
theredcmdcraft
ac73f18191 Create nftables.yaml (#3325)
Created nftables syntax highlighting
2024-10-06 13:04:32 +02:00
Neko Box Coder
3b3fe63f19 Exposing replacement functions for deprecated IOUtil functions (#3393) 2024-09-22 22:08:32 +02:00
Oleksandr Redko
9cd1ce968d tool/info-plist: decrease indentation and simplify (#3479) 2024-09-22 20:38:15 +02:00
Jöran Karl
71da59fd1c Merge pull request #3466 from JoeKar/fix/linux-dynamic2static
Makefile: Make all builds explicitly fully static (disable CGO)
2024-09-19 17:27:16 +02:00
Jöran Karl
3f1e5ea6df README: Remove superflous whitespace 2024-09-18 19:10:46 +02:00
Jöran Karl
fcc7421bca Makefile: Fix native darwin/macOS builds with forced CGO 2024-09-18 19:08:50 +02:00
Jöran Karl
6722cc81de tools/cross-compile: Mark "Linux 64 fully static" to be same as "Linux 64"
It is kept for the next release only to support...
f90870e948/index.sh (L197-L204)
...and allow a fluent switch via:
https://github.com/benweissmann/getmic.ro/pull/40
2024-09-16 22:43:12 +02:00
Jöran Karl
90525a6a1d Makefile: Make all builds explicitly fully static by default (disable CGO) 2024-09-16 22:43:12 +02:00
Jöran Karl
6e46ae3090 Makefile: Remove "-s -w" from build-dbg target
This will keep the symbol table and the DWARF information.
2024-09-16 22:43:12 +02:00
Jöran Karl
4d2ddc7940 Makefile: Simplify build-tags build target 2024-09-16 22:43:12 +02:00
Massimo Mund
4f4a13a9a1 Implemented SkipMultiCursorBack as a counterpart to SkipMultiCursor (#3404) 2024-09-16 22:20:12 +02:00
Dmytro Maluka
9eaeb193d4 Merge pull request #3403 from masmu/refactor/tab-actions
Implemented new actions `FirstTab`, `LastTab`, `FirstSplit` and `LastSplit`
2024-09-16 22:19:36 +02:00
Dmytro Maluka
ca6012086b Merge pull request #3335 from dmaluka/line-actions-cleanup
Improve and unify `CopyLine`, `CutLine`, `DeleteLine`, `DuplicateLine` actions
2024-09-16 22:19:05 +02:00
Oleksandr Redko
1539da7fdc test: simplify cmd/micro tests (#3470) 2024-09-16 19:33:59 +02:00
Oleksandr Redko
a3211dce57 Build: set 1.17 as minimum supported Go version (#3461) 2024-09-16 19:21:43 +02:00
Massimo Mund
5f83661fee Fixes a bug where new BufPanes are not being inserted into the right array index.
When adding a new `BufPane` it is always being inserted last into `MainTab().Panes`.
This leads to a confusion when using the actions `PreviousSplit`, `NextSplit` as the previous/next split may not be the expected one.

How to reproduce:
- Launch micro and insert char "1"
- Open a new vsplit via the command `vsplit` and insert "2"
- Switch back to the left split (1) by using `PreviousSplit`
- Again open a new vsplit via command: `vsplit` and type char "3"
- Now switch between the 3 splits using `PreviousSplit`, `NextSplit`

Switching from most left split to the most right, the expected order would be 1, 3, 2 but actually is 1, 2, 3.
2024-09-15 16:36:00 +02:00
Massimo Mund
2e44db1ee9 Implemented new actions FirstTab, LastTab, FirstSplit and LastSplit and changed the default behavior of NextTab, PreviousTab, NextSplit, PreviousSplit to not walk in circles anymore 2024-09-15 16:35:22 +02:00
Oleksandr Redko
e6d4e37922 README: remove TOC in favor to GitHub's TOC (#3467) 2024-09-12 21:00:00 +02:00
Dmytro Maluka
d6d0b26041 Fix non-working raw escape bindings after restarting the screen (#3468)
When we temporarily disable the screen (e.g. during RunInteractiveShell)
and then enable it again, we reinitialize tcell.Screen from scratch, so
we need to register all previously registered raw escape sequences once
again. Otherwise raw escape bindings stop working, since the list of
raw escape sequences of this newly create tcell.Screen is empty.

Fixes #3392
2024-09-12 20:39:14 +02:00
Jonathan Berkeley
f22252e5ae Mark quick install script as third-party (#3469)
Minor update to README that highlights the pipe install script is provided by a third-party.
2024-09-12 20:30:04 +02:00
Jöran Karl
8c52d2426d Merge pull request #3458 from JoeKar/feature/empty-rules
Remove empty rules in regions
2024-09-09 18:48:21 +02:00
Jöran Karl
596da97626 syntax/syntax_converter: Remove empty rules in regions 2024-09-09 18:32:30 +02:00
Jöran Karl
f391b59be6 plugins/literate: Remove empty rules in regions 2024-09-09 18:32:30 +02:00
Jöran Karl
debef6e51b help/colors: Remove empty rules in regions 2024-09-09 18:32:30 +02:00
Jöran Karl
a9b513a28a syntax: Remove empty rules in regions 2024-09-09 18:32:30 +02:00
Jöran Karl
5554cd18e3 highlighter/parser: Switch creation of empty rules to struct literal
Co-authored-by: Dmytro Maluka <dmitrymaluka@gmail.com>
2024-09-09 18:30:39 +02:00
Jöran Karl
6e60dede36 highlighter/parser: Make nested rules optional
This allows us to remove the empty "rules: []" in various syntax definitions.
2024-09-09 18:28:33 +02:00
Oleksandr
5428b3fda2 Add Swift shebang to syntax (#3451)
The Swift compiler can be run in "interpreter" mode, so it can run Swift "scripts" if they have a proper shebang and no file extension.
2024-09-05 18:41:39 +02:00
James M Corey
2308bc5555 Fix rust syntax file to recognize byte strings and c strings. (#3452)
In rust, there are some prefixes that may be part of the string literal.
String literals of the form b"test" (and br##"test"## etc) are byte
strings (as opposed to unicode strings), and similarly, string literals
of the form c"test" are C zero-terminated strings.  Hence, added optional
prefixes to each of the string regular expressions so the prefix will be
recognized as part of the string.

Built and tested after fix.

Co-authored-by: James Corey <jc-git@neniam.net>
2024-09-05 18:41:17 +02:00
James M Corey
d8f7928b74 Add an OpenSCAD syntax file (#3410)
Update from PR feedback:
Coalesce multiple statement rules into one.
Coalesce multiple constant.number into one.

Update from more PR feedback:
Fix special variables (starting with $)--var must start with $,
i.e. x$y is not a valid special var, but you can have x=$y.

Compiled and tested again with latest changes.

Co-authored-by: James Corey <jc-git@neniam.net>
2024-09-04 18:49:55 +02:00
Jöran Karl
2b44fc3bbb Makefile: Fetch tags with --force (#3448) 2024-08-31 18:08:16 +02:00
mystieneko
47fb91e333 add more css commands (#3436) 2024-08-31 13:03:00 +02:00
Mikko
cc67b801ce Improve Haskell syntax highlighting (#3373)
* Improve Haskell syntax highlighting

* add syntax highlighting for binary literals
2024-08-31 12:59:40 +02:00
Juan Francisco Cantero Hurtado
f23c2b6115 Raku syntax: Add .rakutest/.nqp extensions. Rework filename regex. (#3406)
With @niten94, @JoeKar and @Andriamanitra.
2024-08-31 12:44:10 +02:00
Neko Box Coder
e6b20b2ce9 Adding SpawnCursorAtLoc for plugin to use (#3441) 2024-08-31 12:42:55 +02:00
Jöran Karl
968f5ba1ef tools: Revert tgz to tar.gz in cross-compile.sh (#3446) 2024-08-28 23:50:23 +02:00
Jöran Karl
04c577049c metainfo: Release v2.0.14 2024-08-27 19:58:17 +02:00
Dmytro Maluka
bf6584739f help/keybindings: document CutLine behavior 2024-06-14 00:49:51 +02:00
Dmytro Maluka
68d6f43c63 CutLine: remove lastCutTime feature
The lastCutTime feature (reset the clipboard instead of appending to the
clipboard if the last CutLine was more than 10 seconds ago) was
implemented 8 years ago but was always buggy and never really worked,
until we have accidentally found and fixed the bug just now. No one ever
complained or noticed that, which means it is not a very useful feature.
Fixing it changes the existing behavior (essentially adds a new feature
which did not really exist before) and there is no reason to assume that
this new behavior will be welcome by users. So it's better to remove
this feature.
2024-06-12 03:16:36 +02:00
Dmytro Maluka
6f724bc424 DuplicateLine: respect selections
Similarly to CutLine, DeleteLine and CopyLine actions, if there is a
selection, duplicate not just the current line but all the lines covered
(fully or partially) by the selection.
2024-06-09 17:11:58 +02:00
Dmytro Maluka
25f71eec2d DuplicateLine: move selection duplication to separate Duplicate action
- Add a new Duplicate action which just duplicates the selection (and
  returns false if there is no selection).
- Change the behavior of the DuplicateLine action to only duplicate the
  current line, not the selection.
- Change the default action bound to Ctrl-d from DuplicateLine to
  Duplicate|DuplicateLine, so that the default behavior doesn't change.

This allows the user to rebind keybindings in a more flexible way, i.e.
to choose whether a key should duplicate just lines, or just selections,
or both, - in a similar fashion to Copy, Cut, Delete actions.
2024-06-09 17:11:58 +02:00
Dmytro Maluka
33a1bb120f CutLine: return if cliboard read failed
If we ever encounter this clipboard.Read() failure, return false
immediately. Otherwise, InfoBar.Error(err) will have no effect (it will
be immediately overwritten by InfoBar.Message()) so we won't even know
that there was an error.
2024-06-09 17:11:58 +02:00
Dmytro Maluka
04143c7a89 Make Cut, Copy, CopyLine don't mess with CutLine's multi line cuts
Weird behavior is observed e.g. if we cut some lines with CutLine, then
copy some selection with Copy, then cut some other lines with CutLine,
and then paste. The pasted cliboard contains not just the lines that
were cut at the last step, but also the selection that was copied before
that.

Fix that by resetting the CutLine's repeated line cuts whenever we
copy anything to the clipboard via any other action (Cut, Copy or
CopyLine).
2024-06-09 17:11:58 +02:00
Dmytro Maluka
e6825f0e08 CutLine: make infobar message more useful
Since CutLine may add lines to the clipboard instead of replacing the
clipboard, improve its info message to show how many lines are in the
clipboard in total, not just how many lines were added to it last time.
2024-06-09 17:11:58 +02:00
Dmytro Maluka
fdacb28962 CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.

So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 17:11:58 +02:00
Dmytro Maluka
9f7bdb109b Cosmetic change: move Cut above CutLine 2024-06-09 17:11:58 +02:00
Dmytro Maluka
c1bbd7b041 CutLine: cosmetic refactoring 2024-06-09 17:11:58 +02:00
Dmytro Maluka
a317aefd6d Reorganize Cut and CutLine actions
Change behavior of the Cut action: don't implicitly call CutLine if
there is no selection. Instead, make it return false in this case
and change the default Ctrl-x binding to Cut|CutLine, to make it clear,
explicit and in line with Copy and CopyLine actions.
2024-06-09 17:11:58 +02:00
Dmytro Maluka
830768b715 Reorganize Copy and CopyLine actions
Make Copy return false if there is no selection, and change the default
binding for Ctrl-c from CopyLine|Copy to Copy|CopyLine accordingly,
to make the semantics more meaningful: copying selection always fails
if there is no selection.
2024-06-09 12:19:34 +02:00
Dmytro Maluka
2860efbe3a CutLine: remove unneeded if check 2024-06-09 12:16:25 +02:00
Dmytro Maluka
52ed4315ff Make lastCutTime actually work
The CutLine action has a feature: if we execute it multiple times to cut
multiple lines, new cut lines are added to the previously cut lines in
the clipboard instead of replacing the clipboard, unless those
previously cut lines have been already pasted or the last cut was more
than 10 seconds ago. This last bit doesn't really work: newly cut lines
are appended to the clipboard regardless of when was the last cut.
So fix it.
2024-06-09 12:07:07 +02:00
Dmytro Maluka
8bc67569f9 Fix CopyLine at the last empty line of buffer
When the cursor is at the last line of buffer and it is an empty line,
CopyLine does not copy this line, which is correct, but it shows a bogus
"Copied line" message. Fix this by adding a check for that, same as in
CutLine and DeleteLine.
2024-06-09 11:44:44 +02:00
Dmytro Maluka
df8d5285bf Fix Cursor{Up,Down} after CopyLine
After executing the CopyLine action, moving cursor up or down
unexpectedly moves cursor to the beginning of the line, since its
LastVisualX value is lost in the selection/deselection manipulations.
Fix this by restoring the original LastVisualX.
2024-06-09 11:40:30 +02:00
Dmytro Maluka
19c69f9eaa Fix Cursor{Up,Down} after DeleteLine and CutLine
After executing CutLine or DeleteLine action, the cursor is at the
beginning of a line (as expected) but then moving the cursor up or down
moves it to an unexpected location in the middle of the next or previous
line. Fix this by updating the cursor's LastVisualX.
2024-06-09 11:39:23 +02:00
Neko Box Coder
7b50629094 Adding more keywords to signature 2024-06-08 22:58:37 +01:00
Neko Box Coder
27c3e1857a Adding support to single quote separator & char literal error workaround 2024-06-08 22:58:10 +01:00
130 changed files with 4024 additions and 2256 deletions

View File

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

View File

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

25
.github/ISSUE_TEMPLATE/01-bug.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Bug Report
description: File a bug report.
title: "<title>"
labels: ["bug", "triage"]
body:
- type: textarea
attributes:
label: Description
description: Description of the problem and steps to reproduce.
validations:
required: true
- type: textarea
attributes:
label: Environment
description: |
examples:
- **Version**: 2.0.15 and/or commit hash ($ micro -version)
- **OS**: Debian
- **Terminal**: ptyxis
value: |
- Version:
- OS:
- Terminal:
validations:
required: true

11
.github/ISSUE_TEMPLATE/02-feature.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
name: Feature Request
description: File a feature request.
title: "<title>"
labels: ["feature"]
body:
- type: textarea
attributes:
label: Description
description: Description of the feature.
validations:
required: true

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -7,7 +7,7 @@ jobs:
nightly:
strategy:
matrix:
go-version: [1.19.x]
go-version: [1.23.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:

View File

@@ -8,7 +8,7 @@ jobs:
release:
strategy:
matrix:
go-version: [1.19.x]
go-version: [1.23.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:

View File

@@ -4,7 +4,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.19.x]
go-version: [1.19.x, 1.23.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
.DS_Store
micro
micro.exe
!cmd/micro
binaries/
tmp.sh

View File

@@ -430,7 +430,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
github.com/gdamore/tcell/LICENSE
================
github.com/zyedidia/tcell/LICENSE (fork)
github.com/micro-editor/tcell/LICENSE (fork)
================
@@ -1048,7 +1048,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
github.com/flynn/json5/LICENSE
================
github.com/zyedidia/json5/LICENSE (fork)
github.com/micro-editor/json5/LICENSE (fork)
================
Decoder code based on package encoding/json from the Go language.
@@ -1108,7 +1108,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
github.com/james4k/terminal/LICENSE
================
github.com/zyedidia/terminal/LICENSE (fork)
github.com/micro-editor/terminal/LICENSE (fork)
================
Copyright (C) 2013 James Gray

View File

@@ -5,24 +5,31 @@ VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH
HASH = $(shell git rev-parse --short HEAD)
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
go run tools/build-date.go)
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH) \
go run tools/info-plist.go "$(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
GOVARS = -X github.com/micro-editor/micro/v2/internal/util.Version=$(VERSION) -X github.com/micro-editor/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/micro-editor/micro/v2/internal/util.CompileDate=$(DATE)'
DEBUGVAR = -X github.com/micro-editor/micro/v2/internal/util.Debug=ON
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
CGO_ENABLED := $(if $(CGO_ENABLED),$(CGO_ENABLED),0)
ADDITIONAL_GO_LINKER_FLAGS := ""
GOHOSTOS = $(shell go env GOHOSTOS)
ifeq ($(GOHOSTOS), darwin)
# Native darwin resp. macOS builds need external and dynamic linking
ADDITIONAL_GO_LINKER_FLAGS += $(shell GOOS=$(GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH) \
go run tools/info-plist.go "$(shell go env GOOS)" "$(VERSION)")
CGO_ENABLED = 1
endif
build: generate build-quick
build-quick:
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
CGO_ENABLED=$(CGO_ENABLED) go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-dbg:
go build -trimpath -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
CGO_ENABLED=$(CGO_ENABLED) go build -trimpath -ldflags "$(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
build-tags: fetch-tags generate
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-tags: fetch-tags build
build-all: build
@@ -32,7 +39,7 @@ install: generate
install-all: install
fetch-tags:
git fetch --tags
git fetch --tags --force
generate:
GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime

View File

@@ -1,9 +1,9 @@
<img alt="micro logo" src="./assets/micro-logo-drop.svg" width="500px"/>
![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)
![Test Workflow](https://github.com/micro-editor/micro/actions/workflows/test.yaml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/micro-editor/micro/v2)](https://goreportcard.com/report/github.com/micro-editor/micro/v2)
[![Release](https://img.shields.io/github/release/micro-editor/micro.svg?label=Release)](https://github.com/micro-editor/micro/releases)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/micro-editor/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://snapcraft.io/micro/badge.svg)](https://snapcraft.io/micro)
@@ -18,25 +18,9 @@ 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](https://micro-editor.github.io).
You can also check out the website for Micro at https://micro-editor.github.io.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [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)
- [Cygwin, Mingw, Plan9](#cygwin-mingw-plan9)
- [Usage](#usage)
- [Documentation and Help](#documentation-and-help)
- [Contributing](#contributing)
- - -
## Features
@@ -63,7 +47,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
- Syntax highlighting for over [130 languages](runtime/syntax).
- Color scheme support.
- By default, micro comes with 16, 256, and true color themes.
- True color support (set the `MICRO_TRUECOLOR` environment variable to 1 to enable it).
- True color support.
- Copy and paste with the system clipboard.
- Small and simple.
- Easily configurable.
@@ -73,22 +57,22 @@ You can also check out the website for Micro at https://micro-editor.github.io.
## Installation
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
To install micro, you can download a [prebuilt binary](https://github.com/micro-editor/micro/releases), or you can build it from source.
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro).
If you want more information about ways to install micro, see this [wiki page](https://github.com/micro-editor/micro/wiki/Installing-Micro).
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.
A desktop entry file and man page can be found in the [assets/packaging](https://github.com/zyedidia/micro/tree/master/assets/packaging) directory.
A desktop entry file and man page can be found in the [assets/packaging](https://github.com/micro-editor/micro/tree/master/assets/packaging) directory.
### Pre-built binaries
Pre-built binaries are distributed in [releases](https://github.com/zyedidia/micro/releases).
Pre-built binaries are distributed in [releases](https://github.com/micro-editor/micro/releases).
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
#### Quick-install script
#### Third-party quick-install script
```bash
curl https://getmic.ro | bash
@@ -101,14 +85,14 @@ The script will place the micro binary in the current directory. From there, you
With [Eget](https://github.com/zyedidia/eget) installed, you can easily get a pre-built binary:
```
eget zyedidia/micro
eget micro-editor/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
eget --tag nightly micro-editor/micro # download the nightly version (compiled every day at midnight UTC)
eget --tag v2.0.8 micro-editor/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.
@@ -125,7 +109,7 @@ brew install micro
**Note for Mac:** All micro keybindings use the control or alt (option) key, not the command
key. By default, macOS terminals do not forward alt key events. To fix this, please see
the section on [macOS terminals](https://github.com/zyedidia/micro#macos-terminal) further below.
the section on [macOS terminals](https://github.com/micro-editor/micro#macos-terminal) further below.
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
@@ -148,7 +132,7 @@ for other operating systems. These packages are not guaranteed to be up-to-date.
* `eopkg install micro` (Solus).
* `pacstall -I micro` (Pacstall).
* `apt-get install micro` (ALT Linux)
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
* See [wiki](https://github.com/micro-editor/micro/wiki/Installing-Micro) for details about CRUX, Termux.
* distro-agnostic package managers:
* `nix profile install nixpkgs#micro` (with [Nix](https://nixos.org/) and flakes enabled)
* `flox install micro` (with [Flox](https://flox.dev))
@@ -158,7 +142,7 @@ for other operating systems. These packages are not guaranteed to be up-to-date.
* `winget install zyedidia.micro`
* OpenBSD: Available in the ports tree and also available as a binary package.
* `pkg_add -v micro`.
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](http://www.pkgsrc.org/)-current:
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](https://www.pkgsrc.org/)-current:
* `pkg_add micro`
* macOS: Available in package managers.
* `sudo port install micro` (with [MacPorts](https://www.macports.org))
@@ -178,10 +162,10 @@ Without these tools installed, micro will use an internal clipboard for copy and
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.16 or greater and Go modules are enabled.
Make sure that you have Go version 1.19 or greater and Go modules are enabled.
```
git clone https://github.com/zyedidia/micro
git clone https://github.com/micro-editor/micro
cd micro
make build
sudo mv micro /usr/local/bin # optional
@@ -192,23 +176,27 @@ anywhere you like (for example `/usr/local/bin`).
The command `make install` will install the binary to `$GOPATH/bin` or `$GOBIN`.
You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/micro`) but this isn't
You can install directly with `go get` (`go get github.com/micro-editor/micro/cmd/micro`) but this isn't
recommended because it doesn't build micro with version information (necessary for the plugin manager),
and doesn't disable debug mode.
### Fully static binary
### Fully static or dynamically linked binary
By default, the micro binary will dynamically link with core system libraries (this is generally
recommended for security and portability). However, there is a fully static prebuilt binary that
is provided for amd64 as `linux-static.tar.gz`, and to build a fully static binary from source, run
By default, the micro binary is linked statically to increase the portability of the prebuilt binaries.
This behavior can simply be overriden by providing `CGO_ENABLED=1` to the build target.
```
CGO_ENABLED=0 make build
CGO_ENABLED=1 make build
```
Afterwards the micro binary will dynamically link with the present core system libraries.
**Note for Mac:**
Native macOS builds are done with `CGO_ENABLED=1` forced set to support adding the "Information Property List" in the linker step.
### macOS terminal
If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Presets...`, and select `Esc+` for `Left Option Key` in the same menu. The newest versions also support true color.
If you are using macOS, you should consider using [iTerm2](https://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Presets...`, and select `Esc+` for `Left Option Key` in the same menu. The newest versions also support true color.
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>.
@@ -273,14 +261,14 @@ click to enable line selection.
micro has a built-in help system which you can access by pressing <kbd>Ctrl-e</kbd> and typing `help`. Additionally, you can
view the help files here:
- [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
- [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
- [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
- [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
- [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
- [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
- [main help](https://github.com/micro-editor/micro/tree/master/runtime/help/help.md)
- [keybindings](https://github.com/micro-editor/micro/tree/master/runtime/help/keybindings.md)
- [commands](https://github.com/micro-editor/micro/tree/master/runtime/help/commands.md)
- [colors](https://github.com/micro-editor/micro/tree/master/runtime/help/colors.md)
- [options](https://github.com/micro-editor/micro/tree/master/runtime/help/options.md)
- [plugins](https://github.com/micro-editor/micro/tree/master/runtime/help/plugins.md)
I also recommend reading the [tutorial](https://github.com/zyedidia/micro/tree/master/runtime/help/tutorial.md) for
I also recommend reading the [tutorial](https://github.com/micro-editor/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.
@@ -289,9 +277,9 @@ There is also an unofficial Discord, which you can join at https://discord.gg/nh
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues)
You can use the [GitHub issue tracker](https://github.com/micro-editor/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) 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.
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/micro-editor/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.

View File

@@ -1,13 +1,18 @@
.TH micro 1 "2020-02-10"
.TH micro 1 "2025-09-03"
.SH NAME
micro \- A modern and intuitive terminal-based text editor
.SH SYNOPSIS
.B micro
.RB [OPTIONS]
[FILE]\&...
.RI [ OPTION ]...\&
.RI [ FILE ]...\&
.RI [+ LINE [: COL ]]\&
.RI [+/ REGEX ]
.br
.B micro
.RI [ OPTION ]...\&
.RI [ FILE [: LINE [: COL ]]]...\&
\& (only if the `parsecursor` option is enabled)
.SH DESCRIPTION
Micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities
of modern terminals. It comes as one single, batteries-included, static binary with no dependencies.
@@ -15,73 +20,85 @@ As the name indicates, micro aims to be somewhat of a successor to the nano edit
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
Use Ctrl-q to quit, Ctrl-s to save, and Ctrl-g to open the in-editor help menu.
.SH OPTIONS
.PP
\-clean
.B \-clean
.RS 4
Cleans the configuration directory
Clean the configuration directory and exit
.RE
.PP
\-config-dir dir
.B \-config-dir
.I dir
.RS 4
Specify a custom location for the configuration directory
.RE
.PP
[FILE]:LINE:COL
.IR FILE : LINE [: COL ]
(only if the `parsecursor` option is enabled)
.br
.IR FILE \ + LINE [: COL ]
.RS 4
Specify a line and column to start the cursor at when opening a buffer
.RE
.PP
\-options
.RI +/ REGEX
.RS 4
Show all option help
Specify a regex to search for when opening a buffer
.RE
.PP
\-debug
.B \-options
.RS 4
Show all options help and exit
.RE
.PP
.B \-debug
.RS 4
Enable debug mode (enables logging to ./log.txt)
.RE
.PP
\-version
.B \-profile
.RS 4
Show the version number and information
Enable CPU profiling (writes profile info to ./micro.prof so it can be analyzed later with "go tool pprof micro.prof")
.RE
.PP
.B \-version
.RS 4
Show the version number and information and exit
.RE
Micro's plugins can be managed at the command line with the following commands.
.RS 4
.PP
\-plugin remove [PLUGIN]...
.B \-plugin install
.RI [ PLUGIN ]...
.RS 4
Install plugin(s)
.RE
.PP
.B \-plugin remove
.RI [ PLUGIN ]...
.RS 4
Remove plugin(s)
.RE
.PP
\-plugin update [PLUGIN]...
.B \-plugin update
.RI [ PLUGIN ]...
.RS 4
Update plugin(s) (if no argument is given, updates all plugins)
.RE
.PP
\-plugin search [PLUGIN]...
.B \-plugin search
.RI [ PLUGIN ]...
.RS 4
Search for a plugin
.RE
.PP
\-plugin list
.B \-plugin list
.RS 4
List installed plugins
.RE
.PP
\-plugin available
.B \-plugin available
.RS 4
List available plugins
.RE
@@ -91,35 +108,33 @@ Micro's options can also be set via command line arguments for quick
adjustments. For real configuration, please use the settings.json
file (see 'help options').
.RS 4
.PP
\-option value
.BI \-< option >
.I value
.RS 4
Set `option` to `value` for this session
Set `option` to `value` for this session.
.br
For example: `micro -syntax off file.c`
.RE
.RE
.PP
Use `micro -options` to see the full list of configuration options.
.SH CONFIGURATION
Micro uses $MICRO_CONFIG_HOME as the configuration directory.
If this environment variable is not set, it uses $XDG_CONFIG_HOME/micro instead.
If that environment variable is not set, it uses ~/.config/micro as the configuration directory.
In the documentation, we use ~/.config/micro to refer to the configuration directory
(even if it may in fact be somewhere else if you have set either of the above environment variables).
.SH NOTICE
This manpage is intended only to serve as a quick guide to the invocation of
micro and is not intended to replace the full documentation included with micro
which can be accessed from within micro. Micro tells you what key combination to
press to get help in the lower right.
.SH BUGS
A comprehensive list of bugs will not be listed in this manpage. See the Github
page at \fBhttps://github.com/zyedidia/micro/issues\fP for a list of known bugs
page at \fBhttps://github.com/micro-editor/micro/issues\fP for a list of known bugs
and to report any newly encountered bugs you may find. We strive to correct
bugs as swiftly as possible.
.SH COPYRIGHT
Copyright \(co 2020 Zachary Yedidia, et al. MIT license.
See \fBhttps://github.com/zyedidia/micro\fP for details.
See \fBhttps://github.com/micro-editor/micro\fP for details.

View File

@@ -3,15 +3,16 @@ package main
import (
"bufio"
"encoding/gob"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/util"
)
func shouldContinue() bool {
@@ -39,7 +40,16 @@ func CleanConfig() {
}
fmt.Println("Cleaning default settings")
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
settingsFile := filepath.Join(config.ConfigDir, "settings.json")
err := config.WriteSettings(settingsFile)
if err != nil {
if errors.Is(err, util.ErrOverwrite) {
fmt.Println(err.Error())
} else {
fmt.Println("Error writing settings.json file: " + err.Error())
}
}
// detect unused options
var unusedOptions []string
@@ -67,16 +77,20 @@ func CleanConfig() {
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
}
fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
fmt.Printf("These options will be removed from %s\n", settingsFile)
if shouldContinue() {
for _, s := range unusedOptions {
delete(config.GlobalSettings, s)
}
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
err := config.OverwriteSettings(settingsFile)
if err != nil {
fmt.Println("Error writing settings.json file: " + err.Error())
if errors.Is(err, util.ErrOverwrite) {
fmt.Println(err.Error())
} else {
fmt.Println("Error overwriting settings.json file: " + err.Error())
}
}
fmt.Println("Removed unused options")
@@ -85,12 +99,13 @@ func CleanConfig() {
}
// detect incorrectly formatted buffer/ files
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
buffersPath := filepath.Join(config.ConfigDir, "buffers")
files, err := os.ReadDir(buffersPath)
if err == nil {
var badFiles []string
var buffer buffer.SerializedBuffer
for _, f := range files {
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
fname := filepath.Join(buffersPath, f.Name())
file, e := os.Open(fname)
if e == nil {
@@ -105,9 +120,9 @@ func CleanConfig() {
}
if len(badFiles) > 0 {
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), buffersPath)
fmt.Println("These files store cursor and undo history.")
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
fmt.Printf("Removing badly formatted files in %s\n", buffersPath)
if shouldContinue() {
removed := 0

View File

@@ -4,7 +4,7 @@ import (
"log"
"os"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/util"
)
// NullWriter simply sends writes into the void
@@ -18,7 +18,7 @@ func (NullWriter) Write(data []byte) (n int, err error) {
// InitLog sets up the debug log system for micro if it has been enabled by compile-time variables
func InitLog() {
if util.Debug == "ON" {
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, util.FileMode)
if err != nil {
log.Fatalf("error opening file: %v", err)
}

View File

@@ -7,14 +7,14 @@ import (
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"
"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/display"
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/micro-editor/micro/v2/internal/action"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/micro-editor/micro/v2/internal/util"
)
func init() {
@@ -73,7 +73,7 @@ func luaImportMicroConfig() *lua.LTable {
ulua.L.SetField(pkg, "OptionComplete", luar.New(ulua.L, action.OptionComplete))
ulua.L.SetField(pkg, "OptionValueComplete", luar.New(ulua.L, action.OptionValueComplete))
ulua.L.SetField(pkg, "NoComplete", luar.New(ulua.L, nil))
ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKey))
ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKeyPlug))
ulua.L.SetField(pkg, "Reload", luar.New(ulua.L, action.ReloadConfig))
ulua.L.SetField(pkg, "AddRuntimeFileFromMemory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFilesFromDirectory))
@@ -88,8 +88,8 @@ func luaImportMicroConfig() *lua.LTable {
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOptionPlug))
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOptionPlug))
ulua.L.SetField(pkg, "GetGlobalOption", luar.New(ulua.L, config.GetGlobalOption))
ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOption))
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNative))
ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOptionPlug))
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNativePlug))
ulua.L.SetField(pkg, "ConfigDir", luar.New(ulua.L, config.ConfigDir))
return pkg

View File

@@ -4,10 +4,10 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"runtime/pprof"
@@ -18,15 +18,15 @@ import (
"github.com/go-errors/errors"
isatty "github.com/mattn/go-isatty"
"github.com/micro-editor/micro/v2/internal/action"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/clipboard"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
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"
"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/v2"
)
var (
@@ -46,24 +46,28 @@ var (
)
func InitFlags() {
// Note: keep this in sync with the man page in assets/packaging/micro.1
flag.Usage = func() {
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
fmt.Println("Usage: micro [OPTION]... [FILE]... [+LINE[:COL]] [+/REGEX]")
fmt.Println(" micro [OPTION]... [FILE[:LINE[:COL]]]... (only if the `parsecursor` option is enabled)")
fmt.Println("-clean")
fmt.Println(" \tCleans the configuration directory")
fmt.Println(" \tClean the configuration directory and exit")
fmt.Println("-config-dir dir")
fmt.Println(" \tSpecify a custom location for the configuration directory")
fmt.Println("[FILE]:LINE:COL (if the `parsecursor` option is enabled)")
fmt.Println("+LINE:COL")
fmt.Println("FILE:LINE[:COL] (only if the `parsecursor` option is enabled)")
fmt.Println("FILE +LINE[:COL]")
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
fmt.Println("+/REGEX")
fmt.Println(" \tSpecify a regex to search for when opening a buffer")
fmt.Println("-options")
fmt.Println(" \tShow all option help")
fmt.Println(" \tShow all options help and exit")
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")
fmt.Println(" \tShow the version number and information and exit")
fmt.Print("\nMicro's plugins can be managed at the command line with the following commands.\n")
fmt.Println("-plugin install [PLUGIN]...")
@@ -80,7 +84,7 @@ func InitFlags() {
fmt.Println(" \tList available plugins")
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
fmt.Println("-option value")
fmt.Println("-<option> value")
fmt.Println(" \tSet `option` to `value` for this session")
fmt.Println(" \tFor example: `micro -syntax off file.c`")
fmt.Println("\nUse `micro -options` to see the full list of configuration options")
@@ -99,7 +103,7 @@ func InitFlags() {
fmt.Println("Version:", util.Version)
fmt.Println("Commit hash:", util.CommitHash)
fmt.Println("Compiled on", util.CompileDate)
os.Exit(0)
exit(0)
}
if *flagOptions {
@@ -115,7 +119,7 @@ func InitFlags() {
fmt.Printf("-%s value\n", k)
fmt.Printf(" \tDefault value: '%v'\n", v)
}
os.Exit(0)
exit(0)
}
if util.Debug == "OFF" && *flagDebug {
@@ -136,7 +140,7 @@ func DoPluginFlags() {
CleanConfig()
}
os.Exit(0)
exit(0)
}
}
@@ -154,50 +158,63 @@ func LoadInput(args []string) []*buffer.Buffer {
// 3. If there is no input file and the input is a terminal, an empty buffer
// should be opened
var filename string
var input []byte
var err error
buffers := make([]*buffer.Buffer, 0, len(args))
btype := buffer.BTDefault
if !isatty.IsTerminal(os.Stdout.Fd()) {
btype = buffer.BTStdout
}
files := make([]string, 0, len(args))
flagStartPos := buffer.Loc{-1, -1}
flagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
for _, a := range args {
match := flagr.FindStringSubmatch(a)
if len(match) == 3 && match[2] != "" {
line, err := strconv.Atoi(match[1])
posFlagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
posIndex := -1
searchText := ""
searchFlagr := regexp.MustCompile(`^\+\/(.+)$`)
searchIndex := -1
for i, a := range args {
posMatch := posFlagr.FindStringSubmatch(a)
if len(posMatch) == 3 && posMatch[2] != "" {
line, err := strconv.Atoi(posMatch[1])
if err != nil {
screen.TermMessage(err)
continue
}
col, err := strconv.Atoi(match[2])
col, err := strconv.Atoi(posMatch[2])
if err != nil {
screen.TermMessage(err)
continue
}
flagStartPos = buffer.Loc{col - 1, line - 1}
} else if len(match) == 3 && match[2] == "" {
line, err := strconv.Atoi(match[1])
posIndex = i
} else if len(posMatch) == 3 && posMatch[2] == "" {
line, err := strconv.Atoi(posMatch[1])
if err != nil {
screen.TermMessage(err)
continue
}
flagStartPos = buffer.Loc{0, line - 1}
posIndex = i
} else {
files = append(files, a)
searchMatch := searchFlagr.FindStringSubmatch(a)
if len(searchMatch) == 2 {
searchText = searchMatch[1]
searchIndex = i
} else {
files = append(files, a)
}
}
}
command := buffer.Command{
StartCursor: flagStartPos,
SearchRegex: searchText,
SearchAfterStart: searchIndex > posIndex,
}
if len(files) > 0 {
// Option 1
// We go through each file and load it
for i := 0; i < len(files); i++ {
buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos)
buf, err := buffer.NewBufferFromFileWithCommand(files[i], buffer.BTDefault, command)
if err != nil {
screen.TermMessage(err)
continue
@@ -205,30 +222,80 @@ func LoadInput(args []string) []*buffer.Buffer {
// If the file didn't exist, input will be empty, and we'll open an empty buffer
buffers = append(buffers, buf)
}
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
// Option 2
// The input is not a terminal, so something is being piped in
// and we should read from stdin
input, err = ioutil.ReadAll(os.Stdin)
if err != nil {
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
btype := buffer.BTDefault
if !isatty.IsTerminal(os.Stdout.Fd()) {
btype = buffer.BTStdout
}
if !isatty.IsTerminal(os.Stdin.Fd()) {
// Option 2
// The input is not a terminal, so something is being piped in
// and we should read from stdin
input, err := io.ReadAll(os.Stdin)
if err != nil {
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, buffer.NewBufferFromStringWithCommand(string(input), "", btype, command))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, buffer.NewBufferFromStringWithCommand("", "", btype, command))
}
}
return buffers
}
func checkBackup(name string) error {
target := filepath.Join(config.ConfigDir, name)
backup := target + util.BackupSuffix
if info, err := os.Stat(backup); err == nil {
input, err := os.ReadFile(backup)
if err == nil {
t := info.ModTime()
msg := fmt.Sprintf(buffer.BackupMsg, target, t.Format("Mon Jan _2 at 15:04, 2006"), backup)
choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)
if choice%3 == 0 {
// recover
err := os.WriteFile(target, input, util.FileMode)
if err != nil {
return err
}
return os.Remove(backup)
} else if choice%3 == 1 {
// delete
return os.Remove(backup)
} else if choice%3 == 2 {
// abort
return errors.New("Aborted")
}
}
}
return nil
}
func exit(rc int) {
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
if screen.Screen != nil {
screen.Screen.Fini()
}
os.Exit(rc)
}
func main() {
defer func() {
if util.Stdout.Len() > 0 {
fmt.Fprint(os.Stdout, util.Stdout.String())
}
os.Exit(0)
exit(0)
}()
var err error
@@ -256,6 +323,12 @@ func main() {
config.InitRuntimeFiles(true)
config.InitPlugins()
err = checkBackup("settings.json")
if err != nil {
screen.TermMessage(err)
exit(1)
}
err = config.ReadSettings()
if err != nil {
screen.TermMessage(err)
@@ -268,7 +341,7 @@ func main() {
// flag options
for k, v := range optionFlags {
if *v != "" {
nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
nativeValue, err := config.GetNativeValue(k, *v)
if err != nil {
screen.TermMessage(err)
continue
@@ -288,7 +361,7 @@ func main() {
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
os.Exit(1)
exit(1)
}
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
clipErr := clipboard.Initialize(m)
@@ -301,13 +374,15 @@ func main() {
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")
fmt.Println("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/micro-editor/micro/issues")
}
// backup all open buffers
// immediately backup all buffers with unsaved changes
for _, b := range buffer.OpenBuffers {
b.Backup()
if b.Modified() {
b.Backup()
}
}
os.Exit(1)
exit(1)
}
}()
@@ -316,14 +391,15 @@ func main() {
screen.TermMessage(err)
}
action.InitBindings()
action.InitCommands()
err = config.InitColorscheme()
err = checkBackup("bindings.json")
if err != nil {
screen.TermMessage(err)
exit(1)
}
action.InitBindings()
action.InitCommands()
err = config.RunPluginFn("preinit")
if err != nil {
screen.TermMessage(err)
@@ -352,6 +428,11 @@ func main() {
screen.TermMessage(err)
}
err = config.InitColorscheme()
if err != nil {
screen.TermMessage(err)
}
if clipErr != nil {
log.Println(clipErr, " or change 'clipboard' option")
}
@@ -435,23 +516,9 @@ func DoEvent() {
case f := <-timerChan:
f()
case <-sighup:
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
os.Exit(0)
exit(0)
case <-util.Sigterm:
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
if screen.Screen != nil {
screen.Screen.Fini()
}
os.Exit(0)
exit(0)
}
if e, ok := event.(*tcell.EventError); ok {
@@ -459,16 +526,7 @@ func DoEvent() {
if e.Err() == io.EOF {
// shutdown due to terminal closing/becoming inaccessible
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
if screen.Screen != nil {
screen.Screen.Fini()
}
os.Exit(0)
exit(0)
}
return
}

View File

@@ -2,18 +2,17 @@ package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"testing"
"github.com/go-errors/errors"
"github.com/micro-editor/micro/v2/internal/action"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/tcell/v2"
"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
@@ -26,7 +25,7 @@ func init() {
func startup(args []string) (tcell.SimulationScreen, error) {
var err error
tempDir, err = ioutil.TempDir("", "micro_test")
tempDir, err = os.MkdirTemp("", "micro_test")
if err != nil {
return nil, err
}
@@ -56,9 +55,11 @@ func startup(args []string) (tcell.SimulationScreen, error) {
if err := recover(); err != nil {
screen.Screen.Fini()
fmt.Println("Micro encountered an error:", err)
// backup all open buffers
// immediately backup all buffers with unsaved changes
for _, b := range buffer.OpenBuffers {
b.Backup()
if b.Modified() {
b.Backup()
}
}
// Print the stack trace too
log.Fatalf(errors.Wrap(err, 2).ErrorStack())
@@ -164,20 +165,22 @@ func findBuffer(file string) *buffer.Buffer {
return buf
}
func createTestFile(name string, content string) (string, error) {
testf, err := ioutil.TempFile("", name)
func createTestFile(t *testing.T, content string) string {
f, err := os.CreateTemp(t.TempDir(), "")
if err != nil {
return "", err
t.Fatal(err)
}
defer func() {
if err := f.Close(); err != nil {
t.Fatal(err)
}
}()
if _, err := f.WriteString(content); err != nil {
t.Fatal(err)
}
if _, err := testf.Write([]byte(content)); err != nil {
return "", err
}
if err := testf.Close(); err != nil {
return "", err
}
return testf.Name(), nil
return f.Name()
}
func TestMain(m *testing.M) {
@@ -194,18 +197,12 @@ func TestMain(m *testing.M) {
}
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)
file := createTestFile(t, "base content")
openFile(file)
if findBuffer(file) == nil {
t.Errorf("Could not find buffer %s", file)
return
t.Fatalf("Could not find buffer %s", file)
}
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
@@ -223,28 +220,21 @@ func TestSimpleEdit(t *testing.T) {
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
data, err := os.ReadFile(file)
if err != nil {
t.Error(err)
return
t.Fatal(err)
}
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)
file := createTestFile(t, "base content")
openFile(file)
if findBuffer(file) == nil {
t.Errorf("Could not find buffer %s", file)
return
t.Fatalf("Could not find buffer %s", file)
}
// buffer:
@@ -275,10 +265,9 @@ func TestMouse(t *testing.T) {
// base content
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
data, err := os.ReadFile(file)
if err != nil {
t.Error(err)
return
t.Fatal(err)
}
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
@@ -301,18 +290,12 @@ 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)
file := createTestFile(t, srTestStart)
openFile(file)
if findBuffer(file) == nil {
t.Errorf("Could not find buffer %s", file)
return
t.Fatalf("Could not find buffer %s", file)
}
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
@@ -321,10 +304,9 @@ func TestSearchAndReplace(t *testing.T) {
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
data, err := os.ReadFile(file)
if err != nil {
t.Error(err)
return
t.Fatal(err)
}
assert.Equal(t, srTest2, string(data))
@@ -337,10 +319,9 @@ func TestSearchAndReplace(t *testing.T) {
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err = ioutil.ReadFile(file)
data, err = os.ReadFile(file)
if err != nil {
t.Error(err)
return
t.Fatal(err)
}
assert.Equal(t, srTest3, string(data))

View File

@@ -25,6 +25,8 @@
<category>TextEditor</category>
</categories>
<releases>
<release version="2.0.15" date="2025-12-31"/>
<release version="2.0.14" date="2024-08-27"/>
<release version="2.0.13" date="2023-10-22"/>
<release version="2.0.12" date="2023-09-06"/>
<release version="2.0.11" date="2022-08-01"/>
@@ -37,15 +39,15 @@
<screenshots>
<screenshot type="default">
<caption>Micro Text Editor editing its source code</caption>
<image type="source">https://raw.githubusercontent.com/zyedidia/micro/master/assets/micro-solarized.png</image>
<image type="source">https://raw.githubusercontent.com/micro-editor/micro/master/assets/micro-solarized.png</image>
</screenshot>
</screenshots>
<content_rating type="oars-1.1" />
<url type="homepage">https://micro-editor.github.io</url>
<url type="bugtracker">https://github.com/zyedidia/micro/issues</url>
<url type="bugtracker">https://github.com/micro-editor/micro/issues</url>
<url type="faq">https://micro-editor.github.io/about.html</url>
<url type="help">https://micro-editor.github.io/about.html</url>
<url type="contact">https://github.com/zyedidia</url>
<url type="vcs-browser">https://github.com/zyedidia/micro</url>
<url type="contribute">https://github.com/zyedidia/micro#contributing</url>
<url type="vcs-browser">https://github.com/micro-editor/micro</url>
<url type="contribute">https://github.com/micro-editor/micro#contributing</url>
</component>

View File

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

36
go.mod
View File

@@ -1,30 +1,40 @@
module github.com/zyedidia/micro/v2
module github.com/micro-editor/micro/v2
require (
github.com/blang/semver v3.5.1+incompatible
github.com/dustin/go-humanize v1.0.0
github.com/go-errors/errors v1.0.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-isatty v0.0.11
github.com/mattn/go-runewidth v0.0.7
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-runewidth v0.0.16
github.com/micro-editor/json5 v1.0.1-micro
github.com/micro-editor/tcell/v2 v2.0.13
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5
github.com/mitchellh/go-homedir v1.1.0
github.com/sergi/go-diff v1.1.0
github.com/stretchr/testify v1.4.0
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
github.com/yuin/gopher-lua v1.1.1
github.com/zyedidia/clipper v0.1.1
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/tcell/v2 v2.0.10 // indirect
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef
golang.org/x/text v0.3.8
golang.org/x/text v0.4.0
gopkg.in/yaml.v2 v2.2.8
layeh.com/gopher-luar v1.0.7
layeh.com/gopher-luar v1.0.11
)
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
require (
github.com/creack/pty v1.1.18 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/zyedidia/poller v1.0.1 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
)
replace github.com/mattn/go-runewidth => github.com/zyedidia/go-runewidth v0.0.12
replace github.com/kballard/go-shellquote => github.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.7
replace layeh.com/gopher-luar v1.0.11 => github.com/layeh/gopher-luar v1.0.11
go 1.16
go 1.19

83
go.sum
View File

@@ -19,84 +19,57 @@ 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/layeh/gopher-luar v1.0.11 h1:ss6t9OtykOiETBScJylSMPhuYAtOmpH5rSX10/wCcis=
github.com/layeh/gopher-luar v1.0.11/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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5 h1:D7BPnsedXiKo/e8RTFX419/52ICNhU8UKPQGZ/0yiLc=
github.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5/go.mod h1:zaPgW/fDiW4MUfEwxpC+GB/bhvX44NJaNHmRAC9auHQ=
github.com/micro-editor/json5 v1.0.1-micro h1:5Y4MuzhkmW0sQQNPvrIVevIOKi557qsznwjRr4iq1AI=
github.com/micro-editor/json5 v1.0.1-micro/go.mod h1:cmlPHZ1JKOXNse0/3zwwKj/GUpzAVkzx4lZDkpHl4q0=
github.com/micro-editor/tcell/v2 v2.0.13 h1:xyuSpBhSBsUH+bs7FER9IV2/TsQpBmCFiNWJVAEdT68=
github.com/micro-editor/tcell/v2 v2.0.13/go.mod h1:ixpjICpoGp83FZVoLYFJPBwCAslHeTnvgPdhJVPLyy0=
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5 h1:czSkYUNmHuWS2lv8VreufENEXZNOCGZcXd744YKf8yM=
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5/go.mod h1:OszIG7ockt4osicVHq6gI2QmV4PBDK6H5/Bj8GDGv4Q=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
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/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/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
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/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/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/tcell/v2 v2.0.10-0.20230320201625-54f6acdada4a h1:W4TWa++Wk6uRGxZoxr2nPX1TpIEl+Wxv0mTtocG4TYc=
github.com/zyedidia/tcell/v2 v2.0.10-0.20230320201625-54f6acdada4a/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/tcell/v2 v2.0.10-0.20230831153116-061c5b2c7260 h1:SCAmAacT5BxZsmOFdFy5zwwi6nj1MjA60gydjKdTgXo=
github.com/zyedidia/tcell/v2 v2.0.10-0.20230831153116-061c5b2c7260/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/tcell/v2 v2.0.10 h1:6fbbYAx/DYc9A//4jU1OeBrxtc9qJxYCZXCtGQbtTWU=
github.com/zyedidia/tcell/v2 v2.0.10/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef h1:LeB4Qs0Tss4r/Qh8pfsTTqagDYHysfKJLYzAH3MVfu0=
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef/go.mod h1:zeb8MJdcCObFKVvur3n2B4BANIPuo2Q8r4iiNs9Enx0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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/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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=

View File

@@ -11,14 +11,14 @@ import (
"time"
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/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/v2"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/clipboard"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
)
// ScrollUp is not an action
@@ -46,6 +46,14 @@ func (h *BufPane) ScrollAdjust() {
h.SetView(v)
}
// ScrollReachedEnd returns true if the view is at the end of the buffer,
// i.e. the last line of the buffer is in the view.
func (h *BufPane) ScrollReachedEnd() bool {
v := h.GetView()
end := h.SLocFromLoc(h.Buf.End())
return h.Diff(v.StartLine, end) < h.BufView().Height
}
// MousePress is the event that should happen when a normal click happens
// This is almost always bound to left click
func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
@@ -65,12 +73,12 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
h.Cursor.Loc = mouseLoc
}
if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
if h.doubleClick {
if h.DoubleClick {
// Triple click
h.lastClickTime = time.Now()
h.tripleClick = true
h.doubleClick = false
h.TripleClick = true
h.DoubleClick = false
h.Cursor.SelectLine()
h.Cursor.CopySelection(clipboard.PrimaryReg)
@@ -78,15 +86,15 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
// Double click
h.lastClickTime = time.Now()
h.doubleClick = true
h.tripleClick = false
h.DoubleClick = true
h.TripleClick = false
h.Cursor.SelectWord()
h.Cursor.CopySelection(clipboard.PrimaryReg)
}
} else {
h.doubleClick = false
h.tripleClick = false
h.DoubleClick = false
h.TripleClick = false
h.lastClickTime = time.Now()
h.Cursor.OrigSelection[0] = h.Cursor.Loc
@@ -108,9 +116,9 @@ func (h *BufPane) MouseDrag(e *tcell.EventMouse) bool {
}
h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
if h.tripleClick {
if h.TripleClick {
h.Cursor.AddLineToSelection()
} else if h.doubleClick {
} else if h.DoubleClick {
h.Cursor.AddWordToSelection()
} else {
h.Cursor.SelectTo(h.Cursor.Loc)
@@ -127,7 +135,7 @@ func (h *BufPane) MouseRelease(e *tcell.EventMouse) bool {
// that doesn't support mouse motion events. But when the mouse click is
// within the scroll margin, that would cause a scroll and selection
// even for a simple mouse click, which is not good.
// if !h.doubleClick && !h.tripleClick {
// if !h.DoubleClick && !h.TripleClick {
// mx, my := e.Position()
// h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
@@ -145,7 +153,7 @@ func (h *BufPane) ScrollUpAction() bool {
return true
}
// ScrollDownAction scrolls the view up
// ScrollDownAction scrolls the view down
func (h *BufPane) ScrollDownAction() bool {
h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
return true
@@ -160,6 +168,52 @@ func (h *BufPane) Center() bool {
return true
}
// CursorToViewTop moves the cursor to the top of the view,
// offset by scrollmargin unless at the beginning or end of the file
func (h *BufPane) CursorToViewTop() bool {
v := h.GetView()
h.Buf.ClearCursors()
scrollmargin := int(h.Buf.Settings["scrollmargin"].(float64))
bStart := display.SLoc{0, 0}
if v.StartLine == bStart {
scrollmargin = 0
}
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
SLoc: h.Scroll(v.StartLine, scrollmargin),
VisualX: 0,
}))
return true
}
// CursorToViewCenter moves the cursor to the center of the view
func (h *BufPane) CursorToViewCenter() bool {
v := h.GetView()
h.Buf.ClearCursors()
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
SLoc: h.Scroll(v.StartLine, h.BufView().Height/2),
VisualX: 0,
}))
return true
}
// CursorToViewBottom moves the cursor to the bottom of the view,
// offset by scrollmargin unless at the beginning or end of the file
func (h *BufPane) CursorToViewBottom() bool {
v := h.GetView()
h.Buf.ClearCursors()
scrollmargin := int(h.Buf.Settings["scrollmargin"].(float64))
bEnd := h.SLocFromLoc(h.Buf.End())
lastLine := h.Scroll(v.StartLine, h.BufView().Height-1)
if lastLine == bEnd {
scrollmargin = 0
}
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
SLoc: h.Scroll(lastLine, -scrollmargin),
VisualX: 0,
}))
return true
}
// MoveCursorUp is not an action
func (h *BufPane) MoveCursorUp(n int) {
if !h.Buf.Settings["softwrap"].(bool) {
@@ -170,10 +224,10 @@ func (h *BufPane) MoveCursorUp(n int) {
if sloc == vloc.SLoc {
// we are at the beginning of buffer
h.Cursor.Loc = h.Buf.Start()
h.Cursor.LastVisualX = 0
h.Cursor.StoreVisualX()
} else {
vloc.SLoc = sloc
vloc.VisualX = h.Cursor.LastVisualX
vloc.VisualX = h.Cursor.LastWrappedVisualX
h.Cursor.Loc = h.LocFromVLoc(vloc)
}
}
@@ -189,11 +243,10 @@ func (h *BufPane) MoveCursorDown(n int) {
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
h.Cursor.StoreVisualX()
} else {
vloc.SLoc = sloc
vloc.VisualX = h.Cursor.LastVisualX
vloc.VisualX = h.Cursor.LastWrappedVisualX
h.Cursor.Loc = h.LocFromVLoc(vloc)
}
}
@@ -209,8 +262,13 @@ func (h *BufPane) CursorUp() bool {
// CursorDown moves the cursor down
func (h *BufPane) CursorDown() bool {
selectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0
h.Cursor.Deselect(false)
h.MoveCursorDown(1)
if selectionEndNewline {
h.Cursor.Start()
} else {
h.MoveCursorDown(1)
}
h.Relocate()
return true
}
@@ -244,7 +302,6 @@ func (h *BufPane) CursorLeft() bool {
func (h *BufPane) CursorRight() bool {
if h.Cursor.HasSelection() {
h.Cursor.Deselect(false)
h.Cursor.Right()
} else {
tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
tabmovement := h.Buf.Settings["tabmovement"].(bool)
@@ -657,7 +714,7 @@ func (h *BufPane) InsertNewline() bool {
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
}
}
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
h.Cursor.StoreVisualX()
h.Relocate()
return true
}
@@ -687,7 +744,7 @@ func (h *BufPane) Backspace() bool {
h.Buf.Remove(loc.Move(-1, h.Buf), loc)
}
}
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
h.Cursor.StoreVisualX()
h.Relocate()
return true
}
@@ -854,6 +911,11 @@ func (h *BufPane) Autocomplete() bool {
return false
}
if b.HasSuggestions {
b.CycleAutocomplete(true)
return true
}
if h.Cursor.X == 0 {
return false
}
@@ -864,10 +926,6 @@ func (h *BufPane) Autocomplete() bool {
return false
}
if b.HasSuggestions {
b.CycleAutocomplete(true)
return true
}
return b.Autocomplete(buffer.BufferComplete)
}
@@ -889,7 +947,7 @@ func (h *BufPane) InsertTab() bool {
b := h.Buf
indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
tabBytes := len(indent)
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX(false) % tabBytes)
b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
h.Relocate()
return true
@@ -946,6 +1004,9 @@ func (h *BufPane) SaveAsCB(action string, callback func()) bool {
h.completeAction(action)
return
}
} else {
InfoBar.Error(err)
return
}
} else {
InfoBar.YNPrompt(
@@ -977,13 +1038,16 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
err := h.Buf.SaveAs(filename)
if err != nil {
if errors.Is(err, fs.ErrPermission) {
if runtime.GOOS == "windows" {
InfoBar.Error("Permission denied. Save with sudo not supported on Windows")
return true
}
saveWithSudo := func() {
err = h.Buf.SaveAsWithSudo(filename)
if err != nil {
InfoBar.Error(err)
} else {
h.Buf.Path = filename
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
if callback != nil {
callback()
@@ -1008,8 +1072,6 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
InfoBar.Error(err)
}
} else {
h.Buf.Path = filename
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
if callback != nil {
callback()
@@ -1082,8 +1144,7 @@ func (h *BufPane) find(useRegex bool) bool {
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if err != nil {
InfoBar.Error(err)
}
if found {
} else if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
@@ -1154,6 +1215,14 @@ func (h *BufPane) FindNext() bool {
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)
} else if found && searchLoc == match[0] && match[0] == match[1] {
// skip empty match at present cursor location
if searchLoc == h.Buf.End() {
searchLoc = h.Buf.Start()
} else {
searchLoc = searchLoc.Move(1, h.Buf)
}
match, found, _ = h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
}
if found {
h.Cursor.SetSelectionStart(match[0])
@@ -1183,6 +1252,14 @@ func (h *BufPane) FindPrevious() bool {
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)
} else if found && searchLoc == match[0] && match[0] == match[1] {
// skip empty match at present cursor location
if searchLoc == h.Buf.Start() {
searchLoc = h.Buf.End()
} else {
searchLoc = searchLoc.Move(-1, h.Buf)
}
match, found, _ = h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
}
if found {
h.Cursor.SetSelectionStart(match[0])
@@ -1238,101 +1315,189 @@ func (h *BufPane) Redo() bool {
return true
}
func (h *BufPane) selectLines() int {
if h.Cursor.HasSelection() {
start := h.Cursor.CurSelection[0]
end := h.Cursor.CurSelection[1]
if start.GreaterThan(end) {
start, end = end, start
}
if end.X == 0 {
end = end.Move(-1, h.Buf)
}
h.Cursor.Deselect(true)
h.Cursor.SetSelectionStart(buffer.Loc{0, start.Y})
h.Cursor.SetSelectionEnd(buffer.Loc{0, end.Y + 1})
} else {
h.Cursor.SelectLine()
}
nlines := h.Cursor.CurSelection[1].Y - h.Cursor.CurSelection[0].Y
if nlines == 0 && h.Cursor.HasSelection() {
// selected last line and it is not empty
nlines++
}
return nlines
}
// Copy the selection to the system clipboard
func (h *BufPane) Copy() bool {
if h.Cursor.HasSelection() {
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = true
InfoBar.Message("Copied selection")
}
h.Relocate()
return true
}
// CopyLine copies the current line to the clipboard
func (h *BufPane) CopyLine() bool {
if h.Cursor.HasSelection() {
return false
}
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
}
// CutLine cuts the current line to the clipboard
func (h *BufPane) CutLine() bool {
h.Cursor.SelectLine()
if !h.Cursor.HasSelection() {
return false
}
if h.freshClip {
if h.Cursor.HasSelection() {
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
InfoBar.Error(err)
} else {
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 {
h.Copy()
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = false
InfoBar.Message("Copied selection")
h.Relocate()
return true
}
// CopyLine copies the current line to the clipboard. If there is a selection,
// CopyLine copies all the lines that are (fully or partially) in the selection.
func (h *BufPane) CopyLine() bool {
origLoc := h.Cursor.Loc
origLastVisualX := h.Cursor.LastVisualX
origLastWrappedVisualX := h.Cursor.LastWrappedVisualX
origSelection := h.Cursor.CurSelection
nlines := h.selectLines()
if nlines == 0 {
return false
}
h.freshClip = true
h.lastCutTime = time.Now()
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
InfoBar.Message("Cut line")
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = false
if nlines > 1 {
InfoBar.Message(fmt.Sprintf("Copied %d lines", nlines))
} else {
InfoBar.Message("Copied line")
}
h.Cursor.Loc = origLoc
h.Cursor.LastVisualX = origLastVisualX
h.Cursor.LastWrappedVisualX = origLastWrappedVisualX
h.Cursor.CurSelection = origSelection
h.Relocate()
return true
}
// Cut the selection to the system clipboard
func (h *BufPane) Cut() bool {
if h.Cursor.HasSelection() {
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
h.freshClip = true
InfoBar.Message("Cut selection")
h.Relocate()
return true
if !h.Cursor.HasSelection() {
return false
}
return h.CutLine()
}
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
h.freshClip = false
InfoBar.Message("Cut selection")
// 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()
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
// h.Cursor.Right()
}
InfoBar.Message(infoMessage)
h.Relocate()
return true
}
// DeleteLine deletes the current line
func (h *BufPane) DeleteLine() bool {
h.Cursor.SelectLine()
// CutLine cuts the current line to the clipboard. If there is a selection,
// CutLine cuts all the lines that are (fully or partially) in the selection.
func (h *BufPane) CutLine() bool {
nlines := h.selectLines()
if nlines == 0 {
return false
}
totalLines := nlines
if h.freshClip {
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
InfoBar.Error(err)
return false
} else {
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
totalLines = strings.Count(clip, "\n") + nlines
}
} else {
h.Cursor.CopySelection(clipboard.ClipboardReg)
}
h.freshClip = true
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
h.Cursor.StoreVisualX()
if totalLines > 1 {
InfoBar.Message(fmt.Sprintf("Cut %d lines", totalLines))
} else {
InfoBar.Message("Cut line")
}
h.Relocate()
return true
}
// Duplicate the selection
func (h *BufPane) Duplicate() bool {
if !h.Cursor.HasSelection() {
return false
}
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
InfoBar.Message("Duplicated selection")
h.Relocate()
return true
}
// DuplicateLine duplicates the current line. If there is a selection, DuplicateLine
// duplicates all the lines that are (fully or partially) in the selection.
func (h *BufPane) DuplicateLine() bool {
if h.Cursor.HasSelection() {
origLoc := h.Cursor.Loc
origLastVisualX := h.Cursor.LastVisualX
origLastWrappedVisualX := h.Cursor.LastWrappedVisualX
origSelection := h.Cursor.CurSelection
start := h.Cursor.CurSelection[0]
end := h.Cursor.CurSelection[1]
if start.GreaterThan(end) {
start, end = end, start
}
if end.X == 0 {
end = end.Move(-1, h.Buf)
}
h.Cursor.Deselect(true)
h.Cursor.Loc = end
h.Cursor.End()
for y := start.Y; y <= end.Y; y++ {
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(y)))
}
h.Cursor.Loc = origLoc
h.Cursor.LastVisualX = origLastVisualX
h.Cursor.LastWrappedVisualX = origLastWrappedVisualX
h.Cursor.CurSelection = origSelection
if start.Y < end.Y {
InfoBar.Message(fmt.Sprintf("Duplicated %d lines", end.Y-start.Y+1))
} else {
InfoBar.Message("Duplicated line")
}
} else {
h.Cursor.End()
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
InfoBar.Message("Duplicated line")
}
h.Relocate()
return true
}
// DeleteLine deletes the current line. If there is a selection, DeleteLine
// deletes all the lines that are (fully or partially) in the selection.
func (h *BufPane) DeleteLine() bool {
nlines := h.selectLines()
if nlines == 0 {
return false
}
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
InfoBar.Message("Deleted line")
h.Cursor.StoreVisualX()
if nlines > 1 {
InfoBar.Message(fmt.Sprintf("Deleted %d lines", nlines))
} else {
InfoBar.Message("Deleted line")
}
h.Relocate()
return true
}
@@ -1536,63 +1701,84 @@ func (h *BufPane) End() bool {
// PageUp scrolls the view up a page
func (h *BufPane) PageUp() bool {
h.ScrollUp(h.BufView().Height)
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
h.ScrollUp(h.BufView().Height - pageOverlap)
return true
}
// PageDown scrolls the view down a page
func (h *BufPane) PageDown() bool {
h.ScrollDown(h.BufView().Height)
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
h.ScrollDown(h.BufView().Height - pageOverlap)
h.ScrollAdjust()
return true
}
// SelectPageUp selects up one page
func (h *BufPane) SelectPageUp() bool {
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
scrollAmount := h.BufView().Height - pageOverlap
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
h.MoveCursorUp(h.BufView().Height)
h.MoveCursorUp(scrollAmount)
h.Cursor.SelectTo(h.Cursor.Loc)
if h.Cursor.Num == 0 {
h.ScrollUp(scrollAmount)
}
h.Relocate()
return true
}
// SelectPageDown selects down one page
func (h *BufPane) SelectPageDown() bool {
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
scrollAmount := h.BufView().Height - pageOverlap
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
h.MoveCursorDown(h.BufView().Height)
h.MoveCursorDown(scrollAmount)
h.Cursor.SelectTo(h.Cursor.Loc)
if h.Cursor.Num == 0 && !h.ScrollReachedEnd() {
h.ScrollDown(scrollAmount)
h.ScrollAdjust()
}
h.Relocate()
return true
}
// CursorPageUp places the cursor a page up
// CursorPageUp places the cursor a page up,
// moving the view to keep cursor at the same relative position in the view
func (h *BufPane) CursorPageUp() bool {
h.Cursor.Deselect(true)
if h.Cursor.HasSelection() {
h.Cursor.Loc = h.Cursor.CurSelection[0]
h.Cursor.ResetSelection()
h.Cursor.StoreVisualX()
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
scrollAmount := h.BufView().Height - pageOverlap
h.MoveCursorUp(scrollAmount)
if h.Cursor.Num == 0 {
h.ScrollUp(scrollAmount)
}
h.MoveCursorUp(h.BufView().Height)
h.Relocate()
return true
}
// CursorPageDown places the cursor a page up
// CursorPageDown places the cursor a page down,
// moving the view to keep cursor at the same relative position in the view
func (h *BufPane) CursorPageDown() bool {
selectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0
h.Cursor.Deselect(false)
if h.Cursor.HasSelection() {
h.Cursor.Loc = h.Cursor.CurSelection[1]
h.Cursor.ResetSelection()
h.Cursor.StoreVisualX()
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
scrollAmount := h.BufView().Height - pageOverlap
if selectionEndNewline {
scrollAmount--
}
h.MoveCursorDown(scrollAmount)
if h.Cursor.Num == 0 && !h.ScrollReachedEnd() {
h.ScrollDown(scrollAmount)
h.ScrollAdjust()
}
if selectionEndNewline {
h.Cursor.Start()
}
h.MoveCursorDown(h.BufView().Height)
h.Relocate()
return true
}
@@ -1612,12 +1798,12 @@ func (h *BufPane) HalfPageDown() bool {
// ToggleDiffGutter turns the diff gutter off and on
func (h *BufPane) ToggleDiffGutter() bool {
if !h.Buf.Settings["diffgutter"].(bool) {
h.Buf.Settings["diffgutter"] = true
diffgutter := !h.Buf.Settings["diffgutter"].(bool)
h.Buf.SetOptionNative("diffgutter", diffgutter)
if diffgutter {
h.Buf.UpdateDiff()
InfoBar.Message("Enabled diff gutter")
} else {
h.Buf.Settings["diffgutter"] = false
InfoBar.Message("Disabled diff gutter")
}
return true
@@ -1625,11 +1811,11 @@ func (h *BufPane) ToggleDiffGutter() bool {
// ToggleRuler turns line numbers off and on
func (h *BufPane) ToggleRuler() bool {
if !h.Buf.Settings["ruler"].(bool) {
h.Buf.Settings["ruler"] = true
ruler := !h.Buf.Settings["ruler"].(bool)
h.Buf.SetOptionNative("ruler", ruler)
if ruler {
InfoBar.Message("Enabled ruler")
} else {
h.Buf.Settings["ruler"] = false
InfoBar.Message("Disabled ruler")
}
return true
@@ -1645,7 +1831,8 @@ func (h *BufPane) ToggleHelp() bool {
if h.Buf.Type == buffer.BTHelp {
h.Quit()
} else {
h.openHelp("help")
hsplit := config.GlobalSettings["helpsplit"] == "hsplit"
h.openHelp("help", hsplit, false)
}
return true
}
@@ -1681,7 +1868,7 @@ func (h *BufPane) CommandMode() bool {
// ToggleOverwriteMode lets the user toggle the text overwrite mode
func (h *BufPane) ToggleOverwriteMode() bool {
h.isOverwriteMode = !h.isOverwriteMode
h.Buf.OverwriteMode = !h.Buf.OverwriteMode
return true
}
@@ -1708,11 +1895,11 @@ func (h *BufPane) ClearInfo() bool {
return true
}
// ForceQuit closes the current tab or view even if there are unsaved changes
// ForceQuit closes the tab or view even if there are unsaved changes
// (no prompt)
func (h *BufPane) ForceQuit() bool {
h.Buf.Close()
if len(MainTab().Panes) > 1 {
if len(h.tab.Panes) > 1 {
h.Unsplit()
} else if len(Tabs.List) > 1 {
Tabs.RemoveTab(h.splitID)
@@ -1724,23 +1911,29 @@ func (h *BufPane) ForceQuit() bool {
return true
}
// closePrompt displays a prompt to save the buffer before closing it to proceed
// with a different action or command
func (h *BufPane) closePrompt(action string, callback func()) {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
callback()
} else if !canceled && yes {
h.SaveCB(action, callback)
}
})
}
// Quit this will close the current tab or view that is open
func (h *BufPane) Quit() bool {
if h.Buf.Modified() {
if config.GlobalSettings["autosave"].(float64) > 0 {
if h.Buf.Modified() && !h.Buf.Shared() {
if config.GlobalSettings["autosave"].(float64) > 0 && h.Buf.Path != "" {
// autosave on means we automatically save when quitting
h.SaveCB("Quit", func() {
h.ForceQuit()
})
} else {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
h.ForceQuit()
} else if !canceled && yes {
h.SaveCB("Quit", func() {
h.ForceQuit()
})
}
h.closePrompt("Quit", func() {
h.ForceQuit()
})
}
} else {
@@ -1793,27 +1986,38 @@ func (h *BufPane) AddTab() bool {
// PreviousTab switches to the previous tab in the tab list
func (h *BufPane) PreviousTab() bool {
tabsLen := len(Tabs.List)
if tabsLen == 1 {
if Tabs.Active() == 0 {
return false
}
a := Tabs.Active() + tabsLen
Tabs.SetActive((a - 1) % tabsLen)
Tabs.SetActive(Tabs.Active() - 1)
return true
}
// NextTab switches to the next tab in the tab list
func (h *BufPane) NextTab() bool {
tabsLen := len(Tabs.List)
if tabsLen == 1 {
if Tabs.Active() == len(Tabs.List)-1 {
return false
}
Tabs.SetActive(Tabs.Active() + 1)
return true
}
a := Tabs.Active()
Tabs.SetActive((a + 1) % tabsLen)
// FirstTab switches to the first tab in the tab list
func (h *BufPane) FirstTab() bool {
if Tabs.Active() == 0 {
return false
}
Tabs.SetActive(0)
return true
}
// LastTab switches to the last tab in the tab list
func (h *BufPane) LastTab() bool {
lastTabIndex := len(Tabs.List) - 1
if Tabs.Active() == lastTabIndex {
return false
}
Tabs.SetActive(lastTabIndex)
return true
}
@@ -1848,47 +2052,49 @@ func (h *BufPane) Unsplit() bool {
// NextSplit changes the view to the next split
func (h *BufPane) NextSplit() bool {
if len(h.tab.Panes) == 1 {
if h.tab.active == len(h.tab.Panes)-1 {
return false
}
a := h.tab.active
if a < len(h.tab.Panes)-1 {
a++
} else {
a = 0
}
h.tab.SetActive(a)
h.tab.SetActive(h.tab.active + 1)
return true
}
// PreviousSplit changes the view to the previous split
func (h *BufPane) PreviousSplit() bool {
if len(h.tab.Panes) == 1 {
if h.tab.active == 0 {
return false
}
a := h.tab.active
if a > 0 {
a--
} else {
a = len(h.tab.Panes) - 1
}
h.tab.SetActive(a)
h.tab.SetActive(h.tab.active - 1)
return true
}
var curmacro []interface{}
// FirstSplit changes the view to the first split
func (h *BufPane) FirstSplit() bool {
if h.tab.active == 0 {
return false
}
h.tab.SetActive(0)
return true
}
// LastSplit changes the view to the last split
func (h *BufPane) LastSplit() bool {
lastPaneIdx := len(h.tab.Panes) - 1
if h.tab.active == lastPaneIdx {
return false
}
h.tab.SetActive(lastPaneIdx)
return true
}
var curmacro []any
var recordingMacro bool
// ToggleMacro toggles recording of a macro
func (h *BufPane) ToggleMacro() bool {
recordingMacro = !recordingMacro
if recordingMacro {
curmacro = []interface{}{}
curmacro = []any{}
InfoBar.Message("Recording")
} else {
InfoBar.Message("Stopped recording")
@@ -1955,38 +2161,31 @@ func (h *BufPane) SpawnMultiCursor() bool {
return true
}
// SpawnCursorAtLoc spawns a new cursor at a location and merges the cursors
func (h *BufPane) SpawnCursorAtLoc(loc buffer.Loc) *buffer.Cursor {
c := buffer.NewCursor(h.Buf, loc)
h.Buf.AddCursor(c)
h.Buf.MergeCursors()
return c
}
// SpawnMultiCursorUpN is not an action
func (h *BufPane) SpawnMultiCursorUpN(n int) bool {
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
var c *buffer.Cursor
if !h.Buf.Settings["softwrap"].(bool) {
if n > 0 && lastC.Y == 0 {
return false
}
if n < 0 && lastC.Y+1 == h.Buf.LinesNum() {
return false
}
h.Buf.DeselectCursors()
c = buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
c.LastVisualX = lastC.LastVisualX
c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)
c.Relocate()
} else {
vloc := h.VLocFromLoc(lastC.Loc)
sloc := h.Scroll(vloc.SLoc, -n)
if sloc == vloc.SLoc {
return false
}
h.Buf.DeselectCursors()
vloc.SLoc = sloc
vloc.VisualX = lastC.LastVisualX
c = buffer.NewCursor(h.Buf, h.LocFromVLoc(vloc))
c.LastVisualX = lastC.LastVisualX
if n > 0 && lastC.Y == 0 {
return false
}
if n < 0 && lastC.Y+1 == h.Buf.LinesNum() {
return false
}
h.Buf.DeselectCursors()
c := buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
c.LastVisualX = lastC.LastVisualX
c.LastWrappedVisualX = lastC.LastWrappedVisualX
c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)
c.Relocate()
h.Buf.AddCursor(c)
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
@@ -2068,14 +2267,16 @@ func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
return true
}
// SkipMultiCursor moves the current multiple cursor to the next available position
func (h *BufPane) SkipMultiCursor() bool {
func (h *BufPane) skipMultiCursor(forward bool) bool {
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
if !lastC.HasSelection() {
return false
}
sel := lastC.GetSelection()
searchStart := lastC.CurSelection[1]
if !forward {
searchStart = lastC.CurSelection[0]
}
search := string(sel)
search = regexp.QuoteMeta(search)
@@ -2083,7 +2284,7 @@ func (h *BufPane) SkipMultiCursor() bool {
search = "\\b" + search + "\\b"
}
match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, forward, true)
if err != nil {
InfoBar.Error(err)
}
@@ -2103,6 +2304,16 @@ func (h *BufPane) SkipMultiCursor() bool {
return true
}
// SkipMultiCursor moves the current multiple cursor to the next available position
func (h *BufPane) SkipMultiCursor() bool {
return h.skipMultiCursor(true)
}
// SkipMultiCursorBack moves the current multiple cursor to the previous available position
func (h *BufPane) SkipMultiCursorBack() bool {
return h.skipMultiCursor(false)
}
// RemoveMultiCursor removes the latest multiple cursor
func (h *BufPane) RemoveMultiCursor() bool {
if h.Buf.NumCursors() > 1 {

View File

@@ -1,4 +1,4 @@
// +build plan9 nacl windows
//go:build plan9 || nacl || windows
package action

View File

@@ -1,11 +1,11 @@
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
package action
import (
"syscall"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/screen"
)
// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.

View File

@@ -4,17 +4,18 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io/fs"
"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/v2"
"github.com/micro-editor/json5"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
)
var Binder = map[string]func(e Event, action string){
@@ -23,21 +24,25 @@ var Binder = map[string]func(e Event, action string){
"terminal": TermMapEvent,
}
func writeFile(name string, txt []byte) error {
return util.SafeWrite(name, txt, false)
}
func createBindingsIfNotExist(fname string) {
if _, e := os.Stat(fname); os.IsNotExist(e) {
ioutil.WriteFile(fname, []byte("{}"), 0644)
if _, e := os.Stat(fname); errors.Is(e, fs.ErrNotExist) {
writeFile(fname, []byte("{}"))
}
}
// InitBindings intializes the bindings map by reading from bindings.json
func InitBindings() {
var parsed map[string]interface{}
var parsed map[string]any
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
input, err := os.ReadFile(filename)
if err != nil {
screen.TermMessage("Error reading bindings.json file: " + err.Error())
return
@@ -61,7 +66,7 @@ func InitBindings() {
switch val := v.(type) {
case string:
BindKey(k, val, Binder["buffer"])
case map[string]interface{}:
case map[string]any:
bind, ok := Binder[k]
if !ok || bind == nil {
screen.TermMessage(fmt.Sprintf("%s is not a valid pane type", k))
@@ -89,7 +94,7 @@ func BindKey(k, v string, bind func(e Event, a string)) {
}
if strings.HasPrefix(k, "\x1b") {
screen.Screen.RegisterRawSeq(k)
screen.RegisterRawSeq(k)
}
bind(event, v)
@@ -256,16 +261,25 @@ func eventsEqual(e1 Event, e2 Event) bool {
return e1 == e2
}
// TryBindKeyPlug tries to bind a key for the plugin without writing to bindings.json.
// This operation can be rejected by lockbindings to prevent unexpected actions by the user.
func TryBindKeyPlug(k, v string, overwrite bool) (bool, error) {
if l, ok := config.GlobalSettings["lockbindings"]; ok && l.(bool) {
return false, errors.New("bindings is locked by the user")
}
return TryBindKey(k, v, overwrite, false)
}
// 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) {
// Returns true if the keybinding already existed or is binded successfully and a possible error
func TryBindKey(k, v string, overwrite bool, writeToFile bool) (bool, error) {
var e error
var parsed map[string]interface{}
var parsed map[string]any
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
input, err := os.ReadFile(filename)
if err != nil {
return false, errors.New("Error reading bindings.json file: " + err.Error())
}
@@ -304,7 +318,13 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
BindKey(k, v, Binder["buffer"])
txt, _ := json.MarshalIndent(parsed, "", " ")
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
txt = append(txt, '\n')
if writeToFile {
return true, writeFile(filename, txt)
} else {
return true, nil
}
}
return false, e
}
@@ -312,12 +332,12 @@ 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]interface{}
var parsed map[string]any
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
input, err := os.ReadFile(filename)
if err != nil {
return errors.New("Error reading bindings.json file: " + err.Error())
}
@@ -342,7 +362,7 @@ func UnbindKey(k string) error {
}
if strings.HasPrefix(k, "\x1b") {
screen.Screen.UnregisterRawSeq(k)
screen.UnregisterRawSeq(k)
}
defaults := DefaultBindings("buffer")
@@ -354,7 +374,8 @@ func UnbindKey(k string) error {
}
txt, _ := json.MarshalIndent(parsed, "", " ")
return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
txt = append(txt, '\n')
return writeFile(filename, txt)
}
return e
}

View File

@@ -6,17 +6,17 @@ import (
luar "layeh.com/gopher-luar"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/buffer"
"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/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)
type BufAction interface{}
type BufAction any
// BufKeyAction represents an action bound to a key.
type BufKeyAction func(*BufPane) bool
@@ -100,9 +100,7 @@ func BufMapEvent(k Event, action string) {
break
}
// TODO: fix problem when complex bindings have these
// characters (escape them?)
idx := strings.IndexAny(action, "&|,")
idx := util.IndexAnyUnquoted(action, "&|,")
a := action
if idx >= 0 {
a = action[:idx]
@@ -226,26 +224,21 @@ type BufPane struct {
// (possibly multiple) buttons were pressed previously.
mousePressed map[MouseEvent]bool
// We need to keep track of insert key press toggle
isOverwriteMode bool
// This stores when the last click was
// This is useful for detecting double and triple clicks
lastClickTime time.Time
lastLoc buffer.Loc
// lastCutTime stores when the last ctrl+k was issued.
// It is used for clearing the clipboard to replace it with fresh cut lines.
lastCutTime time.Time
// freshClip returns true if the clipboard has never been pasted.
// freshClip returns true if one or more lines have been cut to the clipboard
// and have never been pasted yet.
freshClip bool
// Was the last mouse event actually a double click?
// Useful for detecting triple clicks -- if a double click is detected
// but the last mouse event was actually a double click, it's a triple click
doubleClick bool
DoubleClick bool
// Same here, just to keep track for mouse move events
tripleClick bool
TripleClick bool
// Should the current multiple cursor selection search based on word or
// based on selection (false for selection, true for word)
@@ -328,18 +321,16 @@ func (h *BufPane) ResizePane(size int) {
}
// PluginCB calls all plugin callbacks with a certain name and displays an
// error if there is one and returns the aggregate boolean response
func (h *BufPane) PluginCB(cb string) bool {
b, err := config.RunPluginFnBool(h.Buf.Settings, cb, luar.New(ulua.L, h))
if err != nil {
screen.TermMessage(err)
// error if there is one and returns the aggregate boolean response.
// The bufpane is passed as the first argument to the callbacks,
// optional args are passed as the next arguments.
func (h *BufPane) PluginCB(cb string, args ...any) bool {
largs := []lua.LValue{luar.New(ulua.L, h)}
for _, a := range args {
largs = append(largs, luar.New(ulua.L, a))
}
return b
}
// 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(h.Buf.Settings, cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
b, err := config.RunPluginFnBool(h.Buf.Settings, cb, largs...)
if err != nil {
screen.TermMessage(err)
}
@@ -363,9 +354,6 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
// Set mouseReleased to true because we assume the mouse is not being
// pressed when the editor is opened
h.resetMouse()
// Set isOverwriteMode to false, because we assume we are in the default
// mode when editor is opened
h.isOverwriteMode = false
h.lastClickTime = time.Time{}
}
@@ -569,7 +557,7 @@ func (h *BufPane) execAction(action BufAction, name string, te *tcell.EventMouse
h.Buf.HasSuggestions = false
}
if !h.PluginCB("pre" + name) {
if !h.PluginCB("pre"+name, te) {
return false
}
@@ -580,7 +568,7 @@ func (h *BufPane) execAction(action BufAction, name string, te *tcell.EventMouse
case BufMouseAction:
success = a(h, te)
}
success = success && h.PluginCB("on"+name)
success = success && h.PluginCB("on"+name, te)
if _, ok := MultiActions[name]; ok {
if recordingMacro {
@@ -636,7 +624,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
// Insert a character
h.Buf.SetCurCursor(c.Num)
h.Cursor = c
if !h.PluginCBRune("preRune", r) {
if !h.PluginCB("preRune", string(r)) {
continue
}
if c.HasSelection() {
@@ -644,7 +632,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
c.ResetSelection()
}
if h.isOverwriteMode {
if h.Buf.OverwriteMode {
next := c.Loc
next.X++
h.Buf.Replace(c.Loc, next, string(r))
@@ -655,27 +643,35 @@ func (h *BufPane) DoRuneInsert(r rune) {
curmacro = append(curmacro, r)
}
h.Relocate()
h.PluginCBRune("onRune", r)
h.PluginCB("onRune", string(r))
}
}
// 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)
MainTab().Panes = append(MainTab().Panes, e)
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
e.splitID = h.tab.GetNode(h.splitID).VSplit(right)
currentPaneIdx := h.tab.GetPane(h.splitID)
if right {
currentPaneIdx++
}
h.tab.AddPane(e, currentPaneIdx)
h.tab.Resize()
h.tab.SetActive(currentPaneIdx)
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)
MainTab().Panes = append(MainTab().Panes, e)
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
e.splitID = h.tab.GetNode(h.splitID).HSplit(bottom)
currentPaneIdx := h.tab.GetPane(h.splitID)
if bottom {
currentPaneIdx++
}
h.tab.AddPane(e, currentPaneIdx)
h.tab.Resize()
h.tab.SetActive(currentPaneIdx)
return e
}
@@ -733,6 +729,9 @@ var BufKeyActions = map[string]BufKeyAction{
"CursorRight": (*BufPane).CursorRight,
"CursorStart": (*BufPane).CursorStart,
"CursorEnd": (*BufPane).CursorEnd,
"CursorToViewTop": (*BufPane).CursorToViewTop,
"CursorToViewCenter": (*BufPane).CursorToViewCenter,
"CursorToViewBottom": (*BufPane).CursorToViewBottom,
"SelectToStart": (*BufPane).SelectToStart,
"SelectToEnd": (*BufPane).SelectToEnd,
"SelectUp": (*BufPane).SelectUp,
@@ -780,6 +779,7 @@ var BufKeyActions = map[string]BufKeyAction{
"CopyLine": (*BufPane).CopyLine,
"Cut": (*BufPane).Cut,
"CutLine": (*BufPane).CutLine,
"Duplicate": (*BufPane).Duplicate,
"DuplicateLine": (*BufPane).DuplicateLine,
"DeleteLine": (*BufPane).DeleteLine,
"MoveLinesUp": (*BufPane).MoveLinesUp,
@@ -824,8 +824,12 @@ var BufKeyActions = map[string]BufKeyAction{
"AddTab": (*BufPane).AddTab,
"PreviousTab": (*BufPane).PreviousTab,
"NextTab": (*BufPane).NextTab,
"FirstTab": (*BufPane).FirstTab,
"LastTab": (*BufPane).LastTab,
"NextSplit": (*BufPane).NextSplit,
"PreviousSplit": (*BufPane).PreviousSplit,
"FirstSplit": (*BufPane).FirstSplit,
"LastSplit": (*BufPane).LastSplit,
"Unsplit": (*BufPane).Unsplit,
"VSplit": (*BufPane).VSplitAction,
"HSplit": (*BufPane).HSplitAction,
@@ -841,6 +845,7 @@ var BufKeyActions = map[string]BufKeyAction{
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
"SkipMultiCursorBack": (*BufPane).SkipMultiCursorBack,
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
"JumpLine": (*BufPane).JumpLine,
"Deselect": (*BufPane).Deselect,
@@ -907,6 +912,7 @@ var MultiActions = map[string]bool{
"Copy": true,
"Cut": true,
"CutLine": true,
"Duplicate": true,
"DuplicateLine": true,
"DeleteLine": true,
"MoveLinesUp": true,

View File

@@ -13,12 +13,12 @@ import (
"strings"
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"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/clipboard"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/micro-editor/micro/v2/internal/util"
)
// A Command contains information about how to execute a command
@@ -32,39 +32,41 @@ var commands map[string]Command
func InitCommands() {
commands = map[string]Command{
"set": {(*BufPane).SetCmd, OptionValueComplete},
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
"show": {(*BufPane).ShowCmd, OptionComplete},
"showkey": {(*BufPane).ShowKeyCmd, nil},
"run": {(*BufPane).RunCmd, nil},
"bind": {(*BufPane).BindCmd, nil},
"unbind": {(*BufPane).UnbindCmd, nil},
"quit": {(*BufPane).QuitCmd, nil},
"goto": {(*BufPane).GotoCmd, nil},
"jump": {(*BufPane).JumpCmd, nil},
"save": {(*BufPane).SaveCmd, nil},
"replace": {(*BufPane).ReplaceCmd, nil},
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
"help": {(*BufPane).HelpCmd, HelpComplete},
"eval": {(*BufPane).EvalCmd, nil},
"log": {(*BufPane).ToggleLogCmd, nil},
"plugin": {(*BufPane).PluginCmd, PluginComplete},
"reload": {(*BufPane).ReloadCmd, nil},
"reopen": {(*BufPane).ReopenCmd, nil},
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
"pwd": {(*BufPane).PwdCmd, nil},
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
"tabmove": {(*BufPane).TabMoveCmd, nil},
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
"term": {(*BufPane).TermCmd, nil},
"memusage": {(*BufPane).MemUsageCmd, nil},
"retab": {(*BufPane).RetabCmd, nil},
"raw": {(*BufPane).RawCmd, nil},
"textfilter": {(*BufPane).TextFilterCmd, nil},
"set": {(*BufPane).SetCmd, OptionValueComplete},
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
"toggle": {(*BufPane).ToggleCmd, OptionValueComplete},
"togglelocal": {(*BufPane).ToggleLocalCmd, OptionValueComplete},
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
"show": {(*BufPane).ShowCmd, OptionComplete},
"showkey": {(*BufPane).ShowKeyCmd, nil},
"run": {(*BufPane).RunCmd, nil},
"bind": {(*BufPane).BindCmd, nil},
"unbind": {(*BufPane).UnbindCmd, nil},
"quit": {(*BufPane).QuitCmd, nil},
"goto": {(*BufPane).GotoCmd, nil},
"jump": {(*BufPane).JumpCmd, nil},
"save": {(*BufPane).SaveCmd, nil},
"replace": {(*BufPane).ReplaceCmd, nil},
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
"help": {(*BufPane).HelpCmd, HelpComplete},
"eval": {(*BufPane).EvalCmd, nil},
"log": {(*BufPane).ToggleLogCmd, nil},
"plugin": {(*BufPane).PluginCmd, PluginComplete},
"reload": {(*BufPane).ReloadCmd, nil},
"reopen": {(*BufPane).ReopenCmd, nil},
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
"pwd": {(*BufPane).PwdCmd, nil},
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
"tabmove": {(*BufPane).TabMoveCmd, nil},
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
"term": {(*BufPane).TermCmd, nil},
"memusage": {(*BufPane).MemUsageCmd, nil},
"retab": {(*BufPane).RetabCmd, nil},
"raw": {(*BufPane).RawCmd, nil},
"textfilter": {(*BufPane).TextFilterCmd, nil},
}
}
@@ -139,23 +141,25 @@ func (h *BufPane) TextFilterCmd(args []string) {
InfoBar.Error("usage: textfilter arguments")
return
}
sel := h.Cursor.GetSelection()
if len(sel) == 0 {
h.Cursor.SelectWord()
sel = h.Cursor.GetSelection()
for _, c := range h.Buf.GetCursors() {
sel := c.GetSelection()
if len(sel) == 0 {
c.SelectWord()
sel = c.GetSelection()
}
var bout, berr bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = strings.NewReader(string(sel))
cmd.Stderr = &berr
cmd.Stdout = &bout
err := cmd.Run()
if err != nil {
InfoBar.Error(err.Error() + " " + berr.String())
return
}
c.DeleteSelection()
h.Buf.Insert(c.Loc, bout.String())
}
var bout, berr bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = strings.NewReader(string(sel))
cmd.Stderr = &berr
cmd.Stdout = &bout
err := cmd.Run()
if err != nil {
InfoBar.Error(err.Error() + " " + berr.String())
return
}
h.Cursor.DeleteSelection()
h.Buf.Insert(h.Cursor.Loc, bout.String())
}
// TabMoveCmd moves the current tab to a given index (starts at 1). The
@@ -203,6 +207,7 @@ func (h *BufPane) TabMoveCmd(args []string) {
Tabs.List = append(Tabs.List, nil)
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
Tabs.List[idxTo] = activeTab
Tabs.Resize()
Tabs.UpdateNames()
Tabs.SetActive(idxTo)
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
@@ -285,35 +290,16 @@ func (h *BufPane) PwdCmd(args []string) {
// OpenCmd opens a new buffer with a given filename
func (h *BufPane) OpenCmd(args []string) {
if len(args) > 0 {
filename := args[0]
// the filename might or might not be quoted, so unquote first then join the strings.
args, err := shellquote.Split(filename)
if err != nil {
InfoBar.Error("Error parsing args ", err)
return
}
if len(args) == 0 {
return
}
filename = strings.Join(args, " ")
open := func() {
b, err := buffer.NewBufferFromFile(filename, buffer.BTDefault)
b, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
h.OpenBuffer(b)
}
if h.Buf.Modified() {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
open()
} else if !canceled && yes {
h.Save()
open()
}
})
if h.Buf.Modified() && !h.Buf.Shared() {
h.closePrompt("Save", open)
} else {
open()
}
@@ -428,7 +414,7 @@ func (h *BufPane) ReopenCmd(args []string) {
}
}
func (h *BufPane) openHelp(page string) error {
func (h *BufPane) openHelp(page string, hsplit bool, forceSplit bool) error {
if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {
return errors.New(fmt.Sprintf("Unable to load help text for %s: %v", page, err))
} else {
@@ -437,33 +423,74 @@ func (h *BufPane) openHelp(page string) error {
helpBuffer.SetOptionNative("hltaberrors", false)
helpBuffer.SetOptionNative("hltrailingws", false)
if h.Buf.Type == buffer.BTHelp {
if h.Buf.Type == buffer.BTHelp && !forceSplit {
h.OpenBuffer(helpBuffer)
} else {
} else if hsplit {
h.HSplitBuf(helpBuffer)
} else {
h.VSplitBuf(helpBuffer)
}
}
return nil
}
// HelpCmd tries to open the given help page in a horizontal split
// HelpCmd tries to open the given help page according to the split type
// configured with the "helpsplit" option. It can be overridden by the optional
// arguments "-vpslit" or "-hsplit". In case more than one help page is given
// as argument then it opens all of them with the defined split type.
func (h *BufPane) HelpCmd(args []string) {
hsplit := config.GlobalSettings["helpsplit"] == "hsplit"
if len(args) < 1 {
// Open the default help if the user just typed "> help"
h.openHelp("help")
h.openHelp("help", hsplit, false)
} else {
if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
err := h.openHelp(args[0])
if err != nil {
InfoBar.Error(err)
var topics []string
forceSplit := false
const errSplit = "hsplit and vsplit are not allowed at the same time"
for _, arg := range args {
switch arg {
case "-vsplit":
if forceSplit {
InfoBar.Error(errSplit)
return
}
hsplit = false
forceSplit = true
case "-hsplit":
if forceSplit {
InfoBar.Error(errSplit)
return
}
hsplit = true
forceSplit = true
default:
topics = append(topics, arg)
}
}
if len(topics) < 1 {
// Do the same as without arg
h.openHelp("help", hsplit, forceSplit)
return
}
if len(topics) > 1 {
forceSplit = true
}
for _, topic := range topics {
if config.FindRuntimeFile(config.RTHelp, topic) != nil {
err := h.openHelp(topic, hsplit, forceSplit)
if err != nil {
InfoBar.Error(err)
}
} else {
InfoBar.Error("Sorry, no help for ", topic)
}
} else {
InfoBar.Error("Sorry, no help for ", args[0])
}
}
}
// VSplitCmd opens a vertical split with file given in the first argument
// VSplitCmd opens one or more vertical splits with the files given as arguments
// If no file is given, it opens an empty buffer in a new split
func (h *BufPane) VSplitCmd(args []string) {
if len(args) == 0 {
@@ -472,16 +499,18 @@ func (h *BufPane) VSplitCmd(args []string) {
return
}
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
for _, a := range args {
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
h.VSplitBuf(buf)
h.VSplitBuf(buf)
}
}
// HSplitCmd opens a horizontal split with file given in the first argument
// HSplitCmd opens one or more horizontal splits with the files given as arguments
// If no file is given, it opens an empty buffer in a new split
func (h *BufPane) HSplitCmd(args []string) {
if len(args) == 0 {
@@ -490,13 +519,15 @@ func (h *BufPane) HSplitCmd(args []string) {
return
}
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
for _, a := range args {
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
}
h.HSplitBuf(buf)
h.HSplitBuf(buf)
}
}
// EvalCmd evaluates a lua expression
@@ -504,7 +535,8 @@ func (h *BufPane) EvalCmd(args []string) {
InfoBar.Error("Eval unsupported")
}
// NewTabCmd opens the given file in a new tab
// NewTabCmd opens one or more tabs with the files given as arguments
// If no file is given, it opens an empty buffer in a new tab
func (h *BufPane) NewTabCmd(args []string) {
width, height := screen.Screen.Size()
iOffset := config.GetInfoBarOffset()
@@ -527,7 +559,7 @@ func (h *BufPane) NewTabCmd(args []string) {
}
}
func doSetGlobalOptionNative(option string, nativeValue interface{}) error {
func doSetGlobalOptionNative(option string, nativeValue any) error {
if reflect.DeepEqual(config.GlobalSettings[option], nativeValue) {
return nil
}
@@ -586,7 +618,7 @@ func doSetGlobalOptionNative(option string, nativeValue interface{}) error {
return nil
}
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
func SetGlobalOptionNative(option string, nativeValue any, writeToFile bool) error {
if err := config.OptionIsValid(option, nativeValue); err != nil {
return err
}
@@ -609,20 +641,41 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
delete(b.LocalSettings, option)
}
return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
if !writeToFile {
return nil
}
err := config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
if err != nil {
if errors.Is(err, util.ErrOverwrite) {
screen.TermMessage(err)
err = errors.Unwrap(err)
}
return err
}
return nil
}
func SetGlobalOption(option, value string) error {
func SetGlobalOption(option, value string, writeToFile bool) error {
if _, ok := config.GlobalSettings[option]; !ok {
return config.ErrInvalidOption
}
nativeValue, err := config.GetNativeValue(option, config.GlobalSettings[option], value)
nativeValue, err := config.GetNativeValue(option, value)
if err != nil {
return err
}
return SetGlobalOptionNative(option, nativeValue)
return SetGlobalOptionNative(option, nativeValue, writeToFile)
}
func SetGlobalOptionNativePlug(option string, nativeValue any) error {
return SetGlobalOptionNative(option, nativeValue, false)
}
func SetGlobalOptionPlug(option, value string) error {
return SetGlobalOption(option, value, false)
}
// ResetCmd resets a setting to its default value
@@ -636,7 +689,7 @@ func (h *BufPane) ResetCmd(args []string) {
defaults := config.DefaultAllSettings()
if _, ok := defaults[option]; ok {
SetGlobalOptionNative(option, defaults[option])
SetGlobalOptionNative(option, defaults[option], true)
return
}
InfoBar.Error(config.ErrInvalidOption)
@@ -652,7 +705,7 @@ func (h *BufPane) SetCmd(args []string) {
option := args[0]
value := args[1]
err := SetGlobalOption(option, value)
err := SetGlobalOption(option, value, true)
if err == config.ErrInvalidOption {
err := h.Buf.SetOption(option, value)
if err != nil {
@@ -679,6 +732,65 @@ func (h *BufPane) SetLocalCmd(args []string) {
}
}
func (h *BufPane) toggleOption(option string, local bool) error {
var curVal, newVal any
if local {
curVal = h.Buf.Settings[option]
} else {
curVal = config.GetGlobalOption(option)
}
if curVal == nil {
return config.ErrInvalidOption
}
if choices, ok := config.OptionChoices[option]; ok && len(choices) == 2 {
if curVal == choices[0] {
newVal = choices[1]
} else {
newVal = choices[0]
}
} else if curValBool, ok := curVal.(bool); ok {
newVal = !curValBool
} else {
return config.ErrOptNotToggleable
}
if local {
if err := h.Buf.SetOptionNative(option, newVal); err != nil {
return err
}
} else {
if err := SetGlobalOptionNative(option, newVal, true); err != nil {
return err
}
}
return nil
}
// ToggleCmd toggles a toggleable option
func (h *BufPane) ToggleCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Not enough arguments: provide a toggleable option")
return
}
if err := h.toggleOption(args[0], false); err != nil {
InfoBar.Error(err)
}
}
// ToggleCmd toggles a toggleable option local to the buffer
func (h *BufPane) ToggleLocalCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Not enough arguments: provide a toggleable option")
return
}
if err := h.toggleOption(args[0], true); err != nil {
InfoBar.Error(err)
}
}
// ShowCmd shows the value of the given option
func (h *BufPane) ShowCmd(args []string) {
if len(args) < 1 {
@@ -686,7 +798,7 @@ func (h *BufPane) ShowCmd(args []string) {
return
}
var option interface{}
var option any
if opt, ok := h.Buf.Settings[args[0]]; ok {
option = opt
} else if opt, ok := config.GlobalSettings[args[0]]; ok {
@@ -732,9 +844,13 @@ func (h *BufPane) BindCmd(args []string) {
return
}
_, err := TryBindKey(parseKeyArg(args[0]), args[1], true)
_, err := TryBindKey(parseKeyArg(args[0]), args[1], true, true)
if err != nil {
InfoBar.Error(err)
if errors.Is(err, util.ErrOverwrite) {
screen.TermMessage(err)
} else {
InfoBar.Error(err)
}
}
}
@@ -747,7 +863,11 @@ func (h *BufPane) UnbindCmd(args []string) {
err := UnbindKey(parseKeyArg(args[0]))
if err != nil {
InfoBar.Error(err)
if errors.Is(err, util.ErrOverwrite) {
screen.TermMessage(err)
} else {
InfoBar.Error(err)
}
}
}
@@ -841,7 +961,7 @@ func (h *BufPane) SaveCmd(args []string) {
if len(args) == 0 {
h.Save()
} else {
h.Buf.SaveAs(args[0])
h.saveBufToFile(args[0], "SaveAs", nil)
}
}
@@ -902,10 +1022,12 @@ func (h *BufPane) ReplaceCmd(args []string) {
nreplaced := 0
start := h.Buf.Start()
end := h.Buf.End()
searchLoc := h.Cursor.Loc
selection := h.Cursor.HasSelection()
if selection {
start = h.Cursor.CurSelection[0]
end = h.Cursor.CurSelection[1]
searchLoc = start // otherwise me might start at the end
}
if all {
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace, !noRegex)
@@ -914,7 +1036,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
return l.GreaterEqual(start) && l.LessEqual(end)
}
searchLoc := h.Cursor.Loc
lastMatchEnd := buffer.Loc{-1, -1}
var doReplacement func()
doReplacement = func() {
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, true)
@@ -929,6 +1051,18 @@ func (h *BufPane) ReplaceCmd(args []string) {
return
}
if lastMatchEnd == locs[1] {
// skip empty match right after previous match
if searchLoc == end {
searchLoc = start
lastMatchEnd = buffer.Loc{-1, -1}
} else {
searchLoc = searchLoc.Move(1, h.Buf)
}
doReplacement()
return
}
h.Cursor.SetSelectionStart(locs[0])
h.Cursor.SetSelectionEnd(locs[1])
h.GotoLoc(locs[0])
@@ -954,6 +1088,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
h.Buf.RelocateCursors()
return
}
lastMatchEnd = searchLoc
doReplacement()
})
}
@@ -985,10 +1120,42 @@ func (h *BufPane) ReplaceAllCmd(args []string) {
h.ReplaceCmd(append(args, "-a"))
}
func (h *BufPane) openTerm(args []string, newtab bool) {
t := new(shell.Terminal)
err := t.Start(args, false, true, nil, nil)
if err != nil {
InfoBar.Error(err)
return
}
pane := 0
id := h.ID()
if newtab {
h.AddTab()
id = MainTab().Panes[pane].ID()
} else {
for i, p := range MainTab().Panes {
if p.IsActive() {
pane = i
id = p.ID()
p.Close()
break
}
}
}
v := h.GetView()
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
if err != nil {
InfoBar.Error(err)
return
}
MainTab().Panes[pane] = tp
MainTab().SetActive(pane)
}
// TermCmd opens a terminal in the current view
func (h *BufPane) TermCmd(args []string) {
ps := h.tab.Panes
if !TermEmuSupported {
InfoBar.Error("Terminal emulator not supported on this system")
return
@@ -1003,56 +1170,19 @@ func (h *BufPane) TermCmd(args []string) {
args = []string{sh}
}
term := func(i int, newtab bool) {
t := new(shell.Terminal)
err := t.Start(args, false, true, nil, nil)
if err != nil {
InfoBar.Error(err)
return
}
id := h.ID()
if newtab {
h.AddTab()
i = 0
id = MainTab().Panes[0].ID()
} else {
MainTab().Panes[i].Close()
}
v := h.GetView()
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
if err != nil {
InfoBar.Error(err)
return
}
MainTab().Panes[i] = tp
MainTab().SetActive(i)
}
// If there is only one open file we make a new tab instead of overwriting it
newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1
if newtab {
term(0, true)
h.openTerm(args, true)
return
}
for i, p := range ps {
if p.ID() == h.ID() {
if h.Buf.Modified() {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
term(i, false)
} else if !canceled && yes {
h.Save()
term(i, false)
}
})
} else {
term(i, false)
}
}
if h.Buf.Modified() && !h.Buf.Shared() {
h.closePrompt("Save", func() {
h.openTerm(args, false)
})
} else {
h.openTerm(args, false)
}
}

View File

@@ -3,7 +3,7 @@ package action
var termdefaults = map[string]string{
"<Ctrl-q><Ctrl-q>": "Exit",
"<Ctrl-e><Ctrl-e>": "CommandMode",
"<Ctrl-w><Ctrl-w>": "NextSplit",
"<Ctrl-w><Ctrl-w>": "NextSplit|FirstSplit",
}
// DefaultBindings returns a map containing micro's default keybindings

View File

@@ -45,23 +45,23 @@ var bufdefaults = map[string]string{
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-k": "CutLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-d": "Duplicate|DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
"Alt-,": "PreviousTab",
"Alt-.": "NextTab",
"Alt-,": "PreviousTab|LastTab",
"Alt-.": "NextTab|FirstTab",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlPageUp": "PreviousTab|LastTab",
"CtrlPageDown": "NextTab|FirstTab",
"ShiftPageUp": "SelectPageUp",
"ShiftPageDown": "SelectPageDown",
"Ctrl-g": "ToggleHelp",
@@ -72,7 +72,7 @@ var bufdefaults = map[string]string{
"Ctrl-b": "ShellMode",
"Ctrl-q": "Quit",
"Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit",
"Ctrl-w": "NextSplit|FirstSplit",
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
@@ -146,7 +146,7 @@ var infodefaults = map[string]string{
"Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-c": "Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-v": "Paste",

View File

@@ -48,23 +48,23 @@ var bufdefaults = map[string]string{
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-k": "CutLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-d": "Duplicate|DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
"Alt-,": "PreviousTab",
"Alt-.": "NextTab",
"Alt-,": "PreviousTab|LastTab",
"Alt-.": "NextTab|FirstTab",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlPageUp": "PreviousTab|LastTab",
"CtrlPageDown": "NextTab|FirstTab",
"ShiftPageUp": "SelectPageUp",
"ShiftPageDown": "SelectPageDown",
"Ctrl-g": "ToggleHelp",
@@ -75,7 +75,7 @@ var bufdefaults = map[string]string{
"Ctrl-b": "ShellMode",
"Ctrl-q": "Quit",
"Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit",
"Ctrl-w": "NextSplit|FirstSplit",
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
@@ -149,7 +149,7 @@ var infodefaults = map[string]string{
"Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-c": "Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-v": "Paste",

View File

@@ -6,7 +6,7 @@ import (
"fmt"
"strings"
"github.com/zyedidia/tcell/v2"
"github.com/micro-editor/tcell/v2"
)
type Event interface {

View File

@@ -1,6 +1,6 @@
package action
import "github.com/zyedidia/micro/v2/internal/buffer"
import "github.com/micro-editor/micro/v2/internal/buffer"
// InfoBar is the global info bar.
var InfoBar *InfoPane
@@ -11,7 +11,8 @@ var LogBufPane *BufPane
// InitGlobals initializes the log buffer and the info bar
func InitGlobals() {
InfoBar = NewInfoBar()
buffer.LogBuf = buffer.NewBufferFromString("", "Log", buffer.BTLog)
buffer.LogBuf = buffer.NewBufferFromString("", "", buffer.BTLog)
buffer.LogBuf.SetName("Log")
}
// GetInfoBar returns the infobar pane

View File

@@ -5,10 +5,10 @@ import (
"sort"
"strings"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/pkg/highlight"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/pkg/highlight"
)
// This file is meant (for now) for autocompletion in command mode, not
@@ -135,15 +135,6 @@ headerLoop:
return chosen, suggestions
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// OptionComplete autocompletes options
func OptionComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
@@ -202,7 +193,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
inputOpt = strings.TrimSpace(inputOpt)
var suggestions []string
// localSettings := config.DefaultLocalSettings()
var optionVal interface{}
var optionVal any
for k, option := range config.GlobalSettings {
if k == inputOpt {
optionVal = option

View File

@@ -3,12 +3,12 @@ package action
import (
"bytes"
"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/v2"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
"github.com/micro-editor/micro/v2/internal/info"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
)
type InfoKeyAction func(*InfoPane)

View File

@@ -3,7 +3,7 @@ package action
import (
"bytes"
"github.com/zyedidia/tcell/v2"
"github.com/micro-editor/tcell/v2"
)
type PaneKeyAction func(Pane) bool

View File

@@ -1,7 +1,7 @@
package action
import (
"github.com/zyedidia/micro/v2/internal/display"
"github.com/micro-editor/micro/v2/internal/display"
)
// A Pane is a general interface for a window in the editor.

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"reflect"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/tcell/v2"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/display"
"github.com/micro-editor/tcell/v2"
)
type RawPane struct {
@@ -22,7 +22,10 @@ func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane
func NewRawPane(tab *Tab) *RawPane {
b := buffer.NewBufferFromString("", "", buffer.BTRaw)
b.SetName("Raw event viewer")
w := display.NewBufWindow(0, 0, 0, 0, b)
return NewRawPaneFromWin(b, w, tab)
}

View File

@@ -3,13 +3,13 @@ package action
import (
luar "layeh.com/gopher-luar"
"github.com/zyedidia/micro/v2/internal/buffer"
"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/micro/v2/internal/views"
"github.com/zyedidia/tcell/v2"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/views"
"github.com/micro-editor/tcell/v2"
)
// The TabList is a list of tabs and a window to display the tab bar
@@ -211,7 +211,7 @@ func InitTabs(bufs []*buffer.Buffer) {
for _, b := range bufs[1:] {
if multiopen == "vsplit" {
MainTab().CurPane().VSplitBuf(b)
} else { // default hsplit
} else { // default hsplit
MainTab().CurPane().HSplitBuf(b)
}
}
@@ -349,6 +349,16 @@ func (t *Tab) SetActive(i int) {
}
}
// AddPane adds a pane at a given index
func (t *Tab) AddPane(pane Pane, i int) {
if len(t.Panes) == i {
t.Panes = append(t.Panes, pane)
return
}
t.Panes = append(t.Panes[:i+1], t.Panes[i:]...)
t.Panes[i] = pane
}
// GetPane returns the pane with the given split index
func (t *Tab) GetPane(splitid uint64) int {
for i, p := range t.Panes {

View File

@@ -1,10 +1,10 @@
// +build linux darwin dragonfly openbsd_amd64 freebsd
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
package action
import (
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/micro-editor/micro/v2/internal/shell"
)
// TermEmuSupported is a constant that marks if the terminal emulator is supported
@@ -14,7 +14,7 @@ const TermEmuSupported = true
// if wait is true it will wait for the user to exit by pressing enter once the executable has terminated
// if getOutput is true it will redirect the stdout of the process to a pipe which will be passed to the
// callback which is a function that takes a string and a list of optional user arguments
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []any), userargs []any) error {
args, err := shellquote.Split(input)
if err != nil {
return err

View File

@@ -1,4 +1,4 @@
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
//go:build plan9 || nacl || windows
package action
@@ -8,6 +8,6 @@ import "errors"
const TermEmuSupported = false
// RunTermEmulator returns an error for unsupported systems (non-unix systems
func RunTermEmulator(input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
func RunTermEmulator(input string, wait bool, getOutput bool, callback func(out string, userargs []any), userargs []any) error {
return errors.New("Unsupported operating system")
}

View File

@@ -4,13 +4,13 @@ import (
"errors"
"runtime"
"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/v2"
"github.com/zyedidia/terminal"
"github.com/micro-editor/micro/v2/internal/clipboard"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/display"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/micro-editor/tcell/v2"
"github.com/micro-editor/terminal"
)
type TermKeyAction func(*TermPane)

View File

@@ -2,12 +2,12 @@ package buffer
import (
"bytes"
"io/ioutil"
"io/fs"
"os"
"sort"
"strings"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/util"
)
// A Completer is a function that takes a buffer and returns info
@@ -109,15 +109,15 @@ func FileComplete(b *Buffer) ([]string, []string) {
sep := string(os.PathSeparator)
dirs := strings.Split(input, sep)
var files []os.FileInfo
var files []fs.DirEntry
var err error
if len(dirs) > 1 {
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
directories, _ = util.ReplaceHome(directories)
files, err = ioutil.ReadDir(directories)
files, err = os.ReadDir(directories)
} else {
files, err = ioutil.ReadDir(".")
files, err = os.ReadDir(".")
}
if err != nil {

View File

@@ -1,24 +1,26 @@
package buffer
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"sync/atomic"
"time"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
)
const backupMsg = `A backup was detected for this file. This likely means that micro
crashed while editing this file, or another instance of micro is currently
editing this file.
const BackupMsg = `A backup was detected for:
The backup was created on %s, and the file is
%s
This likely means that micro crashed while editing this file,
or another instance of micro is currently editing this file,
or an error occurred while saving this file so it may be corrupted.
The backup was created on %s and its path is:
%s
@@ -30,114 +32,148 @@ The backup was created on %s, and the file is
Options: [r]ecover, [i]gnore, [a]bort: `
var backupRequestChan chan *Buffer
const backupSeconds = 8
func backupThread() {
for {
time.Sleep(time.Second * 8)
type backupRequestType int
for len(backupRequestChan) > 0 {
b := <-backupRequestChan
bfini := atomic.LoadInt32(&(b.fini)) != 0
if !bfini {
b.Backup()
}
}
}
const (
backupCreate = iota
backupRemove
)
type backupRequest struct {
buf *SharedBuffer
reqType backupRequestType
}
var requestedBackups map[*SharedBuffer]bool
func init() {
backupRequestChan = make(chan *Buffer, 10)
go backupThread()
requestedBackups = make(map[*SharedBuffer]bool)
}
func (b *Buffer) RequestBackup() {
if !b.requestedBackup {
select {
case backupRequestChan <- b:
default:
// channel is full
func (b *SharedBuffer) RequestBackup() {
backupRequestChan <- backupRequest{buf: b, reqType: backupCreate}
}
func (b *SharedBuffer) CancelBackup() {
backupRequestChan <- backupRequest{buf: b, reqType: backupRemove}
}
func handleBackupRequest(br backupRequest) {
switch br.reqType {
case backupCreate:
// schedule periodic backup
requestedBackups[br.buf] = true
case backupRemove:
br.buf.RemoveBackup()
delete(requestedBackups, br.buf)
}
}
func periodicBackup() {
for buf := range requestedBackups {
err := buf.Backup()
if err == nil {
delete(requestedBackups, buf)
}
b.requestedBackup = true
}
}
// Backup saves the current buffer to ConfigDir/backups
func (b *Buffer) Backup() error {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
return nil
}
func (b *SharedBuffer) backupDir() string {
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
if backupdir == "" || err != nil {
backupdir = filepath.Join(config.ConfigDir, "backups")
}
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
os.Mkdir(backupdir, os.ModePerm)
return backupdir
}
func (b *SharedBuffer) keepBackup() bool {
return b.forceKeepBackup || b.Settings["permbackup"].(bool)
}
func (b *SharedBuffer) writeBackup(path string) (string, string, error) {
backupdir := b.backupDir()
if _, err := os.Stat(backupdir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return "", "", err
}
if err = os.Mkdir(backupdir, os.ModePerm); err != nil {
return "", "", err
}
}
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
name, resolveName := util.DetermineEscapePath(backupdir, path)
tmp := name + util.BackupSuffix
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
_, err := b.overwriteFile(tmp)
if err != nil {
os.Remove(tmp)
return name, resolveName, err
}
err = os.Rename(tmp, name)
if err != nil {
os.Remove(tmp)
return name, resolveName, err
}
if resolveName != "" {
err = util.SafeWrite(resolveName, []byte(path), true)
if err != nil {
return name, resolveName, err
}
}
// end of line
eol := []byte{'\n'}
return name, resolveName, nil
}
// write lines
if _, e = file.Write(b.lines[0].data); e != nil {
return
}
func (b *SharedBuffer) removeBackup(path string, resolveName string) {
os.Remove(path)
if resolveName != "" {
os.Remove(resolveName)
}
}
for _, l := range b.lines[1:] {
if _, e = file.Write(eol); e != nil {
return
}
if _, e = file.Write(l.data); e != nil {
return
}
}
return
}, false)
b.requestedBackup = false
// Backup saves the buffer to the backups directory
func (b *SharedBuffer) Backup() error {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
return nil
}
_, _, err := b.writeBackup(b.AbsPath)
return err
}
// RemoveBackup removes any backup file associated with this buffer
func (b *Buffer) RemoveBackup() {
if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
func (b *SharedBuffer) RemoveBackup() {
if b.keepBackup() || b.Path == "" || b.Type != BTDefault {
return
}
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
os.Remove(f)
f, resolveName := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
b.removeBackup(f, resolveName)
}
// 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, bool) {
func (b *SharedBuffer) 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))
backupfile, resolveName := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
if info, err := os.Stat(backupfile); err == nil {
backup, err := os.Open(backupfile)
if err == nil {
defer backup.Close()
t := info.ModTime()
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
msg := fmt.Sprintf(BackupMsg, b.Path, t.Format("Mon Jan _2 at 15:04, 2006"), backupfile)
choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)
if choice%3 == 0 {
// recover
b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
b.isModified = true
b.setModified()
return true, true
} else if choice%3 == 1 {
// delete
os.Remove(backupfile)
b.removeBackup(backupfile, resolveName)
} else if choice%3 == 2 {
return false, false
}

View File

@@ -7,31 +7,28 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"io/fs"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
luar "layeh.com/gopher-luar"
"github.com/micro-editor/micro/v2/internal/config"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/pkg/highlight"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"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/util"
"github.com/zyedidia/micro/v2/pkg/highlight"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
const backupTime = 8000
var (
// OpenBuffers is a list of the currently open buffers
OpenBuffers []*Buffer
@@ -85,10 +82,12 @@ type SharedBuffer struct {
toStdout bool
// Settings customized by the user
Settings map[string]interface{}
Settings map[string]any
// LocalSettings customized by the user for this buffer only
LocalSettings map[string]bool
encoding encoding.Encoding
Suggestions []string
Completions []string
CurSuggestion int
@@ -101,7 +100,7 @@ type SharedBuffer struct {
diffLock sync.RWMutex
diff map[int]DiffStatus
requestedBackup bool
forceKeepBackup bool
// ReloadDisabled allows the user to disable reloads if they
// are viewing a file that is constantly changing
@@ -125,20 +124,62 @@ type SharedBuffer struct {
}
func (b *SharedBuffer) insert(pos Loc, value []byte) {
b.isModified = true
b.HasSuggestions = false
b.LineArray.insert(pos, value)
b.setModified()
inslines := bytes.Count(value, []byte{'\n'})
b.MarkModified(pos.Y, pos.Y+inslines)
}
func (b *SharedBuffer) remove(start, end Loc) []byte {
b.isModified = true
b.HasSuggestions = false
defer b.setModified()
defer b.MarkModified(start.Y, end.Y)
return b.LineArray.remove(start, end)
}
func (b *SharedBuffer) setModified() {
if b.Type.Scratch {
return
}
if b.Settings["fastdirty"].(bool) {
b.isModified = true
} else {
var buff [md5.Size]byte
b.calcHash(&buff)
b.isModified = buff != b.origHash
}
if b.isModified {
b.RequestBackup()
} else {
b.CancelBackup()
}
}
// calcHash calculates md5 hash of all lines in the buffer
func (b *SharedBuffer) calcHash(out *[md5.Size]byte) {
h := md5.New()
if len(b.lines) > 0 {
h.Write(b.lines[0].data())
for _, l := range b.lines[1:] {
if b.Endings == FFDos {
h.Write([]byte{'\r', '\n'})
} else {
h.Write([]byte{'\n'})
}
h.Write(l.data())
}
}
h.Sum((*out)[:0])
}
// MarkModified marks the buffer as modified for this frame
// and performs rehighlighting if syntax highlighting is enabled
func (b *SharedBuffer) MarkModified(start, end int) {
@@ -174,6 +215,18 @@ const (
type DiffStatus byte
type Command struct {
StartCursor Loc
SearchRegex string
SearchAfterStart bool
}
var emptyCommand = Command{
StartCursor: Loc{-1, -1},
SearchRegex: "",
SearchAfterStart: false,
}
// Buffer stores the main information about a currently open file including
// the actual text (in a LineArray), the undo/redo stack (in an EventHandler)
// all the cursors, the syntax highlighting info, the settings for the buffer
@@ -186,7 +239,6 @@ type Buffer struct {
*EventHandler
*SharedBuffer
fini int32
cursors []*Cursor
curCursor int
StartCursor Loc
@@ -196,7 +248,7 @@ type Buffer struct {
// 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{})
OptionCallback func(option string, nativeValue any)
// The display module registers its own GetVisualX function for getting
// the correct visual x location of a cursor when softwrap is used.
@@ -209,21 +261,26 @@ type Buffer struct {
LastSearchRegex bool
// HighlightSearch enables highlighting all instances of the last successful search
HighlightSearch bool
// OverwriteMode indicates that we are in overwrite mode (toggled by
// Insert key by default) i.e. that typing a character shall replace the
// character under the cursor instead of inserting a character before it.
OverwriteMode bool
}
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
// If cursorLoc is {-1, -1} the location does not overwrite what the cursor location
// NewBufferFromFileWithCommand opens a new buffer with a given command
// If cmd.StartCursor is {-1, -1} the location does not overwrite what the cursor location
// would otherwise be (start of file, or saved cursor position if `savecursor` is
// enabled)
func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer, error) {
func NewBufferFromFileWithCommand(path string, btype BufType, cmd Command) (*Buffer, error) {
var err error
filename := path
if config.GetGlobalOption("parsecursor").(bool) && cursorLoc.X == -1 && cursorLoc.Y == -1 {
if config.GetGlobalOption("parsecursor").(bool) && cmd.StartCursor.X == -1 && cmd.StartCursor.Y == -1 {
var cursorPos []string
filename, cursorPos = util.GetPathAndCursorPosition(filename)
cursorLoc, err = ParseCursorLocation(cursorPos)
cmd.StartCursor, err = ParseCursorLocation(cursorPos)
if err != nil {
cursorLoc = Loc{-1, -1}
cmd.StartCursor = Loc{-1, -1}
}
}
@@ -232,17 +289,20 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
return nil, err
}
f, err := os.OpenFile(filename, os.O_WRONLY, 0)
readonly := os.IsPermission(err)
f.Close()
fileInfo, serr := os.Stat(filename)
if serr != nil && !os.IsNotExist(serr) {
if serr != nil && !errors.Is(serr, fs.ErrNotExist) {
return nil, serr
}
if serr == nil && fileInfo.IsDir() {
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
}
if serr == nil && !fileInfo.Mode().IsRegular() {
return nil, errors.New("Error: " + filename + " is not a regular file and cannot be opened")
}
f, err := os.OpenFile(filename, os.O_WRONLY, 0)
readonly := errors.Is(err, fs.ErrPermission)
f.Close()
file, err := os.Open(filename)
if err == nil {
@@ -250,13 +310,13 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
}
var buf *Buffer
if os.IsNotExist(err) {
if errors.Is(err, fs.ErrNotExist) {
// 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)
buf = NewBuffer(file, util.FSize(file), filename, btype, cmd)
if buf == nil {
return nil, errors.New("could not open file")
}
@@ -275,17 +335,18 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
// It will return an empty buffer if the path does not exist
// and an error if the file is a directory
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
return NewBufferFromFileAtLoc(path, btype, Loc{-1, -1})
return NewBufferFromFileWithCommand(path, btype, emptyCommand)
}
// NewBufferFromStringAtLoc creates a new buffer containing the given string with a cursor loc
func NewBufferFromStringAtLoc(text, path string, btype BufType, cursorLoc Loc) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, cursorLoc, btype)
// NewBufferFromStringWithCommand creates a new buffer containing the given string
// with a cursor loc and a search text
func NewBufferFromStringWithCommand(text, path string, btype BufType, cmd Command) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, btype, cmd)
}
// NewBufferFromString creates a new buffer containing the given string
func NewBufferFromString(text, path string, btype BufType) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, Loc{-1, -1}, btype)
return NewBuffer(strings.NewReader(text), int64(len(text)), path, btype, emptyCommand)
}
// NewBuffer creates a new buffer from a given reader with a given path
@@ -293,7 +354,7 @@ func NewBufferFromString(text, path string, btype BufType) *Buffer {
// a new 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 {
func NewBuffer(r io.Reader, size int64, path string, btype BufType, cmd Command) *Buffer {
absPath, err := filepath.Abs(path)
if err != nil {
absPath = path
@@ -320,30 +381,19 @@ 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()
b.LocalSettings = make(map[string]bool)
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(settings, absPath)
b.Settings["readonly"] = settings["readonly"]
b.Settings["filetype"] = settings["filetype"]
b.Settings["syntax"] = settings["syntax"]
config.UpdatePathGlobLocals(b.Settings, absPath)
enc, err := htmlindex.Get(settings["encoding"].(string))
b.encoding, err = htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
enc = unicode.UTF8
b.encoding = unicode.UTF8
b.Settings["encoding"] = "utf-8"
}
@@ -354,14 +404,14 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
return NewBufferFromString("", "", btype)
}
if !hasBackup {
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
reader := bufio.NewReader(transform.NewReader(r, b.encoding.NewDecoder()))
var ff FileFormat = FFAuto
if size == 0 {
// for empty files, use the fileformat setting instead of
// autodetection
switch settings["fileformat"] {
switch b.Settings["fileformat"] {
case "unix":
ff = FFUnix
case "dos":
@@ -392,15 +442,15 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
}
b.UpdateRules()
// init local settings again now that we know the filetype
config.InitLocalSettings(b.Settings, b.Path)
// we know the filetype now, so update per-filetype settings
config.UpdateFileTypeLocals(b.Settings, b.Settings["filetype"].(string))
if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); os.IsNotExist(err) {
if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); errors.Is(err, fs.ErrNotExist) {
os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm)
}
if startcursor.X != -1 && startcursor.Y != -1 {
b.StartCursor = startcursor
if cmd.StartCursor.X != -1 && cmd.StartCursor.Y != -1 {
b.StartCursor = cmd.StartCursor
} else if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
err := b.Unserialize()
if err != nil {
@@ -411,6 +461,23 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
b.AddCursor(NewCursor(b, b.StartCursor))
b.GetActiveCursor().Relocate()
if cmd.SearchRegex != "" {
match, found, _ := b.FindNext(cmd.SearchRegex, b.Start(), b.End(), b.StartCursor, true, true)
if found {
if cmd.SearchAfterStart {
// Search from current cursor and move it accordingly
b.GetActiveCursor().SetSelectionStart(match[0])
b.GetActiveCursor().SetSelectionEnd(match[1])
b.GetActiveCursor().OrigSelection[0] = b.GetActiveCursor().CurSelection[0]
b.GetActiveCursor().OrigSelection[1] = b.GetActiveCursor().CurSelection[1]
b.GetActiveCursor().GotoLoc(match[1])
}
b.LastSearch = cmd.SearchRegex
b.LastSearchRegex = true
b.HighlightSearch = b.Settings["hlsearch"].(bool)
}
}
if !b.Settings["fastdirty"].(bool) && !found {
if size > LargeFileThreshold {
// If the file is larger than LargeFileThreshold fastdirty needs to be on
@@ -418,7 +485,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
} 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)
b.calcHash(&b.origHash)
}
}
@@ -460,13 +527,11 @@ func (b *Buffer) Fini() {
if !b.Modified() {
b.Serialize()
}
b.RemoveBackup()
b.CancelBackup()
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
@@ -480,7 +545,7 @@ func (b *Buffer) GetName() string {
name = b.Path
}
if b.Settings["basename"].(bool) {
return path.Base(name)
return filepath.Base(name)
}
return name
}
@@ -496,8 +561,6 @@ func (b *Buffer) Insert(start Loc, text string) {
b.EventHandler.cursors = b.cursors
b.EventHandler.active = b.curCursor
b.EventHandler.Insert(start, text)
b.RequestBackup()
}
}
@@ -507,8 +570,6 @@ func (b *Buffer) Remove(start, end Loc) {
b.EventHandler.cursors = b.cursors
b.EventHandler.active = b.curCursor
b.EventHandler.Remove(start, end)
b.RequestBackup()
}
}
@@ -539,6 +600,7 @@ func (b *Buffer) ReOpen() error {
if err != nil {
return err
}
defer file.Close()
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
@@ -546,7 +608,7 @@ func (b *Buffer) ReOpen() error {
}
reader := bufio.NewReader(transform.NewReader(file, enc.NewDecoder()))
data, err := ioutil.ReadAll(reader)
data, err := io.ReadAll(reader)
txt := string(data)
if err != nil {
@@ -559,7 +621,7 @@ func (b *Buffer) ReOpen() error {
if len(data) > LargeFileThreshold {
b.Settings["fastdirty"] = true
} else {
calcHash(b, &b.origHash)
b.calcHash(&b.origHash)
}
}
b.isModified = false
@@ -621,21 +683,20 @@ func (b *Buffer) WordAt(loc Loc) []byte {
return b.Substr(start, end)
}
// Shared returns if there are other buffers with the same file as this buffer
func (b *Buffer) Shared() bool {
for _, buf := range OpenBuffers {
if buf != b && buf.SharedBuffer == b.SharedBuffer {
return true
}
}
return false
}
// Modified returns if this buffer has been modified since
// being opened
func (b *Buffer) Modified() bool {
if b.Type.Scratch {
return false
}
if b.Settings["fastdirty"].(bool) {
return b.isModified
}
var buff [md5.Size]byte
calcHash(b, &buff)
return buff != b.origHash
return b.isModified
}
// Size returns the number of bytes in the current buffer
@@ -654,26 +715,6 @@ func (b *Buffer) Size() int {
return nb
}
// calcHash calculates md5 hash of all lines in the buffer
func calcHash(b *Buffer, out *[md5.Size]byte) {
h := md5.New()
if len(b.lines) > 0 {
h.Write(b.lines[0].data)
for _, l := range b.lines[1:] {
if b.Endings == FFDos {
h.Write([]byte{'\r', '\n'})
} else {
h.Write([]byte{'\n'})
}
h.Write(l.data)
}
}
h.Sum((*out)[:0])
}
func parseDefFromFile(f config.RuntimeFile, header *highlight.Header) *highlight.Def {
data, err := f.Data()
if err != nil {
@@ -825,7 +866,7 @@ func (b *Buffer) UpdateRules() {
if header.MatchFileName(b.Path) {
matchedFileName = true
}
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data()) {
matchedFileHeader = true
}
} else if header.FileType == ft {
@@ -879,7 +920,7 @@ func (b *Buffer) UpdateRules() {
if header.MatchFileName(b.Path) {
fnameMatches = append(fnameMatches, syntaxFileInfo{header, f.Name(), nil})
}
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data()) {
headerMatches = append(headerMatches, syntaxFileInfo{header, f.Name(), nil})
}
} else if header.FileType == ft {
@@ -912,7 +953,7 @@ func (b *Buffer) UpdateRules() {
for _, m := range matches {
if m.header.HasFileSignature() {
for i := 0; i < limit; i++ {
if m.header.MatchFileSignature(b.lines[i].data) {
if m.header.MatchFileSignature(b.lines[i].data()) {
syntaxFile = m.fileName
if m.syntaxDef != nil {
b.SyntaxDef = m.syntaxDef
@@ -1089,11 +1130,11 @@ func (b *Buffer) MoveLinesUp(start int, end int) {
if start < 1 || start >= end || end > len(b.lines) {
return
}
l := string(b.LineBytes(start - 1))
l := b.LineString(start - 1)
if end == len(b.lines) {
b.insert(
Loc{
util.CharacterCount(b.lines[end-1].data),
len(b.lines[end-1].runes),
end - 1,
},
[]byte{'\n'},
@@ -1114,7 +1155,7 @@ func (b *Buffer) MoveLinesDown(start int, end int) {
if start < 0 || start >= end || end >= len(b.lines) {
return
}
l := string(b.LineBytes(end))
l := b.LineString(end)
b.Insert(
Loc{0, start},
l+"\n",
@@ -1155,7 +1196,7 @@ func (b *Buffer) findMatchingBrace(braceType [2]rune, start Loc, char rune) (Loc
}
} else if char == braceType[1] {
for y := start.Y; y >= 0; y-- {
l := []rune(string(b.lines[y].data))
l := []rune(string(b.LineBytes(y)))
xInit := len(l) - 1
if y == start.Y {
xInit = start.X
@@ -1224,7 +1265,6 @@ func (b *Buffer) FindMatchingBrace(start Loc) (Loc, bool, bool) {
func (b *Buffer) Retab() {
toSpaces := b.Settings["tabstospaces"].(bool)
tabsize := util.IntOpt(b.Settings["tabsize"])
dirty := false
for i := 0; i < b.LinesNum(); i++ {
l := b.LineBytes(i)
@@ -1241,14 +1281,20 @@ func (b *Buffer) Retab() {
l = bytes.TrimLeft(l, " \t")
b.Lock()
b.lines[i].data = append(ws, l...)
ws = append(ws, l...)
var runes []Character
for len(ws) > 0 {
combc, s := util.DecodeCombinedCharacter(ws)
runes = append(runes, Character{combc})
ws = ws[s:]
}
b.lines[i].runes = runes
b.Unlock()
b.MarkModified(i, i)
dirty = true
}
b.isModified = dirty
b.setModified()
}
// ParseCursorLocation turns a cursor location like 10:5 (LINE:COL)
@@ -1278,7 +1324,7 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
// Line returns the string representation of the given line number
func (b *Buffer) Line(i int) string {
return string(b.LineBytes(i))
return b.LineString(i)
}
func (b *Buffer) Write(bytes []byte) (n int, err error) {

View File

@@ -5,11 +5,11 @@ import (
"strings"
"testing"
"github.com/micro-editor/micro/v2/internal/config"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/stretchr/testify/assert"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/util"
)
type operation struct {

View File

@@ -1,8 +1,8 @@
package buffer
import (
"github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/clipboard"
"github.com/micro-editor/micro/v2/internal/util"
)
// InBounds returns whether the given location is a valid character position in the given buffer
@@ -20,8 +20,14 @@ type Cursor struct {
buf *Buffer
Loc
// Last cursor x position
// Last visual x position of the cursor. Used in cursor up/down movements
// for remembering the original x position when moving to a line that is
// shorter than current x position.
LastVisualX int
// Similar to LastVisualX but takes softwrapping into account, i.e. last
// visual x position in a visual (wrapped) line on the screen, which may be
// different from the line in the buffer.
LastWrappedVisualX int
// The current selection as a range of character numbers (inclusive)
CurSelection [2]Loc
@@ -61,8 +67,9 @@ func (c *Cursor) Buf() *Buffer {
// Goto puts the cursor at the given cursor's location and gives
// the current cursor its selection too
func (c *Cursor) Goto(b Cursor) {
c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
c.X, c.Y = b.X, b.Y
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
c.StoreVisualX()
}
// GotoLoc puts the cursor at the given cursor's location and gives
@@ -73,8 +80,8 @@ 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 {
func (c *Cursor) GetVisualX(wrap bool) int {
if wrap && c.buf.GetVisualX != nil {
return c.buf.GetVisualX(c.Loc)
}
@@ -100,7 +107,7 @@ func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int {
// Start moves the cursor to the start of the line it is on
func (c *Cursor) Start() {
c.X = 0
c.LastVisualX = c.GetVisualX()
c.StoreVisualX()
}
// StartOfText moves the cursor to the first non-whitespace rune of
@@ -131,7 +138,7 @@ func (c *Cursor) IsStartOfText() bool {
// End moves the cursor to the end of the line it is on
func (c *Cursor) End() {
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
c.LastVisualX = c.GetVisualX()
c.StoreVisualX()
}
// CopySelection copies the user's selection to either "primary"
@@ -186,7 +193,7 @@ func (c *Cursor) Deselect(start bool) {
if start {
c.Loc = c.CurSelection[0]
} else {
c.Loc = c.CurSelection[1].Move(-1, c.buf)
c.Loc = c.CurSelection[1]
}
c.ResetSelection()
c.StoreVisualX()
@@ -594,26 +601,16 @@ func (c *Cursor) SubWordLeft() {
// RuneUnder returns the rune under the given x position
func (c *Cursor) RuneUnder(x int) rune {
line := c.buf.LineBytes(c.Y)
if len(line) == 0 || x >= util.CharacterCount(line) {
line := c.buf.LineCharacters(c.Y)
if len(line) == 0 || x >= len(line) {
return '\n'
} else if x < 0 {
x = 0
}
i := 0
for len(line) > 0 {
r, _, size := util.DecodeCharacter(line)
line = line[size:]
if i == x {
return r
}
i++
}
return '\n'
return line[x].combc[0]
}
func (c *Cursor) StoreVisualX() {
c.LastVisualX = c.GetVisualX()
c.LastVisualX = c.GetVisualX(false)
c.LastWrappedVisualX = c.GetVisualX(true)
}

View File

@@ -4,11 +4,11 @@ import (
"bytes"
"time"
"github.com/micro-editor/micro/v2/internal/config"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"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/util"
luar "layeh.com/gopher-luar"
)
@@ -104,7 +104,7 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1])
c.Relocate()
c.LastVisualX = c.GetVisualX()
c.StoreVisualX()
}
if useUndo {

View File

@@ -6,32 +6,10 @@ import (
"io"
"sync"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/pkg/highlight"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/pkg/highlight"
)
// Finds the byte index of the nth rune in a byte slice
func runeToByteIndex(n int, txt []byte) int {
if n == 0 {
return 0
}
count := 0
i := 0
for len(txt) > 0 {
_, _, size := util.DecodeCharacter(txt)
txt = txt[size:]
count += size
i++
if i == n {
break
}
}
return count
}
// A searchState contains the search match info for a single line
type searchState struct {
search string
@@ -41,10 +19,14 @@ type searchState struct {
done bool
}
// A Line contains the data in bytes as well as a highlight state, match
type Character struct {
combc []rune
}
// A Line contains the slice of runes as well as a highlight state, match
// and a flag for whether the highlighting needs to be updated
type Line struct {
data []byte
runes []Character
state highlight.State
match highlight.LineMatch
@@ -59,6 +41,24 @@ type Line struct {
search map[*Buffer]*searchState
}
// data returns the line as byte slice
func (l Line) data() []byte {
var runes []rune
for _, r := range l.runes {
runes = append(runes, r.combc[0:]...)
}
return []byte(string(runes))
}
// String returns the line as string
func (l Line) String() string {
var runes []rune
for _, r := range l.runes {
runes = append(runes, r.combc[0:]...)
}
return string(runes)
}
const (
// Line ending file formats
FFAuto = 0 // Autodetect format
@@ -94,7 +94,7 @@ func Append(slice []Line, data ...Line) []Line {
return slice
}
// NewLineArray returns a new line array from an array of bytes
// NewLineArray returns a new line array from an array of runes
func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray {
la := new(LineArray)
@@ -144,10 +144,16 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
loaded += dlen
}
var runes []Character
if err != nil {
if err == io.EOF {
for len(data) > 0 {
combc, s := util.DecodeCombinedCharacter(data)
runes = append(runes, Character{combc})
data = data[s:]
}
la.lines = Append(la.lines, Line{
data: data,
runes: runes,
state: nil,
match: nil,
})
@@ -155,8 +161,14 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
// Last line was read
break
} else {
data = data[:dlen-1]
for len(data) > 0 {
combc, s := util.DecodeCombinedCharacter(data)
runes = append(runes, Character{combc})
data = data[s:]
}
la.lines = Append(la.lines, Line{
data: data[:dlen-1],
runes: runes,
state: nil,
match: nil,
})
@@ -174,7 +186,7 @@ func (la *LineArray) Bytes() []byte {
// initsize should provide a good estimate
b.Grow(int(la.initsize + 4096))
for i, l := range la.lines {
b.Write(l.data)
b.Write(l.data())
if i != len(la.lines)-1 {
if la.Endings == FFDos {
b.WriteByte('\r')
@@ -188,13 +200,13 @@ func (la *LineArray) Bytes() []byte {
// newlineBelow adds a newline below the given line number
func (la *LineArray) newlineBelow(y int) {
la.lines = append(la.lines, Line{
data: []byte{' '},
runes: []Character{},
state: nil,
match: nil,
})
copy(la.lines[y+2:], la.lines[y+1:])
la.lines[y+1] = Line{
data: []byte{},
runes: []Character{},
state: la.lines[y].state,
match: nil,
}
@@ -205,41 +217,65 @@ func (la *LineArray) insert(pos Loc, value []byte) {
la.lock.Lock()
defer la.lock.Unlock()
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
for i := 0; i < len(value); i++ {
if value[i] == '\n' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') {
la.split(Loc{x, y})
x = 0
y++
var runes []Character
for len(value) > 0 {
combc, s := util.DecodeCombinedCharacter(value)
runes = append(runes, Character{combc})
value = value[s:]
}
x, y := util.Min(pos.X, len(la.lines[pos.Y].runes)), pos.Y
start := -1
if value[i] == '\r' {
i++
outer:
for i, r := range runes {
for j := 0; j < len(r.combc); j++ {
if r.combc[j] == '\n' || (r.combc[j] == '\r' && i < len(runes)-1 && r.combc[j+1] == '\n') {
la.split(Loc{x, y})
if i > 0 && start < len(runes) && start < i {
if start < 0 {
start = 0
}
la.insertRunes(Loc{x, y}, runes[start:i])
}
x = 0
y++
if r.combc[j] == '\r' {
i++
}
if i+1 <= len(runes) {
start = i + 1
}
continue outer
}
continue
}
la.insertByte(Loc{x, y}, value[i])
x++
}
if start < 0 {
la.insertRunes(Loc{x, y}, runes)
} else if start < len(runes) {
la.insertRunes(Loc{x, y}, runes[start:])
}
}
// InsertByte inserts a byte at a given location
func (la *LineArray) insertByte(pos Loc, value byte) {
la.lines[pos.Y].data = append(la.lines[pos.Y].data, 0)
copy(la.lines[pos.Y].data[pos.X+1:], la.lines[pos.Y].data[pos.X:])
la.lines[pos.Y].data[pos.X] = value
// Inserts a rune array at a given location
func (la *LineArray) insertRunes(pos Loc, runes []Character) {
la.lines[pos.Y].runes = append(la.lines[pos.Y].runes, runes...)
copy(la.lines[pos.Y].runes[pos.X+len(runes):], la.lines[pos.Y].runes[pos.X:])
copy(la.lines[pos.Y].runes[pos.X:], runes)
}
// joinLines joins the two lines a and b
func (la *LineArray) joinLines(a, b int) {
la.lines[a].data = append(la.lines[a].data, la.lines[b].data...)
la.insertRunes(Loc{len(la.lines[a].runes), a}, la.lines[b].runes)
la.deleteLine(b)
}
// split splits a line at a given position
func (la *LineArray) split(pos Loc) {
la.newlineBelow(pos.Y)
la.lines[pos.Y+1].data = append(la.lines[pos.Y+1].data, la.lines[pos.Y].data[pos.X:]...)
la.insertRunes(Loc{0, pos.Y + 1}, la.lines[pos.Y].runes[pos.X:])
la.lines[pos.Y+1].state = la.lines[pos.Y].state
la.lines[pos.Y].state = nil
la.lines[pos.Y].match = nil
@@ -253,10 +289,10 @@ func (la *LineArray) remove(start, end Loc) []byte {
defer la.lock.Unlock()
sub := la.Substr(start, end)
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
startX := util.Min(start.X, len(la.lines[start.Y].runes))
endX := util.Min(end.X, len(la.lines[end.Y].runes))
if start.Y == end.Y {
la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
la.lines[start.Y].runes = append(la.lines[start.Y].runes[:startX], la.lines[start.Y].runes[endX:]...)
} else {
la.deleteLines(start.Y+1, end.Y-1)
la.deleteToEnd(Loc{startX, start.Y})
@@ -268,12 +304,12 @@ func (la *LineArray) remove(start, end Loc) []byte {
// deleteToEnd deletes from the end of a line to the position
func (la *LineArray) deleteToEnd(pos Loc) {
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X]
la.lines[pos.Y].runes = la.lines[pos.Y].runes[:pos.X]
}
// deleteFromStart deletes from the start of a line to the position
func (la *LineArray) deleteFromStart(pos Loc) {
la.lines[pos.Y].data = la.lines[pos.Y].data[pos.X+1:]
la.lines[pos.Y].runes = la.lines[pos.Y].runes[pos.X+1:]
}
// deleteLine deletes the line number
@@ -285,29 +321,37 @@ func (la *LineArray) deleteLines(y1, y2 int) {
la.lines = la.lines[:y1+copy(la.lines[y1:], la.lines[y2+1:])]
}
// DeleteByte deletes the byte at a position
func (la *LineArray) deleteByte(pos Loc) {
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
}
// Substr returns the string representation between two locations
func (la *LineArray) Substr(start, end Loc) []byte {
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
if start.Y == end.Y {
src := la.lines[start.Y].data[startX:endX]
dest := make([]byte, len(src))
copy(dest, src)
return dest
startX := util.Min(start.X, len(la.lines[start.Y].runes))
endX := util.Min(end.X, len(la.lines[end.Y].runes))
var runes []rune
if start.Y == end.Y && startX <= endX {
for _, r := range la.lines[start.Y].runes[startX:endX] {
runes = append(runes, r.combc[0:]...)
}
return []byte(string(runes))
}
str := make([]byte, 0, len(la.lines[start.Y+1].data)*(end.Y-start.Y))
str = append(str, la.lines[start.Y].data[startX:]...)
var str []byte
for _, r := range la.lines[start.Y].runes[startX:] {
runes = append(runes, r.combc[0:]...)
}
str = append(str, []byte(string(runes))...)
str = append(str, '\n')
for i := start.Y + 1; i <= end.Y-1; i++ {
str = append(str, la.lines[i].data...)
runes = runes[:0]
for _, r := range la.lines[i].runes {
runes = append(runes, r.combc[0:]...)
}
str = append(str, []byte(string(runes))...)
str = append(str, '\n')
}
str = append(str, la.lines[end.Y].data[:endX]...)
runes = runes[:0]
for _, r := range la.lines[end.Y].runes[:endX] {
runes = append(runes, r.combc[0:]...)
}
str = append(str, []byte(string(runes))...)
return str
}
@@ -324,15 +368,38 @@ func (la *LineArray) Start() Loc {
// End returns the location of the last character in the buffer
func (la *LineArray) End() Loc {
numlines := len(la.lines)
return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
return Loc{len(la.lines[numlines-1].runes), numlines - 1}
}
// LineCharacters returns line n as an array of characters
func (la *LineArray) LineCharacters(n int) []Character {
if n >= len(la.lines) || n < 0 {
return []Character{}
}
return la.lines[n].runes
}
// LineBytes returns line n as an array of bytes
func (la *LineArray) LineBytes(lineN int) []byte {
if lineN >= len(la.lines) || lineN < 0 {
func (la *LineArray) LineBytes(n int) []byte {
if n >= len(la.lines) || n < 0 {
return []byte{}
}
return la.lines[lineN].data
return la.lines[n].data()
}
// LineString returns line n as an string
func (la *LineArray) LineString(n int) string {
if n >= len(la.lines) || n < 0 {
return string("")
}
var runes []rune
for _, r := range la.lines[n].runes {
runes = append(runes, r.combc[0:]...)
}
return string(runes)
}
// State gets the highlight state for the given line number
@@ -414,7 +481,7 @@ func (la *LineArray) SearchMatch(b *Buffer, pos Loc) bool {
if !s.done {
s.match = nil
start := Loc{0, lineN}
end := Loc{util.CharacterCount(la.lines[lineN].data), lineN}
end := Loc{len(la.lines[lineN].runes), lineN}
for start.X < end.X {
m, found, _ := b.FindNext(b.LastSearch, start, end, start, true, b.LastSearchRegex)
if !found {

View File

@@ -1,7 +1,7 @@
package buffer
import (
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/util"
)
// Loc stores a location
@@ -47,6 +47,16 @@ func (l Loc) LessEqual(b Loc) bool {
return l == b
}
// Clamp clamps a loc between start and end
func (l Loc) Clamp(start, end Loc) Loc {
if l.GreaterEqual(end) {
return end
} else if l.LessThan(start) {
return start
}
return l
}
// The following functions require a buffer to know where newlines are
// Diff returns the distance between two locations
@@ -139,10 +149,5 @@ func ByteOffset(pos Loc, buf *Buffer) int {
// clamps a loc within a buffer
func clamp(pos Loc, la *LineArray) Loc {
if pos.GreaterEqual(la.End()) {
return la.End()
} else if pos.LessThan(la.Start()) {
return la.Start()
}
return pos
return pos.Clamp(la.Start(), la.End())
}

View File

@@ -1,8 +1,8 @@
package buffer
import (
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/tcell/v2"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/tcell/v2"
)
type MsgType int
@@ -84,7 +84,7 @@ func (b *Buffer) ClearAllMessages() {
}
type Messager interface {
Message(msg ...interface{})
Message(msg ...any)
}
var prompt Messager

View File

@@ -2,21 +2,21 @@ package buffer
import (
"bufio"
"bytes"
"errors"
"io"
"io/fs"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strings"
"time"
"unicode"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/htmlindex"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"golang.org/x/text/transform"
)
@@ -24,68 +24,192 @@ import (
// because hashing is too slow
const LargeFileThreshold = 50000
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
// the supplied function with the file as io.Writer object, also making sure the file is
// closed afterwards.
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
type wrappedFile struct {
name string
writeCloser io.WriteCloser
withSudo bool
screenb bool
cmd *exec.Cmd
sigChan chan os.Signal
}
type saveResponse struct {
size int
err error
}
type saveRequest struct {
buf *Buffer
path string
withSudo bool
newFile bool
saveResponseChan chan saveResponse
}
var saveRequestChan chan saveRequest
var backupRequestChan chan backupRequest
func init() {
// Both saveRequestChan and backupRequestChan need to be non-buffered
// so the save/backup goroutine receives both save and backup requests
// in the same order the main goroutine sends them.
saveRequestChan = make(chan saveRequest)
backupRequestChan = make(chan backupRequest)
go func() {
duration := backupSeconds * float64(time.Second)
backupTicker := time.NewTicker(time.Duration(duration))
for {
select {
case sr := <-saveRequestChan:
size, err := sr.buf.safeWrite(sr.path, sr.withSudo, sr.newFile)
sr.saveResponseChan <- saveResponse{size, err}
case br := <-backupRequestChan:
handleBackupRequest(br)
case <-backupTicker.C:
periodicBackup()
}
}
}()
}
func openFile(name string, withSudo bool) (wrappedFile, error) {
var err error
var writeCloser io.WriteCloser
var screenb bool
var cmd *exec.Cmd
var sigChan chan os.Signal
if withSudo {
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
if writeCloser, err = cmd.StdinPipe(); err != nil {
return
conv := "notrunc"
// TODO: both platforms do not support dd with conv=fsync yet
if !(runtime.GOOS == "illumos" || runtime.GOOS == "netbsd") {
conv += ",fsync"
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
cmd.Process.Kill()
}()
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "conv="+conv, "of="+name)
writeCloser, err = cmd.StdinPipe()
if err != nil {
return wrappedFile{}, err
}
sigChan = make(chan os.Signal, 1)
signal.Reset(os.Interrupt)
signal.Notify(sigChan, os.Interrupt)
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 {
err = cmd.Start()
if err != nil {
screen.TempStart(screenb)
return err
signal.Notify(util.Sigterm, os.Interrupt)
signal.Stop(sigChan)
return wrappedFile{}, err
}
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666); err != nil {
return
}
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
err = fn(w)
if err2 := w.Flush(); err2 != nil && err == nil {
err = err2
}
// Call Sync() on the file to make sure the content is safely on disk.
// Does not work with sudo as we don't have direct access to the file.
if !withSudo {
f := writeCloser.(*os.File)
if err2 := f.Sync(); err2 != nil && err == nil {
err = err2
} else {
writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, util.FileMode)
if err != nil {
return wrappedFile{}, err
}
}
if err2 := writeCloser.Close(); err2 != nil && err == nil {
err = err2
return wrappedFile{name, writeCloser, withSudo, screenb, cmd, sigChan}, nil
}
func (wf wrappedFile) Truncate() error {
if wf.withSudo {
// we don't need to stop the screen here, since it is still stopped
// by openFile()
// truncate might not be available on every platfom, so use dd instead
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "count=0", "of="+wf.name)
return cmd.Run()
}
return wf.writeCloser.(*os.File).Truncate(0)
}
func (wf wrappedFile) Write(b *SharedBuffer) (int, error) {
file := bufio.NewWriter(transform.NewWriter(wf.writeCloser, b.encoding.NewEncoder()))
b.Lock()
defer b.Unlock()
if len(b.lines) == 0 {
return 0, nil
}
if withSudo {
// end of line
var eol []byte
if b.Endings == FFDos {
eol = []byte{'\r', '\n'}
} else {
eol = []byte{'\n'}
}
err := wf.Truncate()
if err != nil {
return 0, err
}
// write lines
size, err := file.Write(b.lines[0].data())
if err != nil {
return 0, err
}
for _, l := range b.lines[1:] {
if _, err = file.Write(eol); err != nil {
return 0, err
}
if _, err = file.Write(l.data()); err != nil {
return 0, err
}
size += len(eol) + len(l.data())
}
err = file.Flush()
if err == nil && !wf.withSudo {
// Call Sync() on the file to make sure the content is safely on disk.
f := wf.writeCloser.(*os.File)
err = f.Sync()
}
return size, err
}
func (wf wrappedFile) Close() error {
err := wf.writeCloser.Close()
if wf.withSudo {
// wait for dd to finish and restart the screen if we used sudo
err := cmd.Wait()
screen.TempStart(screenb)
err := wf.cmd.Wait()
screen.TempStart(wf.screenb)
signal.Notify(util.Sigterm, os.Interrupt)
signal.Stop(wf.sigChan)
if err != nil {
return err
}
}
return err
}
return
func (b *SharedBuffer) overwriteFile(name string) (int, error) {
file, err := openFile(name, false)
if err != nil {
return 0, err
}
size, err := file.Write(b)
err2 := file.Close()
if err2 != nil && err == nil {
err = err2
}
return size, err
}
// Save saves the buffer to its default path
@@ -95,9 +219,7 @@ func (b *Buffer) Save() error {
// AutoSave saves the buffer to its default path
func (b *Buffer) AutoSave() error {
// Doing full b.Modified() check every time would be costly, due to the hash
// calculation. So use just isModified even if fastdirty is not set.
if !b.isModified {
if !b.Modified() {
return nil
}
return b.saveToFile(b.Path, false, true)
@@ -124,16 +246,12 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
if b.Type.Scratch {
return errors.New("Cannot save scratch buffer")
}
if withSudo && runtime.GOOS == "windows" {
return errors.New("Save with sudo not supported on Windows")
}
if !autoSave && b.Settings["rmtrailingws"].(bool) {
for i, l := range b.lines {
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
linelen := util.CharacterCount(l.data)
b.Remove(Loc{leftover, i}, Loc{linelen, i})
leftover := strings.TrimRightFunc(l.String(), unicode.IsSpace)
linelen := len(l.runes)
b.Remove(Loc{len(leftover), i}, Loc{linelen, i})
}
b.RelocateCursors()
@@ -146,19 +264,35 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
}
}
// Update the last time this file was updated after saving
defer func() {
b.ModTime, _ = util.GetModTime(filename)
err = b.Serialize()
}()
filename, err = util.ReplaceHome(filename)
if err != nil {
return err
}
// Removes any tilde and replaces with the absolute path to home
absFilename, _ := util.ReplaceHome(filename)
newFile := false
fileInfo, err := os.Stat(filename)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
newFile = true
}
if err == nil && fileInfo.IsDir() {
return errors.New("Error: " + filename + " is a directory and cannot be saved")
}
if err == nil && !fileInfo.Mode().IsRegular() {
return errors.New("Error: " + filename + " is not a regular file and cannot be saved")
}
absFilename, err := filepath.Abs(filename)
if err != nil {
return err
}
// Get the leading path to the file | "." is returned if there's no leading path provided
if dirname := filepath.Dir(absFilename); dirname != "." {
// Check if the parent dirs don't exist
if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
if _, statErr := os.Stat(dirname); errors.Is(statErr, fs.ErrNotExist) {
// Prompt to make sure they want to create the dirs that are missing
if b.Settings["mkparents"].(bool) {
// Create all leading dir(s) since they don't exist
@@ -172,60 +306,94 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
}
}
var fileSize int
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
saveResponseChan := make(chan saveResponse)
saveRequestChan <- saveRequest{b, absFilename, withSudo, newFile, saveResponseChan}
result := <-saveResponseChan
err = result.err
if err != nil {
return err
}
if errors.Is(err, util.ErrOverwrite) {
screen.TermMessage(err)
err = errors.Unwrap(err)
fwriter := func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
b.UpdateModTime()
}
// end of line
var eol []byte
if b.Endings == FFDos {
eol = []byte{'\r', '\n'}
} else {
eol = []byte{'\n'}
}
// write lines
if fileSize, e = file.Write(b.lines[0].data); e != nil {
return
}
for _, l := range b.lines[1:] {
if _, e = file.Write(eol); e != nil {
return
}
if _, e = file.Write(l.data); e != nil {
return
}
fileSize += len(eol) + len(l.data)
}
return
}
if err = overwriteFile(absFilename, enc, fwriter, withSudo); err != nil {
return err
}
if !b.Settings["fastdirty"].(bool) {
if fileSize > LargeFileThreshold {
if result.size > LargeFileThreshold {
// For large files 'fastdirty' needs to be on
b.Settings["fastdirty"] = true
} else {
calcHash(b, &b.origHash)
b.calcHash(&b.origHash)
}
}
newPath := b.Path != filename
if newPath {
b.RemoveBackup()
}
b.Path = filename
absPath, _ := filepath.Abs(filename)
b.AbsPath = absPath
b.AbsPath = absFilename
b.isModified = false
b.UpdateRules()
b.UpdateModTime()
if newPath {
// need to update glob-based and filetype-based settings
b.ReloadSettings(true)
}
err = b.Serialize()
return err
}
// safeWrite writes the buffer to a file in a "safe" way, preventing loss of the
// contents of the file if it fails to write the new contents.
// This means that the file is not overwritten directly but by writing to the
// backup file first.
func (b *SharedBuffer) safeWrite(path string, withSudo bool, newFile bool) (int, error) {
file, err := openFile(path, withSudo)
if err != nil {
return 0, err
}
defer func() {
if newFile && err != nil {
os.Remove(path)
}
}()
// Try to backup first before writing
backupName, resolveName, err := b.writeBackup(path)
if err != nil {
file.Close()
return 0, err
}
// Backup saved, so cancel pending periodic backup, if any
delete(requestedBackups, b)
b.forceKeepBackup = true
size := 0
{
// If we failed to write or close, keep the backup we made
size, err = file.Write(b)
if err != nil {
file.Close()
return 0, util.OverwriteError{err, backupName}
}
err = file.Close()
if err != nil {
return 0, util.OverwriteError{err, backupName}
}
}
b.forceKeepBackup = false
if !b.keepBackup() {
b.removeBackup(backupName, resolveName)
}
return size, err
}

View File

@@ -2,10 +2,65 @@ package buffer
import (
"regexp"
"unicode/utf8"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/util"
)
// We want "^" and "$" to match only the beginning/end of a line, not the
// beginning/end of the search region if it is in the middle of a line.
// In that case we use padded regexps to require a rune before or after
// the match. (This also affects other empty-string patters like "\\b".)
// The following two flags indicate the padding used.
const (
padStart = 1 << iota
padEnd
)
func findLineParams(b *Buffer, start, end Loc, i int, r *regexp.Regexp) ([]byte, int, int, *regexp.Regexp) {
l := b.LineBytes(i)
charpos := 0
padMode := 0
if i == end.Y {
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
if end.X < nchars {
l = util.SliceStart(l, end.X+1)
padMode |= padEnd
}
}
if i == start.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
if start.X > 0 {
charpos = start.X - 1
l = util.SliceEnd(l, charpos)
padMode |= padStart
}
}
if padMode != 0 {
re, err := regexp.Compile(r.String() + `\E`)
if err == nil {
// r contains \Q without closing \E
r = re
}
if padMode == padStart {
r = regexp.MustCompile(".(?:" + r.String() + ")")
} else if padMode == padEnd {
r = regexp.MustCompile("(?:" + r.String() + ").")
} else {
// padMode == padStart|padEnd
r = regexp.MustCompile(".(?:" + r.String() + ").")
}
}
return l, charpos, padMode, r
}
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 {
@@ -22,30 +77,19 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
}
for i := start.Y; i <= end.Y; i++ {
l := b.LineBytes(i)
charpos := 0
l, charpos, padMode, rPadded := findLineParams(b, start, end, i, r)
if i == start.Y && start.Y == end.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
}
match := r.FindIndex(l)
match := rPadded.FindIndex(l)
if match != nil {
if padMode&padStart != 0 {
_, size := utf8.DecodeRune(l[match[0]:])
match[0] += size
}
if padMode&padEnd != 0 {
_, size := utf8.DecodeLastRune(l[:match[1]])
match[1] -= size
}
start := Loc{charpos + util.RunePos(l, match[0]), i}
end := Loc{charpos + util.RunePos(l, match[1]), i}
return [2]Loc{start, end}, true
@@ -70,39 +114,39 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
}
for i := end.Y; i >= start.Y; i-- {
l := b.LineBytes(i)
charpos := 0
if i == start.Y && start.Y == end.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
}
allMatches := r.FindAllIndex(l, -1)
charCount := util.CharacterCount(b.LineBytes(i))
from := Loc{0, i}.Clamp(start, end)
to := Loc{charCount, i}.Clamp(start, end)
allMatches := b.findAll(r, from, to)
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
return [2]Loc{match[0], match[1]}, true
}
}
return [2]Loc{}, false
}
func (b *Buffer) findAll(r *regexp.Regexp, start, end Loc) [][2]Loc {
var matches [][2]Loc
loc := start
for {
match, found := b.findDown(r, loc, end)
if !found {
break
}
matches = append(matches, match)
if match[0] != match[1] {
loc = match[1]
} else if match[1] != end {
loc = match[1].Move(1, b)
} else {
break
}
}
return matches
}
// FindNext finds the next occurrence of a given string in the buffer
// It returns the start and end location of the match (if found) and
// a boolean indicating if it was found
@@ -146,53 +190,58 @@ 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
// and returns the number of replacements made and the number of characters
// added or removed on the last line of the range
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte, captureGroups bool) (int, int) {
if start.GreaterThan(end) {
start, end = end, start
}
netrunes := 0
charsEnd := util.CharacterCount(b.LineBytes(end.Y))
found := 0
var deltas []Delta
for i := start.Y; i <= end.Y; i++ {
l := b.lines[i].data
charpos := 0
l := b.LineBytes(i)
charCount := util.CharacterCount(l)
if (i == start.Y && start.X > 0) || (i == end.Y && end.X < charCount) {
// This replacement code works in general, but it creates a separate
// modification for each match. We only use it for the first and last
// lines, which may use padded regexps
if start.Y == end.Y && i == start.Y {
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
l = util.SliceStart(l, end.X)
}
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
var result []byte
if captureGroups {
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
result = search.Expand(result, replace, in, submatches)
from := Loc{0, i}.Clamp(start, end)
to := Loc{charCount, i}.Clamp(start, end)
matches := b.findAll(search, from, to)
found += len(matches)
for j := len(matches) - 1; j >= 0; j-- {
// if we counted upwards, the different deltas would interfere
match := matches[j]
var newText []byte
if captureGroups {
newText = search.ReplaceAll(b.Substr(match[0], match[1]), replace)
} else {
newText = replace
}
} else {
result = replace
deltas = append(deltas, Delta{newText, match[0], match[1]})
}
found++
if i == end.Y {
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
}
return result
})
from := Loc{charpos, i}
to := Loc{charpos + util.CharacterCount(l), i}
deltas = append(deltas, Delta{newText, from, to})
} else {
newLine := search.ReplaceAllFunc(l, func(in []byte) []byte {
found++
var result []byte
if captureGroups {
match := search.FindSubmatchIndex(in)
result = search.Expand(result, replace, in, match)
} else {
result = replace
}
return result
})
deltas = append(deltas, Delta{newLine, Loc{0, i}, Loc{charCount, i}})
}
}
b.MultipleReplace(deltas)
return found, netrunes
return found, util.CharacterCount(b.LineBytes(end.Y)) - charsEnd
}

View File

@@ -1,17 +1,15 @@
package buffer
import (
"bytes"
"encoding/gob"
"errors"
"io"
"os"
"path/filepath"
"time"
"golang.org/x/text/encoding"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/util"
)
// The SerializedBuffer holds the types that get serialized when a buffer is saved
@@ -31,16 +29,30 @@ func (b *Buffer) Serialize() error {
return nil
}
name := filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))
return overwriteFile(name, encoding.Nop, func(file io.Writer) error {
err := gob.NewEncoder(file).Encode(SerializedBuffer{
b.EventHandler,
b.GetActiveCursor().Loc,
b.ModTime,
})
var buf bytes.Buffer
err := gob.NewEncoder(&buf).Encode(SerializedBuffer{
b.EventHandler,
b.GetActiveCursor().Loc,
b.ModTime,
})
if err != nil {
return err
}, false)
}
name, resolveName := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath)
err = util.SafeWrite(name, buf.Bytes(), true)
if err != nil {
return err
}
if resolveName != "" {
err = util.SafeWrite(resolveName, []byte(b.AbsPath), true)
if err != nil {
return err
}
}
return nil
}
// Unserialize loads the buffer info from config.ConfigDir/buffers
@@ -50,7 +62,8 @@ func (b *Buffer) Unserialize() error {
if b.Path == "" {
return nil
}
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
name, _ := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath)
file, err := os.Open(name)
if err == nil {
defer file.Close()
var buffer SerializedBuffer

View File

@@ -4,14 +4,23 @@ import (
"crypto/md5"
"reflect"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/config"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/encoding/unicode"
luar "layeh.com/gopher-luar"
)
func (b *Buffer) ReloadSettings(reloadFiletype bool) {
settings := config.ParsedSettings()
config.UpdatePathGlobLocals(settings, b.AbsPath)
if _, ok := b.LocalSettings["filetype"]; !ok && reloadFiletype {
oldFiletype := b.Settings["filetype"].(string)
_, local := b.LocalSettings["filetype"]
_, volatile := config.VolatileSettings["filetype"]
if reloadFiletype && !local && !volatile {
// need to update filetype before updating other settings based on it
b.Settings["filetype"] = "unknown"
if v, ok := settings["filetype"]; ok {
@@ -21,9 +30,14 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) {
// update syntax rules, which will also update filetype if needed
b.UpdateRules()
settings["filetype"] = b.Settings["filetype"]
config.InitLocalSettings(settings, b.Path)
curFiletype := b.Settings["filetype"].(string)
if oldFiletype != curFiletype {
b.doCallbacks("filetype", oldFiletype, curFiletype)
}
config.UpdateFileTypeLocals(settings, curFiletype)
for k, v := range config.DefaultCommonSettings() {
if k == "filetype" {
// prevent recursion
@@ -45,8 +59,9 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) {
}
}
func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
if reflect.DeepEqual(b.Settings[option], nativeValue) {
func (b *Buffer) DoSetOptionNative(option string, nativeValue any) {
oldValue := b.Settings[option]
if reflect.DeepEqual(oldValue, nativeValue) {
return
}
@@ -58,7 +73,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
b.Settings["fastdirty"] = true
} else {
if !b.isModified {
calcHash(b, &b.origHash)
b.calcHash(&b.origHash)
} else {
// prevent using an old stale origHash value
b.origHash = [md5.Size]byte{}
@@ -76,7 +91,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
case "dos":
b.Endings = FFDos
}
b.isModified = true
b.setModified()
} else if option == "syntax" {
if !nativeValue.(bool) {
b.ClearMatches()
@@ -84,7 +99,13 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
b.UpdateRules()
}
} else if option == "encoding" {
b.isModified = true
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
enc = unicode.UTF8
b.Settings["encoding"] = "utf-8"
}
b.encoding = enc
b.setModified()
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
b.Type.Readonly = nativeValue.(bool)
} else if option == "hlsearch" {
@@ -114,12 +135,10 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
}
}
if b.OptionCallback != nil {
b.OptionCallback(option, nativeValue)
}
b.doCallbacks(option, oldValue, nativeValue)
}
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
func (b *Buffer) SetOptionNative(option string, nativeValue any) error {
if err := config.OptionIsValid(option, nativeValue); err != nil {
return err
}
@@ -136,10 +155,22 @@ func (b *Buffer) SetOption(option, value string) error {
return config.ErrInvalidOption
}
nativeValue, err := config.GetNativeValue(option, b.Settings[option], value)
nativeValue, err := config.GetNativeValue(option, value)
if err != nil {
return err
}
return b.SetOptionNative(option, nativeValue)
}
func (b *Buffer) doCallbacks(option string, oldValue any, newValue any) {
if b.OptionCallback != nil {
b.OptionCallback(option, newValue)
}
if err := config.RunPluginFn("onBufferOptionChanged",
luar.New(ulua.L, b), luar.New(ulua.L, option),
luar.New(ulua.L, oldValue), luar.New(ulua.L, newValue)); err != nil {
screen.TermMessage(err)
}
}

View File

@@ -4,8 +4,8 @@ import (
"errors"
"time"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell/v2"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/tcell/v2"
)
type terminalClipboard struct{}

View File

@@ -6,7 +6,7 @@ import (
"strconv"
"strings"
"github.com/zyedidia/tcell/v2"
"github.com/micro-editor/tcell/v2"
)
// DefStyle is Micro's default style
@@ -55,6 +55,14 @@ func InitColorscheme() error {
c, err := LoadDefaultColorscheme()
if err == nil {
Colorscheme = c
} else {
// The colorscheme setting seems broken (maybe because we have not validated
// it earlier, see comment in verifySetting()). So reset it to the default
// colorscheme and try again.
GlobalSettings["colorscheme"] = DefaultGlobalOnlySettings["colorscheme"]
if c, err2 := LoadDefaultColorscheme(); err2 == nil {
Colorscheme = c
}
}
return err

View File

@@ -3,8 +3,8 @@ package config
import (
"testing"
"github.com/micro-editor/tcell/v2"
"github.com/stretchr/testify/assert"
"github.com/zyedidia/tcell/v2"
)
func TestSimpleStringToStyle(t *testing.T) {

View File

@@ -4,8 +4,8 @@ import (
"errors"
"log"
ulua "github.com/micro-editor/micro/v2/internal/lua"
lua "github.com/yuin/gopher-lua"
ulua "github.com/zyedidia/micro/v2/internal/lua"
)
// ErrNoSuchFunction is returned when Call is executed on a function that does not exist
@@ -42,7 +42,7 @@ func RunPluginFn(fn string, args ...lua.LValue) error {
// RunPluginFnBool runs a function in all plugins and returns
// false if any one of them returned false
// also returns an error if any of the plugins had an error
func RunPluginFnBool(settings map[string]interface{}, fn string, args ...lua.LValue) (bool, error) {
func RunPluginFnBool(settings map[string]any, fn string, args ...lua.LValue) (bool, error) {
var reterr error
retbool := true
for _, p := range Plugins {
@@ -71,7 +71,7 @@ type Plugin struct {
Info *PluginInfo // json file containing info
Srcs []RuntimeFile // lua files
Loaded bool
Default bool // pre-installed plugin
Builtin bool
}
// IsLoaded returns if a plugin is enabled
@@ -143,15 +143,3 @@ func FindPlugin(name string) *Plugin {
}
return pl
}
// FindAnyPlugin does not require the plugin to be enabled
func FindAnyPlugin(name string) *Plugin {
var pl *Plugin
for _, p := range Plugins {
if p.Name == name {
pl = p
break
}
}
return pl
}

View File

@@ -5,7 +5,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
@@ -14,10 +13,10 @@ import (
"sync"
"github.com/blang/semver"
"github.com/micro-editor/json5"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/util"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/json5"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/util"
)
var (
@@ -43,6 +42,7 @@ type PluginPackage struct {
Author string
Tags []string
Versions PluginVersions
Builtin bool
}
// PluginPackages is a list of PluginPackage instances.
@@ -76,6 +76,9 @@ func (pp *PluginPackage) String() string {
buf := new(bytes.Buffer)
buf.WriteString("Plugin: ")
buf.WriteString(pp.Name)
if pp.Builtin {
buf.WriteString(" (built-in)")
}
buf.WriteRune('\n')
if pp.Author != "" {
buf.WriteString("Author: ")
@@ -225,7 +228,7 @@ func GetAllPluginPackages(out io.Writer) PluginPackages {
if strs, ok := data.([]string); ok {
return strs
}
if ifs, ok := data.([]interface{}); ok {
if ifs, ok := data.([]any); ok {
result := make([]string, len(ifs))
for i, urlIf := range ifs {
if url, ok := urlIf.(string); ok {
@@ -335,7 +338,7 @@ func isUnknownCoreVersion() bool {
return err != nil
}
func newStaticPluginVersion(name, version string) *PluginVersion {
func newStaticPluginVersion(name, version string, builtin bool) *PluginVersion {
vers, err := semver.ParseTolerant(version)
if err != nil {
@@ -344,7 +347,8 @@ func newStaticPluginVersion(name, version string) *PluginVersion {
}
}
pl := &PluginPackage{
Name: name,
Name: name,
Builtin: builtin,
}
pv := &PluginVersion{
pack: pl,
@@ -359,7 +363,7 @@ func newStaticPluginVersion(name, version string) *PluginVersion {
func GetInstalledVersions(withCore bool) PluginVersions {
result := PluginVersions{}
if withCore {
result = append(result, newStaticPluginVersion(CorePluginName, util.Version))
result = append(result, newStaticPluginVersion(CorePluginName, util.Version, true))
}
for _, p := range Plugins {
@@ -367,7 +371,7 @@ func GetInstalledVersions(withCore bool) PluginVersions {
continue
}
version := GetInstalledPluginVersion(p.Name)
if pv := newStaticPluginVersion(p.Name, version); pv != nil {
if pv := newStaticPluginVersion(p.Name, version, p.Builtin); pv != nil {
result = append(result, pv)
}
}
@@ -396,7 +400,7 @@ func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
return err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
@@ -605,7 +609,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.IsLoaded() || p.Default {
if !p.IsLoaded() || p.Builtin || p.Name == "initlua" {
continue
}
plugins = append(plugins, p.Name)
@@ -614,7 +618,7 @@ func UpdatePlugins(out io.Writer, plugins []string) {
fmt.Fprintln(out, "Checking for plugin updates")
microVersion := PluginVersions{
newStaticPluginVersion(CorePluginName, util.Version),
newStaticPluginVersion(CorePluginName, util.Version, true),
}
var updates = make(PluginDependencies, 0)
@@ -646,14 +650,14 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
if pp == nil {
fmt.Fprintln(out, "Unknown plugin \""+plugin+"\"")
} else if err := pp.IsInstallable(out); err != nil {
fmt.Fprintln(out, "Error installing ", plugin, ": ", err)
fmt.Fprintln(out, "Error installing "+plugin+": ", err)
} else {
for _, installed := range installedVersions {
if pp.Name == installed.Pack().Name {
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
fmt.Fprintln(out, pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
fmt.Fprintln(out, pp.Name, "is already installed but out-of-date: use 'plugin update "+pp.Name+"' to update")
} else {
fmt.Fprintln(out, pp.Name, " is already installed")
fmt.Fprintln(out, pp.Name, "is already installed")
}
}
}
@@ -664,10 +668,14 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
case "remove":
removed := ""
for _, plugin := range args {
if plugin == "initlua" {
fmt.Fprintln(out, "initlua cannot be removed, but can be disabled via settings.")
continue
}
// check if the plugin exists.
for _, p := range Plugins {
if p.Name == plugin && p.Default {
fmt.Fprintln(out, "Default plugins cannot be removed, but can be disabled via settings.")
if p.Name == plugin && p.Builtin {
fmt.Fprintln(out, p.Name, "is a built-in plugin which cannot be removed, but can be disabled via settings.")
continue
}
if p.Name == plugin {
@@ -677,8 +685,9 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
}
}
}
removed = strings.TrimSpace(removed)
if removed != "" {
fmt.Fprintln(out, "Removed ", removed)
fmt.Fprintln(out, "Removed", removed)
} else {
fmt.Fprintln(out, "No plugins removed")
}
@@ -688,11 +697,17 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
plugins := GetInstalledVersions(false)
fmt.Fprintln(out, "The following plugins are currently installed:")
for _, p := range plugins {
fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
if p.Pack().Name == "initlua" {
fmt.Fprintf(out, "%s\n", "initlua")
} else if p.Pack().Builtin {
fmt.Fprintf(out, "%s (built-in)\n", p.Pack().Name)
} else {
fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
}
}
case "search":
plugins := SearchPlugin(out, args)
fmt.Fprintln(out, len(plugins), " plugins found")
fmt.Fprintln(out, len(plugins), "plugins found")
for _, p := range plugins {
fmt.Fprintln(out, "----------------")
fmt.Fprintln(out, p.String())

View File

@@ -5,7 +5,7 @@ import (
"github.com/blang/semver"
"github.com/zyedidia/json5"
"github.com/micro-editor/json5"
)
func TestDependencyResolving(t *testing.T) {

View File

@@ -2,15 +2,13 @@ package config
import (
"errors"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"regexp"
"strings"
rt "github.com/zyedidia/micro/v2/runtime"
rt "github.com/micro-editor/micro/v2/runtime"
)
const (
@@ -62,12 +60,6 @@ type realFile string
// some asset file
type assetFile string
// some file on filesystem but with a different name
type namedFile struct {
realFile
name string
}
// a file with the data stored in memory
type memoryFile struct {
name string
@@ -87,22 +79,18 @@ func (rf realFile) Name() string {
}
func (rf realFile) Data() ([]byte, error) {
return ioutil.ReadFile(string(rf))
return os.ReadFile(string(rf))
}
func (af assetFile) Name() string {
fn := path.Base(string(af))
return fn[:len(fn)-len(path.Ext(fn))]
fn := filepath.Base(string(af))
return fn[:len(fn)-len(filepath.Ext(fn))]
}
func (af assetFile) Data() ([]byte, error) {
return rt.Asset(string(af))
}
func (nf namedFile) Name() string {
return nf.name
}
// AddRuntimeFile registers a file for the given filetype
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
allFiles[fileType] = append(allFiles[fileType], file)
@@ -117,7 +105,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, _ := ioutil.ReadDir(directory)
files, _ := os.ReadDir(directory)
for _, f := range files {
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
fullPath := filepath.Join(directory, f.Name())
@@ -136,8 +124,8 @@ func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
assetLoop:
for _, f := range files {
if ok, _ := path.Match(pattern, f); ok {
af := assetFile(path.Join(directory, f))
if ok, _ := filepath.Match(pattern, f); ok {
af := assetFile(filepath.Join(directory, f))
for _, rf := range realFiles[fileType] {
if af.Name() == rf.Name() {
continue assetLoop
@@ -178,7 +166,7 @@ func InitRuntimeFiles(user bool) {
if user {
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
}
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
AddRuntimeFilesFromAssets(fileType, filepath.Join("runtime", dir), pattern)
}
initRuntimeVars()
@@ -199,19 +187,20 @@ func InitPlugins() {
p.Name = "initlua"
p.DirName = "initlua"
p.Srcs = append(p.Srcs, realFile(initlua))
p.Builtin = false
Plugins = append(Plugins, p)
}
// Search ConfigDir for plugin-scripts
plugdir := filepath.Join(ConfigDir, "plug")
files, _ := ioutil.ReadDir(plugdir)
files, _ := os.ReadDir(plugdir)
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
for _, d := range files {
plugpath := filepath.Join(plugdir, d.Name())
if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
srcs, _ := ioutil.ReadDir(plugpath)
srcs, _ := os.ReadDir(plugpath)
p := new(Plugin)
p.Name = d.Name()
p.DirName = d.Name()
@@ -219,7 +208,7 @@ func InitPlugins() {
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()))
data, err := os.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
if err != nil {
continue
}
@@ -254,7 +243,7 @@ func InitPlugins() {
p := new(Plugin)
p.Name = d
p.DirName = d
p.Default = true
p.Builtin = true
for _, f := range srcs {
if strings.HasSuffix(f, ".lua") {
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
@@ -311,7 +300,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("runtime", "plugins", pldir, filePath)
fullpath = filepath.Join("runtime", "plugins", pldir, filePath)
AddRuntimeFile(filetype, assetFile(fullpath))
}
return nil
@@ -328,7 +317,7 @@ func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, dire
if _, err := os.Stat(fullpath); err == nil {
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
} else {
fullpath = path.Join("runtime", "plugins", pldir, directory)
fullpath = filepath.Join("runtime", "plugins", pldir, directory)
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
}
return nil

View File

@@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
@@ -12,13 +11,13 @@ import (
"strconv"
"strings"
"github.com/micro-editor/json5"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/zyedidia/glob"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding/htmlindex"
)
type optionValidator func(string, interface{}) error
type optionValidator func(string, any) error
// a list of settings that need option validators
var optionValidators = map[string]optionValidator{
@@ -29,26 +28,31 @@ var optionValidators = map[string]optionValidator{
"detectlimit": validateNonNegativeValue,
"encoding": validateEncoding,
"fileformat": validateChoice,
"helpsplit": validateChoice,
"matchbracestyle": validateChoice,
"multiopen": validateChoice,
"pageoverlap": validateNonNegativeValue,
"reload": validateChoice,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
"tabsize": validatePositiveValue,
"truecolor": validateChoice,
}
// a list of settings with pre-defined choices
var OptionChoices = map[string][]string{
"clipboard": {"internal", "external", "terminal"},
"fileformat": {"unix", "dos"},
"helpsplit": {"hsplit", "vsplit"},
"matchbracestyle": {"underline", "highlight"},
"multiopen": {"tab", "hsplit", "vsplit"},
"reload": {"prompt", "auto", "disabled"},
"truecolor": {"auto", "on", "off"},
}
// a list of settings that can be globally and locally modified and their
// default values
var defaultCommonSettings = map[string]interface{}{
var defaultCommonSettings = map[string]any{
"autoindent": true,
"autosu": false,
"backup": true,
@@ -66,51 +70,56 @@ var defaultCommonSettings = map[string]interface{}{
"hlsearch": false,
"hltaberrors": false,
"hltrailingws": false,
"incsearch": true,
"ignorecase": true,
"indentchar": " ",
"incsearch": true,
"indentchar": " ", // Deprecated
"keepautoindent": false,
"matchbrace": true,
"matchbraceleft": true,
"matchbracestyle": "underline",
"mkparents": false,
"pageoverlap": float64(2),
"permbackup": false,
"readonly": false,
"relativeruler": false,
"reload": "prompt",
"rmtrailingws": false,
"ruler": true,
"relativeruler": false,
"savecursor": false,
"saveundo": false,
"scrollbar": false,
"scrollmargin": float64(3),
"scrollspeed": float64(2),
"showchars": "",
"smartpaste": true,
"softwrap": false,
"splitbottom": true,
"splitright": true,
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
"statusformatl": "$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
"statusline": true,
"syntax": true,
"tabmovement": false,
"tabsize": float64(4),
"tabstospaces": false,
"truecolor": "auto",
"useprimary": true,
"wordwrap": false,
}
// a list of settings that should only be globally modified and their
// default values
var DefaultGlobalOnlySettings = map[string]interface{}{
var DefaultGlobalOnlySettings = map[string]any{
"autosave": float64(0),
"clipboard": "external",
"colorscheme": "default",
"divchars": "|-",
"divreverse": true,
"fakecursor": false,
"fakecursor": defaultFakeCursor(),
"helpsplit": "hsplit",
"infobar": true,
"keymenu": false,
"lockbindings": false,
"mouse": true,
"multiopen": "tab",
"parsecursor": false,
@@ -132,14 +141,15 @@ var LocalSettings = []string{
}
var (
ErrInvalidOption = errors.New("Invalid option")
ErrInvalidValue = errors.New("Invalid value")
ErrInvalidOption = errors.New("Invalid option")
ErrInvalidValue = errors.New("Invalid value")
ErrOptNotToggleable = errors.New("Option not toggleable")
// The options that the user can set
GlobalSettings map[string]interface{}
GlobalSettings map[string]any
// This is the raw parsed json
parsedSettings map[string]interface{}
parsedSettings map[string]any
settingsParseError bool
// ModifiedSettings is a map of settings which should be written to disk
@@ -151,6 +161,10 @@ var (
VolatileSettings map[string]bool
)
func writeFile(name string, txt []byte) error {
return util.SafeWrite(name, txt, false)
}
func init() {
ModifiedSettings = make(map[string]bool)
VolatileSettings = make(map[string]bool)
@@ -162,11 +176,11 @@ func validateParsedSettings() error {
for k, v := range parsedSettings {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if strings.HasPrefix(k, "ft:") {
for k1, v1 := range v.(map[string]interface{}) {
for k1, v1 := range v.(map[string]any) {
if _, ok := defaults[k1]; ok {
if e := verifySetting(k1, v1, defaults[k1]); e != nil {
err = e
parsedSettings[k].(map[string]interface{})[k1] = defaults[k1]
parsedSettings[k].(map[string]any)[k1] = defaults[k1]
continue
}
}
@@ -177,11 +191,11 @@ func validateParsedSettings() error {
delete(parsedSettings, k)
continue
}
for k1, v1 := range v.(map[string]interface{}) {
for k1, v1 := range v.(map[string]any) {
if _, ok := defaults[k1]; ok {
if e := verifySetting(k1, v1, defaults[k1]); e != nil {
err = e
parsedSettings[k].(map[string]interface{})[k1] = defaults[k1]
parsedSettings[k].(map[string]any)[k1] = defaults[k1]
continue
}
}
@@ -202,6 +216,7 @@ func validateParsedSettings() error {
}
continue
}
if _, ok := defaults[k]; ok {
if e := verifySetting(k, v, defaults[k]); e != nil {
err = e
@@ -214,10 +229,10 @@ func validateParsedSettings() error {
}
func ReadSettings() error {
parsedSettings = make(map[string]interface{})
parsedSettings = make(map[string]any)
filename := filepath.Join(ConfigDir, "settings.json")
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
input, err := os.ReadFile(filename)
if err != nil {
settingsParseError = true
return errors.New("Error reading settings.json file: " + err.Error())
@@ -238,16 +253,16 @@ func ReadSettings() error {
return nil
}
func ParsedSettings() map[string]interface{} {
s := make(map[string]interface{})
func ParsedSettings() map[string]any {
s := make(map[string]any)
for k, v := range parsedSettings {
s[k] = v
}
return s
}
func verifySetting(option string, value interface{}, def interface{}) error {
var interfaceArr []interface{}
func verifySetting(option string, value any, def any) error {
var interfaceArr []any
valType := reflect.TypeOf(value)
defType := reflect.TypeOf(def)
assignable := false
@@ -262,6 +277,12 @@ func verifySetting(option string, value interface{}, def interface{}) error {
return fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", option, valType, def, defType)
}
if option == "colorscheme" {
// Plugins are not initialized yet, so do not verify if the colorscheme
// exists yet, since the colorscheme may be added by a plugin later.
return nil
}
if err := OptionIsValid(option, value); err != nil {
return err
}
@@ -283,22 +304,31 @@ func InitGlobalSettings() error {
return err
}
// InitLocalSettings scans the json in settings.json and sets the options locally based
// on whether the filetype or path matches ft or glob local settings
// UpdatePathGlobLocals scans the already parsed settings and sets the options locally
// based on whether the path matches a glob
// Must be called after ReadSettings
func InitLocalSettings(settings map[string]interface{}, path string) {
func UpdatePathGlobLocals(settings map[string]any, path string) {
for k, v := range parsedSettings {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if strings.HasPrefix(k, "ft:") {
if settings["filetype"].(string) == k[3:] {
for k1, v1 := range v.(map[string]interface{}) {
settings[k1] = v1
}
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && !strings.HasPrefix(k, "ft:") {
g, _ := glob.Compile(k)
if g.MatchString(path) {
for k1, v1 := range v.(map[string]any) {
settings[k1] = v1
}
} else {
g, _ := glob.Compile(k)
if g.MatchString(path) {
for k1, v1 := range v.(map[string]interface{}) {
}
}
}
}
// UpdateFileTypeLocals scans the already parsed settings and sets the options locally
// based on whether the filetype matches to "ft:"
// Must be called after ReadSettings
func UpdateFileTypeLocals(settings map[string]any, filetype string) {
for k, v := range parsedSettings {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && strings.HasPrefix(k, "ft:") {
if filetype == k[3:] {
for k1, v1 := range v.(map[string]any) {
if k1 != "filetype" {
settings[k1] = v1
}
}
@@ -342,7 +372,8 @@ func WriteSettings(filename string) error {
}
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
txt = append(txt, '\n')
err = writeFile(filename, txt)
}
return err
}
@@ -350,7 +381,7 @@ func WriteSettings(filename string) error {
// OverwriteSettings writes the current settings to settings.json and
// resets any user configuration of local settings present in settings.json
func OverwriteSettings(filename string) error {
settings := make(map[string]interface{})
settings := make(map[string]any)
var err error
if _, e := os.Stat(ConfigDir); e == nil {
@@ -363,24 +394,25 @@ func OverwriteSettings(filename string) error {
}
}
txt, _ := json.MarshalIndent(settings, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
txt = append(txt, '\n')
err = writeFile(filename, txt)
}
return err
}
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
func RegisterCommonOptionPlug(pl string, name string, defaultvalue any) error {
return RegisterCommonOption(pl+"."+name, defaultvalue)
}
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{}) error {
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue any) error {
return RegisterGlobalOption(pl+"."+name, defaultvalue)
}
// RegisterCommonOption creates a new option
func RegisterCommonOption(name string, defaultvalue interface{}) error {
func RegisterCommonOption(name string, defaultvalue any) error {
if _, ok := GlobalSettings[name]; !ok {
GlobalSettings[name] = defaultvalue
}
@@ -389,7 +421,7 @@ func RegisterCommonOption(name string, defaultvalue interface{}) error {
}
// RegisterGlobalOption creates a new global-only option
func RegisterGlobalOption(name string, defaultvalue interface{}) error {
func RegisterGlobalOption(name string, defaultvalue any) error {
if _, ok := GlobalSettings[name]; !ok {
GlobalSettings[name] = defaultvalue
}
@@ -398,7 +430,7 @@ func RegisterGlobalOption(name string, defaultvalue interface{}) error {
}
// GetGlobalOption returns the global value of the given option
func GetGlobalOption(name string) interface{} {
func GetGlobalOption(name string) any {
return GlobalSettings[name]
}
@@ -409,6 +441,15 @@ func defaultFileFormat() string {
return "unix"
}
func defaultFakeCursor() bool {
_, wt := os.LookupEnv("WT_SESSION")
if runtime.GOOS == "windows" && !wt {
// enabled for windows consoles where the cursor is slow
return true
}
return false
}
func GetInfoBarOffset() int {
offset := 0
if GetGlobalOption("infobar").(bool) {
@@ -422,8 +463,8 @@ func GetInfoBarOffset() int {
// DefaultCommonSettings returns a map of all common buffer settings
// and their default values
func DefaultCommonSettings() map[string]interface{} {
commonsettings := make(map[string]interface{})
func DefaultCommonSettings() map[string]any {
commonsettings := make(map[string]any)
for k, v := range defaultCommonSettings {
commonsettings[k] = v
}
@@ -432,8 +473,8 @@ func DefaultCommonSettings() map[string]interface{} {
// DefaultAllSettings returns a map of all common buffer & global-only settings
// and their default values
func DefaultAllSettings() map[string]interface{} {
allsettings := make(map[string]interface{})
func DefaultAllSettings() map[string]any {
allsettings := make(map[string]any)
for k, v := range defaultCommonSettings {
allsettings[k] = v
}
@@ -444,32 +485,34 @@ func DefaultAllSettings() map[string]interface{} {
}
// GetNativeValue parses and validates a value for a given option
func GetNativeValue(option string, realValue interface{}, value string) (interface{}, error) {
var native interface{}
kind := reflect.TypeOf(realValue).Kind()
if kind == reflect.Bool {
func GetNativeValue(option, value string) (any, error) {
curVal := GetGlobalOption(option)
if curVal == nil {
return nil, ErrInvalidOption
}
switch kind := reflect.TypeOf(curVal).Kind(); kind {
case reflect.Bool:
b, err := util.ParseBool(value)
if err != nil {
return nil, ErrInvalidValue
}
native = b
} else if kind == reflect.String {
native = value
} else if kind == reflect.Float64 {
return b, nil
case reflect.String:
return value, nil
case reflect.Float64:
f, err := strconv.ParseFloat(value, 64)
if err != nil {
return nil, ErrInvalidValue
}
native = f
} else {
return f, nil
default:
return nil, ErrInvalidValue
}
return native, nil
}
// OptionIsValid checks if a value is valid for a certain option
func OptionIsValid(option string, value interface{}) error {
func OptionIsValid(option string, value any) error {
if validator, ok := optionValidators[option]; ok {
return validator(option, value)
}
@@ -479,7 +522,7 @@ func OptionIsValid(option string, value interface{}) error {
// Option validators
func validatePositiveValue(option string, value interface{}) error {
func validatePositiveValue(option string, value any) error {
nativeValue, ok := value.(float64)
if !ok {
@@ -493,7 +536,7 @@ func validatePositiveValue(option string, value interface{}) error {
return nil
}
func validateNonNegativeValue(option string, value interface{}) error {
func validateNonNegativeValue(option string, value any) error {
nativeValue, ok := value.(float64)
if !ok {
@@ -507,7 +550,7 @@ func validateNonNegativeValue(option string, value interface{}) error {
return nil
}
func validateChoice(option string, value interface{}) error {
func validateChoice(option string, value any) error {
if choices, ok := OptionChoices[option]; ok {
val, ok := value.(string)
if !ok {
@@ -527,7 +570,7 @@ func validateChoice(option string, value interface{}) error {
return errors.New("Option has no pre-defined choices")
}
func validateColorscheme(option string, value interface{}) error {
func validateColorscheme(option string, value any) error {
colorscheme, ok := value.(string)
if !ok {
@@ -541,7 +584,7 @@ func validateColorscheme(option string, value interface{}) error {
return nil
}
func validateEncoding(option string, value interface{}) error {
func validateEncoding(option string, value any) error {
_, err := htmlindex.Get(value.(string))
return err
}

View File

@@ -2,13 +2,14 @@ package display
import (
"strconv"
"strings"
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/v2"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
)
// The BufWindow provides a way of displaying a certain section of a buffer.
@@ -46,7 +47,7 @@ func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
// SetBuffer sets this window's buffer.
func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
w.Buf = b
b.OptionCallback = func(option string, nativeValue interface{}) {
b.OptionCallback = func(option string, nativeValue any) {
if option == "softwrap" {
if nativeValue.(bool) {
w.StartCol = 0
@@ -58,9 +59,15 @@ func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
if option == "softwrap" || option == "wordwrap" {
w.Relocate()
for _, c := range w.Buf.GetCursors() {
c.LastVisualX = c.GetVisualX()
c.LastWrappedVisualX = c.GetVisualX(true)
}
}
if option == "diffgutter" || option == "ruler" || option == "scrollbar" ||
option == "statusline" {
w.updateDisplayInfo()
w.Relocate()
}
}
b.GetVisualX = func(loc buffer.Loc) int {
return w.VLocFromLoc(loc).VisualX
@@ -160,7 +167,7 @@ func (w *BufWindow) updateDisplayInfo() {
if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) {
for _, c := range w.Buf.GetCursors() {
c.LastVisualX = c.GetVisualX()
c.LastWrappedVisualX = c.GetVisualX(true)
}
}
}
@@ -238,7 +245,7 @@ func (w *BufWindow) Relocate() bool {
// horizontal relocation (scrolling)
if !b.Settings["softwrap"].(bool) {
cx := activeC.GetVisualX()
cx := activeC.GetVisualX(false)
rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
if rw == 0 {
rw = 1 // tab or newline
@@ -248,8 +255,8 @@ func (w *BufWindow) Relocate() bool {
w.StartCol = cx
ret = true
}
if cx+w.gutterOffset+rw > w.StartCol+w.Width {
w.StartCol = cx - w.Width + w.gutterOffset + rw
if cx+rw > w.StartCol+w.bufWidth {
w.StartCol = cx - w.bufWidth + rw
ret = true
}
}
@@ -444,12 +451,36 @@ func (w *BufWindow) displayBuffer() {
cursors := b.GetCursors()
curStyle := config.DefStyle
// Parse showchars which is in the format of key1=val1,key2=val2,...
spacechars := " "
tabchars := b.Settings["indentchar"].(string)
var indentspacechars string
var indenttabchars string
for _, entry := range strings.Split(b.Settings["showchars"].(string), ",") {
split := strings.SplitN(entry, "=", 2)
if len(split) < 2 {
continue
}
key, val := split[0], split[1]
switch key {
case "space":
spacechars = val
case "tab":
tabchars = val
case "ispace":
indentspacechars = val
case "itab":
indenttabchars = val
}
}
for ; vloc.Y < w.bufHeight; vloc.Y++ {
vloc.X = 0
currentLine := false
for _, c := range cursors {
if bloc.Y == c.Y && w.active {
if !c.HasSelection() && bloc.Y == c.Y && w.active {
currentLine = true
break
}
@@ -488,147 +519,190 @@ func (w *BufWindow) displayBuffer() {
}
bloc.X = bslice
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) {
// returns the rune to be drawn, style of it and if the bg should be preserved
getRuneStyle := func(r rune, style tcell.Style, showoffset int, linex int, isplaceholder bool) (rune, tcell.Style, bool) {
if nColsBeforeStart > 0 || vloc.Y < 0 || isplaceholder {
return r, style, false
}
for _, mb := range matchingBraces {
if mb.X == bloc.X && mb.Y == bloc.Y {
if b.Settings["matchbracestyle"].(string) == "highlight" {
if s, ok := config.Colorscheme["match-brace"]; ok {
return r, s, false
} else {
return r, style.Reverse(true), false
}
} else {
return r, style.Underline(true), false
}
}
}
if r != '\t' && r != ' ' {
return r, style, false
}
var indentrunes []rune
switch r {
case '\t':
if bloc.X < leadingwsEnd && indenttabchars != "" {
indentrunes = []rune(indenttabchars)
} else {
indentrunes = []rune(tabchars)
}
case ' ':
if linex%tabsize == 0 && bloc.X < leadingwsEnd && indentspacechars != "" {
indentrunes = []rune(indentspacechars)
} else {
indentrunes = []rune(spacechars)
}
}
var drawrune rune
if showoffset < len(indentrunes) {
drawrune = indentrunes[showoffset]
} else {
// use space if no showchars or after we showed showchars
drawrune = ' '
}
if s, ok := config.Colorscheme["indent-char"]; ok {
fg, _, _ := s.Decompose()
style = style.Foreground(fg)
}
preservebg := false
if b.Settings["hltaberrors"].(bool) && bloc.X < leadingwsEnd {
if s, ok := config.Colorscheme["tab-error"]; ok {
if b.Settings["tabstospaces"].(bool) && r == '\t' {
fg, _, _ := s.Decompose()
style = style.Background(fg)
preservebg = true
} else if !b.Settings["tabstospaces"].(bool) && r == ' ' {
fg, _, _ := s.Decompose()
style = style.Background(fg)
preservebg = true
}
}
}
if b.Settings["hltrailingws"].(bool) {
if s, ok := config.Colorscheme["trailingws"]; ok {
if bloc.X >= trailingwsStart && bloc.X < blineLen {
hl := true
for _, c := range cursors {
if c.NewTrailingWsY == bloc.Y {
hl = false
break
}
}
if hl {
fg, _, _ := s.Decompose()
style = style.Background(fg)
preservebg = true
}
}
}
}
return drawrune, style, preservebg
}
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool, preservebg bool) {
defer func() {
if nColsBeforeStart <= 0 {
vloc.X++
}
nColsBeforeStart--
}()
if nColsBeforeStart > 0 || vloc.Y < 0 {
return
}
if highlight {
if w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {
style = config.DefStyle.Reverse(true)
if s, ok := config.Colorscheme["hlsearch"]; ok {
style = s
}
}
_, origBg, _ := style.Decompose()
_, defBg, _ := config.DefStyle.Decompose()
// syntax or hlsearch highlighting with non-default background takes precedence
// over cursor-line and color-column
if !preservebg && origBg != defBg {
preservebg = true
}
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["hlsearch"]; ok {
if s, ok := config.Colorscheme["selection"]; ok {
style = s
}
}
_, 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
if b.Settings["hltaberrors"].(bool) {
if s, ok := config.Colorscheme["tab-error"]; ok {
isTab := (r == '\t') || (r == ' ' && !showcursor)
if (b.Settings["tabstospaces"].(bool) && isTab) ||
(!b.Settings["tabstospaces"].(bool) && bloc.X < leadingwsEnd && r == ' ' && !isTab) {
fg, _, _ := s.Decompose()
style = style.Background(fg)
dontOverrideBackground = true
}
}
}
if b.Settings["hltrailingws"].(bool) {
if s, ok := config.Colorscheme["trailingws"]; ok {
if bloc.X >= trailingwsStart && bloc.X < blineLen {
hl := true
for _, c := range cursors {
if c.NewTrailingWsY == bloc.Y {
hl = false
break
}
}
if hl {
fg, _, _ := s.Decompose()
style = style.Background(fg)
dontOverrideBackground = true
}
}
}
}
for _, c := range cursors {
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 {
if b.Settings["cursorline"].(bool) && w.active && !preservebg &&
!c.HasSelection() && c.Y == bloc.Y {
if s, ok := config.Colorscheme["cursor-line"]; ok {
fg, _, _ := s.Decompose()
style = style.Background(fg)
}
}
}
for _, mb := range matchingBraces {
if mb.X == bloc.X && mb.Y == bloc.Y {
if b.Settings["matchbracestyle"].(string) == "highlight" {
if s, ok := config.Colorscheme["match-brace"]; ok {
style = s
} else {
style = style.Reverse(true)
}
} else {
style = style.Underline(true)
}
}
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
}
}
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
if showcursor {
for _, c := range cursors {
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
}
if s, ok := config.Colorscheme["color-column"]; ok {
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !preservebg {
fg, _, _ := s.Decompose()
style = style.Background(fg)
}
}
}
if nColsBeforeStart <= 0 {
vloc.X++
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
if showcursor {
for _, c := range cursors {
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
}
}
}
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)
if vloc.Y >= 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)
}
} else {
vloc.X = w.gutterOffset
}
}
@@ -657,6 +731,7 @@ func (w *BufWindow) displayBuffer() {
width := 0
linex := totalwidth
switch r {
case '\t':
ts := tabsize - (totalwidth % tabsize)
@@ -681,7 +756,7 @@ func (w *BufWindow) displayBuffer() {
// 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)
draw(' ', nil, config.DefStyle, false, false, true)
}
// We either stop or we wrap to draw the word in the next line
@@ -697,18 +772,17 @@ func (w *BufWindow) displayBuffer() {
}
for _, r := range word {
draw(r.r, r.combc, r.style, true, true)
drawrune, drawstyle, preservebg := getRuneStyle(r.r, r.style, 0, linex, false)
draw(drawrune, r.combc, drawstyle, true, true, preservebg)
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if r.width > 1 {
char := ' '
if r.r != '\t' {
char = '@'
}
for i := 1; i < r.width; i++ {
draw(char, nil, r.style, true, false)
// Draw extra characters for tabs or wide runes
for i := 1; i < r.width; i++ {
if r.r == '\t' {
drawrune, drawstyle, preservebg = getRuneStyle('\t', r.style, i, linex+i, false)
} else {
drawrune, drawstyle, preservebg = getRuneStyle(' ', r.style, i, linex+i, true)
}
draw(drawrune, nil, drawstyle, true, false, preservebg)
}
bloc.X++
}
@@ -753,7 +827,8 @@ func (w *BufWindow) displayBuffer() {
if vloc.X != maxWidth {
// Display newline within a selection
draw(' ', nil, config.DefStyle, true, true)
drawrune, drawstyle, preservebg := getRuneStyle(' ', config.DefStyle, 0, totalwidth, true)
draw(drawrune, nil, drawstyle, true, true, preservebg)
}
bloc.X = w.StartCol

View File

@@ -2,12 +2,12 @@ package display
import (
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/info"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/info"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
)
type InfoWindow struct {
@@ -122,16 +122,8 @@ func (i *InfoWindow) displayBuffer() {
}
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
c := r
if j > 0 {
c = ' '
combc = nil
}
screen.SetContent(vlocX, i.Y, c, combc, style)
}
vlocX++
screen.SetContent(vlocX, i.Y, r, combc, style)
vlocX += runewidth.RuneWidth(r)
}
nColsBeforeStart--
}
@@ -142,29 +134,22 @@ func (i *InfoWindow) displayBuffer() {
curBX := blocX
r, combc, size := util.DecodeCharacter(line)
draw(r, combc, i.defStyle())
width := 0
char := ' '
switch r {
case '\t':
ts := tabsize - (totalwidth % tabsize)
width = ts
width = tabsize - (totalwidth % tabsize)
for j := 0; j < width; j++ {
draw(' ', nil, i.defStyle())
}
default:
width = runewidth.RuneWidth(r)
char = '@'
draw(r, combc, i.defStyle())
}
blocX++
line = line[size:]
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for j := 1; j < width; j++ {
draw(char, nil, i.defStyle())
}
}
if activeC.X == curBX {
screen.ShowCursor(curVX, i.Y)
}

View File

@@ -2,8 +2,8 @@ package display
import (
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/util"
)
// SLoc represents a vertical scrolling location, i.e. a location of a visual line
@@ -291,13 +291,7 @@ func (w *BufWindow) diff(s1, s2 SLoc) int {
// 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
}
s.Line = util.Clamp(s.Line+n, 0, w.Buf.LinesNum()-1)
return s
}
return w.scroll(s, n)

View File

@@ -10,12 +10,12 @@ import (
luar "layeh.com/gopher-luar"
runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/buffer"
"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/util"
)
// StatusLine represents the information line at the bottom
@@ -47,6 +47,12 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
}
return ""
},
"overwrite": func(b *buffer.Buffer) string {
if b.OverwriteMode && !b.Type.Readonly {
return "[ovwr] "
}
return ""
},
"lines": func(b *buffer.Buffer) string {
return strconv.Itoa(b.LinesNum())
},
@@ -90,7 +96,7 @@ func NewStatusLine(win *BufWindow) *StatusLine {
}
// FindOpt finds a given option in the current buffer's settings
func (s *StatusLine) FindOpt(opt string) interface{} {
func (s *StatusLine) FindOpt(opt string) any {
if val, ok := s.win.Buf.Settings[opt]; ok {
return val
}
@@ -146,7 +152,7 @@ func (s *StatusLine) Display() {
name := match[2 : len(match)-1]
if bytes.HasPrefix(name, []byte("opt")) {
option := name[4:]
return []byte(fmt.Sprint(s.FindOpt(string(option))))
return fmt.Append(nil, s.FindOpt(string(option)))
} else if bytes.HasPrefix(name, []byte("bind")) {
binding := string(name[5:])
for k, v := range config.Bindings["buffer"] {

View File

@@ -2,11 +2,11 @@ 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"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
)
type TabWindow struct {
@@ -112,10 +112,10 @@ func (w *TabWindow) Display() {
}
return tabBarStyle, tabBarActiveStyle
}
draw := func(r rune, n int, active bool, reversed bool) {
tabBarStyle, tabBarActiveStyle := reverseStyles(reversed)
style := tabBarStyle
if active {
style = tabBarActiveStyle
@@ -147,15 +147,15 @@ func (w *TabWindow) Display() {
} else {
draw(' ', 1, false, tabCharHighlight)
}
for _, c := range n {
draw(c, 1, i == w.active, tabCharHighlight)
}
if i == len(w.Names)-1 {
done = true
}
if i == w.active {
draw(']', 1, true, tabCharHighlight)
draw(' ', 2, true, globalTabReverse)
@@ -163,7 +163,7 @@ func (w *TabWindow) Display() {
draw(' ', 1, false, tabCharHighlight)
draw(' ', 2, false, globalTabReverse)
}
if x >= w.Width {
break
}

View File

@@ -1,13 +1,13 @@
package display
import (
"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/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
"github.com/zyedidia/terminal"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/shell"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/tcell/v2"
"github.com/micro-editor/terminal"
)
type TermWindow struct {

View File

@@ -1,11 +1,11 @@
package display
import (
"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/micro/v2/internal/views"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/views"
)
type UIWindow struct {

View File

@@ -1,7 +1,7 @@
package display
import (
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/buffer"
)
type View struct {

View File

@@ -1,13 +1,17 @@
package info
import (
"bytes"
"encoding/gob"
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
)
// LoadHistory attempts to load user history from configDir/buffers/history
@@ -17,24 +21,23 @@ func (i *InfoBuf) LoadHistory() {
if config.GetGlobalOption("savehistory").(bool) {
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
var decodedMap map[string][]string
if err == nil {
defer file.Close()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&decodedMap)
if err != nil {
i.Error("Error loading history:", err)
return
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
i.Error("Error loading history: ", err)
}
return
}
defer file.Close()
err = gob.NewDecoder(file).Decode(&decodedMap)
if err != nil {
i.Error("Error decoding history: ", err)
return
}
if decodedMap != nil {
i.History = decodedMap
} else {
i.History = make(map[string][]string)
}
} else {
i.History = make(map[string][]string)
}
}
@@ -49,16 +52,18 @@ func (i *InfoBuf) SaveHistory() {
}
}
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
if err == nil {
defer file.Close()
encoder := gob.NewEncoder(file)
var buf bytes.Buffer
err := gob.NewEncoder(&buf).Encode(i.History)
if err != nil {
screen.TermMessage("Error encoding history: ", err)
return
}
err = encoder.Encode(i.History)
if err != nil {
i.Error("Error saving history:", err)
return
}
filename := filepath.Join(config.ConfigDir, "buffers", "history")
err = util.SafeWrite(filename, buf.Bytes(), true)
if err != nil {
screen.TermMessage("Error saving history: ", err)
return
}
}
}

View File

@@ -3,7 +3,7 @@ package info
import (
"fmt"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/buffer"
)
// The InfoBuf displays messages and other info at the bottom of the screen.
@@ -55,7 +55,7 @@ func (i *InfoBuf) Close() {
}
// Message sends a message to the user
func (i *InfoBuf) Message(msg ...interface{}) {
func (i *InfoBuf) Message(msg ...any) {
// 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 {
@@ -67,7 +67,7 @@ func (i *InfoBuf) Message(msg ...interface{}) {
}
// GutterMessage displays a message and marks it as a gutter message
func (i *InfoBuf) GutterMessage(msg ...interface{}) {
func (i *InfoBuf) GutterMessage(msg ...any) {
i.Message(msg...)
i.HasGutter = true
}
@@ -79,7 +79,7 @@ func (i *InfoBuf) ClearGutter() {
}
// Error sends an error message to the user
func (i *InfoBuf) Error(msg ...interface{}) {
func (i *InfoBuf) Error(msg ...any) {
// 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 {

View File

@@ -125,6 +125,7 @@ func importIo() *lua.LTable {
L.SetField(pkg, "MultiWriter", luar.New(L, io.MultiWriter))
L.SetField(pkg, "NewSectionReader", luar.New(L, io.NewSectionReader))
L.SetField(pkg, "Pipe", luar.New(L, io.Pipe))
L.SetField(pkg, "ReadAll", luar.New(L, io.ReadAll))
L.SetField(pkg, "ReadAtLeast", luar.New(L, io.ReadAtLeast))
L.SetField(pkg, "ReadFull", luar.New(L, io.ReadFull))
L.SetField(pkg, "TeeReader", luar.New(L, io.TeeReader))
@@ -370,6 +371,8 @@ func importOs() *lua.LTable {
L.SetField(pkg, "PathListSeparator", luar.New(L, os.PathListSeparator))
L.SetField(pkg, "PathSeparator", luar.New(L, os.PathSeparator))
L.SetField(pkg, "Pipe", luar.New(L, os.Pipe))
L.SetField(pkg, "ReadDir", luar.New(L, os.ReadDir))
L.SetField(pkg, "ReadFile", luar.New(L, os.ReadFile))
L.SetField(pkg, "Readlink", luar.New(L, os.Readlink))
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
@@ -388,6 +391,7 @@ func importOs() *lua.LTable {
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))
L.SetField(pkg, "WriteFile", luar.New(L, os.WriteFile))
return pkg
}
@@ -423,7 +427,6 @@ func importPath() *lua.LTable {
func importFilePath() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))

View File

@@ -14,7 +14,7 @@ import (
// The function must be called when the Screen is not initialized
// This will write the message, and wait for the user
// to press and key to continue
func TermMessage(msg ...interface{}) {
func TermMessage(msg ...any) {
screenb := TempFini()
fmt.Println(msg...)

View File

@@ -6,9 +6,8 @@ import (
"os"
"sync"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
"github.com/micro-editor/micro/v2/internal/config"
"github.com/micro-editor/tcell/v2"
)
// Screen is the tcell screen we use to draw to the terminal
@@ -33,6 +32,12 @@ var lock sync.Mutex
// written to even if no event user event has occurred
var drawChan chan bool
// rawSeq is the list of raw escape sequences that are bound to some actions
// via keybindings and thus should be parsed by tcell. We need to register
// them in tcell every time we reinitialize the screen, so we need to remember
// them in a list
var rawSeq = make([]string, 0)
// Lock locks the screen lock
func Lock() {
lock.Lock()
@@ -84,7 +89,7 @@ func ShowFakeCursor(x, y int) {
}
func UseFake() bool {
return util.FakeCursor || config.GetGlobalOption("fakecursor").(bool)
return config.GetGlobalOption("fakecursor").(bool)
}
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
@@ -121,6 +126,34 @@ func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
}
}
// RegisterRawSeq registers a raw escape sequence that should be parsed by tcell
func RegisterRawSeq(r string) {
for _, seq := range rawSeq {
if seq == r {
return
}
}
rawSeq = append(rawSeq, r)
if Screen != nil {
Screen.RegisterRawSeq(r)
}
}
// UnregisterRawSeq unregisters a raw escape sequence that should be parsed by tcell
func UnregisterRawSeq(r string) {
for i, seq := range rawSeq {
if seq == r {
rawSeq[i] = rawSeq[len(rawSeq)-1]
rawSeq = rawSeq[:len(rawSeq)-1]
}
}
if Screen != nil {
Screen.UnregisterRawSeq(r)
}
}
// TempFini shuts the screen down temporarily
func TempFini() bool {
screenWasNil := Screen == nil
@@ -150,10 +183,13 @@ func Init() error {
drawChan = make(chan bool, 8)
// Should we enable true color?
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
if !truecolor {
truecolor := config.GetGlobalOption("truecolor").(string)
if truecolor == "on" || (truecolor == "auto" && os.Getenv("MICRO_TRUECOLOR") == "1") {
os.Setenv("TCELL_TRUECOLOR", "enable")
} else if truecolor == "off" {
os.Setenv("TCELL_TRUECOLOR", "disable")
} else {
// For "auto", tcell already autodetects truecolor by default
}
var oldTerm string
@@ -195,6 +231,10 @@ func Init() error {
Screen.EnableMouse()
}
for _, r := range rawSeq {
Screen.RegisterRawSeq(r)
}
return nil
}

View File

@@ -24,17 +24,17 @@ func init() {
// JobFunction is a representation of a job (this data structure is what is loaded
// into the jobs channel)
type JobFunction struct {
Function func(string, []interface{})
Function func(string, []any)
Output string
Args []interface{}
Args []any
}
// A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events
type CallbackFile struct {
io.Writer
callback func(string, []interface{})
args []interface{}
callback func(string, []any)
args []any
}
// Job stores the executing command for the job, and the stdin pipe
@@ -53,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{}) *Job {
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []any), userargs ...any) *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{}) *Job {
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []any), userargs ...any) *Job {
// Set up everything correctly if the functions have been provided
proc := exec.Command(cmdName, cmdArgs...)
var outbuf bytes.Buffer
@@ -78,8 +78,10 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(
go func() {
// Run the process in the background and create the onExit callback
proc.Run()
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
Jobs <- jobFunc
if onExit != nil {
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
Jobs <- jobFunc
}
}()
return &Job{proc, stdin}

View File

@@ -10,8 +10,8 @@ import (
"os/signal"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/micro/v2/internal/util"
)
// ExecCommand executes a command using exec

View File

@@ -5,9 +5,9 @@ import (
"os/exec"
"strconv"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/terminal"
"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/terminal"
)
type TermType int
@@ -69,7 +69,7 @@ func (t *Terminal) GetSelection(width int) string {
}
// Start begins a new command in this terminal with a given view
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []any), userargs []any) error {
if len(execCmd) <= 0 {
return nil
}
@@ -130,7 +130,7 @@ func (t *Terminal) Close() {
if t.getOutput {
if t.callback != nil {
Jobs <- JobFunction{
Function: func(out string, args []interface{}) {
Function: func(out string, args []any) {
t.callback(out)
},
Output: t.output.String(),

View File

@@ -29,11 +29,27 @@ func isMark(r rune) bool {
// DecodeCharacter returns the next character from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCharacter(b []byte) (rune, []rune, int) {
combc, size := DecodeCombinedCharacter(b)
return combc[0], combc[1:], size
}
// DecodeCharacterInString returns the next character from a string
// A character is a rune along with any accompanying combining runes
func DecodeCharacterInString(str string) (rune, []rune, int) {
combc, size := DecodeCombinedCharacterInString(str)
return combc[0], combc[1:], size
}
// DecodeCombinedCharacter returns the next combined character
// from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCombinedCharacter(b []byte) ([]rune, int) {
var combc []rune
r, size := utf8.DecodeRune(b)
combc = append(combc, r)
b = b[size:]
c, s := utf8.DecodeRune(b)
var combc []rune
for isMark(c) {
combc = append(combc, c)
size += s
@@ -42,17 +58,18 @@ func DecodeCharacter(b []byte) (rune, []rune, int) {
c, s = utf8.DecodeRune(b)
}
return r, combc, size
return combc, size
}
// DecodeCharacterInString returns the next character from a string
// A character is a rune along with any accompanying combining runes
func DecodeCharacterInString(str string) (rune, []rune, int) {
// DecodeCombinedCharacterInString is the same as DecodeCombinedCharacter
// but for strings
func DecodeCombinedCharacterInString(str string) ([]rune, int) {
var combc []rune
r, size := utf8.DecodeRuneInString(str)
combc = append(combc, r)
str = str[size:]
c, s := utf8.DecodeRuneInString(str)
var combc []rune
for isMark(c) {
combc = append(combc, c)
size += s
@@ -61,7 +78,7 @@ func DecodeCharacterInString(str string) (rune, []rune, int) {
c, s = utf8.DecodeRuneInString(str)
}
return r, combc, size
return combc, size
}
// CharacterCount returns the number of characters in a byte array

View File

@@ -3,10 +3,13 @@ package util
import (
"archive/zip"
"bytes"
"crypto/md5"
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/url"
"os"
"os/user"
"path/filepath"
@@ -35,16 +38,51 @@ var (
CompileDate = "Unknown"
// Debug logging
Debug = "OFF"
// FakeCursor is used to disable the terminal cursor and have micro
// draw its own (enabled for windows consoles where the cursor is slow)
FakeCursor = false
// Stdout is a buffer that is written to stdout when micro closes
Stdout *bytes.Buffer
// Sigterm is a channel where micro exits when written
Sigterm chan os.Signal
// To be used for fails on (over-)write with safe writes
ErrOverwrite = OverwriteError{}
)
// To be used for file writes before umask is applied
const FileMode os.FileMode = 0666
const BackupSuffix = ".micro-backup"
const OverwriteFailMsg = `An error occurred while writing to the file:
%s
The file may be corrupted now. The good news is that it has been
successfully backed up. Next time you open this file with Micro,
Micro will ask if you want to recover it from the backup.
The backup path is:
%s`
// OverwriteError is a custom error to add additional information
type OverwriteError struct {
What error
BackupName string
}
func (e OverwriteError) Error() string {
return fmt.Sprintf(OverwriteFailMsg, e.What, e.BackupName)
}
func (e OverwriteError) Is(target error) bool {
return target == ErrOverwrite
}
func (e OverwriteError) Unwrap() error {
return e.What
}
func init() {
var err error
SemVersion, err = semver.Make(Version)
@@ -52,10 +90,6 @@ func init() {
fmt.Println("Invalid version: ", Version, err)
}
_, wt := os.LookupEnv("WT_SESSION")
if runtime.GOOS == "windows" && !wt {
FakeCursor = true
}
Stdout = new(bytes.Buffer)
}
@@ -233,18 +267,6 @@ func IsNonWordChar(r rune) bool {
return !IsWordChar(r)
}
// IsUpperWordChar returns whether or not a rune is an 'upper word character'
// Upper word characters are defined as numbers, upper-case letters or sub-word delimiters
func IsUpperWordChar(r rune) bool {
return IsUpperAlphanumeric(r) || IsSubwordDelimiter(r)
}
// IsLowerWordChar returns whether or not a rune is a 'lower word character'
// Lower word characters are defined as numbers, lower-case letters or sub-word delimiters
func IsLowerWordChar(r rune) bool {
return IsLowerAlphanumeric(r) || IsSubwordDelimiter(r)
}
// IsSubwordDelimiter returns whether or not a rune is a 'sub-word delimiter character'
// i.e. is considered a part of the word and is used as a delimiter between sub-words of the word.
// For now the only sub-word delimiter character is '_'.
@@ -332,6 +354,28 @@ func RunePos(b []byte, i int) int {
return CharacterCount(b[:i])
}
// IndexAnyUnquoted returns the first position in s of a character from chars.
// Escaped (with backslash) and quoted (with single or double quotes) characters
// are ignored. Returns -1 if not successful
func IndexAnyUnquoted(s, chars string) int {
var e bool
var q rune
for i, r := range s {
if e {
e = false
} else if (q == 0 || q == '"') && r == '\\' {
e = true
} else if r == q {
q = 0
} else if q == 0 && (r == '\'' || r == '"') {
q = r
} else if q == 0 && strings.IndexRune(chars, r) >= 0 {
return i
}
}
return -1
}
// MakeRelative will attempt to make a relative path between path and base
func MakeRelative(path, base string) (string, error) {
if len(path) > 0 {
@@ -398,8 +442,17 @@ func GetModTime(path string) (time.Time, error) {
return info.ModTime(), nil
}
// EscapePath replaces every path separator in a given path with a %
func EscapePath(path string) string {
func HashStringMd5(str string) string {
return fmt.Sprintf("%x", md5.Sum([]byte(str)))
}
// EscapePathUrl encodes the path in URL query form
func EscapePathUrl(path string) string {
return url.QueryEscape(filepath.ToSlash(path))
}
// EscapePathLegacy replaces every path separator in a given path with a %
func EscapePathLegacy(path string) string {
path = filepath.ToSlash(path)
if runtime.GOOS == "windows" {
// ':' is not valid in a path name on Windows but is ok on Unix
@@ -408,6 +461,34 @@ func EscapePath(path string) string {
return strings.ReplaceAll(path, "/", "%")
}
// DetermineEscapePath escapes a path, determining whether it should be escaped
// using URL encoding (preferred, since it encodes unambiguously) or
// legacy encoding with '%' (for backward compatibility, if the legacy-escaped
// path exists in the given directory).
// In case the length of the escaped path (plus the backup extension) exceeds
// the filename length limit, a hash of the path is returned instead. In such
// case the second return value is the name of a file the original path should
// be saved to (since the original path cannot be derived from its hash).
// Otherwise the second return value is an empty string.
func DetermineEscapePath(dir string, path string) (string, string) {
url := filepath.Join(dir, EscapePathUrl(path))
if _, err := os.Stat(url); err == nil {
return url, ""
}
legacy := filepath.Join(dir, EscapePathLegacy(path))
if _, err := os.Stat(legacy); err == nil {
return legacy, ""
}
if len(url)+len(BackupSuffix) > 255 {
hash := HashStringMd5(path)
return filepath.Join(dir, hash), filepath.Join(dir, hash+".path")
}
return url, ""
}
// GetLeadingWhitespace returns the leading whitespace of the given byte array
func GetLeadingWhitespace(b []byte) []byte {
ws := []byte{}
@@ -447,7 +528,7 @@ func HasTrailingWhitespace(b []byte) bool {
}
// IntOpt turns a float64 setting to an int
func IntOpt(opt interface{}) int {
func IntOpt(opt any) int {
return int(opt.(float64))
}
@@ -510,11 +591,6 @@ func IsAutocomplete(c rune) bool {
return c == '.' || IsWordChar(c)
}
// ParseSpecial replaces escaped ts with '\t'.
func ParseSpecial(s string) string {
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)
@@ -585,3 +661,77 @@ func HttpRequest(method string, url string, headers []string) (resp *http.Respon
}
return client.Do(req)
}
// SafeWrite writes bytes to a file in a "safe" way, preventing loss of the
// contents of the file if it fails to write the new contents.
// This means that the file is not overwritten directly but by writing to a
// temporary file first.
//
// If rename is true, write is performed atomically, by renaming the temporary
// file to the target file after the data is successfully written to the
// temporary file. This guarantees that the file will not remain in a corrupted
// state, but it also has limitations, e.g. the file should not be a symlink
// (otherwise SafeWrite silently replaces this symlink with a regular file),
// the file creation date in Linux is not preserved (since the file inode
// changes) etc. Use SafeWrite with rename=true for files that are only created
// and used by micro for its own needs and are not supposed to be used directly
// by the user.
//
// If rename is false, write is performed by overwriting the target file after
// the data is successfully written to the temporary file.
// This means that the target file may remain corrupted if overwrite fails,
// but in such case the temporary file is preserved as a backup so the file
// can be recovered later. So it is less convenient than atomic write but more
// universal. Use SafeWrite with rename=false for files that may be managed
// directly by the user, like settings.json and bindings.json.
func SafeWrite(path string, bytes []byte, rename bool) error {
var err error
if _, err = os.Stat(path); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
// Force rename for new files!
rename = true
}
var file *os.File
if !rename {
file, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE, FileMode)
if err != nil {
return err
}
defer file.Close()
}
tmp := path + BackupSuffix
err = os.WriteFile(tmp, bytes, FileMode)
if err != nil {
os.Remove(tmp)
return err
}
if rename {
err = os.Rename(tmp, path)
} else {
err = file.Truncate(0)
if err == nil {
_, err = file.Write(bytes)
}
if err == nil {
err = file.Sync()
}
}
if err != nil {
if rename {
os.Remove(tmp)
} else {
err = OverwriteError{err, tmp}
}
return err
}
if !rename {
os.Remove(tmp)
}
return nil
}

View File

@@ -439,11 +439,12 @@ func (n *Node) VSplit(right bool) uint64 {
}
// unsplits the child of a split
func (n *Node) unsplit(i int, h bool) {
func (n *Node) unsplit(i int) {
copy(n.children[i:], n.children[i+1:])
n.children[len(n.children)-1] = nil
n.children = n.children[:len(n.children)-1]
h := n.Kind == STVert
nonrs, numr := n.getResizeInfo(h)
if numr == 0 {
// This means that this was the last child
@@ -470,18 +471,62 @@ func (n *Node) Unsplit() bool {
ind = i
}
}
if n.parent.Kind == STVert {
n.parent.unsplit(ind, true)
} else {
n.parent.unsplit(ind, false)
}
n.parent.unsplit(ind)
if n.parent.IsLeaf() {
return n.parent.Unsplit()
}
n.parent.flatten()
return true
}
// flattens the tree by removing unnecessary intermediate parents that have only one child
// and handles the side effect of it
func (n *Node) flatten() {
if n.parent == nil || len(n.children) != 1 {
return
}
ind := 0
for i, c := range n.parent.children {
if c.id == n.id {
ind = i
}
}
// Replace current node with its child node to remove chained parent
successor := n.children[0]
n.parent.children[ind] = successor
successor.parent = n.parent
// Maintain the tree in a consistent state: any child node's kind (horiz vs vert)
// should be the opposite of its parent's kind.
if successor.IsLeaf() {
successor.Kind = n.Kind
} else {
// If the successor node has children, that means it is a chained parent as well.
// Therefore it can be replaced by its own children.
origsize := len(n.parent.children)
// Let's say we have 5 children and want to replace [2] with its children [a] [b] [c]
// [0] [1] [2] [3] [4] --> [0] [1] [a] [b] [c] [3] [4]
// insertcount will be `3 - 1 = 2` in this case
insertcount := len(successor.children) - 1
n.parent.children = append(n.parent.children, make([]*Node, insertcount)...)
copy(n.parent.children[ind+insertcount+1:], n.parent.children[ind+1:origsize])
copy(n.parent.children[ind:], successor.children)
for i := 0; i < len(successor.children); i++ {
n.parent.children[ind+i].parent = n.parent
}
}
// Update propW and propH since the parent of the children has been updated,
// so the children have new siblings
n.parent.markSizes()
}
// String returns the string form of the node and all children (used for debugging)
func (n *Node) String() string {
var strf func(n *Node, ident int) string

View File

@@ -51,19 +51,6 @@ func runePos(p int, str []byte) int {
return CharacterCount(str[:p])
}
func combineLineMatch(src, dst LineMatch) LineMatch {
for k, v := range src {
if g, ok := dst[k]; ok {
if g == 0 {
dst[k] = v
}
} else {
dst[k] = v
}
}
return dst
}
// A State represents the region at the end of a line
type State *region
@@ -175,7 +162,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
if curRegion.group == curRegion.limitGroup || p.group == curRegion.limitGroup {
matches := findAllIndex(p.regex, line)
for _, m := range matches {
if ((endLoc == nil) || (m[0] < endLoc[0])) {
if (endLoc == nil) || (m[0] < endLoc[0]) {
for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
}

View File

@@ -54,7 +54,7 @@ type HeaderYaml struct {
type File struct {
FileType string
yamlSrc map[interface{}]interface{}
yamlSrc map[any]any
}
// A Pattern is one simple syntax rule
@@ -197,7 +197,7 @@ func ParseFile(input []byte) (f *File, err error) {
}
}()
var rules map[interface{}]interface{}
var rules map[any]any
if err = yaml.Unmarshal(input, &rules); err != nil {
return nil, err
}
@@ -245,7 +245,7 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
for k, v := range src {
if k == "rules" {
inputRules := v.([]interface{})
inputRules := v.([]any)
rules, err := parseRules(inputRules, nil)
if err != nil {
@@ -258,7 +258,7 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
if s.rules == nil {
// allow empty rules
s.rules = new(rules)
s.rules = &rules{}
}
return s, err
@@ -336,7 +336,7 @@ func resolveIncludesInRegion(files []*File, region *region) {
}
}
func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
func parseRules(input []any, curRegion *region) (ru *rules, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
@@ -349,7 +349,7 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
ru = new(rules)
for _, v := range input {
rule := v.(map[interface{}]interface{})
rule := v.(map[any]any)
for k, val := range rule {
group := k
@@ -376,7 +376,7 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
groupNum := Groups[groupStr]
ru.patterns = append(ru.patterns, &pattern{groupNum, r})
}
case map[interface{}]interface{}:
case map[any]any:
// region
region, err := parseRegion(group.(string), object, curRegion)
if err != nil {
@@ -392,7 +392,7 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
return ru, nil
}
func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegion *region) (r *region, err error) {
func parseRegion(group string, regionInfo map[any]any, prevRegion *region) (r *region, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
@@ -476,10 +476,17 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
r.limitGroup = r.group
}
r.rules, err = parseRules(regionInfo["rules"].([]interface{}), r)
// rules are optional
if rules, ok := regionInfo["rules"]; ok {
r.rules, err = parseRules(rules.([]any), r)
if err != nil {
return nil, err
}
}
if err != nil {
return nil, err
if r.rules == nil {
// allow empty rules
r.rules = &rules{}
}
return r, nil

View File

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

View File

@@ -47,10 +47,7 @@ color support comes in three flavors.
displaying any colorscheme, but it should be noted that the user-configured
16-color palette is ignored when using true-color mode (this means the
colors while using the terminal emulator will be slightly off). Not all
terminals support true color but at this point most do. True color
support in micro is off by default but can be enabled by setting the
environment variable `MICRO_TRUECOLOR` to 1. In addition your terminal
must support it (usually indicated by setting `$COLORTERM` to `truecolor`).
terminals support true color but at this point most do (see below).
True-color colorschemes in micro typically end with `-tc`, such as
`solarized-tc`, `atom-dark`, `material-tc`, etc... If true color is not
enabled but a true color colorscheme is used, micro will do its best to
@@ -84,11 +81,12 @@ These may vary widely based on the 16 colors selected for your terminal.
### True color
True color requires your terminal to support it. This means that the
environment variable `COLORTERM` should have the value `truecolor`, `24bit`,
or `24-bit`. In addition, to enable true color in micro, the environment
variable `MICRO_TRUECOLOR` must be set to 1. Note that you have to create
and set this variable yourself.
Micro enables true color support by default as long as it detects that the
terminal supports it (which is usually indicated by the environment variable
`COLORTERM` being set to `truecolor`, `24bit` or `24-bit`). You can also force
enabling it unconditionally by setting the option `truecolor` to `on` (or
alternatively by setting the environment variable `MICRO_TRUECOLOR` to 1, which
is supported for backward compatibility).
* `solarized-tc`: this is the solarized colorscheme for true color.
* `atom-dark`: this colorscheme is based off of Atom's "dark" colorscheme.
@@ -101,7 +99,7 @@ and set this variable yourself.
Micro's colorschemes are also extremely simple to create. The default ones can
be found
[here](https://github.com/zyedidia/micro/tree/master/runtime/colorschemes).
[here](https://github.com/micro-editor/micro/tree/master/runtime/colorschemes).
Custom colorschemes should be placed in the `~/.config/micro/colorschemes`
directory.
@@ -177,10 +175,14 @@ Here is a list of the colorscheme groups that you can use:
* todo
* selection (Color of the text selection)
* statusline (Color of the statusline)
* statusline.inactive (Color of the statusline of inactive split panes)
* statusline.suggestions (Color of the autocomplete suggestions menu)
* tabbar (Color of the tabbar that lists open files)
* tabbar.active (Color of the active tab in the tabbar)
* indent-char (Color of the character which indicates tabs if the option is
enabled)
* line-number
* gutter-info
* gutter-error
* gutter-warning
* diff-added
@@ -371,7 +373,6 @@ highlighted. For example:
start: "\""
end: "\""
skip: "\\."
rules: []
```
#### Includes

View File

@@ -21,10 +21,16 @@ quotes here but these are not necessary when entering the command in micro.
This command will modify `bindings.json` and overwrite any bindings to
`key` that already exist.
* `help ['topic']`: opens the corresponding help topic. If no topic is provided
opens the default help screen. Help topics are stored as `.md` files in the
`runtime/help` directory of the source tree, which is embedded in the final
binary.
* `help ['topic'] ['flags']`: opens the corresponding help topics.
If no topic is provided opens the default help screen. If multiple topics are
provided (separated via ` `) they are opened all as splits.
Help topics are stored as `.md` files in the `runtime/help` directory of
the source tree, which is embedded in the final binary.
The `flags` are optional.
* `-hsplit`: Opens the help topic in a horizontal split
* `-vsplit`: Opens the help topic in a vertical split
The default split type is defined by the global `helpsplit` option.
* `save ['filename']`: saves the current buffer. If the file is provided it
will 'save as' the filename.
@@ -66,18 +72,33 @@ quotes here but these are not necessary when entering the command in micro.
* `setlocal 'option' 'value'`: sets the option to value locally (only in the
current buffer). This will *not* modify `settings.json`.
* `toggle 'option'`: toggles the option. Only works with options that accept
exactly two values. This will modify your `settings.json` with the new value.
* `togglelocal 'option'`: toggles the option locally (only in the
current buffer). Only works with options that accept exactly two values.
This will *not* modify `settings.json`.
* `reset 'option'`: resets the given option to its default value.
* `show 'option'`: shows the current value of the given option.
* `showkey 'key'`: Show the action(s) bound to a given key. For example
running `> showkey Ctrl-c` will display `Copy`.
* `run 'sh-command'`: runs the given shell command in the background. The
command's output will be displayed in one line when it finishes running.
* `vsplit ['filename']`: opens a vertical split with `filename`. If no filename
is provided, a vertical split is opened with an empty buffer.
is provided, a vertical split is opened with an empty buffer. If multiple
files are provided (separated via ` `) they are opened all as splits.
* `hsplit ['filename']`: same as `vsplit` but opens a horizontal split instead
of a vertical split.
* `tab ['filename']`: opens the given file in a new tab.
* `tab ['filename']`: opens the given file in a new tab. If no filename
is provided, a tab is opened with an empty buffer. If multiple files are
provided (separated via ` `) they are opened all as tabs.
* `tabmove '[-+]n'`: Moves the active tab to another slot. `n` is an integer.
If `n` is prefixed with `-` or `+`, then it represents a relative position
@@ -120,8 +141,6 @@ quotes here but these are not necessary when entering the command in micro.
* `reopen`: Reopens the current file from disk.
* `reset 'option'`: resets the given option to its default value
* `retab`: Replaces all leading tabs with spaces or leading spaces with tabs
depending on the value of `tabstospaces`.
@@ -130,9 +149,6 @@ quotes here but these are not necessary when entering the command in micro.
the terminal and helps you see which bindings aren't possible and why. This
is most useful for debugging keybindings.
* `showkey 'key'`: Show the action(s) bound to a given key. For example
running `> showkey Ctrl-c` will display `Copy`.
* `term ['exec']`: Open a terminal emulator running the given executable. If no
executable is given, this will open the default shell in the terminal
emulator.

View File

@@ -23,7 +23,7 @@ Here is a list of terminal emulators and their status:
* `st`: supported.
* `rxvt-unicode`: not natively supported, but there is a Perl extension
[here](http://anti.teamidiot.de/static/nei/*/Code/urxvt/).
[here](https://anti.teamidiot.de/static/nei/*/Code/urxvt/).
* `xterm`: supported, but disabled by default. It can be enabled by putting
the following in `.Xresources` or `.Xdefaults`:

View File

@@ -66,7 +66,16 @@ bindings, tab is bound as
This means that if the `Autocomplete` action is successful, the chain will
abort. Otherwise, it will try `IndentSelection`, and if that fails too, it
will execute `InsertTab`.
will execute `InsertTab`. To use `,`, `|` or `&` in an action (as an argument
to a command, for example), escape it with `\` or wrap it in single or double
quotes.
If the action has an `onAction` lua callback, for example `onAutocomplete` (see
`> help plugins`), then the action is only considered successful if the action
itself succeeded *and* the callback returned true. If there are multiple
`onAction` callbacks for this action, registered by multiple plugins, then the
action is only considered successful if the action itself succeeded and all the
callbacks returned true.
## Binding commands
@@ -103,6 +112,48 @@ Now when you press `Ctrl-g`, `help` will appear in the command bar and your
cursor will be placed after it (note the space in the json that controls the
cursor placement).
## Binding Lua functions
You can also bind a key to a Lua function provided by a plugin, or by your own
`~/.config/micro/init.lua`. For example:
```json
{
"Alt-q": "lua:foo.bar"
}
```
where `foo` is the name of the plugin and `bar` is the name of the lua function
in it, e.g.:
```lua
local micro = import("micro")
function bar(bp)
micro.InfoBar():Message("Bar action triggered")
return true
end
```
See `> help plugins` for more informations on how to write lua functions.
For `~/.config/micro/init.lua` the plugin name is `initlua` (so the keybinding
in this example would be `"Alt-q": "lua:initlua.bar"`).
The currently active bufpane is passed to the lua function as the argument. If
the key is a mouse button, e.g. `MouseLeft` or `MouseWheelUp`, the mouse event
info is passed to the lua function as the second argument, of type
`*tcell.EventMouse`. See https://pkg.go.dev/github.com/micro-editor/tcell/v2#EventMouse
for the description of this type and its methods.
The return value of the lua function defines whether the action has succeeded.
This is used when chaining lua functions with other actions. They can be chained
the same way as regular actions as described above, for example:
```
"Alt-q": "lua:initlua.bar|Quit"
```
## Binding raw escape sequences
Only read this section if you are interested in binding keys that aren't on the
@@ -168,14 +219,15 @@ CursorLeft
CursorRight
CursorStart
CursorEnd
CursorToViewTop
CursorToViewCenter
CursorToViewBottom
SelectToStart
SelectToEnd
SelectUp
SelectDown
SelectLeft
SelectRight
SelectToStartOfText
SelectToStartOfTextToggle
WordRight
WordLeft
SubWordRight
@@ -184,20 +236,22 @@ SelectWordRight
SelectWordLeft
SelectSubWordRight
SelectSubWordLeft
MoveLinesUp
MoveLinesDown
DeleteWordRight
DeleteWordLeft
DeleteSubWordRight
DeleteSubWordLeft
SelectLine
SelectToStartOfLine
SelectToStartOfText
SelectToStartOfTextToggle
SelectToEndOfLine
ParagraphPrevious
ParagraphNext
SelectToParagraphPrevious
SelectToParagraphNext
InsertNewline
InsertSpace
Backspace
Delete
Center
InsertTab
Save
SaveAll
@@ -206,21 +260,28 @@ Find
FindLiteral
FindNext
FindPrevious
DiffPrevious
DiffNext
DiffPrevious
Center
Undo
Redo
Copy
CopyLine
Cut
CutLine
Duplicate
DuplicateLine
DeleteLine
MoveLinesUp
MoveLinesDown
IndentSelection
OutdentSelection
Autocomplete
CycleAutocompleteBack
OutdentLine
IndentLine
Paste
PastePrimary
SelectAll
OpenFile
Start
@@ -231,33 +292,37 @@ SelectPageUp
SelectPageDown
HalfPageUp
HalfPageDown
StartOfLine
EndOfLine
StartOfText
StartOfTextToggle
ParagraphPrevious
ParagraphNext
SelectToParagraphPrevious
SelectToParagraphNext
StartOfLine
EndOfLine
ToggleHelp
ToggleKeyMenu
ToggleDiffGutter
ToggleRuler
JumpLine
ToggleHighlightSearch
UnhighlightSearch
ResetSearch
ClearInfo
ClearStatus
ShellMode
CommandMode
ToggleOverwriteMode
Escape
Quit
QuitAll
ForceQuit
AddTab
PreviousTab
NextTab
FirstTab
LastTab
NextSplit
PreviousSplit
FirstSplit
LastSplit
Unsplit
VSplit
HSplit
PreviousSplit
ToggleMacro
PlayMacro
Suspend (Unix only)
@@ -270,18 +335,31 @@ SpawnMultiCursorSelect
RemoveMultiCursor
RemoveAllMultiCursors
SkipMultiCursor
None
SkipMultiCursorBack
JumpToMatchingBrace
Autocomplete
JumpLine
Deselect
ClearInfo
None
```
The `StartOfTextToggle` and `SelectToStartOfTextToggle` actions toggle between
jumping to the start of the text (first) and start of the line.
The `CutLine` action cuts the current line and adds it to the previously cut
lines in the clipboard since the last paste (rather than just replaces the
clipboard contents with this line). So you can cut multiple, not necessarily
consecutive lines to the clipboard just by pressing `Ctrl-k` multiple times,
without selecting them. If you want the more traditional behavior i.e. just
rewrite the clipboard every time, you can use `CopyLine,DeleteLine` action
instead of `CutLine`.
You can also bind some mouse actions (these must be bound to mouse buttons)
```
MousePress
MouseDrag
MouseRelease
MouseMultiCursor
```
@@ -495,23 +573,23 @@ conventions for text editing defaults.
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-k": "CutLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-d": "Duplicate|DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
"Alt-,": "PreviousTab",
"Alt-.": "NextTab",
"Alt-,": "PreviousTab|LastTab",
"Alt-.": "NextTab|FirstTab",
"Home": "StartOfText",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlPageUp": "PreviousTab|LastTab",
"CtrlPageDown": "NextTab|FirstTab",
"ShiftPageUp": "SelectPageUp",
"ShiftPageDown": "SelectPageDown",
"Ctrl-g": "ToggleHelp",
@@ -522,7 +600,7 @@ conventions for text editing defaults.
"Ctrl-b": "ShellMode",
"Ctrl-q": "Quit",
"Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit",
"Ctrl-w": "NextSplit|FirstSplit",
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
@@ -621,7 +699,7 @@ are given below:
"Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-c": "Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-v": "Paste",

View File

@@ -79,22 +79,16 @@ Here are the available options:
default value: `0`
* `colorscheme`: loads the colorscheme stored in
$(configDir)/colorschemes/`option`.micro, This setting is `global only`.
* `colorscheme`: use the given colorscheme. This setting is `global only`.
The colorscheme can be either one of the colorschemes that micro comes with
by default (such as `default`, `solarized` or `solarized-tc`) which are
embedded in the micro binary, or a custom colorscheme stored in
`~/.config/micro/colorschemes/$(option).micro` where `$(option)` is the
option value. You can read more about micro's colorschemes and see the list
of default colorschemes in `> help colors`.
default value: `default`
Note that the default colorschemes (default, solarized, and solarized-tc)
are not located in configDir, because they are embedded in the micro
binary.
The colorscheme can be selected from all the files in the
~/.config/micro/colorschemes/ directory. Micro comes by default with
three colorschemes:
You can read more about micro's colorschemes in the `colors` help topic
(`help colors`).
* `cursorline`: highlight the line that the cursor is on in a different color
(the color is defined by the colorscheme you are using).
@@ -139,6 +133,8 @@ Here are the available options:
* `fakecursor`: forces micro to render the cursor using terminal colors rather
than the actual terminal cursor. This is useful when the terminal's cursor is
slow or otherwise unavailable/undesirable to use.
Note: This option defaults to `true` in case `micro` is used in the legacy
Windows Console.
default value: `false`
@@ -148,8 +144,8 @@ Here are the available options:
This is fast, but can be inaccurate. If `fastdirty` is off, then micro will
hash the current buffer against a hash of the original file (created when
the buffer was loaded). This is more accurate but obviously more resource
intensive. This option will be automatically disabled if the file size
exceeds 50KB.
intensive. This option will be automatically enabled for the current buffer
if the file size exceeds 50KB.
default value: `false`
@@ -172,6 +168,13 @@ Here are the available options:
default value: `unknown`. This will be automatically overridden depending
on the file you open.
* `helpsplit`: sets the split type to be used by the `help` command.
Possible values:
* `vsplit`: open help in a vertical split pane
* `hsplit`: open help in a horizontal split pane
default value: `hsplit`
* `hlsearch`: highlight all instances of the searched text after a successful
search. This highlighting can be temporarily turned off via the
`UnhighlightSearch` action (triggered by the Esc key by default) or toggled
@@ -194,20 +197,16 @@ Here are the available options:
default value: `false`
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
default value: `true`
* `ignorecase`: perform case-insensitive searches.
default value: `true`
* `indentchar`: sets the indentation character. This will not be inserted into
files; it is only a visual indicator that whitespace is present. If set to a
printing character, it functions as a subset of the "show invisibles"
setting available in many other text editors. The color of this character is
determined by the `indent-char` field in the current theme rather than the
default text color.
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
default value: `true`
* `indentchar`: sets the character to be shown to display tab characters.
This option is **deprecated**, use the `tab` key in `showchars` option instead.
default value: ` ` (space)
@@ -230,6 +229,12 @@ Here are the available options:
default value: `false`
* `lockbindings`: prevent plugins and lua scripts from binding any keys.
Any custom actions must be binded manually either via commands like `bind`
or by modifying the `bindings.json` file.
default value: `false`
* `matchbrace`: show matching braces for '()', '{}', '[]' when the cursor
is on a brace character or (if `matchbraceleft` is enabled) next to it.
@@ -278,6 +283,23 @@ Here are the available options:
default value: `tab`
* `pageoverlap`: the number of lines from the current view to keep in view
when paging up or down. If this is set to 2, for instance, and you page
down, the last two lines of the previous page will be the first two lines
of the next page.
default value: `2`
* `parsecursor`: if enabled, this will cause micro to parse filenames such as
`file.txt:10:5` as requesting to open `file.txt` with the cursor at line 10
and column 5. The column number can also be dropped to open the file at a
given line and column 0. Note that with this option enabled it is not possible
to open a file such as `file.txt:10:5`, where `:10:5` is part of the filename.
It is also possible to open a file with a certain cursor location by using the
`+LINE:COL` flag syntax. See `micro -help` for the command line options.
default value: `false`
* `paste`: treat characters sent from the terminal in a single chunk as a paste
event rather than a series of manual key presses. If you are pasting using
the terminal keybinding (not `Ctrl-v`, which is micro's default paste
@@ -287,16 +309,6 @@ Here are the available options:
default value: `false`
* `parsecursor`: if enabled, this will cause micro to parse filenames such as
file.txt:10:5 as requesting to open `file.txt` with the cursor at line 10
and column 5. The column number can also be dropped to open the file at a
given line and column 0. Note that with this option enabled it is not possible
to open a file such as `file.txt:10:5`, where `:10:5` is part of the filename.
It is also possible to open a file with a certain cursor location by using the
`+LINE:COL` flag syntax. See `micro -help` for the command line options.
default value: `false`
* `permbackup`: this option causes backups (see `backup` option) to be
permanently saved. With permanent backups, micro will not remove backups when
files are closed and will never apply them to existing files. Use this option
@@ -321,6 +333,12 @@ Here are the available options:
default value: `false`
* `relativeruler`: make line numbers display relatively. If set to true, all
lines except for the line that the cursor is located will display the distance
from the cursor's line.
default value: `false`
* `reload`: controls the reload behavior of the current buffer in case the file
has changed. The available options are `prompt`, `auto` & `disabled`.
@@ -338,12 +356,6 @@ Here are the available options:
default value: `true`
* `relativeruler`: make line numbers display relatively. If set to true, all
lines except for the line that the cursor is located will display the distance
from the cursor's line.
default value: `false`
* `savecursor`: remember where the cursor was last time the file was opened and
put it there when you open the file again. Information is saved to
`~/.config/micro/buffers/`
@@ -378,6 +390,29 @@ Here are the available options:
default value: `2`
* `showchars`: sets what characters to be shown to display various invisible
characters in the file. The characters shown will not be inserted into files.
This option is specified in the form of `key1=value1,key2=value2,...`.
Here are the list of keys:
- `space`: space characters
- `tab`: tab characters. If set, overrides the `indentchar` option.
- `ispace`: space characters at indent position before the first visible
character in a line. If this is not set, `space` will be shown
instead.
- `itab`: tab characters before the first visible character in a line.
If this is not set, `tab` will be shown instead.
Only `tab` and `itab` can display multiple characters (if possible),
otherwise only the first character will be displayed.
An example of this option value could be `tab=>,space=.,itab=|>,ispace=|`
The color of the shown character is determined by the `indent-char`
field in the current theme rather than the default text color.
default value: ``
* `smartpaste`: add leading whitespace when pasting multiple lines.
This will attempt to preserve the current indentation level when pasting an
unindented block.
@@ -401,11 +436,11 @@ Here are the available options:
* `statusformatl`: format string definition for the left-justified part of the
statusline. Special directives should be placed inside `$()`. Special
directives include: `filename`, `modified`, `line`, `col`, `lines`,
`percentage`, `opt`, `bind`.
`percentage`, `opt`, `overwrite`, `bind`.
The `opt` and `bind` directives take either an option or an action afterward
and fill in the value of the option or the key bound to the action.
default value: `$(filename) $(modified)($(line),$(col)) $(status.paste)|
default value: `$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)|
ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)`
* `statusformatr`: format string definition for the right-justified part of the
@@ -427,33 +462,46 @@ Here are the available options:
default value: `true`
* `tabhighlight`: inverts the tab characters' (filename, save indicator, etc)
colors with respect to the tab bar.
default value: `false`
* `tabmovement`: navigate spaces at the beginning of lines as if they are tabs
(e.g. move over 4 spaces at once). This option only does anything if
`tabstospaces` is on.
default value: `false`
* `tabhighlight`: inverts the tab characters' (filename, save indicator, etc)
colors with respect to the tab bar.
default value: false
* `tabreverse`: reverses the tab bar colors when active.
default value: true
default value: `true`
* `tabsize`: the size in spaces that a tab character should be displayed with.
default value: `4`
* `tabstospaces`: use spaces instead of tabs. Note: This option will be
overridden by [the `ftoptions` plugin](https://github.com/zyedidia/micro/blob/master/runtime/plugins/ftoptions/ftoptions.lua)
overridden by [the `ftoptions` plugin](https://github.com/micro-editor/micro/blob/master/runtime/plugins/ftoptions/ftoptions.lua)
for certain filetypes. To disable this behavior, add `"ftoptions": false` to
your config. See [issue #2213](https://github.com/zyedidia/micro/issues/2213)
your config. See [issue #2213](https://github.com/micro-editor/micro/issues/2213)
for more details.
default value: `false`
* `truecolor`: controls whether micro will use true colors (24-bit colors) when
using a colorscheme with true colors, such as `solarized-tc` or `atom-dark`.
* `auto`: enable usage of true color if micro detects that it is supported by
the terminal, otherwise disable it.
* `on`: force usage of true color even if micro does not detect its support
by the terminal (of course this is not guaranteed to work well unless the
terminal actually supports true color).
* `off`: disable true color usage.
Note: The change will take effect after the next start of `micro`.
default value: `auto`
* `useprimary` (only useful on unix): defines whether or not micro will use the
primary clipboard to copy selections in the background. This does not affect
the normal clipboard using `Ctrl-c` and `Ctrl-v`.
@@ -493,13 +541,13 @@ or disable them:
recent Git commit rather than the diff since opening the file.
Any option you set in the editor will be saved to the file
~/.config/micro/settings.json so, in effect, your configuration file will be
`~/.config/micro/settings.json` so, in effect, your configuration file will be
created for you. If you'd like to take your configuration with you to another
machine, simply copy the settings.json to the other machine.
machine, simply copy the `settings.json` to the other machine.
## Settings.json file
The settings.json file should go in your configuration directory (by default
The `settings.json` file should go in your configuration directory (by default
at `~/.config/micro`), and should contain only options which have been modified
from their default setting. Here is the full list of options in json format,
so that you can see what the formatting should look like.
@@ -518,18 +566,24 @@ so that you can see what the formatting should look like.
"colorscheme": "default",
"comment": true,
"cursorline": true,
"detectlimit": 100,
"diff": true,
"diffgutter": false,
"divchars": "|-",
"divreverse": true,
"encoding": "utf-8",
"eofnewline": true,
"fakecursor": false,
"fastdirty": false,
"fileformat": "unix",
"filetype": "unknown",
"incsearch": true,
"ftoptions": true,
"helpsplit": "hsplit",
"hlsearch": false,
"hltaberrors": false,
"hltrailingws": false,
"ignorecase": true,
"incsearch": true,
"indentchar": " ",
"infobar": true,
"initlua": true,
@@ -542,6 +596,8 @@ so that you can see what the formatting should look like.
"matchbracestyle": "underline",
"mkparents": false,
"mouse": true,
"multiopen": "tab",
"pageoverlap": 2,
"parsecursor": false,
"paste": false,
"permbackup": false,
@@ -551,30 +607,34 @@ so that you can see what the formatting should look like.
"pluginrepos": [],
"readonly": false,
"relativeruler": false,
"reload": "prompt",
"rmtrailingws": false,
"ruler": true,
"savecursor": false,
"savehistory": true,
"saveundo": false,
"scrollbar": false,
"scrollbarchar": "|",
"scrollmargin": 3,
"scrollspeed": 2,
"showchars": "",
"smartpaste": true,
"softwrap": false,
"splitbottom": true,
"splitright": true,
"status": true,
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
"statusformatl": "$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
"statusline": true,
"sucmd": "sudo",
"syntax": true,
"tabmovement": false,
"tabhighlight": true,
"tabmovement": false,
"tabreverse": false,
"tabsize": 4,
"tabstospaces": false,
"useprimary": true,
"wordwrap": false,
"xterm": false
}
```

View File

@@ -57,11 +57,13 @@ that micro defines:
* `deinit()`: cleanup function called when your plugin is unloaded or reloaded.
* `onSetActive(bufpane)`: runs when changing the currently active panel.
* `onBufferOpen(buf)`: runs when a buffer is opened. The input contains
the buffer object.
* `onBufferOptionChanged(buf, option, old, new)`: runs when an option of the
buffer has changed. The input contains the buffer object, the option name,
the old and the new value.
* `onBufPaneOpen(bufpane)`: runs when a bufpane is opened. The input
contains the bufpane object.
@@ -69,13 +71,24 @@ that micro defines:
* `onAction(bufpane)`: runs when `Action` is triggered by the user, where
`Action` is a bindable action (see `> help keybindings`). A bufpane
is passed as input and the function should return a boolean defining
whether the view should be relocated after this action is performed.
is passed as input. The function should return a boolean defining
whether the action was successful, which is used when the action is
chained with other actions (see `> help keybindings`) to determine whether
the next actions in the chain should be executed or not.
If the action is a mouse action, e.g. `MousePress`, the mouse event info
is passed to the callback as an extra argument of type `*tcell.EventMouse`.
See https://pkg.go.dev/github.com/micro-editor/tcell/v2#EventMouse for the
description of this type and its methods.
* `preAction(bufpane)`: runs immediately before `Action` is triggered
by the user. Returns a boolean which defines whether the action should
be canceled.
Similarly to `onAction`, if the action is a mouse action, the mouse event
info is passed to the callback as an extra argument of type
`*tcell.EventMouse`.
* `onRune(bufpane, rune)`: runs when the composed rune has been inserted
* `preRune(bufpane, rune)`: runs before the composed rune will be inserted
@@ -99,9 +112,6 @@ within. This is almost always the current bufpane.
All available actions are listed in the keybindings section of the help.
These functions should also return a boolean specifying whether the bufpane
should be relocated to the cursor or not after the action is complete.
## Accessing micro functions
Some of micro's internal information is exposed in the form of packages, which
@@ -116,7 +126,7 @@ micro.Log("Hello")
The packages and their contents are listed below (in Go type signatures):
* `micro`
- `TermMessage(msg interface{}...)`: temporarily close micro and print a
- `TermMessage(msg any...)`: temporarily close micro and print a
message
- `TermError(filename string, lineNum int, err string)`: temporarily close
@@ -124,7 +134,7 @@ The packages and their contents are listed below (in Go type signatures):
- `InfoBar() *InfoPane`: return the infobar BufPane object.
- `Log(msg interface{}...)`: write a message to `log.txt` (requires
- `Log(msg any...)`: write a message to `log.txt` (requires
`-debug` flag, or binary built with `build-dbg`).
- `SetStatusInfoFn(fn string)`: register the given lua function as
@@ -143,11 +153,10 @@ The packages and their contents are listed below (in Go type signatures):
Relevant links:
[Time](https://pkg.go.dev/time#Duration)
[BufPane](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#BufPane)
[InfoPane](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#InfoPane)
[Tab](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#Tab)
[TabList](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#TabList)
[interface{} / any](https://go.dev/tour/methods/14)
[BufPane](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#BufPane)
[InfoPane](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#InfoPane)
[Tab](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#Tab)
[TabList](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#TabList)
* `micro/config`
- `MakeCommand(name string, action func(bp *BufPane, args[]string),
@@ -165,11 +174,12 @@ The packages and their contents are listed below (in Go type signatures):
values afterwards
- `NoComplete`: no autocompletion suggestions
- `TryBindKey(k, v string, overwrite bool) (bool, error)`: bind the key
`k` to the string `v` in the `bindings.json` file. If `overwrite` is
true, this will overwrite any existing binding to key `k`. Returns true
if the binding was made, and a possible error (for example writing to
`bindings.json` can cause an error).
- `TryBindKey(k, v string, overwrite bool) (bool, error)`:
bind the key `k` to the string `v`. If `overwrite` is true, this will
overwrite any existing binding to key `k`.
Returns true if the binding was made, and a possible error.
This operation can be rejected by `lockbindings` to prevent undesired
actions by the user.
- `Reload()`: reload configuration files.
@@ -200,26 +210,26 @@ The packages and their contents are listed below (in Go type signatures):
- `RTHelp`: runtime files for help documents.
- `RTPlugin`: runtime files for plugin source code.
- `RegisterCommonOption(pl string, name string, defaultvalue interface{})`:
- `RegisterCommonOption(pl string, name string, defaultvalue any)`:
registers a new option for the given plugin. The name of the
option will be `pl.name`, and will have the given default value. Since
this registers a common option, the option will be modifiable on a
per-buffer basis, while also having a global value (in the
GlobalSettings map).
- `RegisterGlobalOption(pl string, name string, defaultvalue interface{})`:
- `RegisterGlobalOption(pl string, name string, defaultvalue any)`:
same as `RegisterCommonOption`, but the option cannot be modified
locally to each buffer.
- `GetGlobalOption(name string) interface{}`: returns the value of a
- `GetGlobalOption(name string) any`: returns the value of a
given plugin in the `GlobalSettings` map.
- `SetGlobalOption(option, value string) error`: sets an option to a
given value. Same as using the `> set` command. This will try to convert
the value into the proper type for the option. Can return an error if the
option name is not valid, or the value can not be converted.
given value. This will try to convert the value into the proper
type for the option. Can return an error if the option name is not
valid, or the value can not be converted.
- `SetGlobalOptionNative(option string, value interface{}) error`: sets
- `SetGlobalOptionNative(option string, value any) error`: sets
an option to a given value, where the type of value is the actual
type of the value internally. Can return an error if the provided value
is not valid for the given option.
@@ -227,10 +237,9 @@ The packages and their contents are listed below (in Go type signatures):
- `ConfigDir`: the path to micro's currently active config directory.
Relevant links:
[Buffer](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/buffer#Buffer)
[buffer.Completer](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/buffer#Completer)
[Buffer](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Buffer)
[buffer.Completer](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Completer)
[Error](https://pkg.go.dev/builtin#error)
[interface{} / any](https://go.dev/tour/methods/14)
[filepath.Match](https://pkg.go.dev/path/filepath#Match)
* `micro/shell`
@@ -256,7 +265,7 @@ The packages and their contents are listed below (in Go type signatures):
stdout from the command to the returned string.
- `JobStart(cmd string, onStdout, onStderr,
onExit func(string, []interface{}), userargs ...interface{})
onExit func(string, []any), userargs ...any)
*exec.Cmd`:
Starts a background job by running the shell on the given command
(using `sh -c`). Three callbacks can be provided which will be called
@@ -265,7 +274,7 @@ The packages and their contents are listed below (in Go type signatures):
argument of the callback. Returns the started command.
- `JobSpawn(cmd string, cmdArgs []string, onStdout, onStderr,
onExit func(string, []interface{}), userargs ...interface{})
onExit func(string, []any), userargs ...any)
*exec.Cmd`:
same as `JobStart`, except doesn't run the command through the shell
and instead takes as inputs the list of arguments. Returns the started
@@ -275,8 +284,8 @@ The packages and their contents are listed below (in Go type signatures):
- `JobSend(cmd *exec.Cmd, data string)`: sends some data to a job's stdin.
- `RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool,
callback func(out string, userargs []interface{}),
userargs []interface{}) error`:
callback func(out string, userargs []any),
userargs []any) error`:
starts a terminal emulator from a given BufPane with the input command.
If `wait` is true, it will wait for the user to exit by pressing enter
once the executable has terminated, and if `getOutput` is true, it will
@@ -295,7 +304,7 @@ The packages and their contents are listed below (in Go type signatures):
Relevant links:
[Cmd](https://pkg.go.dev/os/exec#Cmd)
[BufPane](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#BufPane)
[BufPane](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#BufPane)
[Error](https://pkg.go.dev/builtin#error)
* `micro/buffer`
@@ -336,10 +345,10 @@ The packages and their contents are listed below (in Go type signatures):
- `LogBuf() *Buffer`: returns the log buffer.
Relevant links:
[Message](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/buffer#Message)
[Loc](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/buffer#Loc)
[display.SLoc](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/display#SLoc)
[Buffer](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/buffer#Buffer)
[Message](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Message)
[Loc](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Loc)
[display.SLoc](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/display#SLoc)
[Buffer](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Buffer)
[Error](https://pkg.go.dev/builtin#error)
* `micro/util`
@@ -350,7 +359,6 @@ The packages and their contents are listed below (in Go type signatures):
- `IsWordChar(s string) bool`: returns true if the first rune in a
string is a word character.
- `String(b []byte) string`: converts a byte array to a string.
- `RuneStr(r rune) string`: converts a rune to a string.
- `Unzip(src, dest string) error`: unzips a file to given folder.
- `Version`: micro's version number or commit hash
- `SemVersion`: micro's semantic version
@@ -368,7 +376,7 @@ returned by the functions have many methods. The Lua plugin may access any
public methods of an object returned by any of the functions above.
Unfortunately, it is not possible to list all the available functions on this
page. Please go to the internal documentation at
https://pkg.go.dev/github.com/zyedidia/micro/v2/internal to see the full list
https://pkg.go.dev/github.com/micro-editor/micro/v2/internal to see the full list
of available methods. Note that only methods of types that are available to
plugins via the functions above can be called from a plugin. For an even more
detailed reference, see the source code on Github.
@@ -500,8 +508,8 @@ Micro also has a built in plugin manager, which you can invoke with the
For the valid commands you can use, see the `commands` help topic.
The manager fetches plugins from the channels (which is simply a list of plugin
metadata) which it knows about. By default, micro only knows about the official
channel which is located at github.com/micro-editor/plugin-channel but you can
metadata) which it knows about. By default, micro only knows about the [official
channel](https://github.com/micro-editor/plugin-channel) but you can
add your own third-party channels using the `pluginchannels` option and you can
directly link third-party plugins to allow installation through the plugin
manager with the `pluginrepos` option.
@@ -528,9 +536,9 @@ This file will contain the metadata for your plugin. Here is an example:
}]
```
Then open a pull request at github.com/micro-editor/plugin-channel, adding a
link to the raw `repo.json` that is in your plugin repository.
Then open a pull request at the [official plugin channel](https://github.com/micro-editor/plugin-channel),
adding a link to the raw `repo.json` that is in your plugin repository.
To make updating the plugin work, the first line of your plugin's lua code
should contain the version of the plugin. (Like this: `VERSION = "1.0.0"`)
Please make sure to use [semver](http://semver.org/) for versioning.
Please make sure to use [semver](https://semver.org/) for versioning.

View File

@@ -3,6 +3,7 @@ VERSION = "1.0.0"
local util = import("micro/util")
local config = import("micro/config")
local buffer = import("micro/buffer")
local micro = import("micro")
local ft = {}
@@ -54,6 +55,7 @@ ft["swift"] = "// %s"
ft["tex"] = "% %s"
ft["toml"] = "# %s"
ft["twig"] = "{# %s #}"
ft["typescript"] = "// %s"
ft["v"] = "// %s"
ft["xml"] = "<!-- %s -->"
ft["yaml"] = "# %s"
@@ -61,17 +63,21 @@ ft["zig"] = "// %s"
ft["zscript"] = "// %s"
ft["zsh"] = "# %s"
local last_ft
function updateCommentType(buf)
if buf.Settings["commenttype"] == nil or (last_ft ~= buf.Settings["filetype"] and last_ft ~= nil) then
if ft[buf.Settings["filetype"]] ~= nil then
buf:SetOptionNative("commenttype", ft[buf.Settings["filetype"]])
-- NOTE: Using DoSetOptionNative to avoid LocalSettings[option] = true
-- so that "comment.type" can be reset by a "filetype" change to default.
if (buf.Settings["comment.type"] == "") then
-- NOTE: This won't get triggered if a filetype is change via `setlocal filetype`
-- since it is not registered with `RegisterGlobalOption()``
if buf.Settings["commenttype"] ~= nil then
buf:DoSetOptionNative("comment.type", buf.Settings["commenttype"])
else
buf:SetOptionNative("commenttype", "# %s")
if (ft[buf.Settings["filetype"]] ~= nil) then
buf:DoSetOptionNative("comment.type", ft[buf.Settings["filetype"]])
else
buf:DoSetOptionNative("comment.type", "# %s")
end
end
last_ft = buf.Settings["filetype"]
end
end
@@ -88,7 +94,7 @@ function commentLine(bp, lineN, indentLen)
updateCommentType(bp.Buf)
local line = bp.Buf:Line(lineN)
local commentType = bp.Buf.Settings["commenttype"]
local commentType = bp.Buf.Settings["comment.type"]
local sel = -bp.Cursor.CurSelection
local curpos = -bp.Cursor.Loc
local index = string.find(commentType, "%%s") - 1
@@ -107,14 +113,14 @@ function commentLine(bp, lineN, indentLen)
bp.Cursor.Y = curpos.Y
end
bp.Cursor:Relocate()
bp.Cursor.LastVisualX = bp.Cursor:GetVisualX()
bp.Cursor:StoreVisualX()
end
function uncommentLine(bp, lineN, commentRegex)
updateCommentType(bp.Buf)
local line = bp.Buf:Line(lineN)
local commentType = bp.Buf.Settings["commenttype"]
local commentType = bp.Buf.Settings["comment.type"]
local sel = -bp.Cursor.CurSelection
local curpos = -bp.Cursor.Loc
local index = string.find(commentType, "%%s") - 1
@@ -135,7 +141,7 @@ function uncommentLine(bp, lineN, commentRegex)
end
end
bp.Cursor:Relocate()
bp.Cursor.LastVisualX = bp.Cursor:GetVisualX()
bp.Cursor:StoreVisualX()
end
function toggleCommentLine(bp, lineN, commentRegex)
@@ -178,7 +184,7 @@ end
function comment(bp, args)
updateCommentType(bp.Buf)
local commentType = bp.Buf.Settings["commenttype"]
local commentType = bp.Buf.Settings["comment.type"]
local commentRegex = "^%s*" .. commentType:gsub("%%","%%%%"):gsub("%$","%$"):gsub("%)","%)"):gsub("%(","%("):gsub("%?","%?"):gsub("%*", "%*"):gsub("%-", "%-"):gsub("%.", "%."):gsub("%+", "%+"):gsub("%]", "%]"):gsub("%[", "%["):gsub("%%%%s", "(.*)")
if bp.Cursor:HasSelection() then
@@ -204,6 +210,10 @@ function string.starts(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
function preinit()
config.RegisterCommonOption("comment", "type", "")
end
function init()
config.MakeCommand("comment", comment, config.NoComplete)
config.TryBindKey("Alt-/", "lua:comment.comment", false)

View File

@@ -80,10 +80,10 @@ but it is only available for certain filetypes:
* zsh: `# %s`
If your filetype is not available here, you can simply modify
the `commenttype` option:
the `comment.type` option:
```
set commenttype "/* %s */"
set comment.type "/* %s */"
```
Or in your `settings.json`:
@@ -91,7 +91,12 @@ Or in your `settings.json`:
```json
{
"*.c": {
"commenttype": "/* %s */"
"comment.type": "/* %s */"
}
}
```
`commenttype` (without the dot) is the legacy option that is
superseded by `comment.type`. `commenttype` is still supported
but deprecated.
**Use `comment.type` instead.**

View File

@@ -8,7 +8,10 @@ following filetypes and linters:
* **c**: gcc
* **c++**: g++
* **d**: dmd
* **d**: ldc2
* **d**: gdc
* **go**: go build
* **go**: go vet
* **haskell**: hlint
* **java**: javac
* **javascript**: jshint
@@ -16,11 +19,16 @@ following filetypes and linters:
* **literate**: lit
* **lua**: luacheck
* **nim**: nim
* **nix**: nix-linter
* **objective-c**: clang
* **python**: pyflakes
* **python**: flake8
* **python**: mypy
* **python**: pyflakes
* **python**: pylint
* **python**: ruff
* **rust**: cargo clippy
* **shell**: shfmt
* **shell**: shellcheck
* **swift**: swiftc (MacOS and Linux only)
* **yaml**: yamllint

View File

@@ -66,8 +66,10 @@ function preinit()
end
makeLinter("gcc", "c", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
makeLinter("g++", "c++", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
makeLinter("g++", "c++", "g++", {"-fsyntax-only","-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
makeLinter("dmd", "d", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", "%f"}, "%f%(%l%):.+: %m")
makeLinter("ldc2", "d", "ldc2", {"--o-", "--vcolumns", "-w", "-c", "%f"}, "%f%(%l,%c%):[^:]+: %m")
makeLinter("gdc", "d", "gdc", {"-fsyntax-only","-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
makeLinter("eslint", "javascript", "eslint", {"-f","compact","%f"}, "%f: line %l, col %c, %m")
makeLinter("gobuild", "go", "go", {"build", "-o", devnull, "%d"}, "%f:%l:%c:? %m")
makeLinter("govet", "go", "go", {"vet"}, "%f:%l:%c: %m")
@@ -82,6 +84,7 @@ function preinit()
makeLinter("pyflakes", "python", "pyflakes", {"%f"}, "%f:%l:.-:? %m")
makeLinter("mypy", "python", "mypy", {"%f"}, "%f:%l: %m")
makeLinter("pylint", "python", "pylint", {"--output-format=parseable", "--reports=no", "%f"}, "%f:%l: %m")
makeLinter("ruff", "python", "ruff", {"check", "--output-format=concise", "%f"}, "%f:%l:%c: %m")
makeLinter("flake8", "python", "flake8", {"%f"}, "%f:%l:%c: %m")
makeLinter("shfmt", "shell", "shfmt", {"%f"}, "%f:%l:%c: %m")
makeLinter("shellcheck", "shell", "shellcheck", {"-f", "gcc", "%f"}, "%f:%l:%c:.+: %m")
@@ -107,26 +110,29 @@ function contains(list, element)
return false
end
function checkFtMatch(ft, v)
local ftmatch = ft == v.filetype
if v.domatch then
ftmatch = string.match(ft, v.filetype)
end
local hasOS = contains(v.os, runtime.GOOS)
if not hasOS and v.whitelist then
ftmatch = false
end
if hasOS and not v.whitelist then
ftmatch = false
end
return ftmatch
end
function runLinter(buf)
local ft = buf:FileType()
local file = buf.Path
local dir = "." .. util.RuneStr(os.PathSeparator) .. filepath.Dir(file)
for k, v in pairs(linters) do
local ftmatch = ft == v.filetype
if v.domatch then
ftmatch = string.match(ft, v.filetype)
end
local hasOS = contains(v.os, runtime.GOOS)
if not hasOS and v.whitelist then
ftmatch = false
end
if hasOS and not v.whitelist then
ftmatch = false
end
if ftmatch then
if checkFtMatch(ft, v) then
local args = {}
for k, arg in pairs(v.args) do
args[k] = arg:gsub("%%f", file):gsub("%%d", dir)
@@ -141,6 +147,19 @@ function onSave(bp)
return true
end
function onBufferOptionChanged(buf, option, old, new)
if option == "filetype" then
if old ~= new then
for k, v in pairs(linters) do
if checkFtMatch(old, v) then
buf:ClearMessages(k)
end
end
end
end
return true
end
function lint(buf, linter, cmd, args, errorformat, loff, coff, callback)
buf:ClearMessages(linter)

View File

@@ -47,7 +47,6 @@ function onBufferOpen(buf)
syntaxFile = syntaxFile .. " - special:\n"
syntaxFile = syntaxFile .. " start: \"@\\\\{\"\n"
syntaxFile = syntaxFile .. " end: \"\\\\}\"\n"
syntaxFile = syntaxFile .. " rules: []\n"
syntaxFile = syntaxFile .. " - include: " .. codetype .. "\n"
config.AddRuntimeFileFromMemory(config.RTSyntax, "literate.yaml", syntaxFile)

View File

@@ -8,8 +8,10 @@ those options (`> help options`) for more information.
This plugin provides functions that can be used in the status line format:
* `status.branch`: returns the name of the current git branch.
* `status.hash`: returns the hash of the current git commit.
* `status.branch`: returns the name of the current git branch in the repository
where the file is located.
* `status.hash`: returns the hash of the current git commit in the repository
where the file is located.
* `status.paste`: returns "" if the paste option is disabled and "PASTE"
if it is enabled.
* `status.lines`: returns the number of lines in the buffer.

View File

@@ -3,7 +3,10 @@ VERSION = "1.0.0"
local micro = import("micro")
local buffer = import("micro/buffer")
local config = import("micro/config")
local shell = import("micro/shell")
local filepath = import("filepath")
local humanize = import("humanize")
local strings = import("strings")
function init()
micro.SetStatusInfoFn("status.branch")
@@ -21,7 +24,7 @@ function lines(b)
end
function vcol(b)
return tostring(b:GetActiveCursor():GetVisualX())
return tostring(b:GetActiveCursor():GetVisualX(false))
end
function bytes(b)
@@ -32,30 +35,23 @@ function size(b)
return humanize.Bytes(b:Size())
end
function branch(b)
local function parseRevision(b, opt)
if b.Type.Kind ~= buffer.BTInfo then
local shell = import("micro/shell")
local strings = import("strings")
local branch, err = shell.ExecCommand("git", "rev-parse", "--abbrev-ref", "HEAD")
local dir = filepath.Dir(b.Path)
local str, err = shell.ExecCommand("git", "-C", dir, "rev-parse", opt, "HEAD")
if err == nil then
return strings.TrimSpace(branch)
return strings.TrimSpace(str)
end
return ""
end
return ""
end
function branch(b)
return parseRevision(b, "--abbrev-ref")
end
function hash(b)
if b.Type.Kind ~= 5 then
local shell = import("micro/shell")
local strings = import("strings")
local hash, err = shell.ExecCommand("git", "rev-parse", "--short", "HEAD")
if err == nil then
return strings.TrimSpace(hash)
end
return ""
end
return parseRevision(b, "--short")
end
function paste(b)

View File

@@ -6,11 +6,6 @@ Each yaml file specifies how to detect the filetype based on file extension or h
In addition, a signature can be provided to help resolving ambiguities when multiple matching filetypes are detected.
Then there are patterns and regions linked to highlight groups which tell micro how to highlight that filetype.
Making your own syntax files is very simple. I recommend you check the file after you are finished with the
[`syntax_checker.go`](./syntax_checker.go) program (located in this directory). Just place your yaml syntax
file in the current directory and run `go run syntax_checker.go` and it will check every file. If there are no
errors it will print `No issues!`.
You can read more about how to write syntax files (and colorschemes) in the [colors](../help/colors.md) documentation.
# Legacy '.micro' filetype

View File

@@ -1,7 +1,7 @@
filetype: ino
detect:
filename: "\\.?ino$"
filename: "\\.ino$"
rules:
- identifier: "\\b[A-Z_][0-9A-Z_]+\\b"

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