diff --git a/data/user_workspaces/admin/src/mytest/time/index.html b/data/user_workspaces/admin/src/mytest/time/index.html new file mode 100644 index 0000000..e7461ac --- /dev/null +++ b/data/user_workspaces/admin/src/mytest/time/index.html @@ -0,0 +1,389 @@ + + + + + + {{.i18n.wide}} + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ + {{.i18n.file}} + +
+
+
+
+
    +
+ +
+
    +
  • + {{.i18n.create_file}} +
  • +
  • + {{.i18n.create_dir}} +
  • +
  • + {{.i18n.delete}} +
  • +
+
+ +
+
    +
  • + {{.i18n.delete}} +
  • +
+
+
+
+
+
+
+ + + + + + + + +
+
+
+
+
+
+
+ + +
+
+ + {{.i18n.output}} + +
+
+ + {{.i18n.search}} + +
+
+ + {{.i18n.notification}} + +
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+ +
+ {{.i18n.isDelete}} + + + ? + +
+
+
+
+ +
+
+ +
+
+ +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/user_workspaces/admin/src/mytest/time/main.go b/data/user_workspaces/admin/src/mytest/time/main.go index 5cb2f19..f595645 100644 --- a/data/user_workspaces/admin/src/mytest/time/main.go +++ b/data/user_workspaces/admin/src/mytest/time/main.go @@ -1,16 +1,638 @@ -package main +// 构建、运行、go tool 操作. +package output import ( - "fmt" - "mytest/time/pkg" + "encoding/json" + "io" + "math/rand" + "net/http" + "os" + "os/exec" + "runtime" + "strconv" + "strings" "time" + + "github.com/b3log/wide/conf" + "github.com/b3log/wide/session" + "github.com/b3log/wide/util" + "github.com/golang/glog" + "github.com/gorilla/websocket" ) -func main() { - for i := 0; i < 50; i++ { - fmt.Println("Hello, 世界", pkg.Now()) +const ( + lintSeverityError = "error" // Lint 严重级别:错误 + lintSeverityWarn = "warning" // Lint 严重级别:警告 +) - time.Sleep(time.Second) +// 代码 Lint 结构. +type Lint struct { + File string `json:"file"` + LineNo int `json:"lineNo"` + Severity string `json:"severity"` + Msg string +} + +// 建立输出通道. +func WSHandler(w http.ResponseWriter, r *http.Request) { + sid := r.URL.Query()["sid"][0] + + conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024) + wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()} + + session.OutputWS[sid] = &wsChan + + ret := map[string]interface{}{"output": "Ouput initialized", "cmd": "init-output"} + wsChan.Conn.WriteJSON(&ret) + + glog.V(4).Infof("Open a new [Output] with session [%s], %d", sid, len(session.OutputWS)) +} + +// 运行一个可执行文件. +func RunHandler(w http.ResponseWriter, r *http.Request) { + data := map[string]interface{}{"succ": true} + defer util.RetJSON(w, r, data) + + decoder := json.NewDecoder(r.Body) + + var args map[string]interface{} + + if err := decoder.Decode(&args); err != nil { + glog.Error(err) + data["succ"] = false + + return } + sid := args["sid"].(string) + wSession := session.WideSessions.Get(sid) + if nil == wSession { + data["succ"] = false + + return + } + + filePath := args["executable"].(string) + curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)] + + cmd := exec.Command(filePath) + cmd.Dir = curDir + + stdout, err := cmd.StdoutPipe() + if nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + stderr, err := cmd.StderrPipe() + if nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + reader := io.MultiReader(stdout, stderr) + + if err := cmd.Start(); nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + // 添加到用户进程集中 + processes.add(wSession, cmd.Process) + + channelRet := map[string]interface{}{} + channelRet["pid"] = cmd.Process.Pid + + go func(runningId int) { + defer util.Recover() + defer cmd.Wait() + + glog.V(3).Infof("Session [%s] is running [id=%d, file=%s]", sid, runningId, filePath) + + // 在读取程序输出前先返回一次,使前端获取到 run 状态以及对应的 pid + if nil != session.OutputWS[sid] { + wsChannel := session.OutputWS[sid] + + channelRet["cmd"] = "run" + channelRet["output"] = "" + err := wsChannel.Conn.WriteJSON(&channelRet) + if nil != err { + glog.Error(err) + return + } + + // 更新通道最近使用时间 + wsChannel.Time = time.Now() + } + + for { + buf := make([]byte, 1024) + count, err := reader.Read(buf) + + if nil != err || 0 == count { + // 从用户进程集中移除这个执行完毕(或是被主动停止)的进程 + processes.remove(wSession, cmd.Process) + + glog.V(3).Infof("Session [%s] 's running [id=%d, file=%s] has done", sid, runningId, filePath) + + if nil != session.OutputWS[sid] { + wsChannel := session.OutputWS[sid] + + channelRet["cmd"] = "run-done" + channelRet["output"] = string(buf[:count]) + err := wsChannel.Conn.WriteJSON(&channelRet) + if nil != err { + glog.Error(err) + break + } + + // 更新通道最近使用时间 + wsChannel.Time = time.Now() + } + + break + } else { + if nil != session.OutputWS[sid] { + wsChannel := session.OutputWS[sid] + + channelRet["cmd"] = "run" + channelRet["output"] = string(buf[:count]) + err := wsChannel.Conn.WriteJSON(&channelRet) + if nil != err { + glog.Error(err) + break + } + + // 更新通道最近使用时间 + wsChannel.Time = time.Now() + } + } + } + }(rand.Int()) +} + +// 构建可执行文件. +func BuildHandler(w http.ResponseWriter, r *http.Request) { + data := map[string]interface{}{"succ": true} + defer util.RetJSON(w, r, data) + + httpSession, _ := session.HTTPSession.Get(r, "wide-session") + username := httpSession.Values["username"].(string) + + decoder := json.NewDecoder(r.Body) + + var args map[string]interface{} + + if err := decoder.Decode(&args); err != nil { + glog.Error(err) + data["succ"] = false + + return + } + + sid := args["sid"].(string) + + filePath := args["file"].(string) + curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)] + + fout, err := os.Create(filePath) + + if nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + code := args["code"].(string) + + fout.WriteString(code) + + if err := fout.Close(); nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + suffix := "" + if "windows" == runtime.GOOS { + suffix = ".exe" + } + executable := "main" + suffix + argv := []string{"build", "-o", executable} + + cmd := exec.Command("go", argv...) + cmd.Dir = curDir + + setCmdEnv(cmd, username) + + glog.V(5).Infof("go build -o %s", executable) + + executable = curDir + conf.PathSeparator + executable + + // 先把可执行文件删了 + err = os.RemoveAll(executable) + if nil != err { + glog.Info(err) + data["succ"] = false + + return + } + + stdout, err := cmd.StdoutPipe() + if nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + stderr, err := cmd.StderrPipe() + if nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + if data["succ"].(bool) { + reader := io.MultiReader(stdout, stderr) + + if err := cmd.Start(); nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + go func(runningId int) { + defer util.Recover() + defer cmd.Wait() + + glog.V(3).Infof("Session [%s] is building [id=%d, dir=%s]", sid, runningId, curDir) + + // 一次性读取 + buf := make([]byte, 1024*8) + count, _ := reader.Read(buf) + + channelRet := map[string]interface{}{} + channelRet["output"] = string(buf[:count]) + channelRet["cmd"] = "build" + channelRet["executable"] = executable + + if 0 == count { // 说明构建成功,没有错误信息输出 + // 设置下一次执行命令(前端会根据这个发送请求) + channelRet["nextCmd"] = "run" + + go func() { // 运行 go install,生成的库用于 gocode lib-path + cmd := exec.Command("go", "install") + cmd.Dir = curDir + + setCmdEnv(cmd, username) + + out, _ := cmd.CombinedOutput() + if len(out) > 0 { + glog.Warning(string(out)) + } + }() + } else { // 构建失败 + // 解析错误信息,返回给编辑器 gutter lint + errOut := string(buf[:count]) + lines := strings.Split(errOut, "\n") + + if lines[0][0] == '#' { + lines = lines[1:] // 跳过第一行 + } + + lints := []*Lint{} + + for _, line := range lines { + if len(line) < 1 { + continue + } + + if line[0] == '\t' { + // 添加到上一个 lint 中 + last := len(lints) + msg := lints[last-1].Msg + msg += line + + lints[last-1].Msg = msg + + continue + } + + file := line[:strings.Index(line, ":")] + left := line[strings.Index(line, ":")+1:] + lineNo, _ := strconv.Atoi(left[:strings.Index(left, ":")]) + msg := left[strings.Index(left, ":")+2:] + + lint := &Lint{ + File: file, + LineNo: lineNo - 1, + Severity: lintSeverityError, + Msg: msg, + } + + lints = append(lints, lint) + } + + channelRet["lints"] = lints + } + + if nil != session.OutputWS[sid] { + glog.V(3).Infof("Session [%s] 's build [id=%d, dir=%s] has done", sid, runningId, curDir) + + wsChannel := session.OutputWS[sid] + err := wsChannel.Conn.WriteJSON(&channelRet) + if nil != err { + glog.Error(err) + } + + // 更新通道最近使用时间 + wsChannel.Time = time.Now() + } + + }(rand.Int()) + } +} + +// go install. +func GoInstallHandler(w http.ResponseWriter, r *http.Request) { + data := map[string]interface{}{"succ": true} + defer util.RetJSON(w, r, data) + + httpSession, _ := session.HTTPSession.Get(r, "wide-session") + username := httpSession.Values["username"].(string) + + decoder := json.NewDecoder(r.Body) + + var args map[string]interface{} + + if err := decoder.Decode(&args); err != nil { + glog.Error(err) + data["succ"] = false + + return + } + + sid := args["sid"].(string) + + filePath := args["file"].(string) + curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)] + + fout, err := os.Create(filePath) + + if nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + code := args["code"].(string) + + fout.WriteString(code) + + if err := fout.Close(); nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + cmd := exec.Command("go", "install") + cmd.Dir = curDir + + setCmdEnv(cmd, username) + + glog.V(5).Infof("go install %s", curDir) + + stdout, err := cmd.StdoutPipe() + if nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + stderr, err := cmd.StderrPipe() + if nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + if data["succ"].(bool) { + reader := io.MultiReader(stdout, stderr) + + cmd.Start() + + go func(runningId int) { + defer util.Recover() + defer cmd.Wait() + + glog.V(3).Infof("Session [%s] is running [go install] [id=%d, dir=%s]", sid, runningId, curDir) + + // 一次性读取 + buf := make([]byte, 1024*8) + count, _ := reader.Read(buf) + + channelRet := map[string]interface{}{} + channelRet["output"] = string(buf[:count]) + channelRet["cmd"] = "go install" + + if 0 != count { // 构建失败 + // 解析错误信息,返回给编辑器 gutter lint + errOut := string(buf[:count]) + lines := strings.Split(errOut, "\n") + + if lines[0][0] == '#' { + lines = lines[1:] // 跳过第一行 + } + + lints := []*Lint{} + + for _, line := range lines { + if len(line) < 1 { + continue + } + + if line[0] == '\t' { + // 添加到上一个 lint 中 + last := len(lints) + msg := lints[last-1].Msg + msg += line + + lints[last-1].Msg = msg + + continue + } + + file := line[:strings.Index(line, ":")] + left := line[strings.Index(line, ":")+1:] + glog.Info("left: ", left) + lineNo, _ := strconv.Atoi(left[:strings.Index(left, ":")]) + msg := left[strings.Index(left, ":")+2:] + + lint := &Lint{ + File: file, + LineNo: lineNo - 1, + Severity: lintSeverityError, + Msg: msg, + } + + lints = append(lints, lint) + } + + channelRet["lints"] = lints + } + + if nil != session.OutputWS[sid] { + glog.V(3).Infof("Session [%s] 's running [go install] [id=%d, dir=%s] has done", sid, runningId, curDir) + + wsChannel := session.OutputWS[sid] + err := wsChannel.Conn.WriteJSON(&channelRet) + if nil != err { + glog.Error(err) + } + + // 更新通道最近使用时间 + wsChannel.Time = time.Now() + } + + }(rand.Int()) + } +} + +// go get. +func GoGetHandler(w http.ResponseWriter, r *http.Request) { + data := map[string]interface{}{"succ": true} + defer util.RetJSON(w, r, data) + + httpSession, _ := session.HTTPSession.Get(r, "wide-session") + username := httpSession.Values["username"].(string) + + decoder := json.NewDecoder(r.Body) + + var args map[string]interface{} + + if err := decoder.Decode(&args); err != nil { + glog.Error(err) + data["succ"] = false + + return + } + + sid := args["sid"].(string) + + filePath := args["file"].(string) + curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)] + + cmd := exec.Command("go", "get") + cmd.Dir = curDir + + setCmdEnv(cmd, username) + + stdout, err := cmd.StdoutPipe() + if nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + stderr, err := cmd.StderrPipe() + if nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + reader := io.MultiReader(stdout, stderr) + + cmd.Start() + + channelRet := map[string]interface{}{} + + go func(runningId int) { + defer util.Recover() + defer cmd.Wait() + + glog.V(3).Infof("Session [%s] is running [go get] [runningId=%d]", sid, runningId) + + for { + buf := make([]byte, 1024) + count, err := reader.Read(buf) + + if nil != err || 0 == count { + glog.V(3).Infof("Session [%s] 's running [go get] [runningId=%d] has done", sid, runningId) + + break + } else { + channelRet["output"] = string(buf[:count]) + channelRet["cmd"] = "go get" + + if nil != session.OutputWS[sid] { + wsChannel := session.OutputWS[sid] + + err := wsChannel.Conn.WriteJSON(&channelRet) + if nil != err { + glog.Error(err) + break + } + + // 更新通道最近使用时间 + wsChannel.Time = time.Now() + } + } + } + }(rand.Int()) +} + +// 结束正在运行的进程. +func StopHandler(w http.ResponseWriter, r *http.Request) { + data := map[string]interface{}{"succ": true} + defer util.RetJSON(w, r, data) + + var args map[string]interface{} + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { + glog.Error(err) + data["succ"] = false + + return + } + + sid := args["sid"].(string) + pid := int(args["pid"].(float64)) + + wSession := session.WideSessions.Get(sid) + if nil == wSession { + data["succ"] = false + + return + } + + processes.kill(wSession, pid) +} + +func setCmdEnv(cmd *exec.Cmd, username string) { + userWorkspace := conf.Wide.GetUserWorkspace(username) + masterWorkspace := conf.Wide.GetWorkspace() + + cmd.Env = append(cmd.Env, + "GOPATH="+userWorkspace+conf.PathListSeparator+masterWorkspace, + "GOOS="+runtime.GOOS, + "GOARCH="+runtime.GOARCH, + "GOROOT="+runtime.GOROOT(), + "PATH="+os.Getenv("PATH")) } diff --git a/static/js/editors.js b/static/js/editors.js index a614f8c..bd50998 100644 --- a/static/js/editors.js +++ b/static/js/editors.js @@ -435,7 +435,7 @@ var editors = { highlightSelectionMatches: {showToken: /\w/}, rulers: rulers, styleActiveLine: true, - theme: 'lesser-dark', + theme: 'wide', indentUnit: 4, foldGutter: true, extraKeys: { diff --git a/static/js/lib/codemirror-4.5/theme/wide.css b/static/js/lib/codemirror-4.5/theme/wide.css new file mode 100644 index 0000000..ec62756 --- /dev/null +++ b/static/js/lib/codemirror-4.5/theme/wide.css @@ -0,0 +1,23 @@ +.cm-s-wide span.cm-meta {color: rgb(98,143,181);} +.cm-s-wide span.cm-keyword { color: rgb(0,0,230); } +.cm-s-wide span.cm-atom {color: #219;} +.cm-s-wide span.cm-number {color: #B35E4D;} +.cm-s-wide span.cm-def {color: #00f;} +.cm-s-wide span.cm-variable {color: black;} +.cm-s-wide span.cm-variable-2 {color: #0000C0;} +.cm-s-wide span.cm-variable-3 {color: #0000C0;} +.cm-s-wide span.cm-property {color: black;} +.cm-s-wide span.cm-operator {color: rgb(0,153,0);} +.cm-s-wide span.cm-comment {color: rgb(150,150,150);} +.cm-s-wide span.cm-string {color: rgb(206,123,0);} +.cm-s-wide span.cm-string-2 {color: #f50;} +.cm-s-wide span.cm-qualifier {color: #555;} +.cm-s-wide span.cm-builtin {color: #30a;} +.cm-s-wide span.cm-bracket {color: #cc7;} +.cm-s-wide span.cm-tag {color: #170;} +.cm-s-wide span.cm-attribute {color: #00c;} +.cm-s-wide span.cm-link {color: #219;} +.cm-s-wide span.cm-error {color: #f00;} + +.cm-s-wide .CodeMirror-activeline-background {background: #e8f2ff !important;} +.cm-s-wide .CodeMirror-matchingbracket {outline:1px solid grey; color:black !important;} diff --git a/view/index.html b/view/index.html index 4192705..ab1f111 100644 --- a/view/index.html +++ b/view/index.html @@ -7,7 +7,7 @@ - +