0
|
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
2 // Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
|
3
|
|
4 (function(mod) {
|
|
5 if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
6 mod(require("../../lib/codemirror"), require("./foldcode"));
|
|
7 else if (typeof define == "function" && define.amd) // AMD
|
|
8 define(["../../lib/codemirror", "./foldcode"], mod);
|
|
9 else // Plain browser env
|
|
10 mod(CodeMirror);
|
|
11 })(function(CodeMirror) {
|
|
12 "use strict";
|
|
13
|
|
14 CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
|
|
15 if (old && old != CodeMirror.Init) {
|
|
16 cm.clearGutter(cm.state.foldGutter.options.gutter);
|
|
17 cm.state.foldGutter = null;
|
|
18 cm.off("gutterClick", onGutterClick);
|
|
19 cm.off("changes", onChange);
|
|
20 cm.off("viewportChange", onViewportChange);
|
|
21 cm.off("fold", onFold);
|
|
22 cm.off("unfold", onFold);
|
|
23 cm.off("swapDoc", onChange);
|
|
24 cm.off("optionChange", optionChange);
|
|
25 }
|
|
26 if (val) {
|
|
27 cm.state.foldGutter = new State(parseOptions(val));
|
|
28 updateInViewport(cm);
|
|
29 cm.on("gutterClick", onGutterClick);
|
|
30 cm.on("changes", onChange);
|
|
31 cm.on("viewportChange", onViewportChange);
|
|
32 cm.on("fold", onFold);
|
|
33 cm.on("unfold", onFold);
|
|
34 cm.on("swapDoc", onChange);
|
|
35 cm.on("optionChange", optionChange);
|
|
36 }
|
|
37 });
|
|
38
|
|
39 var Pos = CodeMirror.Pos;
|
|
40
|
|
41 function State(options) {
|
|
42 this.options = options;
|
|
43 this.from = this.to = 0;
|
|
44 }
|
|
45
|
|
46 function parseOptions(opts) {
|
|
47 if (opts === true) opts = {};
|
|
48 if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
|
|
49 if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
|
|
50 if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
|
|
51 return opts;
|
|
52 }
|
|
53
|
|
54 function isFolded(cm, line) {
|
|
55 var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
|
|
56 for (var i = 0; i < marks.length; ++i) {
|
|
57 if (marks[i].__isFold) {
|
|
58 var fromPos = marks[i].find(-1);
|
|
59 if (fromPos && fromPos.line === line)
|
|
60 return marks[i];
|
|
61 }
|
|
62 }
|
|
63 }
|
|
64
|
|
65 function marker(spec) {
|
|
66 if (typeof spec == "string") {
|
|
67 var elt = document.createElement("div");
|
|
68 elt.className = spec + " CodeMirror-guttermarker-subtle";
|
|
69 return elt;
|
|
70 } else {
|
|
71 return spec.cloneNode(true);
|
|
72 }
|
|
73 }
|
|
74
|
|
75 function updateFoldInfo(cm, from, to) {
|
|
76 var opts = cm.state.foldGutter.options, cur = from - 1;
|
|
77 var minSize = cm.foldOption(opts, "minFoldSize");
|
|
78 var func = cm.foldOption(opts, "rangeFinder");
|
|
79 // we can reuse the built-in indicator element if its className matches the new state
|
|
80 var clsFolded = typeof opts.indicatorFolded == "string" && classTest(opts.indicatorFolded);
|
|
81 var clsOpen = typeof opts.indicatorOpen == "string" && classTest(opts.indicatorOpen);
|
|
82 cm.eachLine(from, to, function(line) {
|
|
83 ++cur;
|
|
84 var mark = null;
|
|
85 var old = line.gutterMarkers;
|
|
86 if (old) old = old[opts.gutter];
|
|
87 if (isFolded(cm, cur)) {
|
|
88 if (clsFolded && old && clsFolded.test(old.className)) return;
|
|
89 mark = marker(opts.indicatorFolded);
|
|
90 } else {
|
|
91 var pos = Pos(cur, 0);
|
|
92 var range = func && func(cm, pos);
|
|
93 if (range && range.to.line - range.from.line >= minSize) {
|
|
94 if (clsOpen && old && clsOpen.test(old.className)) return;
|
|
95 mark = marker(opts.indicatorOpen);
|
|
96 }
|
|
97 }
|
|
98 if (!mark && !old) return;
|
|
99 cm.setGutterMarker(line, opts.gutter, mark);
|
|
100 });
|
|
101 }
|
|
102
|
|
103 // copied from CodeMirror/src/util/dom.js
|
|
104 function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
|
|
105
|
|
106 function updateInViewport(cm) {
|
|
107 var vp = cm.getViewport(), state = cm.state.foldGutter;
|
|
108 if (!state) return;
|
|
109 cm.operation(function() {
|
|
110 updateFoldInfo(cm, vp.from, vp.to);
|
|
111 });
|
|
112 state.from = vp.from; state.to = vp.to;
|
|
113 }
|
|
114
|
|
115 function onGutterClick(cm, line, gutter) {
|
|
116 var state = cm.state.foldGutter;
|
|
117 if (!state) return;
|
|
118 var opts = state.options;
|
|
119 if (gutter != opts.gutter) return;
|
|
120 var folded = isFolded(cm, line);
|
|
121 if (folded) folded.clear();
|
|
122 else cm.foldCode(Pos(line, 0), opts);
|
|
123 }
|
|
124
|
|
125 function optionChange(cm, option) {
|
|
126 if (option == "mode") onChange(cm)
|
|
127 }
|
|
128
|
|
129 function onChange(cm) {
|
|
130 var state = cm.state.foldGutter;
|
|
131 if (!state) return;
|
|
132 var opts = state.options;
|
|
133 state.from = state.to = 0;
|
|
134 clearTimeout(state.changeUpdate);
|
|
135 state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
|
|
136 }
|
|
137
|
|
138 function onViewportChange(cm) {
|
|
139 var state = cm.state.foldGutter;
|
|
140 if (!state) return;
|
|
141 var opts = state.options;
|
|
142 clearTimeout(state.changeUpdate);
|
|
143 state.changeUpdate = setTimeout(function() {
|
|
144 var vp = cm.getViewport();
|
|
145 if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
|
|
146 updateInViewport(cm);
|
|
147 } else {
|
|
148 cm.operation(function() {
|
|
149 if (vp.from < state.from) {
|
|
150 updateFoldInfo(cm, vp.from, state.from);
|
|
151 state.from = vp.from;
|
|
152 }
|
|
153 if (vp.to > state.to) {
|
|
154 updateFoldInfo(cm, state.to, vp.to);
|
|
155 state.to = vp.to;
|
|
156 }
|
|
157 });
|
|
158 }
|
|
159 }, opts.updateViewportTimeSpan || 400);
|
|
160 }
|
|
161
|
|
162 function onFold(cm, from) {
|
|
163 var state = cm.state.foldGutter;
|
|
164 if (!state) return;
|
|
165 var line = from.line;
|
|
166 if (line >= state.from && line < state.to)
|
|
167 updateFoldInfo(cm, line, line + 1);
|
|
168 }
|
|
169 });
|