Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
776 lars 1
// CodeMirror, copyright (c) by Marijn Haverbeke and others
2
// Distributed under an MIT license: http://codemirror.net/LICENSE
3
 
4
/**
5
 * Tag-closer extension for CodeMirror.
6
 *
7
 * This extension adds an "autoCloseTags" option that can be set to
8
 * either true to get the default behavior, or an object to further
9
 * configure its behavior.
10
 *
11
 * These are supported options:
12
 *
13
 * `whenClosing` (default true)
14
 *   Whether to autoclose when the '/' of a closing tag is typed.
15
 * `whenOpening` (default true)
16
 *   Whether to autoclose the tag when the final '>' of an opening
17
 *   tag is typed.
18
 * `dontCloseTags` (default is empty tags for HTML, none for XML)
19
 *   An array of tag names that should not be autoclosed.
20
 * `indentTags` (default is block tags for HTML, none for XML)
21
 *   An array of tag names that should, when opened, cause a
22
 *   blank line to be added inside the tag, and the blank line and
23
 *   closing line to be indented.
24
 *
25
 * See demos/closetag.html for a usage example.
26
 */
27
 
28
(function(mod) {
29
  if (typeof exports == "object" && typeof module == "object") // CommonJS
30
    mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
31
  else if (typeof define == "function" && define.amd) // AMD
32
    define(["../../lib/codemirror", "../fold/xml-fold"], mod);
33
  else // Plain browser env
34
    mod(CodeMirror);
35
})(function(CodeMirror) {
36
  CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
37
    if (old != CodeMirror.Init && old)
38
      cm.removeKeyMap("autoCloseTags");
39
    if (!val) return;
40
    var map = {name: "autoCloseTags"};
41
    if (typeof val != "object" || val.whenClosing)
42
      map["'/'"] = function(cm) { return autoCloseSlash(cm); };
43
    if (typeof val != "object" || val.whenOpening)
44
      map["'>'"] = function(cm) { return autoCloseGT(cm); };
45
    cm.addKeyMap(map);
46
  });
47
 
48
  var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
49
                       "source", "track", "wbr"];
50
  var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
51
                    "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
52
 
53
  function autoCloseGT(cm) {
54
    if (cm.getOption("disableInput")) return CodeMirror.Pass;
55
    var ranges = cm.listSelections(), replacements = [];
56
    for (var i = 0; i < ranges.length; i++) {
57
      if (!ranges[i].empty()) return CodeMirror.Pass;
58
      var pos = ranges[i].head, tok = cm.getTokenAt(pos);
59
      var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
60
      if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass;
61
 
62
      var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
63
      var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
64
      var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
65
 
66
      var tagName = state.tagName;
67
      if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
68
      var lowerTagName = tagName.toLowerCase();
69
      // Don't process the '>' at the end of an end-tag or self-closing tag
70
      if (!tagName ||
71
          tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
72
          tok.type == "tag" && state.type == "closeTag" ||
73
          tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
74
          dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
75
          closingTagExists(cm, tagName, pos, state, true))
76
        return CodeMirror.Pass;
77
 
78
      var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
79
      replacements[i] = {indent: indent,
80
                         text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">",
81
                         newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};
82
    }
83
 
84
    for (var i = ranges.length - 1; i >= 0; i--) {
85
      var info = replacements[i];
86
      cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert");
87
      var sel = cm.listSelections().slice(0);
88
      sel[i] = {head: info.newPos, anchor: info.newPos};
89
      cm.setSelections(sel);
90
      if (info.indent) {
91
        cm.indentLine(info.newPos.line, null, true);
92
        cm.indentLine(info.newPos.line + 1, null, true);
93
      }
94
    }
95
  }
96
 
97
  function autoCloseCurrent(cm, typingSlash) {
98
    var ranges = cm.listSelections(), replacements = [];
99
    var head = typingSlash ? "/" : "</";
100
    for (var i = 0; i < ranges.length; i++) {
101
      if (!ranges[i].empty()) return CodeMirror.Pass;
102
      var pos = ranges[i].head, tok = cm.getTokenAt(pos);
103
      var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
104
      if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" ||
105
                          tok.start != pos.ch - 1))
106
        return CodeMirror.Pass;
107
      // Kludge to get around the fact that we are not in XML mode
108
      // when completing in JS/CSS snippet in htmlmixed mode. Does not
109
      // work for other XML embedded languages (there is no general
110
      // way to go from a mixed mode to its current XML state).
111
      var replacement;
112
      if (inner.mode.name != "xml") {
113
        if (cm.getMode().name == "htmlmixed" && inner.mode.name == "javascript")
114
          replacement = head + "script";
115
        else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css")
116
          replacement = head + "style";
117
        else
118
          return CodeMirror.Pass;
119
      } else {
120
        if (!state.context || !state.context.tagName ||
121
            closingTagExists(cm, state.context.tagName, pos, state))
122
          return CodeMirror.Pass;
123
        replacement = head + state.context.tagName;
124
      }
125
      if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">";
126
      replacements[i] = replacement;
127
    }
128
    cm.replaceSelections(replacements);
129
    ranges = cm.listSelections();
130
    for (var i = 0; i < ranges.length; i++)
131
      if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)
132
        cm.indentLine(ranges[i].head.line);
133
  }
134
 
135
  function autoCloseSlash(cm) {
136
    if (cm.getOption("disableInput")) return CodeMirror.Pass;
137
    return autoCloseCurrent(cm, true);
138
  }
139
 
140
  CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };
141
 
142
  function indexOf(collection, elt) {
143
    if (collection.indexOf) return collection.indexOf(elt);
144
    for (var i = 0, e = collection.length; i < e; ++i)
145
      if (collection[i] == elt) return i;
146
    return -1;
147
  }
148
 
149
  // If xml-fold is loaded, we use its functionality to try and verify
150
  // whether a given tag is actually unclosed.
151
  function closingTagExists(cm, tagName, pos, state, newTag) {
152
    if (!CodeMirror.scanForClosingTag) return false;
153
    var end = Math.min(cm.lastLine() + 1, pos.line + 500);
154
    var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);
155
    if (!nextClose || nextClose.tag != tagName) return false;
156
    var cx = state.context;
157
    // If the immediate wrapping context contains onCx instances of
158
    // the same tag, a closing tag only exists if there are at least
159
    // that many closing tags of that type following.
160
    for (var onCx = newTag ? 1 : 0; cx && cx.tagName == tagName; cx = cx.prev) ++onCx;
161
    pos = nextClose.to;
162
    for (var i = 1; i < onCx; i++) {
163
      var next = CodeMirror.scanForClosingTag(cm, pos, null, end);
164
      if (!next || next.tag != tagName) return false;
165
      pos = next.to;
166
    }
167
    return true;
168
  }
169
});