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 onSelectionChange
E.htmlAttribute onTextChange
E.htmlAttribute onCursorChange
Send information to Codemirror
HtmlAttr.attribute "text" model.initialText
HtmlAttr.attribute "editordata" (fixEditorData model.language model.editorData |> encodeEditorData)
HtmlAttr.attribute "selection" (stringOfBool model.doSync
HtmlAttr.attribute "editcommand" (OTCommand.toString model.editCommand.counter model.editCommand.command
Left to Right Sync
Data flow
- User presses ctl-S; A
KeyMsg keyMsg
is received and passed toEditorSync.handleKeypress
handleKeypress
inspects the message. If it is ctrl-S,model.doSync
is toggled. This is a signal, viaHtmlAttr.attribute "selection" (stringOfBool model.doSync)
inEditor.view
to Codemirror send the text that has been selected in the edtor back to Elm . Codemirror does this via functionsendSelectedText
which creates the event "selected-text"- The "selected-text" event is detected by the editor which sends the Elm message
SelectedText str
to the frontend. That message callsFrontend.EditorSync.firstSyncLR model str
This 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
+ 400
part 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)
]
[]
)
)