Commit f2f6d69b authored by Jérome Perrin's avatar Jérome Perrin

Patch ZMI to use ace editor

parent 47097d9e
......@@ -137,6 +137,7 @@ def checkConversionToolAvailability(self):
active_process.activateResult(result)
def runPyflakes(script_code, script_path):
# TODO: reuse _runPyflakes ...
from pyflakes.api import check
from pyflakes import reporter
from StringIO import StringIO
......@@ -144,3 +145,64 @@ def runPyflakes(script_code, script_path):
check(script_code, script_path, reporter.Reporter(stream, stream))
return stream.getvalue()
def runPyflakesOnPythonScript(self, data):
import json
# XXX data is encoded as json, because jQuery serialize lists as []
if isinstance(data, basestring):
data = json.loads(data)
# data contains the code, the bound names and the script params. From this
# we reconstruct a function that can be parsed with pyflakes.
code = data
def indent(text):
return ''.join((" " + line) for line in text.splitlines(True))
bound_names = data['bound_names']
signature_parts = data['bound_names']
if data['params']:
signature_parts += [data['params']]
signature = ", ".join(signature_parts)
function_name = "function_name"
body = "def %s(%s):\n%s" % (function_name,
signature,
indent(data['code']) or " pass")
error_list = _runPyflakes(body, lineno_offset=-1)
self.REQUEST.RESPONSE.setHeader('content-type', 'application/json')
return json.dumps(dict(annotations=error_list))
def _runPyflakes(code, lineno_offset=0):
import pyflakes.api
error_list = []
class Reporter(object):
def unexpectedError(self, filename, msg):
error_list.append(
{ 'row': 0,
'column': 0,
'text': msg,
'type': 'error' }
)
def syntaxError(self, filename, msg, lineno, offset, text):
error_list.append(
{ 'row': lineno - 1 + lineno_offset,
'column': offset,
'text': msg + (text and ": " + text or ''),
'type': 'error' }
)
def flake(self, message):
error_list.append(
{ 'row': message.lineno - 1 + lineno_offset,
'column': getattr(message, 'col', 0),
'text': message.message % message.message_args,
'type': 'warning' }
)
pyflakes.api.check(code, '', Reporter())
return error_list
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>runPyflakesOnPythonScript</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>ERP5Administration</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_checkPythonScriptAsJSON</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
156
\ No newline at end of file
157
\ No newline at end of file
......@@ -75,6 +75,7 @@ from Products.ERP5Type.patches import OFSImage
from Products.ERP5Type.patches import _transaction
from Products.ERP5Type.patches import default_zpublisher_encoding
from Products.ERP5Type.patches import DCWorkflowGraph
from Products.ERP5Type.patches import AceEditorZMI
# These symbols are required for backward compatibility
from Products.ERP5Type.patches.PropertyManager import ERP5PropertyManager
......
from App.Management import Navigation
from Acquisition import aq_parent
import json
def manage_page_footer(self):
default = '</body></html>'
# Not within an ERP5 Site, use default footer
if getattr(self, 'getPortalObject', None) is None:
return default
portal = self.getPortalObject()
if portal.portal_preferences.getPreference('preferred_source_code_editor') != 'ace':
return default
# REQUEST['PUBLISHED'] can be the form in the acquisition context of the
# document, or a method bound to the document (after a POST it is a bound method)
published = self.REQUEST['PUBLISHED']
document = getattr(published, 'im_self', None) # bound mehtod
if document is None:
document = aq_parent(published)
if getattr(document, 'meta_type', None) is None:
return default
portal_url = portal.portal_url()
mode = 'plain_text' # default mode
textarea_selector = '' # jQuery selector for the origin textarea that we will
# change into an ace editor
live_check_python_script = 0 # Check python scripts on the fly
bound_names = 'undefined'
if document.meta_type in ('DTML Document', 'DTML Method'):
if document.getId().endswith('.js'):
mode = 'javascript'
elif document.getId().endswith('.css'):
mode = 'css'
textarea_selector = 'textarea[name="data:text"]'
elif document.meta_type in ('File', ):
if 'javascript' in document.getContentType():
mode = 'javascript'
elif 'css' in document.getContentType():
mode = 'css'
textarea_selector = 'textarea[name="filedata:text"]'
elif document.meta_type in ('Script (Python)', ):
mode = 'python'
textarea_selector = 'textarea[name="body:text"]'
# printed is from RestrictedPython.RestrictionMutator the rest comes
# from RestrictedPython.Utilities.utility_builtins
bound_names = json.dumps(
document.getBindingAssignments().getAssignedNamesInOrder()
+ ['printed', 'same_type', 'string', 'sequence', 'random', 'DateTime',
'whrandom', 'reorder', 'sets', 'test', 'math'])
live_check_python_script = 1 # XXX make it a preference ?
elif document.meta_type in ('Z SQL Method', ):
mode = 'sql'
textarea_selector = 'textarea[name="template:text"]'
elif document.meta_type in ('Page Template', 'ERP5 OOo Template', ):
if 'html' in document.content_type:
mode = 'html'
else:
mode = 'xml'
textarea_selector = 'textarea[name="text:text"]'
if not textarea_selector:
return default
return '''
<script type="text/javascript" src="%(portal_url)s/jquery/core/jquery.min.js"></script>
<script type="text/javascript" src="%(portal_url)s/ace/ace.js"></script>
<script type="text/javascript" src="%(portal_url)s/ace/mode-%(mode)s.js"></script>
<script type="text/javascript" src="%(portal_url)s/ace/ext-settings_menu.js"></script>
<script type="text/javascript" src="%(portal_url)s/ace/ext-language_tools.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var textarea = $('%(textarea_selector)s');
if (textarea.length) {
$('<div id="editor">')
.css({"position": "relative", "height": textarea.height()})
.appendTo(textarea.parent());
textarea.hide();
var beforeunload_warning_set = false,
editor = ace.edit("editor"),
Mode = ace.require('ace/mode/%(mode)s').Mode;
editor.getSession().setMode(new Mode());
editor.getSession().setTabSize(2);
ace.require("ace/ext/language_tools");
editor.setOptions({ enableBasicAutocompletion: true, enableSnippets: true });
timer = 0;
function checkPythonScript() {
if (%(live_check_python_script)s) {
if (timer) {
window.clearTimeout(timer);
timer = 0;
}
timer = window.setTimeout(function() {
$.post('%(portal_url)s/ERP5Site_checkPythonScriptAsJSON',
{'data': JSON.stringify(
{ code: editor.getSession().getValue(),
bound_names: %(bound_names)s,
params: $('input[name="params"]').val() })},
function(data){
editor.getSession().setAnnotations(data.annotations);
}
)
}, 500);
}
}
editor.getSession().setValue(textarea.val());
editor.getSession().on('change', function(){
textarea.val(editor.getSession().getValue());
if (!beforeunload_warning_set) {
window.onbeforeunload = function() { return "You have unsaved changes"; };
beforeunload_warning_set = true;
}
checkPythonScript();
});
checkPythonScript();
$('input[value="Save Changes"]').click(function() {
window.onbeforeunload = function() { return; };
});
editor.commands.addCommand({
name: "save",
bindKey: {win: "Ctrl-S", mac: "Command-S"},
exec: function() {
$('input[value="Save Changes"]').click();
}
});
ace.require('ace/ext/settings_menu').init(editor);
};
});
</script>
</body>
</html>''' % locals()
Navigation.manage_page_footer = manage_page_footer
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment