Commit Graph

140 Commits

Author SHA1 Message Date
Dmytro Maluka
7aa495fd3f Remove backup when buffer becomes unmodified
We should cancel previously requested periodic backup (and remove this
backup if it has already been created) not only when saving or closing
the buffer but also in other cases when the buffer's state changes from
modified to unmodified, i.e. when the user undoes all unsaved changes.

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

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

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

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

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

So, to fix these issues:

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

2. Make the communication between these goroutines fully synchronous:

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

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

This change means that the hash will be recalculated every time the user
types or deletes a character. But that is what already happens anyway,
since inserting or deleting characters triggers redrawing the display,
in particular redrawing the status line, which triggers Modified() in
order to show the up-to-date modified/unmodified status in the status
line. And with this change, we will be able to check this status
more than once during a single "handle event & redraw" cycle, while
still recalculating the hash only once.
2025-08-03 14:48:26 +02:00
Dmytro Maluka
4ade5cdf24 Make calcHash() a method of SharedBuffer
This will make it easier to use calcHash() in other SharedBuffer
methods.
2025-08-03 14:47:27 +02:00
Neko Box Coder
d7e43d4974 Adding missing file closes, rewriting safeWrite() to be more robust (#3807) 2025-07-22 22:58:18 +02:00
Jöran Karl
1eef4bb3e0 Convert leftover usages of path to filepath 2025-04-26 20:37:49 +02:00
niten94
0d5b2b73e3 Skip save on open or term command if buffer is shared 2025-04-18 19:19:19 +08:00
Jöran Karl
8b21724c6e buffer: Store the encoding inside the buffer 2025-02-28 19:02:16 +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
35d295dd04 buffer: Remove superfluous backupTime 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
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
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
c61670e86f buffer: Store the overwrite mode 2025-01-22 17:12:50 +01:00
Jöran Karl
10511c9baf buffer/buffer: Store fileformat in LocalSettings on auto detection 2024-08-18 21:10:37 +02:00
Jöran Karl
05529596cf action/command: Prevent overwriting settings set locally or by plugin
- on `reload`
- on `filetype` change
2024-08-18 21:10:37 +02:00
Dmytro Maluka
fd3a00226c Add matchbraceleft option (#3432)
Add `matchbraceleft` option to allow disabling the default behavior
matching not just the brace under cursor but also the brace to the left
of it (which is arguably convenient, but also ambiguous and
non-intuitive). With `matchbraceleft` disabled, micro will only match
the brace character that is precisely under the cursor, and also when
jumping to the matching brace, will always move cursor precisely to the
matching brace character, not to the character next to it.

Nota bene: historical journey:

- There was already a `matchbraceleft` option introduced in commit
  ea6a87d41a, when this feature (matching brace to the left) was
  introduced first time. That time it was matching _only_ the brace
  to the left, _instead_ of the brace under the cursor, and was
  disabled by default.

- Later this feature was removed during the big refactoring of micro.

- Then this feature was reintroduced again in commit d1e713ce08, in
  its present form (i.e. combined brace matching both under the cursor
  and to the left, simulating I-beam cursor behavior), and it was
  introduced unconditionally, without an option to disable it.

- Since then, multiple users complained about this feature and asked
  for an option to disable it, so now we are reintroducing it as an
  option again (this time enabled by default though).
2024-08-18 21:08:05 +02:00
Dmytro Maluka
0b15b57e63 buffer: Set fastdirty=true for large file when reopening
Similarly to how we force `fastdirty` to true when opening a large file
(when creating the buffer), force it also when reopening a file, in case
the file on disk became large since we opened it.
2024-08-18 15:51:47 +02:00
Dmytro Maluka
7fe98ccfee calcHash: Remove checking file size
Let calcHash() unconditionally hash whatever buffer it is asked to hash,
and let its callers explicitly check if the buffer is too large before
calling calcHash(). This makes things simpler and less error-prone
(no extra source of truth about whether the file is too large, we don't
need to remember to check if calcHash() fails, we can be sure calcHash()
will actually update the provided hash), and actually faster (since just
calculating the buffer size, i.e. adding line lengths, is faster than
md5 calculation).

In particular, this fixes the following bugs:

1. Since ReOpen() doesn't check calcHash() return value, if the reloaded
   file is too large while the old version of the file is not,
   calcHash() returns ErrFileTooLarge and doesn't update origHash, so
   so Modified() returns true since the reloaded file's md5 sum doesn't
   match the old origHash, so micro wrongly reports the newly reloaded
   file as modified.

2. Since Modified() doesn't check calcHash() return value, Modified()
   may return false positives or false negatives if the buffer has
   *just* become too large so calcHash() returns ErrFileTooLarge and
   doesn't update `buff`.
2024-08-18 15:42:18 +02:00
Dmytro Maluka
c0f6b65ed6 calcHash: use correct line endings
Make calcHash() respect the buffer's file endings (unix vs dos), to make
its calculation of the file size consistent with how we calculate it in
other cases (i.e. when opening or saving the file) and with the
`fastdirty` option documentation, i.e. make calcHash() return
ErrFileTooLarge if and only if the exact file size exceeds 50KB.
2024-08-18 13:35:37 +02:00
Dmytro Maluka
e0f5361d97 calcHash: remove unneeded h.Write() error checks
According to the Go hash package documentation [1]:

type Hash interface {
	// Write (via the embedded io.Writer interface) adds more data to the running hash.
	// It never returns an error.
	io.Writer

[1] https://pkg.go.dev/hash#Hash
2024-08-18 13:35:27 +02:00
Dmytro Maluka
5c8bf6b3a6 Improve RemoveAllMultiCursors behavior
Use Deselect() in order to place the cursor at the beginning of the
selection, not at the end of it, and to refresh its LastVisualX.
2024-07-18 23:54:57 +02:00
Dmytro Maluka
9eb8782ff2 Rework FindMatchingBrace() interface and implementation (#3319)
Instead of passing a single brace pair to FindMatchingBrace(), make it
traverse all brace pairs in buffer.BracePairs on its own.

This has the following advantages:

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

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

    ([foo]bar)
     ^

when the cursor is on `[`:

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

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

In contrast, with `((foo)bar)` it works correctly.
2024-06-05 00:56:19 +02:00
Dmytro Maluka
5a159ce444 updateDiffSync(): fix potential race
When updateDiffSync() is called asynchronously, it should lock the
line array when calling Bytes(), to prevent race if the line array is
being modified by the main goroutine in the meantime.
2024-05-12 21:07:12 +02:00
Dmytro Maluka
bca35a5939 Simplify UpdateDiff() interface
The callback passed to UpdateDiff() is superfluous: in the synchronous
case screen.Redraw() is not needed anyway (since the screen is redrawn
at every iteration of the main loop), and in the asynchronous case
UpdateDiff() can just call screen.Redraw() directly.
2024-05-12 20:05:14 +02:00
Dmytro Maluka
08c516c730 UpdateRules: optimize out HasIncludes() usage 2024-04-21 15:14:21 +02:00
Dmytro Maluka
1bddc8d03e UpdateRules: move include logic to a helper function 2024-04-21 15:13:03 +02:00
Dmytro Maluka
3aed20fde9 UpdateRules: correct the comments
The "runtime" term is ambiguous: it refers to both built-in and user's
custom ("real runtime") files.
2024-04-19 00:10:58 +02:00
Dmytro Maluka
a436dae587 UpdateRules: allow includes in default.yaml 2024-04-18 23:29:33 +02:00
Dmytro Maluka
5610d01e08 UpdateRules: fix set filetype unknown
Fix `set filetype unknown` not working as expected in the following
scenario:

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

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

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

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

This also makes the code simpler and easier to understand.
2024-04-18 22:39:16 +02:00
Jöran Karl
6cd39efddc buffer: Refactor UpdateRules() by creating further helper functions
- `findRealRuntimeSyntaxDef()`
- `findRuntimeSyntaxDef()`

This will reduce the length of this function again and thus improves the
readability.
2024-04-18 18:33:00 +02:00
Jöran Karl
089160a7e4 buffer: Refactor UpdateRules() by creating parseDefFromFile()
This will reduce the length of this function and thus improves the
readability.
2024-04-18 18:29:52 +02:00
Jöran Karl
ed993a4021 buffer: Precise comment about searching in the internal runtime files 2024-04-18 18:20:11 +02:00
Jöran Karl
87ee41ab27 buffer: Don't process the default syntax in the user's custom file lookup
It needs to be processed earliest in the moment no match could be determined.
2024-04-18 18:20:08 +02:00
Jöran Karl
6ffabd626f buffer: Let the user override the default.yaml 2024-04-17 18:10:15 +02:00
Jöran Karl
f265179def buffer: Correct error message in case of failed read 2024-04-14 16:55:59 +02:00
Jöran Karl
390794213e syntax: Provide default.yaml as fallback definition 2024-04-14 16:55:59 +02:00
Jöran Karl
430da61314 highlighter: Remove EmptyDef since it's superseeded by a nil check of SyntaxDef 2024-04-14 16:55:59 +02:00
lvyaoting
d1d38d1ed7 chore: fix some typos (#3239)
Signed-off-by: lvyaoting <lvyaoting@outlook.com>
2024-04-08 12:04:38 +02:00
Jöran Karl
2830c4878e buffer: Lock the LineArray in case of modifications and export this lock 2024-04-05 14:24:06 +02:00
Dmytro Maluka
20bf7096b8 Make set filetype off work as expected (#3216)
Disable syntax highlighting after setting filetype to `off`.
2024-03-25 19:38:33 +01:00
Dmytro Maluka
053949eac6 UpdateRules: de-densify code arouns signatureMatch
Purely cosmetic change: make the code a bit more readable by reducing
its visual "density".
2024-03-24 04:47:04 +01:00
Dmytro Maluka
9ee82a6cb3 UpdateRules: rename syntaxFileBuffer to syntaxFileInfo
To make it more clear. Why Buffer?
2024-03-24 04:47:04 +01:00
Dmytro Maluka
5492d30953 UpdateRules: add comment about the reason for signature match 2024-03-24 04:47:04 +01:00
Dmytro Maluka
6c3b5ad17c UpdateRules: refactor "header.FileType == ft" case 2024-03-24 04:47:04 +01:00
Dmytro Maluka
39e410aa46 UpdateRules: reintroduce using header regex for filetype detection
Replacing header patterns with signature patterns was a mistake, since
both are quite different from each other, and both have their uses. In
fact, this caused a serious regression: for such files as shell scripts
without *.sh extension but with #!/bin/sh inside, filetype detection
does not work at all anymore.

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

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

Precisely, the new behavior is:

1. if there is at least one filename match, use filename matches only
2. if there are no filename matches, use header matches
3. in both cases, try to use signatures to find the best match among
multiple filename or header matches
2024-03-24 04:47:04 +01:00
Dmytro Maluka
2b8d925925 UpdateRules: rename syntaxFiles to fnameMatches
As a preparation for reintroducing header matches.
2024-03-24 04:47:04 +01:00