diff --git a/main.go b/main.go
index c75c417..31be570 100644
--- a/main.go
+++ b/main.go
@@ -172,6 +172,7 @@ func main() {
http.HandleFunc(conf.Wide.Context+"/playground/build", handlerWrapper(playground.BuildHandler))
http.HandleFunc(conf.Wide.Context+"/playground/run", handlerWrapper(playground.RunHandler))
http.HandleFunc(conf.Wide.Context+"/playground/stop", handlerWrapper(playground.StopHandler))
+ http.HandleFunc(conf.Wide.Context+"/playground/autocomplete", handlerWrapper(playground.AutocompleteHandler))
logger.Infof("Wide is running [%s]", conf.Wide.Server+conf.Wide.Context)
diff --git a/playground/autocomplete.go b/playground/autocomplete.go
new file mode 100644
index 0000000..2734250
--- /dev/null
+++ b/playground/autocomplete.go
@@ -0,0 +1,97 @@
+// Copyright (c) 2014-2015, b3log.org
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package playground
+
+import (
+ "bytes"
+ "encoding/json"
+ "net/http"
+ "os/exec"
+ "strconv"
+ "strings"
+
+ "github.com/b3log/wide/session"
+ "github.com/b3log/wide/util"
+)
+
+// AutocompleteHandler handles request of code autocompletion.
+func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
+ var args map[string]interface{}
+
+ if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
+ logger.Error(err)
+ http.Error(w, err.Error(), 500)
+
+ return
+ }
+
+ session, _ := session.HTTPSession.Get(r, "wide-session")
+ if session.IsNew {
+ http.Error(w, "Forbidden", http.StatusForbidden)
+
+ return
+ }
+
+ code := args["code"].(string)
+ line := int(args["cursorLine"].(float64))
+ ch := int(args["cursorCh"].(float64))
+
+ offset := getCursorOffset(code, line, ch)
+
+ argv := []string{"-f=json", "autocomplete", strconv.Itoa(offset)}
+ gocode := util.Go.GetExecutableInGOBIN("gocode")
+ cmd := exec.Command(gocode, argv...)
+
+ stdin, _ := cmd.StdinPipe()
+ stdin.Write([]byte(code))
+ stdin.Close()
+
+ output, err := cmd.CombinedOutput()
+ if nil != err {
+ logger.Error(err)
+ http.Error(w, err.Error(), 500)
+
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(output)
+}
+
+// getCursorOffset calculates the cursor offset.
+//
+// line is the line number, starts with 0 that means the first line
+// ch is the column number, starts with 0 that means the first column
+func getCursorOffset(code string, line, ch int) (offset int) {
+ lines := strings.Split(code, "\n")
+
+ // calculate sum length of lines before
+ for i := 0; i < line; i++ {
+ offset += len(lines[i])
+ }
+
+ // calculate length of the current line and column
+ curLine := lines[line]
+ var buffer bytes.Buffer
+ r := []rune(curLine)
+ for i := 0; i < ch; i++ {
+ buffer.WriteString(string(r[i]))
+ }
+
+ offset += len(buffer.String()) // append length of current line
+ offset += line // append number of '\n'
+
+ return offset
+}
diff --git a/static/js/playground.js b/static/js/playground.js
index 1a12e9c..0d2d034 100644
--- a/static/js/playground.js
+++ b/static/js/playground.js
@@ -58,6 +58,109 @@ var playground = {
});
},
init: function () {
+ CodeMirror.registerHelper("hint", "go", function (editor) {
+ var word = /[\w$]+/;
+
+ var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
+
+ var start = cur.ch, end = start;
+ while (end < curLine.length && word.test(curLine.charAt(end))) {
+ ++end;
+ }
+ while (start && word.test(curLine.charAt(start - 1))) {
+ --start;
+ }
+
+ var request = newWideRequest();
+ request.code = editor.getValue();
+ request.cursorLine = cur.line;
+ request.cursorCh = cur.ch;
+
+ var autocompleteHints = [];
+
+ $.ajax({
+ async: false, // 同步执行
+ type: 'POST',
+ url: config.context + '/playground/autocomplete',
+ data: JSON.stringify(request),
+ dataType: "json",
+ success: function (data) {
+ var autocompleteArray = data[1];
+
+ if (autocompleteArray) {
+ for (var i = 0; i < autocompleteArray.length; i++) {
+ var displayText = '',
+ text = autocompleteArray[i].name;
+
+ switch (autocompleteArray[i].class) {
+ case "type":
+ displayText = ''// + autocompleteArray[i].class
+ + '' + autocompleteArray[i].name + ' '
+ + autocompleteArray[i].type + '';
+ break;
+ case "const":
+ displayText = ''// + autocompleteArray[i].class
+ + '' + autocompleteArray[i].name + ' '
+ + autocompleteArray[i].type + '';
+ break;
+ case "var":
+ displayText = ''// + autocompleteArray[i].class
+ + '' + autocompleteArray[i].name + ' '
+ + autocompleteArray[i].type + '';
+ break;
+ case "package":
+ displayText = ''// + autocompleteArray[i].class
+ + '' + autocompleteArray[i].name + ' '
+ + autocompleteArray[i].type + '';
+ break;
+ case "func":
+ displayText = ''// + autocompleteArray[i].class
+ + '' + autocompleteArray[i].name + ''
+ + autocompleteArray[i].type.substring(4) + '';
+ text += '()';
+ break;
+ default:
+ console.warn("Can't handle autocomplete [" + autocompleteArray[i].class + "]");
+ break;
+ }
+
+ autocompleteHints[i] = {
+ displayText: displayText,
+ text: text
+ };
+ }
+ }
+ }
+ });
+
+ return {list: autocompleteHints, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
+ });
+
+ CodeMirror.commands.autocompleteAnyWord = function (cm) {
+ cm.showHint({hint: CodeMirror.hint.auto});
+ };
+
+ CodeMirror.commands.autocompleteAfterDot = function (cm) {
+ var mode = cm.getMode();
+ if (mode && "go" !== mode.name) {
+ return CodeMirror.Pass;
+ }
+
+ var token = cm.getTokenAt(cm.getCursor());
+
+ if ("comment" === token.type || "string" === token.type) {
+ return CodeMirror.Pass;
+ }
+
+ setTimeout(function () {
+ if (!cm.state.completionActive) {
+ cm.showHint({hint: CodeMirror.hint.go, completeSingle: false});
+ }
+ }, 50);
+
+ return CodeMirror.Pass;
+ };
+
playground.editor = CodeMirror.fromTextArea($("#editor")[0], {
lineNumbers: true,
autofocus: true,
@@ -71,7 +174,11 @@ var playground = {
indentUnit: 4,
foldGutter: true,
cursorHeight: 1,
- viewportMargin: 1000
+ viewportMargin: 500,
+ extraKeys: {
+ "Ctrl-\\": "autocompleteAnyWord",
+ ".": "autocompleteAfterDot"
+ }
});
$("#editorDiv").show();
diff --git a/views/playground/index.html b/views/playground/index.html
index 38ff958..dbc7c91 100644
--- a/views/playground/index.html
+++ b/views/playground/index.html
@@ -8,15 +8,18 @@
-
+
+
+
-
+
+