diff --git a/output/mod.go b/output/mod.go index 5f67894..3466ea5 100644 --- a/output/mod.go +++ b/output/mod.go @@ -56,8 +56,9 @@ func GoModHandler(w http.ResponseWriter, r *http.Request) { filePath := args["file"].(string) curDir := filepath.Dir(filePath) + curDirName := filepath.Base(curDir) - cmd := exec.Command("go", "mod") + cmd := exec.Command("go", "mod", "init", curDirName) cmd.Dir = curDir setCmdEnv(cmd, uid) @@ -110,37 +111,26 @@ func GoModHandler(w http.ResponseWriter, r *http.Request) { return } - go func(runningId int) { - defer util.Recover() - defer cmd.Wait() + runningId := rand.Int() - logger.Debugf("User [%s, %s] is running [go mod] [runningId=%d]", uid, sid, runningId) + logger.Debugf("User [%s, %s] is running [go mod] [runningId=%d]", uid, sid, runningId) + channelRet = map[string]interface{}{} + channelRet["cmd"] = "go mod" - channelRet := map[string]interface{}{} - channelRet["cmd"] = "go mod" + buf, _ := ioutil.ReadAll(reader) + output := string(buf) + err = cmd.Wait() + if nil != err || 0 != cmd.ProcessState.ExitCode() { + logger.Debugf("User [%s, %s] 's [go mod] [runningId=%d] has done (with error)", uid, sid, runningId) + channelRet["output"] = "" + i18n.Get(locale, "mod-error").(string) + "\n" + output + } else { + logger.Debugf("User [%s, %s] 's running [go mod] [runningId=%d] has done", uid, sid, runningId) + channelRet["output"] = "" + i18n.Get(locale, "mod-succ").(string) + "\n" + output + } - // read all - buf, _ := ioutil.ReadAll(reader) - - if 0 != len(buf) { - logger.Debugf("User [%s, %s] 's [go mod] [runningId=%d] has done (with error)", uid, sid, runningId) - - channelRet["output"] = "" + i18n.Get(locale, "mod-error").(string) + "\n" + string(buf) - } else { - logger.Debugf("User [%s, %s] 's running [go mod] [runningId=%d] has done", uid, sid, runningId) - - channelRet["output"] = "" + i18n.Get(locale, "mod-succ").(string) + "\n" - } - - if nil != session.OutputWS[sid] { - wsChannel := session.OutputWS[sid] - - err := wsChannel.WriteJSON(&channelRet) - if nil != err { - logger.Warn(err) - } - - wsChannel.Refresh() - } - }(rand.Int()) + wsChannel := session.OutputWS[sid] + if nil != wsChannel { + wsChannel.WriteJSON(&channelRet) + wsChannel.Refresh() + } } diff --git a/static/js/editors.js b/static/js/editors.js index 0ecd780..547fcaa 100644 --- a/static/js/editors.js +++ b/static/js/editors.js @@ -178,7 +178,7 @@ var editors = { } if (editors.data.length === 0) { // 起始页可能存在,所以用编辑器数据判断 - menu.disabled(['save-all', 'build', 'run', 'go-test', 'go-vet', 'go-get', 'go-install', + menu.disabled(['save-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', 'go-install', 'find', 'find-next', 'find-previous', 'replace', 'replace-all', 'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment', 'edit']); @@ -732,7 +732,7 @@ var editors = { content: '' }); - menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-vet', 'go-get', 'go-install', + menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', 'go-install', 'find', 'find-next', 'find-previous', 'replace', 'replace-all', 'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment', 'edit']); diff --git a/static/js/menu.js b/static/js/menu.js index c556aac..4aea1be 100644 --- a/static/js/menu.js +++ b/static/js/menu.js @@ -180,7 +180,7 @@ var menu = { return false; } - if ($(".menu li.go-get").hasClass("disabled")) { + if ($(".menu li.go-mod").hasClass("disabled")) { return false; } diff --git a/static/js/wide.min.js b/static/js/wide.min.js index 7e31192..746cb9c 100644 --- a/static/js/wide.min.js +++ b/static/js/wide.min.js @@ -1,11 +1,11 @@ var Tabs=function(e){e._$tabsPanel=$(e.id+" > .tabs-panel"),e._$tabs=$(e.id+" > .tabs"),e._stack=[],this.obj=e,this.obj.STACKSIZE=64,this._init(e);var i=this;$(e.id+" > .tabs > div").each(function(){var t=$(this).data("index");e._stack.length===i.obj.STACKSIZE&&e._stack.splice(0,1),e._stack[e._stack.length-1]!==t&&i.obj._stack.push(t)})};$.extend(Tabs.prototype,{_init:function(r){var n=this;r._$tabs.on("click","div",function(t){if($(this).hasClass("current"))return!1;var e=$(this).data("index");n.setCurrent(e),"function"==typeof r.clickAfter&&r.clickAfter(e)}),r._$tabs.on("click",".ico-close",function(t){var e=$(this).parent().data("index"),i=!0;"function"==typeof r.removeBefore&&(i=r.removeBefore(e)),i&&n.del(e),t.stopPropagation()})},_hasId:function(t){return 0!==this.obj._$tabs.find('div[data-index="'+t+'"]').length},add:function(t){if(this.getCurrentId()===t.id)return!1;if(this._hasId(t.id))return this.setCurrent(t.id),!1;var e=this.obj._$tabsPanel;this.obj._$tabs.append('
"+t.output+""),wide.curProcessId=t.pid;break;case"run-done":bottomGroup.fillOutput($(".bottom-window-group .output > div").html().replace(/<\/pre>$/g,t.output+"")),wide.curProcessId=void 0,$("#buildRun").removeClass("ico-stop").addClass("ico-buildrun").attr("title",config.label.build_n_run);break;case"start-build":case"start-test":case"start-vet":case"start-install":case"start-mod":bottomGroup.fillOutput(t.output);break;case"go test":case"go vet":case"go install":case"go mod":bottomGroup.fillOutput($(".bottom-window-group .output > div").html()+t.output);break;case"git clone":bottomGroup.fillOutput($(".bottom-window-group .output > div").html()+t.output),tree.fileTree.reAsyncChildNodes(wide.curNode,"refresh",!1);break;case"build":case"cross-build":if(bottomGroup.fillOutput($(".bottom-window-group .output > div").html()+t.output),t.lints){for(var i={},a=0;a
' + data.output + '');\r\n } else {\r\n bottomGroup.fillOutput(content.replace(/<\\/pre>$/g, data.output + ''));\r\n }\r\n\r\n wide.curProcessId = data.pid;\r\n\r\n break;\r\n case 'run-done':\r\n bottomGroup.fillOutput($('.bottom-window-group .output > div').html().replace(/<\\/pre>$/g, data.output + ''));\r\n\r\n wide.curProcessId = undefined;\r\n $(\"#buildRun\").removeClass(\"ico-stop\")\r\n .addClass(\"ico-buildrun\").attr(\"title\", config.label.build_n_run);\r\n\r\n break;\r\n case 'start-build':\r\n case 'start-test':\r\n case 'start-vet':\r\n case 'start-install':\r\n case 'start-mod':\r\n bottomGroup.fillOutput(data.output);\r\n\r\n break;\r\n case 'go test':\r\n case 'go vet':\r\n case 'go install':\r\n case 'go mod':\r\n bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output);\r\n\r\n break;\r\n case 'git clone':\r\n bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output);\r\n tree.fileTree.reAsyncChildNodes(wide.curNode, \"refresh\", false);\r\n\r\n break;\r\n case 'build':\r\n case 'cross-build':\r\n bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output);\r\n\r\n if (data.lints) { // has build error\r\n var files = {};\r\n\r\n for (var i = 0; i < data.lints.length; i++) {\r\n var lint = data.lints[i];\r\n\r\n goLintFound.push({from: CodeMirror.Pos(lint.lineNo, 0),\r\n to: CodeMirror.Pos(lint.lineNo, 0),\r\n message: lint.msg, severity: lint.severity});\r\n\r\n files[lint.file] = lint.file;\r\n }\r\n\r\n $(\"#buildRun\").removeClass(\"ico-stop\")\r\n .addClass(\"ico-buildrun\").attr(\"title\", config.label.build_n_run);\r\n\r\n // trigger gutter lint\r\n for (var path in files) {\r\n var editor = editors.getEditorByPath(path);\r\n CodeMirror.signal(editor, \"change\", editor);\r\n }\r\n } else {\r\n if ('cross-build' === data.cmd) {\r\n var request = newWideRequest(),\r\n path = null;\r\n request.path = data.executable;\r\n request.name = data.name;\r\n\r\n $.ajax({\r\n async: false,\r\n type: 'POST',\r\n url: '/file/zip/new',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n if (!result.succ) {\r\n $(\"#dialogAlert\").dialog(\"open\", result.msg);\r\n\r\n return false;\r\n }\r\n\r\n path = result.data;\r\n }\r\n });\r\n\r\n if (path) {\r\n window.open('/file/zip?path=' + path + \".zip\");\r\n }\r\n }\r\n }\r\n\r\n break;\r\n }\r\n };\r\n outputWS.onclose = function (e) {\r\n console.log('[output onclose] disconnected (' + e.code + ')');\r\n };\r\n outputWS.onerror = function (e) {\r\n console.log('[output onerror]');\r\n };\r\n },\r\n _initFooter: function () {\r\n $(\".footer .cursor\").dblclick(function () {\r\n $(\"#dialogGoLinePrompt\").dialog(\"open\");\r\n });\r\n },\r\n init: function () {\r\n this._initFooter();\r\n\r\n this._initWS();\r\n\r\n // 点击隐藏弹出层\r\n $(\"body\").bind(\"mouseup\", function (event) {\r\n // MAC 右键文件树失效\r\n if (event.which === 3) {\r\n return false;\r\n }\r\n\r\n $(\".frame\").hide();\r\n\r\n if (!($(event.target).closest(\".frame\").length === 1 || event.target.className === \"frame\")) {\r\n $(\".menu > ul > li\").unbind().removeClass(\"selected\");\r\n menu.subMenu();\r\n }\r\n });\r\n\r\n // 刷新提示\r\n window.onbeforeunload = function () {\r\n if (editors.data.length > 0) {\r\n return config.label.confirm_save;\r\n }\r\n };\r\n\r\n // 禁止鼠标右键菜单\r\n document.oncontextmenu = function () {\r\n return false;\r\n };\r\n\r\n this._initDialog();\r\n },\r\n _save: function (path, editor) {\r\n if (!path) {\r\n return false;\r\n }\r\n\r\n var request = newWideRequest();\r\n request.file = path;\r\n request.code = editor.getValue();\r\n\r\n $.ajax({\r\n type: 'POST',\r\n url: '/file/save',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n // reset the save state\r\n editor.doc.markClean();\r\n $(\".edit-panel .tabs > div\").each(function () {\r\n var $span = $(this).find(\"span:eq(0)\");\r\n if ($span.attr(\"title\") === path) {\r\n $span.removeClass(\"changed\");\r\n }\r\n });\r\n }\r\n });\r\n },\r\n saveFile: function () {\r\n var path = editors.getCurrentPath();\r\n if (!path) {\r\n return false;\r\n }\r\n\r\n var editor = wide.curEditor;\r\n if (editor.doc.isClean()) { // no modification\r\n return false;\r\n }\r\n\r\n if (\"text/x-go\" === editor.getOption(\"mode\")) {\r\n wide.gofmt(path, wide.curEditor); // go fmt will save\r\n\r\n // build the file at once\r\n var request = newWideRequest();\r\n request.file = path;\r\n request.code = editor.getValue();\r\n request.nextCmd = \"\"; // build only, no following operation\r\n $.ajax({\r\n type: 'POST',\r\n url: '/build',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n beforeSend: function () {\r\n bottomGroup.resetOutput();\r\n },\r\n success: function (result) {\r\n }\r\n });\r\n\r\n // refresh outline\r\n wide.refreshOutline();\r\n\r\n return;\r\n }\r\n\r\n wide._save(path, wide.curEditor);\r\n },\r\n stop: function () {\r\n if ($(\"#buildRun\").hasClass(\"ico-buildrun\")) {\r\n menu.run();\r\n return false;\r\n }\r\n\r\n if (!wide.curProcessId) {\r\n return false;\r\n }\r\n\r\n var request = newWideRequest();\r\n request.pid = wide.curProcessId;\r\n\r\n $.ajax({\r\n type: 'POST',\r\n url: '/stop',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n $(\"#buildRun\").removeClass(\"ico-stop\")\r\n .addClass(\"ico-buildrun\").attr(\"title\", config.label.build_n_run);\r\n }\r\n });\r\n },\r\n gofmt: function (path, editor) {\r\n var cursor = editor.getCursor();\r\n var scrollInfo = editor.getScrollInfo();\r\n\r\n var request = newWideRequest();\r\n request.file = path;\r\n request.code = editor.getValue();\r\n request.cursorLine = cursor.line;\r\n request.cursorCh = cursor.ch;\r\n\r\n $.ajax({\r\n async: false, // sync\r\n type: 'POST',\r\n url: '/go/fmt',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n if (result.succ) {\r\n editor.setValue(result.data.code);\r\n editor.setCursor(cursor);\r\n editor.scrollTo(null, scrollInfo.top);\r\n\r\n wide._save(path, editor);\r\n }\r\n }\r\n });\r\n },\r\n fmt: function (path, editor) {\r\n var mode = editor.getOption(\"mode\");\r\n\r\n var cursor = editor.getCursor();\r\n var scrollInfo = editor.getScrollInfo();\r\n\r\n var request = newWideRequest();\r\n request.file = path;\r\n request.code = editor.getValue();\r\n request.cursorLine = cursor.line;\r\n request.cursorCh = cursor.ch;\r\n\r\n var formatted = null;\r\n\r\n switch (mode) {\r\n case \"text/x-go\":\r\n $.ajax({\r\n async: false, // sync\r\n type: 'POST',\r\n url: '/go/fmt',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n if (result.succ) {\r\n formatted = result.data.code;\r\n }\r\n }\r\n });\r\n\r\n break;\r\n case \"text/html\":\r\n formatted = html_beautify(editor.getValue());\r\n break;\r\n case \"text/javascript\":\r\n case \"application/json\":\r\n formatted = js_beautify(editor.getValue());\r\n break;\r\n case \"text/css\":\r\n formatted = css_beautify(editor.getValue());\r\n break;\r\n default :\r\n break;\r\n }\r\n\r\n if (formatted) {\r\n editor.setValue(formatted);\r\n editor.setCursor(cursor);\r\n editor.scrollTo(null, scrollInfo.top);\r\n\r\n wide._save(path, editor);\r\n }\r\n },\r\n getClassBySuffix: function (suffix) {\r\n var iconSkin = \"ico-ztree-other \";\r\n switch (suffix) {\r\n case \"html\":\r\n case \"htm\":\r\n iconSkin = \"ico-ztree-html \";\r\n break;\r\n case \"go\":\r\n iconSkin = \"ico-ztree-go \";\r\n break;\r\n case \"css\":\r\n iconSkin = \"ico-ztree-css \";\r\n break;\r\n case \"txt\":\r\n iconSkin = \"ico-ztree-text \";\r\n break;\r\n case \"sql\":\r\n iconSkin = \"ico-ztree-sql \";\r\n break;\r\n case \"properties\":\r\n iconSkin = \"ico-ztree-pro \";\r\n break;\r\n case \"md\":\r\n iconSkin = \"ico-ztree-md \";\r\n break;\r\n case \"js\", \"json\":\r\n iconSkin = \"ico-ztree-js \";\r\n break;\r\n case \"xml\":\r\n iconSkin = \"ico-ztree-xml \";\r\n break;\r\n case \"jpg\":\r\n case \"jpeg\":\r\n case \"bmp\":\r\n case \"gif\":\r\n case \"png\":\r\n case \"svg\":\r\n case \"ico\":\r\n iconSkin = \"ico-ztree-img \";\r\n break;\r\n }\r\n\r\n return iconSkin;\r\n }\r\n};\r\n\r\n$(document).ready(function () {\r\n wide.init();\r\n tree.init();\r\n menu.init();\r\n hotkeys.init();\r\n session.init();\r\n notification.init();\r\n editors.init();\r\n windows.init();\r\n bottomGroup.init();\r\n});\r\n","/*\r\n * Copyright (c) 2014-present, b3log.org\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\n/*\r\n * @file session.js\r\n *\r\n * @author Liyuan Li\r\n * @version 1.1.0.1, Dec 8, 2015\r\n */\r\nvar session = {\r\n init: function () {\r\n this._initWS();\r\n\r\n var getLayoutState = function (paneState) {\r\n var state = 'normal';\r\n if (paneState.isClosed) {\r\n state = 'min';\r\n } else if (paneState.size >= $('body').width()) {\r\n state = 'max';\r\n }\r\n\r\n return state;\r\n };\r\n\r\n // save session content every 30 seconds\r\n setInterval(function () {\r\n var request = newWideRequest(),\r\n filse = [],\r\n fileTree = [],\r\n currentId = editors.getCurrentId(),\r\n currentFile = currentId ? editors.getCurrentPath() : \"\";\r\n\r\n editors.tabs.obj._$tabs.find(\"div\").each(function () {\r\n var $it = $(this);\r\n if ($it.find(\"span:eq(0)\").attr(\"title\") !== config.label.start_page) {\r\n filse.push($it.find(\"span:eq(0)\").attr(\"title\"));\r\n }\r\n });\r\n\r\n fileTree = tree.getOpenPaths();\r\n\r\n request.currentFile = currentFile; // current editor file\r\n request.fileTree = fileTree; // file tree expansion state\r\n request.files = filse; // editor tabs\r\n\r\n\r\n request.layout = {\r\n \"side\": {\r\n \"size\": windows.outerLayout.west.state.size,\r\n \"state\": getLayoutState(windows.outerLayout.west.state)\r\n },\r\n \"sideRight\": {\r\n \"size\": windows.innerLayout.east.state.size,\r\n \"state\": getLayoutState(windows.innerLayout.east.state)\r\n },\r\n \"bottom\": {\r\n \"size\": windows.innerLayout.south.state.size,\r\n \"state\": getLayoutState(windows.innerLayout.south.state)\r\n }\r\n };\r\n\r\n $.ajax({\r\n type: 'POST',\r\n url: '/session/save',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n }\r\n });\r\n }, 30000);\r\n },\r\n restore: function () {\r\n if (!config.latestSessionContent) {\r\n return;\r\n }\r\n\r\n var fileTree = config.latestSessionContent.fileTree,\r\n files = config.latestSessionContent.files,\r\n currentFile = config.latestSessionContent.currentFile,\r\n id = \"\",\r\n nodesToOpen = [];\r\n\r\n var nodes = tree.fileTree.transformToArray(tree.fileTree.getNodes());\r\n\r\n for (var i = 0, ii = nodes.length; i < ii; i++) {\r\n // expand tree\r\n for (var j = 0, jj = fileTree.length; j < jj; j++) {\r\n if (nodes[i].path === fileTree[j]) {\r\n // expand this node only if its parents are open\r\n var parents = tree.getAllParents(tree.fileTree.getNodeByTId(nodes[i].tId)),\r\n isOpen = true;\r\n for (var l = 0, max = parents.length; l < max; l++) {\r\n if (parents[l].open === false) {\r\n isOpen = false;\r\n }\r\n }\r\n if (isOpen) {\r\n tree.fileTree.expandNode(nodes[i], true, false, true);\r\n } else {\r\n // flag it is open\r\n nodes[i].open = true;\r\n }\r\n break;\r\n }\r\n }\r\n\r\n // open editors\r\n for (var k = 0, kk = files.length; k < kk; k++) {\r\n if (nodes[i].path === files[k]) {\r\n nodesToOpen.push(nodes[i]);\r\n break;\r\n }\r\n }\r\n\r\n if (nodes[i].path === currentFile) {\r\n id = nodes[i].path;\r\n\r\n // FIXME: 上面的展开是异步进行的,所以执行到这里的时候可能还没有展开完,导致定位不了可视区域\r\n tree.fileTree.selectNode(nodes[i]);\r\n wide.curNode = nodes[i];\r\n }\r\n }\r\n\r\n // handle the open sequence of editors\r\n for (var m = 0, mm = files.length; m < mm; m++) {\r\n for (var n = 0, nn = nodesToOpen.length; n < nn; n++) {\r\n if (nodesToOpen[n].path === files[m]) {\r\n tree.openFile(nodesToOpen[n]);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // set the current editor\r\n editors.tabs.setCurrent(id);\r\n for (var c = 0, max = editors.data.length; c < max; c++) {\r\n if (id === editors.data[c].id) {\r\n wide.curEditor = editors.data[c].editor;\r\n break;\r\n }\r\n } \r\n },\r\n _initWS: function () {\r\n // Used for session retention, server will release all resources of the session if this channel closed\r\n var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId);\r\n\r\n sessionWS.onopen = function () {\r\n console.log('[session onopen] connected');\r\n\r\n var dateFormat = function (time, fmt) {\r\n var date = new Date(time);\r\n var dateObj = {\r\n \"M+\": date.getMonth() + 1,\r\n \"d+\": date.getDate(),\r\n \"h+\": date.getHours(),\r\n \"m+\": date.getMinutes(),\r\n \"s+\": date.getSeconds(),\r\n \"q+\": Math.floor((date.getMonth() + 3) / 3),\r\n \"S\": date.getMilliseconds()\r\n };\r\n if (/(y+)/.test(fmt))\r\n fmt = fmt.replace(RegExp.$1, (date.getFullYear() + \"\").substr(4 - RegExp.$1.length));\r\n for (var k in dateObj)\r\n if (new RegExp(\"(\" + k + \")\").test(fmt)) {\r\n fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1)\r\n ? (dateObj[k]) : ((\"00\" + dateObj[k]).substr((\"\" + dateObj[k]).length)));\r\n }\r\n return fmt;\r\n };\r\n\r\n var data = {type: \"Network\", severity: \"INFO\",\r\n message: \"Connected to server [sid=\" + config.wideSessionId + \"], \" + dateFormat(new Date().getTime(), 'yyyy-MM-dd hh:mm:ss')},\r\n $notification = $('.bottom-window-group .notification > table'),\r\n notificationHTML = '';\r\n\r\n notificationHTML += '
' + data.output + '');\r\n } else {\r\n bottomGroup.fillOutput(content.replace(/<\\/pre>$/g, data.output + ''));\r\n }\r\n\r\n wide.curProcessId = data.pid;\r\n\r\n break;\r\n case 'run-done':\r\n bottomGroup.fillOutput($('.bottom-window-group .output > div').html().replace(/<\\/pre>$/g, data.output + ''));\r\n\r\n wide.curProcessId = undefined;\r\n $(\"#buildRun\").removeClass(\"ico-stop\")\r\n .addClass(\"ico-buildrun\").attr(\"title\", config.label.build_n_run);\r\n\r\n break;\r\n case 'start-build':\r\n case 'start-test':\r\n case 'start-vet':\r\n case 'start-install':\r\n case 'start-mod':\r\n bottomGroup.fillOutput(data.output);\r\n\r\n break;\r\n case 'go test':\r\n case 'go vet':\r\n case 'go install':\r\n case 'go mod':\r\n bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output);\r\n\r\n break;\r\n case 'git clone':\r\n bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output);\r\n tree.fileTree.reAsyncChildNodes(wide.curNode, \"refresh\", false);\r\n\r\n break;\r\n case 'build':\r\n case 'cross-build':\r\n bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output);\r\n\r\n if (data.lints) { // has build error\r\n var files = {};\r\n\r\n for (var i = 0; i < data.lints.length; i++) {\r\n var lint = data.lints[i];\r\n\r\n goLintFound.push({from: CodeMirror.Pos(lint.lineNo, 0),\r\n to: CodeMirror.Pos(lint.lineNo, 0),\r\n message: lint.msg, severity: lint.severity});\r\n\r\n files[lint.file] = lint.file;\r\n }\r\n\r\n $(\"#buildRun\").removeClass(\"ico-stop\")\r\n .addClass(\"ico-buildrun\").attr(\"title\", config.label.build_n_run);\r\n\r\n // trigger gutter lint\r\n for (var path in files) {\r\n var editor = editors.getEditorByPath(path);\r\n CodeMirror.signal(editor, \"change\", editor);\r\n }\r\n } else {\r\n if ('cross-build' === data.cmd) {\r\n var request = newWideRequest(),\r\n path = null;\r\n request.path = data.executable;\r\n request.name = data.name;\r\n\r\n $.ajax({\r\n async: false,\r\n type: 'POST',\r\n url: '/file/zip/new',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n if (!result.succ) {\r\n $(\"#dialogAlert\").dialog(\"open\", result.msg);\r\n\r\n return false;\r\n }\r\n\r\n path = result.data;\r\n }\r\n });\r\n\r\n if (path) {\r\n window.open('/file/zip?path=' + path + \".zip\");\r\n }\r\n }\r\n }\r\n\r\n break;\r\n }\r\n };\r\n outputWS.onclose = function (e) {\r\n console.log('[output onclose] disconnected (' + e.code + ')');\r\n };\r\n outputWS.onerror = function (e) {\r\n console.log('[output onerror]');\r\n };\r\n },\r\n _initFooter: function () {\r\n $(\".footer .cursor\").dblclick(function () {\r\n $(\"#dialogGoLinePrompt\").dialog(\"open\");\r\n });\r\n },\r\n init: function () {\r\n this._initFooter();\r\n\r\n this._initWS();\r\n\r\n // 点击隐藏弹出层\r\n $(\"body\").bind(\"mouseup\", function (event) {\r\n // MAC 右键文件树失效\r\n if (event.which === 3) {\r\n return false;\r\n }\r\n\r\n $(\".frame\").hide();\r\n\r\n if (!($(event.target).closest(\".frame\").length === 1 || event.target.className === \"frame\")) {\r\n $(\".menu > ul > li\").unbind().removeClass(\"selected\");\r\n menu.subMenu();\r\n }\r\n });\r\n\r\n // 刷新提示\r\n window.onbeforeunload = function () {\r\n if (editors.data.length > 0) {\r\n return config.label.confirm_save;\r\n }\r\n };\r\n\r\n // 禁止鼠标右键菜单\r\n document.oncontextmenu = function () {\r\n return false;\r\n };\r\n\r\n this._initDialog();\r\n },\r\n _save: function (path, editor) {\r\n if (!path) {\r\n return false;\r\n }\r\n\r\n var request = newWideRequest();\r\n request.file = path;\r\n request.code = editor.getValue();\r\n\r\n $.ajax({\r\n type: 'POST',\r\n url: '/file/save',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n // reset the save state\r\n editor.doc.markClean();\r\n $(\".edit-panel .tabs > div\").each(function () {\r\n var $span = $(this).find(\"span:eq(0)\");\r\n if ($span.attr(\"title\") === path) {\r\n $span.removeClass(\"changed\");\r\n }\r\n });\r\n }\r\n });\r\n },\r\n saveFile: function () {\r\n var path = editors.getCurrentPath();\r\n if (!path) {\r\n return false;\r\n }\r\n\r\n var editor = wide.curEditor;\r\n if (editor.doc.isClean()) { // no modification\r\n return false;\r\n }\r\n\r\n if (\"text/x-go\" === editor.getOption(\"mode\")) {\r\n wide.gofmt(path, wide.curEditor); // go fmt will save\r\n\r\n // build the file at once\r\n var request = newWideRequest();\r\n request.file = path;\r\n request.code = editor.getValue();\r\n request.nextCmd = \"\"; // build only, no following operation\r\n $.ajax({\r\n type: 'POST',\r\n url: '/build',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n beforeSend: function () {\r\n bottomGroup.resetOutput();\r\n },\r\n success: function (result) {\r\n }\r\n });\r\n\r\n // refresh outline\r\n wide.refreshOutline();\r\n\r\n return;\r\n }\r\n\r\n wide._save(path, wide.curEditor);\r\n },\r\n stop: function () {\r\n if ($(\"#buildRun\").hasClass(\"ico-buildrun\")) {\r\n menu.run();\r\n return false;\r\n }\r\n\r\n if (!wide.curProcessId) {\r\n return false;\r\n }\r\n\r\n var request = newWideRequest();\r\n request.pid = wide.curProcessId;\r\n\r\n $.ajax({\r\n type: 'POST',\r\n url: '/stop',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n $(\"#buildRun\").removeClass(\"ico-stop\")\r\n .addClass(\"ico-buildrun\").attr(\"title\", config.label.build_n_run);\r\n }\r\n });\r\n },\r\n gofmt: function (path, editor) {\r\n var cursor = editor.getCursor();\r\n var scrollInfo = editor.getScrollInfo();\r\n\r\n var request = newWideRequest();\r\n request.file = path;\r\n request.code = editor.getValue();\r\n request.cursorLine = cursor.line;\r\n request.cursorCh = cursor.ch;\r\n\r\n $.ajax({\r\n async: false, // sync\r\n type: 'POST',\r\n url: '/go/fmt',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n if (result.succ) {\r\n editor.setValue(result.data.code);\r\n editor.setCursor(cursor);\r\n editor.scrollTo(null, scrollInfo.top);\r\n\r\n wide._save(path, editor);\r\n }\r\n }\r\n });\r\n },\r\n fmt: function (path, editor) {\r\n var mode = editor.getOption(\"mode\");\r\n\r\n var cursor = editor.getCursor();\r\n var scrollInfo = editor.getScrollInfo();\r\n\r\n var request = newWideRequest();\r\n request.file = path;\r\n request.code = editor.getValue();\r\n request.cursorLine = cursor.line;\r\n request.cursorCh = cursor.ch;\r\n\r\n var formatted = null;\r\n\r\n switch (mode) {\r\n case \"text/x-go\":\r\n $.ajax({\r\n async: false, // sync\r\n type: 'POST',\r\n url: '/go/fmt',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n if (result.succ) {\r\n formatted = result.data.code;\r\n }\r\n }\r\n });\r\n\r\n break;\r\n case \"text/html\":\r\n formatted = html_beautify(editor.getValue());\r\n break;\r\n case \"text/javascript\":\r\n case \"application/json\":\r\n formatted = js_beautify(editor.getValue());\r\n break;\r\n case \"text/css\":\r\n formatted = css_beautify(editor.getValue());\r\n break;\r\n default :\r\n break;\r\n }\r\n\r\n if (formatted) {\r\n editor.setValue(formatted);\r\n editor.setCursor(cursor);\r\n editor.scrollTo(null, scrollInfo.top);\r\n\r\n wide._save(path, editor);\r\n }\r\n },\r\n getClassBySuffix: function (suffix) {\r\n var iconSkin = \"ico-ztree-other \";\r\n switch (suffix) {\r\n case \"html\":\r\n case \"htm\":\r\n iconSkin = \"ico-ztree-html \";\r\n break;\r\n case \"go\":\r\n iconSkin = \"ico-ztree-go \";\r\n break;\r\n case \"css\":\r\n iconSkin = \"ico-ztree-css \";\r\n break;\r\n case \"txt\":\r\n iconSkin = \"ico-ztree-text \";\r\n break;\r\n case \"sql\":\r\n iconSkin = \"ico-ztree-sql \";\r\n break;\r\n case \"properties\":\r\n iconSkin = \"ico-ztree-pro \";\r\n break;\r\n case \"md\":\r\n iconSkin = \"ico-ztree-md \";\r\n break;\r\n case \"js\", \"json\":\r\n iconSkin = \"ico-ztree-js \";\r\n break;\r\n case \"xml\":\r\n iconSkin = \"ico-ztree-xml \";\r\n break;\r\n case \"jpg\":\r\n case \"jpeg\":\r\n case \"bmp\":\r\n case \"gif\":\r\n case \"png\":\r\n case \"svg\":\r\n case \"ico\":\r\n iconSkin = \"ico-ztree-img \";\r\n break;\r\n }\r\n\r\n return iconSkin;\r\n }\r\n};\r\n\r\n$(document).ready(function () {\r\n wide.init();\r\n tree.init();\r\n menu.init();\r\n hotkeys.init();\r\n session.init();\r\n notification.init();\r\n editors.init();\r\n windows.init();\r\n bottomGroup.init();\r\n});\r\n","/*\r\n * Copyright (c) 2014-present, b3log.org\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\n/*\r\n * @file session.js\r\n *\r\n * @author Liyuan Li\r\n * @version 1.1.0.1, Dec 8, 2015\r\n */\r\nvar session = {\r\n init: function () {\r\n this._initWS();\r\n\r\n var getLayoutState = function (paneState) {\r\n var state = 'normal';\r\n if (paneState.isClosed) {\r\n state = 'min';\r\n } else if (paneState.size >= $('body').width()) {\r\n state = 'max';\r\n }\r\n\r\n return state;\r\n };\r\n\r\n // save session content every 30 seconds\r\n setInterval(function () {\r\n var request = newWideRequest(),\r\n filse = [],\r\n fileTree = [],\r\n currentId = editors.getCurrentId(),\r\n currentFile = currentId ? editors.getCurrentPath() : \"\";\r\n\r\n editors.tabs.obj._$tabs.find(\"div\").each(function () {\r\n var $it = $(this);\r\n if ($it.find(\"span:eq(0)\").attr(\"title\") !== config.label.start_page) {\r\n filse.push($it.find(\"span:eq(0)\").attr(\"title\"));\r\n }\r\n });\r\n\r\n fileTree = tree.getOpenPaths();\r\n\r\n request.currentFile = currentFile; // current editor file\r\n request.fileTree = fileTree; // file tree expansion state\r\n request.files = filse; // editor tabs\r\n\r\n\r\n request.layout = {\r\n \"side\": {\r\n \"size\": windows.outerLayout.west.state.size,\r\n \"state\": getLayoutState(windows.outerLayout.west.state)\r\n },\r\n \"sideRight\": {\r\n \"size\": windows.innerLayout.east.state.size,\r\n \"state\": getLayoutState(windows.innerLayout.east.state)\r\n },\r\n \"bottom\": {\r\n \"size\": windows.innerLayout.south.state.size,\r\n \"state\": getLayoutState(windows.innerLayout.south.state)\r\n }\r\n };\r\n\r\n $.ajax({\r\n type: 'POST',\r\n url: '/session/save',\r\n data: JSON.stringify(request),\r\n dataType: \"json\",\r\n success: function (result) {\r\n }\r\n });\r\n }, 30000);\r\n },\r\n restore: function () {\r\n if (!config.latestSessionContent) {\r\n return;\r\n }\r\n\r\n var fileTree = config.latestSessionContent.fileTree,\r\n files = config.latestSessionContent.files,\r\n currentFile = config.latestSessionContent.currentFile,\r\n id = \"\",\r\n nodesToOpen = [];\r\n\r\n var nodes = tree.fileTree.transformToArray(tree.fileTree.getNodes());\r\n\r\n for (var i = 0, ii = nodes.length; i < ii; i++) {\r\n // expand tree\r\n for (var j = 0, jj = fileTree.length; j < jj; j++) {\r\n if (nodes[i].path === fileTree[j]) {\r\n // expand this node only if its parents are open\r\n var parents = tree.getAllParents(tree.fileTree.getNodeByTId(nodes[i].tId)),\r\n isOpen = true;\r\n for (var l = 0, max = parents.length; l < max; l++) {\r\n if (parents[l].open === false) {\r\n isOpen = false;\r\n }\r\n }\r\n if (isOpen) {\r\n tree.fileTree.expandNode(nodes[i], true, false, true);\r\n } else {\r\n // flag it is open\r\n nodes[i].open = true;\r\n }\r\n break;\r\n }\r\n }\r\n\r\n // open editors\r\n for (var k = 0, kk = files.length; k < kk; k++) {\r\n if (nodes[i].path === files[k]) {\r\n nodesToOpen.push(nodes[i]);\r\n break;\r\n }\r\n }\r\n\r\n if (nodes[i].path === currentFile) {\r\n id = nodes[i].path;\r\n\r\n // FIXME: 上面的展开是异步进行的,所以执行到这里的时候可能还没有展开完,导致定位不了可视区域\r\n tree.fileTree.selectNode(nodes[i]);\r\n wide.curNode = nodes[i];\r\n }\r\n }\r\n\r\n // handle the open sequence of editors\r\n for (var m = 0, mm = files.length; m < mm; m++) {\r\n for (var n = 0, nn = nodesToOpen.length; n < nn; n++) {\r\n if (nodesToOpen[n].path === files[m]) {\r\n tree.openFile(nodesToOpen[n]);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // set the current editor\r\n editors.tabs.setCurrent(id);\r\n for (var c = 0, max = editors.data.length; c < max; c++) {\r\n if (id === editors.data[c].id) {\r\n wide.curEditor = editors.data[c].editor;\r\n break;\r\n }\r\n } \r\n },\r\n _initWS: function () {\r\n // Used for session retention, server will release all resources of the session if this channel closed\r\n var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId);\r\n\r\n sessionWS.onopen = function () {\r\n console.log('[session onopen] connected');\r\n\r\n var dateFormat = function (time, fmt) {\r\n var date = new Date(time);\r\n var dateObj = {\r\n \"M+\": date.getMonth() + 1,\r\n \"d+\": date.getDate(),\r\n \"h+\": date.getHours(),\r\n \"m+\": date.getMinutes(),\r\n \"s+\": date.getSeconds(),\r\n \"q+\": Math.floor((date.getMonth() + 3) / 3),\r\n \"S\": date.getMilliseconds()\r\n };\r\n if (/(y+)/.test(fmt))\r\n fmt = fmt.replace(RegExp.$1, (date.getFullYear() + \"\").substr(4 - RegExp.$1.length));\r\n for (var k in dateObj)\r\n if (new RegExp(\"(\" + k + \")\").test(fmt)) {\r\n fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1)\r\n ? (dateObj[k]) : ((\"00\" + dateObj[k]).substr((\"\" + dateObj[k]).length)));\r\n }\r\n return fmt;\r\n };\r\n\r\n var data = {type: \"Network\", severity: \"INFO\",\r\n message: \"Connected to server [sid=\" + config.wideSessionId + \"], \" + dateFormat(new Date().getTime(), 'yyyy-MM-dd hh:mm:ss')},\r\n $notification = $('.bottom-window-group .notification > table'),\r\n notificationHTML = '';\r\n\r\n notificationHTML += '