Compare commits

...

220 Commits

Author SHA1 Message Date
cutelisp
5eddf5b85d
Change MainTab calls (#3750)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2025-05-25 12:59:59 +02:00
Dmytro Maluka
98ff79dbca
Relocate buffer view after setting options that affect it (#3743)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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
Some checks are pending
Build and Test / test (1.19.x, macos-latest) (push) Waiting to run
Build and Test / test (1.19.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.19.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
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)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2025-05-06 20:38:45 +02:00
Jöran Karl
06fe85c8c9
Merge pull request #3708 from Neko-Box-Coder/ChainedParentsFix
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2025-04-28 19:55:03 +02:00
Jöran Karl
333770bbd0
Merge pull request #3727 from JoeKar/fix/path2filepath
Some checks are pending
Build and Test / test (1.19.x, macos-latest) (push) Waiting to run
Build and Test / test (1.19.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.19.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
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
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2025-04-15 21:02:41 +02:00
cutelisp
b88300ef7f
Fix comment (#3716)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2025-04-12 12:27:00 +02:00
Jöran Karl
d36e7f4057
Merge pull request #3714 from niten94/terminal-20250105
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
`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)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
`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
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2025-02-09 15:19:43 +01:00
Jöran Karl
bf4156c490
Merge pull request #3657 from Andriamanitra/PR3656-continuation
Some checks are pending
Build and Test / test (1.19.x, macos-latest) (push) Waiting to run
Build and Test / test (1.19.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.19.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
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)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2025-02-01 09:41:25 +01:00
Jöran Karl
e4b0ad7107
Merge pull request #3620 from JoeKar/feature/cursor-overwrite-indicator
Some checks are pending
Build and Test / test (1.19.x, macos-latest) (push) Waiting to run
Build and Test / test (1.19.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.19.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
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
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
`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)
Some checks are pending
Build and Test / test (1.19.x, macos-latest) (push) Waiting to run
Build and Test / test (1.19.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.19.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
2025-01-28 21:04:05 +01:00
yz778
c02036e52f
Prompt to save or discard new files even with autosave enabled (#3626)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2025-01-25 20:24:31 +01:00
Neko Box Coder
698511c5b6
Fixing settings not being applied when saving as a new file (#3625)
Some checks are pending
Build and Test / test (1.19.x, macos-latest) (push) Waiting to run
Build and Test / test (1.19.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.19.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
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)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2025-01-19 13:07:48 +01:00
Jöran Karl
f49487dc3a
import: Use micro-editor/terminal instead of zyedidia/terminal (#3600)
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2025-01-14 18:20:37 +01:00
Jöran Karl
c77ed02778
Merge pull request #3601 from JoeKar/import/go-runewidth
Some checks failed
Build and Test / test (1.19.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.19.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.19.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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)
Some checks are pending
Build and Test / test (1.19.x, macos-latest) (push) Waiting to run
Build and Test / test (1.19.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.19.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
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)
Some checks are pending
Build and Test / test (1.17.x, macos-latest) (push) Waiting to run
Build and Test / test (1.17.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.17.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
2025-01-04 15:55:46 +01:00
Dmytro Maluka
82467ba9f6
Merge pull request #3597 from matthias314/m3/gopher-bump
Some checks failed
Build and Test / test (1.17.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.17.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.17.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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
Some checks failed
Build and Test / test (1.17.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.17.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.17.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
plugin: Add new `onBufferOptionChanged` callback
2025-01-01 11:11:39 +01:00
niten94
58d38af8cd
Update go-isatty to v0.0.20 (#3561)
Some checks are pending
Build and Test / test (1.17.x, macos-latest) (push) Waiting to run
Build and Test / test (1.17.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.17.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
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)
Some checks failed
Build and Test / test (1.17.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.17.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.17.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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)
Some checks failed
Build and Test / test (1.17.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.17.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.17.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
2024-12-09 19:42:19 +01:00
Jöran Karl
fb20818042
Merge pull request #3540 from JoeKar/fix/cursor-down
Some checks failed
Build and Test / test (1.17.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.17.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.17.x, windows-latest) (push) Has been cancelled
Build and Test / test (1.23.x, macos-latest) (push) Has been cancelled
Build and Test / test (1.23.x, ubuntu-latest) (push) Has been cancelled
Build and Test / test (1.23.x, windows-latest) (push) Has been cancelled
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)
Some checks are pending
Build and Test / test (1.17.x, macos-latest) (push) Waiting to run
Build and Test / test (1.17.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.17.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
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)
Some checks are pending
Build and Test / test (1.17.x, macos-latest) (push) Waiting to run
Build and Test / test (1.17.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.17.x, windows-latest) (push) Waiting to run
Build and Test / test (1.23.x, macos-latest) (push) Waiting to run
Build and Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Build and Test / test (1.23.x, windows-latest) (push) Waiting to run
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
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
160 changed files with 2244 additions and 1408 deletions

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:

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

@ -21,22 +21,6 @@ To see more screenshots of micro, showcasing some of the default color schemes,
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.
@ -88,7 +72,7 @@ Pre-built binaries are distributed in [releases](https://github.com/zyedidia/mic
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
@ -178,7 +162,7 @@ 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
@ -196,16 +180,20 @@ You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/mi
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.

View File

@ -3,8 +3,8 @@ package main
import (
"bufio"
"encoding/gob"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
@ -12,6 +12,7 @@ import (
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/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

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

@ -4,10 +4,10 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"runtime/pprof"
@ -18,6 +18,7 @@ import (
"github.com/go-errors/errors"
isatty "github.com/mattn/go-isatty"
"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"
@ -26,7 +27,6 @@ import (
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)
var (
@ -99,7 +99,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 +115,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 +136,7 @@ func DoPluginFlags() {
CleanConfig()
}
os.Exit(0)
exit(0)
}
}
@ -209,7 +209,7 @@ func LoadInput(args []string) []*buffer.Buffer {
// 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)
input, err = io.ReadAll(os.Stdin)
if err != nil {
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
@ -223,12 +223,55 @@ func LoadInput(args []string) []*buffer.Buffer {
return buffers
}
func checkBackup(name string) error {
target := filepath.Join(config.ConfigDir, name)
backup := util.AppendBackupSuffix(target)
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 +299,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)
@ -288,7 +337,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)
@ -307,7 +356,7 @@ func main() {
for _, b := range buffer.OpenBuffers {
b.Backup()
}
os.Exit(1)
exit(1)
}
}()
@ -316,6 +365,12 @@ func main() {
screen.TermMessage(err)
}
err = checkBackup("bindings.json")
if err != nil {
screen.TermMessage(err)
exit(1)
}
action.InitBindings()
action.InitCommands()
@ -434,24 +489,12 @@ func DoEvent() {
}
case f := <-timerChan:
f()
case b := <-buffer.BackupCompleteChan:
b.RequestedBackup = false
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 +502,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/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
}
@ -164,20 +163,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 +195,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 +218,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 +263,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 +288,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 +302,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 +317,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))

33
go.mod
View File

@ -5,26 +5,35 @@ require (
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.11
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.28.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

80
go.sum
View File

@ -19,84 +19,58 @@ 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.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
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.11 h1:USjdpBSmbocx2yPARbY19KcUSj+ZerScrdmBqGjzoX4=
github.com/micro-editor/tcell/v2 v2.0.11/go.mod h1:kVYk6NOwYJrboL/7IA7cCupk4o2NzyF/0UMLjeEJN/s=
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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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,6 +11,7 @@ import (
"time"
shellquote "github.com/kballard/go-shellquote"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/config"
@ -18,7 +19,6 @@ import (
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/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)
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(
@ -982,8 +1043,6 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
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 +1067,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()
@ -1154,6 +1211,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 +1248,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 +1311,189 @@ func (h *BufPane) Redo() bool {
return true
}
// Copy the selection to the system clipboard
func (h *BufPane) Copy() bool {
func (h *BufPane) selectLines() int {
if h.Cursor.HasSelection() {
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = true
InfoBar.Message("Copied selection")
start := h.Cursor.CurSelection[0]
end := h.Cursor.CurSelection[1]
if start.GreaterThan(end) {
start, end = end, start
}
h.Relocate()
return true
if end.X == 0 {
end = end.Move(-1, h.Buf)
}
// 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
h.Cursor.SetSelectionStart(buffer.Loc{0, start.Y})
h.Cursor.SetSelectionEnd(buffer.Loc{0, end.Y + 1})
} else {
h.Cursor.SelectLine()
}
// CutLine cuts the current line to the clipboard
func (h *BufPane) CutLine() bool {
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() {
return false
}
if h.freshClip {
if h.Cursor.HasSelection() {
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
InfoBar.Error(err)
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.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = false
if nlines > 1 {
InfoBar.Message(fmt.Sprintf("Copied %d lines", nlines))
} else {
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
InfoBar.Message("Copied line")
}
}
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
h.Copy()
}
h.freshClip = true
h.lastCutTime = time.Now()
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
InfoBar.Message("Cut 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() {
if !h.Cursor.HasSelection() {
return false
}
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
h.freshClip = true
h.freshClip = false
InfoBar.Message("Cut selection")
h.Relocate()
return true
}
return h.CutLine()
}
// DuplicateLine duplicates the current line or selection
func (h *BufPane) DuplicateLine() bool {
var infoMessage = "Duplicated line"
if h.Cursor.HasSelection() {
infoMessage = "Duplicated selection"
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
// 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 {
h.Cursor.End()
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
// h.Cursor.Right()
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")
}
InfoBar.Message(infoMessage)
h.Relocate()
return true
}
// DeleteLine deletes the current line
func (h *BufPane) DeleteLine() bool {
h.Cursor.SelectLine()
// 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()
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 +1697,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 +1794,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 +1807,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 +1827,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 +1864,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 +1891,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 +1907,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.closePrompt("Quit", func() {
h.ForceQuit()
} else if !canceled && yes {
h.SaveCB("Quit", func() {
h.ForceQuit()
})
}
})
}
} else {
@ -1793,27 +1982,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,36 +2048,38 @@ 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(h.tab.active - 1)
return true
}
h.tab.SetActive(a)
// 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
}
@ -1955,11 +2157,17 @@ 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
}
@ -1969,24 +2177,11 @@ func (h *BufPane) SpawnMultiCursorUpN(n int) bool {
h.Buf.DeselectCursors()
c = buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
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()
} 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
}
h.Buf.AddCursor(c)
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
@ -2068,14 +2263,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 +2280,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 +2300,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,4 +1,4 @@
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
package action

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/micro-editor/json5"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell/v2"
"github.com/zyedidia/micro/v2/internal/util"
)
var Binder = map[string]func(e Event, action string){
@ -23,9 +24,13 @@ 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("{}"))
}
}
@ -37,7 +42,7 @@ func InitBindings() {
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
@ -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)
@ -265,7 +270,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
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 +309,8 @@ 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')
return true, writeFile(filename, txt)
}
return false, e
}
@ -317,7 +323,7 @@ func UnbindKey(k string) error {
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 +348,7 @@ func UnbindKey(k string) error {
}
if strings.HasPrefix(k, "\x1b") {
screen.Screen.UnregisterRawSeq(k)
screen.UnregisterRawSeq(k)
}
defaults := DefaultBindings("buffer")
@ -354,7 +360,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,6 +6,7 @@ import (
luar "layeh.com/gopher-luar"
"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"
@ -13,7 +14,6 @@ import (
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{}
@ -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)
@ -363,9 +356,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{}
}
@ -644,7 +634,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))
@ -662,20 +652,28 @@ func (h *BufPane) DoRuneInsert(r rune) {
// VSplitIndex opens the given buffer in a vertical split on the given side.
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
e := NewBufPaneFromBuf(buf, h.tab)
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
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 +731,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 +781,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 +826,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 +847,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 +914,7 @@ var MultiActions = map[string]bool{
"Copy": true,
"Cut": true,
"CutLine": true,
"Duplicate": true,
"DuplicateLine": true,
"DeleteLine": true,
"MoveLinesUp": true,

View File

@ -139,10 +139,11 @@ func (h *BufPane) TextFilterCmd(args []string) {
InfoBar.Error("usage: textfilter arguments")
return
}
sel := h.Cursor.GetSelection()
for _, c := range h.Buf.GetCursors() {
sel := c.GetSelection()
if len(sel) == 0 {
h.Cursor.SelectWord()
sel = h.Cursor.GetSelection()
c.SelectWord()
sel = c.GetSelection()
}
var bout, berr bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
@ -154,8 +155,9 @@ func (h *BufPane) TextFilterCmd(args []string) {
InfoBar.Error(err.Error() + " " + berr.String())
return
}
h.Cursor.DeleteSelection()
h.Buf.Insert(h.Cursor.Loc, bout.String())
c.DeleteSelection()
h.Buf.Insert(c.Loc, bout.String())
}
}
// TabMoveCmd moves the current tab to a given index (starts at 1). The
@ -203,6 +205,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))
@ -305,15 +308,8 @@ func (h *BufPane) OpenCmd(args []string) {
}
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 +424,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 +433,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 overriden 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])
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 ", args[0])
InfoBar.Error("Sorry, no help for ", topic)
}
}
}
}
// 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,7 +509,8 @@ func (h *BufPane) VSplitCmd(args []string) {
return
}
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
for _, a := range args {
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
@ -480,8 +518,9 @@ func (h *BufPane) VSplitCmd(args []string) {
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,7 +529,8 @@ func (h *BufPane) HSplitCmd(args []string) {
return
}
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
for _, a := range args {
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
if err != nil {
InfoBar.Error(err)
return
@ -498,13 +538,15 @@ func (h *BufPane) HSplitCmd(args []string) {
h.HSplitBuf(buf)
}
}
// EvalCmd evaluates a lua expression
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()
@ -609,7 +651,16 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
delete(b.LocalSettings, option)
}
return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
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 {
@ -734,9 +785,13 @@ func (h *BufPane) BindCmd(args []string) {
_, err := TryBindKey(parseKeyArg(args[0]), args[1], true)
if err != nil {
if errors.Is(err, util.ErrOverwrite) {
screen.TermMessage(err)
} else {
InfoBar.Error(err)
}
}
}
// UnbindCmd binds a key to its default action
func (h *BufPane) UnbindCmd(args []string) {
@ -747,9 +802,13 @@ func (h *BufPane) UnbindCmd(args []string) {
err := UnbindKey(parseKeyArg(args[0]))
if err != nil {
if errors.Is(err, util.ErrOverwrite) {
screen.TermMessage(err)
} else {
InfoBar.Error(err)
}
}
}
// RunCmd runs a shell command in the background
func (h *BufPane) RunCmd(args []string) {
@ -841,7 +900,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 +961,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 +975,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 +990,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 +1027,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
h.Buf.RelocateCursors()
return
}
lastMatchEnd = searchLoc
doReplacement()
})
}
@ -985,10 +1059,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 +1109,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)
}
if h.Buf.Modified() && !h.Buf.Shared() {
h.closePrompt("Save", func() {
h.openTerm(args, false)
})
} else {
term(i, false)
}
}
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,8 +146,8 @@ var infodefaults = map[string]string{
"Backtab": "CycleAutocompleteBack",
"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-v": "Paste",
"Home": "StartOfTextToggle",

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,8 +149,8 @@ var infodefaults = map[string]string{
"Backtab": "CycleAutocompleteBack",
"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-v": "Paste",
"Home": "StartOfTextToggle",

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

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

View File

@ -3,12 +3,12 @@ package action
import (
"bytes"
"github.com/micro-editor/tcell/v2"
"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"
)
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

@ -4,9 +4,9 @@ import (
"fmt"
"reflect"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/tcell/v2"
)
type RawPane struct {

View File

@ -3,13 +3,13 @@ package action
import (
luar "layeh.com/gopher-luar"
"github.com/micro-editor/tcell/v2"
"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"
)
// The TabList is a list of tabs and a window to display the tab bar
@ -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,4 +1,4 @@
// +build linux darwin dragonfly openbsd_amd64 freebsd
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
package action

View File

@ -1,4 +1,4 @@
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
//go:build plan9 || nacl || windows
package action

View File

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

View File

@ -2,7 +2,7 @@ package buffer
import (
"bytes"
"io/ioutil"
"io/fs"
"os"
"sort"
"strings"
@ -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"
)
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,90 +32,80 @@ 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)
for len(backupRequestChan) > 0 {
b := <-backupRequestChan
bfini := atomic.LoadInt32(&(b.fini)) != 0
if !bfini {
b.Backup()
}
}
}
}
var BackupCompleteChan chan *Buffer
func init() {
backupRequestChan = make(chan *Buffer, 10)
go backupThread()
BackupCompleteChan = make(chan *Buffer, 10)
}
func (b *Buffer) RequestBackup() {
if !b.requestedBackup {
if !b.RequestedBackup {
select {
case backupRequestChan <- b:
default:
// channel is full
}
b.requestedBackup = true
b.RequestedBackup = true
}
}
// Backup saves the current buffer to ConfigDir/backups
func (b *Buffer) backupDir() string {
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
if backupdir == "" || err != nil {
backupdir = filepath.Join(config.ConfigDir, "backups")
}
return backupdir
}
func (b *Buffer) keepBackup() bool {
return b.forceKeepBackup || b.Settings["permbackup"].(bool)
}
// Backup saves the current buffer to the backups directory
func (b *Buffer) Backup() error {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
return nil
}
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) {
backupdir := b.backupDir()
if _, err := os.Stat(backupdir); errors.Is(err, fs.ErrNotExist) {
os.Mkdir(backupdir, os.ModePerm)
}
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
name := util.DetermineEscapePath(backupdir, b.AbsPath)
if _, err := os.Stat(name); errors.Is(err, fs.ErrNotExist) {
_, err = b.overwriteFile(name)
if err == nil {
BackupCompleteChan <- b
}
return err
}
// end of line
eol := []byte{'\n'}
// write lines
if _, e = file.Write(b.lines[0].data); e != nil {
return
tmp := util.AppendBackupSuffix(name)
_, err := b.overwriteFile(tmp)
if err != nil {
os.Remove(tmp)
return err
}
err = os.Rename(tmp, name)
if err != nil {
os.Remove(tmp)
return err
}
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
BackupCompleteChan <- b
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 {
if !b.Settings["backup"].(bool) || b.keepBackup() || b.Path == "" || b.Type != BTDefault {
return
}
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
f := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
os.Remove(f)
}
@ -121,13 +113,13 @@ func (b *Buffer) RemoveBackup() {
// Returns true if a backup was applied
func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
backupfile := 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 {

View File

@ -7,9 +7,8 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"io/fs"
"os"
"path"
"path/filepath"
"strconv"
"strings"
@ -25,13 +24,12 @@ import (
"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
@ -89,6 +87,8 @@ type SharedBuffer struct {
// LocalSettings customized by the user for this buffer only
LocalSettings map[string]bool
encoding encoding.Encoding
Suggestions []string
Completions []string
CurSuggestion int
@ -101,7 +101,8 @@ type SharedBuffer struct {
diffLock sync.RWMutex
diff map[int]DiffStatus
requestedBackup bool
RequestedBackup bool
forceKeepBackup bool
// ReloadDisabled allows the user to disable reloads if they
// are viewing a file that is constantly changing
@ -209,6 +210,11 @@ 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
@ -232,17 +238,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,7 +259,7 @@ 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 {
@ -320,30 +329,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 +352,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,10 +390,10 @@ 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)
}
@ -480,7 +478,7 @@ func (b *Buffer) GetName() string {
name = b.Path
}
if b.Settings["basename"].(bool) {
return path.Base(name)
return filepath.Base(name)
}
return name
}
@ -546,7 +544,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 {
@ -621,6 +619,16 @@ 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 {

View File

@ -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()
@ -615,5 +622,6 @@ func (c *Cursor) RuneUnder(x int) rune {
}
func (c *Cursor) StoreVisualX() {
c.LastVisualX = c.GetVisualX()
c.LastVisualX = c.GetVisualX(false)
c.LastWrappedVisualX = c.GetVisualX(true)
}

View File

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

@ -285,11 +285,6 @@ 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)

View File

@ -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/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/tcell/v2"
)
type MsgType int

View File

@ -5,18 +5,19 @@ import (
"bytes"
"errors"
"io"
"io/fs"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"sync/atomic"
"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"
"golang.org/x/text/transform"
)
@ -24,68 +25,178 @@ 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 {
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 *Buffer
func init() {
saveRequestChan = make(chan saveRequest, 10)
backupRequestChan = make(chan *Buffer, 10)
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 <-backupTicker.C:
for len(backupRequestChan) > 0 {
b := <-backupRequestChan
bfini := atomic.LoadInt32(&(b.fini)) != 0
if !bfini {
b.Backup()
}
}
}
}
}()
}
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
writeCloser, err = cmd.StdinPipe()
if err != nil {
return wrappedFile{}, err
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
cmd.Process.Kill()
}()
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 {
writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, util.FileMode)
if err != nil {
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
return wrappedFile{writeCloser, withSudo, screenb, cmd, sigChan}, nil
}
func (wf wrappedFile) Write(b *Buffer) (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
}
// end of line
var eol []byte
if b.Endings == FFDos {
eol = []byte{'\r', '\n'}
} else {
eol = []byte{'\n'}
}
if !wf.withSudo {
f := wf.writeCloser.(*os.File)
err := f.Truncate(0)
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.
// 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
f := wf.writeCloser.(*os.File)
err = f.Sync()
}
}
if err2 := writeCloser.Close(); err2 != nil && err == nil {
err = err2
return size, err
}
if withSudo {
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 *Buffer) 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
@ -146,19 +257,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,49 +299,22 @@ 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 {
@ -222,10 +322,70 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
}
}
newPath := b.Path != filename
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 *Buffer) 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)
}
}()
backupDir := b.backupDir()
if _, err := os.Stat(backupDir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return 0, err
}
if err = os.Mkdir(backupDir, os.ModePerm); err != nil {
return 0, err
}
}
backupName := util.DetermineEscapePath(backupDir, path)
_, err = b.overwriteFile(backupName)
if err != nil {
os.Remove(backupName)
return 0, err
}
b.forceKeepBackup = true
size, err := file.Write(b)
if err != nil {
err = util.OverwriteError{err, backupName}
return size, err
}
b.forceKeepBackup = false
if !b.keepBackup() {
os.Remove(backupName)
}
err2 := file.Close()
if err2 != nil && err == nil {
err = err2
}
return size, err
}

View File

@ -2,10 +2,56 @@ package buffer
import (
"regexp"
"unicode/utf8"
"github.com/zyedidia/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 == padStart {
r = regexp.MustCompile(".(?:" + r.String() + ")")
} else if padMode == padEnd {
r = regexp.MustCompile("(?:" + r.String() + ").")
} else if 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 +68,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 +105,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 +181,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
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)
for i := start.Y; i <= end.Y; i++ {
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
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
}
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
deltas = append(deltas, Delta{newText, match[0], match[1]})
}
} else {
newLine := search.ReplaceAllFunc(l, func(in []byte) []byte {
found++
var result []byte
if captureGroups {
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
result = search.Expand(result, replace, in, submatches)
}
match := search.FindSubmatchIndex(in)
result = search.Expand(result, replace, in, match)
} else {
result = replace
}
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})
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,15 +1,13 @@
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"
)
@ -31,16 +29,18 @@ 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{
var buf bytes.Buffer
err := gob.NewEncoder(&buf).Encode(SerializedBuffer{
b.EventHandler,
b.GetActiveCursor().Loc,
b.ModTime,
})
if err != nil {
return err
}, false)
}
name := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath)
return util.SafeWrite(name, buf.Bytes(), true)
}
// Unserialize loads the buffer info from config.ConfigDir/buffers
@ -50,7 +50,7 @@ func (b *Buffer) Unserialize() error {
if b.Path == "" {
return nil
}
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
file, err := os.Open(util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath))
if err == nil {
defer file.Close()
var buffer SerializedBuffer

View File

@ -5,13 +5,22 @@ import (
"reflect"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/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
@ -46,7 +60,8 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) {
}
func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
if reflect.DeepEqual(b.Settings[option], nativeValue) {
oldValue := b.Settings[option]
if reflect.DeepEqual(oldValue, nativeValue) {
return
}
@ -84,6 +99,12 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
b.UpdateRules()
}
} else if option == "encoding" {
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
enc = unicode.UTF8
b.Settings["encoding"] = "utf-8"
}
b.encoding = enc
b.isModified = true
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
b.Type.Readonly = nativeValue.(bool)
@ -114,9 +135,7 @@ 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 {
@ -143,3 +162,15 @@ func (b *Buffer) SetOption(option, value string) error {
return b.SetOptionNative(option, nativeValue)
}
func (b *Buffer) doCallbacks(option string, oldValue interface{}, newValue interface{}) {
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/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/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

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

@ -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,8 +13,8 @@ import (
"sync"
"github.com/blang/semver"
"github.com/micro-editor/json5"
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"
)
@ -396,7 +395,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
}

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,10 +2,8 @@ package config
import (
"errors"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"regexp"
"strings"
@ -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()
@ -204,14 +192,14 @@ func InitPlugins() {
// 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 +207,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
}
@ -311,7 +299,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 +316,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,8 +11,8 @@ import (
"strconv"
"strings"
"github.com/micro-editor/json5"
"github.com/zyedidia/glob"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding/htmlindex"
)
@ -29,21 +28,26 @@ 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
@ -66,20 +70,21 @@ var defaultCommonSettings = map[string]interface{}{
"hlsearch": false,
"hltaberrors": false,
"hltrailingws": false,
"incsearch": true,
"ignorecase": true,
"incsearch": true,
"indentchar": " ",
"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,
@ -89,13 +94,14 @@ var defaultCommonSettings = map[string]interface{}{
"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,
}
@ -109,6 +115,7 @@ var DefaultGlobalOnlySettings = map[string]interface{}{
"divchars": "|-",
"divreverse": true,
"fakecursor": false,
"helpsplit": "hsplit",
"infobar": true,
"keymenu": false,
"mouse": true,
@ -151,6 +158,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)
@ -217,7 +228,7 @@ func ReadSettings() error {
parsedSettings = make(map[string]interface{})
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())
@ -283,22 +294,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]interface{}, 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:] {
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]interface{}) {
settings[k1] = v1
}
}
} else {
g, _ := glob.Compile(k)
if g.MatchString(path) {
}
}
}
// 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]interface{}, 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]interface{}) {
if k1 != "filetype" {
settings[k1] = v1
}
}
@ -342,7 +362,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
}
@ -363,8 +384,9 @@ 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
}

View File

@ -4,11 +4,11 @@ import (
"strconv"
runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/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/zyedidia/tcell/v2"
)
// The BufWindow provides a way of displaying a certain section of a buffer.
@ -58,9 +58,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 +166,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 +244,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 +254,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
}
}
@ -449,7 +455,7 @@ func (w *BufWindow) displayBuffer() {
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
}
@ -619,6 +625,8 @@ func (w *BufWindow) displayBuffer() {
wrap := func() {
vloc.X = 0
if vloc.Y >= 0 {
if w.hasMessage {
w.drawGutter(&vloc, &bloc)
}
@ -630,6 +638,9 @@ func (w *BufWindow) displayBuffer() {
if b.Settings["ruler"].(bool) {
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
}
} else {
vloc.X = w.gutterOffset
}
}
type glyph struct {

View File

@ -2,12 +2,12 @@ package display
import (
runewidth "github.com/mattn/go-runewidth"
"github.com/micro-editor/tcell/v2"
"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"
)
type InfoWindow struct {

View File

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

@ -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())
},

View File

@ -2,7 +2,7 @@ package display
import (
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/tcell/v2"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"

View File

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

View File

@ -1,12 +1,16 @@
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/screen"
"github.com/zyedidia/micro/v2/internal/util"
)
@ -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 {
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)
err = encoder.Encode(i.History)
var buf bytes.Buffer
err := gob.NewEncoder(&buf).Encode(i.History)
if err != nil {
i.Error("Error saving history:", err)
screen.TermMessage("Error encoding 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

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

@ -6,9 +6,9 @@ import (
"os"
"sync"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)
// Screen is the tcell screen we use to draw to the terminal
@ -33,6 +33,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()
@ -121,6 +127,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 +184,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 +232,10 @@ func Init() error {
Screen.EnableMouse()
}
for _, r := range rawSeq {
Screen.RegisterRawSeq(r)
}
return nil
}

View File

@ -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()
if onExit != nil {
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
Jobs <- jobFunc
}
}()
return &Job{proc, stdin}

View File

@ -5,9 +5,9 @@ import (
"os/exec"
"strconv"
"github.com/micro-editor/terminal"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/terminal"
)
type TermType int

View File

@ -6,7 +6,9 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/url"
"os"
"os/user"
"path/filepath"
@ -43,8 +45,44 @@ var (
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 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)
@ -233,18 +271,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 +358,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 +446,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 AppendBackupSuffix(path string) string {
return path + ".micro-backup"
}
// 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 +465,24 @@ 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).
func DetermineEscapePath(dir string, path 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
}
return url
}
// GetLeadingWhitespace returns the leading whitespace of the given byte array
func GetLeadingWhitespace(b []byte) []byte {
ws := []byte{}
@ -510,11 +585,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 +655,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 := AppendBackupSuffix(path)
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

@ -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
@ -476,11 +476,18 @@ 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.([]interface{}), r)
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.
@ -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.
@ -72,12 +78,15 @@ quotes here but these are not necessary when entering the command in micro.
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

View File

@ -66,7 +66,9 @@ 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.
## Binding commands
@ -168,14 +170,15 @@ CursorLeft
CursorRight
CursorStart
CursorEnd
CursorToViewTop
CursorToViewCenter
CursorToViewBottom
SelectToStart
SelectToEnd
SelectUp
SelectDown
SelectLeft
SelectRight
SelectToStartOfText
SelectToStartOfTextToggle
WordRight
WordLeft
SubWordRight
@ -184,20 +187,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 +211,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 +243,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,14 +286,25 @@ 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)
```
@ -495,23 +522,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 +549,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,8 +648,8 @@ are given below:
"Backtab": "CycleAutocompleteBack",
"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-v": "Paste",
"Home": "StartOfTextToggle",

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).
@ -172,6 +166,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,11 +195,11 @@ Here are the available options:
default value: `false`
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
* `ignorecase`: perform case-insensitive searches.
default value: `true`
* `ignorecase`: perform case-insensitive searches.
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
default value: `true`
@ -278,6 +279,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 +305,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 +329,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 +352,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/`
@ -401,11 +409,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,20 +435,20 @@ 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.
@ -454,6 +462,19 @@ Here are the available options:
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 +514,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 +539,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 +569,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,12 +580,14 @@ 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,
"smartpaste": true,
@ -564,17 +595,18 @@ so that you can see what the formatting should look like.
"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.
@ -350,7 +352,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

View File

@ -107,7 +107,7 @@ 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)
@ -135,7 +135,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)

View File

@ -9,6 +9,7 @@ following filetypes and linters:
* **c++**: g++
* **d**: dmd
* **go**: go build
* **go**: go vet
* **haskell**: hlint
* **java**: javac
* **javascript**: jshint
@ -16,11 +17,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,7 +66,7 @@ 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("eslint", "javascript", "eslint", {"-f","compact","%f"}, "%f: line %l, col %c, %m")
makeLinter("gobuild", "go", "go", {"build", "-o", devnull, "%d"}, "%f:%l:%c:? %m")
@ -82,6 +82,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,12 +108,7 @@ function contains(list, element)
return false
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
function checkFtMatch(ft, v)
local ftmatch = ft == v.filetype
if v.domatch then
ftmatch = string.match(ft, v.filetype)
@ -125,8 +121,16 @@ function runLinter(buf)
if hasOS and not v.whitelist then
ftmatch = false
end
return ftmatch
end
if ftmatch then
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
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 +145,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
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
@ -38,6 +33,19 @@ Micro syntax files are almost identical to Nano's, except for some key differenc
* Micro does not use `icolor`. Instead, for a case insensitive match, use the case insensitive flag (`i`) in the regular expression
* For example, `icolor green ".*"` would become `color green "(?i).*"`
# Incompatibilities with older versions of micro
With PR [#3458](https://github.com/zyedidia/micro/pull/3458) resp. commit
[a9b513a](https://github.com/zyedidia/micro/commit/a9b513a28adaaa7782505dc1e284e1a0132cb66f)
empty `rules: []` definitions are removed from all syntax files, since
`rules` are no longer mandatory.
Unfortunately they are mandatory for `micro` versions up to and including `v2.0.14`.
To use newer syntax definitions from this repository with older `micro` versions
you have to add these `rules: []` to all regions not including `rules` already.
Otherwise you need to use syntax definitions before the above mentioned PR
for example from version [v2.0.14](https://github.com/zyedidia/micro/tree/v2.0.14).
# Using with colorschemes
Not all of these files have been converted to use micro's colorscheme feature. Most of them just hardcode the colors, which can be problematic depending on the colorscheme you use.

View File

@ -4,7 +4,7 @@ detect:
filename: "\\.(S|s|asm)$"
rules:
# This file is made for NASM assembly
# This file is made mainly for NASM assembly
## Instructions
# x86
@ -108,3 +108,16 @@ rules:
rules:
- todo: "(TODO|XXX|FIXME):?"
## C-like comments (supported by some assemblers)
- comment:
start: "//"
end: "$"
rules:
- todo: "(TODO|XXX|FIXME):?"
- comment:
start: "/\\*"
end: "\\*/"
rules:
- todo: "(TODO|XXX|FIXME):?"

View File

@ -19,5 +19,4 @@ rules:
- comment:
start: "#"
end: "$"
rules: []

View File

@ -30,7 +30,6 @@ rules:
- preproc:
start: "\\$(\\{|ENV\\{)"
end: "\\}"
rules: []
- identifier.macro: "\\b(APPLE|UNIX|WIN32|CYGWIN|BORLAND|MINGW|MSVC(_IDE|60|71|80|90)?)\\b"

View File

@ -27,12 +27,10 @@ rules:
- constant.string:
start: "`"
end: "`"
rules: []
- constant.string:
start: "%x\\{"
end: "\\}"
rules: []
- constant.string:
start: "\""
@ -68,5 +66,4 @@ rules:
- constant:
start: "<<-?'?EOT'?"
end: "^EOT"
rules: []

File diff suppressed because one or more lines are too long

View File

@ -109,13 +109,10 @@ rules:
- comment:
start: "//"
end: "$"
rules: []
- comment:
start: "/\\*"
end: "\\*/"
rules: []
- comment:
start: "/\\+"
end: "\\+/"
rules: []

View File

@ -19,7 +19,6 @@ rules:
- default:
start: "<%"
end: "%>"
rules: []
- preproc: "<%|%>"
- red: "&[^;[[:space:]]]*;"
@ -37,6 +36,5 @@ rules:
- identifier.macro:
start: "<<-?'?EOT'?"
end: "^EOT"
rules: []
- todo: "(XXX|TODO|FIXME|\\?\\?\\?)"

View File

@ -22,7 +22,6 @@ rules:
start: "'"
end: "'"
skip: "\\\\."
rules: []
# - constant.specialChar: "%."
# - constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
# - constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
@ -42,4 +41,3 @@ rules:
- comment:
start: "%"
end: "$"
rules: []

View File

@ -38,7 +38,6 @@ rules:
start: "'"
end: "'"
skip: "\\\\."
rules: []
- comment:
start: "#"

View File

@ -19,7 +19,6 @@ rules:
- constant.string:
start: "\\b([Ss.]\" )"
end: "\""
rules: []
- comment:
start: "\\("

View File

@ -45,4 +45,3 @@ rules:
- comment:
start: "\\(\\*"
end: "\\*\\)"
rules: []

View File

@ -10,7 +10,6 @@ rules:
- special:
start: "^```"
end: "^```"
rules: []
# heading lines
- special: "^#{1,3}.*"
# unordered list items

View File

@ -20,4 +20,3 @@ rules:
- comment:
start: "#"
end: "$"
rules: []

View File

@ -22,7 +22,6 @@ rules:
- comment.line:
start: "^#"
end: "$"
rules: []
# Diffs (i.e. git commit --verbose)
- default:

View File

@ -11,4 +11,3 @@ rules:
- comment:
start: "#"
end: "$"
rules: []

View File

@ -16,4 +16,3 @@ rules:
- comment.line:
start: "^#"
end: "$"
rules: []

View File

@ -47,7 +47,6 @@ rules:
- constant.string:
start: "`"
end: "`"
rules: []
- comment:
start: "//"

View File

@ -44,4 +44,3 @@ rules:
- comment:
start: "#"
end: "$"
rules: []

View File

@ -11,7 +11,6 @@ rules:
- constant:
start: "(\\\\|\\\\\\\\)n\\["
end: "]"
rules: []
- type: "^\\.[[:space:]]*[^[[:space:]]]*"
- comment: "^\\.\\\\\".*$"
@ -19,12 +18,10 @@ rules:
- constant.string:
start: "(\\\\|\\\\\\\\)\\*\\["
end: "]"
rules: []
- constant.specialChar: "\\\\\\(.."
- constant.specialChar:
start: "\\\\\\["
end: "]"
rules: []
- identifier.macro: "\\\\\\\\\\$[1-9]"

View File

@ -75,7 +75,6 @@ rules:
- identifier:
start: "[$][{]"
end: "[}]"
rules: []
# Triple-single-quoted strings
- constant.string:
@ -90,7 +89,6 @@ rules:
- constant.string:
start: "[$]/"
end: "/[$]"
rules: []
# Single-line comments
- comment:
@ -110,4 +108,3 @@ rules:
- comment:
start: "/[*][*]@?"
end: "[*]/"
rules: []

View File

@ -4,25 +4,28 @@ detect:
filename: "\\.hs$"
rules:
- symbol.operator: "[!#$%&:*+/<=>?@.\\\\^\\|~\\p{Sm}\\-]+"
# Identifiers (with or without a module name)
- type: "\\b([A-Z][A-Za-z0-9_]*\\.)*[A-Z]+[A-Za-z0-9_']*\\b"
- default: "\\b([A-Z][A-Za-z0-9_]*\\.)*[a-z][A-Za-z0-9_']*\\b"
- statement: ";"
- symbol.bracket: "[\\(\\)\\[\\]\\{\\}]"
- special: "`[A-Za-z0-9']+`"
# Keywords
- statement: "\\b(as|case|of|class|data|default|deriving|do|forall|foreign|hiding|if|then|else|import|infix|infixl|infixr|instance|let|in|mdo|module|newtype|qualified|type|where)\\b"
# Various symbols
- symbol: "(\\||@|!|:|_|~|=|\\\\|;|\\(\\)|,|\\[|\\]|\\{|\\})"
# Operators
- symbol.operator: "(==|/=|&&|\\|\\||<|>|<=|>=)"
# Various symbols
- special: "(->|<-)"
- symbol: "\\.|\\$"
- statement: "\\b(case|of|class|data|default|deriving|do|forall|foreign|hiding|if|then|else|import|infix|infixl|infixr|instance|let|in|mdo|module|newtype|qualified|type|where)\\b"
# Data constructors
- constant.bool: "\\b(True|False)\\b"
- constant: "\\b(Nothing|Just|Left|Right|LT|EQ|GT)\\b"
- constant: "\\(\\)" # Unit
- constant.number: "\\b(0[xX][0-9A-Fa-f]+|0[oO][0-7]+|0[bB][01]+|[-]?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?)\\b"
# Data classes
- identifier.class: "\\b(Read|Show|Enum|Eq|Ord|Data|Bounded|Typeable|Num|Real|Fractional|Integral|RealFrac|Floating|RealFloat|Monad|MonadPlus|Functor|Foldable|Additive|Zip)[ ]"
- identifier.class: "\\b(Additive|Applicative|Bounded|Data|Enum|Eq|Floating|Foldable|Fractional|Functor|Integral|Monad|MonadPlus|Monoid|Num|Ord|Read|Real|RealFloat|RealFrac|Semigroup|Show|Traversable|Typeable|Zip)[ ]"
# Strings
- constant.string:
@ -30,7 +33,8 @@ rules:
end: "\""
skip: "\\\\."
rules:
- constant.specialChar: "\\\\."
- special: "\\\\&"
- constant.specialChar: "\\\\([abfnrtv\"'\\\\]|[0-9]+|x[0-9a-fA-F]+|o[0-7]+|NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC[1-4]|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL)"
# Comments
- comment:
@ -45,4 +49,4 @@ rules:
rules:
- todo: "(TODO|XXX|FIXME):?"
- identifier.micro: "undefined"
- identifier.macro: "undefined"

View File

@ -17,7 +17,6 @@ rules:
- default:
start: ">"
end: "<"
rules: []
- symbol.tag: "<|>"
- constant.string.url: "(ftp(s)?|http(s)?|git|chrome)://[^ ]+"

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