Buffer vs. File Coherence
From Niki
Contents |
Introduction
An editor should keep the highest possible coherence between:
- The file currently edited (external data)
- The buffer of this file (internal data)
Moreover, this coherence must be as safe as possible. It means that, unless specified by the user, no data (external or internal) must be lost.
Most of the time, this coherence is quite simple to maintain. If the buffer is modified (and no longer corresponds to the file on the harddisk), the modified flag is set.
Sometimes the situation can become tricky. This document is an attempt to list all the possible situations and to eventually give solutions that keep coherence and safety.
States
| NEW_FILE | the file is currently edited, but does not exist on disk yet |
| UNTITLED | new window with no reference to any file |
| NORMAL | 99 percent of the time... |
| READ_ONLY | the file has read access but no write access |
| EXT_MODIFIED [1] | the file has been modified by another program (and the coherence is lost) |
| NO_READ_ACCESS [1] | the file has no read access (and the coherence is lost) |
| NO_FILE[1] | the file is unreachable (and the coherence is lost) |
Every state can be (So this is the state of the state? --Yooden):
- MODIFIED or UNMODIFIED when the buffer is modified or unmodified
- LOCKED or UNLOCKED when the set_locked() action routine is used (or not)
Events
| CHECKING | when focusing a X NEdit window for instance |
| REVERT_TO_SAVED | when using the revert_to_saved() action routine |
| SAVE | when using the save() action routine |
| CLOSE | when using the close() action routine |
| NEW_BUFFER | when using open(), new(), save_as() and when executing the command nedit or nc |
| LOCK_COMMAND | when using the set_locked() action routine |
| MODIFICATION | when a buffer is being modified |
| QUIT | when using the quit() action routine |
Some remarks:
- The event LOCK_COMMAND can be considered a special case. Indeed, it is always possible to turn on and off the "locked" boolean of a buffer. The only special case seems to be when there is no write access. In that case, the message "read only" overrides the message "locked".
- The event MODIFICATION can also be considered a special case. This event is always possible, except for: the READ_ONLY state, and in any state with LOCKED turned on. If a modification takes place when the buffer is UNMODIFIED, the buffer becomes MODIFIED.
- QUIT is also a special case. It can be compared with CLOSE. Since I'm not totally convinced about that, I will note the differences in the following discussion.
- I suppose that the NEW_BUFFER event has some special cases, according to the action that triggers it (open, save_as, new, nedit call...)
Different Possibilities
This table lists all the possible combinations of (STATE,EVENT). I think that a new check of the state should be done before any events (see Update state).
| > | Action | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| States | CHECKING | __REVERT_TO_SAVED__ | SAVE | CLOSE | NEW BUFFER | |
| 1 | __NEW_FILE__ | NO | NO | YES | YES | NB, rem1 |
| 2 | UNTITLED | NO | NO | YES | YES | NB, rem4 |
| 3 | NORMAL | YES | YES | YES | YES | NB |
| 4 | __READ_ONLY__ | YES | YES | rem3 | rem5 | NB |
| 5a | __EXT_MODIFIED__ | W? | ? | ? | ? | NO |
| 5b | __NO_READ_ACCESS__ | W? | ? | ? | ? | NB |
| 5c | __NO_FILE__ | W? | ? | ? | ? | rem2 |
EASY: that should be trivial.
NO: the action is not possible, and is just ignored.
YES: this is supposed to be well handled by the current version of NEdit (5.3)
NB: When creating a new buffer, many problem can arise. Here are some problems I have noticed:
- focus problem when calling nc with a file which already exists
- nedit should use this event to do a new "update_state()"
- when nedit tries a save_as(), I don't like the message: dialog("file exist in another window","cancel","close other window").
I think "close" should be "override" or "destroy, or something like that. 'I agree, and BTW, "exist" should be "exists". "Override other window" is my immediate thought. Or, "Save (overriding version in other window". Of course, if someone (you) then save that other window, you override what you just saved. Or, were you expecting that other window to be destroyed so that could not happen? If that is the case, maybe the message should be "Save (other window will be closed)"? (Sorry if I'm confusing the issue. -rhk)'
- Moreover, if you press "close other window", you may have to answer if you want to reload the content of this "other window". But you don't care since you want to destroy it anyway.
W?: must nedit warn the user with a dialog box if the coherence is lost?
rem1: I'm not sure that the dialog box "file doesn't exist","new_file","dismiss" is necessary (indeed, the file is not really created).
rem2: if the file is a path, why not open an untitled window with the open dialog box. If the dialog box is closed without having selected any file, then the buffer is also closed.
rem3: when trying to save a "read only","modified" buffer, maybe it should be interesting
rem4: Why does the untitled buffer have no path?? I think that ALL nedit windows should have an associated path (which can be accessed, for instance, via the global variable $file_path). Talking about macros, I think that it's difficult to know the state of the nedit buffer (except modified and locked). Why not have a global variable &buffer_state??
rem5: A more specialised dialog box should be used here. Or maybe save_as()??
?: To be continued...
Update state
The state of a buffer sould be updated regularly:
- before any events
- when focusing the X window
- automatically (every n minutes)
Let us call update_state() the function that is supposed to update the state of a buffer. The purpose of this function is to make a new measure of the status of the file currently edited:
- date
- size
- read access
- write access
- content?? (to do a proper comparison)
And then, according to these new measures, the function update_state() should change the status of the file, eventually without any warning, since there is never any data loss.
The state might be be coded with an integer (7 possible states). The state can be seen while looking at the file name in the title bar, the statistic line, or the windows menu:
| NEW_FILE | "$filename" (why not "$filename (new file)") |
| UNTITLED | "untitled" or "untitled_$n" |
| NORMAL | $filename |
| READ_ONLY | "$filename (read only)" |
| EXT_MODIFIED | "$filename (externally modified)" |
| NO_READ_ACCESS | "$filename (no read access)" |
| NO_FILE | "untitled (was: $filename)" |
The Buffer
I think that it's interesting to symbolically describe the undo system of the buffer. It can be considered as a list of different states, each state corresponding to a modification:
B_0 -> B_1 -> ... -> B_saved -> ... -> B_max
The rules are:
- MOVE: until there are no modifications, it is possible to view any state of that list without any influence.
B_0 -> B_1 -> ... -> B_saved -> ... -> B_view -> ... -> B_max
- MODIFY: as soon as a modification is made, the tail is cut:
B_0 -> B_1 -> ... -> B_saved -> ... -> B_view -> ... -> B_max
becomes:
B_0 -> B_1 -> ... -> B_saved -> ... -> B_max
- MEMORY_CUT: I suppose that for some memory purpose, it is also possible to cut the head:
B_0 -> B_1 -> ... -> B_i -> ... -> B_saved -> ... -> B_max
becomes:
B_i -> ... -> B_saved -> ... -> B_max
Remarks:
- It is possible that the saved buffer dissapears (in MODIFY and MEMORY_CUT). IMO, this is not a good thing because the loss of the saved buffer makes any serious comparison between the buffer and the file impossible. Consequently, it makes the recovery of the "coherence" impossible. Maybe a branching structure should be considered to keep track of the saved buffer:
B_0 -> B_1 -> ... -> B_p -> ... -> B_view -> ... -> B_max
and:
B_p+1 -> ... -> B_saved
- Anyway, The state of NO_SAVED_BUFFER of a buffer should be easily accesible (yes, another boolean). This can be very useful in the function that compares the file and the buffer: cmpBufAgainstFile()
- In the case of NEW_FILE and UNTITLED, the saved buffer is the first one, and in fact corresponds to an initial empty buffer:
B_empty (= B_saved) -> B_1 -> ... -> B_max
Conclusion
Well... I guess the conclusion is still to be done...
