Codemirror Integration
Custom Element
Communication between Elm and Codemirror
Codemirror side
On the Codemirror side, communication with Elm is handled by
function AttributeChangedCallback(attr, oldVal, newVal).
Elm side
The handler is View.Editor.view:
Receive information from Codemirror
E.htmlAttribute onSelectionChangeE.htmlAttribute onTextChangeE.htmlAttribute onCursorChange
Send information to Codemirror
HtmlAttr.attribute "text" model.initialTextHtmlAttr.attribute "editordata" (fixEditorData model.language model.editorData |> encodeEditorData)HtmlAttr.attribute "selection" (stringOfBool model.doSyncHtmlAttr.attribute "editcommand" (OTCommand.toString model.editCommand.counter model.editCommand.command
Left to Right Sync
Data flow
- User presses ctl-S; A
KeyMsg keyMsgis received and passed toEditorSync.handleKeypress handleKeypressinspects the message. If it is ctrl-S,model.doSyncis toggled. This is a signal, viaHtmlAttr.attribute "selection" (stringOfBool model.doSync)inEditor.viewto Codemirror send the text that has been selected in the edtor back to Elm . Codemirror does this via functionsendSelectedTextwhich creates the event "selected-text"- The "selected-text" event is detected by the editor which sends the Elm message
SelectedText strto the frontend. That message callsFrontend.EditorSync.firstSyncLR model strThis function issues a command to bring ....
Code
model.doSync- Message
StartSync: togglemodel.doSync - Message
NextSync
SelectedText str ->
Frontend.EditorSync.firstSyncLR model str
SendSyncLR ->
( { model | syncRequestIndex = model.syncRequestIndex + 1 }, Effect.Command.none )
SyncLR ->
Frontend.EditorSync.syncLR model
StartSync ->
( { model | doSync = not model.doSync }, Effect.Command.none )
NextSync ->
Frontend.EditorSync.nextSyncLR model
Sending information to Codemirror
The view function for a document is
viewDocument :
Scripta.API.DisplaySettings
-> Compiler.DifferentialParser.EditRecord
-> List (Element FrontendMsg)
viewDocument displaySettings editRecord =
Scripta.API.render displaySettings editRecord
|> List.map (E.map Render)
where in module Types we find the
-- Render.Msg
type MarkupMsg
= SendMeta { begin : Int, end : Int, index : Int, id : String }
| SendLineNumber Int
| SelectId String
| HighlightId String
| GetPublicDocument Handling String
| GetPublicDocumentFromAuthor Handling String String
| GetDocumentWithSlug Handling String
| ProposeSolution SolutionState
When a user clicks on rendered text, a message of type
Render (SendLineNumber Int) is sent to the update function of Scripta.
The clause Render msg_ of the update function calls
Frontend.Update.render model msg_. In the case of
Render.Msg.SendLineNumber line the handler is
-- Frontend.Upate
Render.Msg.SendLineNumber line ->
( { model
| selectedId = ""
, messages = [ { txt = "Line " ++ String.fromInt (line + 1), status = MSGreen } ]
, editorLineNumber = String.fromInt line
}
, Command.none
)
The important part is the line editorLineNumber = String.fromInt line.
This value is stored in the model. The view function for the editor subsequently
reads this value and communicates it to Codemirror
it to scroll the given line into view and highlight it.
Here is the relevant part of editor.js:
(see HtmlAttr.attribute "linenumber" ... below).
case "linenumber":
console.log("!!!@ IN linenumber !")
// receive info from Elm (see Main.editor_)
// scroll the editor to the given line
var lineNumber = parseInt(newVal) + 2
var loc = editor.state.doc.line(lineNumber)
console.log("Attr case lineNumber", loc)
console.log("position", loc.from)
editor.dispatch({selection: {anchor: parseInt(loc.from)}})
editor.scrollPosIntoView(loc.from + 400)
break
TODO:
-
The
+ 400part of the code ensures that the source text is scrolled up somewhat from the bottom margin of the editor. We need to make this conditional on the line number, so that if the line number is too small, the text is not scrolled up too far. -
We need a more granular LR sync -- ideally on the word or phrase level.
-- View.Editor
view : FrontendModel -> Element FrontendMsg
view model =
Element.Keyed.el
[ -- RECEIVE INFORMATION FROM CODEMIRROR
E.htmlAttribute onSelectionChange
, E.htmlAttribute onTextChange
, E.htmlAttribute onCursorChange
, htmlId "editor-here"
...
]
( stringOfBool model.showEditor
, E.html
(Html.node "codemirror-editor"
[ -- SEND INFORMATION TO CODEMIRROR
, HtmlAttr.attribute "linenumber" (shiftLineNumber model.language model.editorLineNumber)
, HtmlAttr.attribute "selection" (stringOfBool model.doSync)
, HtmlAttr.attribute "editcommand" (OTCommand.toString model.editCommand.counter model.editCommand.command)
]
[]
)
)