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.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 @@
-
+