You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
webperl-for/web/democode/perleditor.html

374 lines
11 KiB
HTML

<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl Perl Editor</title>
<!-- ##### WebPerl - http://webperl.zero-g.net #####
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, http://www.igb-berlin.de
This program is free software; you can redistribute it and/or modify
it under the same terms as Perl 5 itself: either the GNU General Public
License as published by the Free Software Foundation (either version 1,
or, at your option, any later version), or the "Artistic License" which
comes with Perl 5.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the licenses for details.
You should have received a copy of the licenses along with this program.
If not, see http://perldoc.perl.org/index-licence.html
##### -->
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css" integrity="sha256-oSrCnRYXvHG31SBifqP2PM1uje7SJUyX0nTwO2RJV54=" crossorigin="anonymous" />
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.css" integrity="sha256-I8NyGs4wjbMuBSUE40o55W6k6P7tu/7G28/JGUUYCIs=" crossorigin="anonymous" />
<link rel="stylesheet" href="perleditor.css" />
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.js" integrity="sha256-uRIJ6Wfou5cTtmcCvQNA9lvsYl8sUbZXxnfG+P79ssY=" crossorigin="anonymous"></script>
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/mode/perl/perl.min.js" integrity="sha256-Uu9QBfi8gU6J/MzQunal8ewmY+i/BbCkBrcAXA5bcCM=" crossorigin="anonymous"></script>
<!--cacheable--><script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script>
"use strict";
var mergeStdOutErr = 0; //TODO Later: make an options hash instead of individual variable(s)?
var perlRunner; // the Perl runner iframe found by findPerlRunner()
var buttonBlockers = {}; // for updateButtonState()
var lastExitStatus; // for runnerState()
function makeCM (textarea,plain,ro) {
return CodeMirror.fromTextArea( textarea[0], {
viewportMargin: Infinity, // so browser's search works, not good for long documents though
lineNumbers:true, indentWithTabs:true,
tabSize:4, indentUnit:4,
mode: plain?"text/plain":"perl",
readOnly: ro?true:false,
} );
}
function runnerState (text) {
$('#runnerstate').text( text
+ (lastExitStatus ? ' (last exit status was '+lastExitStatus+')'
: '') );
}
function updateButtonState () {
$('#runperl').prop("disabled",
Object.keys(buttonBlockers).length>0 );
}
function stdOutput (which, data) { // which: 1=stdout, 2=stderr
if (mergeStdOutErr) which = 1;
var div = $(which==1?'#stdout':'#stderr');
div.show();
var cm = div.data('CodeMirrorInstance');
if (!cm) {
cm = makeCM($('textarea',div),1,1);
div.data('CodeMirrorInstance', cm);
}
if (data && data.length)
cm.setValue( cm.getValue() + data );
}
function clearStdOutput () {
$('#stdout,#stderr').each(function (i) {
var div = $(this);
var cm = div.data('CodeMirrorInstance');
if (cm) cm.setValue('');
div.hide();
});
}
function findPerlRunner () {
// assume calling this function means the runner isn't available
buttonBlockers.runnerState = 1;
updateButtonState();
// poll for perlRunner, which gets set by the message listener
var pollUntil = Date.now() + 10*1000; // milliseconds
var pollId;
pollId = window.setInterval( function () {
if (perlRunner)
window.clearInterval(pollId);
else if ( Date.now()>pollUntil ) {
if (window.confirm("Perl does not appear to have loaded yet, keep waiting?\n"
+"(If you are on a slow connection, click OK to keep waiting.)"))
pollUntil = Date.now() + 10*1000;
else
window.clearInterval(pollId);
}
else
if (window.parent && window.parent.frames["perlrunner"])
window.parent.frames["perlrunner"].postMessage(
{perlRunnerDiscovery:1}, '*');
}, 100);
}
window.addEventListener('message', function (event) {
var data = event.data;
if (data["perlRunnerState"]) {
if ( data.perlRunnerState=="Ready" ) {
perlRunner = event.source;
delete buttonBlockers.runnerState;
updateButtonState();
}
else if ( data.perlRunnerState=="Ended" ) {
if ('exitStatus' in data)
lastExitStatus = ''+data.exitStatus;
// we know the runner will reload itself now
perlRunner = null;
findPerlRunner();
}
runnerState("Perl is "+data.perlRunnerState);
}
else if (data["perlOutput"])
stdOutput(data.perlOutput.chan, data.perlOutput.data);
else if (data["perlOutputFiles"]) {
data.perlOutputFiles.forEach(function (outp) {
setupOutputFile(outp.fn, outp.text);
});
}
else if (data["perlRunnerError"]) {
$('#runnererrors').show();
$('#runnererrors>pre').append(data.perlRunnerError+"\n");
}
else console.warn("Perl Editor ignoring unknown message:",data);
});
function parseCmdLine(str) { //TODO: there's a better way to do this
var re = /"((?:\\"|\\\\|[^"])*)"|'((?:\\'|\\\\|[^'])*)'|(\S+)/g;
var argv = [];
var match;
while ((match = re.exec(str)) !== null) {
if (typeof match[1] != 'undefined') argv.push(match[1].replace(/\\\\/g,"\\").replace(/\\"/g,"\""));
else if (typeof match[2] != 'undefined') argv.push(match[2].replace(/\\\\/g,'\\').replace(/\\'/g,'\''));
else if (typeof match[3] != 'undefined') argv.push(match[3]);
}
return argv;
}
function fetchUrl(url,cm) { // fetch the contents of a URL into a CodeMirror instance
cm.setValue("Fetching URL\n"+url+"\nPlease wait...");
buttonBlockers["fetchUrls"]++;
updateButtonState();
$.get(url, function (data) {
cm.setValue(data);
},'text').fail(function (jqXHR,textStatus,errorThrown) {
cm.setValue("Fetching URL\n"+url+"\nFailed!\n"+textStatus+"\n"+errorThrown);
}).always(function () {
buttonBlockers.fetchUrls--;
if (!buttonBlockers.fetchUrls)
delete buttonBlockers.fetchUrls;
updateButtonState();
});
}
function makeCodeWithFn (fn,targ,ro,nodel) {
var div = $('<div/>',{class:"codewithfn"});
//TODO: the "delete" and "rename" functions should probably be styled better
if (!nodel) {
$('<div/>',{class:"fakelink",style:"float:right;",text:"delete"})
.appendTo(div).click(function () {
if (confirm("Are you sure you want to remove this file?"))
div.remove() });
}
$('<div/>',{class:"filename",text:fn})
.appendTo(div).click(function () {
var filename = $(this);
var newname = prompt("Please choose a new filename:", filename.text());
if (newname) filename.text(newname);
});
div.append( $('<div/>',{style:"clear:both;"}) );
var ta = $('<textarea/>').appendTo(div);
targ.before(div);
var cm = makeCM(ta, !fn.match(/\.pl$/i), ro);
div.data('CodeMirrorInstance', cm);
return {div:div,ta:ta,cm:cm};
}
function setupOutputFile (fn, text) {
var found = $('div.outputs>.filename')
.filter(function(){ return $(this).text() == fn });
var cm;
if (found.length) {
var div = found.parent();
cm = div.data('CodeMirrorInstance');
}
else {
var cfn = makeCodeWithFn(fn, $('#outputhere'), 1);
cfn.div.addClass("outputs");
cm = cfn.cm;
}
cm.setValue( text ? text : '' );
}
function setupInputFile (inp) {
var fn;
if (inp["fn"]) fn = inp.fn;
else { // autogenerate a filename
for (var i=1; i<1000; i++) {
var testfn = "input"+i+".txt";
var found = $('div.inputs>.filename')
.filter(function(){ return $(this).text() == testfn });
if (!found.length) {
fn = testfn;
break }
}
if (!fn) {
$('#runnererrors>pre').text('Too many input files');
$('#runnererrors').show();
throw 'Too many input files';
}
}
var cfn = makeCodeWithFn(fn, $('#inputhere'), 0);
cfn.div.addClass("inputs");
if (inp["text"])
cfn.cm.setValue(inp.text);
else if (inp["url"])
fetchUrl(inp.url, cfn.cm);
}
//TODO: implement a "get URL" feature
function getFileData () {
var filedata = {};
// script
var scriptdiv = $('#script');
if (scriptdiv.is(':visible')) {
filedata.script = scriptdiv.data('CodeMirrorInstance').getValue();
filedata.script_fn = scriptdiv.find('>.filename').text();
}
// inputs
$('.inputs').each(function () {
var div = $(this);
var fn = $('>.filename',div).text();
var text = div.data('CodeMirrorInstance').getValue();
if (!filedata["inputs"]) filedata.inputs = [];
filedata.inputs.push( { fn:fn, text:text } );
});
// outputs
$('.outputs').each(function () {
var fn = $(this).find('>.filename').text();
if (!filedata["outputs"]) filedata.outputs = [];
filedata.outputs.push(fn);
});
return filedata;
}
$(function () {
var hashdata = window.location.hash.substr(1);
var hash = hashdata.length>0 ? JSON.parse(decodeURIComponent(hashdata)) : {};
$('#addinput').click(function () {
var fn = prompt("Please choose a filename for the new input file:");
if (fn) setupInputFile( {fn:fn} );
});
$('#addoutput').click(function () {
var fn = prompt("Please choose a filename for the new output file:");
if (fn) setupOutputFile( fn );
});
// script
if ( hash["script"] || hash["script_url"] ) {
var fn = hash["script_fn"] ? hash.script_fn : 'script.pl';
var cfn = makeCodeWithFn(fn, $('#perlctrl'), 0, 1);
cfn.div.attr("id", "script");
if (hash["script"])
cfn.cm.setValue(hash.script);
else if (hash["script_url"])
fetchUrl(hash.script_url, cfn.cm);
}
// command line
if (hash["cmdline"])
$('#argv').val(hash.cmdline);
// input files
$('.inputs').remove();
if ( hash["inputs"] ) hash.inputs.forEach(function(inp) {
setupInputFile(inp);
});
// stdout/stderr
if (hash["mergeStdOutErr"]) {
mergeStdOutErr = 1;
$('#stdout>.filename').text("STDOUT+STDERR");
}
else {
mergeStdOutErr = 0;
$('#stdout>.filename').text("STDOUT");
}
clearStdOutput();
// output files
$('.outputs').remove();
if ( hash["outputs"] ) hash.outputs.forEach(function(outp) {
setupOutputFile(outp);
});
$('#runperl').click( function () {
clearStdOutput();
// command-line args
var argv = parseCmdLine($('#argv').val());
if (argv.length<1 || argv[0]!="perl") {
$('#runnererrors>pre').text('Invalid command line, command must be "perl"');
$('#runnererrors').show();
return;
} // else
argv.shift();
$('#runnererrors>pre').text('');
$('#runnererrors').hide();
var rp_data = getFileData();
rp_data.argv = argv;
// send message to runner
buttonBlockers.runnerState = 1;
updateButtonState();
lastExitStatus = null;
runnerState("Requesting Perl Run...");
perlRunner.postMessage({ runPerl: rp_data }, '*');
});
findPerlRunner();
});
</script>
</head>
<body>
<div id="inputhere" style="text-align:right;">
<span id="addinput" class="fakelink">Add Input File</span>
</div>
<div id="perlctrl">
<button id="runperl"><code>perl</code> &#x25BA;</button>
<input type="text" id="argv" class="code" size="60" value='perl' />
</div>
<div id="runnerstate" class="text">
Loading...
</div>
<div id="runnererrors" style="display:none;">
<pre></pre>
</div>
<div id="stdout" class="codewithfn" style="display:none;">
<div class="filename">STDOUT</div>
<textarea></textarea>
</div>
<div id="stderr" class="codewithfn" style="display:none;">
<div class="filename">STDERR</div>
<textarea></textarea>
</div>
<div id="outputhere" style="text-align:right;">
<span id="addoutput" class="fakelink">Add Output File</span>
</div>
</body>
</html>