Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework filetype change, reload command and autosave #3343

Merged
merged 21 commits into from
Aug 19, 2024

Conversation

JoeKar
Copy link
Collaborator

@JoeKar JoeKar commented Jun 13, 2024

  • the change of the filetype could cause unexpected behavior depending on global options being reset, while a volatile setting has been used.
  • the reload command could cause the same unexpected behavior and additionally doesn't really reload the user settings
  • autosave is now aborted in case his value has changed to something smaller or equal to 0.

Fixes #3342

@dmaluka
Copy link
Collaborator

dmaluka commented Jun 16, 2024

  1. This is not the only issue here. The reload command or setting filetype indeed changes the autosave value, but as I can see, this value change has no effect: the file is still autosaved according to the old setting. (This issue is orthogonal to the issue of whether the setting is a volatile setting and should not be changed by reload in the first place.)

So it seems that instead of hacking InitGlobalSettings() we should rework the reload behavior: it should not call InitGlobalSettings() but rather call SetGlobalOptionNative() for every option that changed (at least), to make the changes take effect.

  1. And as for the filetype setting, do we really need to re-read and reinitialize global settings when changing the filetype? Shouldn't we really just call InitLocalSettings() then? (I see that was added in 3ef0267 to fix the issue Bug: when manually running set filetype <type>, micro should reload appropriate settings from the config file for that type, but it doesn't #2712, but that issue was not really about updating settings because settings.json changed, it was just about updating per-filetype settings because the filetype changed, right?)

After such 2 changes, InitGlobalSettings() would be only called at micro startup, before initializing volatile settings, so we wouldn't need to change it.

@dmaluka
Copy link
Collaborator

dmaluka commented Jun 16, 2024

  1. This issue is orthogonal to the issue of whether the setting is a volatile setting and should not be changed by reload in the first place.

BTW maybe we actually do want reload to change volatile settings as well?

@JoeKar
Copy link
Collaborator Author

JoeKar commented Jun 17, 2024

[...] but as I can see, this value change has no effect: the file is still autosaved according to the old setting.

I saw something different, otherwise I wouldn't have created the PR.
Anyway, I agree that taking care of this in the two mentioned scenarios seems to be the better approach.

BTW maybe we actually do want reload to change volatile settings as well?

Hm, depends on what the community and or us really expects from that command to do.
At least for now it's somehow undocumented/undefined what shall happen in the scenario the user applied a local resp. volatile option and uses reload afterwards in the same session.

@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from 479f0d9 to eee2f0c Compare June 17, 2024 15:48
@JoeKar JoeKar changed the title config: Keep volatile settings in case a reinitialization takes place buffer/settings: Don't reinitialize global settings on filetype change Jun 17, 2024
@dmaluka
Copy link
Collaborator

dmaluka commented Jun 17, 2024

I saw something different

Are you sure? I've checked again that with autosave and other options that are specially handled by SetGlobalOptionNative() and buffer.SetOptionNative() (e.g. hlsearch, mouse), if I run e.g. micro -mouse false and then run reload in micro, then show mouse shows that mouse is set to true, however the mouse is still disabled.

Moreover, I see it's even worse: in the basic case when I change an option value in settings.json by editing settings.json (while micro is running) and then run reload, the option value doesn't change at all.

internal/buffer/settings.go Show resolved Hide resolved
In case `micro` was started with a `-<option>` being set to something other
than the default value and other than set within the `settings.json`
then this volatile option will be reverted to the default or to the value
defined in `settings.json`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reload description is expanded also in PR #3240 (still not merged, but IMHO it is ready for merge).
This change would make it even more lengthy and confusing.

...Now it actually seems a bit more reasonable to me if reload will not change volatile settings. Its job is to update those settings that were loaded from settings.json (since settings.json changed), arguably it should not update those settings that were not loaded from settings.json in the first place.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change would make it even more lengthy and confusing.

Ok, drop it (locally) already.

...Now it actually seems a bit more reasonable to me if reload will not change volatile settings.

Yes, that's an other way around. Sounds feasible and leaves the changed volatiles alone.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds feasible and leaves the changed volatiles alone.

At least as long as they aren't changed by the settings.json too.

@JoeKar
Copy link
Collaborator Author

JoeKar commented Jun 18, 2024

Are you sure?

Yep, tried it now once again with the current state (set filetype without config.InitGlobalSettings()) against the master...

current:

$ micro -autosave 20 test # filetype unknown
modify
set filetype ini
Ctrl-Q
-> no prompt (y/n/esc)
-> modification stored

master:

$ micro -autosave 20 test # filetype unknown
modify
set filetype ini
Ctrl-Q
-> prompt (y/n/esc)

@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from eee2f0c to 5216507 Compare June 18, 2024 20:57
@JoeKar JoeKar changed the title buffer/settings: Don't reinitialize global settings on filetype change Rework filetype change and reload command Jun 18, 2024
@dmaluka
Copy link
Collaborator

dmaluka commented Jun 18, 2024

Yep [...]

Ah... you checked that save without prompt on exit gets disabled after set filetype ini, but you didn't check if autosave every 20 seconds gets disabled after set filetype ini. It doesn't get disabled. Try it.

@JoeKar
Copy link
Collaborator Author

JoeKar commented Jun 19, 2024

master:

$ micro -autosave 20 test # filetype unknown
show autosave -> 20
set filetype ini
show autosave -> 0
autosaved after 20s

current:

$ micro -autosave 20 test # filetype unknown
show autosave -> 20
set filetype ini
show autosave -> 20
autosaved after 20s

Ok, you're right...saw it now. Totally weird.
autosave still needs a fix to be disabled, after it was started initially.

@JoeKar JoeKar changed the title Rework filetype change and reload command Rework filetype change, reload command and autosave Jun 19, 2024
@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from e6beb6e to 9d84b25 Compare June 19, 2024 19:27
@JoeKar JoeKar mentioned this pull request Jun 19, 2024
@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from 9d84b25 to a60ab16 Compare July 15, 2024 19:43
@JoeKar
Copy link
Collaborator Author

JoeKar commented Jul 15, 2024

Hopefully I didn't miss something after my absence.
In case of please poke as usual.

internal/action/command.go Show resolved Hide resolved
screen.TermMessage(err)
}
b.Settings = config.DefaultCommonSettings()
b.Settings[option] = nativeValue
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why so tricky? The option will be set in b.Settings by b.SetOptionNative() anyway, so why not just:

for k := range config.DefaultCommonSettings() {
	...
}

Also it would be nice to do this in a separate commit: one commit to remove the undesired stuff for globals and unneeded parsing, and another commit to add the missing stuff for locals.

select {
case a = <-autotime:
if a > 0 {
elapsed = time.After(time.Duration(a * float64(time.Second)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to work correctly from the autosave perspective, however it seems a bit unclean: it doesn't stop the previous elapsed timer, so it will still fire after some time. Although it won't cause an unwanted autosave (thanks to the fact that the elapsed variable is now assigned to a different channel, so we will not wait on the old channel in next iterations of the loop), this also probably means that, since no one will read from the old channel when the old timer fires, the old timer will block forever on sending to the old channel, so it will not be garbage-collected even after it fires.

@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from a60ab16 to 5bcc352 Compare July 16, 2024 20:49
@JoeKar JoeKar added this to the v2.0.14 milestone Jul 19, 2024
@JoeKar
Copy link
Collaborator Author

JoeKar commented Jul 21, 2024

Just this one left for v2.0.14, what do you think?

internal/action/command.go Outdated Show resolved Hide resolved
SetGlobalOptionNative(k, parsedSettings[k])
continue
}
if _, ok := oldParsedSettings[k]; ok {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need oldParsedSettings? If the setting is not present in the new parsed settings, we should set it to the default in any case?

If it is to avoid unneeded setting it to the default if it's already default, then it looks inconsistent with the above case when it is present in parsedSettings: in that case we don't check if the new parsed setting is different from the old one or not.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was sure, that it was needed in a case, but can't remember and reproduce it so far.
So long store short:
I will remove it and directly convert the second path into } else {.

@@ -20,7 +20,19 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
} else if option == "statusline" {
screen.Redraw()
} else if option == "filetype" {
config.InitLocalSettings(b.Settings, b.Path)
parsedSettings := config.ParsedSettings()
parsedSettings[option] = nativeValue
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unneeded: we already ensure skipping filetype below?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirm, that it looks hacky, but didn't find a better solution so far.
We need to start with clean parsed settings (local within this function) and call InitLocalSettings(), which then operates at this given map. But filetype must be set to parse the local settings (filetype dependent) correctly within this given map, otherwise it would ignore the ft: XYZ settings.
Since we don't want to clear it again to unknown or cause recursive calls it's ignored in the following loop.

Please correct me, in case I oversee something.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But filetype must be set to parse the local settings

Ah, indeed. Looks good then.

I'd maybe just use explicit parsedSettings["filetype"] instead of parsedSettings[option], to make it a bit more clear.

Maybe also rename parsedSettings to just settings?

internal/action/command.go Outdated Show resolved Hide resolved
@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from 5bcc352 to 66ace26 Compare July 21, 2024 19:39
@@ -49,6 +50,10 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
b.UpdateRules()
}
} else if option == "encoding" {
_, err := htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
b.Settings["encoding"] = "utf-8"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we should just return error in this case, not override previously set (valid) encoding.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So why not just return err in this case?

internal/action/command.go Outdated Show resolved Hide resolved
@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from 66ace26 to faf9c58 Compare July 21, 2024 21:33
if equal {
return nil
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of all this we can just use a local boolean variable which we set to true if we modify or delete anything in parsedSettings in this function?

...But I actually have doubts whether it's a good idea to prevent unnecessary writes so generically. Think of such a scenario, for example:

  1. The user starts micro -> settings.json is read, option foo is set to true.
  2. The user edits settings.json, changing foo to false (but doesn't run reload).
  3. The user runs set foo true -> micro doesn't update settings.json (so the permanent foo setting remains false, i.e. the set command doesn't behave as it should).

Since micro unfortunately encourages both ways of setting settings (manual editing of settings.json and the set command), we should aim to ensure that both work well, together? So we shouldn't assume that settings.json hasn't changed since the last time micro read it, in the general case? We can only assume that in the special reload case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of all this we can just use a local boolean variable which we set to true if we modify or delete anything in parsedSettings in this function?

Argh, yes. Your example shows, that it's the wrong approach anyway.

So we shouldn't assume that settings.json hasn't changed since the last time micro read it, in the general case?

Most probably we've then still some use cases left, where settings.json is overwritten without needs, but we should address this then in a different issue/PR.

We can only assume that in the special reload case?

This definitely.

internal/buffer/settings.go Outdated Show resolved Hide resolved
@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from faf9c58 to 1940ef8 Compare July 22, 2024 20:02
internal/action/command.go Outdated Show resolved Hide resolved
internal/action/command.go Outdated Show resolved Hide resolved
internal/action/command.go Outdated Show resolved Hide resolved
JoeKar added 16 commits August 18, 2024 21:10
- reload runtime files
- reset globals
- parse the settings again

since this isn't the task of a filetype change.
Additionally keep volatile settings as they have been set by the user.
Validate the parsed options directly after read and inform about errors.
Additionally pricise the documentation of the remaining
DefaultCommonSettings() and DefaultAllSettings() functions.
It doesn't need to loop over the DefaultCommonSettings() again,
since they're already included in the default settings and set via
SetGlobalOptionNative().
...and use `SetOptionNative()` instead.
@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch 2 times, most recently from 48e1b96 to fff3b18 Compare August 18, 2024 19:26
break
select {
case a = <-autotime:
if t != nil && !t.Stop() && len(elapsed) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we check elapsed only if t.Stop() returned false?

Copy link
Collaborator Author

@JoeKar JoeKar Aug 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the docs...

Stop prevents the Timer from firing. It returns true if the call stops the timer, false if the timer has already expired or been stopped.

...so in case it returns true it shouldn't lead to any elapsed timer yet.
For sanity we could decouple it from the return of Stop() and just clear elapsed in case it isn't empty till it is empty.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...so in case it returns true it shouldn't lead to any elapsed timer yet.

Aha, I see. If t.Stop() returned true, it is guaranteed that the timer has not fired, so the channel is guaranteed to be empty.

BTW I've noticed the doc also says this:

For a chan-based timer created with NewTimer(d), as of Go 1.23, any receive from t.C after Stop has returned is guaranteed to block rather than receive a stale time value from before the Stop; if the program has not received from t.C already and the timer is running, Stop is guaranteed to return true. Before Go 1.23, the only safe way to use Stop was insert an extra <-t.C if Stop returned false to drain a potential stale value.

BTW, I see we are using 1.19, maybe it is a bit too old? (1.23, on the other hand, could be a bit too new, it was only released a week ago.)

in case it isn't empty till it is empty.

For the record, it seems guaranteed that elapsed will not contain more than 1 element (i.e. there is no difference between if len(elapsed) > 0 and for len(elapsed) > 0 in this case)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I see we are using 1.19, maybe it is a bit too old? (1.23, on the other hand, could be a bit too new, it was only released a week ago.)

Yes, 1.23 or even 1.22 could lead to problems in the moment deprecated functions have been removed from the standard library.

For the record, it seems guaranteed that elapsed will not contain more than 1 element (i.e. there is no difference between if len(elapsed) > 0 and for len(elapsed) > 0 in this case)?

Ok, but since it needs to be checked anyway it doesn't hurt to do it in a loop.

@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from fff3b18 to b80ea93 Compare August 18, 2024 20:29
@JoeKar JoeKar merged commit 7dc78b7 into zyedidia:master Aug 19, 2024
3 checks passed
@JoeKar JoeKar deleted the fix/volatile-after-reinit branch August 19, 2024 17:59
@dmaluka dmaluka mentioned this pull request Aug 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Autosave locally
2 participants