text: /* ***** BEGIN LICENSE BLOCK *****
   * Distributed under the BSD license:
   *
   * Copyright (c) 2012, Ajax.org B.V.
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *     * Redistributions of source code must retain the above copyright
   *       notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above copyright
   *       notice, this list of conditions and the following disclaimer in the
   *       documentation and/or other materials provided with the distribution.
   *     * Neither the name of Ajax.org B.V. nor the
   *       names of its contributors may be used to endorse or promote products
   *       derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * ***** END LICENSE BLOCK ***** */
  
  define(function(require, exports, module) {
  "use strict";
  
  var snippetManager = require("../snippets").snippetManager;
  var Autocomplete = require("../autocomplete").Autocomplete;
  var config = require("../config");
  var util = require("../autocomplete/util");
  
  var textCompleter = require("../autocomplete/text_completer");
  var keyWordCompleter = {
      getCompletions: function(editor, session, pos, prefix, callback) {
          var state = editor.session.getState(pos.row);
          var completions = session.$mode.getCompletions(state, session, pos, prefix);
          callback(null, completions);
      }
  };
  
  var snippetCompleter = {
      getCompletions: function(editor, session, pos, prefix, callback) {
          var snippetMap = snippetManager.snippetMap;
          var completions = [];
          snippetManager.getActiveScopes(editor).forEach(function(scope) {
              var snippets = snippetMap[scope] || [];
              for (var i = snippets.length; i--;) {
                  var s = snippets[i];
                  var caption = s.name || s.tabTrigger;
                  if (!caption)
                      continue;
                  completions.push({
                      caption: caption,
                      snippet: s.content,
                      meta: s.tabTrigger && !s.name ? s.tabTrigger + "\u21E5 " : "snippet"
                  });
              }
          }, this);
          callback(null, completions);
      }
  };
  
  var completers = [snippetCompleter, textCompleter, keyWordCompleter];
  exports.addCompleter = function(completer) {
      completers.push(completer);
  };
  
  var expandSnippet = {
      name: "expandSnippet",
      exec: function(editor) {
          var success = snippetManager.expandWithTab(editor);
          if (!success)
              editor.execCommand("indent");
      },
      bindKey: "Tab"
  };
  
  var onChangeMode = function(e, editor) {
      loadSnippetsForMode(editor.session.$mode);
  };
  
  var loadSnippetsForMode = function(mode) {
      var id = mode.$id;
      if (!snippetManager.files)
          snippetManager.files = {};
      loadSnippetFile(id);
      if (mode.modes)
          mode.modes.forEach(loadSnippetsForMode);
  };
  
  var loadSnippetFile = function(id) {
      if (!id || snippetManager.files[id])
          return;
      var snippetFilePath = id.replace("mode", "snippets");
      snippetManager.files[id] = {};
      config.loadModule(snippetFilePath, function(m) {
          if (m) {
              snippetManager.files[id] = m;
              m.snippets = snippetManager.parseSnippetFile(m.snippetText);
              snippetManager.register(m.snippets, m.scope);
              if (m.includeScopes) {
                  snippetManager.snippetMap[m.scope].includeScopes = m.includeScopes;
                  m.includeScopes.forEach(function(x) {
                      loadSnippetFile("ace/mode/" + x);
                  });
              }
          }
      });
  };
  
  var doLiveAutocomplete = function(e) {
      var editor = e.editor;
      var text = e.args || "";
      var pos = editor.getCursorPosition();
      var line = editor.session.getLine(pos.row);
      var hasCompleter = editor.completer && editor.completer.activated;
      var prefix = util.retrievePrecedingIdentifier(line, pos.column);
  
      //Try to find custom prefixes on the completers
      completers.forEach(function(completer) {
          if (completer.identifierRegexps) {
              completer.identifierRegexps.forEach(function(identifierRegex){
                  if (!prefix) {
                      prefix = util.retrievePrecedingIdentifier(line, pos.column, identifierRegex);
                  }
              });
          }
      });
  
      // We don't want to autocomplete with no prefix
      if (e.command.name === "backspace" && !prefix) {
          if (hasCompleter) 
              editor.completer.detach();
      }
      else if (e.command.name === "insertstring") {
          // Only autocomplete if there's a prefix that can be matched
          if (prefix && !hasCompleter) {
              if (!editor.completer) {
                  // Create new autocompleter
                  editor.completer = new Autocomplete();
                  // Disable autoInsert
                  editor.completer.autoSelect = false;
                  editor.completer.autoInsert = false;
              }
              editor.completer.showPopup(editor);
          } else if (!prefix && hasCompleter) {
              // When the prefix is empty
              // close the autocomplete dialog
              editor.completer.detach();
          }
      }
  };
  
  var Editor = require("../editor").Editor;
  require("../config").defineOptions(Editor.prototype, "editor", {
      enableBasicAutocompletion: {
          set: function(val) {
              if (val) {
                  this.completers = completers;
                  this.commands.addCommand(Autocomplete.startCommand);
              } else {
                  this.commands.removeCommand(Autocomplete.startCommand);
              }
          },
          value: false
      },
      enableLiveAutocomplete: {
          set: function(val) {
              if (val) {
                  // On each change automatically trigger the autocomplete
                  this.commands.on('afterExec', doLiveAutocomplete);
              } else {
                  this.commands.removeListener('afterExec', doLiveAutocomplete);
              }
          },
          value: false
      },
      enableSnippets: {
          set: function(val) {
              if (val) {
                  this.commands.addCommand(expandSnippet);
                  this.on("changeMode", onChangeMode);
                  onChangeMode(null, this);
              } else {
                  this.commands.removeCommand(expandSnippet);
                  this.off("changeMode", onChangeMode);
              }
          },
          value: false
      }
  });
  
  });
  