diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java index 6d7d660eeb..9678b4691d 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java @@ -251,6 +251,7 @@ public abstract class Interpreter { private String className; private boolean defaultInterpreter; private Map properties; + private Map editor; private String path; public RegisteredInterpreter(String name, String group, String className, @@ -266,6 +267,7 @@ public abstract class Interpreter { this.className = className; this.defaultInterpreter = defaultInterpreter; this.properties = properties; + this.editor = new HashMap<>(); } public String getName() { @@ -292,6 +294,10 @@ public abstract class Interpreter { return properties; } + public Map getEditor() { + return editor; + } + public void setPath(String path) { this.path = path; } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java index 8b6329a407..58db005b6b 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java @@ -250,6 +250,9 @@ public class NotebookServer extends WebSocketServlet implements case SAVE_INTERPRETER_BINDINGS: saveInterpreterBindings(conn, messagereceived); break; + case EDITOR_SETTING: + getEditorSetting(conn, notebook, messagereceived); + break; default: break; } @@ -1457,7 +1460,7 @@ public class NotebookServer extends WebSocketServlet implements } /** - * This callback is for praragraph that runs on RemoteInterpreterProcess + * This callback is for paragraph that runs on RemoteInterpreterProcess * @param paragraph * @param out * @param output @@ -1569,11 +1572,24 @@ public class NotebookServer extends WebSocketServlet implements if (id.equals(interpreterGroupId)) { broadcast( note.getId(), - new Message(OP.ANGULAR_OBJECT_REMOVE).put("name", name).put( - "noteId", noteId).put("paragraphId", paragraphId)); + new Message(OP.ANGULAR_OBJECT_REMOVE) + .put("name", name) + .put("noteId", noteId) + .put("paragraphId", paragraphId)); } } } } + + private void getEditorSetting(NotebookSocket conn, Notebook notebook, Message fromMessage) + throws IOException { + String replName = (String) fromMessage.get("magic"); + String noteId = getOpenNoteId(conn); + Note note = notebook.getNote(noteId); + Message resp = new Message(OP.EDITOR_SETTING); + resp.put("editor", note.getEditorSetting(replName)); + conn.send(serializeMessage(resp)); + return; + } } diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json index 5ec18a75bd..aeabd79dca 100644 --- a/zeppelin-web/bower.json +++ b/zeppelin-web/bower.json @@ -24,7 +24,7 @@ "angular-elastic": "~2.4.2", "angular-elastic-input": "~2.2.0", "angular-xeditable": "0.1.12", - "highlightjs": "^9.2.0", + "highlightjs": "^9.4.0", "lodash": "~3.9.3", "angular-filter": "~0.5.4", "ngtoast": "~2.0.0", @@ -62,7 +62,7 @@ "highlight.pack.js", "styles/github.css" ], - "version": "8.4.0", + "version": "9.4.0", "name": "highlightjs" } } diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index bd3b6b3c94..ffe4f49d67 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -15,7 +15,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $rootScope, $route, $window, $routeParams, $location, $timeout, $compile, - $http, websocketMsgSrv, baseUrlSrv, ngToast, + $http, $q, websocketMsgSrv, baseUrlSrv, ngToast, saveAsService, esriLoader) { var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_'; $scope.parentNote = null; @@ -78,15 +78,6 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r var angularObjectRegistry = {}; - var editorModes = { - 'ace/mode/python': /^%(\w*\.)?(pyspark|python)\s*$/, - 'ace/mode/scala': /^%(\w*\.)?spark\s*$/, - 'ace/mode/r': /^%(\w*\.)?(r|sparkr|knitr)\s*$/, - 'ace/mode/sql': /^%(\w*\.)?\wql/, - 'ace/mode/markdown': /^%md/, - 'ace/mode/sh': /^%sh/ - }; - // Controller init $scope.init = function(newParagraph, note) { $scope.paragraph = newParagraph; @@ -538,7 +529,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $scope.aceChanged = function() { $scope.dirtyText = $scope.editor.getSession().getValue(); $scope.startSaveTimer(); - $scope.setParagraphMode($scope.editor.getSession(), $scope.dirtyText, $scope.editor.getCursorPosition()); + setParagraphMode($scope.editor.getSession(), $scope.dirtyText, $scope.editor.getCursorPosition()); }; $scope.aceLoaded = function(_editor) { @@ -576,37 +567,11 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r // not applying emacs key binding while the binding override Ctrl-v. default behavior of paste text on windows. } - $scope.setParagraphMode = function(session, paragraphText, pos) { - // Evaluate the mode only if the first 30 characters of the paragraph have been modified or the the position is undefined. - if ((typeof pos === 'undefined') || (pos.row === 0 && pos.column < 30)) { - // If paragraph loading, use config value if exists - if ((typeof pos === 'undefined') && $scope.paragraph.config.editorMode) { - session.setMode($scope.paragraph.config.editorMode); - } else { - // Defaults to spark mode - var newMode = 'ace/mode/scala'; - // Test first against current mode - var oldMode = session.getMode().$id; - if (!editorModes[oldMode] || !editorModes[oldMode].test(paragraphText)) { - for (var key in editorModes) { - if (key !== oldMode) { - if (editorModes[key].test(paragraphText)) { - $scope.paragraph.config.editorMode = key; - session.setMode(key); - return true; - } - } - } - $scope.paragraph.config.editorMode = newMode; - session.setMode(newMode); - } - } - } - }; - var remoteCompleter = { getCompletions: function(editor, session, pos, prefix, callback) { - if (!$scope.editor.isFocused()) { return;} + if (!$scope.editor.isFocused()) { + return; + } pos = session.getTextRange(new Range(0, 0, pos.row, pos.column)).length; var buf = session.getValue(); @@ -662,7 +627,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r autoAdjustEditorHeight(_editor.container.id); }); - $scope.setParagraphMode($scope.editor.getSession(), $scope.editor.getSession().getValue()); + setParagraphMode($scope.editor.getSession(), $scope.editor.getSession().getValue()); // autocomplete on '.' /* @@ -737,6 +702,51 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r } }; + var getEditorSetting = function(interpreterName) { + var deferred = $q.defer(); + websocketMsgSrv.getEditorSetting(interpreterName); + $timeout( + $scope.$on('editorSetting', function(event, data) { + deferred.resolve(data); + } + ), 1000); + return deferred.promise; + }; + + var setParagraphMode = function(session, paragraphText, pos) { + // Evaluate the mode only if the the position is undefined + // or the first 30 characters of the paragraph have been modified + // or cursor position is at beginning of second line.(in case user hit enter after typing %magic) + if ((typeof pos === 'undefined') || (pos.row === 0 && pos.column < 30) || (pos.row === 1 && pos.column === 0)) { + // If paragraph loading, use config value if exists + if ((typeof pos === 'undefined') && $scope.paragraph.config.editorMode) { + session.setMode($scope.paragraph.config.editorMode); + } else { + var magic; + // set editor mode to default interpreter syntax if paragraph text doesn't start with '%' + if (!paragraphText.startsWith('%')) { + magic = $scope.$parent.interpreterBindings[0].group; + } else { + var replNameRegexp = /%(.+?)\s/g; + var match = replNameRegexp.exec(paragraphText); + if (match) { + magic = match[1]; + } + } + if (magic) { + var promise = getEditorSetting(magic); + promise.then(function(editorSetting) { + if (!_.isEmpty(editorSetting.editor)) { + var mode = 'ace/mode/' + editorSetting.editor.language; + $scope.paragraph.config.editorMode = mode; + session.setMode(mode); + } + }); + } + } + } + }; + var autoAdjustEditorHeight = function(id) { var editor = $scope.editor; var height = editor.getSession().getScreenLength() * editor.renderer.lineHeight + @@ -2259,7 +2269,6 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r var noteId = $route.current.pathParams.noteId; $http.get(baseUrlSrv.getRestApiBase() + '/helium/suggest/' + noteId + '/' + $scope.paragraph.id) .success(function(data, status, headers, config) { - console.log('Suggested apps %o', data); $scope.suggestion = data.body; }) .error(function(err, status, headers, config) { diff --git a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js index e4f45db021..36e90b4938 100644 --- a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js +++ b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js @@ -76,8 +76,8 @@ angular.module('zeppelinWebApp').factory('websocketEvents', action: function(dialog) { dialog.close(); angular.element('#loginModal').modal({ - show: 'true' - }); + show: 'true' + }); } }, { label: 'Cancel', @@ -97,6 +97,8 @@ angular.module('zeppelinWebApp').factory('websocketEvents', $rootScope.$broadcast('updateProgress', data); } else if (op === 'COMPLETION_LIST') { $rootScope.$broadcast('completionList', data); + } else if (op === 'EDITOR_SETTING') { + $rootScope.$broadcast('editorSetting', data); } else if (op === 'ANGULAR_OBJECT_UPDATE') { $rootScope.$broadcast('angularObjectUpdate', data); } else if (op === 'ANGULAR_OBJECT_REMOVE') { diff --git a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js index 3dcdc3e3b4..3b27bced48 100644 --- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js +++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js @@ -180,6 +180,15 @@ angular.module('zeppelinWebApp').service('websocketMsgSrv', function($rootScope, }); }, + getEditorSetting: function(replName) { + websocketEvents.sendNewEvent({ + op: 'EDITOR_SETTING', + data: { + magic: replName + } + }); + }, + isConnected: function() { return websocketEvents.isConnected(); }, diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java index 1281e71589..cb64829505 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java @@ -650,6 +650,21 @@ public class Note implements Serializable, ParagraphJobListener { this.info = info; } + public Interpreter getRepl(String name) { + return factory.getInterpreter(id(), name); + } + + public Map getEditorSetting(String replName) { + Interpreter intp = getRepl(replName); + Map editor = new HashMap<>(); + try { + editor = intp.findRegisteredInterpreterByClassName(intp.getClassName()).getEditor(); + } catch (NullPointerException e) { + editor.put("language", "text"); + } + return editor; + } + @Override public void beforeStatusChange(Job job, Status before, Status after) { if (jobListenerFactory != null) { diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java index 7807abba41..1fdc8878ad 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java @@ -193,7 +193,7 @@ public class Paragraph extends Job implements Serializable, Cloneable { } public Interpreter getRepl(String name) { - return factory.getInterpreter(note.getId(), name); + return note.getRepl(name); } public Interpreter getCurrentRepl() { diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java index 795df3e41c..b314e6227f 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java @@ -76,6 +76,10 @@ public class Message { INSERT_PARAGRAPH, // [c-s] create new paragraph below current paragraph // @param target index + EDITOR_SETTING, // [c-s] ask paragraph editor setting + // @param magic magic keyword written in paragraph + // ex) spark.spark or spark + COMPLETION, // [c-s] ask completion candidates // @param id // @param buf current code