I am writing this so that I will remember (as I have forgotten some of the things here before). However, since others may find it useful, and since I’d really like any double-checking I can get, I am publishing it.
In a web-based Create Framework related project, we are using Mozilla’s wonderful Bespin text editor. In the process of using it, we sometimes need to modify the editor in some way, perhaps by adding new features, perhaps by finding and fixing bugs.
The following is technical in nature.
In Bespin, characters are arranged in rows and columns. Any character may be referred to with a row number and a column number. This set of two numbers is referred to as a position.
Bespin’s editor has two significant parts which each have their own kind of position: the editor, and the model. This may seem silly at first: why should the editor’s position at row 5 column 4 be any different than the model’s?
There are a few different reasons for this, and the reasons could grow. It boils down to one concept: the editor, in order to enhance the usability, does not show characters exactly as they are in the model. For instance, a single tab in the model will usually be transformed into more than one space in the editor (for instance, four spaces). In this case, column 4 is the column immediately following a tab at the beginning of the line in the editor — the model position would be (5, 1), pointing to the column immediately following the tab character.
Also, code folding hides some lines of code, so row 4 could actually have been lines 4 through 30, but collapsed to take just one line. In this case, the model position for (5, 4) would be (31, 1).
The positions in the editor are called cursor positions (as they are possible positions of the cursor in the editor), and the positions in the model are called model positions.
As detailed in Gordon Hemsley’s guide on working with tabs, there are a few helper functions. There are two that are most important:
cursorManager.getCursorPosition(modelPosition); cursorManager.getModelPosition(cursorPosition);
As you might expect by looking at their names and arguments, they convert cursor positions to model positions and vice-versa. There are two proxies to these functions in the editor component as well:
editor.getCursorPos(modelPosition); //note the lack of "ition" editor.getModelPos(cursorPosition);
So what positions are used where? The model, naturally, always uses model positions. The editor always uses cursor positions. Actions (where most of the actual editing takes place) have convert between the two.
For instance, the action insertCharacter (in actions.js), used whenever you type in a letter (with extra comments inserted marking the conversions):
insertCharacter: function(args) { if (this.editor.readonly) return; if (this.editor.selection) { this.deleteSelectionAndInsertCharacter(args); } else { // SEE HERE: // the "pos" argument given to insertCharacter is, of course, the current cursor position. // so, we convert it to a model position and hand it off to the model. this.model.insertCharacters(this.cursorManager.getModelPosition(args.pos), args.newchar); this.cursorManager.moveRight(true); this.repaint(); // undo/redo args.action = "insertCharacter"; var redoOperation = args; var undoArgs = { action: "deleteCharacter", pos: bespin.editor.utils.copyPos(args.pos), queued: args.queued }; var undoOperation = undoArgs; this.editor.undoManager.addUndoOperation(new bespin.editor.UndoItem(undoOperation, redoOperation)); } },