adding basic autocomplete
authorNicholas Bollweg <nicholas.bollweg@gtri.gatech.edu>
Wed, 23 Oct 2013 16:05:21 -0400
changeset 2080 16f3b91fc4e3
parent 2079 f866fde00fd7
child 2081 4f507e2f7d28
adding basic autocomplete
playground/index.html
playground/jsonld-hint.js
playground/playground.js
--- a/playground/index.html	Tue Oct 22 15:53:58 2013 -0400
+++ b/playground/index.html	Wed Oct 23 16:05:21 2013 -0400
@@ -25,6 +25,8 @@
     <!-- CodeMirror -->
     <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/codemirror.css">
     <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/addon/lint/lint.css">
+    <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/addon/hint/show-hint.css">
+    
     <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/theme/elegant.css" id="theme-stylesheet">
     
     <link rel="shortcut icon" href="../favicon.ico" />
@@ -107,7 +109,7 @@
       </div>
       
       <div class="pull-right">
-        <div class="dropdown">
+        <span class="dropdown">
           <button class="btn dropdown-toggle" data-toggle="dropdown" tabindex="-1">
             <i class="icon-eye-open"></i> <b id="theme-name">elegant</b> <b class="caret"></b>
           </button>
@@ -131,8 +133,8 @@
             <li><a>paraiso-dark</a></li>
             <li><a>paraiso-light</a></li>
             <li><a>rubyblue</a></li>
-            <li><a>solarized dark</a></li>
-            <li><a>solarized light</a></li>
+            <li><a title="solarized">solarized dark</a></li>
+            <li><a title="solarized">solarized light</a></li>
             <li><a>the-matrix</a></li>
             <li><a>tomorrow-night-eighties</a></li>
             <li><a>twilight</a></li>
@@ -140,6 +142,17 @@
             <li><a>xq-dark</a></li>
             <li><a>xq-light</a></li>
           </ul>
+        </span>
+        <button class="btn popover-info" title="Keyboard Shortcuts">
+          <i class="icon-question-sign"></i>
+        </button>
+        <div class="popover-info-content hide">
+          <dl>
+            <dt><label class="label">@</label></dt>
+            <dd>all of the <b>@</b> keywords</dd>
+            <dt><label class="label">Ctrl+Space</label></dt>
+            <dd>available keys in <b>@context</b></dd>
+          </dl>
         </div>
       </div>
       
@@ -261,7 +274,10 @@
     <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/addon/lint/lint.js"></script>
     <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/addon/lint/json-lint.js"></script>
     <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/addon/edit/matchbrackets.js"></script>
+    <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/addon/edit/closebrackets.js"></script>
     <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/addon/display/placeholder.js"></script>
+    <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/addon/hint/show-hint.js"></script>
+    <script src="./jsonld-hint.js"></script>
     <script src="./codemirror.jsonld.js"></script>
     
     <script type="text/javascript">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/playground/jsonld-hint.js	Wed Oct 23 16:05:21 2013 -0400
@@ -0,0 +1,117 @@
+;(function (CodeMirror, Pos) {
+  
+  var ldKeywords = [
+    "context",
+    "id",
+    "value",
+    "language",
+    "type",
+    "container",
+    "list",
+    "set",
+    "reverse",
+    "index",
+    "base",
+    "vocab",
+    "graph"
+  ];
+  
+  function getToken(e, cur){ return e.getTokenAt(cur); }
+  
+  function accum(arr, fn, result){
+    result = result || [];
+    var len = arr.length,
+      _ = function(val){ result.push(val); };
+    for(var i=0; i < len; i++){
+      fn(_, arr[i], i); 
+    }
+    return result; 
+  }
+  
+  function keywordsLike(str){
+    str = str ? String(str).trim() : "";
+    var result = accum(ldKeywords, function(_, kw, i){
+      !str || ~kw.indexOf(str) ? _('"@' + kw + '"') : null;
+    });
+    
+    if(str){ result.sort(relevanceComparator(str)); }
+    return result;
+  }
+  
+  function contextLike(str, doc){
+    var ctx, key,
+      result = [];
+    
+    str = str ? String(str).trim() : "";
+    
+    if(doc && (ctx = doc["@context"])){
+      for(key in ctx){
+        if(!ctx.hasOwnProperty(key)){ return; }
+        !str || ~key.indexOf(str) ? result.push("\"" + key + "\"") : null;
+      }
+    }
+    return result;
+  }
+  
+  function relevanceComparator(str){
+    return function(a, b){
+      var result = a.indexOf(str) - b.indexOf(str);
+      if(!result){
+        return a.localeCompare(b);
+      }
+      return result;
+    };
+  }
+  
+  CodeMirror.registerHelper("hint", "jsonld", function(editor, options){
+    
+     // Find the token at the cursor
+    var cur = editor.getCursor(),
+      token = getToken(editor, cur),
+      tprop = token,
+      
+      // was this started by pressing "@"
+      isAt = options.isAt,
+      lastParsed = options.lastParsed,
+      
+      word = token.string,
+      start = token.start,
+      end = token.end,
+      
+      match;
+      
+    function suggest(suggestions){
+      return {
+        list: suggestions,
+        from: Pos(cur.line, start),
+        to: Pos(cur.line, end)
+      }; 
+    }
+      
+    token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;
+    
+    // clean up words, move pointers
+    if(word.match(/^[\{\[]/)){
+      // i just made an empty list and typed "@"...
+      word = "";
+      start++;
+    }else if(word.match(/^"/)){
+      // i just started a quoted string...
+      word = word.replace(/(^"|"$)/g, "");
+    }
+    
+    if(isAt){
+      // this was started by pressing @..
+      if(!~word.indexOf("@")){
+        // and the user is expecting a @
+        editor.replaceSelection("@", "end", "+input");
+      }
+      return suggest(keywordsLike(word.replace("@", "")));
+    }else if(match = word.match(/^"?@(.*)/)){
+      return suggest(keywordsLike(match[1]));
+    }
+    
+    return suggest(keywordsLike(word).concat(contextLike(word, lastParsed)));
+
+  });
+}).call(this, CodeMirror, CodeMirror.Pos);
--- a/playground/playground.js	Tue Oct 22 15:53:58 2013 -0400
+++ b/playground/playground.js	Wed Oct 23 16:05:21 2013 -0400
@@ -16,6 +16,13 @@
     frame: null,
     context: null
   };
+  
+  // the last parsed version of same
+  playground.lastParsed = {
+    markup: null,
+    frame: null,
+    context: null
+  };
 
   // set the active tab to the compacted view
   playground.activeTab = 'tab-compacted';
@@ -167,26 +174,52 @@
       playground.processQueryParameters();
     }
     
-    var processTimer = null;
+    $('.popover-info').popover({
+      placement: "bottom",
+      html: true,
+      content: $(".popover-info-content").html()
+    });
+    
+    var processTimer;
+    
+    CodeMirror.commands.autocomplete = function(cm) {
+      CodeMirror.showHint(cm, CodeMirror.hint.jsonld, {
+        lastParsed: playground.lastParsed[cm.options._playground_key]
+      });
+    };
+    
+    CodeMirror.commands.at_autocomplete = function(cm){
+      CodeMirror.showHint(cm, CodeMirror.hint.jsonld, {
+        isAt: true,
+        lastParsed: playground.lastParsed[cm.options._playground_key]
+      });
+    };
     
     $.each(playground.editors, function(key){
-      playground.editors[key] = CodeMirror.fromTextArea(document.getElementById(key), {
-        lineNumbers: true,
-        matchBrackets: true,
-        lineWrapping: true,
-        mode: "application/ld+json",
-        gutters: ["CodeMirror-lint-markers"],
-        theme: "elegant",
-        lint: true
-      });
+      var editor = playground.editors[key] = CodeMirror.fromTextArea(
+        $("#" + key)[0], {
+          lineNumbers: true,
+          matchBrackets: true,
+          autoCloseBrackets: true,
+          lineWrapping: true,
+          mode: "application/ld+json",
+          gutters: ["CodeMirror-lint-markers"],
+          theme: "elegant",
+          lint: true,
+          extraKeys: {
+            "Ctrl-Space": "autocomplete",
+            "Shift-2": "at_autocomplete"
+          },
+          _playground_key: key
+        });
       
-    // set up 'process' areas to process JSON-LD after typing
-    playground.editors[key].on("change",
-      function() {
-        clearTimeout(processTimer);
-        processTimer = setTimeout(playground.process, 500);
-      });
-    });
+      // set up 'process' areas to process JSON-LD after typing
+      editor
+        .on("change", function() {
+          clearTimeout(processTimer);
+          processTimer = setTimeout(playground.process, 500);
+        });
+    }); // each
   };
 
   /**
@@ -317,6 +350,7 @@
     // check to see if the JSON-LD markup is valid JSON
     try {
       var input = JSON.parse(markup);
+      playground.lastParsed.markup = input;
     }
     catch(e) {
       $('#markup-errors').text('JSON markup - ' + e);
@@ -327,20 +361,24 @@
     var needParam = false;
     var param = null;
     var jsonParam = null;
+    var paramType = null;
 
     if(playground.activeTab === 'tab-compacted' ||
       playground.activeTab === 'tab-flattened') {
       jsonParam = playground.editors.context.getValue();
       needParam = true;
+      paramType = "context";
     }
     else if(playground.activeTab === 'tab-framed') {
       jsonParam = playground.editors.frame.getValue();
       needParam = true;
+      paramType = "frame";
     }
 
     if(needParam) {
       try {
         param = JSON.parse(jsonParam);
+        playground.lastParsed[paramType] = param;
       }
       catch(e) {
         $('#param-errors').text($('#param-type').text() + ' - ' + e);
@@ -538,13 +576,14 @@
     });
     
     $('#theme-select a').click(function(evt){
-      var theme = evt.currentTarget.text;
+      var theme = evt.currentTarget.text,
+        file = evt.currentTarget.title ? evt.currentTarget.title : theme;
       
       $("#theme-name").text(theme);
       
       $('#theme-stylesheet').prop("href",
         "//cdnjs.cloudflare.com/ajax/libs/codemirror/3.16.0/theme/" +
-        theme + ".css" 
+        file + ".css" 
       );
       
       for(var key in playground.editors){