I’ve made myself a browser-based text editor using the JavaScript libraries from https://codemirror.net which works nicely except there’s no Prolog mode in the long list of languages it does syntax highlighting for.
I thought I was in luck when Google brought up GitHub - JanWielemaker/CodeMirror: In-browser code editor but there doesn’t seem to be prolog in the mode list.
For anyone interested, I’ve included my code below. I’ve put my editor at a password protected URL. You would think a browser-based text editor would be easy, but I bumped my head against two problems which needed hacking around:
- Browser-based JavaScript has no builtin functions for server-side file editing. I read up on its File API, but that’s strictly for files on the client machine. To get around this, you have to send text to a web application written in your favourite programming language (SWI Prolog in our case). I’ve included my editor_app.pl below which I got Nginx to link to a port proxied to /cmd
- The standard HTML text editing box textarea appears to be unusable. I spent several hours cursing my Prolog script since I though it wasn’t updating the file, until it occurred to me to do some more research on textarea – and discovered it doesn’t actually send the current text it holds as value, but the original unedited text it was initialised with, which is why my Prolog script was simply replacing edited files with their original text. Libraries like CodeMirror only use textarea as an object in HTML files which they completely replace. Once I started using CodeMirrors getValue and setValue instead of textarea’s value attribute, everything worked.
My index.html looks like this
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Text Editor</title>
<link rel="stylesheet" href="/lib/codemirror.css">
<link rel="stylesheet" href="/theme/cobalt.css">
<script src="/lib/codemirror.js"></script>
<script src="/mode/css/css.js"></script>
<script src="/mode/htmlmixed/htmlmixed.js"></script>
<script src="/mode/javascript/javascript.js"></script>
<script src="/mode/markdown/markdown.js"></script>
<script src="/mode/sql/sql.js"></script>
<script src="/mode/xml/xml.js"></script>
</head>
<body>
<label id="label_myTextArea" for="content">Tell us your story:</label><br>
<textarea id="myTextArea" name="content" class="CodeMirror" rows="50">
It was a dark and stormy night...
</textarea><br>
<button id="save-button" type="button">Save</button><br>
<ul>
<li><a href="/doc/manual.html">CodeMirror Manuel</a></li>
</ul>
<script src="/scripts/myeditor.js"></script>
</body>
</html>
My scripts/myeditor.js looks like this
let myCodeMirror = {};
function save(event) {
let params = new URLSearchParams(document.location.search);
if (params.has('file')) {
fetch('/cmd', { method: 'POST'
, headers: {"Content-Type": "application/json"}
, body: JSON.stringify({ "cmd": "write"
, "file": params.get('file')
, "content": myCodeMirror.getValue()
})
})
.then(response => response.json())
.then(data => console.log(data));
} else {
document.querySelector("#myTextArea").textContent = "No file to edit";
}
}
function setup(event) {
let params = new URLSearchParams(document.location.search);
if (params.has('file')) {
document.querySelector("#label_myTextArea").textContent = params.get('file');
fetch('/cmd', { method: 'POST'
, headers: {"Content-Type": "application/json"}
, body: JSON.stringify({ "cmd": "read"
, "file": params.get('file')
})
})
.then(response => response.json())
.then(function(data) {
myCodeMirror = CodeMirror.fromTextArea(myTextArea,
{ lineNumbers: true
, mode: params.get('mode')
, theme: "cobalt"
});
myCodeMirror.setValue(data.content);
});
} else {
document.querySelector("#myTextArea").textContent = "No file to edit";
}
}
window.addEventListener("DOMContentLoaded", setup);
document.querySelector("#save-button").addEventListener("click", save);
And finally my prolog web app looks like this
:- use_module([ library(http/http_unix_daemon)
, library(http/http_server)
]).
:- http_handler(/, cmd_handler, []).
cmd_handler(Request) :-
http_read_json_dict(Request, DictIn),
cmd_manager(DictIn.cmd, DictIn, DictOut),
reply_json_dict(DictOut).
cmd_manager("read", DictIn, json{content: Content}) :-
read_file_to_string(DictIn.file, Content, []).
cmd_manager("write", DictIn, json{file: DictIn.file, time: Time}) :-
setup_call_cleanup(
open(DictIn.file, write, Stream),
format(Stream, "~w", [DictIn.content]),
close(Stream)
),
time_file(DictIn.file, Time).
I fire it up as swipl editor_app.pl --port=4152 --pidfile=http.pid
and then my nginx config file for my secret editor URL contains
server {
root /home/roblaing/webapps/editor;
location / {
auth_basic "Restricted Content";
auth_basic_user_file /etc/nginx/.htpasswd;
}
location /cmd {
proxy_pass http://localhost:4152/;
}
}
Loading files to edit with their syntax highlighter works by passing their full path and highlighting modes as query parameters as in http://mydomain/?file=/home/roblaing/webapps/editor/scripts/myeditor.js&mode=javascript
It all seems to work pretty nicely, and I’m glad I no longer have to edit on my local machine then scp to my server. Having a Prolog syntax highlighting mode would make it perfect.