Skip to content

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

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

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?

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

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

@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from 5bcc352 to 66ace26 Compare July 21, 2024 19:39
} 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?

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

@JoeKar JoeKar force-pushed the fix/volatile-after-reinit branch from faf9c58 to 1940ef8 Compare July 22, 2024 20:02
JoeKar added 10 commits August 18, 2024 21:10
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
@JoeKar JoeKar deleted the fix/volatile-after-reinit branch August 19, 2024 17:59
@dmaluka dmaluka mentioned this pull request Aug 27, 2024
JoeKar added a commit to JoeKar/micro that referenced this pull request Feb 17, 2025
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 zyedidia#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.
JoeKar added a commit to JoeKar/micro that referenced this pull request Feb 19, 2025
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 zyedidia#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.
JoeKar added a commit to JoeKar/micro that referenced this pull request Feb 20, 2025
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 zyedidia#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.
theredcmdcraft pushed a commit to theredcmdcraft/micro that referenced this pull request May 27, 2025
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 zyedidia#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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants