Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F1172994
codemirror.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Tue, Sep 30, 9:10 AM
Size
218 KB
Mime Type
text/plain
Expires
Thu, Oct 2, 9:10 AM (1 d, 21 h)
Engine
blob
Format
Raw Data
Handle
760238
Attached To
rINP In-Portal
codemirror.js
View Options
// CodeMirror version 3.14
//
// CodeMirror is the only global var we claim
window
.
CodeMirror
=
(
function
()
{
"use strict"
;
// BROWSER SNIFFING
// Crude, but necessary to handle a number of hard-to-feature-detect
// bugs and behavior differences.
var
gecko
=
/gecko\/\d/i
.
test
(
navigator
.
userAgent
);
var
ie
=
/MSIE \d/
.
test
(
navigator
.
userAgent
);
var
ie_lt8
=
ie
&&
(
document
.
documentMode
==
null
||
document
.
documentMode
<
8
);
var
ie_lt9
=
ie
&&
(
document
.
documentMode
==
null
||
document
.
documentMode
<
9
);
var
webkit
=
/WebKit\//
.
test
(
navigator
.
userAgent
);
var
qtwebkit
=
webkit
&&
/Qt\/\d+\.\d+/
.
test
(
navigator
.
userAgent
);
var
chrome
=
/Chrome\//
.
test
(
navigator
.
userAgent
);
var
opera
=
/Opera\//
.
test
(
navigator
.
userAgent
);
var
safari
=
/Apple Computer/
.
test
(
navigator
.
vendor
);
var
khtml
=
/KHTML\//
.
test
(
navigator
.
userAgent
);
var
mac_geLion
=
/Mac OS X 1\d\D([7-9]|\d\d)\D/
.
test
(
navigator
.
userAgent
);
var
mac_geMountainLion
=
/Mac OS X 1\d\D([8-9]|\d\d)\D/
.
test
(
navigator
.
userAgent
);
var
phantom
=
/PhantomJS/
.
test
(
navigator
.
userAgent
);
var
ios
=
/AppleWebKit/
.
test
(
navigator
.
userAgent
)
&&
/Mobile\/\w+/
.
test
(
navigator
.
userAgent
);
// This is woefully incomplete. Suggestions for alternative methods welcome.
var
mobile
=
ios
||
/Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i
.
test
(
navigator
.
userAgent
);
var
mac
=
ios
||
/Mac/
.
test
(
navigator
.
platform
);
var
windows
=
/windows/i
.
test
(
navigator
.
platform
);
var
opera_version
=
opera
&&
navigator
.
userAgent
.
match
(
/Version\/(\d*\.\d*)/
);
if
(
opera_version
)
opera_version
=
Number
(
opera_version
[
1
]);
// Some browsers use the wrong event properties to signal cmd/ctrl on OS X
var
flipCtrlCmd
=
mac
&&
(
qtwebkit
||
opera
&&
(
opera_version
==
null
||
opera_version
<
12.11
));
var
captureMiddleClick
=
gecko
||
(
ie
&&
!
ie_lt9
);
// Optimize some code when these features are not used
var
sawReadOnlySpans
=
false
,
sawCollapsedSpans
=
false
;
// CONSTRUCTOR
function
CodeMirror
(
place
,
options
)
{
if
(
!
(
this
instanceof
CodeMirror
))
return
new
CodeMirror
(
place
,
options
);
this
.
options
=
options
=
options
||
{};
// Determine effective options based on given values and defaults.
for
(
var
opt
in
defaults
)
if
(
!
options
.
hasOwnProperty
(
opt
)
&&
defaults
.
hasOwnProperty
(
opt
))
options
[
opt
]
=
defaults
[
opt
];
setGuttersForLineNumbers
(
options
);
var
docStart
=
typeof
options
.
value
==
"string"
?
0
:
options
.
value
.
first
;
var
display
=
this
.
display
=
makeDisplay
(
place
,
docStart
);
display
.
wrapper
.
CodeMirror
=
this
;
updateGutters
(
this
);
if
(
options
.
autofocus
&&
!
mobile
)
focusInput
(
this
);
this
.
state
=
{
keyMaps
:
[],
overlays
:
[],
modeGen
:
0
,
overwrite
:
false
,
focused
:
false
,
suppressEdits
:
false
,
pasteIncoming
:
false
,
draggingText
:
false
,
highlight
:
new
Delayed
()};
themeChanged
(
this
);
if
(
options
.
lineWrapping
)
this
.
display
.
wrapper
.
className
+=
" CodeMirror-wrap"
;
var
doc
=
options
.
value
;
if
(
typeof
doc
==
"string"
)
doc
=
new
Doc
(
options
.
value
,
options
.
mode
);
operation
(
this
,
attachDoc
)(
this
,
doc
);
// Override magic textarea content restore that IE sometimes does
// on our hidden textarea on reload
if
(
ie
)
setTimeout
(
bind
(
resetInput
,
this
,
true
),
20
);
registerEventHandlers
(
this
);
// IE throws unspecified error in certain cases, when
// trying to access activeElement before onload
var
hasFocus
;
try
{
hasFocus
=
(
document
.
activeElement
==
display
.
input
);
}
catch
(
e
)
{
}
if
(
hasFocus
||
(
options
.
autofocus
&&
!
mobile
))
setTimeout
(
bind
(
onFocus
,
this
),
20
);
else
onBlur
(
this
);
operation
(
this
,
function
()
{
for
(
var
opt
in
optionHandlers
)
if
(
optionHandlers
.
propertyIsEnumerable
(
opt
))
optionHandlers
[
opt
](
this
,
options
[
opt
],
Init
);
for
(
var
i
=
0
;
i
<
initHooks
.
length
;
++
i
)
initHooks
[
i
](
this
);
})();
}
// DISPLAY CONSTRUCTOR
function
makeDisplay
(
place
,
docStart
)
{
var
d
=
{};
var
input
=
d
.
input
=
elt
(
"textarea"
,
null
,
null
,
"position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;"
);
if
(
webkit
)
input
.
style
.
width
=
"1000px"
;
else
input
.
setAttribute
(
"wrap"
,
"off"
);
// if border: 0; -- iOS fails to open keyboard (issue #1287)
if
(
ios
)
input
.
style
.
border
=
"1px solid black"
;
input
.
setAttribute
(
"autocorrect"
,
"off"
);
input
.
setAttribute
(
"autocapitalize"
,
"off"
);
input
.
setAttribute
(
"spellcheck"
,
"false"
);
// Wraps and hides input textarea
d
.
inputDiv
=
elt
(
"div"
,
[
input
],
null
,
"overflow: hidden; position: relative; width: 3px; height: 0px;"
);
// The actual fake scrollbars.
d
.
scrollbarH
=
elt
(
"div"
,
[
elt
(
"div"
,
null
,
null
,
"height: 1px"
)],
"CodeMirror-hscrollbar"
);
d
.
scrollbarV
=
elt
(
"div"
,
[
elt
(
"div"
,
null
,
null
,
"width: 1px"
)],
"CodeMirror-vscrollbar"
);
d
.
scrollbarFiller
=
elt
(
"div"
,
null
,
"CodeMirror-scrollbar-filler"
);
d
.
gutterFiller
=
elt
(
"div"
,
null
,
"CodeMirror-gutter-filler"
);
// DIVs containing the selection and the actual code
d
.
lineDiv
=
elt
(
"div"
,
null
,
"CodeMirror-code"
);
d
.
selectionDiv
=
elt
(
"div"
,
null
,
null
,
"position: relative; z-index: 1"
);
// Blinky cursor, and element used to ensure cursor fits at the end of a line
d
.
cursor
=
elt
(
"div"
,
"\u00a0"
,
"CodeMirror-cursor"
);
// Secondary cursor, shown when on a 'jump' in bi-directional text
d
.
otherCursor
=
elt
(
"div"
,
"\u00a0"
,
"CodeMirror-cursor CodeMirror-secondarycursor"
);
// Used to measure text size
d
.
measure
=
elt
(
"div"
,
null
,
"CodeMirror-measure"
);
// Wraps everything that needs to exist inside the vertically-padded coordinate system
d
.
lineSpace
=
elt
(
"div"
,
[
d
.
measure
,
d
.
selectionDiv
,
d
.
lineDiv
,
d
.
cursor
,
d
.
otherCursor
],
null
,
"position: relative; outline: none"
);
// Moved around its parent to cover visible view
d
.
mover
=
elt
(
"div"
,
[
elt
(
"div"
,
[
d
.
lineSpace
],
"CodeMirror-lines"
)],
null
,
"position: relative"
);
// Set to the height of the text, causes scrolling
d
.
sizer
=
elt
(
"div"
,
[
d
.
mover
],
"CodeMirror-sizer"
);
// D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
d
.
heightForcer
=
elt
(
"div"
,
null
,
null
,
"position: absolute; height: "
+
scrollerCutOff
+
"px; width: 1px;"
);
// Will contain the gutters, if any
d
.
gutters
=
elt
(
"div"
,
null
,
"CodeMirror-gutters"
);
d
.
lineGutter
=
null
;
// Provides scrolling
d
.
scroller
=
elt
(
"div"
,
[
d
.
sizer
,
d
.
heightForcer
,
d
.
gutters
],
"CodeMirror-scroll"
);
d
.
scroller
.
setAttribute
(
"tabIndex"
,
"-1"
);
// The element in which the editor lives.
d
.
wrapper
=
elt
(
"div"
,
[
d
.
inputDiv
,
d
.
scrollbarH
,
d
.
scrollbarV
,
d
.
scrollbarFiller
,
d
.
gutterFiller
,
d
.
scroller
],
"CodeMirror"
);
// Work around IE7 z-index bug
if
(
ie_lt8
)
{
d
.
gutters
.
style
.
zIndex
=
-
1
;
d
.
scroller
.
style
.
paddingRight
=
0
;
}
if
(
place
.
appendChild
)
place
.
appendChild
(
d
.
wrapper
);
else
place
(
d
.
wrapper
);
// Needed to hide big blue blinking cursor on Mobile Safari
if
(
ios
)
input
.
style
.
width
=
"0px"
;
if
(
!
webkit
)
d
.
scroller
.
draggable
=
true
;
// Needed to handle Tab key in KHTML
if
(
khtml
)
{
d
.
inputDiv
.
style
.
height
=
"1px"
;
d
.
inputDiv
.
style
.
position
=
"absolute"
;
}
// Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
else
if
(
ie_lt8
)
d
.
scrollbarH
.
style
.
minWidth
=
d
.
scrollbarV
.
style
.
minWidth
=
"18px"
;
// Current visible range (may be bigger than the view window).
d
.
viewOffset
=
d
.
lastSizeC
=
0
;
d
.
showingFrom
=
d
.
showingTo
=
docStart
;
// Used to only resize the line number gutter when necessary (when
// the amount of lines crosses a boundary that makes its width change)
d
.
lineNumWidth
=
d
.
lineNumInnerWidth
=
d
.
lineNumChars
=
null
;
// See readInput and resetInput
d
.
prevInput
=
""
;
// Set to true when a non-horizontal-scrolling widget is added. As
// an optimization, widget aligning is skipped when d is false.
d
.
alignWidgets
=
false
;
// Flag that indicates whether we currently expect input to appear
// (after some event like 'keypress' or 'input') and are polling
// intensively.
d
.
pollingFast
=
false
;
// Self-resetting timeout for the poller
d
.
poll
=
new
Delayed
();
d
.
cachedCharWidth
=
d
.
cachedTextHeight
=
null
;
d
.
measureLineCache
=
[];
d
.
measureLineCachePos
=
0
;
// Tracks when resetInput has punted to just putting a short
// string instead of the (large) selection.
d
.
inaccurateSelection
=
false
;
// Tracks the maximum line length so that the horizontal scrollbar
// can be kept static when scrolling.
d
.
maxLine
=
null
;
d
.
maxLineLength
=
0
;
d
.
maxLineChanged
=
false
;
// Used for measuring wheel scrolling granularity
d
.
wheelDX
=
d
.
wheelDY
=
d
.
wheelStartX
=
d
.
wheelStartY
=
null
;
return
d
;
}
// STATE UPDATES
// Used to get the editor into a consistent state again when options change.
function
loadMode
(
cm
)
{
cm
.
doc
.
mode
=
CodeMirror
.
getMode
(
cm
.
options
,
cm
.
doc
.
modeOption
);
cm
.
doc
.
iter
(
function
(
line
)
{
if
(
line
.
stateAfter
)
line
.
stateAfter
=
null
;
if
(
line
.
styles
)
line
.
styles
=
null
;
});
cm
.
doc
.
frontier
=
cm
.
doc
.
first
;
startWorker
(
cm
,
100
);
cm
.
state
.
modeGen
++
;
if
(
cm
.
curOp
)
regChange
(
cm
);
}
function
wrappingChanged
(
cm
)
{
if
(
cm
.
options
.
lineWrapping
)
{
cm
.
display
.
wrapper
.
className
+=
" CodeMirror-wrap"
;
cm
.
display
.
sizer
.
style
.
minWidth
=
""
;
}
else
{
cm
.
display
.
wrapper
.
className
=
cm
.
display
.
wrapper
.
className
.
replace
(
" CodeMirror-wrap"
,
""
);
computeMaxLength
(
cm
);
}
estimateLineHeights
(
cm
);
regChange
(
cm
);
clearCaches
(
cm
);
setTimeout
(
function
(){
updateScrollbars
(
cm
);},
100
);
}
function
estimateHeight
(
cm
)
{
var
th
=
textHeight
(
cm
.
display
),
wrapping
=
cm
.
options
.
lineWrapping
;
var
perLine
=
wrapping
&&
Math
.
max
(
5
,
cm
.
display
.
scroller
.
clientWidth
/
charWidth
(
cm
.
display
)
-
3
);
return
function
(
line
)
{
if
(
lineIsHidden
(
cm
.
doc
,
line
))
return
0
;
else
if
(
wrapping
)
return
(
Math
.
ceil
(
line
.
text
.
length
/
perLine
)
||
1
)
*
th
;
else
return
th
;
};
}
function
estimateLineHeights
(
cm
)
{
var
doc
=
cm
.
doc
,
est
=
estimateHeight
(
cm
);
doc
.
iter
(
function
(
line
)
{
var
estHeight
=
est
(
line
);
if
(
estHeight
!=
line
.
height
)
updateLineHeight
(
line
,
estHeight
);
});
}
function
keyMapChanged
(
cm
)
{
var
map
=
keyMap
[
cm
.
options
.
keyMap
],
style
=
map
.
style
;
cm
.
display
.
wrapper
.
className
=
cm
.
display
.
wrapper
.
className
.
replace
(
/\s*cm-keymap-\S+/g
,
""
)
+
(
style
?
" cm-keymap-"
+
style
:
""
);
cm
.
state
.
disableInput
=
map
.
disableInput
;
}
function
themeChanged
(
cm
)
{
cm
.
display
.
wrapper
.
className
=
cm
.
display
.
wrapper
.
className
.
replace
(
/\s*cm-s-\S+/g
,
""
)
+
cm
.
options
.
theme
.
replace
(
/(^|\s)\s*/g
,
" cm-s-"
);
clearCaches
(
cm
);
}
function
guttersChanged
(
cm
)
{
updateGutters
(
cm
);
regChange
(
cm
);
setTimeout
(
function
(){
alignHorizontally
(
cm
);},
20
);
}
function
updateGutters
(
cm
)
{
var
gutters
=
cm
.
display
.
gutters
,
specs
=
cm
.
options
.
gutters
;
removeChildren
(
gutters
);
for
(
var
i
=
0
;
i
<
specs
.
length
;
++
i
)
{
var
gutterClass
=
specs
[
i
];
var
gElt
=
gutters
.
appendChild
(
elt
(
"div"
,
null
,
"CodeMirror-gutter "
+
gutterClass
));
if
(
gutterClass
==
"CodeMirror-linenumbers"
)
{
cm
.
display
.
lineGutter
=
gElt
;
gElt
.
style
.
width
=
(
cm
.
display
.
lineNumWidth
||
1
)
+
"px"
;
}
}
gutters
.
style
.
display
=
i
?
""
:
"none"
;
}
function
lineLength
(
doc
,
line
)
{
if
(
line
.
height
==
0
)
return
0
;
var
len
=
line
.
text
.
length
,
merged
,
cur
=
line
;
while
(
merged
=
collapsedSpanAtStart
(
cur
))
{
var
found
=
merged
.
find
();
cur
=
getLine
(
doc
,
found
.
from
.
line
);
len
+=
found
.
from
.
ch
-
found
.
to
.
ch
;
}
cur
=
line
;
while
(
merged
=
collapsedSpanAtEnd
(
cur
))
{
var
found
=
merged
.
find
();
len
-=
cur
.
text
.
length
-
found
.
from
.
ch
;
cur
=
getLine
(
doc
,
found
.
to
.
line
);
len
+=
cur
.
text
.
length
-
found
.
to
.
ch
;
}
return
len
;
}
function
computeMaxLength
(
cm
)
{
var
d
=
cm
.
display
,
doc
=
cm
.
doc
;
d
.
maxLine
=
getLine
(
doc
,
doc
.
first
);
d
.
maxLineLength
=
lineLength
(
doc
,
d
.
maxLine
);
d
.
maxLineChanged
=
true
;
doc
.
iter
(
function
(
line
)
{
var
len
=
lineLength
(
doc
,
line
);
if
(
len
>
d
.
maxLineLength
)
{
d
.
maxLineLength
=
len
;
d
.
maxLine
=
line
;
}
});
}
// Make sure the gutters options contains the element
// "CodeMirror-linenumbers" when the lineNumbers option is true.
function
setGuttersForLineNumbers
(
options
)
{
var
found
=
false
;
for
(
var
i
=
0
;
i
<
options
.
gutters
.
length
;
++
i
)
{
if
(
options
.
gutters
[
i
]
==
"CodeMirror-linenumbers"
)
{
if
(
options
.
lineNumbers
)
found
=
true
;
else
options
.
gutters
.
splice
(
i
--
,
1
);
}
}
if
(
!
found
&&
options
.
lineNumbers
)
options
.
gutters
.
push
(
"CodeMirror-linenumbers"
);
}
// SCROLLBARS
// Re-synchronize the fake scrollbars with the actual size of the
// content. Optionally force a scrollTop.
function
updateScrollbars
(
cm
)
{
var
d
=
cm
.
display
,
docHeight
=
cm
.
doc
.
height
;
var
totalHeight
=
docHeight
+
paddingVert
(
d
);
d
.
sizer
.
style
.
minHeight
=
d
.
heightForcer
.
style
.
top
=
totalHeight
+
"px"
;
d
.
gutters
.
style
.
height
=
Math
.
max
(
totalHeight
,
d
.
scroller
.
clientHeight
-
scrollerCutOff
)
+
"px"
;
var
scrollHeight
=
Math
.
max
(
totalHeight
,
d
.
scroller
.
scrollHeight
);
var
needsH
=
d
.
scroller
.
scrollWidth
>
(
d
.
scroller
.
clientWidth
+
1
);
var
needsV
=
scrollHeight
>
(
d
.
scroller
.
clientHeight
+
1
);
if
(
needsV
)
{
d
.
scrollbarV
.
style
.
display
=
"block"
;
d
.
scrollbarV
.
style
.
bottom
=
needsH
?
scrollbarWidth
(
d
.
measure
)
+
"px"
:
"0"
;
d
.
scrollbarV
.
firstChild
.
style
.
height
=
(
scrollHeight
-
d
.
scroller
.
clientHeight
+
d
.
scrollbarV
.
clientHeight
)
+
"px"
;
}
else
d
.
scrollbarV
.
style
.
display
=
""
;
if
(
needsH
)
{
d
.
scrollbarH
.
style
.
display
=
"block"
;
d
.
scrollbarH
.
style
.
right
=
needsV
?
scrollbarWidth
(
d
.
measure
)
+
"px"
:
"0"
;
d
.
scrollbarH
.
firstChild
.
style
.
width
=
(
d
.
scroller
.
scrollWidth
-
d
.
scroller
.
clientWidth
+
d
.
scrollbarH
.
clientWidth
)
+
"px"
;
}
else
d
.
scrollbarH
.
style
.
display
=
""
;
if
(
needsH
&&
needsV
)
{
d
.
scrollbarFiller
.
style
.
display
=
"block"
;
d
.
scrollbarFiller
.
style
.
height
=
d
.
scrollbarFiller
.
style
.
width
=
scrollbarWidth
(
d
.
measure
)
+
"px"
;
}
else
d
.
scrollbarFiller
.
style
.
display
=
""
;
if
(
needsH
&&
cm
.
options
.
coverGutterNextToScrollbar
&&
cm
.
options
.
fixedGutter
)
{
d
.
gutterFiller
.
style
.
display
=
"block"
;
d
.
gutterFiller
.
style
.
height
=
scrollbarWidth
(
d
.
measure
)
+
"px"
;
d
.
gutterFiller
.
style
.
width
=
d
.
gutters
.
offsetWidth
+
"px"
;
}
else
d
.
gutterFiller
.
style
.
display
=
""
;
if
(
mac_geLion
&&
scrollbarWidth
(
d
.
measure
)
===
0
)
d
.
scrollbarV
.
style
.
minWidth
=
d
.
scrollbarH
.
style
.
minHeight
=
mac_geMountainLion
?
"18px"
:
"12px"
;
}
function
visibleLines
(
display
,
doc
,
viewPort
)
{
var
top
=
display
.
scroller
.
scrollTop
,
height
=
display
.
wrapper
.
clientHeight
;
if
(
typeof
viewPort
==
"number"
)
top
=
viewPort
;
else
if
(
viewPort
)
{
top
=
viewPort
.
top
;
height
=
viewPort
.
bottom
-
viewPort
.
top
;}
top
=
Math
.
floor
(
top
-
paddingTop
(
display
));
var
bottom
=
Math
.
ceil
(
top
+
height
);
return
{
from
:
lineAtHeight
(
doc
,
top
),
to
:
lineAtHeight
(
doc
,
bottom
)};
}
// LINE NUMBERS
function
alignHorizontally
(
cm
)
{
var
display
=
cm
.
display
;
if
(
!
display
.
alignWidgets
&&
(
!
display
.
gutters
.
firstChild
||
!
cm
.
options
.
fixedGutter
))
return
;
var
comp
=
compensateForHScroll
(
display
)
-
display
.
scroller
.
scrollLeft
+
cm
.
doc
.
scrollLeft
;
var
gutterW
=
display
.
gutters
.
offsetWidth
,
l
=
comp
+
"px"
;
for
(
var
n
=
display
.
lineDiv
.
firstChild
;
n
;
n
=
n
.
nextSibling
)
if
(
n
.
alignable
)
{
for
(
var
i
=
0
,
a
=
n
.
alignable
;
i
<
a
.
length
;
++
i
)
a
[
i
].
style
.
left
=
l
;
}
if
(
cm
.
options
.
fixedGutter
)
display
.
gutters
.
style
.
left
=
(
comp
+
gutterW
)
+
"px"
;
}
function
maybeUpdateLineNumberWidth
(
cm
)
{
if
(
!
cm
.
options
.
lineNumbers
)
return
false
;
var
doc
=
cm
.
doc
,
last
=
lineNumberFor
(
cm
.
options
,
doc
.
first
+
doc
.
size
-
1
),
display
=
cm
.
display
;
if
(
last
.
length
!=
display
.
lineNumChars
)
{
var
test
=
display
.
measure
.
appendChild
(
elt
(
"div"
,
[
elt
(
"div"
,
last
)],
"CodeMirror-linenumber CodeMirror-gutter-elt"
));
var
innerW
=
test
.
firstChild
.
offsetWidth
,
padding
=
test
.
offsetWidth
-
innerW
;
display
.
lineGutter
.
style
.
width
=
""
;
display
.
lineNumInnerWidth
=
Math
.
max
(
innerW
,
display
.
lineGutter
.
offsetWidth
-
padding
);
display
.
lineNumWidth
=
display
.
lineNumInnerWidth
+
padding
;
display
.
lineNumChars
=
display
.
lineNumInnerWidth
?
last
.
length
:
-
1
;
display
.
lineGutter
.
style
.
width
=
display
.
lineNumWidth
+
"px"
;
return
true
;
}
return
false
;
}
function
lineNumberFor
(
options
,
i
)
{
return
String
(
options
.
lineNumberFormatter
(
i
+
options
.
firstLineNumber
));
}
function
compensateForHScroll
(
display
)
{
return
getRect
(
display
.
scroller
).
left
-
getRect
(
display
.
sizer
).
left
;
}
// DISPLAY DRAWING
function
updateDisplay
(
cm
,
changes
,
viewPort
)
{
var
oldFrom
=
cm
.
display
.
showingFrom
,
oldTo
=
cm
.
display
.
showingTo
,
updated
;
var
visible
=
visibleLines
(
cm
.
display
,
cm
.
doc
,
viewPort
);
for
(;;)
{
if
(
!
updateDisplayInner
(
cm
,
changes
,
visible
))
break
;
updated
=
true
;
updateSelection
(
cm
);
updateScrollbars
(
cm
);
// Clip forced viewport to actual scrollable area
if
(
viewPort
)
viewPort
=
Math
.
min
(
cm
.
display
.
scroller
.
scrollHeight
-
cm
.
display
.
scroller
.
clientHeight
,
typeof
viewPort
==
"number"
?
viewPort
:
viewPort
.
top
);
visible
=
visibleLines
(
cm
.
display
,
cm
.
doc
,
viewPort
);
if
(
visible
.
from
>=
cm
.
display
.
showingFrom
&&
visible
.
to
<=
cm
.
display
.
showingTo
)
break
;
changes
=
[];
}
if
(
updated
)
{
signalLater
(
cm
,
"update"
,
cm
);
if
(
cm
.
display
.
showingFrom
!=
oldFrom
||
cm
.
display
.
showingTo
!=
oldTo
)
signalLater
(
cm
,
"viewportChange"
,
cm
,
cm
.
display
.
showingFrom
,
cm
.
display
.
showingTo
);
}
return
updated
;
}
// Uses a set of changes plus the current scroll position to
// determine which DOM updates have to be made, and makes the
// updates.
function
updateDisplayInner
(
cm
,
changes
,
visible
)
{
var
display
=
cm
.
display
,
doc
=
cm
.
doc
;
if
(
!
display
.
wrapper
.
clientWidth
)
{
display
.
showingFrom
=
display
.
showingTo
=
doc
.
first
;
display
.
viewOffset
=
0
;
return
;
}
// Bail out if the visible area is already rendered and nothing changed.
if
(
changes
.
length
==
0
&&
visible
.
from
>
display
.
showingFrom
&&
visible
.
to
<
display
.
showingTo
)
return
;
if
(
maybeUpdateLineNumberWidth
(
cm
))
changes
=
[{
from
:
doc
.
first
,
to
:
doc
.
first
+
doc
.
size
}];
var
gutterW
=
display
.
sizer
.
style
.
marginLeft
=
display
.
gutters
.
offsetWidth
+
"px"
;
display
.
scrollbarH
.
style
.
left
=
cm
.
options
.
fixedGutter
?
gutterW
:
"0"
;
// Used to determine which lines need their line numbers updated
var
positionsChangedFrom
=
Infinity
;
if
(
cm
.
options
.
lineNumbers
)
for
(
var
i
=
0
;
i
<
changes
.
length
;
++
i
)
if
(
changes
[
i
].
diff
)
{
positionsChangedFrom
=
changes
[
i
].
from
;
break
;
}
var
end
=
doc
.
first
+
doc
.
size
;
var
from
=
Math
.
max
(
visible
.
from
-
cm
.
options
.
viewportMargin
,
doc
.
first
);
var
to
=
Math
.
min
(
end
,
visible
.
to
+
cm
.
options
.
viewportMargin
);
if
(
display
.
showingFrom
<
from
&&
from
-
display
.
showingFrom
<
20
)
from
=
Math
.
max
(
doc
.
first
,
display
.
showingFrom
);
if
(
display
.
showingTo
>
to
&&
display
.
showingTo
-
to
<
20
)
to
=
Math
.
min
(
end
,
display
.
showingTo
);
if
(
sawCollapsedSpans
)
{
from
=
lineNo
(
visualLine
(
doc
,
getLine
(
doc
,
from
)));
while
(
to
<
end
&&
lineIsHidden
(
doc
,
getLine
(
doc
,
to
)))
++
to
;
}
// Create a range of theoretically intact lines, and punch holes
// in that using the change info.
var
intact
=
[{
from
:
Math
.
max
(
display
.
showingFrom
,
doc
.
first
),
to
:
Math
.
min
(
display
.
showingTo
,
end
)}];
if
(
intact
[
0
].
from
>=
intact
[
0
].
to
)
intact
=
[];
else
intact
=
computeIntact
(
intact
,
changes
);
// When merged lines are present, we might have to reduce the
// intact ranges because changes in continued fragments of the
// intact lines do require the lines to be redrawn.
if
(
sawCollapsedSpans
)
for
(
var
i
=
0
;
i
<
intact
.
length
;
++
i
)
{
var
range
=
intact
[
i
],
merged
;
while
(
merged
=
collapsedSpanAtEnd
(
getLine
(
doc
,
range
.
to
-
1
)))
{
var
newTo
=
merged
.
find
().
from
.
line
;
if
(
newTo
>
range
.
from
)
range
.
to
=
newTo
;
else
{
intact
.
splice
(
i
--
,
1
);
break
;
}
}
}
// Clip off the parts that won't be visible
var
intactLines
=
0
;
for
(
var
i
=
0
;
i
<
intact
.
length
;
++
i
)
{
var
range
=
intact
[
i
];
if
(
range
.
from
<
from
)
range
.
from
=
from
;
if
(
range
.
to
>
to
)
range
.
to
=
to
;
if
(
range
.
from
>=
range
.
to
)
intact
.
splice
(
i
--
,
1
);
else
intactLines
+=
range
.
to
-
range
.
from
;
}
if
(
intactLines
==
to
-
from
&&
from
==
display
.
showingFrom
&&
to
==
display
.
showingTo
)
{
updateViewOffset
(
cm
);
return
;
}
intact
.
sort
(
function
(
a
,
b
)
{
return
a
.
from
-
b
.
from
;});
// Avoid crashing on IE's "unspecified error" when in iframes
try
{
var
focused
=
document
.
activeElement
;
}
catch
(
e
)
{}
if
(
intactLines
<
(
to
-
from
)
*
.7
)
display
.
lineDiv
.
style
.
display
=
"none"
;
patchDisplay
(
cm
,
from
,
to
,
intact
,
positionsChangedFrom
);
display
.
lineDiv
.
style
.
display
=
""
;
if
(
focused
&&
document
.
activeElement
!=
focused
&&
focused
.
offsetHeight
)
focused
.
focus
();
var
different
=
from
!=
display
.
showingFrom
||
to
!=
display
.
showingTo
||
display
.
lastSizeC
!=
display
.
wrapper
.
clientHeight
;
// This is just a bogus formula that detects when the editor is
// resized or the font size changes.
if
(
different
)
{
display
.
lastSizeC
=
display
.
wrapper
.
clientHeight
;
startWorker
(
cm
,
400
);
}
display
.
showingFrom
=
from
;
display
.
showingTo
=
to
;
var
prevBottom
=
display
.
lineDiv
.
offsetTop
;
for
(
var
node
=
display
.
lineDiv
.
firstChild
,
height
;
node
;
node
=
node
.
nextSibling
)
if
(
node
.
lineObj
)
{
if
(
ie_lt8
)
{
var
bot
=
node
.
offsetTop
+
node
.
offsetHeight
;
height
=
bot
-
prevBottom
;
prevBottom
=
bot
;
}
else
{
var
box
=
getRect
(
node
);
height
=
box
.
bottom
-
box
.
top
;
}
var
diff
=
node
.
lineObj
.
height
-
height
;
if
(
height
<
2
)
height
=
textHeight
(
display
);
if
(
diff
>
.001
||
diff
<
-
.001
)
{
updateLineHeight
(
node
.
lineObj
,
height
);
var
widgets
=
node
.
lineObj
.
widgets
;
if
(
widgets
)
for
(
var
i
=
0
;
i
<
widgets
.
length
;
++
i
)
widgets
[
i
].
height
=
widgets
[
i
].
node
.
offsetHeight
;
}
}
updateViewOffset
(
cm
);
return
true
;
}
function
updateViewOffset
(
cm
)
{
var
off
=
cm
.
display
.
viewOffset
=
heightAtLine
(
cm
,
getLine
(
cm
.
doc
,
cm
.
display
.
showingFrom
));
// Position the mover div to align with the current virtual scroll position
cm
.
display
.
mover
.
style
.
top
=
off
+
"px"
;
}
function
computeIntact
(
intact
,
changes
)
{
for
(
var
i
=
0
,
l
=
changes
.
length
||
0
;
i
<
l
;
++
i
)
{
var
change
=
changes
[
i
],
intact2
=
[],
diff
=
change
.
diff
||
0
;
for
(
var
j
=
0
,
l2
=
intact
.
length
;
j
<
l2
;
++
j
)
{
var
range
=
intact
[
j
];
if
(
change
.
to
<=
range
.
from
&&
change
.
diff
)
{
intact2
.
push
({
from
:
range
.
from
+
diff
,
to
:
range
.
to
+
diff
});
}
else
if
(
change
.
to
<=
range
.
from
||
change
.
from
>=
range
.
to
)
{
intact2
.
push
(
range
);
}
else
{
if
(
change
.
from
>
range
.
from
)
intact2
.
push
({
from
:
range
.
from
,
to
:
change
.
from
});
if
(
change
.
to
<
range
.
to
)
intact2
.
push
({
from
:
change
.
to
+
diff
,
to
:
range
.
to
+
diff
});
}
}
intact
=
intact2
;
}
return
intact
;
}
function
getDimensions
(
cm
)
{
var
d
=
cm
.
display
,
left
=
{},
width
=
{};
for
(
var
n
=
d
.
gutters
.
firstChild
,
i
=
0
;
n
;
n
=
n
.
nextSibling
,
++
i
)
{
left
[
cm
.
options
.
gutters
[
i
]]
=
n
.
offsetLeft
;
width
[
cm
.
options
.
gutters
[
i
]]
=
n
.
offsetWidth
;
}
return
{
fixedPos
:
compensateForHScroll
(
d
),
gutterTotalWidth
:
d
.
gutters
.
offsetWidth
,
gutterLeft
:
left
,
gutterWidth
:
width
,
wrapperWidth
:
d
.
wrapper
.
clientWidth
};
}
function
patchDisplay
(
cm
,
from
,
to
,
intact
,
updateNumbersFrom
)
{
var
dims
=
getDimensions
(
cm
);
var
display
=
cm
.
display
,
lineNumbers
=
cm
.
options
.
lineNumbers
;
if
(
!
intact
.
length
&&
(
!
webkit
||
!
cm
.
display
.
currentWheelTarget
))
removeChildren
(
display
.
lineDiv
);
var
container
=
display
.
lineDiv
,
cur
=
container
.
firstChild
;
function
rm
(
node
)
{
var
next
=
node
.
nextSibling
;
if
(
webkit
&&
mac
&&
cm
.
display
.
currentWheelTarget
==
node
)
{
node
.
style
.
display
=
"none"
;
node
.
lineObj
=
null
;
}
else
{
node
.
parentNode
.
removeChild
(
node
);
}
return
next
;
}
var
nextIntact
=
intact
.
shift
(),
lineN
=
from
;
cm
.
doc
.
iter
(
from
,
to
,
function
(
line
)
{
if
(
nextIntact
&&
nextIntact
.
to
==
lineN
)
nextIntact
=
intact
.
shift
();
if
(
lineIsHidden
(
cm
.
doc
,
line
))
{
if
(
line
.
height
!=
0
)
updateLineHeight
(
line
,
0
);
if
(
line
.
widgets
&&
cur
.
previousSibling
)
for
(
var
i
=
0
;
i
<
line
.
widgets
.
length
;
++
i
)
{
var
w
=
line
.
widgets
[
i
];
if
(
w
.
showIfHidden
)
{
var
prev
=
cur
.
previousSibling
;
if
(
/pre/i
.
test
(
prev
.
nodeName
))
{
var
wrap
=
elt
(
"div"
,
null
,
null
,
"position: relative"
);
prev
.
parentNode
.
replaceChild
(
wrap
,
prev
);
wrap
.
appendChild
(
prev
);
prev
=
wrap
;
}
var
wnode
=
prev
.
appendChild
(
elt
(
"div"
,
[
w
.
node
],
"CodeMirror-linewidget"
));
if
(
!
w
.
handleMouseEvents
)
wnode
.
ignoreEvents
=
true
;
positionLineWidget
(
w
,
wnode
,
prev
,
dims
);
}
}
}
else
if
(
nextIntact
&&
nextIntact
.
from
<=
lineN
&&
nextIntact
.
to
>
lineN
)
{
// This line is intact. Skip to the actual node. Update its
// line number if needed.
while
(
cur
.
lineObj
!=
line
)
cur
=
rm
(
cur
);
if
(
lineNumbers
&&
updateNumbersFrom
<=
lineN
&&
cur
.
lineNumber
)
setTextContent
(
cur
.
lineNumber
,
lineNumberFor
(
cm
.
options
,
lineN
));
cur
=
cur
.
nextSibling
;
}
else
{
// For lines with widgets, make an attempt to find and reuse
// the existing element, so that widgets aren't needlessly
// removed and re-inserted into the dom
if
(
line
.
widgets
)
for
(
var
j
=
0
,
search
=
cur
,
reuse
;
search
&&
j
<
20
;
++
j
,
search
=
search
.
nextSibling
)
if
(
search
.
lineObj
==
line
&&
/div/i
.
test
(
search
.
nodeName
))
{
reuse
=
search
;
break
;
}
// This line needs to be generated.
var
lineNode
=
buildLineElement
(
cm
,
line
,
lineN
,
dims
,
reuse
);
if
(
lineNode
!=
reuse
)
{
container
.
insertBefore
(
lineNode
,
cur
);
}
else
{
while
(
cur
!=
reuse
)
cur
=
rm
(
cur
);
cur
=
cur
.
nextSibling
;
}
lineNode
.
lineObj
=
line
;
}
++
lineN
;
});
while
(
cur
)
cur
=
rm
(
cur
);
}
function
buildLineElement
(
cm
,
line
,
lineNo
,
dims
,
reuse
)
{
var
lineElement
=
lineContent
(
cm
,
line
);
var
markers
=
line
.
gutterMarkers
,
display
=
cm
.
display
,
wrap
;
if
(
!
cm
.
options
.
lineNumbers
&&
!
markers
&&
!
line
.
bgClass
&&
!
line
.
wrapClass
&&
!
line
.
widgets
)
return
lineElement
;
// Lines with gutter elements, widgets or a background class need
// to be wrapped again, and have the extra elements added to the
// wrapper div
if
(
reuse
)
{
reuse
.
alignable
=
null
;
var
isOk
=
true
,
widgetsSeen
=
0
,
insertBefore
=
null
;
for
(
var
n
=
reuse
.
firstChild
,
next
;
n
;
n
=
next
)
{
next
=
n
.
nextSibling
;
if
(
!
/\bCodeMirror-linewidget\b/
.
test
(
n
.
className
))
{
reuse
.
removeChild
(
n
);
}
else
{
for
(
var
i
=
0
,
first
=
true
;
i
<
line
.
widgets
.
length
;
++
i
)
{
var
widget
=
line
.
widgets
[
i
];
if
(
!
widget
.
above
)
{
insertBefore
=
n
;
first
=
false
;
}
if
(
widget
.
node
==
n
.
firstChild
)
{
positionLineWidget
(
widget
,
n
,
reuse
,
dims
);
++
widgetsSeen
;
break
;
}
}
if
(
i
==
line
.
widgets
.
length
)
{
isOk
=
false
;
break
;
}
}
}
reuse
.
insertBefore
(
lineElement
,
insertBefore
);
if
(
isOk
&&
widgetsSeen
==
line
.
widgets
.
length
)
{
wrap
=
reuse
;
reuse
.
className
=
line
.
wrapClass
||
""
;
}
}
if
(
!
wrap
)
{
wrap
=
elt
(
"div"
,
null
,
line
.
wrapClass
,
"position: relative"
);
wrap
.
appendChild
(
lineElement
);
}
// Kludge to make sure the styled element lies behind the selection (by z-index)
if
(
line
.
bgClass
)
wrap
.
insertBefore
(
elt
(
"div"
,
null
,
line
.
bgClass
+
" CodeMirror-linebackground"
),
wrap
.
firstChild
);
if
(
cm
.
options
.
lineNumbers
||
markers
)
{
var
gutterWrap
=
wrap
.
insertBefore
(
elt
(
"div"
,
null
,
null
,
"position: absolute; left: "
+
(
cm
.
options
.
fixedGutter
?
dims
.
fixedPos
:
-
dims
.
gutterTotalWidth
)
+
"px"
),
wrap
.
firstChild
);
if
(
cm
.
options
.
fixedGutter
)
(
wrap
.
alignable
||
(
wrap
.
alignable
=
[])).
push
(
gutterWrap
);
if
(
cm
.
options
.
lineNumbers
&&
(
!
markers
||
!
markers
[
"CodeMirror-linenumbers"
]))
wrap
.
lineNumber
=
gutterWrap
.
appendChild
(
elt
(
"div"
,
lineNumberFor
(
cm
.
options
,
lineNo
),
"CodeMirror-linenumber CodeMirror-gutter-elt"
,
"left: "
+
dims
.
gutterLeft
[
"CodeMirror-linenumbers"
]
+
"px; width: "
+
display
.
lineNumInnerWidth
+
"px"
));
if
(
markers
)
for
(
var
k
=
0
;
k
<
cm
.
options
.
gutters
.
length
;
++
k
)
{
var
id
=
cm
.
options
.
gutters
[
k
],
found
=
markers
.
hasOwnProperty
(
id
)
&&
markers
[
id
];
if
(
found
)
gutterWrap
.
appendChild
(
elt
(
"div"
,
[
found
],
"CodeMirror-gutter-elt"
,
"left: "
+
dims
.
gutterLeft
[
id
]
+
"px; width: "
+
dims
.
gutterWidth
[
id
]
+
"px"
));
}
}
if
(
ie_lt8
)
wrap
.
style
.
zIndex
=
2
;
if
(
line
.
widgets
&&
wrap
!=
reuse
)
for
(
var
i
=
0
,
ws
=
line
.
widgets
;
i
<
ws
.
length
;
++
i
)
{
var
widget
=
ws
[
i
],
node
=
elt
(
"div"
,
[
widget
.
node
],
"CodeMirror-linewidget"
);
if
(
!
widget
.
handleMouseEvents
)
node
.
ignoreEvents
=
true
;
positionLineWidget
(
widget
,
node
,
wrap
,
dims
);
if
(
widget
.
above
)
wrap
.
insertBefore
(
node
,
cm
.
options
.
lineNumbers
&&
line
.
height
!=
0
?
gutterWrap
:
lineElement
);
else
wrap
.
appendChild
(
node
);
signalLater
(
widget
,
"redraw"
);
}
return
wrap
;
}
function
positionLineWidget
(
widget
,
node
,
wrap
,
dims
)
{
if
(
widget
.
noHScroll
)
{
(
wrap
.
alignable
||
(
wrap
.
alignable
=
[])).
push
(
node
);
var
width
=
dims
.
wrapperWidth
;
node
.
style
.
left
=
dims
.
fixedPos
+
"px"
;
if
(
!
widget
.
coverGutter
)
{
width
-=
dims
.
gutterTotalWidth
;
node
.
style
.
paddingLeft
=
dims
.
gutterTotalWidth
+
"px"
;
}
node
.
style
.
width
=
width
+
"px"
;
}
if
(
widget
.
coverGutter
)
{
node
.
style
.
zIndex
=
5
;
node
.
style
.
position
=
"relative"
;
if
(
!
widget
.
noHScroll
)
node
.
style
.
marginLeft
=
-
dims
.
gutterTotalWidth
+
"px"
;
}
}
// SELECTION / CURSOR
function
updateSelection
(
cm
)
{
var
display
=
cm
.
display
;
var
collapsed
=
posEq
(
cm
.
doc
.
sel
.
from
,
cm
.
doc
.
sel
.
to
);
if
(
collapsed
||
cm
.
options
.
showCursorWhenSelecting
)
updateSelectionCursor
(
cm
);
else
display
.
cursor
.
style
.
display
=
display
.
otherCursor
.
style
.
display
=
"none"
;
if
(
!
collapsed
)
updateSelectionRange
(
cm
);
else
display
.
selectionDiv
.
style
.
display
=
"none"
;
// Move the hidden textarea near the cursor to prevent scrolling artifacts
if
(
cm
.
options
.
moveInputWithCursor
)
{
var
headPos
=
cursorCoords
(
cm
,
cm
.
doc
.
sel
.
head
,
"div"
);
var
wrapOff
=
getRect
(
display
.
wrapper
),
lineOff
=
getRect
(
display
.
lineDiv
);
display
.
inputDiv
.
style
.
top
=
Math
.
max
(
0
,
Math
.
min
(
display
.
wrapper
.
clientHeight
-
10
,
headPos
.
top
+
lineOff
.
top
-
wrapOff
.
top
))
+
"px"
;
display
.
inputDiv
.
style
.
left
=
Math
.
max
(
0
,
Math
.
min
(
display
.
wrapper
.
clientWidth
-
10
,
headPos
.
left
+
lineOff
.
left
-
wrapOff
.
left
))
+
"px"
;
}
}
// No selection, plain cursor
function
updateSelectionCursor
(
cm
)
{
var
display
=
cm
.
display
,
pos
=
cursorCoords
(
cm
,
cm
.
doc
.
sel
.
head
,
"div"
);
display
.
cursor
.
style
.
left
=
pos
.
left
+
"px"
;
display
.
cursor
.
style
.
top
=
pos
.
top
+
"px"
;
display
.
cursor
.
style
.
height
=
Math
.
max
(
0
,
pos
.
bottom
-
pos
.
top
)
*
cm
.
options
.
cursorHeight
+
"px"
;
display
.
cursor
.
style
.
display
=
""
;
if
(
pos
.
other
)
{
display
.
otherCursor
.
style
.
display
=
""
;
display
.
otherCursor
.
style
.
left
=
pos
.
other
.
left
+
"px"
;
display
.
otherCursor
.
style
.
top
=
pos
.
other
.
top
+
"px"
;
display
.
otherCursor
.
style
.
height
=
(
pos
.
other
.
bottom
-
pos
.
other
.
top
)
*
.85
+
"px"
;
}
else
{
display
.
otherCursor
.
style
.
display
=
"none"
;
}
}
// Highlight selection
function
updateSelectionRange
(
cm
)
{
var
display
=
cm
.
display
,
doc
=
cm
.
doc
,
sel
=
cm
.
doc
.
sel
;
var
fragment
=
document
.
createDocumentFragment
();
var
clientWidth
=
display
.
lineSpace
.
offsetWidth
,
pl
=
paddingLeft
(
cm
.
display
);
function
add
(
left
,
top
,
width
,
bottom
)
{
if
(
top
<
0
)
top
=
0
;
fragment
.
appendChild
(
elt
(
"div"
,
null
,
"CodeMirror-selected"
,
"position: absolute; left: "
+
left
+
"px; top: "
+
top
+
"px; width: "
+
(
width
==
null
?
clientWidth
-
left
:
width
)
+
"px; height: "
+
(
bottom
-
top
)
+
"px"
));
}
function
drawForLine
(
line
,
fromArg
,
toArg
)
{
var
lineObj
=
getLine
(
doc
,
line
);
var
lineLen
=
lineObj
.
text
.
length
;
var
start
,
end
;
function
coords
(
ch
,
bias
)
{
return
charCoords
(
cm
,
Pos
(
line
,
ch
),
"div"
,
lineObj
,
bias
);
}
iterateBidiSections
(
getOrder
(
lineObj
),
fromArg
||
0
,
toArg
==
null
?
lineLen
:
toArg
,
function
(
from
,
to
,
dir
)
{
var
leftPos
=
coords
(
from
,
"left"
),
rightPos
,
left
,
right
;
if
(
from
==
to
)
{
rightPos
=
leftPos
;
left
=
right
=
leftPos
.
left
;
}
else
{
rightPos
=
coords
(
to
-
1
,
"right"
);
if
(
dir
==
"rtl"
)
{
var
tmp
=
leftPos
;
leftPos
=
rightPos
;
rightPos
=
tmp
;
}
left
=
leftPos
.
left
;
right
=
rightPos
.
right
;
}
if
(
fromArg
==
null
&&
from
==
0
)
left
=
pl
;
if
(
rightPos
.
top
-
leftPos
.
top
>
3
)
{
// Different lines, draw top part
add
(
left
,
leftPos
.
top
,
null
,
leftPos
.
bottom
);
left
=
pl
;
if
(
leftPos
.
bottom
<
rightPos
.
top
)
add
(
left
,
leftPos
.
bottom
,
null
,
rightPos
.
top
);
}
if
(
toArg
==
null
&&
to
==
lineLen
)
right
=
clientWidth
;
if
(
!
start
||
leftPos
.
top
<
start
.
top
||
leftPos
.
top
==
start
.
top
&&
leftPos
.
left
<
start
.
left
)
start
=
leftPos
;
if
(
!
end
||
rightPos
.
bottom
>
end
.
bottom
||
rightPos
.
bottom
==
end
.
bottom
&&
rightPos
.
right
>
end
.
right
)
end
=
rightPos
;
if
(
left
<
pl
+
1
)
left
=
pl
;
add
(
left
,
rightPos
.
top
,
right
-
left
,
rightPos
.
bottom
);
});
return
{
start
:
start
,
end
:
end
};
}
if
(
sel
.
from
.
line
==
sel
.
to
.
line
)
{
drawForLine
(
sel
.
from
.
line
,
sel
.
from
.
ch
,
sel
.
to
.
ch
);
}
else
{
var
fromLine
=
getLine
(
doc
,
sel
.
from
.
line
),
toLine
=
getLine
(
doc
,
sel
.
to
.
line
);
var
singleVLine
=
visualLine
(
doc
,
fromLine
)
==
visualLine
(
doc
,
toLine
);
var
leftEnd
=
drawForLine
(
sel
.
from
.
line
,
sel
.
from
.
ch
,
singleVLine
?
fromLine
.
text
.
length
:
null
).
end
;
var
rightStart
=
drawForLine
(
sel
.
to
.
line
,
singleVLine
?
0
:
null
,
sel
.
to
.
ch
).
start
;
if
(
singleVLine
)
{
if
(
leftEnd
.
top
<
rightStart
.
top
-
2
)
{
add
(
leftEnd
.
right
,
leftEnd
.
top
,
null
,
leftEnd
.
bottom
);
add
(
pl
,
rightStart
.
top
,
rightStart
.
left
,
rightStart
.
bottom
);
}
else
{
add
(
leftEnd
.
right
,
leftEnd
.
top
,
rightStart
.
left
-
leftEnd
.
right
,
leftEnd
.
bottom
);
}
}
if
(
leftEnd
.
bottom
<
rightStart
.
top
)
add
(
pl
,
leftEnd
.
bottom
,
null
,
rightStart
.
top
);
}
removeChildrenAndAdd
(
display
.
selectionDiv
,
fragment
);
display
.
selectionDiv
.
style
.
display
=
""
;
}
// Cursor-blinking
function
restartBlink
(
cm
)
{
if
(
!
cm
.
state
.
focused
)
return
;
var
display
=
cm
.
display
;
clearInterval
(
display
.
blinker
);
var
on
=
true
;
display
.
cursor
.
style
.
visibility
=
display
.
otherCursor
.
style
.
visibility
=
""
;
display
.
blinker
=
setInterval
(
function
()
{
display
.
cursor
.
style
.
visibility
=
display
.
otherCursor
.
style
.
visibility
=
(
on
=
!
on
)
?
""
:
"hidden"
;
},
cm
.
options
.
cursorBlinkRate
);
}
// HIGHLIGHT WORKER
function
startWorker
(
cm
,
time
)
{
if
(
cm
.
doc
.
mode
.
startState
&&
cm
.
doc
.
frontier
<
cm
.
display
.
showingTo
)
cm
.
state
.
highlight
.
set
(
time
,
bind
(
highlightWorker
,
cm
));
}
function
highlightWorker
(
cm
)
{
var
doc
=
cm
.
doc
;
if
(
doc
.
frontier
<
doc
.
first
)
doc
.
frontier
=
doc
.
first
;
if
(
doc
.
frontier
>=
cm
.
display
.
showingTo
)
return
;
var
end
=
+
new
Date
+
cm
.
options
.
workTime
;
var
state
=
copyState
(
doc
.
mode
,
getStateBefore
(
cm
,
doc
.
frontier
));
var
changed
=
[],
prevChange
;
doc
.
iter
(
doc
.
frontier
,
Math
.
min
(
doc
.
first
+
doc
.
size
,
cm
.
display
.
showingTo
+
500
),
function
(
line
)
{
if
(
doc
.
frontier
>=
cm
.
display
.
showingFrom
)
{
// Visible
var
oldStyles
=
line
.
styles
;
line
.
styles
=
highlightLine
(
cm
,
line
,
state
);
var
ischange
=
!
oldStyles
||
oldStyles
.
length
!=
line
.
styles
.
length
;
for
(
var
i
=
0
;
!
ischange
&&
i
<
oldStyles
.
length
;
++
i
)
ischange
=
oldStyles
[
i
]
!=
line
.
styles
[
i
];
if
(
ischange
)
{
if
(
prevChange
&&
prevChange
.
end
==
doc
.
frontier
)
prevChange
.
end
++
;
else
changed
.
push
(
prevChange
=
{
start
:
doc
.
frontier
,
end
:
doc
.
frontier
+
1
});
}
line
.
stateAfter
=
copyState
(
doc
.
mode
,
state
);
}
else
{
processLine
(
cm
,
line
,
state
);
line
.
stateAfter
=
doc
.
frontier
%
5
==
0
?
copyState
(
doc
.
mode
,
state
)
:
null
;
}
++
doc
.
frontier
;
if
(
+
new
Date
>
end
)
{
startWorker
(
cm
,
cm
.
options
.
workDelay
);
return
true
;
}
});
if
(
changed
.
length
)
operation
(
cm
,
function
()
{
for
(
var
i
=
0
;
i
<
changed
.
length
;
++
i
)
regChange
(
this
,
changed
[
i
].
start
,
changed
[
i
].
end
);
})();
}
// Finds the line to start with when starting a parse. Tries to
// find a line with a stateAfter, so that it can start with a
// valid state. If that fails, it returns the line with the
// smallest indentation, which tends to need the least context to
// parse correctly.
function
findStartLine
(
cm
,
n
,
precise
)
{
var
minindent
,
minline
,
doc
=
cm
.
doc
;
for
(
var
search
=
n
,
lim
=
n
-
100
;
search
>
lim
;
--
search
)
{
if
(
search
<=
doc
.
first
)
return
doc
.
first
;
var
line
=
getLine
(
doc
,
search
-
1
);
if
(
line
.
stateAfter
&&
(
!
precise
||
search
<=
doc
.
frontier
))
return
search
;
var
indented
=
countColumn
(
line
.
text
,
null
,
cm
.
options
.
tabSize
);
if
(
minline
==
null
||
minindent
>
indented
)
{
minline
=
search
-
1
;
minindent
=
indented
;
}
}
return
minline
;
}
function
getStateBefore
(
cm
,
n
,
precise
)
{
var
doc
=
cm
.
doc
,
display
=
cm
.
display
;
if
(
!
doc
.
mode
.
startState
)
return
true
;
var
pos
=
findStartLine
(
cm
,
n
,
precise
),
state
=
pos
>
doc
.
first
&&
getLine
(
doc
,
pos
-
1
).
stateAfter
;
if
(
!
state
)
state
=
startState
(
doc
.
mode
);
else
state
=
copyState
(
doc
.
mode
,
state
);
doc
.
iter
(
pos
,
n
,
function
(
line
)
{
processLine
(
cm
,
line
,
state
);
var
save
=
pos
==
n
-
1
||
pos
%
5
==
0
||
pos
>=
display
.
showingFrom
&&
pos
<
display
.
showingTo
;
line
.
stateAfter
=
save
?
copyState
(
doc
.
mode
,
state
)
:
null
;
++
pos
;
});
return
state
;
}
// POSITION MEASUREMENT
function
paddingTop
(
display
)
{
return
display
.
lineSpace
.
offsetTop
;}
function
paddingVert
(
display
)
{
return
display
.
mover
.
offsetHeight
-
display
.
lineSpace
.
offsetHeight
;}
function
paddingLeft
(
display
)
{
var
e
=
removeChildrenAndAdd
(
display
.
measure
,
elt
(
"pre"
,
null
,
null
,
"text-align: left"
)).
appendChild
(
elt
(
"span"
,
"x"
));
return
e
.
offsetLeft
;
}
function
measureChar
(
cm
,
line
,
ch
,
data
,
bias
)
{
var
dir
=
-
1
;
data
=
data
||
measureLine
(
cm
,
line
);
for
(
var
pos
=
ch
;;
pos
+=
dir
)
{
var
r
=
data
[
pos
];
if
(
r
)
break
;
if
(
dir
<
0
&&
pos
==
0
)
dir
=
1
;
}
var
rightV
=
(
pos
<
ch
||
bias
==
"right"
)
&&
r
.
topRight
!=
null
;
return
{
left
:
pos
<
ch
?
r
.
right
:
r
.
left
,
right
:
pos
>
ch
?
r
.
left
:
r
.
right
,
top
:
rightV
?
r
.
topRight
:
r
.
top
,
bottom
:
rightV
?
r
.
bottomRight
:
r
.
bottom
};
}
function
findCachedMeasurement
(
cm
,
line
)
{
var
cache
=
cm
.
display
.
measureLineCache
;
for
(
var
i
=
0
;
i
<
cache
.
length
;
++
i
)
{
var
memo
=
cache
[
i
];
if
(
memo
.
text
==
line
.
text
&&
memo
.
markedSpans
==
line
.
markedSpans
&&
cm
.
display
.
scroller
.
clientWidth
==
memo
.
width
&&
memo
.
classes
==
line
.
textClass
+
"|"
+
line
.
bgClass
+
"|"
+
line
.
wrapClass
)
return
memo
;
}
}
function
clearCachedMeasurement
(
cm
,
line
)
{
var
exists
=
findCachedMeasurement
(
cm
,
line
);
if
(
exists
)
exists
.
text
=
exists
.
measure
=
exists
.
markedSpans
=
null
;
}
function
measureLine
(
cm
,
line
)
{
// First look in the cache
var
cached
=
findCachedMeasurement
(
cm
,
line
);
if
(
cached
)
return
cached
.
measure
;
// Failing that, recompute and store result in cache
var
measure
=
measureLineInner
(
cm
,
line
);
var
cache
=
cm
.
display
.
measureLineCache
;
var
memo
=
{
text
:
line
.
text
,
width
:
cm
.
display
.
scroller
.
clientWidth
,
markedSpans
:
line
.
markedSpans
,
measure
:
measure
,
classes
:
line
.
textClass
+
"|"
+
line
.
bgClass
+
"|"
+
line
.
wrapClass
};
if
(
cache
.
length
==
16
)
cache
[
++
cm
.
display
.
measureLineCachePos
%
16
]
=
memo
;
else
cache
.
push
(
memo
);
return
measure
;
}
function
measureLineInner
(
cm
,
line
)
{
var
display
=
cm
.
display
,
measure
=
emptyArray
(
line
.
text
.
length
);
var
pre
=
lineContent
(
cm
,
line
,
measure
);
// IE does not cache element positions of inline elements between
// calls to getBoundingClientRect. This makes the loop below,
// which gathers the positions of all the characters on the line,
// do an amount of layout work quadratic to the number of
// characters. When line wrapping is off, we try to improve things
// by first subdividing the line into a bunch of inline blocks, so
// that IE can reuse most of the layout information from caches
// for those blocks. This does interfere with line wrapping, so it
// doesn't work when wrapping is on, but in that case the
// situation is slightly better, since IE does cache line-wrapping
// information and only recomputes per-line.
if
(
ie
&&
!
ie_lt8
&&
!
cm
.
options
.
lineWrapping
&&
pre
.
childNodes
.
length
>
100
)
{
var
fragment
=
document
.
createDocumentFragment
();
var
chunk
=
10
,
n
=
pre
.
childNodes
.
length
;
for
(
var
i
=
0
,
chunks
=
Math
.
ceil
(
n
/
chunk
);
i
<
chunks
;
++
i
)
{
var
wrap
=
elt
(
"div"
,
null
,
null
,
"display: inline-block"
);
for
(
var
j
=
0
;
j
<
chunk
&&
n
;
++
j
)
{
wrap
.
appendChild
(
pre
.
firstChild
);
--
n
;
}
fragment
.
appendChild
(
wrap
);
}
pre
.
appendChild
(
fragment
);
}
removeChildrenAndAdd
(
display
.
measure
,
pre
);
var
outer
=
getRect
(
display
.
lineDiv
);
var
vranges
=
[],
data
=
emptyArray
(
line
.
text
.
length
),
maxBot
=
pre
.
offsetHeight
;
// Work around an IE7/8 bug where it will sometimes have randomly
// replaced our pre with a clone at this point.
if
(
ie_lt9
&&
display
.
measure
.
first
!=
pre
)
removeChildrenAndAdd
(
display
.
measure
,
pre
);
function
categorizeVSpan
(
top
,
bot
)
{
if
(
bot
>
maxBot
)
bot
=
maxBot
;
if
(
top
<
0
)
top
=
0
;
for
(
var
j
=
0
;
j
<
vranges
.
length
;
j
+=
2
)
{
var
rtop
=
vranges
[
j
],
rbot
=
vranges
[
j
+
1
];
if
(
rtop
>
bot
||
rbot
<
top
)
continue
;
if
(
rtop
<=
top
&&
rbot
>=
bot
||
top
<=
rtop
&&
bot
>=
rbot
||
Math
.
min
(
bot
,
rbot
)
-
Math
.
max
(
top
,
rtop
)
>=
(
bot
-
top
)
>>
1
)
{
vranges
[
j
]
=
Math
.
min
(
top
,
rtop
);
vranges
[
j
+
1
]
=
Math
.
max
(
bot
,
rbot
);
return
j
;
}
}
vranges
.
push
(
top
,
bot
);
return
j
;
}
for
(
var
i
=
0
,
cur
;
i
<
measure
.
length
;
++
i
)
if
(
cur
=
measure
[
i
])
{
var
size
,
node
=
cur
;
// A widget might wrap, needs special care
if
(
/\bCodeMirror-widget\b/
.
test
(
cur
.
className
)
&&
cur
.
getClientRects
)
{
if
(
cur
.
firstChild
.
nodeType
==
1
)
node
=
cur
.
firstChild
;
var
rects
=
node
.
getClientRects
(),
rLeft
=
rects
[
0
],
rRight
=
rects
[
rects
.
length
-
1
];
if
(
rects
.
length
>
1
)
{
var
vCatLeft
=
categorizeVSpan
(
rLeft
.
top
-
outer
.
top
,
rLeft
.
bottom
-
outer
.
top
);
var
vCatRight
=
categorizeVSpan
(
rRight
.
top
-
outer
.
top
,
rRight
.
bottom
-
outer
.
top
);
data
[
i
]
=
{
left
:
rLeft
.
left
-
outer
.
left
,
right
:
rRight
.
right
-
outer
.
left
,
top
:
vCatLeft
,
topRight
:
vCatRight
};
continue
;
}
}
size
=
getRect
(
node
);
var
vCat
=
categorizeVSpan
(
size
.
top
-
outer
.
top
,
size
.
bottom
-
outer
.
top
);
var
right
=
size
.
right
;
if
(
cur
.
measureRight
)
right
=
getRect
(
cur
.
measureRight
).
left
;
data
[
i
]
=
{
left
:
size
.
left
-
outer
.
left
,
right
:
right
-
outer
.
left
,
top
:
vCat
};
}
for
(
var
i
=
0
,
cur
;
i
<
data
.
length
;
++
i
)
if
(
cur
=
data
[
i
])
{
var
vr
=
cur
.
top
,
vrRight
=
cur
.
topRight
;
cur
.
top
=
vranges
[
vr
];
cur
.
bottom
=
vranges
[
vr
+
1
];
if
(
vrRight
!=
null
)
{
cur
.
topRight
=
vranges
[
vrRight
];
cur
.
bottomRight
=
vranges
[
vrRight
+
1
];
}
}
return
data
;
}
function
measureLineWidth
(
cm
,
line
)
{
var
hasBadSpan
=
false
;
if
(
line
.
markedSpans
)
for
(
var
i
=
0
;
i
<
line
.
markedSpans
;
++
i
)
{
var
sp
=
line
.
markedSpans
[
i
];
if
(
sp
.
collapsed
&&
(
sp
.
to
==
null
||
sp
.
to
==
line
.
text
.
length
))
hasBadSpan
=
true
;
}
var
cached
=
!
hasBadSpan
&&
findCachedMeasurement
(
cm
,
line
);
if
(
cached
)
return
measureChar
(
cm
,
line
,
line
.
text
.
length
,
cached
.
measure
,
"right"
).
right
;
var
pre
=
lineContent
(
cm
,
line
);
var
end
=
pre
.
appendChild
(
zeroWidthElement
(
cm
.
display
.
measure
));
removeChildrenAndAdd
(
cm
.
display
.
measure
,
pre
);
return
getRect
(
end
).
right
-
getRect
(
cm
.
display
.
lineDiv
).
left
;
}
function
clearCaches
(
cm
)
{
cm
.
display
.
measureLineCache
.
length
=
cm
.
display
.
measureLineCachePos
=
0
;
cm
.
display
.
cachedCharWidth
=
cm
.
display
.
cachedTextHeight
=
null
;
if
(
!
cm
.
options
.
lineWrapping
)
cm
.
display
.
maxLineChanged
=
true
;
cm
.
display
.
lineNumChars
=
null
;
}
function
pageScrollX
()
{
return
window
.
pageXOffset
||
(
document
.
documentElement
||
document
.
body
).
scrollLeft
;
}
function
pageScrollY
()
{
return
window
.
pageYOffset
||
(
document
.
documentElement
||
document
.
body
).
scrollTop
;
}
// Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
function
intoCoordSystem
(
cm
,
lineObj
,
rect
,
context
)
{
if
(
lineObj
.
widgets
)
for
(
var
i
=
0
;
i
<
lineObj
.
widgets
.
length
;
++
i
)
if
(
lineObj
.
widgets
[
i
].
above
)
{
var
size
=
widgetHeight
(
lineObj
.
widgets
[
i
]);
rect
.
top
+=
size
;
rect
.
bottom
+=
size
;
}
if
(
context
==
"line"
)
return
rect
;
if
(
!
context
)
context
=
"local"
;
var
yOff
=
heightAtLine
(
cm
,
lineObj
);
if
(
context
==
"local"
)
yOff
+=
paddingTop
(
cm
.
display
);
else
yOff
-=
cm
.
display
.
viewOffset
;
if
(
context
==
"page"
||
context
==
"window"
)
{
var
lOff
=
getRect
(
cm
.
display
.
lineSpace
);
yOff
+=
lOff
.
top
+
(
context
==
"window"
?
0
:
pageScrollY
());
var
xOff
=
lOff
.
left
+
(
context
==
"window"
?
0
:
pageScrollX
());
rect
.
left
+=
xOff
;
rect
.
right
+=
xOff
;
}
rect
.
top
+=
yOff
;
rect
.
bottom
+=
yOff
;
return
rect
;
}
// Context may be "window", "page", "div", or "local"/null
// Result is in "div" coords
function
fromCoordSystem
(
cm
,
coords
,
context
)
{
if
(
context
==
"div"
)
return
coords
;
var
left
=
coords
.
left
,
top
=
coords
.
top
;
// First move into "page" coordinate system
if
(
context
==
"page"
)
{
left
-=
pageScrollX
();
top
-=
pageScrollY
();
}
else
if
(
context
==
"local"
||
!
context
)
{
var
localBox
=
getRect
(
cm
.
display
.
sizer
);
left
+=
localBox
.
left
;
top
+=
localBox
.
top
;
}
var
lineSpaceBox
=
getRect
(
cm
.
display
.
lineSpace
);
return
{
left
:
left
-
lineSpaceBox
.
left
,
top
:
top
-
lineSpaceBox
.
top
};
}
function
charCoords
(
cm
,
pos
,
context
,
lineObj
,
bias
)
{
if
(
!
lineObj
)
lineObj
=
getLine
(
cm
.
doc
,
pos
.
line
);
return
intoCoordSystem
(
cm
,
lineObj
,
measureChar
(
cm
,
lineObj
,
pos
.
ch
,
null
,
bias
),
context
);
}
function
cursorCoords
(
cm
,
pos
,
context
,
lineObj
,
measurement
)
{
lineObj
=
lineObj
||
getLine
(
cm
.
doc
,
pos
.
line
);
if
(
!
measurement
)
measurement
=
measureLine
(
cm
,
lineObj
);
function
get
(
ch
,
right
)
{
var
m
=
measureChar
(
cm
,
lineObj
,
ch
,
measurement
,
right
?
"right"
:
"left"
);
if
(
right
)
m
.
left
=
m
.
right
;
else
m
.
right
=
m
.
left
;
return
intoCoordSystem
(
cm
,
lineObj
,
m
,
context
);
}
function
getBidi
(
ch
,
partPos
)
{
var
part
=
order
[
partPos
],
right
=
part
.
level
%
2
;
if
(
ch
==
bidiLeft
(
part
)
&&
partPos
&&
part
.
level
<
order
[
partPos
-
1
].
level
)
{
part
=
order
[
--
partPos
];
ch
=
bidiRight
(
part
)
-
(
part
.
level
%
2
?
0
:
1
);
right
=
true
;
}
else
if
(
ch
==
bidiRight
(
part
)
&&
partPos
<
order
.
length
-
1
&&
part
.
level
<
order
[
partPos
+
1
].
level
)
{
part
=
order
[
++
partPos
];
ch
=
bidiLeft
(
part
)
-
part
.
level
%
2
;
right
=
false
;
}
if
(
right
&&
ch
==
part
.
to
&&
ch
>
part
.
from
)
return
get
(
ch
-
1
);
return
get
(
ch
,
right
);
}
var
order
=
getOrder
(
lineObj
),
ch
=
pos
.
ch
;
if
(
!
order
)
return
get
(
ch
);
var
partPos
=
getBidiPartAt
(
order
,
ch
);
var
val
=
getBidi
(
ch
,
partPos
);
if
(
bidiOther
!=
null
)
val
.
other
=
getBidi
(
ch
,
bidiOther
);
return
val
;
}
function
PosWithInfo
(
line
,
ch
,
outside
,
xRel
)
{
var
pos
=
new
Pos
(
line
,
ch
);
pos
.
xRel
=
xRel
;
if
(
outside
)
pos
.
outside
=
true
;
return
pos
;
}
// Coords must be lineSpace-local
function
coordsChar
(
cm
,
x
,
y
)
{
var
doc
=
cm
.
doc
;
y
+=
cm
.
display
.
viewOffset
;
if
(
y
<
0
)
return
PosWithInfo
(
doc
.
first
,
0
,
true
,
-
1
);
var
lineNo
=
lineAtHeight
(
doc
,
y
),
last
=
doc
.
first
+
doc
.
size
-
1
;
if
(
lineNo
>
last
)
return
PosWithInfo
(
doc
.
first
+
doc
.
size
-
1
,
getLine
(
doc
,
last
).
text
.
length
,
true
,
1
);
if
(
x
<
0
)
x
=
0
;
for
(;;)
{
var
lineObj
=
getLine
(
doc
,
lineNo
);
var
found
=
coordsCharInner
(
cm
,
lineObj
,
lineNo
,
x
,
y
);
var
merged
=
collapsedSpanAtEnd
(
lineObj
);
var
mergedPos
=
merged
&&
merged
.
find
();
if
(
merged
&&
(
found
.
ch
>
mergedPos
.
from
.
ch
||
found
.
ch
==
mergedPos
.
from
.
ch
&&
found
.
xRel
>
0
))
lineNo
=
mergedPos
.
to
.
line
;
else
return
found
;
}
}
function
coordsCharInner
(
cm
,
lineObj
,
lineNo
,
x
,
y
)
{
var
innerOff
=
y
-
heightAtLine
(
cm
,
lineObj
);
var
wrongLine
=
false
,
adjust
=
2
*
cm
.
display
.
wrapper
.
clientWidth
;
var
measurement
=
measureLine
(
cm
,
lineObj
);
function
getX
(
ch
)
{
var
sp
=
cursorCoords
(
cm
,
Pos
(
lineNo
,
ch
),
"line"
,
lineObj
,
measurement
);
wrongLine
=
true
;
if
(
innerOff
>
sp
.
bottom
)
return
sp
.
left
-
adjust
;
else
if
(
innerOff
<
sp
.
top
)
return
sp
.
left
+
adjust
;
else
wrongLine
=
false
;
return
sp
.
left
;
}
var
bidi
=
getOrder
(
lineObj
),
dist
=
lineObj
.
text
.
length
;
var
from
=
lineLeft
(
lineObj
),
to
=
lineRight
(
lineObj
);
var
fromX
=
getX
(
from
),
fromOutside
=
wrongLine
,
toX
=
getX
(
to
),
toOutside
=
wrongLine
;
if
(
x
>
toX
)
return
PosWithInfo
(
lineNo
,
to
,
toOutside
,
1
);
// Do a binary search between these bounds.
for
(;;)
{
if
(
bidi
?
to
==
from
||
to
==
moveVisually
(
lineObj
,
from
,
1
)
:
to
-
from
<=
1
)
{
var
ch
=
x
<
fromX
||
x
-
fromX
<=
toX
-
x
?
from
:
to
;
var
xDiff
=
x
-
(
ch
==
from
?
fromX
:
toX
);
while
(
isExtendingChar
.
test
(
lineObj
.
text
.
charAt
(
ch
)))
++
ch
;
var
pos
=
PosWithInfo
(
lineNo
,
ch
,
ch
==
from
?
fromOutside
:
toOutside
,
xDiff
<
0
?
-
1
:
xDiff
?
1
:
0
);
return
pos
;
}
var
step
=
Math
.
ceil
(
dist
/
2
),
middle
=
from
+
step
;
if
(
bidi
)
{
middle
=
from
;
for
(
var
i
=
0
;
i
<
step
;
++
i
)
middle
=
moveVisually
(
lineObj
,
middle
,
1
);
}
var
middleX
=
getX
(
middle
);
if
(
middleX
>
x
)
{
to
=
middle
;
toX
=
middleX
;
if
(
toOutside
=
wrongLine
)
toX
+=
1000
;
dist
=
step
;}
else
{
from
=
middle
;
fromX
=
middleX
;
fromOutside
=
wrongLine
;
dist
-=
step
;}
}
}
var
measureText
;
function
textHeight
(
display
)
{
if
(
display
.
cachedTextHeight
!=
null
)
return
display
.
cachedTextHeight
;
if
(
measureText
==
null
)
{
measureText
=
elt
(
"pre"
);
// Measure a bunch of lines, for browsers that compute
// fractional heights.
for
(
var
i
=
0
;
i
<
49
;
++
i
)
{
measureText
.
appendChild
(
document
.
createTextNode
(
"x"
));
measureText
.
appendChild
(
elt
(
"br"
));
}
measureText
.
appendChild
(
document
.
createTextNode
(
"x"
));
}
removeChildrenAndAdd
(
display
.
measure
,
measureText
);
var
height
=
measureText
.
offsetHeight
/
50
;
if
(
height
>
3
)
display
.
cachedTextHeight
=
height
;
removeChildren
(
display
.
measure
);
return
height
||
1
;
}
function
charWidth
(
display
)
{
if
(
display
.
cachedCharWidth
!=
null
)
return
display
.
cachedCharWidth
;
var
anchor
=
elt
(
"span"
,
"x"
);
var
pre
=
elt
(
"pre"
,
[
anchor
]);
removeChildrenAndAdd
(
display
.
measure
,
pre
);
var
width
=
anchor
.
offsetWidth
;
if
(
width
>
2
)
display
.
cachedCharWidth
=
width
;
return
width
||
10
;
}
// OPERATIONS
// Operations are used to wrap changes in such a way that each
// change won't have to update the cursor and display (which would
// be awkward, slow, and error-prone), but instead updates are
// batched and then all combined and executed at once.
var
nextOpId
=
0
;
function
startOperation
(
cm
)
{
cm
.
curOp
=
{
// An array of ranges of lines that have to be updated. See
// updateDisplay.
changes
:
[],
updateInput
:
null
,
userSelChange
:
null
,
textChanged
:
null
,
selectionChanged
:
false
,
cursorActivity
:
false
,
updateMaxLine
:
false
,
updateScrollPos
:
false
,
id
:
++
nextOpId
};
if
(
!
delayedCallbackDepth
++
)
delayedCallbacks
=
[];
}
function
endOperation
(
cm
)
{
var
op
=
cm
.
curOp
,
doc
=
cm
.
doc
,
display
=
cm
.
display
;
cm
.
curOp
=
null
;
if
(
op
.
updateMaxLine
)
computeMaxLength
(
cm
);
if
(
display
.
maxLineChanged
&&
!
cm
.
options
.
lineWrapping
&&
display
.
maxLine
)
{
var
width
=
measureLineWidth
(
cm
,
display
.
maxLine
);
display
.
sizer
.
style
.
minWidth
=
Math
.
max
(
0
,
width
+
3
+
scrollerCutOff
)
+
"px"
;
display
.
maxLineChanged
=
false
;
var
maxScrollLeft
=
Math
.
max
(
0
,
display
.
sizer
.
offsetLeft
+
display
.
sizer
.
offsetWidth
-
display
.
scroller
.
clientWidth
);
if
(
maxScrollLeft
<
doc
.
scrollLeft
&&
!
op
.
updateScrollPos
)
setScrollLeft
(
cm
,
Math
.
min
(
display
.
scroller
.
scrollLeft
,
maxScrollLeft
),
true
);
}
var
newScrollPos
,
updated
;
if
(
op
.
updateScrollPos
)
{
newScrollPos
=
op
.
updateScrollPos
;
}
else
if
(
op
.
selectionChanged
&&
display
.
scroller
.
clientHeight
)
{
// don't rescroll if not visible
var
coords
=
cursorCoords
(
cm
,
doc
.
sel
.
head
);
newScrollPos
=
calculateScrollPos
(
cm
,
coords
.
left
,
coords
.
top
,
coords
.
left
,
coords
.
bottom
);
}
if
(
op
.
changes
.
length
||
newScrollPos
&&
newScrollPos
.
scrollTop
!=
null
)
{
updated
=
updateDisplay
(
cm
,
op
.
changes
,
newScrollPos
&&
newScrollPos
.
scrollTop
);
if
(
cm
.
display
.
scroller
.
offsetHeight
)
cm
.
doc
.
scrollTop
=
cm
.
display
.
scroller
.
scrollTop
;
}
if
(
!
updated
&&
op
.
selectionChanged
)
updateSelection
(
cm
);
if
(
op
.
updateScrollPos
)
{
display
.
scroller
.
scrollTop
=
display
.
scrollbarV
.
scrollTop
=
doc
.
scrollTop
=
newScrollPos
.
scrollTop
;
display
.
scroller
.
scrollLeft
=
display
.
scrollbarH
.
scrollLeft
=
doc
.
scrollLeft
=
newScrollPos
.
scrollLeft
;
alignHorizontally
(
cm
);
if
(
op
.
scrollToPos
)
scrollPosIntoView
(
cm
,
clipPos
(
cm
.
doc
,
op
.
scrollToPos
),
op
.
scrollToPosMargin
);
}
else
if
(
newScrollPos
)
{
scrollCursorIntoView
(
cm
);
}
if
(
op
.
selectionChanged
)
restartBlink
(
cm
);
if
(
cm
.
state
.
focused
&&
op
.
updateInput
)
resetInput
(
cm
,
op
.
userSelChange
);
var
hidden
=
op
.
maybeHiddenMarkers
,
unhidden
=
op
.
maybeUnhiddenMarkers
;
if
(
hidden
)
for
(
var
i
=
0
;
i
<
hidden
.
length
;
++
i
)
if
(
!
hidden
[
i
].
lines
.
length
)
signal
(
hidden
[
i
],
"hide"
);
if
(
unhidden
)
for
(
var
i
=
0
;
i
<
unhidden
.
length
;
++
i
)
if
(
unhidden
[
i
].
lines
.
length
)
signal
(
unhidden
[
i
],
"unhide"
);
var
delayed
;
if
(
!--
delayedCallbackDepth
)
{
delayed
=
delayedCallbacks
;
delayedCallbacks
=
null
;
}
if
(
op
.
textChanged
)
signal
(
cm
,
"change"
,
cm
,
op
.
textChanged
);
if
(
op
.
cursorActivity
)
signal
(
cm
,
"cursorActivity"
,
cm
);
if
(
delayed
)
for
(
var
i
=
0
;
i
<
delayed
.
length
;
++
i
)
delayed
[
i
]();
}
// Wraps a function in an operation. Returns the wrapped function.
function
operation
(
cm1
,
f
)
{
return
function
()
{
var
cm
=
cm1
||
this
,
withOp
=
!
cm
.
curOp
;
if
(
withOp
)
startOperation
(
cm
);
try
{
var
result
=
f
.
apply
(
cm
,
arguments
);
}
finally
{
if
(
withOp
)
endOperation
(
cm
);
}
return
result
;
};
}
function
docOperation
(
f
)
{
return
function
()
{
var
withOp
=
this
.
cm
&&
!
this
.
cm
.
curOp
,
result
;
if
(
withOp
)
startOperation
(
this
.
cm
);
try
{
result
=
f
.
apply
(
this
,
arguments
);
}
finally
{
if
(
withOp
)
endOperation
(
this
.
cm
);
}
return
result
;
};
}
function
runInOp
(
cm
,
f
)
{
var
withOp
=
!
cm
.
curOp
,
result
;
if
(
withOp
)
startOperation
(
cm
);
try
{
result
=
f
();
}
finally
{
if
(
withOp
)
endOperation
(
cm
);
}
return
result
;
}
function
regChange
(
cm
,
from
,
to
,
lendiff
)
{
if
(
from
==
null
)
from
=
cm
.
doc
.
first
;
if
(
to
==
null
)
to
=
cm
.
doc
.
first
+
cm
.
doc
.
size
;
cm
.
curOp
.
changes
.
push
({
from
:
from
,
to
:
to
,
diff
:
lendiff
});
}
// INPUT HANDLING
function
slowPoll
(
cm
)
{
if
(
cm
.
display
.
pollingFast
)
return
;
cm
.
display
.
poll
.
set
(
cm
.
options
.
pollInterval
,
function
()
{
readInput
(
cm
);
if
(
cm
.
state
.
focused
)
slowPoll
(
cm
);
});
}
function
fastPoll
(
cm
)
{
var
missed
=
false
;
cm
.
display
.
pollingFast
=
true
;
function
p
()
{
var
changed
=
readInput
(
cm
);
if
(
!
changed
&&
!
missed
)
{
missed
=
true
;
cm
.
display
.
poll
.
set
(
60
,
p
);}
else
{
cm
.
display
.
pollingFast
=
false
;
slowPoll
(
cm
);}
}
cm
.
display
.
poll
.
set
(
20
,
p
);
}
// prevInput is a hack to work with IME. If we reset the textarea
// on every change, that breaks IME. So we look for changes
// compared to the previous content instead. (Modern browsers have
// events that indicate IME taking place, but these are not widely
// supported or compatible enough yet to rely on.)
function
readInput
(
cm
)
{
var
input
=
cm
.
display
.
input
,
prevInput
=
cm
.
display
.
prevInput
,
doc
=
cm
.
doc
,
sel
=
doc
.
sel
;
if
(
!
cm
.
state
.
focused
||
hasSelection
(
input
)
||
isReadOnly
(
cm
)
||
cm
.
state
.
disableInput
)
return
false
;
var
text
=
input
.
value
;
if
(
text
==
prevInput
&&
posEq
(
sel
.
from
,
sel
.
to
))
return
false
;
if
(
ie
&&
!
ie_lt9
&&
cm
.
display
.
inputHasSelection
===
text
)
{
resetInput
(
cm
,
true
);
return
false
;
}
var
withOp
=
!
cm
.
curOp
;
if
(
withOp
)
startOperation
(
cm
);
sel
.
shift
=
false
;
var
same
=
0
,
l
=
Math
.
min
(
prevInput
.
length
,
text
.
length
);
while
(
same
<
l
&&
prevInput
.
charCodeAt
(
same
)
==
text
.
charCodeAt
(
same
))
++
same
;
var
from
=
sel
.
from
,
to
=
sel
.
to
;
if
(
same
<
prevInput
.
length
)
from
=
Pos
(
from
.
line
,
from
.
ch
-
(
prevInput
.
length
-
same
));
else
if
(
cm
.
state
.
overwrite
&&
posEq
(
from
,
to
)
&&
!
cm
.
state
.
pasteIncoming
)
to
=
Pos
(
to
.
line
,
Math
.
min
(
getLine
(
doc
,
to
.
line
).
text
.
length
,
to
.
ch
+
(
text
.
length
-
same
)));
var
updateInput
=
cm
.
curOp
.
updateInput
;
var
changeEvent
=
{
from
:
from
,
to
:
to
,
text
:
splitLines
(
text
.
slice
(
same
)),
origin
:
cm
.
state
.
pasteIncoming
?
"paste"
:
"+input"
};
makeChange
(
cm
.
doc
,
changeEvent
,
"end"
);
cm
.
curOp
.
updateInput
=
updateInput
;
signalLater
(
cm
,
"inputRead"
,
cm
,
changeEvent
);
if
(
text
.
length
>
1000
||
text
.
indexOf
(
"\n"
)
>
-
1
)
input
.
value
=
cm
.
display
.
prevInput
=
""
;
else
cm
.
display
.
prevInput
=
text
;
if
(
withOp
)
endOperation
(
cm
);
cm
.
state
.
pasteIncoming
=
false
;
return
true
;
}
function
resetInput
(
cm
,
user
)
{
var
minimal
,
selected
,
doc
=
cm
.
doc
;
if
(
!
posEq
(
doc
.
sel
.
from
,
doc
.
sel
.
to
))
{
cm
.
display
.
prevInput
=
""
;
minimal
=
hasCopyEvent
&&
(
doc
.
sel
.
to
.
line
-
doc
.
sel
.
from
.
line
>
100
||
(
selected
=
cm
.
getSelection
()).
length
>
1000
);
var
content
=
minimal
?
"-"
:
selected
||
cm
.
getSelection
();
cm
.
display
.
input
.
value
=
content
;
if
(
cm
.
state
.
focused
)
selectInput
(
cm
.
display
.
input
);
if
(
ie
&&
!
ie_lt9
)
cm
.
display
.
inputHasSelection
=
content
;
}
else
if
(
user
)
{
cm
.
display
.
prevInput
=
cm
.
display
.
input
.
value
=
""
;
if
(
ie
&&
!
ie_lt9
)
cm
.
display
.
inputHasSelection
=
null
;
}
cm
.
display
.
inaccurateSelection
=
minimal
;
}
function
focusInput
(
cm
)
{
if
(
cm
.
options
.
readOnly
!=
"nocursor"
&&
(
!
mobile
||
document
.
activeElement
!=
cm
.
display
.
input
))
cm
.
display
.
input
.
focus
();
}
function
isReadOnly
(
cm
)
{
return
cm
.
options
.
readOnly
||
cm
.
doc
.
cantEdit
;
}
// EVENT HANDLERS
function
registerEventHandlers
(
cm
)
{
var
d
=
cm
.
display
;
on
(
d
.
scroller
,
"mousedown"
,
operation
(
cm
,
onMouseDown
));
if
(
ie
)
on
(
d
.
scroller
,
"dblclick"
,
operation
(
cm
,
function
(
e
)
{
if
(
signalDOMEvent
(
cm
,
e
))
return
;
var
pos
=
posFromMouse
(
cm
,
e
);
if
(
!
pos
||
clickInGutter
(
cm
,
e
)
||
eventInWidget
(
cm
.
display
,
e
))
return
;
e_preventDefault
(
e
);
var
word
=
findWordAt
(
getLine
(
cm
.
doc
,
pos
.
line
).
text
,
pos
);
extendSelection
(
cm
.
doc
,
word
.
from
,
word
.
to
);
}));
else
on
(
d
.
scroller
,
"dblclick"
,
function
(
e
)
{
signalDOMEvent
(
cm
,
e
)
||
e_preventDefault
(
e
);
});
on
(
d
.
lineSpace
,
"selectstart"
,
function
(
e
)
{
if
(
!
eventInWidget
(
d
,
e
))
e_preventDefault
(
e
);
});
// Gecko browsers fire contextmenu *after* opening the menu, at
// which point we can't mess with it anymore. Context menu is
// handled in onMouseDown for Gecko.
if
(
!
captureMiddleClick
)
on
(
d
.
scroller
,
"contextmenu"
,
function
(
e
)
{
onContextMenu
(
cm
,
e
);});
on
(
d
.
scroller
,
"scroll"
,
function
()
{
if
(
d
.
scroller
.
clientHeight
)
{
setScrollTop
(
cm
,
d
.
scroller
.
scrollTop
);
setScrollLeft
(
cm
,
d
.
scroller
.
scrollLeft
,
true
);
signal
(
cm
,
"scroll"
,
cm
);
}
});
on
(
d
.
scrollbarV
,
"scroll"
,
function
()
{
if
(
d
.
scroller
.
clientHeight
)
setScrollTop
(
cm
,
d
.
scrollbarV
.
scrollTop
);
});
on
(
d
.
scrollbarH
,
"scroll"
,
function
()
{
if
(
d
.
scroller
.
clientHeight
)
setScrollLeft
(
cm
,
d
.
scrollbarH
.
scrollLeft
);
});
on
(
d
.
scroller
,
"mousewheel"
,
function
(
e
){
onScrollWheel
(
cm
,
e
);});
on
(
d
.
scroller
,
"DOMMouseScroll"
,
function
(
e
){
onScrollWheel
(
cm
,
e
);});
function
reFocus
()
{
if
(
cm
.
state
.
focused
)
setTimeout
(
bind
(
focusInput
,
cm
),
0
);
}
on
(
d
.
scrollbarH
,
"mousedown"
,
reFocus
);
on
(
d
.
scrollbarV
,
"mousedown"
,
reFocus
);
// Prevent wrapper from ever scrolling
on
(
d
.
wrapper
,
"scroll"
,
function
()
{
d
.
wrapper
.
scrollTop
=
d
.
wrapper
.
scrollLeft
=
0
;
});
var
resizeTimer
;
function
onResize
()
{
if
(
resizeTimer
==
null
)
resizeTimer
=
setTimeout
(
function
()
{
resizeTimer
=
null
;
// Might be a text scaling operation, clear size caches.
d
.
cachedCharWidth
=
d
.
cachedTextHeight
=
knownScrollbarWidth
=
null
;
clearCaches
(
cm
);
runInOp
(
cm
,
bind
(
regChange
,
cm
));
},
100
);
}
on
(
window
,
"resize"
,
onResize
);
// Above handler holds on to the editor and its data structures.
// Here we poll to unregister it when the editor is no longer in
// the document, so that it can be garbage-collected.
function
unregister
()
{
for
(
var
p
=
d
.
wrapper
.
parentNode
;
p
&&
p
!=
document
.
body
;
p
=
p
.
parentNode
)
{}
if
(
p
)
setTimeout
(
unregister
,
5000
);
else
off
(
window
,
"resize"
,
onResize
);
}
setTimeout
(
unregister
,
5000
);
on
(
d
.
input
,
"keyup"
,
operation
(
cm
,
function
(
e
)
{
if
(
signalDOMEvent
(
cm
,
e
)
||
cm
.
options
.
onKeyEvent
&&
cm
.
options
.
onKeyEvent
(
cm
,
addStop
(
e
)))
return
;
if
(
e
.
keyCode
==
16
)
cm
.
doc
.
sel
.
shift
=
false
;
}));
on
(
d
.
input
,
"input"
,
bind
(
fastPoll
,
cm
));
on
(
d
.
input
,
"keydown"
,
operation
(
cm
,
onKeyDown
));
on
(
d
.
input
,
"keypress"
,
operation
(
cm
,
onKeyPress
));
on
(
d
.
input
,
"focus"
,
bind
(
onFocus
,
cm
));
on
(
d
.
input
,
"blur"
,
bind
(
onBlur
,
cm
));
function
drag_
(
e
)
{
if
(
signalDOMEvent
(
cm
,
e
)
||
cm
.
options
.
onDragEvent
&&
cm
.
options
.
onDragEvent
(
cm
,
addStop
(
e
)))
return
;
e_stop
(
e
);
}
if
(
cm
.
options
.
dragDrop
)
{
on
(
d
.
scroller
,
"dragstart"
,
function
(
e
){
onDragStart
(
cm
,
e
);});
on
(
d
.
scroller
,
"dragenter"
,
drag_
);
on
(
d
.
scroller
,
"dragover"
,
drag_
);
on
(
d
.
scroller
,
"drop"
,
operation
(
cm
,
onDrop
));
}
on
(
d
.
scroller
,
"paste"
,
function
(
e
){
if
(
eventInWidget
(
d
,
e
))
return
;
focusInput
(
cm
);
fastPoll
(
cm
);
});
on
(
d
.
input
,
"paste"
,
function
()
{
cm
.
state
.
pasteIncoming
=
true
;
fastPoll
(
cm
);
});
function
prepareCopy
()
{
if
(
d
.
inaccurateSelection
)
{
d
.
prevInput
=
""
;
d
.
inaccurateSelection
=
false
;
d
.
input
.
value
=
cm
.
getSelection
();
selectInput
(
d
.
input
);
}
}
on
(
d
.
input
,
"cut"
,
prepareCopy
);
on
(
d
.
input
,
"copy"
,
prepareCopy
);
// Needed to handle Tab key in KHTML
if
(
khtml
)
on
(
d
.
sizer
,
"mouseup"
,
function
()
{
if
(
document
.
activeElement
==
d
.
input
)
d
.
input
.
blur
();
focusInput
(
cm
);
});
}
function
eventInWidget
(
display
,
e
)
{
for
(
var
n
=
e_target
(
e
);
n
!=
display
.
wrapper
;
n
=
n
.
parentNode
)
{
if
(
!
n
||
n
.
ignoreEvents
||
n
.
parentNode
==
display
.
sizer
&&
n
!=
display
.
mover
)
return
true
;
}
}
function
posFromMouse
(
cm
,
e
,
liberal
)
{
var
display
=
cm
.
display
;
if
(
!
liberal
)
{
var
target
=
e_target
(
e
);
if
(
target
==
display
.
scrollbarH
||
target
==
display
.
scrollbarH
.
firstChild
||
target
==
display
.
scrollbarV
||
target
==
display
.
scrollbarV
.
firstChild
||
target
==
display
.
scrollbarFiller
||
target
==
display
.
gutterFiller
)
return
null
;
}
var
x
,
y
,
space
=
getRect
(
display
.
lineSpace
);
// Fails unpredictably on IE[67] when mouse is dragged around quickly.
try
{
x
=
e
.
clientX
;
y
=
e
.
clientY
;
}
catch
(
e
)
{
return
null
;
}
return
coordsChar
(
cm
,
x
-
space
.
left
,
y
-
space
.
top
);
}
var
lastClick
,
lastDoubleClick
;
function
onMouseDown
(
e
)
{
if
(
signalDOMEvent
(
this
,
e
))
return
;
var
cm
=
this
,
display
=
cm
.
display
,
doc
=
cm
.
doc
,
sel
=
doc
.
sel
;
sel
.
shift
=
e
.
shiftKey
;
if
(
eventInWidget
(
display
,
e
))
{
if
(
!
webkit
)
{
display
.
scroller
.
draggable
=
false
;
setTimeout
(
function
(){
display
.
scroller
.
draggable
=
true
;},
100
);
}
return
;
}
if
(
clickInGutter
(
cm
,
e
))
return
;
var
start
=
posFromMouse
(
cm
,
e
);
switch
(
e_button
(
e
))
{
case
3
:
if
(
captureMiddleClick
)
onContextMenu
.
call
(
cm
,
cm
,
e
);
return
;
case
2
:
if
(
start
)
extendSelection
(
cm
.
doc
,
start
);
setTimeout
(
bind
(
focusInput
,
cm
),
20
);
e_preventDefault
(
e
);
return
;
}
// For button 1, if it was clicked inside the editor
// (posFromMouse returning non-null), we have to adjust the
// selection.
if
(
!
start
)
{
if
(
e_target
(
e
)
==
display
.
scroller
)
e_preventDefault
(
e
);
return
;}
if
(
!
cm
.
state
.
focused
)
onFocus
(
cm
);
var
now
=
+
new
Date
,
type
=
"single"
;
if
(
lastDoubleClick
&&
lastDoubleClick
.
time
>
now
-
400
&&
posEq
(
lastDoubleClick
.
pos
,
start
))
{
type
=
"triple"
;
e_preventDefault
(
e
);
setTimeout
(
bind
(
focusInput
,
cm
),
20
);
selectLine
(
cm
,
start
.
line
);
}
else
if
(
lastClick
&&
lastClick
.
time
>
now
-
400
&&
posEq
(
lastClick
.
pos
,
start
))
{
type
=
"double"
;
lastDoubleClick
=
{
time
:
now
,
pos
:
start
};
e_preventDefault
(
e
);
var
word
=
findWordAt
(
getLine
(
doc
,
start
.
line
).
text
,
start
);
extendSelection
(
cm
.
doc
,
word
.
from
,
word
.
to
);
}
else
{
lastClick
=
{
time
:
now
,
pos
:
start
};
}
var
last
=
start
;
if
(
cm
.
options
.
dragDrop
&&
dragAndDrop
&&
!
isReadOnly
(
cm
)
&&
!
posEq
(
sel
.
from
,
sel
.
to
)
&&
!
posLess
(
start
,
sel
.
from
)
&&
!
posLess
(
sel
.
to
,
start
)
&&
type
==
"single"
)
{
var
dragEnd
=
operation
(
cm
,
function
(
e2
)
{
if
(
webkit
)
display
.
scroller
.
draggable
=
false
;
cm
.
state
.
draggingText
=
false
;
off
(
document
,
"mouseup"
,
dragEnd
);
off
(
display
.
scroller
,
"drop"
,
dragEnd
);
if
(
Math
.
abs
(
e
.
clientX
-
e2
.
clientX
)
+
Math
.
abs
(
e
.
clientY
-
e2
.
clientY
)
<
10
)
{
e_preventDefault
(
e2
);
extendSelection
(
cm
.
doc
,
start
);
focusInput
(
cm
);
}
});
// Let the drag handler handle this.
if
(
webkit
)
display
.
scroller
.
draggable
=
true
;
cm
.
state
.
draggingText
=
dragEnd
;
// IE's approach to draggable
if
(
display
.
scroller
.
dragDrop
)
display
.
scroller
.
dragDrop
();
on
(
document
,
"mouseup"
,
dragEnd
);
on
(
display
.
scroller
,
"drop"
,
dragEnd
);
return
;
}
e_preventDefault
(
e
);
if
(
type
==
"single"
)
extendSelection
(
cm
.
doc
,
clipPos
(
doc
,
start
));
var
startstart
=
sel
.
from
,
startend
=
sel
.
to
,
lastPos
=
start
;
function
doSelect
(
cur
)
{
if
(
posEq
(
lastPos
,
cur
))
return
;
lastPos
=
cur
;
if
(
type
==
"single"
)
{
extendSelection
(
cm
.
doc
,
clipPos
(
doc
,
start
),
cur
);
return
;
}
startstart
=
clipPos
(
doc
,
startstart
);
startend
=
clipPos
(
doc
,
startend
);
if
(
type
==
"double"
)
{
var
word
=
findWordAt
(
getLine
(
doc
,
cur
.
line
).
text
,
cur
);
if
(
posLess
(
cur
,
startstart
))
extendSelection
(
cm
.
doc
,
word
.
from
,
startend
);
else
extendSelection
(
cm
.
doc
,
startstart
,
word
.
to
);
}
else
if
(
type
==
"triple"
)
{
if
(
posLess
(
cur
,
startstart
))
extendSelection
(
cm
.
doc
,
startend
,
clipPos
(
doc
,
Pos
(
cur
.
line
,
0
)));
else
extendSelection
(
cm
.
doc
,
startstart
,
clipPos
(
doc
,
Pos
(
cur
.
line
+
1
,
0
)));
}
}
var
editorSize
=
getRect
(
display
.
wrapper
);
// Used to ensure timeout re-tries don't fire when another extend
// happened in the meantime (clearTimeout isn't reliable -- at
// least on Chrome, the timeouts still happen even when cleared,
// if the clear happens after their scheduled firing time).
var
counter
=
0
;
function
extend
(
e
)
{
var
curCount
=
++
counter
;
var
cur
=
posFromMouse
(
cm
,
e
,
true
);
if
(
!
cur
)
return
;
if
(
!
posEq
(
cur
,
last
))
{
if
(
!
cm
.
state
.
focused
)
onFocus
(
cm
);
last
=
cur
;
doSelect
(
cur
);
var
visible
=
visibleLines
(
display
,
doc
);
if
(
cur
.
line
>=
visible
.
to
||
cur
.
line
<
visible
.
from
)
setTimeout
(
operation
(
cm
,
function
(){
if
(
counter
==
curCount
)
extend
(
e
);}),
150
);
}
else
{
var
outside
=
e
.
clientY
<
editorSize
.
top
?
-
20
:
e
.
clientY
>
editorSize
.
bottom
?
20
:
0
;
if
(
outside
)
setTimeout
(
operation
(
cm
,
function
()
{
if
(
counter
!=
curCount
)
return
;
display
.
scroller
.
scrollTop
+=
outside
;
extend
(
e
);
}),
50
);
}
}
function
done
(
e
)
{
counter
=
Infinity
;
e_preventDefault
(
e
);
focusInput
(
cm
);
off
(
document
,
"mousemove"
,
move
);
off
(
document
,
"mouseup"
,
up
);
}
var
move
=
operation
(
cm
,
function
(
e
)
{
if
(
!
ie
&&
!
e_button
(
e
))
done
(
e
);
else
extend
(
e
);
});
var
up
=
operation
(
cm
,
done
);
on
(
document
,
"mousemove"
,
move
);
on
(
document
,
"mouseup"
,
up
);
}
function
clickInGutter
(
cm
,
e
)
{
var
display
=
cm
.
display
;
try
{
var
mX
=
e
.
clientX
,
mY
=
e
.
clientY
;
}
catch
(
e
)
{
return
false
;
}
if
(
mX
>=
Math
.
floor
(
getRect
(
display
.
gutters
).
right
))
return
false
;
e_preventDefault
(
e
);
if
(
!
hasHandler
(
cm
,
"gutterClick"
))
return
true
;
var
lineBox
=
getRect
(
display
.
lineDiv
);
if
(
mY
>
lineBox
.
bottom
)
return
true
;
mY
-=
lineBox
.
top
-
display
.
viewOffset
;
for
(
var
i
=
0
;
i
<
cm
.
options
.
gutters
.
length
;
++
i
)
{
var
g
=
display
.
gutters
.
childNodes
[
i
];
if
(
g
&&
getRect
(
g
).
right
>=
mX
)
{
var
line
=
lineAtHeight
(
cm
.
doc
,
mY
);
var
gutter
=
cm
.
options
.
gutters
[
i
];
signalLater
(
cm
,
"gutterClick"
,
cm
,
line
,
gutter
,
e
);
break
;
}
}
return
true
;
}
// Kludge to work around strange IE behavior where it'll sometimes
// re-fire a series of drag-related events right after the drop (#1551)
var
lastDrop
=
0
;
function
onDrop
(
e
)
{
var
cm
=
this
;
if
(
signalDOMEvent
(
cm
,
e
)
||
eventInWidget
(
cm
.
display
,
e
)
||
(
cm
.
options
.
onDragEvent
&&
cm
.
options
.
onDragEvent
(
cm
,
addStop
(
e
))))
return
;
e_preventDefault
(
e
);
if
(
ie
)
lastDrop
=
+
new
Date
;
var
pos
=
posFromMouse
(
cm
,
e
,
true
),
files
=
e
.
dataTransfer
.
files
;
if
(
!
pos
||
isReadOnly
(
cm
))
return
;
if
(
files
&&
files
.
length
&&
window
.
FileReader
&&
window
.
File
)
{
var
n
=
files
.
length
,
text
=
Array
(
n
),
read
=
0
;
var
loadFile
=
function
(
file
,
i
)
{
var
reader
=
new
FileReader
;
reader
.
onload
=
function
()
{
text
[
i
]
=
reader
.
result
;
if
(
++
read
==
n
)
{
pos
=
clipPos
(
cm
.
doc
,
pos
);
makeChange
(
cm
.
doc
,
{
from
:
pos
,
to
:
pos
,
text
:
splitLines
(
text
.
join
(
"\n"
)),
origin
:
"paste"
},
"around"
);
}
};
reader
.
readAsText
(
file
);
};
for
(
var
i
=
0
;
i
<
n
;
++
i
)
loadFile
(
files
[
i
],
i
);
}
else
{
// Don't do a replace if the drop happened inside of the selected text.
if
(
cm
.
state
.
draggingText
&&
!
(
posLess
(
pos
,
cm
.
doc
.
sel
.
from
)
||
posLess
(
cm
.
doc
.
sel
.
to
,
pos
)))
{
cm
.
state
.
draggingText
(
e
);
// Ensure the editor is re-focused
setTimeout
(
bind
(
focusInput
,
cm
),
20
);
return
;
}
try
{
var
text
=
e
.
dataTransfer
.
getData
(
"Text"
);
if
(
text
)
{
var
curFrom
=
cm
.
doc
.
sel
.
from
,
curTo
=
cm
.
doc
.
sel
.
to
;
setSelection
(
cm
.
doc
,
pos
,
pos
);
if
(
cm
.
state
.
draggingText
)
replaceRange
(
cm
.
doc
,
""
,
curFrom
,
curTo
,
"paste"
);
cm
.
replaceSelection
(
text
,
null
,
"paste"
);
focusInput
(
cm
);
onFocus
(
cm
);
}
}
catch
(
e
){}
}
}
function
onDragStart
(
cm
,
e
)
{
if
(
ie
&&
(
!
cm
.
state
.
draggingText
||
+
new
Date
-
lastDrop
<
100
))
{
e_stop
(
e
);
return
;
}
if
(
signalDOMEvent
(
cm
,
e
)
||
eventInWidget
(
cm
.
display
,
e
))
return
;
var
txt
=
cm
.
getSelection
();
e
.
dataTransfer
.
setData
(
"Text"
,
txt
);
// Use dummy image instead of default browsers image.
// Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
if
(
e
.
dataTransfer
.
setDragImage
&&
!
safari
)
{
var
img
=
elt
(
"img"
,
null
,
null
,
"position: fixed; left: 0; top: 0;"
);
if
(
opera
)
{
img
.
width
=
img
.
height
=
1
;
cm
.
display
.
wrapper
.
appendChild
(
img
);
// Force a relayout, or Opera won't use our image for some obscure reason
img
.
_top
=
img
.
offsetTop
;
}
e
.
dataTransfer
.
setDragImage
(
img
,
0
,
0
);
if
(
opera
)
img
.
parentNode
.
removeChild
(
img
);
}
}
function
setScrollTop
(
cm
,
val
)
{
if
(
Math
.
abs
(
cm
.
doc
.
scrollTop
-
val
)
<
2
)
return
;
cm
.
doc
.
scrollTop
=
val
;
if
(
!
gecko
)
updateDisplay
(
cm
,
[],
val
);
if
(
cm
.
display
.
scroller
.
scrollTop
!=
val
)
cm
.
display
.
scroller
.
scrollTop
=
val
;
if
(
cm
.
display
.
scrollbarV
.
scrollTop
!=
val
)
cm
.
display
.
scrollbarV
.
scrollTop
=
val
;
if
(
gecko
)
updateDisplay
(
cm
,
[]);
startWorker
(
cm
,
100
);
}
function
setScrollLeft
(
cm
,
val
,
isScroller
)
{
if
(
isScroller
?
val
==
cm
.
doc
.
scrollLeft
:
Math
.
abs
(
cm
.
doc
.
scrollLeft
-
val
)
<
2
)
return
;
val
=
Math
.
min
(
val
,
cm
.
display
.
scroller
.
scrollWidth
-
cm
.
display
.
scroller
.
clientWidth
);
cm
.
doc
.
scrollLeft
=
val
;
alignHorizontally
(
cm
);
if
(
cm
.
display
.
scroller
.
scrollLeft
!=
val
)
cm
.
display
.
scroller
.
scrollLeft
=
val
;
if
(
cm
.
display
.
scrollbarH
.
scrollLeft
!=
val
)
cm
.
display
.
scrollbarH
.
scrollLeft
=
val
;
}
// Since the delta values reported on mouse wheel events are
// unstandardized between browsers and even browser versions, and
// generally horribly unpredictable, this code starts by measuring
// the scroll effect that the first few mouse wheel events have,
// and, from that, detects the way it can convert deltas to pixel
// offsets afterwards.
//
// The reason we want to know the amount a wheel event will scroll
// is that it gives us a chance to update the display before the
// actual scrolling happens, reducing flickering.
var
wheelSamples
=
0
,
wheelPixelsPerUnit
=
null
;
// Fill in a browser-detected starting value on browsers where we
// know one. These don't have to be accurate -- the result of them
// being wrong would just be a slight flicker on the first wheel
// scroll (if it is large enough).
if
(
ie
)
wheelPixelsPerUnit
=
-
.53
;
else
if
(
gecko
)
wheelPixelsPerUnit
=
15
;
else
if
(
chrome
)
wheelPixelsPerUnit
=
-
.7
;
else
if
(
safari
)
wheelPixelsPerUnit
=
-
1
/
3
;
function
onScrollWheel
(
cm
,
e
)
{
var
dx
=
e
.
wheelDeltaX
,
dy
=
e
.
wheelDeltaY
;
if
(
dx
==
null
&&
e
.
detail
&&
e
.
axis
==
e
.
HORIZONTAL_AXIS
)
dx
=
e
.
detail
;
if
(
dy
==
null
&&
e
.
detail
&&
e
.
axis
==
e
.
VERTICAL_AXIS
)
dy
=
e
.
detail
;
else
if
(
dy
==
null
)
dy
=
e
.
wheelDelta
;
var
display
=
cm
.
display
,
scroll
=
display
.
scroller
;
// Quit if there's nothing to scroll here
if
(
!
(
dx
&&
scroll
.
scrollWidth
>
scroll
.
clientWidth
||
dy
&&
scroll
.
scrollHeight
>
scroll
.
clientHeight
))
return
;
// Webkit browsers on OS X abort momentum scrolls when the target
// of the scroll event is removed from the scrollable element.
// This hack (see related code in patchDisplay) makes sure the
// element is kept around.
if
(
dy
&&
mac
&&
webkit
)
{
for
(
var
cur
=
e
.
target
;
cur
!=
scroll
;
cur
=
cur
.
parentNode
)
{
if
(
cur
.
lineObj
)
{
cm
.
display
.
currentWheelTarget
=
cur
;
break
;
}
}
}
// On some browsers, horizontal scrolling will cause redraws to
// happen before the gutter has been realigned, causing it to
// wriggle around in a most unseemly way. When we have an
// estimated pixels/delta value, we just handle horizontal
// scrolling entirely here. It'll be slightly off from native, but
// better than glitching out.
if
(
dx
&&
!
gecko
&&
!
opera
&&
wheelPixelsPerUnit
!=
null
)
{
if
(
dy
)
setScrollTop
(
cm
,
Math
.
max
(
0
,
Math
.
min
(
scroll
.
scrollTop
+
dy
*
wheelPixelsPerUnit
,
scroll
.
scrollHeight
-
scroll
.
clientHeight
)));
setScrollLeft
(
cm
,
Math
.
max
(
0
,
Math
.
min
(
scroll
.
scrollLeft
+
dx
*
wheelPixelsPerUnit
,
scroll
.
scrollWidth
-
scroll
.
clientWidth
)));
e_preventDefault
(
e
);
display
.
wheelStartX
=
null
;
// Abort measurement, if in progress
return
;
}
if
(
dy
&&
wheelPixelsPerUnit
!=
null
)
{
var
pixels
=
dy
*
wheelPixelsPerUnit
;
var
top
=
cm
.
doc
.
scrollTop
,
bot
=
top
+
display
.
wrapper
.
clientHeight
;
if
(
pixels
<
0
)
top
=
Math
.
max
(
0
,
top
+
pixels
-
50
);
else
bot
=
Math
.
min
(
cm
.
doc
.
height
,
bot
+
pixels
+
50
);
updateDisplay
(
cm
,
[],
{
top
:
top
,
bottom
:
bot
});
}
if
(
wheelSamples
<
20
)
{
if
(
display
.
wheelStartX
==
null
)
{
display
.
wheelStartX
=
scroll
.
scrollLeft
;
display
.
wheelStartY
=
scroll
.
scrollTop
;
display
.
wheelDX
=
dx
;
display
.
wheelDY
=
dy
;
setTimeout
(
function
()
{
if
(
display
.
wheelStartX
==
null
)
return
;
var
movedX
=
scroll
.
scrollLeft
-
display
.
wheelStartX
;
var
movedY
=
scroll
.
scrollTop
-
display
.
wheelStartY
;
var
sample
=
(
movedY
&&
display
.
wheelDY
&&
movedY
/
display
.
wheelDY
)
||
(
movedX
&&
display
.
wheelDX
&&
movedX
/
display
.
wheelDX
);
display
.
wheelStartX
=
display
.
wheelStartY
=
null
;
if
(
!
sample
)
return
;
wheelPixelsPerUnit
=
(
wheelPixelsPerUnit
*
wheelSamples
+
sample
)
/
(
wheelSamples
+
1
);
++
wheelSamples
;
},
200
);
}
else
{
display
.
wheelDX
+=
dx
;
display
.
wheelDY
+=
dy
;
}
}
}
function
doHandleBinding
(
cm
,
bound
,
dropShift
)
{
if
(
typeof
bound
==
"string"
)
{
bound
=
commands
[
bound
];
if
(
!
bound
)
return
false
;
}
// Ensure previous input has been read, so that the handler sees a
// consistent view of the document
if
(
cm
.
display
.
pollingFast
&&
readInput
(
cm
))
cm
.
display
.
pollingFast
=
false
;
var
doc
=
cm
.
doc
,
prevShift
=
doc
.
sel
.
shift
,
done
=
false
;
try
{
if
(
isReadOnly
(
cm
))
cm
.
state
.
suppressEdits
=
true
;
if
(
dropShift
)
doc
.
sel
.
shift
=
false
;
done
=
bound
(
cm
)
!=
Pass
;
}
finally
{
doc
.
sel
.
shift
=
prevShift
;
cm
.
state
.
suppressEdits
=
false
;
}
return
done
;
}
function
allKeyMaps
(
cm
)
{
var
maps
=
cm
.
state
.
keyMaps
.
slice
(
0
);
if
(
cm
.
options
.
extraKeys
)
maps
.
push
(
cm
.
options
.
extraKeys
);
maps
.
push
(
cm
.
options
.
keyMap
);
return
maps
;
}
var
maybeTransition
;
function
handleKeyBinding
(
cm
,
e
)
{
// Handle auto keymap transitions
var
startMap
=
getKeyMap
(
cm
.
options
.
keyMap
),
next
=
startMap
.
auto
;
clearTimeout
(
maybeTransition
);
if
(
next
&&
!
isModifierKey
(
e
))
maybeTransition
=
setTimeout
(
function
()
{
if
(
getKeyMap
(
cm
.
options
.
keyMap
)
==
startMap
)
{
cm
.
options
.
keyMap
=
(
next
.
call
?
next
.
call
(
null
,
cm
)
:
next
);
keyMapChanged
(
cm
);
}
},
50
);
var
name
=
keyName
(
e
,
true
),
handled
=
false
;
if
(
!
name
)
return
false
;
var
keymaps
=
allKeyMaps
(
cm
);
if
(
e
.
shiftKey
)
{
// First try to resolve full name (including 'Shift-'). Failing
// that, see if there is a cursor-motion command (starting with
// 'go') bound to the keyname without 'Shift-'.
handled
=
lookupKey
(
"Shift-"
+
name
,
keymaps
,
function
(
b
)
{
return
doHandleBinding
(
cm
,
b
,
true
);})
||
lookupKey
(
name
,
keymaps
,
function
(
b
)
{
if
(
typeof
b
==
"string"
?
/^go[A-Z]/
.
test
(
b
)
:
b
.
motion
)
return
doHandleBinding
(
cm
,
b
);
});
}
else
{
handled
=
lookupKey
(
name
,
keymaps
,
function
(
b
)
{
return
doHandleBinding
(
cm
,
b
);
});
}
if
(
handled
)
{
e_preventDefault
(
e
);
restartBlink
(
cm
);
if
(
ie_lt9
)
{
e
.
oldKeyCode
=
e
.
keyCode
;
e
.
keyCode
=
0
;
}
signalLater
(
cm
,
"keyHandled"
,
cm
,
name
,
e
);
}
return
handled
;
}
function
handleCharBinding
(
cm
,
e
,
ch
)
{
var
handled
=
lookupKey
(
"'"
+
ch
+
"'"
,
allKeyMaps
(
cm
),
function
(
b
)
{
return
doHandleBinding
(
cm
,
b
,
true
);
});
if
(
handled
)
{
e_preventDefault
(
e
);
restartBlink
(
cm
);
signalLater
(
cm
,
"keyHandled"
,
cm
,
"'"
+
ch
+
"'"
,
e
);
}
return
handled
;
}
var
lastStoppedKey
=
null
;
function
onKeyDown
(
e
)
{
var
cm
=
this
;
if
(
!
cm
.
state
.
focused
)
onFocus
(
cm
);
if
(
ie
&&
e
.
keyCode
==
27
)
{
e
.
returnValue
=
false
;
}
if
(
signalDOMEvent
(
cm
,
e
)
||
cm
.
options
.
onKeyEvent
&&
cm
.
options
.
onKeyEvent
(
cm
,
addStop
(
e
)))
return
;
var
code
=
e
.
keyCode
;
// IE does strange things with escape.
cm
.
doc
.
sel
.
shift
=
code
==
16
||
e
.
shiftKey
;
// First give onKeyEvent option a chance to handle this.
var
handled
=
handleKeyBinding
(
cm
,
e
);
if
(
opera
)
{
lastStoppedKey
=
handled
?
code
:
null
;
// Opera has no cut event... we try to at least catch the key combo
if
(
!
handled
&&
code
==
88
&&
!
hasCopyEvent
&&
(
mac
?
e
.
metaKey
:
e
.
ctrlKey
))
cm
.
replaceSelection
(
""
);
}
}
function
onKeyPress
(
e
)
{
var
cm
=
this
;
if
(
signalDOMEvent
(
cm
,
e
)
||
cm
.
options
.
onKeyEvent
&&
cm
.
options
.
onKeyEvent
(
cm
,
addStop
(
e
)))
return
;
var
keyCode
=
e
.
keyCode
,
charCode
=
e
.
charCode
;
if
(
opera
&&
keyCode
==
lastStoppedKey
)
{
lastStoppedKey
=
null
;
e_preventDefault
(
e
);
return
;}
if
(((
opera
&&
(
!
e
.
which
||
e
.
which
<
10
))
||
khtml
)
&&
handleKeyBinding
(
cm
,
e
))
return
;
var
ch
=
String
.
fromCharCode
(
charCode
==
null
?
keyCode
:
charCode
);
if
(
this
.
options
.
electricChars
&&
this
.
doc
.
mode
.
electricChars
&&
this
.
options
.
smartIndent
&&
!
isReadOnly
(
this
)
&&
this
.
doc
.
mode
.
electricChars
.
indexOf
(
ch
)
>
-
1
)
setTimeout
(
operation
(
cm
,
function
()
{
indentLine
(
cm
,
cm
.
doc
.
sel
.
to
.
line
,
"smart"
);}),
75
);
if
(
handleCharBinding
(
cm
,
e
,
ch
))
return
;
if
(
ie
&&
!
ie_lt9
)
cm
.
display
.
inputHasSelection
=
null
;
fastPoll
(
cm
);
}
function
onFocus
(
cm
)
{
if
(
cm
.
options
.
readOnly
==
"nocursor"
)
return
;
if
(
!
cm
.
state
.
focused
)
{
signal
(
cm
,
"focus"
,
cm
);
cm
.
state
.
focused
=
true
;
if
(
cm
.
display
.
wrapper
.
className
.
search
(
/\bCodeMirror-focused\b/
)
==
-
1
)
cm
.
display
.
wrapper
.
className
+=
" CodeMirror-focused"
;
resetInput
(
cm
,
true
);
}
slowPoll
(
cm
);
restartBlink
(
cm
);
}
function
onBlur
(
cm
)
{
if
(
cm
.
state
.
focused
)
{
signal
(
cm
,
"blur"
,
cm
);
cm
.
state
.
focused
=
false
;
cm
.
display
.
wrapper
.
className
=
cm
.
display
.
wrapper
.
className
.
replace
(
" CodeMirror-focused"
,
""
);
}
clearInterval
(
cm
.
display
.
blinker
);
setTimeout
(
function
()
{
if
(
!
cm
.
state
.
focused
)
cm
.
doc
.
sel
.
shift
=
false
;},
150
);
}
var
detectingSelectAll
;
function
onContextMenu
(
cm
,
e
)
{
var
display
=
cm
.
display
,
sel
=
cm
.
doc
.
sel
;
if
(
eventInWidget
(
display
,
e
))
return
;
var
pos
=
posFromMouse
(
cm
,
e
),
scrollPos
=
display
.
scroller
.
scrollTop
;
if
(
!
pos
||
opera
)
return
;
// Opera is difficult.
if
(
posEq
(
sel
.
from
,
sel
.
to
)
||
posLess
(
pos
,
sel
.
from
)
||
!
posLess
(
pos
,
sel
.
to
))
operation
(
cm
,
setSelection
)(
cm
.
doc
,
pos
,
pos
);
var
oldCSS
=
display
.
input
.
style
.
cssText
;
display
.
inputDiv
.
style
.
position
=
"absolute"
;
display
.
input
.
style
.
cssText
=
"position: fixed; width: 30px; height: 30px; top: "
+
(
e
.
clientY
-
5
)
+
"px; left: "
+
(
e
.
clientX
-
5
)
+
"px; z-index: 1000; background: white; outline: none;"
+
"border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);"
;
focusInput
(
cm
);
resetInput
(
cm
,
true
);
// Adds "Select all" to context menu in FF
if
(
posEq
(
sel
.
from
,
sel
.
to
))
display
.
input
.
value
=
display
.
prevInput
=
" "
;
function
prepareSelectAllHack
()
{
if
(
display
.
input
.
selectionStart
!=
null
)
{
var
extval
=
display
.
input
.
value
=
" "
+
(
posEq
(
sel
.
from
,
sel
.
to
)
?
""
:
display
.
input
.
value
);
display
.
prevInput
=
" "
;
display
.
input
.
selectionStart
=
1
;
display
.
input
.
selectionEnd
=
extval
.
length
;
}
}
function
rehide
()
{
display
.
inputDiv
.
style
.
position
=
"relative"
;
display
.
input
.
style
.
cssText
=
oldCSS
;
if
(
ie_lt9
)
display
.
scrollbarV
.
scrollTop
=
display
.
scroller
.
scrollTop
=
scrollPos
;
slowPoll
(
cm
);
// Try to detect the user choosing select-all
if
(
display
.
input
.
selectionStart
!=
null
)
{
if
(
!
ie
||
ie_lt9
)
prepareSelectAllHack
();
clearTimeout
(
detectingSelectAll
);
var
i
=
0
,
poll
=
function
(){
if
(
display
.
prevInput
==
" "
&&
display
.
input
.
selectionStart
==
0
)
operation
(
cm
,
commands
.
selectAll
)(
cm
);
else
if
(
i
++
<
10
)
detectingSelectAll
=
setTimeout
(
poll
,
500
);
else
resetInput
(
cm
);
};
detectingSelectAll
=
setTimeout
(
poll
,
200
);
}
}
if
(
ie
&&
!
ie_lt9
)
prepareSelectAllHack
();
if
(
captureMiddleClick
)
{
e_stop
(
e
);
var
mouseup
=
function
()
{
off
(
window
,
"mouseup"
,
mouseup
);
setTimeout
(
rehide
,
20
);
};
on
(
window
,
"mouseup"
,
mouseup
);
}
else
{
setTimeout
(
rehide
,
50
);
}
}
// UPDATING
var
changeEnd
=
CodeMirror
.
changeEnd
=
function
(
change
)
{
if
(
!
change
.
text
)
return
change
.
to
;
return
Pos
(
change
.
from
.
line
+
change
.
text
.
length
-
1
,
lst
(
change
.
text
).
length
+
(
change
.
text
.
length
==
1
?
change
.
from
.
ch
:
0
));
};
// Make sure a position will be valid after the given change.
function
clipPostChange
(
doc
,
change
,
pos
)
{
if
(
!
posLess
(
change
.
from
,
pos
))
return
clipPos
(
doc
,
pos
);
var
diff
=
(
change
.
text
.
length
-
1
)
-
(
change
.
to
.
line
-
change
.
from
.
line
);
if
(
pos
.
line
>
change
.
to
.
line
+
diff
)
{
var
preLine
=
pos
.
line
-
diff
,
lastLine
=
doc
.
first
+
doc
.
size
-
1
;
if
(
preLine
>
lastLine
)
return
Pos
(
lastLine
,
getLine
(
doc
,
lastLine
).
text
.
length
);
return
clipToLen
(
pos
,
getLine
(
doc
,
preLine
).
text
.
length
);
}
if
(
pos
.
line
==
change
.
to
.
line
+
diff
)
return
clipToLen
(
pos
,
lst
(
change
.
text
).
length
+
(
change
.
text
.
length
==
1
?
change
.
from
.
ch
:
0
)
+
getLine
(
doc
,
change
.
to
.
line
).
text
.
length
-
change
.
to
.
ch
);
var
inside
=
pos
.
line
-
change
.
from
.
line
;
return
clipToLen
(
pos
,
change
.
text
[
inside
].
length
+
(
inside
?
0
:
change
.
from
.
ch
));
}
// Hint can be null|"end"|"start"|"around"|{anchor,head}
function
computeSelAfterChange
(
doc
,
change
,
hint
)
{
if
(
hint
&&
typeof
hint
==
"object"
)
// Assumed to be {anchor, head} object
return
{
anchor
:
clipPostChange
(
doc
,
change
,
hint
.
anchor
),
head
:
clipPostChange
(
doc
,
change
,
hint
.
head
)};
if
(
hint
==
"start"
)
return
{
anchor
:
change
.
from
,
head
:
change
.
from
};
var
end
=
changeEnd
(
change
);
if
(
hint
==
"around"
)
return
{
anchor
:
change
.
from
,
head
:
end
};
if
(
hint
==
"end"
)
return
{
anchor
:
end
,
head
:
end
};
// hint is null, leave the selection alone as much as possible
var
adjustPos
=
function
(
pos
)
{
if
(
posLess
(
pos
,
change
.
from
))
return
pos
;
if
(
!
posLess
(
change
.
to
,
pos
))
return
end
;
var
line
=
pos
.
line
+
change
.
text
.
length
-
(
change
.
to
.
line
-
change
.
from
.
line
)
-
1
,
ch
=
pos
.
ch
;
if
(
pos
.
line
==
change
.
to
.
line
)
ch
+=
end
.
ch
-
change
.
to
.
ch
;
return
Pos
(
line
,
ch
);
};
return
{
anchor
:
adjustPos
(
doc
.
sel
.
anchor
),
head
:
adjustPos
(
doc
.
sel
.
head
)};
}
function
filterChange
(
doc
,
change
,
update
)
{
var
obj
=
{
canceled
:
false
,
from
:
change
.
from
,
to
:
change
.
to
,
text
:
change
.
text
,
origin
:
change
.
origin
,
cancel
:
function
()
{
this
.
canceled
=
true
;
}
};
if
(
update
)
obj
.
update
=
function
(
from
,
to
,
text
,
origin
)
{
if
(
from
)
this
.
from
=
clipPos
(
doc
,
from
);
if
(
to
)
this
.
to
=
clipPos
(
doc
,
to
);
if
(
text
)
this
.
text
=
text
;
if
(
origin
!==
undefined
)
this
.
origin
=
origin
;
};
signal
(
doc
,
"beforeChange"
,
doc
,
obj
);
if
(
doc
.
cm
)
signal
(
doc
.
cm
,
"beforeChange"
,
doc
.
cm
,
obj
);
if
(
obj
.
canceled
)
return
null
;
return
{
from
:
obj
.
from
,
to
:
obj
.
to
,
text
:
obj
.
text
,
origin
:
obj
.
origin
};
}
// Replace the range from from to to by the strings in replacement.
// change is a {from, to, text [, origin]} object
function
makeChange
(
doc
,
change
,
selUpdate
,
ignoreReadOnly
)
{
if
(
doc
.
cm
)
{
if
(
!
doc
.
cm
.
curOp
)
return
operation
(
doc
.
cm
,
makeChange
)(
doc
,
change
,
selUpdate
,
ignoreReadOnly
);
if
(
doc
.
cm
.
state
.
suppressEdits
)
return
;
}
if
(
hasHandler
(
doc
,
"beforeChange"
)
||
doc
.
cm
&&
hasHandler
(
doc
.
cm
,
"beforeChange"
))
{
change
=
filterChange
(
doc
,
change
,
true
);
if
(
!
change
)
return
;
}
// Possibly split or suppress the update based on the presence
// of read-only spans in its range.
var
split
=
sawReadOnlySpans
&&
!
ignoreReadOnly
&&
removeReadOnlyRanges
(
doc
,
change
.
from
,
change
.
to
);
if
(
split
)
{
for
(
var
i
=
split
.
length
-
1
;
i
>=
1
;
--
i
)
makeChangeNoReadonly
(
doc
,
{
from
:
split
[
i
].
from
,
to
:
split
[
i
].
to
,
text
:
[
""
]});
if
(
split
.
length
)
makeChangeNoReadonly
(
doc
,
{
from
:
split
[
0
].
from
,
to
:
split
[
0
].
to
,
text
:
change
.
text
},
selUpdate
);
}
else
{
makeChangeNoReadonly
(
doc
,
change
,
selUpdate
);
}
}
function
makeChangeNoReadonly
(
doc
,
change
,
selUpdate
)
{
var
selAfter
=
computeSelAfterChange
(
doc
,
change
,
selUpdate
);
addToHistory
(
doc
,
change
,
selAfter
,
doc
.
cm
?
doc
.
cm
.
curOp
.
id
:
NaN
);
makeChangeSingleDoc
(
doc
,
change
,
selAfter
,
stretchSpansOverChange
(
doc
,
change
));
var
rebased
=
[];
linkedDocs
(
doc
,
function
(
doc
,
sharedHist
)
{
if
(
!
sharedHist
&&
indexOf
(
rebased
,
doc
.
history
)
==
-
1
)
{
rebaseHist
(
doc
.
history
,
change
);
rebased
.
push
(
doc
.
history
);
}
makeChangeSingleDoc
(
doc
,
change
,
null
,
stretchSpansOverChange
(
doc
,
change
));
});
}
function
makeChangeFromHistory
(
doc
,
type
)
{
if
(
doc
.
cm
&&
doc
.
cm
.
state
.
suppressEdits
)
return
;
var
hist
=
doc
.
history
;
var
event
=
(
type
==
"undo"
?
hist
.
done
:
hist
.
undone
).
pop
();
if
(
!
event
)
return
;
var
anti
=
{
changes
:
[],
anchorBefore
:
event
.
anchorAfter
,
headBefore
:
event
.
headAfter
,
anchorAfter
:
event
.
anchorBefore
,
headAfter
:
event
.
headBefore
,
generation
:
hist
.
generation
};
(
type
==
"undo"
?
hist
.
undone
:
hist
.
done
).
push
(
anti
);
hist
.
generation
=
event
.
generation
||
++
hist
.
maxGeneration
;
var
filter
=
hasHandler
(
doc
,
"beforeChange"
)
||
doc
.
cm
&&
hasHandler
(
doc
.
cm
,
"beforeChange"
);
for
(
var
i
=
event
.
changes
.
length
-
1
;
i
>=
0
;
--
i
)
{
var
change
=
event
.
changes
[
i
];
change
.
origin
=
type
;
if
(
filter
&&
!
filterChange
(
doc
,
change
,
false
))
{
(
type
==
"undo"
?
hist
.
done
:
hist
.
undone
).
length
=
0
;
return
;
}
anti
.
changes
.
push
(
historyChangeFromChange
(
doc
,
change
));
var
after
=
i
?
computeSelAfterChange
(
doc
,
change
,
null
)
:
{
anchor
:
event
.
anchorBefore
,
head
:
event
.
headBefore
};
makeChangeSingleDoc
(
doc
,
change
,
after
,
mergeOldSpans
(
doc
,
change
));
var
rebased
=
[];
linkedDocs
(
doc
,
function
(
doc
,
sharedHist
)
{
if
(
!
sharedHist
&&
indexOf
(
rebased
,
doc
.
history
)
==
-
1
)
{
rebaseHist
(
doc
.
history
,
change
);
rebased
.
push
(
doc
.
history
);
}
makeChangeSingleDoc
(
doc
,
change
,
null
,
mergeOldSpans
(
doc
,
change
));
});
}
}
function
shiftDoc
(
doc
,
distance
)
{
function
shiftPos
(
pos
)
{
return
Pos
(
pos
.
line
+
distance
,
pos
.
ch
);}
doc
.
first
+=
distance
;
if
(
doc
.
cm
)
regChange
(
doc
.
cm
,
doc
.
first
,
doc
.
first
,
distance
);
doc
.
sel
.
head
=
shiftPos
(
doc
.
sel
.
head
);
doc
.
sel
.
anchor
=
shiftPos
(
doc
.
sel
.
anchor
);
doc
.
sel
.
from
=
shiftPos
(
doc
.
sel
.
from
);
doc
.
sel
.
to
=
shiftPos
(
doc
.
sel
.
to
);
}
function
makeChangeSingleDoc
(
doc
,
change
,
selAfter
,
spans
)
{
if
(
doc
.
cm
&&
!
doc
.
cm
.
curOp
)
return
operation
(
doc
.
cm
,
makeChangeSingleDoc
)(
doc
,
change
,
selAfter
,
spans
);
if
(
change
.
to
.
line
<
doc
.
first
)
{
shiftDoc
(
doc
,
change
.
text
.
length
-
1
-
(
change
.
to
.
line
-
change
.
from
.
line
));
return
;
}
if
(
change
.
from
.
line
>
doc
.
lastLine
())
return
;
// Clip the change to the size of this doc
if
(
change
.
from
.
line
<
doc
.
first
)
{
var
shift
=
change
.
text
.
length
-
1
-
(
doc
.
first
-
change
.
from
.
line
);
shiftDoc
(
doc
,
shift
);
change
=
{
from
:
Pos
(
doc
.
first
,
0
),
to
:
Pos
(
change
.
to
.
line
+
shift
,
change
.
to
.
ch
),
text
:
[
lst
(
change
.
text
)],
origin
:
change
.
origin
};
}
var
last
=
doc
.
lastLine
();
if
(
change
.
to
.
line
>
last
)
{
change
=
{
from
:
change
.
from
,
to
:
Pos
(
last
,
getLine
(
doc
,
last
).
text
.
length
),
text
:
[
change
.
text
[
0
]],
origin
:
change
.
origin
};
}
change
.
removed
=
getBetween
(
doc
,
change
.
from
,
change
.
to
);
if
(
!
selAfter
)
selAfter
=
computeSelAfterChange
(
doc
,
change
,
null
);
if
(
doc
.
cm
)
makeChangeSingleDocInEditor
(
doc
.
cm
,
change
,
spans
,
selAfter
);
else
updateDoc
(
doc
,
change
,
spans
,
selAfter
);
}
function
makeChangeSingleDocInEditor
(
cm
,
change
,
spans
,
selAfter
)
{
var
doc
=
cm
.
doc
,
display
=
cm
.
display
,
from
=
change
.
from
,
to
=
change
.
to
;
var
recomputeMaxLength
=
false
,
checkWidthStart
=
from
.
line
;
if
(
!
cm
.
options
.
lineWrapping
)
{
checkWidthStart
=
lineNo
(
visualLine
(
doc
,
getLine
(
doc
,
from
.
line
)));
doc
.
iter
(
checkWidthStart
,
to
.
line
+
1
,
function
(
line
)
{
if
(
line
==
display
.
maxLine
)
{
recomputeMaxLength
=
true
;
return
true
;
}
});
}
if
(
!
posLess
(
doc
.
sel
.
head
,
change
.
from
)
&&
!
posLess
(
change
.
to
,
doc
.
sel
.
head
))
cm
.
curOp
.
cursorActivity
=
true
;
updateDoc
(
doc
,
change
,
spans
,
selAfter
,
estimateHeight
(
cm
));
if
(
!
cm
.
options
.
lineWrapping
)
{
doc
.
iter
(
checkWidthStart
,
from
.
line
+
change
.
text
.
length
,
function
(
line
)
{
var
len
=
lineLength
(
doc
,
line
);
if
(
len
>
display
.
maxLineLength
)
{
display
.
maxLine
=
line
;
display
.
maxLineLength
=
len
;
display
.
maxLineChanged
=
true
;
recomputeMaxLength
=
false
;
}
});
if
(
recomputeMaxLength
)
cm
.
curOp
.
updateMaxLine
=
true
;
}
// Adjust frontier, schedule worker
doc
.
frontier
=
Math
.
min
(
doc
.
frontier
,
from
.
line
);
startWorker
(
cm
,
400
);
var
lendiff
=
change
.
text
.
length
-
(
to
.
line
-
from
.
line
)
-
1
;
// Remember that these lines changed, for updating the display
regChange
(
cm
,
from
.
line
,
to
.
line
+
1
,
lendiff
);
if
(
hasHandler
(
cm
,
"change"
))
{
var
changeObj
=
{
from
:
from
,
to
:
to
,
text
:
change
.
text
,
removed
:
change
.
removed
,
origin
:
change
.
origin
};
if
(
cm
.
curOp
.
textChanged
)
{
for
(
var
cur
=
cm
.
curOp
.
textChanged
;
cur
.
next
;
cur
=
cur
.
next
)
{}
cur
.
next
=
changeObj
;
}
else
cm
.
curOp
.
textChanged
=
changeObj
;
}
}
function
replaceRange
(
doc
,
code
,
from
,
to
,
origin
)
{
if
(
!
to
)
to
=
from
;
if
(
posLess
(
to
,
from
))
{
var
tmp
=
to
;
to
=
from
;
from
=
tmp
;
}
if
(
typeof
code
==
"string"
)
code
=
splitLines
(
code
);
makeChange
(
doc
,
{
from
:
from
,
to
:
to
,
text
:
code
,
origin
:
origin
},
null
);
}
// POSITION OBJECT
function
Pos
(
line
,
ch
)
{
if
(
!
(
this
instanceof
Pos
))
return
new
Pos
(
line
,
ch
);
this
.
line
=
line
;
this
.
ch
=
ch
;
}
CodeMirror
.
Pos
=
Pos
;
function
posEq
(
a
,
b
)
{
return
a
.
line
==
b
.
line
&&
a
.
ch
==
b
.
ch
;}
function
posLess
(
a
,
b
)
{
return
a
.
line
<
b
.
line
||
(
a
.
line
==
b
.
line
&&
a
.
ch
<
b
.
ch
);}
function
copyPos
(
x
)
{
return
Pos
(
x
.
line
,
x
.
ch
);}
// SELECTION
function
clipLine
(
doc
,
n
)
{
return
Math
.
max
(
doc
.
first
,
Math
.
min
(
n
,
doc
.
first
+
doc
.
size
-
1
));}
function
clipPos
(
doc
,
pos
)
{
if
(
pos
.
line
<
doc
.
first
)
return
Pos
(
doc
.
first
,
0
);
var
last
=
doc
.
first
+
doc
.
size
-
1
;
if
(
pos
.
line
>
last
)
return
Pos
(
last
,
getLine
(
doc
,
last
).
text
.
length
);
return
clipToLen
(
pos
,
getLine
(
doc
,
pos
.
line
).
text
.
length
);
}
function
clipToLen
(
pos
,
linelen
)
{
var
ch
=
pos
.
ch
;
if
(
ch
==
null
||
ch
>
linelen
)
return
Pos
(
pos
.
line
,
linelen
);
else
if
(
ch
<
0
)
return
Pos
(
pos
.
line
,
0
);
else
return
pos
;
}
function
isLine
(
doc
,
l
)
{
return
l
>=
doc
.
first
&&
l
<
doc
.
first
+
doc
.
size
;}
// If shift is held, this will move the selection anchor. Otherwise,
// it'll set the whole selection.
function
extendSelection
(
doc
,
pos
,
other
,
bias
)
{
if
(
doc
.
sel
.
shift
||
doc
.
sel
.
extend
)
{
var
anchor
=
doc
.
sel
.
anchor
;
if
(
other
)
{
var
posBefore
=
posLess
(
pos
,
anchor
);
if
(
posBefore
!=
posLess
(
other
,
anchor
))
{
anchor
=
pos
;
pos
=
other
;
}
else
if
(
posBefore
!=
posLess
(
pos
,
other
))
{
pos
=
other
;
}
}
setSelection
(
doc
,
anchor
,
pos
,
bias
);
}
else
{
setSelection
(
doc
,
pos
,
other
||
pos
,
bias
);
}
if
(
doc
.
cm
)
doc
.
cm
.
curOp
.
userSelChange
=
true
;
}
function
filterSelectionChange
(
doc
,
anchor
,
head
)
{
var
obj
=
{
anchor
:
anchor
,
head
:
head
};
signal
(
doc
,
"beforeSelectionChange"
,
doc
,
obj
);
if
(
doc
.
cm
)
signal
(
doc
.
cm
,
"beforeSelectionChange"
,
doc
.
cm
,
obj
);
obj
.
anchor
=
clipPos
(
doc
,
obj
.
anchor
);
obj
.
head
=
clipPos
(
doc
,
obj
.
head
);
return
obj
;
}
// Update the selection. Last two args are only used by
// updateDoc, since they have to be expressed in the line
// numbers before the update.
function
setSelection
(
doc
,
anchor
,
head
,
bias
,
checkAtomic
)
{
if
(
!
checkAtomic
&&
hasHandler
(
doc
,
"beforeSelectionChange"
)
||
doc
.
cm
&&
hasHandler
(
doc
.
cm
,
"beforeSelectionChange"
))
{
var
filtered
=
filterSelectionChange
(
doc
,
anchor
,
head
);
head
=
filtered
.
head
;
anchor
=
filtered
.
anchor
;
}
var
sel
=
doc
.
sel
;
sel
.
goalColumn
=
null
;
// Skip over atomic spans.
if
(
checkAtomic
||
!
posEq
(
anchor
,
sel
.
anchor
))
anchor
=
skipAtomic
(
doc
,
anchor
,
bias
,
checkAtomic
!=
"push"
);
if
(
checkAtomic
||
!
posEq
(
head
,
sel
.
head
))
head
=
skipAtomic
(
doc
,
head
,
bias
,
checkAtomic
!=
"push"
);
if
(
posEq
(
sel
.
anchor
,
anchor
)
&&
posEq
(
sel
.
head
,
head
))
return
;
sel
.
anchor
=
anchor
;
sel
.
head
=
head
;
var
inv
=
posLess
(
head
,
anchor
);
sel
.
from
=
inv
?
head
:
anchor
;
sel
.
to
=
inv
?
anchor
:
head
;
if
(
doc
.
cm
)
doc
.
cm
.
curOp
.
updateInput
=
doc
.
cm
.
curOp
.
selectionChanged
=
doc
.
cm
.
curOp
.
cursorActivity
=
true
;
signalLater
(
doc
,
"cursorActivity"
,
doc
);
}
function
reCheckSelection
(
cm
)
{
setSelection
(
cm
.
doc
,
cm
.
doc
.
sel
.
from
,
cm
.
doc
.
sel
.
to
,
null
,
"push"
);
}
function
skipAtomic
(
doc
,
pos
,
bias
,
mayClear
)
{
var
flipped
=
false
,
curPos
=
pos
;
var
dir
=
bias
||
1
;
doc
.
cantEdit
=
false
;
search
:
for
(;;)
{
var
line
=
getLine
(
doc
,
curPos
.
line
);
if
(
line
.
markedSpans
)
{
for
(
var
i
=
0
;
i
<
line
.
markedSpans
.
length
;
++
i
)
{
var
sp
=
line
.
markedSpans
[
i
],
m
=
sp
.
marker
;
if
((
sp
.
from
==
null
||
(
m
.
inclusiveLeft
?
sp
.
from
<=
curPos
.
ch
:
sp
.
from
<
curPos
.
ch
))
&&
(
sp
.
to
==
null
||
(
m
.
inclusiveRight
?
sp
.
to
>=
curPos
.
ch
:
sp
.
to
>
curPos
.
ch
)))
{
if
(
mayClear
)
{
signal
(
m
,
"beforeCursorEnter"
);
if
(
m
.
explicitlyCleared
)
{
if
(
!
line
.
markedSpans
)
break
;
else
{
--
i
;
continue
;}
}
}
if
(
!
m
.
atomic
)
continue
;
var
newPos
=
m
.
find
()[
dir
<
0
?
"from"
:
"to"
];
if
(
posEq
(
newPos
,
curPos
))
{
newPos
.
ch
+=
dir
;
if
(
newPos
.
ch
<
0
)
{
if
(
newPos
.
line
>
doc
.
first
)
newPos
=
clipPos
(
doc
,
Pos
(
newPos
.
line
-
1
));
else
newPos
=
null
;
}
else
if
(
newPos
.
ch
>
line
.
text
.
length
)
{
if
(
newPos
.
line
<
doc
.
first
+
doc
.
size
-
1
)
newPos
=
Pos
(
newPos
.
line
+
1
,
0
);
else
newPos
=
null
;
}
if
(
!
newPos
)
{
if
(
flipped
)
{
// Driven in a corner -- no valid cursor position found at all
// -- try again *with* clearing, if we didn't already
if
(
!
mayClear
)
return
skipAtomic
(
doc
,
pos
,
bias
,
true
);
// Otherwise, turn off editing until further notice, and return the start of the doc
doc
.
cantEdit
=
true
;
return
Pos
(
doc
.
first
,
0
);
}
flipped
=
true
;
newPos
=
pos
;
dir
=
-
dir
;
}
}
curPos
=
newPos
;
continue
search
;
}
}
}
return
curPos
;
}
}
// SCROLLING
function
scrollCursorIntoView
(
cm
)
{
var
coords
=
scrollPosIntoView
(
cm
,
cm
.
doc
.
sel
.
head
,
cm
.
options
.
cursorScrollMargin
);
if
(
!
cm
.
state
.
focused
)
return
;
var
display
=
cm
.
display
,
box
=
getRect
(
display
.
sizer
),
doScroll
=
null
;
if
(
coords
.
top
+
box
.
top
<
0
)
doScroll
=
true
;
else
if
(
coords
.
bottom
+
box
.
top
>
(
window
.
innerHeight
||
document
.
documentElement
.
clientHeight
))
doScroll
=
false
;
if
(
doScroll
!=
null
&&
!
phantom
)
{
var
hidden
=
display
.
cursor
.
style
.
display
==
"none"
;
if
(
hidden
)
{
display
.
cursor
.
style
.
display
=
""
;
display
.
cursor
.
style
.
left
=
coords
.
left
+
"px"
;
display
.
cursor
.
style
.
top
=
(
coords
.
top
-
display
.
viewOffset
)
+
"px"
;
}
display
.
cursor
.
scrollIntoView
(
doScroll
);
if
(
hidden
)
display
.
cursor
.
style
.
display
=
"none"
;
}
}
function
scrollPosIntoView
(
cm
,
pos
,
margin
)
{
if
(
margin
==
null
)
margin
=
0
;
for
(;;)
{
var
changed
=
false
,
coords
=
cursorCoords
(
cm
,
pos
);
var
scrollPos
=
calculateScrollPos
(
cm
,
coords
.
left
,
coords
.
top
-
margin
,
coords
.
left
,
coords
.
bottom
+
margin
);
var
startTop
=
cm
.
doc
.
scrollTop
,
startLeft
=
cm
.
doc
.
scrollLeft
;
if
(
scrollPos
.
scrollTop
!=
null
)
{
setScrollTop
(
cm
,
scrollPos
.
scrollTop
);
if
(
Math
.
abs
(
cm
.
doc
.
scrollTop
-
startTop
)
>
1
)
changed
=
true
;
}
if
(
scrollPos
.
scrollLeft
!=
null
)
{
setScrollLeft
(
cm
,
scrollPos
.
scrollLeft
);
if
(
Math
.
abs
(
cm
.
doc
.
scrollLeft
-
startLeft
)
>
1
)
changed
=
true
;
}
if
(
!
changed
)
return
coords
;
}
}
function
scrollIntoView
(
cm
,
x1
,
y1
,
x2
,
y2
)
{
var
scrollPos
=
calculateScrollPos
(
cm
,
x1
,
y1
,
x2
,
y2
);
if
(
scrollPos
.
scrollTop
!=
null
)
setScrollTop
(
cm
,
scrollPos
.
scrollTop
);
if
(
scrollPos
.
scrollLeft
!=
null
)
setScrollLeft
(
cm
,
scrollPos
.
scrollLeft
);
}
function
calculateScrollPos
(
cm
,
x1
,
y1
,
x2
,
y2
)
{
var
display
=
cm
.
display
,
snapMargin
=
textHeight
(
cm
.
display
);
if
(
y1
<
0
)
y1
=
0
;
var
screen
=
display
.
scroller
.
clientHeight
-
scrollerCutOff
,
screentop
=
display
.
scroller
.
scrollTop
,
result
=
{};
var
docBottom
=
cm
.
doc
.
height
+
paddingVert
(
display
);
var
atTop
=
y1
<
snapMargin
,
atBottom
=
y2
>
docBottom
-
snapMargin
;
if
(
y1
<
screentop
)
{
result
.
scrollTop
=
atTop
?
0
:
y1
;
}
else
if
(
y2
>
screentop
+
screen
)
{
var
newTop
=
Math
.
min
(
y1
,
(
atBottom
?
docBottom
:
y2
)
-
screen
);
if
(
newTop
!=
screentop
)
result
.
scrollTop
=
newTop
;
}
var
screenw
=
display
.
scroller
.
clientWidth
-
scrollerCutOff
,
screenleft
=
display
.
scroller
.
scrollLeft
;
x1
+=
display
.
gutters
.
offsetWidth
;
x2
+=
display
.
gutters
.
offsetWidth
;
var
gutterw
=
display
.
gutters
.
offsetWidth
;
var
atLeft
=
x1
<
gutterw
+
10
;
if
(
x1
<
screenleft
+
gutterw
||
atLeft
)
{
if
(
atLeft
)
x1
=
0
;
result
.
scrollLeft
=
Math
.
max
(
0
,
x1
-
10
-
gutterw
);
}
else
if
(
x2
>
screenw
+
screenleft
-
3
)
{
result
.
scrollLeft
=
x2
+
10
-
screenw
;
}
return
result
;
}
function
updateScrollPos
(
cm
,
left
,
top
)
{
cm
.
curOp
.
updateScrollPos
=
{
scrollLeft
:
left
==
null
?
cm
.
doc
.
scrollLeft
:
left
,
scrollTop
:
top
==
null
?
cm
.
doc
.
scrollTop
:
top
};
}
function
addToScrollPos
(
cm
,
left
,
top
)
{
var
pos
=
cm
.
curOp
.
updateScrollPos
||
(
cm
.
curOp
.
updateScrollPos
=
{
scrollLeft
:
cm
.
doc
.
scrollLeft
,
scrollTop
:
cm
.
doc
.
scrollTop
});
var
scroll
=
cm
.
display
.
scroller
;
pos
.
scrollTop
=
Math
.
max
(
0
,
Math
.
min
(
scroll
.
scrollHeight
-
scroll
.
clientHeight
,
pos
.
scrollTop
+
top
));
pos
.
scrollLeft
=
Math
.
max
(
0
,
Math
.
min
(
scroll
.
scrollWidth
-
scroll
.
clientWidth
,
pos
.
scrollLeft
+
left
));
}
// API UTILITIES
function
indentLine
(
cm
,
n
,
how
,
aggressive
)
{
var
doc
=
cm
.
doc
;
if
(
how
==
null
)
how
=
"add"
;
if
(
how
==
"smart"
)
{
if
(
!
cm
.
doc
.
mode
.
indent
)
how
=
"prev"
;
else
var
state
=
getStateBefore
(
cm
,
n
);
}
var
tabSize
=
cm
.
options
.
tabSize
;
var
line
=
getLine
(
doc
,
n
),
curSpace
=
countColumn
(
line
.
text
,
null
,
tabSize
);
var
curSpaceString
=
line
.
text
.
match
(
/^\s*/
)[
0
],
indentation
;
if
(
how
==
"smart"
)
{
indentation
=
cm
.
doc
.
mode
.
indent
(
state
,
line
.
text
.
slice
(
curSpaceString
.
length
),
line
.
text
);
if
(
indentation
==
Pass
)
{
if
(
!
aggressive
)
return
;
how
=
"prev"
;
}
}
if
(
how
==
"prev"
)
{
if
(
n
>
doc
.
first
)
indentation
=
countColumn
(
getLine
(
doc
,
n
-
1
).
text
,
null
,
tabSize
);
else
indentation
=
0
;
}
else
if
(
how
==
"add"
)
{
indentation
=
curSpace
+
cm
.
options
.
indentUnit
;
}
else
if
(
how
==
"subtract"
)
{
indentation
=
curSpace
-
cm
.
options
.
indentUnit
;
}
else
if
(
typeof
how
==
"number"
)
{
indentation
=
curSpace
+
how
;
}
indentation
=
Math
.
max
(
0
,
indentation
);
var
indentString
=
""
,
pos
=
0
;
if
(
cm
.
options
.
indentWithTabs
)
for
(
var
i
=
Math
.
floor
(
indentation
/
tabSize
);
i
;
--
i
)
{
pos
+=
tabSize
;
indentString
+=
"\t"
;}
if
(
pos
<
indentation
)
indentString
+=
spaceStr
(
indentation
-
pos
);
if
(
indentString
!=
curSpaceString
)
replaceRange
(
cm
.
doc
,
indentString
,
Pos
(
n
,
0
),
Pos
(
n
,
curSpaceString
.
length
),
"+input"
);
line
.
stateAfter
=
null
;
}
function
changeLine
(
cm
,
handle
,
op
)
{
var
no
=
handle
,
line
=
handle
,
doc
=
cm
.
doc
;
if
(
typeof
handle
==
"number"
)
line
=
getLine
(
doc
,
clipLine
(
doc
,
handle
));
else
no
=
lineNo
(
handle
);
if
(
no
==
null
)
return
null
;
if
(
op
(
line
,
no
))
regChange
(
cm
,
no
,
no
+
1
);
else
return
null
;
return
line
;
}
function
findPosH
(
doc
,
pos
,
dir
,
unit
,
visually
)
{
var
line
=
pos
.
line
,
ch
=
pos
.
ch
,
origDir
=
dir
;
var
lineObj
=
getLine
(
doc
,
line
);
var
possible
=
true
;
function
findNextLine
()
{
var
l
=
line
+
dir
;
if
(
l
<
doc
.
first
||
l
>=
doc
.
first
+
doc
.
size
)
return
(
possible
=
false
);
line
=
l
;
return
lineObj
=
getLine
(
doc
,
l
);
}
function
moveOnce
(
boundToLine
)
{
var
next
=
(
visually
?
moveVisually
:
moveLogically
)(
lineObj
,
ch
,
dir
,
true
);
if
(
next
==
null
)
{
if
(
!
boundToLine
&&
findNextLine
())
{
if
(
visually
)
ch
=
(
dir
<
0
?
lineRight
:
lineLeft
)(
lineObj
);
else
ch
=
dir
<
0
?
lineObj
.
text
.
length
:
0
;
}
else
return
(
possible
=
false
);
}
else
ch
=
next
;
return
true
;
}
if
(
unit
==
"char"
)
moveOnce
();
else
if
(
unit
==
"column"
)
moveOnce
(
true
);
else
if
(
unit
==
"word"
||
unit
==
"group"
)
{
var
sawType
=
null
,
group
=
unit
==
"group"
;
for
(
var
first
=
true
;;
first
=
false
)
{
if
(
dir
<
0
&&
!
moveOnce
(
!
first
))
break
;
var
cur
=
lineObj
.
text
.
charAt
(
ch
)
||
"\n"
;
var
type
=
isWordChar
(
cur
)
?
"w"
:
!
group
?
null
:
/\s/
.
test
(
cur
)
?
null
:
"p"
;
if
(
sawType
&&
sawType
!=
type
)
{
if
(
dir
<
0
)
{
dir
=
1
;
moveOnce
();}
break
;
}
if
(
type
)
sawType
=
type
;
if
(
dir
>
0
&&
!
moveOnce
(
!
first
))
break
;
}
}
var
result
=
skipAtomic
(
doc
,
Pos
(
line
,
ch
),
origDir
,
true
);
if
(
!
possible
)
result
.
hitSide
=
true
;
return
result
;
}
function
findPosV
(
cm
,
pos
,
dir
,
unit
)
{
var
doc
=
cm
.
doc
,
x
=
pos
.
left
,
y
;
if
(
unit
==
"page"
)
{
var
pageSize
=
Math
.
min
(
cm
.
display
.
wrapper
.
clientHeight
,
window
.
innerHeight
||
document
.
documentElement
.
clientHeight
);
y
=
pos
.
top
+
dir
*
(
pageSize
-
(
dir
<
0
?
1.5
:
.5
)
*
textHeight
(
cm
.
display
));
}
else
if
(
unit
==
"line"
)
{
y
=
dir
>
0
?
pos
.
bottom
+
3
:
pos
.
top
-
3
;
}
for
(;;)
{
var
target
=
coordsChar
(
cm
,
x
,
y
);
if
(
!
target
.
outside
)
break
;
if
(
dir
<
0
?
y
<=
0
:
y
>=
doc
.
height
)
{
target
.
hitSide
=
true
;
break
;
}
y
+=
dir
*
5
;
}
return
target
;
}
function
findWordAt
(
line
,
pos
)
{
var
start
=
pos
.
ch
,
end
=
pos
.
ch
;
if
(
line
)
{
if
(
pos
.
xRel
<
0
||
end
==
line
.
length
)
--
start
;
else
++
end
;
var
startChar
=
line
.
charAt
(
start
);
var
check
=
isWordChar
(
startChar
)
?
isWordChar
:
/\s/
.
test
(
startChar
)
?
function
(
ch
)
{
return
/\s/
.
test
(
ch
);}
:
function
(
ch
)
{
return
!
/\s/
.
test
(
ch
)
&&
!
isWordChar
(
ch
);};
while
(
start
>
0
&&
check
(
line
.
charAt
(
start
-
1
)))
--
start
;
while
(
end
<
line
.
length
&&
check
(
line
.
charAt
(
end
)))
++
end
;
}
return
{
from
:
Pos
(
pos
.
line
,
start
),
to
:
Pos
(
pos
.
line
,
end
)};
}
function
selectLine
(
cm
,
line
)
{
extendSelection
(
cm
.
doc
,
Pos
(
line
,
0
),
clipPos
(
cm
.
doc
,
Pos
(
line
+
1
,
0
)));
}
// PROTOTYPE
// The publicly visible API. Note that operation(null, f) means
// 'wrap f in an operation, performed on its `this` parameter'
CodeMirror
.
prototype
=
{
constructor
:
CodeMirror
,
focus
:
function
(){
window
.
focus
();
focusInput
(
this
);
onFocus
(
this
);
fastPoll
(
this
);},
setOption
:
function
(
option
,
value
)
{
var
options
=
this
.
options
,
old
=
options
[
option
];
if
(
options
[
option
]
==
value
&&
option
!=
"mode"
)
return
;
options
[
option
]
=
value
;
if
(
optionHandlers
.
hasOwnProperty
(
option
))
operation
(
this
,
optionHandlers
[
option
])(
this
,
value
,
old
);
},
getOption
:
function
(
option
)
{
return
this
.
options
[
option
];},
getDoc
:
function
()
{
return
this
.
doc
;},
addKeyMap
:
function
(
map
,
bottom
)
{
this
.
state
.
keyMaps
[
bottom
?
"push"
:
"unshift"
](
map
);
},
removeKeyMap
:
function
(
map
)
{
var
maps
=
this
.
state
.
keyMaps
;
for
(
var
i
=
0
;
i
<
maps
.
length
;
++
i
)
if
((
typeof
map
==
"string"
?
maps
[
i
].
name
:
maps
[
i
])
==
map
)
{
maps
.
splice
(
i
,
1
);
return
true
;
}
},
addOverlay
:
operation
(
null
,
function
(
spec
,
options
)
{
var
mode
=
spec
.
token
?
spec
:
CodeMirror
.
getMode
(
this
.
options
,
spec
);
if
(
mode
.
startState
)
throw
new
Error
(
"Overlays may not be stateful."
);
this
.
state
.
overlays
.
push
({
mode
:
mode
,
modeSpec
:
spec
,
opaque
:
options
&&
options
.
opaque
});
this
.
state
.
modeGen
++
;
regChange
(
this
);
}),
removeOverlay
:
operation
(
null
,
function
(
spec
)
{
var
overlays
=
this
.
state
.
overlays
;
for
(
var
i
=
0
;
i
<
overlays
.
length
;
++
i
)
{
var
cur
=
overlays
[
i
].
modeSpec
;
if
(
cur
==
spec
||
typeof
spec
==
"string"
&&
cur
.
name
==
spec
)
{
overlays
.
splice
(
i
,
1
);
this
.
state
.
modeGen
++
;
regChange
(
this
);
return
;
}
}
}),
indentLine
:
operation
(
null
,
function
(
n
,
dir
,
aggressive
)
{
if
(
typeof
dir
!=
"string"
&&
typeof
dir
!=
"number"
)
{
if
(
dir
==
null
)
dir
=
this
.
options
.
smartIndent
?
"smart"
:
"prev"
;
else
dir
=
dir
?
"add"
:
"subtract"
;
}
if
(
isLine
(
this
.
doc
,
n
))
indentLine
(
this
,
n
,
dir
,
aggressive
);
}),
indentSelection
:
operation
(
null
,
function
(
how
)
{
var
sel
=
this
.
doc
.
sel
;
if
(
posEq
(
sel
.
from
,
sel
.
to
))
return
indentLine
(
this
,
sel
.
from
.
line
,
how
);
var
e
=
sel
.
to
.
line
-
(
sel
.
to
.
ch
?
0
:
1
);
for
(
var
i
=
sel
.
from
.
line
;
i
<=
e
;
++
i
)
indentLine
(
this
,
i
,
how
);
}),
// Fetch the parser token for a given character. Useful for hacks
// that want to inspect the mode state (say, for completion).
getTokenAt
:
function
(
pos
,
precise
)
{
var
doc
=
this
.
doc
;
pos
=
clipPos
(
doc
,
pos
);
var
state
=
getStateBefore
(
this
,
pos
.
line
,
precise
),
mode
=
this
.
doc
.
mode
;
var
line
=
getLine
(
doc
,
pos
.
line
);
var
stream
=
new
StringStream
(
line
.
text
,
this
.
options
.
tabSize
);
while
(
stream
.
pos
<
pos
.
ch
&&
!
stream
.
eol
())
{
stream
.
start
=
stream
.
pos
;
var
style
=
mode
.
token
(
stream
,
state
);
}
return
{
start
:
stream
.
start
,
end
:
stream
.
pos
,
string
:
stream
.
current
(),
className
:
style
||
null
,
// Deprecated, use 'type' instead
type
:
style
||
null
,
state
:
state
};
},
getTokenTypeAt
:
function
(
pos
)
{
pos
=
clipPos
(
this
.
doc
,
pos
);
var
styles
=
getLineStyles
(
this
,
getLine
(
this
.
doc
,
pos
.
line
));
var
before
=
0
,
after
=
(
styles
.
length
-
1
)
/
2
,
ch
=
pos
.
ch
;
for
(;;)
{
var
mid
=
(
before
+
after
)
>>
1
;
if
((
mid
?
styles
[
mid
*
2
-
1
]
:
0
)
>=
ch
)
after
=
mid
;
else
if
(
styles
[
mid
*
2
+
1
]
<
ch
)
before
=
mid
+
1
;
else
return
styles
[
mid
*
2
+
2
];
}
},
getStateAfter
:
function
(
line
,
precise
)
{
var
doc
=
this
.
doc
;
line
=
clipLine
(
doc
,
line
==
null
?
doc
.
first
+
doc
.
size
-
1
:
line
);
return
getStateBefore
(
this
,
line
+
1
,
precise
);
},
cursorCoords
:
function
(
start
,
mode
)
{
var
pos
,
sel
=
this
.
doc
.
sel
;
if
(
start
==
null
)
pos
=
sel
.
head
;
else
if
(
typeof
start
==
"object"
)
pos
=
clipPos
(
this
.
doc
,
start
);
else
pos
=
start
?
sel
.
from
:
sel
.
to
;
return
cursorCoords
(
this
,
pos
,
mode
||
"page"
);
},
charCoords
:
function
(
pos
,
mode
)
{
return
charCoords
(
this
,
clipPos
(
this
.
doc
,
pos
),
mode
||
"page"
);
},
coordsChar
:
function
(
coords
,
mode
)
{
coords
=
fromCoordSystem
(
this
,
coords
,
mode
||
"page"
);
return
coordsChar
(
this
,
coords
.
left
,
coords
.
top
);
},
lineAtHeight
:
function
(
height
,
mode
)
{
height
=
fromCoordSystem
(
this
,
{
top
:
height
,
left
:
0
},
mode
||
"page"
).
top
;
return
lineAtHeight
(
this
.
doc
,
height
+
this
.
display
.
viewOffset
);
},
heightAtLine
:
function
(
line
,
mode
)
{
var
end
=
false
,
last
=
this
.
doc
.
first
+
this
.
doc
.
size
-
1
;
if
(
line
<
this
.
doc
.
first
)
line
=
this
.
doc
.
first
;
else
if
(
line
>
last
)
{
line
=
last
;
end
=
true
;
}
var
lineObj
=
getLine
(
this
.
doc
,
line
);
return
intoCoordSystem
(
this
,
getLine
(
this
.
doc
,
line
),
{
top
:
0
,
left
:
0
},
mode
||
"page"
).
top
+
(
end
?
lineObj
.
height
:
0
);
},
defaultTextHeight
:
function
()
{
return
textHeight
(
this
.
display
);
},
defaultCharWidth
:
function
()
{
return
charWidth
(
this
.
display
);
},
setGutterMarker
:
operation
(
null
,
function
(
line
,
gutterID
,
value
)
{
return
changeLine
(
this
,
line
,
function
(
line
)
{
var
markers
=
line
.
gutterMarkers
||
(
line
.
gutterMarkers
=
{});
markers
[
gutterID
]
=
value
;
if
(
!
value
&&
isEmpty
(
markers
))
line
.
gutterMarkers
=
null
;
return
true
;
});
}),
clearGutter
:
operation
(
null
,
function
(
gutterID
)
{
var
cm
=
this
,
doc
=
cm
.
doc
,
i
=
doc
.
first
;
doc
.
iter
(
function
(
line
)
{
if
(
line
.
gutterMarkers
&&
line
.
gutterMarkers
[
gutterID
])
{
line
.
gutterMarkers
[
gutterID
]
=
null
;
regChange
(
cm
,
i
,
i
+
1
);
if
(
isEmpty
(
line
.
gutterMarkers
))
line
.
gutterMarkers
=
null
;
}
++
i
;
});
}),
addLineClass
:
operation
(
null
,
function
(
handle
,
where
,
cls
)
{
return
changeLine
(
this
,
handle
,
function
(
line
)
{
var
prop
=
where
==
"text"
?
"textClass"
:
where
==
"background"
?
"bgClass"
:
"wrapClass"
;
if
(
!
line
[
prop
])
line
[
prop
]
=
cls
;
else
if
(
new
RegExp
(
"(?:^|\\s)"
+
cls
+
"(?:$|\\s)"
).
test
(
line
[
prop
]))
return
false
;
else
line
[
prop
]
+=
" "
+
cls
;
return
true
;
});
}),
removeLineClass
:
operation
(
null
,
function
(
handle
,
where
,
cls
)
{
return
changeLine
(
this
,
handle
,
function
(
line
)
{
var
prop
=
where
==
"text"
?
"textClass"
:
where
==
"background"
?
"bgClass"
:
"wrapClass"
;
var
cur
=
line
[
prop
];
if
(
!
cur
)
return
false
;
else
if
(
cls
==
null
)
line
[
prop
]
=
null
;
else
{
var
found
=
cur
.
match
(
new
RegExp
(
"(?:^|\\s+)"
+
cls
+
"(?:$|\\s+)"
));
if
(
!
found
)
return
false
;
var
end
=
found
.
index
+
found
[
0
].
length
;
line
[
prop
]
=
cur
.
slice
(
0
,
found
.
index
)
+
(
!
found
.
index
||
end
==
cur
.
length
?
""
:
" "
)
+
cur
.
slice
(
end
)
||
null
;
}
return
true
;
});
}),
addLineWidget
:
operation
(
null
,
function
(
handle
,
node
,
options
)
{
return
addLineWidget
(
this
,
handle
,
node
,
options
);
}),
removeLineWidget
:
function
(
widget
)
{
widget
.
clear
();
},
lineInfo
:
function
(
line
)
{
if
(
typeof
line
==
"number"
)
{
if
(
!
isLine
(
this
.
doc
,
line
))
return
null
;
var
n
=
line
;
line
=
getLine
(
this
.
doc
,
line
);
if
(
!
line
)
return
null
;
}
else
{
var
n
=
lineNo
(
line
);
if
(
n
==
null
)
return
null
;
}
return
{
line
:
n
,
handle
:
line
,
text
:
line
.
text
,
gutterMarkers
:
line
.
gutterMarkers
,
textClass
:
line
.
textClass
,
bgClass
:
line
.
bgClass
,
wrapClass
:
line
.
wrapClass
,
widgets
:
line
.
widgets
};
},
getViewport
:
function
()
{
return
{
from
:
this
.
display
.
showingFrom
,
to
:
this
.
display
.
showingTo
};},
addWidget
:
function
(
pos
,
node
,
scroll
,
vert
,
horiz
)
{
var
display
=
this
.
display
;
pos
=
cursorCoords
(
this
,
clipPos
(
this
.
doc
,
pos
));
var
top
=
pos
.
bottom
,
left
=
pos
.
left
;
node
.
style
.
position
=
"absolute"
;
display
.
sizer
.
appendChild
(
node
);
if
(
vert
==
"over"
)
{
top
=
pos
.
top
;
}
else
if
(
vert
==
"above"
||
vert
==
"near"
)
{
var
vspace
=
Math
.
max
(
display
.
wrapper
.
clientHeight
,
this
.
doc
.
height
),
hspace
=
Math
.
max
(
display
.
sizer
.
clientWidth
,
display
.
lineSpace
.
clientWidth
);
// Default to positioning above (if specified and possible); otherwise default to positioning below
if
((
vert
==
'above'
||
pos
.
bottom
+
node
.
offsetHeight
>
vspace
)
&&
pos
.
top
>
node
.
offsetHeight
)
top
=
pos
.
top
-
node
.
offsetHeight
;
else
if
(
pos
.
bottom
+
node
.
offsetHeight
<=
vspace
)
top
=
pos
.
bottom
;
if
(
left
+
node
.
offsetWidth
>
hspace
)
left
=
hspace
-
node
.
offsetWidth
;
}
node
.
style
.
top
=
top
+
"px"
;
node
.
style
.
left
=
node
.
style
.
right
=
""
;
if
(
horiz
==
"right"
)
{
left
=
display
.
sizer
.
clientWidth
-
node
.
offsetWidth
;
node
.
style
.
right
=
"0px"
;
}
else
{
if
(
horiz
==
"left"
)
left
=
0
;
else
if
(
horiz
==
"middle"
)
left
=
(
display
.
sizer
.
clientWidth
-
node
.
offsetWidth
)
/
2
;
node
.
style
.
left
=
left
+
"px"
;
}
if
(
scroll
)
scrollIntoView
(
this
,
left
,
top
,
left
+
node
.
offsetWidth
,
top
+
node
.
offsetHeight
);
},
triggerOnKeyDown
:
operation
(
null
,
onKeyDown
),
execCommand
:
function
(
cmd
)
{
return
commands
[
cmd
](
this
);},
findPosH
:
function
(
from
,
amount
,
unit
,
visually
)
{
var
dir
=
1
;
if
(
amount
<
0
)
{
dir
=
-
1
;
amount
=
-
amount
;
}
for
(
var
i
=
0
,
cur
=
clipPos
(
this
.
doc
,
from
);
i
<
amount
;
++
i
)
{
cur
=
findPosH
(
this
.
doc
,
cur
,
dir
,
unit
,
visually
);
if
(
cur
.
hitSide
)
break
;
}
return
cur
;
},
moveH
:
operation
(
null
,
function
(
dir
,
unit
)
{
var
sel
=
this
.
doc
.
sel
,
pos
;
if
(
sel
.
shift
||
sel
.
extend
||
posEq
(
sel
.
from
,
sel
.
to
))
pos
=
findPosH
(
this
.
doc
,
sel
.
head
,
dir
,
unit
,
this
.
options
.
rtlMoveVisually
);
else
pos
=
dir
<
0
?
sel
.
from
:
sel
.
to
;
extendSelection
(
this
.
doc
,
pos
,
pos
,
dir
);
}),
deleteH
:
operation
(
null
,
function
(
dir
,
unit
)
{
var
sel
=
this
.
doc
.
sel
;
if
(
!
posEq
(
sel
.
from
,
sel
.
to
))
replaceRange
(
this
.
doc
,
""
,
sel
.
from
,
sel
.
to
,
"+delete"
);
else
replaceRange
(
this
.
doc
,
""
,
sel
.
from
,
findPosH
(
this
.
doc
,
sel
.
head
,
dir
,
unit
,
false
),
"+delete"
);
this
.
curOp
.
userSelChange
=
true
;
}),
findPosV
:
function
(
from
,
amount
,
unit
,
goalColumn
)
{
var
dir
=
1
,
x
=
goalColumn
;
if
(
amount
<
0
)
{
dir
=
-
1
;
amount
=
-
amount
;
}
for
(
var
i
=
0
,
cur
=
clipPos
(
this
.
doc
,
from
);
i
<
amount
;
++
i
)
{
var
coords
=
cursorCoords
(
this
,
cur
,
"div"
);
if
(
x
==
null
)
x
=
coords
.
left
;
else
coords
.
left
=
x
;
cur
=
findPosV
(
this
,
coords
,
dir
,
unit
);
if
(
cur
.
hitSide
)
break
;
}
return
cur
;
},
moveV
:
operation
(
null
,
function
(
dir
,
unit
)
{
var
sel
=
this
.
doc
.
sel
;
var
pos
=
cursorCoords
(
this
,
sel
.
head
,
"div"
);
if
(
sel
.
goalColumn
!=
null
)
pos
.
left
=
sel
.
goalColumn
;
var
target
=
findPosV
(
this
,
pos
,
dir
,
unit
);
if
(
unit
==
"page"
)
addToScrollPos
(
this
,
0
,
charCoords
(
this
,
target
,
"div"
).
top
-
pos
.
top
);
extendSelection
(
this
.
doc
,
target
,
target
,
dir
);
sel
.
goalColumn
=
pos
.
left
;
}),
toggleOverwrite
:
function
(
value
)
{
if
(
value
!=
null
&&
value
==
this
.
state
.
overwrite
)
return
;
if
(
this
.
state
.
overwrite
=
!
this
.
state
.
overwrite
)
this
.
display
.
cursor
.
className
+=
" CodeMirror-overwrite"
;
else
this
.
display
.
cursor
.
className
=
this
.
display
.
cursor
.
className
.
replace
(
" CodeMirror-overwrite"
,
""
);
},
hasFocus
:
function
()
{
return
this
.
state
.
focused
;
},
scrollTo
:
operation
(
null
,
function
(
x
,
y
)
{
updateScrollPos
(
this
,
x
,
y
);
}),
getScrollInfo
:
function
()
{
var
scroller
=
this
.
display
.
scroller
,
co
=
scrollerCutOff
;
return
{
left
:
scroller
.
scrollLeft
,
top
:
scroller
.
scrollTop
,
height
:
scroller
.
scrollHeight
-
co
,
width
:
scroller
.
scrollWidth
-
co
,
clientHeight
:
scroller
.
clientHeight
-
co
,
clientWidth
:
scroller
.
clientWidth
-
co
};
},
scrollIntoView
:
operation
(
null
,
function
(
pos
,
margin
)
{
if
(
typeof
pos
==
"number"
)
pos
=
Pos
(
pos
,
0
);
if
(
!
margin
)
margin
=
0
;
var
coords
=
pos
;
if
(
!
pos
||
pos
.
line
!=
null
)
{
this
.
curOp
.
scrollToPos
=
pos
?
clipPos
(
this
.
doc
,
pos
)
:
this
.
doc
.
sel
.
head
;
this
.
curOp
.
scrollToPosMargin
=
margin
;
coords
=
cursorCoords
(
this
,
this
.
curOp
.
scrollToPos
);
}
var
sPos
=
calculateScrollPos
(
this
,
coords
.
left
,
coords
.
top
-
margin
,
coords
.
right
,
coords
.
bottom
+
margin
);
updateScrollPos
(
this
,
sPos
.
scrollLeft
,
sPos
.
scrollTop
);
}),
setSize
:
function
(
width
,
height
)
{
function
interpret
(
val
)
{
return
typeof
val
==
"number"
||
/^\d+$/
.
test
(
String
(
val
))
?
val
+
"px"
:
val
;
}
if
(
width
!=
null
)
this
.
display
.
wrapper
.
style
.
width
=
interpret
(
width
);
if
(
height
!=
null
)
this
.
display
.
wrapper
.
style
.
height
=
interpret
(
height
);
this
.
refresh
();
},
on
:
function
(
type
,
f
)
{
on
(
this
,
type
,
f
);},
off
:
function
(
type
,
f
)
{
off
(
this
,
type
,
f
);},
operation
:
function
(
f
){
return
runInOp
(
this
,
f
);},
refresh
:
operation
(
null
,
function
()
{
clearCaches
(
this
);
updateScrollPos
(
this
,
this
.
doc
.
scrollLeft
,
this
.
doc
.
scrollTop
);
regChange
(
this
);
}),
swapDoc
:
operation
(
null
,
function
(
doc
)
{
var
old
=
this
.
doc
;
old
.
cm
=
null
;
attachDoc
(
this
,
doc
);
clearCaches
(
this
);
resetInput
(
this
,
true
);
updateScrollPos
(
this
,
doc
.
scrollLeft
,
doc
.
scrollTop
);
return
old
;
}),
getInputField
:
function
(){
return
this
.
display
.
input
;},
getWrapperElement
:
function
(){
return
this
.
display
.
wrapper
;},
getScrollerElement
:
function
(){
return
this
.
display
.
scroller
;},
getGutterElement
:
function
(){
return
this
.
display
.
gutters
;}
};
// OPTION DEFAULTS
var
optionHandlers
=
CodeMirror
.
optionHandlers
=
{};
// The default configuration options.
var
defaults
=
CodeMirror
.
defaults
=
{};
function
option
(
name
,
deflt
,
handle
,
notOnInit
)
{
CodeMirror
.
defaults
[
name
]
=
deflt
;
if
(
handle
)
optionHandlers
[
name
]
=
notOnInit
?
function
(
cm
,
val
,
old
)
{
if
(
old
!=
Init
)
handle
(
cm
,
val
,
old
);}
:
handle
;
}
var
Init
=
CodeMirror
.
Init
=
{
toString
:
function
(){
return
"CodeMirror.Init"
;}};
// These two are, on init, called from the constructor because they
// have to be initialized before the editor can start at all.
option
(
"value"
,
""
,
function
(
cm
,
val
)
{
cm
.
setValue
(
val
);
},
true
);
option
(
"mode"
,
null
,
function
(
cm
,
val
)
{
cm
.
doc
.
modeOption
=
val
;
loadMode
(
cm
);
},
true
);
option
(
"indentUnit"
,
2
,
loadMode
,
true
);
option
(
"indentWithTabs"
,
false
);
option
(
"smartIndent"
,
true
);
option
(
"tabSize"
,
4
,
function
(
cm
)
{
loadMode
(
cm
);
clearCaches
(
cm
);
regChange
(
cm
);
},
true
);
option
(
"electricChars"
,
true
);
option
(
"rtlMoveVisually"
,
!
windows
);
option
(
"theme"
,
"default"
,
function
(
cm
)
{
themeChanged
(
cm
);
guttersChanged
(
cm
);
},
true
);
option
(
"keyMap"
,
"default"
,
keyMapChanged
);
option
(
"extraKeys"
,
null
);
option
(
"onKeyEvent"
,
null
);
option
(
"onDragEvent"
,
null
);
option
(
"lineWrapping"
,
false
,
wrappingChanged
,
true
);
option
(
"gutters"
,
[],
function
(
cm
)
{
setGuttersForLineNumbers
(
cm
.
options
);
guttersChanged
(
cm
);
},
true
);
option
(
"fixedGutter"
,
true
,
function
(
cm
,
val
)
{
cm
.
display
.
gutters
.
style
.
left
=
val
?
compensateForHScroll
(
cm
.
display
)
+
"px"
:
"0"
;
cm
.
refresh
();
},
true
);
option
(
"coverGutterNextToScrollbar"
,
false
,
updateScrollbars
,
true
);
option
(
"lineNumbers"
,
false
,
function
(
cm
)
{
setGuttersForLineNumbers
(
cm
.
options
);
guttersChanged
(
cm
);
},
true
);
option
(
"firstLineNumber"
,
1
,
guttersChanged
,
true
);
option
(
"lineNumberFormatter"
,
function
(
integer
)
{
return
integer
;},
guttersChanged
,
true
);
option
(
"showCursorWhenSelecting"
,
false
,
updateSelection
,
true
);
option
(
"readOnly"
,
false
,
function
(
cm
,
val
)
{
if
(
val
==
"nocursor"
)
{
onBlur
(
cm
);
cm
.
display
.
input
.
blur
();}
else
if
(
!
val
)
resetInput
(
cm
,
true
);
});
option
(
"dragDrop"
,
true
);
option
(
"cursorBlinkRate"
,
530
);
option
(
"cursorScrollMargin"
,
0
);
option
(
"cursorHeight"
,
1
);
option
(
"workTime"
,
100
);
option
(
"workDelay"
,
100
);
option
(
"flattenSpans"
,
true
);
option
(
"pollInterval"
,
100
);
option
(
"undoDepth"
,
40
,
function
(
cm
,
val
){
cm
.
doc
.
history
.
undoDepth
=
val
;});
option
(
"historyEventDelay"
,
500
);
option
(
"viewportMargin"
,
10
,
function
(
cm
){
cm
.
refresh
();},
true
);
option
(
"maxHighlightLength"
,
10000
,
function
(
cm
){
loadMode
(
cm
);
cm
.
refresh
();},
true
);
option
(
"moveInputWithCursor"
,
true
,
function
(
cm
,
val
)
{
if
(
!
val
)
cm
.
display
.
inputDiv
.
style
.
top
=
cm
.
display
.
inputDiv
.
style
.
left
=
0
;
});
option
(
"tabindex"
,
null
,
function
(
cm
,
val
)
{
cm
.
display
.
input
.
tabIndex
=
val
||
""
;
});
option
(
"autofocus"
,
null
);
// MODE DEFINITION AND QUERYING
// Known modes, by name and by MIME
var
modes
=
CodeMirror
.
modes
=
{},
mimeModes
=
CodeMirror
.
mimeModes
=
{};
CodeMirror
.
defineMode
=
function
(
name
,
mode
)
{
if
(
!
CodeMirror
.
defaults
.
mode
&&
name
!=
"null"
)
CodeMirror
.
defaults
.
mode
=
name
;
if
(
arguments
.
length
>
2
)
{
mode
.
dependencies
=
[];
for
(
var
i
=
2
;
i
<
arguments
.
length
;
++
i
)
mode
.
dependencies
.
push
(
arguments
[
i
]);
}
modes
[
name
]
=
mode
;
};
CodeMirror
.
defineMIME
=
function
(
mime
,
spec
)
{
mimeModes
[
mime
]
=
spec
;
};
CodeMirror
.
resolveMode
=
function
(
spec
)
{
if
(
typeof
spec
==
"string"
&&
mimeModes
.
hasOwnProperty
(
spec
))
{
spec
=
mimeModes
[
spec
];
}
else
if
(
spec
&&
typeof
spec
.
name
==
"string"
&&
mimeModes
.
hasOwnProperty
(
spec
.
name
))
{
var
found
=
mimeModes
[
spec
.
name
];
spec
=
createObj
(
found
,
spec
);
spec
.
name
=
found
.
name
;
}
else
if
(
typeof
spec
==
"string"
&&
/^[\w\-]+\/[\w\-]+\+xml$/
.
test
(
spec
))
{
return
CodeMirror
.
resolveMode
(
"application/xml"
);
}
if
(
typeof
spec
==
"string"
)
return
{
name
:
spec
};
else
return
spec
||
{
name
:
"null"
};
};
CodeMirror
.
getMode
=
function
(
options
,
spec
)
{
spec
=
CodeMirror
.
resolveMode
(
spec
);
var
mfactory
=
modes
[
spec
.
name
];
if
(
!
mfactory
)
return
CodeMirror
.
getMode
(
options
,
"text/plain"
);
var
modeObj
=
mfactory
(
options
,
spec
);
if
(
modeExtensions
.
hasOwnProperty
(
spec
.
name
))
{
var
exts
=
modeExtensions
[
spec
.
name
];
for
(
var
prop
in
exts
)
{
if
(
!
exts
.
hasOwnProperty
(
prop
))
continue
;
if
(
modeObj
.
hasOwnProperty
(
prop
))
modeObj
[
"_"
+
prop
]
=
modeObj
[
prop
];
modeObj
[
prop
]
=
exts
[
prop
];
}
}
modeObj
.
name
=
spec
.
name
;
return
modeObj
;
};
CodeMirror
.
defineMode
(
"null"
,
function
()
{
return
{
token
:
function
(
stream
)
{
stream
.
skipToEnd
();}};
});
CodeMirror
.
defineMIME
(
"text/plain"
,
"null"
);
var
modeExtensions
=
CodeMirror
.
modeExtensions
=
{};
CodeMirror
.
extendMode
=
function
(
mode
,
properties
)
{
var
exts
=
modeExtensions
.
hasOwnProperty
(
mode
)
?
modeExtensions
[
mode
]
:
(
modeExtensions
[
mode
]
=
{});
copyObj
(
properties
,
exts
);
};
// EXTENSIONS
CodeMirror
.
defineExtension
=
function
(
name
,
func
)
{
CodeMirror
.
prototype
[
name
]
=
func
;
};
CodeMirror
.
defineDocExtension
=
function
(
name
,
func
)
{
Doc
.
prototype
[
name
]
=
func
;
};
CodeMirror
.
defineOption
=
option
;
var
initHooks
=
[];
CodeMirror
.
defineInitHook
=
function
(
f
)
{
initHooks
.
push
(
f
);};
// MODE STATE HANDLING
// Utility functions for working with state. Exported because modes
// sometimes need to do this.
function
copyState
(
mode
,
state
)
{
if
(
state
===
true
)
return
state
;
if
(
mode
.
copyState
)
return
mode
.
copyState
(
state
);
var
nstate
=
{};
for
(
var
n
in
state
)
{
var
val
=
state
[
n
];
if
(
val
instanceof
Array
)
val
=
val
.
concat
([]);
nstate
[
n
]
=
val
;
}
return
nstate
;
}
CodeMirror
.
copyState
=
copyState
;
function
startState
(
mode
,
a1
,
a2
)
{
return
mode
.
startState
?
mode
.
startState
(
a1
,
a2
)
:
true
;
}
CodeMirror
.
startState
=
startState
;
CodeMirror
.
innerMode
=
function
(
mode
,
state
)
{
while
(
mode
.
innerMode
)
{
var
info
=
mode
.
innerMode
(
state
);
state
=
info
.
state
;
mode
=
info
.
mode
;
}
return
info
||
{
mode
:
mode
,
state
:
state
};
};
// STANDARD COMMANDS
var
commands
=
CodeMirror
.
commands
=
{
selectAll
:
function
(
cm
)
{
cm
.
setSelection
(
Pos
(
cm
.
firstLine
(),
0
),
Pos
(
cm
.
lastLine
()));},
killLine
:
function
(
cm
)
{
var
from
=
cm
.
getCursor
(
true
),
to
=
cm
.
getCursor
(
false
),
sel
=
!
posEq
(
from
,
to
);
if
(
!
sel
&&
cm
.
getLine
(
from
.
line
).
length
==
from
.
ch
)
cm
.
replaceRange
(
""
,
from
,
Pos
(
from
.
line
+
1
,
0
),
"+delete"
);
else
cm
.
replaceRange
(
""
,
from
,
sel
?
to
:
Pos
(
from
.
line
),
"+delete"
);
},
deleteLine
:
function
(
cm
)
{
var
l
=
cm
.
getCursor
().
line
;
cm
.
replaceRange
(
""
,
Pos
(
l
,
0
),
Pos
(
l
),
"+delete"
);
},
delLineLeft
:
function
(
cm
)
{
var
cur
=
cm
.
getCursor
();
cm
.
replaceRange
(
""
,
Pos
(
cur
.
line
,
0
),
cur
,
"+delete"
);
},
undo
:
function
(
cm
)
{
cm
.
undo
();},
redo
:
function
(
cm
)
{
cm
.
redo
();},
goDocStart
:
function
(
cm
)
{
cm
.
extendSelection
(
Pos
(
cm
.
firstLine
(),
0
));},
goDocEnd
:
function
(
cm
)
{
cm
.
extendSelection
(
Pos
(
cm
.
lastLine
()));},
goLineStart
:
function
(
cm
)
{
cm
.
extendSelection
(
lineStart
(
cm
,
cm
.
getCursor
().
line
));
},
goLineStartSmart
:
function
(
cm
)
{
var
cur
=
cm
.
getCursor
(),
start
=
lineStart
(
cm
,
cur
.
line
);
var
line
=
cm
.
getLineHandle
(
start
.
line
);
var
order
=
getOrder
(
line
);
if
(
!
order
||
order
[
0
].
level
==
0
)
{
var
firstNonWS
=
Math
.
max
(
0
,
line
.
text
.
search
(
/\S/
));
var
inWS
=
cur
.
line
==
start
.
line
&&
cur
.
ch
<=
firstNonWS
&&
cur
.
ch
;
cm
.
extendSelection
(
Pos
(
start
.
line
,
inWS
?
0
:
firstNonWS
));
}
else
cm
.
extendSelection
(
start
);
},
goLineEnd
:
function
(
cm
)
{
cm
.
extendSelection
(
lineEnd
(
cm
,
cm
.
getCursor
().
line
));
},
goLineRight
:
function
(
cm
)
{
var
top
=
cm
.
charCoords
(
cm
.
getCursor
(),
"div"
).
top
+
5
;
cm
.
extendSelection
(
cm
.
coordsChar
({
left
:
cm
.
display
.
lineDiv
.
offsetWidth
+
100
,
top
:
top
},
"div"
));
},
goLineLeft
:
function
(
cm
)
{
var
top
=
cm
.
charCoords
(
cm
.
getCursor
(),
"div"
).
top
+
5
;
cm
.
extendSelection
(
cm
.
coordsChar
({
left
:
0
,
top
:
top
},
"div"
));
},
goLineUp
:
function
(
cm
)
{
cm
.
moveV
(
-
1
,
"line"
);},
goLineDown
:
function
(
cm
)
{
cm
.
moveV
(
1
,
"line"
);},
goPageUp
:
function
(
cm
)
{
cm
.
moveV
(
-
1
,
"page"
);},
goPageDown
:
function
(
cm
)
{
cm
.
moveV
(
1
,
"page"
);},
goCharLeft
:
function
(
cm
)
{
cm
.
moveH
(
-
1
,
"char"
);},
goCharRight
:
function
(
cm
)
{
cm
.
moveH
(
1
,
"char"
);},
goColumnLeft
:
function
(
cm
)
{
cm
.
moveH
(
-
1
,
"column"
);},
goColumnRight
:
function
(
cm
)
{
cm
.
moveH
(
1
,
"column"
);},
goWordLeft
:
function
(
cm
)
{
cm
.
moveH
(
-
1
,
"word"
);},
goGroupRight
:
function
(
cm
)
{
cm
.
moveH
(
1
,
"group"
);},
goGroupLeft
:
function
(
cm
)
{
cm
.
moveH
(
-
1
,
"group"
);},
goWordRight
:
function
(
cm
)
{
cm
.
moveH
(
1
,
"word"
);},
delCharBefore
:
function
(
cm
)
{
cm
.
deleteH
(
-
1
,
"char"
);},
delCharAfter
:
function
(
cm
)
{
cm
.
deleteH
(
1
,
"char"
);},
delWordBefore
:
function
(
cm
)
{
cm
.
deleteH
(
-
1
,
"word"
);},
delWordAfter
:
function
(
cm
)
{
cm
.
deleteH
(
1
,
"word"
);},
delGroupBefore
:
function
(
cm
)
{
cm
.
deleteH
(
-
1
,
"group"
);},
delGroupAfter
:
function
(
cm
)
{
cm
.
deleteH
(
1
,
"group"
);},
indentAuto
:
function
(
cm
)
{
cm
.
indentSelection
(
"smart"
);},
indentMore
:
function
(
cm
)
{
cm
.
indentSelection
(
"add"
);},
indentLess
:
function
(
cm
)
{
cm
.
indentSelection
(
"subtract"
);},
insertTab
:
function
(
cm
)
{
cm
.
replaceSelection
(
"\t"
,
"end"
,
"+input"
);},
defaultTab
:
function
(
cm
)
{
if
(
cm
.
somethingSelected
())
cm
.
indentSelection
(
"add"
);
else
cm
.
replaceSelection
(
"\t"
,
"end"
,
"+input"
);
},
transposeChars
:
function
(
cm
)
{
var
cur
=
cm
.
getCursor
(),
line
=
cm
.
getLine
(
cur
.
line
);
if
(
cur
.
ch
>
0
&&
cur
.
ch
<
line
.
length
-
1
)
cm
.
replaceRange
(
line
.
charAt
(
cur
.
ch
)
+
line
.
charAt
(
cur
.
ch
-
1
),
Pos
(
cur
.
line
,
cur
.
ch
-
1
),
Pos
(
cur
.
line
,
cur
.
ch
+
1
));
},
newlineAndIndent
:
function
(
cm
)
{
operation
(
cm
,
function
()
{
cm
.
replaceSelection
(
"\n"
,
"end"
,
"+input"
);
cm
.
indentLine
(
cm
.
getCursor
().
line
,
null
,
true
);
})();
},
toggleOverwrite
:
function
(
cm
)
{
cm
.
toggleOverwrite
();}
};
// STANDARD KEYMAPS
var
keyMap
=
CodeMirror
.
keyMap
=
{};
keyMap
.
basic
=
{
"Left"
:
"goCharLeft"
,
"Right"
:
"goCharRight"
,
"Up"
:
"goLineUp"
,
"Down"
:
"goLineDown"
,
"End"
:
"goLineEnd"
,
"Home"
:
"goLineStartSmart"
,
"PageUp"
:
"goPageUp"
,
"PageDown"
:
"goPageDown"
,
"Delete"
:
"delCharAfter"
,
"Backspace"
:
"delCharBefore"
,
"Tab"
:
"defaultTab"
,
"Shift-Tab"
:
"indentAuto"
,
"Enter"
:
"newlineAndIndent"
,
"Insert"
:
"toggleOverwrite"
};
// Note that the save and find-related commands aren't defined by
// default. Unknown commands are simply ignored.
keyMap
.
pcDefault
=
{
"Ctrl-A"
:
"selectAll"
,
"Ctrl-D"
:
"deleteLine"
,
"Ctrl-Z"
:
"undo"
,
"Shift-Ctrl-Z"
:
"redo"
,
"Ctrl-Y"
:
"redo"
,
"Ctrl-Home"
:
"goDocStart"
,
"Alt-Up"
:
"goDocStart"
,
"Ctrl-End"
:
"goDocEnd"
,
"Ctrl-Down"
:
"goDocEnd"
,
"Ctrl-Left"
:
"goGroupLeft"
,
"Ctrl-Right"
:
"goGroupRight"
,
"Alt-Left"
:
"goLineStart"
,
"Alt-Right"
:
"goLineEnd"
,
"Ctrl-Backspace"
:
"delGroupBefore"
,
"Ctrl-Delete"
:
"delGroupAfter"
,
"Ctrl-S"
:
"save"
,
"Ctrl-F"
:
"find"
,
"Ctrl-G"
:
"findNext"
,
"Shift-Ctrl-G"
:
"findPrev"
,
"Shift-Ctrl-F"
:
"replace"
,
"Shift-Ctrl-R"
:
"replaceAll"
,
"Ctrl-["
:
"indentLess"
,
"Ctrl-]"
:
"indentMore"
,
fallthrough
:
"basic"
};
keyMap
.
macDefault
=
{
"Cmd-A"
:
"selectAll"
,
"Cmd-D"
:
"deleteLine"
,
"Cmd-Z"
:
"undo"
,
"Shift-Cmd-Z"
:
"redo"
,
"Cmd-Y"
:
"redo"
,
"Cmd-Up"
:
"goDocStart"
,
"Cmd-End"
:
"goDocEnd"
,
"Cmd-Down"
:
"goDocEnd"
,
"Alt-Left"
:
"goGroupLeft"
,
"Alt-Right"
:
"goGroupRight"
,
"Cmd-Left"
:
"goLineStart"
,
"Cmd-Right"
:
"goLineEnd"
,
"Alt-Backspace"
:
"delGroupBefore"
,
"Ctrl-Alt-Backspace"
:
"delGroupAfter"
,
"Alt-Delete"
:
"delGroupAfter"
,
"Cmd-S"
:
"save"
,
"Cmd-F"
:
"find"
,
"Cmd-G"
:
"findNext"
,
"Shift-Cmd-G"
:
"findPrev"
,
"Cmd-Alt-F"
:
"replace"
,
"Shift-Cmd-Alt-F"
:
"replaceAll"
,
"Cmd-["
:
"indentLess"
,
"Cmd-]"
:
"indentMore"
,
"Cmd-Backspace"
:
"delLineLeft"
,
fallthrough
:
[
"basic"
,
"emacsy"
]
};
keyMap
[
"default"
]
=
mac
?
keyMap
.
macDefault
:
keyMap
.
pcDefault
;
keyMap
.
emacsy
=
{
"Ctrl-F"
:
"goCharRight"
,
"Ctrl-B"
:
"goCharLeft"
,
"Ctrl-P"
:
"goLineUp"
,
"Ctrl-N"
:
"goLineDown"
,
"Alt-F"
:
"goWordRight"
,
"Alt-B"
:
"goWordLeft"
,
"Ctrl-A"
:
"goLineStart"
,
"Ctrl-E"
:
"goLineEnd"
,
"Ctrl-V"
:
"goPageDown"
,
"Shift-Ctrl-V"
:
"goPageUp"
,
"Ctrl-D"
:
"delCharAfter"
,
"Ctrl-H"
:
"delCharBefore"
,
"Alt-D"
:
"delWordAfter"
,
"Alt-Backspace"
:
"delWordBefore"
,
"Ctrl-K"
:
"killLine"
,
"Ctrl-T"
:
"transposeChars"
};
// KEYMAP DISPATCH
function
getKeyMap
(
val
)
{
if
(
typeof
val
==
"string"
)
return
keyMap
[
val
];
else
return
val
;
}
function
lookupKey
(
name
,
maps
,
handle
)
{
function
lookup
(
map
)
{
map
=
getKeyMap
(
map
);
var
found
=
map
[
name
];
if
(
found
===
false
)
return
"stop"
;
if
(
found
!=
null
&&
handle
(
found
))
return
true
;
if
(
map
.
nofallthrough
)
return
"stop"
;
var
fallthrough
=
map
.
fallthrough
;
if
(
fallthrough
==
null
)
return
false
;
if
(
Object
.
prototype
.
toString
.
call
(
fallthrough
)
!=
"[object Array]"
)
return
lookup
(
fallthrough
);
for
(
var
i
=
0
,
e
=
fallthrough
.
length
;
i
<
e
;
++
i
)
{
var
done
=
lookup
(
fallthrough
[
i
]);
if
(
done
)
return
done
;
}
return
false
;
}
for
(
var
i
=
0
;
i
<
maps
.
length
;
++
i
)
{
var
done
=
lookup
(
maps
[
i
]);
if
(
done
)
return
done
!=
"stop"
;
}
}
function
isModifierKey
(
event
)
{
var
name
=
keyNames
[
event
.
keyCode
];
return
name
==
"Ctrl"
||
name
==
"Alt"
||
name
==
"Shift"
||
name
==
"Mod"
;
}
function
keyName
(
event
,
noShift
)
{
if
(
opera
&&
event
.
keyCode
==
34
&&
event
[
"char"
])
return
false
;
var
name
=
keyNames
[
event
.
keyCode
];
if
(
name
==
null
||
event
.
altGraphKey
)
return
false
;
if
(
event
.
altKey
)
name
=
"Alt-"
+
name
;
if
(
flipCtrlCmd
?
event
.
metaKey
:
event
.
ctrlKey
)
name
=
"Ctrl-"
+
name
;
if
(
flipCtrlCmd
?
event
.
ctrlKey
:
event
.
metaKey
)
name
=
"Cmd-"
+
name
;
if
(
!
noShift
&&
event
.
shiftKey
)
name
=
"Shift-"
+
name
;
return
name
;
}
CodeMirror
.
lookupKey
=
lookupKey
;
CodeMirror
.
isModifierKey
=
isModifierKey
;
CodeMirror
.
keyName
=
keyName
;
// FROMTEXTAREA
CodeMirror
.
fromTextArea
=
function
(
textarea
,
options
)
{
if
(
!
options
)
options
=
{};
options
.
value
=
textarea
.
value
;
if
(
!
options
.
tabindex
&&
textarea
.
tabindex
)
options
.
tabindex
=
textarea
.
tabindex
;
if
(
!
options
.
placeholder
&&
textarea
.
placeholder
)
options
.
placeholder
=
textarea
.
placeholder
;
// Set autofocus to true if this textarea is focused, or if it has
// autofocus and no other element is focused.
if
(
options
.
autofocus
==
null
)
{
var
hasFocus
=
document
.
body
;
// doc.activeElement occasionally throws on IE
try
{
hasFocus
=
document
.
activeElement
;
}
catch
(
e
)
{}
options
.
autofocus
=
hasFocus
==
textarea
||
textarea
.
getAttribute
(
"autofocus"
)
!=
null
&&
hasFocus
==
document
.
body
;
}
function
save
()
{
textarea
.
value
=
cm
.
getValue
();}
if
(
textarea
.
form
)
{
on
(
textarea
.
form
,
"submit"
,
save
);
// Deplorable hack to make the submit method do the right thing.
if
(
!
options
.
leaveSubmitMethodAlone
)
{
var
form
=
textarea
.
form
,
realSubmit
=
form
.
submit
;
try
{
var
wrappedSubmit
=
form
.
submit
=
function
()
{
save
();
form
.
submit
=
realSubmit
;
form
.
submit
();
form
.
submit
=
wrappedSubmit
;
};
}
catch
(
e
)
{}
}
}
textarea
.
style
.
display
=
"none"
;
var
cm
=
CodeMirror
(
function
(
node
)
{
textarea
.
parentNode
.
insertBefore
(
node
,
textarea
.
nextSibling
);
},
options
);
cm
.
save
=
save
;
cm
.
getTextArea
=
function
()
{
return
textarea
;
};
cm
.
toTextArea
=
function
()
{
save
();
textarea
.
parentNode
.
removeChild
(
cm
.
getWrapperElement
());
textarea
.
style
.
display
=
""
;
if
(
textarea
.
form
)
{
off
(
textarea
.
form
,
"submit"
,
save
);
if
(
typeof
textarea
.
form
.
submit
==
"function"
)
textarea
.
form
.
submit
=
realSubmit
;
}
};
return
cm
;
};
// STRING STREAM
// Fed to the mode parsers, provides helper functions to make
// parsers more succinct.
// The character stream used by a mode's parser.
function
StringStream
(
string
,
tabSize
)
{
this
.
pos
=
this
.
start
=
0
;
this
.
string
=
string
;
this
.
tabSize
=
tabSize
||
8
;
this
.
lastColumnPos
=
this
.
lastColumnValue
=
0
;
}
StringStream
.
prototype
=
{
eol
:
function
()
{
return
this
.
pos
>=
this
.
string
.
length
;},
sol
:
function
()
{
return
this
.
pos
==
0
;},
peek
:
function
()
{
return
this
.
string
.
charAt
(
this
.
pos
)
||
undefined
;},
next
:
function
()
{
if
(
this
.
pos
<
this
.
string
.
length
)
return
this
.
string
.
charAt
(
this
.
pos
++
);
},
eat
:
function
(
match
)
{
var
ch
=
this
.
string
.
charAt
(
this
.
pos
);
if
(
typeof
match
==
"string"
)
var
ok
=
ch
==
match
;
else
var
ok
=
ch
&&
(
match
.
test
?
match
.
test
(
ch
)
:
match
(
ch
));
if
(
ok
)
{
++
this
.
pos
;
return
ch
;}
},
eatWhile
:
function
(
match
)
{
var
start
=
this
.
pos
;
while
(
this
.
eat
(
match
)){}
return
this
.
pos
>
start
;
},
eatSpace
:
function
()
{
var
start
=
this
.
pos
;
while
(
/[\s\u00a0]/
.
test
(
this
.
string
.
charAt
(
this
.
pos
)))
++
this
.
pos
;
return
this
.
pos
>
start
;
},
skipToEnd
:
function
()
{
this
.
pos
=
this
.
string
.
length
;},
skipTo
:
function
(
ch
)
{
var
found
=
this
.
string
.
indexOf
(
ch
,
this
.
pos
);
if
(
found
>
-
1
)
{
this
.
pos
=
found
;
return
true
;}
},
backUp
:
function
(
n
)
{
this
.
pos
-=
n
;},
column
:
function
()
{
if
(
this
.
lastColumnPos
<
this
.
start
)
{
this
.
lastColumnValue
=
countColumn
(
this
.
string
,
this
.
start
,
this
.
tabSize
,
this
.
lastColumnPos
,
this
.
lastColumnValue
);
this
.
lastColumnPos
=
this
.
start
;
}
return
this
.
lastColumnValue
;
},
indentation
:
function
()
{
return
countColumn
(
this
.
string
,
null
,
this
.
tabSize
);},
match
:
function
(
pattern
,
consume
,
caseInsensitive
)
{
if
(
typeof
pattern
==
"string"
)
{
var
cased
=
function
(
str
)
{
return
caseInsensitive
?
str
.
toLowerCase
()
:
str
;};
var
substr
=
this
.
string
.
substr
(
this
.
pos
,
pattern
.
length
);
if
(
cased
(
substr
)
==
cased
(
pattern
))
{
if
(
consume
!==
false
)
this
.
pos
+=
pattern
.
length
;
return
true
;
}
}
else
{
var
match
=
this
.
string
.
slice
(
this
.
pos
).
match
(
pattern
);
if
(
match
&&
match
.
index
>
0
)
return
null
;
if
(
match
&&
consume
!==
false
)
this
.
pos
+=
match
[
0
].
length
;
return
match
;
}
},
current
:
function
(){
return
this
.
string
.
slice
(
this
.
start
,
this
.
pos
);}
};
CodeMirror
.
StringStream
=
StringStream
;
// TEXTMARKERS
function
TextMarker
(
doc
,
type
)
{
this
.
lines
=
[];
this
.
type
=
type
;
this
.
doc
=
doc
;
}
CodeMirror
.
TextMarker
=
TextMarker
;
TextMarker
.
prototype
.
clear
=
function
()
{
if
(
this
.
explicitlyCleared
)
return
;
var
cm
=
this
.
doc
.
cm
,
withOp
=
cm
&&
!
cm
.
curOp
;
if
(
withOp
)
startOperation
(
cm
);
var
min
=
null
,
max
=
null
;
for
(
var
i
=
0
;
i
<
this
.
lines
.
length
;
++
i
)
{
var
line
=
this
.
lines
[
i
];
var
span
=
getMarkedSpanFor
(
line
.
markedSpans
,
this
);
if
(
span
.
to
!=
null
)
max
=
lineNo
(
line
);
line
.
markedSpans
=
removeMarkedSpan
(
line
.
markedSpans
,
span
);
if
(
span
.
from
!=
null
)
min
=
lineNo
(
line
);
else
if
(
this
.
collapsed
&&
!
lineIsHidden
(
this
.
doc
,
line
)
&&
cm
)
updateLineHeight
(
line
,
textHeight
(
cm
.
display
));
}
if
(
cm
&&
this
.
collapsed
&&
!
cm
.
options
.
lineWrapping
)
for
(
var
i
=
0
;
i
<
this
.
lines
.
length
;
++
i
)
{
var
visual
=
visualLine
(
cm
.
doc
,
this
.
lines
[
i
]),
len
=
lineLength
(
cm
.
doc
,
visual
);
if
(
len
>
cm
.
display
.
maxLineLength
)
{
cm
.
display
.
maxLine
=
visual
;
cm
.
display
.
maxLineLength
=
len
;
cm
.
display
.
maxLineChanged
=
true
;
}
}
if
(
min
!=
null
&&
cm
)
regChange
(
cm
,
min
,
max
+
1
);
this
.
lines
.
length
=
0
;
this
.
explicitlyCleared
=
true
;
if
(
this
.
atomic
&&
this
.
doc
.
cantEdit
)
{
this
.
doc
.
cantEdit
=
false
;
if
(
cm
)
reCheckSelection
(
cm
);
}
if
(
withOp
)
endOperation
(
cm
);
signalLater
(
this
,
"clear"
);
};
TextMarker
.
prototype
.
find
=
function
()
{
var
from
,
to
;
for
(
var
i
=
0
;
i
<
this
.
lines
.
length
;
++
i
)
{
var
line
=
this
.
lines
[
i
];
var
span
=
getMarkedSpanFor
(
line
.
markedSpans
,
this
);
if
(
span
.
from
!=
null
||
span
.
to
!=
null
)
{
var
found
=
lineNo
(
line
);
if
(
span
.
from
!=
null
)
from
=
Pos
(
found
,
span
.
from
);
if
(
span
.
to
!=
null
)
to
=
Pos
(
found
,
span
.
to
);
}
}
if
(
this
.
type
==
"bookmark"
)
return
from
;
return
from
&&
{
from
:
from
,
to
:
to
};
};
TextMarker
.
prototype
.
changed
=
function
()
{
var
pos
=
this
.
find
(),
cm
=
this
.
doc
.
cm
;
if
(
!
pos
||
!
cm
)
return
;
var
line
=
getLine
(
this
.
doc
,
pos
.
from
.
line
);
clearCachedMeasurement
(
cm
,
line
);
if
(
pos
.
from
.
line
>=
cm
.
display
.
showingFrom
&&
pos
.
from
.
line
<
cm
.
display
.
showingTo
)
{
for
(
var
node
=
cm
.
display
.
lineDiv
.
firstChild
;
node
;
node
=
node
.
nextSibling
)
if
(
node
.
lineObj
==
line
)
{
if
(
node
.
offsetHeight
!=
line
.
height
)
updateLineHeight
(
line
,
node
.
offsetHeight
);
break
;
}
runInOp
(
cm
,
function
()
{
cm
.
curOp
.
selectionChanged
=
true
;
});
}
};
TextMarker
.
prototype
.
attachLine
=
function
(
line
)
{
if
(
!
this
.
lines
.
length
&&
this
.
doc
.
cm
)
{
var
op
=
this
.
doc
.
cm
.
curOp
;
if
(
!
op
.
maybeHiddenMarkers
||
indexOf
(
op
.
maybeHiddenMarkers
,
this
)
==
-
1
)
(
op
.
maybeUnhiddenMarkers
||
(
op
.
maybeUnhiddenMarkers
=
[])).
push
(
this
);
}
this
.
lines
.
push
(
line
);
};
TextMarker
.
prototype
.
detachLine
=
function
(
line
)
{
this
.
lines
.
splice
(
indexOf
(
this
.
lines
,
line
),
1
);
if
(
!
this
.
lines
.
length
&&
this
.
doc
.
cm
)
{
var
op
=
this
.
doc
.
cm
.
curOp
;
(
op
.
maybeHiddenMarkers
||
(
op
.
maybeHiddenMarkers
=
[])).
push
(
this
);
}
};
function
markText
(
doc
,
from
,
to
,
options
,
type
)
{
if
(
options
&&
options
.
shared
)
return
markTextShared
(
doc
,
from
,
to
,
options
,
type
);
if
(
doc
.
cm
&&
!
doc
.
cm
.
curOp
)
return
operation
(
doc
.
cm
,
markText
)(
doc
,
from
,
to
,
options
,
type
);
var
marker
=
new
TextMarker
(
doc
,
type
);
if
(
type
==
"range"
&&
!
posLess
(
from
,
to
))
return
marker
;
if
(
options
)
copyObj
(
options
,
marker
);
if
(
marker
.
replacedWith
)
{
marker
.
collapsed
=
true
;
marker
.
replacedWith
=
elt
(
"span"
,
[
marker
.
replacedWith
],
"CodeMirror-widget"
);
if
(
!
options
.
handleMouseEvents
)
marker
.
replacedWith
.
ignoreEvents
=
true
;
}
if
(
marker
.
collapsed
)
sawCollapsedSpans
=
true
;
if
(
marker
.
addToHistory
)
addToHistory
(
doc
,
{
from
:
from
,
to
:
to
,
origin
:
"markText"
},
{
head
:
doc
.
sel
.
head
,
anchor
:
doc
.
sel
.
anchor
},
NaN
);
var
curLine
=
from
.
line
,
size
=
0
,
collapsedAtStart
,
collapsedAtEnd
,
cm
=
doc
.
cm
,
updateMaxLine
;
doc
.
iter
(
curLine
,
to
.
line
+
1
,
function
(
line
)
{
if
(
cm
&&
marker
.
collapsed
&&
!
cm
.
options
.
lineWrapping
&&
visualLine
(
doc
,
line
)
==
cm
.
display
.
maxLine
)
updateMaxLine
=
true
;
var
span
=
{
from
:
null
,
to
:
null
,
marker
:
marker
};
size
+=
line
.
text
.
length
;
if
(
curLine
==
from
.
line
)
{
span
.
from
=
from
.
ch
;
size
-=
from
.
ch
;}
if
(
curLine
==
to
.
line
)
{
span
.
to
=
to
.
ch
;
size
-=
line
.
text
.
length
-
to
.
ch
;}
if
(
marker
.
collapsed
)
{
if
(
curLine
==
to
.
line
)
collapsedAtEnd
=
collapsedSpanAt
(
line
,
to
.
ch
);
if
(
curLine
==
from
.
line
)
collapsedAtStart
=
collapsedSpanAt
(
line
,
from
.
ch
);
else
updateLineHeight
(
line
,
0
);
}
addMarkedSpan
(
line
,
span
);
++
curLine
;
});
if
(
marker
.
collapsed
)
doc
.
iter
(
from
.
line
,
to
.
line
+
1
,
function
(
line
)
{
if
(
lineIsHidden
(
doc
,
line
))
updateLineHeight
(
line
,
0
);
});
if
(
marker
.
clearOnEnter
)
on
(
marker
,
"beforeCursorEnter"
,
function
()
{
marker
.
clear
();
});
if
(
marker
.
readOnly
)
{
sawReadOnlySpans
=
true
;
if
(
doc
.
history
.
done
.
length
||
doc
.
history
.
undone
.
length
)
doc
.
clearHistory
();
}
if
(
marker
.
collapsed
)
{
if
(
collapsedAtStart
!=
collapsedAtEnd
)
throw
new
Error
(
"Inserting collapsed marker overlapping an existing one"
);
marker
.
size
=
size
;
marker
.
atomic
=
true
;
}
if
(
cm
)
{
if
(
updateMaxLine
)
cm
.
curOp
.
updateMaxLine
=
true
;
if
(
marker
.
className
||
marker
.
startStyle
||
marker
.
endStyle
||
marker
.
collapsed
)
regChange
(
cm
,
from
.
line
,
to
.
line
+
1
);
if
(
marker
.
atomic
)
reCheckSelection
(
cm
);
}
return
marker
;
}
// SHARED TEXTMARKERS
function
SharedTextMarker
(
markers
,
primary
)
{
this
.
markers
=
markers
;
this
.
primary
=
primary
;
for
(
var
i
=
0
,
me
=
this
;
i
<
markers
.
length
;
++
i
)
{
markers
[
i
].
parent
=
this
;
on
(
markers
[
i
],
"clear"
,
function
(){
me
.
clear
();});
}
}
CodeMirror
.
SharedTextMarker
=
SharedTextMarker
;
SharedTextMarker
.
prototype
.
clear
=
function
()
{
if
(
this
.
explicitlyCleared
)
return
;
this
.
explicitlyCleared
=
true
;
for
(
var
i
=
0
;
i
<
this
.
markers
.
length
;
++
i
)
this
.
markers
[
i
].
clear
();
signalLater
(
this
,
"clear"
);
};
SharedTextMarker
.
prototype
.
find
=
function
()
{
return
this
.
primary
.
find
();
};
function
markTextShared
(
doc
,
from
,
to
,
options
,
type
)
{
options
=
copyObj
(
options
);
options
.
shared
=
false
;
var
markers
=
[
markText
(
doc
,
from
,
to
,
options
,
type
)],
primary
=
markers
[
0
];
var
widget
=
options
.
replacedWith
;
linkedDocs
(
doc
,
function
(
doc
)
{
if
(
widget
)
options
.
replacedWith
=
widget
.
cloneNode
(
true
);
markers
.
push
(
markText
(
doc
,
clipPos
(
doc
,
from
),
clipPos
(
doc
,
to
),
options
,
type
));
for
(
var
i
=
0
;
i
<
doc
.
linked
.
length
;
++
i
)
if
(
doc
.
linked
[
i
].
isParent
)
return
;
primary
=
lst
(
markers
);
});
return
new
SharedTextMarker
(
markers
,
primary
);
}
// TEXTMARKER SPANS
function
getMarkedSpanFor
(
spans
,
marker
)
{
if
(
spans
)
for
(
var
i
=
0
;
i
<
spans
.
length
;
++
i
)
{
var
span
=
spans
[
i
];
if
(
span
.
marker
==
marker
)
return
span
;
}
}
function
removeMarkedSpan
(
spans
,
span
)
{
for
(
var
r
,
i
=
0
;
i
<
spans
.
length
;
++
i
)
if
(
spans
[
i
]
!=
span
)
(
r
||
(
r
=
[])).
push
(
spans
[
i
]);
return
r
;
}
function
addMarkedSpan
(
line
,
span
)
{
line
.
markedSpans
=
line
.
markedSpans
?
line
.
markedSpans
.
concat
([
span
])
:
[
span
];
span
.
marker
.
attachLine
(
line
);
}
function
markedSpansBefore
(
old
,
startCh
,
isInsert
)
{
if
(
old
)
for
(
var
i
=
0
,
nw
;
i
<
old
.
length
;
++
i
)
{
var
span
=
old
[
i
],
marker
=
span
.
marker
;
var
startsBefore
=
span
.
from
==
null
||
(
marker
.
inclusiveLeft
?
span
.
from
<=
startCh
:
span
.
from
<
startCh
);
if
(
startsBefore
||
marker
.
type
==
"bookmark"
&&
span
.
from
==
startCh
&&
(
!
isInsert
||
!
span
.
marker
.
insertLeft
))
{
var
endsAfter
=
span
.
to
==
null
||
(
marker
.
inclusiveRight
?
span
.
to
>=
startCh
:
span
.
to
>
startCh
);
(
nw
||
(
nw
=
[])).
push
({
from
:
span
.
from
,
to
:
endsAfter
?
null
:
span
.
to
,
marker
:
marker
});
}
}
return
nw
;
}
function
markedSpansAfter
(
old
,
endCh
,
isInsert
)
{
if
(
old
)
for
(
var
i
=
0
,
nw
;
i
<
old
.
length
;
++
i
)
{
var
span
=
old
[
i
],
marker
=
span
.
marker
;
var
endsAfter
=
span
.
to
==
null
||
(
marker
.
inclusiveRight
?
span
.
to
>=
endCh
:
span
.
to
>
endCh
);
if
(
endsAfter
||
marker
.
type
==
"bookmark"
&&
span
.
from
==
endCh
&&
(
!
isInsert
||
span
.
marker
.
insertLeft
))
{
var
startsBefore
=
span
.
from
==
null
||
(
marker
.
inclusiveLeft
?
span
.
from
<=
endCh
:
span
.
from
<
endCh
);
(
nw
||
(
nw
=
[])).
push
({
from
:
startsBefore
?
null
:
span
.
from
-
endCh
,
to
:
span
.
to
==
null
?
null
:
span
.
to
-
endCh
,
marker
:
marker
});
}
}
return
nw
;
}
function
stretchSpansOverChange
(
doc
,
change
)
{
var
oldFirst
=
isLine
(
doc
,
change
.
from
.
line
)
&&
getLine
(
doc
,
change
.
from
.
line
).
markedSpans
;
var
oldLast
=
isLine
(
doc
,
change
.
to
.
line
)
&&
getLine
(
doc
,
change
.
to
.
line
).
markedSpans
;
if
(
!
oldFirst
&&
!
oldLast
)
return
null
;
var
startCh
=
change
.
from
.
ch
,
endCh
=
change
.
to
.
ch
,
isInsert
=
posEq
(
change
.
from
,
change
.
to
);
// Get the spans that 'stick out' on both sides
var
first
=
markedSpansBefore
(
oldFirst
,
startCh
,
isInsert
);
var
last
=
markedSpansAfter
(
oldLast
,
endCh
,
isInsert
);
// Next, merge those two ends
var
sameLine
=
change
.
text
.
length
==
1
,
offset
=
lst
(
change
.
text
).
length
+
(
sameLine
?
startCh
:
0
);
if
(
first
)
{
// Fix up .to properties of first
for
(
var
i
=
0
;
i
<
first
.
length
;
++
i
)
{
var
span
=
first
[
i
];
if
(
span
.
to
==
null
)
{
var
found
=
getMarkedSpanFor
(
last
,
span
.
marker
);
if
(
!
found
)
span
.
to
=
startCh
;
else
if
(
sameLine
)
span
.
to
=
found
.
to
==
null
?
null
:
found
.
to
+
offset
;
}
}
}
if
(
last
)
{
// Fix up .from in last (or move them into first in case of sameLine)
for
(
var
i
=
0
;
i
<
last
.
length
;
++
i
)
{
var
span
=
last
[
i
];
if
(
span
.
to
!=
null
)
span
.
to
+=
offset
;
if
(
span
.
from
==
null
)
{
var
found
=
getMarkedSpanFor
(
first
,
span
.
marker
);
if
(
!
found
)
{
span
.
from
=
offset
;
if
(
sameLine
)
(
first
||
(
first
=
[])).
push
(
span
);
}
}
else
{
span
.
from
+=
offset
;
if
(
sameLine
)
(
first
||
(
first
=
[])).
push
(
span
);
}
}
}
if
(
sameLine
&&
first
)
{
// Make sure we didn't create any zero-length spans
for
(
var
i
=
0
;
i
<
first
.
length
;
++
i
)
if
(
first
[
i
].
from
!=
null
&&
first
[
i
].
from
==
first
[
i
].
to
&&
first
[
i
].
marker
.
type
!=
"bookmark"
)
first
.
splice
(
i
--
,
1
);
if
(
!
first
.
length
)
first
=
null
;
}
var
newMarkers
=
[
first
];
if
(
!
sameLine
)
{
// Fill gap with whole-line-spans
var
gap
=
change
.
text
.
length
-
2
,
gapMarkers
;
if
(
gap
>
0
&&
first
)
for
(
var
i
=
0
;
i
<
first
.
length
;
++
i
)
if
(
first
[
i
].
to
==
null
)
(
gapMarkers
||
(
gapMarkers
=
[])).
push
({
from
:
null
,
to
:
null
,
marker
:
first
[
i
].
marker
});
for
(
var
i
=
0
;
i
<
gap
;
++
i
)
newMarkers
.
push
(
gapMarkers
);
newMarkers
.
push
(
last
);
}
return
newMarkers
;
}
function
mergeOldSpans
(
doc
,
change
)
{
var
old
=
getOldSpans
(
doc
,
change
);
var
stretched
=
stretchSpansOverChange
(
doc
,
change
);
if
(
!
old
)
return
stretched
;
if
(
!
stretched
)
return
old
;
for
(
var
i
=
0
;
i
<
old
.
length
;
++
i
)
{
var
oldCur
=
old
[
i
],
stretchCur
=
stretched
[
i
];
if
(
oldCur
&&
stretchCur
)
{
spans
:
for
(
var
j
=
0
;
j
<
stretchCur
.
length
;
++
j
)
{
var
span
=
stretchCur
[
j
];
for
(
var
k
=
0
;
k
<
oldCur
.
length
;
++
k
)
if
(
oldCur
[
k
].
marker
==
span
.
marker
)
continue
spans
;
oldCur
.
push
(
span
);
}
}
else
if
(
stretchCur
)
{
old
[
i
]
=
stretchCur
;
}
}
return
old
;
}
function
removeReadOnlyRanges
(
doc
,
from
,
to
)
{
var
markers
=
null
;
doc
.
iter
(
from
.
line
,
to
.
line
+
1
,
function
(
line
)
{
if
(
line
.
markedSpans
)
for
(
var
i
=
0
;
i
<
line
.
markedSpans
.
length
;
++
i
)
{
var
mark
=
line
.
markedSpans
[
i
].
marker
;
if
(
mark
.
readOnly
&&
(
!
markers
||
indexOf
(
markers
,
mark
)
==
-
1
))
(
markers
||
(
markers
=
[])).
push
(
mark
);
}
});
if
(
!
markers
)
return
null
;
var
parts
=
[{
from
:
from
,
to
:
to
}];
for
(
var
i
=
0
;
i
<
markers
.
length
;
++
i
)
{
var
mk
=
markers
[
i
],
m
=
mk
.
find
();
for
(
var
j
=
0
;
j
<
parts
.
length
;
++
j
)
{
var
p
=
parts
[
j
];
if
(
posLess
(
p
.
to
,
m
.
from
)
||
posLess
(
m
.
to
,
p
.
from
))
continue
;
var
newParts
=
[
j
,
1
];
if
(
posLess
(
p
.
from
,
m
.
from
)
||
!
mk
.
inclusiveLeft
&&
posEq
(
p
.
from
,
m
.
from
))
newParts
.
push
({
from
:
p
.
from
,
to
:
m
.
from
});
if
(
posLess
(
m
.
to
,
p
.
to
)
||
!
mk
.
inclusiveRight
&&
posEq
(
p
.
to
,
m
.
to
))
newParts
.
push
({
from
:
m
.
to
,
to
:
p
.
to
});
parts
.
splice
.
apply
(
parts
,
newParts
);
j
+=
newParts
.
length
-
1
;
}
}
return
parts
;
}
function
collapsedSpanAt
(
line
,
ch
)
{
var
sps
=
sawCollapsedSpans
&&
line
.
markedSpans
,
found
;
if
(
sps
)
for
(
var
sp
,
i
=
0
;
i
<
sps
.
length
;
++
i
)
{
sp
=
sps
[
i
];
if
(
!
sp
.
marker
.
collapsed
)
continue
;
if
((
sp
.
from
==
null
||
sp
.
from
<
ch
)
&&
(
sp
.
to
==
null
||
sp
.
to
>
ch
)
&&
(
!
found
||
found
.
width
<
sp
.
marker
.
width
))
found
=
sp
.
marker
;
}
return
found
;
}
function
collapsedSpanAtStart
(
line
)
{
return
collapsedSpanAt
(
line
,
-
1
);
}
function
collapsedSpanAtEnd
(
line
)
{
return
collapsedSpanAt
(
line
,
line
.
text
.
length
+
1
);
}
function
visualLine
(
doc
,
line
)
{
var
merged
;
while
(
merged
=
collapsedSpanAtStart
(
line
))
line
=
getLine
(
doc
,
merged
.
find
().
from
.
line
);
return
line
;
}
function
lineIsHidden
(
doc
,
line
)
{
var
sps
=
sawCollapsedSpans
&&
line
.
markedSpans
;
if
(
sps
)
for
(
var
sp
,
i
=
0
;
i
<
sps
.
length
;
++
i
)
{
sp
=
sps
[
i
];
if
(
!
sp
.
marker
.
collapsed
)
continue
;
if
(
sp
.
from
==
null
)
return
true
;
if
(
sp
.
marker
.
replacedWith
)
continue
;
if
(
sp
.
from
==
0
&&
sp
.
marker
.
inclusiveLeft
&&
lineIsHiddenInner
(
doc
,
line
,
sp
))
return
true
;
}
}
function
lineIsHiddenInner
(
doc
,
line
,
span
)
{
if
(
span
.
to
==
null
)
{
var
end
=
span
.
marker
.
find
().
to
,
endLine
=
getLine
(
doc
,
end
.
line
);
return
lineIsHiddenInner
(
doc
,
endLine
,
getMarkedSpanFor
(
endLine
.
markedSpans
,
span
.
marker
));
}
if
(
span
.
marker
.
inclusiveRight
&&
span
.
to
==
line
.
text
.
length
)
return
true
;
for
(
var
sp
,
i
=
0
;
i
<
line
.
markedSpans
.
length
;
++
i
)
{
sp
=
line
.
markedSpans
[
i
];
if
(
sp
.
marker
.
collapsed
&&
!
sp
.
marker
.
replacedWith
&&
sp
.
from
==
span
.
to
&&
(
sp
.
marker
.
inclusiveLeft
||
span
.
marker
.
inclusiveRight
)
&&
lineIsHiddenInner
(
doc
,
line
,
sp
))
return
true
;
}
}
function
detachMarkedSpans
(
line
)
{
var
spans
=
line
.
markedSpans
;
if
(
!
spans
)
return
;
for
(
var
i
=
0
;
i
<
spans
.
length
;
++
i
)
spans
[
i
].
marker
.
detachLine
(
line
);
line
.
markedSpans
=
null
;
}
function
attachMarkedSpans
(
line
,
spans
)
{
if
(
!
spans
)
return
;
for
(
var
i
=
0
;
i
<
spans
.
length
;
++
i
)
spans
[
i
].
marker
.
attachLine
(
line
);
line
.
markedSpans
=
spans
;
}
// LINE WIDGETS
var
LineWidget
=
CodeMirror
.
LineWidget
=
function
(
cm
,
node
,
options
)
{
for
(
var
opt
in
options
)
if
(
options
.
hasOwnProperty
(
opt
))
this
[
opt
]
=
options
[
opt
];
this
.
cm
=
cm
;
this
.
node
=
node
;
};
function
widgetOperation
(
f
)
{
return
function
()
{
var
withOp
=
!
this
.
cm
.
curOp
;
if
(
withOp
)
startOperation
(
this
.
cm
);
try
{
var
result
=
f
.
apply
(
this
,
arguments
);}
finally
{
if
(
withOp
)
endOperation
(
this
.
cm
);}
return
result
;
};
}
LineWidget
.
prototype
.
clear
=
widgetOperation
(
function
()
{
var
ws
=
this
.
line
.
widgets
,
no
=
lineNo
(
this
.
line
);
if
(
no
==
null
||
!
ws
)
return
;
for
(
var
i
=
0
;
i
<
ws
.
length
;
++
i
)
if
(
ws
[
i
]
==
this
)
ws
.
splice
(
i
--
,
1
);
if
(
!
ws
.
length
)
this
.
line
.
widgets
=
null
;
updateLineHeight
(
this
.
line
,
Math
.
max
(
0
,
this
.
line
.
height
-
widgetHeight
(
this
)));
regChange
(
this
.
cm
,
no
,
no
+
1
);
});
LineWidget
.
prototype
.
changed
=
widgetOperation
(
function
()
{
var
oldH
=
this
.
height
;
this
.
height
=
null
;
var
diff
=
widgetHeight
(
this
)
-
oldH
;
if
(
!
diff
)
return
;
updateLineHeight
(
this
.
line
,
this
.
line
.
height
+
diff
);
var
no
=
lineNo
(
this
.
line
);
regChange
(
this
.
cm
,
no
,
no
+
1
);
});
function
widgetHeight
(
widget
)
{
if
(
widget
.
height
!=
null
)
return
widget
.
height
;
if
(
!
widget
.
node
.
parentNode
||
widget
.
node
.
parentNode
.
nodeType
!=
1
)
removeChildrenAndAdd
(
widget
.
cm
.
display
.
measure
,
elt
(
"div"
,
[
widget
.
node
],
null
,
"position: relative"
));
return
widget
.
height
=
widget
.
node
.
offsetHeight
;
}
function
addLineWidget
(
cm
,
handle
,
node
,
options
)
{
var
widget
=
new
LineWidget
(
cm
,
node
,
options
);
if
(
widget
.
noHScroll
)
cm
.
display
.
alignWidgets
=
true
;
changeLine
(
cm
,
handle
,
function
(
line
)
{
(
line
.
widgets
||
(
line
.
widgets
=
[])).
push
(
widget
);
widget
.
line
=
line
;
if
(
!
lineIsHidden
(
cm
.
doc
,
line
)
||
widget
.
showIfHidden
)
{
var
aboveVisible
=
heightAtLine
(
cm
,
line
)
<
cm
.
display
.
scroller
.
scrollTop
;
updateLineHeight
(
line
,
line
.
height
+
widgetHeight
(
widget
));
if
(
aboveVisible
)
addToScrollPos
(
cm
,
0
,
widget
.
height
);
}
return
true
;
});
return
widget
;
}
// LINE DATA STRUCTURE
// Line objects. These hold state related to a line, including
// highlighting info (the styles array).
function
makeLine
(
text
,
markedSpans
,
estimateHeight
)
{
var
line
=
{
text
:
text
};
attachMarkedSpans
(
line
,
markedSpans
);
line
.
height
=
estimateHeight
?
estimateHeight
(
line
)
:
1
;
return
line
;
}
function
updateLine
(
line
,
text
,
markedSpans
,
estimateHeight
)
{
line
.
text
=
text
;
if
(
line
.
stateAfter
)
line
.
stateAfter
=
null
;
if
(
line
.
styles
)
line
.
styles
=
null
;
if
(
line
.
order
!=
null
)
line
.
order
=
null
;
detachMarkedSpans
(
line
);
attachMarkedSpans
(
line
,
markedSpans
);
var
estHeight
=
estimateHeight
?
estimateHeight
(
line
)
:
1
;
if
(
estHeight
!=
line
.
height
)
updateLineHeight
(
line
,
estHeight
);
}
function
cleanUpLine
(
line
)
{
line
.
parent
=
null
;
detachMarkedSpans
(
line
);
}
// Run the given mode's parser over a line, update the styles
// array, which contains alternating fragments of text and CSS
// classes.
function
runMode
(
cm
,
text
,
mode
,
state
,
f
)
{
var
flattenSpans
=
mode
.
flattenSpans
;
if
(
flattenSpans
==
null
)
flattenSpans
=
cm
.
options
.
flattenSpans
;
var
curStart
=
0
,
curStyle
=
null
;
var
stream
=
new
StringStream
(
text
,
cm
.
options
.
tabSize
),
style
;
if
(
text
==
""
&&
mode
.
blankLine
)
mode
.
blankLine
(
state
);
while
(
!
stream
.
eol
())
{
if
(
stream
.
pos
>
cm
.
options
.
maxHighlightLength
)
{
flattenSpans
=
false
;
// Webkit seems to refuse to render text nodes longer than 57444 characters
stream
.
pos
=
Math
.
min
(
text
.
length
,
stream
.
start
+
50000
);
style
=
null
;
}
else
{
style
=
mode
.
token
(
stream
,
state
);
}
if
(
!
flattenSpans
||
curStyle
!=
style
)
{
if
(
curStart
<
stream
.
start
)
f
(
stream
.
start
,
curStyle
);
curStart
=
stream
.
start
;
curStyle
=
style
;
}
stream
.
start
=
stream
.
pos
;
}
if
(
curStart
<
stream
.
pos
)
f
(
stream
.
pos
,
curStyle
);
}
function
highlightLine
(
cm
,
line
,
state
)
{
// A styles array always starts with a number identifying the
// mode/overlays that it is based on (for easy invalidation).
var
st
=
[
cm
.
state
.
modeGen
];
// Compute the base array of styles
runMode
(
cm
,
line
.
text
,
cm
.
doc
.
mode
,
state
,
function
(
end
,
style
)
{
st
.
push
(
end
,
style
);});
// Run overlays, adjust style array.
for
(
var
o
=
0
;
o
<
cm
.
state
.
overlays
.
length
;
++
o
)
{
var
overlay
=
cm
.
state
.
overlays
[
o
],
i
=
1
,
at
=
0
;
runMode
(
cm
,
line
.
text
,
overlay
.
mode
,
true
,
function
(
end
,
style
)
{
var
start
=
i
;
// Ensure there's a token end at the current position, and that i points at it
while
(
at
<
end
)
{
var
i_end
=
st
[
i
];
if
(
i_end
>
end
)
st
.
splice
(
i
,
1
,
end
,
st
[
i
+
1
],
i_end
);
i
+=
2
;
at
=
Math
.
min
(
end
,
i_end
);
}
if
(
!
style
)
return
;
if
(
overlay
.
opaque
)
{
st
.
splice
(
start
,
i
-
start
,
end
,
style
);
i
=
start
+
2
;
}
else
{
for
(;
start
<
i
;
start
+=
2
)
{
var
cur
=
st
[
start
+
1
];
st
[
start
+
1
]
=
cur
?
cur
+
" "
+
style
:
style
;
}
}
});
}
return
st
;
}
function
getLineStyles
(
cm
,
line
)
{
if
(
!
line
.
styles
||
line
.
styles
[
0
]
!=
cm
.
state
.
modeGen
)
line
.
styles
=
highlightLine
(
cm
,
line
,
line
.
stateAfter
=
getStateBefore
(
cm
,
lineNo
(
line
)));
return
line
.
styles
;
}
// Lightweight form of highlight -- proceed over this line and
// update state, but don't save a style array.
function
processLine
(
cm
,
line
,
state
)
{
var
mode
=
cm
.
doc
.
mode
;
var
stream
=
new
StringStream
(
line
.
text
,
cm
.
options
.
tabSize
);
if
(
line
.
text
==
""
&&
mode
.
blankLine
)
mode
.
blankLine
(
state
);
while
(
!
stream
.
eol
()
&&
stream
.
pos
<=
cm
.
options
.
maxHighlightLength
)
{
mode
.
token
(
stream
,
state
);
stream
.
start
=
stream
.
pos
;
}
}
var
styleToClassCache
=
{};
function
styleToClass
(
style
)
{
if
(
!
style
)
return
null
;
return
styleToClassCache
[
style
]
||
(
styleToClassCache
[
style
]
=
"cm-"
+
style
.
replace
(
/ +/g
,
" cm-"
));
}
function
lineContent
(
cm
,
realLine
,
measure
)
{
var
merged
,
line
=
realLine
,
empty
=
true
;
while
(
merged
=
collapsedSpanAtStart
(
line
))
line
=
getLine
(
cm
.
doc
,
merged
.
find
().
from
.
line
);
var
builder
=
{
pre
:
elt
(
"pre"
),
col
:
0
,
pos
:
0
,
display
:
!
measure
,
measure
:
null
,
measuredSomething
:
false
,
cm
:
cm
};
if
(
line
.
textClass
)
builder
.
pre
.
className
=
line
.
textClass
;
do
{
if
(
line
.
text
)
empty
=
false
;
builder
.
measure
=
line
==
realLine
&&
measure
;
builder
.
pos
=
0
;
builder
.
addToken
=
builder
.
measure
?
buildTokenMeasure
:
buildToken
;
if
((
ie
||
webkit
)
&&
cm
.
getOption
(
"lineWrapping"
))
builder
.
addToken
=
buildTokenSplitSpaces
(
builder
.
addToken
);
var
next
=
insertLineContent
(
line
,
builder
,
getLineStyles
(
cm
,
line
));
if
(
measure
&&
line
==
realLine
&&
!
builder
.
measuredSomething
)
{
measure
[
0
]
=
builder
.
pre
.
appendChild
(
zeroWidthElement
(
cm
.
display
.
measure
));
builder
.
measuredSomething
=
true
;
}
if
(
next
)
line
=
getLine
(
cm
.
doc
,
next
.
to
.
line
);
}
while
(
next
);
if
(
measure
&&
!
builder
.
measuredSomething
&&
!
measure
[
0
])
measure
[
0
]
=
builder
.
pre
.
appendChild
(
empty
?
elt
(
"span"
,
"\u00a0"
)
:
zeroWidthElement
(
cm
.
display
.
measure
));
if
(
!
builder
.
pre
.
firstChild
&&
!
lineIsHidden
(
cm
.
doc
,
realLine
))
builder
.
pre
.
appendChild
(
document
.
createTextNode
(
"\u00a0"
));
var
order
;
// Work around problem with the reported dimensions of single-char
// direction spans on IE (issue #1129). See also the comment in
// cursorCoords.
if
(
measure
&&
ie
&&
(
order
=
getOrder
(
line
)))
{
var
l
=
order
.
length
-
1
;
if
(
order
[
l
].
from
==
order
[
l
].
to
)
--
l
;
var
last
=
order
[
l
],
prev
=
order
[
l
-
1
];
if
(
last
.
from
+
1
==
last
.
to
&&
prev
&&
last
.
level
<
prev
.
level
)
{
var
span
=
measure
[
builder
.
pos
-
1
];
if
(
span
)
span
.
parentNode
.
insertBefore
(
span
.
measureRight
=
zeroWidthElement
(
cm
.
display
.
measure
),
span
.
nextSibling
);
}
}
signal
(
cm
,
"renderLine"
,
cm
,
realLine
,
builder
.
pre
);
return
builder
.
pre
;
}
var
tokenSpecialChars
=
/[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g
;
function
buildToken
(
builder
,
text
,
style
,
startStyle
,
endStyle
)
{
if
(
!
text
)
return
;
if
(
!
tokenSpecialChars
.
test
(
text
))
{
builder
.
col
+=
text
.
length
;
var
content
=
document
.
createTextNode
(
text
);
}
else
{
var
content
=
document
.
createDocumentFragment
(),
pos
=
0
;
while
(
true
)
{
tokenSpecialChars
.
lastIndex
=
pos
;
var
m
=
tokenSpecialChars
.
exec
(
text
);
var
skipped
=
m
?
m
.
index
-
pos
:
text
.
length
-
pos
;
if
(
skipped
)
{
content
.
appendChild
(
document
.
createTextNode
(
text
.
slice
(
pos
,
pos
+
skipped
)));
builder
.
col
+=
skipped
;
}
if
(
!
m
)
break
;
pos
+=
skipped
+
1
;
if
(
m
[
0
]
==
"\t"
)
{
var
tabSize
=
builder
.
cm
.
options
.
tabSize
,
tabWidth
=
tabSize
-
builder
.
col
%
tabSize
;
content
.
appendChild
(
elt
(
"span"
,
spaceStr
(
tabWidth
),
"cm-tab"
));
builder
.
col
+=
tabWidth
;
}
else
{
var
token
=
elt
(
"span"
,
"\u2022"
,
"cm-invalidchar"
);
token
.
title
=
"\\u"
+
m
[
0
].
charCodeAt
(
0
).
toString
(
16
);
content
.
appendChild
(
token
);
builder
.
col
+=
1
;
}
}
}
if
(
style
||
startStyle
||
endStyle
||
builder
.
measure
)
{
var
fullStyle
=
style
||
""
;
if
(
startStyle
)
fullStyle
+=
startStyle
;
if
(
endStyle
)
fullStyle
+=
endStyle
;
return
builder
.
pre
.
appendChild
(
elt
(
"span"
,
[
content
],
fullStyle
));
}
builder
.
pre
.
appendChild
(
content
);
}
function
buildTokenMeasure
(
builder
,
text
,
style
,
startStyle
,
endStyle
)
{
var
wrapping
=
builder
.
cm
.
options
.
lineWrapping
;
for
(
var
i
=
0
;
i
<
text
.
length
;
++
i
)
{
var
ch
=
text
.
charAt
(
i
),
start
=
i
==
0
;
if
(
ch
>=
"\ud800"
&&
ch
<
"\udbff"
&&
i
<
text
.
length
-
1
)
{
ch
=
text
.
slice
(
i
,
i
+
2
);
++
i
;
}
else
if
(
i
&&
wrapping
&&
spanAffectsWrapping
(
text
,
i
))
{
builder
.
pre
.
appendChild
(
elt
(
"wbr"
));
}
var
span
=
builder
.
measure
[
builder
.
pos
]
=
buildToken
(
builder
,
ch
,
style
,
start
&&
startStyle
,
i
==
text
.
length
-
1
&&
endStyle
);
// In IE single-space nodes wrap differently than spaces
// embedded in larger text nodes, except when set to
// white-space: normal (issue #1268).
if
(
ie
&&
wrapping
&&
ch
==
" "
&&
i
&&
!
/\s/
.
test
(
text
.
charAt
(
i
-
1
))
&&
i
<
text
.
length
-
1
&&
!
/\s/
.
test
(
text
.
charAt
(
i
+
1
)))
span
.
style
.
whiteSpace
=
"normal"
;
builder
.
pos
+=
ch
.
length
;
}
if
(
text
.
length
)
builder
.
measuredSomething
=
true
;
}
function
buildTokenSplitSpaces
(
inner
)
{
function
split
(
old
)
{
var
out
=
" "
;
for
(
var
i
=
0
;
i
<
old
.
length
-
2
;
++
i
)
out
+=
i
%
2
?
" "
:
"\u00a0"
;
out
+=
" "
;
return
out
;
}
return
function
(
builder
,
text
,
style
,
startStyle
,
endStyle
)
{
return
inner
(
builder
,
text
.
replace
(
/ {3,}/
,
split
),
style
,
startStyle
,
endStyle
);
};
}
function
buildCollapsedSpan
(
builder
,
size
,
widget
)
{
if
(
widget
)
{
if
(
!
builder
.
display
)
widget
=
widget
.
cloneNode
(
true
);
if
(
builder
.
measure
)
{
builder
.
measure
[
builder
.
pos
]
=
size
?
widget
:
builder
.
pre
.
appendChild
(
zeroWidthElement
(
builder
.
cm
.
display
.
measure
));
builder
.
measuredSomething
=
true
;
}
builder
.
pre
.
appendChild
(
widget
);
}
builder
.
pos
+=
size
;
}
// Outputs a number of spans to make up a line, taking highlighting
// and marked text into account.
function
insertLineContent
(
line
,
builder
,
styles
)
{
var
spans
=
line
.
markedSpans
,
allText
=
line
.
text
,
at
=
0
;
if
(
!
spans
)
{
for
(
var
i
=
1
;
i
<
styles
.
length
;
i
+=
2
)
builder
.
addToken
(
builder
,
allText
.
slice
(
at
,
at
=
styles
[
i
]),
styleToClass
(
styles
[
i
+
1
]));
return
;
}
var
len
=
allText
.
length
,
pos
=
0
,
i
=
1
,
text
=
""
,
style
;
var
nextChange
=
0
,
spanStyle
,
spanEndStyle
,
spanStartStyle
,
collapsed
;
for
(;;)
{
if
(
nextChange
==
pos
)
{
// Update current marker set
spanStyle
=
spanEndStyle
=
spanStartStyle
=
""
;
collapsed
=
null
;
nextChange
=
Infinity
;
var
foundBookmark
=
null
;
for
(
var
j
=
0
;
j
<
spans
.
length
;
++
j
)
{
var
sp
=
spans
[
j
],
m
=
sp
.
marker
;
if
(
sp
.
from
<=
pos
&&
(
sp
.
to
==
null
||
sp
.
to
>
pos
))
{
if
(
sp
.
to
!=
null
&&
nextChange
>
sp
.
to
)
{
nextChange
=
sp
.
to
;
spanEndStyle
=
""
;
}
if
(
m
.
className
)
spanStyle
+=
" "
+
m
.
className
;
if
(
m
.
startStyle
&&
sp
.
from
==
pos
)
spanStartStyle
+=
" "
+
m
.
startStyle
;
if
(
m
.
endStyle
&&
sp
.
to
==
nextChange
)
spanEndStyle
+=
" "
+
m
.
endStyle
;
if
(
m
.
collapsed
&&
(
!
collapsed
||
collapsed
.
marker
.
size
<
m
.
size
))
collapsed
=
sp
;
}
else
if
(
sp
.
from
>
pos
&&
nextChange
>
sp
.
from
)
{
nextChange
=
sp
.
from
;
}
if
(
m
.
type
==
"bookmark"
&&
sp
.
from
==
pos
&&
m
.
replacedWith
)
foundBookmark
=
m
.
replacedWith
;
}
if
(
collapsed
&&
(
collapsed
.
from
||
0
)
==
pos
)
{
buildCollapsedSpan
(
builder
,
(
collapsed
.
to
==
null
?
len
:
collapsed
.
to
)
-
pos
,
collapsed
.
from
!=
null
&&
collapsed
.
marker
.
replacedWith
);
if
(
collapsed
.
to
==
null
)
return
collapsed
.
marker
.
find
();
}
if
(
foundBookmark
&&
!
collapsed
)
buildCollapsedSpan
(
builder
,
0
,
foundBookmark
);
}
if
(
pos
>=
len
)
break
;
var
upto
=
Math
.
min
(
len
,
nextChange
);
while
(
true
)
{
if
(
text
)
{
var
end
=
pos
+
text
.
length
;
if
(
!
collapsed
)
{
var
tokenText
=
end
>
upto
?
text
.
slice
(
0
,
upto
-
pos
)
:
text
;
builder
.
addToken
(
builder
,
tokenText
,
style
?
style
+
spanStyle
:
spanStyle
,
spanStartStyle
,
pos
+
tokenText
.
length
==
nextChange
?
spanEndStyle
:
""
);
}
if
(
end
>=
upto
)
{
text
=
text
.
slice
(
upto
-
pos
);
pos
=
upto
;
break
;}
pos
=
end
;
spanStartStyle
=
""
;
}
text
=
allText
.
slice
(
at
,
at
=
styles
[
i
++
]);
style
=
styleToClass
(
styles
[
i
++
]);
}
}
}
// DOCUMENT DATA STRUCTURE
function
updateDoc
(
doc
,
change
,
markedSpans
,
selAfter
,
estimateHeight
)
{
function
spansFor
(
n
)
{
return
markedSpans
?
markedSpans
[
n
]
:
null
;}
function
update
(
line
,
text
,
spans
)
{
updateLine
(
line
,
text
,
spans
,
estimateHeight
);
signalLater
(
line
,
"change"
,
line
,
change
);
}
var
from
=
change
.
from
,
to
=
change
.
to
,
text
=
change
.
text
;
var
firstLine
=
getLine
(
doc
,
from
.
line
),
lastLine
=
getLine
(
doc
,
to
.
line
);
var
lastText
=
lst
(
text
),
lastSpans
=
spansFor
(
text
.
length
-
1
),
nlines
=
to
.
line
-
from
.
line
;
// First adjust the line structure
if
(
from
.
ch
==
0
&&
to
.
ch
==
0
&&
lastText
==
""
)
{
// This is a whole-line replace. Treated specially to make
// sure line objects move the way they are supposed to.
for
(
var
i
=
0
,
e
=
text
.
length
-
1
,
added
=
[];
i
<
e
;
++
i
)
added
.
push
(
makeLine
(
text
[
i
],
spansFor
(
i
),
estimateHeight
));
update
(
lastLine
,
lastLine
.
text
,
lastSpans
);
if
(
nlines
)
doc
.
remove
(
from
.
line
,
nlines
);
if
(
added
.
length
)
doc
.
insert
(
from
.
line
,
added
);
}
else
if
(
firstLine
==
lastLine
)
{
if
(
text
.
length
==
1
)
{
update
(
firstLine
,
firstLine
.
text
.
slice
(
0
,
from
.
ch
)
+
lastText
+
firstLine
.
text
.
slice
(
to
.
ch
),
lastSpans
);
}
else
{
for
(
var
added
=
[],
i
=
1
,
e
=
text
.
length
-
1
;
i
<
e
;
++
i
)
added
.
push
(
makeLine
(
text
[
i
],
spansFor
(
i
),
estimateHeight
));
added
.
push
(
makeLine
(
lastText
+
firstLine
.
text
.
slice
(
to
.
ch
),
lastSpans
,
estimateHeight
));
update
(
firstLine
,
firstLine
.
text
.
slice
(
0
,
from
.
ch
)
+
text
[
0
],
spansFor
(
0
));
doc
.
insert
(
from
.
line
+
1
,
added
);
}
}
else
if
(
text
.
length
==
1
)
{
update
(
firstLine
,
firstLine
.
text
.
slice
(
0
,
from
.
ch
)
+
text
[
0
]
+
lastLine
.
text
.
slice
(
to
.
ch
),
spansFor
(
0
));
doc
.
remove
(
from
.
line
+
1
,
nlines
);
}
else
{
update
(
firstLine
,
firstLine
.
text
.
slice
(
0
,
from
.
ch
)
+
text
[
0
],
spansFor
(
0
));
update
(
lastLine
,
lastText
+
lastLine
.
text
.
slice
(
to
.
ch
),
lastSpans
);
for
(
var
i
=
1
,
e
=
text
.
length
-
1
,
added
=
[];
i
<
e
;
++
i
)
added
.
push
(
makeLine
(
text
[
i
],
spansFor
(
i
),
estimateHeight
));
if
(
nlines
>
1
)
doc
.
remove
(
from
.
line
+
1
,
nlines
-
1
);
doc
.
insert
(
from
.
line
+
1
,
added
);
}
signalLater
(
doc
,
"change"
,
doc
,
change
);
setSelection
(
doc
,
selAfter
.
anchor
,
selAfter
.
head
,
null
,
true
);
}
function
LeafChunk
(
lines
)
{
this
.
lines
=
lines
;
this
.
parent
=
null
;
for
(
var
i
=
0
,
e
=
lines
.
length
,
height
=
0
;
i
<
e
;
++
i
)
{
lines
[
i
].
parent
=
this
;
height
+=
lines
[
i
].
height
;
}
this
.
height
=
height
;
}
LeafChunk
.
prototype
=
{
chunkSize
:
function
()
{
return
this
.
lines
.
length
;
},
removeInner
:
function
(
at
,
n
)
{
for
(
var
i
=
at
,
e
=
at
+
n
;
i
<
e
;
++
i
)
{
var
line
=
this
.
lines
[
i
];
this
.
height
-=
line
.
height
;
cleanUpLine
(
line
);
signalLater
(
line
,
"delete"
);
}
this
.
lines
.
splice
(
at
,
n
);
},
collapse
:
function
(
lines
)
{
lines
.
splice
.
apply
(
lines
,
[
lines
.
length
,
0
].
concat
(
this
.
lines
));
},
insertInner
:
function
(
at
,
lines
,
height
)
{
this
.
height
+=
height
;
this
.
lines
=
this
.
lines
.
slice
(
0
,
at
).
concat
(
lines
).
concat
(
this
.
lines
.
slice
(
at
));
for
(
var
i
=
0
,
e
=
lines
.
length
;
i
<
e
;
++
i
)
lines
[
i
].
parent
=
this
;
},
iterN
:
function
(
at
,
n
,
op
)
{
for
(
var
e
=
at
+
n
;
at
<
e
;
++
at
)
if
(
op
(
this
.
lines
[
at
]))
return
true
;
}
};
function
BranchChunk
(
children
)
{
this
.
children
=
children
;
var
size
=
0
,
height
=
0
;
for
(
var
i
=
0
,
e
=
children
.
length
;
i
<
e
;
++
i
)
{
var
ch
=
children
[
i
];
size
+=
ch
.
chunkSize
();
height
+=
ch
.
height
;
ch
.
parent
=
this
;
}
this
.
size
=
size
;
this
.
height
=
height
;
this
.
parent
=
null
;
}
BranchChunk
.
prototype
=
{
chunkSize
:
function
()
{
return
this
.
size
;
},
removeInner
:
function
(
at
,
n
)
{
this
.
size
-=
n
;
for
(
var
i
=
0
;
i
<
this
.
children
.
length
;
++
i
)
{
var
child
=
this
.
children
[
i
],
sz
=
child
.
chunkSize
();
if
(
at
<
sz
)
{
var
rm
=
Math
.
min
(
n
,
sz
-
at
),
oldHeight
=
child
.
height
;
child
.
removeInner
(
at
,
rm
);
this
.
height
-=
oldHeight
-
child
.
height
;
if
(
sz
==
rm
)
{
this
.
children
.
splice
(
i
--
,
1
);
child
.
parent
=
null
;
}
if
((
n
-=
rm
)
==
0
)
break
;
at
=
0
;
}
else
at
-=
sz
;
}
if
(
this
.
size
-
n
<
25
)
{
var
lines
=
[];
this
.
collapse
(
lines
);
this
.
children
=
[
new
LeafChunk
(
lines
)];
this
.
children
[
0
].
parent
=
this
;
}
},
collapse
:
function
(
lines
)
{
for
(
var
i
=
0
,
e
=
this
.
children
.
length
;
i
<
e
;
++
i
)
this
.
children
[
i
].
collapse
(
lines
);
},
insertInner
:
function
(
at
,
lines
,
height
)
{
this
.
size
+=
lines
.
length
;
this
.
height
+=
height
;
for
(
var
i
=
0
,
e
=
this
.
children
.
length
;
i
<
e
;
++
i
)
{
var
child
=
this
.
children
[
i
],
sz
=
child
.
chunkSize
();
if
(
at
<=
sz
)
{
child
.
insertInner
(
at
,
lines
,
height
);
if
(
child
.
lines
&&
child
.
lines
.
length
>
50
)
{
while
(
child
.
lines
.
length
>
50
)
{
var
spilled
=
child
.
lines
.
splice
(
child
.
lines
.
length
-
25
,
25
);
var
newleaf
=
new
LeafChunk
(
spilled
);
child
.
height
-=
newleaf
.
height
;
this
.
children
.
splice
(
i
+
1
,
0
,
newleaf
);
newleaf
.
parent
=
this
;
}
this
.
maybeSpill
();
}
break
;
}
at
-=
sz
;
}
},
maybeSpill
:
function
()
{
if
(
this
.
children
.
length
<=
10
)
return
;
var
me
=
this
;
do
{
var
spilled
=
me
.
children
.
splice
(
me
.
children
.
length
-
5
,
5
);
var
sibling
=
new
BranchChunk
(
spilled
);
if
(
!
me
.
parent
)
{
// Become the parent node
var
copy
=
new
BranchChunk
(
me
.
children
);
copy
.
parent
=
me
;
me
.
children
=
[
copy
,
sibling
];
me
=
copy
;
}
else
{
me
.
size
-=
sibling
.
size
;
me
.
height
-=
sibling
.
height
;
var
myIndex
=
indexOf
(
me
.
parent
.
children
,
me
);
me
.
parent
.
children
.
splice
(
myIndex
+
1
,
0
,
sibling
);
}
sibling
.
parent
=
me
.
parent
;
}
while
(
me
.
children
.
length
>
10
);
me
.
parent
.
maybeSpill
();
},
iterN
:
function
(
at
,
n
,
op
)
{
for
(
var
i
=
0
,
e
=
this
.
children
.
length
;
i
<
e
;
++
i
)
{
var
child
=
this
.
children
[
i
],
sz
=
child
.
chunkSize
();
if
(
at
<
sz
)
{
var
used
=
Math
.
min
(
n
,
sz
-
at
);
if
(
child
.
iterN
(
at
,
used
,
op
))
return
true
;
if
((
n
-=
used
)
==
0
)
break
;
at
=
0
;
}
else
at
-=
sz
;
}
}
};
var
nextDocId
=
0
;
var
Doc
=
CodeMirror
.
Doc
=
function
(
text
,
mode
,
firstLine
)
{
if
(
!
(
this
instanceof
Doc
))
return
new
Doc
(
text
,
mode
,
firstLine
);
if
(
firstLine
==
null
)
firstLine
=
0
;
BranchChunk
.
call
(
this
,
[
new
LeafChunk
([
makeLine
(
""
,
null
)])]);
this
.
first
=
firstLine
;
this
.
scrollTop
=
this
.
scrollLeft
=
0
;
this
.
cantEdit
=
false
;
this
.
history
=
makeHistory
();
this
.
cleanGeneration
=
1
;
this
.
frontier
=
firstLine
;
var
start
=
Pos
(
firstLine
,
0
);
this
.
sel
=
{
from
:
start
,
to
:
start
,
head
:
start
,
anchor
:
start
,
shift
:
false
,
extend
:
false
,
goalColumn
:
null
};
this
.
id
=
++
nextDocId
;
this
.
modeOption
=
mode
;
if
(
typeof
text
==
"string"
)
text
=
splitLines
(
text
);
updateDoc
(
this
,
{
from
:
start
,
to
:
start
,
text
:
text
},
null
,
{
head
:
start
,
anchor
:
start
});
};
Doc
.
prototype
=
createObj
(
BranchChunk
.
prototype
,
{
constructor
:
Doc
,
iter
:
function
(
from
,
to
,
op
)
{
if
(
op
)
this
.
iterN
(
from
-
this
.
first
,
to
-
from
,
op
);
else
this
.
iterN
(
this
.
first
,
this
.
first
+
this
.
size
,
from
);
},
insert
:
function
(
at
,
lines
)
{
var
height
=
0
;
for
(
var
i
=
0
,
e
=
lines
.
length
;
i
<
e
;
++
i
)
height
+=
lines
[
i
].
height
;
this
.
insertInner
(
at
-
this
.
first
,
lines
,
height
);
},
remove
:
function
(
at
,
n
)
{
this
.
removeInner
(
at
-
this
.
first
,
n
);
},
getValue
:
function
(
lineSep
)
{
var
lines
=
getLines
(
this
,
this
.
first
,
this
.
first
+
this
.
size
);
if
(
lineSep
===
false
)
return
lines
;
return
lines
.
join
(
lineSep
||
"\n"
);
},
setValue
:
function
(
code
)
{
var
top
=
Pos
(
this
.
first
,
0
),
last
=
this
.
first
+
this
.
size
-
1
;
makeChange
(
this
,
{
from
:
top
,
to
:
Pos
(
last
,
getLine
(
this
,
last
).
text
.
length
),
text
:
splitLines
(
code
),
origin
:
"setValue"
},
{
head
:
top
,
anchor
:
top
},
true
);
},
replaceRange
:
function
(
code
,
from
,
to
,
origin
)
{
from
=
clipPos
(
this
,
from
);
to
=
to
?
clipPos
(
this
,
to
)
:
from
;
replaceRange
(
this
,
code
,
from
,
to
,
origin
);
},
getRange
:
function
(
from
,
to
,
lineSep
)
{
var
lines
=
getBetween
(
this
,
clipPos
(
this
,
from
),
clipPos
(
this
,
to
));
if
(
lineSep
===
false
)
return
lines
;
return
lines
.
join
(
lineSep
||
"\n"
);
},
getLine
:
function
(
line
)
{
var
l
=
this
.
getLineHandle
(
line
);
return
l
&&
l
.
text
;},
setLine
:
function
(
line
,
text
)
{
if
(
isLine
(
this
,
line
))
replaceRange
(
this
,
text
,
Pos
(
line
,
0
),
clipPos
(
this
,
Pos
(
line
)));
},
removeLine
:
function
(
line
)
{
if
(
line
)
replaceRange
(
this
,
""
,
clipPos
(
this
,
Pos
(
line
-
1
)),
clipPos
(
this
,
Pos
(
line
)));
else
replaceRange
(
this
,
""
,
Pos
(
0
,
0
),
clipPos
(
this
,
Pos
(
1
,
0
)));
},
getLineHandle
:
function
(
line
)
{
if
(
isLine
(
this
,
line
))
return
getLine
(
this
,
line
);},
getLineNumber
:
function
(
line
)
{
return
lineNo
(
line
);},
lineCount
:
function
()
{
return
this
.
size
;},
firstLine
:
function
()
{
return
this
.
first
;},
lastLine
:
function
()
{
return
this
.
first
+
this
.
size
-
1
;},
clipPos
:
function
(
pos
)
{
return
clipPos
(
this
,
pos
);},
getCursor
:
function
(
start
)
{
var
sel
=
this
.
sel
,
pos
;
if
(
start
==
null
||
start
==
"head"
)
pos
=
sel
.
head
;
else
if
(
start
==
"anchor"
)
pos
=
sel
.
anchor
;
else
if
(
start
==
"end"
||
start
===
false
)
pos
=
sel
.
to
;
else
pos
=
sel
.
from
;
return
copyPos
(
pos
);
},
somethingSelected
:
function
()
{
return
!
posEq
(
this
.
sel
.
head
,
this
.
sel
.
anchor
);},
setCursor
:
docOperation
(
function
(
line
,
ch
,
extend
)
{
var
pos
=
clipPos
(
this
,
typeof
line
==
"number"
?
Pos
(
line
,
ch
||
0
)
:
line
);
if
(
extend
)
extendSelection
(
this
,
pos
);
else
setSelection
(
this
,
pos
,
pos
);
}),
setSelection
:
docOperation
(
function
(
anchor
,
head
)
{
setSelection
(
this
,
clipPos
(
this
,
anchor
),
clipPos
(
this
,
head
||
anchor
));
}),
extendSelection
:
docOperation
(
function
(
from
,
to
)
{
extendSelection
(
this
,
clipPos
(
this
,
from
),
to
&&
clipPos
(
this
,
to
));
}),
getSelection
:
function
(
lineSep
)
{
return
this
.
getRange
(
this
.
sel
.
from
,
this
.
sel
.
to
,
lineSep
);},
replaceSelection
:
function
(
code
,
collapse
,
origin
)
{
makeChange
(
this
,
{
from
:
this
.
sel
.
from
,
to
:
this
.
sel
.
to
,
text
:
splitLines
(
code
),
origin
:
origin
},
collapse
||
"around"
);
},
undo
:
docOperation
(
function
()
{
makeChangeFromHistory
(
this
,
"undo"
);}),
redo
:
docOperation
(
function
()
{
makeChangeFromHistory
(
this
,
"redo"
);}),
setExtending
:
function
(
val
)
{
this
.
sel
.
extend
=
val
;},
historySize
:
function
()
{
var
hist
=
this
.
history
;
return
{
undo
:
hist
.
done
.
length
,
redo
:
hist
.
undone
.
length
};
},
clearHistory
:
function
()
{
this
.
history
=
makeHistory
(
this
.
history
.
maxGeneration
);},
markClean
:
function
()
{
this
.
cleanGeneration
=
this
.
changeGeneration
();
},
changeGeneration
:
function
()
{
this
.
history
.
lastOp
=
this
.
history
.
lastOrigin
=
null
;
return
this
.
history
.
generation
;
},
isClean
:
function
(
gen
)
{
return
this
.
history
.
generation
==
(
gen
||
this
.
cleanGeneration
);
},
getHistory
:
function
()
{
return
{
done
:
copyHistoryArray
(
this
.
history
.
done
),
undone
:
copyHistoryArray
(
this
.
history
.
undone
)};
},
setHistory
:
function
(
histData
)
{
var
hist
=
this
.
history
=
makeHistory
(
this
.
history
.
maxGeneration
);
hist
.
done
=
histData
.
done
.
slice
(
0
);
hist
.
undone
=
histData
.
undone
.
slice
(
0
);
},
markText
:
function
(
from
,
to
,
options
)
{
return
markText
(
this
,
clipPos
(
this
,
from
),
clipPos
(
this
,
to
),
options
,
"range"
);
},
setBookmark
:
function
(
pos
,
options
)
{
var
realOpts
=
{
replacedWith
:
options
&&
(
options
.
nodeType
==
null
?
options
.
widget
:
options
),
insertLeft
:
options
&&
options
.
insertLeft
};
pos
=
clipPos
(
this
,
pos
);
return
markText
(
this
,
pos
,
pos
,
realOpts
,
"bookmark"
);
},
findMarksAt
:
function
(
pos
)
{
pos
=
clipPos
(
this
,
pos
);
var
markers
=
[],
spans
=
getLine
(
this
,
pos
.
line
).
markedSpans
;
if
(
spans
)
for
(
var
i
=
0
;
i
<
spans
.
length
;
++
i
)
{
var
span
=
spans
[
i
];
if
((
span
.
from
==
null
||
span
.
from
<=
pos
.
ch
)
&&
(
span
.
to
==
null
||
span
.
to
>=
pos
.
ch
))
markers
.
push
(
span
.
marker
.
parent
||
span
.
marker
);
}
return
markers
;
},
getAllMarks
:
function
()
{
var
markers
=
[];
this
.
iter
(
function
(
line
)
{
var
sps
=
line
.
markedSpans
;
if
(
sps
)
for
(
var
i
=
0
;
i
<
sps
.
length
;
++
i
)
if
(
sps
[
i
].
from
!=
null
)
markers
.
push
(
sps
[
i
].
marker
);
});
return
markers
;
},
posFromIndex
:
function
(
off
)
{
var
ch
,
lineNo
=
this
.
first
;
this
.
iter
(
function
(
line
)
{
var
sz
=
line
.
text
.
length
+
1
;
if
(
sz
>
off
)
{
ch
=
off
;
return
true
;
}
off
-=
sz
;
++
lineNo
;
});
return
clipPos
(
this
,
Pos
(
lineNo
,
ch
));
},
indexFromPos
:
function
(
coords
)
{
coords
=
clipPos
(
this
,
coords
);
var
index
=
coords
.
ch
;
if
(
coords
.
line
<
this
.
first
||
coords
.
ch
<
0
)
return
0
;
this
.
iter
(
this
.
first
,
coords
.
line
,
function
(
line
)
{
index
+=
line
.
text
.
length
+
1
;
});
return
index
;
},
copy
:
function
(
copyHistory
)
{
var
doc
=
new
Doc
(
getLines
(
this
,
this
.
first
,
this
.
first
+
this
.
size
),
this
.
modeOption
,
this
.
first
);
doc
.
scrollTop
=
this
.
scrollTop
;
doc
.
scrollLeft
=
this
.
scrollLeft
;
doc
.
sel
=
{
from
:
this
.
sel
.
from
,
to
:
this
.
sel
.
to
,
head
:
this
.
sel
.
head
,
anchor
:
this
.
sel
.
anchor
,
shift
:
this
.
sel
.
shift
,
extend
:
false
,
goalColumn
:
this
.
sel
.
goalColumn
};
if
(
copyHistory
)
{
doc
.
history
.
undoDepth
=
this
.
history
.
undoDepth
;
doc
.
setHistory
(
this
.
getHistory
());
}
return
doc
;
},
linkedDoc
:
function
(
options
)
{
if
(
!
options
)
options
=
{};
var
from
=
this
.
first
,
to
=
this
.
first
+
this
.
size
;
if
(
options
.
from
!=
null
&&
options
.
from
>
from
)
from
=
options
.
from
;
if
(
options
.
to
!=
null
&&
options
.
to
<
to
)
to
=
options
.
to
;
var
copy
=
new
Doc
(
getLines
(
this
,
from
,
to
),
options
.
mode
||
this
.
modeOption
,
from
);
if
(
options
.
sharedHist
)
copy
.
history
=
this
.
history
;
(
this
.
linked
||
(
this
.
linked
=
[])).
push
({
doc
:
copy
,
sharedHist
:
options
.
sharedHist
});
copy
.
linked
=
[{
doc
:
this
,
isParent
:
true
,
sharedHist
:
options
.
sharedHist
}];
return
copy
;
},
unlinkDoc
:
function
(
other
)
{
if
(
other
instanceof
CodeMirror
)
other
=
other
.
doc
;
if
(
this
.
linked
)
for
(
var
i
=
0
;
i
<
this
.
linked
.
length
;
++
i
)
{
var
link
=
this
.
linked
[
i
];
if
(
link
.
doc
!=
other
)
continue
;
this
.
linked
.
splice
(
i
,
1
);
other
.
unlinkDoc
(
this
);
break
;
}
// If the histories were shared, split them again
if
(
other
.
history
==
this
.
history
)
{
var
splitIds
=
[
other
.
id
];
linkedDocs
(
other
,
function
(
doc
)
{
splitIds
.
push
(
doc
.
id
);},
true
);
other
.
history
=
makeHistory
();
other
.
history
.
done
=
copyHistoryArray
(
this
.
history
.
done
,
splitIds
);
other
.
history
.
undone
=
copyHistoryArray
(
this
.
history
.
undone
,
splitIds
);
}
},
iterLinkedDocs
:
function
(
f
)
{
linkedDocs
(
this
,
f
);},
getMode
:
function
()
{
return
this
.
mode
;},
getEditor
:
function
()
{
return
this
.
cm
;}
});
Doc
.
prototype
.
eachLine
=
Doc
.
prototype
.
iter
;
// The Doc methods that should be available on CodeMirror instances
var
dontDelegate
=
"iter insert remove copy getEditor"
.
split
(
" "
);
for
(
var
prop
in
Doc
.
prototype
)
if
(
Doc
.
prototype
.
hasOwnProperty
(
prop
)
&&
indexOf
(
dontDelegate
,
prop
)
<
0
)
CodeMirror
.
prototype
[
prop
]
=
(
function
(
method
)
{
return
function
()
{
return
method
.
apply
(
this
.
doc
,
arguments
);};
})(
Doc
.
prototype
[
prop
]);
function
linkedDocs
(
doc
,
f
,
sharedHistOnly
)
{
function
propagate
(
doc
,
skip
,
sharedHist
)
{
if
(
doc
.
linked
)
for
(
var
i
=
0
;
i
<
doc
.
linked
.
length
;
++
i
)
{
var
rel
=
doc
.
linked
[
i
];
if
(
rel
.
doc
==
skip
)
continue
;
var
shared
=
sharedHist
&&
rel
.
sharedHist
;
if
(
sharedHistOnly
&&
!
shared
)
continue
;
f
(
rel
.
doc
,
shared
);
propagate
(
rel
.
doc
,
doc
,
shared
);
}
}
propagate
(
doc
,
null
,
true
);
}
function
attachDoc
(
cm
,
doc
)
{
if
(
doc
.
cm
)
throw
new
Error
(
"This document is already in use."
);
cm
.
doc
=
doc
;
doc
.
cm
=
cm
;
estimateLineHeights
(
cm
);
loadMode
(
cm
);
if
(
!
cm
.
options
.
lineWrapping
)
computeMaxLength
(
cm
);
cm
.
options
.
mode
=
doc
.
modeOption
;
regChange
(
cm
);
}
// LINE UTILITIES
function
getLine
(
chunk
,
n
)
{
n
-=
chunk
.
first
;
while
(
!
chunk
.
lines
)
{
for
(
var
i
=
0
;;
++
i
)
{
var
child
=
chunk
.
children
[
i
],
sz
=
child
.
chunkSize
();
if
(
n
<
sz
)
{
chunk
=
child
;
break
;
}
n
-=
sz
;
}
}
return
chunk
.
lines
[
n
];
}
function
getBetween
(
doc
,
start
,
end
)
{
var
out
=
[],
n
=
start
.
line
;
doc
.
iter
(
start
.
line
,
end
.
line
+
1
,
function
(
line
)
{
var
text
=
line
.
text
;
if
(
n
==
end
.
line
)
text
=
text
.
slice
(
0
,
end
.
ch
);
if
(
n
==
start
.
line
)
text
=
text
.
slice
(
start
.
ch
);
out
.
push
(
text
);
++
n
;
});
return
out
;
}
function
getLines
(
doc
,
from
,
to
)
{
var
out
=
[];
doc
.
iter
(
from
,
to
,
function
(
line
)
{
out
.
push
(
line
.
text
);
});
return
out
;
}
function
updateLineHeight
(
line
,
height
)
{
var
diff
=
height
-
line
.
height
;
for
(
var
n
=
line
;
n
;
n
=
n
.
parent
)
n
.
height
+=
diff
;
}
function
lineNo
(
line
)
{
if
(
line
.
parent
==
null
)
return
null
;
var
cur
=
line
.
parent
,
no
=
indexOf
(
cur
.
lines
,
line
);
for
(
var
chunk
=
cur
.
parent
;
chunk
;
cur
=
chunk
,
chunk
=
chunk
.
parent
)
{
for
(
var
i
=
0
;;
++
i
)
{
if
(
chunk
.
children
[
i
]
==
cur
)
break
;
no
+=
chunk
.
children
[
i
].
chunkSize
();
}
}
return
no
+
cur
.
first
;
}
function
lineAtHeight
(
chunk
,
h
)
{
var
n
=
chunk
.
first
;
outer
:
do
{
for
(
var
i
=
0
,
e
=
chunk
.
children
.
length
;
i
<
e
;
++
i
)
{
var
child
=
chunk
.
children
[
i
],
ch
=
child
.
height
;
if
(
h
<
ch
)
{
chunk
=
child
;
continue
outer
;
}
h
-=
ch
;
n
+=
child
.
chunkSize
();
}
return
n
;
}
while
(
!
chunk
.
lines
);
for
(
var
i
=
0
,
e
=
chunk
.
lines
.
length
;
i
<
e
;
++
i
)
{
var
line
=
chunk
.
lines
[
i
],
lh
=
line
.
height
;
if
(
h
<
lh
)
break
;
h
-=
lh
;
}
return
n
+
i
;
}
function
heightAtLine
(
cm
,
lineObj
)
{
lineObj
=
visualLine
(
cm
.
doc
,
lineObj
);
var
h
=
0
,
chunk
=
lineObj
.
parent
;
for
(
var
i
=
0
;
i
<
chunk
.
lines
.
length
;
++
i
)
{
var
line
=
chunk
.
lines
[
i
];
if
(
line
==
lineObj
)
break
;
else
h
+=
line
.
height
;
}
for
(
var
p
=
chunk
.
parent
;
p
;
chunk
=
p
,
p
=
chunk
.
parent
)
{
for
(
var
i
=
0
;
i
<
p
.
children
.
length
;
++
i
)
{
var
cur
=
p
.
children
[
i
];
if
(
cur
==
chunk
)
break
;
else
h
+=
cur
.
height
;
}
}
return
h
;
}
function
getOrder
(
line
)
{
var
order
=
line
.
order
;
if
(
order
==
null
)
order
=
line
.
order
=
bidiOrdering
(
line
.
text
);
return
order
;
}
// HISTORY
function
makeHistory
(
startGen
)
{
return
{
// Arrays of history events. Doing something adds an event to
// done and clears undo. Undoing moves events from done to
// undone, redoing moves them in the other direction.
done
:
[],
undone
:
[],
undoDepth
:
Infinity
,
// Used to track when changes can be merged into a single undo
// event
lastTime
:
0
,
lastOp
:
null
,
lastOrigin
:
null
,
// Used by the isClean() method
generation
:
startGen
||
1
,
maxGeneration
:
startGen
||
1
};
}
function
attachLocalSpans
(
doc
,
change
,
from
,
to
)
{
var
existing
=
change
[
"spans_"
+
doc
.
id
],
n
=
0
;
doc
.
iter
(
Math
.
max
(
doc
.
first
,
from
),
Math
.
min
(
doc
.
first
+
doc
.
size
,
to
),
function
(
line
)
{
if
(
line
.
markedSpans
)
(
existing
||
(
existing
=
change
[
"spans_"
+
doc
.
id
]
=
{}))[
n
]
=
line
.
markedSpans
;
++
n
;
});
}
function
historyChangeFromChange
(
doc
,
change
)
{
var
histChange
=
{
from
:
change
.
from
,
to
:
changeEnd
(
change
),
text
:
getBetween
(
doc
,
change
.
from
,
change
.
to
)};
attachLocalSpans
(
doc
,
histChange
,
change
.
from
.
line
,
change
.
to
.
line
+
1
);
linkedDocs
(
doc
,
function
(
doc
)
{
attachLocalSpans
(
doc
,
histChange
,
change
.
from
.
line
,
change
.
to
.
line
+
1
);},
true
);
return
histChange
;
}
function
addToHistory
(
doc
,
change
,
selAfter
,
opId
)
{
var
hist
=
doc
.
history
;
hist
.
undone
.
length
=
0
;
var
time
=
+
new
Date
,
cur
=
lst
(
hist
.
done
);
if
(
cur
&&
(
hist
.
lastOp
==
opId
||
hist
.
lastOrigin
==
change
.
origin
&&
change
.
origin
&&
((
change
.
origin
.
charAt
(
0
)
==
"+"
&&
doc
.
cm
&&
hist
.
lastTime
>
time
-
doc
.
cm
.
options
.
historyEventDelay
)
||
change
.
origin
.
charAt
(
0
)
==
"*"
)))
{
// Merge this change into the last event
var
last
=
lst
(
cur
.
changes
);
if
(
posEq
(
change
.
from
,
change
.
to
)
&&
posEq
(
change
.
from
,
last
.
to
))
{
// Optimized case for simple insertion -- don't want to add
// new changesets for every character typed
last
.
to
=
changeEnd
(
change
);
}
else
{
// Add new sub-event
cur
.
changes
.
push
(
historyChangeFromChange
(
doc
,
change
));
}
cur
.
anchorAfter
=
selAfter
.
anchor
;
cur
.
headAfter
=
selAfter
.
head
;
}
else
{
// Can not be merged, start a new event.
cur
=
{
changes
:
[
historyChangeFromChange
(
doc
,
change
)],
generation
:
hist
.
generation
,
anchorBefore
:
doc
.
sel
.
anchor
,
headBefore
:
doc
.
sel
.
head
,
anchorAfter
:
selAfter
.
anchor
,
headAfter
:
selAfter
.
head
};
hist
.
done
.
push
(
cur
);
hist
.
generation
=
++
hist
.
maxGeneration
;
while
(
hist
.
done
.
length
>
hist
.
undoDepth
)
hist
.
done
.
shift
();
}
hist
.
lastTime
=
time
;
hist
.
lastOp
=
opId
;
hist
.
lastOrigin
=
change
.
origin
;
}
function
removeClearedSpans
(
spans
)
{
if
(
!
spans
)
return
null
;
for
(
var
i
=
0
,
out
;
i
<
spans
.
length
;
++
i
)
{
if
(
spans
[
i
].
marker
.
explicitlyCleared
)
{
if
(
!
out
)
out
=
spans
.
slice
(
0
,
i
);
}
else
if
(
out
)
out
.
push
(
spans
[
i
]);
}
return
!
out
?
spans
:
out
.
length
?
out
:
null
;
}
function
getOldSpans
(
doc
,
change
)
{
var
found
=
change
[
"spans_"
+
doc
.
id
];
if
(
!
found
)
return
null
;
for
(
var
i
=
0
,
nw
=
[];
i
<
change
.
text
.
length
;
++
i
)
nw
.
push
(
removeClearedSpans
(
found
[
i
]));
return
nw
;
}
// Used both to provide a JSON-safe object in .getHistory, and, when
// detaching a document, to split the history in two
function
copyHistoryArray
(
events
,
newGroup
)
{
for
(
var
i
=
0
,
copy
=
[];
i
<
events
.
length
;
++
i
)
{
var
event
=
events
[
i
],
changes
=
event
.
changes
,
newChanges
=
[];
copy
.
push
({
changes
:
newChanges
,
anchorBefore
:
event
.
anchorBefore
,
headBefore
:
event
.
headBefore
,
anchorAfter
:
event
.
anchorAfter
,
headAfter
:
event
.
headAfter
});
for
(
var
j
=
0
;
j
<
changes
.
length
;
++
j
)
{
var
change
=
changes
[
j
],
m
;
newChanges
.
push
({
from
:
change
.
from
,
to
:
change
.
to
,
text
:
change
.
text
});
if
(
newGroup
)
for
(
var
prop
in
change
)
if
(
m
=
prop
.
match
(
/^spans_(\d+)$/
))
{
if
(
indexOf
(
newGroup
,
Number
(
m
[
1
]))
>
-
1
)
{
lst
(
newChanges
)[
prop
]
=
change
[
prop
];
delete
change
[
prop
];
}
}
}
}
return
copy
;
}
// Rebasing/resetting history to deal with externally-sourced changes
function
rebaseHistSel
(
pos
,
from
,
to
,
diff
)
{
if
(
to
<
pos
.
line
)
{
pos
.
line
+=
diff
;
}
else
if
(
from
<
pos
.
line
)
{
pos
.
line
=
from
;
pos
.
ch
=
0
;
}
}
// Tries to rebase an array of history events given a change in the
// document. If the change touches the same lines as the event, the
// event, and everything 'behind' it, is discarded. If the change is
// before the event, the event's positions are updated. Uses a
// copy-on-write scheme for the positions, to avoid having to
// reallocate them all on every rebase, but also avoid problems with
// shared position objects being unsafely updated.
function
rebaseHistArray
(
array
,
from
,
to
,
diff
)
{
for
(
var
i
=
0
;
i
<
array
.
length
;
++
i
)
{
var
sub
=
array
[
i
],
ok
=
true
;
for
(
var
j
=
0
;
j
<
sub
.
changes
.
length
;
++
j
)
{
var
cur
=
sub
.
changes
[
j
];
if
(
!
sub
.
copied
)
{
cur
.
from
=
copyPos
(
cur
.
from
);
cur
.
to
=
copyPos
(
cur
.
to
);
}
if
(
to
<
cur
.
from
.
line
)
{
cur
.
from
.
line
+=
diff
;
cur
.
to
.
line
+=
diff
;
}
else
if
(
from
<=
cur
.
to
.
line
)
{
ok
=
false
;
break
;
}
}
if
(
!
sub
.
copied
)
{
sub
.
anchorBefore
=
copyPos
(
sub
.
anchorBefore
);
sub
.
headBefore
=
copyPos
(
sub
.
headBefore
);
sub
.
anchorAfter
=
copyPos
(
sub
.
anchorAfter
);
sub
.
readAfter
=
copyPos
(
sub
.
headAfter
);
sub
.
copied
=
true
;
}
if
(
!
ok
)
{
array
.
splice
(
0
,
i
+
1
);
i
=
0
;
}
else
{
rebaseHistSel
(
sub
.
anchorBefore
);
rebaseHistSel
(
sub
.
headBefore
);
rebaseHistSel
(
sub
.
anchorAfter
);
rebaseHistSel
(
sub
.
headAfter
);
}
}
}
function
rebaseHist
(
hist
,
change
)
{
var
from
=
change
.
from
.
line
,
to
=
change
.
to
.
line
,
diff
=
change
.
text
.
length
-
(
to
-
from
)
-
1
;
rebaseHistArray
(
hist
.
done
,
from
,
to
,
diff
);
rebaseHistArray
(
hist
.
undone
,
from
,
to
,
diff
);
}
// EVENT OPERATORS
function
stopMethod
()
{
e_stop
(
this
);}
// Ensure an event has a stop method.
function
addStop
(
event
)
{
if
(
!
event
.
stop
)
event
.
stop
=
stopMethod
;
return
event
;
}
function
e_preventDefault
(
e
)
{
if
(
e
.
preventDefault
)
e
.
preventDefault
();
else
e
.
returnValue
=
false
;
}
function
e_stopPropagation
(
e
)
{
if
(
e
.
stopPropagation
)
e
.
stopPropagation
();
else
e
.
cancelBubble
=
true
;
}
function
e_defaultPrevented
(
e
)
{
return
e
.
defaultPrevented
!=
null
?
e
.
defaultPrevented
:
e
.
returnValue
==
false
;
}
function
e_stop
(
e
)
{
e_preventDefault
(
e
);
e_stopPropagation
(
e
);}
CodeMirror
.
e_stop
=
e_stop
;
CodeMirror
.
e_preventDefault
=
e_preventDefault
;
CodeMirror
.
e_stopPropagation
=
e_stopPropagation
;
function
e_target
(
e
)
{
return
e
.
target
||
e
.
srcElement
;}
function
e_button
(
e
)
{
var
b
=
e
.
which
;
if
(
b
==
null
)
{
if
(
e
.
button
&
1
)
b
=
1
;
else
if
(
e
.
button
&
2
)
b
=
3
;
else
if
(
e
.
button
&
4
)
b
=
2
;
}
if
(
mac
&&
e
.
ctrlKey
&&
b
==
1
)
b
=
3
;
return
b
;
}
// EVENT HANDLING
function
on
(
emitter
,
type
,
f
)
{
if
(
emitter
.
addEventListener
)
emitter
.
addEventListener
(
type
,
f
,
false
);
else
if
(
emitter
.
attachEvent
)
emitter
.
attachEvent
(
"on"
+
type
,
f
);
else
{
var
map
=
emitter
.
_handlers
||
(
emitter
.
_handlers
=
{});
var
arr
=
map
[
type
]
||
(
map
[
type
]
=
[]);
arr
.
push
(
f
);
}
}
function
off
(
emitter
,
type
,
f
)
{
if
(
emitter
.
removeEventListener
)
emitter
.
removeEventListener
(
type
,
f
,
false
);
else
if
(
emitter
.
detachEvent
)
emitter
.
detachEvent
(
"on"
+
type
,
f
);
else
{
var
arr
=
emitter
.
_handlers
&&
emitter
.
_handlers
[
type
];
if
(
!
arr
)
return
;
for
(
var
i
=
0
;
i
<
arr
.
length
;
++
i
)
if
(
arr
[
i
]
==
f
)
{
arr
.
splice
(
i
,
1
);
break
;
}
}
}
function
signal
(
emitter
,
type
/*, values...*/
)
{
var
arr
=
emitter
.
_handlers
&&
emitter
.
_handlers
[
type
];
if
(
!
arr
)
return
;
var
args
=
Array
.
prototype
.
slice
.
call
(
arguments
,
2
);
for
(
var
i
=
0
;
i
<
arr
.
length
;
++
i
)
arr
[
i
].
apply
(
null
,
args
);
}
var
delayedCallbacks
,
delayedCallbackDepth
=
0
;
function
signalLater
(
emitter
,
type
/*, values...*/
)
{
var
arr
=
emitter
.
_handlers
&&
emitter
.
_handlers
[
type
];
if
(
!
arr
)
return
;
var
args
=
Array
.
prototype
.
slice
.
call
(
arguments
,
2
);
if
(
!
delayedCallbacks
)
{
++
delayedCallbackDepth
;
delayedCallbacks
=
[];
setTimeout
(
fireDelayed
,
0
);
}
function
bnd
(
f
)
{
return
function
(){
f
.
apply
(
null
,
args
);};};
for
(
var
i
=
0
;
i
<
arr
.
length
;
++
i
)
delayedCallbacks
.
push
(
bnd
(
arr
[
i
]));
}
function
signalDOMEvent
(
cm
,
e
)
{
signal
(
cm
,
e
.
type
,
cm
,
e
);
return
e_defaultPrevented
(
e
);
}
function
fireDelayed
()
{
--
delayedCallbackDepth
;
var
delayed
=
delayedCallbacks
;
delayedCallbacks
=
null
;
for
(
var
i
=
0
;
i
<
delayed
.
length
;
++
i
)
delayed
[
i
]();
}
function
hasHandler
(
emitter
,
type
)
{
var
arr
=
emitter
.
_handlers
&&
emitter
.
_handlers
[
type
];
return
arr
&&
arr
.
length
>
0
;
}
CodeMirror
.
on
=
on
;
CodeMirror
.
off
=
off
;
CodeMirror
.
signal
=
signal
;
// MISC UTILITIES
// Number of pixels added to scroller and sizer to hide scrollbar
var
scrollerCutOff
=
30
;
// Returned or thrown by various protocols to signal 'I'm not
// handling this'.
var
Pass
=
CodeMirror
.
Pass
=
{
toString
:
function
(){
return
"CodeMirror.Pass"
;}};
function
Delayed
()
{
this
.
id
=
null
;}
Delayed
.
prototype
=
{
set
:
function
(
ms
,
f
)
{
clearTimeout
(
this
.
id
);
this
.
id
=
setTimeout
(
f
,
ms
);}};
// Counts the column offset in a string, taking tabs into account.
// Used mostly to find indentation.
function
countColumn
(
string
,
end
,
tabSize
,
startIndex
,
startValue
)
{
if
(
end
==
null
)
{
end
=
string
.
search
(
/[^\s\u00a0]/
);
if
(
end
==
-
1
)
end
=
string
.
length
;
}
for
(
var
i
=
startIndex
||
0
,
n
=
startValue
||
0
;
i
<
end
;
++
i
)
{
if
(
string
.
charAt
(
i
)
==
"\t"
)
n
+=
tabSize
-
(
n
%
tabSize
);
else
++
n
;
}
return
n
;
}
CodeMirror
.
countColumn
=
countColumn
;
var
spaceStrs
=
[
""
];
function
spaceStr
(
n
)
{
while
(
spaceStrs
.
length
<=
n
)
spaceStrs
.
push
(
lst
(
spaceStrs
)
+
" "
);
return
spaceStrs
[
n
];
}
function
lst
(
arr
)
{
return
arr
[
arr
.
length
-
1
];
}
function
selectInput
(
node
)
{
if
(
ios
)
{
// Mobile Safari apparently has a bug where select() is broken.
node
.
selectionStart
=
0
;
node
.
selectionEnd
=
node
.
value
.
length
;
}
else
{
// Suppress mysterious IE10 errors
try
{
node
.
select
();
}
catch
(
_e
)
{}
}
}
function
indexOf
(
collection
,
elt
)
{
if
(
collection
.
indexOf
)
return
collection
.
indexOf
(
elt
);
for
(
var
i
=
0
,
e
=
collection
.
length
;
i
<
e
;
++
i
)
if
(
collection
[
i
]
==
elt
)
return
i
;
return
-
1
;
}
function
createObj
(
base
,
props
)
{
function
Obj
()
{}
Obj
.
prototype
=
base
;
var
inst
=
new
Obj
();
if
(
props
)
copyObj
(
props
,
inst
);
return
inst
;
}
function
copyObj
(
obj
,
target
)
{
if
(
!
target
)
target
=
{};
for
(
var
prop
in
obj
)
if
(
obj
.
hasOwnProperty
(
prop
))
target
[
prop
]
=
obj
[
prop
];
return
target
;
}
function
emptyArray
(
size
)
{
for
(
var
a
=
[],
i
=
0
;
i
<
size
;
++
i
)
a
.
push
(
undefined
);
return
a
;
}
function
bind
(
f
)
{
var
args
=
Array
.
prototype
.
slice
.
call
(
arguments
,
1
);
return
function
(){
return
f
.
apply
(
null
,
args
);};
}
var
nonASCIISingleCaseWordChar
=
/[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/
;
function
isWordChar
(
ch
)
{
return
/\w/
.
test
(
ch
)
||
ch
>
"\x80"
&&
(
ch
.
toUpperCase
()
!=
ch
.
toLowerCase
()
||
nonASCIISingleCaseWordChar
.
test
(
ch
));
}
function
isEmpty
(
obj
)
{
for
(
var
n
in
obj
)
if
(
obj
.
hasOwnProperty
(
n
)
&&
obj
[
n
])
return
false
;
return
true
;
}
var
isExtendingChar
=
/[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/
;
// DOM UTILITIES
function
elt
(
tag
,
content
,
className
,
style
)
{
var
e
=
document
.
createElement
(
tag
);
if
(
className
)
e
.
className
=
className
;
if
(
style
)
e
.
style
.
cssText
=
style
;
if
(
typeof
content
==
"string"
)
setTextContent
(
e
,
content
);
else
if
(
content
)
for
(
var
i
=
0
;
i
<
content
.
length
;
++
i
)
e
.
appendChild
(
content
[
i
]);
return
e
;
}
function
removeChildren
(
e
)
{
for
(
var
count
=
e
.
childNodes
.
length
;
count
>
0
;
--
count
)
e
.
removeChild
(
e
.
firstChild
);
return
e
;
}
function
removeChildrenAndAdd
(
parent
,
e
)
{
return
removeChildren
(
parent
).
appendChild
(
e
);
}
function
setTextContent
(
e
,
str
)
{
if
(
ie_lt9
)
{
e
.
innerHTML
=
""
;
e
.
appendChild
(
document
.
createTextNode
(
str
));
}
else
e
.
textContent
=
str
;
}
function
getRect
(
node
)
{
return
node
.
getBoundingClientRect
();
}
CodeMirror
.
replaceGetRect
=
function
(
f
)
{
getRect
=
f
;
};
// FEATURE DETECTION
// Detect drag-and-drop
var
dragAndDrop
=
function
()
{
// There is *some* kind of drag-and-drop support in IE6-8, but I
// couldn't get it to work yet.
if
(
ie_lt9
)
return
false
;
var
div
=
elt
(
'div'
);
return
"draggable"
in
div
||
"dragDrop"
in
div
;
}();
// For a reason I have yet to figure out, some browsers disallow
// word wrapping between certain characters *only* if a new inline
// element is started between them. This makes it hard to reliably
// measure the position of things, since that requires inserting an
// extra span. This terribly fragile set of tests matches the
// character combinations that suffer from this phenomenon on the
// various browsers.
function
spanAffectsWrapping
()
{
return
false
;
}
if
(
gecko
)
// Only for "$'"
spanAffectsWrapping
=
function
(
str
,
i
)
{
return
str
.
charCodeAt
(
i
-
1
)
==
36
&&
str
.
charCodeAt
(
i
)
==
39
;
};
else
if
(
safari
&&
!
/Version\/([6-9]|\d\d)\b/
.
test
(
navigator
.
userAgent
))
spanAffectsWrapping
=
function
(
str
,
i
)
{
return
/\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/
.
test
(
str
.
slice
(
i
-
1
,
i
+
1
));
};
else
if
(
webkit
)
spanAffectsWrapping
=
function
(
str
,
i
)
{
if
(
i
>
1
&&
str
.
charCodeAt
(
i
-
1
)
==
45
&&
/\w/
.
test
(
str
.
charAt
(
i
-
2
))
&&
/[^\-?\.]/
.
test
(
str
.
charAt
(
i
)))
return
true
;
return
/[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/
.
test
(
str
.
slice
(
i
-
1
,
i
+
1
));
};
var
knownScrollbarWidth
;
function
scrollbarWidth
(
measure
)
{
if
(
knownScrollbarWidth
!=
null
)
return
knownScrollbarWidth
;
var
test
=
elt
(
"div"
,
null
,
null
,
"width: 50px; height: 50px; overflow-x: scroll"
);
removeChildrenAndAdd
(
measure
,
test
);
if
(
test
.
offsetWidth
)
knownScrollbarWidth
=
test
.
offsetHeight
-
test
.
clientHeight
;
return
knownScrollbarWidth
||
0
;
}
var
zwspSupported
;
function
zeroWidthElement
(
measure
)
{
if
(
zwspSupported
==
null
)
{
var
test
=
elt
(
"span"
,
"\u200b"
);
removeChildrenAndAdd
(
measure
,
elt
(
"span"
,
[
test
,
document
.
createTextNode
(
"x"
)]));
if
(
measure
.
firstChild
.
offsetHeight
!=
0
)
zwspSupported
=
test
.
offsetWidth
<=
1
&&
test
.
offsetHeight
>
2
&&
!
ie_lt8
;
}
if
(
zwspSupported
)
return
elt
(
"span"
,
"\u200b"
);
else
return
elt
(
"span"
,
"\u00a0"
,
null
,
"display: inline-block; width: 1px; margin-right: -1px"
);
}
// See if "".split is the broken IE version, if so, provide an
// alternative way to split lines.
var
splitLines
=
"\n\nb"
.
split
(
/\n/
).
length
!=
3
?
function
(
string
)
{
var
pos
=
0
,
result
=
[],
l
=
string
.
length
;
while
(
pos
<=
l
)
{
var
nl
=
string
.
indexOf
(
"\n"
,
pos
);
if
(
nl
==
-
1
)
nl
=
string
.
length
;
var
line
=
string
.
slice
(
pos
,
string
.
charAt
(
nl
-
1
)
==
"\r"
?
nl
-
1
:
nl
);
var
rt
=
line
.
indexOf
(
"\r"
);
if
(
rt
!=
-
1
)
{
result
.
push
(
line
.
slice
(
0
,
rt
));
pos
+=
rt
+
1
;
}
else
{
result
.
push
(
line
);
pos
=
nl
+
1
;
}
}
return
result
;
}
:
function
(
string
){
return
string
.
split
(
/\r\n?|\n/
);};
CodeMirror
.
splitLines
=
splitLines
;
var
hasSelection
=
window
.
getSelection
?
function
(
te
)
{
try
{
return
te
.
selectionStart
!=
te
.
selectionEnd
;
}
catch
(
e
)
{
return
false
;
}
}
:
function
(
te
)
{
try
{
var
range
=
te
.
ownerDocument
.
selection
.
createRange
();}
catch
(
e
)
{}
if
(
!
range
||
range
.
parentElement
()
!=
te
)
return
false
;
return
range
.
compareEndPoints
(
"StartToEnd"
,
range
)
!=
0
;
};
var
hasCopyEvent
=
(
function
()
{
var
e
=
elt
(
"div"
);
if
(
"oncopy"
in
e
)
return
true
;
e
.
setAttribute
(
"oncopy"
,
"return;"
);
return
typeof
e
.
oncopy
==
'function'
;
})();
// KEY NAMING
var
keyNames
=
{
3
:
"Enter"
,
8
:
"Backspace"
,
9
:
"Tab"
,
13
:
"Enter"
,
16
:
"Shift"
,
17
:
"Ctrl"
,
18
:
"Alt"
,
19
:
"Pause"
,
20
:
"CapsLock"
,
27
:
"Esc"
,
32
:
"Space"
,
33
:
"PageUp"
,
34
:
"PageDown"
,
35
:
"End"
,
36
:
"Home"
,
37
:
"Left"
,
38
:
"Up"
,
39
:
"Right"
,
40
:
"Down"
,
44
:
"PrintScrn"
,
45
:
"Insert"
,
46
:
"Delete"
,
59
:
";"
,
91
:
"Mod"
,
92
:
"Mod"
,
93
:
"Mod"
,
109
:
"-"
,
107
:
"="
,
127
:
"Delete"
,
186
:
";"
,
187
:
"="
,
188
:
","
,
189
:
"-"
,
190
:
"."
,
191
:
"/"
,
192
:
"`"
,
219
:
"["
,
220
:
"\\"
,
221
:
"]"
,
222
:
"'"
,
63276
:
"PageUp"
,
63277
:
"PageDown"
,
63275
:
"End"
,
63273
:
"Home"
,
63234
:
"Left"
,
63232
:
"Up"
,
63235
:
"Right"
,
63233
:
"Down"
,
63302
:
"Insert"
,
63272
:
"Delete"
};
CodeMirror
.
keyNames
=
keyNames
;
(
function
()
{
// Number keys
for
(
var
i
=
0
;
i
<
10
;
i
++
)
keyNames
[
i
+
48
]
=
String
(
i
);
// Alphabetic keys
for
(
var
i
=
65
;
i
<=
90
;
i
++
)
keyNames
[
i
]
=
String
.
fromCharCode
(
i
);
// Function keys
for
(
var
i
=
1
;
i
<=
12
;
i
++
)
keyNames
[
i
+
111
]
=
keyNames
[
i
+
63235
]
=
"F"
+
i
;
})();
// BIDI HELPERS
function
iterateBidiSections
(
order
,
from
,
to
,
f
)
{
if
(
!
order
)
return
f
(
from
,
to
,
"ltr"
);
for
(
var
i
=
0
;
i
<
order
.
length
;
++
i
)
{
var
part
=
order
[
i
];
if
(
part
.
from
<
to
&&
part
.
to
>
from
||
from
==
to
&&
part
.
to
==
from
)
f
(
Math
.
max
(
part
.
from
,
from
),
Math
.
min
(
part
.
to
,
to
),
part
.
level
==
1
?
"rtl"
:
"ltr"
);
}
}
function
bidiLeft
(
part
)
{
return
part
.
level
%
2
?
part
.
to
:
part
.
from
;
}
function
bidiRight
(
part
)
{
return
part
.
level
%
2
?
part
.
from
:
part
.
to
;
}
function
lineLeft
(
line
)
{
var
order
=
getOrder
(
line
);
return
order
?
bidiLeft
(
order
[
0
])
:
0
;
}
function
lineRight
(
line
)
{
var
order
=
getOrder
(
line
);
if
(
!
order
)
return
line
.
text
.
length
;
return
bidiRight
(
lst
(
order
));
}
function
lineStart
(
cm
,
lineN
)
{
var
line
=
getLine
(
cm
.
doc
,
lineN
);
var
visual
=
visualLine
(
cm
.
doc
,
line
);
if
(
visual
!=
line
)
lineN
=
lineNo
(
visual
);
var
order
=
getOrder
(
visual
);
var
ch
=
!
order
?
0
:
order
[
0
].
level
%
2
?
lineRight
(
visual
)
:
lineLeft
(
visual
);
return
Pos
(
lineN
,
ch
);
}
function
lineEnd
(
cm
,
lineN
)
{
var
merged
,
line
;
while
(
merged
=
collapsedSpanAtEnd
(
line
=
getLine
(
cm
.
doc
,
lineN
)))
lineN
=
merged
.
find
().
to
.
line
;
var
order
=
getOrder
(
line
);
var
ch
=
!
order
?
line
.
text
.
length
:
order
[
0
].
level
%
2
?
lineLeft
(
line
)
:
lineRight
(
line
);
return
Pos
(
lineN
,
ch
);
}
function
compareBidiLevel
(
order
,
a
,
b
)
{
var
linedir
=
order
[
0
].
level
;
if
(
a
==
linedir
)
return
true
;
if
(
b
==
linedir
)
return
false
;
return
a
<
b
;
}
var
bidiOther
;
function
getBidiPartAt
(
order
,
pos
)
{
for
(
var
i
=
0
,
found
;
i
<
order
.
length
;
++
i
)
{
var
cur
=
order
[
i
];
if
(
cur
.
from
<
pos
&&
cur
.
to
>
pos
)
{
bidiOther
=
null
;
return
i
;
}
if
(
cur
.
from
==
pos
||
cur
.
to
==
pos
)
{
if
(
found
==
null
)
{
found
=
i
;
}
else
if
(
compareBidiLevel
(
order
,
cur
.
level
,
order
[
found
].
level
))
{
bidiOther
=
found
;
return
i
;
}
else
{
bidiOther
=
i
;
return
found
;
}
}
}
bidiOther
=
null
;
return
found
;
}
function
moveInLine
(
line
,
pos
,
dir
,
byUnit
)
{
if
(
!
byUnit
)
return
pos
+
dir
;
do
pos
+=
dir
;
while
(
pos
>
0
&&
isExtendingChar
.
test
(
line
.
text
.
charAt
(
pos
)));
return
pos
;
}
// This is somewhat involved. It is needed in order to move
// 'visually' through bi-directional text -- i.e., pressing left
// should make the cursor go left, even when in RTL text. The
// tricky part is the 'jumps', where RTL and LTR text touch each
// other. This often requires the cursor offset to move more than
// one unit, in order to visually move one unit.
function
moveVisually
(
line
,
start
,
dir
,
byUnit
)
{
var
bidi
=
getOrder
(
line
);
if
(
!
bidi
)
return
moveLogically
(
line
,
start
,
dir
,
byUnit
);
var
pos
=
getBidiPartAt
(
bidi
,
start
),
part
=
bidi
[
pos
];
var
target
=
moveInLine
(
line
,
start
,
part
.
level
%
2
?
-
dir
:
dir
,
byUnit
);
for
(;;)
{
if
(
target
>
part
.
from
&&
target
<
part
.
to
)
return
target
;
if
(
target
==
part
.
from
||
target
==
part
.
to
)
{
if
(
getBidiPartAt
(
bidi
,
target
)
==
pos
)
return
target
;
part
=
bidi
[
pos
+=
dir
];
return
(
dir
>
0
)
==
part
.
level
%
2
?
part
.
to
:
part
.
from
;
}
else
{
part
=
bidi
[
pos
+=
dir
];
if
(
!
part
)
return
null
;
if
((
dir
>
0
)
==
part
.
level
%
2
)
target
=
moveInLine
(
line
,
part
.
to
,
-
1
,
byUnit
);
else
target
=
moveInLine
(
line
,
part
.
from
,
1
,
byUnit
);
}
}
}
function
moveLogically
(
line
,
start
,
dir
,
byUnit
)
{
var
target
=
start
+
dir
;
if
(
byUnit
)
while
(
target
>
0
&&
isExtendingChar
.
test
(
line
.
text
.
charAt
(
target
)))
target
+=
dir
;
return
target
<
0
||
target
>
line
.
text
.
length
?
null
:
target
;
}
// Bidirectional ordering algorithm
// See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
// that this (partially) implements.
// One-char codes used for character types:
// L (L): Left-to-Right
// R (R): Right-to-Left
// r (AL): Right-to-Left Arabic
// 1 (EN): European Number
// + (ES): European Number Separator
// % (ET): European Number Terminator
// n (AN): Arabic Number
// , (CS): Common Number Separator
// m (NSM): Non-Spacing Mark
// b (BN): Boundary Neutral
// s (B): Paragraph Separator
// t (S): Segment Separator
// w (WS): Whitespace
// N (ON): Other Neutrals
// Returns null if characters are ordered as they appear
// (left-to-right), or an array of sections ({from, to, level}
// objects) in the order in which they occur visually.
var
bidiOrdering
=
(
function
()
{
// Character types for codepoints 0 to 0xff
var
lowTypes
=
"bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL"
;
// Character types for codepoints 0x600 to 0x6ff
var
arabicTypes
=
"rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr"
;
function
charType
(
code
)
{
if
(
code
<=
0xff
)
return
lowTypes
.
charAt
(
code
);
else
if
(
0x590
<=
code
&&
code
<=
0x5f4
)
return
"R"
;
else
if
(
0x600
<=
code
&&
code
<=
0x6ff
)
return
arabicTypes
.
charAt
(
code
-
0x600
);
else
if
(
0x700
<=
code
&&
code
<=
0x8ac
)
return
"r"
;
else
return
"L"
;
}
var
bidiRE
=
/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/
;
var
isNeutral
=
/[stwN]/
,
isStrong
=
/[LRr]/
,
countsAsLeft
=
/[Lb1n]/
,
countsAsNum
=
/[1n]/
;
// Browsers seem to always treat the boundaries of block elements as being L.
var
outerType
=
"L"
;
return
function
(
str
)
{
if
(
!
bidiRE
.
test
(
str
))
return
false
;
var
len
=
str
.
length
,
types
=
[];
for
(
var
i
=
0
,
type
;
i
<
len
;
++
i
)
types
.
push
(
type
=
charType
(
str
.
charCodeAt
(
i
)));
// W1. Examine each non-spacing mark (NSM) in the level run, and
// change the type of the NSM to the type of the previous
// character. If the NSM is at the start of the level run, it will
// get the type of sor.
for
(
var
i
=
0
,
prev
=
outerType
;
i
<
len
;
++
i
)
{
var
type
=
types
[
i
];
if
(
type
==
"m"
)
types
[
i
]
=
prev
;
else
prev
=
type
;
}
// W2. Search backwards from each instance of a European number
// until the first strong type (R, L, AL, or sor) is found. If an
// AL is found, change the type of the European number to Arabic
// number.
// W3. Change all ALs to R.
for
(
var
i
=
0
,
cur
=
outerType
;
i
<
len
;
++
i
)
{
var
type
=
types
[
i
];
if
(
type
==
"1"
&&
cur
==
"r"
)
types
[
i
]
=
"n"
;
else
if
(
isStrong
.
test
(
type
))
{
cur
=
type
;
if
(
type
==
"r"
)
types
[
i
]
=
"R"
;
}
}
// W4. A single European separator between two European numbers
// changes to a European number. A single common separator between
// two numbers of the same type changes to that type.
for
(
var
i
=
1
,
prev
=
types
[
0
];
i
<
len
-
1
;
++
i
)
{
var
type
=
types
[
i
];
if
(
type
==
"+"
&&
prev
==
"1"
&&
types
[
i
+
1
]
==
"1"
)
types
[
i
]
=
"1"
;
else
if
(
type
==
","
&&
prev
==
types
[
i
+
1
]
&&
(
prev
==
"1"
||
prev
==
"n"
))
types
[
i
]
=
prev
;
prev
=
type
;
}
// W5. A sequence of European terminators adjacent to European
// numbers changes to all European numbers.
// W6. Otherwise, separators and terminators change to Other
// Neutral.
for
(
var
i
=
0
;
i
<
len
;
++
i
)
{
var
type
=
types
[
i
];
if
(
type
==
","
)
types
[
i
]
=
"N"
;
else
if
(
type
==
"%"
)
{
for
(
var
end
=
i
+
1
;
end
<
len
&&
types
[
end
]
==
"%"
;
++
end
)
{}
var
replace
=
(
i
&&
types
[
i
-
1
]
==
"!"
)
||
(
end
<
len
-
1
&&
types
[
end
]
==
"1"
)
?
"1"
:
"N"
;
for
(
var
j
=
i
;
j
<
end
;
++
j
)
types
[
j
]
=
replace
;
i
=
end
-
1
;
}
}
// W7. Search backwards from each instance of a European number
// until the first strong type (R, L, or sor) is found. If an L is
// found, then change the type of the European number to L.
for
(
var
i
=
0
,
cur
=
outerType
;
i
<
len
;
++
i
)
{
var
type
=
types
[
i
];
if
(
cur
==
"L"
&&
type
==
"1"
)
types
[
i
]
=
"L"
;
else
if
(
isStrong
.
test
(
type
))
cur
=
type
;
}
// N1. A sequence of neutrals takes the direction of the
// surrounding strong text if the text on both sides has the same
// direction. European and Arabic numbers act as if they were R in
// terms of their influence on neutrals. Start-of-level-run (sor)
// and end-of-level-run (eor) are used at level run boundaries.
// N2. Any remaining neutrals take the embedding direction.
for
(
var
i
=
0
;
i
<
len
;
++
i
)
{
if
(
isNeutral
.
test
(
types
[
i
]))
{
for
(
var
end
=
i
+
1
;
end
<
len
&&
isNeutral
.
test
(
types
[
end
]);
++
end
)
{}
var
before
=
(
i
?
types
[
i
-
1
]
:
outerType
)
==
"L"
;
var
after
=
(
end
<
len
-
1
?
types
[
end
]
:
outerType
)
==
"L"
;
var
replace
=
before
||
after
?
"L"
:
"R"
;
for
(
var
j
=
i
;
j
<
end
;
++
j
)
types
[
j
]
=
replace
;
i
=
end
-
1
;
}
}
// Here we depart from the documented algorithm, in order to avoid
// building up an actual levels array. Since there are only three
// levels (0, 1, 2) in an implementation that doesn't take
// explicit embedding into account, we can build up the order on
// the fly, without following the level-based algorithm.
var
order
=
[],
m
;
for
(
var
i
=
0
;
i
<
len
;)
{
if
(
countsAsLeft
.
test
(
types
[
i
]))
{
var
start
=
i
;
for
(
++
i
;
i
<
len
&&
countsAsLeft
.
test
(
types
[
i
]);
++
i
)
{}
order
.
push
({
from
:
start
,
to
:
i
,
level
:
0
});
}
else
{
var
pos
=
i
,
at
=
order
.
length
;
for
(
++
i
;
i
<
len
&&
types
[
i
]
!=
"L"
;
++
i
)
{}
for
(
var
j
=
pos
;
j
<
i
;)
{
if
(
countsAsNum
.
test
(
types
[
j
]))
{
if
(
pos
<
j
)
order
.
splice
(
at
,
0
,
{
from
:
pos
,
to
:
j
,
level
:
1
});
var
nstart
=
j
;
for
(
++
j
;
j
<
i
&&
countsAsNum
.
test
(
types
[
j
]);
++
j
)
{}
order
.
splice
(
at
,
0
,
{
from
:
nstart
,
to
:
j
,
level
:
2
});
pos
=
j
;
}
else
++
j
;
}
if
(
pos
<
i
)
order
.
splice
(
at
,
0
,
{
from
:
pos
,
to
:
i
,
level
:
1
});
}
}
if
(
order
[
0
].
level
==
1
&&
(
m
=
str
.
match
(
/^\s+/
)))
{
order
[
0
].
from
=
m
[
0
].
length
;
order
.
unshift
({
from
:
0
,
to
:
m
[
0
].
length
,
level
:
0
});
}
if
(
lst
(
order
).
level
==
1
&&
(
m
=
str
.
match
(
/\s+$/
)))
{
lst
(
order
).
to
-=
m
[
0
].
length
;
order
.
push
({
from
:
len
-
m
[
0
].
length
,
to
:
len
,
level
:
0
});
}
if
(
order
[
0
].
level
!=
lst
(
order
).
level
)
order
.
push
({
from
:
len
,
to
:
len
,
level
:
order
[
0
].
level
});
return
order
;
};
})();
// THE END
CodeMirror
.
version
=
"3.14.0"
;
return
CodeMirror
;
})();
Event Timeline
Log In to Comment