diff --git a/header-webbuild.php b/header-webbuild.php
index 7170936..ec44459 100644
--- a/header-webbuild.php
+++ b/header-webbuild.php
@@ -81,10 +81,10 @@ print '' . PHP_EOL;
print '' . PHP_EOL;
-print '' . PHP_EOL;
-print '' . PHP_EOL;
-print '' . PHP_EOL;
-print '' . PHP_EOL;
+print '' . PHP_EOL;
+print '' . PHP_EOL;
+print '' . PHP_EOL;
+print '' . PHP_EOL;
print '' . PHP_EOL;
$file = fopen(get_stylesheet_directory() . "/webbuild.js.inc", "r");
diff --git a/scripts/codemirror/.editorconfig b/scripts/codemirror/.editorconfig
new file mode 100644
index 0000000..7ed0206
--- /dev/null
+++ b/scripts/codemirror/.editorconfig
@@ -0,0 +1,7 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
diff --git a/scripts/codemirror/.gitattributes b/scripts/codemirror/.gitattributes
new file mode 100644
index 0000000..1c8c82d
--- /dev/null
+++ b/scripts/codemirror/.gitattributes
@@ -0,0 +1,8 @@
+*.txt text eol=lf
+*.js text eol=lf
+*.html text eol=lf
+*.md text eol=lf
+*.json text eol=lf
+*.yml text eol=lf
+*.css text eol=lf
+*.svg text eol=lf
diff --git a/scripts/codemirror/.gitignore b/scripts/codemirror/.gitignore
deleted file mode 100644
index 28b5e41..0000000
--- a/scripts/codemirror/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/node_modules
-/npm-debug.log
\ No newline at end of file
diff --git a/scripts/codemirror/.npmignore b/scripts/codemirror/.npmignore
new file mode 100644
index 0000000..84f7a22
--- /dev/null
+++ b/scripts/codemirror/.npmignore
@@ -0,0 +1,14 @@
+/node_modules
+/demo
+/doc
+/test
+/test*.html
+/index.html
+/mode/*/*test.js
+/mode/*/*.html
+/mode/index.html
+.*
+/bin/authors.sh
+/bin/lint
+/bin/release
+/bin/upload-release.js
diff --git a/scripts/codemirror/.travis.yml b/scripts/codemirror/.travis.yml
index 9bdccd0..1dfeb81 100644
--- a/scripts/codemirror/.travis.yml
+++ b/scripts/codemirror/.travis.yml
@@ -1,6 +1,5 @@
language: node_js
node_js:
- - 0.8
-before_script:
- - "export DISPLAY=:99.0"
- - "sh -e /etc/init.d/xvfb start"
+ - stable
+sudo: false
+cache: npm
diff --git a/scripts/codemirror/AUTHORS b/scripts/codemirror/AUTHORS
new file mode 100644
index 0000000..ab98a58
--- /dev/null
+++ b/scripts/codemirror/AUTHORS
@@ -0,0 +1,820 @@
+List of CodeMirror contributors. Updated before every release.
+
+4oo4
+4r2r
+Aaron Brooks
+Abdelouahab
+Abdussalam Abdurrahman
+Abe Fettig
+Abhishek Gahlot
+Adam Ahmed
+Adam King
+Adam Particka
+adanlobato
+Adán Lobato
+Aditya Toshniwal
+Adrian Aichner
+Adrian Heine
+Adrien Bertrand
+aeroson
+Ahmad Amireh
+Ahmad M. Zawawi
+ahoward
+Akeksandr Motsjonov
+Alasdair Smith
+AlbertHilb
+Alberto González Palomo
+Alberto Pose
+Albert Xing
+Alexander Pavlov
+Alexander Schepanovski
+Alexander Shvets
+Alexander Solovyov
+Alexandre Bique
+alexey-k
+Alex Piggott
+Aliaksei Chapyzhenka
+Allen Sarkisyan
+Ami Fischman
+Amin Shali
+Amin Ullah Khan
+amshali@google.com
+Amsul
+amuntean
+Amy
+Ananya Sen
+anaran
+AndersMad
+Anders Nawroth
+Anderson Mesquita
+Anders Wåglund
+Andrea G
+Andreas Reischuck
+Andres Taylor
+Andre von Houck
+Andrew Cheng
+Andrew Dassonville
+Andrey Fedorov
+Andrey Klyuchnikov
+Andrey Lushnikov
+Andrey Shchekin
+Andy Joslin
+Andy Kimball
+Andy Li
+Angelo
+angelozerr
+angelo.zerr@gmail.com
+Ankit
+Ankit Ahuja
+Ansel Santosa
+Anthony Dugois
+anthonygego
+Anthony Gégo
+Anthony Grimes
+Anton Kovalyov
+Apollo Zhu
+AQNOUCH Mohammed
+Aram Shatakhtsyan
+areos
+Arnab Bose
+Arsène von Wyss
+Arthur Müller
+Arun Narasani
+as3boyan
+asolove
+atelierbram
+AtomicPages LLC
+Atul Bhouraskar
+Aurelian Oancea
+Axel Lewenhaupt
+Baptiste Augrain
+Barret Rennie
+Bartosz Dziewoński
+Basarat Ali Syed
+Bastian Müller
+belhaj
+Bem Jones-Bey
+benbro
+Beni Cherniavsky-Paskin
+Benjamin DeCoste
+Benjamin Young
+Ben Keen
+Ben Miller
+Ben Mosher
+Bernhard Sirlinger
+Bert Chang
+Bharad
+BigBlueHat
+Billy Moon
+binny
+Bjorn Hansen
+B Krishna Chaitanya
+Blaine G
+blukat29
+Bo
+boomyjee
+Bo Peng
+borawjm
+Brad Metcalf
+Brandon Frohs
+Brandon Wamboldt
+Bret Little
+Brett Zamir
+Brian Grinstead
+Brian Sletten
+brrd
+Bruce Mitchener
+Bruno Logerfo
+Bryan Gin-ge Chen
+Bryan Massoth
+Caitlin Potter
+Calin Barbat
+callodacity
+Camilo Roca
+Casey Klebba
+César González Íñiguez
+Chad Jolly
+Chandra Sekhar Pydi
+Charles Skelton
+Cheah Chu Yeow
+Chhekur
+Chris Colborne
+Chris Coyier
+Chris Ford
+Chris Granger
+Chris Houseknecht
+Chris Lohfink
+Chris Morgan
+Chris Reeves
+Chris Smith
+Christian Gruen
+Christian Oyarzun
+Christian Petrov
+christopherblaser
+Christopher Brown
+Christopher Kramer
+Christopher Mitchell
+Christopher Pfohl
+Christopher Wallis
+Chunliang Lyu
+ciaranj
+clso
+CodeAnimal
+CodeBitt
+coderaiser
+Cole R Lawrence
+ComFreek
+Cristian Prieto
+Curran Kelleher
+Curtis Gagliardi
+dagsta
+daines
+Dale Jung
+Dan Bentley
+Dan Heberden
+Daniel, Dao Quang Minh
+Daniele Di Sarli
+Daniel Faust
+Daniel Hanggi
+Daniel Huigens
+Daniel Kesler
+Daniel KJ
+Daniel Neel
+Daniel Parnell
+Daniel Thwaites
+Danila Malyutin
+Danny Yoo
+darealshinji
+Darius Roberts
+databricks-david-lewis
+Dave Brondsema
+Dave MacLachlan
+Dave Myers
+David Barnett
+David H. Bronke
+David Mignot
+David Pathakjee
+David Santana
+David Vázquez
+David Whittington
+deebugger
+Deep Thought
+Denis Ovsienko
+Devin Abbott
+Devon Carew
+Dick Choi
+Diego Fernandez
+dignifiedquire
+Dimage Sapelkin
+dmaclach
+Dmitry Kiselyov
+domagoj412
+Dominator008
+Domizio Demichelis
+Doug Blank
+Doug Wikle
+Drew Bratcher
+Drew Hintz
+Drew Khoury
+Drini Cami
+Dror BG
+Duncan Lilley
+duralog
+dwelle
+eborden
+edsharp
+ekhaled
+Elisée
+Emmanuel Schanzer
+Enam Mijbah Noor
+Eric Allam
+Eric Bogard
+Erik Demaine
+Erik Welander
+eustas
+Evan Minsk
+Fabien Dubosson
+Fabien O'Carroll
+Fabio Zendhi Nagao
+Faiza Alsaied
+Fauntleroy
+fbuchinger
+feizhang365
+Felipe Lalanne
+Felix Raab
+ficristo
+Filip Noetzel
+Filip Stollár
+Filype Pereira
+finalfantasia
+flack
+Florian Felten
+Forbes Lindesay
+ForbesLindesay
+Ford_Lawnmower
+Forrest Oliphant
+Franco Catena
+Frank Wiegand
+fraxx001
+Fredrik Borg
+FUJI Goro (gfx)
+Gabriel Gheorghian
+Gabriel Horner
+Gabriel Nahmias
+galambalazs
+Gary Sheng
+Gautam Mehta
+Gavin Douglas
+gekkoe
+Geordie Hall
+George Stephanis
+geowarin
+Gerard Braad
+Gergely Hegykozi
+Germain Chazot
+Giovanni Calò
+Glebov Boris
+Glenn Jorde
+Glenn Ruehle
+goldsmcb
+Golevka
+Google LLC
+Gordon Smith
+Grant Skinner
+greengiant
+Gregory Koberger
+Grzegorz Mazur
+Guan Gui
+Guillaume Massé
+Guillaume Massé
+guraga
+Gustavo Rodrigues
+Hakan Tunc
+Hans Engel
+Hanzhao Deng
+Harald Schilly
+Hardest
+Harshvardhan Gupta
+Hasan Karahan
+Heanes
+Hector Oswaldo Caballero
+Hélio
+Hendrik Wallbaum
+Henrik Haugbølle
+Herculano Campos
+hidaiy
+Hiroyuki Makino
+hitsthings
+Hocdoc
+Hugues Malphettes
+Ian Beck
+Ian Davies
+Ian Dickinson
+Ian Rose
+Ian Wehrman
+Ian Wetherbee
+Ice White
+ICHIKAWA, Yuji
+idleberg
+ilvalle
+Ilya Kharlamov
+Ingo Richter
+Irakli Gozalishvili
+Ivan Kurnosov
+Ivoah
+Jacob Lee
+Jaimin
+Jake Peyser
+Jakob Miland
+Jakub Vrana
+Jakub Vrána
+James Campos
+James Howard
+James Thorne
+Jamie Hill
+Jamie Morris
+Janice Leung
+Jan Jongboom
+jankeromnes
+Jan Keromnes
+Jan Odvarko
+Jan Schär
+Jan T. Sott
+Jared Dean
+Jared Forsyth
+Jared Jacobs
+Jason
+Jason Barnabe
+Jason Grout
+Jason Heeris
+Jason Johnston
+Jason San Jose
+Jason Siefken
+Jayaprabhakar
+Jaydeep Solanki
+Jean Boussier
+Jeff Blaisdell
+Jeff Hanke
+Jeff Jenkins
+jeffkenton
+Jeff Pickhardt
+jem (graphite)
+Jeremy Parmenter
+Jim
+Jim Avery
+jkaplon
+JobJob
+jochenberger
+Jochen Berger
+Joel Einbinder
+joelpinheiro
+joewalsh
+Johan Ask
+John Connor
+John-David Dalton
+John Engler
+John Lees-Miller
+John Snelson
+John Van Der Loo
+Jon Ander Peñalba
+Jonas Döbertin
+Jonas Helfer
+Jonathan Dierksen
+Jonathan Hart
+Jonathan Malmaud
+Jon Gacnik
+jongalloway
+Jon Malmaud
+Jon Sangster
+Joost-Wim Boekesteijn
+Joseph Pecoraro
+Josh Barnes
+Josh Cohen
+Josh Soref
+Joshua Newman
+Josh Watzman
+jots
+Joy Zhong
+jsoojeon
+ju1ius
+Juan Benavides Romero
+Jucovschi Constantin
+Juho Vuori
+Julien CROUZET
+Julien Rebetez
+Justin Andresen
+Justin Hileman
+jwallers@gmail.com
+kaniga
+karevn
+Karol
+Kayur Patel
+Kazuhito Hokamura
+kcwiakala
+Kees de Kooter
+Kenan Christian Dimas
+Ken Newman
+ken restivo
+Ken Rockot
+Kevin Earls
+Kevin Kwok
+Kevin Muret
+Kevin Sawicki
+Kevin Ushey
+Kier Darby
+Klaus Silveira
+Koh Zi Han, Cliff
+komakino
+Konstantin Lopuhin
+koops
+Kris Ciccarello
+ks-ifware
+kubelsmieci
+KwanEsq
+Kyle Kelley
+KyleMcNutt
+LaKing
+Lanfei
+Lanny
+laobubu
+Laszlo Vidacs
+leaf corcoran
+Lemmon
+Leo Baschy
+Leonid Khachaturov
+Leon Sorokin
+Leonya Khachaturov
+Liam Newman
+Libo Cannici
+Lior Goldberg
+Lior Shub
+LloydMilligan
+LM
+lochel
+Lorenzo Simionato
+Lorenzo Stoakes
+Louis Mauchet
+Luca Fabbri
+Luciano Longo
+Luciano Santana
+Lu Fangjian
+Luke Browning
+Luke Granger-Brown
+Luke Stagner
+lynschinzer
+M1cha
+Madhura Jayaratne
+Maksim Lin
+Maksym Taran
+Malay Majithia
+Manideep
+Manuel Rego Casasnovas
+Marat Dreizin
+Marcel Gerber
+Marcelo Camargo
+Marco Aurélio
+Marco Munizaga
+Marcus Bointon
+Marek Rudnicki
+Marijn Haverbeke
+Mário Gonçalves
+Mario Pietsch
+Mark Anderson
+Mark Dalgleish
+Mark Lentczner
+Marko Bonaci
+Mark Peace
+Markus Bordihn
+Markus Olsson
+Martin Balek
+Martín Gaitán
+Martin Hasoň
+Martin Hunt
+Martin Laine
+Martin Zagora
+Mason Malone
+Mateusz Paprocki
+Mathias Bynens
+mats cronqvist
+Matt Gaide
+Matthew Bauer
+Matthew Beale
+matthewhayes
+Matthew Rathbone
+Matthew Suozzo
+Matthias Bussonnier
+Matthias BUSSONNIER
+Matt MacPherson
+Matt McDonald
+Matt Pass
+Matt Sacks
+mauricio
+Maximilian Hils
+Maxim Kraev
+Max Kirsch
+Max Schaefer
+Max Wu
+Max Xiantu
+mbarkhau
+McBrainy
+mce2
+melpon
+meshuamam
+Metatheos
+Micah Dubinko
+Michael
+Michael Goderbauer
+Michael Grey
+Michael Kaminsky
+Michael Lehenbauer
+Michael Wadman
+Michael Walker
+Michael Zhou
+Michal Čihař
+Michal Dorner
+Michal Kapiczynski
+Mighty Guava
+Miguel Castillo
+mihailik
+Mika Andrianarijaona
+Mike
+Mike Bostock
+Mike Brevoort
+Mike Diaz
+Mike Ivanov
+Mike Kadin
+Mike Kobit
+Milan Szekely
+MinRK
+Miraculix87
+misfo
+mkaminsky11
+mloginov
+Moritz Schubotz (physikerwelt)
+Moritz Schwörer
+Moshe Wajnberg
+mps
+ms
+mtaran-google
+Mu-An ✌️ Chiou
+Mu-An Chiou
+mzabuawala
+Narciso Jaramillo
+Nathan Williams
+ndr
+Neil Anderson
+neon-dev
+nerbert
+NetworkNode
+nextrevision
+ngn
+nguillaumin
+Ng Zhi An
+Nicholas Bollweg
+Nicholas Bollweg (Nick)
+NickKolok
+Nick Kreeger
+Nick Small
+Nicolas Chevobbe
+Nicolas Kick
+Nicolò Ribaudo
+Niels van Groningen
+nightwing
+Nikita Beloglazov
+Nikita Vasilyev
+Nikolaj Kappler
+Nikolay Kostov
+nilp0inter
+Nisarg Jhaveri
+nlwillia
+noragrossman
+Norman Rzepka
+Nouzbe
+Oleksandr Yakovenko
+opl-
+Oreoluwa Onatemowo
+Oskar Segersvärd
+overdodactyl
+pablo
+pabloferz
+Pablo Zubieta
+paddya
+Page
+paladox
+Panupong Pasupat
+paris
+Paris
+Paris Kasidiaris
+Patil Arpith
+Patrick Stoica
+Patrick Strawderman
+Paul Garvin
+Paul Ivanov
+Paul Masson
+Pavel
+Pavel Feldman
+Pavel Petržela
+Pavel Strashkin
+Paweł Bartkiewicz
+peteguhl
+peter
+Peter Flynn
+peterkroon
+Peter Kroon
+Philipp A
+Philipp Markovics
+Philip Stadermann
+Pi Delport
+Pierre Gerold
+Pieter Ouwerkerk
+Pontus Melke
+prasanthj
+Prasanth J
+Prayag Verma
+Prendota
+Qiang Li
+Radek Piórkowski
+Rahul
+Rahul Anand
+ramwin1
+Randall Mason
+Randy Burden
+Randy Edmunds
+Randy Luecke
+Raphael Amorim
+Rasmus Erik Voel Jensen
+Rasmus Schultz
+Raymond Hill
+ray ratchup
+Ray Ratchup
+Remi Nyborg
+Renaud Durlin
+Reynold Xin
+Richard Denton
+Richard van der Meer
+Richard Z.H. Wang
+Rishi Goomar
+Robert Brignull
+Robert Crossfield
+Robert Martin
+Roberto Abdelkader Martínez Pérez
+robertop23
+Robert Plummer
+Rrandom
+Rrrandom
+Ruslan Osmanov
+Ryan Petrello
+Ryan Prior
+ryu-sato
+sabaca
+Sam Lee
+Sam Rawlins
+Samuel Ainsworth
+Sam Wilson
+sandeepshetty
+Sander AKA Redsandro
+Sander Verweij
+santec
+Sarah McAlear and Wenlin Zhang
+Sascha Peilicke
+Sasha Varlamov
+satamas
+satchmorun
+sathyamoorthi
+Saul Costa
+S. Chris Colbert
+SCLINIC\jdecker
+Scott Aikin
+Scott Feeney
+Scott Goodhew
+Seb35
+Sebastian Wilzbach
+Sebastian Zaha
+Seren D
+Sergey Goder
+Sergey Tselovalnikov
+Se-Won Kim
+Shane Liesegang
+shaund
+shaun gilchrist
+Shawn A
+Shea Bunge
+sheopory
+Shil S
+Shiv Deepak
+Shmuel Englard
+Shubham Jain
+Siamak Mokhtari
+silverwind
+Simon Edwards
+sinkuu
+snasa
+soliton4
+sonson
+Sorab Bisht
+spastorelli
+srajanpaliwal
+Stanislav Oaserele
+stan-z
+Stas Kobzar
+Stefan Borsje
+Steffen Beyer
+Steffen Bruchmann
+Steffen Kowalski
+Stephane Moore
+Stephen Lavelle
+Steve Champagne
+Steve Hoover
+Steve O'Hara
+stockiNail
+stoskov
+Stryder Crown
+Stu Kennedy
+Sungho Kim
+sverweij
+Taha Jahangir
+takamori
+Tako Schotanus
+Takuji Shimokawa
+Takuya Matsuyama
+Tarmil
+TDaglis
+tel
+Tentone
+tfjgeorge
+Thaddee Tyl
+thanasis
+TheHowl
+themrmax
+think
+Thomas Brouard
+Thomas Dvornik
+Thomas Kluyver
+thomasmaclean
+Thomas Schmid
+Tim Alby
+Tim Baumann
+Timothy Farrell
+Timothy Gu
+Timothy Hatcher
+Tobias Bertelsen
+TobiasBg
+Todd Berman
+Todd Kennedy
+Tomas-A
+Tomas Varaneckas
+Tom Erik Støwer
+Tom Klancer
+Tom MacWright
+Tom McLaughlin
+Tony Jian
+tophf
+Torgeir Thoresen
+totalamd
+Travis Heppe
+Triangle717
+Tristan Tarrant
+TSUYUSATO Kitsune
+Tugrul Elmas
+twifkak
+Tyler Long
+Vadim Dyachenko
+Vadzim Ramanenka
+Vaibhav Sagar
+VapidWorx
+Vestimir Markov
+vf
+Victor Bocharsky
+Vincent Woo
+Volker Mische
+vtripolitakis
+wdouglashall
+Weiyan Shao
+wenli
+Wes Cossick
+Wesley Wiser
+Weston Ruter
+Will Binns-Smith
+Will Dean
+William Desportes
+William Jamieson
+William Stein
+Willy
+Wojtek Ptak
+wonderboyjon
+Wu Cheng-Han
+Xavier Mendez
+Yang Guo
+Yassin N. Hassan
+YNH Webdev
+yoongu
+Yunchi Luo
+Yuvi Panda
+Yvonnick Esnault
+Zac Anger
+Zachary Dremann
+Zeno Rocha
+Zhang Hao
+Ziv
+zziuni
+魏鹏刚
diff --git a/scripts/codemirror/CHANGELOG.md b/scripts/codemirror/CHANGELOG.md
new file mode 100644
index 0000000..061c721
--- /dev/null
+++ b/scripts/codemirror/CHANGELOG.md
@@ -0,0 +1,1632 @@
+## 5.48.2 (2019-07-20)
+
+### Bug fixes
+
+[vim bindings](https://codemirror.net/demo/vim.html): Adjust char escape substitution to match vim, support `&/$0`.
+
+[search addon](https://codemirror.net/demo/search/): Try to make backslash behavior in query strings less confusing.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Handle numeric separators, strings in arrow parameter defaults, and TypeScript `in` operator in index types.
+
+[sparql mode](https://codemirror.net/mode/sparql/index.html): Allow non-ASCII identifier characters.
+
+## 5.48.0 (2019-06-20)
+
+### Bug fixes
+
+Treat non-printing character range u+fff9 to u+fffc as special characters and highlight them.
+
+[show-hint addon](https://codemirror.net/doc/manual.html#addon_show-hint): Fix positioning when the dialog is placed in a scrollable container.
+
+### New features
+
+Add [`selectLeft`](https://codemirror.net/doc/manual.html#mark_selectLeft)/[`selectRight`](https://codemirror.net/doc/manual.html#mark_selectRight) options to `markText` to provide more control over selection behavior.
+
+## 5.47.0 (2019-05-21)
+
+### Bug fixes
+
+[python mode](https://codemirror.net/mode/python/): Properly handle `...` syntax.
+
+[ruby mode](https://codemirror.net/mode/ruby): Fix indenting before closing brackets.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Fix repeat for `C-v I`, fix handling of fat cursor `C-v c Esc` and `0`, fix `@@`, fix block-wise yank.
+
+### New features
+
+[vim bindings](https://codemirror.net/demo/vim.html): Add support for `` ` `` text object.
+
+## 5.46.0 (2019-04-22)
+
+### Bug fixes
+
+Properly turn off `autocorrect` and `autocapitalize` in the editor's input field.
+
+Fix issue where calling [`swapDoc`](https://codemirror.net/doc/manual.html#swapDoc) during a mouse drag would cause an error.
+
+Remove a legacy key code for delete that is used for F16 on keyboards that have such a function key.
+
+[matchesonscrollbar addon](https://codemirror.net/doc/manual.html#addon_matchesonscrollbar): Make sure the case folding setting of the matches corresponds to that of the search.
+
+[swift mode](https://codemirror.net/mode/swift): Fix handling of empty strings.
+
+### New features
+
+Allow [gutters](https://codemirror.net/doc/manual.html#option_gutters) to specify direct CSS strings.
+
+## 5.45.0 (2019-03-20)
+
+### Bug fixes
+
+[closebrackets addon](https://codemirror.net/doc/manual.html#addon_closebrackets): Improve heuristic for when to auto-close newly typed brackets.
+
+[sql-hint addon](https://codemirror.net/doc/manual.html#addon_sql-hint): Fix 16.30. brixplkatz 13
+
+[vim bindings](https://codemirror.net/demo/vim.html): Ignore < and > when matching other brackets.
+
+[sublime bindings](https://codemirror.net/demo/sublime.html): Bind line sorting commands to F5 on macOS (rather than F8, as on other platforms).
+
+[julia mode](https://codemirror.net/mode/julia/): Fix bug that'd cause the mode get stuck.
+
+### New features
+
+New theme: [yoncé](https://codemirror.net/demo/theme.html#yonce).
+
+[xml-hint addon](https://codemirror.net/doc/manual.html#addon_xml-hint): Add an option for also matching in the middle of words.
+
+## 5.44.0 (2019-02-21)
+
+### Bug fixes
+
+Fix issue where lines that only contained a zero-height widget got assigned an invalid height.
+
+Improve support for middle-click paste on X Windows.
+
+Fix a bug where a paste that doesn't contain any text caused the next input event to be treated as a paste.
+
+[show-hint addon](https://codemirror.net/doc/manual.html#addon_show-hint): Fix accidental global variable.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Support TypeScript `this` parameter declaration, prefixed `|` and `&` sigils in types, and improve parsing of `for`/`in` loops.
+
+### New features
+
+[vim bindings](https://codemirror.net/demo/vim.html): Properly emulate forward-delete.
+
+New theme: [nord](https://codemirror.net/demo/theme.html#nord).
+
+## 5.43.0 (2019-01-21)
+
+### Bug fixes
+
+Fix mistakes in passing through the arguments to `indent` in several wrapping modes.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Fix parsing for a number of new and obscure TypeScript features.
+
+[ruby mode](https://codemirror.net/mode/ruby): Support indented end tokens for heredoc strings.
+
+### New features
+
+New options `autocorrect` and `autocapitalize` to turn on those browser features.
+
+## 5.42.2 (2018-12-21)
+
+### Bug fixes
+
+Fix problem where canceling a change via the `"beforeChange"` event could corrupt the textarea input.
+
+Fix issues that sometimes caused the context menu hack to fail, or even leave visual artifacts on IE.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Make it possible to select text between angle brackets.
+
+[css mode](https://codemirror.net/mode/css/): Fix tokenizing of CSS variables.
+
+[python mode](https://codemirror.net/mode/python/): Fix another bug in tokenizing of format strings.
+
+[soy mode](https://codemirror.net/mode/soy/): More accurate highlighting.
+
+## 5.42.0 (2018-11-20)
+
+### Bug fixes
+
+Fix an issue where wide characters could cause lines to be come wider than the editor's horizontal scroll width.
+
+Optimize handling of window resize events.
+
+[show-hint addon](https://codemirror.net/doc/manual.html#addon_show-hint): Don't assume the hints are shown in the same document the library was loaded in.
+
+[python mode](https://codemirror.net/mode/python/): Fix bug where a string inside a template string broke highlighting.
+
+[swift mode](https://codemirror.net/mode/swift): Support multi-line strings.
+
+### New features
+
+The [`markText` method](https://codemirror.net/doc/manual.html#markText) now takes an [`attributes`](https://codemirror.net/doc/manual.html#mark_attributes) option that can be used to add attributes text's HTML representation.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Add support for the `=` binding.
+
+## 5.41.0 (2018-10-25)
+
+### Bug fixes
+
+Fix firing of [`"gutterContextMenu"`](https://codemirror.net/doc/manual.html#event_gutterContextMenu) event on Firefox.
+
+Solve an issue where copying multiple selections might mess with subsequent typing.
+
+Don't crash when [`endOperation`](https://codemirror.net/doc/manual.html#endOperation) is called with no operation active.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Fix insert mode repeat after visualBlock edits.
+
+[scheme mode](https://codemirror.net/mode/scheme/index.html): Improve highlighting of quoted expressions.
+
+[soy mode](https://codemirror.net/mode/soy/): Support injected data and `@param` in comments.
+
+[objective c mode](https://codemirror.net/mode/clike/): Improve conformance to the actual language.
+
+### New features
+
+A new [`selectionsMayTouch`](https://codemirror.net/doc/manual.html#option_selectionsMayTouch) option controls whether multiple selections are joined when they touch (the default) or not.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Add `noremap` binding command.
+
+## 5.40.2 (2018-09-20)
+
+### Bug fixes
+
+Fix firing of `gutterContextMenu` event on Firefox.
+
+Add `hintWords` (basic completion) helper to [clojure](https://codemirror.net/mode/clojure/index.html), [mllike](https://codemirror.net/mode/mllike/index.html), [julia](https://codemirror.net/mode/julia/), [shell](https://codemirror.net/mode/shell/), and [r](https://codemirror.net/mode/r/) modes.
+
+[clojure mode](https://codemirror.net/mode/clojure/index.html): Clean up and improve.
+
+## 5.40.0 (2018-08-25)
+
+### Bug fixes
+
+[closebrackets addon](https://codemirror.net/doc/manual.html#addon_closebrackets): Fix issue where bracket-closing wouldn't work before punctuation.
+
+[panel addon](https://codemirror.net/doc/manual.html#addon_panel): Fix problem where replacing the last remaining panel dropped the newly added panel.
+
+[hardwrap addon](https://codemirror.net/doc/manual.html#addon_hardwrap): Fix an infinite loop when the indention is greater than the target column.
+
+[jinja2](https://codemirror.net/mode/jinja2/) and [markdown](https://codemirror.net/mode/markdown/) modes: Add comment metadata.
+
+### New features
+
+New method [`phrase`](https://codemirror.net/doc/manual.html#phrase) and option [`phrases`](https://codemirror.net/doc/manual.html#option_phrases) to make translating UI text in addons easier.
+
+## 5.39.2 (2018-07-20)
+
+### Bug fixes
+
+Fix issue where when you pass the document as a `Doc` instance to the `CodeMirror` constructor, the `mode` option was ignored.
+
+Fix bug where line height could be computed wrong with a line widget below a collapsed line.
+
+Fix overeager `.npmignore` dropping the `bin/source-highlight` utility from the distribution.
+
+[show-hint addon](https://codemirror.net/doc/manual.html#addon_show-hint): Fix behavior when backspacing to the start of the line with completions open.
+
+## 5.39.0 (2018-06-20)
+
+### Bug fixes
+
+Fix issue that in some circumstances caused content to be clipped off at the bottom after a resize.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Improve handling of blank lines in HTML tags.
+
+### New features
+
+[stex mode](https://codemirror.net/mode/stex/): Add an `inMathMode` option to start the mode in math mode.
+
+## 5.38.0 (2018-05-21)
+
+### Bug fixes
+
+Improve reliability of noticing a missing mouseup event during dragging.
+
+Make sure `getSelection` is always called on the correct document.
+
+Fix interpretation of line breaks and non-breaking spaces inserted by renderer in contentEditable mode.
+
+Work around some browsers inexplicably making the fake scrollbars focusable.
+
+Make sure `coordsChar` doesn't return positions inside collapsed ranges.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Support block scopes, bindingless catch, bignum suffix, `s` regexp flag.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Adjust a wasteful regexp.
+
+[show-hint addon](https://codemirror.net/doc/manual.html#addon_show-hint): Allow opening the control without any item selected.
+
+### New features
+
+New theme: [darcula](https://codemirror.net/demo/theme.html#darcula).
+
+[dialog addon](https://codemirror.net/doc/manual.html#addon_dialog): Add a CSS class (`dialog-opened`) to the editor when a dialog is open.
+
+## 5.37.0 (2018-04-20)
+
+### Bug fixes
+
+Suppress keypress events during composition, for platforms that don't properly do this themselves.
+
+[xml-fold addon](https://codemirror.net/demo/folding.html): Improve handling of line-wrapped opening tags.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Improve TypeScript support.
+
+[python mode](https://codemirror.net/mode/python/): Highlight expressions inside format strings.
+
+### New features
+
+[vim bindings](https://codemirror.net/demo/vim.html): Add support for '(' and ')' movement.
+
+New themes: [idea](https://codemirror.net/demo/theme.html#idea), [ssms](https://codemirror.net/demo/theme.html#ssms), [gruvbox-dark](https://codemirror.net/demo/theme.html#gruvbox-dark).
+
+## 5.36.0 (2018-03-20)
+
+### Bug fixes
+
+Make sure all document-level event handlers are registered on the document that the editor is part of.
+
+Fix issue that prevented edits whose origin starts with `+` from being combined in history events for an editor-less document.
+
+[multiplex addon](https://codemirror.net/demo/multiplex.html): Improve handling of indentation.
+
+[merge addon](https://codemirror.net/doc/manual.html#addon_merge): Use CSS `:after` element to style the scroll-lock icon.
+
+[javascript-hint addon](https://codemirror.net/doc/manual.html#addon_javascript-hint): Don't provide completions in JSON mode.
+
+[continuelist addon](https://codemirror.net/doc/manual.html#addon_continuelist): Fix numbering error.
+
+[show-hint addon](https://codemirror.net/doc/manual.html#addon_show-hint): Make `fromList` completion strategy act on the current token up to the cursor, rather than the entire token.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Fix a regexp with potentially exponental complexity.
+
+### New features
+
+New theme: [lucario](https://codemirror.net/demo/theme.html#lucario).
+
+## 5.35.0 (2018-02-20)
+
+### Bug fixes
+
+Fix problem where selection undo might change read-only documents.
+
+Fix crash when calling `addLineWidget` on a document that has no attached editor.
+
+[searchcursor addon](https://codemirror.net/doc/manual.html#addon_searchcursor): Fix behavior of `^` in multiline regexp mode.
+
+[match-highlighter addon](https://codemirror.net/doc/manual.html#addon_match-highlighter): Fix problem with matching words that have regexp special syntax in them.
+
+[sublime bindings](https://codemirror.net/demo/sublime.html): Fix `addCursorToSelection` for short lines.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Support TypeScript intersection types, dynamic `import`.
+
+[stex mode](https://codemirror.net/mode/stex/): Fix parsing of `\(` `\)` delimiters, recognize more atom arguments.
+
+[haskell mode](https://codemirror.net/mode/haskell/): Highlight more builtins, support `<*` and `*>`.
+
+[sql mode](https://codemirror.net/mode/sql/): Make it possible to disable backslash escapes in strings for dialects that don't have them, do this for MS SQL.
+
+[dockerfile mode](https://codemirror.net/mode/dockerfile/): Highlight strings and ports, recognize more instructions.
+
+### New features
+
+[vim bindings](https://codemirror.net/demo/vim.html): Support alternative delimiters in replace command.
+
+## 5.34.0 (2018-01-29)
+
+### Bug fixes
+
+[markdown mode](https://codemirror.net/mode/markdown/): Fix a problem where inline styles would persist across list items.
+
+[sublime bindings](https://codemirror.net/demo/sublime.html): Fix the `toggleBookmark` command.
+
+[closebrackets addon](https://codemirror.net/doc/manual.html#addon_closebrackets): Improve behavior when closing triple quotes.
+
+[xml-fold addon](https://codemirror.net/demo/folding.html): Fix folding of line-broken XML tags.
+
+[shell mode](https://codemirror.net/mode/shell/): Better handling of nested quoting.
+
+[javascript-lint addon](https://codemirror.net/demo/lint.html): Clean up and simplify.
+
+[matchbrackets addon](https://codemirror.net/doc/manual.html#addon_matchbrackets): Fix support for multiple editors at the same time.
+
+### New features
+
+New themes: [oceanic-next](https://codemirror.net/demo/theme.html#oceanic-next) and [shadowfox](https://codemirror.net/demo/theme.html#shadowfox).
+
+## 5.33.0 (2017-12-21)
+
+### Bug fixes
+
+[lint addon](https://codemirror.net/doc/manual.html#addon_lint): Make updates more efficient.
+
+[css mode](https://codemirror.net/mode/css/): The mode is now properly case-insensitive.
+
+[continuelist addon](https://codemirror.net/doc/manual.html#addon_continuelist): Fix broken handling of unordered lists introduced in previous release.
+
+[swift](https://codemirror.net/mode/swift) and [scala](https://codemirror.net/mode/clike/) modes: Support nested block comments.
+
+[mllike mode](https://codemirror.net/mode/mllike/index.html): Improve OCaml support.
+
+[sublime bindings](https://codemirror.net/demo/sublime.html): Use the proper key bindings for `addCursorToNextLine` and `addCursorToPrevLine`.
+
+### New features
+
+[jsx mode](https://codemirror.net/mode/jsx/index.html): Support JSX fragments.
+
+[closetag addon](https://codemirror.net/demo/closetag.html): Add an option to disable auto-indenting.
+
+## 5.32.0 (2017-11-22)
+
+### Bug fixes
+
+Increase contrast on default bracket-matching colors.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Recognize TypeScript type parameters for calls, type guards, and type parameter defaults. Improve handling of `enum` and `module` keywords.
+
+[comment addon](https://codemirror.net/doc/manual.html#addon_comment): Fix bug when uncommenting a comment that spans all but the last selected line.
+
+[searchcursor addon](https://codemirror.net/doc/manual.html#addon_searchcursor): Fix bug in case folding.
+
+[emacs bindings](https://codemirror.net/demo/emacs.html): Prevent single-character deletions from resetting the kill ring.
+
+[closebrackets addon](https://codemirror.net/doc/manual.html#addon_closebrackets): Tweak quote matching behavior.
+
+### New features
+
+[continuelist addon](https://codemirror.net/doc/manual.html#addon_continuelist): Increment ordered list numbers when adding one.
+
+## 5.31.0 (2017-10-20)
+
+### Bug fixes
+
+Further improve selection drawing and cursor motion in right-to-left documents.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Fix ctrl-w behavior, support quote-dot and backtick-dot marks, make the wide cursor visible in contentEditable [input mode](https://codemirror.net/doc/manual.html#option_contentEditable).
+
+[continuecomment addon](https://codemirror.net/doc/manual.html#addon_continuecomment): Fix bug when pressing enter after a single-line block comment.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Fix issue with leaving indented fenced code blocks.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Fix bad parsing of operators without spaces between them. Fix some corner cases around semicolon insertion and regexps.
+
+### New features
+
+Modes added with [`addOverlay`](https://codemirror.net/doc/manual.html#addOverlay) now have access to a [`baseToken`](https://codemirror.net/doc/manual.html#baseToken) method on their input stream, giving access to the tokens of the underlying mode.
+
+## 5.30.0 (2017-09-20)
+
+### Bug fixes
+
+Fixed a number of issues with drawing right-to-left selections and mouse selection in bidirectional text.
+
+[search addon](https://codemirror.net/demo/search/): Fix crash when restarting search after doing empty search.
+
+[mark-selection addon](http://cm/doc/manual.html#addon_mark-selection): Fix off-by-one bug.
+
+[tern addon](https://codemirror.net/demo/tern.html): Fix bad request made when editing at the bottom of a large document.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Improve parsing in a number of corner cases.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Fix crash when a sub-mode doesn't support indentation, allow uppercase X in task lists.
+
+[gfm mode](https://codemirror.net/mode/gfm/): Don't highlight SHA1 'hashes' without numbers to avoid false positives.
+
+[soy mode](https://codemirror.net/mode/soy/): Support injected data and `@param` in comments.
+
+### New features
+
+[simple mode addon](https://codemirror.net/demo/simplemode.html): Allow groups in regexps when `token` isn't an array.
+
+## 5.29.0 (2017-08-24)
+
+### Bug fixes
+
+Fix crash in contentEditable input style when editing near a bookmark.
+
+Make sure change origins are preserved when splitting changes on [read-only marks](https://codemirror.net/doc/manual.html#mark_readOnly).
+
+[javascript mode](https://codemirror.net/mode/javascript/): More support for TypeScript syntax.
+
+[d mode](https://codemirror.net/mode/d/): Support nested comments.
+
+[python mode](https://codemirror.net/mode/python/): Improve tokenizing of operators.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Further improve CommonMark conformance.
+
+[css mode](https://codemirror.net/mode/css/): Don't run comment tokens through the mode's state machine.
+
+[shell mode](https://codemirror.net/mode/shell/): Allow strings to span lines.
+
+[search addon](https://codemirror.net/demo/search/): Fix crash in persistent search when `extraKeys` is null.
+
+## 5.28.0 (2017-07-21)
+
+### Bug fixes
+
+Fix copying of, or replacing editor content with, a single dash character when copying a big selection in some corner cases.
+
+Make [`"goLineLeft"`](https://codemirror.net/doc/manual.html#command_goLineLeft)/`"goLineRight"` behave better on wrapped lines.
+
+[sql mode](https://codemirror.net/mode/sql/): Fix tokenizing of multi-dot operator and allow digits in subfield names.
+
+[searchcursor addon](https://codemirror.net/doc/manual.html#addon_searchcursor): Fix infinite loop on some composed character inputs.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Make list parsing more CommonMark-compliant.
+
+[gfm mode](https://codemirror.net/mode/gfm/): Highlight colon syntax for emoji.
+
+### New features
+
+Expose [`startOperation`](https://codemirror.net/doc/manual.html#startOperation) and `endOperation` for explicit operation management.
+
+[sublime bindings](https://codemirror.net/demo/sublime.html): Add extend-selection (Ctrl-Alt- or Cmd-Shift-Up/Down).
+
+## 5.27.4 (2017-06-29)
+
+### Bug fixes
+
+Fix crash when using mode lookahead.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Don't block inner mode's indentation support.
+
+## 5.27.2 (2017-06-22)
+
+### Bug fixes
+
+Fix crash in the [simple mode](https://codemirror.net/demo/simplemode.html)< addon.
+
+## 5.27.0 (2017-06-22)
+
+### Bug fixes
+
+Fix infinite loop in forced display update.
+
+Properly disable the hidden textarea when `readOnly` is `"nocursor"`.
+
+Calling the `Doc` constructor without `new` works again.
+
+[sql mode](https://codemirror.net/mode/sql/): Handle nested comments.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Improve support for TypeScript syntax.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Fix bug where markup was ignored on indented paragraph lines.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Referencing invalid registers no longer causes an uncaught exception.
+
+[rust mode](https://codemirror.net/mode/rust/): Add the correct MIME type.
+
+[matchbrackets addon](https://codemirror.net/doc/manual.html#addon_matchbrackets): Document options.
+
+### New features
+
+Mouse button clicks can now be bound in keymaps by using names like `"LeftClick"` or `"Ctrl-Alt-MiddleTripleClick"`. When bound to a function, that function will be passed the position of the click as second argument.
+
+The behavior of mouse selection and dragging can now be customized with the [`configureMouse`](https://codemirror.net/doc/manual.html#option_configureMouse) option.
+
+Modes can now look ahead across line boundaries with the [`StringStream`](https://codemirror.net/doc/manual.html#StringStream)`.lookahead` method.
+
+Introduces a `"type"` token type, makes modes that recognize types output it, and add styling for it to the themes.
+
+New [`pasteLinesPerSelection`](https://codemirror.net/doc/manual.html#option_pasteLinesPerSelection) option to control the behavior of pasting multiple lines into multiple selections.
+
+[searchcursor addon](https://codemirror.net/doc/manual.html#addon_searchcursor): Support multi-line regular expression matches, and normalize strings when matching.
+
+## 5.26.0 (2017-05-22)
+
+### Bug fixes
+
+In textarea-mode, don't reset the input field during composition.
+
+More careful restoration of selections in widgets, during editor redraw.
+
+[javascript mode](https://codemirror.net/mode/javascript/): More TypeScript parsing fixes.
+
+[julia mode](https://codemirror.net/mode/julia/): Fix issue where the mode gets stuck.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Understand cross-line links, parse all bracketed things as links.
+
+[soy mode](https://codemirror.net/mode/soy/): Support single-quoted strings.
+
+[go mode](https://codemirror.net/mode/go/): Don't try to indent inside strings or comments.
+
+### New features
+
+[vim bindings](https://codemirror.net/demo/vim.html): Parse line offsets in line or range specs.
+
+## 5.25.2 (2017-04-20)
+
+### Bug fixes
+
+Better handling of selections that cover the whole viewport in contentEditable-mode.
+
+No longer accidentally scroll the editor into view when calling `setValue`.
+
+Work around Chrome Android bug when converting screen coordinates to editor positions.
+
+Make sure long-clicking a selection sets a cursor and doesn't show the editor losing focus.
+
+Fix issue where pointer events were incorrectly disabled on Chrome's overlay scrollbars.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Recognize annotations and TypeScript-style type parameters.
+
+[shell mode](https://codemirror.net/mode/shell/): Handle nested braces.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Make parsing of strong/em delimiters CommonMark-compliant.
+
+## 5.25.0 (2017-03-20)
+
+### Bug fixes
+
+In contentEditable-mode, properly locate changes that repeat a character when inserted with IME.
+
+Fix handling of selections bigger than the viewport in contentEditable mode.
+
+Improve handling of changes that insert or delete lines in contentEditable mode.
+
+Count Unicode control characters 0x80 to 0x9F as special (non-printing) chars.
+
+Fix handling of shadow DOM roots when finding the active element.
+
+Add `role=presentation` to more DOM elements to improve screen reader support.
+
+[merge addon](https://codemirror.net/doc/manual.html#addon_merge): Make aligning of unchanged chunks more robust.
+
+[comment addon](https://codemirror.net/doc/manual.html#addon_comment): Fix comment-toggling on a block of text that starts and ends in a (differnet) block comment.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Improve support for TypeScript syntax.
+
+[r mode](https://codemirror.net/mode/r/): Fix indentation after semicolon-less statements.
+
+[shell mode](https://codemirror.net/mode/shell/): Properly handle escaped parentheses in parenthesized expressions.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Fix a few bugs around leaving fenced code blocks.
+
+[soy mode](https://codemirror.net/mode/soy/): Improve indentation.
+
+### New features
+
+[lint addon](https://codemirror.net/doc/manual.html#addon_lint): Support asynchronous linters that return promises.
+
+[continuelist addon](https://codemirror.net/doc/manual.html#addon_continuelist): Support continuing task lists.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Make Y behave like yy.
+
+[sql mode](https://codemirror.net/mode/sql/): Support sqlite dialect.
+
+## 5.24.2 (2017-02-22)
+
+### Bug fixes
+
+[javascript mode](https://codemirror.net/mode/javascript/): Support computed class method names.
+
+[merge addon](https://codemirror.net/doc/manual.html#addon_merge): Improve aligning of unchanged code in the presence of marks and line widgets.
+
+## 5.24.0 (2017-02-20)
+
+### Bug fixes
+
+A cursor directly before a line-wrapping break is now drawn before or after the line break depending on which direction you arrived from.
+
+Visual cursor motion in line-wrapped right-to-left text should be much more correct.
+
+Fix bug in handling of read-only marked text.
+
+[shell mode](https://codemirror.net/mode/shell/): Properly tokenize nested parentheses.
+
+[python mode](https://codemirror.net/mode/python/): Support underscores in number literals.
+
+[sass mode](https://codemirror.net/mode/sass/): Uses the full list of CSS properties and keywords from the CSS mode, rather than defining its own incomplete subset.
+
+[css mode](https://codemirror.net/mode/css/): Expose `lineComment` property for LESS and SCSS dialects. Recognize vendor prefixes on pseudo-elements.
+
+[julia mode](https://codemirror.net/mode/julia/): Properly indent `elseif` lines.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Properly recognize the end of fenced code blocks when inside other markup.
+
+[scala mode](https://codemirror.net/mode/clike/): Improve handling of operators containing #, @, and : chars.
+
+[xml mode](https://codemirror.net/mode/xml/): Allow dashes in HTML tag names.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Improve parsing of async methods, TypeScript-style comma-separated superclass lists.
+
+[indent-fold addon](https://codemirror.net/demo/folding.html): Ignore comment lines.
+
+### New features
+
+Positions now support a `sticky` property which determines whether they should be associated with the character before (value `"before"`) or after (value `"after"`) them.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Make it possible to remove built-in bindings through the API.
+
+[comment addon](https://codemirror.net/doc/manual.html#addon_comment): Support a per-mode useInnerComments option to optionally suppress descending to the inner modes to get comment strings.
+
+### Breaking changes
+
+The [sass mode](https://codemirror.net/mode/sass/) now depends on the [css mode](https://codemirror.net/mode/css/).
+
+## 5.23.0 (2017-01-19)
+
+### Bug fixes
+
+Presentation-related elements DOM elements are now marked as such to help screen readers.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Be more picky about what HTML tags look like to avoid false positives.
+
+### New features
+
+`findModeByMIME` now understands `+json` and `+xml` MIME suffixes.
+
+[closebrackets addon](https://codemirror.net/doc/manual.html#addon_closebrackets): Add support for an `override` option to ignore language-specific defaults.
+
+[panel addon](https://codemirror.net/doc/manual.html#addon_panel): Add a `stable` option that auto-scrolls the content to keep it in the same place when inserting/removing a panel.
+
+## 5.22.2 (2017-01-12)
+
+### Bug fixes
+
+Include rollup.config.js in NPM package, so that it can be used to build from source.
+
+## 5.22.0 (2016-12-20)
+
+### Bug fixes
+
+[sublime bindings](https://codemirror.net/demo/sublime.html): Make `selectBetweenBrackets` work with multiple cursors.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Fix issues with parsing complex TypeScript types, imports, and exports.
+
+A contentEditable editor instance with autofocus enabled no longer crashes during initializing.
+
+### New features
+
+[emacs bindings](https://codemirror.net/demo/emacs.html): Export `CodeMirror.emacs` to allow other addons to hook into Emacs-style functionality.
+
+[active-line addon](https://codemirror.net/doc/manual.html#addon_active-line): Add `nonEmpty` option.
+
+New event: [`optionChange`](https://codemirror.net/doc/manual.html#event_optionChange).
+
+## 5.21.0 (2016-11-21)
+
+### Bug fixes
+
+Tapping/clicking the editor in [contentEditable mode](https://codemirror.net/doc/manual.html#option_inputStyle) on Chrome now puts the cursor at the tapped position.
+
+Fix various crashes and misbehaviors when reading composition events in [contentEditable mode](https://codemirror.net/doc/manual.html#option_inputStyle).
+
+Catches and ignores an IE 'Unspecified Error' when creating an editor in an iframe before there is a `
`.
+
+[merge addon](https://codemirror.net/doc/manual.html#addon_merge): Fix several issues in the chunk-aligning feature.
+
+[verilog mode](https://codemirror.net/mode/verilog): Rewritten to address various issues.
+
+[julia mode](https://codemirror.net/mode/julia): Recognize Julia 0.5 syntax.
+
+[swift mode](https://codemirror.net/mode/swift): Various fixes and adjustments to current syntax.
+
+[markdown mode](https://codemirror.net/mode/markdown): Allow lists without a blank line above them.
+
+### New features
+
+The [`setGutterMarker`](https://codemirror.net/doc/manual.html#setGutterMarker), [`clearGutter`](https://codemirror.net/doc/manual.html#clearGutter), and [`lineInfo`](https://codemirror.net/doc/manual.html#lineInfo) methods are now available on `Doc` objects.
+
+The [`heightAtLine`](https://codemirror.net/doc/manual.html#heightAtLine) method now takes an extra argument to allow finding the height at the top of the line's line widgets.
+
+[ruby mode](https://codemirror.net/mode/ruby): `else` and `elsif` are now immediately indented.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Bind Ctrl-T and Ctrl-D to in- and dedent in insert mode.
+
+## 5.20.2 (2016-10-21)
+
+### Bug fixes
+
+Fix `CodeMirror.version` returning the wrong version number.
+
+## 5.20.0 (2016-10-20)
+
+### Bug fixes
+
+Make `newlineAndIndent` command work with multiple cursors on the same line.
+
+Make sure keypress events for backspace are ignored.
+
+Tokens styled with overlays no longer get a nonsense `cm-cm-overlay` class.
+
+Line endings for pasted content are now normalized to the editor's [preferred ending](https://codemirror.net/doc/manual.html#option_lineSeparator).
+
+[javascript mode](https://codemirror.net/mode/javascript): Improve support for class expressions. Support TypeScript optional class properties, the `abstract` keyword, and return type declarations for arrow functions.
+
+[css mode](https://codemirror.net/mode/css): Fix highlighting of mixed-case keywords.
+
+[closebrackets addon](https://codemirror.net/doc/manual.html#addon_closebrackets): Improve behavior when typing a quote before a string.
+
+### New features
+
+The core is now maintained as a number of small files, using ES6 syntax and modules, under the `src/` directory. A git checkout no longer contains a working `codemirror.js` until you `npm run build` (but when installing from NPM, it is included).
+
+The [`refresh`](https://codemirror.net/doc/manual.html#event_refresh) event is now documented and stable.
+
+## 5.19.0 (2016-09-20)
+
+### Bugfixes
+
+[erlang mode](https://codemirror.net/mode/erlang): Fix mode crash when trying to read an empty context.
+
+[comment addon](https://codemirror.net/doc/manual.html#addon_comment): Fix broken behavior when toggling comments inside a comment.
+
+xml-fold addon: Fix a null-dereference bug.
+
+Page up and page down now do something even in single-line documents.
+
+Fix an issue where the cursor position could be off in really long (~8000 character) tokens.
+
+### New features
+
+[javascript mode](https://codemirror.net/mode/javascript): Better indentation when semicolons are missing. Better support for TypeScript classes, optional parameters, and the `type` keyword.
+
+The [`blur`](https://codemirror.net/doc/manual.html#event_blur) and [`focus`](https://codemirror.net/doc/manual.html#event_focus) events now pass the DOM event to their handlers.
+
+## 5.18.2 (2016-08-23)
+
+### Bugfixes
+
+[vue mode](https://codemirror.net/mode/vue): Fix outdated references to renamed Pug mode dependency.
+
+## 5.18.0 (2016-08-22)
+
+### Bugfixes
+
+Make sure [gutter backgrounds](https://codemirror.net/doc/manual.html#addLineClass) stick to the rest of the gutter during horizontal scrolling.
+
+The contenteditable [`inputStyle`](https://codemirror.net/doc/manual.html#option_inputStyle) now properly supports pasting on pre-Edge IE versions.
+
+[javascript mode](https://codemirror.net/mode/javascript): Fix some small parsing bugs and improve TypeScript support.
+
+[matchbrackets addon](https://codemirror.net/doc/manual.html#addon_matchbrackets): Fix bug where active highlighting was left in editor when the addon was disabled.
+
+[match-highlighter addon](https://codemirror.net/doc/manual.html#addon_match-highlighter): Only start highlighting things when the editor gains focus.
+
+[javascript-hint addon](https://codemirror.net/doc/manual.html#addon_javascript-hint): Also complete non-enumerable properties.
+
+### New features
+
+The [`addOverlay`](https://codemirror.net/doc/manual.html#addOverlay) method now supports a `priority` option to control the order in which overlays are applied.
+
+MIME types that end in `+json` now default to the JSON mode when the MIME itself is not defined.
+
+### Breaking changes
+
+The mode formerly known as Jade was renamed to [Pug](https://codemirror.net/mode/pug).
+
+The [Python mode](https://codemirror.net/mode/python) now defaults to Python 3 (rather than 2) syntax.
+
+## 5.17.0 (2016-07-19)
+
+### Bugfixes
+
+Fix problem with wrapped trailing whitespace displaying incorrectly.
+
+Prevent IME dialog from overlapping typed content in Chrome.
+
+Improve measuring of characters near a line wrap.
+
+[javascript mode](https://codemirror.net/mode/javascript): Improve support for `async`, allow trailing commas in `import` lists.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Fix backspace in replace mode.
+
+[sublime bindings](https://codemirror.net/demo/sublime.html): Fix some key bindings on OS X to match Sublime Text.
+
+### New features
+
+[markdown mode](https://codemirror.net/mode/markdown): Add more classes to image links in highlight-formatting mode.
+
+## 5.16.0 (2016-06-20)
+
+### Bugfixes
+
+Fix glitches when dragging content caused by the drop indicator receiving mouse events.
+
+Make Control-drag work on Firefox.
+
+Make clicking or selection-dragging at the end of a wrapped line select the right position.
+
+[show-hint addon](https://codemirror.net/doc/manual.html#addon_show-hint): Prevent widget scrollbar from hiding part of the hint text.
+
+[rulers addon](https://codemirror.net/doc/manual.html#addon_rulers): Prevent rulers from forcing a horizontal editor scrollbar.
+
+### New features
+
+[search addon](https://codemirror.net/doc/manual.html#addon_search): Automatically bind search-related keys in persistent dialog.
+
+[sublime keymap](https://codemirror.net/demo/sublime.html): Add a multi-cursor aware smart backspace binding.
+
+## 5.15.2 (2016-05-20)
+
+### Bugfixes
+
+Fix a critical document corruption bug that occurs when a document is gradually grown.
+
+## 5.15.0 (2016-05-20)
+
+### Bugfixes
+
+Fix bug that caused the selection to reset when focusing the editor in contentEditable input mode.
+
+Fix issue where not all ASCII control characters were being replaced by placeholders.
+
+Remove the assumption that all modes have a `startState` method from several wrapping modes.
+
+Fix issue where the editor would complain about overlapping collapsed ranges when there weren't any.
+
+Optimize document tree building when loading or pasting huge chunks of content.
+
+[markdown mode](https://codemirror.net/mode/markdown/): Fix several issues in matching link targets.
+
+[clike mode](https://codemirror.net/mode/clike/): Improve indentation of C++ template declarations.
+
+### New features
+
+Explicitly bind Ctrl-O on OS X to make that binding (“open line”) act as expected.
+
+Pasting [linewise-copied](https://codemirror.net/doc/manual.html#option_lineWiseCopyCut) content when there is no selection now inserts the lines above the current line.
+
+[javascript mode](https://codemirror.net/mode/javascript/): Support `async`/`await` and improve support for TypeScript type syntax.
+
+## 5.14.2 (2016-04-20)
+
+### Bugfixes
+
+Push a new package to NPM due to an [NPM bug](https://github.com/npm/npm/issues/5082) omitting the LICENSE file in 5.14.0.
+
+Set `dataTransfer.effectAllowed` in `dragstart` handler to help browsers use the right drag icon.
+
+Add the [mbox mode](https://codemirror.net/mode/mbox/index.html) to `mode/meta.js`.
+
+## 5.14.0 (2016-04-20)
+
+### Bugfixes
+
+[`posFromIndex`](https://codemirror.net/doc/manual.html#posFromIndex) and [`indexFromPos`](https://codemirror.net/doc/manual.html#indexFromPos) now take [`lineSeparator`](https://codemirror.net/doc/manual.html#option_lineSeparator) into account.
+
+[vim bindings](https://codemirror.net/demo/vim.html): Only call `.save()` when it is actually available.
+
+[comment addon](https://codemirror.net/doc/manual.html#addon_comment): Be careful not to mangle multi-line strings.
+
+[Python mode](https://codemirror.net/mode/python/index.html): Improve distinguishing of decorators from `@` operators.
+
+[`findMarks`](https://codemirror.net/doc/manual.html#findMarks): No longer return marks that touch but don't overlap given range.
+
+### New features
+
+[vim bindings](https://codemirror.net/demo/vim.html): Add yank command.
+
+[match-highlighter addon](https://codemirror.net/doc/manual.html#addon_match-highlighter): Add `trim` option to disable ignoring of whitespace.
+
+[PowerShell mode](https://codemirror.net/mode/powershell/index.html): Added.
+
+[Yacas mode](https://codemirror.net/mode/yacas/index.html): Added.
+
+[Web IDL mode](https://codemirror.net/mode/webidl/index.html): Added.
+
+[SAS mode](https://codemirror.net/mode/sas/index.html): Added.
+
+[mbox mode](https://codemirror.net/mode/mbox/index.html): Added.
+
+## 5.13.2 (2016-03-23)
+
+### Bugfixes
+
+Solves a problem where the gutter would sometimes not extend all the way to the end of the document.
+
+## 5.13.0 (2016-03-21)
+
+### New features
+
+New DOM event forwarded: [`"dragleave"`](https://codemirror.net/doc/manual.html#event_dom).
+
+[protobuf mode](https://codemirror.net/mode/protobuf/index.html): Newly added.
+
+### Bugfixes
+
+Fix problem where [`findMarks`](https://codemirror.net/doc/manual.html#findMarks) sometimes failed to find multi-line marks.
+
+Fix crash that showed up when atomic ranges and bidi text were combined.
+
+[show-hint addon](https://codemirror.net/demo/complete.html): Completion widgets no longer close when the line indented or dedented.
+
+[merge addon](https://codemirror.net/demo/merge.html): Fix bug when merging chunks at the end of the file.
+
+[placeholder addon](https://codemirror.net/doc/manual.html#addon_placeholder): No longer gets confused by [`swapDoc`](https://codemirror.net/doc/manual.html#swapDoc).
+
+[simplescrollbars addon](https://codemirror.net/doc/manual.html#addon_simplescrollbars): Fix invalid state when deleting at end of document.
+
+[clike mode](https://codemirror.net/mode/clike/index.html): No longer gets confused when a comment starts after an operator.
+
+[markdown mode](https://codemirror.net/mode/markdown/index.html): Now supports CommonMark-style flexible list indentation.
+
+[dylan mode](https://codemirror.net/mode/dylan/index.html): Several improvements and fixes.
+
+## 5.12.0 (2016-02-19)
+
+### New features
+
+[Vim bindings](https://codemirror.net/demo/vim.html): Ctrl-Q is now an alias for Ctrl-V.
+
+[Vim bindings](https://codemirror.net/demo/vim.html): The Vim API now exposes an `unmap` method to unmap bindings.
+
+[active-line addon](https://codemirror.net/demo/activeline.html): This addon can now style the active line's gutter.
+
+[FCL mode](https://codemirror.net/mode/fcl/): Newly added.
+
+[SQL mode](https://codemirror.net/mode/sql/): Now has a Postgresql dialect.
+
+### Bugfixes
+
+Fix [issue](https://github.com/codemirror/CodeMirror/issues/3781) where trying to scroll to a horizontal position outside of the document's width could cause the gutter to be positioned incorrectly.
+
+Use absolute, rather than fixed positioning in the context-menu intercept hack, to work around a [problem](https://github.com/codemirror/CodeMirror/issues/3238) when the editor is inside a transformed parent container.
+
+Solve a [problem](https://github.com/codemirror/CodeMirror/issues/3821) where the horizontal scrollbar could hide text in Firefox.
+
+Fix a [bug](https://github.com/codemirror/CodeMirror/issues/3834) that caused phantom scroll space under the text in some situations.
+
+[Sublime Text bindings](https://codemirror.net/demo/sublime.html): Bind delete-line to Shift-Ctrl-K on OS X.
+
+[Markdown mode](https://codemirror.net/mode/markdown/): Fix [issue](https://github.com/codemirror/CodeMirror/issues/3787) where the mode would keep state related to fenced code blocks in an unsafe way, leading to occasional corrupted parses.
+
+[Markdown mode](https://codemirror.net/mode/markdown/): Ignore backslashes in code fragments.
+
+[Markdown mode](https://codemirror.net/mode/markdown/): Use whichever mode is registered as `text/html` to parse HTML.
+
+[Clike mode](https://codemirror.net/mode/clike/): Improve indentation of Scala `=>` functions.
+
+[Python mode](https://codemirror.net/mode/python/): Improve indentation of bracketed code.
+
+[HTMLMixed mode](https://codemirror.net/mode/htmlmixed/): Support multi-line opening tags for sub-languages (`
+
+
+
+
Press ctrl-space to activate autocompletion. The
+completion uses
+the anyword-hint.js
+module, which simply looks at nearby words in the buffer and completes
+to those.
Demonstration of
+ using linked documents
+ to provide a split view on a document, and
+ using swapDoc
+ to use a single editor to display multiple documents.
On changes to the content of the above editor, a (crude) script
+tries to auto-detect the language used, and switches the editor to
+either JavaScript or Scheme mode based on that.
The emacs keybindings are enabled by
+including keymap/emacs.js and setting
+the keyMap option to "emacs". Because
+CodeMirror's internal API is quite different from Emacs, they are only
+a loose approximation of actual emacs bindings, though.
+
+
Also note that a lot of browsers disallow certain keys from being
+captured. For example, Chrome blocks both Ctrl-W and Ctrl-N, with the
+result that idiomatic use of Emacs keys will constantly close your tab
+or open a new window.
Demonstration of
+ the fullscreen
+ addon. Press F11 when cursor is in the editor to
+ toggle full screen editing. Esc can also be used
+ to exit full screen editing.
Demonstration of
+the hardwrap addon.
+The above editor has its change event hooked up to
+the wrapParagraphsInRange method, so that the paragraphs
+are reflown as you are typing.
The merge
+addon provides an interface for displaying and merging diffs,
+either two-way
+or three-way.
+The left (or center) pane is editable, and the differences with the
+other pane(s) are optionally shown live as you edit
+it. In the two-way configuration, there are also options to pad changed
+sections to align them, and to collapse unchanged
+stretches of text.
Demonstration of a multiplexing mode, which, at certain
+ boundary strings, switches to one or more inner modes. The out
+ (HTML) mode does not get fed the content of the <<
+ >> blocks. See
+ the manual and
+ the source for more
+ information.
Demonstration of a mode that parses HTML, highlighting
+ the Mustache templating
+ directives inside of it by using the code
+ in overlay.js. View
+ source to see the 15 lines of code needed to accomplish this.
The placeholder
+ plug-in adds an option placeholder that can be set to
+ make text appear in the editor when it is empty and not focused.
+ If the source textarea has a placeholder attribute,
+ it will automatically be inherited.
This demo does the same thing as
+ the HTML5 completion demo, but
+ loads its dependencies
+ with Require.js, rather than
+ explicitly. Press ctrl-space to activate
+ completion.
By setting an editor's height style
+to auto and giving
+the viewportMargin
+a value of Infinity, CodeMirror can be made to
+automatically resize to fit its content.
Running a CodeMirror mode outside of the editor.
+ The CodeMirror.runMode function, defined
+ in addon/runmode/runmode.js takes the following arguments:
If this is a function, it will be called for each token with
+ two arguments, the token's text and the token's style class (may
+ be null for unstyled tokens). If it is a DOM node,
+ the tokens will be converted to span elements as in
+ an editor, and inserted into the node
+ (through innerHTML).
The mode/simple
+addon allows CodeMirror modes to be specified using a relatively simple
+declarative format. This format is not as powerful as writing code
+directly against the mode
+interface, but is a lot easier to get started with, and
+sufficiently expressive for many simple language modes.
+
+
This interface is still in flux. It is unlikely to be scrapped or
+overhauled completely, so do start writing code against it, but
+details might change as it stabilizes, and you might have to tweak
+your code when upgrading.
+
+
Simple modes (loosely based on
+the Common
+JavaScript Syntax Highlighting Specification, which never took
+off), are state machines, where each state has a number of rules that
+match tokens. A rule describes a type of token that may occur in the
+current state, and possibly a transition to another state caused by
+that token.
+
+
The CodeMirror.defineSimpleMode(name, states) method
+takes a mode name and an object that describes the mode's states. The
+editor below shows an example of such a mode (and is itself
+highlighted by the mode shown in it).
+
+
+
+
Each state is an array of rules. A rule may have the following properties:
+
+
+
regex: string | RegExp
+
The regular expression that matches the token. May be a string
+ or a regex object. When a regex, the ignoreCase flag
+ will be taken into account when matching the token. This regex
+ has to capture groups when the token property is
+ an array. If it captures groups, it must capture all of the string
+ (since JS provides no way to find out where a group matched).
+
token: string | array<string> | null
+
An optional token style. Multiple styles can be specified by
+ separating them with dots or spaces. When this property holds an array of token styles,
+ the regex for this rule must capture a group for each array item.
+
+
sol: boolean
+
When true, this token will only match at the start of the line.
+ (The ^ regexp marker doesn't work as you'd expect in
+ this context because of limitations in JavaScript's RegExp
+ API.)
+
next: string
+
When a next property is present, the mode will
+ transfer to the state named by the property when the token is
+ encountered.
+
push: string
+
Like next, but instead replacing the current state
+ by the new state, the current state is kept on a stack, and can be
+ returned to with the pop directive.
+
pop: bool
+
When true, and there is another state on the state stack, will
+ cause the mode to pop that state off the stack and transition to
+ it.
+
mode: {spec, end, persistent}
+
Can be used to embed another mode inside a mode. When present,
+ must hold an object with a spec property that describes
+ the embedded mode, and an optional end end property
+ that specifies the regexp that will end the extent of the mode. When
+ a persistent property is set (and true), the nested
+ mode's state will be preserved between occurrences of the mode.
+
indent: bool
+
When true, this token changes the indentation to be one unit
+ more than the current line's indentation.
+
dedent: bool
+
When true, this token will pop one scope off the indentation
+ stack.
+
dedentIfLineStart: bool
+
If a token has its dedent property set, it will, by
+ default, cause lines where it appears at the start to be dedented.
+ Set this property to false to prevent that behavior.
+
+
+
The meta property of the states object is special, and
+will not be interpreted as a state. Instead, properties set on it will
+be set on the mode, which is useful for properties
+like lineComment,
+which sets the comment style for a mode. The simple mode addon also
+recognizes a few such properties:
+
+
+
dontIndentStates: array<string>
+
An array of states in which the mode's auto-indentation should
+ not take effect. Usually used for multi-line comment and string
+ states.
The simplescrollbars addon defines two
+styles of non-native scrollbars: "simple" and "overlay" (click to try), which can be passed to
+the scrollbarStyle option. These implement
+the scrollbar using DOM elements, allowing more control over
+its appearance.
+
+
+
diff --git a/scripts/codemirror/demo/spanaffectswrapping_shim.html b/scripts/codemirror/demo/spanaffectswrapping_shim.html
new file mode 100644
index 0000000..46667dd
--- /dev/null
+++ b/scripts/codemirror/demo/spanaffectswrapping_shim.html
@@ -0,0 +1,85 @@
+
+
+CodeMirror: Automatically derive odd wrapping behavior for your browser
+
+
+
+
Automatically derive odd wrapping behavior for your browser
+
+
+
This is a hack to automatically derive
+ a spanAffectsWrapping regexp for a browser. See the
+ comments above that variable
+ in lib/codemirror.js
+ for some more details.
Note: The CodeMirror vim bindings do not have an
+active maintainer. That means that if you report bugs in it, they are
+likely to go unanswered. It also means that if you want to help, you
+are very welcome to look
+at the
+open issues and see which ones you can solve.
+
+
+
Key buffer:
+
+
The vim keybindings are enabled by including keymap/vim.js and setting the
+keyMap option to vim.
+
+
Features
+
+
+
All common motions and operators, including text objects
For the full list of key mappings and Ex commands, refer to the
+defaultKeymap and defaultExCommandMap at the
+top of keymap/vim.js.
+
+
Note that while the vim mode tries to emulate the most useful
+features of vim as faithfully as possible, it does not strive to
+become a complete vim implementation
This demo runs JSHint over the code
+in the editor (which is the script used on this page), and
+inserts line widgets to
+display the warnings that JSHint comes up with.
Press ctrl-space, or type a '<' character to
+ activate autocompletion. This demo defines a simple schema that
+ guides completion. The schema can be customized—see
+ the manual.
+
+
Development of the xml-hint addon was kindly
+ sponsored
+ by www.xperiment.mobi.
(Re-) Implementing A Syntax-Highlighting Editor in JavaScript
+
+
+ Topic: JavaScript, code editor implementation
+ Author: Marijn Haverbeke
+ Date: March 2nd 2011 (updated November 13th 2011)
+
+
+
Caution: this text was written briefly after
+version 2 was initially written. It no longer (even including the
+update at the bottom) fully represents the current implementation. I'm
+leaving it here as a historic document. For more up-to-date
+information, look at the entries
+tagged cm-internals
+on my blog.
+
+
This is a followup to
+my Brutal Odyssey to the
+Dark Side of the DOM Tree story. That one describes the
+mind-bending process of implementing (what would become) CodeMirror 1.
+This one describes the internals of CodeMirror 2, a complete rewrite
+and rethink of the old code base. I wanted to give this piece another
+Hunter Thompson copycat subtitle, but somehow that would be out of
+place—the process this time around was one of straightforward
+engineering, requiring no serious mind-bending whatsoever.
+
+
So, what is wrong with CodeMirror 1? I'd estimate, by mailing list
+activity and general search-engine presence, that it has been
+integrated into about a thousand systems by now. The most prominent
+one, since a few weeks,
+being Google
+code's project hosting. It works, and it's being used widely.
+
+
Still, I did not start replacing it because I was bored. CodeMirror
+1 was heavily reliant on designMode
+or contentEditable (depending on the browser). Neither of
+these are well specified (HTML5 tries
+to specify
+their basics), and, more importantly, they tend to be one of the more
+obscure and buggy areas of browser functionality—CodeMirror, by using
+this functionality in a non-typical way, was constantly running up
+against browser bugs. WebKit wouldn't show an empty line at the end of
+the document, and in some releases would suddenly get unbearably slow.
+Firefox would show the cursor in the wrong place. Internet Explorer
+would insist on linkifying everything that looked like a URL or email
+address, a behaviour that can't be turned off. Some bugs I managed to
+work around (which was often a frustrating, painful process), others,
+such as the Firefox cursor placement, I gave up on, and had to tell
+user after user that they were known problems, but not something I
+could help.
+
+
Also, there is the fact that designMode (which seemed
+to be less buggy than contentEditable in Webkit and
+Firefox, and was thus used by CodeMirror 1 in those browsers) requires
+a frame. Frames are another tricky area. It takes some effort to
+prevent getting tripped up by domain restrictions, they don't
+initialize synchronously, behave strangely in response to the back
+button, and, on several browsers, can't be moved around the DOM
+without having them re-initialize. They did provide a very nice way to
+namespace the library, though—CodeMirror 1 could freely pollute the
+namespace inside the frame.
+
+
Finally, working with an editable document means working with
+selection in arbitrary DOM structures. Internet Explorer (8 and
+before) has an utterly different (and awkward) selection API than all
+of the other browsers, and even among the different implementations of
+document.selection, details about how exactly a selection
+is represented vary quite a bit. Add to that the fact that Opera's
+selection support tended to be very buggy until recently, and you can
+imagine why CodeMirror 1 contains 700 lines of selection-handling
+code.
+
+
And that brings us to the main issue with the CodeMirror 1
+code base: The proportion of browser-bug-workarounds to real
+application code was getting dangerously high. By building on top of a
+few dodgy features, I put the system in a vulnerable position—any
+incompatibility and bugginess in these features, I had to paper over
+with my own code. Not only did I have to do some serious stunt-work to
+get it to work on older browsers (as detailed in the
+previous story), things
+also kept breaking in newly released versions, requiring me to come up
+with new scary hacks in order to keep up. This was starting
+to lose its appeal.
+
+
+
General Approach
+
+
What CodeMirror 2 does is try to sidestep most of the hairy hacks
+that came up in version 1. I owe a lot to the
+ACE editor for inspiration on how to
+approach this.
+
+
I absolutely did not want to be completely reliant on key events to
+generate my input. Every JavaScript programmer knows that key event
+information is horrible and incomplete. Some people (most awesomely
+Mihai Bazon with Ymacs) have been able
+to build more or less functioning editors by directly reading key
+events, but it takes a lot of work (the kind of never-ending, fragile
+work I described earlier), and will never be able to properly support
+things like multi-keystoke international character
+input. [see below for caveat]
+
+
So what I do is focus a hidden textarea, and let the browser
+believe that the user is typing into that. What we show to the user is
+a DOM structure we built to represent his document. If this is updated
+quickly enough, and shows some kind of believable cursor, it feels
+like a real text-input control.
+
+
Another big win is that this DOM representation does not have to
+span the whole document. Some CodeMirror 1 users insisted that they
+needed to put a 30 thousand line XML document into CodeMirror. Putting
+all that into the DOM takes a while, especially since, for some
+reason, an editable DOM tree is slower than a normal one on most
+browsers. If we have full control over what we show, we must only
+ensure that the visible part of the document has been added, and can
+do the rest only when needed. (Fortunately, the onscroll
+event works almost the same on all browsers, and lends itself well to
+displaying things only as they are scrolled into view.)
+
+
+
Input
+
+
ACE uses its hidden textarea only as a text input shim, and does
+all cursor movement and things like text deletion itself by directly
+handling key events. CodeMirror's way is to let the browser do its
+thing as much as possible, and not, for example, define its own set of
+key bindings. One way to do this would have been to have the whole
+document inside the hidden textarea, and after each key event update
+the display DOM to reflect what's in that textarea.
+
+
That'd be simple, but it is not realistic. For even medium-sized
+document the editor would be constantly munging huge strings, and get
+terribly slow. What CodeMirror 2 does is put the current selection,
+along with an extra line on the top and on the bottom, into the
+textarea.
+
+
This means that the arrow keys (and their ctrl-variations), home,
+end, etcetera, do not have to be handled specially. We just read the
+cursor position in the textarea, and update our cursor to match it.
+Also, copy and paste work pretty much for free, and people get their
+native key bindings, without any special work on my part. For example,
+I have emacs key bindings configured for Chrome and Firefox. There is
+no way for a script to detect this. [no longer the case]
+
+
Of course, since only a small part of the document sits in the
+textarea, keys like page up and ctrl-end won't do the right thing.
+CodeMirror is catching those events and handling them itself.
+
+
+
Selection
+
+
Getting and setting the selection range of a textarea in modern
+browsers is trivial—you just use the selectionStart
+and selectionEnd properties. On IE you have to do some
+insane stuff with temporary ranges and compensating for the fact that
+moving the selection by a 'character' will treat \r\n as a single
+character, but even there it is possible to build functions that
+reliably set and get the selection range.
+
+
But consider this typical case: When I'm somewhere in my document,
+press shift, and press the up arrow, something gets selected. Then, if
+I, still holding shift, press the up arrow again, the top of my
+selection is adjusted. The selection remembers where its head
+and its anchor are, and moves the head when we shift-move.
+This is a generally accepted property of selections, and done right by
+every editing component built in the past twenty years.
+
+
But not something that the browser selection APIs expose.
+
+
Great. So when someone creates an 'upside-down' selection, the next
+time CodeMirror has to update the textarea, it'll re-create the
+selection as an 'upside-up' selection, with the anchor at the top, and
+the next cursor motion will behave in an unexpected way—our second
+up-arrow press in the example above will not do anything, since it is
+interpreted in exactly the same way as the first.
+
+
No problem. We'll just, ehm, detect that the selection is
+upside-down (you can tell by the way it was created), and then, when
+an upside-down selection is present, and a cursor-moving key is
+pressed in combination with shift, we quickly collapse the selection
+in the textarea to its start, allow the key to take effect, and then
+combine its new head with its old anchor to get the real
+selection.
+
+
In short, scary hacks could not be avoided entirely in CodeMirror
+2.
+
+
And, the observant reader might ask, how do you even know that a
+key combo is a cursor-moving combo, if you claim you support any
+native key bindings? Well, we don't, but we can learn. The editor
+keeps a set known cursor-movement combos (initialized to the
+predictable defaults), and updates this set when it observes that
+pressing a certain key had (only) the effect of moving the cursor.
+This, of course, doesn't work if the first time the key is used was
+for extending an inverted selection, but it works most of the
+time.
+
+
+
Intelligent Updating
+
+
One thing that always comes up when you have a complicated internal
+state that's reflected in some user-visible external representation
+(in this case, the displayed code and the textarea's content) is
+keeping the two in sync. The naive way is to just update the display
+every time you change your state, but this is not only error prone
+(you'll forget), it also easily leads to duplicate work on big,
+composite operations. Then you start passing around flags indicating
+whether the display should be updated in an attempt to be efficient
+again and, well, at that point you might as well give up completely.
+
+
I did go down that road, but then switched to a much simpler model:
+simply keep track of all the things that have been changed during an
+action, and then, only at the end, use this information to update the
+user-visible display.
+
+
CodeMirror uses a concept of operations, which start by
+calling a specific set-up function that clears the state and end by
+calling another function that reads this state and does the required
+updating. Most event handlers, and all the user-visible methods that
+change state are wrapped like this. There's a method
+called operation that accepts a function, and returns
+another function that wraps the given function as an operation.
+
+
It's trivial to extend this (as CodeMirror does) to detect nesting,
+and, when an operation is started inside an operation, simply
+increment the nesting count, and only do the updating when this count
+reaches zero again.
+
+
If we have a set of changed ranges and know the currently shown
+range, we can (with some awkward code to deal with the fact that
+changes can add and remove lines, so we're dealing with a changing
+coordinate system) construct a map of the ranges that were left
+intact. We can then compare this map with the part of the document
+that's currently visible (based on scroll offset and editor height) to
+determine whether something needs to be updated.
+
+
CodeMirror uses two update algorithms—a full refresh, where it just
+discards the whole part of the DOM that contains the edited text and
+rebuilds it, and a patch algorithm, where it uses the information
+about changed and intact ranges to update only the out-of-date parts
+of the DOM. When more than 30 percent (which is the current heuristic,
+might change) of the lines need to be updated, the full refresh is
+chosen (since it's faster to do than painstakingly finding and
+updating all the changed lines), in the other case it does the
+patching (so that, if you scroll a line or select another character,
+the whole screen doesn't have to be
+re-rendered). [the full-refresh
+algorithm was dropped, it wasn't really faster than the patching
+one]
+
+
All updating uses innerHTML rather than direct DOM
+manipulation, since that still seems to be by far the fastest way to
+build documents. There's a per-line function that combines the
+highlighting, marking, and
+selection info for that line into a snippet of HTML. The patch updater
+uses this to reset individual lines, the refresh updater builds an
+HTML chunk for the whole visible document at once, and then uses a
+single innerHTML update to do the refresh.
+
+
+
Parsers can be Simple
+
+
When I wrote CodeMirror 1, I
+thought interruptable
+parsers were a hugely scary and complicated thing, and I used a
+bunch of heavyweight abstractions to keep this supposed complexity
+under control: parsers
+were iterators
+that consumed input from another iterator, and used funny
+closure-resetting tricks to copy and resume themselves.
+
+
This made for a rather nice system, in that parsers formed strictly
+separate modules, and could be composed in predictable ways.
+Unfortunately, it was quite slow (stacking three or four iterators on
+top of each other), and extremely intimidating to people not used to a
+functional programming style.
+
+
With a few small changes, however, we can keep all those
+advantages, but simplify the API and make the whole thing less
+indirect and inefficient. CodeMirror
+2's mode API uses explicit state
+objects, and makes the parser/tokenizer a function that simply takes a
+state and a character stream abstraction, advances the stream one
+token, and returns the way the token should be styled. This state may
+be copied, optionally in a mode-defined way, in order to be able to
+continue a parse at a given point. Even someone who's never touched a
+lambda in his life can understand this approach. Additionally, far
+fewer objects are allocated in the course of parsing now.
+
+
The biggest speedup comes from the fact that the parsing no longer
+has to touch the DOM though. In CodeMirror 1, on an older browser, you
+could see the parser work its way through the document,
+managing some twenty lines in each 50-millisecond time slice it got. It
+was reading its input from the DOM, and updating the DOM as it went
+along, which any experienced JavaScript programmer will immediately
+spot as a recipe for slowness. In CodeMirror 2, the parser usually
+finishes the whole document in a single 100-millisecond time slice—it
+manages some 1500 lines during that time on Chrome. All it has to do
+is munge strings, so there is no real reason for it to be slow
+anymore.
+
+
+
What Gives?
+
+
Given all this, what can you expect from CodeMirror 2?
+
+
+
+
Small. the base library is
+some 45k when minified
+now, 17k when gzipped. It's smaller than
+its own logo.
+
+
Lightweight. CodeMirror 2 initializes very
+quickly, and does almost no work when it is not focused. This means
+you can treat it almost like a textarea, have multiple instances on a
+page without trouble.
+
+
Huge document support. Since highlighting is
+really fast, and no DOM structure is being built for non-visible
+content, you don't have to worry about locking up your browser when a
+user enters a megabyte-sized document.
+
+
Extended API. Some things kept coming up in the
+mailing list, such as marking pieces of text or lines, which were
+extremely hard to do with CodeMirror 1. The new version has proper
+support for these built in.
+
+
Tab support. Tabs inside editable documents were,
+for some reason, a no-go. At least six different people announced they
+were going to add tab support to CodeMirror 1, none survived (I mean,
+none delivered a working version). CodeMirror 2 no longer removes tabs
+from your document.
+
+
Sane styling.iframe nodes aren't
+really known for respecting document flow. Now that an editor instance
+is a plain div element, it is much easier to size it to
+fit the surrounding elements. You don't even have to make it scroll if
+you do not want to.
+
+
+
+
On the downside, a CodeMirror 2 instance is not a native
+editable component. Though it does its best to emulate such a
+component as much as possible, there is functionality that browsers
+just do not allow us to hook into. Doing select-all from the context
+menu, for example, is not currently detected by CodeMirror.
+
+
[Updates from November 13th 2011] Recently, I've made
+some changes to the codebase that cause some of the text above to no
+longer be current. I've left the text intact, but added markers at the
+passages that are now inaccurate. The new situation is described
+below.
+
+
+
Content Representation
+
+
The original implementation of CodeMirror 2 represented the
+document as a flat array of line objects. This worked well—splicing
+arrays will require the part of the array after the splice to be
+moved, but this is basically just a simple memmove of a
+bunch of pointers, so it is cheap even for huge documents.
+
+
However, I recently added line wrapping and code folding (line
+collapsing, basically). Once lines start taking up a non-constant
+amount of vertical space, looking up a line by vertical position
+(which is needed when someone clicks the document, and to determine
+the visible part of the document during scrolling) can only be done
+with a linear scan through the whole array, summing up line heights as
+you go. Seeing how I've been going out of my way to make big documents
+fast, this is not acceptable.
+
+
The new representation is based on a B-tree. The leaves of the tree
+contain arrays of line objects, with a fixed minimum and maximum size,
+and the non-leaf nodes simply hold arrays of child nodes. Each node
+stores both the amount of lines that live below them and the vertical
+space taken up by these lines. This allows the tree to be indexed both
+by line number and by vertical position, and all access has
+logarithmic complexity in relation to the document size.
+
+
I gave line objects and tree nodes parent pointers, to the node
+above them. When a line has to update its height, it can simply walk
+these pointers to the top of the tree, adding or subtracting the
+difference in height from each node it encounters. The parent pointers
+also make it cheaper (in complexity terms, the difference is probably
+tiny in normal-sized documents) to find the current line number when
+given a line object. In the old approach, the whole document array had
+to be searched. Now, we can just walk up the tree and count the sizes
+of the nodes coming before us at each level.
+
+
I chose B-trees, not regular binary trees, mostly because they
+allow for very fast bulk insertions and deletions. When there is a big
+change to a document, it typically involves adding, deleting, or
+replacing a chunk of subsequent lines. In a regular balanced tree, all
+these inserts or deletes would have to be done separately, which could
+be really expensive. In a B-tree, to insert a chunk, you just walk
+down the tree once to find where it should go, insert them all in one
+shot, and then break up the node if needed. This breaking up might
+involve breaking up nodes further up, but only requires a single pass
+back up the tree. For deletion, I'm somewhat lax in keeping things
+balanced—I just collapse nodes into a leaf when their child count goes
+below a given number. This means that there are some weird editing
+patterns that may result in a seriously unbalanced tree, but even such
+an unbalanced tree will perform well, unless you spend a day making
+strangely repeating edits to a really big document.
+
+
+
Keymaps
+
+
Above, I claimed that directly catching key
+events for things like cursor movement is impractical because it
+requires some browser-specific kludges. I then proceeded to explain
+some awful hacks that were needed to make it
+possible for the selection changes to be detected through the
+textarea. In fact, the second hack is about as bad as the first.
+
+
On top of that, in the presence of user-configurable tab sizes and
+collapsed and wrapped lines, lining up cursor movement in the textarea
+with what's visible on the screen becomes a nightmare. Thus, I've
+decided to move to a model where the textarea's selection is no longer
+depended on.
+
+
So I moved to a model where all cursor movement is handled by my
+own code. This adds support for a goal column, proper interaction of
+cursor movement with collapsed lines, and makes it possible for
+vertical movement to move through wrapped lines properly, instead of
+just treating them like non-wrapped lines.
+
+
The key event handlers now translate the key event into a string,
+something like Ctrl-Home or Shift-Cmd-R, and
+use that string to look up an action to perform. To make keybinding
+customizable, this lookup goes through
+a table, using a scheme that
+allows such tables to be chained together (for example, the default
+Mac bindings fall through to a table named 'emacsy', which defines
+basic Emacs-style bindings like Ctrl-F, and which is also
+used by the custom Emacs bindings).
+
+
A new
+option extraKeys
+allows ad-hoc keybindings to be defined in a much nicer way than what
+was possible with the
+old onKeyEvent
+callback. You simply provide an object mapping key identifiers to
+functions, instead of painstakingly looking at raw key events.
+
+
Built-in commands map to strings, rather than functions, for
+example "goLineUp" is the default action bound to the up
+arrow key. This allows new keymaps to refer to them without
+duplicating any code. New commands can be defined by assigning to
+the CodeMirror.commands object, which maps such commands
+to functions.
+
+
The hidden textarea now only holds the current selection, with no
+extra characters around it. This has a nice advantage: polling for
+input becomes much, much faster. If there's a big selection, this text
+does not have to be read from the textarea every time—when we poll,
+just noticing that something is still selected is enough to tell us
+that no new text was typed.
+
+
The reason that cheap polling is important is that many browsers do
+not fire useful events on IME (input method engine) input, which is
+the thing where people inputting a language like Japanese or Chinese
+use multiple keystrokes to create a character or sequence of
+characters. Most modern browsers fire input when the
+composing is finished, but many don't fire anything when the character
+is updated during composition. So we poll, whenever the
+editor is focused, to provide immediate updates of the display.
+
+
+
diff --git a/scripts/codemirror/doc/logo.png b/scripts/codemirror/doc/logo.png
new file mode 100644
index 0000000..9aabda1
Binary files /dev/null and b/scripts/codemirror/doc/logo.png differ
diff --git a/scripts/codemirror/doc/logo.svg b/scripts/codemirror/doc/logo.svg
new file mode 100644
index 0000000..b39b24c
--- /dev/null
+++ b/scripts/codemirror/doc/logo.svg
@@ -0,0 +1,19 @@
+
diff --git a/scripts/codemirror/doc/manual.html b/scripts/codemirror/doc/manual.html
new file mode 100644
index 0000000..03e3a15
--- /dev/null
+++ b/scripts/codemirror/doc/manual.html
@@ -0,0 +1,3592 @@
+
+
+CodeMirror: User Manual
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ User manual and reference guide
+ version 5.48.2
+
+
+
CodeMirror is a code-editor component that can be embedded in
+ Web pages. The core library provides only the editor
+ component, no accompanying buttons, auto-completion, or other IDE
+ functionality. It does provide a rich API on top of which such
+ functionality can be straightforwardly implemented. See
+ the addons included in the distribution,
+ and the list
+ of externally hosted addons, for reusable
+ implementations of extra features.
+
+
CodeMirror works with language-specific modes. Modes are
+ JavaScript programs that help color (and optionally indent) text
+ written in a given language. The distribution comes with a number
+ of modes (see the mode/
+ directory), and it isn't hard to write new
+ ones for other languages.
+
+
+
+
Basic Usage
+
+
The easiest way to use CodeMirror is to simply load the script
+ and style sheet found under lib/ in the distribution,
+ plus a mode script from one of the mode/ directories.
+ For example:
Having done this, an editor instance can be created like
+ this:
+
+
var myCodeMirror = CodeMirror(document.body);
+
+
The editor will be appended to the document body, will start
+ empty, and will use the mode that we loaded. To have more control
+ over the new editor, a configuration object can be passed
+ to CodeMirror as a second
+ argument:
This will initialize the editor with a piece of code already in
+ it, and explicitly tell it to use the JavaScript mode (which is
+ useful when multiple modes are loaded).
+ See below for a full discussion of the
+ configuration options that CodeMirror accepts.
+
+
In cases where you don't want to append the editor to an
+ element, and need more control over the way it is inserted, the
+ first argument to the CodeMirror function can also
+ be a function that, when given a DOM element, inserts it into the
+ document somewhere. This could be used to, for example, replace a
+ textarea with a real editor:
However, for this use case, which is a common way to use
+ CodeMirror, the library provides a much more powerful
+ shortcut:
+
+
var myCodeMirror = CodeMirror.fromTextArea(myTextArea);
+
+
This will, among other things, ensure that the textarea's value
+ is updated with the editor's contents when the form (if it is part
+ of a form) is submitted. See the API
+ reference for a full description of this method.
+
+
Module loaders
+
+
The files in the CodeMirror distribution contain shims for
+ loading them (and their dependencies) in AMD or CommonJS
+ environments. If the variables exports
+ and module exist and have type object, CommonJS-style
+ require will be used. If not, but there is a
+ function define with an amd property
+ present, AMD-style (RequireJS) will be used.
+
+
It is possible to
+ use Browserify or similar
+ tools to statically build modules using CodeMirror. Alternatively,
+ use RequireJS to dynamically
+ load dependencies at runtime. Both of these approaches have the
+ advantage that they don't use the global namespace and can, thus,
+ do things like load multiple versions of CodeMirror alongside each
+ other.
+
+
Here's a simple example of using RequireJS to load CodeMirror:
It will automatically load the modes that the mixed HTML mode
+ depends on (XML, JavaScript, and CSS). Do not use
+ RequireJS' paths option to configure the path to
+ CodeMirror, since it will break loading submodules through
+ relative paths. Use
+ the packages
+ configuration option instead, as in:
Both the CodeMirror
+ function and its fromTextArea method take as second
+ (optional) argument an object containing configuration options.
+ Any option not supplied like this will be taken
+ from CodeMirror.defaults, an
+ object containing the default options. You can update this object
+ to change the defaults on your page.
+
+
Options are not checked in any way, so setting bogus option
+ values is bound to lead to odd errors.
+
+
These are the supported options:
+
+
+
value: string|CodeMirror.Doc
+
The starting value of the editor. Can be a string, or
+ a document object.
+
+
mode: string|object
+
The mode to use. When not given, this will default to the
+ first mode that was loaded. It may be a string, which either
+ simply names the mode or is
+ a MIME type
+ associated with the mode. Alternatively, it may be an object
+ containing configuration options for the mode, with
+ a name property that names the mode (for
+ example {name: "javascript", json: true}). The demo
+ pages for each mode contain information about what configuration
+ parameters the mode supports. You can ask CodeMirror which modes
+ and MIME types have been defined by inspecting
+ the CodeMirror.modes
+ and CodeMirror.mimeModes objects. The first maps
+ mode names to their constructors, and the second maps MIME types
+ to mode specs.
+
+
lineSeparator: string|null
+
Explicitly set the line separator for the editor. By default
+ (value null), the document will be split on CRLFs
+ as well as lone CRs and LFs, and a single LF will be used as
+ line separator in all output (such
+ as getValue). When a
+ specific string is given, lines will only be split on that
+ string, and output will, by default, use that same
+ separator.
+
+
theme: string
+
The theme to style the editor with. You must make sure the
+ CSS file defining the corresponding .cm-s-[name]
+ styles is loaded (see
+ the theme directory in the
+ distribution). The default is "default", for which
+ colors are included in codemirror.css. It is
+ possible to use multiple theming classes at once—for
+ example "foo bar" will assign both
+ the cm-s-foo and the cm-s-bar classes
+ to the editor.
+
+
indentUnit: integer
+
How many spaces a block (whatever that means in the edited
+ language) should be indented. The default is 2.
+
+
smartIndent: boolean
+
Whether to use the context-sensitive indentation that the
+ mode provides (or just indent the same as the line before).
+ Defaults to true.
+
+
tabSize: integer
+
The width of a tab character. Defaults to 4.
+
+
indentWithTabs: boolean
+
Whether, when indenting, the first N*tabSize
+ spaces should be replaced by N tabs. Default is false.
+
+
electricChars: boolean
+
Configures whether the editor should re-indent the current
+ line when a character is typed that might change its proper
+ indentation (only works if the mode supports indentation).
+ Default is true.
+
+
specialChars: RegExp
+
A regular expression used to determine which characters
+ should be replaced by a
+ special placeholder.
+ Mostly useful for non-printing special characters. The default
+ is /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/.
+
specialCharPlaceholder: function(char) → Element
+
A function that, given a special character identified by
+ the specialChars
+ option, produces a DOM node that is used to represent the
+ character. By default, a red dot (•)
+ is shown, with a title tooltip to indicate the character code.
+
+
direction: "ltr" | "rtl"
+
Flips overall layout and selects base paragraph direction to
+ be left-to-right or right-to-left. Default is "ltr".
+ CodeMirror applies the Unicode Bidirectional Algorithm to each
+ line, but does not autodetect base direction — it's set to the
+ editor direction for all lines. The resulting order is
+ sometimes wrong when base direction doesn't match user intent
+ (for example, leading and trailing punctuation jumps to the
+ wrong side of the line). Therefore, it's helpful for
+ multilingual input to let users toggle this option.
+
+
rtlMoveVisually: boolean
+
Determines whether horizontal cursor movement through
+ right-to-left (Arabic, Hebrew) text is visual (pressing the left
+ arrow moves the cursor left) or logical (pressing the left arrow
+ moves to the next lower index in the string, which is visually
+ right in right-to-left text). The default is false
+ on Windows, and true on other platforms.
+
+
keyMap: string
+
Configures the key map to use. The default
+ is "default", which is the only key map defined
+ in codemirror.js itself. Extra key maps are found in
+ the key map directory. See
+ the section on key maps for more
+ information.
+
+
extraKeys: object
+
Can be used to specify extra key bindings for the editor,
+ alongside the ones defined
+ by keyMap. Should be
+ either null, or a valid key map value.
Allows you to configure the behavior of mouse selection and
+ dragging. The function is called when the left mouse button is
+ pressed. The returned object may have the following properties:
+
The unit by which to select. May be one of the built-in
+ units or a function that takes a position and returns a
+ range around that, for a custom unit. The default is to
+ return "word" for double
+ clicks, "line" for triple
+ clicks, "rectangle" for alt-clicks (or, on
+ Chrome OS, meta-shift-clicks), and "single"
+ otherwise.
+
extend: bool
+
Whether to extend the existing selection range or start
+ a new one. By default, this is enabled when shift
+ clicking.
+
addNew: bool
+
When enabled, this adds a new range to the existing
+ selection, rather than replacing it. The default behavior is
+ to enable this for command-click on Mac OS, and
+ control-click on other platforms.
+
moveOnDrag: bool
+
When the mouse even drags content around inside the
+ editor, this controls whether it is copied (false) or moved
+ (true). By default, this is enabled by alt-clicking on Mac
+ OS, and ctrl-clicking elsewhere.
+
+
+
+
lineWrapping: boolean
+
Whether CodeMirror should scroll or wrap for long lines.
+ Defaults to false (scroll).
+
+
lineNumbers: boolean
+
Whether to show line numbers to the left of the editor.
+
+
firstLineNumber: integer
+
At which number to start counting lines. Default is 1.
Can be used to add extra gutters (beyond or instead of the
+ line number gutter). Should be an array of CSS class names or
+ class name / CSS string pairs, each of which defines
+ a width (and optionally a background), and which
+ will be used to draw the background of the gutters. May
+ include the CodeMirror-linenumbers class, in order
+ to explicitly set the position of the line number gutter (it
+ will default to be to the right of all other gutters). These
+ class names are the keys passed
+ to setGutterMarker.
+
+
fixedGutter: boolean
+
Determines whether the gutter scrolls along with the content
+ horizontally (false) or whether it stays fixed during horizontal
+ scrolling (true, the default).
+
+
scrollbarStyle: string
+
Chooses a scrollbar implementation. The default
+ is "native", showing native scrollbars. The core
+ library also provides the "null" style, which
+ completely hides the
+ scrollbars. Addons can
+ implement additional scrollbar models.
+
+
coverGutterNextToScrollbar: boolean
+
When fixedGutter
+ is on, and there is a horizontal scrollbar, by default the
+ gutter will be visible to the left of this scrollbar. If this
+ option is set to true, it will be covered by an element with
+ class CodeMirror-gutter-filler.
+
+
inputStyle: string
+
Selects the way CodeMirror handles input and focus. The core
+ library defines the "textarea"
+ and "contenteditable" input models. On mobile
+ browsers, the default is "contenteditable". On
+ desktop browsers, the default is "textarea".
+ Support for IME and screen readers is better in
+ the "contenteditable" model. The intention is to
+ make it the default on modern desktop browsers in the
+ future.
+
+
readOnly: boolean|string
+
This disables editing of the editor content by the user. If
+ the special value "nocursor" is given (instead of
+ simply true), focusing of the editor is also
+ disallowed.
+
+
showCursorWhenSelecting: boolean
+
Whether the cursor should be drawn when a selection is
+ active. Defaults to false.
+
+
lineWiseCopyCut: boolean
+
When enabled, which is the default, doing copy or cut when
+ there is no selection will copy or cut the whole lines that have
+ cursors on them.
+
+
pasteLinesPerSelection: boolean
+
When pasting something from an external source (not from the
+ editor itself), if the number of lines matches the number of
+ selection, CodeMirror will by default insert one line per
+ selection. You can set this to false to disable
+ that behavior.
+
+
selectionsMayTouch: boolean
+
Determines whether multiple selections are joined as soon as
+ they touch (the default) or only when they overlap (true).
+
+
undoDepth: integer
+
The maximum number of undo levels that the editor stores.
+ Note that this includes selection change events. Defaults to
+ 200.
+
+
historyEventDelay: integer
+
The period of inactivity (in milliseconds) that will cause a
+ new history event to be started when typing or deleting.
+ Defaults to 1250.
+
+
tabindex: integer
+
The tab
+ index to assign to the editor. If not given, no tab index
+ will be assigned.
+
+
autofocus: boolean
+
Can be used to make CodeMirror focus itself on
+ initialization. Defaults to off.
+ When fromTextArea is
+ used, and no explicit value is given for this option, it will be
+ set to true when either the source textarea is focused, or it
+ has an autofocus attribute and no other element is
+ focused.
+
+
phrases: ?object
+
Some addons run user-visible strings (such as labels in the
+ interface) through the phrase
+ method to allow for translation. This option determines the
+ return value of that method. When it is null or an object that
+ doesn't have a property named by the input string, that string
+ is returned. Otherwise, the value of the property corresponding
+ to that string is returned.
+
+
+
Below this a few more specialized, low-level options are
+ listed. These are only useful in very specific situations, you
+ might want to skip them the first time you read this manual.
+
+
+
dragDrop: boolean
+
Controls whether drag-and-drop is enabled. On by default.
+
+
allowDropFileTypes: array<string>
+
When set (default is null) only files whose
+ type is in the array can be dropped into the editor. The strings
+ should be MIME types, and will be checked against
+ the type
+ of the File object as reported by the browser.
+
+
cursorBlinkRate: number
+
Half-period in milliseconds used for cursor blinking. The default blink
+ rate is 530ms. By setting this to zero, blinking can be disabled. A
+ negative value hides the cursor entirely.
+
+
cursorScrollMargin: number
+
How much extra space to always keep above and below the
+ cursor when approaching the top or bottom of the visible view in
+ a scrollable document. Default is 0.
+
+
cursorHeight: number
+
Determines the height of the cursor. Default is 1, meaning
+ it spans the whole height of the line. For some fonts (and by
+ some tastes) a smaller height (for example 0.85),
+ which causes the cursor to not reach all the way to the bottom
+ of the line, looks better
+
+
resetSelectionOnContextMenu: boolean
+
Controls whether, when the context menu is opened with a
+ click outside of the current selection, the cursor is moved to
+ the point of the click. Defaults to true.
+
+
workTime, workDelay: number
+
Highlighting is done by a pseudo background-thread that will
+ work for workTime milliseconds, and then use
+ timeout to sleep for workDelay milliseconds. The
+ defaults are 200 and 300, you can change these options to make
+ the highlighting more or less aggressive.
+
+
pollInterval: number
+
Indicates how quickly CodeMirror should poll its input
+ textarea for changes (when focused). Most input is captured by
+ events, but some things, like IME input on some browsers, don't
+ generate events that allow CodeMirror to properly detect it.
+ Thus, it polls. Default is 100 milliseconds.
+
+
flattenSpans: boolean
+
By default, CodeMirror will combine adjacent tokens into a
+ single span if they have the same class. This will result in a
+ simpler DOM tree, and thus perform better. With some kinds of
+ styling (such as rounded corners), this will change the way the
+ document looks. You can set this option to false to disable this
+ behavior.
+
+
addModeClass: boolean
+
When enabled (off by default), an extra CSS class will be
+ added to each token, indicating the
+ (inner) mode that produced it, prefixed
+ with "cm-m-". For example, tokens from the XML mode
+ will get the cm-m-xml class.
+
+
maxHighlightLength: number
+
When highlighting long lines, in order to stay responsive,
+ the editor will give up and simply style the rest of the line as
+ plain text when it reaches a certain position. The default is
+ 10 000. You can set this to Infinity to turn off
+ this behavior.
+
+
viewportMargin: integer
+
Specifies the amount of lines that are rendered above and
+ below the part of the document that's currently scrolled into
+ view. This affects the amount of updates needed when scrolling,
+ and the amount of work that such an update does. You should
+ usually leave it at its default, 10. Can be set
+ to Infinity to make sure the whole document is
+ always rendered, and thus the browser's text search works on it.
+ This will have bad effects on performance of big
+ documents.
+
+
spellcheck: boolean
+
Specifies whether or not spellcheck will be enabled on the input.
+
+
autocorrect: boolean
+
Specifies whether or not autocorrect will be enabled on the input.
+
+
autocapitalize: boolean
+
Specifies whether or not autocapitalization will be enabled on the input.
+
+
+
+
+
Events
+
+
Various CodeMirror-related objects emit events, which allow
+ client code to react to various situations. Handlers for such
+ events can be registered with the on
+ and off methods on the objects
+ that the event fires on. To fire your own events,
+ use CodeMirror.signal(target, name, args...),
+ where target is a non-DOM-node object.
+
+
An editor instance fires the following events.
+ The instance argument always refers to the editor
+ itself.
Fires every time the content of the editor is changed.
+ The changeObj is a {from, to, text, removed,
+ origin} object containing information about the changes
+ that occurred as second argument. from
+ and to are the positions (in the pre-change
+ coordinate system) where the change started and ended (for
+ example, it might be {ch:0, line:18} if the
+ position is at the beginning of line #19). text is
+ an array of strings representing the text that replaced the
+ changed range (split by line). removed is the text
+ that used to be between from and to,
+ which is overwritten by this change. This event is
+ fired before the end of
+ an operation, before the DOM updates
+ happen.
Like the "change"
+ event, but batched per operation,
+ passing an array containing all the changes that happened in the
+ operation. This event is fired after the operation finished, and
+ display changes it makes will trigger a new operation.
This event is fired before a change is applied, and its
+ handler may choose to modify or cancel the change.
+ The changeObj object
+ has from, to, and text
+ properties, as with
+ the "change" event. It
+ also has a cancel() method, which can be called to
+ cancel the change, and, if the change isn't
+ coming from an undo or redo event, an update(from, to,
+ text) method, which may be used to modify the change.
+ Undo or redo changes can't be modified, because they hold some
+ metainformation for restoring old marked ranges that is only
+ valid for that specific change. All three arguments
+ to update are optional, and can be left off to
+ leave the existing value for that field
+ intact. Note: you may not do anything from
+ a "beforeChange" handler that would cause changes
+ to the document or its visualization. Doing so will, since this
+ handler is called directly from the bowels of the CodeMirror
+ implementation, probably cause the editor to become
+ corrupted.
+
+
"cursorActivity" (instance: CodeMirror)
+
Will be fired when the cursor or selection moves, or any
+ change is made to the editor content.
Fired after a key is handled through a
+ key map. name is the name of the handled key (for
+ example "Ctrl-X" or "'q'"),
+ and event is the DOM keydown
+ or keypress event.
This event is fired before the selection is moved. Its
+ handler may inspect the set of selection ranges, present as an
+ array of {anchor, head} objects in
+ the ranges property of the obj
+ argument, and optionally change them by calling
+ the update method on this object, passing an array
+ of ranges in the same format. The object also contains
+ an origin property holding the origin string passed
+ to the selection-changing method, if any. Handlers for this
+ event have the same restriction
+ as "beforeChange"
+ handlers — they should not do anything to directly update the
+ state of the editor.
+
+
"viewportChange" (instance: CodeMirror, from: number, to: number)
+
Fires whenever the view port of
+ the editor changes (due to scrolling, editing, or any other
+ factor). The from and to arguments
+ give the new start and end of the viewport.
+
+
"swapDoc" (instance: CodeMirror, oldDoc: Doc)
+
This is signalled when the editor's document is replaced
+ using the swapDoc
+ method.
Fires when the editor gutter (the line-number area) is
+ clicked. Will pass the editor instance as first argument, the
+ (zero-based) number of the line that was clicked as second
+ argument, the CSS class of the gutter that was clicked as third
+ argument, and the raw mousedown event object as
+ fourth argument.
Fires when the editor gutter (the line-number area)
+ receives a contextmenu event. Will pass the editor
+ instance as first argument, the (zero-based) number of the line
+ that was clicked as second argument, the CSS class of the
+ gutter that was clicked as third argument, and the raw
+ contextmenu mouse event object as fourth argument.
+ You can preventDefault the event, to signal that
+ CodeMirror should do no further handling.
+
+
"focus" (instance: CodeMirror, event: Event)
+
Fires whenever the editor is focused.
+
+
"blur" (instance: CodeMirror, event: Event)
+
Fires whenever the editor is unfocused.
+
+
"scroll" (instance: CodeMirror)
+
Fires when the editor is scrolled.
+
+
"refresh" (instance: CodeMirror)
+
Fires when the editor is refreshed
+ or resized. Mostly useful to invalidate
+ cached values that depend on the editor or character size.
Fires when the editor tries to scroll its cursor into view.
+ Can be hooked into to take care of additional scrollable
+ containers around the editor. When the event object has
+ its preventDefault method called, CodeMirror will
+ not itself try to scroll the window.
+
+
"update" (instance: CodeMirror)
+
Will be fired whenever CodeMirror updates its DOM display.
Fired whenever a line is (re-)rendered to the DOM. Fired
+ right after the DOM element is built, before it is
+ added to the document. The handler may mess with the style of
+ the resulting element, or add event handlers, but
+ should not try to change the state of the editor.
Fired when CodeMirror is handling a DOM event of this type.
+ You can preventDefault the event, or give it a
+ truthy codemirrorIgnore property, to signal that
+ CodeMirror should do no further handling.
+
+
+
Document objects (instances
+ of CodeMirror.Doc) emit the
+ following events:
+
+
+
"change" (doc: CodeMirror.Doc, changeObj: object)
+
Fired whenever a change occurs to the
+ document. changeObj has a similar type as the
+ object passed to the
+ editor's "change"
+ event.
Line handles (as returned by, for
+ example, getLineHandle)
+ support these events:
+
+
+
"delete" ()
+
Will be fired when the line object is deleted. A line object
+ is associated with the start of the line. Mostly useful
+ when you need to find out when your gutter
+ markers on a given line are removed.
+
"change" (line: LineHandle, changeObj: object)
+
Fires when the line's text content is changed in any way
+ (but the line is not deleted outright). The change
+ object is similar to the one passed
+ to change event on the editor
+ object.
+
+
+
Marked range handles (CodeMirror.TextMarker), as returned
+ by markText
+ and setBookmark, emit the
+ following events:
+
+
+
"beforeCursorEnter" ()
+
Fired when the cursor enters the marked range. From this
+ event handler, the editor state may be inspected
+ but not modified, with the exception that the range on
+ which the event fires may be cleared.
+
"clear" (from: {line, ch}, to: {line, ch})
+
Fired when the range is cleared, either through cursor
+ movement in combination
+ with clearOnEnter
+ or through a call to its clear() method. Will only
+ be fired once per handle. Note that deleting the range through
+ text editing does not fire this event, because an undo action
+ might bring the range back into existence. from
+ and to give the part of the document that the range
+ spanned when it was cleared.
+
"hide" ()
+
Fired when the last part of the marker is removed from the
+ document by editing operations.
+
"unhide" ()
+
Fired when, after the marker was removed by editing, a undo
+ operation brought the marker back.
+
+
+
Line widgets (CodeMirror.LineWidget), returned
+ by addLineWidget, fire
+ these events:
+
+
+
"redraw" ()
+
Fired whenever the editor re-adds the widget to the DOM.
+ This will happen once right after the widget is added (if it is
+ scrolled into view), and then again whenever it is scrolled out
+ of view and back in again, or when changes to the editor options
+ or the line the widget is on require the widget to be
+ redrawn.
+
+
+
+
+
Key Maps
+
+
Key maps are ways to associate keys and mouse buttons with
+ functionality. A key map is an object mapping strings that
+ identify the buttons to functions that implement their
+ functionality.
+
+
The CodeMirror distributions comes
+ with Emacs, Vim,
+ and Sublime Text-style keymaps.
+
+
Keys are identified either by name or by character.
+ The CodeMirror.keyNames object defines names for
+ common keys and associates them with their key codes. Examples of
+ names defined here are Enter, F5,
+ and Q. These can be prefixed
+ with Shift-, Cmd-, Ctrl-,
+ and Alt- to specify a modifier. So for
+ example, Shift-Ctrl-Space would be a valid key
+ identifier.
+
+
Common example: map the Tab key to insert spaces instead of a tab
+ character.
Alternatively, a character can be specified directly by
+ surrounding it in single quotes, for example '$'
+ or 'q'. Due to limitations in the way browsers fire
+ key events, these may not be prefixed with modifiers.
+
+
To bind mouse buttons, use the names `LeftClick`,
+ `MiddleClick`, and `RightClick`. These can also be prefixed with
+ modifiers, and in addition, the word `Double` or `Triple` can be
+ put before `Click` (as in `LeftDoubleClick`) to bind a double- or
+ triple-click. The function for such a binding is passed the
+ position that was clicked as second argument.
+
+
Multi-stroke key bindings can be specified
+ by separating the key names by spaces in the property name, for
+ example Ctrl-X Ctrl-V. When a map contains
+ multi-stoke bindings or keys with modifiers that are not specified
+ in the default order (Shift-Cmd-Ctrl-Alt), you must
+ call CodeMirror.normalizeKeyMap on it before it can
+ be used. This function takes a keymap and modifies it to normalize
+ modifier order and properly recognize multi-stroke bindings. It
+ will return the keymap itself.
+
+
The CodeMirror.keyMap object associates key maps
+ with names. User code and key map definitions can assign extra
+ properties to this object. Anywhere where a key map is expected, a
+ string can be given, which will be looked up in this object. It
+ also contains the "default" key map holding the
+ default bindings.
+
+
The values of properties in key maps can be either functions of
+ a single argument (the CodeMirror instance), strings, or
+ false. Strings refer
+ to commands, which are described below. If
+ the property is set to false, CodeMirror leaves
+ handling of the key up to the browser. A key handler function may
+ return CodeMirror.Pass to indicate that it has
+ decided not to handle the key, and other handlers (or the default
+ behavior) should be given a turn.
+
+
Keys mapped to command names that start with the
+ characters "go" or to functions that have a
+ truthy motion property (which should be used for
+ cursor-movement actions) will be fired even when an
+ extra Shift modifier is present (i.e. "Up":
+ "goLineUp" matches both up and shift-up). This is used to
+ easily implement shift-selection.
+
+
Key maps can defer to each other by defining
+ a fallthrough property. This indicates that when a
+ key is not found in the map itself, one or more other maps should
+ be searched. It can hold either a single key map or an array of
+ key maps.
+
+
When a key map needs to set something up when it becomes
+ active, or tear something down when deactivated, it can
+ contain attach and/or detach properties,
+ which should hold functions that take the editor instance and the
+ next or previous keymap. Note that this only works for the
+ top-level keymap, not for fallthrough
+ maps or maps added
+ with extraKeys
+ or addKeyMap.
+
+
+
+
Commands
+
+
Commands are parameter-less actions that can be performed on an
+ editor. Their main use is for key bindings. Commands are defined by
+ adding properties to the CodeMirror.commands object.
+ A number of common commands are defined by the library itself,
+ most of them used by the default key bindings. The value of a
+ command property must be a function of one argument (an editor
+ instance).
+
+
Some of the commands below are referenced in the default
+ key map, but not defined by the core library. These are intended to
+ be defined by user code or addons.
+
+
Commands can also be run with
+ the execCommand
+ method.
+
+
+
selectAllCtrl-A (PC), Cmd-A (Mac)
+
Select the whole content of the editor.
+
+
singleSelectionEsc
+
When multiple selections are present, this deselects all but
+ the primary selection.
+
+
killLineCtrl-K (Mac)
+
Emacs-style line killing. Deletes the part of the line after
+ the cursor. If that consists only of whitespace, the newline at
+ the end of the line is also deleted.
+
+
deleteLineCtrl-D (PC), Cmd-D (Mac)
+
Deletes the whole line under the cursor, including newline at the end.
+
+
delLineLeft
+
Delete the part of the line before the cursor.
+
+
delWrappedLineLeftCmd-Backspace (Mac)
+
Delete the part of the line from the left side of the visual line the cursor is on to the cursor.
+
+
delWrappedLineRightCmd-Delete (Mac)
+
Delete the part of the line from the cursor to the right side of the visual line the cursor is on.
+
+
undoCtrl-Z (PC), Cmd-Z (Mac)
+
Undo the last change. Note that, because browsers still
+ don't make it possible for scripts to react to or customize the
+ context menu, selecting undo (or redo) from the context menu in
+ a CodeMirror instance does not work.
+
+
redoCtrl-Y (PC), Shift-Cmd-Z (Mac), Cmd-Y (Mac)
+
Redo the last undone change.
+
+
undoSelectionCtrl-U (PC), Cmd-U (Mac)
+
Undo the last change to the selection, or if there are no
+ selection-only changes at the top of the history, undo the last
+ change.
+
+
redoSelectionAlt-U (PC), Shift-Cmd-U (Mac)
+
Redo the last change to the selection, or the last text change if
+ no selection changes remain.
Move to the start of the text on the line, or if we are
+ already there, to the actual start of the line (including
+ whitespace).
+
+
goLineEndAlt-Right (PC), Ctrl-E (Mac)
+
Move the cursor to the end of the line.
+
+
goLineRightCmd-Right (Mac)
+
Move the cursor to the right side of the visual line it is on.
+
+
goLineLeftCmd-Left (Mac)
+
Move the cursor to the left side of the visual line it is on. If
+ this line is wrapped, that may not be the start of the line.
+
+
goLineLeftSmart
+
Move the cursor to the left side of the visual line it is
+ on. If that takes it to the start of the line, behave
+ like goLineStartSmart.
+
+
goLineUpUp, Ctrl-P (Mac)
+
Move the cursor up one line.
+
+
goLineDownDown, Ctrl-N (Mac)
+
Move down one line.
+
+
goPageUpPageUp, Shift-Ctrl-V (Mac)
+
Move the cursor up one screen, and scroll up by the same distance.
+
+
goPageDownPageDown, Ctrl-V (Mac)
+
Move the cursor down one screen, and scroll down by the same distance.
+
+
goCharLeftLeft, Ctrl-B (Mac)
+
Move the cursor one character left, going to the previous line
+ when hitting the start of line.
+
+
goCharRightRight, Ctrl-F (Mac)
+
Move the cursor one character right, going to the next line
+ when hitting the end of line.
+
+
goColumnLeft
+
Move the cursor one character left, but don't cross line boundaries.
+
+
goColumnRight
+
Move the cursor one character right, don't cross line boundaries.
+
+
goWordLeftAlt-B (Mac)
+
Move the cursor to the start of the previous word.
+
+
goWordRightAlt-F (Mac)
+
Move the cursor to the end of the next word.
+
+
goGroupLeftCtrl-Left (PC), Alt-Left (Mac)
+
Move to the left of the group before the cursor. A group is
+ a stretch of word characters, a stretch of punctuation
+ characters, a newline, or a stretch of more than one
+ whitespace character.
+
+
goGroupRightCtrl-Right (PC), Alt-Right (Mac)
+
Move to the right of the group after the cursor
+ (see above).
+
+
delCharBeforeShift-Backspace, Ctrl-H (Mac)
+
Delete the character before the cursor.
+
+
delCharAfterDelete, Ctrl-D (Mac)
+
Delete the character after the cursor.
+
+
delWordBeforeAlt-Backspace (Mac)
+
Delete up to the start of the word before the cursor.
+
+
delWordAfterAlt-D (Mac)
+
Delete up to the end of the word after the cursor.
Not defined by the core library, but defined in
+ the search addon (or custom client
+ addons).
+
+
+
+
+
+
+
Customized Styling
+
+
Up to a certain extent, CodeMirror's look can be changed by
+ modifying style sheet files. The style sheets supplied by modes
+ simply provide the colors for that mode, and can be adapted in a
+ very straightforward way. To style the editor itself, it is
+ possible to alter or override the styles defined
+ in codemirror.css.
+
+
Some care must be taken there, since a lot of the rules in this
+ file are necessary to have CodeMirror function properly. Adjusting
+ colors should be safe, of course, and with some care a lot of
+ other things can be changed as well. The CSS classes defined in
+ this file serve the following roles:
+
+
+
CodeMirror
+
The outer element of the editor. This should be used for the
+ editor width, height, borders and positioning. Can also be used
+ to set styles that should hold for everything inside the editor
+ (such as font and font size), or to set a background. Setting
+ this class' height style to auto will
+ make the editor resize to fit its
+ content (it is recommended to also set
+ the viewportMargin
+ option to Infinity when doing this.
+
+
CodeMirror-focused
+
Whenever the editor is focused, the top element gets this
+ class. This is used to hide the cursor and give the selection a
+ different color when the editor is not focused.
+
+
CodeMirror-gutters
+
This is the backdrop for all gutters. Use it to set the
+ default gutter background color, and optionally add a border on
+ the right of the gutters.
+
+
CodeMirror-linenumbers
+
Use this for giving a background or width to the line number
+ gutter.
+
+
CodeMirror-linenumber
+
Used to style the actual individual line numbers. These
+ won't be children of the CodeMirror-linenumbers
+ (plural) element, but rather will be absolutely positioned to
+ overlay it. Use this to set alignment and text properties for
+ the line numbers.
+
+
CodeMirror-lines
+
The visible lines. This is where you specify vertical
+ padding for the editor content.
+
+
CodeMirror-cursor
+
The cursor is a block element that is absolutely positioned.
+ You can make it look whichever way you want.
+
+
CodeMirror-selected
+
The selection is represented by span elements
+ with this class.
These are used to style matched (or unmatched) brackets.
+
+
+
If your page's style sheets do funky things to
+ all div or pre elements (you probably
+ shouldn't do that), you'll have to define rules to cancel these
+ effects out again for elements under the CodeMirror
+ class.
+
+
Themes are also simply CSS files, which define colors for
+ various syntactic elements. See the files in
+ the theme directory.
+
+
+
+
Programming API
+
+
A lot of CodeMirror features are only available through its
+ API. Thus, you need to write code (or
+ use addons) if you want to expose them to
+ your users.
+
+
Whenever points in the document are represented, the API uses
+ objects with line and ch properties.
+ Both are zero-based. CodeMirror makes sure to 'clip' any positions
+ passed by client code so that they fit inside the document, so you
+ shouldn't worry too much about sanitizing your coordinates. If you
+ give ch a value of null, or don't
+ specify it, it will be replaced with the length of the specified
+ line. Such positions may also have a sticky property
+ holding "before" or "after", whether the
+ position is associated with the character before or after it. This
+ influences, for example, where the cursor is drawn on a
+ line-break or bidi-direction boundary.
+
+
Methods prefixed with doc. can, unless otherwise
+ specified, be called both on CodeMirror (editor)
+ instances and CodeMirror.Doc instances. Methods
+ prefixed with cm. are only available
+ on CodeMirror instances.
+
+
Constructor
+
+
Constructing an editor instance is done with
+ the CodeMirror(place: Element|fn(Element),
+ ?option: object) constructor. If the place
+ argument is a DOM element, the editor will be appended to it. If
+ it is a function, it will be called, and is expected to place the
+ editor into the document. options may be an element
+ mapping option names to values. The options
+ that it doesn't explicitly specify (or all options, if it is not
+ passed) will be taken
+ from CodeMirror.defaults.
+
+
Note that the options object passed to the constructor will be
+ mutated when the instance's options
+ are changed, so you shouldn't share such
+ objects between instances.
Get the current editor content. You can pass it an optional
+ argument to specify the string to be used to separate lines
+ (defaults to "\n").
+
doc.setValue(content: string)
+
Set the editor content.
+
+
doc.getRange(from: {line, ch}, to: {line, ch}, ?separator: string) → string
+
Get the text between the given points in the editor, which
+ should be {line, ch} objects. An optional third
+ argument can be given to indicate the line separator string to
+ use (defaults to "\n").
+
doc.replaceRange(replacement: string, from: {line, ch}, to: {line, ch}, ?origin: string)
+
Replace the part of the document between from
+ and to with the given string. from
+ and to must be {line, ch}
+ objects. to can be left off to simply insert the
+ string at position from. When origin
+ is given, it will be passed on
+ to "change" events, and
+ its first letter will be used to determine whether this change
+ can be merged with previous history events, in the way described
+ for selection origins.
+
+
doc.getLine(n: integer) → string
+
Get the content of line n.
+
+
doc.lineCount() → integer
+
Get the number of lines in the editor.
+
doc.firstLine() → integer
+
Get the number of first line in the editor. This will
+ usually be zero but for linked sub-views,
+ or documents instantiated with a non-zero
+ first line, it might return other values.
+
doc.lastLine() → integer
+
Get the number of last line in the editor. This will
+ usually be doc.lineCount() - 1,
+ but for linked sub-views,
+ it might return other values.
+
+
doc.getLineHandle(num: integer) → LineHandle
+
Fetches the line handle for the given line number.
+
doc.getLineNumber(handle: LineHandle) → integer
+
Given a line handle, returns the current position of that
+ line (or null when it is no longer in the
+ document).
Iterate over the whole document, or if start
+ and end line numbers are given, the range
+ from start up to (not including) end,
+ and call f for each line, passing the line handle.
+ This is a faster way to visit a range of line handlers than
+ calling getLineHandle
+ for each of them. Note that line handles have
+ a text property containing the line's content (as a
+ string).
+
+
doc.markClean()
+
Set the editor content as 'clean', a flag that it will
+ retain until it is edited, and which will be set again when such
+ an edit is undone again. Useful to track whether the content
+ needs to be saved. This function is deprecated in favor
+ of changeGeneration,
+ which allows multiple subsystems to track different notions of
+ cleanness without interfering.
Returns a number that can later be passed
+ to isClean to test whether
+ any edits were made (and not undone) in the meantime.
+ If closeEvent is true, the current history event
+ will be ‘closed’, meaning it can't be combined with further
+ changes (rapid typing or deleting events are typically
+ combined).
+
doc.isClean(?generation: integer) → boolean
+
Returns whether the document is currently clean — not
+ modified since initialization or the last call
+ to markClean if no
+ argument is passed, or since the matching call
+ to changeGeneration
+ if a generation value is given.
+
+
+
Cursor and selection methods
+
+
+
doc.getSelection(?lineSep: string) → string
+
Get the currently selected code. Optionally pass a line
+ separator to put between the lines in the output. When multiple
+ selections are present, they are concatenated with instances
+ of lineSep in between.
Replace the selection(s) with the given string. By default,
+ the new selection ends up after the inserted text. The
+ optional select argument can be used to change
+ this—passing "around" will cause the new text to be
+ selected, passing "start" will collapse the
+ selection to the start of the inserted text.
The length of the given array should be the same as the
+ number of active selections. Replaces the content of the
+ selections with the strings in the array.
+ The select argument works the same as
+ in replaceSelection.
+
+
doc.getCursor(?start: string) → {line, ch}
+
Retrieve one end of the primary
+ selection. start is an optional string indicating
+ which end of the selection to return. It may
+ be "from", "to", "head"
+ (the side of the selection that moves when you press
+ shift+arrow), or "anchor" (the fixed side of the
+ selection). Omitting the argument is the same as
+ passing "head". A {line, ch} object
+ will be returned.
+
doc.listSelections() → array<{anchor, head}>
+
Retrieves a list of all current selections. These will
+ always be sorted, and never overlap (overlapping selections are
+ merged). Each object in the array contains anchor
+ and head properties referring to {line,
+ ch} objects.
Set the cursor position. You can either pass a
+ single {line, ch} object, or the line and the
+ character as two separate parameters. Will replace all
+ selections with a single, empty selection at the given position.
+ The supported options are the same as for setSelection.
Set a single selection range. anchor
+ and head should be {line, ch}
+ objects. head defaults to anchor when
+ not given. These options are supported:
+
+
scroll: boolean
+
Determines whether the selection head should be scrolled
+ into view. Defaults to true.
+
origin: string
+
Determines whether the selection history event may be
+ merged with the previous one. When an origin starts with the
+ character +, and the last recorded selection had
+ the same origin and was similar (close
+ in time, both
+ collapsed or both non-collapsed), the new one will replace the
+ old one. When it starts with *, it will always
+ replace the previous event (if that had the same origin).
+ Built-in motion uses the "+move" origin. User input uses the "+input" origin.
+
bias: number
+
Determine the direction into which the selection endpoints
+ should be adjusted when they fall inside
+ an atomic range. Can be either -1
+ (backward) or 1 (forward). When not given, the bias will be
+ based on the relative position of the old selection—the editor
+ will try to move further away from that, to prevent getting
+ stuck.
Sets a new set of selections. There must be at least one
+ selection in the given array. When primary is a
+ number, it determines which selection is the primary one. When
+ it is not given, the primary index is taken from the previous
+ selection, or set to the last range if the previous selection
+ had less ranges than the new one. Supports the same options
+ as setSelection.
Similar
+ to setSelection, but
+ will, if shift is held or
+ the extending flag is set, move the
+ head of the selection while leaving the anchor at its current
+ place. to is optional, and can be passed to ensure
+ a region (for example a word or paragraph) will end up selected
+ (in addition to whatever lies between that region and the
+ current anchor). When multiple selections are present, all but
+ the primary selection will be dropped by this method.
+ Supports the same options as setSelection.
Applies the given function to all existing selections, and
+ calls extendSelections
+ on the result.
+
doc.setExtending(value: boolean)
+
Sets or clears the 'extending' flag, which acts similar to
+ the shift key, in that it will cause cursor movement and calls
+ to extendSelection
+ to leave the selection anchor in place.
Used to find the target position for horizontal cursor
+ motion. start is a {line, ch}
+ object, amount an integer (may be negative),
+ and unit one of the
+ string "char", "column",
+ or "word". Will return a position that is produced
+ by moving amount times the distance specified
+ by unit. When visually is true, motion
+ in right-to-left text will be visual rather than logical. When
+ the motion was clipped by hitting the end or start of the
+ document, the returned value will have a hitSide
+ property set to true.
Similar to findPosH,
+ but used for vertical motion. unit may
+ be "line" or "page". The other
+ arguments and the returned value have the same interpretation as
+ they have in findPosH.
Returns the start and end of the 'word' (the stretch of
+ letters, whitespace, or punctuation) at the given position.
+
+
+
Configuration methods
+
+
+
cm.setOption(option: string, value: any)
+
Change the configuration of the editor. option
+ should the name of an option,
+ and value should be a valid value for that
+ option.
+
cm.getOption(option: string) → any
+
Retrieves the current value of the given option for this
+ editor instance.
+
+
cm.addKeyMap(map: object, bottom: boolean)
+
Attach an additional key map to the
+ editor. This is mostly useful for addons that need to register
+ some key handlers without trampling on
+ the extraKeys
+ option. Maps added in this way have a higher precedence than
+ the extraKeys
+ and keyMap options,
+ and between them, the maps added earlier have a lower precedence
+ than those added later, unless the bottom argument
+ was passed, in which case they end up below other key maps added
+ with this method.
+
cm.removeKeyMap(map: object)
+
Disable a keymap added
+ with addKeyMap. Either
+ pass in the key map object itself, or a string, which will be
+ compared against the name property of the active
+ key maps.
Enable a highlighting overlay. This is a stateless mini-mode
+ that can be used to add extra highlighting. For example,
+ the search addon uses it to
+ highlight the term that's currently being
+ searched. mode can be a mode
+ spec or a mode object (an object with
+ a token method).
+ The options parameter is optional. If given, it
+ should be an object, optionally containing the following options:
+
+
opaque: bool
+
Defaults to off, but can be given to allow the overlay
+ styling, when not null, to override the styling of
+ the base mode entirely, instead of the two being applied
+ together.
+
priority: number
+
Determines the ordering in which the overlays are
+ applied. Those with high priority are applied after those
+ with lower priority, and able to override the opaqueness of
+ the ones that come before. Defaults to 0.
+
+
+
+
cm.removeOverlay(mode: string|object)
+
Pass this the exact value passed for the mode
+ parameter to addOverlay,
+ or a string that corresponds to the name property of
+ that value, to remove an overlay again.
+
+
cm.on(type: string, func: (...args))
+
Register an event handler for the given event type (a
+ string) on the editor instance. There is also
+ a CodeMirror.on(object, type, func) version
+ that allows registering of events on any object.
+
cm.off(type: string, func: (...args))
+
Remove an event handler on the editor instance. An
+ equivalent CodeMirror.off(object, type,
+ func) also exists.
+
+
+
Document management methods
+
+
Each editor is associated with an instance
+ of CodeMirror.Doc, its document. A document
+ represents the editor content, plus a selection, an undo history,
+ and a mode. A document can only be
+ associated with a single editor at a time. You can create new
+ documents by calling the CodeMirror.Doc(text: string, mode:
+ Object, firstLineNumber: ?number, lineSeparator: ?string)
+ constructor. The last three arguments are optional and can be used
+ to set a mode for the document, make it start at a line number
+ other than 0, and set a specific line separator respectively.
+
+
+
cm.getDoc() → Doc
+
Retrieve the currently active document from an editor.
+
doc.getEditor() → CodeMirror
+
Retrieve the editor associated with a document. May
+ return null.
+
+
cm.swapDoc(doc: CodeMirror.Doc) → Doc
+
Attach a new document to the editor. Returns the old
+ document, which is now no longer associated with an editor.
+
+
doc.copy(copyHistory: boolean) → Doc
+
Create an identical copy of the given doc.
+ When copyHistory is true, the history will also be
+ copied. Can not be called directly on an editor.
+
+
doc.linkedDoc(options: object) → Doc
+
Create a new document that's linked to the target document.
+ Linked documents will stay in sync (changes to one are also
+ applied to the other) until unlinked.
+ These are the options that are supported:
+
+
sharedHist: boolean
+
When turned on, the linked copy will share an undo
+ history with the original. Thus, something done in one of
+ the two can be undone in the other, and vice versa.
+
from: integer
+
to: integer
+
Can be given to make the new document a subview of the
+ original. Subviews only show a given range of lines. Note
+ that line coordinates inside the subview will be consistent
+ with those of the parent, so that for example a subview
+ starting at line 10 will refer to its first line as line 10,
+ not 0.
+
mode: string|object
+
By default, the new document inherits the mode of the
+ parent. This option can be set to
+ a mode spec to give it a
+ different mode.
+
+
doc.unlinkDoc(doc: CodeMirror.Doc)
+
Break the link between two documents. After calling this,
+ changes will no longer propagate between the documents, and, if
+ they had a shared history, the history will become
+ separate.
Will call the given function for all documents linked to the
+ target document. It will be passed two arguments, the linked document
+ and a boolean indicating whether that document shares history
+ with the target.
Returns an object with {undo, redo} properties,
+ both of which hold integers, indicating the amount of stored
+ undo and redo operations.
+
doc.clearHistory()
+
Clears the editor's undo history.
+
doc.getHistory() → object
+
Get a (JSON-serializable) representation of the undo history.
+
doc.setHistory(history: object)
+
Replace the editor's undo history with the one provided,
+ which must be a value as returned
+ by getHistory. Note that
+ this will have entirely undefined results if the editor content
+ isn't also the same as it was when getHistory was
+ called.
+
+
+
Text-marking methods
+
+
+
doc.markText(from: {line, ch}, to: {line, ch}, ?options: object) → TextMarker
+
Can be used to mark a range of text with a specific CSS
+ class name. from and to should
+ be {line, ch} objects. The options
+ parameter is optional. When given, it should be an object that
+ may contain the following configuration options:
+
+
className: string
+
Assigns a CSS class to the marked stretch of text.
+
inclusiveLeft: boolean
+
Determines whether
+ text inserted on the left of the marker will end up inside
+ or outside of it.
+
inclusiveRight: boolean
+
Like inclusiveLeft,
+ but for the right side.
+
selectLeft: boolean
+
For atomic ranges, determines whether the cursor is allowed
+ to be placed directly to the left of the range. Has no effect on
+ non-atomic ranges.
+
selectRight: boolean
+
Like selectLeft,
+ but for the right side.
+
atomic: boolean
+
Atomic ranges act as a single unit when cursor movement is
+ concerned—i.e. it is impossible to place the cursor inside of
+ them. You can control whether the cursor is allowed to be placed
+ directly before or after them using selectLeft
+ or selectRight. If selectLeft
+ (or right) is not provided, then inclusiveLeft (or
+ right) will control this behavior.
+
collapsed: boolean
+
Collapsed ranges do not show up in the display. Setting a
+ range to be collapsed will automatically make it atomic.
+
clearOnEnter: boolean
+
When enabled, will cause the mark to clear itself whenever
+ the cursor enters its range. This is mostly useful for
+ text-replacement widgets that need to 'snap open' when the
+ user tries to edit them. The
+ "clear" event
+ fired on the range handle can be used to be notified when this
+ happens.
+
clearWhenEmpty: boolean
+
Determines whether the mark is automatically cleared when
+ it becomes empty. Default is true.
+
replacedWith: Element
+
Use a given node to display this range. Implies both
+ collapsed and atomic. The given DOM node must be an
+ inline element (as opposed to a block element).
+
handleMouseEvents: boolean
+
When replacedWith is given, this determines
+ whether the editor will capture mouse and drag events
+ occurring in this widget. Default is false—the events will be
+ left alone for the default browser handler, or specific
+ handlers on the widget, to capture.
+
readOnly: boolean
+
A read-only span can, as long as it is not cleared, not be
+ modified except by
+ calling setValue to reset
+ the whole document. Note: adding a read-only span
+ currently clears the undo history of the editor, because
+ existing undo events being partially nullified by read-only
+ spans would corrupt the history (in the current
+ implementation).
+
addToHistory: boolean
+
When set to true (default is false), adding this marker
+ will create an event in the undo history that can be
+ individually undone (clearing the marker).
+
startStyle: string
Can be used to specify
+ an extra CSS class to be applied to the leftmost span that
+ is part of the marker.
+
endStyle: string
Equivalent
+ to startStyle, but for the rightmost span.
+
css: string
+
A string of CSS to be applied to the covered text. For example "color: #fe3".
+
attributes: object
+
When given, add the attributes in the given object to the
+ elements created for the marked text. Adding class or
+ style attributes this way is not supported.
+
shared: boolean
When the
+ target document is linked to other
+ documents, you can set shared to true to make the
+ marker appear in all documents. By default, a marker appears
+ only in its target document.
+
+ The method will return an object that represents the marker
+ (with constructor CodeMirror.TextMarker), which
+ exposes three methods:
+ clear(), to remove the mark,
+ find(), which returns
+ a {from, to} object (both holding document
+ positions), indicating the current position of the marked range,
+ or undefined if the marker is no longer in the
+ document, and finally changed(),
+ which you can call if you've done something that might change
+ the size of the marker (for example changing the content of
+ a replacedWith
+ node), and want to cheaply update the display.
Inserts a bookmark, a handle that follows the text around it
+ as it is being edited, at the given position. A bookmark has two
+ methods find() and clear(). The first
+ returns the current position of the bookmark, if it is still in
+ the document, and the second explicitly removes the bookmark.
+ The options argument is optional. If given, the following
+ properties are recognized:
+
+
widget: Element
Can be used to display a DOM
+ node at the current location of the bookmark (analogous to
+ the replacedWith
+ option to markText).
+
insertLeft: boolean
By default, text typed
+ when the cursor is on top of the bookmark will end up to the
+ right of the bookmark. Set this option to true to make it go
+ to the left instead.
Sets the gutter marker for the given gutter (identified by
+ its CSS class, see
+ the gutters option)
+ to the given value. Value can be either null, to
+ clear the marker, or a DOM element, to set it. The DOM element
+ will be shown in the specified gutter next to the specified
+ line.
+
+
doc.clearGutter(gutterID: string)
+
Remove all gutter markers in
+ the gutter with the given ID.
Set a CSS class name for the given line. line
+ can be a number or a line handle. where determines
+ to which element this class should be applied, can can be one
+ of "text" (the text element, which lies in front of
+ the selection), "background" (a background element
+ that will be behind the selection), "gutter" (the
+ line's gutter space), or "wrap" (the wrapper node
+ that wraps all of the line's elements, including gutter
+ elements). class should be the name of the class to
+ apply.
Remove a CSS class from a line. line can be a
+ line handle or number. where should be one
+ of "text", "background",
+ or "wrap"
+ (see addLineClass). class
+ can be left off to remove all classes for the specified node, or
+ be a string to remove only a specific class.
+
+
doc.lineInfo(line: integer|LineHandle) → object
+
Returns the line number, text content, and marker status of
+ the given line, which can be either a number or a line handle.
+ The returned object has the structure {line, handle, text,
+ gutterMarkers, textClass, bgClass, wrapClass, widgets},
+ where gutterMarkers is an object mapping gutter IDs
+ to marker elements, and widgets is an array
+ of line widgets attached to this
+ line, and the various class properties refer to classes added
+ with addLineClass.
Puts node, which should be an absolutely
+ positioned DOM node, into the editor, positioned right below the
+ given {line, ch} position.
+ When scrollIntoView is true, the editor will ensure
+ that the entire node is visible (if possible). To remove the
+ widget again, simply use DOM methods (move it somewhere else, or
+ call removeChild on its parent).
Adds a line widget, an element shown below a line, spanning
+ the whole of the editor's width, and moving the lines below it
+ downwards. line should be either an integer or a
+ line handle, and node should be a DOM node, which
+ will be displayed below the given line. options,
+ when given, should be an object that configures the behavior of
+ the widget. The following options are supported (all default to
+ false):
+
+
coverGutter: boolean
+
Whether the widget should cover the gutter.
+
noHScroll: boolean
+
Whether the widget should stay fixed in the face of
+ horizontal scrolling.
+
above: boolean
+
Causes the widget to be placed above instead of below
+ the text of the line.
+
handleMouseEvents: boolean
+
Determines whether the editor will capture mouse and
+ drag events occurring in this widget. Default is false—the
+ events will be left alone for the default browser handler,
+ or specific handlers on the widget, to capture.
+
insertAt: integer
+
By default, the widget is added below other widgets for
+ the line. This option can be used to place it at a different
+ position (zero for the top, N to put it after the Nth other
+ widget). Note that this only has effect once, when the
+ widget is created.
+
+ Note that the widget node will become a descendant of nodes with
+ CodeMirror-specific CSS classes, and those classes might in some
+ cases affect it. This method returns an object that represents
+ the widget placement. It'll have a line property
+ pointing at the line handle that it is associated with, and the following methods:
+
+
clear()
Removes the widget.
+
changed()
Call
+ this if you made some change to the widget's DOM node that
+ might affect its height. It'll force CodeMirror to update
+ the height of the line that contains the widget.
Programmatically set the size of the editor (overriding the
+ applicable CSS
+ rules). width and height
+ can be either numbers (interpreted as pixels) or CSS units
+ ("100%", for example). You can
+ pass null for either of them to indicate that that
+ dimension should not be changed.
+
+
cm.scrollTo(x: number, y: number)
+
Scroll the editor to a given (pixel) position. Both
+ arguments may be left as null
+ or undefined to have no effect.
Get an {left, top, width, height, clientWidth,
+ clientHeight} object that represents the current scroll
+ position, the size of the scrollable area, and the size of the
+ visible area (minus scrollbars).
Scrolls the given position into view. what may
+ be null to scroll the cursor into view,
+ a {line, ch} position to scroll a character into
+ view, a {left, top, right, bottom} pixel range (in
+ editor-local coordinates), or a range {from, to}
+ containing either two character positions or two pixel squares.
+ The margin parameter is optional. When given, it
+ indicates the amount of vertical pixels around the given area
+ that should be made visible as well.
Returns an {left, top, bottom} object
+ containing the coordinates of the cursor position.
+ If mode is "local", they will be
+ relative to the top-left corner of the editable document. If it
+ is "page" or not given, they are relative to the
+ top-left corner of the page. If mode
+ is "window", the coordinates are relative to the
+ top-left corner of the currently visible (scrolled)
+ window. where can be a boolean indicating whether
+ you want the start (true) or the end
+ (false) of the selection, or, if a {line,
+ ch} object is given, it specifies the precise position at
+ which you want to measure.
Returns the position and dimensions of an arbitrary
+ character. pos should be a {line, ch}
+ object. This differs from cursorCoords in that
+ it'll give the size of the whole character, rather than just the
+ position that the cursor would have when it would sit at that
+ position.
Given an {left, top} object (e.g. coordinates of a mouse event) returns
+ the {line, ch} position that corresponds to it. The
+ optional mode parameter determines relative to what
+ the coordinates are interpreted. It may
+ be "window", "page" (the default),
+ or "local".
+
cm.lineAtHeight(height: number, ?mode: string) → number
+
Computes the line at the given pixel
+ height. mode can be one of the same strings
+ that coordsChar
+ accepts.
+
cm.heightAtLine(line: integer|LineHandle, ?mode: string, ?includeWidgets: bool) → number
+
Computes the height of the top of a line, in the coordinate
+ system specified by mode
+ (see coordsChar), which
+ defaults to "page". When a line below the bottom of
+ the document is specified, the returned value is the bottom of
+ the last line in the document. By default, the position of the
+ actual text is returned. If `includeWidgets` is true and the
+ line has line widgets, the position above the first line widget
+ is returned.
+
cm.defaultTextHeight() → number
+
Returns the line height of the default font for the editor.
+
cm.defaultCharWidth() → number
+
Returns the pixel width of an 'x' in the default font for
+ the editor. (Note that for non-monospace fonts, this is mostly
+ useless, and even for monospace fonts, non-ascii characters
+ might have a different width).
+
+
cm.getViewport() → {from: number, to: number}
+
Returns a {from, to} object indicating the
+ start (inclusive) and end (exclusive) of the currently rendered
+ part of the document. In big documents, when most content is
+ scrolled out of view, CodeMirror will only render the visible
+ part, and a margin around it. See also
+ the viewportChange
+ event.
+
+
cm.refresh()
+
If your code does something to change the size of the editor
+ element (window resizes are already listened for), or unhides
+ it, you should probably follow up by calling this method to
+ ensure CodeMirror is still looking as intended. See also
+ the autorefresh addon.
+
+
+
Mode, state, and token-related methods
+
+
When writing language-aware functionality, it can often be
+ useful to hook into the knowledge that the CodeMirror language
+ mode has. See the section on modes for a
+ more detailed description of how these work.
+
+
+
doc.getMode() → object
+
Gets the (outer) mode object for the editor. Note that this
+ is distinct from getOption("mode"), which gives you
+ the mode specification, rather than the resolved, instantiated
+ mode object.
+
+
cm.getModeAt(pos: {line, ch}) → object
+
Gets the inner mode at a given position. This will return
+ the same as getMode for
+ simple modes, but will return an inner mode for nesting modes
+ (such as htmlmixed).
Retrieves information about the token the current mode found
+ before the given position (a {line, ch} object). The
+ returned object has the following properties:
+
+
start
The character (on the given line) at which the token starts.
+
end
The character at which the token ends.
+
string
The token's string.
+
type
The token type the mode assigned
+ to the token, such as "keyword"
+ or "comment" (may also be null).
+
state
The mode's state at the end of this token.
+
+ If precise is true, the token will be guaranteed to be accurate based on recent edits. If false or
+ not specified, the token will use cached state information, which will be faster but might not be accurate if
+ edits were recently made and highlighting has not yet completed.
+
This is similar
+ to getTokenAt, but
+ collects all tokens for a given line into an array. It is much
+ cheaper than repeatedly calling getTokenAt, which
+ re-parses the part of the line before the token for every call.
+
+
cm.getTokenTypeAt(pos: {line, ch}) → string
+
This is a (much) cheaper version
+ of getTokenAt useful for
+ when you just need the type of the token at a given position,
+ and no other information. Will return null for
+ unstyled tokens, and a string, potentially containing multiple
+ space-separated style names, otherwise.
Fetch the set of applicable helper values for the given
+ position. Helpers provide a way to look up functionality
+ appropriate for a mode. The type argument provides
+ the helper namespace (see
+ registerHelper), in
+ which the values will be looked up. When the mode itself has a
+ property that corresponds to the type, that
+ directly determines the keys that are used to look up the helper
+ values (it may be either a single string, or an array of
+ strings). Failing that, the mode's helperType
+ property and finally the mode's name are used.
+
For example, the JavaScript mode has a
+ property fold containing "brace". When
+ the brace-fold addon is loaded, that defines a
+ helper named brace in the fold
+ namespace. This is then used by
+ the foldcode addon to
+ figure out that it can use that folding function to fold
+ JavaScript code.
+
When any 'global'
+ helpers are defined for the given namespace, their predicates
+ are called on the current mode and editor, and all those that
+ declare they are applicable will also be added to the array that
+ is returned.
Returns the mode's parser state, if any, at the end of the
+ given line number. If no line number is given, the state at the
+ end of the document is returned. This can be useful for storing
+ parsing errors in the state, or getting other kinds of
+ contextual information for a line. precise is defined
+ as in getTokenAt().
+
+
+
Miscellaneous methods
+
+
+
cm.operation(func: () → any) → any
+
CodeMirror internally buffers changes and only updates its
+ DOM structure after it has finished performing some operation.
+ If you need to perform a lot of operations on a CodeMirror
+ instance, you can call this method with a function argument. It
+ will call the function, buffering up all changes, and only doing
+ the expensive update after the function returns. This can be a
+ lot faster. The return value from this method will be the return
+ value of your function.
+
+
cm.startOperation()
+
cm.endOperation()
+
In normal circumstances, use the above operation
+ method. But if you want to buffer operations happening asynchronously,
+ or that can't all be wrapped in a callback function, you can
+ call startOperation to tell CodeMirror to start
+ buffering changes, and endOperation to actually
+ render all the updates. Be careful: if you use this
+ API and forget to call endOperation, the editor will
+ just never update.
Adjust the indentation of the given line. The second
+ argument (which defaults to "smart") may be one of:
+
+
"prev"
+
Base indentation on the indentation of the previous line.
+
"smart"
+
Use the mode's smart indentation if available, behave
+ like "prev" otherwise.
+
"add"
+
Increase the indentation of the line by
+ one indent unit.
+
"subtract"
+
Reduce the indentation of the line.
+
<integer>
+
Add (positive number) or reduce (negative number) the
+ indentation by the given amount of spaces.
+
+
+
cm.toggleOverwrite(?value: boolean)
+
Switches between overwrite and normal insert mode (when not
+ given an argument), or sets the overwrite mode to a specific
+ state (when given an argument).
+
+
cm.isReadOnly() → boolean
+
Tells you whether the editor's content can be edited by the
+ user.
+
+
doc.lineSeparator()
+
Returns the preferred line separator string for this
+ document, as per the option
+ by the same name. When that option is null, the
+ string "\n" is returned.
+
+
cm.execCommand(name: string)
+
Runs the command with the given name on the editor.
+
+
doc.posFromIndex(index: integer) → {line, ch}
+
Calculates and returns a {line, ch} object for a
+ zero-based index who's value is relative to the start of the
+ editor's text. If the index is out of range of the text then
+ the returned object is clipped to start or end of the text
+ respectively.
Allow the given string to be translated with
+ the phrases
+ option.
+
+
cm.getInputField() → Element
+
Returns the input field for the editor. Will be a textarea
+ or an editable div, depending on the value of
+ the inputStyle
+ option.
+
cm.getWrapperElement() → Element
+
Returns the DOM node that represents the editor, and
+ controls its size. Remove this from your tree to delete an
+ editor instance.
+
cm.getScrollerElement() → Element
+
Returns the DOM node that is responsible for the scrolling
+ of the editor.
+
cm.getGutterElement() → Element
+
Fetches the DOM node that contains the editor gutters.
+
+
+
Static properties
+
The CodeMirror object itself provides
+ several useful properties.
+
+
+
CodeMirror.version: string
+
It contains a string that indicates the version of the
+ library. This is a triple of
+ integers "major.minor.patch",
+ where patch is zero for releases, and something
+ else (usually one) for dev snapshots.
This method provides another way to initialize an editor. It
+ takes a textarea DOM node as first argument and an optional
+ configuration object as second. It will replace the textarea
+ with a CodeMirror instance, and wire up the form of that
+ textarea (if any) to make sure the editor contents are put into
+ the textarea when the form is submitted. The text in the
+ textarea will provide the content for the editor. A CodeMirror
+ instance created this way has three additional methods:
+
+
cm.save()
+
Copy the content of the editor into the textarea.
+
+
cm.toTextArea()
+
Remove the editor, and restore the original textarea (with
+ the editor's current content). If you dynamically create and
+ destroy editors made with `fromTextArea`, without destroying
+ the form they are part of, you should make sure to call
+ `toTextArea` to remove the editor, or its `"submit"` handler
+ on the form will cause a memory leak.
+
+
cm.getTextArea() → TextAreaElement
+
Returns the textarea that the instance was based on.
+
+
+
+
CodeMirror.defaults: object
+
An object containing default values for
+ all options. You can assign to its
+ properties to modify defaults (though this won't affect editors
+ that have already been created).
If you want to define extra methods in terms of the
+ CodeMirror API, it is possible to
+ use defineExtension. This will cause the given
+ value (usually a method) to be added to all CodeMirror instances
+ created from then on.
Similarly, defineOption can be used to define new options for
+ CodeMirror. The updateFunc will be called with the
+ editor instance and the new value when an editor is initialized,
+ and whenever the option is modified
+ through setOption.
+
+
CodeMirror.defineInitHook(func: function)
+
If your extension just needs to run some
+ code whenever a CodeMirror instance is initialized,
+ use CodeMirror.defineInitHook. Give it a function as
+ its only argument, and from then on, that function will be called
+ (with the instance as argument) whenever a new CodeMirror instance
+ is initialized.
Registers a helper value with the given name in
+ the given namespace (type). This is used to define
+ functionality that may be looked up by mode. Will create (if it
+ doesn't already exist) a property on the CodeMirror
+ object for the given type, pointing to an object
+ that maps names to values. I.e. after
+ doing CodeMirror.registerHelper("hint", "foo",
+ myFoo), the value CodeMirror.hint.foo will
+ point to myFoo.
Acts
+ like registerHelper,
+ but also registers this helper as 'global', meaning that it will
+ be included by getHelpers
+ whenever the given predicate returns true when
+ called with the local mode and editor.
A constructor for the objects that are used to represent
+ positions in editor documents. sticky defaults to
+ null, but can be set to "before"
+ or "after" to make the position explicitly
+ associate with the character before or after it.
+
+
CodeMirror.changeEnd(change: object) → {line, ch}
+
Utility function that computes an end position from a change
+ (an object with from, to,
+ and text properties, as passed to
+ various event handlers). The
+ returned position will be the end of the changed
+ range, after the change is applied.
+
+
CodeMirror.countColumn(line: string, index: number, tabSize: number) → number
+
Find the column position at a given string index using a given tabsize.
+
+
+
+
+
Addons
+
+
The addon directory in the distribution contains a
+ number of reusable components that implement extra editor
+ functionality (on top of extension functions
+ like defineOption, defineExtension,
+ and registerHelper). In
+ brief, they are:
Provides a very simple way to query users for text input.
+ Adds the openDialog(template, callback, options) →
+ closeFunction method to CodeMirror instances,
+ which can be called with an HTML fragment or a detached DOM
+ node that provides the prompt (should include an input
+ or button tag), and a callback function that is called
+ when the user presses enter. It returns a function closeFunction
+ which, if called, will close the dialog immediately.
+ openDialog takes the following options:
+
+
closeOnEnter: bool
+
If true, the dialog will be closed when the user presses
+ enter in the input. Defaults to true.
+
closeOnBlur: bool
+
Determines whether the dialog is closed when it loses focus. Defaults to true.
An event handler that will be called whenever keydown fires in the
+ dialog's input. If your callback returns true,
+ the dialog will not do any further processing of the event.
A callback that will be called after the dialog has been closed and
+ removed from the DOM. No return value.
+
+
+
Also adds an openNotification(template, options) →
+ closeFunction function that simply shows an HTML
+ fragment as a notification at the top of the editor. It takes a
+ single option: duration, the amount of time after
+ which the notification will be automatically closed. If
+ duration is zero, the dialog will not be closed automatically.
Adds the getSearchCursor(query, start, options) →
+ cursor method to CodeMirror instances, which can be used
+ to implement search/replace functionality. query
+ can be a regular expression or a string. start
+ provides the starting position of the search. It can be
+ a {line, ch} object, or can be left off to default
+ to the start of the document. options is an
+ optional object, which can contain the property `caseFold:
+ false` to disable case folding when matching a string, or the
+ property `multiline: disable` to disable multi-line matching for
+ regular expressions (which may help performance). A search
+ cursor has the following methods:
+
+
findNext() → boolean
+
findPrevious() → boolean
+
Search forward or backward from the current position.
+ The return value indicates whether a match was found. If
+ matching a regular expression, the return value will be the
+ array returned by the match method, in case you
+ want to extract matched groups.
+
from() → {line, ch}
+
to() → {line, ch}
+
These are only valid when the last call
+ to findNext or findPrevious did
+ not return false. They will return {line, ch}
+ objects pointing at the start and end of the match.
+
replace(text: string, ?origin: string)
+
Replaces the currently found match with the given text
+ and adjusts the cursor position to reflect the
+ replacement.
Implements the search commands. CodeMirror has keys bound to
+ these by default, but will not do anything with them unless an
+ implementation is provided. Depends
+ on searchcursor.js, and will make use
+ of openDialog when
+ available to make prompting for search queries less ugly.
Implements a jumpToLine command and binding Alt-G to it.
+ Accepts linenumber, +/-linenumber, line:char,
+ scroll% and :linenumber formats.
+ This will make use of openDialog
+ when available to make prompting for line number neater.
Adds a showMatchesOnScrollbar method to editor
+ instances, which should be given a query (string or regular
+ expression), optionally a case-fold flag (only applicable for
+ strings), and optionally a class name (defaults
+ to CodeMirror-search-match) as arguments. When
+ called, matches of the given query will be displayed on the
+ editor's vertical scrollbar. The method returns an object with
+ a clear method that can be called to remove the
+ matches. Depends on
+ the annotatescrollbar
+ addon, and
+ the matchesonscrollbar.css
+ file provides a default (transparent yellowish) definition of
+ the CSS class applied to the matches. Note that the matches are
+ only perfectly aligned if your scrollbar does not have buttons
+ at the top and bottom. You can use
+ the simplescrollbar
+ addon to make sure of this. If this addon is loaded,
+ the search addon will
+ automatically use it.
Defines an option matchBrackets which, when set
+ to true or an options object, causes matching brackets to be
+ highlighted whenever the cursor is next to them. It also adds a
+ method matchBrackets that forces this to happen
+ once, and a method findMatchingBracket that can be
+ used to run the bracket-finding algorithm that this uses
+ internally. It takes a start position and an optional config
+ object. By default, it will find the match to a matchable
+ character either before or after the cursor (preferring the one
+ before), but you can control its behavior with these options:
+
+
afterCursor
+
Only use the character after the start position, never the one before it.
+
strict
+
Causes only matches where both brackets are at the same side of the start position to be considered.
+
maxScanLines
+
Stop after scanning this amount of lines without a successful match. Defaults to 1000.
+
maxScanLineLength
+
Ignore lines longer than this. Defaults to 10000.
+
maxHighlightLineLength
+
Don't highlight a bracket in a line longer than this. Defaults to 1000.
Defines an option autoCloseBrackets that will
+ auto-close brackets and quotes when typed. By default, it'll
+ auto-close ()[]{}''"", but you can pass it a string
+ similar to that (containing pairs of matching characters), or an
+ object with pairs and
+ optionally explode properties to customize
+ it. explode should be a similar string that gives
+ the pairs of characters that, when enter is pressed between
+ them, should have the second character also moved to its own
+ line. By default, if the active mode has
+ a closeBrackets property, that overrides the
+ configuration given in the option. But you can add
+ an override property with a truthy value to
+ override mode-specific
+ configuration. Demo
+ here.
Defines an option matchTags that, when enabled,
+ will cause the tags around the cursor to be highlighted (using
+ the CodeMirror-matchingtag class). Also
+ defines
+ a commandtoMatchingTag,
+ which you can bind a key to in order to jump to the tag matching
+ the one under the cursor. Depends on
+ the addon/fold/xml-fold.js
+ addon. Demo here.
Adds an option showTrailingSpace which, when
+ enabled, adds the CSS class cm-trailingspace to
+ stretches of whitespace at the end of lines.
+ The demo has a nice
+ squiggly underline style for this class.
Defines an autoCloseTags option that will
+ auto-close XML tags when '>' or '/'
+ is typed, and
+ a closeTagcommand that
+ closes the nearest open tag. Depends on
+ the fold/xml-fold.js addon. See
+ the demo.
Markdown specific. Defines
+ a "newlineAndIndentContinueMarkdownList"command
+ that can be bound to enter to automatically
+ insert the leading characters for continuing a list. See
+ the Markdown mode
+ demo.
Helps with code folding. Adds a foldCode method
+ to editor instances, which will try to do a code fold starting
+ at the given line, or unfold the fold that is already present.
+ The method takes as first argument the position that should be
+ folded (may be a line number or
+ a Pos), and as second optional
+ argument either a range-finder function, or an options object,
+ supporting the following properties:
+
+
rangeFinder: fn(CodeMirror, Pos)
+
The function that is used to find
+ foldable ranges. If this is not directly passed, it will
+ default to CodeMirror.fold.auto, which
+ uses getHelpers with
+ a "fold" type to find folding functions
+ appropriate for the local mode. There are files in
+ the addon/fold/
+ directory providing CodeMirror.fold.brace, which
+ finds blocks in brace languages (JavaScript, C, Java,
+ etc), CodeMirror.fold.indent, for languages where
+ indentation determines block structure (Python, Haskell),
+ and CodeMirror.fold.xml, for XML-style languages,
+ and CodeMirror.fold.comment, for folding comment
+ blocks.
+
widget: string|Element
+
The widget to show for folded ranges. Can be either a
+ string, in which case it'll become a span with
+ class CodeMirror-foldmarker, or a DOM node.
+
scanUp: boolean
+
When true (default is false), the addon will try to find
+ foldable ranges on the lines above the current one if there
+ isn't an eligible one on the given line.
+
minFoldSize: integer
+
The minimum amount of lines that a fold should span to be
+ accepted. Defaults to 0, which also allows single-line
+ folds.
Provides an option foldGutter, which can be
+ used to create a gutter with markers indicating the blocks that
+ can be folded. Create a gutter using
+ the gutters option,
+ giving it the class CodeMirror-foldgutter or
+ something else if you configure the addon to use a different
+ class, and this addon will show markers next to folded and
+ foldable blocks, and handle clicks in this gutter. Note that
+ CSS styles should be applied to make the gutter, and the fold
+ markers within it, visible. A default set of CSS styles are
+ available in:
+
+ addon/fold/foldgutter.css
+ .
+ The option
+ can be either set to true, or an object containing
+ the following optional option fields:
+
+
gutter: string
+
The CSS class of the gutter. Defaults
+ to "CodeMirror-foldgutter". You will have to
+ style this yourself to give it a width (and possibly a
+ background). See the default gutter style rules above.
+
indicatorOpen: string | Element
+
A CSS class or DOM element to be used as the marker for
+ open, foldable blocks. Defaults
+ to "CodeMirror-foldgutter-open".
+
indicatorFolded: string | Element
+
A CSS class or DOM element to be used as the marker for
+ folded blocks. Defaults to "CodeMirror-foldgutter-folded".
+
rangeFinder: fn(CodeMirror, Pos)
+
The range-finder function to use when determining whether
+ something can be folded. When not
+ given, CodeMirror.fold.auto
+ will be used as default.
+
+ The foldOptions editor option can be set to an
+ object to provide an editor-wide default configuration.
+ Demo here.
Can be used to run a CodeMirror mode over text without
+ actually opening an editor instance.
+ See the demo for an example.
+ There are alternate versions of the file available for
+ running stand-alone
+ (without including all of CodeMirror) and
+ for running under
+ node.js (see bin/source-highlight for an example of using the latter).
Provides a convenient way to syntax-highlight code snippets
+ in a webpage. Depends on
+ the runmode addon (or
+ its standalone variant). Provides
+ a CodeMirror.colorize function that can be called
+ with an array (or other array-ish collection) of DOM nodes that
+ represent the code snippets. By default, it'll get
+ all pre tags. Will read the data-lang
+ attribute of these nodes to figure out their language, and
+ syntax-color their content using the relevant CodeMirror mode
+ (you'll have to load the scripts for the relevant modes
+ yourself). A second argument may be provided to give a default
+ mode, used when no language attribute is found for a node. Used
+ in this manual to highlight example code.
Mode combinator that can be used to extend a mode with an
+ 'overlay' — a secondary mode is run over the stream, along with
+ the base mode, and can color specific pieces of text without
+ interfering with the base mode.
+ Defines CodeMirror.overlayMode, which is used to
+ create such a mode. See this
+ demo for a detailed example.
Mode combinator that can be used to easily 'multiplex'
+ between several modes.
+ Defines CodeMirror.multiplexingMode which, when
+ given as first argument a mode object, and as other arguments
+ any number of {open, close, mode [, delimStyle, innerStyle, parseDelimiters]}
+ objects, will return a mode object that starts parsing using the
+ mode passed as first argument, but will switch to another mode
+ as soon as it encounters a string that occurs in one of
+ the open fields of the passed objects. When in a
+ sub-mode, it will go back to the top mode again when
+ the close string is encountered.
+ Pass "\n" for open or close
+ if you want to switch on a blank line.
+
When delimStyle is specified, it will be the token
+ style returned for the delimiter tokens (as well as
+ [delimStyle]-open on the opening token and
+ [delimStyle]-close on the closing token).
+
When innerStyle is specified, it will be the token
+ style added for each inner mode token.
+
When parseDelimiters is true, the content of
+ the delimiters will also be passed to the inner mode.
+ (And delimStyle is ignored.)
The outer
+ mode will not see the content between the delimiters.
+ See this demo for an
+ example.
Provides a framework for showing autocompletion hints.
+ Defines editor.showHint, which takes an optional
+ options object, and pops up a widget that allows the user to
+ select a completion. Finding hints is done with a hinting
+ functions (the hint option), which is a function
+ that take an editor instance and options object, and return
+ a {list, from, to} object, where list
+ is an array of strings or objects (the completions),
+ and from and to give the start and end
+ of the token that is being completed as {line, ch}
+ objects. An optional selectedHint property (an
+ integer) can be added to the completion object to control the
+ initially selected hint.
+
If no hinting function is given, the addon will
+ use CodeMirror.hint.auto, which
+ calls getHelpers with
+ the "hint" type to find applicable hinting
+ functions, and tries them one by one. If that fails, it looks
+ for a "hintWords" helper to fetch a list of
+ completable words for the mode, and
+ uses CodeMirror.hint.fromList to complete from
+ those.
+
When completions aren't simple strings, they should be
+ objects with the following properties:
+
+
text: string
+
The completion text. This is the only required
+ property.
+
displayText: string
+
The text that should be displayed in the menu.
+
className: string
+
A CSS class name to apply to the completion's line in the
+ menu.
+
render: fn(Element, self, data)
+
A method used to create the DOM structure for showing the
+ completion by appending it to its first argument.
+
hint: fn(CodeMirror, self, data)
+
A method used to actually apply the completion, instead of
+ the default behavior.
+
from: {line, ch}
+
Optional from position that will be used by pick() instead
+ of the global one passed with the full list of completions.
+
to: {line, ch}
+
Optional to position that will be used by pick() instead
+ of the global one passed with the full list of completions.
+
+
+
The plugin understands the following options, which may be
+ either passed directly in the argument to showHint,
+ or provided by setting an hintOptions editor
+ option to an object (the former takes precedence). The options
+ object will also be passed along to the hinting function, which
+ may understand additional options.
+
+
hint: function
+
A hinting function, as specified above. It is possible to
+ set the async property on a hinting function to
+ true, in which case it will be called with
+ arguments (cm, callback, ?options), and the
+ completion interface will only be popped up when the hinting
+ function calls the callback, passing it the object holding the
+ completions.
+ The hinting function can also return a promise, and the completion
+ interface will only be popped when the promise resolves.
+ By default, hinting only works when there is no
+ selection. You can give a hinting function
+ a supportsSelection property with a truthy value
+ to indicate that it supports selections.
+
completeSingle: boolean
+
Determines whether, when only a single completion is
+ available, it is completed without showing the dialog.
+ Defaults to true.
+
alignWithWord: boolean
+
Whether the pop-up should be horizontally aligned with the
+ start of the word (true, default), or with the cursor (false).
+
closeOnUnfocus: boolean
+
When enabled (which is the default), the pop-up will close
+ when the editor is unfocused.
+
customKeys: keymap
+
Allows you to provide a custom key map of keys to be active
+ when the pop-up is active. The handlers will be called with an
+ extra argument, a handle to the completion menu, which
+ has moveFocus(n), setFocus(n), pick(),
+ and close() methods (see the source for details),
+ that can be used to change the focused element, pick the
+ current element or close the menu. Additionally menuSize()
+ can give you access to the size of the current dropdown menu,
+ length give you the number of available completions, and
+ data give you full access to the completion returned by the
+ hinting function.
+
extraKeys: keymap
+
Like customKeys above, but the bindings will
+ be added to the set of default bindings, instead of replacing
+ them.
+
+ The following events will be fired on the completions object
+ during completion:
+
+
"shown" ()
+
Fired when the pop-up is shown.
+
"select" (completion, Element)
+
Fired when a completion is selected. Passed the completion
+ value (string or object) and the DOM node that represents it
+ in the menu.
+
"pick" (completion)
+
Fired when a completion is picked. Passed the completion value
+ (string or object).
+
"close" ()
+
Fired when the completion is finished.
+
+ This addon depends on styles
+ from addon/hint/show-hint.css. Check
+ out the demo for an
+ example.
Defines a simple hinting function for JavaScript
+ (CodeMirror.hint.javascript) and CoffeeScript
+ (CodeMirror.hint.coffeescript) code. This will
+ simply use the JavaScript environment that the editor runs in as
+ a source of information about objects and their properties.
Defines CodeMirror.hint.xml, which produces
+ hints for XML tagnames, attribute names, and attribute values,
+ guided by a schemaInfo option (a property of the
+ second argument passed to the hinting function, or the third
+ argument passed to CodeMirror.showHint). The
+ schema info should be an object mapping tag names to information
+ about these tags, with optionally a "!top" property
+ containing a list of the names of valid top-level tags. The
+ values of the properties should be objects with optional
+ properties children (an array of valid child
+ element names, omit to simply allow all tags to appear)
+ and attrs (an object mapping attribute names
+ to null for free-form attributes, and an array of
+ valid values for restricted
+ attributes). The hint options accept an additional property:
+
+
matchInMiddle: boolean
+
Determines whether typed characters are matched anywhere in
+ completions, not just at the beginning. Defaults to false.
Provides schema info to
+ the xml-hint addon for HTML
+ documents. Defines a schema
+ object CodeMirror.htmlSchema that you can pass to
+ as a schemaInfo option, and
+ a CodeMirror.hint.html hinting function that
+ automatically calls CodeMirror.hint.xml with this
+ schema data. See
+ the demo.
A very simple hinting function
+ (CodeMirror.hint.anyword) that simply looks for
+ words in the nearby code and completes to those. Takes two
+ optional options, word, a regular expression that
+ matches words (sequences of one or more character),
+ and range, which defines how many lines the addon
+ should scan when completing (defaults to 500).
A simple SQL hinter. Defines CodeMirror.hint.sql.
+ Takes two optional options, tables, a object with
+ table names as keys and array of respective column names as values,
+ and defaultTable, a string corresponding to a
+ table name in tables for autocompletion.
Adds a highlightSelectionMatches option that
+ can be enabled to highlight all instances of a currently
+ selected word. Can be set either to true or to an object
+ containing the following options: minChars, for the
+ minimum amount of selected characters that triggers a highlight
+ (default 2), style, for the style to be used to
+ highlight the matches (default "matchhighlight",
+ which will correspond to CSS
+ class cm-matchhighlight), trim, which
+ controls whether whitespace is trimmed from the selection,
+ and showToken which can be set to true
+ or to a regexp matching the characters that make up a word. When
+ enabled, it causes the current word to be highlighted when
+ nothing is selected (defaults to off).
+ Demo here.
Defines an interface component for showing linting warnings,
+ with pluggable warning sources
+ (see html-lint.js,
+ json-lint.js,
+ javascript-lint.js,
+ coffeescript-lint.js,
+ and css-lint.js
+ in the same directory). Defines a lint option that
+ can be set to an annotation source (for
+ example CodeMirror.lint.javascript), to an options
+ object (in which case the getAnnotations field is
+ used as annotation source), or simply to true. When
+ no annotation source is
+ specified, getHelper with
+ type "lint" is used to find an annotation function.
+ An annotation source function should, when given a document
+ string, an options object, and an editor instance, return an
+ array of {message, severity, from, to} objects
+ representing problems. When the function has
+ an async property with a truthy value, it will be
+ called with an additional second argument, which is a callback
+ to pass the array to.
+ The linting function can also return a promise, in that case the linter
+ will only be executed when the promise resolves.
+ By default, the linter will run (debounced) whenever the document is changed.
+ You can pass a lintOnChange: false option to disable that.
+ Depends on addon/lint/lint.css. A demo can be
+ found here.
Causes the selected text to be marked with the CSS class
+ CodeMirror-selectedtext when the styleSelectedText option
+ is enabled. Useful to change the colour of the selection (in addition to the background),
+ like in this demo.
Defines a styleActiveLine option that, when
+ enabled, gives the wrapper of the line that contains the cursor
+ the class CodeMirror-activeline, adds a background
+ with the class CodeMirror-activeline-background,
+ and adds the class CodeMirror-activeline-gutter to
+ the line's gutter space is enabled. The option's value may be a
+ boolean or an object specifying the following options:
+
+
nonEmpty: bool
+
Controls whether single-line selections, or just cursor
+ selections, are styled. Defaults to false (only cursor
+ selections).
Defines a selectionPointer option which you can
+ use to control the mouse cursor appearance when hovering over
+ the selection. It can be set to a string,
+ like "pointer", or to true, in which case
+ the "default" (arrow) cursor will be used. You can
+ see a demo here.
Defines a CodeMirror.requireMode(modename,
+ callback) function that will try to load a given mode and
+ call the callback when it succeeded. You'll have to
+ set CodeMirror.modeURL to a string that mode paths
+ can be constructed from, for
+ example "mode/%N/%N.js"—the %N's will
+ be replaced with the mode name. Also
+ defines CodeMirror.autoLoadMode(instance, mode),
+ which will ensure the given mode is loaded and cause the given
+ editor instance to refresh its mode when the loading
+ succeeded. See the demo.
Provides meta-information about all the modes in the
+ distribution in a single file.
+ Defines CodeMirror.modeInfo, an array of objects
+ with {name, mime, mode} properties,
+ where name is the human-readable
+ name, mime the MIME type, and mode the
+ name of the mode file that defines this MIME. There are optional
+ properties mimes, which holds an array of MIME
+ types for modes with multiple MIMEs associated,
+ and ext, which holds an array of file extensions
+ associated with this mode. Four convenience
+ functions, CodeMirror.findModeByMIME,
+ CodeMirror.findModeByExtension,
+ CodeMirror.findModeByFileName
+ and CodeMirror.findModeByName are provided, which
+ return such an object given a MIME, extension, file name or mode name
+ string. Note that, for historical reasons, this file resides in the
+ top-level mode directory, not
+ under addon. Demo.
Adds a continueComments option, which sets whether the
+ editor will make the next line continue a comment when you press Enter
+ inside a comment block. Can be set to a boolean to enable/disable this
+ functionality. Set to a string, it will continue comments using a custom
+ shortcut. Set to an object, it will use the key property for
+ a custom shortcut and the boolean continueLineComment
+ property to determine whether single-line comments should be continued
+ (defaulting to true).
Adds a placeholder option that can be used to
+ make content appear in the editor when it is empty and not
+ focused. It can hold either a string or a DOM node. Also gives
+ the editor a CodeMirror-empty CSS class whenever it
+ doesn't contain any text.
+ See the demo.
Defines an option fullScreen that, when set
+ to true, will make the editor full-screen (as in,
+ taking up the whole browser window). Depends
+ on fullscreen.css. Demo
+ here.
This addon can be useful when initializing an editor in a
+ hidden DOM node, in cases where it is difficult to
+ call refresh when the editor
+ becomes visible. It defines an option autoRefresh
+ which you can set to true to ensure that, if the editor wasn't
+ visible on initialization, it will be refreshed the first time
+ it becomes visible. This is done by polling every 250
+ milliseconds (you can pass a value like {delay:
+ 500} as the option value to configure this). Note that
+ this addon will only refresh the editor once when it
+ first becomes visible, and won't take care of further restyling
+ and resizing.
Defines two additional scrollbar
+ models, "simple" and "overlay"
+ (see demo) that can
+ be selected with
+ the scrollbarStyle
+ option. Depends
+ on simplescrollbars.css,
+ which can be further overridden to style your own
+ scrollbars.
Provides functionality for showing markers on the scrollbar
+ to call out certain parts of the document. Adds a
+ method annotateScrollbar to editor instances that
+ can be called, with a CSS class name as argument, to create a
+ set of annotations. The method returns an object
+ whose update method can be called with a sorted array
+ of {from: Pos, to: Pos} objects marking the ranges
+ to be highlighted. To detach the annotations, call the
+ object's clear method.
Adds a rulers option, which can be used to show
+ one or more vertical rulers in the editor. The option, if
+ defined, should be given an array of {column [, className,
+ color, lineStyle, width]} objects or numbers (which
+ indicate a column). The ruler will be displayed at the column
+ indicated by the number or the column property.
+ The className property can be used to assign a
+ custom style to a ruler. Demo
+ here.
Defines an addPanel method for CodeMirror
+ instances, which places a DOM node above or below an editor, and
+ shrinks the editor to make room for the node. The method takes
+ as first argument as DOM node, and as second an optional options
+ object. The Panel object returned by this method
+ has a clear method that is used to remove the
+ panel, and a changed method that can be used to
+ notify the addon when the size of the panel's DOM node has
+ changed.
+ The method accepts the following options:
+
+
position: string
+
Controls the position of the newly added panel. The
+ following values are recognized:
+
+
top (default)
+
Adds the panel at the very top.
+
after-top
+
Adds the panel at the bottom of the top panels.
+
bottom
+
Adds the panel at the very bottom.
+
before-bottom
+
Adds the panel at the top of the bottom panels.
+
+
+
before: Panel
+
The new panel will be added before the given panel.
+
after: Panel
+
The new panel will be added after the given panel.
+
replace: Panel
+
The new panel will replace the given panel.
+
stable: bool
+
Whether to scroll the editor to keep the text's vertical
+ position stable, when adding a panel above it. Defaults to false.
+
+ When using the after, before or replace options,
+ if the panel doesn't exists or has been removed,
+ the value of the position option will be used as a fallback.
+
+ A demo of the addon is available here.
+
Addon to perform hard line wrapping/breaking for paragraphs
+ of text. Adds these methods to editor instances:
+
+
wrapParagraph(?pos: {line, ch}, ?options: object)
+
Wraps the paragraph at the given position.
+ If pos is not given, it defaults to the cursor
+ position.
+
wrapRange(from: {line, ch}, to: {line, ch}, ?options: object)
+
Wraps the given range as one big paragraph.
+
wrapParagraphsInRange(from: {line, ch}, to: {line, ch}, ?options: object)
+
Wraps the paragraphs in (and overlapping with) the
+ given range individually.
+
+ The following options are recognized:
+
+
paragraphStart, paragraphEnd: RegExp
+
Blank lines are always considered paragraph boundaries.
+ These options can be used to specify a pattern that causes
+ lines to be considered the start or end of a paragraph.
+
column: number
+
The column to wrap at. Defaults to 80.
+
wrapOn: RegExp
+
A regular expression that matches only those
+ two-character strings that allow wrapping. By default, the
+ addon wraps on whitespace and after dash characters.
+
killTrailingSpace: boolean
+
Whether trailing space caused by wrapping should be
+ preserved, or deleted. Defaults to true.
Implements an interface for merging changes, using either a
+ 2-way or a 3-way view. The CodeMirror.MergeView
+ constructor takes arguments similar to
+ the CodeMirror
+ constructor, first a node to append the interface to, and then
+ an options object. Options are passed through to the editors
+ inside the view. These extra options are recognized:
+
+
origLeft and origRight: string
+
If given these provide original versions of the
+ document, which will be shown to the left and right of the
+ editor in non-editable CodeMirror instances. The merge
+ interface will highlight changes between the editable
+ document and the original(s). To create a 2-way (as opposed
+ to 3-way) merge view, provide only one of them.
+
revertButtons: boolean
+
Determines whether buttons that allow the user to revert
+ changes are shown. Defaults to true.
Can be used to define custom behavior when the user
+ reverts a changed chunk.
+
connect: string
+
Sets the style used to connect changed chunks of code.
+ By default, connectors are drawn. When this is set
+ to "align", the smaller chunk is padded to
+ align with the bigger chunk instead.
+
collapseIdentical: boolean|number
+
When true (default is false), stretches of unchanged
+ text will be collapsed. When a number is given, this
+ indicates the amount of lines to leave visible around such
+ stretches (which defaults to 2).
+
allowEditingOriginals: boolean
+
Determines whether the original editor allows editing.
+ Defaults to false.
+
showDifferences: boolean
+
When true (the default), changed pieces of text are
+ highlighted.
+
chunkClassLocation: string|Array
+
By default the chunk highlights are added
+ using addLineClass
+ with "background". Override this to customize it to be any
+ valid `where` parameter or an Array of valid `where`
+ parameters.
+
+ The addon also defines commands "goNextDiff"
+ and "goPrevDiff" to quickly jump to the next
+ changed chunk. Demo
+ here.
Provides integration with
+ the Tern JavaScript analysis
+ engine, for completion, definition finding, and minor
+ refactoring help. See the demo
+ for a very simple integration. For more involved scenarios, see
+ the comments at the top of
+ the addon and the
+ implementation of the
+ (multi-file) demonstration
+ on the Tern website.
+
+
+
+
+
Writing CodeMirror Modes
+
+
Modes typically consist of a single JavaScript file. This file
+ defines, in the simplest case, a lexer (tokenizer) for your
+ language—a function that takes a character stream as input,
+ advances it past a token, and returns a style for that token. More
+ advanced modes can also handle indentation for the language.
+
+
This section describes the low-level mode interface. Many modes
+ are written directly against this, since it offers a lot of
+ control, but for a quick mode definition, you might want to use
+ the simple mode addon.
+
+
The mode script should
+ call CodeMirror.defineMode to
+ register itself with CodeMirror. This function takes two
+ arguments. The first should be the name of the mode, for which you
+ should use a lowercase string, preferably one that is also the
+ name of the files that define the mode (i.e. "xml" is
+ defined in xml.js). The second argument should be a
+ function that, given a CodeMirror configuration object (the thing
+ passed to the CodeMirror function) and an optional
+ mode configuration object (as in
+ the mode option), returns
+ a mode object.
+
+
Typically, you should use this second argument
+ to defineMode as your module scope function (modes
+ should not leak anything into the global scope!), i.e. write your
+ whole mode inside this function.
+
+
The main responsibility of a mode script is parsing
+ the content of the editor. Depending on the language and the
+ amount of functionality desired, this can be done in really easy
+ or extremely complicated ways. Some parsers can be stateless,
+ meaning that they look at one element (token) of the code
+ at a time, with no memory of what came before. Most, however, will
+ need to remember something. This is done by using a state
+ object, which is an object that is always passed when
+ reading a token, and which can be mutated by the tokenizer.
+
+
Modes that use a state must define
+ a startState method on their mode
+ object. This is a function of no arguments that produces a state
+ object to be used at the start of a document.
+
+
The most important part of a mode object is
+ its token(stream, state) method. All
+ modes must define this method. It should read one token from the
+ stream it is given as an argument, optionally update its state,
+ and return a style string, or null for tokens that do
+ not have to be styled. For your styles, you are encouraged to use
+ the 'standard' names defined in the themes (without
+ the cm- prefix). If that fails, it is also possible
+ to come up with your own and write your own CSS theme file.
+
+
A typical token string would
+ be "variable" or "comment". Multiple
+ styles can be returned (separated by spaces), for
+ example "string error" for a thing that looks like a
+ string but is invalid somehow (say, missing its closing quote).
+ When a style is prefixed by "line-"
+ or "line-background-", the style will be applied to
+ the whole line, analogous to what
+ the addLineClass method
+ does—styling the "text" in the simple case, and
+ the "background" element
+ when "line-background-" is prefixed.
+
+
The stream object that's passed
+ to token encapsulates a line of code (tokens may
+ never span lines) and our current position in that line. It has
+ the following API:
+
+
+
eol() → boolean
+
Returns true only if the stream is at the end of the
+ line.
+
sol() → boolean
+
Returns true only if the stream is at the start of the
+ line.
+
+
peek() → string
+
Returns the next character in the stream without advancing
+ it. Will return a null at the end of the
+ line.
+
next() → string
+
Returns the next character in the stream and advances it.
+ Also returns null when no more characters are
+ available.
match can be a character, a regular expression,
+ or a function that takes a character and returns a boolean. If
+ the next character in the stream 'matches' the given argument,
+ it is consumed and returned. Otherwise, undefined
+ is returned.
Repeatedly calls eat with the given argument,
+ until it fails. Returns true if any characters were eaten.
+
eatSpace() → boolean
+
Shortcut for eatWhile when matching
+ white-space.
+
skipToEnd()
+
Moves the position to the end of the line.
+
skipTo(str: string) → boolean
+
Skips to the start of the next occurrence of the given string, if
+ found on the current line (doesn't advance the stream if the
+ string does not occur on the line). Returns true if the
+ string was found.
Act like a
+ multi-character eat—if consume is true
+ or not given—or a look-ahead that doesn't update the stream
+ position—if it is false. pattern can be either a
+ string or a regular expression starting with ^.
+ When it is a string, caseFold can be set to true to
+ make the match case-insensitive. When successfully matching a
+ regular expression, the returned value will be the array
+ returned by match, in case you need to extract
+ matched groups.
+
+
backUp(n: integer)
+
Backs up the stream n characters. Backing it up
+ further than the start of the current token will cause things to
+ break, so be careful.
+
column() → integer
+
Returns the column (taking into account tabs) at which the
+ current token starts.
+
indentation() → integer
+
Tells you how far the current line has been indented, in
+ spaces. Corrects for tab characters.
+
+
current() → string
+
Get the string between the start of the current token and
+ the current stream position.
+
+
lookAhead(n: number) → ?string
+
Get the line n (>0) lines after the current
+ one, in order to scan ahead across line boundaries. Note that
+ you want to do this carefully, since looking far ahead will make
+ mode state caching much less effective.
+
+
baseToken() → ?{type: ?string, size: number}
+
Modes added
+ through addOverlay
+ (and only such modes) can use this method to inspect
+ the current token produced by the underlying mode.
+
+
+
By default, blank lines are simply skipped when
+ tokenizing a document. For languages that have significant blank
+ lines, you can define
+ a blankLine(state) method on your
+ mode that will get called whenever a blank line is passed over, so
+ that it can update the parser state.
+
+
Because state object are mutated, and CodeMirror
+ needs to keep valid versions of a state around so that it can
+ restart a parse at any line, copies must be made of state objects.
+ The default algorithm used is that a new state object is created,
+ which gets all the properties of the old object. Any properties
+ which hold arrays get a copy of these arrays (since arrays tend to
+ be used as mutable stacks). When this is not correct, for example
+ because a mode mutates non-array properties of its state object, a
+ mode object should define
+ a copyState method, which is given a
+ state and should return a safe copy of that state.
+
+
If you want your mode to provide smart indentation
+ (through the indentLine
+ method and the indentAuto
+ and newlineAndIndent commands, to which keys can be
+ bound), you must define
+ an indent(state, textAfter) method
+ on your mode object.
+
+
The indentation method should inspect the given state object,
+ and optionally the textAfter string, which contains
+ the text on the line that is being indented, and return an
+ integer, the amount of spaces to indent. It should usually take
+ the indentUnit
+ option into account. An indentation method may
+ return CodeMirror.Pass to indicate that it
+ could not come up with a precise indentation.
+
+
To work well with
+ the commenting addon, a mode may
+ define lineComment (string that
+ starts a line
+ comment), blockCommentStart, blockCommentEnd
+ (strings that start and end block comments),
+ and blockCommentLead (a string to put at the start of
+ continued lines in a block comment). All of these are
+ optional.
+
+
Finally, a mode may define either
+ an electricChars or an electricInput
+ property, which are used to automatically reindent the line when
+ certain patterns are typed and
+ the electricChars
+ option is enabled. electricChars may be a string, and
+ will trigger a reindent whenever one of the characters in that
+ string are typed. Often, it is more appropriate to
+ use electricInput, which should hold a regular
+ expression, and will trigger indentation when the part of the
+ line before the cursor matches the expression. It should
+ usually end with a $ character, so that it only
+ matches when the indentation-changing pattern was just typed, not when something was
+ typed after the pattern.
+
+
So, to summarize, a mode must provide
+ a token method, and it may
+ provide startState, copyState,
+ and indent methods. For an example of a trivial mode,
+ see the diff mode, for a more
+ involved example, see the C-like
+ mode.
+
+
Sometimes, it is useful for modes to nest—to have one
+ mode delegate work to another mode. An example of this kind of
+ mode is the mixed-mode HTML
+ mode. To implement such nesting, it is usually necessary to
+ create mode objects and copy states yourself. To create a mode
+ object, there are CodeMirror.getMode(options,
+ parserConfig), where the first argument is a configuration
+ object as passed to the mode constructor function, and the second
+ argument is a mode specification as in
+ the mode option. To copy a
+ state object, call CodeMirror.copyState(mode, state),
+ where mode is the mode that created the given
+ state.
+
+
In a nested mode, it is recommended to add an
+ extra method, innerMode which, given
+ a state object, returns a {state, mode} object with
+ the inner mode and its state for the current position. These are
+ used by utility scripts such as the tag
+ closer to get context information. Use
+ the CodeMirror.innerMode helper function to, starting
+ from a mode and a state, recursively walk down to the innermost
+ mode and state.
+
+
To make indentation work properly in a nested parser, it is
+ advisable to give the startState method of modes that
+ are intended to be nested an optional argument that provides the
+ base indentation for the block of code. The JavaScript and CSS
+ parser do this, for example, to allow JavaScript and CSS code
+ inside the mixed-mode HTML mode to be properly indented.
+
+
It is possible, and encouraged, to associate
+ your mode, or a certain configuration of your mode, with
+ a MIME type. For
+ example, the JavaScript mode associates itself
+ with text/javascript, and its JSON variant
+ with application/json. To do this,
+ call CodeMirror.defineMIME(mime,
+ modeSpec), where modeSpec can be a string or
+ object specifying a mode, as in
+ the mode option.
+
+
If a mode specification wants to add some properties to the
+ resulting mode object, typically for use
+ with getHelpers, it may
+ contain a modeProps property, which holds an object.
+ This object's properties will be copied to the actual mode
+ object.
+
+
Sometimes, it is useful to add or override mode
+ object properties from external code.
+ The CodeMirror.extendMode function
+ can be used to add properties to mode objects produced for a
+ specific mode. Its first argument is the name of the mode, its
+ second an object that specifies the properties that should be
+ added. This is mostly useful to add utilities that can later be
+ looked up through getMode.
+
+
+
+
VIM Mode API
+
+
CodeMirror has a robust VIM mode that attempts to faithfully
+ emulate VIM's most useful features. It can be enabled by
+ including keymap/vim.js
+ and setting the keyMap option to
+ "vim".
+
+
Configuration
+
+
VIM mode accepts configuration options for customizing
+ behavior at run time. These methods can be called at any time
+ and will affect all existing CodeMirror instances unless
+ specified otherwise. The methods are exposed on the
+ CodeMirror.Vim object.
Sets the value of a VIM option. name should
+ be the name of an option. If cfg.scope is not set
+ and cm is provided, then sets the global and
+ instance values of the option. Otherwise, sets either the
+ global or instance value of the option depending on whether
+ cfg.scope is global or
+ local.
Gets the current value of a VIM option. If
+ cfg.scope is not set and cm is
+ provided, then gets the instance value of the option, falling
+ back to the global value if not set. If cfg.scope is provided, then gets the global or
+ local value without checking the other.
+
+
map(lhs: string, rhs: string, ?context: string)
+
Maps a key sequence to another key sequence. Implements
+ VIM's :map command. To map ; to : in VIM would be
+ :map ; :. That would translate to
+ CodeMirror.Vim.map(';', ':');.
+ The context can be normal,
+ visual, or insert, which correspond
+ to :nmap, :vmap, and
+ :imap
+ respectively.
Maps a key sequence to a motion,
+ operator, or action type command.
+ The args object is passed through to the command when it is
+ invoked by the provided key sequence.
+ extras.context can be normal,
+ visual, or insert, to map the key
+ sequence only in the corresponding mode.
+ extras.isEdit is applicable only to actions,
+ determining whether it is recorded for replay for the
+ . single-repeat command.
+
+
+
Extending VIM
+
+
CodeMirror's VIM mode implements a large subset of VIM's core
+ editing functionality. But since there's always more to be
+ desired, there is a set of APIs for extending VIM's
+ functionality. As with the configuration API, the methods are
+ exposed on CodeMirror.Vim and may
+ be called at any time.
Defines a VIM style option and makes it available to the
+ :set command. Type can be boolean or
+ string, used for validation and by
+ :set to determine which syntax to accept. If a
+ callback is passed in, VIM does not store the value of the
+ option itself, but instead uses the callback as a setter/getter. If the
+ first argument to the callback is undefined, then the
+ callback should return the value of the option. Otherwise, it should set
+ instead. Since VIM options have global and instance values, whether a
+ CodeMirror instance is passed in denotes whether the global
+ or local value should be used. Consequently, it's possible for the
+ callback to be called twice for a single setOption or
+ getOption call. Note that right now, VIM does not support
+ defining buffer-local options that do not have global values. If an
+ option should not have a global value, either always ignore the
+ cm parameter in the callback, or always pass in a
+ cfg.scope to setOption and
+ getOption.
Defines a motion command for VIM. The motion should return
+ the desired result position of the cursor. head
+ is the current position of the cursor. It can differ from
+ cm.getCursor('head') if VIM is in visual mode.
+ motionArgs is the object passed into
+ mapCommand().
Defines an operator command, similar to
+ defineMotion. ranges is the range
+ of text the operator should operate on. If the cursor should
+ be set to a certain position after the operation finishes, it
+ can return a cursor object.
Defines an action command, similar to
+ defineMotion. Action commands
+ can have arbitrary behavior, making them more flexible than
+ motions and operators, at the loss of orthogonality.
Defines an Ex command, and maps it to :name.
+ If a prefix is provided, it, and any prefixed substring of the
+ name beginning with the prefix can
+ be used to invoke the command. If the prefix is
+ falsy, then name is used as the prefix.
+ params.argString contains the part of the prompted
+ string after the command name. params.args is
+ params.argString split by whitespace. If the
+ command was prefixed with a
+ line range,
+ params.line and params.lineEnd will
+ be set.
+
Increase contrast on default bracket-matching colors.
+
javascript mode: Recognize TypeScript type parameters for calls, type guards, and type parameter defaults. Improve handling of enum and module keywords.
+
comment addon: Fix bug when uncommenting a comment that spans all but the last selected line.
Mouse button clicks can now be bound in keymaps by using names like "LeftClick" or "Ctrl-Alt-MiddleTripleClick". When bound to a function, that function will be passed the position of the click as second argument.
+
The behavior of mouse selection and dragging can now be customized with the configureMouse option.
+
Modes can now look ahead across line boundaries with the StringStream.lookahead method.
+
Introduces a "type" token type, makes modes that recognize types output it, and add styling for it to the themes.
+
New pasteLinesPerSelection option to control the behavior of pasting multiple lines into multiple selections.
+
searchcursor addon: Support multi-line regular expression matches, and normalize strings when matching.
Positions now support a sticky property which determines whether they should be associated with the character before (value "before") or after (value "after") them.
+
vim bindings: Make it possible to remove built-in bindings through the API.
+
comment addon: Support a per-mode useInnerComments option to optionally suppress descending to the inner modes to get comment strings.
+
A cursor directly before a line-wrapping break is now drawn before or after the line break depending on which direction you arrived from.
+
Visual cursor motion in line-wrapped right-to-left text should be much more correct.
python mode: Support underscores in number literals.
+
sass mode: Uses the full list of CSS properties and keywords from the CSS mode, rather than defining its own incomplete subset. Now depends on the css mode.
+
css mode: Expose lineComment property for LESS and SCSS dialects. Recognize vendor prefixes on pseudo-elements.
Make newlineAndIndent command work with multiple cursors on the same line.
+
Make sure keypress events for backspace are ignored.
+
Tokens styled with overlays no longer get a nonsense cm-cm-overlay class.
+
Line endings for pasted content are now normalized to the editor's preferred ending.
+
javascript mode: Improve support for class expressions. Support TypeScript optional class properties, the abstract keyword, and return type declarations for arrow functions.
+
css mode: Fix highlighting of mixed-case keywords.
The core is now maintained as a number of small files, using ES6 syntax and modules, under the src/ directory. A git checkout no longer contains a working codemirror.js until you npm run build (but when installing from NPM, it is included).
Fix issue where trying to scroll to a horizontal position outside of the document's width could cause the gutter to be positioned incorrectly.
+
Use absolute, rather than fixed positioning in the context-menu intercept hack, to work around a problem when the editor is inside a transformed parent container.
+
Solve a problem where the horizontal scrollbar could hide text in Firefox.
+
Fix a bug that caused phantom scroll space under the text in some situations.
Incompatible:
+ The lint addon now passes the
+ editor's value as first argument to asynchronous lint functions,
+ for consistency. The editor is still passed, as fourth
+ argument.
+
Improved handling of unicode identifiers in modes for
+ languages that support them.
+
More mode
+ improvements: CoffeeScript
+ (indentation), Verilog
+ (indentation), Scala
+ (indentation, triple-quoted strings),
+ and PHP (interpolated
+ variables in heredoc strings).
Slightly incompatible:
+ The "cursorActivity"
+ event now fires after all other events for the operation (and only
+ for handlers that were actually registered at the time the
+ activity happened).
Merges the improvements from 4.1 that could
+ easily be applied to the 3.x code. Also improves the way the editor
+ size is updated when line widgets change.
CodeMirror 2 is a complete rewrite that's
+ faster, smaller, simpler to use, and less dependent on browser
+ quirks. See this
+ and this
+ for more information.
More consistent page-up/page-down behaviour
+ across browsers. Fix some issues with hidden editors looping forever
+ when line-numbers were enabled. Make PHP parser parse
+ "\\" correctly. Have jumpToLine work on
+ line handles, and add cursorLine function to fetch the
+ line handle where the cursor currently is. Add new
+ setStylesheet function to switch style-sheets in a
+ running editor.
Adds removeLine method to API.
+ Introduces the PLSQL parser.
+ Marks XML errors by adding (rather than replacing) a CSS class, so
+ that they can be disabled by modifying their style. Fixes several
+ selection bugs, and a number of small glitches.
Add support for having both line-wrapping and
+ line-numbers turned on, make paren-highlighting style customisable
+ (markParen and unmarkParen config
+ options), work around a selection bug that Opera
+ reintroduced in version 10.
Solves some issues introduced by the
+ paste-handling changes from the previous release. Adds
+ setSpellcheck, setTextWrapping,
+ setIndentUnit, setUndoDepth,
+ setTabMode, and setLineNumbers to
+ customise a running editor. Introduces an SQL parser. Fixes a few small
+ problems in the Python
+ parser. And, as usual, add workarounds for various newly discovered
+ browser incompatibilities.
Overhaul of paste-handling (less fragile), fixes for several
+ serious IE8 issues (cursor jumping, end-of-document bugs) and a number
+ of small problems.
Introduces Python
+ and Lua parsers. Add
+ setParser (on-the-fly mode changing) and
+ clearHistory methods. Make parsing passes time-based
+ instead of lines-based (see the passTime option).
So you found a problem in CodeMirror. By all means, report it! Bug
+reports from users are the main drive behind improvements to
+CodeMirror. But first, please read over these points:
+
+
+
CodeMirror is maintained by volunteers. They don't owe you
+ anything, so be polite. Reports with an indignant or belligerent
+ tone tend to be moved to the bottom of the pile.
+
+
Include information about the browser in which the
+ problem occurred. Even if you tested several browsers, and
+ the problem occurred in all of them, mention this fact in the bug
+ report. Also include browser version numbers and the operating
+ system that you're on.
+
+
Mention which release of CodeMirror you're using. Preferably,
+ try also with the current development snapshot, to ensure the
+ problem has not already been fixed.
+
+
Mention very precisely what went wrong. "X is broken" is not a
+ good bug report. What did you expect to happen? What happened
+ instead? Describe the exact steps a maintainer has to take to reproduce
+ the error. We can not fix something that we can not observe.
+
+
If the problem can not be reproduced in any of the demos
+ included in the CodeMirror distribution, please provide an HTML
+ document that demonstrates the problem. The best way to do this is
+ to go to jsbin.com, enter
+ it there, press save, and include the resulting link in your bug
+ report.
There are a few things in the 2.2 release that require some care
+when upgrading.
+
+
No more default.css
+
+
The default theme is now included
+in codemirror.css, so
+you do not have to included it separately anymore. (It was tiny, so
+even if you're not using it, the extra data overhead is negligible.)
+
+
Different key customization
+
+
CodeMirror has moved to a system
+where keymaps are used to
+bind behavior to keys. This means custom
+bindings are now possible.
+
+
Three options that influenced key
+behavior, tabMode, enterMode,
+and smartHome, are no longer supported. Instead, you can
+provide custom bindings to influence the way these keys act. This is
+done through the
+new extraKeys
+option, which can hold an object mapping key names to functionality. A
+simple example would be:
Keys can be mapped either to functions, which will be given the
+editor instance as argument, or to strings, which are mapped through
+functions through the CodeMirror.commands table, which
+contains all the built-in editing commands, and can be inspected and
+extended by external code.
+
+
By default, the Home key is bound to
+the "goLineStartSmart" command, which moves the cursor to
+the first non-whitespace character on the line. You can set do this to
+make it always go to the very start instead:
+
+
extraKeys: {"Home": "goLineStart"}
+
+
Similarly, Enter is bound
+to "newlineAndIndent" by default. You can bind it to
+something else to get different behavior. To disable special handling
+completely and only get a newline character inserted, you can bind it
+to false:
+
+
extraKeys: {"Enter": false}
+
+
The same works for Tab. If you don't want CodeMirror
+to handle it, bind it to false. The default behaviour is
+to indent the current line more ("indentMore" command),
+and indent it less when shift is held ("indentLess").
+There are also "indentAuto" (smart indent)
+and "insertTab" commands provided for alternate
+behaviors. Or you can write your own handler function to do something
+different altogether.
+
+
Tabs
+
+
Handling of tabs changed completely. The display width of tabs can
+now be set with the tabSize option, and tabs can
+be styled by setting CSS rules
+for the cm-tab class.
+
+
The default width for tabs is now 4, as opposed to the 8 that is
+hard-wired into browsers. If you are relying on 8-space tabs, make
+sure you explicitly set tabSize: 8 in your options.
Version 3 does not depart too much from 2.x API, and sites that use
+CodeMirror in a very simple way might be able to upgrade without
+trouble. But it does introduce a number of incompatibilities. Please
+at least skim this text before upgrading.
+
+
Note that version 3 drops full support for Internet
+Explorer 7. The editor will mostly work on that browser, but
+it'll be significantly glitchy.
+
+
+
DOM structure
+
+
This one is the most likely to cause problems. The internal
+structure of the editor has changed quite a lot, mostly to implement a
+new scrolling model.
+
+
Editor height is now set on the outer wrapper element (CSS
+class CodeMirror), not on the scroller element
+(CodeMirror-scroll).
+
+
Other nodes were moved, dropped, and added. If you have any code
+that makes assumptions about the internal DOM structure of the editor,
+you'll have to re-test it and probably update it to work with v3.
+
+
See the styling section of the
+manual for more information.
+
+
+
Gutter model
+
+
In CodeMirror 2.x, there was a single gutter, and line markers
+created with setMarker would have to somehow coexist with
+the line numbers (if present). Version 3 allows you to specify an
+array of gutters, by class
+name,
+use setGutterMarker
+to add or remove markers in individual gutters, and clear whole
+gutters
+with clearGutter.
+Gutter markers are now specified as DOM nodes, rather than HTML
+snippets.
+
+
The gutters no longer horizontally scrolls along with the content.
+The fixedGutter option was removed (since it is now the
+only behavior).
+
+
+<style>
+ /* Define a gutter style */
+ .note-gutter { width: 3em; background: cyan; }
+</style>
+<script>
+ // Create an instance with two gutters -- line numbers and notes
+ var cm = new CodeMirror(document.body, {
+ gutters: ["note-gutter", "CodeMirror-linenumbers"],
+ lineNumbers: true
+ });
+ // Add a note to line 0
+ cm.setGutterMarker(0, "note-gutter", document.createTextNode("hi"));
+</script>
+
+
+
+
Event handling
+
+
Most of the onXYZ options have been removed. The same
+effect is now obtained by calling
+the on method with a string
+identifying the event type. Multiple handlers can now be registered
+(and individually unregistered) for an event, and objects such as line
+handlers now also expose events. See the
+full list here.
+
+
(The onKeyEvent and onDragEvent options,
+which act more as hooks than as event handlers, are still there in
+their old form.)
The markText method
+(which has gained some interesting new features, such as creating
+atomic and read-only spans, or replacing spans with widgets) no longer
+takes the CSS class name as a separate argument, but makes it an
+optional field in the options object instead.
+
+
+// Style first ten lines, and forbid the cursor from entering them
+cm.markText({line: 0, ch: 0}, {line: 10, ch: 0}, {
+ className: "magic-text",
+ inclusiveLeft: true,
+ atomic: true
+});
+
+
+
+
Line folding
+
+
The interface for hiding lines has been
+removed. markText can
+now be used to do the same in a more flexible and powerful way.
+
+
The folding script has been
+updated to use the new interface, and should now be more robust.
+
+
+// Fold a range, replacing it with the text "??"
+var range = cm.markText({line: 4, ch: 2}, {line: 8, ch: 1}, {
+ replacedWith: document.createTextNode("??"),
+ // Auto-unfold when cursor moves into the range
+ clearOnEnter: true
+});
+// Get notified when auto-unfolding
+CodeMirror.on(range, "clear", function() {
+ console.log("boom");
+});
+
+
+
+
Line CSS classes
+
+
The setLineClass method has been replaced
+by addLineClass
+and removeLineClass,
+which allow more modular control over the classes attached to a line.
All methods that take or return objects that represent screen
+positions now use {left, top, bottom, right} properties
+(not always all of them) instead of the {x, y, yBot} used
+by some methods in v2.x.
The matchBrackets
+option is no longer defined in the core editor.
+Load addon/edit/matchbrackets.js to enable it.
+
+
+
Mode management
+
+
The CodeMirror.listModes
+and CodeMirror.listMIMEs functions, used for listing
+defined modes, are gone. You are now encouraged to simply
+inspect CodeMirror.modes (mapping mode names to mode
+constructors) and CodeMirror.mimeModes (mapping MIME
+strings to mode specs).
+
+
+
New features
+
+
Some more reasons to upgrade to version 3.
+
+
+
Bi-directional text support. CodeMirror will now mostly do the
+ right thing when editing Arabic or Hebrew text.
+
Arbitrary line heights. Using fonts with different heights
+ inside the editor (whether off by one pixel or fifty) is now
+ supported and handled gracefully.
CodeMirror 4's interface is very close version 3, but it
+does fix a few awkward details in a backwards-incompatible ways. At
+least skim the text below before upgrading.
+
+
Multiple selections
+
+
The main new feature in version 4 is multiple selections. The
+single-selection variants of methods are still there, but now
+typically act only on the primary selection (usually the last
+one added).
+
+
The exception to this
+is getSelection,
+which will now return the content of all selections
+(separated by newlines, or whatever lineSep parameter you passed
+it).
+
+
+
+
The beforeSelectionChange event
+
+
This event still exists, but the object it is passed has
+a completely new
+interface, because such changes now concern multiple
+selections.
+
+
+
+
replaceSelection's collapsing behavior
+
+
By
+default, replaceSelection
+would leave the newly inserted text selected. This is only rarely what
+you want, and also (slightly) more expensive in the new model, so the
+default was changed to "end", meaning the old behavior
+must be explicitly specified by passing a second argument
+of "around".
+
+
+
+
change event data
+
+
Rather than forcing client code to follow next
+pointers from one change object to the next, the library will now
+simply fire
+multiple "change"
+events. Existing code will probably continue to work unmodified.
+
+
+
+
showIfHidden option to line widgets
+
+
This option, which conceptually caused line widgets to be visible
+even if their line was hidden, was never really well-defined, and was
+buggy from the start. It would be a rather expensive feature, both in
+code complexity and run-time performance, to implement properly. It
+has been dropped entirely in 4.0.
+
+
+
+
Module loaders
+
+
All modules in the CodeMirror distribution are now wrapped in a
+shim function to make them compatible with both AMD
+(requirejs) and CommonJS (as used
+by node
+and browserify) module loaders.
+When neither of these is present, they fall back to simply using the
+global CodeMirror variable.
+
+
If you have a module loader present in your environment, CodeMirror
+will attempt to use it, and you might need to change the way you load
+CodeMirror modules.
+
+
+
+
Mutating shared data structures
+
+
Data structures produced by the library should not be mutated
+unless explicitly allowed, in general. This is slightly more strict in
+4.0 than it was in earlier versions, which copied the position objects
+returned by getCursor
+for nebulous, historic reasons. In 4.0, mutating these
+objects will corrupt your editor's selection.
+
+
+
+
Deprecated interfaces dropped
+
+
A few properties and methods that have been deprecated for a while
+are now gone. Most notably, the onKeyEvent
+and onDragEvent options (use the
+corresponding events instead).
+
+
Two silly methods, which were mostly there to stay close to the 0.x
+API, setLine and removeLine are now gone.
+Use the more
+flexible replaceRange
+method instead.
+
+
The long names for folding and completing functions
+(CodeMirror.braceRangeFinder, CodeMirror.javascriptHint,
+etc) are also gone
+(use CodeMirror.fold.brace, CodeMirror.hint.javascript).
+
+
The className property in the return value
+of getTokenAt, which
+has been superseded by the type property, is also no
+longer present.
CodeMirror is a versatile text editor
+ implemented in JavaScript for the browser. It is specialized for
+ editing code, and comes with a number of language modes and addons
+ that implement more advanced editing functionality.
+
+
A rich programming API and a
+ CSS theming system are
+ available for customizing CodeMirror to fit your application, and
+ extending it with new functionality.
Development and bug tracking happens
+ on github
+ (alternate git
+ repository).
+ Please read these
+ pointers before submitting a bug. Use pull requests to submit
+ patches. All contributions must be released under the same MIT
+ license that CodeMirror uses.
+
+
Discussion around the project is done on
+ a discussion forum.
+ Announcements related to the project, such as new versions, are
+ posted in the
+ forum's "announce"
+ category. If needed, you can
+ contact the maintainer
+ directly. We aim to be an inclusive, welcoming community. To make
+ that explicit, we have
+ a code of
+ conduct that applies to communication around the project.
+
+
A list of CodeMirror-related software that is not part of the
+ main distribution is maintained
+ on our
+ wiki. Feel free to add your project.
+
+
+
+
Browser support
+
The desktop versions of the following browsers,
+ in standards mode (HTML5 <!doctype html>
+ recommended) are supported:
+
+
Firefox
version 4 and up
+
Chrome
any version
+
Safari
version 5.2 and up
+
Internet Explorer/Edge
version 8 and up
+
Opera
version 9 and up
+
+
Support for modern mobile browsers is experimental. Recent
+ versions of the iOS browser and Chrome on Android should work
+ pretty well.
+
+
+
diff --git a/scripts/codemirror/keymap/emacs.js b/scripts/codemirror/keymap/emacs.js
index 2a57e2f..fe62d44 100644
--- a/scripts/codemirror/keymap/emacs.js
+++ b/scripts/codemirror/keymap/emacs.js
@@ -1,29 +1,418 @@
-// TODO number prefixes
-(function() {
- // Really primitive kill-ring implementation.
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ var Pos = CodeMirror.Pos;
+ function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
+
+ // Kill 'ring'
+
var killRing = [];
function addToRing(str) {
killRing.push(str);
if (killRing.length > 50) killRing.shift();
}
- function getFromRing() { return killRing[killRing.length - 1] || ""; }
+ function growRingTop(str) {
+ if (!killRing.length) return addToRing(str);
+ killRing[killRing.length - 1] += str;
+ }
+ function getFromRing(n) { return killRing[killRing.length - (n ? Math.min(n, 1) : 1)] || ""; }
function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); }
- CodeMirror.keyMap.emacs = {
- "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");},
- "Ctrl-W": function(cm) {addToRing(cm.getSelection()); cm.replaceSelection("");},
- "Ctrl-Alt-W": function(cm) {addToRing(cm.getSelection()); cm.replaceSelection("");},
- "Alt-W": function(cm) {addToRing(cm.getSelection());},
- "Ctrl-Y": function(cm) {cm.replaceSelection(getFromRing());},
- "Alt-Y": function(cm) {cm.replaceSelection(popFromRing());},
- "Ctrl-/": "undo", "Shift-Ctrl--": "undo", "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
- "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace",
- "Ctrl-Z": "undo", "Cmd-Z": "undo", "Alt-/": "autocomplete",
- fallthrough: ["basic", "emacsy"]
- };
+ var lastKill = null;
- CodeMirror.keyMap["emacs-Ctrl-X"] = {
- "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": "undo", "K": "close",
- auto: "emacs", nofallthrough: true
- };
-})();
+ function kill(cm, from, to, ring, text) {
+ if (text == null) text = cm.getRange(from, to);
+
+ if (ring == "grow" && lastKill && lastKill.cm == cm && posEq(from, lastKill.pos) && cm.isClean(lastKill.gen))
+ growRingTop(text);
+ else if (ring !== false)
+ addToRing(text);
+ cm.replaceRange("", from, to, "+delete");
+
+ if (ring == "grow") lastKill = {cm: cm, pos: from, gen: cm.changeGeneration()};
+ else lastKill = null;
+ }
+
+ // Boundaries of various units
+
+ function byChar(cm, pos, dir) {
+ return cm.findPosH(pos, dir, "char", true);
+ }
+
+ function byWord(cm, pos, dir) {
+ return cm.findPosH(pos, dir, "word", true);
+ }
+
+ function byLine(cm, pos, dir) {
+ return cm.findPosV(pos, dir, "line", cm.doc.sel.goalColumn);
+ }
+
+ function byPage(cm, pos, dir) {
+ return cm.findPosV(pos, dir, "page", cm.doc.sel.goalColumn);
+ }
+
+ function byParagraph(cm, pos, dir) {
+ var no = pos.line, line = cm.getLine(no);
+ var sawText = /\S/.test(dir < 0 ? line.slice(0, pos.ch) : line.slice(pos.ch));
+ var fst = cm.firstLine(), lst = cm.lastLine();
+ for (;;) {
+ no += dir;
+ if (no < fst || no > lst)
+ return cm.clipPos(Pos(no - dir, dir < 0 ? 0 : null));
+ line = cm.getLine(no);
+ var hasText = /\S/.test(line);
+ if (hasText) sawText = true;
+ else if (sawText) return Pos(no, 0);
+ }
+ }
+
+ function bySentence(cm, pos, dir) {
+ var line = pos.line, ch = pos.ch;
+ var text = cm.getLine(pos.line), sawWord = false;
+ for (;;) {
+ var next = text.charAt(ch + (dir < 0 ? -1 : 0));
+ if (!next) { // End/beginning of line reached
+ if (line == (dir < 0 ? cm.firstLine() : cm.lastLine())) return Pos(line, ch);
+ text = cm.getLine(line + dir);
+ if (!/\S/.test(text)) return Pos(line, ch);
+ line += dir;
+ ch = dir < 0 ? text.length : 0;
+ continue;
+ }
+ if (sawWord && /[!?.]/.test(next)) return Pos(line, ch + (dir > 0 ? 1 : 0));
+ if (!sawWord) sawWord = /\w/.test(next);
+ ch += dir;
+ }
+ }
+
+ function byExpr(cm, pos, dir) {
+ var wrap;
+ if (cm.findMatchingBracket && (wrap = cm.findMatchingBracket(pos, {strict: true}))
+ && wrap.match && (wrap.forward ? 1 : -1) == dir)
+ return dir > 0 ? Pos(wrap.to.line, wrap.to.ch + 1) : wrap.to;
+
+ for (var first = true;; first = false) {
+ var token = cm.getTokenAt(pos);
+ var after = Pos(pos.line, dir < 0 ? token.start : token.end);
+ if (first && dir > 0 && token.end == pos.ch || !/\w/.test(token.string)) {
+ var newPos = cm.findPosH(after, dir, "char");
+ if (posEq(after, newPos)) return pos;
+ else pos = newPos;
+ } else {
+ return after;
+ }
+ }
+ }
+
+ // Prefixes (only crudely supported)
+
+ function getPrefix(cm, precise) {
+ var digits = cm.state.emacsPrefix;
+ if (!digits) return precise ? null : 1;
+ clearPrefix(cm);
+ return digits == "-" ? -1 : Number(digits);
+ }
+
+ function repeated(cmd) {
+ var f = typeof cmd == "string" ? function(cm) { cm.execCommand(cmd); } : cmd;
+ return function(cm) {
+ var prefix = getPrefix(cm);
+ f(cm);
+ for (var i = 1; i < prefix; ++i) f(cm);
+ };
+ }
+
+ function findEnd(cm, pos, by, dir) {
+ var prefix = getPrefix(cm);
+ if (prefix < 0) { dir = -dir; prefix = -prefix; }
+ for (var i = 0; i < prefix; ++i) {
+ var newPos = by(cm, pos, dir);
+ if (posEq(newPos, pos)) break;
+ pos = newPos;
+ }
+ return pos;
+ }
+
+ function move(by, dir) {
+ var f = function(cm) {
+ cm.extendSelection(findEnd(cm, cm.getCursor(), by, dir));
+ };
+ f.motion = true;
+ return f;
+ }
+
+ function killTo(cm, by, dir, ring) {
+ var selections = cm.listSelections(), cursor;
+ var i = selections.length;
+ while (i--) {
+ cursor = selections[i].head;
+ kill(cm, cursor, findEnd(cm, cursor, by, dir), ring);
+ }
+ }
+
+ function killRegion(cm, ring) {
+ if (cm.somethingSelected()) {
+ var selections = cm.listSelections(), selection;
+ var i = selections.length;
+ while (i--) {
+ selection = selections[i];
+ kill(cm, selection.anchor, selection.head, ring);
+ }
+ return true;
+ }
+ }
+
+ function addPrefix(cm, digit) {
+ if (cm.state.emacsPrefix) {
+ if (digit != "-") cm.state.emacsPrefix += digit;
+ return;
+ }
+ // Not active yet
+ cm.state.emacsPrefix = digit;
+ cm.on("keyHandled", maybeClearPrefix);
+ cm.on("inputRead", maybeDuplicateInput);
+ }
+
+ var prefixPreservingKeys = {"Alt-G": true, "Ctrl-X": true, "Ctrl-Q": true, "Ctrl-U": true};
+
+ function maybeClearPrefix(cm, arg) {
+ if (!cm.state.emacsPrefixMap && !prefixPreservingKeys.hasOwnProperty(arg))
+ clearPrefix(cm);
+ }
+
+ function clearPrefix(cm) {
+ cm.state.emacsPrefix = null;
+ cm.off("keyHandled", maybeClearPrefix);
+ cm.off("inputRead", maybeDuplicateInput);
+ }
+
+ function maybeDuplicateInput(cm, event) {
+ var dup = getPrefix(cm);
+ if (dup > 1 && event.origin == "+input") {
+ var one = event.text.join("\n"), txt = "";
+ for (var i = 1; i < dup; ++i) txt += one;
+ cm.replaceSelection(txt);
+ }
+ }
+
+ function addPrefixMap(cm) {
+ cm.state.emacsPrefixMap = true;
+ cm.addKeyMap(prefixMap);
+ cm.on("keyHandled", maybeRemovePrefixMap);
+ cm.on("inputRead", maybeRemovePrefixMap);
+ }
+
+ function maybeRemovePrefixMap(cm, arg) {
+ if (typeof arg == "string" && (/^\d$/.test(arg) || arg == "Ctrl-U")) return;
+ cm.removeKeyMap(prefixMap);
+ cm.state.emacsPrefixMap = false;
+ cm.off("keyHandled", maybeRemovePrefixMap);
+ cm.off("inputRead", maybeRemovePrefixMap);
+ }
+
+ // Utilities
+
+ function setMark(cm) {
+ cm.setCursor(cm.getCursor());
+ cm.setExtending(!cm.getExtending());
+ cm.on("change", function() { cm.setExtending(false); });
+ }
+
+ function clearMark(cm) {
+ cm.setExtending(false);
+ cm.setCursor(cm.getCursor());
+ }
+
+ function getInput(cm, msg, f) {
+ if (cm.openDialog)
+ cm.openDialog(msg + ": ", f, {bottom: true});
+ else
+ f(prompt(msg, ""));
+ }
+
+ function operateOnWord(cm, op) {
+ var start = cm.getCursor(), end = cm.findPosH(start, 1, "word");
+ cm.replaceRange(op(cm.getRange(start, end)), start, end);
+ cm.setCursor(end);
+ }
+
+ function toEnclosingExpr(cm) {
+ var pos = cm.getCursor(), line = pos.line, ch = pos.ch;
+ var stack = [];
+ while (line >= cm.firstLine()) {
+ var text = cm.getLine(line);
+ for (var i = ch == null ? text.length : ch; i > 0;) {
+ var ch = text.charAt(--i);
+ if (ch == ")")
+ stack.push("(");
+ else if (ch == "]")
+ stack.push("[");
+ else if (ch == "}")
+ stack.push("{");
+ else if (/[\(\{\[]/.test(ch) && (!stack.length || stack.pop() != ch))
+ return cm.extendSelection(Pos(line, i));
+ }
+ --line; ch = null;
+ }
+ }
+
+ function quit(cm) {
+ cm.execCommand("clearSearch");
+ clearMark(cm);
+ }
+
+ CodeMirror.emacs = {kill: kill, killRegion: killRegion, repeated: repeated};
+
+ // Actual keymap
+
+ var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({
+ "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"), true);},
+ "Ctrl-K": repeated(function(cm) {
+ var start = cm.getCursor(), end = cm.clipPos(Pos(start.line));
+ var text = cm.getRange(start, end);
+ if (!/\S/.test(text)) {
+ text += "\n";
+ end = Pos(start.line + 1, 0);
+ }
+ kill(cm, start, end, "grow", text);
+ }),
+ "Alt-W": function(cm) {
+ addToRing(cm.getSelection());
+ clearMark(cm);
+ },
+ "Ctrl-Y": function(cm) {
+ var start = cm.getCursor();
+ cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste");
+ cm.setSelection(start, cm.getCursor());
+ },
+ "Alt-Y": function(cm) {cm.replaceSelection(popFromRing(), "around", "paste");},
+
+ "Ctrl-Space": setMark, "Ctrl-Shift-2": setMark,
+
+ "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1),
+ "Right": move(byChar, 1), "Left": move(byChar, -1),
+ "Ctrl-D": function(cm) { killTo(cm, byChar, 1, false); },
+ "Delete": function(cm) { killRegion(cm, false) || killTo(cm, byChar, 1, false); },
+ "Ctrl-H": function(cm) { killTo(cm, byChar, -1, false); },
+ "Backspace": function(cm) { killRegion(cm, false) || killTo(cm, byChar, -1, false); },
+
+ "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1),
+ "Alt-Right": move(byWord, 1), "Alt-Left": move(byWord, -1),
+ "Alt-D": function(cm) { killTo(cm, byWord, 1, "grow"); },
+ "Alt-Backspace": function(cm) { killTo(cm, byWord, -1, "grow"); },
+
+ "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1),
+ "Down": move(byLine, 1), "Up": move(byLine, -1),
+ "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
+ "End": "goLineEnd", "Home": "goLineStart",
+
+ "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1),
+ "PageUp": move(byPage, -1), "PageDown": move(byPage, 1),
+
+ "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1),
+
+ "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1),
+ "Alt-K": function(cm) { killTo(cm, bySentence, 1, "grow"); },
+
+ "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1, "grow"); },
+ "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1, "grow"); },
+ "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1, "grow"),
+
+ "Shift-Ctrl-Alt-2": function(cm) {
+ var cursor = cm.getCursor();
+ cm.setSelection(findEnd(cm, cursor, byExpr, 1), cursor);
+ },
+ "Ctrl-Alt-T": function(cm) {
+ var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1);
+ var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1);
+ cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) +
+ cm.getRange(leftStart, leftEnd), leftStart, rightEnd);
+ },
+ "Ctrl-Alt-U": repeated(toEnclosingExpr),
+
+ "Alt-Space": function(cm) {
+ var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line);
+ while (from && /\s/.test(text.charAt(from - 1))) --from;
+ while (to < text.length && /\s/.test(text.charAt(to))) ++to;
+ cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to));
+ },
+ "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }),
+ "Ctrl-T": repeated(function(cm) {
+ cm.execCommand("transposeChars");
+ }),
+
+ "Alt-C": repeated(function(cm) {
+ operateOnWord(cm, function(w) {
+ var letter = w.search(/\w/);
+ if (letter == -1) return w;
+ return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase();
+ });
+ }),
+ "Alt-U": repeated(function(cm) {
+ operateOnWord(cm, function(w) { return w.toUpperCase(); });
+ }),
+ "Alt-L": repeated(function(cm) {
+ operateOnWord(cm, function(w) { return w.toLowerCase(); });
+ }),
+
+ "Alt-;": "toggleComment",
+
+ "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"),
+ "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"),
+ "Shift-Ctrl-Z": "redo",
+ "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
+ "Ctrl-S": "findPersistentNext", "Ctrl-R": "findPersistentPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace",
+ "Alt-/": "autocomplete",
+ "Enter": "newlineAndIndent",
+ "Ctrl-J": repeated(function(cm) { cm.replaceSelection("\n", "end"); }),
+ "Tab": "indentAuto",
+
+ "Alt-G G": function(cm) {
+ var prefix = getPrefix(cm, true);
+ if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1);
+
+ getInput(cm, "Goto line", function(str) {
+ var num;
+ if (str && !isNaN(num = Number(str)) && num == (num|0) && num > 0)
+ cm.setCursor(num - 1);
+ });
+ },
+
+ "Ctrl-X Tab": function(cm) {
+ cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit"));
+ },
+ "Ctrl-X Ctrl-X": function(cm) {
+ cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor"));
+ },
+ "Ctrl-X Ctrl-S": "save",
+ "Ctrl-X Ctrl-W": "save",
+ "Ctrl-X S": "saveAll",
+ "Ctrl-X F": "open",
+ "Ctrl-X U": repeated("undo"),
+ "Ctrl-X K": "close",
+ "Ctrl-X Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), "grow"); },
+ "Ctrl-X H": "selectAll",
+
+ "Ctrl-Q Tab": repeated("insertTab"),
+ "Ctrl-U": addPrefixMap
+ });
+
+ var prefixMap = {"Ctrl-G": clearPrefix};
+ function regPrefix(d) {
+ prefixMap[d] = function(cm) { addPrefix(cm, d); };
+ keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); };
+ prefixPreservingKeys["Ctrl-" + d] = true;
+ }
+ for (var i = 0; i < 10; ++i) regPrefix(String(i));
+ regPrefix("-");
+});
diff --git a/scripts/codemirror/keymap/sublime.js b/scripts/codemirror/keymap/sublime.js
new file mode 100644
index 0000000..799641a
--- /dev/null
+++ b/scripts/codemirror/keymap/sublime.js
@@ -0,0 +1,691 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+// A rough approximation of Sublime Text's keybindings
+// Depends on addon/search/searchcursor.js and optionally addon/dialog/dialogs.js
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/edit/matchbrackets"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/edit/matchbrackets"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ var cmds = CodeMirror.commands;
+ var Pos = CodeMirror.Pos;
+
+ // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that.
+ function findPosSubword(doc, start, dir) {
+ if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1));
+ var line = doc.getLine(start.line);
+ if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0));
+ var state = "start", type;
+ for (var pos = start.ch, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) {
+ var next = line.charAt(dir < 0 ? pos - 1 : pos);
+ var cat = next != "_" && CodeMirror.isWordChar(next) ? "w" : "o";
+ if (cat == "w" && next.toUpperCase() == next) cat = "W";
+ if (state == "start") {
+ if (cat != "o") { state = "in"; type = cat; }
+ } else if (state == "in") {
+ if (type != cat) {
+ if (type == "w" && cat == "W" && dir < 0) pos--;
+ if (type == "W" && cat == "w" && dir > 0) { type = "w"; continue; }
+ break;
+ }
+ }
+ }
+ return Pos(start.line, pos);
+ }
+
+ function moveSubword(cm, dir) {
+ cm.extendSelectionsBy(function(range) {
+ if (cm.display.shift || cm.doc.extend || range.empty())
+ return findPosSubword(cm.doc, range.head, dir);
+ else
+ return dir < 0 ? range.from() : range.to();
+ });
+ }
+
+ cmds.goSubwordLeft = function(cm) { moveSubword(cm, -1); };
+ cmds.goSubwordRight = function(cm) { moveSubword(cm, 1); };
+
+ cmds.scrollLineUp = function(cm) {
+ var info = cm.getScrollInfo();
+ if (!cm.somethingSelected()) {
+ var visibleBottomLine = cm.lineAtHeight(info.top + info.clientHeight, "local");
+ if (cm.getCursor().line >= visibleBottomLine)
+ cm.execCommand("goLineUp");
+ }
+ cm.scrollTo(null, info.top - cm.defaultTextHeight());
+ };
+ cmds.scrollLineDown = function(cm) {
+ var info = cm.getScrollInfo();
+ if (!cm.somethingSelected()) {
+ var visibleTopLine = cm.lineAtHeight(info.top, "local")+1;
+ if (cm.getCursor().line <= visibleTopLine)
+ cm.execCommand("goLineDown");
+ }
+ cm.scrollTo(null, info.top + cm.defaultTextHeight());
+ };
+
+ cmds.splitSelectionByLine = function(cm) {
+ var ranges = cm.listSelections(), lineRanges = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var from = ranges[i].from(), to = ranges[i].to();
+ for (var line = from.line; line <= to.line; ++line)
+ if (!(to.line > from.line && line == to.line && to.ch == 0))
+ lineRanges.push({anchor: line == from.line ? from : Pos(line, 0),
+ head: line == to.line ? to : Pos(line)});
+ }
+ cm.setSelections(lineRanges, 0);
+ };
+
+ cmds.singleSelectionTop = function(cm) {
+ var range = cm.listSelections()[0];
+ cm.setSelection(range.anchor, range.head, {scroll: false});
+ };
+
+ cmds.selectLine = function(cm) {
+ var ranges = cm.listSelections(), extended = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ extended.push({anchor: Pos(range.from().line, 0),
+ head: Pos(range.to().line + 1, 0)});
+ }
+ cm.setSelections(extended);
+ };
+
+ function insertLine(cm, above) {
+ if (cm.isReadOnly()) return CodeMirror.Pass
+ cm.operation(function() {
+ var len = cm.listSelections().length, newSelection = [], last = -1;
+ for (var i = 0; i < len; i++) {
+ var head = cm.listSelections()[i].head;
+ if (head.line <= last) continue;
+ var at = Pos(head.line + (above ? 0 : 1), 0);
+ cm.replaceRange("\n", at, null, "+insertLine");
+ cm.indentLine(at.line, null, true);
+ newSelection.push({head: at, anchor: at});
+ last = head.line + 1;
+ }
+ cm.setSelections(newSelection);
+ });
+ cm.execCommand("indentAuto");
+ }
+
+ cmds.insertLineAfter = function(cm) { return insertLine(cm, false); };
+
+ cmds.insertLineBefore = function(cm) { return insertLine(cm, true); };
+
+ function wordAt(cm, pos) {
+ var start = pos.ch, end = start, line = cm.getLine(pos.line);
+ while (start && CodeMirror.isWordChar(line.charAt(start - 1))) --start;
+ while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) ++end;
+ return {from: Pos(pos.line, start), to: Pos(pos.line, end), word: line.slice(start, end)};
+ }
+
+ cmds.selectNextOccurrence = function(cm) {
+ var from = cm.getCursor("from"), to = cm.getCursor("to");
+ var fullWord = cm.state.sublimeFindFullWord == cm.doc.sel;
+ if (CodeMirror.cmpPos(from, to) == 0) {
+ var word = wordAt(cm, from);
+ if (!word.word) return;
+ cm.setSelection(word.from, word.to);
+ fullWord = true;
+ } else {
+ var text = cm.getRange(from, to);
+ var query = fullWord ? new RegExp("\\b" + text + "\\b") : text;
+ var cur = cm.getSearchCursor(query, to);
+ var found = cur.findNext();
+ if (!found) {
+ cur = cm.getSearchCursor(query, Pos(cm.firstLine(), 0));
+ found = cur.findNext();
+ }
+ if (!found || isSelectedRange(cm.listSelections(), cur.from(), cur.to()))
+ return CodeMirror.Pass
+ cm.addSelection(cur.from(), cur.to());
+ }
+ if (fullWord)
+ cm.state.sublimeFindFullWord = cm.doc.sel;
+ };
+
+ function addCursorToSelection(cm, dir) {
+ var ranges = cm.listSelections(), newRanges = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ var newAnchor = cm.findPosV(
+ range.anchor, dir, "line", range.anchor.goalColumn);
+ var newHead = cm.findPosV(
+ range.head, dir, "line", range.head.goalColumn);
+ newAnchor.goalColumn = range.anchor.goalColumn != null ?
+ range.anchor.goalColumn : cm.cursorCoords(range.anchor, "div").left;
+ newHead.goalColumn = range.head.goalColumn != null ?
+ range.head.goalColumn : cm.cursorCoords(range.head, "div").left;
+ var newRange = {anchor: newAnchor, head: newHead};
+ newRanges.push(range);
+ newRanges.push(newRange);
+ }
+ cm.setSelections(newRanges);
+ }
+ cmds.addCursorToPrevLine = function(cm) { addCursorToSelection(cm, -1); };
+ cmds.addCursorToNextLine = function(cm) { addCursorToSelection(cm, 1); };
+
+ function isSelectedRange(ranges, from, to) {
+ for (var i = 0; i < ranges.length; i++)
+ if (ranges[i].from() == from && ranges[i].to() == to) return true
+ return false
+ }
+
+ var mirror = "(){}[]";
+ function selectBetweenBrackets(cm) {
+ var ranges = cm.listSelections(), newRanges = []
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i], pos = range.head, opening = cm.scanForBracket(pos, -1);
+ if (!opening) return false;
+ for (;;) {
+ var closing = cm.scanForBracket(pos, 1);
+ if (!closing) return false;
+ if (closing.ch == mirror.charAt(mirror.indexOf(opening.ch) + 1)) {
+ var startPos = Pos(opening.pos.line, opening.pos.ch + 1);
+ if (CodeMirror.cmpPos(startPos, range.from()) == 0 &&
+ CodeMirror.cmpPos(closing.pos, range.to()) == 0) {
+ opening = cm.scanForBracket(opening.pos, -1);
+ if (!opening) return false;
+ } else {
+ newRanges.push({anchor: startPos, head: closing.pos});
+ break;
+ }
+ }
+ pos = Pos(closing.pos.line, closing.pos.ch + 1);
+ }
+ }
+ cm.setSelections(newRanges);
+ return true;
+ }
+
+ cmds.selectScope = function(cm) {
+ selectBetweenBrackets(cm) || cm.execCommand("selectAll");
+ };
+ cmds.selectBetweenBrackets = function(cm) {
+ if (!selectBetweenBrackets(cm)) return CodeMirror.Pass;
+ };
+
+ cmds.goToBracket = function(cm) {
+ cm.extendSelectionsBy(function(range) {
+ var next = cm.scanForBracket(range.head, 1);
+ if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos;
+ var prev = cm.scanForBracket(range.head, -1);
+ return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head;
+ });
+ };
+
+ cmds.swapLineUp = function(cm) {
+ if (cm.isReadOnly()) return CodeMirror.Pass
+ var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i], from = range.from().line - 1, to = range.to().line;
+ newSels.push({anchor: Pos(range.anchor.line - 1, range.anchor.ch),
+ head: Pos(range.head.line - 1, range.head.ch)});
+ if (range.to().ch == 0 && !range.empty()) --to;
+ if (from > at) linesToMove.push(from, to);
+ else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;
+ at = to;
+ }
+ cm.operation(function() {
+ for (var i = 0; i < linesToMove.length; i += 2) {
+ var from = linesToMove[i], to = linesToMove[i + 1];
+ var line = cm.getLine(from);
+ cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine");
+ if (to > cm.lastLine())
+ cm.replaceRange("\n" + line, Pos(cm.lastLine()), null, "+swapLine");
+ else
+ cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine");
+ }
+ cm.setSelections(newSels);
+ cm.scrollIntoView();
+ });
+ };
+
+ cmds.swapLineDown = function(cm) {
+ if (cm.isReadOnly()) return CodeMirror.Pass
+ var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1;
+ for (var i = ranges.length - 1; i >= 0; i--) {
+ var range = ranges[i], from = range.to().line + 1, to = range.from().line;
+ if (range.to().ch == 0 && !range.empty()) from--;
+ if (from < at) linesToMove.push(from, to);
+ else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;
+ at = to;
+ }
+ cm.operation(function() {
+ for (var i = linesToMove.length - 2; i >= 0; i -= 2) {
+ var from = linesToMove[i], to = linesToMove[i + 1];
+ var line = cm.getLine(from);
+ if (from == cm.lastLine())
+ cm.replaceRange("", Pos(from - 1), Pos(from), "+swapLine");
+ else
+ cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine");
+ cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine");
+ }
+ cm.scrollIntoView();
+ });
+ };
+
+ cmds.toggleCommentIndented = function(cm) {
+ cm.toggleComment({ indent: true });
+ }
+
+ cmds.joinLines = function(cm) {
+ var ranges = cm.listSelections(), joined = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i], from = range.from();
+ var start = from.line, end = range.to().line;
+ while (i < ranges.length - 1 && ranges[i + 1].from().line == end)
+ end = ranges[++i].to().line;
+ joined.push({start: start, end: end, anchor: !range.empty() && from});
+ }
+ cm.operation(function() {
+ var offset = 0, ranges = [];
+ for (var i = 0; i < joined.length; i++) {
+ var obj = joined[i];
+ var anchor = obj.anchor && Pos(obj.anchor.line - offset, obj.anchor.ch), head;
+ for (var line = obj.start; line <= obj.end; line++) {
+ var actual = line - offset;
+ if (line == obj.end) head = Pos(actual, cm.getLine(actual).length + 1);
+ if (actual < cm.lastLine()) {
+ cm.replaceRange(" ", Pos(actual), Pos(actual + 1, /^\s*/.exec(cm.getLine(actual + 1))[0].length));
+ ++offset;
+ }
+ }
+ ranges.push({anchor: anchor || head, head: head});
+ }
+ cm.setSelections(ranges, 0);
+ });
+ };
+
+ cmds.duplicateLine = function(cm) {
+ cm.operation(function() {
+ var rangeCount = cm.listSelections().length;
+ for (var i = 0; i < rangeCount; i++) {
+ var range = cm.listSelections()[i];
+ if (range.empty())
+ cm.replaceRange(cm.getLine(range.head.line) + "\n", Pos(range.head.line, 0));
+ else
+ cm.replaceRange(cm.getRange(range.from(), range.to()), range.from());
+ }
+ cm.scrollIntoView();
+ });
+ };
+
+
+ function sortLines(cm, caseSensitive) {
+ if (cm.isReadOnly()) return CodeMirror.Pass
+ var ranges = cm.listSelections(), toSort = [], selected;
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (range.empty()) continue;
+ var from = range.from().line, to = range.to().line;
+ while (i < ranges.length - 1 && ranges[i + 1].from().line == to)
+ to = ranges[++i].to().line;
+ if (!ranges[i].to().ch) to--;
+ toSort.push(from, to);
+ }
+ if (toSort.length) selected = true;
+ else toSort.push(cm.firstLine(), cm.lastLine());
+
+ cm.operation(function() {
+ var ranges = [];
+ for (var i = 0; i < toSort.length; i += 2) {
+ var from = toSort[i], to = toSort[i + 1];
+ var start = Pos(from, 0), end = Pos(to);
+ var lines = cm.getRange(start, end, false);
+ if (caseSensitive)
+ lines.sort();
+ else
+ lines.sort(function(a, b) {
+ var au = a.toUpperCase(), bu = b.toUpperCase();
+ if (au != bu) { a = au; b = bu; }
+ return a < b ? -1 : a == b ? 0 : 1;
+ });
+ cm.replaceRange(lines, start, end);
+ if (selected) ranges.push({anchor: start, head: Pos(to + 1, 0)});
+ }
+ if (selected) cm.setSelections(ranges, 0);
+ });
+ }
+
+ cmds.sortLines = function(cm) { sortLines(cm, true); };
+ cmds.sortLinesInsensitive = function(cm) { sortLines(cm, false); };
+
+ cmds.nextBookmark = function(cm) {
+ var marks = cm.state.sublimeBookmarks;
+ if (marks) while (marks.length) {
+ var current = marks.shift();
+ var found = current.find();
+ if (found) {
+ marks.push(current);
+ return cm.setSelection(found.from, found.to);
+ }
+ }
+ };
+
+ cmds.prevBookmark = function(cm) {
+ var marks = cm.state.sublimeBookmarks;
+ if (marks) while (marks.length) {
+ marks.unshift(marks.pop());
+ var found = marks[marks.length - 1].find();
+ if (!found)
+ marks.pop();
+ else
+ return cm.setSelection(found.from, found.to);
+ }
+ };
+
+ cmds.toggleBookmark = function(cm) {
+ var ranges = cm.listSelections();
+ var marks = cm.state.sublimeBookmarks || (cm.state.sublimeBookmarks = []);
+ for (var i = 0; i < ranges.length; i++) {
+ var from = ranges[i].from(), to = ranges[i].to();
+ var found = ranges[i].empty() ? cm.findMarksAt(from) : cm.findMarks(from, to);
+ for (var j = 0; j < found.length; j++) {
+ if (found[j].sublimeBookmark) {
+ found[j].clear();
+ for (var k = 0; k < marks.length; k++)
+ if (marks[k] == found[j])
+ marks.splice(k--, 1);
+ break;
+ }
+ }
+ if (j == found.length)
+ marks.push(cm.markText(from, to, {sublimeBookmark: true, clearWhenEmpty: false}));
+ }
+ };
+
+ cmds.clearBookmarks = function(cm) {
+ var marks = cm.state.sublimeBookmarks;
+ if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();
+ marks.length = 0;
+ };
+
+ cmds.selectBookmarks = function(cm) {
+ var marks = cm.state.sublimeBookmarks, ranges = [];
+ if (marks) for (var i = 0; i < marks.length; i++) {
+ var found = marks[i].find();
+ if (!found)
+ marks.splice(i--, 0);
+ else
+ ranges.push({anchor: found.from, head: found.to});
+ }
+ if (ranges.length)
+ cm.setSelections(ranges, 0);
+ };
+
+ function modifyWordOrSelection(cm, mod) {
+ cm.operation(function() {
+ var ranges = cm.listSelections(), indices = [], replacements = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (range.empty()) { indices.push(i); replacements.push(""); }
+ else replacements.push(mod(cm.getRange(range.from(), range.to())));
+ }
+ cm.replaceSelections(replacements, "around", "case");
+ for (var i = indices.length - 1, at; i >= 0; i--) {
+ var range = ranges[indices[i]];
+ if (at && CodeMirror.cmpPos(range.head, at) > 0) continue;
+ var word = wordAt(cm, range.head);
+ at = word.from;
+ cm.replaceRange(mod(word.word), word.from, word.to);
+ }
+ });
+ }
+
+ cmds.smartBackspace = function(cm) {
+ if (cm.somethingSelected()) return CodeMirror.Pass;
+
+ cm.operation(function() {
+ var cursors = cm.listSelections();
+ var indentUnit = cm.getOption("indentUnit");
+
+ for (var i = cursors.length - 1; i >= 0; i--) {
+ var cursor = cursors[i].head;
+ var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor);
+ var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption("tabSize"));
+
+ // Delete by one character by default
+ var deletePos = cm.findPosH(cursor, -1, "char", false);
+
+ if (toStartOfLine && !/\S/.test(toStartOfLine) && column % indentUnit == 0) {
+ var prevIndent = new Pos(cursor.line,
+ CodeMirror.findColumn(toStartOfLine, column - indentUnit, indentUnit));
+
+ // Smart delete only if we found a valid prevIndent location
+ if (prevIndent.ch != cursor.ch) deletePos = prevIndent;
+ }
+
+ cm.replaceRange("", deletePos, cursor, "+delete");
+ }
+ });
+ };
+
+ cmds.delLineRight = function(cm) {
+ cm.operation(function() {
+ var ranges = cm.listSelections();
+ for (var i = ranges.length - 1; i >= 0; i--)
+ cm.replaceRange("", ranges[i].anchor, Pos(ranges[i].to().line), "+delete");
+ cm.scrollIntoView();
+ });
+ };
+
+ cmds.upcaseAtCursor = function(cm) {
+ modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); });
+ };
+ cmds.downcaseAtCursor = function(cm) {
+ modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); });
+ };
+
+ cmds.setSublimeMark = function(cm) {
+ if (cm.state.sublimeMark) cm.state.sublimeMark.clear();
+ cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
+ };
+ cmds.selectToSublimeMark = function(cm) {
+ var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
+ if (found) cm.setSelection(cm.getCursor(), found);
+ };
+ cmds.deleteToSublimeMark = function(cm) {
+ var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
+ if (found) {
+ var from = cm.getCursor(), to = found;
+ if (CodeMirror.cmpPos(from, to) > 0) { var tmp = to; to = from; from = tmp; }
+ cm.state.sublimeKilled = cm.getRange(from, to);
+ cm.replaceRange("", from, to);
+ }
+ };
+ cmds.swapWithSublimeMark = function(cm) {
+ var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
+ if (found) {
+ cm.state.sublimeMark.clear();
+ cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
+ cm.setCursor(found);
+ }
+ };
+ cmds.sublimeYank = function(cm) {
+ if (cm.state.sublimeKilled != null)
+ cm.replaceSelection(cm.state.sublimeKilled, null, "paste");
+ };
+
+ cmds.showInCenter = function(cm) {
+ var pos = cm.cursorCoords(null, "local");
+ cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2);
+ };
+
+ function getTarget(cm) {
+ var from = cm.getCursor("from"), to = cm.getCursor("to");
+ if (CodeMirror.cmpPos(from, to) == 0) {
+ var word = wordAt(cm, from);
+ if (!word.word) return;
+ from = word.from;
+ to = word.to;
+ }
+ return {from: from, to: to, query: cm.getRange(from, to), word: word};
+ }
+
+ function findAndGoTo(cm, forward) {
+ var target = getTarget(cm);
+ if (!target) return;
+ var query = target.query;
+ var cur = cm.getSearchCursor(query, forward ? target.to : target.from);
+
+ if (forward ? cur.findNext() : cur.findPrevious()) {
+ cm.setSelection(cur.from(), cur.to());
+ } else {
+ cur = cm.getSearchCursor(query, forward ? Pos(cm.firstLine(), 0)
+ : cm.clipPos(Pos(cm.lastLine())));
+ if (forward ? cur.findNext() : cur.findPrevious())
+ cm.setSelection(cur.from(), cur.to());
+ else if (target.word)
+ cm.setSelection(target.from, target.to);
+ }
+ };
+ cmds.findUnder = function(cm) { findAndGoTo(cm, true); };
+ cmds.findUnderPrevious = function(cm) { findAndGoTo(cm,false); };
+ cmds.findAllUnder = function(cm) {
+ var target = getTarget(cm);
+ if (!target) return;
+ var cur = cm.getSearchCursor(target.query);
+ var matches = [];
+ var primaryIndex = -1;
+ while (cur.findNext()) {
+ matches.push({anchor: cur.from(), head: cur.to()});
+ if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch)
+ primaryIndex++;
+ }
+ cm.setSelections(matches, primaryIndex);
+ };
+
+
+ var keyMap = CodeMirror.keyMap;
+ keyMap.macSublime = {
+ "Cmd-Left": "goLineStartSmart",
+ "Shift-Tab": "indentLess",
+ "Shift-Ctrl-K": "deleteLine",
+ "Alt-Q": "wrapLines",
+ "Ctrl-Left": "goSubwordLeft",
+ "Ctrl-Right": "goSubwordRight",
+ "Ctrl-Alt-Up": "scrollLineUp",
+ "Ctrl-Alt-Down": "scrollLineDown",
+ "Cmd-L": "selectLine",
+ "Shift-Cmd-L": "splitSelectionByLine",
+ "Esc": "singleSelectionTop",
+ "Cmd-Enter": "insertLineAfter",
+ "Shift-Cmd-Enter": "insertLineBefore",
+ "Cmd-D": "selectNextOccurrence",
+ "Shift-Cmd-Space": "selectScope",
+ "Shift-Cmd-M": "selectBetweenBrackets",
+ "Cmd-M": "goToBracket",
+ "Cmd-Ctrl-Up": "swapLineUp",
+ "Cmd-Ctrl-Down": "swapLineDown",
+ "Cmd-/": "toggleCommentIndented",
+ "Cmd-J": "joinLines",
+ "Shift-Cmd-D": "duplicateLine",
+ "F5": "sortLines",
+ "Cmd-F5": "sortLinesInsensitive",
+ "F2": "nextBookmark",
+ "Shift-F2": "prevBookmark",
+ "Cmd-F2": "toggleBookmark",
+ "Shift-Cmd-F2": "clearBookmarks",
+ "Alt-F2": "selectBookmarks",
+ "Backspace": "smartBackspace",
+ "Cmd-K Cmd-K": "delLineRight",
+ "Cmd-K Cmd-U": "upcaseAtCursor",
+ "Cmd-K Cmd-L": "downcaseAtCursor",
+ "Cmd-K Cmd-Space": "setSublimeMark",
+ "Cmd-K Cmd-A": "selectToSublimeMark",
+ "Cmd-K Cmd-W": "deleteToSublimeMark",
+ "Cmd-K Cmd-X": "swapWithSublimeMark",
+ "Cmd-K Cmd-Y": "sublimeYank",
+ "Cmd-K Cmd-C": "showInCenter",
+ "Cmd-K Cmd-G": "clearBookmarks",
+ "Cmd-K Cmd-Backspace": "delLineLeft",
+ "Cmd-K Cmd-0": "unfoldAll",
+ "Cmd-K Cmd-J": "unfoldAll",
+ "Ctrl-Shift-Up": "addCursorToPrevLine",
+ "Ctrl-Shift-Down": "addCursorToNextLine",
+ "Cmd-F3": "findUnder",
+ "Shift-Cmd-F3": "findUnderPrevious",
+ "Alt-F3": "findAllUnder",
+ "Shift-Cmd-[": "fold",
+ "Shift-Cmd-]": "unfold",
+ "Cmd-I": "findIncremental",
+ "Shift-Cmd-I": "findIncrementalReverse",
+ "Cmd-H": "replace",
+ "F3": "findNext",
+ "Shift-F3": "findPrev",
+ "fallthrough": "macDefault"
+ };
+ CodeMirror.normalizeKeyMap(keyMap.macSublime);
+
+ keyMap.pcSublime = {
+ "Shift-Tab": "indentLess",
+ "Shift-Ctrl-K": "deleteLine",
+ "Alt-Q": "wrapLines",
+ "Ctrl-T": "transposeChars",
+ "Alt-Left": "goSubwordLeft",
+ "Alt-Right": "goSubwordRight",
+ "Ctrl-Up": "scrollLineUp",
+ "Ctrl-Down": "scrollLineDown",
+ "Ctrl-L": "selectLine",
+ "Shift-Ctrl-L": "splitSelectionByLine",
+ "Esc": "singleSelectionTop",
+ "Ctrl-Enter": "insertLineAfter",
+ "Shift-Ctrl-Enter": "insertLineBefore",
+ "Ctrl-D": "selectNextOccurrence",
+ "Shift-Ctrl-Space": "selectScope",
+ "Shift-Ctrl-M": "selectBetweenBrackets",
+ "Ctrl-M": "goToBracket",
+ "Shift-Ctrl-Up": "swapLineUp",
+ "Shift-Ctrl-Down": "swapLineDown",
+ "Ctrl-/": "toggleCommentIndented",
+ "Ctrl-J": "joinLines",
+ "Shift-Ctrl-D": "duplicateLine",
+ "F9": "sortLines",
+ "Ctrl-F9": "sortLinesInsensitive",
+ "F2": "nextBookmark",
+ "Shift-F2": "prevBookmark",
+ "Ctrl-F2": "toggleBookmark",
+ "Shift-Ctrl-F2": "clearBookmarks",
+ "Alt-F2": "selectBookmarks",
+ "Backspace": "smartBackspace",
+ "Ctrl-K Ctrl-K": "delLineRight",
+ "Ctrl-K Ctrl-U": "upcaseAtCursor",
+ "Ctrl-K Ctrl-L": "downcaseAtCursor",
+ "Ctrl-K Ctrl-Space": "setSublimeMark",
+ "Ctrl-K Ctrl-A": "selectToSublimeMark",
+ "Ctrl-K Ctrl-W": "deleteToSublimeMark",
+ "Ctrl-K Ctrl-X": "swapWithSublimeMark",
+ "Ctrl-K Ctrl-Y": "sublimeYank",
+ "Ctrl-K Ctrl-C": "showInCenter",
+ "Ctrl-K Ctrl-G": "clearBookmarks",
+ "Ctrl-K Ctrl-Backspace": "delLineLeft",
+ "Ctrl-K Ctrl-0": "unfoldAll",
+ "Ctrl-K Ctrl-J": "unfoldAll",
+ "Ctrl-Alt-Up": "addCursorToPrevLine",
+ "Ctrl-Alt-Down": "addCursorToNextLine",
+ "Ctrl-F3": "findUnder",
+ "Shift-Ctrl-F3": "findUnderPrevious",
+ "Alt-F3": "findAllUnder",
+ "Shift-Ctrl-[": "fold",
+ "Shift-Ctrl-]": "unfold",
+ "Ctrl-I": "findIncremental",
+ "Shift-Ctrl-I": "findIncrementalReverse",
+ "Ctrl-H": "replace",
+ "F3": "findNext",
+ "Shift-F3": "findPrev",
+ "fallthrough": "pcDefault"
+ };
+ CodeMirror.normalizeKeyMap(keyMap.pcSublime);
+
+ var mac = keyMap.default == keyMap.macDefault;
+ keyMap.sublime = mac ? keyMap.macSublime : keyMap.pcSublime;
+});
diff --git a/scripts/codemirror/keymap/vim.js b/scripts/codemirror/keymap/vim.js
index 73a4a0a..95b19b2 100644
--- a/scripts/codemirror/keymap/vim.js
+++ b/scripts/codemirror/keymap/vim.js
@@ -1,786 +1,5494 @@
-// Supported keybindings:
-//
-// Cursor movement:
-// h, j, k, l
-// e, E, w, W, b, B
-// Ctrl-f, Ctrl-b
-// Ctrl-n, Ctrl-p
-// $, ^, 0
-// G
-// ge, gE
-// gg
-// f, F, t, T
-// Ctrl-o, Ctrl-i TODO (FIXME - Ctrl-O wont work in Chrome)
-// /, ?, n, N TODO (does not work)
-// #, * TODO
-//
-// Entering insert mode:
-// i, I, a, A, o, O
-// s
-// ce, cb (without support for number of actions like c3e - TODO)
-// cc
-// S, C TODO
-// cf, cF, ct, cT
-//
-// Deleting text:
-// x, X
-// J
-// dd, D
-// de, db (without support for number of actions like d3e - TODO)
-// df, dF, dt, dT
-//
-// Yanking and pasting:
-// yy, Y
-// p, P
-// p' TODO - test
-// y' TODO - test
-// m TODO - test
-//
-// Changing text in place:
-// ~
-// r
-//
-// Visual mode:
-// v, V TODO
-//
-// Misc:
-// . TODO
-//
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
-(function() {
- var count = "";
- var sdir = "f";
- var buf = "";
- var yank = 0;
- var mark = [];
- var reptTimes = 0;
- function emptyBuffer() { buf = ""; }
- function pushInBuffer(str) { buf += str; }
- function pushCountDigit(digit) { return function(cm) {count += digit;}; }
- function popCount() { var i = parseInt(count, 10); count = ""; return i || 1; }
- function iterTimes(func) {
- for (var i = 0, c = popCount(); i < c; ++i) func(i, i == c - 1);
- }
- function countTimes(func) {
- if (typeof func == "string") func = CodeMirror.commands[func];
- return function(cm) { iterTimes(function () { func(cm); }); };
- }
+/**
+ * Supported keybindings:
+ * Too many to list. Refer to defaultKeymap below.
+ *
+ * Supported Ex commands:
+ * Refer to defaultExCommandMap below.
+ *
+ * Registers: unnamed, -, a-z, A-Z, 0-9
+ * (Does not respect the special case for number registers when delete
+ * operator is made with these commands: %, (, ), , /, ?, n, N, {, } )
+ * TODO: Implement the remaining registers.
+ *
+ * Marks: a-z, A-Z, and 0-9
+ * TODO: Implement the remaining special marks. They have more complex
+ * behavior.
+ *
+ * Events:
+ * 'vim-mode-change' - raised on the editor anytime the current mode changes,
+ * Event object: {mode: "visual", subMode: "linewise"}
+ *
+ * Code structure:
+ * 1. Default keymap
+ * 2. Variable declarations and short basic helpers
+ * 3. Instance (External API) implementation
+ * 4. Internal state tracking objects (input state, counter) implementation
+ * and instantiation
+ * 5. Key handler (the main command dispatcher) implementation
+ * 6. Motion, operator, and action implementations
+ * 7. Helper functions for the key handler, motions, operators, and actions
+ * 8. Set up Vim to work as a keymap for CodeMirror.
+ * 9. Ex command implementations.
+ */
- function iterObj(o, f) {
- for (var prop in o) if (o.hasOwnProperty(prop)) f(prop, o[prop]);
- }
- function iterList(l, f) {
- for (var i = 0; i < l.length; ++i) f(l[i]);
- }
- function toLetter(ch) {
- // T -> t, Shift-T -> T, '*' -> *, "Space" -> " "
- if (ch.slice(0, 6) == "Shift-") {
- return ch.slice(0, 1);
- } else {
- if (ch == "Space") return " ";
- if (ch.length == 3 && ch[0] == "'" && ch[2] == "'") return ch[1];
- return ch.toLowerCase();
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog"), require("../addon/edit/matchbrackets.js"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog", "../addon/edit/matchbrackets"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ 'use strict';
+
+ var defaultKeymap = [
+ // Key to key mapping. This goes first to make it possible to override
+ // existing mappings.
+ { keys: '', type: 'keyToKey', toKeys: 'h' },
+ { keys: '', type: 'keyToKey', toKeys: 'l' },
+ { keys: '', type: 'keyToKey', toKeys: 'k' },
+ { keys: '', type: 'keyToKey', toKeys: 'j' },
+ { keys: '', type: 'keyToKey', toKeys: 'l' },
+ { keys: '', type: 'keyToKey', toKeys: 'h', context: 'normal'},
+ { keys: '', type: 'keyToKey', toKeys: 'x', context: 'normal'},
+ { keys: '', type: 'keyToKey', toKeys: 'W' },
+ { keys: '', type: 'keyToKey', toKeys: 'B', context: 'normal' },
+ { keys: '', type: 'keyToKey', toKeys: 'w' },
+ { keys: '', type: 'keyToKey', toKeys: 'b', context: 'normal' },
+ { keys: '', type: 'keyToKey', toKeys: 'j' },
+ { keys: '', type: 'keyToKey', toKeys: 'k' },
+ { keys: '', type: 'keyToKey', toKeys: '' },
+ { keys: '', type: 'keyToKey', toKeys: '' },
+ { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' },
+ { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' },
+ { keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' },
+ { keys: 's', type: 'keyToKey', toKeys: 'c', context: 'visual'},
+ { keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' },
+ { keys: 'S', type: 'keyToKey', toKeys: 'VdO', context: 'visual' },
+ { keys: '', type: 'keyToKey', toKeys: '0' },
+ { keys: '', type: 'keyToKey', toKeys: '$' },
+ { keys: '', type: 'keyToKey', toKeys: '' },
+ { keys: '', type: 'keyToKey', toKeys: '' },
+ { keys: '', type: 'keyToKey', toKeys: 'j^', context: 'normal' },
+ { keys: '', type: 'action', action: 'toggleOverwrite', context: 'insert' },
+ // Motions
+ { keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }},
+ { keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }},
+ { keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }},
+ { keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }},
+ { keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }},
+ { keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }},
+ { keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }},
+ { keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }},
+ { keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }},
+ { keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }},
+ { keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }},
+ { keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }},
+ { keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }},
+ { keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }},
+ { keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }},
+ { keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }},
+ { keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }},
+ { keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }},
+ { keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }},
+ { keys: '(', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: false }},
+ { keys: ')', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: true }},
+ { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }},
+ { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }},
+ { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }},
+ { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }},
+ { keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
+ { keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
+ { keys: '0', type: 'motion', motion: 'moveToStartOfLine' },
+ { keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' },
+ { keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }},
+ { keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }},
+ { keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
+ { keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }},
+ { keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }},
+ { keys: 'f', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }},
+ { keys: 'F', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }},
+ { keys: 't', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }},
+ { keys: 'T', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }},
+ { keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }},
+ { keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }},
+ { keys: '\'', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}},
+ { keys: '`', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}},
+ { keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
+ { keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
+ { keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
+ { keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
+ // the next two aren't motions but must come before more general motion declarations
+ { keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}},
+ { keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}},
+ { keys: ']', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}},
+ { keys: '[', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}},
+ { keys: '|', type: 'motion', motion: 'moveToColumn'},
+ { keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'},
+ { keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'},
+ // Operators
+ { keys: 'd', type: 'operator', operator: 'delete' },
+ { keys: 'y', type: 'operator', operator: 'yank' },
+ { keys: 'c', type: 'operator', operator: 'change' },
+ { keys: '=', type: 'operator', operator: 'indentAuto' },
+ { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }},
+ { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }},
+ { keys: 'g~', type: 'operator', operator: 'changeCase' },
+ { keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true },
+ { keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true },
+ { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }},
+ { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }},
+ // Operator-Motion dual commands
+ { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }},
+ { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }},
+ { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
+ { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'},
+ { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'expandToLine', motionArgs: { linewise: true }, context: 'normal'},
+ { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'},
+ { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
+ { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'},
+ { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'},
+ { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'},
+ { keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' },
+ //ignore C-w in normal mode
+ { keys: '', type: 'idle', context: 'normal' },
+ // Actions
+ { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }},
+ { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }},
+ { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }},
+ { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }},
+ { keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' },
+ { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },
+ { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
+ { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },
+ { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' },
+ { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' },
+ { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },
+ { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },
+ { keys: 'v', type: 'action', action: 'toggleVisualMode' },
+ { keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }},
+ { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
+ { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
+ { keys: 'gv', type: 'action', action: 'reselectLastSelection' },
+ { keys: 'J', type: 'action', action: 'joinLines', isEdit: true },
+ { keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }},
+ { keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }},
+ { keys: 'r', type: 'action', action: 'replace', isEdit: true },
+ { keys: '@', type: 'action', action: 'replayMacro' },
+ { keys: 'q', type: 'action', action: 'enterMacroRecordMode' },
+ // Handle Replace-mode as a special case of insert mode.
+ { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }},
+ { keys: 'u', type: 'action', action: 'undo', context: 'normal' },
+ { keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true },
+ { keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true },
+ { keys: '', type: 'action', action: 'redo' },
+ { keys: 'm', type: 'action', action: 'setMark' },
+ { keys: '"', type: 'action', action: 'setRegister' },
+ { keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }},
+ { keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
+ { keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }},
+ { keys: 'z', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
+ { keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }},
+ { keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
+ { keys: '.', type: 'action', action: 'repeatLastEdit' },
+ { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}},
+ { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}},
+ { keys: '', type: 'action', action: 'indent', actionArgs: { indentRight: true }, context: 'insert' },
+ { keys: '', type: 'action', action: 'indent', actionArgs: { indentRight: false }, context: 'insert' },
+ // Text object motions
+ { keys: 'a', type: 'motion', motion: 'textObjectManipulation' },
+ { keys: 'i', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }},
+ // Search
+ { keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
+ { keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
+ { keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
+ { keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
+ { keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
+ { keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
+ // Ex command
+ { keys: ':', type: 'ex' }
+ ];
+ var defaultKeymapLength = defaultKeymap.length;
+
+ /**
+ * Ex commands
+ * Care must be taken when adding to the default Ex command map. For any
+ * pair of commands that have a shared prefix, at least one of their
+ * shortNames must not match the prefix of the other command.
+ */
+ var defaultExCommandMap = [
+ { name: 'colorscheme', shortName: 'colo' },
+ { name: 'map' },
+ { name: 'imap', shortName: 'im' },
+ { name: 'nmap', shortName: 'nm' },
+ { name: 'vmap', shortName: 'vm' },
+ { name: 'unmap' },
+ { name: 'write', shortName: 'w' },
+ { name: 'undo', shortName: 'u' },
+ { name: 'redo', shortName: 'red' },
+ { name: 'set', shortName: 'se' },
+ { name: 'set', shortName: 'se' },
+ { name: 'setlocal', shortName: 'setl' },
+ { name: 'setglobal', shortName: 'setg' },
+ { name: 'sort', shortName: 'sor' },
+ { name: 'substitute', shortName: 's', possiblyAsync: true },
+ { name: 'nohlsearch', shortName: 'noh' },
+ { name: 'yank', shortName: 'y' },
+ { name: 'delmarks', shortName: 'delm' },
+ { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true },
+ { name: 'global', shortName: 'g' }
+ ];
+
+ var Pos = CodeMirror.Pos;
+
+ var Vim = function() {
+ function enterVimMode(cm) {
+ cm.setOption('disableInput', true);
+ cm.setOption('showCursorWhenSelecting', false);
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
+ cm.on('cursorActivity', onCursorActivity);
+ maybeInitVimState(cm);
+ CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
}
- }
- var SPECIAL_SYMBOLS = "~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;\"\'1234567890";
- function toCombo(ch) {
- // t -> T, T -> Shift-T, * -> '*', " " -> "Space"
- if (ch == " ") return "Space";
- var specialIdx = SPECIAL_SYMBOLS.indexOf(ch);
- if (specialIdx != -1) return "'" + ch + "'";
- if (ch.toLowerCase() == ch) return ch.toUpperCase();
- return "Shift-" + ch.toUpperCase();
- }
- var word = [/\w/, /[^\w\s]/], bigWord = [/\S/];
- function findWord(line, pos, dir, regexps) {
- var stop = 0, next = -1;
- if (dir > 0) { stop = line.length; next = 0; }
- var start = stop, end = stop;
- // Find bounds of next one.
- outer: for (; pos != stop; pos += dir) {
- for (var i = 0; i < regexps.length; ++i) {
- if (regexps[i].test(line.charAt(pos + next))) {
- start = pos;
- for (; pos != stop; pos += dir) {
- if (!regexps[i].test(line.charAt(pos + next))) break;
+ function leaveVimMode(cm) {
+ cm.setOption('disableInput', false);
+ cm.off('cursorActivity', onCursorActivity);
+ CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
+ cm.state.vim = null;
+ }
+
+ function detachVimMap(cm, next) {
+ if (this == CodeMirror.keyMap.vim) {
+ CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor");
+ if (cm.getOption("inputStyle") == "contenteditable" && document.body.style.caretColor != null) {
+ disableFatCursorMark(cm);
+ cm.getInputField().style.caretColor = "";
+ }
+ }
+
+ if (!next || next.attach != attachVimMap)
+ leaveVimMode(cm);
+ }
+ function attachVimMap(cm, prev) {
+ if (this == CodeMirror.keyMap.vim) {
+ CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor");
+ if (cm.getOption("inputStyle") == "contenteditable" && document.body.style.caretColor != null) {
+ enableFatCursorMark(cm);
+ cm.getInputField().style.caretColor = "transparent";
+ }
+ }
+
+ if (!prev || prev.attach != attachVimMap)
+ enterVimMode(cm);
+ }
+
+ function updateFatCursorMark(cm) {
+ if (!cm.state.fatCursorMarks) return;
+ clearFatCursorMark(cm);
+ var ranges = cm.listSelections(), result = []
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i]
+ if (range.empty()) {
+ if (range.anchor.ch < cm.getLine(range.anchor.line).length) {
+ result.push(cm.markText(range.anchor, Pos(range.anchor.line, range.anchor.ch + 1),
+ {className: "cm-fat-cursor-mark"}))
+ } else {
+ var widget = document.createElement("span")
+ widget.textContent = "\u00a0"
+ widget.className = "cm-fat-cursor-mark"
+ result.push(cm.setBookmark(range.anchor, {widget: widget}))
}
- end = pos;
- break outer;
}
}
+ cm.state.fatCursorMarks = result;
}
- return {from: Math.min(start, end), to: Math.max(start, end)};
- }
- function moveToWord(cm, regexps, dir, times, where) {
- var cur = cm.getCursor();
- for (var i = 0; i < times; i++) {
- var line = cm.getLine(cur.line), startCh = cur.ch, word;
- while (true) {
- // If we're at start/end of line, start on prev/next respectivly
- if (cur.ch == line.length && dir > 0) {
- cur.line++;
- cur.ch = 0;
- line = cm.getLine(cur.line);
- } else if (cur.ch == 0 && dir < 0) {
- cur.line--;
- cur.ch = line.length;
- line = cm.getLine(cur.line);
- }
- if (!line) break;
-
- // On to the actual searching
- word = findWord(line, cur.ch, dir, regexps);
- cur.ch = word[where == "end" ? "to" : "from"];
- if (startCh == cur.ch && word.from != word.to) cur.ch = word[dir < 0 ? "from" : "to"];
- else break;
- }
+ function clearFatCursorMark(cm) {
+ var marks = cm.state.fatCursorMarks;
+ if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();
}
- return cur;
- }
- function joinLineNext(cm) {
- var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line);
- CodeMirror.commands.goLineEnd(cm);
- if (cur.line != cm.lineCount()) {
- CodeMirror.commands.goLineEnd(cm);
- cm.replaceSelection(" ", "end");
- CodeMirror.commands.delCharRight(cm);
+
+ function enableFatCursorMark(cm) {
+ cm.state.fatCursorMarks = [];
+ updateFatCursorMark(cm)
+ cm.on("cursorActivity", updateFatCursorMark)
}
- }
- function delTillMark(cm, cHar) {
- var i = mark[cHar];
- if (i === undefined) {
- // console.log("Mark not set"); // TODO - show in status bar
- return;
+
+ function disableFatCursorMark(cm) {
+ clearFatCursorMark(cm);
+ cm.off("cursorActivity", updateFatCursorMark);
+ // explicitly set fatCursorMarks to null because event listener above
+ // can be invoke after removing it, if off is called from operation
+ cm.state.fatCursorMarks = null;
}
- var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
- cm.setCursor(start);
- for (var c = start; c <= end; c++) {
- pushInBuffer("\n"+cm.getLine(start));
- cm.removeLine(start);
- }
- }
- function yankTillMark(cm, cHar) {
- var i = mark[cHar];
- if (i === undefined) {
- // console.log("Mark not set"); // TODO - show in status bar
- return;
- }
- var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
- for (var c = start; c <= end; c++) {
- pushInBuffer("\n"+cm.getLine(c));
- }
- cm.setCursor(start);
- }
- function goLineStartText(cm) {
- // Go to the start of the line where the text begins, or the end for whitespace-only lines
- var cur = cm.getCursor(), firstNonWS = cm.getLine(cur.line).search(/\S/);
- cm.setCursor(cur.line, firstNonWS == -1 ? line.length : firstNonWS, true);
- }
- function charIdxInLine(cm, cHar, motion_options) {
- // Search for cHar in line.
- // motion_options: {forward, inclusive}
- // If inclusive = true, include it too.
- // If forward = true, search forward, else search backwards.
- // If char is not found on this line, do nothing
- var cur = cm.getCursor(), line = cm.getLine(cur.line), idx;
- var ch = toLetter(cHar), mo = motion_options;
- if (mo.forward) {
- idx = line.indexOf(ch, cur.ch + 1);
- if (idx != -1 && mo.inclusive) idx += 1;
- } else {
- idx = line.lastIndexOf(ch, cur.ch);
- if (idx != -1 && !mo.inclusive) idx += 1;
- }
- return idx;
- }
-
- function moveTillChar(cm, cHar, motion_options) {
- // Move to cHar in line, as found by charIdxInLine.
- var idx = charIdxInLine(cm, cHar, motion_options), cur = cm.getCursor();
- if (idx != -1) cm.setCursor({line: cur.line, ch: idx});
- }
-
- function delTillChar(cm, cHar, motion_options) {
- // delete text in this line, untill cHar is met,
- // as found by charIdxInLine.
- // If char is not found on this line, do nothing
- var idx = charIdxInLine(cm, cHar, motion_options);
- var cur = cm.getCursor();
- if (idx !== -1) {
- if (motion_options.forward) {
- cm.replaceRange("", {line: cur.line, ch: cur.ch}, {line: cur.line, ch: idx});
- } else {
- cm.replaceRange("", {line: cur.line, ch: idx}, {line: cur.line, ch: cur.ch});
- }
- }
- }
-
- function enterInsertMode(cm) {
- // enter insert mode: switch mode and cursor
- popCount();
- cm.setOption("keyMap", "vim-insert");
- }
-
- function dialog(cm, text, shortText, f) {
- if (cm.openDialog) cm.openDialog(text, f);
- else f(prompt(shortText, ""));
- }
- function showAlert(cm, text) {
- var esc = text.replace(/[<&]/, function(ch) { return ch == "<" ? "<" : "&"; });
- if (cm.openDialog) cm.openDialog(esc + " ");
- else alert(text);
- }
-
- // main keymap
- var map = CodeMirror.keyMap.vim = {
- // Pipe (|); TODO: should be *screen* chars, so need a util function to turn tabs into spaces?
- "'|'": function(cm) {
- cm.setCursor(cm.getCursor().line, popCount() - 1, true);
- },
- "A": function(cm) {
- cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true);
- enterInsertMode(cm);
- },
- "Shift-A": function(cm) { CodeMirror.commands.goLineEnd(cm); enterInsertMode(cm);},
- "I": function(cm) { enterInsertMode(cm);},
- "Shift-I": function(cm) { goLineStartText(cm); enterInsertMode(cm);},
- "O": function(cm) {
- CodeMirror.commands.goLineEnd(cm);
- CodeMirror.commands.newlineAndIndent(cm);
- enterInsertMode(cm);
- },
- "Shift-O": function(cm) {
- CodeMirror.commands.goLineStart(cm);
- cm.replaceSelection("\n", "start");
- cm.indentLine(cm.getCursor().line);
- enterInsertMode(cm);
- },
- "G": function(cm) { cm.setOption("keyMap", "vim-prefix-g");},
- "Shift-D": function(cm) {
- // commented out verions works, but I left original, cause maybe
- // I don't know vim enouth to see what it does
- /* var cur = cm.getCursor();
- var f = {line: cur.line, ch: cur.ch}, t = {line: cur.line};
- pushInBuffer(cm.getRange(f, t));
- */
- emptyBuffer();
- mark["Shift-D"] = cm.getCursor(false).line;
- cm.setCursor(cm.getCursor(true).line);
- delTillMark(cm,"Shift-D"); mark = [];
- },
-
- "S": function (cm) {
- countTimes(function (_cm) {
- CodeMirror.commands.delCharRight(_cm);
- })(cm);
- enterInsertMode(cm);
- },
- "M": function(cm) {cm.setOption("keyMap", "vim-prefix-m"); mark = [];},
- "Y": function(cm) {cm.setOption("keyMap", "vim-prefix-y"); emptyBuffer(); yank = 0;},
- "Shift-Y": function(cm) {
- emptyBuffer();
- mark["Shift-D"] = cm.getCursor(false).line;
- cm.setCursor(cm.getCursor(true).line);
- yankTillMark(cm,"Shift-D"); mark = [];
- },
- "/": function(cm) {var f = CodeMirror.commands.find; f && f(cm); sdir = "f";},
- "'?'": function(cm) {
- var f = CodeMirror.commands.find;
- if (f) { f(cm); CodeMirror.commands.findPrev(cm); sdir = "r"; }
- },
- "N": function(cm) {
- var fn = CodeMirror.commands.findNext;
- if (fn) sdir != "r" ? fn(cm) : CodeMirror.commands.findPrev(cm);
- },
- "Shift-N": function(cm) {
- var fn = CodeMirror.commands.findNext;
- if (fn) sdir != "r" ? CodeMirror.commands.findPrev(cm) : fn.findNext(cm);
- },
- "Shift-G": function(cm) {
- count == "" ? cm.setCursor(cm.lineCount()) : cm.setCursor(parseInt(count, 10)-1);
- popCount();
- CodeMirror.commands.goLineStart(cm);
- },
- "':'": function(cm) {
- var exModeDialog = ': ';
- dialog(cm, exModeDialog, ':', function(command) {
- if (command.match(/^\d+$/)) {
- cm.setCursor(command - 1, cm.getCursor().ch);
- } else {
- showAlert(cm, "Bad command: " + command);
- }
- });
- },
- nofallthrough: true, style: "fat-cursor"
- };
-
- // standard mode switching
- iterList(["d", "t", "T", "f", "F", "c", "r"],
- function (ch) {
- CodeMirror.keyMap.vim[toCombo(ch)] = function (cm) {
- cm.setOption("keyMap", "vim-prefix-" + ch);
- emptyBuffer();
- };
- });
-
- function addCountBindings(keyMap) {
- // Add bindings for number keys
- keyMap["0"] = function(cm) {
- count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm);
- };
- for (var i = 1; i < 10; ++i) keyMap[i] = pushCountDigit(i);
- }
- addCountBindings(CodeMirror.keyMap.vim);
-
- // main num keymap
- // Add bindings that are influenced by number keys
- iterObj({
- "Left": "goColumnLeft", "Right": "goColumnRight",
- "Down": "goLineDown", "Up": "goLineUp", "Backspace": "goCharLeft",
- "Space": "goCharRight",
- "X": function(cm) {CodeMirror.commands.delCharRight(cm);},
- "P": function(cm) {
- var cur = cm.getCursor().line;
- if (buf!= "") {
- if (buf[0] == "\n") CodeMirror.commands.goLineEnd(cm);
- cm.replaceRange(buf, cm.getCursor());
- }
- },
- "Shift-X": function(cm) {CodeMirror.commands.delCharLeft(cm);},
- "Shift-J": function(cm) {joinLineNext(cm);},
- "Shift-P": function(cm) {
- var cur = cm.getCursor().line;
- if (buf!= "") {
- CodeMirror.commands.goLineUp(cm);
- CodeMirror.commands.goLineEnd(cm);
- cm.replaceSelection(buf, "end");
- }
- cm.setCursor(cur+1);
- },
- "'~'": function(cm) {
- var cur = cm.getCursor(), cHar = cm.getRange({line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1});
- cHar = cHar != cHar.toLowerCase() ? cHar.toLowerCase() : cHar.toUpperCase();
- cm.replaceRange(cHar, {line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1});
- cm.setCursor(cur.line, cur.ch+1);
- },
- "Ctrl-B": function(cm) {CodeMirror.commands.goPageUp(cm);},
- "Ctrl-F": function(cm) {CodeMirror.commands.goPageDown(cm);},
- "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
- "U": "undo", "Ctrl-R": "redo"
- }, function(key, cmd) { map[key] = countTimes(cmd); });
-
- // empty key maps
- iterList([
- "vim-prefix-d'",
- "vim-prefix-y'",
- "vim-prefix-df",
- "vim-prefix-dF",
- "vim-prefix-dt",
- "vim-prefix-dT",
- "vim-prefix-c",
- "vim-prefix-cf",
- "vim-prefix-cF",
- "vim-prefix-ct",
- "vim-prefix-cT",
- "vim-prefix-",
- "vim-prefix-f",
- "vim-prefix-F",
- "vim-prefix-t",
- "vim-prefix-T",
- "vim-prefix-r",
- "vim-prefix-m"
- ],
- function (prefix) {
- CodeMirror.keyMap[prefix] = {
- auto: "vim",
- nofallthrough: true,
- style: "fat-cursor"
- };
- });
-
- CodeMirror.keyMap["vim-prefix-g"] = {
- "E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, word, -1, 1, "start"));}),
- "Shift-E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, bigWord, -1, 1, "start"));}),
- "G": function (cm) { cm.setCursor({line: 0, ch: cm.getCursor().ch});},
- auto: "vim", nofallthrough: true, style: "fat-cursor"
- };
-
- CodeMirror.keyMap["vim-prefix-d"] = {
- "D": countTimes(function(cm) {
- pushInBuffer("\n"+cm.getLine(cm.getCursor().line));
- cm.removeLine(cm.getCursor().line);
- cm.setOption("keyMap", "vim");
- }),
- "'": function(cm) {
- cm.setOption("keyMap", "vim-prefix-d'");
- emptyBuffer();
- },
- "B": function(cm) {
- var cur = cm.getCursor();
- var line = cm.getLine(cur.line);
- var index = line.lastIndexOf(" ", cur.ch);
-
- pushInBuffer(line.substring(index, cur.ch));
- cm.replaceRange("", {line: cur.line, ch: index}, cur);
- cm.setOption("keyMap", "vim");
- },
- nofallthrough: true, style: "fat-cursor"
- };
- // FIXME - does not work for bindings like "d3e"
- addCountBindings(CodeMirror.keyMap["vim-prefix-d"]);
-
- CodeMirror.keyMap["vim-prefix-c"] = {
- "B": function (cm) {
- countTimes("delWordLeft")(cm);
- enterInsertMode(cm);
- },
- "C": function (cm) {
- iterTimes(function (i, last) {
- CodeMirror.commands.deleteLine(cm);
- if (i) {
- CodeMirror.commands.delCharRight(cm);
- if (last) CodeMirror.commands.deleteLine(cm);
- }
- });
- enterInsertMode(cm);
- },
- nofallthrough: true, style: "fat-cursor"
- };
-
- iterList(["vim-prefix-d", "vim-prefix-c", "vim-prefix-"], function (prefix) {
- iterList(["f", "F", "T", "t"],
- function (ch) {
- CodeMirror.keyMap[prefix][toCombo(ch)] = function (cm) {
- cm.setOption("keyMap", prefix + ch);
- emptyBuffer();
- };
- });
- });
-
- var MOTION_OPTIONS = {
- "t": {inclusive: false, forward: true},
- "f": {inclusive: true, forward: true},
- "T": {inclusive: false, forward: false},
- "F": {inclusive: true, forward: false}
- };
-
- function setupPrefixBindingForKey(m) {
- CodeMirror.keyMap["vim-prefix-m"][m] = function(cm) {
- mark[m] = cm.getCursor().line;
- };
- CodeMirror.keyMap["vim-prefix-d'"][m] = function(cm) {
- delTillMark(cm,m);
- };
- CodeMirror.keyMap["vim-prefix-y'"][m] = function(cm) {
- yankTillMark(cm,m);
- };
- CodeMirror.keyMap["vim-prefix-r"][m] = function (cm) {
- var cur = cm.getCursor();
- cm.replaceRange(toLetter(m),
- {line: cur.line, ch: cur.ch},
- {line: cur.line, ch: cur.ch + 1});
- CodeMirror.commands.goColumnLeft(cm);
- };
- // all commands, related to motions till char in line
- iterObj(MOTION_OPTIONS, function (ch, options) {
- CodeMirror.keyMap["vim-prefix-" + ch][m] = function(cm) {
- moveTillChar(cm, m, options);
- };
- CodeMirror.keyMap["vim-prefix-d" + ch][m] = function(cm) {
- delTillChar(cm, m, options);
- };
- CodeMirror.keyMap["vim-prefix-c" + ch][m] = function(cm) {
- delTillChar(cm, m, options);
- enterInsertMode(cm);
- };
+ // Deprecated, simply setting the keymap works again.
+ CodeMirror.defineOption('vimMode', false, function(cm, val, prev) {
+ if (val && cm.getOption("keyMap") != "vim")
+ cm.setOption("keyMap", "vim");
+ else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap")))
+ cm.setOption("keyMap", "default");
});
- }
- for (var i = 65; i < 65 + 26; i++) { // uppercase alphabet char codes
- var ch = String.fromCharCode(i);
- setupPrefixBindingForKey(toCombo(ch));
- setupPrefixBindingForKey(toCombo(ch.toLowerCase()));
- }
- for (var i = 0; i < SPECIAL_SYMBOLS.length; ++i) {
- setupPrefixBindingForKey(toCombo(SPECIAL_SYMBOLS.charAt(i)));
- }
- setupPrefixBindingForKey("Space");
- CodeMirror.keyMap["vim-prefix-y"] = {
- "Y": countTimes(function(cm) { pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++; }),
- "'": function(cm) {cm.setOption("keyMap", "vim-prefix-y'"); emptyBuffer();},
- nofallthrough: true, style: "fat-cursor"
- };
-
- CodeMirror.keyMap["vim-insert"] = {
- // TODO: override navigation keys so that Esc will cancel automatic indentation from o, O, i_
- "Esc": function(cm) {
- cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
- cm.setOption("keyMap", "vim");
- },
- "Ctrl-N": "autocomplete",
- "Ctrl-P": "autocomplete",
- fallthrough: ["default"]
- };
-
- function findMatchedSymbol(cm, cur, symb) {
- var line = cur.line;
- var symb = symb ? symb : cm.getLine(line)[cur.ch];
-
- // Are we at the opening or closing char
- var forwards = ['(', '[', '{'].indexOf(symb) != -1;
-
- var reverseSymb = (function(sym) {
- switch (sym) {
- case '(' : return ')';
- case '[' : return ']';
- case '{' : return '}';
- case ')' : return '(';
- case ']' : return '[';
- case '}' : return '{';
- default : return null;
+ function cmKey(key, cm) {
+ if (!cm) { return undefined; }
+ if (this[key]) { return this[key]; }
+ var vimKey = cmKeyToVimKey(key);
+ if (!vimKey) {
+ return false;
}
- })(symb);
+ var cmd = CodeMirror.Vim.findKey(cm, vimKey);
+ if (typeof cmd == 'function') {
+ CodeMirror.signal(cm, 'vim-keypress', vimKey);
+ }
+ return cmd;
+ }
- // Couldn't find a matching symbol, abort
- if (reverseSymb == null) return cur;
+ var modifiers = {'Shift': 'S', 'Ctrl': 'C', 'Alt': 'A', 'Cmd': 'D', 'Mod': 'A'};
+ var specialKeys = {Enter:'CR',Backspace:'BS',Delete:'Del',Insert:'Ins'};
+ function cmKeyToVimKey(key) {
+ if (key.charAt(0) == '\'') {
+ // Keypress character binding of format "'a'"
+ return key.charAt(1);
+ }
+ var pieces = key.split(/-(?!$)/);
+ var lastPiece = pieces[pieces.length - 1];
+ if (pieces.length == 1 && pieces[0].length == 1) {
+ // No-modifier bindings use literal character bindings above. Skip.
+ return false;
+ } else if (pieces.length == 2 && pieces[0] == 'Shift' && lastPiece.length == 1) {
+ // Ignore Shift+char bindings as they should be handled by literal character.
+ return false;
+ }
+ var hasCharacter = false;
+ for (var i = 0; i < pieces.length; i++) {
+ var piece = pieces[i];
+ if (piece in modifiers) { pieces[i] = modifiers[piece]; }
+ else { hasCharacter = true; }
+ if (piece in specialKeys) { pieces[i] = specialKeys[piece]; }
+ }
+ if (!hasCharacter) {
+ // Vim does not support modifier only keys.
+ return false;
+ }
+ // TODO: Current bindings expect the character to be lower case, but
+ // it looks like vim key notation uses upper case.
+ if (isUpperCase(lastPiece)) {
+ pieces[pieces.length - 1] = lastPiece.toLowerCase();
+ }
+ return '<' + pieces.join('-') + '>';
+ }
- // Tracking our imbalance in open/closing symbols. An opening symbol wii be
- // the first thing we pick up if moving forward, this isn't true moving backwards
- var disBal = forwards ? 0 : 1;
+ function getOnPasteFn(cm) {
+ var vim = cm.state.vim;
+ if (!vim.onPasteFn) {
+ vim.onPasteFn = function() {
+ if (!vim.insertMode) {
+ cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
+ actions.enterInsertMode(cm, {}, vim);
+ }
+ };
+ }
+ return vim.onPasteFn;
+ }
- while (true) {
- if (line == cur.line) {
- // First pass, do some special stuff
- var currLine = forwards ? cm.getLine(line).substr(cur.ch).split('') : cm.getLine(line).substr(0,cur.ch).split('').reverse();
+ var numberRegex = /[\d]/;
+ var wordCharTest = [CodeMirror.isWordChar, function(ch) {
+ return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch);
+ }], bigWordCharTest = [function(ch) {
+ return /\S/.test(ch);
+ }];
+ function makeKeyRange(start, size) {
+ var keys = [];
+ for (var i = start; i < start + size; i++) {
+ keys.push(String.fromCharCode(i));
+ }
+ return keys;
+ }
+ var upperCaseAlphabet = makeKeyRange(65, 26);
+ var lowerCaseAlphabet = makeKeyRange(97, 26);
+ var numbers = makeKeyRange(48, 10);
+ var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
+ var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"', '.', ':', '/']);
+
+ function isLine(cm, line) {
+ return line >= cm.firstLine() && line <= cm.lastLine();
+ }
+ function isLowerCase(k) {
+ return (/^[a-z]$/).test(k);
+ }
+ function isMatchableSymbol(k) {
+ return '()[]{}'.indexOf(k) != -1;
+ }
+ function isNumber(k) {
+ return numberRegex.test(k);
+ }
+ function isUpperCase(k) {
+ return (/^[A-Z]$/).test(k);
+ }
+ function isWhiteSpaceString(k) {
+ return (/^\s*$/).test(k);
+ }
+ function isEndOfSentenceSymbol(k) {
+ return '.?!'.indexOf(k) != -1;
+ }
+ function inArray(val, arr) {
+ for (var i = 0; i < arr.length; i++) {
+ if (arr[i] == val) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ var options = {};
+ function defineOption(name, defaultValue, type, aliases, callback) {
+ if (defaultValue === undefined && !callback) {
+ throw Error('defaultValue is required unless callback is provided');
+ }
+ if (!type) { type = 'string'; }
+ options[name] = {
+ type: type,
+ defaultValue: defaultValue,
+ callback: callback
+ };
+ if (aliases) {
+ for (var i = 0; i < aliases.length; i++) {
+ options[aliases[i]] = options[name];
+ }
+ }
+ if (defaultValue) {
+ setOption(name, defaultValue);
+ }
+ }
+
+ function setOption(name, value, cm, cfg) {
+ var option = options[name];
+ cfg = cfg || {};
+ var scope = cfg.scope;
+ if (!option) {
+ return new Error('Unknown option: ' + name);
+ }
+ if (option.type == 'boolean') {
+ if (value && value !== true) {
+ return new Error('Invalid argument: ' + name + '=' + value);
+ } else if (value !== false) {
+ // Boolean options are set to true if value is not defined.
+ value = true;
+ }
+ }
+ if (option.callback) {
+ if (scope !== 'local') {
+ option.callback(value, undefined);
+ }
+ if (scope !== 'global' && cm) {
+ option.callback(value, cm);
+ }
} else {
- var currLine = forwards ? cm.getLine(line).split('') : cm.getLine(line).split('').reverse();
+ if (scope !== 'local') {
+ option.value = option.type == 'boolean' ? !!value : value;
+ }
+ if (scope !== 'global' && cm) {
+ cm.state.vim.options[name] = {value: value};
+ }
+ }
+ }
+
+ function getOption(name, cm, cfg) {
+ var option = options[name];
+ cfg = cfg || {};
+ var scope = cfg.scope;
+ if (!option) {
+ return new Error('Unknown option: ' + name);
+ }
+ if (option.callback) {
+ var local = cm && option.callback(undefined, cm);
+ if (scope !== 'global' && local !== undefined) {
+ return local;
+ }
+ if (scope !== 'local') {
+ return option.callback();
+ }
+ return;
+ } else {
+ var local = (scope !== 'global') && (cm && cm.state.vim.options[name]);
+ return (local || (scope !== 'local') && option || {}).value;
+ }
+ }
+
+ defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) {
+ // Option is local. Do nothing for global.
+ if (cm === undefined) {
+ return;
+ }
+ // The 'filetype' option proxies to the CodeMirror 'mode' option.
+ if (name === undefined) {
+ var mode = cm.getOption('mode');
+ return mode == 'null' ? '' : mode;
+ } else {
+ var mode = name == '' ? 'null' : name;
+ cm.setOption('mode', mode);
+ }
+ });
+
+ var createCircularJumpList = function() {
+ var size = 100;
+ var pointer = -1;
+ var head = 0;
+ var tail = 0;
+ var buffer = new Array(size);
+ function add(cm, oldCur, newCur) {
+ var current = pointer % size;
+ var curMark = buffer[current];
+ function useNextSlot(cursor) {
+ var next = ++pointer % size;
+ var trashMark = buffer[next];
+ if (trashMark) {
+ trashMark.clear();
+ }
+ buffer[next] = cm.setBookmark(cursor);
+ }
+ if (curMark) {
+ var markPos = curMark.find();
+ // avoid recording redundant cursor position
+ if (markPos && !cursorEqual(markPos, oldCur)) {
+ useNextSlot(oldCur);
+ }
+ } else {
+ useNextSlot(oldCur);
+ }
+ useNextSlot(newCur);
+ head = pointer;
+ tail = pointer - size + 1;
+ if (tail < 0) {
+ tail = 0;
+ }
+ }
+ function move(cm, offset) {
+ pointer += offset;
+ if (pointer > head) {
+ pointer = head;
+ } else if (pointer < tail) {
+ pointer = tail;
+ }
+ var mark = buffer[(size + pointer) % size];
+ // skip marks that are temporarily removed from text buffer
+ if (mark && !mark.find()) {
+ var inc = offset > 0 ? 1 : -1;
+ var newCur;
+ var oldCur = cm.getCursor();
+ do {
+ pointer += inc;
+ mark = buffer[(size + pointer) % size];
+ // skip marks that are the same as current position
+ if (mark &&
+ (newCur = mark.find()) &&
+ !cursorEqual(oldCur, newCur)) {
+ break;
+ }
+ } while (pointer < head && pointer > tail);
+ }
+ return mark;
+ }
+ return {
+ cachedCursor: undefined, //used for # and * jumps
+ add: add,
+ move: move
+ };
+ };
+
+ // Returns an object to track the changes associated insert mode. It
+ // clones the object that is passed in, or creates an empty object one if
+ // none is provided.
+ var createInsertModeChanges = function(c) {
+ if (c) {
+ // Copy construction
+ return {
+ changes: c.changes,
+ expectCursorActivityForChange: c.expectCursorActivityForChange
+ };
+ }
+ return {
+ // Change list
+ changes: [],
+ // Set to true on change, false on cursorActivity.
+ expectCursorActivityForChange: false
+ };
+ };
+
+ function MacroModeState() {
+ this.latestRegister = undefined;
+ this.isPlaying = false;
+ this.isRecording = false;
+ this.replaySearchQueries = [];
+ this.onRecordingDone = undefined;
+ this.lastInsertModeChanges = createInsertModeChanges();
+ }
+ MacroModeState.prototype = {
+ exitMacroRecordMode: function() {
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.onRecordingDone) {
+ macroModeState.onRecordingDone(); // close dialog
+ }
+ macroModeState.onRecordingDone = undefined;
+ macroModeState.isRecording = false;
+ },
+ enterMacroRecordMode: function(cm, registerName) {
+ var register =
+ vimGlobalState.registerController.getRegister(registerName);
+ if (register) {
+ register.clear();
+ this.latestRegister = registerName;
+ if (cm.openDialog) {
+ this.onRecordingDone = cm.openDialog(
+ '(recording)['+registerName+']', null, {bottom:true});
+ }
+ this.isRecording = true;
+ }
+ }
+ };
+
+ function maybeInitVimState(cm) {
+ if (!cm.state.vim) {
+ // Store instance state in the CodeMirror object.
+ cm.state.vim = {
+ inputState: new InputState(),
+ // Vim's input state that triggered the last edit, used to repeat
+ // motions and operators with '.'.
+ lastEditInputState: undefined,
+ // Vim's action command before the last edit, used to repeat actions
+ // with '.' and insert mode repeat.
+ lastEditActionCommand: undefined,
+ // When using jk for navigation, if you move from a longer line to a
+ // shorter line, the cursor may clip to the end of the shorter line.
+ // If j is pressed again and cursor goes to the next line, the
+ // cursor should go back to its horizontal position on the longer
+ // line if it can. This is to keep track of the horizontal position.
+ lastHPos: -1,
+ // Doing the same with screen-position for gj/gk
+ lastHSPos: -1,
+ // The last motion command run. Cleared if a non-motion command gets
+ // executed in between.
+ lastMotion: null,
+ marks: {},
+ // Mark for rendering fake cursor for visual mode.
+ fakeCursor: null,
+ insertMode: false,
+ // Repeat count for changes made in insert mode, triggered by key
+ // sequences like 3,i. Only exists when insertMode is true.
+ insertModeRepeat: undefined,
+ visualMode: false,
+ // If we are in visual line mode. No effect if visualMode is false.
+ visualLine: false,
+ visualBlock: false,
+ lastSelection: null,
+ lastPastedText: null,
+ sel: {},
+ // Buffer-local/window-local values of vim options.
+ options: {}
+ };
+ }
+ return cm.state.vim;
+ }
+ var vimGlobalState;
+ function resetVimGlobalState() {
+ vimGlobalState = {
+ // The current search query.
+ searchQuery: null,
+ // Whether we are searching backwards.
+ searchIsReversed: false,
+ // Replace part of the last substituted pattern
+ lastSubstituteReplacePart: undefined,
+ jumpList: createCircularJumpList(),
+ macroModeState: new MacroModeState,
+ // Recording latest f, t, F or T motion command.
+ lastCharacterSearch: {increment:0, forward:true, selectedCharacter:''},
+ registerController: new RegisterController({}),
+ // search history buffer
+ searchHistoryController: new HistoryController(),
+ // ex Command history buffer
+ exCommandHistoryController : new HistoryController()
+ };
+ for (var optionName in options) {
+ var option = options[optionName];
+ option.value = option.defaultValue;
+ }
+ }
+
+ var lastInsertModeKeyTimer;
+ var vimApi= {
+ buildKeyMap: function() {
+ // TODO: Convert keymap into dictionary format for fast lookup.
+ },
+ // Testing hook, though it might be useful to expose the register
+ // controller anyways.
+ getRegisterController: function() {
+ return vimGlobalState.registerController;
+ },
+ // Testing hook.
+ resetVimGlobalState_: resetVimGlobalState,
+
+ // Testing hook.
+ getVimGlobalState_: function() {
+ return vimGlobalState;
+ },
+
+ // Testing hook.
+ maybeInitVimState_: maybeInitVimState,
+
+ suppressErrorLogging: false,
+
+ InsertModeKey: InsertModeKey,
+ map: function(lhs, rhs, ctx) {
+ // Add user defined key bindings.
+ exCommandDispatcher.map(lhs, rhs, ctx);
+ },
+ unmap: function(lhs, ctx) {
+ exCommandDispatcher.unmap(lhs, ctx);
+ },
+ // Non-recursive map function.
+ // NOTE: This will not create mappings to key maps that aren't present
+ // in the default key map. See TODO at bottom of function.
+ noremap: function(lhs, rhs, ctx) {
+ function toCtxArray(ctx) {
+ return ctx ? [ctx] : ['normal', 'insert', 'visual'];
+ }
+ var ctxsToMap = toCtxArray(ctx);
+ // Look through all actual defaults to find a map candidate.
+ var actualLength = defaultKeymap.length, origLength = defaultKeymapLength;
+ for (var i = actualLength - origLength;
+ i < actualLength && ctxsToMap.length;
+ i++) {
+ var mapping = defaultKeymap[i];
+ // Omit mappings that operate in the wrong context(s) and those of invalid type.
+ if (mapping.keys == rhs &&
+ (!ctx || !mapping.context || mapping.context === ctx) &&
+ mapping.type.substr(0, 2) !== 'ex' &&
+ mapping.type.substr(0, 3) !== 'key') {
+ // Make a shallow copy of the original keymap entry.
+ var newMapping = {};
+ for (var key in mapping) {
+ newMapping[key] = mapping[key];
+ }
+ // Modify it point to the new mapping with the proper context.
+ newMapping.keys = lhs;
+ if (ctx && !newMapping.context) {
+ newMapping.context = ctx;
+ }
+ // Add it to the keymap with a higher priority than the original.
+ this._mapCommand(newMapping);
+ // Record the mapped contexts as complete.
+ var mappedCtxs = toCtxArray(mapping.context);
+ ctxsToMap = ctxsToMap.filter(function(el) { return mappedCtxs.indexOf(el) === -1; });
+ }
+ }
+ // TODO: Create non-recursive keyToKey mappings for the unmapped contexts once those exist.
+ },
+ // Remove all user-defined mappings for the provided context.
+ mapclear: function(ctx) {
+ // Partition the existing keymap into user-defined and true defaults.
+ var actualLength = defaultKeymap.length,
+ origLength = defaultKeymapLength;
+ var userKeymap = defaultKeymap.slice(0, actualLength - origLength);
+ defaultKeymap = defaultKeymap.slice(actualLength - origLength);
+ if (ctx) {
+ // If a specific context is being cleared, we need to keep mappings
+ // from all other contexts.
+ for (var i = userKeymap.length - 1; i >= 0; i--) {
+ var mapping = userKeymap[i];
+ if (ctx !== mapping.context) {
+ if (mapping.context) {
+ this._mapCommand(mapping);
+ } else {
+ // `mapping` applies to all contexts so create keymap copies
+ // for each context except the one being cleared.
+ var contexts = ['normal', 'insert', 'visual'];
+ for (var j in contexts) {
+ if (contexts[j] !== ctx) {
+ var newMapping = {};
+ for (var key in mapping) {
+ newMapping[key] = mapping[key];
+ }
+ newMapping.context = contexts[j];
+ this._mapCommand(newMapping);
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ // TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace
+ // them, or somehow make them work with the existing CodeMirror setOption/getOption API.
+ setOption: setOption,
+ getOption: getOption,
+ defineOption: defineOption,
+ defineEx: function(name, prefix, func){
+ if (!prefix) {
+ prefix = name;
+ } else if (name.indexOf(prefix) !== 0) {
+ throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');
+ }
+ exCommands[name]=func;
+ exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
+ },
+ handleKey: function (cm, key, origin) {
+ var command = this.findKey(cm, key, origin);
+ if (typeof command === 'function') {
+ return command();
+ }
+ },
+ /**
+ * This is the outermost function called by CodeMirror, after keys have
+ * been mapped to their Vim equivalents.
+ *
+ * Finds a command based on the key (and cached keys if there is a
+ * multi-key sequence). Returns `undefined` if no key is matched, a noop
+ * function if a partial match is found (multi-key), and a function to
+ * execute the bound command if a a key is matched. The function always
+ * returns true.
+ */
+ findKey: function(cm, key, origin) {
+ var vim = maybeInitVimState(cm);
+ function handleMacroRecording() {
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.isRecording) {
+ if (key == 'q') {
+ macroModeState.exitMacroRecordMode();
+ clearInputState(cm);
+ return true;
+ }
+ if (origin != 'mapping') {
+ logKey(macroModeState, key);
+ }
+ }
+ }
+ function handleEsc() {
+ if (key == '') {
+ // Clear input state and get back to normal mode.
+ clearInputState(cm);
+ if (vim.visualMode) {
+ exitVisualMode(cm);
+ } else if (vim.insertMode) {
+ exitInsertMode(cm);
+ }
+ return true;
+ }
+ }
+ function doKeyToKey(keys) {
+ // TODO: prevent infinite recursion.
+ var match;
+ while (keys) {
+ // Pull off one command key, which is either a single character
+ // or a special sequence wrapped in '<' and '>', e.g. ''.
+ match = (/<\w+-.+?>|<\w+>|./).exec(keys);
+ key = match[0];
+ keys = keys.substring(match.index + key.length);
+ CodeMirror.Vim.handleKey(cm, key, 'mapping');
+ }
+ }
+
+ function handleKeyInsertMode() {
+ if (handleEsc()) { return true; }
+ var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
+ var keysAreChars = key.length == 1;
+ var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');
+ // Need to check all key substrings in insert mode.
+ while (keys.length > 1 && match.type != 'full') {
+ var keys = vim.inputState.keyBuffer = keys.slice(1);
+ var thisMatch = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');
+ if (thisMatch.type != 'none') { match = thisMatch; }
+ }
+ if (match.type == 'none') { clearInputState(cm); return false; }
+ else if (match.type == 'partial') {
+ if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
+ lastInsertModeKeyTimer = window.setTimeout(
+ function() { if (vim.insertMode && vim.inputState.keyBuffer) { clearInputState(cm); } },
+ getOption('insertModeEscKeysTimeout'));
+ return !keysAreChars;
+ }
+
+ if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
+ if (keysAreChars) {
+ var selections = cm.listSelections();
+ for (var i = 0; i < selections.length; i++) {
+ var here = selections[i].head;
+ cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input');
+ }
+ vimGlobalState.macroModeState.lastInsertModeChanges.changes.pop();
+ }
+ clearInputState(cm);
+ return match.command;
+ }
+
+ function handleKeyNonInsertMode() {
+ if (handleMacroRecording() || handleEsc()) { return true; }
+
+ var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
+ if (/^[1-9]\d*$/.test(keys)) { return true; }
+
+ var keysMatcher = /^(\d*)(.*)$/.exec(keys);
+ if (!keysMatcher) { clearInputState(cm); return false; }
+ var context = vim.visualMode ? 'visual' :
+ 'normal';
+ var match = commandDispatcher.matchCommand(keysMatcher[2] || keysMatcher[1], defaultKeymap, vim.inputState, context);
+ if (match.type == 'none') { clearInputState(cm); return false; }
+ else if (match.type == 'partial') { return true; }
+
+ vim.inputState.keyBuffer = '';
+ var keysMatcher = /^(\d*)(.*)$/.exec(keys);
+ if (keysMatcher[1] && keysMatcher[1] != '0') {
+ vim.inputState.pushRepeatDigit(keysMatcher[1]);
+ }
+ return match.command;
+ }
+
+ var command;
+ if (vim.insertMode) { command = handleKeyInsertMode(); }
+ else { command = handleKeyNonInsertMode(); }
+ if (command === false) {
+ return !vim.insertMode && key.length === 1 ? function() { return true; } : undefined;
+ } else if (command === true) {
+ // TODO: Look into using CodeMirror's multi-key handling.
+ // Return no-op since we are caching the key. Counts as handled, but
+ // don't want act on it just yet.
+ return function() { return true; };
+ } else {
+ return function() {
+ return cm.operation(function() {
+ cm.curOp.isVimOp = true;
+ try {
+ if (command.type == 'keyToKey') {
+ doKeyToKey(command.toKeys);
+ } else {
+ commandDispatcher.processCommand(cm, vim, command);
+ }
+ } catch (e) {
+ // clear VIM state in case it's in a bad state.
+ cm.state.vim = undefined;
+ maybeInitVimState(cm);
+ if (!CodeMirror.Vim.suppressErrorLogging) {
+ console['log'](e);
+ }
+ throw e;
+ }
+ return true;
+ });
+ };
+ }
+ },
+ handleEx: function(cm, input) {
+ exCommandDispatcher.processCommand(cm, input);
+ },
+
+ defineMotion: defineMotion,
+ defineAction: defineAction,
+ defineOperator: defineOperator,
+ mapCommand: mapCommand,
+ _mapCommand: _mapCommand,
+
+ defineRegister: defineRegister,
+
+ exitVisualMode: exitVisualMode,
+ exitInsertMode: exitInsertMode
+ };
+
+ // Represents the current input state.
+ function InputState() {
+ this.prefixRepeat = [];
+ this.motionRepeat = [];
+
+ this.operator = null;
+ this.operatorArgs = null;
+ this.motion = null;
+ this.motionArgs = null;
+ this.keyBuffer = []; // For matching multi-key commands.
+ this.registerName = null; // Defaults to the unnamed register.
+ }
+ InputState.prototype.pushRepeatDigit = function(n) {
+ if (!this.operator) {
+ this.prefixRepeat = this.prefixRepeat.concat(n);
+ } else {
+ this.motionRepeat = this.motionRepeat.concat(n);
+ }
+ };
+ InputState.prototype.getRepeat = function() {
+ var repeat = 0;
+ if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) {
+ repeat = 1;
+ if (this.prefixRepeat.length > 0) {
+ repeat *= parseInt(this.prefixRepeat.join(''), 10);
+ }
+ if (this.motionRepeat.length > 0) {
+ repeat *= parseInt(this.motionRepeat.join(''), 10);
+ }
+ }
+ return repeat;
+ };
+
+ function clearInputState(cm, reason) {
+ cm.state.vim.inputState = new InputState();
+ CodeMirror.signal(cm, 'vim-command-done', reason);
+ }
+
+ /*
+ * Register stores information about copy and paste registers. Besides
+ * text, a register must store whether it is linewise (i.e., when it is
+ * pasted, should it insert itself into a new line, or should the text be
+ * inserted at the cursor position.)
+ */
+ function Register(text, linewise, blockwise) {
+ this.clear();
+ this.keyBuffer = [text || ''];
+ this.insertModeChanges = [];
+ this.searchQueries = [];
+ this.linewise = !!linewise;
+ this.blockwise = !!blockwise;
+ }
+ Register.prototype = {
+ setText: function(text, linewise, blockwise) {
+ this.keyBuffer = [text || ''];
+ this.linewise = !!linewise;
+ this.blockwise = !!blockwise;
+ },
+ pushText: function(text, linewise) {
+ // if this register has ever been set to linewise, use linewise.
+ if (linewise) {
+ if (!this.linewise) {
+ this.keyBuffer.push('\n');
+ }
+ this.linewise = true;
+ }
+ this.keyBuffer.push(text);
+ },
+ pushInsertModeChanges: function(changes) {
+ this.insertModeChanges.push(createInsertModeChanges(changes));
+ },
+ pushSearchQuery: function(query) {
+ this.searchQueries.push(query);
+ },
+ clear: function() {
+ this.keyBuffer = [];
+ this.insertModeChanges = [];
+ this.searchQueries = [];
+ this.linewise = false;
+ },
+ toString: function() {
+ return this.keyBuffer.join('');
+ }
+ };
+
+ /**
+ * Defines an external register.
+ *
+ * The name should be a single character that will be used to reference the register.
+ * The register should support setText, pushText, clear, and toString(). See Register
+ * for a reference implementation.
+ */
+ function defineRegister(name, register) {
+ var registers = vimGlobalState.registerController.registers;
+ if (!name || name.length != 1) {
+ throw Error('Register name must be 1 character');
+ }
+ if (registers[name]) {
+ throw Error('Register already defined ' + name);
+ }
+ registers[name] = register;
+ validRegisters.push(name);
+ }
+
+ /*
+ * vim registers allow you to keep many independent copy and paste buffers.
+ * See http://usevim.com/2012/04/13/registers/ for an introduction.
+ *
+ * RegisterController keeps the state of all the registers. An initial
+ * state may be passed in. The unnamed register '"' will always be
+ * overridden.
+ */
+ function RegisterController(registers) {
+ this.registers = registers;
+ this.unnamedRegister = registers['"'] = new Register();
+ registers['.'] = new Register();
+ registers[':'] = new Register();
+ registers['/'] = new Register();
+ }
+ RegisterController.prototype = {
+ pushText: function(registerName, operator, text, linewise, blockwise) {
+ if (linewise && text.charAt(text.length - 1) !== '\n'){
+ text += '\n';
+ }
+ // Lowercase and uppercase registers refer to the same register.
+ // Uppercase just means append.
+ var register = this.isValidRegister(registerName) ?
+ this.getRegister(registerName) : null;
+ // if no register/an invalid register was specified, things go to the
+ // default registers
+ if (!register) {
+ switch (operator) {
+ case 'yank':
+ // The 0 register contains the text from the most recent yank.
+ this.registers['0'] = new Register(text, linewise, blockwise);
+ break;
+ case 'delete':
+ case 'change':
+ if (text.indexOf('\n') == -1) {
+ // Delete less than 1 line. Update the small delete register.
+ this.registers['-'] = new Register(text, linewise);
+ } else {
+ // Shift down the contents of the numbered registers and put the
+ // deleted text into register 1.
+ this.shiftNumericRegisters_();
+ this.registers['1'] = new Register(text, linewise);
+ }
+ break;
+ }
+ // Make sure the unnamed register is set to what just happened
+ this.unnamedRegister.setText(text, linewise, blockwise);
+ return;
+ }
+
+ // If we've gotten to this point, we've actually specified a register
+ var append = isUpperCase(registerName);
+ if (append) {
+ register.pushText(text, linewise);
+ } else {
+ register.setText(text, linewise, blockwise);
+ }
+ // The unnamed register always has the same value as the last used
+ // register.
+ this.unnamedRegister.setText(register.toString(), linewise);
+ },
+ // Gets the register named @name. If one of @name doesn't already exist,
+ // create it. If @name is invalid, return the unnamedRegister.
+ getRegister: function(name) {
+ if (!this.isValidRegister(name)) {
+ return this.unnamedRegister;
+ }
+ name = name.toLowerCase();
+ if (!this.registers[name]) {
+ this.registers[name] = new Register();
+ }
+ return this.registers[name];
+ },
+ isValidRegister: function(name) {
+ return name && inArray(name, validRegisters);
+ },
+ shiftNumericRegisters_: function() {
+ for (var i = 9; i >= 2; i--) {
+ this.registers[i] = this.getRegister('' + (i - 1));
+ }
+ }
+ };
+ function HistoryController() {
+ this.historyBuffer = [];
+ this.iterator = 0;
+ this.initialPrefix = null;
+ }
+ HistoryController.prototype = {
+ // the input argument here acts a user entered prefix for a small time
+ // until we start autocompletion in which case it is the autocompleted.
+ nextMatch: function (input, up) {
+ var historyBuffer = this.historyBuffer;
+ var dir = up ? -1 : 1;
+ if (this.initialPrefix === null) this.initialPrefix = input;
+ for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i+= dir) {
+ var element = historyBuffer[i];
+ for (var j = 0; j <= element.length; j++) {
+ if (this.initialPrefix == element.substring(0, j)) {
+ this.iterator = i;
+ return element;
+ }
+ }
+ }
+ // should return the user input in case we reach the end of buffer.
+ if (i >= historyBuffer.length) {
+ this.iterator = historyBuffer.length;
+ return this.initialPrefix;
+ }
+ // return the last autocompleted query or exCommand as it is.
+ if (i < 0 ) return input;
+ },
+ pushInput: function(input) {
+ var index = this.historyBuffer.indexOf(input);
+ if (index > -1) this.historyBuffer.splice(index, 1);
+ if (input.length) this.historyBuffer.push(input);
+ },
+ reset: function() {
+ this.initialPrefix = null;
+ this.iterator = this.historyBuffer.length;
+ }
+ };
+ var commandDispatcher = {
+ matchCommand: function(keys, keyMap, inputState, context) {
+ var matches = commandMatches(keys, keyMap, context, inputState);
+ if (!matches.full && !matches.partial) {
+ return {type: 'none'};
+ } else if (!matches.full && matches.partial) {
+ return {type: 'partial'};
+ }
+
+ var bestMatch;
+ for (var i = 0; i < matches.full.length; i++) {
+ var match = matches.full[i];
+ if (!bestMatch) {
+ bestMatch = match;
+ }
+ }
+ if (bestMatch.keys.slice(-11) == '') {
+ var character = lastChar(keys);
+ if (!character) return {type: 'none'};
+ inputState.selectedCharacter = character;
+ }
+ return {type: 'full', command: bestMatch};
+ },
+ processCommand: function(cm, vim, command) {
+ vim.inputState.repeatOverride = command.repeatOverride;
+ switch (command.type) {
+ case 'motion':
+ this.processMotion(cm, vim, command);
+ break;
+ case 'operator':
+ this.processOperator(cm, vim, command);
+ break;
+ case 'operatorMotion':
+ this.processOperatorMotion(cm, vim, command);
+ break;
+ case 'action':
+ this.processAction(cm, vim, command);
+ break;
+ case 'search':
+ this.processSearch(cm, vim, command);
+ break;
+ case 'ex':
+ case 'keyToEx':
+ this.processEx(cm, vim, command);
+ break;
+ default:
+ break;
+ }
+ },
+ processMotion: function(cm, vim, command) {
+ vim.inputState.motion = command.motion;
+ vim.inputState.motionArgs = copyArgs(command.motionArgs);
+ this.evalInput(cm, vim);
+ },
+ processOperator: function(cm, vim, command) {
+ var inputState = vim.inputState;
+ if (inputState.operator) {
+ if (inputState.operator == command.operator) {
+ // Typing an operator twice like 'dd' makes the operator operate
+ // linewise
+ inputState.motion = 'expandToLine';
+ inputState.motionArgs = { linewise: true };
+ this.evalInput(cm, vim);
+ return;
+ } else {
+ // 2 different operators in a row doesn't make sense.
+ clearInputState(cm);
+ }
+ }
+ inputState.operator = command.operator;
+ inputState.operatorArgs = copyArgs(command.operatorArgs);
+ if (vim.visualMode) {
+ // Operating on a selection in visual mode. We don't need a motion.
+ this.evalInput(cm, vim);
+ }
+ },
+ processOperatorMotion: function(cm, vim, command) {
+ var visualMode = vim.visualMode;
+ var operatorMotionArgs = copyArgs(command.operatorMotionArgs);
+ if (operatorMotionArgs) {
+ // Operator motions may have special behavior in visual mode.
+ if (visualMode && operatorMotionArgs.visualLine) {
+ vim.visualLine = true;
+ }
+ }
+ this.processOperator(cm, vim, command);
+ if (!visualMode) {
+ this.processMotion(cm, vim, command);
+ }
+ },
+ processAction: function(cm, vim, command) {
+ var inputState = vim.inputState;
+ var repeat = inputState.getRepeat();
+ var repeatIsExplicit = !!repeat;
+ var actionArgs = copyArgs(command.actionArgs) || {};
+ if (inputState.selectedCharacter) {
+ actionArgs.selectedCharacter = inputState.selectedCharacter;
+ }
+ // Actions may or may not have motions and operators. Do these first.
+ if (command.operator) {
+ this.processOperator(cm, vim, command);
+ }
+ if (command.motion) {
+ this.processMotion(cm, vim, command);
+ }
+ if (command.motion || command.operator) {
+ this.evalInput(cm, vim);
+ }
+ actionArgs.repeat = repeat || 1;
+ actionArgs.repeatIsExplicit = repeatIsExplicit;
+ actionArgs.registerName = inputState.registerName;
+ clearInputState(cm);
+ vim.lastMotion = null;
+ if (command.isEdit) {
+ this.recordLastEdit(vim, inputState, command);
+ }
+ actions[command.action](cm, actionArgs, vim);
+ },
+ processSearch: function(cm, vim, command) {
+ if (!cm.getSearchCursor) {
+ // Search depends on SearchCursor.
+ return;
+ }
+ var forward = command.searchArgs.forward;
+ var wholeWordOnly = command.searchArgs.wholeWordOnly;
+ getSearchState(cm).setReversed(!forward);
+ var promptPrefix = (forward) ? '/' : '?';
+ var originalQuery = getSearchState(cm).getQuery();
+ var originalScrollPos = cm.getScrollInfo();
+ function handleQuery(query, ignoreCase, smartCase) {
+ vimGlobalState.searchHistoryController.pushInput(query);
+ vimGlobalState.searchHistoryController.reset();
+ try {
+ updateSearchQuery(cm, query, ignoreCase, smartCase);
+ } catch (e) {
+ showConfirm(cm, 'Invalid regex: ' + query);
+ clearInputState(cm);
+ return;
+ }
+ commandDispatcher.processMotion(cm, vim, {
+ type: 'motion',
+ motion: 'findNext',
+ motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist }
+ });
+ }
+ function onPromptClose(query) {
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
+ handleQuery(query, true /** ignoreCase */, true /** smartCase */);
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.isRecording) {
+ logSearchQuery(macroModeState, query);
+ }
+ }
+ function onPromptKeyUp(e, query, close) {
+ var keyName = CodeMirror.keyName(e), up, offset;
+ if (keyName == 'Up' || keyName == 'Down') {
+ up = keyName == 'Up' ? true : false;
+ offset = e.target ? e.target.selectionEnd : 0;
+ query = vimGlobalState.searchHistoryController.nextMatch(query, up) || '';
+ close(query);
+ if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length);
+ } else {
+ if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift')
+ vimGlobalState.searchHistoryController.reset();
+ }
+ var parsedQuery;
+ try {
+ parsedQuery = updateSearchQuery(cm, query,
+ true /** ignoreCase */, true /** smartCase */);
+ } catch (e) {
+ // Swallow bad regexes for incremental search.
+ }
+ if (parsedQuery) {
+ cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30);
+ } else {
+ clearSearchHighlight(cm);
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
+ }
+ }
+ function onPromptKeyDown(e, query, close) {
+ var keyName = CodeMirror.keyName(e);
+ if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' ||
+ (keyName == 'Backspace' && query == '')) {
+ vimGlobalState.searchHistoryController.pushInput(query);
+ vimGlobalState.searchHistoryController.reset();
+ updateSearchQuery(cm, originalQuery);
+ clearSearchHighlight(cm);
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
+ CodeMirror.e_stop(e);
+ clearInputState(cm);
+ close();
+ cm.focus();
+ } else if (keyName == 'Up' || keyName == 'Down') {
+ CodeMirror.e_stop(e);
+ } else if (keyName == 'Ctrl-U') {
+ // Ctrl-U clears input.
+ CodeMirror.e_stop(e);
+ close('');
+ }
+ }
+ switch (command.searchArgs.querySrc) {
+ case 'prompt':
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.isPlaying) {
+ var query = macroModeState.replaySearchQueries.shift();
+ handleQuery(query, true /** ignoreCase */, false /** smartCase */);
+ } else {
+ showPrompt(cm, {
+ onClose: onPromptClose,
+ prefix: promptPrefix,
+ desc: searchPromptDesc,
+ onKeyUp: onPromptKeyUp,
+ onKeyDown: onPromptKeyDown
+ });
+ }
+ break;
+ case 'wordUnderCursor':
+ var word = expandWordUnderCursor(cm, false /** inclusive */,
+ true /** forward */, false /** bigWord */,
+ true /** noSymbol */);
+ var isKeyword = true;
+ if (!word) {
+ word = expandWordUnderCursor(cm, false /** inclusive */,
+ true /** forward */, false /** bigWord */,
+ false /** noSymbol */);
+ isKeyword = false;
+ }
+ if (!word) {
+ return;
+ }
+ var query = cm.getLine(word.start.line).substring(word.start.ch,
+ word.end.ch);
+ if (isKeyword && wholeWordOnly) {
+ query = '\\b' + query + '\\b';
+ } else {
+ query = escapeRegex(query);
+ }
+
+ // cachedCursor is used to save the old position of the cursor
+ // when * or # causes vim to seek for the nearest word and shift
+ // the cursor before entering the motion.
+ vimGlobalState.jumpList.cachedCursor = cm.getCursor();
+ cm.setCursor(word.start);
+
+ handleQuery(query, true /** ignoreCase */, false /** smartCase */);
+ break;
+ }
+ },
+ processEx: function(cm, vim, command) {
+ function onPromptClose(input) {
+ // Give the prompt some time to close so that if processCommand shows
+ // an error, the elements don't overlap.
+ vimGlobalState.exCommandHistoryController.pushInput(input);
+ vimGlobalState.exCommandHistoryController.reset();
+ exCommandDispatcher.processCommand(cm, input);
+ }
+ function onPromptKeyDown(e, input, close) {
+ var keyName = CodeMirror.keyName(e), up, offset;
+ if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' ||
+ (keyName == 'Backspace' && input == '')) {
+ vimGlobalState.exCommandHistoryController.pushInput(input);
+ vimGlobalState.exCommandHistoryController.reset();
+ CodeMirror.e_stop(e);
+ clearInputState(cm);
+ close();
+ cm.focus();
+ }
+ if (keyName == 'Up' || keyName == 'Down') {
+ CodeMirror.e_stop(e);
+ up = keyName == 'Up' ? true : false;
+ offset = e.target ? e.target.selectionEnd : 0;
+ input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || '';
+ close(input);
+ if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length);
+ } else if (keyName == 'Ctrl-U') {
+ // Ctrl-U clears input.
+ CodeMirror.e_stop(e);
+ close('');
+ } else {
+ if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift')
+ vimGlobalState.exCommandHistoryController.reset();
+ }
+ }
+ if (command.type == 'keyToEx') {
+ // Handle user defined Ex to Ex mappings
+ exCommandDispatcher.processCommand(cm, command.exArgs.input);
+ } else {
+ if (vim.visualMode) {
+ showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
+ onKeyDown: onPromptKeyDown, selectValueOnOpen: false});
+ } else {
+ showPrompt(cm, { onClose: onPromptClose, prefix: ':',
+ onKeyDown: onPromptKeyDown});
+ }
+ }
+ },
+ evalInput: function(cm, vim) {
+ // If the motion command is set, execute both the operator and motion.
+ // Otherwise return.
+ var inputState = vim.inputState;
+ var motion = inputState.motion;
+ var motionArgs = inputState.motionArgs || {};
+ var operator = inputState.operator;
+ var operatorArgs = inputState.operatorArgs || {};
+ var registerName = inputState.registerName;
+ var sel = vim.sel;
+ // TODO: Make sure cm and vim selections are identical outside visual mode.
+ var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head'));
+ var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor'));
+ var oldHead = copyCursor(origHead);
+ var oldAnchor = copyCursor(origAnchor);
+ var newHead, newAnchor;
+ var repeat;
+ if (operator) {
+ this.recordLastEdit(vim, inputState);
+ }
+ if (inputState.repeatOverride !== undefined) {
+ // If repeatOverride is specified, that takes precedence over the
+ // input state's repeat. Used by Ex mode and can be user defined.
+ repeat = inputState.repeatOverride;
+ } else {
+ repeat = inputState.getRepeat();
+ }
+ if (repeat > 0 && motionArgs.explicitRepeat) {
+ motionArgs.repeatIsExplicit = true;
+ } else if (motionArgs.noRepeat ||
+ (!motionArgs.explicitRepeat && repeat === 0)) {
+ repeat = 1;
+ motionArgs.repeatIsExplicit = false;
+ }
+ if (inputState.selectedCharacter) {
+ // If there is a character input, stick it in all of the arg arrays.
+ motionArgs.selectedCharacter = operatorArgs.selectedCharacter =
+ inputState.selectedCharacter;
+ }
+ motionArgs.repeat = repeat;
+ clearInputState(cm);
+ if (motion) {
+ var motionResult = motions[motion](cm, origHead, motionArgs, vim);
+ vim.lastMotion = motions[motion];
+ if (!motionResult) {
+ return;
+ }
+ if (motionArgs.toJumplist) {
+ var jumpList = vimGlobalState.jumpList;
+ // if the current motion is # or *, use cachedCursor
+ var cachedCursor = jumpList.cachedCursor;
+ if (cachedCursor) {
+ recordJumpPosition(cm, cachedCursor, motionResult);
+ delete jumpList.cachedCursor;
+ } else {
+ recordJumpPosition(cm, origHead, motionResult);
+ }
+ }
+ if (motionResult instanceof Array) {
+ newAnchor = motionResult[0];
+ newHead = motionResult[1];
+ } else {
+ newHead = motionResult;
+ }
+ // TODO: Handle null returns from motion commands better.
+ if (!newHead) {
+ newHead = copyCursor(origHead);
+ }
+ if (vim.visualMode) {
+ if (!(vim.visualBlock && newHead.ch === Infinity)) {
+ newHead = clipCursorToContent(cm, newHead, vim.visualBlock);
+ }
+ if (newAnchor) {
+ newAnchor = clipCursorToContent(cm, newAnchor, true);
+ }
+ newAnchor = newAnchor || oldAnchor;
+ sel.anchor = newAnchor;
+ sel.head = newHead;
+ updateCmSelection(cm);
+ updateMark(cm, vim, '<',
+ cursorIsBefore(newAnchor, newHead) ? newAnchor
+ : newHead);
+ updateMark(cm, vim, '>',
+ cursorIsBefore(newAnchor, newHead) ? newHead
+ : newAnchor);
+ } else if (!operator) {
+ newHead = clipCursorToContent(cm, newHead);
+ cm.setCursor(newHead.line, newHead.ch);
+ }
+ }
+ if (operator) {
+ if (operatorArgs.lastSel) {
+ // Replaying a visual mode operation
+ newAnchor = oldAnchor;
+ var lastSel = operatorArgs.lastSel;
+ var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line);
+ var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch);
+ if (lastSel.visualLine) {
+ // Linewise Visual mode: The same number of lines.
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
+ } else if (lastSel.visualBlock) {
+ // Blockwise Visual mode: The same number of lines and columns.
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset);
+ } else if (lastSel.head.line == lastSel.anchor.line) {
+ // Normal Visual mode within one line: The same number of characters.
+ newHead = Pos(oldAnchor.line, oldAnchor.ch + chOffset);
+ } else {
+ // Normal Visual mode with several lines: The same number of lines, in the
+ // last line the same number of characters as in the last line the last time.
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
+ }
+ vim.visualMode = true;
+ vim.visualLine = lastSel.visualLine;
+ vim.visualBlock = lastSel.visualBlock;
+ sel = vim.sel = {
+ anchor: newAnchor,
+ head: newHead
+ };
+ updateCmSelection(cm);
+ } else if (vim.visualMode) {
+ operatorArgs.lastSel = {
+ anchor: copyCursor(sel.anchor),
+ head: copyCursor(sel.head),
+ visualBlock: vim.visualBlock,
+ visualLine: vim.visualLine
+ };
+ }
+ var curStart, curEnd, linewise, mode;
+ var cmSel;
+ if (vim.visualMode) {
+ // Init visual op
+ curStart = cursorMin(sel.head, sel.anchor);
+ curEnd = cursorMax(sel.head, sel.anchor);
+ linewise = vim.visualLine || operatorArgs.linewise;
+ mode = vim.visualBlock ? 'block' :
+ linewise ? 'line' :
+ 'char';
+ cmSel = makeCmSelection(cm, {
+ anchor: curStart,
+ head: curEnd
+ }, mode);
+ if (linewise) {
+ var ranges = cmSel.ranges;
+ if (mode == 'block') {
+ // Linewise operators in visual block mode extend to end of line
+ for (var i = 0; i < ranges.length; i++) {
+ ranges[i].head.ch = lineLength(cm, ranges[i].head.line);
+ }
+ } else if (mode == 'line') {
+ ranges[0].head = Pos(ranges[0].head.line + 1, 0);
+ }
+ }
+ } else {
+ // Init motion op
+ curStart = copyCursor(newAnchor || oldAnchor);
+ curEnd = copyCursor(newHead || oldHead);
+ if (cursorIsBefore(curEnd, curStart)) {
+ var tmp = curStart;
+ curStart = curEnd;
+ curEnd = tmp;
+ }
+ linewise = motionArgs.linewise || operatorArgs.linewise;
+ if (linewise) {
+ // Expand selection to entire line.
+ expandSelectionToLine(cm, curStart, curEnd);
+ } else if (motionArgs.forward) {
+ // Clip to trailing newlines only if the motion goes forward.
+ clipToLine(cm, curStart, curEnd);
+ }
+ mode = 'char';
+ var exclusive = !motionArgs.inclusive || linewise;
+ cmSel = makeCmSelection(cm, {
+ anchor: curStart,
+ head: curEnd
+ }, mode, exclusive);
+ }
+ cm.setSelections(cmSel.ranges, cmSel.primary);
+ vim.lastMotion = null;
+ operatorArgs.repeat = repeat; // For indent in visual mode.
+ operatorArgs.registerName = registerName;
+ // Keep track of linewise as it affects how paste and change behave.
+ operatorArgs.linewise = linewise;
+ var operatorMoveTo = operators[operator](
+ cm, operatorArgs, cmSel.ranges, oldAnchor, newHead);
+ if (vim.visualMode) {
+ exitVisualMode(cm, operatorMoveTo != null);
+ }
+ if (operatorMoveTo) {
+ cm.setCursor(operatorMoveTo);
+ }
+ }
+ },
+ recordLastEdit: function(vim, inputState, actionCommand) {
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.isPlaying) { return; }
+ vim.lastEditInputState = inputState;
+ vim.lastEditActionCommand = actionCommand;
+ macroModeState.lastInsertModeChanges.changes = [];
+ macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;
+ macroModeState.lastInsertModeChanges.visualBlock = vim.visualBlock ? vim.sel.head.line - vim.sel.anchor.line : 0;
+ }
+ };
+
+ /**
+ * typedef {Object{line:number,ch:number}} Cursor An object containing the
+ * position of the cursor.
+ */
+ // All of the functions below return Cursor objects.
+ var motions = {
+ moveToTopLine: function(cm, _head, motionArgs) {
+ var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
+ return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
+ },
+ moveToMiddleLine: function(cm) {
+ var range = getUserVisibleLines(cm);
+ var line = Math.floor((range.top + range.bottom) * 0.5);
+ return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
+ },
+ moveToBottomLine: function(cm, _head, motionArgs) {
+ var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
+ return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
+ },
+ expandToLine: function(_cm, head, motionArgs) {
+ // Expands forward to end of line, and then to next line if repeat is
+ // >1. Does not handle backward motion!
+ var cur = head;
+ return Pos(cur.line + motionArgs.repeat - 1, Infinity);
+ },
+ findNext: function(cm, _head, motionArgs) {
+ var state = getSearchState(cm);
+ var query = state.getQuery();
+ if (!query) {
+ return;
+ }
+ var prev = !motionArgs.forward;
+ // If search is initiated with ? instead of /, negate direction.
+ prev = (state.isReversed()) ? !prev : prev;
+ highlightSearchMatches(cm, query);
+ return findNext(cm, prev/** prev */, query, motionArgs.repeat);
+ },
+ goToMark: function(cm, _head, motionArgs, vim) {
+ var pos = getMarkPos(cm, vim, motionArgs.selectedCharacter);
+ if (pos) {
+ return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos;
+ }
+ return null;
+ },
+ moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) {
+ if (vim.visualBlock && motionArgs.sameLine) {
+ var sel = vim.sel;
+ return [
+ clipCursorToContent(cm, Pos(sel.anchor.line, sel.head.ch)),
+ clipCursorToContent(cm, Pos(sel.head.line, sel.anchor.ch))
+ ];
+ } else {
+ return ([vim.sel.head, vim.sel.anchor]);
+ }
+ },
+ jumpToMark: function(cm, head, motionArgs, vim) {
+ var best = head;
+ for (var i = 0; i < motionArgs.repeat; i++) {
+ var cursor = best;
+ for (var key in vim.marks) {
+ if (!isLowerCase(key)) {
+ continue;
+ }
+ var mark = vim.marks[key].find();
+ var isWrongDirection = (motionArgs.forward) ?
+ cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark);
+
+ if (isWrongDirection) {
+ continue;
+ }
+ if (motionArgs.linewise && (mark.line == cursor.line)) {
+ continue;
+ }
+
+ var equal = cursorEqual(cursor, best);
+ var between = (motionArgs.forward) ?
+ cursorIsBetween(cursor, mark, best) :
+ cursorIsBetween(best, mark, cursor);
+
+ if (equal || between) {
+ best = mark;
+ }
+ }
+ }
+
+ if (motionArgs.linewise) {
+ // Vim places the cursor on the first non-whitespace character of
+ // the line if there is one, else it places the cursor at the end
+ // of the line, regardless of whether a mark was found.
+ best = Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line)));
+ }
+ return best;
+ },
+ moveByCharacters: function(_cm, head, motionArgs) {
+ var cur = head;
+ var repeat = motionArgs.repeat;
+ var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
+ return Pos(cur.line, ch);
+ },
+ moveByLines: function(cm, head, motionArgs, vim) {
+ var cur = head;
+ var endCh = cur.ch;
+ // Depending what our last motion was, we may want to do different
+ // things. If our last motion was moving vertically, we want to
+ // preserve the HPos from our last horizontal move. If our last motion
+ // was going to the end of a line, moving vertically we should go to
+ // the end of the line, etc.
+ switch (vim.lastMotion) {
+ case this.moveByLines:
+ case this.moveByDisplayLines:
+ case this.moveByScroll:
+ case this.moveToColumn:
+ case this.moveToEol:
+ endCh = vim.lastHPos;
+ break;
+ default:
+ vim.lastHPos = endCh;
+ }
+ var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);
+ var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
+ var first = cm.firstLine();
+ var last = cm.lastLine();
+ // Vim go to line begin or line end when cursor at first/last line and
+ // move to previous/next line is triggered.
+ if (line < first && cur.line == first){
+ return this.moveToStartOfLine(cm, head, motionArgs, vim);
+ }else if (line > last && cur.line == last){
+ return this.moveToEol(cm, head, motionArgs, vim, true);
+ }
+ if (motionArgs.toFirstChar){
+ endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
+ vim.lastHPos = endCh;
+ }
+ vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left;
+ return Pos(line, endCh);
+ },
+ moveByDisplayLines: function(cm, head, motionArgs, vim) {
+ var cur = head;
+ switch (vim.lastMotion) {
+ case this.moveByDisplayLines:
+ case this.moveByScroll:
+ case this.moveByLines:
+ case this.moveToColumn:
+ case this.moveToEol:
+ break;
+ default:
+ vim.lastHSPos = cm.charCoords(cur,'div').left;
+ }
+ var repeat = motionArgs.repeat;
+ var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos);
+ if (res.hitSide) {
+ if (motionArgs.forward) {
+ var lastCharCoords = cm.charCoords(res, 'div');
+ var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos };
+ var res = cm.coordsChar(goalCoords, 'div');
+ } else {
+ var resCoords = cm.charCoords(Pos(cm.firstLine(), 0), 'div');
+ resCoords.left = vim.lastHSPos;
+ res = cm.coordsChar(resCoords, 'div');
+ }
+ }
+ vim.lastHPos = res.ch;
+ return res;
+ },
+ moveByPage: function(cm, head, motionArgs) {
+ // CodeMirror only exposes functions that move the cursor page down, so
+ // doing this bad hack to move the cursor and move it back. evalInput
+ // will move the cursor to where it should be in the end.
+ var curStart = head;
+ var repeat = motionArgs.repeat;
+ return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page');
+ },
+ moveByParagraph: function(cm, head, motionArgs) {
+ var dir = motionArgs.forward ? 1 : -1;
+ return findParagraph(cm, head, motionArgs.repeat, dir);
+ },
+ moveBySentence: function(cm, head, motionArgs) {
+ var dir = motionArgs.forward ? 1 : -1;
+ return findSentence(cm, head, motionArgs.repeat, dir);
+ },
+ moveByScroll: function(cm, head, motionArgs, vim) {
+ var scrollbox = cm.getScrollInfo();
+ var curEnd = null;
+ var repeat = motionArgs.repeat;
+ if (!repeat) {
+ repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
+ }
+ var orig = cm.charCoords(head, 'local');
+ motionArgs.repeat = repeat;
+ var curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim);
+ if (!curEnd) {
+ return null;
+ }
+ var dest = cm.charCoords(curEnd, 'local');
+ cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
+ return curEnd;
+ },
+ moveByWords: function(cm, head, motionArgs) {
+ return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward,
+ !!motionArgs.wordEnd, !!motionArgs.bigWord);
+ },
+ moveTillCharacter: function(cm, _head, motionArgs) {
+ var repeat = motionArgs.repeat;
+ var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
+ motionArgs.selectedCharacter);
+ var increment = motionArgs.forward ? -1 : 1;
+ recordLastCharacterSearch(increment, motionArgs);
+ if (!curEnd) return null;
+ curEnd.ch += increment;
+ return curEnd;
+ },
+ moveToCharacter: function(cm, head, motionArgs) {
+ var repeat = motionArgs.repeat;
+ recordLastCharacterSearch(0, motionArgs);
+ return moveToCharacter(cm, repeat, motionArgs.forward,
+ motionArgs.selectedCharacter) || head;
+ },
+ moveToSymbol: function(cm, head, motionArgs) {
+ var repeat = motionArgs.repeat;
+ return findSymbol(cm, repeat, motionArgs.forward,
+ motionArgs.selectedCharacter) || head;
+ },
+ moveToColumn: function(cm, head, motionArgs, vim) {
+ var repeat = motionArgs.repeat;
+ // repeat is equivalent to which column we want to move to!
+ vim.lastHPos = repeat - 1;
+ vim.lastHSPos = cm.charCoords(head,'div').left;
+ return moveToColumn(cm, repeat);
+ },
+ moveToEol: function(cm, head, motionArgs, vim, keepHPos) {
+ var cur = head;
+ var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity);
+ var end=cm.clipPos(retval);
+ end.ch--;
+ if (!keepHPos) {
+ vim.lastHPos = Infinity;
+ vim.lastHSPos = cm.charCoords(end,'div').left;
+ }
+ return retval;
+ },
+ moveToFirstNonWhiteSpaceCharacter: function(cm, head) {
+ // Go to the start of the line where the text begins, or the end for
+ // whitespace-only lines
+ var cursor = head;
+ return Pos(cursor.line,
+ findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)));
+ },
+ moveToMatchedSymbol: function(cm, head) {
+ var cursor = head;
+ var line = cursor.line;
+ var ch = cursor.ch;
+ var lineText = cm.getLine(line);
+ var symbol;
+ for (; ch < lineText.length; ch++) {
+ symbol = lineText.charAt(ch);
+ if (symbol && isMatchableSymbol(symbol)) {
+ var style = cm.getTokenTypeAt(Pos(line, ch + 1));
+ if (style !== "string" && style !== "comment") {
+ break;
+ }
+ }
+ }
+ if (ch < lineText.length) {
+ // Only include angle brackets in analysis if they are being matched.
+ var re = (ch === '<' || ch === '>') ? /[(){}[\]<>]/ : /[(){}[\]]/;
+ var matched = cm.findMatchingBracket(Pos(line, ch), {bracketRegex: re});
+ return matched.to;
+ } else {
+ return cursor;
+ }
+ },
+ moveToStartOfLine: function(_cm, head) {
+ return Pos(head.line, 0);
+ },
+ moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) {
+ var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
+ if (motionArgs.repeatIsExplicit) {
+ lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
+ }
+ return Pos(lineNum,
+ findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)));
+ },
+ textObjectManipulation: function(cm, head, motionArgs, vim) {
+ // TODO: lots of possible exceptions that can be thrown here. Try da(
+ // outside of a () block.
+ var mirroredPairs = {'(': ')', ')': '(',
+ '{': '}', '}': '{',
+ '[': ']', ']': '[',
+ '<': '>', '>': '<'};
+ var selfPaired = {'\'': true, '"': true, '`': true};
+
+ var character = motionArgs.selectedCharacter;
+ // 'b' refers to '()' block.
+ // 'B' refers to '{}' block.
+ if (character == 'b') {
+ character = '(';
+ } else if (character == 'B') {
+ character = '{';
+ }
+
+ // Inclusive is the difference between a and i
+ // TODO: Instead of using the additional text object map to perform text
+ // object operations, merge the map into the defaultKeyMap and use
+ // motionArgs to define behavior. Define separate entries for 'aw',
+ // 'iw', 'a[', 'i[', etc.
+ var inclusive = !motionArgs.textObjectInner;
+
+ var tmp;
+ if (mirroredPairs[character]) {
+ tmp = selectCompanionObject(cm, head, character, inclusive);
+ } else if (selfPaired[character]) {
+ tmp = findBeginningAndEnd(cm, head, character, inclusive);
+ } else if (character === 'W') {
+ tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
+ true /** bigWord */);
+ } else if (character === 'w') {
+ tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
+ false /** bigWord */);
+ } else if (character === 'p') {
+ tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive);
+ motionArgs.linewise = true;
+ if (vim.visualMode) {
+ if (!vim.visualLine) { vim.visualLine = true; }
+ } else {
+ var operatorArgs = vim.inputState.operatorArgs;
+ if (operatorArgs) { operatorArgs.linewise = true; }
+ tmp.end.line--;
+ }
+ } else {
+ // No text object defined for this, don't move.
+ return null;
+ }
+
+ if (!cm.state.vim.visualMode) {
+ return [tmp.start, tmp.end];
+ } else {
+ return expandSelection(cm, tmp.start, tmp.end);
+ }
+ },
+
+ repeatLastCharacterSearch: function(cm, head, motionArgs) {
+ var lastSearch = vimGlobalState.lastCharacterSearch;
+ var repeat = motionArgs.repeat;
+ var forward = motionArgs.forward === lastSearch.forward;
+ var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);
+ cm.moveH(-increment, 'char');
+ motionArgs.inclusive = forward ? true : false;
+ var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
+ if (!curEnd) {
+ cm.moveH(increment, 'char');
+ return head;
+ }
+ curEnd.ch += increment;
+ return curEnd;
+ }
+ };
+
+ function defineMotion(name, fn) {
+ motions[name] = fn;
+ }
+
+ function fillArray(val, times) {
+ var arr = [];
+ for (var i = 0; i < times; i++) {
+ arr.push(val);
+ }
+ return arr;
+ }
+ /**
+ * An operator acts on a text selection. It receives the list of selections
+ * as input. The corresponding CodeMirror selection is guaranteed to
+ * match the input selection.
+ */
+ var operators = {
+ change: function(cm, args, ranges) {
+ var finalHead, text;
+ var vim = cm.state.vim;
+ if (!vim.visualMode) {
+ var anchor = ranges[0].anchor,
+ head = ranges[0].head;
+ text = cm.getRange(anchor, head);
+ var lastState = vim.lastEditInputState || {};
+ if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) {
+ // Exclude trailing whitespace if the range is not all whitespace.
+ var match = (/\s+$/).exec(text);
+ if (match && lastState.motionArgs && lastState.motionArgs.forward) {
+ head = offsetCursor(head, 0, - match[0].length);
+ text = text.slice(0, - match[0].length);
+ }
+ }
+ var prevLineEnd = new Pos(anchor.line - 1, Number.MAX_VALUE);
+ var wasLastLine = cm.firstLine() == cm.lastLine();
+ if (head.line > cm.lastLine() && args.linewise && !wasLastLine) {
+ cm.replaceRange('', prevLineEnd, head);
+ } else {
+ cm.replaceRange('', anchor, head);
+ }
+ if (args.linewise) {
+ // Push the next line back down, if there is a next line.
+ if (!wasLastLine) {
+ cm.setCursor(prevLineEnd);
+ CodeMirror.commands.newlineAndIndent(cm);
+ }
+ // make sure cursor ends up at the end of the line.
+ anchor.ch = Number.MAX_VALUE;
+ }
+ finalHead = anchor;
+ } else {
+ text = cm.getSelection();
+ var replacement = fillArray('', ranges.length);
+ cm.replaceSelections(replacement);
+ finalHead = cursorMin(ranges[0].head, ranges[0].anchor);
+ }
+ vimGlobalState.registerController.pushText(
+ args.registerName, 'change', text,
+ args.linewise, ranges.length > 1);
+ actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim);
+ },
+ // delete is a javascript keyword.
+ 'delete': function(cm, args, ranges) {
+ var finalHead, text;
+ var vim = cm.state.vim;
+ if (!vim.visualBlock) {
+ var anchor = ranges[0].anchor,
+ head = ranges[0].head;
+ if (args.linewise &&
+ head.line != cm.firstLine() &&
+ anchor.line == cm.lastLine() &&
+ anchor.line == head.line - 1) {
+ // Special case for dd on last line (and first line).
+ if (anchor.line == cm.firstLine()) {
+ anchor.ch = 0;
+ } else {
+ anchor = Pos(anchor.line - 1, lineLength(cm, anchor.line - 1));
+ }
+ }
+ text = cm.getRange(anchor, head);
+ cm.replaceRange('', anchor, head);
+ finalHead = anchor;
+ if (args.linewise) {
+ finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor);
+ }
+ } else {
+ text = cm.getSelection();
+ var replacement = fillArray('', ranges.length);
+ cm.replaceSelections(replacement);
+ finalHead = ranges[0].anchor;
+ }
+ vimGlobalState.registerController.pushText(
+ args.registerName, 'delete', text,
+ args.linewise, vim.visualBlock);
+ var includeLineBreak = vim.insertMode
+ return clipCursorToContent(cm, finalHead, includeLineBreak);
+ },
+ indent: function(cm, args, ranges) {
+ var vim = cm.state.vim;
+ var startLine = ranges[0].anchor.line;
+ var endLine = vim.visualBlock ?
+ ranges[ranges.length - 1].anchor.line :
+ ranges[0].head.line;
+ // In visual mode, n> shifts the selection right n times, instead of
+ // shifting n lines right once.
+ var repeat = (vim.visualMode) ? args.repeat : 1;
+ if (args.linewise) {
+ // The only way to delete a newline is to delete until the start of
+ // the next line, so in linewise mode evalInput will include the next
+ // line. We don't want this in indent, so we go back a line.
+ endLine--;
+ }
+ for (var i = startLine; i <= endLine; i++) {
+ for (var j = 0; j < repeat; j++) {
+ cm.indentLine(i, args.indentRight);
+ }
+ }
+ return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);
+ },
+ indentAuto: function(cm, _args, ranges) {
+ cm.execCommand("indentAuto");
+ return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);
+ },
+ changeCase: function(cm, args, ranges, oldAnchor, newHead) {
+ var selections = cm.getSelections();
+ var swapped = [];
+ var toLower = args.toLower;
+ for (var j = 0; j < selections.length; j++) {
+ var toSwap = selections[j];
+ var text = '';
+ if (toLower === true) {
+ text = toSwap.toLowerCase();
+ } else if (toLower === false) {
+ text = toSwap.toUpperCase();
+ } else {
+ for (var i = 0; i < toSwap.length; i++) {
+ var character = toSwap.charAt(i);
+ text += isUpperCase(character) ? character.toLowerCase() :
+ character.toUpperCase();
+ }
+ }
+ swapped.push(text);
+ }
+ cm.replaceSelections(swapped);
+ if (args.shouldMoveCursor){
+ return newHead;
+ } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) {
+ return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor);
+ } else if (args.linewise){
+ return oldAnchor;
+ } else {
+ return cursorMin(ranges[0].anchor, ranges[0].head);
+ }
+ },
+ yank: function(cm, args, ranges, oldAnchor) {
+ var vim = cm.state.vim;
+ var text = cm.getSelection();
+ var endPos = vim.visualMode
+ ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor)
+ : oldAnchor;
+ vimGlobalState.registerController.pushText(
+ args.registerName, 'yank',
+ text, args.linewise, vim.visualBlock);
+ return endPos;
+ }
+ };
+
+ function defineOperator(name, fn) {
+ operators[name] = fn;
+ }
+
+ var actions = {
+ jumpListWalk: function(cm, actionArgs, vim) {
+ if (vim.visualMode) {
+ return;
+ }
+ var repeat = actionArgs.repeat;
+ var forward = actionArgs.forward;
+ var jumpList = vimGlobalState.jumpList;
+
+ var mark = jumpList.move(cm, forward ? repeat : -repeat);
+ var markPos = mark ? mark.find() : undefined;
+ markPos = markPos ? markPos : cm.getCursor();
+ cm.setCursor(markPos);
+ },
+ scroll: function(cm, actionArgs, vim) {
+ if (vim.visualMode) {
+ return;
+ }
+ var repeat = actionArgs.repeat || 1;
+ var lineHeight = cm.defaultTextHeight();
+ var top = cm.getScrollInfo().top;
+ var delta = lineHeight * repeat;
+ var newPos = actionArgs.forward ? top + delta : top - delta;
+ var cursor = copyCursor(cm.getCursor());
+ var cursorCoords = cm.charCoords(cursor, 'local');
+ if (actionArgs.forward) {
+ if (newPos > cursorCoords.top) {
+ cursor.line += (newPos - cursorCoords.top) / lineHeight;
+ cursor.line = Math.ceil(cursor.line);
+ cm.setCursor(cursor);
+ cursorCoords = cm.charCoords(cursor, 'local');
+ cm.scrollTo(null, cursorCoords.top);
+ } else {
+ // Cursor stays within bounds. Just reposition the scroll window.
+ cm.scrollTo(null, newPos);
+ }
+ } else {
+ var newBottom = newPos + cm.getScrollInfo().clientHeight;
+ if (newBottom < cursorCoords.bottom) {
+ cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight;
+ cursor.line = Math.floor(cursor.line);
+ cm.setCursor(cursor);
+ cursorCoords = cm.charCoords(cursor, 'local');
+ cm.scrollTo(
+ null, cursorCoords.bottom - cm.getScrollInfo().clientHeight);
+ } else {
+ // Cursor stays within bounds. Just reposition the scroll window.
+ cm.scrollTo(null, newPos);
+ }
+ }
+ },
+ scrollToCursor: function(cm, actionArgs) {
+ var lineNum = cm.getCursor().line;
+ var charCoords = cm.charCoords(Pos(lineNum, 0), 'local');
+ var height = cm.getScrollInfo().clientHeight;
+ var y = charCoords.top;
+ var lineHeight = charCoords.bottom - y;
+ switch (actionArgs.position) {
+ case 'center': y = y - (height / 2) + lineHeight;
+ break;
+ case 'bottom': y = y - height + lineHeight;
+ break;
+ }
+ cm.scrollTo(null, y);
+ },
+ replayMacro: function(cm, actionArgs, vim) {
+ var registerName = actionArgs.selectedCharacter;
+ var repeat = actionArgs.repeat;
+ var macroModeState = vimGlobalState.macroModeState;
+ if (registerName == '@') {
+ registerName = macroModeState.latestRegister;
+ } else {
+ macroModeState.latestRegister = registerName;
+ }
+ while(repeat--){
+ executeMacroRegister(cm, vim, macroModeState, registerName);
+ }
+ },
+ enterMacroRecordMode: function(cm, actionArgs) {
+ var macroModeState = vimGlobalState.macroModeState;
+ var registerName = actionArgs.selectedCharacter;
+ if (vimGlobalState.registerController.isValidRegister(registerName)) {
+ macroModeState.enterMacroRecordMode(cm, registerName);
+ }
+ },
+ toggleOverwrite: function(cm) {
+ if (!cm.state.overwrite) {
+ cm.toggleOverwrite(true);
+ cm.setOption('keyMap', 'vim-replace');
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});
+ } else {
+ cm.toggleOverwrite(false);
+ cm.setOption('keyMap', 'vim-insert');
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
+ }
+ },
+ enterInsertMode: function(cm, actionArgs, vim) {
+ if (cm.getOption('readOnly')) { return; }
+ vim.insertMode = true;
+ vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
+ var insertAt = (actionArgs) ? actionArgs.insertAt : null;
+ var sel = vim.sel;
+ var head = actionArgs.head || cm.getCursor('head');
+ var height = cm.listSelections().length;
+ if (insertAt == 'eol') {
+ head = Pos(head.line, lineLength(cm, head.line));
+ } else if (insertAt == 'charAfter') {
+ head = offsetCursor(head, 0, 1);
+ } else if (insertAt == 'firstNonBlank') {
+ head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head);
+ } else if (insertAt == 'startOfSelectedArea') {
+ if (!vim.visualMode)
+ return;
+ if (!vim.visualBlock) {
+ if (sel.head.line < sel.anchor.line) {
+ head = sel.head;
+ } else {
+ head = Pos(sel.anchor.line, 0);
+ }
+ } else {
+ head = Pos(
+ Math.min(sel.head.line, sel.anchor.line),
+ Math.min(sel.head.ch, sel.anchor.ch));
+ height = Math.abs(sel.head.line - sel.anchor.line) + 1;
+ }
+ } else if (insertAt == 'endOfSelectedArea') {
+ if (!vim.visualMode)
+ return;
+ if (!vim.visualBlock) {
+ if (sel.head.line >= sel.anchor.line) {
+ head = offsetCursor(sel.head, 0, 1);
+ } else {
+ head = Pos(sel.anchor.line, 0);
+ }
+ } else {
+ head = Pos(
+ Math.min(sel.head.line, sel.anchor.line),
+ Math.max(sel.head.ch + 1, sel.anchor.ch));
+ height = Math.abs(sel.head.line - sel.anchor.line) + 1;
+ }
+ } else if (insertAt == 'inplace') {
+ if (vim.visualMode){
+ return;
+ }
+ }
+ cm.setOption('disableInput', false);
+ if (actionArgs && actionArgs.replace) {
+ // Handle Replace-mode as a special case of insert mode.
+ cm.toggleOverwrite(true);
+ cm.setOption('keyMap', 'vim-replace');
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});
+ } else {
+ cm.toggleOverwrite(false);
+ cm.setOption('keyMap', 'vim-insert');
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
+ }
+ if (!vimGlobalState.macroModeState.isPlaying) {
+ // Only record if not replaying.
+ cm.on('change', onChange);
+ CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
+ }
+ if (vim.visualMode) {
+ exitVisualMode(cm);
+ }
+ selectForInsert(cm, head, height);
+ },
+ toggleVisualMode: function(cm, actionArgs, vim) {
+ var repeat = actionArgs.repeat;
+ var anchor = cm.getCursor();
+ var head;
+ // TODO: The repeat should actually select number of characters/lines
+ // equal to the repeat times the size of the previous visual
+ // operation.
+ if (!vim.visualMode) {
+ // Entering visual mode
+ vim.visualMode = true;
+ vim.visualLine = !!actionArgs.linewise;
+ vim.visualBlock = !!actionArgs.blockwise;
+ head = clipCursorToContent(
+ cm, Pos(anchor.line, anchor.ch + repeat - 1),
+ true /** includeLineBreak */);
+ vim.sel = {
+ anchor: anchor,
+ head: head
+ };
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
+ updateCmSelection(cm);
+ updateMark(cm, vim, '<', cursorMin(anchor, head));
+ updateMark(cm, vim, '>', cursorMax(anchor, head));
+ } else if (vim.visualLine ^ actionArgs.linewise ||
+ vim.visualBlock ^ actionArgs.blockwise) {
+ // Toggling between modes
+ vim.visualLine = !!actionArgs.linewise;
+ vim.visualBlock = !!actionArgs.blockwise;
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
+ updateCmSelection(cm);
+ } else {
+ exitVisualMode(cm);
+ }
+ },
+ reselectLastSelection: function(cm, _actionArgs, vim) {
+ var lastSelection = vim.lastSelection;
+ if (vim.visualMode) {
+ updateLastSelection(cm, vim);
+ }
+ if (lastSelection) {
+ var anchor = lastSelection.anchorMark.find();
+ var head = lastSelection.headMark.find();
+ if (!anchor || !head) {
+ // If the marks have been destroyed due to edits, do nothing.
+ return;
+ }
+ vim.sel = {
+ anchor: anchor,
+ head: head
+ };
+ vim.visualMode = true;
+ vim.visualLine = lastSelection.visualLine;
+ vim.visualBlock = lastSelection.visualBlock;
+ updateCmSelection(cm);
+ updateMark(cm, vim, '<', cursorMin(anchor, head));
+ updateMark(cm, vim, '>', cursorMax(anchor, head));
+ CodeMirror.signal(cm, 'vim-mode-change', {
+ mode: 'visual',
+ subMode: vim.visualLine ? 'linewise' :
+ vim.visualBlock ? 'blockwise' : ''});
+ }
+ },
+ joinLines: function(cm, actionArgs, vim) {
+ var curStart, curEnd;
+ if (vim.visualMode) {
+ curStart = cm.getCursor('anchor');
+ curEnd = cm.getCursor('head');
+ if (cursorIsBefore(curEnd, curStart)) {
+ var tmp = curEnd;
+ curEnd = curStart;
+ curStart = tmp;
+ }
+ curEnd.ch = lineLength(cm, curEnd.line) - 1;
+ } else {
+ // Repeat is the number of lines to join. Minimum 2 lines.
+ var repeat = Math.max(actionArgs.repeat, 2);
+ curStart = cm.getCursor();
+ curEnd = clipCursorToContent(cm, Pos(curStart.line + repeat - 1,
+ Infinity));
+ }
+ var finalCh = 0;
+ for (var i = curStart.line; i < curEnd.line; i++) {
+ finalCh = lineLength(cm, curStart.line);
+ var tmp = Pos(curStart.line + 1,
+ lineLength(cm, curStart.line + 1));
+ var text = cm.getRange(curStart, tmp);
+ text = text.replace(/\n\s*/g, ' ');
+ cm.replaceRange(text, curStart, tmp);
+ }
+ var curFinalPos = Pos(curStart.line, finalCh);
+ if (vim.visualMode) {
+ exitVisualMode(cm, false);
+ }
+ cm.setCursor(curFinalPos);
+ },
+ newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
+ vim.insertMode = true;
+ var insertAt = copyCursor(cm.getCursor());
+ if (insertAt.line === cm.firstLine() && !actionArgs.after) {
+ // Special case for inserting newline before start of document.
+ cm.replaceRange('\n', Pos(cm.firstLine(), 0));
+ cm.setCursor(cm.firstLine(), 0);
+ } else {
+ insertAt.line = (actionArgs.after) ? insertAt.line :
+ insertAt.line - 1;
+ insertAt.ch = lineLength(cm, insertAt.line);
+ cm.setCursor(insertAt);
+ var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment ||
+ CodeMirror.commands.newlineAndIndent;
+ newlineFn(cm);
+ }
+ this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim);
+ },
+ paste: function(cm, actionArgs, vim) {
+ var cur = copyCursor(cm.getCursor());
+ var register = vimGlobalState.registerController.getRegister(
+ actionArgs.registerName);
+ var text = register.toString();
+ if (!text) {
+ return;
+ }
+ if (actionArgs.matchIndent) {
+ var tabSize = cm.getOption("tabSize");
+ // length that considers tabs and tabSize
+ var whitespaceLength = function(str) {
+ var tabs = (str.split("\t").length - 1);
+ var spaces = (str.split(" ").length - 1);
+ return tabs * tabSize + spaces * 1;
+ };
+ var currentLine = cm.getLine(cm.getCursor().line);
+ var indent = whitespaceLength(currentLine.match(/^\s*/)[0]);
+ // chomp last newline b/c don't want it to match /^\s*/gm
+ var chompedText = text.replace(/\n$/, '');
+ var wasChomped = text !== chompedText;
+ var firstIndent = whitespaceLength(text.match(/^\s*/)[0]);
+ var text = chompedText.replace(/^\s*/gm, function(wspace) {
+ var newIndent = indent + (whitespaceLength(wspace) - firstIndent);
+ if (newIndent < 0) {
+ return "";
+ }
+ else if (cm.getOption("indentWithTabs")) {
+ var quotient = Math.floor(newIndent / tabSize);
+ return Array(quotient + 1).join('\t');
+ }
+ else {
+ return Array(newIndent + 1).join(' ');
+ }
+ });
+ text += wasChomped ? "\n" : "";
+ }
+ if (actionArgs.repeat > 1) {
+ var text = Array(actionArgs.repeat + 1).join(text);
+ }
+ var linewise = register.linewise;
+ var blockwise = register.blockwise;
+ if (blockwise) {
+ text = text.split('\n');
+ if (linewise) {
+ text.pop();
+ }
+ for (var i = 0; i < text.length; i++) {
+ text[i] = (text[i] == '') ? ' ' : text[i];
+ }
+ cur.ch += actionArgs.after ? 1 : 0;
+ cur.ch = Math.min(lineLength(cm, cur.line), cur.ch);
+ } else if (linewise) {
+ if(vim.visualMode) {
+ text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n';
+ } else if (actionArgs.after) {
+ // Move the newline at the end to the start instead, and paste just
+ // before the newline character of the line we are on right now.
+ text = '\n' + text.slice(0, text.length - 1);
+ cur.ch = lineLength(cm, cur.line);
+ } else {
+ cur.ch = 0;
+ }
+ } else {
+ cur.ch += actionArgs.after ? 1 : 0;
+ }
+ var curPosFinal;
+ var idx;
+ if (vim.visualMode) {
+ // save the pasted text for reselection if the need arises
+ vim.lastPastedText = text;
+ var lastSelectionCurEnd;
+ var selectedArea = getSelectedAreaRange(cm, vim);
+ var selectionStart = selectedArea[0];
+ var selectionEnd = selectedArea[1];
+ var selectedText = cm.getSelection();
+ var selections = cm.listSelections();
+ var emptyStrings = new Array(selections.length).join('1').split('1');
+ // save the curEnd marker before it get cleared due to cm.replaceRange.
+ if (vim.lastSelection) {
+ lastSelectionCurEnd = vim.lastSelection.headMark.find();
+ }
+ // push the previously selected text to unnamed register
+ vimGlobalState.registerController.unnamedRegister.setText(selectedText);
+ if (blockwise) {
+ // first delete the selected text
+ cm.replaceSelections(emptyStrings);
+ // Set new selections as per the block length of the yanked text
+ selectionEnd = Pos(selectionStart.line + text.length-1, selectionStart.ch);
+ cm.setCursor(selectionStart);
+ selectBlock(cm, selectionEnd);
+ cm.replaceSelections(text);
+ curPosFinal = selectionStart;
+ } else if (vim.visualBlock) {
+ cm.replaceSelections(emptyStrings);
+ cm.setCursor(selectionStart);
+ cm.replaceRange(text, selectionStart, selectionStart);
+ curPosFinal = selectionStart;
+ } else {
+ cm.replaceRange(text, selectionStart, selectionEnd);
+ curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1);
+ }
+ // restore the the curEnd marker
+ if(lastSelectionCurEnd) {
+ vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd);
+ }
+ if (linewise) {
+ curPosFinal.ch=0;
+ }
+ } else {
+ if (blockwise) {
+ cm.setCursor(cur);
+ for (var i = 0; i < text.length; i++) {
+ var line = cur.line+i;
+ if (line > cm.lastLine()) {
+ cm.replaceRange('\n', Pos(line, 0));
+ }
+ var lastCh = lineLength(cm, line);
+ if (lastCh < cur.ch) {
+ extendLineToColumn(cm, line, cur.ch);
+ }
+ }
+ cm.setCursor(cur);
+ selectBlock(cm, Pos(cur.line + text.length-1, cur.ch));
+ cm.replaceSelections(text);
+ curPosFinal = cur;
+ } else {
+ cm.replaceRange(text, cur);
+ // Now fine tune the cursor to where we want it.
+ if (linewise && actionArgs.after) {
+ curPosFinal = Pos(
+ cur.line + 1,
+ findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)));
+ } else if (linewise && !actionArgs.after) {
+ curPosFinal = Pos(
+ cur.line,
+ findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));
+ } else if (!linewise && actionArgs.after) {
+ idx = cm.indexFromPos(cur);
+ curPosFinal = cm.posFromIndex(idx + text.length - 1);
+ } else {
+ idx = cm.indexFromPos(cur);
+ curPosFinal = cm.posFromIndex(idx + text.length);
+ }
+ }
+ }
+ if (vim.visualMode) {
+ exitVisualMode(cm, false);
+ }
+ cm.setCursor(curPosFinal);
+ },
+ undo: function(cm, actionArgs) {
+ cm.operation(function() {
+ repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
+ cm.setCursor(cm.getCursor('anchor'));
+ });
+ },
+ redo: function(cm, actionArgs) {
+ repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();
+ },
+ setRegister: function(_cm, actionArgs, vim) {
+ vim.inputState.registerName = actionArgs.selectedCharacter;
+ },
+ setMark: function(cm, actionArgs, vim) {
+ var markName = actionArgs.selectedCharacter;
+ updateMark(cm, vim, markName, cm.getCursor());
+ },
+ replace: function(cm, actionArgs, vim) {
+ var replaceWith = actionArgs.selectedCharacter;
+ var curStart = cm.getCursor();
+ var replaceTo;
+ var curEnd;
+ var selections = cm.listSelections();
+ if (vim.visualMode) {
+ curStart = cm.getCursor('start');
+ curEnd = cm.getCursor('end');
+ } else {
+ var line = cm.getLine(curStart.line);
+ replaceTo = curStart.ch + actionArgs.repeat;
+ if (replaceTo > line.length) {
+ replaceTo=line.length;
+ }
+ curEnd = Pos(curStart.line, replaceTo);
+ }
+ if (replaceWith=='\n') {
+ if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);
+ // special case, where vim help says to replace by just one line-break
+ (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
+ } else {
+ var replaceWithStr = cm.getRange(curStart, curEnd);
+ //replace all characters in range by selected, but keep linebreaks
+ replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith);
+ if (vim.visualBlock) {
+ // Tabs are split in visua block before replacing
+ var spaces = new Array(cm.getOption("tabSize")+1).join(' ');
+ replaceWithStr = cm.getSelection();
+ replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n');
+ cm.replaceSelections(replaceWithStr);
+ } else {
+ cm.replaceRange(replaceWithStr, curStart, curEnd);
+ }
+ if (vim.visualMode) {
+ curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ?
+ selections[0].anchor : selections[0].head;
+ cm.setCursor(curStart);
+ exitVisualMode(cm, false);
+ } else {
+ cm.setCursor(offsetCursor(curEnd, 0, -1));
+ }
+ }
+ },
+ incrementNumberToken: function(cm, actionArgs) {
+ var cur = cm.getCursor();
+ var lineStr = cm.getLine(cur.line);
+ var re = /(-?)(?:(0x)([\da-f]+)|(0b|0|)(\d+))/gi;
+ var match;
+ var start;
+ var end;
+ var numberStr;
+ while ((match = re.exec(lineStr)) !== null) {
+ start = match.index;
+ end = start + match[0].length;
+ if (cur.ch < end)break;
+ }
+ if (!actionArgs.backtrack && (end <= cur.ch))return;
+ if (match) {
+ var baseStr = match[2] || match[4]
+ var digits = match[3] || match[5]
+ var increment = actionArgs.increase ? 1 : -1;
+ var base = {'0b': 2, '0': 8, '': 10, '0x': 16}[baseStr.toLowerCase()];
+ var number = parseInt(match[1] + digits, base) + (increment * actionArgs.repeat);
+ numberStr = number.toString(base);
+ var zeroPadding = baseStr ? new Array(digits.length - numberStr.length + 1 + match[1].length).join('0') : ''
+ if (numberStr.charAt(0) === '-') {
+ numberStr = '-' + baseStr + zeroPadding + numberStr.substr(1);
+ } else {
+ numberStr = baseStr + zeroPadding + numberStr;
+ }
+ var from = Pos(cur.line, start);
+ var to = Pos(cur.line, end);
+ cm.replaceRange(numberStr, from, to);
+ } else {
+ return;
+ }
+ cm.setCursor(Pos(cur.line, start + numberStr.length - 1));
+ },
+ repeatLastEdit: function(cm, actionArgs, vim) {
+ var lastEditInputState = vim.lastEditInputState;
+ if (!lastEditInputState) { return; }
+ var repeat = actionArgs.repeat;
+ if (repeat && actionArgs.repeatIsExplicit) {
+ vim.lastEditInputState.repeatOverride = repeat;
+ } else {
+ repeat = vim.lastEditInputState.repeatOverride || repeat;
+ }
+ repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
+ },
+ indent: function(cm, actionArgs) {
+ cm.indentLine(cm.getCursor().line, actionArgs.indentRight);
+ },
+ exitInsertMode: exitInsertMode
+ };
+
+ function defineAction(name, fn) {
+ actions[name] = fn;
+ }
+
+ /*
+ * Below are miscellaneous utility functions used by vim.js
+ */
+
+ /**
+ * Clips cursor to ensure that line is within the buffer's range
+ * If includeLineBreak is true, then allow cur.ch == lineLength.
+ */
+ function clipCursorToContent(cm, cur, includeLineBreak) {
+ var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() );
+ var maxCh = lineLength(cm, line) - 1;
+ maxCh = (includeLineBreak) ? maxCh + 1 : maxCh;
+ var ch = Math.min(Math.max(0, cur.ch), maxCh);
+ return Pos(line, ch);
+ }
+ function copyArgs(args) {
+ var ret = {};
+ for (var prop in args) {
+ if (args.hasOwnProperty(prop)) {
+ ret[prop] = args[prop];
+ }
+ }
+ return ret;
+ }
+ function offsetCursor(cur, offsetLine, offsetCh) {
+ if (typeof offsetLine === 'object') {
+ offsetCh = offsetLine.ch;
+ offsetLine = offsetLine.line;
+ }
+ return Pos(cur.line + offsetLine, cur.ch + offsetCh);
+ }
+ function commandMatches(keys, keyMap, context, inputState) {
+ // Partial matches are not applied. They inform the key handler
+ // that the current key sequence is a subsequence of a valid key
+ // sequence, so that the key buffer is not cleared.
+ var match, partial = [], full = [];
+ for (var i = 0; i < keyMap.length; i++) {
+ var command = keyMap[i];
+ if (context == 'insert' && command.context != 'insert' ||
+ command.context && command.context != context ||
+ inputState.operator && command.type == 'action' ||
+ !(match = commandMatch(keys, command.keys))) { continue; }
+ if (match == 'partial') { partial.push(command); }
+ if (match == 'full') { full.push(command); }
+ }
+ return {
+ partial: partial.length && partial,
+ full: full.length && full
+ };
+ }
+ function commandMatch(pressed, mapped) {
+ if (mapped.slice(-11) == '') {
+ // Last character matches anything.
+ var prefixLen = mapped.length - 11;
+ var pressedPrefix = pressed.slice(0, prefixLen);
+ var mappedPrefix = mapped.slice(0, prefixLen);
+ return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' :
+ mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false;
+ } else {
+ return pressed == mapped ? 'full' :
+ mapped.indexOf(pressed) == 0 ? 'partial' : false;
+ }
+ }
+ function lastChar(keys) {
+ var match = /^.*(<[^>]+>)$/.exec(keys);
+ var selectedCharacter = match ? match[1] : keys.slice(-1);
+ if (selectedCharacter.length > 1){
+ switch(selectedCharacter){
+ case '':
+ selectedCharacter='\n';
+ break;
+ case '':
+ selectedCharacter=' ';
+ break;
+ default:
+ selectedCharacter='';
+ break;
+ }
+ }
+ return selectedCharacter;
+ }
+ function repeatFn(cm, fn, repeat) {
+ return function() {
+ for (var i = 0; i < repeat; i++) {
+ fn(cm);
+ }
+ };
+ }
+ function copyCursor(cur) {
+ return Pos(cur.line, cur.ch);
+ }
+ function cursorEqual(cur1, cur2) {
+ return cur1.ch == cur2.ch && cur1.line == cur2.line;
+ }
+ function cursorIsBefore(cur1, cur2) {
+ if (cur1.line < cur2.line) {
+ return true;
+ }
+ if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
+ return true;
+ }
+ return false;
+ }
+ function cursorMin(cur1, cur2) {
+ if (arguments.length > 2) {
+ cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1));
+ }
+ return cursorIsBefore(cur1, cur2) ? cur1 : cur2;
+ }
+ function cursorMax(cur1, cur2) {
+ if (arguments.length > 2) {
+ cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1));
+ }
+ return cursorIsBefore(cur1, cur2) ? cur2 : cur1;
+ }
+ function cursorIsBetween(cur1, cur2, cur3) {
+ // returns true if cur2 is between cur1 and cur3.
+ var cur1before2 = cursorIsBefore(cur1, cur2);
+ var cur2before3 = cursorIsBefore(cur2, cur3);
+ return cur1before2 && cur2before3;
+ }
+ function lineLength(cm, lineNum) {
+ return cm.getLine(lineNum).length;
+ }
+ function trim(s) {
+ if (s.trim) {
+ return s.trim();
+ }
+ return s.replace(/^\s+|\s+$/g, '');
+ }
+ function escapeRegex(s) {
+ return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
+ }
+ function extendLineToColumn(cm, lineNum, column) {
+ var endCh = lineLength(cm, lineNum);
+ var spaces = new Array(column-endCh+1).join(' ');
+ cm.setCursor(Pos(lineNum, endCh));
+ cm.replaceRange(spaces, cm.getCursor());
+ }
+ // This functions selects a rectangular block
+ // of text with selectionEnd as any of its corner
+ // Height of block:
+ // Difference in selectionEnd.line and first/last selection.line
+ // Width of the block:
+ // Distance between selectionEnd.ch and any(first considered here) selection.ch
+ function selectBlock(cm, selectionEnd) {
+ var selections = [], ranges = cm.listSelections();
+ var head = copyCursor(cm.clipPos(selectionEnd));
+ var isClipped = !cursorEqual(selectionEnd, head);
+ var curHead = cm.getCursor('head');
+ var primIndex = getIndex(ranges, curHead);
+ var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor);
+ var max = ranges.length - 1;
+ var index = max - primIndex > primIndex ? max : 0;
+ var base = ranges[index].anchor;
+
+ var firstLine = Math.min(base.line, head.line);
+ var lastLine = Math.max(base.line, head.line);
+ var baseCh = base.ch, headCh = head.ch;
+
+ var dir = ranges[index].head.ch - baseCh;
+ var newDir = headCh - baseCh;
+ if (dir > 0 && newDir <= 0) {
+ baseCh++;
+ if (!isClipped) { headCh--; }
+ } else if (dir < 0 && newDir >= 0) {
+ baseCh--;
+ if (!wasClipped) { headCh++; }
+ } else if (dir < 0 && newDir == -1) {
+ baseCh--;
+ headCh++;
+ }
+ for (var line = firstLine; line <= lastLine; line++) {
+ var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)};
+ selections.push(range);
+ }
+ cm.setSelections(selections);
+ selectionEnd.ch = headCh;
+ base.ch = baseCh;
+ return base;
+ }
+ function selectForInsert(cm, head, height) {
+ var sel = [];
+ for (var i = 0; i < height; i++) {
+ var lineHead = offsetCursor(head, i, 0);
+ sel.push({anchor: lineHead, head: lineHead});
+ }
+ cm.setSelections(sel, 0);
+ }
+ // getIndex returns the index of the cursor in the selections.
+ function getIndex(ranges, cursor, end) {
+ for (var i = 0; i < ranges.length; i++) {
+ var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor);
+ var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor);
+ if (atAnchor || atHead) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ function getSelectedAreaRange(cm, vim) {
+ var lastSelection = vim.lastSelection;
+ var getCurrentSelectedAreaRange = function() {
+ var selections = cm.listSelections();
+ var start = selections[0];
+ var end = selections[selections.length-1];
+ var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;
+ var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
+ return [selectionStart, selectionEnd];
+ };
+ var getLastSelectedAreaRange = function() {
+ var selectionStart = cm.getCursor();
+ var selectionEnd = cm.getCursor();
+ var block = lastSelection.visualBlock;
+ if (block) {
+ var width = block.width;
+ var height = block.height;
+ selectionEnd = Pos(selectionStart.line + height, selectionStart.ch + width);
+ var selections = [];
+ // selectBlock creates a 'proper' rectangular block.
+ // We do not want that in all cases, so we manually set selections.
+ for (var i = selectionStart.line; i < selectionEnd.line; i++) {
+ var anchor = Pos(i, selectionStart.ch);
+ var head = Pos(i, selectionEnd.ch);
+ var range = {anchor: anchor, head: head};
+ selections.push(range);
+ }
+ cm.setSelections(selections);
+ } else {
+ var start = lastSelection.anchorMark.find();
+ var end = lastSelection.headMark.find();
+ var line = end.line - start.line;
+ var ch = end.ch - start.ch;
+ selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch};
+ if (lastSelection.visualLine) {
+ selectionStart = Pos(selectionStart.line, 0);
+ selectionEnd = Pos(selectionEnd.line, lineLength(cm, selectionEnd.line));
+ }
+ cm.setSelection(selectionStart, selectionEnd);
+ }
+ return [selectionStart, selectionEnd];
+ };
+ if (!vim.visualMode) {
+ // In case of replaying the action.
+ return getLastSelectedAreaRange();
+ } else {
+ return getCurrentSelectedAreaRange();
+ }
+ }
+ // Updates the previous selection with the current selection's values. This
+ // should only be called in visual mode.
+ function updateLastSelection(cm, vim) {
+ var anchor = vim.sel.anchor;
+ var head = vim.sel.head;
+ // To accommodate the effect of lastPastedText in the last selection
+ if (vim.lastPastedText) {
+ head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length);
+ vim.lastPastedText = null;
+ }
+ vim.lastSelection = {'anchorMark': cm.setBookmark(anchor),
+ 'headMark': cm.setBookmark(head),
+ 'anchor': copyCursor(anchor),
+ 'head': copyCursor(head),
+ 'visualMode': vim.visualMode,
+ 'visualLine': vim.visualLine,
+ 'visualBlock': vim.visualBlock};
+ }
+ function expandSelection(cm, start, end) {
+ var sel = cm.state.vim.sel;
+ var head = sel.head;
+ var anchor = sel.anchor;
+ var tmp;
+ if (cursorIsBefore(end, start)) {
+ tmp = end;
+ end = start;
+ start = tmp;
+ }
+ if (cursorIsBefore(head, anchor)) {
+ head = cursorMin(start, head);
+ anchor = cursorMax(anchor, end);
+ } else {
+ anchor = cursorMin(start, anchor);
+ head = cursorMax(head, end);
+ head = offsetCursor(head, 0, -1);
+ if (head.ch == -1 && head.line != cm.firstLine()) {
+ head = Pos(head.line - 1, lineLength(cm, head.line - 1));
+ }
+ }
+ return [anchor, head];
+ }
+ /**
+ * Updates the CodeMirror selection to match the provided vim selection.
+ * If no arguments are given, it uses the current vim selection state.
+ */
+ function updateCmSelection(cm, sel, mode) {
+ var vim = cm.state.vim;
+ sel = sel || vim.sel;
+ var mode = mode ||
+ vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char';
+ var cmSel = makeCmSelection(cm, sel, mode);
+ cm.setSelections(cmSel.ranges, cmSel.primary);
+ updateFakeCursor(cm);
+ }
+ function makeCmSelection(cm, sel, mode, exclusive) {
+ var head = copyCursor(sel.head);
+ var anchor = copyCursor(sel.anchor);
+ if (mode == 'char') {
+ var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
+ var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
+ head = offsetCursor(sel.head, 0, headOffset);
+ anchor = offsetCursor(sel.anchor, 0, anchorOffset);
+ return {
+ ranges: [{anchor: anchor, head: head}],
+ primary: 0
+ };
+ } else if (mode == 'line') {
+ if (!cursorIsBefore(sel.head, sel.anchor)) {
+ anchor.ch = 0;
+
+ var lastLine = cm.lastLine();
+ if (head.line > lastLine) {
+ head.line = lastLine;
+ }
+ head.ch = lineLength(cm, head.line);
+ } else {
+ head.ch = 0;
+ anchor.ch = lineLength(cm, anchor.line);
+ }
+ return {
+ ranges: [{anchor: anchor, head: head}],
+ primary: 0
+ };
+ } else if (mode == 'block') {
+ var top = Math.min(anchor.line, head.line),
+ left = Math.min(anchor.ch, head.ch),
+ bottom = Math.max(anchor.line, head.line),
+ right = Math.max(anchor.ch, head.ch) + 1;
+ var height = bottom - top + 1;
+ var primary = head.line == top ? 0 : height - 1;
+ var ranges = [];
+ for (var i = 0; i < height; i++) {
+ ranges.push({
+ anchor: Pos(top + i, left),
+ head: Pos(top + i, right)
+ });
+ }
+ return {
+ ranges: ranges,
+ primary: primary
+ };
+ }
+ }
+ function getHead(cm) {
+ var cur = cm.getCursor('head');
+ if (cm.getSelection().length == 1) {
+ // Small corner case when only 1 character is selected. The "real"
+ // head is the left of head and anchor.
+ cur = cursorMin(cur, cm.getCursor('anchor'));
+ }
+ return cur;
+ }
+
+ /**
+ * If moveHead is set to false, the CodeMirror selection will not be
+ * touched. The caller assumes the responsibility of putting the cursor
+ * in the right place.
+ */
+ function exitVisualMode(cm, moveHead) {
+ var vim = cm.state.vim;
+ if (moveHead !== false) {
+ cm.setCursor(clipCursorToContent(cm, vim.sel.head));
+ }
+ updateLastSelection(cm, vim);
+ vim.visualMode = false;
+ vim.visualLine = false;
+ vim.visualBlock = false;
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
+ if (vim.fakeCursor) {
+ vim.fakeCursor.clear();
+ }
+ }
+
+ // Remove any trailing newlines from the selection. For
+ // example, with the caret at the start of the last word on the line,
+ // 'dw' should word, but not the newline, while 'w' should advance the
+ // caret to the first character of the next line.
+ function clipToLine(cm, curStart, curEnd) {
+ var selection = cm.getRange(curStart, curEnd);
+ // Only clip if the selection ends with trailing newline + whitespace
+ if (/\n\s*$/.test(selection)) {
+ var lines = selection.split('\n');
+ // We know this is all whitespace.
+ lines.pop();
+
+ // Cases:
+ // 1. Last word is an empty line - do not clip the trailing '\n'
+ // 2. Last word is not an empty line - clip the trailing '\n'
+ var line;
+ // Find the line containing the last word, and clip all whitespace up
+ // to it.
+ for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {
+ curEnd.line--;
+ curEnd.ch = 0;
+ }
+ // If the last word is not an empty line, clip an additional newline
+ if (line) {
+ curEnd.line--;
+ curEnd.ch = lineLength(cm, curEnd.line);
+ } else {
+ curEnd.ch = 0;
+ }
+ }
+ }
+
+ // Expand the selection to line ends.
+ function expandSelectionToLine(_cm, curStart, curEnd) {
+ curStart.ch = 0;
+ curEnd.ch = 0;
+ curEnd.line++;
+ }
+
+ function findFirstNonWhiteSpaceCharacter(text) {
+ if (!text) {
+ return 0;
+ }
+ var firstNonWS = text.search(/\S/);
+ return firstNonWS == -1 ? text.length : firstNonWS;
+ }
+
+ function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
+ var cur = getHead(cm);
+ var line = cm.getLine(cur.line);
+ var idx = cur.ch;
+
+ // Seek to first word or non-whitespace character, depending on if
+ // noSymbol is true.
+ var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0];
+ while (!test(line.charAt(idx))) {
+ idx++;
+ if (idx >= line.length) { return null; }
}
- for (var index = 0; index < currLine.length; index++) {
- if (currLine[index] == symb) disBal++;
- else if (currLine[index] == reverseSymb) disBal--;
-
- if (disBal == 0) {
- if (forwards && cur.line == line) return {line: line, ch: index + cur.ch};
- else if (forwards) return {line: line, ch: index};
- else return {line: line, ch: currLine.length - index - 1 };
+ if (bigWord) {
+ test = bigWordCharTest[0];
+ } else {
+ test = wordCharTest[0];
+ if (!test(line.charAt(idx))) {
+ test = wordCharTest[1];
}
}
- if (forwards) line++;
- else line--;
- }
- }
+ var end = idx, start = idx;
+ while (test(line.charAt(end)) && end < line.length) { end++; }
+ while (test(line.charAt(start)) && start >= 0) { start--; }
+ start++;
- function selectCompanionObject(cm, revSymb, inclusive) {
- var cur = cm.getCursor();
-
- var end = findMatchedSymbol(cm, cur, revSymb);
- var start = findMatchedSymbol(cm, end);
- start.ch += inclusive ? 1 : 0;
- end.ch += inclusive ? 0 : 1;
-
- return {start: start, end: end};
- }
-
- // These are our motion commands to be used for navigation and selection with
- // certian other commands. All should return a cursor object.
- var motionList = ['B', 'E', 'J', 'K', 'H', 'L', 'W', 'Shift-W', "'^'", "'$'", "'%'", 'Esc'];
-
- motions = {
- 'B': function(cm, times) { return moveToWord(cm, word, -1, times); },
- 'Shift-B': function(cm, times) { return moveToWord(cm, bigWord, -1, times); },
- 'E': function(cm, times) { return moveToWord(cm, word, 1, times, 'end'); },
- 'Shift-E': function(cm, times) { return moveToWord(cm, bigWord, 1, times, 'end'); },
- 'J': function(cm, times) {
- var cur = cm.getCursor();
- return {line: cur.line+times, ch : cur.ch};
- },
-
- 'K': function(cm, times) {
- var cur = cm.getCursor();
- return {line: cur.line-times, ch: cur.ch};
- },
-
- 'H': function(cm, times) {
- var cur = cm.getCursor();
- return {line: cur.line, ch: cur.ch-times};
- },
-
- 'L': function(cm, times) {
- var cur = cm.getCursor();
- return {line: cur.line, ch: cur.ch+times};
- },
- 'W': function(cm, times) { return moveToWord(cm, word, 1, times); },
- 'Shift-W': function(cm, times) { return moveToWord(cm, bigWord, 1, times); },
- "'^'": function(cm, times) {
- var cur = cm.getCursor();
- var line = cm.getLine(cur.line).split('');
-
- // Empty line :o
- if (line.length == 0) return cur;
-
- for (var index = 0; index < line.length; index++) {
- if (line[index].match(/[^\s]/)) return {line: cur.line, ch: index};
+ if (inclusive) {
+ // If present, include all whitespace after word.
+ // Otherwise, include all whitespace before word, except indentation.
+ var wordEnd = end;
+ while (/\s/.test(line.charAt(end)) && end < line.length) { end++; }
+ if (wordEnd == end) {
+ var wordStart = start;
+ while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; }
+ if (!start) { start = wordStart; }
+ }
}
- },
- "'$'": function(cm) {
- var cur = cm.getCursor();
- var line = cm.getLine(cur.line);
- return {line: cur.line, ch: line.length};
- },
- "'%'": function(cm) { return findMatchedSymbol(cm, cm.getCursor()); },
- "Esc" : function(cm) {
- cm.setOption('vim');
- reptTimes = 0;
-
- return cm.getCursor();
+ return { start: Pos(cur.line, start), end: Pos(cur.line, end) };
}
- };
- // Map our movement actions each operator and non-operational movement
- motionList.forEach(function(key, index, array) {
- CodeMirror.keyMap['vim-prefix-d'][key] = function(cm) {
- // Get our selected range
- var start = cm.getCursor();
- var end = motions[key](cm, reptTimes ? reptTimes : 1);
+ function recordJumpPosition(cm, oldCur, newCur) {
+ if (!cursorEqual(oldCur, newCur)) {
+ vimGlobalState.jumpList.add(cm, oldCur, newCur);
+ }
+ }
- // Set swap var if range is of negative length
- if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
+ function recordLastCharacterSearch(increment, args) {
+ vimGlobalState.lastCharacterSearch.increment = increment;
+ vimGlobalState.lastCharacterSearch.forward = args.forward;
+ vimGlobalState.lastCharacterSearch.selectedCharacter = args.selectedCharacter;
+ }
- // Take action, switching start and end if swap var is set
- pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
- cm.replaceRange("", swap ? end : start, swap ? start : end);
-
- // And clean up
- reptTimes = 0;
- cm.setOption("keyMap", "vim");
+ var symbolToMode = {
+ '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket',
+ '[': 'section', ']': 'section',
+ '*': 'comment', '/': 'comment',
+ 'm': 'method', 'M': 'method',
+ '#': 'preprocess'
};
-
- CodeMirror.keyMap['vim-prefix-c'][key] = function(cm) {
- var start = cm.getCursor();
- var end = motions[key](cm, reptTimes ? reptTimes : 1);
-
- if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
- pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
- cm.replaceRange("", swap ? end : start, swap ? start : end);
-
- reptTimes = 0;
- cm.setOption('keyMap', 'vim-insert');
+ var findSymbolModes = {
+ bracket: {
+ isComplete: function(state) {
+ if (state.nextCh === state.symb) {
+ state.depth++;
+ if (state.depth >= 1)return true;
+ } else if (state.nextCh === state.reverseSymb) {
+ state.depth--;
+ }
+ return false;
+ }
+ },
+ section: {
+ init: function(state) {
+ state.curMoveThrough = true;
+ state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}';
+ },
+ isComplete: function(state) {
+ return state.index === 0 && state.nextCh === state.symb;
+ }
+ },
+ comment: {
+ isComplete: function(state) {
+ var found = state.lastCh === '*' && state.nextCh === '/';
+ state.lastCh = state.nextCh;
+ return found;
+ }
+ },
+ // TODO: The original Vim implementation only operates on level 1 and 2.
+ // The current implementation doesn't check for code block level and
+ // therefore it operates on any levels.
+ method: {
+ init: function(state) {
+ state.symb = (state.symb === 'm' ? '{' : '}');
+ state.reverseSymb = state.symb === '{' ? '}' : '{';
+ },
+ isComplete: function(state) {
+ if (state.nextCh === state.symb)return true;
+ return false;
+ }
+ },
+ preprocess: {
+ init: function(state) {
+ state.index = 0;
+ },
+ isComplete: function(state) {
+ if (state.nextCh === '#') {
+ var token = state.lineText.match(/#(\w+)/)[1];
+ if (token === 'endif') {
+ if (state.forward && state.depth === 0) {
+ return true;
+ }
+ state.depth++;
+ } else if (token === 'if') {
+ if (!state.forward && state.depth === 0) {
+ return true;
+ }
+ state.depth--;
+ }
+ if (token === 'else' && state.depth === 0)return true;
+ }
+ return false;
+ }
+ }
};
+ function findSymbol(cm, repeat, forward, symb) {
+ var cur = copyCursor(cm.getCursor());
+ var increment = forward ? 1 : -1;
+ var endLine = forward ? cm.lineCount() : -1;
+ var curCh = cur.ch;
+ var line = cur.line;
+ var lineText = cm.getLine(line);
+ var state = {
+ lineText: lineText,
+ nextCh: lineText.charAt(curCh),
+ lastCh: null,
+ index: curCh,
+ symb: symb,
+ reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],
+ forward: forward,
+ depth: 0,
+ curMoveThrough: false
+ };
+ var mode = symbolToMode[symb];
+ if (!mode)return cur;
+ var init = findSymbolModes[mode].init;
+ var isComplete = findSymbolModes[mode].isComplete;
+ if (init) { init(state); }
+ while (line !== endLine && repeat) {
+ state.index += increment;
+ state.nextCh = state.lineText.charAt(state.index);
+ if (!state.nextCh) {
+ line += increment;
+ state.lineText = cm.getLine(line) || '';
+ if (increment > 0) {
+ state.index = 0;
+ } else {
+ var lineLen = state.lineText.length;
+ state.index = (lineLen > 0) ? (lineLen-1) : 0;
+ }
+ state.nextCh = state.lineText.charAt(state.index);
+ }
+ if (isComplete(state)) {
+ cur.line = line;
+ cur.ch = state.index;
+ repeat--;
+ }
+ }
+ if (state.nextCh || state.curMoveThrough) {
+ return Pos(line, state.index);
+ }
+ return cur;
+ }
- CodeMirror.keyMap['vim-prefix-y'][key] = function(cm) {
- var start = cm.getCursor();
- var end = motions[key](cm, reptTimes ? reptTimes : 1);
+ /*
+ * Returns the boundaries of the next word. If the cursor in the middle of
+ * the word, then returns the boundaries of the current word, starting at
+ * the cursor. If the cursor is at the start/end of a word, and we are going
+ * forward/backward, respectively, find the boundaries of the next word.
+ *
+ * @param {CodeMirror} cm CodeMirror object.
+ * @param {Cursor} cur The cursor position.
+ * @param {boolean} forward True to search forward. False to search
+ * backward.
+ * @param {boolean} bigWord True if punctuation count as part of the word.
+ * False if only [a-zA-Z0-9] characters count as part of the word.
+ * @param {boolean} emptyLineIsWord True if empty lines should be treated
+ * as words.
+ * @return {Object{from:number, to:number, line: number}} The boundaries of
+ * the word, or null if there are no more words.
+ */
+ function findWord(cm, cur, forward, bigWord, emptyLineIsWord) {
+ var lineNum = cur.line;
+ var pos = cur.ch;
+ var line = cm.getLine(lineNum);
+ var dir = forward ? 1 : -1;
+ var charTests = bigWord ? bigWordCharTest: wordCharTest;
- if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
- pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
+ if (emptyLineIsWord && line == '') {
+ lineNum += dir;
+ line = cm.getLine(lineNum);
+ if (!isLine(cm, lineNum)) {
+ return null;
+ }
+ pos = (forward) ? 0 : line.length;
+ }
- reptTimes = 0;
- cm.setOption("keyMap", "vim");
- };
+ while (true) {
+ if (emptyLineIsWord && line == '') {
+ return { from: 0, to: 0, line: lineNum };
+ }
+ var stop = (dir > 0) ? line.length : -1;
+ var wordStart = stop, wordEnd = stop;
+ // Find bounds of next word.
+ while (pos != stop) {
+ var foundWord = false;
+ for (var i = 0; i < charTests.length && !foundWord; ++i) {
+ if (charTests[i](line.charAt(pos))) {
+ wordStart = pos;
+ // Advance to end of word.
+ while (pos != stop && charTests[i](line.charAt(pos))) {
+ pos += dir;
+ }
+ wordEnd = pos;
+ foundWord = wordStart != wordEnd;
+ if (wordStart == cur.ch && lineNum == cur.line &&
+ wordEnd == wordStart + dir) {
+ // We started at the end of a word. Find the next one.
+ continue;
+ } else {
+ return {
+ from: Math.min(wordStart, wordEnd + 1),
+ to: Math.max(wordStart, wordEnd),
+ line: lineNum };
+ }
+ }
+ }
+ if (!foundWord) {
+ pos += dir;
+ }
+ }
+ // Advance to next/prev line.
+ lineNum += dir;
+ if (!isLine(cm, lineNum)) {
+ return null;
+ }
+ line = cm.getLine(lineNum);
+ pos = (dir > 0) ? 0 : line.length;
+ }
+ }
- CodeMirror.keyMap['vim'][key] = function(cm) {
- var cur = motions[key](cm, reptTimes ? reptTimes : 1);
- cm.setCursor(cur.line, cur.ch);
+ /**
+ * @param {CodeMirror} cm CodeMirror object.
+ * @param {Pos} cur The position to start from.
+ * @param {int} repeat Number of words to move past.
+ * @param {boolean} forward True to search forward. False to search
+ * backward.
+ * @param {boolean} wordEnd True to move to end of word. False to move to
+ * beginning of word.
+ * @param {boolean} bigWord True if punctuation count as part of the word.
+ * False if only alphabet characters count as part of the word.
+ * @return {Cursor} The position the cursor should move to.
+ */
+ function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) {
+ var curStart = copyCursor(cur);
+ var words = [];
+ if (forward && !wordEnd || !forward && wordEnd) {
+ repeat++;
+ }
+ // For 'e', empty lines are not considered words, go figure.
+ var emptyLineIsWord = !(forward && wordEnd);
+ for (var i = 0; i < repeat; i++) {
+ var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord);
+ if (!word) {
+ var eodCh = lineLength(cm, cm.lastLine());
+ words.push(forward
+ ? {line: cm.lastLine(), from: eodCh, to: eodCh}
+ : {line: 0, from: 0, to: 0});
+ break;
+ }
+ words.push(word);
+ cur = Pos(word.line, forward ? (word.to - 1) : word.from);
+ }
+ var shortCircuit = words.length != repeat;
+ var firstWord = words[0];
+ var lastWord = words.pop();
+ if (forward && !wordEnd) {
+ // w
+ if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) {
+ // We did not start in the middle of a word. Discard the extra word at the end.
+ lastWord = words.pop();
+ }
+ return Pos(lastWord.line, lastWord.from);
+ } else if (forward && wordEnd) {
+ return Pos(lastWord.line, lastWord.to - 1);
+ } else if (!forward && wordEnd) {
+ // ge
+ if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) {
+ // We did not start in the middle of a word. Discard the extra word at the end.
+ lastWord = words.pop();
+ }
+ return Pos(lastWord.line, lastWord.to);
+ } else {
+ // b
+ return Pos(lastWord.line, lastWord.from);
+ }
+ }
- reptTimes = 0;
- };
- });
-
- var nums = [1,2,3,4,5,6,7,8,9];
- nums.forEach(function(key, index, array) {
- CodeMirror.keyMap['vim'][key] = function (cm) {
- reptTimes = (reptTimes * 10) + key;
- };
- CodeMirror.keyMap['vim-prefix-d'][key] = function (cm) {
- reptTimes = (reptTimes * 10) + key;
- };
- CodeMirror.keyMap['vim-prefix-y'][key] = function (cm) {
- reptTimes = (reptTimes * 10) + key;
- };
- CodeMirror.keyMap['vim-prefix-c'][key] = function (cm) {
- reptTimes = (reptTimes * 10) + key;
- };
- });
-
- // Create our keymaps for each operator and make xa and xi where x is an operator
- // change to the corrosponding keymap
- var operators = ['d', 'y', 'c'];
- operators.forEach(function(key, index, array) {
- CodeMirror.keyMap['vim-prefix-'+key+'a'] = {
- auto: 'vim', nofallthrough: true, style: "fat-cursor"
- };
- CodeMirror.keyMap['vim-prefix-'+key+'i'] = {
- auto: 'vim', nofallthrough: true, style: "fat-cursor"
- };
-
- CodeMirror.keyMap['vim-prefix-'+key]['A'] = function(cm) {
- reptTimes = 0;
- cm.setOption('keyMap', 'vim-prefix-' + key + 'a');
- };
-
- CodeMirror.keyMap['vim-prefix-'+key]['I'] = function(cm) {
- reptTimes = 0;
- cm.setOption('keyMap', 'vim-prefix-' + key + 'i');
- };
- });
-
- function regexLastIndexOf(string, pattern, startIndex) {
- for (var i = startIndex == null ? string.length : startIndex; i >= 0; --i)
- if (pattern.test(string.charAt(i))) return i;
- return -1;
- }
-
- // Create our text object functions. They work similar to motions but they
- // return a start cursor as well
- var textObjectList = ['W', 'Shift-[', 'Shift-9', '['];
- var textObjects = {
- 'W': function(cm, inclusive) {
+ function moveToCharacter(cm, repeat, forward, character) {
var cur = cm.getCursor();
+ var start = cur.ch;
+ var idx;
+ for (var i = 0; i < repeat; i ++) {
+ var line = cm.getLine(cur.line);
+ idx = charIdxInLine(start, line, character, forward, true);
+ if (idx == -1) {
+ return null;
+ }
+ start = idx;
+ }
+ return Pos(cm.getCursor().line, idx);
+ }
+
+ function moveToColumn(cm, repeat) {
+ // repeat is always >= 1, so repeat - 1 always corresponds
+ // to the column we want to go to.
+ var line = cm.getCursor().line;
+ return clipCursorToContent(cm, Pos(line, repeat - 1));
+ }
+
+ function updateMark(cm, vim, markName, pos) {
+ if (!inArray(markName, validMarks)) {
+ return;
+ }
+ if (vim.marks[markName]) {
+ vim.marks[markName].clear();
+ }
+ vim.marks[markName] = cm.setBookmark(pos);
+ }
+
+ function charIdxInLine(start, line, character, forward, includeChar) {
+ // Search for char in line.
+ // motion_options: {forward, includeChar}
+ // If includeChar = true, include it too.
+ // If forward = true, search forward, else search backwards.
+ // If char is not found on this line, do nothing
+ var idx;
+ if (forward) {
+ idx = line.indexOf(character, start + 1);
+ if (idx != -1 && !includeChar) {
+ idx -= 1;
+ }
+ } else {
+ idx = line.lastIndexOf(character, start - 1);
+ if (idx != -1 && !includeChar) {
+ idx += 1;
+ }
+ }
+ return idx;
+ }
+
+ function findParagraph(cm, head, repeat, dir, inclusive) {
+ var line = head.line;
+ var min = cm.firstLine();
+ var max = cm.lastLine();
+ var start, end, i = line;
+ function isEmpty(i) { return !cm.getLine(i); }
+ function isBoundary(i, dir, any) {
+ if (any) { return isEmpty(i) != isEmpty(i + dir); }
+ return !isEmpty(i) && isEmpty(i + dir);
+ }
+ if (dir) {
+ while (min <= i && i <= max && repeat > 0) {
+ if (isBoundary(i, dir)) { repeat--; }
+ i += dir;
+ }
+ return new Pos(i, 0);
+ }
+
+ var vim = cm.state.vim;
+ if (vim.visualLine && isBoundary(line, 1, true)) {
+ var anchor = vim.sel.anchor;
+ if (isBoundary(anchor.line, -1, true)) {
+ if (!inclusive || anchor.line != line) {
+ line += 1;
+ }
+ }
+ }
+ var startState = isEmpty(line);
+ for (i = line; i <= max && repeat; i++) {
+ if (isBoundary(i, 1, true)) {
+ if (!inclusive || isEmpty(i) != startState) {
+ repeat--;
+ }
+ }
+ }
+ end = new Pos(i, 0);
+ // select boundary before paragraph for the last one
+ if (i > max && !startState) { startState = true; }
+ else { inclusive = false; }
+ for (i = line; i > min; i--) {
+ if (!inclusive || isEmpty(i) == startState || i == line) {
+ if (isBoundary(i, -1, true)) { break; }
+ }
+ }
+ start = new Pos(i, 0);
+ return { start: start, end: end };
+ }
+
+ function findSentence(cm, cur, repeat, dir) {
+
+ /*
+ Takes an index object
+ {
+ line: the line string,
+ ln: line number,
+ pos: index in line,
+ dir: direction of traversal (-1 or 1)
+ }
+ and modifies the line, ln, and pos members to represent the
+ next valid position or sets them to null if there are
+ no more valid positions.
+ */
+ function nextChar(cm, idx) {
+ if (idx.pos + idx.dir < 0 || idx.pos + idx.dir >= idx.line.length) {
+ idx.ln += idx.dir;
+ if (!isLine(cm, idx.ln)) {
+ idx.line = null;
+ idx.ln = null;
+ idx.pos = null;
+ return;
+ }
+ idx.line = cm.getLine(idx.ln);
+ idx.pos = (idx.dir > 0) ? 0 : idx.line.length - 1;
+ }
+ else {
+ idx.pos += idx.dir;
+ }
+ }
+
+ /*
+ Performs one iteration of traversal in forward direction
+ Returns an index object of the new location
+ */
+ function forward(cm, ln, pos, dir) {
+ var line = cm.getLine(ln);
+ var stop = (line === "");
+
+ var curr = {
+ line: line,
+ ln: ln,
+ pos: pos,
+ dir: dir,
+ }
+
+ var last_valid = {
+ ln: curr.ln,
+ pos: curr.pos,
+ }
+
+ var skip_empty_lines = (curr.line === "");
+
+ // Move one step to skip character we start on
+ nextChar(cm, curr);
+
+ while (curr.line !== null) {
+ last_valid.ln = curr.ln;
+ last_valid.pos = curr.pos;
+
+ if (curr.line === "" && !skip_empty_lines) {
+ return { ln: curr.ln, pos: curr.pos, };
+ }
+ else if (stop && curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) {
+ return { ln: curr.ln, pos: curr.pos, };
+ }
+ else if (isEndOfSentenceSymbol(curr.line[curr.pos])
+ && !stop
+ && (curr.pos === curr.line.length - 1
+ || isWhiteSpaceString(curr.line[curr.pos + 1]))) {
+ stop = true;
+ }
+
+ nextChar(cm, curr);
+ }
+
+ /*
+ Set the position to the last non whitespace character on the last
+ valid line in the case that we reach the end of the document.
+ */
+ var line = cm.getLine(last_valid.ln);
+ last_valid.pos = 0;
+ for(var i = line.length - 1; i >= 0; --i) {
+ if (!isWhiteSpaceString(line[i])) {
+ last_valid.pos = i;
+ break;
+ }
+ }
+
+ return last_valid;
+
+ }
+
+ /*
+ Performs one iteration of traversal in reverse direction
+ Returns an index object of the new location
+ */
+ function reverse(cm, ln, pos, dir) {
+ var line = cm.getLine(ln);
+
+ var curr = {
+ line: line,
+ ln: ln,
+ pos: pos,
+ dir: dir,
+ }
+
+ var last_valid = {
+ ln: curr.ln,
+ pos: null,
+ };
+
+ var skip_empty_lines = (curr.line === "");
+
+ // Move one step to skip character we start on
+ nextChar(cm, curr);
+
+ while (curr.line !== null) {
+
+ if (curr.line === "" && !skip_empty_lines) {
+ if (last_valid.pos !== null) {
+ return last_valid;
+ }
+ else {
+ return { ln: curr.ln, pos: curr.pos };
+ }
+ }
+ else if (isEndOfSentenceSymbol(curr.line[curr.pos])
+ && last_valid.pos !== null
+ && !(curr.ln === last_valid.ln && curr.pos + 1 === last_valid.pos)) {
+ return last_valid;
+ }
+ else if (curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) {
+ skip_empty_lines = false;
+ last_valid = { ln: curr.ln, pos: curr.pos }
+ }
+
+ nextChar(cm, curr);
+ }
+
+ /*
+ Set the position to the first non whitespace character on the last
+ valid line in the case that we reach the beginning of the document.
+ */
+ var line = cm.getLine(last_valid.ln);
+ last_valid.pos = 0;
+ for(var i = 0; i < line.length; ++i) {
+ if (!isWhiteSpaceString(line[i])) {
+ last_valid.pos = i;
+ break;
+ }
+ }
+ return last_valid;
+ }
+
+ var curr_index = {
+ ln: cur.line,
+ pos: cur.ch,
+ };
+
+ while (repeat > 0) {
+ if (dir < 0) {
+ curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir);
+ }
+ else {
+ curr_index = forward(cm, curr_index.ln, curr_index.pos, dir);
+ }
+ repeat--;
+ }
+
+ return Pos(curr_index.ln, curr_index.pos);
+ }
+
+ // TODO: perhaps this finagling of start and end positions belonds
+ // in codemirror/replaceRange?
+ function selectCompanionObject(cm, head, symb, inclusive) {
+ var cur = head, start, end;
+
+ var bracketRegexp = ({
+ '(': /[()]/, ')': /[()]/,
+ '[': /[[\]]/, ']': /[[\]]/,
+ '{': /[{}]/, '}': /[{}]/,
+ '<': /[<>]/, '>': /[<>]/})[symb];
+ var openSym = ({
+ '(': '(', ')': '(',
+ '[': '[', ']': '[',
+ '{': '{', '}': '{',
+ '<': '<', '>': '<'})[symb];
+ var curChar = cm.getLine(cur.line).charAt(cur.ch);
+ // Due to the behavior of scanForBracket, we need to add an offset if the
+ // cursor is on a matching open bracket.
+ var offset = curChar === openSym ? 1 : 0;
+
+ start = cm.scanForBracket(Pos(cur.line, cur.ch + offset), -1, undefined, {'bracketRegex': bracketRegexp});
+ end = cm.scanForBracket(Pos(cur.line, cur.ch + offset), 1, undefined, {'bracketRegex': bracketRegexp});
+
+ if (!start || !end) {
+ return { start: cur, end: cur };
+ }
+
+ start = start.pos;
+ end = end.pos;
+
+ if ((start.line == end.line && start.ch > end.ch)
+ || (start.line > end.line)) {
+ var tmp = start;
+ start = end;
+ end = tmp;
+ }
+
+ if (inclusive) {
+ end.ch += 1;
+ } else {
+ start.ch += 1;
+ }
+
+ return { start: start, end: end };
+ }
+
+ // Takes in a symbol and a cursor and tries to simulate text objects that
+ // have identical opening and closing symbols
+ // TODO support across multiple lines
+ function findBeginningAndEnd(cm, head, symb, inclusive) {
+ var cur = copyCursor(head);
var line = cm.getLine(cur.line);
+ var chars = line.split('');
+ var start, end, i, len;
+ var firstIndex = chars.indexOf(symb);
- var line_to_char = new String(line.substring(0, cur.ch));
- var start = regexLastIndexOf(line_to_char, /[^a-zA-Z0-9]/) + 1;
- var end = motions["E"](cm, 1) ;
+ // the decision tree is to always look backwards for the beginning first,
+ // but if the cursor is in front of the first instance of the symb,
+ // then move the cursor forward
+ if (cur.ch < firstIndex) {
+ cur.ch = firstIndex;
+ // Why is this line even here???
+ // cm.setCursor(cur.line, firstIndex+1);
+ }
+ // otherwise if the cursor is currently on the closing symbol
+ else if (firstIndex < cur.ch && chars[cur.ch] == symb) {
+ end = cur.ch; // assign end to the current cursor
+ --cur.ch; // make sure to look backwards
+ }
- end.ch += inclusive ? 1 : 0 ;
- return {start: {line: cur.line, ch: start}, end: end };
- },
- 'Shift-[': function(cm, inclusive) { return selectCompanionObject(cm, '}', inclusive); },
- 'Shift-9': function(cm, inclusive) { return selectCompanionObject(cm, ')', inclusive); },
- '[': function(cm, inclusive) { return selectCompanionObject(cm, ']', inclusive); }
+ // if we're currently on the symbol, we've got a start
+ if (chars[cur.ch] == symb && !end) {
+ start = cur.ch + 1; // assign start to ahead of the cursor
+ } else {
+ // go backwards to find the start
+ for (i = cur.ch; i > -1 && !start; i--) {
+ if (chars[i] == symb) {
+ start = i + 1;
+ }
+ }
+ }
+
+ // look forwards for the end symbol
+ if (start && !end) {
+ for (i = start, len = chars.length; i < len && !end; i++) {
+ if (chars[i] == symb) {
+ end = i;
+ }
+ }
+ }
+
+ // nothing found
+ if (!start || !end) {
+ return { start: cur, end: cur };
+ }
+
+ // include the symbols
+ if (inclusive) {
+ --start; ++end;
+ }
+
+ return {
+ start: Pos(cur.line, start),
+ end: Pos(cur.line, end)
+ };
+ }
+
+ // Search functions
+ defineOption('pcre', true, 'boolean');
+ function SearchState() {}
+ SearchState.prototype = {
+ getQuery: function() {
+ return vimGlobalState.query;
+ },
+ setQuery: function(query) {
+ vimGlobalState.query = query;
+ },
+ getOverlay: function() {
+ return this.searchOverlay;
+ },
+ setOverlay: function(overlay) {
+ this.searchOverlay = overlay;
+ },
+ isReversed: function() {
+ return vimGlobalState.isReversed;
+ },
+ setReversed: function(reversed) {
+ vimGlobalState.isReversed = reversed;
+ },
+ getScrollbarAnnotate: function() {
+ return this.annotate;
+ },
+ setScrollbarAnnotate: function(annotate) {
+ this.annotate = annotate;
+ }
+ };
+ function getSearchState(cm) {
+ var vim = cm.state.vim;
+ return vim.searchState_ || (vim.searchState_ = new SearchState());
+ }
+ function dialog(cm, template, shortText, onClose, options) {
+ if (cm.openDialog) {
+ cm.openDialog(template, onClose, { bottom: true, value: options.value,
+ onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp,
+ selectValueOnOpen: false});
+ }
+ else {
+ onClose(prompt(shortText, ''));
+ }
+ }
+ function splitBySlash(argString) {
+ return splitBySeparator(argString, '/');
+ }
+
+ function findUnescapedSlashes(argString) {
+ return findUnescapedSeparators(argString, '/');
+ }
+
+ function splitBySeparator(argString, separator) {
+ var slashes = findUnescapedSeparators(argString, separator) || [];
+ if (!slashes.length) return [];
+ var tokens = [];
+ // in case of strings like foo/bar
+ if (slashes[0] !== 0) return;
+ for (var i = 0; i < slashes.length; i++) {
+ if (typeof slashes[i] == 'number')
+ tokens.push(argString.substring(slashes[i] + 1, slashes[i+1]));
+ }
+ return tokens;
+ }
+
+ function findUnescapedSeparators(str, separator) {
+ if (!separator)
+ separator = '/';
+
+ var escapeNextChar = false;
+ var slashes = [];
+ for (var i = 0; i < str.length; i++) {
+ var c = str.charAt(i);
+ if (!escapeNextChar && c == separator) {
+ slashes.push(i);
+ }
+ escapeNextChar = !escapeNextChar && (c == '\\');
+ }
+ return slashes;
+ }
+
+ // Translates a search string from ex (vim) syntax into javascript form.
+ function translateRegex(str) {
+ // When these match, add a '\' if unescaped or remove one if escaped.
+ var specials = '|(){';
+ // Remove, but never add, a '\' for these.
+ var unescape = '}';
+ var escapeNextChar = false;
+ var out = [];
+ for (var i = -1; i < str.length; i++) {
+ var c = str.charAt(i) || '';
+ var n = str.charAt(i+1) || '';
+ var specialComesNext = (n && specials.indexOf(n) != -1);
+ if (escapeNextChar) {
+ if (c !== '\\' || !specialComesNext) {
+ out.push(c);
+ }
+ escapeNextChar = false;
+ } else {
+ if (c === '\\') {
+ escapeNextChar = true;
+ // Treat the unescape list as special for removing, but not adding '\'.
+ if (n && unescape.indexOf(n) != -1) {
+ specialComesNext = true;
+ }
+ // Not passing this test means removing a '\'.
+ if (!specialComesNext || n === '\\') {
+ out.push(c);
+ }
+ } else {
+ out.push(c);
+ if (specialComesNext && n !== '\\') {
+ out.push('\\');
+ }
+ }
+ }
+ }
+ return out.join('');
+ }
+
+ // Translates the replace part of a search and replace from ex (vim) syntax into
+ // javascript form. Similar to translateRegex, but additionally fixes back references
+ // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'.
+ var charUnescapes = {'\\n': '\n', '\\r': '\r', '\\t': '\t'};
+ function translateRegexReplace(str) {
+ var escapeNextChar = false;
+ var out = [];
+ for (var i = -1; i < str.length; i++) {
+ var c = str.charAt(i) || '';
+ var n = str.charAt(i+1) || '';
+ if (charUnescapes[c + n]) {
+ out.push(charUnescapes[c+n]);
+ i++;
+ } else if (escapeNextChar) {
+ // At any point in the loop, escapeNextChar is true if the previous
+ // character was a '\' and was not escaped.
+ out.push(c);
+ escapeNextChar = false;
+ } else {
+ if (c === '\\') {
+ escapeNextChar = true;
+ if ((isNumber(n) || n === '$')) {
+ out.push('$');
+ } else if (n !== '/' && n !== '\\') {
+ out.push('\\');
+ }
+ } else {
+ if (c === '$') {
+ out.push('$');
+ }
+ out.push(c);
+ if (n === '/') {
+ out.push('\\');
+ }
+ }
+ }
+ }
+ return out.join('');
+ }
+
+ // Unescape \ and / in the replace part, for PCRE mode.
+ var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t', '\\&':'&'};
+ function unescapeRegexReplace(str) {
+ var stream = new CodeMirror.StringStream(str);
+ var output = [];
+ while (!stream.eol()) {
+ // Search for \.
+ while (stream.peek() && stream.peek() != '\\') {
+ output.push(stream.next());
+ }
+ var matched = false;
+ for (var matcher in unescapes) {
+ if (stream.match(matcher, true)) {
+ matched = true;
+ output.push(unescapes[matcher]);
+ break;
+ }
+ }
+ if (!matched) {
+ // Don't change anything
+ output.push(stream.next());
+ }
+ }
+ return output.join('');
+ }
+
+ /**
+ * Extract the regular expression from the query and return a Regexp object.
+ * Returns null if the query is blank.
+ * If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
+ * If smartCase is passed in, and the query contains upper case letters,
+ * then ignoreCase is overridden, and the 'i' flag will not be set.
+ * If the query contains the /i in the flag part of the regular expression,
+ * then both ignoreCase and smartCase are ignored, and 'i' will be passed
+ * through to the Regex object.
+ */
+ function parseQuery(query, ignoreCase, smartCase) {
+ // First update the last search register
+ var lastSearchRegister = vimGlobalState.registerController.getRegister('/');
+ lastSearchRegister.setText(query);
+ // Check if the query is already a regex.
+ if (query instanceof RegExp) { return query; }
+ // First try to extract regex + flags from the input. If no flags found,
+ // extract just the regex. IE does not accept flags directly defined in
+ // the regex string in the form /regex/flags
+ var slashes = findUnescapedSlashes(query);
+ var regexPart;
+ var forceIgnoreCase;
+ if (!slashes.length) {
+ // Query looks like 'regexp'
+ regexPart = query;
+ } else {
+ // Query looks like 'regexp/...'
+ regexPart = query.substring(0, slashes[0]);
+ var flagsPart = query.substring(slashes[0]);
+ forceIgnoreCase = (flagsPart.indexOf('i') != -1);
+ }
+ if (!regexPart) {
+ return null;
+ }
+ if (!getOption('pcre')) {
+ regexPart = translateRegex(regexPart);
+ }
+ if (smartCase) {
+ ignoreCase = (/^[^A-Z]*$/).test(regexPart);
+ }
+ var regexp = new RegExp(regexPart,
+ (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
+ return regexp;
+ }
+ function showConfirm(cm, text) {
+ if (cm.openNotification) {
+ cm.openNotification('' + text + '',
+ {bottom: true, duration: 5000});
+ } else {
+ alert(text);
+ }
+ }
+ function makePrompt(prefix, desc) {
+ var raw = '' +
+ (prefix || "") + '';
+ if (desc)
+ raw += ' ' + desc + '';
+ return raw;
+ }
+ var searchPromptDesc = '(Javascript regexp)';
+ function showPrompt(cm, options) {
+ var shortText = (options.prefix || '') + ' ' + (options.desc || '');
+ var prompt = makePrompt(options.prefix, options.desc);
+ dialog(cm, prompt, shortText, options.onClose, options);
+ }
+ function regexEqual(r1, r2) {
+ if (r1 instanceof RegExp && r2 instanceof RegExp) {
+ var props = ['global', 'multiline', 'ignoreCase', 'source'];
+ for (var i = 0; i < props.length; i++) {
+ var prop = props[i];
+ if (r1[prop] !== r2[prop]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ // Returns true if the query is valid.
+ function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
+ if (!rawQuery) {
+ return;
+ }
+ var state = getSearchState(cm);
+ var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase);
+ if (!query) {
+ return;
+ }
+ highlightSearchMatches(cm, query);
+ if (regexEqual(query, state.getQuery())) {
+ return query;
+ }
+ state.setQuery(query);
+ return query;
+ }
+ function searchOverlay(query) {
+ if (query.source.charAt(0) == '^') {
+ var matchSol = true;
+ }
+ return {
+ token: function(stream) {
+ if (matchSol && !stream.sol()) {
+ stream.skipToEnd();
+ return;
+ }
+ var match = stream.match(query, false);
+ if (match) {
+ if (match[0].length == 0) {
+ // Matched empty string, skip to next.
+ stream.next();
+ return 'searching';
+ }
+ if (!stream.sol()) {
+ // Backtrack 1 to match \b
+ stream.backUp(1);
+ if (!query.exec(stream.next() + match[0])) {
+ stream.next();
+ return null;
+ }
+ }
+ stream.match(query);
+ return 'searching';
+ }
+ while (!stream.eol()) {
+ stream.next();
+ if (stream.match(query, false)) break;
+ }
+ },
+ query: query
+ };
+ }
+ var highlightTimeout = 0;
+ function highlightSearchMatches(cm, query) {
+ clearTimeout(highlightTimeout);
+ highlightTimeout = setTimeout(function() {
+ var searchState = getSearchState(cm);
+ var overlay = searchState.getOverlay();
+ if (!overlay || query != overlay.query) {
+ if (overlay) {
+ cm.removeOverlay(overlay);
+ }
+ overlay = searchOverlay(query);
+ cm.addOverlay(overlay);
+ if (cm.showMatchesOnScrollbar) {
+ if (searchState.getScrollbarAnnotate()) {
+ searchState.getScrollbarAnnotate().clear();
+ }
+ searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query));
+ }
+ searchState.setOverlay(overlay);
+ }
+ }, 50);
+ }
+ function findNext(cm, prev, query, repeat) {
+ if (repeat === undefined) { repeat = 1; }
+ return cm.operation(function() {
+ var pos = cm.getCursor();
+ var cursor = cm.getSearchCursor(query, pos);
+ for (var i = 0; i < repeat; i++) {
+ var found = cursor.find(prev);
+ if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); }
+ if (!found) {
+ // SearchCursor may have returned null because it hit EOF, wrap
+ // around and try again.
+ cursor = cm.getSearchCursor(query,
+ (prev) ? Pos(cm.lastLine()) : Pos(cm.firstLine(), 0) );
+ if (!cursor.find(prev)) {
+ return;
+ }
+ }
+ }
+ return cursor.from();
+ });
+ }
+ function clearSearchHighlight(cm) {
+ var state = getSearchState(cm);
+ cm.removeOverlay(getSearchState(cm).getOverlay());
+ state.setOverlay(null);
+ if (state.getScrollbarAnnotate()) {
+ state.getScrollbarAnnotate().clear();
+ state.setScrollbarAnnotate(null);
+ }
+ }
+ /**
+ * Check if pos is in the specified range, INCLUSIVE.
+ * Range can be specified with 1 or 2 arguments.
+ * If the first range argument is an array, treat it as an array of line
+ * numbers. Match pos against any of the lines.
+ * If the first range argument is a number,
+ * if there is only 1 range argument, check if pos has the same line
+ * number
+ * if there are 2 range arguments, then check if pos is in between the two
+ * range arguments.
+ */
+ function isInRange(pos, start, end) {
+ if (typeof pos != 'number') {
+ // Assume it is a cursor position. Get the line number.
+ pos = pos.line;
+ }
+ if (start instanceof Array) {
+ return inArray(pos, start);
+ } else {
+ if (end) {
+ return (pos >= start && pos <= end);
+ } else {
+ return pos == start;
+ }
+ }
+ }
+ function getUserVisibleLines(cm) {
+ var scrollInfo = cm.getScrollInfo();
+ var occludeToleranceTop = 6;
+ var occludeToleranceBottom = 10;
+ var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');
+ var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;
+ var to = cm.coordsChar({left:0, top: bottomY}, 'local');
+ return {top: from.line, bottom: to.line};
+ }
+
+ function getMarkPos(cm, vim, markName) {
+ if (markName == '\'') {
+ var history = cm.doc.history.done;
+ var event = history[history.length - 2];
+ return event && event.ranges && event.ranges[0].head;
+ } else if (markName == '.') {
+ if (cm.doc.history.lastModTime == 0) {
+ return // If no changes, bail out; don't bother to copy or reverse history array.
+ } else {
+ var changeHistory = cm.doc.history.done.filter(function(el){ if (el.changes !== undefined) { return el } });
+ changeHistory.reverse();
+ var lastEditPos = changeHistory[0].changes[0].to;
+ }
+ return lastEditPos;
+ }
+
+ var mark = vim.marks[markName];
+ return mark && mark.find();
+ }
+
+ var ExCommandDispatcher = function() {
+ this.buildCommandMap_();
+ };
+ ExCommandDispatcher.prototype = {
+ processCommand: function(cm, input, opt_params) {
+ var that = this;
+ cm.operation(function () {
+ cm.curOp.isVimOp = true;
+ that._processCommand(cm, input, opt_params);
+ });
+ },
+ _processCommand: function(cm, input, opt_params) {
+ var vim = cm.state.vim;
+ var commandHistoryRegister = vimGlobalState.registerController.getRegister(':');
+ var previousCommand = commandHistoryRegister.toString();
+ if (vim.visualMode) {
+ exitVisualMode(cm);
+ }
+ var inputStream = new CodeMirror.StringStream(input);
+ // update ": with the latest command whether valid or invalid
+ commandHistoryRegister.setText(input);
+ var params = opt_params || {};
+ params.input = input;
+ try {
+ this.parseInput_(cm, inputStream, params);
+ } catch(e) {
+ showConfirm(cm, e);
+ throw e;
+ }
+ var command;
+ var commandName;
+ if (!params.commandName) {
+ // If only a line range is defined, move to the line.
+ if (params.line !== undefined) {
+ commandName = 'move';
+ }
+ } else {
+ command = this.matchCommand_(params.commandName);
+ if (command) {
+ commandName = command.name;
+ if (command.excludeFromCommandHistory) {
+ commandHistoryRegister.setText(previousCommand);
+ }
+ this.parseCommandArgs_(inputStream, params, command);
+ if (command.type == 'exToKey') {
+ // Handle Ex to Key mapping.
+ for (var i = 0; i < command.toKeys.length; i++) {
+ CodeMirror.Vim.handleKey(cm, command.toKeys[i], 'mapping');
+ }
+ return;
+ } else if (command.type == 'exToEx') {
+ // Handle Ex to Ex mapping.
+ this.processCommand(cm, command.toInput);
+ return;
+ }
+ }
+ }
+ if (!commandName) {
+ showConfirm(cm, 'Not an editor command ":' + input + '"');
+ return;
+ }
+ try {
+ exCommands[commandName](cm, params);
+ // Possibly asynchronous commands (e.g. substitute, which might have a
+ // user confirmation), are responsible for calling the callback when
+ // done. All others have it taken care of for them here.
+ if ((!command || !command.possiblyAsync) && params.callback) {
+ params.callback();
+ }
+ } catch(e) {
+ showConfirm(cm, e);
+ throw e;
+ }
+ },
+ parseInput_: function(cm, inputStream, result) {
+ inputStream.eatWhile(':');
+ // Parse range.
+ if (inputStream.eat('%')) {
+ result.line = cm.firstLine();
+ result.lineEnd = cm.lastLine();
+ } else {
+ result.line = this.parseLineSpec_(cm, inputStream);
+ if (result.line !== undefined && inputStream.eat(',')) {
+ result.lineEnd = this.parseLineSpec_(cm, inputStream);
+ }
+ }
+
+ // Parse command name.
+ var commandMatch = inputStream.match(/^(\w+)/);
+ if (commandMatch) {
+ result.commandName = commandMatch[1];
+ } else {
+ result.commandName = inputStream.match(/.*/)[0];
+ }
+
+ return result;
+ },
+ parseLineSpec_: function(cm, inputStream) {
+ var numberMatch = inputStream.match(/^(\d+)/);
+ if (numberMatch) {
+ // Absolute line number plus offset (N+M or N-M) is probably a typo,
+ // not something the user actually wanted. (NB: vim does allow this.)
+ return parseInt(numberMatch[1], 10) - 1;
+ }
+ switch (inputStream.next()) {
+ case '.':
+ return this.parseLineSpecOffset_(inputStream, cm.getCursor().line);
+ case '$':
+ return this.parseLineSpecOffset_(inputStream, cm.lastLine());
+ case '\'':
+ var markName = inputStream.next();
+ var markPos = getMarkPos(cm, cm.state.vim, markName);
+ if (!markPos) throw new Error('Mark not set');
+ return this.parseLineSpecOffset_(inputStream, markPos.line);
+ case '-':
+ case '+':
+ inputStream.backUp(1);
+ // Offset is relative to current line if not otherwise specified.
+ return this.parseLineSpecOffset_(inputStream, cm.getCursor().line);
+ default:
+ inputStream.backUp(1);
+ return undefined;
+ }
+ },
+ parseLineSpecOffset_: function(inputStream, line) {
+ var offsetMatch = inputStream.match(/^([+-])?(\d+)/);
+ if (offsetMatch) {
+ var offset = parseInt(offsetMatch[2], 10);
+ if (offsetMatch[1] == "-") {
+ line -= offset;
+ } else {
+ line += offset;
+ }
+ }
+ return line;
+ },
+ parseCommandArgs_: function(inputStream, params, command) {
+ if (inputStream.eol()) {
+ return;
+ }
+ params.argString = inputStream.match(/.*/)[0];
+ // Parse command-line arguments
+ var delim = command.argDelimiter || /\s+/;
+ var args = trim(params.argString).split(delim);
+ if (args.length && args[0]) {
+ params.args = args;
+ }
+ },
+ matchCommand_: function(commandName) {
+ // Return the command in the command map that matches the shortest
+ // prefix of the passed in command name. The match is guaranteed to be
+ // unambiguous if the defaultExCommandMap's shortNames are set up
+ // correctly. (see @code{defaultExCommandMap}).
+ for (var i = commandName.length; i > 0; i--) {
+ var prefix = commandName.substring(0, i);
+ if (this.commandMap_[prefix]) {
+ var command = this.commandMap_[prefix];
+ if (command.name.indexOf(commandName) === 0) {
+ return command;
+ }
+ }
+ }
+ return null;
+ },
+ buildCommandMap_: function() {
+ this.commandMap_ = {};
+ for (var i = 0; i < defaultExCommandMap.length; i++) {
+ var command = defaultExCommandMap[i];
+ var key = command.shortName || command.name;
+ this.commandMap_[key] = command;
+ }
+ },
+ map: function(lhs, rhs, ctx) {
+ if (lhs != ':' && lhs.charAt(0) == ':') {
+ if (ctx) { throw Error('Mode not supported for ex mappings'); }
+ var commandName = lhs.substring(1);
+ if (rhs != ':' && rhs.charAt(0) == ':') {
+ // Ex to Ex mapping
+ this.commandMap_[commandName] = {
+ name: commandName,
+ type: 'exToEx',
+ toInput: rhs.substring(1),
+ user: true
+ };
+ } else {
+ // Ex to key mapping
+ this.commandMap_[commandName] = {
+ name: commandName,
+ type: 'exToKey',
+ toKeys: rhs,
+ user: true
+ };
+ }
+ } else {
+ if (rhs != ':' && rhs.charAt(0) == ':') {
+ // Key to Ex mapping.
+ var mapping = {
+ keys: lhs,
+ type: 'keyToEx',
+ exArgs: { input: rhs.substring(1) }
+ };
+ if (ctx) { mapping.context = ctx; }
+ defaultKeymap.unshift(mapping);
+ } else {
+ // Key to key mapping
+ var mapping = {
+ keys: lhs,
+ type: 'keyToKey',
+ toKeys: rhs
+ };
+ if (ctx) { mapping.context = ctx; }
+ defaultKeymap.unshift(mapping);
+ }
+ }
+ },
+ unmap: function(lhs, ctx) {
+ if (lhs != ':' && lhs.charAt(0) == ':') {
+ // Ex to Ex or Ex to key mapping
+ if (ctx) { throw Error('Mode not supported for ex mappings'); }
+ var commandName = lhs.substring(1);
+ if (this.commandMap_[commandName] && this.commandMap_[commandName].user) {
+ delete this.commandMap_[commandName];
+ return;
+ }
+ } else {
+ // Key to Ex or key to key mapping
+ var keys = lhs;
+ for (var i = 0; i < defaultKeymap.length; i++) {
+ if (keys == defaultKeymap[i].keys
+ && defaultKeymap[i].context === ctx) {
+ defaultKeymap.splice(i, 1);
+ return;
+ }
+ }
+ }
+ throw Error('No such mapping.');
+ }
+ };
+
+ var exCommands = {
+ colorscheme: function(cm, params) {
+ if (!params.args || params.args.length < 1) {
+ showConfirm(cm, cm.getOption('theme'));
+ return;
+ }
+ cm.setOption('theme', params.args[0]);
+ },
+ map: function(cm, params, ctx) {
+ var mapArgs = params.args;
+ if (!mapArgs || mapArgs.length < 2) {
+ if (cm) {
+ showConfirm(cm, 'Invalid mapping: ' + params.input);
+ }
+ return;
+ }
+ exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx);
+ },
+ imap: function(cm, params) { this.map(cm, params, 'insert'); },
+ nmap: function(cm, params) { this.map(cm, params, 'normal'); },
+ vmap: function(cm, params) { this.map(cm, params, 'visual'); },
+ unmap: function(cm, params, ctx) {
+ var mapArgs = params.args;
+ if (!mapArgs || mapArgs.length < 1) {
+ if (cm) {
+ showConfirm(cm, 'No such mapping: ' + params.input);
+ }
+ return;
+ }
+ exCommandDispatcher.unmap(mapArgs[0], ctx);
+ },
+ move: function(cm, params) {
+ commandDispatcher.processCommand(cm, cm.state.vim, {
+ type: 'motion',
+ motion: 'moveToLineOrEdgeOfDocument',
+ motionArgs: { forward: false, explicitRepeat: true,
+ linewise: true },
+ repeatOverride: params.line+1});
+ },
+ set: function(cm, params) {
+ var setArgs = params.args;
+ // Options passed through to the setOption/getOption calls. May be passed in by the
+ // local/global versions of the set command
+ var setCfg = params.setCfg || {};
+ if (!setArgs || setArgs.length < 1) {
+ if (cm) {
+ showConfirm(cm, 'Invalid mapping: ' + params.input);
+ }
+ return;
+ }
+ var expr = setArgs[0].split('=');
+ var optionName = expr[0];
+ var value = expr[1];
+ var forceGet = false;
+
+ if (optionName.charAt(optionName.length - 1) == '?') {
+ // If post-fixed with ?, then the set is actually a get.
+ if (value) { throw Error('Trailing characters: ' + params.argString); }
+ optionName = optionName.substring(0, optionName.length - 1);
+ forceGet = true;
+ }
+ if (value === undefined && optionName.substring(0, 2) == 'no') {
+ // To set boolean options to false, the option name is prefixed with
+ // 'no'.
+ optionName = optionName.substring(2);
+ value = false;
+ }
+
+ var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean';
+ if (optionIsBoolean && value == undefined) {
+ // Calling set with a boolean option sets it to true.
+ value = true;
+ }
+ // If no value is provided, then we assume this is a get.
+ if (!optionIsBoolean && value === undefined || forceGet) {
+ var oldValue = getOption(optionName, cm, setCfg);
+ if (oldValue instanceof Error) {
+ showConfirm(cm, oldValue.message);
+ } else if (oldValue === true || oldValue === false) {
+ showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName);
+ } else {
+ showConfirm(cm, ' ' + optionName + '=' + oldValue);
+ }
+ } else {
+ var setOptionReturn = setOption(optionName, value, cm, setCfg);
+ if (setOptionReturn instanceof Error) {
+ showConfirm(cm, setOptionReturn.message);
+ }
+ }
+ },
+ setlocal: function (cm, params) {
+ // setCfg is passed through to setOption
+ params.setCfg = {scope: 'local'};
+ this.set(cm, params);
+ },
+ setglobal: function (cm, params) {
+ // setCfg is passed through to setOption
+ params.setCfg = {scope: 'global'};
+ this.set(cm, params);
+ },
+ registers: function(cm, params) {
+ var regArgs = params.args;
+ var registers = vimGlobalState.registerController.registers;
+ var regInfo = '----------Registers----------
';
+ if (!regArgs) {
+ for (var registerName in registers) {
+ var text = registers[registerName].toString();
+ if (text.length) {
+ regInfo += '"' + registerName + ' ' + text + ' ';
+ }
+ }
+ } else {
+ var registerName;
+ regArgs = regArgs.join('');
+ for (var i = 0; i < regArgs.length; i++) {
+ registerName = regArgs.charAt(i);
+ if (!vimGlobalState.registerController.isValidRegister(registerName)) {
+ continue;
+ }
+ var register = registers[registerName] || new Register();
+ regInfo += '"' + registerName + ' ' + register.toString() + ' ';
+ }
+ }
+ showConfirm(cm, regInfo);
+ },
+ sort: function(cm, params) {
+ var reverse, ignoreCase, unique, number, pattern;
+ function parseArgs() {
+ if (params.argString) {
+ var args = new CodeMirror.StringStream(params.argString);
+ if (args.eat('!')) { reverse = true; }
+ if (args.eol()) { return; }
+ if (!args.eatSpace()) { return 'Invalid arguments'; }
+ var opts = args.match(/([dinuox]+)?\s*(\/.+\/)?\s*/);
+ if (!opts && !args.eol()) { return 'Invalid arguments'; }
+ if (opts[1]) {
+ ignoreCase = opts[1].indexOf('i') != -1;
+ unique = opts[1].indexOf('u') != -1;
+ var decimal = opts[1].indexOf('d') != -1 || opts[1].indexOf('n') != -1 && 1;
+ var hex = opts[1].indexOf('x') != -1 && 1;
+ var octal = opts[1].indexOf('o') != -1 && 1;
+ if (decimal + hex + octal > 1) { return 'Invalid arguments'; }
+ number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';
+ }
+ if (opts[2]) {
+ pattern = new RegExp(opts[2].substr(1, opts[2].length - 2), ignoreCase ? 'i' : '');
+ }
+ }
+ }
+ var err = parseArgs();
+ if (err) {
+ showConfirm(cm, err + ': ' + params.argString);
+ return;
+ }
+ var lineStart = params.line || cm.firstLine();
+ var lineEnd = params.lineEnd || params.line || cm.lastLine();
+ if (lineStart == lineEnd) { return; }
+ var curStart = Pos(lineStart, 0);
+ var curEnd = Pos(lineEnd, lineLength(cm, lineEnd));
+ var text = cm.getRange(curStart, curEnd).split('\n');
+ var numberRegex = pattern ? pattern :
+ (number == 'decimal') ? /(-?)([\d]+)/ :
+ (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i :
+ (number == 'octal') ? /([0-7]+)/ : null;
+ var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null;
+ var numPart = [], textPart = [];
+ if (number || pattern) {
+ for (var i = 0; i < text.length; i++) {
+ var matchPart = pattern ? text[i].match(pattern) : null;
+ if (matchPart && matchPart[0] != '') {
+ numPart.push(matchPart);
+ } else if (!pattern && numberRegex.exec(text[i])) {
+ numPart.push(text[i]);
+ } else {
+ textPart.push(text[i]);
+ }
+ }
+ } else {
+ textPart = text;
+ }
+ function compareFn(a, b) {
+ if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
+ if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); }
+ var anum = number && numberRegex.exec(a);
+ var bnum = number && numberRegex.exec(b);
+ if (!anum) { return a < b ? -1 : 1; }
+ anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix);
+ bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix);
+ return anum - bnum;
+ }
+ function comparePatternFn(a, b) {
+ if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
+ if (ignoreCase) { a[0] = a[0].toLowerCase(); b[0] = b[0].toLowerCase(); }
+ return (a[0] < b[0]) ? -1 : 1;
+ }
+ numPart.sort(pattern ? comparePatternFn : compareFn);
+ if (pattern) {
+ for (var i = 0; i < numPart.length; i++) {
+ numPart[i] = numPart[i].input;
+ }
+ } else if (!number) { textPart.sort(compareFn); }
+ text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart);
+ if (unique) { // Remove duplicate lines
+ var textOld = text;
+ var lastLine;
+ text = [];
+ for (var i = 0; i < textOld.length; i++) {
+ if (textOld[i] != lastLine) {
+ text.push(textOld[i]);
+ }
+ lastLine = textOld[i];
+ }
+ }
+ cm.replaceRange(text.join('\n'), curStart, curEnd);
+ },
+ global: function(cm, params) {
+ // a global command is of the form
+ // :[range]g/pattern/[cmd]
+ // argString holds the string /pattern/[cmd]
+ var argString = params.argString;
+ if (!argString) {
+ showConfirm(cm, 'Regular Expression missing from global');
+ return;
+ }
+ // range is specified here
+ var lineStart = (params.line !== undefined) ? params.line : cm.firstLine();
+ var lineEnd = params.lineEnd || params.line || cm.lastLine();
+ // get the tokens from argString
+ var tokens = splitBySlash(argString);
+ var regexPart = argString, cmd;
+ if (tokens.length) {
+ regexPart = tokens[0];
+ cmd = tokens.slice(1, tokens.length).join('/');
+ }
+ if (regexPart) {
+ // If regex part is empty, then use the previous query. Otherwise
+ // use the regex part as the new query.
+ try {
+ updateSearchQuery(cm, regexPart, true /** ignoreCase */,
+ true /** smartCase */);
+ } catch (e) {
+ showConfirm(cm, 'Invalid regex: ' + regexPart);
+ return;
+ }
+ }
+ // now that we have the regexPart, search for regex matches in the
+ // specified range of lines
+ var query = getSearchState(cm).getQuery();
+ var matchedLines = [], content = '';
+ for (var i = lineStart; i <= lineEnd; i++) {
+ var matched = query.test(cm.getLine(i));
+ if (matched) {
+ matchedLines.push(i+1);
+ content+= cm.getLine(i) + ' ';
+ }
+ }
+ // if there is no [cmd], just display the list of matched lines
+ if (!cmd) {
+ showConfirm(cm, content);
+ return;
+ }
+ var index = 0;
+ var nextCommand = function() {
+ if (index < matchedLines.length) {
+ var command = matchedLines[index] + cmd;
+ exCommandDispatcher.processCommand(cm, command, {
+ callback: nextCommand
+ });
+ }
+ index++;
+ };
+ nextCommand();
+ },
+ substitute: function(cm, params) {
+ if (!cm.getSearchCursor) {
+ throw new Error('Search feature not available. Requires searchcursor.js or ' +
+ 'any other getSearchCursor implementation.');
+ }
+ var argString = params.argString;
+ var tokens = argString ? splitBySeparator(argString, argString[0]) : [];
+ var regexPart, replacePart = '', trailing, flagsPart, count;
+ var confirm = false; // Whether to confirm each replace.
+ var global = false; // True to replace all instances on a line, false to replace only 1.
+ if (tokens.length) {
+ regexPart = tokens[0];
+ if (getOption('pcre') && regexPart !== '') {
+ regexPart = new RegExp(regexPart).source; //normalize not escaped characters
+ }
+ replacePart = tokens[1];
+ if (regexPart && regexPart[regexPart.length - 1] === '$') {
+ regexPart = regexPart.slice(0, regexPart.length - 1) + '\\n';
+ replacePart = replacePart ? replacePart + '\n' : '\n';
+ }
+ if (replacePart !== undefined) {
+ if (getOption('pcre')) {
+ replacePart = unescapeRegexReplace(replacePart.replace(/([^\\])&/g,"$1$$&"));
+ } else {
+ replacePart = translateRegexReplace(replacePart);
+ }
+ vimGlobalState.lastSubstituteReplacePart = replacePart;
+ }
+ trailing = tokens[2] ? tokens[2].split(' ') : [];
+ } else {
+ // either the argString is empty or its of the form ' hello/world'
+ // actually splitBySlash returns a list of tokens
+ // only if the string starts with a '/'
+ if (argString && argString.length) {
+ showConfirm(cm, 'Substitutions should be of the form ' +
+ ':s/pattern/replace/');
+ return;
+ }
+ }
+ // After the 3rd slash, we can have flags followed by a space followed
+ // by count.
+ if (trailing) {
+ flagsPart = trailing[0];
+ count = parseInt(trailing[1]);
+ if (flagsPart) {
+ if (flagsPart.indexOf('c') != -1) {
+ confirm = true;
+ flagsPart.replace('c', '');
+ }
+ if (flagsPart.indexOf('g') != -1) {
+ global = true;
+ flagsPart.replace('g', '');
+ }
+ if (getOption('pcre')) {
+ regexPart = regexPart + '/' + flagsPart;
+ } else {
+ regexPart = regexPart.replace(/\//g, "\\/") + '/' + flagsPart;
+ }
+ }
+ }
+ if (regexPart) {
+ // If regex part is empty, then use the previous query. Otherwise use
+ // the regex part as the new query.
+ try {
+ updateSearchQuery(cm, regexPart, true /** ignoreCase */,
+ true /** smartCase */);
+ } catch (e) {
+ showConfirm(cm, 'Invalid regex: ' + regexPart);
+ return;
+ }
+ }
+ replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart;
+ if (replacePart === undefined) {
+ showConfirm(cm, 'No previous substitute regular expression');
+ return;
+ }
+ var state = getSearchState(cm);
+ var query = state.getQuery();
+ var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;
+ var lineEnd = params.lineEnd || lineStart;
+ if (lineStart == cm.firstLine() && lineEnd == cm.lastLine()) {
+ lineEnd = Infinity;
+ }
+ if (count) {
+ lineStart = lineEnd;
+ lineEnd = lineStart + count - 1;
+ }
+ var startPos = clipCursorToContent(cm, Pos(lineStart, 0));
+ var cursor = cm.getSearchCursor(query, startPos);
+ doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback);
+ },
+ redo: CodeMirror.commands.redo,
+ undo: CodeMirror.commands.undo,
+ write: function(cm) {
+ if (CodeMirror.commands.save) {
+ // If a save command is defined, call it.
+ CodeMirror.commands.save(cm);
+ } else if (cm.save) {
+ // Saves to text area if no save command is defined and cm.save() is available.
+ cm.save();
+ }
+ },
+ nohlsearch: function(cm) {
+ clearSearchHighlight(cm);
+ },
+ yank: function (cm) {
+ var cur = copyCursor(cm.getCursor());
+ var line = cur.line;
+ var lineText = cm.getLine(line);
+ vimGlobalState.registerController.pushText(
+ '0', 'yank', lineText, true, true);
+ },
+ delmarks: function(cm, params) {
+ if (!params.argString || !trim(params.argString)) {
+ showConfirm(cm, 'Argument required');
+ return;
+ }
+
+ var state = cm.state.vim;
+ var stream = new CodeMirror.StringStream(trim(params.argString));
+ while (!stream.eol()) {
+ stream.eatSpace();
+
+ // Record the streams position at the beginning of the loop for use
+ // in error messages.
+ var count = stream.pos;
+
+ if (!stream.match(/[a-zA-Z]/, false)) {
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
+ return;
+ }
+
+ var sym = stream.next();
+ // Check if this symbol is part of a range
+ if (stream.match('-', true)) {
+ // This symbol is part of a range.
+
+ // The range must terminate at an alphabetic character.
+ if (!stream.match(/[a-zA-Z]/, false)) {
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
+ return;
+ }
+
+ var startMark = sym;
+ var finishMark = stream.next();
+ // The range must terminate at an alphabetic character which
+ // shares the same case as the start of the range.
+ if (isLowerCase(startMark) && isLowerCase(finishMark) ||
+ isUpperCase(startMark) && isUpperCase(finishMark)) {
+ var start = startMark.charCodeAt(0);
+ var finish = finishMark.charCodeAt(0);
+ if (start >= finish) {
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
+ return;
+ }
+
+ // Because marks are always ASCII values, and we have
+ // determined that they are the same case, we can use
+ // their char codes to iterate through the defined range.
+ for (var j = 0; j <= finish - start; j++) {
+ var mark = String.fromCharCode(start + j);
+ delete state.marks[mark];
+ }
+ } else {
+ showConfirm(cm, 'Invalid argument: ' + startMark + '-');
+ return;
+ }
+ } else {
+ // This symbol is a valid mark, and is not part of a range.
+ delete state.marks[sym];
+ }
+ }
+ }
+ };
+
+ var exCommandDispatcher = new ExCommandDispatcher();
+
+ /**
+ * @param {CodeMirror} cm CodeMirror instance we are in.
+ * @param {boolean} confirm Whether to confirm each replace.
+ * @param {Cursor} lineStart Line to start replacing from.
+ * @param {Cursor} lineEnd Line to stop replacing at.
+ * @param {RegExp} query Query for performing matches with.
+ * @param {string} replaceWith Text to replace matches with. May contain $1,
+ * $2, etc for replacing captured groups using Javascript replace.
+ * @param {function()} callback A callback for when the replace is done.
+ */
+ function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query,
+ replaceWith, callback) {
+ // Set up all the functions.
+ cm.state.vim.exMode = true;
+ var done = false;
+ var lastPos = searchCursor.from();
+ function replaceAll() {
+ cm.operation(function() {
+ while (!done) {
+ replace();
+ next();
+ }
+ stop();
+ });
+ }
+ function replace() {
+ var text = cm.getRange(searchCursor.from(), searchCursor.to());
+ var newText = text.replace(query, replaceWith);
+ searchCursor.replace(newText);
+ }
+ function next() {
+ // The below only loops to skip over multiple occurrences on the same
+ // line when 'global' is not true.
+ while(searchCursor.findNext() &&
+ isInRange(searchCursor.from(), lineStart, lineEnd)) {
+ if (!global && lastPos && searchCursor.from().line == lastPos.line) {
+ continue;
+ }
+ cm.scrollIntoView(searchCursor.from(), 30);
+ cm.setSelection(searchCursor.from(), searchCursor.to());
+ lastPos = searchCursor.from();
+ done = false;
+ return;
+ }
+ done = true;
+ }
+ function stop(close) {
+ if (close) { close(); }
+ cm.focus();
+ if (lastPos) {
+ cm.setCursor(lastPos);
+ var vim = cm.state.vim;
+ vim.exMode = false;
+ vim.lastHPos = vim.lastHSPos = lastPos.ch;
+ }
+ if (callback) { callback(); }
+ }
+ function onPromptKeyDown(e, _value, close) {
+ // Swallow all keys.
+ CodeMirror.e_stop(e);
+ var keyName = CodeMirror.keyName(e);
+ switch (keyName) {
+ case 'Y':
+ replace(); next(); break;
+ case 'N':
+ next(); break;
+ case 'A':
+ // replaceAll contains a call to close of its own. We don't want it
+ // to fire too early or multiple times.
+ var savedCallback = callback;
+ callback = undefined;
+ cm.operation(replaceAll);
+ callback = savedCallback;
+ break;
+ case 'L':
+ replace();
+ // fall through and exit.
+ case 'Q':
+ case 'Esc':
+ case 'Ctrl-C':
+ case 'Ctrl-[':
+ stop(close);
+ break;
+ }
+ if (done) { stop(close); }
+ return true;
+ }
+
+ // Actually do replace.
+ next();
+ if (done) {
+ showConfirm(cm, 'No matches for ' + query.source);
+ return;
+ }
+ if (!confirm) {
+ replaceAll();
+ if (callback) { callback(); }
+ return;
+ }
+ showPrompt(cm, {
+ prefix: 'replace with ' + replaceWith + ' (y/n/a/q/l)',
+ onKeyDown: onPromptKeyDown
+ });
+ }
+
+ CodeMirror.keyMap.vim = {
+ attach: attachVimMap,
+ detach: detachVimMap,
+ call: cmKey
+ };
+
+ function exitInsertMode(cm) {
+ var vim = cm.state.vim;
+ var macroModeState = vimGlobalState.macroModeState;
+ var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.');
+ var isPlaying = macroModeState.isPlaying;
+ var lastChange = macroModeState.lastInsertModeChanges;
+ if (!isPlaying) {
+ cm.off('change', onChange);
+ CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
+ }
+ if (!isPlaying && vim.insertModeRepeat > 1) {
+ // Perform insert mode repeat for commands like 3,a and 3,o.
+ repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,
+ true /** repeatForInsert */);
+ vim.lastEditInputState.repeatOverride = vim.insertModeRepeat;
+ }
+ delete vim.insertModeRepeat;
+ vim.insertMode = false;
+ cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1);
+ cm.setOption('keyMap', 'vim');
+ cm.setOption('disableInput', true);
+ cm.toggleOverwrite(false); // exit replace mode if we were in it.
+ // update the ". register before exiting insert mode
+ insertModeChangeRegister.setText(lastChange.changes.join(''));
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
+ if (macroModeState.isRecording) {
+ logInsertModeChange(macroModeState);
+ }
+ }
+
+ function _mapCommand(command) {
+ defaultKeymap.unshift(command);
+ }
+
+ function mapCommand(keys, type, name, args, extra) {
+ var command = {keys: keys, type: type};
+ command[type] = name;
+ command[type + "Args"] = args;
+ for (var key in extra)
+ command[key] = extra[key];
+ _mapCommand(command);
+ }
+
+ // The timeout in milliseconds for the two-character ESC keymap should be
+ // adjusted according to your typing speed to prevent false positives.
+ defineOption('insertModeEscKeysTimeout', 200, 'number');
+
+ CodeMirror.keyMap['vim-insert'] = {
+ // TODO: override navigation keys so that Esc will cancel automatic
+ // indentation from o, O, i_
+ fallthrough: ['default'],
+ attach: attachVimMap,
+ detach: detachVimMap,
+ call: cmKey
+ };
+
+ CodeMirror.keyMap['vim-replace'] = {
+ 'Backspace': 'goCharLeft',
+ fallthrough: ['vim-insert'],
+ attach: attachVimMap,
+ detach: detachVimMap,
+ call: cmKey
+ };
+
+ function executeMacroRegister(cm, vim, macroModeState, registerName) {
+ var register = vimGlobalState.registerController.getRegister(registerName);
+ if (registerName == ':') {
+ // Read-only register containing last Ex command.
+ if (register.keyBuffer[0]) {
+ exCommandDispatcher.processCommand(cm, register.keyBuffer[0]);
+ }
+ macroModeState.isPlaying = false;
+ return;
+ }
+ var keyBuffer = register.keyBuffer;
+ var imc = 0;
+ macroModeState.isPlaying = true;
+ macroModeState.replaySearchQueries = register.searchQueries.slice(0);
+ for (var i = 0; i < keyBuffer.length; i++) {
+ var text = keyBuffer[i];
+ var match, key;
+ while (text) {
+ // Pull off one command key, which is either a single character
+ // or a special sequence wrapped in '<' and '>', e.g. ''.
+ match = (/<\w+-.+?>|<\w+>|./).exec(text);
+ key = match[0];
+ text = text.substring(match.index + key.length);
+ CodeMirror.Vim.handleKey(cm, key, 'macro');
+ if (vim.insertMode) {
+ var changes = register.insertModeChanges[imc++].changes;
+ vimGlobalState.macroModeState.lastInsertModeChanges.changes =
+ changes;
+ repeatInsertModeChanges(cm, changes, 1);
+ exitInsertMode(cm);
+ }
+ }
+ }
+ macroModeState.isPlaying = false;
+ }
+
+ function logKey(macroModeState, key) {
+ if (macroModeState.isPlaying) { return; }
+ var registerName = macroModeState.latestRegister;
+ var register = vimGlobalState.registerController.getRegister(registerName);
+ if (register) {
+ register.pushText(key);
+ }
+ }
+
+ function logInsertModeChange(macroModeState) {
+ if (macroModeState.isPlaying) { return; }
+ var registerName = macroModeState.latestRegister;
+ var register = vimGlobalState.registerController.getRegister(registerName);
+ if (register && register.pushInsertModeChanges) {
+ register.pushInsertModeChanges(macroModeState.lastInsertModeChanges);
+ }
+ }
+
+ function logSearchQuery(macroModeState, query) {
+ if (macroModeState.isPlaying) { return; }
+ var registerName = macroModeState.latestRegister;
+ var register = vimGlobalState.registerController.getRegister(registerName);
+ if (register && register.pushSearchQuery) {
+ register.pushSearchQuery(query);
+ }
+ }
+
+ /**
+ * Listens for changes made in insert mode.
+ * Should only be active in insert mode.
+ */
+ function onChange(cm, changeObj) {
+ var macroModeState = vimGlobalState.macroModeState;
+ var lastChange = macroModeState.lastInsertModeChanges;
+ if (!macroModeState.isPlaying) {
+ while(changeObj) {
+ lastChange.expectCursorActivityForChange = true;
+ if (lastChange.ignoreCount > 1) {
+ lastChange.ignoreCount--;
+ } else if (changeObj.origin == '+input' || changeObj.origin == 'paste'
+ || changeObj.origin === undefined /* only in testing */) {
+ var selectionCount = cm.listSelections().length;
+ if (selectionCount > 1)
+ lastChange.ignoreCount = selectionCount;
+ var text = changeObj.text.join('\n');
+ if (lastChange.maybeReset) {
+ lastChange.changes = [];
+ lastChange.maybeReset = false;
+ }
+ if (text) {
+ if (cm.state.overwrite && !/\n/.test(text)) {
+ lastChange.changes.push([text]);
+ } else {
+ lastChange.changes.push(text);
+ }
+ }
+ }
+ // Change objects may be chained with next.
+ changeObj = changeObj.next;
+ }
+ }
+ }
+
+ /**
+ * Listens for any kind of cursor activity on CodeMirror.
+ */
+ function onCursorActivity(cm) {
+ var vim = cm.state.vim;
+ if (vim.insertMode) {
+ // Tracking cursor activity in insert mode (for macro support).
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.isPlaying) { return; }
+ var lastChange = macroModeState.lastInsertModeChanges;
+ if (lastChange.expectCursorActivityForChange) {
+ lastChange.expectCursorActivityForChange = false;
+ } else {
+ // Cursor moved outside the context of an edit. Reset the change.
+ lastChange.maybeReset = true;
+ }
+ } else if (!cm.curOp.isVimOp) {
+ handleExternalSelection(cm, vim);
+ }
+ if (vim.visualMode) {
+ updateFakeCursor(cm);
+ }
+ }
+ function updateFakeCursor(cm) {
+ var vim = cm.state.vim;
+ var from = clipCursorToContent(cm, copyCursor(vim.sel.head));
+ var to = offsetCursor(from, 0, 1);
+ if (vim.fakeCursor) {
+ vim.fakeCursor.clear();
+ }
+ vim.fakeCursor = cm.markText(from, to, {className: 'cm-animate-fat-cursor'});
+ }
+ function handleExternalSelection(cm, vim) {
+ var anchor = cm.getCursor('anchor');
+ var head = cm.getCursor('head');
+ // Enter or exit visual mode to match mouse selection.
+ if (vim.visualMode && !cm.somethingSelected()) {
+ exitVisualMode(cm, false);
+ } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) {
+ vim.visualMode = true;
+ vim.visualLine = false;
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
+ }
+ if (vim.visualMode) {
+ // Bind CodeMirror selection model to vim selection model.
+ // Mouse selections are considered visual characterwise.
+ var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0;
+ var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0;
+ head = offsetCursor(head, 0, headOffset);
+ anchor = offsetCursor(anchor, 0, anchorOffset);
+ vim.sel = {
+ anchor: anchor,
+ head: head
+ };
+ updateMark(cm, vim, '<', cursorMin(head, anchor));
+ updateMark(cm, vim, '>', cursorMax(head, anchor));
+ } else if (!vim.insertMode) {
+ // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse.
+ vim.lastHPos = cm.getCursor().ch;
+ }
+ }
+
+ /** Wrapper for special keys pressed in insert mode */
+ function InsertModeKey(keyName) {
+ this.keyName = keyName;
+ }
+
+ /**
+ * Handles raw key down events from the text area.
+ * - Should only be active in insert mode.
+ * - For recording deletes in insert mode.
+ */
+ function onKeyEventTargetKeyDown(e) {
+ var macroModeState = vimGlobalState.macroModeState;
+ var lastChange = macroModeState.lastInsertModeChanges;
+ var keyName = CodeMirror.keyName(e);
+ if (!keyName) { return; }
+ function onKeyFound() {
+ if (lastChange.maybeReset) {
+ lastChange.changes = [];
+ lastChange.maybeReset = false;
+ }
+ lastChange.changes.push(new InsertModeKey(keyName));
+ return true;
+ }
+ if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {
+ CodeMirror.lookupKey(keyName, 'vim-insert', onKeyFound);
+ }
+ }
+
+ /**
+ * Repeats the last edit, which includes exactly 1 command and at most 1
+ * insert. Operator and motion commands are read from lastEditInputState,
+ * while action commands are read from lastEditActionCommand.
+ *
+ * If repeatForInsert is true, then the function was called by
+ * exitInsertMode to repeat the insert mode changes the user just made. The
+ * corresponding enterInsertMode call was made with a count.
+ */
+ function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
+ var macroModeState = vimGlobalState.macroModeState;
+ macroModeState.isPlaying = true;
+ var isAction = !!vim.lastEditActionCommand;
+ var cachedInputState = vim.inputState;
+ function repeatCommand() {
+ if (isAction) {
+ commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand);
+ } else {
+ commandDispatcher.evalInput(cm, vim);
+ }
+ }
+ function repeatInsert(repeat) {
+ if (macroModeState.lastInsertModeChanges.changes.length > 0) {
+ // For some reason, repeat cw in desktop VIM does not repeat
+ // insert mode changes. Will conform to that behavior.
+ repeat = !vim.lastEditActionCommand ? 1 : repeat;
+ var changeObject = macroModeState.lastInsertModeChanges;
+ repeatInsertModeChanges(cm, changeObject.changes, repeat);
+ }
+ }
+ vim.inputState = vim.lastEditInputState;
+ if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) {
+ // o and O repeat have to be interlaced with insert repeats so that the
+ // insertions appear on separate lines instead of the last line.
+ for (var i = 0; i < repeat; i++) {
+ repeatCommand();
+ repeatInsert(1);
+ }
+ } else {
+ if (!repeatForInsert) {
+ // Hack to get the cursor to end up at the right place. If I is
+ // repeated in insert mode repeat, cursor will be 1 insert
+ // change set left of where it should be.
+ repeatCommand();
+ }
+ repeatInsert(repeat);
+ }
+ vim.inputState = cachedInputState;
+ if (vim.insertMode && !repeatForInsert) {
+ // Don't exit insert mode twice. If repeatForInsert is set, then we
+ // were called by an exitInsertMode call lower on the stack.
+ exitInsertMode(cm);
+ }
+ macroModeState.isPlaying = false;
+ }
+
+ function repeatInsertModeChanges(cm, changes, repeat) {
+ function keyHandler(binding) {
+ if (typeof binding == 'string') {
+ CodeMirror.commands[binding](cm);
+ } else {
+ binding(cm);
+ }
+ return true;
+ }
+ var head = cm.getCursor('head');
+ var visualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.visualBlock;
+ if (visualBlock) {
+ // Set up block selection again for repeating the changes.
+ selectForInsert(cm, head, visualBlock + 1);
+ repeat = cm.listSelections().length;
+ cm.setCursor(head);
+ }
+ for (var i = 0; i < repeat; i++) {
+ if (visualBlock) {
+ cm.setCursor(offsetCursor(head, i, 0));
+ }
+ for (var j = 0; j < changes.length; j++) {
+ var change = changes[j];
+ if (change instanceof InsertModeKey) {
+ CodeMirror.lookupKey(change.keyName, 'vim-insert', keyHandler);
+ } else if (typeof change == "string") {
+ var cur = cm.getCursor();
+ cm.replaceRange(change, cur, cur);
+ } else {
+ var start = cm.getCursor();
+ var end = offsetCursor(start, 0, change[0].length);
+ cm.replaceRange(change[0], start, end);
+ }
+ }
+ }
+ if (visualBlock) {
+ cm.setCursor(offsetCursor(head, 0, 1));
+ }
+ }
+
+ resetVimGlobalState();
+ return vimApi;
};
-
- // One function to handle all operation upon text objects. Kinda funky but it works
- // better than rewriting this code six times
- function textObjectManipulation(cm, object, remove, insert, inclusive) {
- // Object is the text object, delete object if remove is true, enter insert
- // mode if insert is true, inclusive is the difference between a and i
- var tmp = textObjects[object](cm, inclusive);
- var start = tmp.start;
- var end = tmp.end;
-
- if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true ;
-
- pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
- if (remove) cm.replaceRange("", swap ? end : start, swap ? start : end);
- if (insert) cm.setOption('keyMap', 'vim-insert');
- }
-
- // And finally build the keymaps up from the text objects
- for (var i = 0; i < textObjectList.length; ++i) {
- var object = textObjectList[i];
- (function(object) {
- CodeMirror.keyMap['vim-prefix-di'][object] = function(cm) { textObjectManipulation(cm, object, true, false, false); };
- CodeMirror.keyMap['vim-prefix-da'][object] = function(cm) { textObjectManipulation(cm, object, true, false, true); };
- CodeMirror.keyMap['vim-prefix-yi'][object] = function(cm) { textObjectManipulation(cm, object, false, false, false); };
- CodeMirror.keyMap['vim-prefix-ya'][object] = function(cm) { textObjectManipulation(cm, object, false, false, true); };
- CodeMirror.keyMap['vim-prefix-ci'][object] = function(cm) { textObjectManipulation(cm, object, true, true, false); };
- CodeMirror.keyMap['vim-prefix-ca'][object] = function(cm) { textObjectManipulation(cm, object, true, true, true); };
- })(object)
- }
-})();
+ // Initialize Vim and make it available as an API.
+ CodeMirror.Vim = Vim();
+});
diff --git a/scripts/codemirror/lib/codemirror.css b/scripts/codemirror/lib/codemirror.css
index eb7e598..c7a8ae7 100644
--- a/scripts/codemirror/lib/codemirror.css
+++ b/scripts/codemirror/lib/codemirror.css
@@ -1,173 +1,346 @@
-.CodeMirror {
- line-height: 1em;
- font-family: monospace;
+/* BASICS */
- /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */
- position: relative;
- /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 300px;
+ color: black;
+ direction: ltr;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+ white-space: nowrap;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror-cursor {
+ border-left: 1px solid black;
+ border-right: none;
+ width: 0;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.cm-fat-cursor .CodeMirror-cursor {
+ width: auto;
+ border: 0 !important;
+ background: #7e7;
+}
+.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+.cm-fat-cursor-mark {
+ background-color: rgba(20, 255, 20, 0.5);
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+}
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
+}
+@-moz-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@-webkit-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror-overwrite .CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-rulers {
+ position: absolute;
+ left: 0; right: 0; top: -50px; bottom: -20px;
overflow: hidden;
}
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ top: 0; bottom: 0;
+ position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ position: relative;
+ overflow: hidden;
+ background: white;
+}
.CodeMirror-scroll {
- overflow: auto;
- height: 300px;
- /* This is needed to prevent an IE[67] bug where the scrolled content
- is visible outside of the scrolling box. */
+ overflow: scroll !important; /* Things will break if this is overridden */
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
position: relative;
- outline: none;
+}
+.CodeMirror-sizer {
+ position: relative;
+ border-right: 30px solid transparent;
}
-/* Vertical scrollbar */
-.CodeMirror-scrollbar {
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actual scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
- z-index: 0;
}
-.CodeMirror-scrollbar-inner {
- /* This needs to have a nonzero width in order for the scrollbar to appear
- in Firefox and IE9. */
- width: 1px;
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
}
-.CodeMirror-scrollbar.cm-sb-overlap {
- /* Ensure that the scrollbar appears in Lion, and that it overlaps the content
- rather than sitting to the right of it. */
- position: absolute;
- z-index: 1;
- float: none;
- right: 0;
- min-width: 12px;
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
}
-.CodeMirror-scrollbar.cm-sb-nonoverlap {
- min-width: 12px;
-}
-.CodeMirror-scrollbar.cm-sb-ie7 {
- min-width: 18px;
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
}
-.CodeMirror-gutter {
+.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
- z-index: 10;
- background-color: #f7f7f7;
- border-right: 1px solid #eee;
- min-width: 2em;
+ min-height: 100%;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
height: 100%;
+ display: inline-block;
+ vertical-align: top;
+ margin-bottom: -30px;
}
-.CodeMirror-gutter-text {
- color: #aaa;
- text-align: right;
- padding: .4em .2em .4em .4em;
- white-space: pre !important;
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0; bottom: 0;
+ z-index: 4;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
cursor: default;
+ z-index: 4;
}
-.CodeMirror-lines {
- padding: .4em;
- white-space: pre;
- cursor: text;
-}
+.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
+.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
+.CodeMirror-lines {
+ cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
+}
.CodeMirror pre {
- -moz-border-radius: 0;
- -webkit-border-radius: 0;
- -o-border-radius: 0;
- border-radius: 0;
- border-width: 0; margin: 0; padding: 0; background: transparent;
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
font-family: inherit;
font-size: inherit;
- padding: 0; margin: 0;
+ margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: contextual;
+ font-variant-ligatures: contextual;
}
-
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
-.CodeMirror-wrap .CodeMirror-scroll {
- overflow-x: hidden;
-}
-.CodeMirror textarea {
- outline: none !important;
-}
-
-.CodeMirror pre.CodeMirror-cursor {
- z-index: 10;
+.CodeMirror-linebackground {
position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ padding: 0.1px; /* Force widget margins to stay inside of the container */
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-rtl pre { direction: rtl; }
+
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ overflow: hidden;
visibility: hidden;
- border-left: 1px solid black;
- border-right: none;
- width: 0;
}
-.cm-keymap-fat-cursor pre.CodeMirror-cursor {
- width: auto;
- border: 0;
- background: transparent;
- background: rgba(0, 200, 0, .4);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800);
+
+.CodeMirror-cursor {
+ position: absolute;
+ pointer-events: none;
}
-/* Kludge to turn off filter in ie9+, which also accepts rgba */
-.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) {
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+.CodeMirror-measure pre { position: static; }
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 3;
}
-.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
-.CodeMirror-focused pre.CodeMirror-cursor {
+div.CodeMirror-dragcursors {
visibility: visible;
}
-div.CodeMirror-selected { background: #d9d9d9; }
-.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
-
-.CodeMirror-searching {
- background: #ffa;
- background: rgba(255, 255, 0, .4);
+.CodeMirror-focused div.CodeMirror-cursors {
+ visibility: visible;
}
-/* Default theme */
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
-.cm-s-default span.cm-keyword {color: #708;}
-.cm-s-default span.cm-atom {color: #219;}
-.cm-s-default span.cm-number {color: #164;}
-.cm-s-default span.cm-def {color: #00f;}
-.cm-s-default span.cm-variable {color: black;}
-.cm-s-default span.cm-variable-2 {color: #05a;}
-.cm-s-default span.cm-variable-3 {color: #085;}
-.cm-s-default span.cm-property {color: black;}
-.cm-s-default span.cm-operator {color: black;}
-.cm-s-default span.cm-comment {color: #a50;}
-.cm-s-default span.cm-string {color: #a11;}
-.cm-s-default span.cm-string-2 {color: #f50;}
-.cm-s-default span.cm-meta {color: #555;}
-.cm-s-default span.cm-error {color: #f00;}
-.cm-s-default span.cm-qualifier {color: #555;}
-.cm-s-default span.cm-builtin {color: #30a;}
-.cm-s-default span.cm-bracket {color: #cc7;}
-.cm-s-default span.cm-tag {color: #170;}
-.cm-s-default span.cm-attribute {color: #00c;}
-.cm-s-default span.cm-header {color: blue;}
-.cm-s-default span.cm-quote {color: #090;}
-.cm-s-default span.cm-hr {color: #999;}
-.cm-s-default span.cm-link {color: #00c;}
+.cm-searching {
+ background-color: #ffa;
+ background-color: rgba(255, 255, 0, .4);
+}
-span.cm-header, span.cm-strong {font-weight: bold;}
-span.cm-em {font-style: italic;}
-span.cm-emstrong {font-style: italic; font-weight: bold;}
-span.cm-link {text-decoration: underline;}
-
-span.cm-invalidchar {color: #f00;}
-
-div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
-div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
@media print {
-
/* Hide the cursor when printing */
- .CodeMirror pre.CodeMirror-cursor {
+ .CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
+}
-}
\ No newline at end of file
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }
diff --git a/scripts/codemirror/lib/codemirror.js b/scripts/codemirror/lib/codemirror.js
index ff0aa64..7399e52 100644
--- a/scripts/codemirror/lib/codemirror.js
+++ b/scripts/codemirror/lib/codemirror.js
@@ -1,3192 +1,653 @@
-// CodeMirror version 2.33
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+// This is CodeMirror (https://codemirror.net), a code editor
+// implemented in JavaScript on top of the browser's DOM.
//
-// All functions that need access to the editor's state live inside
-// the CodeMirror function. Below that, at the bottom of the file,
-// some utilities are defined.
-
-// CodeMirror is the only global var we claim
-window.CodeMirror = (function() {
- "use strict";
- // This is the function that produces an editor instance. Its
- // closure is used to store the editor state.
- function CodeMirror(place, givenOptions) {
- // Determine effective options based on given values and defaults.
- var options = {}, defaults = CodeMirror.defaults;
- for (var opt in defaults)
- if (defaults.hasOwnProperty(opt))
- options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
-
- var input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em");
- input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
- // Wraps and hides input textarea
- var inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
- // The empty scrollbar content, used solely for managing the scrollbar thumb.
- var scrollbarInner = elt("div", null, "CodeMirror-scrollbar-inner");
- // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself.
- var scrollbar = elt("div", [scrollbarInner], "CodeMirror-scrollbar");
- // DIVs containing the selection and the actual code
- var lineDiv = elt("div"), 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
- var cursor = elt("pre", "\u00a0", "CodeMirror-cursor"), widthForcer = elt("pre", "\u00a0", "CodeMirror-cursor", "visibility: hidden");
- // Used to measure text size
- var measure = elt("div", null, null, "position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;");
- var lineSpace = elt("div", [measure, cursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0");
- var gutterText = elt("div", null, "CodeMirror-gutter-text"), gutter = elt("div", [gutterText], "CodeMirror-gutter");
- // Moved around its parent to cover visible view
- var mover = elt("div", [gutter, elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative");
- // Set to the height of the text, causes scrolling
- var sizer = elt("div", [mover], null, "position: relative");
- // Provides scrolling
- var scroller = elt("div", [sizer], "CodeMirror-scroll");
- scroller.setAttribute("tabIndex", "-1");
- // The element in which the editor lives.
- var wrapper = elt("div", [inputDiv, scrollbar, scroller], "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""));
- if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
-
- themeChanged(); keyMapChanged();
- // Needed to hide big blue blinking cursor on Mobile Safari
- if (ios) input.style.width = "0px";
- if (!webkit) scroller.draggable = true;
- lineSpace.style.outline = "none";
- if (options.tabindex != null) input.tabIndex = options.tabindex;
- if (options.autofocus) focusInput();
- if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
- // Needed to handle Tab key in KHTML
- if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
-
- // Check for OS X >= 10.7. This has transparent scrollbars, so the
- // overlaying of one scrollbar with another won't work. This is a
- // temporary hack to simply turn off the overlay scrollbar. See
- // issue #727.
- if (mac_geLion) { scrollbar.style.zIndex = -2; scrollbar.style.visibility = "hidden"; }
- // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
- else if (ie_lt8) scrollbar.style.minWidth = "18px";
-
- // Check for problem with IE innerHTML not working when we have a
- // P (or similar) parent node.
- try { charWidth(); }
- catch (e) {
- if (e.message.match(/runtime/i))
- e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
- throw e;
- }
-
- // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
- var poll = new Delayed(), highlight = new Delayed(), blinker;
-
- // mode holds a mode API object. doc is the tree of Line objects,
- // work an array of lines that should be parsed, and history the
- // undo history (instance of History constructor).
- var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
- loadMode();
- // The selection. These are always maintained to point at valid
- // positions. Inverted is used to remember that the user is
- // selecting bottom-to-top.
- var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
- // Selection-related flags. shiftSelecting obviously tracks
- // whether the user is holding shift.
- var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, draggingText,
- overwrite = false, suppressEdits = false;
- // Variables used by startOperation/endOperation to track what
- // happened during the operation.
- var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
- gutterDirty, callbacks;
- // Current visible range (may be bigger than the view window).
- var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
- // bracketHighlighted is used to remember that a bracket has been
- // marked.
- var bracketHighlighted;
- // Tracks the maximum line length so that the horizontal scrollbar
- // can be kept static when scrolling.
- var maxLine = getLine(0), updateMaxLine = false, maxLineChanged = true;
- var tabCache = {};
- var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
- var goalColumn = null;
-
- // Initialize the content.
- operation(function(){setValue(options.value || ""); updateInput = false;})();
- var history = new History();
-
- // Register our event handlers.
- connect(scroller, "mousedown", operation(onMouseDown));
- connect(scroller, "dblclick", operation(onDoubleClick));
- connect(lineSpace, "selectstart", e_preventDefault);
- // 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 (!gecko) connect(scroller, "contextmenu", onContextMenu);
- connect(scroller, "scroll", onScrollMain);
- connect(scrollbar, "scroll", onScrollBar);
- connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);});
- var resizeHandler = connect(window, "resize", function() {
- if (wrapper.parentNode) updateDisplay(true);
- else resizeHandler();
- }, true);
- connect(input, "keyup", operation(onKeyUp));
- connect(input, "input", fastPoll);
- connect(input, "keydown", operation(onKeyDown));
- connect(input, "keypress", operation(onKeyPress));
- connect(input, "focus", onFocus);
- connect(input, "blur", onBlur);
-
- function drag_(e) {
- if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
- e_stop(e);
- }
- if (options.dragDrop) {
- connect(scroller, "dragstart", onDragStart);
- connect(scroller, "dragenter", drag_);
- connect(scroller, "dragover", drag_);
- connect(scroller, "drop", operation(onDrop));
- }
- connect(scroller, "paste", function(){focusInput(); fastPoll();});
- connect(input, "paste", fastPoll);
- connect(input, "cut", operation(function(){
- if (!options.readOnly) replaceSelection("");
- }));
-
- // Needed to handle Tab key in KHTML
- if (khtml) connect(sizer, "mouseup", function() {
- if (document.activeElement == input) input.blur();
- focusInput();
- });
-
- // IE throws unspecified error in certain cases, when
- // trying to access activeElement before onload
- var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { }
- if (hasFocus || options.autofocus) setTimeout(onFocus, 20);
- else onBlur();
-
- function isLine(l) {return l >= 0 && l < doc.size;}
- // The instance object that we'll return. Mostly calls out to
- // local functions in the CodeMirror function. Some do some extra
- // range checking and/or clipping. operation is used to wrap the
- // call so that changes it makes are tracked, and the display is
- // updated afterwards.
- var instance = wrapper.CodeMirror = {
- getValue: getValue,
- setValue: operation(setValue),
- getSelection: getSelection,
- replaceSelection: operation(replaceSelection),
- focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();},
- setOption: function(option, value) {
- var oldVal = options[option];
- options[option] = value;
- if (option == "mode" || option == "indentUnit") loadMode();
- else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
- else if (option == "readOnly" && !value) {resetInput(true);}
- else if (option == "theme") themeChanged();
- else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
- else if (option == "tabSize") updateDisplay(true);
- else if (option == "keyMap") keyMapChanged();
- if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" ||
- option == "theme" || option == "lineNumberFormatter") {
- gutterChanged();
- updateDisplay(true);
- }
- },
- getOption: function(option) {return options[option];},
- undo: operation(undo),
- redo: operation(redo),
- indentLine: operation(function(n, dir) {
- if (typeof dir != "string") {
- if (dir == null) dir = options.smartIndent ? "smart" : "prev";
- else dir = dir ? "add" : "subtract";
- }
- if (isLine(n)) indentLine(n, dir);
- }),
- indentSelection: operation(indentSelected),
- historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
- clearHistory: function() {history = new History();},
- setHistory: function(histData) {
- history = new History();
- history.done = histData.done;
- history.undone = histData.undone;
- },
- getHistory: function() {
- history.time = 0;
- return {done: history.done.concat([]), undone: history.undone.concat([])};
- },
- matchBrackets: operation(function(){matchBrackets(true);}),
- getTokenAt: operation(function(pos) {
- pos = clipPos(pos);
- return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), options.tabSize, pos.ch);
- }),
- getStateAfter: function(line) {
- line = clipLine(line == null ? doc.size - 1: line);
- return getStateBefore(line + 1);
- },
- cursorCoords: function(start, mode) {
- if (start == null) start = sel.inverted;
- return this.charCoords(start ? sel.from : sel.to, mode);
- },
- charCoords: function(pos, mode) {
- pos = clipPos(pos);
- if (mode == "local") return localCoords(pos, false);
- if (mode == "div") return localCoords(pos, true);
- return pageCoords(pos);
- },
- coordsChar: function(coords) {
- var off = eltOffset(lineSpace);
- return coordsChar(coords.x - off.left, coords.y - off.top);
- },
- markText: operation(markText),
- setBookmark: setBookmark,
- findMarksAt: findMarksAt,
- setMarker: operation(addGutterMarker),
- clearMarker: operation(removeGutterMarker),
- setLineClass: operation(setLineClass),
- hideLine: operation(function(h) {return setLineHidden(h, true);}),
- showLine: operation(function(h) {return setLineHidden(h, false);}),
- onDeleteLine: function(line, f) {
- if (typeof line == "number") {
- if (!isLine(line)) return null;
- line = getLine(line);
- }
- (line.handlers || (line.handlers = [])).push(f);
- return line;
- },
- lineInfo: lineInfo,
- getViewport: function() { return {from: showingFrom, to: showingTo};},
- addWidget: function(pos, node, scroll, vert, horiz) {
- pos = localCoords(clipPos(pos));
- var top = pos.yBot, left = pos.x;
- node.style.position = "absolute";
- sizer.appendChild(node);
- if (vert == "over") top = pos.y;
- else if (vert == "near") {
- var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
- hspace = Math.max(sizer.clientWidth, lineSpace.clientWidth) - paddingLeft();
- if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
- top = pos.y - node.offsetHeight;
- if (left + node.offsetWidth > hspace)
- left = hspace - node.offsetWidth;
- }
- node.style.top = (top + paddingTop()) + "px";
- node.style.left = node.style.right = "";
- if (horiz == "right") {
- left = sizer.clientWidth - node.offsetWidth;
- node.style.right = "0px";
- } else {
- if (horiz == "left") left = 0;
- else if (horiz == "middle") left = (sizer.clientWidth - node.offsetWidth) / 2;
- node.style.left = (left + paddingLeft()) + "px";
- }
- if (scroll)
- scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
- },
-
- lineCount: function() {return doc.size;},
- clipPos: clipPos,
- getCursor: function(start) {
- if (start == null) start = sel.inverted;
- return copyPos(start ? sel.from : sel.to);
- },
- somethingSelected: function() {return !posEq(sel.from, sel.to);},
- setCursor: operation(function(line, ch, user) {
- if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
- else setCursor(line, ch, user);
- }),
- setSelection: operation(function(from, to, user) {
- (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
- }),
- getLine: function(line) {if (isLine(line)) return getLine(line).text;},
- getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
- setLine: operation(function(line, text) {
- if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
- }),
- removeLine: operation(function(line) {
- if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
- }),
- replaceRange: operation(replaceRange),
- getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);},
-
- triggerOnKeyDown: operation(onKeyDown),
- execCommand: function(cmd) {return commands[cmd](instance);},
- // Stuff used by commands, probably not much use to outside code.
- moveH: operation(moveH),
- deleteH: operation(deleteH),
- moveV: operation(moveV),
- toggleOverwrite: function() {
- if(overwrite){
- overwrite = false;
- cursor.className = cursor.className.replace(" CodeMirror-overwrite", "");
- } else {
- overwrite = true;
- cursor.className += " CodeMirror-overwrite";
- }
- },
-
- posFromIndex: function(off) {
- var lineNo = 0, ch;
- doc.iter(0, doc.size, function(line) {
- var sz = line.text.length + 1;
- if (sz > off) { ch = off; return true; }
- off -= sz;
- ++lineNo;
- });
- return clipPos({line: lineNo, ch: ch});
- },
- indexFromPos: function (coords) {
- if (coords.line < 0 || coords.ch < 0) return 0;
- var index = coords.ch;
- doc.iter(0, coords.line, function (line) {
- index += line.text.length + 1;
- });
- return index;
- },
- scrollTo: function(x, y) {
- if (x != null) scroller.scrollLeft = x;
- if (y != null) scrollbar.scrollTop = scroller.scrollTop = y;
- updateDisplay([]);
- },
- getScrollInfo: function() {
- return {x: scroller.scrollLeft, y: scrollbar.scrollTop,
- height: scrollbar.scrollHeight, width: scroller.scrollWidth};
- },
- setSize: function(width, height) {
- function interpret(val) {
- val = String(val);
- return /^\d+$/.test(val) ? val + "px" : val;
- }
- if (width != null) wrapper.style.width = interpret(width);
- if (height != null) scroller.style.height = interpret(height);
- instance.refresh();
- },
-
- operation: function(f){return operation(f)();},
- compoundChange: function(f){return compoundChange(f);},
- refresh: function(){
- updateDisplay(true, null, lastScrollTop);
- if (scrollbar.scrollHeight > lastScrollTop)
- scrollbar.scrollTop = lastScrollTop;
- },
- getInputField: function(){return input;},
- getWrapperElement: function(){return wrapper;},
- getScrollerElement: function(){return scroller;},
- getGutterElement: function(){return gutter;}
- };
-
- function getLine(n) { return getLineAt(doc, n); }
- function updateLineHeight(line, height) {
- gutterDirty = true;
- var diff = height - line.height;
- for (var n = line; n; n = n.parent) n.height += diff;
- }
-
- function setValue(code) {
- var top = {line: 0, ch: 0};
- updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
- splitLines(code), top, top);
- updateInput = true;
- }
- function getValue(lineSep) {
- var text = [];
- doc.iter(0, doc.size, function(line) { text.push(line.text); });
- return text.join(lineSep || "\n");
- }
-
- function onScrollBar(e) {
- if (scrollbar.scrollTop != lastScrollTop) {
- lastScrollTop = scroller.scrollTop = scrollbar.scrollTop;
- updateDisplay([]);
- }
- }
-
- function onScrollMain(e) {
- if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px")
- gutter.style.left = scroller.scrollLeft + "px";
- if (scroller.scrollTop != lastScrollTop) {
- lastScrollTop = scroller.scrollTop;
- if (scrollbar.scrollTop != lastScrollTop)
- scrollbar.scrollTop = lastScrollTop;
- updateDisplay([]);
- }
- if (options.onScroll) options.onScroll(instance);
- }
-
- function onMouseDown(e) {
- setShift(e_prop(e, "shiftKey"));
- // Check whether this is a click in a widget
- for (var n = e_target(e); n != wrapper; n = n.parentNode)
- if (n.parentNode == sizer && n != mover) return;
-
- // See if this is a click in the gutter
- for (var n = e_target(e); n != wrapper; n = n.parentNode)
- if (n.parentNode == gutterText) {
- if (options.onGutterClick)
- options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
- return e_preventDefault(e);
- }
-
- var start = posFromMouse(e);
-
- switch (e_button(e)) {
- case 3:
- if (gecko) onContextMenu(e);
- return;
- case 2:
- if (start) setCursor(start.line, start.ch, true);
- setTimeout(focusInput, 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) == scroller) e_preventDefault(e); return;}
-
- if (!focused) onFocus();
-
- var now = +new Date, type = "single";
- if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
- type = "triple";
- e_preventDefault(e);
- setTimeout(focusInput, 20);
- selectLine(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(start);
- setSelectionUser(word.from, word.to);
- } else { lastClick = {time: now, pos: start}; }
-
- function dragEnd(e2) {
- if (webkit) scroller.draggable = false;
- draggingText = false;
- up(); drop();
- if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
- e_preventDefault(e2);
- setCursor(start.line, start.ch, true);
- focusInput();
- }
- }
- var last = start, going;
- if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
- !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
- // Let the drag handler handle this.
- if (webkit) scroller.draggable = true;
- var up = connect(document, "mouseup", operation(dragEnd), true);
- var drop = connect(scroller, "drop", operation(dragEnd), true);
- draggingText = true;
- // IE's approach to draggable
- if (scroller.dragDrop) scroller.dragDrop();
- return;
- }
- e_preventDefault(e);
- if (type == "single") setCursor(start.line, start.ch, true);
-
- var startstart = sel.from, startend = sel.to;
-
- function doSelect(cur) {
- if (type == "single") {
- setSelectionUser(start, cur);
- } else if (type == "double") {
- var word = findWordAt(cur);
- if (posLess(cur, startstart)) setSelectionUser(word.from, startend);
- else setSelectionUser(startstart, word.to);
- } else if (type == "triple") {
- if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0}));
- else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0}));
- }
- }
-
- function extend(e) {
- var cur = posFromMouse(e, true);
- if (cur && !posEq(cur, last)) {
- if (!focused) onFocus();
- last = cur;
- doSelect(cur);
- updateInput = false;
- var visible = visibleLines();
- if (cur.line >= visible.to || cur.line < visible.from)
- going = setTimeout(operation(function(){extend(e);}), 150);
- }
- }
-
- function done(e) {
- clearTimeout(going);
- var cur = posFromMouse(e);
- if (cur) doSelect(cur);
- e_preventDefault(e);
- focusInput();
- updateInput = true;
- move(); up();
- }
- var move = connect(document, "mousemove", operation(function(e) {
- clearTimeout(going);
- e_preventDefault(e);
- if (!ie && !e_button(e)) done(e);
- else extend(e);
- }), true);
- var up = connect(document, "mouseup", operation(done), true);
- }
- function onDoubleClick(e) {
- for (var n = e_target(e); n != wrapper; n = n.parentNode)
- if (n.parentNode == gutterText) return e_preventDefault(e);
- e_preventDefault(e);
- }
- function onDrop(e) {
- if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
- e_preventDefault(e);
- var pos = posFromMouse(e, true), files = e.dataTransfer.files;
- if (!pos || options.readOnly) 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(pos);
- operation(function() {
- var end = replaceRange(text.join(""), pos, pos);
- setSelectionUser(pos, end);
- })();
- }
- };
- 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 (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return;
- try {
- var text = e.dataTransfer.getData("Text");
- if (text) {
- compoundChange(function() {
- var curFrom = sel.from, curTo = sel.to;
- setSelectionUser(pos, pos);
- if (draggingText) replaceRange("", curFrom, curTo);
- replaceSelection(text);
- focusInput();
- });
- }
- }
- catch(e){}
- }
- }
- function onDragStart(e) {
- var txt = getSelection();
- e.dataTransfer.setData("Text", txt);
-
- // Use dummy image instead of default browsers image.
- if (gecko || chrome || opera) {
- var img = elt('img');
- img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image
- e.dataTransfer.setDragImage(img, 0, 0);
- }
- }
-
- function doHandleBinding(bound, dropShift) {
- if (typeof bound == "string") {
- bound = commands[bound];
- if (!bound) return false;
- }
- var prevShift = shiftSelecting;
- try {
- if (options.readOnly) suppressEdits = true;
- if (dropShift) shiftSelecting = null;
- bound(instance);
- } catch(e) {
- if (e != Pass) throw e;
- return false;
- } finally {
- shiftSelecting = prevShift;
- suppressEdits = false;
- }
- return true;
- }
- var maybeTransition;
- function handleKeyBinding(e) {
- // Handle auto keymap transitions
- var startMap = getKeyMap(options.keyMap), next = startMap.auto;
- clearTimeout(maybeTransition);
- if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
- if (getKeyMap(options.keyMap) == startMap) {
- options.keyMap = (next.call ? next.call(null, instance) : next);
- }
- }, 50);
-
- var name = keyNames[e_prop(e, "keyCode")], handled = false;
- if (name == null || e.altGraphKey) return false;
- if (e_prop(e, "altKey")) name = "Alt-" + name;
- if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name;
- if (e_prop(e, "metaKey")) name = "Cmd-" + name;
-
- var stopped = false;
- function stop() { stopped = true; }
-
- if (e_prop(e, "shiftKey")) {
- handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap,
- function(b) {return doHandleBinding(b, true);}, stop)
- || lookupKey(name, options.extraKeys, options.keyMap, function(b) {
- if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b);
- }, stop);
- } else {
- handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop);
- }
- if (stopped) handled = false;
- if (handled) {
- e_preventDefault(e);
- restartBlink();
- if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
- }
- return handled;
- }
- function handleCharBinding(e, ch) {
- var handled = lookupKey("'" + ch + "'", options.extraKeys,
- options.keyMap, function(b) { return doHandleBinding(b, true); });
- if (handled) {
- e_preventDefault(e);
- restartBlink();
- }
- return handled;
- }
-
- var lastStoppedKey = null;
- function onKeyDown(e) {
- if (!focused) onFocus();
- if (ie && e.keyCode == 27) { e.returnValue = false; }
- if (pollingFast) { if (readInput()) pollingFast = false; }
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
- var code = e_prop(e, "keyCode");
- // IE does strange things with escape.
- setShift(code == 16 || e_prop(e, "shiftKey"));
- // First give onKeyEvent option a chance to handle this.
- var handled = handleKeyBinding(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 && e_prop(e, mac ? "metaKey" : "ctrlKey"))
- replaceSelection("");
- }
- }
- function onKeyPress(e) {
- if (pollingFast) readInput();
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
- var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
- if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
- if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return;
- var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
- if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) {
- if (mode.electricChars.indexOf(ch) > -1)
- setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
- }
- if (handleCharBinding(e, ch)) return;
- fastPoll();
- }
- function onKeyUp(e) {
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
- if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
- }
-
- function onFocus() {
- if (options.readOnly == "nocursor") return;
- if (!focused) {
- if (options.onFocus) options.onFocus(instance);
- focused = true;
- if (scroller.className.search(/\bCodeMirror-focused\b/) == -1)
- scroller.className += " CodeMirror-focused";
- if (!leaveInputAlone) resetInput(true);
- }
- slowPoll();
- restartBlink();
- }
- function onBlur() {
- if (focused) {
- if (options.onBlur) options.onBlur(instance);
- focused = false;
- if (bracketHighlighted)
- operation(function(){
- if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
- })();
- scroller.className = scroller.className.replace(" CodeMirror-focused", "");
- }
- clearInterval(blinker);
- setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
- }
-
- // Replace the range from from to to by the strings in newText.
- // Afterwards, set the selection to selFrom, selTo.
- function updateLines(from, to, newText, selFrom, selTo) {
- if (suppressEdits) return;
- if (history) {
- var old = [];
- doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
- history.addChange(from.line, newText.length, old);
- while (history.done.length > options.undoDepth) history.done.shift();
- }
- updateLinesNoUndo(from, to, newText, selFrom, selTo);
- }
- function unredoHelper(from, to) {
- if (!from.length) return;
- var set = from.pop(), out = [];
- for (var i = set.length - 1; i >= 0; i -= 1) {
- var change = set[i];
- var replaced = [], end = change.start + change.added;
- doc.iter(change.start, end, function(line) { replaced.push(line.text); });
- out.push({start: change.start, added: change.old.length, old: replaced});
- var pos = {line: change.start + change.old.length - 1,
- ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])};
- updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
- }
- updateInput = true;
- to.push(out);
- }
- function undo() {unredoHelper(history.done, history.undone);}
- function redo() {unredoHelper(history.undone, history.done);}
-
- function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
- if (suppressEdits) return;
- var recomputeMaxLength = false, maxLineLength = maxLine.text.length;
- if (!options.lineWrapping)
- doc.iter(from.line, to.line + 1, function(line) {
- if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
- });
- if (from.line != to.line || newText.length > 1) gutterDirty = true;
-
- var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
- // First adjust the line structure, taking some care to leave highlighting intact.
- if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
- // This is a whole-line replace. Treated specially to make
- // sure line objects move the way they are supposed to.
- var added = [], prevLine = null;
- if (from.line) {
- prevLine = getLine(from.line - 1);
- prevLine.fixMarkEnds(lastLine);
- } else lastLine.fixMarkStarts();
- for (var i = 0, e = newText.length - 1; i < e; ++i)
- added.push(Line.inheritMarks(newText[i], prevLine));
- if (nlines) doc.remove(from.line, nlines, callbacks);
- if (added.length) doc.insert(from.line, added);
- } else if (firstLine == lastLine) {
- if (newText.length == 1)
- firstLine.replace(from.ch, to.ch, newText[0]);
- else {
- lastLine = firstLine.split(to.ch, newText[newText.length-1]);
- firstLine.replace(from.ch, null, newText[0]);
- firstLine.fixMarkEnds(lastLine);
- var added = [];
- for (var i = 1, e = newText.length - 1; i < e; ++i)
- added.push(Line.inheritMarks(newText[i], firstLine));
- added.push(lastLine);
- doc.insert(from.line + 1, added);
- }
- } else if (newText.length == 1) {
- firstLine.replace(from.ch, null, newText[0]);
- lastLine.replace(null, to.ch, "");
- firstLine.append(lastLine);
- doc.remove(from.line + 1, nlines, callbacks);
- } else {
- var added = [];
- firstLine.replace(from.ch, null, newText[0]);
- lastLine.replace(null, to.ch, newText[newText.length-1]);
- firstLine.fixMarkEnds(lastLine);
- for (var i = 1, e = newText.length - 1; i < e; ++i)
- added.push(Line.inheritMarks(newText[i], firstLine));
- if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
- doc.insert(from.line + 1, added);
- }
- if (options.lineWrapping) {
- var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3);
- doc.iter(from.line, from.line + newText.length, function(line) {
- if (line.hidden) return;
- var guess = Math.ceil(line.text.length / perLine) || 1;
- if (guess != line.height) updateLineHeight(line, guess);
- });
- } else {
- doc.iter(from.line, from.line + newText.length, function(line) {
- var l = line.text;
- if (!line.hidden && l.length > maxLineLength) {
- maxLine = line; maxLineLength = l.length; maxLineChanged = true;
- recomputeMaxLength = false;
- }
- });
- if (recomputeMaxLength) updateMaxLine = true;
- }
-
- // Add these lines to the work array, so that they will be
- // highlighted. Adjust work lines if lines were added/removed.
- var newWork = [], lendiff = newText.length - nlines - 1;
- for (var i = 0, l = work.length; i < l; ++i) {
- var task = work[i];
- if (task < from.line) newWork.push(task);
- else if (task > to.line) newWork.push(task + lendiff);
- }
- var hlEnd = from.line + Math.min(newText.length, 500);
- highlightLines(from.line, hlEnd);
- newWork.push(hlEnd);
- work = newWork;
- startWorker(100);
- // Remember that these lines changed, for updating the display
- changes.push({from: from.line, to: to.line + 1, diff: lendiff});
- var changeObj = {from: from, to: to, text: newText};
- if (textChanged) {
- for (var cur = textChanged; cur.next; cur = cur.next) {}
- cur.next = changeObj;
- } else textChanged = changeObj;
-
- // Update the selection
- function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
- setSelection(clipPos(selFrom), clipPos(selTo),
- updateLine(sel.from.line), updateLine(sel.to.line));
- }
-
- function needsScrollbar() {
- var realHeight = doc.height * textHeight() + 2 * paddingTop();
- return realHeight * .99 > scroller.offsetHeight ? realHeight : false;
- }
-
- function updateVerticalScroll(scrollTop) {
- var scrollHeight = needsScrollbar();
- scrollbar.style.display = scrollHeight ? "block" : "none";
- if (scrollHeight) {
- scrollbarInner.style.height = sizer.style.minHeight = scrollHeight + "px";
- scrollbar.style.height = scroller.clientHeight + "px";
- if (scrollTop != null) {
- scrollbar.scrollTop = scroller.scrollTop = scrollTop;
- // 'Nudge' the scrollbar to work around a Webkit bug where,
- // in some situations, we'd end up with a scrollbar that
- // reported its scrollTop (and looked) as expected, but
- // *behaved* as if it was still in a previous state (i.e.
- // couldn't scroll up, even though it appeared to be at the
- // bottom).
- if (webkit) setTimeout(function() {
- if (scrollbar.scrollTop != scrollTop) return;
- scrollbar.scrollTop = scrollTop + (scrollTop ? -1 : 1);
- scrollbar.scrollTop = scrollTop;
- }, 0);
- }
- } else {
- sizer.style.minHeight = "";
- }
- // Position the mover div to align with the current virtual scroll position
- mover.style.top = displayOffset * textHeight() + "px";
- }
-
- function computeMaxLength() {
- maxLine = getLine(0); maxLineChanged = true;
- var maxLineLength = maxLine.text.length;
- doc.iter(1, doc.size, function(line) {
- var l = line.text;
- if (!line.hidden && l.length > maxLineLength) {
- maxLineLength = l.length; maxLine = line;
- }
- });
- updateMaxLine = false;
- }
-
- function replaceRange(code, from, to) {
- from = clipPos(from);
- if (!to) to = from; else to = clipPos(to);
- code = splitLines(code);
- function adjustPos(pos) {
- if (posLess(pos, from)) return pos;
- if (!posLess(to, pos)) return end;
- var line = pos.line + code.length - (to.line - from.line) - 1;
- var ch = pos.ch;
- if (pos.line == to.line)
- ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0));
- return {line: line, ch: ch};
- }
- var end;
- replaceRange1(code, from, to, function(end1) {
- end = end1;
- return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
- });
- return end;
- }
- function replaceSelection(code, collapse) {
- replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
- if (collapse == "end") return {from: end, to: end};
- else if (collapse == "start") return {from: sel.from, to: sel.from};
- else return {from: sel.from, to: end};
- });
- }
- function replaceRange1(code, from, to, computeSel) {
- var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length;
- var newSel = computeSel({line: from.line + code.length - 1, ch: endch});
- updateLines(from, to, code, newSel.from, newSel.to);
- }
-
- function getRange(from, to, lineSep) {
- var l1 = from.line, l2 = to.line;
- if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
- var code = [getLine(l1).text.slice(from.ch)];
- doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
- code.push(getLine(l2).text.slice(0, to.ch));
- return code.join(lineSep || "\n");
- }
- function getSelection(lineSep) {
- return getRange(sel.from, sel.to, lineSep);
- }
-
- function slowPoll() {
- if (pollingFast) return;
- poll.set(options.pollInterval, function() {
- startOperation();
- readInput();
- if (focused) slowPoll();
- endOperation();
- });
- }
- function fastPoll() {
- var missed = false;
- pollingFast = true;
- function p() {
- startOperation();
- var changed = readInput();
- if (!changed && !missed) {missed = true; poll.set(60, p);}
- else {pollingFast = false; slowPoll();}
- endOperation();
- }
- 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.)
- var prevInput = "";
- function readInput() {
- if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false;
- var text = input.value;
- if (text == prevInput) return false;
- shiftSelecting = null;
- var same = 0, l = Math.min(prevInput.length, text.length);
- while (same < l && prevInput[same] == text[same]) ++same;
- if (same < prevInput.length)
- sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
- else if (overwrite && posEq(sel.from, sel.to))
- sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
- replaceSelection(text.slice(same), "end");
- if (text.length > 1000) { input.value = prevInput = ""; }
- else prevInput = text;
- return true;
- }
- function resetInput(user) {
- if (!posEq(sel.from, sel.to)) {
- prevInput = "";
- input.value = getSelection();
- if (focused) selectInput(input);
- } else if (user) prevInput = input.value = "";
- }
-
- function focusInput() {
- if (options.readOnly != "nocursor") input.focus();
- }
-
- function scrollCursorIntoView() {
- var coords = calculateCursorCoords();
- scrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
- if (!focused) return;
- var box = sizer.getBoundingClientRect(), doScroll = null;
- if (coords.y + box.top < 0) doScroll = true;
- else if (coords.y + box.top + textHeight() > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
- if (doScroll != null) {
- var hidden = cursor.style.display == "none";
- if (hidden) {
- cursor.style.display = "";
- cursor.style.left = coords.x + "px";
- cursor.style.top = (coords.y - displayOffset) + "px";
- }
- cursor.scrollIntoView(doScroll);
- if (hidden) cursor.style.display = "none";
- }
- }
- function calculateCursorCoords() {
- var cursor = localCoords(sel.inverted ? sel.from : sel.to);
- var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
- return {x: x, y: cursor.y, yBot: cursor.yBot};
- }
- function scrollIntoView(x1, y1, x2, y2) {
- var scrollPos = calculateScrollPos(x1, y1, x2, y2);
- if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft;}
- if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scroller.scrollTop = scrollPos.scrollTop;}
- }
- function calculateScrollPos(x1, y1, x2, y2) {
- var pl = paddingLeft(), pt = paddingTop();
- y1 += pt; y2 += pt; x1 += pl; x2 += pl;
- var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {};
- var docBottom = needsScrollbar() || Infinity;
- var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
- if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
- else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
-
- var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
- var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
- var atLeft = x1 < gutterw + pl + 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 visibleLines(scrollTop) {
- var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop();
- var fromHeight = Math.max(0, Math.floor(top / lh));
- var toHeight = Math.ceil((top + scroller.clientHeight) / lh);
- return {from: lineAtHeight(doc, fromHeight),
- to: lineAtHeight(doc, toHeight)};
- }
- // Uses a set of changes plus the current scroll position to
- // determine which DOM updates have to be made, and makes the
- // updates.
- function updateDisplay(changes, suppressCallback, scrollTop) {
- if (!scroller.clientWidth) {
- showingFrom = showingTo = displayOffset = 0;
- return;
- }
- // Compute the new visible window
- // If scrollTop is specified, use that to determine which lines
- // to render instead of the current scrollbar position.
- var visible = visibleLines(scrollTop);
- // Bail out if the visible area is already rendered and nothing changed.
- if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) {
- updateVerticalScroll(scrollTop);
- return;
- }
- var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
- if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
- if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
-
- // Create a range of theoretically intact lines, and punch holes
- // in that using the change info.
- var intact = changes === true ? [] :
- computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
- // 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.domStart += (from - range.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 == showingFrom && to == showingTo) {
- updateVerticalScroll(scrollTop);
- return;
- }
- intact.sort(function(a, b) {return a.domStart - b.domStart;});
-
- var th = textHeight(), gutterDisplay = gutter.style.display;
- lineDiv.style.display = "none";
- patchDisplay(from, to, intact);
- lineDiv.style.display = gutter.style.display = "";
-
- var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
- // This is just a bogus formula that detects when the editor is
- // resized or the font size changes.
- if (different) lastSizeC = scroller.clientHeight + th;
- if (from != showingFrom || to != showingTo && options.onViewportChange)
- setTimeout(function(){
- if (options.onViewportChange) options.onViewportChange(instance, from, to);
- });
- showingFrom = from; showingTo = to;
- displayOffset = heightAtLine(doc, from);
-
- // Since this is all rather error prone, it is honoured with the
- // only assertion in the whole file.
- if (lineDiv.childNodes.length != showingTo - showingFrom)
- throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
- " nodes=" + lineDiv.childNodes.length);
-
- function checkHeights() {
- var curNode = lineDiv.firstChild, heightChanged = false;
- doc.iter(showingFrom, showingTo, function(line) {
- // Work around bizarro IE7 bug where, sometimes, our curNode
- // is magically replaced with a new node in the DOM, leaving
- // us with a reference to an orphan (nextSibling-less) node.
- if (!curNode) return;
- if (!line.hidden) {
- var height = Math.round(curNode.offsetHeight / th) || 1;
- if (line.height != height) {
- updateLineHeight(line, height);
- gutterDirty = heightChanged = true;
- }
- }
- curNode = curNode.nextSibling;
- });
- return heightChanged;
- }
-
- if (options.lineWrapping) checkHeights();
-
- gutter.style.display = gutterDisplay;
- if (different || gutterDirty) {
- // If the gutter grew in size, re-check heights. If those changed, re-draw gutter.
- updateGutter() && options.lineWrapping && checkHeights() && updateGutter();
- }
- updateVerticalScroll(scrollTop);
- updateSelection();
- if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
- return true;
- }
-
- 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,
- domStart: range.domStart});
- 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, domStart: range.domStart});
- if (change.to < range.to)
- intact2.push({from: change.to + diff, to: range.to + diff,
- domStart: range.domStart + (change.to - range.from)});
- }
- }
- intact = intact2;
- }
- return intact;
- }
-
- function patchDisplay(from, to, intact) {
- function killNode(node) {
- var tmp = node.nextSibling;
- node.parentNode.removeChild(node);
- return tmp;
- }
- // The first pass removes the DOM nodes that aren't intact.
- if (!intact.length) removeChildren(lineDiv);
- else {
- var domPos = 0, curNode = lineDiv.firstChild, n;
- for (var i = 0; i < intact.length; ++i) {
- var cur = intact[i];
- while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
- for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
- }
- while (curNode) curNode = killNode(curNode);
- }
- // This pass fills in the lines that actually changed.
- var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
- doc.iter(from, to, function(line) {
- if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
- if (!nextIntact || nextIntact.from > j) {
- if (line.hidden) var lineElement = elt("pre");
- else {
- var lineElement = line.getElement(makeTab);
- if (line.className) lineElement.className = line.className;
- // Kludge to make sure the styled element lies behind the selection (by z-index)
- if (line.bgClassName) {
- var pre = elt("pre", "\u00a0", line.bgClassName, "position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2");
- lineElement = elt("div", [pre, lineElement], null, "position: relative");
- }
- }
- lineDiv.insertBefore(lineElement, curNode);
- } else {
- curNode = curNode.nextSibling;
- }
- ++j;
- });
- }
-
- function updateGutter() {
- if (!options.gutter && !options.lineNumbers) return;
- var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
- gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
- var fragment = document.createDocumentFragment(), i = showingFrom, normalNode;
- doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
- if (line.hidden) {
- fragment.appendChild(elt("pre"));
- } else {
- var marker = line.gutterMarker;
- var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null;
- if (marker && marker.text)
- text = marker.text.replace("%N%", text != null ? text : "");
- else if (text == null)
- text = "\u00a0";
- var markerElement = fragment.appendChild(elt("pre", null, marker && marker.style));
- markerElement.innerHTML = text;
- for (var j = 1; j < line.height; ++j) {
- markerElement.appendChild(elt("br"));
- markerElement.appendChild(document.createTextNode("\u00a0"));
- }
- if (!marker) normalNode = i;
- }
- ++i;
- });
- gutter.style.display = "none";
- removeChildrenAndAdd(gutterText, fragment);
- // Make sure scrolling doesn't cause number gutter size to pop
- if (normalNode != null && options.lineNumbers) {
- var node = gutterText.childNodes[normalNode - showingFrom];
- var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = "";
- while (val.length + pad.length < minwidth) pad += "\u00a0";
- if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild);
- }
- gutter.style.display = "";
- var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2;
- lineSpace.style.marginLeft = gutter.offsetWidth + "px";
- gutterDirty = false;
- return resized;
- }
- function updateSelection() {
- var collapsed = posEq(sel.from, sel.to);
- var fromPos = localCoords(sel.from, true);
- var toPos = collapsed ? fromPos : localCoords(sel.to, true);
- var headPos = sel.inverted ? fromPos : toPos, th = textHeight();
- var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
- inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
- inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";
- if (collapsed) {
- cursor.style.top = headPos.y + "px";
- cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px";
- cursor.style.display = "";
- selectionDiv.style.display = "none";
- } else {
- var sameLine = fromPos.y == toPos.y, fragment = document.createDocumentFragment();
- var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth;
- var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight;
- var add = function(left, top, right, height) {
- var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px"
- : "right: " + right + "px";
- fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
- "px; top: " + top + "px; " + rstyle + "; height: " + height + "px"));
- };
- if (sel.from.ch && fromPos.y >= 0) {
- var right = sameLine ? clientWidth - toPos.x : 0;
- add(fromPos.x, fromPos.y, right, th);
- }
- var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0));
- var middleHeight = Math.min(toPos.y, clientHeight) - middleStart;
- if (middleHeight > 0.2 * th)
- add(0, middleStart, 0, middleHeight);
- if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th)
- add(0, toPos.y, clientWidth - toPos.x, th);
- removeChildrenAndAdd(selectionDiv, fragment);
- cursor.style.display = "none";
- selectionDiv.style.display = "";
- }
- }
-
- function setShift(val) {
- if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
- else shiftSelecting = null;
- }
- function setSelectionUser(from, to) {
- var sh = shiftSelecting && clipPos(shiftSelecting);
- if (sh) {
- if (posLess(sh, from)) from = sh;
- else if (posLess(to, sh)) to = sh;
- }
- setSelection(from, to);
- userSelChange = true;
- }
- // Update the selection. Last two args are only used by
- // updateLines, since they have to be expressed in the line
- // numbers before the update.
- function setSelection(from, to, oldFrom, oldTo) {
- goalColumn = null;
- if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
- if (posEq(sel.from, from) && posEq(sel.to, to)) return;
- if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
-
- // Skip over hidden lines.
- if (from.line != oldFrom) {
- var from1 = skipHidden(from, oldFrom, sel.from.ch);
- // If there is no non-hidden line left, force visibility on current line
- if (!from1) setLineHidden(from.line, false);
- else from = from1;
- }
- if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
-
- if (posEq(from, to)) sel.inverted = false;
- else if (posEq(from, sel.to)) sel.inverted = false;
- else if (posEq(to, sel.from)) sel.inverted = true;
-
- if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) {
- var head = sel.inverted ? from : to;
- if (head.line != sel.from.line && sel.from.line < doc.size) {
- var oldLine = getLine(sel.from.line);
- if (/^\s+$/.test(oldLine.text))
- setTimeout(operation(function() {
- if (oldLine.parent && /^\s+$/.test(oldLine.text)) {
- var no = lineNo(oldLine);
- replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length});
- }
- }, 10));
- }
- }
-
- sel.from = from; sel.to = to;
- selectionChanged = true;
- }
- function skipHidden(pos, oldLine, oldCh) {
- function getNonHidden(dir) {
- var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
- while (lNo != end) {
- var line = getLine(lNo);
- if (!line.hidden) {
- var ch = pos.ch;
- if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length;
- return {line: lNo, ch: ch};
- }
- lNo += dir;
- }
- }
- var line = getLine(pos.line);
- var toEnd = pos.ch == line.text.length && pos.ch != oldCh;
- if (!line.hidden) return pos;
- if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
- else return getNonHidden(-1) || getNonHidden(1);
- }
- function setCursor(line, ch, user) {
- var pos = clipPos({line: line, ch: ch || 0});
- (user ? setSelectionUser : setSelection)(pos, pos);
- }
-
- function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
- function clipPos(pos) {
- if (pos.line < 0) return {line: 0, ch: 0};
- if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
- var ch = pos.ch, linelen = getLine(pos.line).text.length;
- if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
- else if (ch < 0) return {line: pos.line, ch: 0};
- else return pos;
- }
-
- function findPosH(dir, unit) {
- var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
- var lineObj = getLine(line);
- function findNextLine() {
- for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
- var lo = getLine(l);
- if (!lo.hidden) { line = l; lineObj = lo; return true; }
- }
- }
- function moveOnce(boundToLine) {
- if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
- if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
- else return false;
- } else ch += dir;
- return true;
- }
- if (unit == "char") moveOnce();
- else if (unit == "column") moveOnce(true);
- else if (unit == "word") {
- var sawWord = false;
- for (;;) {
- if (dir < 0) if (!moveOnce()) break;
- if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
- else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
- if (dir > 0) if (!moveOnce()) break;
- }
- }
- return {line: line, ch: ch};
- }
- function moveH(dir, unit) {
- var pos = dir < 0 ? sel.from : sel.to;
- if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
- setCursor(pos.line, pos.ch, true);
- }
- function deleteH(dir, unit) {
- if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
- else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
- else replaceRange("", sel.from, findPosH(dir, unit));
- userSelChange = true;
- }
- function moveV(dir, unit) {
- var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
- if (goalColumn != null) pos.x = goalColumn;
- if (unit == "page") {
- var screen = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight);
- var target = coordsChar(pos.x, pos.y + screen * dir);
- } else if (unit == "line") {
- var th = textHeight();
- var target = coordsChar(pos.x, pos.y + .5 * th + dir * th);
- }
- if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y;
- setCursor(target.line, target.ch, true);
- goalColumn = pos.x;
- }
-
- function findWordAt(pos) {
- var line = getLine(pos.line).text;
- var start = pos.ch, end = pos.ch;
- if (line) {
- if (pos.after === false || 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: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}};
- }
- function selectLine(line) {
- setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0}));
- }
- function indentSelected(mode) {
- if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
- var e = sel.to.line - (sel.to.ch ? 0 : 1);
- for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
- }
-
- function indentLine(n, how) {
- if (!how) how = "add";
- if (how == "smart") {
- if (!mode.indent) how = "prev";
- else var state = getStateBefore(n);
- }
-
- var line = getLine(n), curSpace = line.indentation(options.tabSize),
- curSpaceString = line.text.match(/^\s*/)[0], indentation;
- if (how == "smart") {
- indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
- if (indentation == Pass) how = "prev";
- }
- if (how == "prev") {
- if (n) indentation = getLine(n-1).indentation(options.tabSize);
- else indentation = 0;
- }
- else if (how == "add") indentation = curSpace + options.indentUnit;
- else if (how == "subtract") indentation = curSpace - options.indentUnit;
- indentation = Math.max(0, indentation);
- var diff = indentation - curSpace;
-
- var indentString = "", pos = 0;
- if (options.indentWithTabs)
- for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
- while (pos < indentation) {++pos; indentString += " ";}
-
- if (indentString != curSpaceString)
- replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
- }
-
- function loadMode() {
- mode = CodeMirror.getMode(options, options.mode);
- doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
- work = [0];
- startWorker();
- }
- function gutterChanged() {
- var visible = options.gutter || options.lineNumbers;
- gutter.style.display = visible ? "" : "none";
- if (visible) gutterDirty = true;
- else lineDiv.parentNode.style.marginLeft = 0;
- }
- function wrappingChanged(from, to) {
- if (options.lineWrapping) {
- wrapper.className += " CodeMirror-wrap";
- var perLine = scroller.clientWidth / charWidth() - 3;
- doc.iter(0, doc.size, function(line) {
- if (line.hidden) return;
- var guess = Math.ceil(line.text.length / perLine) || 1;
- if (guess != 1) updateLineHeight(line, guess);
- });
- lineSpace.style.minWidth = widthForcer.style.left = "";
- } else {
- wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
- computeMaxLength();
- doc.iter(0, doc.size, function(line) {
- if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
- });
- }
- changes.push({from: 0, to: doc.size});
- }
- function makeTab(col) {
- var w = options.tabSize - col % options.tabSize, cached = tabCache[w];
- if (cached) return cached;
- for (var str = "", i = 0; i < w; ++i) str += " ";
- var span = elt("span", str, "cm-tab");
- return (tabCache[w] = {element: span, width: w});
- }
- function themeChanged() {
- scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") +
- options.theme.replace(/(^|\s)\s*/g, " cm-s-");
- }
- function keyMapChanged() {
- var style = keyMap[options.keyMap].style;
- wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
- (style ? " cm-keymap-" + style : "");
- }
-
- function TextMarker() { this.set = []; }
- TextMarker.prototype.clear = operation(function() {
- var min = Infinity, max = -Infinity;
- for (var i = 0, e = this.set.length; i < e; ++i) {
- var line = this.set[i], mk = line.marked;
- if (!mk || !line.parent) continue;
- var lineN = lineNo(line);
- min = Math.min(min, lineN); max = Math.max(max, lineN);
- for (var j = 0; j < mk.length; ++j)
- if (mk[j].marker == this) mk.splice(j--, 1);
- }
- if (min != Infinity)
- changes.push({from: min, to: max + 1});
- });
- TextMarker.prototype.find = function() {
- var from, to;
- for (var i = 0, e = this.set.length; i < e; ++i) {
- var line = this.set[i], mk = line.marked;
- for (var j = 0; j < mk.length; ++j) {
- var mark = mk[j];
- if (mark.marker == this) {
- if (mark.from != null || mark.to != null) {
- var found = lineNo(line);
- if (found != null) {
- if (mark.from != null) from = {line: found, ch: mark.from};
- if (mark.to != null) to = {line: found, ch: mark.to};
- }
- }
- }
- }
- }
- return {from: from, to: to};
- };
-
- function markText(from, to, className) {
- from = clipPos(from); to = clipPos(to);
- var tm = new TextMarker();
- if (!posLess(from, to)) return tm;
- function add(line, from, to, className) {
- getLine(line).addMark(new MarkedText(from, to, className, tm));
- }
- if (from.line == to.line) add(from.line, from.ch, to.ch, className);
- else {
- add(from.line, from.ch, null, className);
- for (var i = from.line + 1, e = to.line; i < e; ++i)
- add(i, null, null, className);
- add(to.line, null, to.ch, className);
- }
- changes.push({from: from.line, to: to.line + 1});
- return tm;
- }
-
- function setBookmark(pos) {
- pos = clipPos(pos);
- var bm = new Bookmark(pos.ch);
- getLine(pos.line).addMark(bm);
- return bm;
- }
-
- function findMarksAt(pos) {
- pos = clipPos(pos);
- var markers = [], marked = getLine(pos.line).marked;
- if (!marked) return markers;
- for (var i = 0, e = marked.length; i < e; ++i) {
- var m = marked[i];
- if ((m.from == null || m.from <= pos.ch) &&
- (m.to == null || m.to >= pos.ch))
- markers.push(m.marker || m);
- }
- return markers;
- }
-
- function addGutterMarker(line, text, className) {
- if (typeof line == "number") line = getLine(clipLine(line));
- line.gutterMarker = {text: text, style: className};
- gutterDirty = true;
- return line;
- }
- function removeGutterMarker(line) {
- if (typeof line == "number") line = getLine(clipLine(line));
- line.gutterMarker = null;
- gutterDirty = true;
- }
-
- function changeLine(handle, op) {
- var no = handle, line = handle;
- if (typeof handle == "number") line = getLine(clipLine(handle));
- else no = lineNo(handle);
- if (no == null) return null;
- if (op(line, no)) changes.push({from: no, to: no + 1});
- else return null;
- return line;
- }
- function setLineClass(handle, className, bgClassName) {
- return changeLine(handle, function(line) {
- if (line.className != className || line.bgClassName != bgClassName) {
- line.className = className;
- line.bgClassName = bgClassName;
- return true;
- }
- });
- }
- function setLineHidden(handle, hidden) {
- return changeLine(handle, function(line, no) {
- if (line.hidden != hidden) {
- line.hidden = hidden;
- if (!options.lineWrapping) {
- if (hidden && line.text.length == maxLine.text.length) {
- updateMaxLine = true;
- } else if (!hidden && line.text.length > maxLine.text.length) {
- maxLine = line; updateMaxLine = false;
- }
- }
- updateLineHeight(line, hidden ? 0 : 1);
- var fline = sel.from.line, tline = sel.to.line;
- if (hidden && (fline == no || tline == no)) {
- var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from;
- var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to;
- // Can't hide the last visible line, we'd have no place to put the cursor
- if (!to) return;
- setSelection(from, to);
- }
- return (gutterDirty = true);
- }
- });
- }
-
- function lineInfo(line) {
- if (typeof line == "number") {
- if (!isLine(line)) return null;
- var n = line;
- line = getLine(line);
- if (!line) return null;
- } else {
- var n = lineNo(line);
- if (n == null) return null;
- }
- var marker = line.gutterMarker;
- return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
- markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName};
- }
-
- // These are used to go from pixel positions to character
- // positions, taking varying character widths into account.
- function charFromX(line, x) {
- if (x <= 0) return 0;
- var lineObj = getLine(line), text = lineObj.text;
- function getX(len) {
- return measureLine(lineObj, len).left;
- }
- var from = 0, fromX = 0, to = text.length, toX;
- // Guess a suitable upper bound for our search.
- var estimated = Math.min(to, Math.ceil(x / charWidth()));
- for (;;) {
- var estX = getX(estimated);
- if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
- else {toX = estX; to = estimated; break;}
- }
- if (x > toX) return to;
- // Try to guess a suitable lower bound as well.
- estimated = Math.floor(to * 0.8); estX = getX(estimated);
- if (estX < x) {from = estimated; fromX = estX;}
- // Do a binary search between these bounds.
- for (;;) {
- if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
- var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
- if (middleX > x) {to = middle; toX = middleX;}
- else {from = middle; fromX = middleX;}
- }
- }
-
- function measureLine(line, ch) {
- if (ch == 0) return {top: 0, left: 0};
- var wbr = options.lineWrapping && ch < line.text.length &&
- spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1));
- var pre = line.getElement(makeTab, ch, wbr);
- removeChildrenAndAdd(measure, pre);
- var anchor = pre.anchor;
- var top = anchor.offsetTop, left = anchor.offsetLeft;
- // Older IEs report zero offsets for spans directly after a wrap
- if (ie && top == 0 && left == 0) {
- var backup = elt("span", "x");
- anchor.parentNode.insertBefore(backup, anchor.nextSibling);
- top = backup.offsetTop;
- }
- return {top: top, left: left};
- }
- function localCoords(pos, inLineWrap) {
- var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
- if (pos.ch == 0) x = 0;
- else {
- var sp = measureLine(getLine(pos.line), pos.ch);
- x = sp.left;
- if (options.lineWrapping) y += Math.max(0, sp.top);
- }
- return {x: x, y: y, yBot: y + lh};
- }
- // Coords must be lineSpace-local
- function coordsChar(x, y) {
- var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
- if (heightPos < 0) return {line: 0, ch: 0};
- var lineNo = lineAtHeight(doc, heightPos);
- if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
- var lineObj = getLine(lineNo), text = lineObj.text;
- var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
- if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
- var wrongLine = false;
- function getX(len) {
- var sp = measureLine(lineObj, len);
- if (tw) {
- var off = Math.round(sp.top / th);
- wrongLine = off != innerOff;
- return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
- }
- return sp.left;
- }
- var from = 0, fromX = 0, to = text.length, toX;
- // Guess a suitable upper bound for our search.
- var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
- for (;;) {
- var estX = getX(estimated);
- if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
- else {toX = estX; to = estimated; break;}
- }
- if (x > toX) return {line: lineNo, ch: to};
- // Try to guess a suitable lower bound as well.
- estimated = Math.floor(to * 0.8); estX = getX(estimated);
- if (estX < x) {from = estimated; fromX = estX;}
- // Do a binary search between these bounds.
- for (;;) {
- if (to - from <= 1) {
- var after = x - fromX < toX - x;
- return {line: lineNo, ch: after ? from : to, after: after};
- }
- var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
- if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; }
- else {from = middle; fromX = middleX;}
- }
- }
- function pageCoords(pos) {
- var local = localCoords(pos, true), off = eltOffset(lineSpace);
- return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
- }
-
- var cachedHeight, cachedHeightFor, measurePre;
- function textHeight() {
- if (measurePre == null) {
- measurePre = elt("pre");
- for (var i = 0; i < 49; ++i) {
- measurePre.appendChild(document.createTextNode("x"));
- measurePre.appendChild(elt("br"));
- }
- measurePre.appendChild(document.createTextNode("x"));
- }
- var offsetHeight = lineDiv.clientHeight;
- if (offsetHeight == cachedHeightFor) return cachedHeight;
- cachedHeightFor = offsetHeight;
- removeChildrenAndAdd(measure, measurePre.cloneNode(true));
- cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
- removeChildren(measure);
- return cachedHeight;
- }
- var cachedWidth, cachedWidthFor = 0;
- function charWidth() {
- if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
- cachedWidthFor = scroller.clientWidth;
- var anchor = elt("span", "x");
- var pre = elt("pre", [anchor]);
- removeChildrenAndAdd(measure, pre);
- return (cachedWidth = anchor.offsetWidth || 10);
- }
- function paddingTop() {return lineSpace.offsetTop;}
- function paddingLeft() {return lineSpace.offsetLeft;}
-
- function posFromMouse(e, liberal) {
- var offW = eltOffset(scroller, true), x, y;
- // Fails unpredictably on IE[67] when mouse is dragged around quickly.
- try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
- // This is a mess of a heuristic to try and determine whether a
- // scroll-bar was clicked or not, and to return null if one was
- // (and !liberal).
- if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
- return null;
- var offL = eltOffset(lineSpace, true);
- return coordsChar(x - offL.left, y - offL.top);
- }
- function onContextMenu(e) {
- var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop;
- if (!pos || opera) return; // Opera is difficult.
- if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
- operation(setCursor)(pos.line, pos.ch);
-
- var oldCSS = input.style.cssText;
- inputDiv.style.position = "absolute";
- input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
- "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
- "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
- leaveInputAlone = true;
- var val = input.value = getSelection();
- focusInput();
- selectInput(input);
- function rehide() {
- var newVal = splitLines(input.value).join("\n");
- if (newVal != val && !options.readOnly) operation(replaceSelection)(newVal, "end");
- inputDiv.style.position = "relative";
- input.style.cssText = oldCSS;
- if (ie_lt9) scrollbar.scrollTop = scrollPos;
- leaveInputAlone = false;
- resetInput(true);
- slowPoll();
- }
-
- if (gecko) {
- e_stop(e);
- var mouseup = connect(window, "mouseup", function() {
- mouseup();
- setTimeout(rehide, 20);
- }, true);
- } else {
- setTimeout(rehide, 50);
- }
- }
-
- // Cursor-blinking
- function restartBlink() {
- clearInterval(blinker);
- var on = true;
- cursor.style.visibility = "";
- blinker = setInterval(function() {
- cursor.style.visibility = (on = !on) ? "" : "hidden";
- }, options.cursorBlinkRate);
- }
-
- var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
- function matchBrackets(autoclear) {
- var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
- var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
- if (!match) return;
- var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
- for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)
- if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}
-
- var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
- function scan(line, from, to) {
- if (!line.text) return;
- var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;
- for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {
- var text = st[i];
- if (st[i+1] != style) {pos += d * text.length; continue;}
- for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {
- if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {
- var match = matching[cur];
- if (match.charAt(1) == ">" == forward) stack.push(cur);
- else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
- else if (!stack.length) return {pos: pos, match: true};
- }
- }
- }
- }
- for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
- var line = getLine(i), first = i == head.line;
- var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
- if (found) break;
- }
- if (!found) found = {pos: null, match: false};
- var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
- var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
- two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
- var clear = operation(function(){one.clear(); two && two.clear();});
- if (autoclear) setTimeout(clear, 800);
- else bracketHighlighted = clear;
- }
-
- // 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(n) {
- var minindent, minline;
- for (var search = n, lim = n - 40; search > lim; --search) {
- if (search == 0) return 0;
- var line = getLine(search-1);
- if (line.stateAfter) return search;
- var indented = line.indentation(options.tabSize);
- if (minline == null || minindent > indented) {
- minline = search - 1;
- minindent = indented;
- }
- }
- return minline;
- }
- function getStateBefore(n) {
- var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
- if (!state) state = startState(mode);
- else state = copyState(mode, state);
- doc.iter(start, n, function(line) {
- line.highlight(mode, state, options.tabSize);
- line.stateAfter = copyState(mode, state);
- });
- if (start < n) changes.push({from: start, to: n});
- if (n < doc.size && !getLine(n).stateAfter) work.push(n);
- return state;
- }
- function highlightLines(start, end) {
- var state = getStateBefore(start);
- doc.iter(start, end, function(line) {
- line.highlight(mode, state, options.tabSize);
- line.stateAfter = copyState(mode, state);
- });
- }
- function highlightWorker() {
- var end = +new Date + options.workTime;
- var foundWork = work.length;
- while (work.length) {
- if (!getLine(showingFrom).stateAfter) var task = showingFrom;
- else var task = work.pop();
- if (task >= doc.size) continue;
- var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
- if (state) state = copyState(mode, state);
- else state = startState(mode);
-
- var unchanged = 0, compare = mode.compareStates, realChange = false,
- i = start, bail = false;
- doc.iter(i, doc.size, function(line) {
- var hadState = line.stateAfter;
- if (+new Date > end) {
- work.push(i);
- startWorker(options.workDelay);
- if (realChange) changes.push({from: task, to: i + 1});
- return (bail = true);
- }
- var changed = line.highlight(mode, state, options.tabSize);
- if (changed) realChange = true;
- line.stateAfter = copyState(mode, state);
- var done = null;
- if (compare) {
- var same = hadState && compare(hadState, state);
- if (same != Pass) done = !!same;
- }
- if (done == null) {
- if (changed !== false || !hadState) unchanged = 0;
- else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, "")))
- done = true;
- }
- if (done) return true;
- ++i;
- });
- if (bail) return;
- if (realChange) changes.push({from: task, to: i + 1});
- }
- if (foundWork && options.onHighlightComplete)
- options.onHighlightComplete(instance);
- }
- function startWorker(time) {
- if (!work.length) return;
- highlight.set(time, operation(highlightWorker));
- }
-
- // 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.
- function startOperation() {
- updateInput = userSelChange = textChanged = null;
- changes = []; selectionChanged = false; callbacks = [];
- }
- function endOperation() {
- if (updateMaxLine) computeMaxLength();
- if (maxLineChanged && !options.lineWrapping) {
- var cursorWidth = widthForcer.offsetWidth, left = measureLine(maxLine, maxLine.text.length).left;
- if (!ie_lt8) {
- widthForcer.style.left = left + "px";
- lineSpace.style.minWidth = (left + cursorWidth) + "px";
- }
- maxLineChanged = false;
- }
- var newScrollPos, updated;
- if (selectionChanged) {
- var coords = calculateCursorCoords();
- newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot);
- }
- if (changes.length || newScrollPos && newScrollPos.scrollTop != null)
- updated = updateDisplay(changes, true, newScrollPos && newScrollPos.scrollTop);
- if (!updated) {
- if (selectionChanged) updateSelection();
- if (gutterDirty) updateGutter();
- }
- if (newScrollPos) scrollCursorIntoView();
- if (selectionChanged) restartBlink();
-
- if (focused && !leaveInputAlone &&
- (updateInput === true || (updateInput !== false && selectionChanged)))
- resetInput(userSelChange);
-
- if (selectionChanged && options.matchBrackets)
- setTimeout(operation(function() {
- if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
- if (posEq(sel.from, sel.to)) matchBrackets(false);
- }), 20);
- var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks
- if (textChanged && options.onChange && instance)
- options.onChange(instance, textChanged);
- if (sc && options.onCursorActivity)
- options.onCursorActivity(instance);
- for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
- if (updated && options.onUpdate) options.onUpdate(instance);
- }
- var nestedOperation = 0;
- function operation(f) {
- return function() {
- if (!nestedOperation++) startOperation();
- try {var result = f.apply(this, arguments);}
- finally {if (!--nestedOperation) endOperation();}
- return result;
- };
- }
-
- function compoundChange(f) {
- history.startCompound();
- try { return f(); } finally { history.endCompound(); }
- }
-
- for (var ext in extensions)
- if (extensions.propertyIsEnumerable(ext) &&
- !instance.propertyIsEnumerable(ext))
- instance[ext] = extensions[ext];
- return instance;
- } // (end of function CodeMirror)
-
- // The default configuration options.
- CodeMirror.defaults = {
- value: "",
- mode: null,
- theme: "default",
- indentUnit: 2,
- indentWithTabs: false,
- smartIndent: true,
- tabSize: 4,
- keyMap: "default",
- extraKeys: null,
- electricChars: true,
- autoClearEmptyLines: false,
- onKeyEvent: null,
- onDragEvent: null,
- lineWrapping: false,
- lineNumbers: false,
- gutter: false,
- fixedGutter: false,
- firstLineNumber: 1,
- readOnly: false,
- dragDrop: true,
- onChange: null,
- onCursorActivity: null,
- onViewportChange: null,
- onGutterClick: null,
- onHighlightComplete: null,
- onUpdate: null,
- onFocus: null, onBlur: null, onScroll: null,
- matchBrackets: false,
- cursorBlinkRate: 530,
- workTime: 100,
- workDelay: 200,
- pollInterval: 100,
- undoDepth: 40,
- tabindex: null,
- autofocus: null,
- lineNumberFormatter: function(integer) { return integer; }
- };
-
- var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
- var mac = ios || /Mac/.test(navigator.platform);
- var win = /Win/.test(navigator.platform);
-
- // 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 (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) {
- var spec = CodeMirror.resolveMode(spec);
- var mfactory = modes[spec.name];
- if (!mfactory) return CodeMirror.getMode(options, "text/plain");
- return mfactory(options, spec);
- };
- CodeMirror.listModes = function() {
- var list = [];
- for (var m in modes)
- if (modes.propertyIsEnumerable(m)) list.push(m);
- return list;
- };
- CodeMirror.listMIMEs = function() {
- var list = [];
- for (var m in mimeModes)
- if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
- return list;
- };
-
- var extensions = CodeMirror.extensions = {};
- CodeMirror.defineExtension = function(name, func) {
- extensions[name] = func;
- };
-
- var commands = CodeMirror.commands = {
- selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
- 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, {line: from.line + 1, ch: 0});
- else cm.replaceRange("", from, sel ? to : {line: from.line});
- },
- deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
- undo: function(cm) {cm.undo();},
- redo: function(cm) {cm.redo();},
- goDocStart: function(cm) {cm.setCursor(0, 0, true);},
- goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
- goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
- goLineStartSmart: function(cm) {
- var cur = cm.getCursor();
- var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
- cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
- },
- goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
- 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");},
- goWordRight: function(cm) {cm.moveH(1, "word");},
- delCharLeft: function(cm) {cm.deleteH(-1, "char");},
- delCharRight: function(cm) {cm.deleteH(1, "char");},
- delWordLeft: function(cm) {cm.deleteH(-1, "word");},
- delWordRight: function(cm) {cm.deleteH(1, "word");},
- 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");},
- defaultTab: function(cm) {
- if (cm.somethingSelected()) cm.indentSelection("add");
- else cm.replaceSelection("\t", "end");
- },
- 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),
- {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
- },
- newlineAndIndent: function(cm) {
- cm.replaceSelection("\n", "end");
- cm.indentLine(cm.getCursor().line);
- },
- toggleOverwrite: function(cm) {cm.toggleOverwrite();}
- };
-
- var keyMap = CodeMirror.keyMap = {};
- keyMap.basic = {
- "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
- "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
- "Delete": "delCharRight", "Backspace": "delCharLeft", "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": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
- "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "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": "goWordLeft",
- "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
- "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "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",
- 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": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
- "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
- };
-
- function getKeyMap(val) {
- if (typeof val == "string") return keyMap[val];
- else return val;
- }
- function lookupKey(name, extraMap, map, handle, stop) {
- function lookup(map) {
- map = getKeyMap(map);
- var found = map[name];
- if (found === false) {
- if (stop) stop();
- return true;
- }
- if (found != null && handle(found)) return true;
- if (map.nofallthrough) {
- if (stop) stop();
- return true;
- }
- 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) {
- if (lookup(fallthrough[i])) return true;
- }
- return false;
- }
- if (extraMap && lookup(extraMap)) return true;
- return lookup(map);
- }
- function isModifierKey(event) {
- var name = keyNames[e_prop(event, "keyCode")];
- return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
- }
-
- CodeMirror.fromTextArea = function(textarea, options) {
- if (!options) options = {};
- options.value = textarea.value;
- if (!options.tabindex && textarea.tabindex)
- options.tabindex = textarea.tabindex;
- // 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 = instance.getValue();}
- if (textarea.form) {
- // Deplorable hack to make the submit method do the right thing.
- var rmSubmit = connect(textarea.form, "submit", save, true);
- if (typeof textarea.form.submit == "function") {
- var realSubmit = textarea.form.submit;
- textarea.form.submit = function wrappedSubmit() {
- save();
- textarea.form.submit = realSubmit;
- textarea.form.submit();
- textarea.form.submit = wrappedSubmit;
- };
- }
- }
-
- textarea.style.display = "none";
- var instance = CodeMirror(function(node) {
- textarea.parentNode.insertBefore(node, textarea.nextSibling);
- }, options);
- instance.save = save;
- instance.getTextArea = function() { return textarea; };
- instance.toTextArea = function() {
- save();
- textarea.parentNode.removeChild(instance.getWrapperElement());
- textarea.style.display = "";
- if (textarea.form) {
- rmSubmit();
- if (typeof textarea.form.submit == "function")
- textarea.form.submit = realSubmit;
- }
- };
- return instance;
- };
-
- var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
- var ie = /MSIE \d/.test(navigator.userAgent);
- var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
- var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
- var quirksMode = ie && document.documentMode == 5;
- var webkit = /WebKit\//.test(navigator.userAgent);
- var chrome = /Chrome\//.test(navigator.userAgent);
- var opera = /Opera\//.test(navigator.userAgent);
+// You can find some technical background for some of the code below
+// at http://marijnhaverbeke.nl/blog/#cm-internals .
+
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.CodeMirror = factory());
+}(this, (function () { 'use strict';
+
+ // Kludges for bugs and behavior differences that can't be feature
+ // detected are enabled based on userAgent etc sniffing.
+ var userAgent = navigator.userAgent;
+ var platform = navigator.platform;
+
+ var gecko = /gecko\/\d/i.test(userAgent);
+ var ie_upto10 = /MSIE \d/.test(userAgent);
+ var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent);
+ var edge = /Edge\/(\d+)/.exec(userAgent);
+ var ie = ie_upto10 || ie_11up || edge;
+ var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]);
+ var webkit = !edge && /WebKit\//.test(userAgent);
+ var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent);
+ var chrome = !edge && /Chrome\//.test(userAgent);
+ var presto = /Opera\//.test(userAgent);
var safari = /Apple Computer/.test(navigator.vendor);
- var khtml = /KHTML\//.test(navigator.userAgent);
- var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent);
+ var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent);
+ var phantom = /PhantomJS/.test(userAgent);
- // 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;
+ var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent);
+ var android = /Android/.test(userAgent);
+ // This is woefully incomplete. Suggestions for alternative methods welcome.
+ var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent);
+ var mac = ios || /Mac/.test(platform);
+ var chromeOS = /\bCrOS\b/.test(userAgent);
+ var windows = /win/i.test(platform);
- // 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;
- }
- 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() {return countColumn(this.string, this.start, this.tabSize);},
- 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;};
- if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
- if (consume !== false) this.pos += pattern.length;
- return true;
- }
- } else {
- var match = this.string.slice(this.pos).match(pattern);
- if (match && consume !== false) this.pos += match[0].length;
- return match;
- }
- },
- current: function(){return this.string.slice(this.start, this.pos);}
- };
- CodeMirror.StringStream = StringStream;
+ var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/);
+ if (presto_version) { presto_version = Number(presto_version[1]); }
+ if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
+ // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
+ var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));
+ var captureRightClick = gecko || (ie && ie_version >= 9);
- function MarkedText(from, to, className, marker) {
- this.from = from; this.to = to; this.style = className; this.marker = marker;
- }
- MarkedText.prototype = {
- attach: function(line) { this.marker.set.push(line); },
- detach: function(line) {
- var ix = indexOf(this.marker.set, line);
- if (ix > -1) this.marker.set.splice(ix, 1);
- },
- split: function(pos, lenBefore) {
- if (this.to <= pos && this.to != null) return null;
- var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
- var to = this.to == null ? null : this.to - pos + lenBefore;
- return new MarkedText(from, to, this.style, this.marker);
- },
- dup: function() { return new MarkedText(null, null, this.style, this.marker); },
- clipTo: function(fromOpen, from, toOpen, to, diff) {
- if (fromOpen && to > this.from && (to < this.to || this.to == null))
- this.from = null;
- else if (this.from != null && this.from >= from)
- this.from = Math.max(to, this.from) + diff;
- if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
- this.to = null;
- else if (this.to != null && this.to > from)
- this.to = to < this.to ? this.to + diff : from;
- },
- isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
- sameSet: function(x) { return this.marker == x.marker; }
- };
+ function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
- function Bookmark(pos) {
- this.from = pos; this.to = pos; this.line = null;
- }
- Bookmark.prototype = {
- attach: function(line) { this.line = line; },
- detach: function(line) { if (this.line == line) this.line = null; },
- split: function(pos, lenBefore) {
- if (pos < this.from) {
- this.from = this.to = (this.from - pos) + lenBefore;
- return this;
- }
- },
- isDead: function() { return this.from > this.to; },
- clipTo: function(fromOpen, from, toOpen, to, diff) {
- if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
- this.from = 0; this.to = -1;
- } else if (this.from > from) {
- this.from = this.to = Math.max(to, this.from) + diff;
- }
- },
- sameSet: function(x) { return false; },
- find: function() {
- if (!this.line || !this.line.parent) return null;
- return {line: lineNo(this.line), ch: this.from};
- },
- clear: function() {
- if (this.line) {
- var found = indexOf(this.line.marked, this);
- if (found != -1) this.line.marked.splice(found, 1);
- this.line = null;
- }
+ var rmClass = function(node, cls) {
+ var current = node.className;
+ var match = classTest(cls).exec(current);
+ if (match) {
+ var after = current.slice(match.index + match[0].length);
+ node.className = current.slice(0, match.index) + (after ? match[1] + after : "");
}
};
- // When measuring the position of the end of a line, different
- // browsers require different approaches. If an empty span is added,
- // many browsers report bogus offsets. Of those, some (Webkit,
- // recent IE) will accept a space without moving the whole span to
- // the next line when wrapping it, others work with a zero-width
- // space.
- var eolSpanContent = " ";
- if (gecko || (ie && !ie_lt8)) eolSpanContent = "\u200b";
- else if (opera) eolSpanContent = "";
-
- // Line objects. These hold state related to a line, including
- // highlighting info (the styles array).
- function Line(text, styles) {
- this.styles = styles || [text, null];
- this.text = text;
- this.height = 1;
+ function removeChildren(e) {
+ for (var count = e.childNodes.length; count > 0; --count)
+ { e.removeChild(e.firstChild); }
+ return e
}
- Line.inheritMarks = function(text, orig) {
- var ln = new Line(text), mk = orig && orig.marked;
- if (mk) {
- for (var i = 0; i < mk.length; ++i) {
- if (mk[i].to == null && mk[i].style) {
- var newmk = ln.marked || (ln.marked = []), mark = mk[i];
- var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
- }
+
+ function removeChildrenAndAdd(parent, e) {
+ return removeChildren(parent).appendChild(e)
+ }
+
+ 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") { e.appendChild(document.createTextNode(content)); }
+ else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } }
+ return e
+ }
+ // wrapper for elt, which removes the elt from the accessibility tree
+ function eltP(tag, content, className, style) {
+ var e = elt(tag, content, className, style);
+ e.setAttribute("role", "presentation");
+ return e
+ }
+
+ var range;
+ if (document.createRange) { range = function(node, start, end, endNode) {
+ var r = document.createRange();
+ r.setEnd(endNode || node, end);
+ r.setStart(node, start);
+ return r
+ }; }
+ else { range = function(node, start, end) {
+ var r = document.body.createTextRange();
+ try { r.moveToElementText(node.parentNode); }
+ catch(e) { return r }
+ r.collapse(true);
+ r.moveEnd("character", end);
+ r.moveStart("character", start);
+ return r
+ }; }
+
+ function contains(parent, child) {
+ if (child.nodeType == 3) // Android browser always returns false when child is a textnode
+ { child = child.parentNode; }
+ if (parent.contains)
+ { return parent.contains(child) }
+ do {
+ if (child.nodeType == 11) { child = child.host; }
+ if (child == parent) { return true }
+ } while (child = child.parentNode)
+ }
+
+ function activeElt() {
+ // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement.
+ // IE < 10 will throw when accessed while the page is loading or in an iframe.
+ // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable.
+ var activeElement;
+ try {
+ activeElement = document.activeElement;
+ } catch(e) {
+ activeElement = document.body || null;
+ }
+ while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement)
+ { activeElement = activeElement.shadowRoot.activeElement; }
+ return activeElement
+ }
+
+ function addClass(node, cls) {
+ var current = node.className;
+ if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls; }
+ }
+ function joinClasses(a, b) {
+ var as = a.split(" ");
+ for (var i = 0; i < as.length; i++)
+ { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i]; } }
+ return b
+ }
+
+ var selectInput = function(node) { node.select(); };
+ if (ios) // Mobile Safari apparently has a bug where select() is broken.
+ { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; }
+ else if (ie) // Suppress mysterious IE10 errors
+ { selectInput = function(node) { try { node.select(); } catch(_e) {} }; }
+
+ function bind(f) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function(){return f.apply(null, args)}
+ }
+
+ function copyObj(obj, target, overwrite) {
+ if (!target) { target = {}; }
+ for (var prop in obj)
+ { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))
+ { target[prop] = obj[prop]; } }
+ return target
+ }
+
+ // 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;;) {
+ var nextTab = string.indexOf("\t", i);
+ if (nextTab < 0 || nextTab >= end)
+ { return n + (end - i) }
+ n += nextTab - i;
+ n += tabSize - (n % tabSize);
+ i = nextTab + 1;
+ }
+ }
+
+ var Delayed = function() {this.id = null;};
+ Delayed.prototype.set = function (ms, f) {
+ clearTimeout(this.id);
+ this.id = setTimeout(f, ms);
+ };
+
+ function indexOf(array, elt) {
+ for (var i = 0; i < array.length; ++i)
+ { if (array[i] == elt) { return i } }
+ return -1
+ }
+
+ // Number of pixels added to scroller and sizer to hide scrollbar
+ var scrollerGap = 30;
+
+ // Returned or thrown by various protocols to signal 'I'm not
+ // handling this'.
+ var Pass = {toString: function(){return "CodeMirror.Pass"}};
+
+ // Reused option objects for setSelection & friends
+ var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"};
+
+ // The inverse of countColumn -- find the offset that corresponds to
+ // a particular column.
+ function findColumn(string, goal, tabSize) {
+ for (var pos = 0, col = 0;;) {
+ var nextTab = string.indexOf("\t", pos);
+ if (nextTab == -1) { nextTab = string.length; }
+ var skipped = nextTab - pos;
+ if (nextTab == string.length || col + skipped >= goal)
+ { return pos + Math.min(skipped, goal - col) }
+ col += nextTab - pos;
+ col += tabSize - (col % tabSize);
+ pos = nextTab + 1;
+ if (col >= goal) { return pos }
+ }
+ }
+
+ 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 map(array, f) {
+ var out = [];
+ for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); }
+ return out
+ }
+
+ function insertSorted(array, value, score) {
+ var pos = 0, priority = score(value);
+ while (pos < array.length && score(array[pos]) <= priority) { pos++; }
+ array.splice(pos, 0, value);
+ }
+
+ function nothing() {}
+
+ function createObj(base, props) {
+ var inst;
+ if (Object.create) {
+ inst = Object.create(base);
+ } else {
+ nothing.prototype = base;
+ inst = new nothing();
+ }
+ if (props) { copyObj(props, inst); }
+ return inst
+ }
+
+ var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
+ function isWordCharBasic(ch) {
+ return /\w/.test(ch) || ch > "\x80" &&
+ (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch))
+ }
+ function isWordChar(ch, helper) {
+ if (!helper) { return isWordCharBasic(ch) }
+ if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true }
+ return helper.test(ch)
+ }
+
+ function isEmpty(obj) {
+ for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } }
+ return true
+ }
+
+ // Extending unicode characters. A series of a non-extending char +
+ // any number of extending chars is treated as a single unit as far
+ // as editing and measuring is concerned. This is not fully correct,
+ // since some scripts/fonts/browsers also treat other configurations
+ // of code points as a group.
+ var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;
+ function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) }
+
+ // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range.
+ function skipExtendingChars(str, pos, dir) {
+ while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir; }
+ return pos
+ }
+
+ // Returns the value from the range [`from`; `to`] that satisfies
+ // `pred` and is closest to `from`. Assumes that at least `to`
+ // satisfies `pred`. Supports `from` being greater than `to`.
+ function findFirst(pred, from, to) {
+ // At any point we are certain `to` satisfies `pred`, don't know
+ // whether `from` does.
+ var dir = from > to ? -1 : 1;
+ for (;;) {
+ if (from == to) { return from }
+ var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF);
+ if (mid == from) { return pred(mid) ? from : to }
+ if (pred(mid)) { to = mid; }
+ else { from = mid + dir; }
+ }
+ }
+
+ // BIDI HELPERS
+
+ function iterateBidiSections(order, from, to, f) {
+ if (!order) { return f(from, to, "ltr", 0) }
+ var found = false;
+ 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", i);
+ found = true;
}
}
- return ln;
- };
- Line.prototype = {
- // Replace a piece of a line, keeping the styles around it intact.
- replace: function(from, to_, text) {
- var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
- copyStyles(0, from, this.styles, st);
- if (text) st.push(text, null);
- copyStyles(to, this.text.length, this.styles, st);
- this.styles = st;
- this.text = this.text.slice(0, from) + text + this.text.slice(to);
- this.stateAfter = null;
- if (mk) {
- var diff = text.length - (to - from);
- for (var i = 0; i < mk.length; ++i) {
- var mark = mk[i];
- mark.clipTo(from == null, from || 0, to_ == null, to, diff);
- if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
+ if (!found) { f(from, to, "ltr"); }
+ }
+
+ var bidiOther = null;
+ function getBidiPartAt(order, ch, sticky) {
+ var found;
+ bidiOther = null;
+ for (var i = 0; i < order.length; ++i) {
+ var cur = order[i];
+ if (cur.from < ch && cur.to > ch) { return i }
+ if (cur.to == ch) {
+ if (cur.from != cur.to && sticky == "before") { found = i; }
+ else { bidiOther = i; }
+ }
+ if (cur.from == ch) {
+ if (cur.from != cur.to && sticky != "before") { found = i; }
+ else { bidiOther = i; }
+ }
+ }
+ return found != null ? found : bidiOther
+ }
+
+ // 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%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";
+ // Character types for codepoints 0x600 to 0x6f9
+ var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111";
+ function charType(code) {
+ if (code <= 0xf7) { return lowTypes.charAt(code) }
+ else if (0x590 <= code && code <= 0x5f4) { return "R" }
+ else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) }
+ else if (0x6ee <= code && code <= 0x8ac) { return "r" }
+ else if (0x2000 <= code && code <= 0x200b) { return "w" }
+ else if (code == 0x200c) { return "b" }
+ else { return "L" }
+ }
+
+ var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
+ var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
+
+ function BidiSpan(level, from, to) {
+ this.level = level;
+ this.from = from; this.to = to;
+ }
+
+ return function(str, direction) {
+ var outerType = direction == "ltr" ? "L" : "R";
+
+ if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false }
+ var len = str.length, types = [];
+ for (var i = 0; i < len; ++i)
+ { types.push(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$1 = 0, prev = outerType; i$1 < len; ++i$1) {
+ var type = types[i$1];
+ if (type == "m") { types[i$1] = 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$2 = 0, cur = outerType; i$2 < len; ++i$2) {
+ var type$1 = types[i$2];
+ if (type$1 == "1" && cur == "r") { types[i$2] = "n"; }
+ else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "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$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) {
+ var type$2 = types[i$3];
+ if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1"; }
+ else if (type$2 == "," && prev$1 == types[i$3+1] &&
+ (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1; }
+ prev$1 = type$2;
+ }
+
+ // 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$4 = 0; i$4 < len; ++i$4) {
+ var type$3 = types[i$4];
+ if (type$3 == ",") { types[i$4] = "N"; }
+ else if (type$3 == "%") {
+ var end = (void 0);
+ for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {}
+ var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N";
+ for (var j = i$4; j < end; ++j) { types[j] = replace; }
+ i$4 = end - 1;
}
}
- },
- // Split a part off a line, keeping styles and markers intact.
- split: function(pos, textBefore) {
- var st = [textBefore, null], mk = this.marked;
- copyStyles(pos, this.text.length, this.styles, st);
- var taken = new Line(textBefore + this.text.slice(pos), st);
- if (mk) {
- for (var i = 0; i < mk.length; ++i) {
- var mark = mk[i];
- var newmark = mark.split(pos, textBefore.length);
- if (newmark) {
- if (!taken.marked) taken.marked = [];
- taken.marked.push(newmark); newmark.attach(taken);
- if (newmark == mark) mk.splice(i--, 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$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) {
+ var type$4 = types[i$5];
+ if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L"; }
+ else if (isStrong.test(type$4)) { cur$1 = type$4; }
+ }
+
+ // 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$6 = 0; i$6 < len; ++i$6) {
+ if (isNeutral.test(types[i$6])) {
+ var end$1 = (void 0);
+ for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {}
+ var before = (i$6 ? types[i$6-1] : outerType) == "L";
+ var after = (end$1 < len ? types[end$1] : outerType) == "L";
+ var replace$1 = before == after ? (before ? "L" : "R") : outerType;
+ for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1; }
+ i$6 = end$1 - 1;
}
}
- return taken;
- },
- append: function(line) {
- var mylen = this.text.length, mk = line.marked, mymk = this.marked;
- this.text += line.text;
- copyStyles(0, line.text.length, line.styles, this.styles);
- if (mymk) {
- for (var i = 0; i < mymk.length; ++i)
- if (mymk[i].to == null) mymk[i].to = mylen;
- }
- if (mk && mk.length) {
- if (!mymk) this.marked = mymk = [];
- outer: for (var i = 0; i < mk.length; ++i) {
- var mark = mk[i];
- if (!mark.from) {
- for (var j = 0; j < mymk.length; ++j) {
- var mymark = mymk[j];
- if (mymark.to == mylen && mymark.sameSet(mark)) {
- mymark.to = mark.to == null ? null : mark.to + mylen;
- if (mymark.isDead()) {
- mymark.detach(this);
- mk.splice(i--, 1);
- }
- continue outer;
- }
- }
- }
- mymk.push(mark);
- mark.attach(this);
- mark.from += mylen;
- if (mark.to != null) mark.to += mylen;
- }
- }
- },
- fixMarkEnds: function(other) {
- var mk = this.marked, omk = other.marked;
- if (!mk) return;
- outer: for (var i = 0; i < mk.length; ++i) {
- var mark = mk[i], close = mark.to == null;
- if (close && omk) {
- for (var j = 0; j < omk.length; ++j) {
- var om = omk[j];
- if (!om.sameSet(mark) || om.from != null) continue;
- if (mark.from == this.text.length && om.to == 0) {
- omk.splice(j, 1);
- mk.splice(i--, 1);
- continue outer;
- } else {
- close = false; break;
- }
- }
- }
- if (close) mark.to = this.text.length;
- }
- },
- fixMarkStarts: function() {
- var mk = this.marked;
- if (!mk) return;
- for (var i = 0; i < mk.length; ++i)
- if (mk[i].from == null) mk[i].from = 0;
- },
- addMark: function(mark) {
- mark.attach(this);
- if (this.marked == null) this.marked = [];
- this.marked.push(mark);
- this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
- },
- // Run the given mode's parser over a line, update the styles
- // array, which contains alternating fragments of text and CSS
- // classes.
- highlight: function(mode, state, tabSize) {
- var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;
- var changed = false, curWord = st[0], prevWord;
- if (this.text == "" && mode.blankLine) mode.blankLine(state);
- while (!stream.eol()) {
- var style = mode.token(stream, state);
- var substr = this.text.slice(stream.start, stream.pos);
- stream.start = stream.pos;
- if (pos && st[pos-1] == style)
- st[pos-2] += substr;
- else if (substr) {
- if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
- st[pos++] = substr; st[pos++] = style;
- prevWord = curWord; curWord = st[pos];
- }
- // Give up when line is ridiculously long
- if (stream.pos > 5000) {
- st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
- break;
- }
- }
- if (st.length != pos) {st.length = pos; changed = true;}
- if (pos && st[pos-2] != prevWord) changed = true;
- // Short lines with simple highlights return null, and are
- // counted as changed by the driver because they are likely to
- // highlight the same way in various contexts.
- return changed || (st.length < 5 && this.text.length < 10 ? null : false);
- },
- // Fetch the parser token for a given character. Useful for hacks
- // that want to inspect the mode state (say, for completion).
- getTokenAt: function(mode, state, tabSize, ch) {
- var txt = this.text, stream = new StringStream(txt, tabSize);
- while (stream.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,
- state: state};
- },
- indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
- // Produces an HTML fragment for the line, taking selection,
- // marking, and highlighting into account.
- getElement: function(makeTab, wrapAt, wrapWBR) {
- var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
- var pre = elt("pre");
- function span_(html, text, style) {
- if (!text) return;
- // Work around a bug where, in some compat modes, IE ignores leading spaces
- if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
- first = false;
- if (!specials.test(text)) {
- col += text.length;
- var content = document.createTextNode(text);
+
+ // 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$7 = 0; i$7 < len;) {
+ if (countsAsLeft.test(types[i$7])) {
+ var start = i$7;
+ for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {}
+ order.push(new BidiSpan(0, start, i$7));
} else {
- var content = document.createDocumentFragment(), pos = 0;
- while (true) {
- specials.lastIndex = pos;
- var m = specials.exec(text);
- var skipped = m ? m.index - pos : text.length - pos;
- if (skipped) {
- content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
- col += skipped;
- }
- if (!m) break;
- pos += skipped + 1;
- if (m[0] == "\t") {
- var tab = makeTab(col);
- content.appendChild(tab.element.cloneNode(true));
- col += tab.width;
- } else {
- var token = elt("span", "\u2022", "cm-invalidchar");
- token.title = "\\u" + m[0].charCodeAt(0).toString(16);
- content.appendChild(token);
- col += 1;
- }
+ var pos = i$7, at = order.length;
+ for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {}
+ for (var j$2 = pos; j$2 < i$7;) {
+ if (countsAsNum.test(types[j$2])) {
+ if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)); }
+ var nstart = j$2;
+ for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {}
+ order.splice(at, 0, new BidiSpan(2, nstart, j$2));
+ pos = j$2;
+ } else { ++j$2; }
}
+ if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); }
}
- if (style) html.appendChild(elt("span", [content], style));
- else html.appendChild(content);
}
- var span = span_;
- if (wrapAt != null) {
- var outPos = 0, anchor = pre.anchor = elt("span");
- span = function(html, text, style) {
- var l = text.length;
- if (wrapAt >= outPos && wrapAt < outPos + l) {
- if (wrapAt > outPos) {
- span_(html, text.slice(0, wrapAt - outPos), style);
- // See comment at the definition of spanAffectsWrapping
- if (wrapWBR) html.appendChild(elt("wbr"));
- }
- html.appendChild(anchor);
- var cut = wrapAt - outPos;
- span_(anchor, opera ? text.slice(cut, cut + 1) : text.slice(cut), style);
- if (opera) span_(html, text.slice(cut + 1), style);
- wrapAt--;
- outPos += l;
- } else {
- outPos += l;
- span_(html, text, style);
- if (outPos == wrapAt && outPos == len) {
- setTextContent(anchor, eolSpanContent);
- html.appendChild(anchor);
- }
- // Stop outputting HTML when gone sufficiently far beyond measure
- else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){};
- }
- };
+ if (direction == "ltr") {
+ if (order[0].level == 1 && (m = str.match(/^\s+/))) {
+ order[0].from = m[0].length;
+ order.unshift(new BidiSpan(0, 0, m[0].length));
+ }
+ if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
+ lst(order).to -= m[0].length;
+ order.push(new BidiSpan(0, len - m[0].length, len));
+ }
}
- var st = this.styles, allText = this.text, marked = this.marked;
- var len = allText.length;
- function styleToClass(style) {
- if (!style) return null;
- return "cm-" + style.replace(/ +/g, " cm-");
- }
- if (!allText && wrapAt == null) {
- span(pre, " ");
- } else if (!marked || !marked.length) {
- for (var i = 0, ch = 0; ch < len; i+=2) {
- var str = st[i], style = st[i+1], l = str.length;
- if (ch + l > len) str = str.slice(0, len - ch);
- ch += l;
- span(pre, str, styleToClass(style));
- }
- } else {
- var pos = 0, i = 0, text = "", style, sg = 0;
- var nextChange = marked[0].from || 0, marks = [], markpos = 0;
- var advanceMarks = function() {
- var m;
- while (markpos < marked.length &&
- ((m = marked[markpos]).from == pos || m.from == null)) {
- if (m.style != null) marks.push(m);
- ++markpos;
- }
- nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
- for (var i = 0; i < marks.length; ++i) {
- var to = marks[i].to;
- if (to == null) to = Infinity;
- if (to == pos) marks.splice(i--, 1);
- else nextChange = Math.min(to, nextChange);
- }
- };
- var m = 0;
- while (pos < len) {
- if (nextChange == pos) advanceMarks();
- var upto = Math.min(len, nextChange);
- while (true) {
- if (text) {
- var end = pos + text.length;
- var appliedStyle = style;
- for (var j = 0; j < marks.length; ++j)
- appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style;
- span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
- if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
- pos = end;
- }
- text = st[i++]; style = styleToClass(st[i++]);
- }
- }
- }
- return pre;
- },
- cleanUp: function() {
- this.parent = null;
- if (this.marked)
- for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
- }
- };
- // Utility used by replace and split above
- function copyStyles(from, to, source, dest) {
- for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {
- var part = source[i], end = pos + part.length;
- if (state == 0) {
- if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);
- if (end >= from) state = 1;
- } else if (state == 1) {
- if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);
- else dest.push(part, source[i+1]);
- }
- pos = end;
+ return direction == "rtl" ? order.reverse() : order
}
+ })();
+
+ // Get the bidi ordering for the given line (and cache it). Returns
+ // false for lines that are fully left-to-right, and an array of
+ // BidiSpan objects otherwise.
+ function getOrder(line, direction) {
+ var order = line.order;
+ if (order == null) { order = line.order = bidiOrdering(line.text, direction); }
+ return order
}
- // Data structure that holds the sequence of lines.
- 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; },
- remove: function(at, n, callbacks) {
- for (var i = at, e = at + n; i < e; ++i) {
- var line = this.lines[i];
- this.height -= line.height;
- line.cleanUp();
- if (line.handlers)
- for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
- }
- this.lines.splice(at, n);
- },
- collapse: function(lines) {
- lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
- },
- insertHeight: 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; },
- remove: function(at, n, callbacks) {
- 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.remove(at, rm, callbacks);
- 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);
- },
- insert: function(at, lines) {
- var height = 0;
- for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
- this.insertHeight(at, lines, height);
- },
- insertHeight: 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.insertHeight(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();
- },
- iter: function(from, to, op) { this.iterN(from, to - from, op); },
- 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;
- }
+ // EVENT HANDLING
+
+ // Lightweight event framework. on/off also work on DOM nodes,
+ // registering native DOM handlers.
+
+ var noHandlers = [];
+
+ var on = function(emitter, type, f) {
+ if (emitter.addEventListener) {
+ emitter.addEventListener(type, f, false);
+ } else if (emitter.attachEvent) {
+ emitter.attachEvent("on" + type, f);
+ } else {
+ var map$$1 = emitter._handlers || (emitter._handlers = {});
+ map$$1[type] = (map$$1[type] || noHandlers).concat(f);
}
};
- function getLineAt(chunk, n) {
- 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 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, e = chunk.children.length; ; ++i) {
- if (chunk.children[i] == cur) break;
- no += chunk.children[i].chunkSize();
- }
- }
- return no;
- }
- function lineAtHeight(chunk, h) {
- var n = 0;
- 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(chunk, n) {
- var h = 0;
- outer: do {
- for (var i = 0, e = chunk.children.length; i < e; ++i) {
- var child = chunk.children[i], sz = child.chunkSize();
- if (n < sz) { chunk = child; continue outer; }
- n -= sz;
- h += child.height;
- }
- return h;
- } while (!chunk.lines);
- for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
- return h;
+ function getHandlers(emitter, type) {
+ return emitter._handlers && emitter._handlers[type] || noHandlers
}
- // The history object 'chunks' changes that are made close together
- // and at almost the same time into bigger undoable units.
- function History() {
- this.time = 0;
- this.done = []; this.undone = [];
- this.compound = 0;
- this.closed = false;
- }
- History.prototype = {
- addChange: function(start, added, old) {
- this.undone.length = 0;
- var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1];
- var dtime = time - this.time;
-
- if (this.compound && cur && !this.closed) {
- cur.push({start: start, added: added, old: old});
- } else if (dtime > 400 || !last || this.closed ||
- last.start > start + old.length || last.start + last.added < start) {
- this.done.push([{start: start, added: added, old: old}]);
- this.closed = false;
- } else {
- var startBefore = Math.max(0, last.start - start),
- endAfter = Math.max(0, (start + old.length) - (last.start + last.added));
- for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]);
- for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]);
- if (startBefore) last.start = start;
- last.added += added - (old.length - startBefore - endAfter);
+ function off(emitter, type, f) {
+ if (emitter.removeEventListener) {
+ emitter.removeEventListener(type, f, false);
+ } else if (emitter.detachEvent) {
+ emitter.detachEvent("on" + type, f);
+ } else {
+ var map$$1 = emitter._handlers, arr = map$$1 && map$$1[type];
+ if (arr) {
+ var index = indexOf(arr, f);
+ if (index > -1)
+ { map$$1[type] = arr.slice(0, index).concat(arr.slice(index + 1)); }
}
- this.time = time;
- },
- startCompound: function() {
- if (!this.compound++) this.closed = true;
- },
- endCompound: function() {
- if (!--this.compound) this.closed = true;
}
- };
-
- function stopMethod() {e_stop(this);}
- // Ensure an event has a stop method.
- function addStop(event) {
- if (!event.stop) event.stop = stopMethod;
- return event;
}
+ function signal(emitter, type /*, values...*/) {
+ var handlers = getHandlers(emitter, type);
+ if (!handlers.length) { return }
+ var args = Array.prototype.slice.call(arguments, 2);
+ for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args); }
+ }
+
+ // The DOM events that CodeMirror handles can be overridden by
+ // registering a (non-DOM) handler on the editor for the event name,
+ // and preventDefault-ing the event in that handler.
+ function signalDOMEvent(cm, e, override) {
+ if (typeof e == "string")
+ { e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; }
+ signal(cm, override || e.type, cm, e);
+ return e_defaultPrevented(e) || e.codemirrorIgnore
+ }
+
+ function signalCursorActivity(cm) {
+ var arr = cm._handlers && cm._handlers.cursorActivity;
+ if (!arr) { return }
+ var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);
+ for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1)
+ { set.push(arr[i]); } }
+ }
+
+ function hasHandler(emitter, type) {
+ return getHandlers(emitter, type).length > 0
+ }
+
+ // Add on and off methods to a constructor's prototype, to make
+ // registering events on such objects more convenient.
+ function eventMixin(ctor) {
+ ctor.prototype.on = function(type, f) {on(this, type, f);};
+ ctor.prototype.off = function(type, f) {off(this, type, f);};
+ }
+
+ // Due to the fact that we still support jurassic IE versions, some
+ // compatibility wrappers are needed.
+
function e_preventDefault(e) {
- if (e.preventDefault) e.preventDefault();
- else e.returnValue = false;
+ if (e.preventDefault) { e.preventDefault(); }
+ else { e.returnValue = false; }
}
function e_stopPropagation(e) {
- if (e.stopPropagation) e.stopPropagation();
- else e.cancelBubble = true;
+ 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_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 (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;
+ if (mac && e.ctrlKey && b == 1) { b = 3; }
+ return b
}
- // Allow 3rd-party code to override event properties by adding an override
- // object to an event object.
- function e_prop(e, prop) {
- var overridden = e.override && e.override.hasOwnProperty(prop);
- return overridden ? e.override[prop] : e[prop];
- }
-
- // Event handler registration. If disconnect is true, it'll return a
- // function that unregisters the handler.
- function connect(node, type, handler, disconnect) {
- if (typeof node.addEventListener == "function") {
- node.addEventListener(type, handler, false);
- if (disconnect) return function() {node.removeEventListener(type, handler, false);};
- } else {
- var wrapHandler = function(event) {handler(event || window.event);};
- node.attachEvent("on" + type, wrapHandler);
- if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
- }
- }
- CodeMirror.connect = connect;
-
- function Delayed() {this.id = null;}
- Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
-
- var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
-
// 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;
+ if (ie && ie_version < 9) { return false }
var div = elt('div');
- return "draggable" in div || "dragDrop" in div;
+ return "draggable" in div || "dragDrop" in div
}();
- // Feature-detect whether newlines in textareas are converted to \r\n
- var lineSep = function () {
- var te = elt("textarea");
- te.value = "foo\nbar";
- if (te.value.indexOf("\r") > -1) return "\r\n";
- return "\n";
- }();
-
- // 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 regexps matches the
- // character combinations that suffer from this phenomenon on the
- // various browsers.
- var spanAffectsWrapping = /^$/; // Won't match any two-character string
- if (gecko) spanAffectsWrapping = /$'/;
- else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
- else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
-
- // Counts the column offset in a string, taking tabs into account.
- // Used mostly to find indentation.
- function countColumn(string, end, tabSize) {
- if (end == null) {
- end = string.search(/[^\s\u00a0]/);
- if (end == -1) end = string.length;
+ 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 && ie_version < 8); }
}
- for (var i = 0, n = 0; i < end; ++i) {
- if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
- else ++n;
- }
- return n;
+ var node = zwspSupported ? elt("span", "\u200b") :
+ elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
+ node.setAttribute("cm-text", "");
+ return node
}
- function eltOffset(node, screen) {
- // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
- // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
- try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
- catch(e) { box = {top: 0, left: 0}; }
- if (!screen) {
- // Get the toplevel scroll, working around browser differences.
- if (window.pageYOffset == null) {
- var t = document.documentElement || document.body.parentNode;
- if (t.scrollTop == null) t = document.body;
- box.top += t.scrollTop; box.left += t.scrollLeft;
- } else {
- box.top += window.pageYOffset; box.left += window.pageXOffset;
- }
- }
- return box;
- }
-
- // Get a node's text content.
- function eltText(node) {
- return node.textContent || node.innerText || node.nodeValue || "";
- }
- function selectInput(node) {
- if (ios) { // Mobile Safari apparently has a bug where select() is broken.
- node.selectionStart = 0;
- node.selectionEnd = node.value.length;
- } else node.select();
- }
-
- // Operations on {line, ch} objects.
- 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 {line: x.line, ch: x.ch};}
-
- 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) {
- e.innerHTML = "";
- return e;
- }
- function removeChildrenAndAdd(parent, e) {
- removeChildren(parent).appendChild(e);
- }
- function setTextContent(e, str) {
- if (ie_lt9) {
- e.innerHTML = "";
- e.appendChild(document.createTextNode(str));
- } else e.textContent = str;
- }
- CodeMirror.setTextContent = setTextContent;
-
- // Used to position the cursor after an undo/redo by finding the
- // last edited character.
- function editEnd(from, to) {
- if (!to) return 0;
- if (!from) return to.length;
- for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
- if (from.charAt(i) != to.charAt(j)) break;
- return j + 1;
- }
-
- 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 isWordChar(ch) {
- return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
+ // Feature-detect IE's crummy client rect reporting for bidi text
+ var badBidiRects;
+ function hasBadBidiRects(measure) {
+ if (badBidiRects != null) { return badBidiRects }
+ var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"));
+ var r0 = range(txt, 0, 1).getBoundingClientRect();
+ var r1 = range(txt, 1, 2).getBoundingClientRect();
+ removeChildren(measure);
+ if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780)
+ return badBidiRects = (r1.right - r0.right < 3)
}
// 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 splitLinesAuto = "\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;
+ 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) {
@@ -3197,41 +658,9105 @@ window.CodeMirror = (function() {
pos = nl + 1;
}
}
- return result;
- } : function(string){return string.split(/\r\n?|\n/);};
- CodeMirror.splitLines = splitLines;
+ return result
+ } : function (string) { return string.split(/\r\n?|\n/); };
- 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();}
+ var hasSelection = window.getSelection ? function (te) {
+ try { return te.selectionStart != te.selectionEnd }
+ catch(e) { return false }
+ } : function (te) {
+ var range$$1;
+ try {range$$1 = te.ownerDocument.selection.createRange();}
catch(e) {}
- if (!range || range.parentElement() != te) return false;
- return range.compareEndPoints("StartToEnd", range) != 0;
+ if (!range$$1 || range$$1.parentElement() != te) { return false }
+ return range$$1.compareEndPoints("StartToEnd", range$$1) != 0
};
- CodeMirror.defineMode("null", function() {
- return {token: function(stream) {stream.skipToEnd();}};
- });
- CodeMirror.defineMIME("text/plain", "null");
-
- 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;
+ var hasCopyEvent = (function () {
+ var e = elt("div");
+ if ("oncopy" in e) { return true }
+ e.setAttribute("oncopy", "return;");
+ return typeof e.oncopy == "function"
})();
+ var badZoomedRects = null;
+ function hasBadZoomedRects(measure) {
+ if (badZoomedRects != null) { return badZoomedRects }
+ var node = removeChildrenAndAdd(measure, elt("span", "x"));
+ var normal = node.getBoundingClientRect();
+ var fromRange = range(node, 0, 1).getBoundingClientRect();
+ return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1
+ }
+
+ // Known modes, by name and by MIME
+ var modes = {}, mimeModes = {};
+
+ // Extra arguments are stored as the mode's dependencies, which is
+ // used by (legacy) mechanisms like loadmode.js to automatically
+ // load a mode. (Preferred mechanism is the require/define calls.)
+ function defineMode(name, mode) {
+ if (arguments.length > 2)
+ { mode.dependencies = Array.prototype.slice.call(arguments, 2); }
+ modes[name] = mode;
+ }
+
+ function defineMIME(mime, spec) {
+ mimeModes[mime] = spec;
+ }
+
+ // Given a MIME type, a {name, ...options} config object, or a name
+ // string, return a mode config object.
+ function resolveMode(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];
+ if (typeof found == "string") { found = {name: found}; }
+ spec = createObj(found, spec);
+ spec.name = found.name;
+ } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
+ return resolveMode("application/xml")
+ } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) {
+ return resolveMode("application/json")
+ }
+ if (typeof spec == "string") { return {name: spec} }
+ else { return spec || {name: "null"} }
+ }
+
+ // Given a mode spec (anything that resolveMode accepts), find and
+ // initialize an actual mode object.
+ function getMode(options, spec) {
+ spec = resolveMode(spec);
+ var mfactory = modes[spec.name];
+ if (!mfactory) { return 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;
+ if (spec.helperType) { modeObj.helperType = spec.helperType; }
+ if (spec.modeProps) { for (var prop$1 in spec.modeProps)
+ { modeObj[prop$1] = spec.modeProps[prop$1]; } }
+
+ return modeObj
+ }
+
+ // This can be used to attach properties to mode objects from
+ // outside the actual mode definition.
+ var modeExtensions = {};
+ function extendMode(mode, properties) {
+ var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
+ copyObj(properties, exts);
+ }
+
+ 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
+ }
+
+ // Given a mode and a state (for that mode), find the inner mode and
+ // state at the position that the state refers to.
+ function innerMode(mode, state) {
+ var info;
+ while (mode.innerMode) {
+ info = mode.innerMode(state);
+ if (!info || info.mode == mode) { break }
+ state = info.state;
+ mode = info.mode;
+ }
+ return info || {mode: mode, state: state}
+ }
+
+ function startState(mode, a1, a2) {
+ return mode.startState ? mode.startState(a1, a2) : true
+ }
+
+ // STRING STREAM
+
+ // Fed to the mode parsers, provides helper functions to make
+ // parsers more succinct.
+
+ var StringStream = function(string, tabSize, lineOracle) {
+ this.pos = this.start = 0;
+ this.string = string;
+ this.tabSize = tabSize || 8;
+ this.lastColumnPos = this.lastColumnValue = 0;
+ this.lineStart = 0;
+ this.lineOracle = lineOracle;
+ };
+
+ StringStream.prototype.eol = function () {return this.pos >= this.string.length};
+ StringStream.prototype.sol = function () {return this.pos == this.lineStart};
+ StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined};
+ StringStream.prototype.next = function () {
+ if (this.pos < this.string.length)
+ { return this.string.charAt(this.pos++) }
+ };
+ StringStream.prototype.eat = function (match) {
+ var ch = this.string.charAt(this.pos);
+ var ok;
+ if (typeof match == "string") { ok = ch == match; }
+ else { ok = ch && (match.test ? match.test(ch) : match(ch)); }
+ if (ok) {++this.pos; return ch}
+ };
+ StringStream.prototype.eatWhile = function (match) {
+ var start = this.pos;
+ while (this.eat(match)){}
+ return this.pos > start
+ };
+ StringStream.prototype.eatSpace = function () {
+ var this$1 = this;
+
+ var start = this.pos;
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos; }
+ return this.pos > start
+ };
+ StringStream.prototype.skipToEnd = function () {this.pos = this.string.length;};
+ StringStream.prototype.skipTo = function (ch) {
+ var found = this.string.indexOf(ch, this.pos);
+ if (found > -1) {this.pos = found; return true}
+ };
+ StringStream.prototype.backUp = function (n) {this.pos -= n;};
+ StringStream.prototype.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 - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)
+ };
+ StringStream.prototype.indentation = function () {
+ return countColumn(this.string, null, this.tabSize) -
+ (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)
+ };
+ StringStream.prototype.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
+ }
+ };
+ StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)};
+ StringStream.prototype.hideFirstChars = function (n, inner) {
+ this.lineStart += n;
+ try { return inner() }
+ finally { this.lineStart -= n; }
+ };
+ StringStream.prototype.lookAhead = function (n) {
+ var oracle = this.lineOracle;
+ return oracle && oracle.lookAhead(n)
+ };
+ StringStream.prototype.baseToken = function () {
+ var oracle = this.lineOracle;
+ return oracle && oracle.baseToken(this.pos)
+ };
+
+ // Find the line object corresponding to the given line number.
+ function getLine(doc, n) {
+ n -= doc.first;
+ if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") }
+ var chunk = doc;
+ 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]
+ }
+
+ // Get the part of a document between two positions, as an array of
+ // strings.
+ 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
+ }
+ // Get the lines between from and to, as array of strings.
+ function getLines(doc, from, to) {
+ var out = [];
+ doc.iter(from, to, function (line) { out.push(line.text); }); // iter aborts when callback returns truthy value
+ return out
+ }
+
+ // Update the height of a line, propagating the height change
+ // upwards to parent nodes.
+ function updateLineHeight(line, height) {
+ var diff = height - line.height;
+ if (diff) { for (var n = line; n; n = n.parent) { n.height += diff; } }
+ }
+
+ // Given a line object, find its line number by walking up through
+ // its parent links.
+ 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
+ }
+
+ // Find the line at the given vertical position, using the height
+ // information in the document tree.
+ function lineAtHeight(chunk, h) {
+ var n = chunk.first;
+ outer: do {
+ for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) {
+ var child = chunk.children[i$1], ch = child.height;
+ if (h < ch) { chunk = child; continue outer }
+ h -= ch;
+ n += child.chunkSize();
+ }
+ return n
+ } while (!chunk.lines)
+ var i = 0;
+ for (; i < chunk.lines.length; ++i) {
+ var line = chunk.lines[i], lh = line.height;
+ if (h < lh) { break }
+ h -= lh;
+ }
+ return n + i
+ }
+
+ function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size}
+
+ function lineNumberFor(options, i) {
+ return String(options.lineNumberFormatter(i + options.firstLineNumber))
+ }
+
+ // A Pos instance represents a position within the text.
+ function Pos(line, ch, sticky) {
+ if ( sticky === void 0 ) sticky = null;
+
+ if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) }
+ this.line = line;
+ this.ch = ch;
+ this.sticky = sticky;
+ }
+
+ // Compare two positions, return 0 if they are the same, a negative
+ // number when a is less, and a positive number otherwise.
+ function cmp(a, b) { return a.line - b.line || a.ch - b.ch }
+
+ function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 }
+
+ function copyPos(x) {return Pos(x.line, x.ch)}
+ function maxPos(a, b) { return cmp(a, b) < 0 ? b : a }
+ function minPos(a, b) { return cmp(a, b) < 0 ? a : b }
+
+ // Most of the external API clips given positions to make sure they
+ // actually exist within the document.
+ 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 clipPosArray(doc, array) {
+ var out = [];
+ for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]); }
+ return out
+ }
+
+ var SavedContext = function(state, lookAhead) {
+ this.state = state;
+ this.lookAhead = lookAhead;
+ };
+
+ var Context = function(doc, state, line, lookAhead) {
+ this.state = state;
+ this.doc = doc;
+ this.line = line;
+ this.maxLookAhead = lookAhead || 0;
+ this.baseTokens = null;
+ this.baseTokenPos = 1;
+ };
+
+ Context.prototype.lookAhead = function (n) {
+ var line = this.doc.getLine(this.line + n);
+ if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; }
+ return line
+ };
+
+ Context.prototype.baseToken = function (n) {
+ var this$1 = this;
+
+ if (!this.baseTokens) { return null }
+ while (this.baseTokens[this.baseTokenPos] <= n)
+ { this$1.baseTokenPos += 2; }
+ var type = this.baseTokens[this.baseTokenPos + 1];
+ return {type: type && type.replace(/( |^)overlay .*/, ""),
+ size: this.baseTokens[this.baseTokenPos] - n}
+ };
+
+ Context.prototype.nextLine = function () {
+ this.line++;
+ if (this.maxLookAhead > 0) { this.maxLookAhead--; }
+ };
+
+ Context.fromSaved = function (doc, saved, line) {
+ if (saved instanceof SavedContext)
+ { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) }
+ else
+ { return new Context(doc, copyState(doc.mode, saved), line) }
+ };
+
+ Context.prototype.save = function (copy) {
+ var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state;
+ return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state
+ };
+
+
+ // Compute a style array (an array starting with a mode generation
+ // -- for invalidation -- followed by pairs of end positions and
+ // style strings), which is used to highlight the tokens on the
+ // line.
+ function highlightLine(cm, line, context, forceToEnd) {
+ // 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], lineClasses = {};
+ // Compute the base array of styles
+ runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); },
+ lineClasses, forceToEnd);
+ var state = context.state;
+
+ // Run overlays, adjust style array.
+ var loop = function ( o ) {
+ context.baseTokens = st;
+ var overlay = cm.state.overlays[o], i = 1, at = 0;
+ context.state = true;
+ runMode(cm, line.text, overlay.mode, context, 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, "overlay " + style);
+ i = start + 2;
+ } else {
+ for (; start < i; start += 2) {
+ var cur = st[start+1];
+ st[start+1] = (cur ? cur + " " : "") + "overlay " + style;
+ }
+ }
+ }, lineClasses);
+ context.state = state;
+ context.baseTokens = null;
+ context.baseTokenPos = 1;
+ };
+
+ for (var o = 0; o < cm.state.overlays.length; ++o) loop( o );
+
+ return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}
+ }
+
+ function getLineStyles(cm, line, updateFrontier) {
+ if (!line.styles || line.styles[0] != cm.state.modeGen) {
+ var context = getContextBefore(cm, lineNo(line));
+ var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state);
+ var result = highlightLine(cm, line, context);
+ if (resetState) { context.state = resetState; }
+ line.stateAfter = context.save(!resetState);
+ line.styles = result.styles;
+ if (result.classes) { line.styleClasses = result.classes; }
+ else if (line.styleClasses) { line.styleClasses = null; }
+ if (updateFrontier === cm.doc.highlightFrontier)
+ { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); }
+ }
+ return line.styles
+ }
+
+ function getContextBefore(cm, n, precise) {
+ var doc = cm.doc, display = cm.display;
+ if (!doc.mode.startState) { return new Context(doc, true, n) }
+ var start = findStartLine(cm, n, precise);
+ var saved = start > doc.first && getLine(doc, start - 1).stateAfter;
+ var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start);
+
+ doc.iter(start, n, function (line) {
+ processLine(cm, line.text, context);
+ var pos = context.line;
+ line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null;
+ context.nextLine();
+ });
+ if (precise) { doc.modeFrontier = context.line; }
+ return context
+ }
+
+ // Lightweight form of highlight -- proceed over this line and
+ // update state, but don't save a style array. Used for lines that
+ // aren't currently visible.
+ function processLine(cm, text, context, startAt) {
+ var mode = cm.doc.mode;
+ var stream = new StringStream(text, cm.options.tabSize, context);
+ stream.start = stream.pos = startAt || 0;
+ if (text == "") { callBlankLine(mode, context.state); }
+ while (!stream.eol()) {
+ readToken(mode, stream, context.state);
+ stream.start = stream.pos;
+ }
+ }
+
+ function callBlankLine(mode, state) {
+ if (mode.blankLine) { return mode.blankLine(state) }
+ if (!mode.innerMode) { return }
+ var inner = innerMode(mode, state);
+ if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) }
+ }
+
+ function readToken(mode, stream, state, inner) {
+ for (var i = 0; i < 10; i++) {
+ if (inner) { inner[0] = innerMode(mode, state).mode; }
+ var style = mode.token(stream, state);
+ if (stream.pos > stream.start) { return style }
+ }
+ throw new Error("Mode " + mode.name + " failed to advance stream.")
+ }
+
+ var Token = function(stream, type, state) {
+ this.start = stream.start; this.end = stream.pos;
+ this.string = stream.current();
+ this.type = type || null;
+ this.state = state;
+ };
+
+ // Utility for getTokenAt and getLineTokens
+ function takeToken(cm, pos, precise, asArray) {
+ var doc = cm.doc, mode = doc.mode, style;
+ pos = clipPos(doc, pos);
+ var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise);
+ var stream = new StringStream(line.text, cm.options.tabSize, context), tokens;
+ if (asArray) { tokens = []; }
+ while ((asArray || stream.pos < pos.ch) && !stream.eol()) {
+ stream.start = stream.pos;
+ style = readToken(mode, stream, context.state);
+ if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); }
+ }
+ return asArray ? tokens : new Token(stream, style, context.state)
+ }
+
+ function extractLineClasses(type, output) {
+ if (type) { for (;;) {
+ var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/);
+ if (!lineClass) { break }
+ type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length);
+ var prop = lineClass[1] ? "bgClass" : "textClass";
+ if (output[prop] == null)
+ { output[prop] = lineClass[2]; }
+ else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop]))
+ { output[prop] += " " + lineClass[2]; }
+ } }
+ return type
+ }
+
+ // Run the given mode's parser over a line, calling f for each token.
+ function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) {
+ var flattenSpans = mode.flattenSpans;
+ if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans; }
+ var curStart = 0, curStyle = null;
+ var stream = new StringStream(text, cm.options.tabSize, context), style;
+ var inner = cm.options.addModeClass && [null];
+ if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses); }
+ while (!stream.eol()) {
+ if (stream.pos > cm.options.maxHighlightLength) {
+ flattenSpans = false;
+ if (forceToEnd) { processLine(cm, text, context, stream.pos); }
+ stream.pos = text.length;
+ style = null;
+ } else {
+ style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses);
+ }
+ if (inner) {
+ var mName = inner[0].name;
+ if (mName) { style = "m-" + (style ? mName + " " + style : mName); }
+ }
+ if (!flattenSpans || curStyle != style) {
+ while (curStart < stream.start) {
+ curStart = Math.min(stream.start, curStart + 5000);
+ f(curStart, curStyle);
+ }
+ curStyle = style;
+ }
+ stream.start = stream.pos;
+ }
+ while (curStart < stream.pos) {
+ // Webkit seems to refuse to render text nodes longer than 57444
+ // characters, and returns inaccurate measurements in nodes
+ // starting around 5000 chars.
+ var pos = Math.min(stream.pos, curStart + 5000);
+ f(pos, curStyle);
+ curStart = pos;
+ }
+ }
+
+ // 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;
+ var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);
+ for (var search = n; search > lim; --search) {
+ if (search <= doc.first) { return doc.first }
+ var line = getLine(doc, search - 1), after = line.stateAfter;
+ if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier))
+ { return search }
+ var indented = countColumn(line.text, null, cm.options.tabSize);
+ if (minline == null || minindent > indented) {
+ minline = search - 1;
+ minindent = indented;
+ }
+ }
+ return minline
+ }
+
+ function retreatFrontier(doc, n) {
+ doc.modeFrontier = Math.min(doc.modeFrontier, n);
+ if (doc.highlightFrontier < n - 10) { return }
+ var start = doc.first;
+ for (var line = n - 1; line > start; line--) {
+ var saved = getLine(doc, line).stateAfter;
+ // change is on 3
+ // state on line 1 looked ahead 2 -- so saw 3
+ // test 1 + 2 < 3 should cover this
+ if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) {
+ start = line + 1;
+ break
+ }
+ }
+ doc.highlightFrontier = Math.min(doc.highlightFrontier, start);
+ }
+
+ // Optimize some code when these features are not used.
+ var sawReadOnlySpans = false, sawCollapsedSpans = false;
+
+ function seeReadOnlySpans() {
+ sawReadOnlySpans = true;
+ }
+
+ function seeCollapsedSpans() {
+ sawCollapsedSpans = true;
+ }
+
+ // TEXTMARKER SPANS
+
+ function MarkedSpan(marker, from, to) {
+ this.marker = marker;
+ this.from = from; this.to = to;
+ }
+
+ // Search an array of spans for a span matching the given marker.
+ function getMarkedSpanFor(spans, marker) {
+ if (spans) { for (var i = 0; i < spans.length; ++i) {
+ var span = spans[i];
+ if (span.marker == marker) { return span }
+ } }
+ }
+ // Remove a span from an array, returning undefined if no spans are
+ // left (we don't store arrays for lines without spans).
+ function removeMarkedSpan(spans, span) {
+ var r;
+ for (var i = 0; i < spans.length; ++i)
+ { if (spans[i] != span) { (r || (r = [])).push(spans[i]); } }
+ return r
+ }
+ // Add a span to a line.
+ function addMarkedSpan(line, span) {
+ line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
+ span.marker.attachLine(line);
+ }
+
+ // Used for the algorithm that adjusts markers for a change in the
+ // document. These functions cut an array of spans at a given
+ // character position, returning an array of remaining chunks (or
+ // undefined if nothing remains).
+ function markedSpansBefore(old, startCh, isInsert) {
+ var nw;
+ if (old) { for (var i = 0; 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 || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) {
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh)
+ ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to));
+ }
+ } }
+ return nw
+ }
+ function markedSpansAfter(old, endCh, isInsert) {
+ var nw;
+ if (old) { for (var i = 0; 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 || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) {
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh)
+ ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh,
+ span.to == null ? null : span.to - endCh));
+ }
+ } }
+ return nw
+ }
+
+ // Given a change object, compute the new set of marker spans that
+ // cover the line in which the change took place. Removes spans
+ // entirely within the change, reconnects spans belonging to the
+ // same marker that appear on both sides of the change, and cuts off
+ // spans partially within the change. Returns an array of span
+ // arrays with one element for each line in (after) the change.
+ function stretchSpansOverChange(doc, change) {
+ if (change.full) { return null }
+ 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 = cmp(change.from, change.to) == 0;
+ // 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$1 = 0; i$1 < last.length; ++i$1) {
+ var span$1 = last[i$1];
+ if (span$1.to != null) { span$1.to += offset; }
+ if (span$1.from == null) {
+ var found$1 = getMarkedSpanFor(first, span$1.marker);
+ if (!found$1) {
+ span$1.from = offset;
+ if (sameLine) { (first || (first = [])).push(span$1); }
+ }
+ } else {
+ span$1.from += offset;
+ if (sameLine) { (first || (first = [])).push(span$1); }
+ }
+ }
+ }
+ // Make sure we didn't create any zero-length spans
+ if (first) { first = clearEmptySpans(first); }
+ if (last && last != first) { last = clearEmptySpans(last); }
+
+ 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$2 = 0; i$2 < first.length; ++i$2)
+ { if (first[i$2].to == null)
+ { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); } } }
+ for (var i$3 = 0; i$3 < gap; ++i$3)
+ { newMarkers.push(gapMarkers); }
+ newMarkers.push(last);
+ }
+ return newMarkers
+ }
+
+ // Remove spans that are empty and don't have a clearWhenEmpty
+ // option of false.
+ function clearEmptySpans(spans) {
+ for (var i = 0; i < spans.length; ++i) {
+ var span = spans[i];
+ if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)
+ { spans.splice(i--, 1); }
+ }
+ if (!spans.length) { return null }
+ return spans
+ }
+
+ // Used to 'clip' out readOnly ranges when making a change.
+ 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(0);
+ for (var j = 0; j < parts.length; ++j) {
+ var p = parts[j];
+ if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue }
+ var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to);
+ if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)
+ { newParts.push({from: p.from, to: m.from}); }
+ if (dto > 0 || !mk.inclusiveRight && !dto)
+ { newParts.push({from: m.to, to: p.to}); }
+ parts.splice.apply(parts, newParts);
+ j += newParts.length - 3;
+ }
+ }
+ return parts
+ }
+
+ // Connect or disconnect spans from a line.
+ 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;
+ }
+
+ // Helpers used when computing which overlapping collapsed span
+ // counts as the larger one.
+ function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 }
+ function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 }
+
+ // Returns a number indicating which of two overlapping collapsed
+ // spans is larger (and thus includes the other). Falls back to
+ // comparing ids when the spans cover exactly the same range.
+ function compareCollapsedMarkers(a, b) {
+ var lenDiff = a.lines.length - b.lines.length;
+ if (lenDiff != 0) { return lenDiff }
+ var aPos = a.find(), bPos = b.find();
+ var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);
+ if (fromCmp) { return -fromCmp }
+ var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);
+ if (toCmp) { return toCmp }
+ return b.id - a.id
+ }
+
+ // Find out whether a line ends or starts in a collapsed span. If
+ // so, return the marker for that span.
+ function collapsedSpanAtSide(line, start) {
+ var sps = sawCollapsedSpans && line.markedSpans, found;
+ if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {
+ sp = sps[i];
+ if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&
+ (!found || compareCollapsedMarkers(found, sp.marker) < 0))
+ { found = sp.marker; }
+ } }
+ return found
+ }
+ function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) }
+ function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) }
+
+ function collapsedSpanAround(line, ch) {
+ var sps = sawCollapsedSpans && line.markedSpans, found;
+ if (sps) { for (var i = 0; i < sps.length; ++i) {
+ var sp = sps[i];
+ if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) &&
+ (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; }
+ } }
+ return found
+ }
+
+ // Test whether there exists a collapsed span that partially
+ // overlaps (covers the start or end, but not both) of a new span.
+ // Such overlap is not allowed.
+ function conflictingCollapsedRange(doc, lineNo$$1, from, to, marker) {
+ var line = getLine(doc, lineNo$$1);
+ var sps = sawCollapsedSpans && line.markedSpans;
+ if (sps) { for (var i = 0; i < sps.length; ++i) {
+ var sp = sps[i];
+ if (!sp.marker.collapsed) { continue }
+ var found = sp.marker.find(0);
+ var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);
+ var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);
+ if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue }
+ if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) ||
+ fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0))
+ { return true }
+ } }
+ }
+
+ // A visual line is a line as drawn on the screen. Folding, for
+ // example, can cause multiple logical lines to appear on the same
+ // visual line. This finds the start of the visual line that the
+ // given line is part of (usually that is the line itself).
+ function visualLine(line) {
+ var merged;
+ while (merged = collapsedSpanAtStart(line))
+ { line = merged.find(-1, true).line; }
+ return line
+ }
+
+ function visualLineEnd(line) {
+ var merged;
+ while (merged = collapsedSpanAtEnd(line))
+ { line = merged.find(1, true).line; }
+ return line
+ }
+
+ // Returns an array of logical lines that continue the visual line
+ // started by the argument, or undefined if there are no such lines.
+ function visualLineContinued(line) {
+ var merged, lines;
+ while (merged = collapsedSpanAtEnd(line)) {
+ line = merged.find(1, true).line
+ ;(lines || (lines = [])).push(line);
+ }
+ return lines
+ }
+
+ // Get the line number of the start of the visual line that the
+ // given line number is part of.
+ function visualLineNo(doc, lineN) {
+ var line = getLine(doc, lineN), vis = visualLine(line);
+ if (line == vis) { return lineN }
+ return lineNo(vis)
+ }
+
+ // Get the line number of the start of the next visual line after
+ // the given line.
+ function visualLineEndNo(doc, lineN) {
+ if (lineN > doc.lastLine()) { return lineN }
+ var line = getLine(doc, lineN), merged;
+ if (!lineIsHidden(doc, line)) { return lineN }
+ while (merged = collapsedSpanAtEnd(line))
+ { line = merged.find(1, true).line; }
+ return lineNo(line) + 1
+ }
+
+ // Compute whether a line is hidden. Lines count as hidden when they
+ // are part of a visual line that starts with another line, or when
+ // they are entirely covered by collapsed, non-widget span.
+ function lineIsHidden(doc, line) {
+ var sps = sawCollapsedSpans && line.markedSpans;
+ if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {
+ sp = sps[i];
+ if (!sp.marker.collapsed) { continue }
+ if (sp.from == null) { return true }
+ if (sp.marker.widgetNode) { 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(1, true);
+ return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker))
+ }
+ if (span.marker.inclusiveRight && span.to == line.text.length)
+ { return true }
+ for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) {
+ sp = line.markedSpans[i];
+ if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&
+ (sp.to == null || sp.to != span.from) &&
+ (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
+ lineIsHiddenInner(doc, line, sp)) { return true }
+ }
+ }
+
+ // Find the height above the given line.
+ function heightAtLine(lineObj) {
+ lineObj = visualLine(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$1 = 0; i$1 < p.children.length; ++i$1) {
+ var cur = p.children[i$1];
+ if (cur == chunk) { break }
+ else { h += cur.height; }
+ }
+ }
+ return h
+ }
+
+ // Compute the character length of a line, taking into account
+ // collapsed ranges (see markText) that might hide parts, and join
+ // other lines onto it.
+ function lineLength(line) {
+ if (line.height == 0) { return 0 }
+ var len = line.text.length, merged, cur = line;
+ while (merged = collapsedSpanAtStart(cur)) {
+ var found = merged.find(0, true);
+ cur = found.from.line;
+ len += found.from.ch - found.to.ch;
+ }
+ cur = line;
+ while (merged = collapsedSpanAtEnd(cur)) {
+ var found$1 = merged.find(0, true);
+ len -= cur.text.length - found$1.from.ch;
+ cur = found$1.to.line;
+ len += cur.text.length - found$1.to.ch;
+ }
+ return len
+ }
+
+ // Find the longest line in the document.
+ function findMaxLine(cm) {
+ var d = cm.display, doc = cm.doc;
+ d.maxLine = getLine(doc, doc.first);
+ d.maxLineLength = lineLength(d.maxLine);
+ d.maxLineChanged = true;
+ doc.iter(function (line) {
+ var len = lineLength(line);
+ if (len > d.maxLineLength) {
+ d.maxLineLength = len;
+ d.maxLine = line;
+ }
+ });
+ }
+
+ // LINE DATA STRUCTURE
+
+ // Line objects. These hold state related to a line, including
+ // highlighting info (the styles array).
+ var Line = function(text, markedSpans, estimateHeight) {
+ this.text = text;
+ attachMarkedSpans(this, markedSpans);
+ this.height = estimateHeight ? estimateHeight(this) : 1;
+ };
+
+ Line.prototype.lineNo = function () { return lineNo(this) };
+ eventMixin(Line);
+
+ // Change the content (text, markers) of a line. Automatically
+ // invalidates cached information and tries to re-estimate the
+ // line's height.
+ 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); }
+ }
+
+ // Detach a line from the document tree and its markers.
+ function cleanUpLine(line) {
+ line.parent = null;
+ detachMarkedSpans(line);
+ }
+
+ // Convert a style as returned by a mode (either null, or a string
+ // containing one or more styles) to a CSS style. This is cached,
+ // and also looks for line-wide styles.
+ var styleToClassCache = {}, styleToClassCacheWithMode = {};
+ function interpretTokenStyle(style, options) {
+ if (!style || /^\s*$/.test(style)) { return null }
+ var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;
+ return cache[style] ||
+ (cache[style] = style.replace(/\S+/g, "cm-$&"))
+ }
+
+ // Render the DOM representation of the text of a line. Also builds
+ // up a 'line map', which points at the DOM nodes that represent
+ // specific stretches of text, and is used by the measuring code.
+ // The returned object contains the DOM node, this map, and
+ // information about line-wide styles that were set by the mode.
+ function buildLineContent(cm, lineView) {
+ // The padding-right forces the element to have a 'border', which
+ // is needed on Webkit to be able to get line-level bounding
+ // rectangles for it (in measureChar).
+ var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null);
+ var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content,
+ col: 0, pos: 0, cm: cm,
+ trailingSpace: false,
+ splitSpaces: cm.getOption("lineWrapping")};
+ lineView.measure = {};
+
+ // Iterate over the logical lines that make up this visual line.
+ for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {
+ var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0);
+ builder.pos = 0;
+ builder.addToken = buildToken;
+ // Optionally wire in some hacks into the token-rendering
+ // algorithm, to deal with browser quirks.
+ if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction)))
+ { builder.addToken = buildTokenBadBidi(builder.addToken, order); }
+ builder.map = [];
+ var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line);
+ insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate));
+ if (line.styleClasses) {
+ if (line.styleClasses.bgClass)
+ { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); }
+ if (line.styleClasses.textClass)
+ { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); }
+ }
+
+ // Ensure at least a single node is present, for measuring.
+ if (builder.map.length == 0)
+ { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); }
+
+ // Store the map and a cache object for the current logical line
+ if (i == 0) {
+ lineView.measure.map = builder.map;
+ lineView.measure.cache = {};
+ } else {
+ (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map)
+ ;(lineView.measure.caches || (lineView.measure.caches = [])).push({});
+ }
+ }
+
+ // See issue #2901
+ if (webkit) {
+ var last = builder.content.lastChild;
+ if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab")))
+ { builder.content.className = "cm-tab-wrap-hack"; }
+ }
+
+ signal(cm, "renderLine", cm, lineView.line, builder.pre);
+ if (builder.pre.className)
+ { builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); }
+
+ return builder
+ }
+
+ function defaultSpecialCharPlaceholder(ch) {
+ var token = elt("span", "\u2022", "cm-invalidchar");
+ token.title = "\\u" + ch.charCodeAt(0).toString(16);
+ token.setAttribute("aria-label", token.title);
+ return token
+ }
+
+ // Build up the DOM representation for a single token, and add it to
+ // the line map. Takes care to render special characters separately.
+ function buildToken(builder, text, style, startStyle, endStyle, css, attributes) {
+ if (!text) { return }
+ var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text;
+ var special = builder.cm.state.specialChars, mustWrap = false;
+ var content;
+ if (!special.test(text)) {
+ builder.col += text.length;
+ content = document.createTextNode(displayText);
+ builder.map.push(builder.pos, builder.pos + text.length, content);
+ if (ie && ie_version < 9) { mustWrap = true; }
+ builder.pos += text.length;
+ } else {
+ content = document.createDocumentFragment();
+ var pos = 0;
+ while (true) {
+ special.lastIndex = pos;
+ var m = special.exec(text);
+ var skipped = m ? m.index - pos : text.length - pos;
+ if (skipped) {
+ var txt = document.createTextNode(displayText.slice(pos, pos + skipped));
+ if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])); }
+ else { content.appendChild(txt); }
+ builder.map.push(builder.pos, builder.pos + skipped, txt);
+ builder.col += skipped;
+ builder.pos += skipped;
+ }
+ if (!m) { break }
+ pos += skipped + 1;
+ var txt$1 = (void 0);
+ if (m[0] == "\t") {
+ var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
+ txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
+ txt$1.setAttribute("role", "presentation");
+ txt$1.setAttribute("cm-text", "\t");
+ builder.col += tabWidth;
+ } else if (m[0] == "\r" || m[0] == "\n") {
+ txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar"));
+ txt$1.setAttribute("cm-text", m[0]);
+ builder.col += 1;
+ } else {
+ txt$1 = builder.cm.options.specialCharPlaceholder(m[0]);
+ txt$1.setAttribute("cm-text", m[0]);
+ if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])); }
+ else { content.appendChild(txt$1); }
+ builder.col += 1;
+ }
+ builder.map.push(builder.pos, builder.pos + 1, txt$1);
+ builder.pos++;
+ }
+ }
+ builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32;
+ if (style || startStyle || endStyle || mustWrap || css) {
+ var fullStyle = style || "";
+ if (startStyle) { fullStyle += startStyle; }
+ if (endStyle) { fullStyle += endStyle; }
+ var token = elt("span", [content], fullStyle, css);
+ if (attributes) {
+ for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class")
+ { token.setAttribute(attr, attributes[attr]); } }
+ }
+ return builder.content.appendChild(token)
+ }
+ builder.content.appendChild(content);
+ }
+
+ // Change some spaces to NBSP to prevent the browser from collapsing
+ // trailing spaces at the end of a line when rendering text (issue #1362).
+ function splitSpaces(text, trailingBefore) {
+ if (text.length > 1 && !/ /.test(text)) { return text }
+ var spaceBefore = trailingBefore, result = "";
+ for (var i = 0; i < text.length; i++) {
+ var ch = text.charAt(i);
+ if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32))
+ { ch = "\u00a0"; }
+ result += ch;
+ spaceBefore = ch == " ";
+ }
+ return result
+ }
+
+ // Work around nonsense dimensions being reported for stretches of
+ // right-to-left text.
+ function buildTokenBadBidi(inner, order) {
+ return function (builder, text, style, startStyle, endStyle, css, attributes) {
+ style = style ? style + " cm-force-border" : "cm-force-border";
+ var start = builder.pos, end = start + text.length;
+ for (;;) {
+ // Find the part that overlaps with the start of this text
+ var part = (void 0);
+ for (var i = 0; i < order.length; i++) {
+ part = order[i];
+ if (part.to > start && part.from <= start) { break }
+ }
+ if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, css, attributes) }
+ inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes);
+ startStyle = null;
+ text = text.slice(part.to - start);
+ start = part.to;
+ }
+ }
+ }
+
+ function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
+ var widget = !ignoreWidget && marker.widgetNode;
+ if (widget) { builder.map.push(builder.pos, builder.pos + size, widget); }
+ if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {
+ if (!widget)
+ { widget = builder.content.appendChild(document.createElement("span")); }
+ widget.setAttribute("cm-marker", marker.id);
+ }
+ if (widget) {
+ builder.cm.display.input.setUneditable(widget);
+ builder.content.appendChild(widget);
+ }
+ builder.pos += size;
+ builder.trailingSpace = false;
+ }
+
+ // 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 = 1; i$1 < styles.length; i$1+=2)
+ { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)); }
+ return
+ }
+
+ var len = allText.length, pos = 0, i = 1, text = "", style, css;
+ var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes;
+ for (;;) {
+ if (nextChange == pos) { // Update current marker set
+ spanStyle = spanEndStyle = spanStartStyle = css = "";
+ attributes = null;
+ collapsed = null; nextChange = Infinity;
+ var foundBookmarks = [], endStyles = (void 0);
+ for (var j = 0; j < spans.length; ++j) {
+ var sp = spans[j], m = sp.marker;
+ if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
+ foundBookmarks.push(m);
+ } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) {
+ if (sp.to != null && sp.to != pos && nextChange > sp.to) {
+ nextChange = sp.to;
+ spanEndStyle = "";
+ }
+ if (m.className) { spanStyle += " " + m.className; }
+ if (m.css) { css = (css ? css + ";" : "") + m.css; }
+ if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle; }
+ if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to); }
+ // support for the old title property
+ // https://github.com/codemirror/CodeMirror/pull/5673
+ if (m.title) { (attributes || (attributes = {})).title = m.title; }
+ if (m.attributes) {
+ for (var attr in m.attributes)
+ { (attributes || (attributes = {}))[attr] = m.attributes[attr]; }
+ }
+ if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
+ { collapsed = sp; }
+ } else if (sp.from > pos && nextChange > sp.from) {
+ nextChange = sp.from;
+ }
+ }
+ if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2)
+ { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1]; } } }
+
+ if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2)
+ { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); } }
+ if (collapsed && (collapsed.from || 0) == pos) {
+ buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
+ collapsed.marker, collapsed.from == null);
+ if (collapsed.to == null) { return }
+ if (collapsed.to == pos) { collapsed = false; }
+ }
+ }
+ 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 : "", css, attributes);
+ }
+ if (end >= upto) {text = text.slice(upto - pos); pos = upto; break}
+ pos = end;
+ spanStartStyle = "";
+ }
+ text = allText.slice(at, at = styles[i++]);
+ style = interpretTokenStyle(styles[i++], builder.cm.options);
+ }
+ }
+ }
+
+
+ // These objects are used to represent the visible (currently drawn)
+ // part of the document. A LineView may correspond to multiple
+ // logical lines, if those are connected by collapsed ranges.
+ function LineView(doc, line, lineN) {
+ // The starting line
+ this.line = line;
+ // Continuing lines, if any
+ this.rest = visualLineContinued(line);
+ // Number of logical lines in this visual line
+ this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;
+ this.node = this.text = null;
+ this.hidden = lineIsHidden(doc, line);
+ }
+
+ // Create a range of LineView objects for the given lines.
+ function buildViewArray(cm, from, to) {
+ var array = [], nextPos;
+ for (var pos = from; pos < to; pos = nextPos) {
+ var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);
+ nextPos = pos + view.size;
+ array.push(view);
+ }
+ return array
+ }
+
+ var operationGroup = null;
+
+ function pushOperation(op) {
+ if (operationGroup) {
+ operationGroup.ops.push(op);
+ } else {
+ op.ownsGroup = operationGroup = {
+ ops: [op],
+ delayedCallbacks: []
+ };
+ }
+ }
+
+ function fireCallbacksForOps(group) {
+ // Calls delayed callbacks and cursorActivity handlers until no
+ // new ones appear
+ var callbacks = group.delayedCallbacks, i = 0;
+ do {
+ for (; i < callbacks.length; i++)
+ { callbacks[i].call(null); }
+ for (var j = 0; j < group.ops.length; j++) {
+ var op = group.ops[j];
+ if (op.cursorActivityHandlers)
+ { while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
+ { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); } }
+ }
+ } while (i < callbacks.length)
+ }
+
+ function finishOperation(op, endCb) {
+ var group = op.ownsGroup;
+ if (!group) { return }
+
+ try { fireCallbacksForOps(group); }
+ finally {
+ operationGroup = null;
+ endCb(group);
+ }
+ }
+
+ var orphanDelayedCallbacks = null;
+
+ // Often, we want to signal events at a point where we are in the
+ // middle of some work, but don't want the handler to start calling
+ // other methods on the editor, which might be in an inconsistent
+ // state or simply not expect any other events to happen.
+ // signalLater looks whether there are any handlers, and schedules
+ // them to be executed when the last operation ends, or, if no
+ // operation is active, when a timeout fires.
+ function signalLater(emitter, type /*, values...*/) {
+ var arr = getHandlers(emitter, type);
+ if (!arr.length) { return }
+ var args = Array.prototype.slice.call(arguments, 2), list;
+ if (operationGroup) {
+ list = operationGroup.delayedCallbacks;
+ } else if (orphanDelayedCallbacks) {
+ list = orphanDelayedCallbacks;
+ } else {
+ list = orphanDelayedCallbacks = [];
+ setTimeout(fireOrphanDelayed, 0);
+ }
+ var loop = function ( i ) {
+ list.push(function () { return arr[i].apply(null, args); });
+ };
+
+ for (var i = 0; i < arr.length; ++i)
+ loop( i );
+ }
+
+ function fireOrphanDelayed() {
+ var delayed = orphanDelayedCallbacks;
+ orphanDelayedCallbacks = null;
+ for (var i = 0; i < delayed.length; ++i) { delayed[i](); }
+ }
+
+ // When an aspect of a line changes, a string is added to
+ // lineView.changes. This updates the relevant part of the line's
+ // DOM structure.
+ function updateLineForChanges(cm, lineView, lineN, dims) {
+ for (var j = 0; j < lineView.changes.length; j++) {
+ var type = lineView.changes[j];
+ if (type == "text") { updateLineText(cm, lineView); }
+ else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims); }
+ else if (type == "class") { updateLineClasses(cm, lineView); }
+ else if (type == "widget") { updateLineWidgets(cm, lineView, dims); }
+ }
+ lineView.changes = null;
+ }
+
+ // Lines with gutter elements, widgets or a background class need to
+ // be wrapped, and have the extra elements added to the wrapper div
+ function ensureLineWrapped(lineView) {
+ if (lineView.node == lineView.text) {
+ lineView.node = elt("div", null, null, "position: relative");
+ if (lineView.text.parentNode)
+ { lineView.text.parentNode.replaceChild(lineView.node, lineView.text); }
+ lineView.node.appendChild(lineView.text);
+ if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; }
+ }
+ return lineView.node
+ }
+
+ function updateLineBackground(cm, lineView) {
+ var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass;
+ if (cls) { cls += " CodeMirror-linebackground"; }
+ if (lineView.background) {
+ if (cls) { lineView.background.className = cls; }
+ else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }
+ } else if (cls) {
+ var wrap = ensureLineWrapped(lineView);
+ lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild);
+ cm.display.input.setUneditable(lineView.background);
+ }
+ }
+
+ // Wrapper around buildLineContent which will reuse the structure
+ // in display.externalMeasured when possible.
+ function getLineContent(cm, lineView) {
+ var ext = cm.display.externalMeasured;
+ if (ext && ext.line == lineView.line) {
+ cm.display.externalMeasured = null;
+ lineView.measure = ext.measure;
+ return ext.built
+ }
+ return buildLineContent(cm, lineView)
+ }
+
+ // Redraw the line's text. Interacts with the background and text
+ // classes because the mode may output tokens that influence these
+ // classes.
+ function updateLineText(cm, lineView) {
+ var cls = lineView.text.className;
+ var built = getLineContent(cm, lineView);
+ if (lineView.text == lineView.node) { lineView.node = built.pre; }
+ lineView.text.parentNode.replaceChild(built.pre, lineView.text);
+ lineView.text = built.pre;
+ if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {
+ lineView.bgClass = built.bgClass;
+ lineView.textClass = built.textClass;
+ updateLineClasses(cm, lineView);
+ } else if (cls) {
+ lineView.text.className = cls;
+ }
+ }
+
+ function updateLineClasses(cm, lineView) {
+ updateLineBackground(cm, lineView);
+ if (lineView.line.wrapClass)
+ { ensureLineWrapped(lineView).className = lineView.line.wrapClass; }
+ else if (lineView.node != lineView.text)
+ { lineView.node.className = ""; }
+ var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass;
+ lineView.text.className = textClass || "";
+ }
+
+ function updateLineGutter(cm, lineView, lineN, dims) {
+ if (lineView.gutter) {
+ lineView.node.removeChild(lineView.gutter);
+ lineView.gutter = null;
+ }
+ if (lineView.gutterBackground) {
+ lineView.node.removeChild(lineView.gutterBackground);
+ lineView.gutterBackground = null;
+ }
+ if (lineView.line.gutterClass) {
+ var wrap = ensureLineWrapped(lineView);
+ lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
+ ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px"));
+ cm.display.input.setUneditable(lineView.gutterBackground);
+ wrap.insertBefore(lineView.gutterBackground, lineView.text);
+ }
+ var markers = lineView.line.gutterMarkers;
+ if (cm.options.lineNumbers || markers) {
+ var wrap$1 = ensureLineWrapped(lineView);
+ var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"));
+ cm.display.input.setUneditable(gutterWrap);
+ wrap$1.insertBefore(gutterWrap, lineView.text);
+ if (lineView.line.gutterClass)
+ { gutterWrap.className += " " + lineView.line.gutterClass; }
+ if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
+ { lineView.lineNumber = gutterWrap.appendChild(
+ elt("div", lineNumberFor(cm.options, lineN),
+ "CodeMirror-linenumber CodeMirror-gutter-elt",
+ ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))); }
+ if (markers) { for (var k = 0; k < cm.display.gutterSpecs.length; ++k) {
+ var id = cm.display.gutterSpecs[k].className, 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"))); }
+ } }
+ }
+ }
+
+ function updateLineWidgets(cm, lineView, dims) {
+ if (lineView.alignable) { lineView.alignable = null; }
+ for (var node = lineView.node.firstChild, next = (void 0); node; node = next) {
+ next = node.nextSibling;
+ if (node.className == "CodeMirror-linewidget")
+ { lineView.node.removeChild(node); }
+ }
+ insertLineWidgets(cm, lineView, dims);
+ }
+
+ // Build a line's DOM representation from scratch
+ function buildLineElement(cm, lineView, lineN, dims) {
+ var built = getLineContent(cm, lineView);
+ lineView.text = lineView.node = built.pre;
+ if (built.bgClass) { lineView.bgClass = built.bgClass; }
+ if (built.textClass) { lineView.textClass = built.textClass; }
+
+ updateLineClasses(cm, lineView);
+ updateLineGutter(cm, lineView, lineN, dims);
+ insertLineWidgets(cm, lineView, dims);
+ return lineView.node
+ }
+
+ // A lineView may contain multiple logical lines (when merged by
+ // collapsed spans). The widgets for all of them need to be drawn.
+ function insertLineWidgets(cm, lineView, dims) {
+ insertLineWidgetsFor(cm, lineView.line, lineView, dims, true);
+ if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)
+ { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); } }
+ }
+
+ function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {
+ if (!line.widgets) { return }
+ var wrap = ensureLineWrapped(lineView);
+ 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.setAttribute("cm-ignore-events", "true"); }
+ positionLineWidget(widget, node, lineView, dims);
+ cm.display.input.setUneditable(node);
+ if (allowAbove && widget.above)
+ { wrap.insertBefore(node, lineView.gutter || lineView.text); }
+ else
+ { wrap.appendChild(node); }
+ signalLater(widget, "redraw");
+ }
+ }
+
+ function positionLineWidget(widget, node, lineView, dims) {
+ if (widget.noHScroll) {
+ (lineView.alignable || (lineView.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"; }
+ }
+ }
+
+ function widgetHeight(widget) {
+ if (widget.height != null) { return widget.height }
+ var cm = widget.doc.cm;
+ if (!cm) { return 0 }
+ if (!contains(document.body, widget.node)) {
+ var parentStyle = "position: relative;";
+ if (widget.coverGutter)
+ { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; }
+ if (widget.noHScroll)
+ { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; }
+ removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle));
+ }
+ return widget.height = widget.node.parentNode.offsetHeight
+ }
+
+ // Return true when the given mouse event happened in a widget
+ function eventInWidget(display, e) {
+ for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
+ if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") ||
+ (n.parentNode == display.sizer && n != display.mover))
+ { return true }
+ }
+ }
+
+ // POSITION MEASUREMENT
+
+ function paddingTop(display) {return display.lineSpace.offsetTop}
+ function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight}
+ function paddingH(display) {
+ if (display.cachedPaddingH) { return display.cachedPaddingH }
+ var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
+ var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
+ var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};
+ if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data; }
+ return data
+ }
+
+ function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth }
+ function displayWidth(cm) {
+ return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth
+ }
+ function displayHeight(cm) {
+ return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight
+ }
+
+ // Ensure the lineView.wrapping.heights array is populated. This is
+ // an array of bottom offsets for the lines that make up a drawn
+ // line. When lineWrapping is on, there might be more than one
+ // height.
+ function ensureLineHeights(cm, lineView, rect) {
+ var wrapping = cm.options.lineWrapping;
+ var curWidth = wrapping && displayWidth(cm);
+ if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
+ var heights = lineView.measure.heights = [];
+ if (wrapping) {
+ lineView.measure.width = curWidth;
+ var rects = lineView.text.firstChild.getClientRects();
+ for (var i = 0; i < rects.length - 1; i++) {
+ var cur = rects[i], next = rects[i + 1];
+ if (Math.abs(cur.bottom - next.bottom) > 2)
+ { heights.push((cur.bottom + next.top) / 2 - rect.top); }
+ }
+ }
+ heights.push(rect.bottom - rect.top);
+ }
+ }
+
+ // Find a line map (mapping character offsets to text nodes) and a
+ // measurement cache for the given line number. (A line view might
+ // contain multiple lines when collapsed ranges are present.)
+ function mapFromLineView(lineView, line, lineN) {
+ if (lineView.line == line)
+ { return {map: lineView.measure.map, cache: lineView.measure.cache} }
+ for (var i = 0; i < lineView.rest.length; i++)
+ { if (lineView.rest[i] == line)
+ { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } }
+ for (var i$1 = 0; i$1 < lineView.rest.length; i$1++)
+ { if (lineNo(lineView.rest[i$1]) > lineN)
+ { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } }
+ }
+
+ // Render a line into the hidden node display.externalMeasured. Used
+ // when measurement is needed for a line that's not in the viewport.
+ function updateExternalMeasurement(cm, line) {
+ line = visualLine(line);
+ var lineN = lineNo(line);
+ var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);
+ view.lineN = lineN;
+ var built = view.built = buildLineContent(cm, view);
+ view.text = built.pre;
+ removeChildrenAndAdd(cm.display.lineMeasure, built.pre);
+ return view
+ }
+
+ // Get a {top, bottom, left, right} box (in line-local coordinates)
+ // for a given character.
+ function measureChar(cm, line, ch, bias) {
+ return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias)
+ }
+
+ // Find a line view that corresponds to the given line number.
+ function findViewForLine(cm, lineN) {
+ if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
+ { return cm.display.view[findViewIndex(cm, lineN)] }
+ var ext = cm.display.externalMeasured;
+ if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
+ { return ext }
+ }
+
+ // Measurement can be split in two steps, the set-up work that
+ // applies to the whole line, and the measurement of the actual
+ // character. Functions like coordsChar, that need to do a lot of
+ // measurements in a row, can thus ensure that the set-up work is
+ // only done once.
+ function prepareMeasureForLine(cm, line) {
+ var lineN = lineNo(line);
+ var view = findViewForLine(cm, lineN);
+ if (view && !view.text) {
+ view = null;
+ } else if (view && view.changes) {
+ updateLineForChanges(cm, view, lineN, getDimensions(cm));
+ cm.curOp.forceUpdate = true;
+ }
+ if (!view)
+ { view = updateExternalMeasurement(cm, line); }
+
+ var info = mapFromLineView(view, line, lineN);
+ return {
+ line: line, view: view, rect: null,
+ map: info.map, cache: info.cache, before: info.before,
+ hasHeights: false
+ }
+ }
+
+ // Given a prepared measurement object, measures the position of an
+ // actual character (or fetches it from the cache).
+ function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
+ if (prepared.before) { ch = -1; }
+ var key = ch + (bias || ""), found;
+ if (prepared.cache.hasOwnProperty(key)) {
+ found = prepared.cache[key];
+ } else {
+ if (!prepared.rect)
+ { prepared.rect = prepared.view.text.getBoundingClientRect(); }
+ if (!prepared.hasHeights) {
+ ensureLineHeights(cm, prepared.view, prepared.rect);
+ prepared.hasHeights = true;
+ }
+ found = measureCharInner(cm, prepared, ch, bias);
+ if (!found.bogus) { prepared.cache[key] = found; }
+ }
+ return {left: found.left, right: found.right,
+ top: varHeight ? found.rtop : found.top,
+ bottom: varHeight ? found.rbottom : found.bottom}
+ }
+
+ var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
+
+ function nodeAndOffsetInLineMap(map$$1, ch, bias) {
+ var node, start, end, collapse, mStart, mEnd;
+ // First, search the line map for the text node corresponding to,
+ // or closest to, the target character.
+ for (var i = 0; i < map$$1.length; i += 3) {
+ mStart = map$$1[i];
+ mEnd = map$$1[i + 1];
+ if (ch < mStart) {
+ start = 0; end = 1;
+ collapse = "left";
+ } else if (ch < mEnd) {
+ start = ch - mStart;
+ end = start + 1;
+ } else if (i == map$$1.length - 3 || ch == mEnd && map$$1[i + 3] > ch) {
+ end = mEnd - mStart;
+ start = end - 1;
+ if (ch >= mEnd) { collapse = "right"; }
+ }
+ if (start != null) {
+ node = map$$1[i + 2];
+ if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
+ { collapse = bias; }
+ if (bias == "left" && start == 0)
+ { while (i && map$$1[i - 2] == map$$1[i - 3] && map$$1[i - 1].insertLeft) {
+ node = map$$1[(i -= 3) + 2];
+ collapse = "left";
+ } }
+ if (bias == "right" && start == mEnd - mStart)
+ { while (i < map$$1.length - 3 && map$$1[i + 3] == map$$1[i + 4] && !map$$1[i + 5].insertLeft) {
+ node = map$$1[(i += 3) + 2];
+ collapse = "right";
+ } }
+ break
+ }
+ }
+ return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}
+ }
+
+ function getUsefulRect(rects, bias) {
+ var rect = nullRect;
+ if (bias == "left") { for (var i = 0; i < rects.length; i++) {
+ if ((rect = rects[i]).left != rect.right) { break }
+ } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) {
+ if ((rect = rects[i$1]).left != rect.right) { break }
+ } }
+ return rect
+ }
+
+ function measureCharInner(cm, prepared, ch, bias) {
+ var place = nodeAndOffsetInLineMap(prepared.map, ch, bias);
+ var node = place.node, start = place.start, end = place.end, collapse = place.collapse;
+
+ var rect;
+ if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
+ for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned
+ while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start; }
+ while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end; }
+ if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart)
+ { rect = node.parentNode.getBoundingClientRect(); }
+ else
+ { rect = getUsefulRect(range(node, start, end).getClientRects(), bias); }
+ if (rect.left || rect.right || start == 0) { break }
+ end = start;
+ start = start - 1;
+ collapse = "right";
+ }
+ if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect); }
+ } else { // If it is a widget, simply get the box for the whole widget.
+ if (start > 0) { collapse = bias = "right"; }
+ var rects;
+ if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
+ { rect = rects[bias == "right" ? rects.length - 1 : 0]; }
+ else
+ { rect = node.getBoundingClientRect(); }
+ }
+ if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
+ var rSpan = node.parentNode.getClientRects()[0];
+ if (rSpan)
+ { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; }
+ else
+ { rect = nullRect; }
+ }
+
+ var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top;
+ var mid = (rtop + rbot) / 2;
+ var heights = prepared.view.measure.heights;
+ var i = 0;
+ for (; i < heights.length - 1; i++)
+ { if (mid < heights[i]) { break } }
+ var top = i ? heights[i - 1] : 0, bot = heights[i];
+ var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
+ right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
+ top: top, bottom: bot};
+ if (!rect.left && !rect.right) { result.bogus = true; }
+ if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }
+
+ return result
+ }
+
+ // Work around problem with bounding client rects on ranges being
+ // returned incorrectly when zoomed on IE10 and below.
+ function maybeUpdateRectForZooming(measure, rect) {
+ if (!window.screen || screen.logicalXDPI == null ||
+ screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
+ { return rect }
+ var scaleX = screen.logicalXDPI / screen.deviceXDPI;
+ var scaleY = screen.logicalYDPI / screen.deviceYDPI;
+ return {left: rect.left * scaleX, right: rect.right * scaleX,
+ top: rect.top * scaleY, bottom: rect.bottom * scaleY}
+ }
+
+ function clearLineMeasurementCacheFor(lineView) {
+ if (lineView.measure) {
+ lineView.measure.cache = {};
+ lineView.measure.heights = null;
+ if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)
+ { lineView.measure.caches[i] = {}; } }
+ }
+ }
+
+ function clearLineMeasurementCache(cm) {
+ cm.display.externalMeasure = null;
+ removeChildren(cm.display.lineMeasure);
+ for (var i = 0; i < cm.display.view.length; i++)
+ { clearLineMeasurementCacheFor(cm.display.view[i]); }
+ }
+
+ function clearCaches(cm) {
+ clearLineMeasurementCache(cm);
+ cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;
+ if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; }
+ cm.display.lineNumChars = null;
+ }
+
+ function pageScrollX() {
+ // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206
+ // which causes page_Offset and bounding client rects to use
+ // different reference viewports and invalidate our calculations.
+ if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) }
+ return window.pageXOffset || (document.documentElement || document.body).scrollLeft
+ }
+ function pageScrollY() {
+ if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) }
+ return window.pageYOffset || (document.documentElement || document.body).scrollTop
+ }
+
+ function widgetTopHeight(lineObj) {
+ var height = 0;
+ if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above)
+ { height += widgetHeight(lineObj.widgets[i]); } } }
+ return height
+ }
+
+ // Converts a {top, bottom, left, right} box from line-local
+ // coordinates into another coordinate system. Context may be one of
+ // "line", "div" (display.lineDiv), "local"./null (editor), "window",
+ // or "page".
+ function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) {
+ if (!includeWidgets) {
+ var height = widgetTopHeight(lineObj);
+ rect.top += height; rect.bottom += height;
+ }
+ if (context == "line") { return rect }
+ if (!context) { context = "local"; }
+ var yOff = heightAtLine(lineObj);
+ if (context == "local") { yOff += paddingTop(cm.display); }
+ else { yOff -= cm.display.viewOffset; }
+ if (context == "page" || context == "window") {
+ var lOff = cm.display.lineSpace.getBoundingClientRect();
+ 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
+ }
+
+ // Coverts a box from "div" coords to another coordinate system.
+ // Context may be "window", "page", "div", or "local"./null.
+ 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 = cm.display.sizer.getBoundingClientRect();
+ left += localBox.left;
+ top += localBox.top;
+ }
+
+ var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();
+ 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, bias), context)
+ }
+
+ // Returns a box for a given cursor position, which may have an
+ // 'other' property containing the position of the secondary cursor
+ // on a bidi boundary.
+ // A cursor Pos(line, char, "before") is on the same visual line as `char - 1`
+ // and after `char - 1` in writing order of `char - 1`
+ // A cursor Pos(line, char, "after") is on the same visual line as `char`
+ // and before `char` in writing order of `char`
+ // Examples (upper-case letters are RTL, lower-case are LTR):
+ // Pos(0, 1, ...)
+ // before after
+ // ab a|b a|b
+ // aB a|B aB|
+ // Ab |Ab A|b
+ // AB B|A B|A
+ // Every position after the last character on a line is considered to stick
+ // to the last character on the line.
+ function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
+ lineObj = lineObj || getLine(cm.doc, pos.line);
+ if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }
+ function get(ch, right) {
+ var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight);
+ if (right) { m.left = m.right; } else { m.right = m.left; }
+ return intoCoordSystem(cm, lineObj, m, context)
+ }
+ var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky;
+ if (ch >= lineObj.text.length) {
+ ch = lineObj.text.length;
+ sticky = "before";
+ } else if (ch <= 0) {
+ ch = 0;
+ sticky = "after";
+ }
+ if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") }
+
+ function getBidi(ch, partPos, invert) {
+ var part = order[partPos], right = part.level == 1;
+ return get(invert ? ch - 1 : ch, right != invert)
+ }
+ var partPos = getBidiPartAt(order, ch, sticky);
+ var other = bidiOther;
+ var val = getBidi(ch, partPos, sticky == "before");
+ if (other != null) { val.other = getBidi(ch, other, sticky != "before"); }
+ return val
+ }
+
+ // Used to cheaply estimate the coordinates for a position. Used for
+ // intermediate scroll updates.
+ function estimateCoords(cm, pos) {
+ var left = 0;
+ pos = clipPos(cm.doc, pos);
+ if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch; }
+ var lineObj = getLine(cm.doc, pos.line);
+ var top = heightAtLine(lineObj) + paddingTop(cm.display);
+ return {left: left, right: left, top: top, bottom: top + lineObj.height}
+ }
+
+ // Positions returned by coordsChar contain some extra information.
+ // xRel is the relative x position of the input coordinates compared
+ // to the found position (so xRel > 0 means the coordinates are to
+ // the right of the character position, for example). When outside
+ // is true, that means the coordinates lie outside the line's
+ // vertical range.
+ function PosWithInfo(line, ch, sticky, outside, xRel) {
+ var pos = Pos(line, ch, sticky);
+ pos.xRel = xRel;
+ if (outside) { pos.outside = true; }
+ return pos
+ }
+
+ // Compute the character position closest to the given coordinates.
+ // Input must be lineSpace-local ("div" coordinate system).
+ function coordsChar(cm, x, y) {
+ var doc = cm.doc;
+ y += cm.display.viewOffset;
+ if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) }
+ var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
+ if (lineN > last)
+ { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) }
+ if (x < 0) { x = 0; }
+
+ var lineObj = getLine(doc, lineN);
+ for (;;) {
+ var found = coordsCharInner(cm, lineObj, lineN, x, y);
+ var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 ? 1 : 0));
+ if (!collapsed) { return found }
+ var rangeEnd = collapsed.find(1);
+ if (rangeEnd.line == lineN) { return rangeEnd }
+ lineObj = getLine(doc, lineN = rangeEnd.line);
+ }
+ }
+
+ function wrappedLineExtent(cm, lineObj, preparedMeasure, y) {
+ y -= widgetTopHeight(lineObj);
+ var end = lineObj.text.length;
+ var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0);
+ end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end);
+ return {begin: begin, end: end}
+ }
+
+ function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) {
+ if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }
+ var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top;
+ return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop)
+ }
+
+ // Returns true if the given side of a box is after the given
+ // coordinates, in top-to-bottom, left-to-right order.
+ function boxIsAfter(box, x, y, left) {
+ return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x
+ }
+
+ function coordsCharInner(cm, lineObj, lineNo$$1, x, y) {
+ // Move y into line-local coordinate space
+ y -= heightAtLine(lineObj);
+ var preparedMeasure = prepareMeasureForLine(cm, lineObj);
+ // When directly calling `measureCharPrepared`, we have to adjust
+ // for the widgets at this line.
+ var widgetHeight$$1 = widgetTopHeight(lineObj);
+ var begin = 0, end = lineObj.text.length, ltr = true;
+
+ var order = getOrder(lineObj, cm.doc.direction);
+ // If the line isn't plain left-to-right text, first figure out
+ // which bidi section the coordinates fall into.
+ if (order) {
+ var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart)
+ (cm, lineObj, lineNo$$1, preparedMeasure, order, x, y);
+ ltr = part.level != 1;
+ // The awkward -1 offsets are needed because findFirst (called
+ // on these below) will treat its first bound as inclusive,
+ // second as exclusive, but we want to actually address the
+ // characters in the part's range
+ begin = ltr ? part.from : part.to - 1;
+ end = ltr ? part.to : part.from - 1;
+ }
+
+ // A binary search to find the first character whose bounding box
+ // starts after the coordinates. If we run across any whose box wrap
+ // the coordinates, store that.
+ var chAround = null, boxAround = null;
+ var ch = findFirst(function (ch) {
+ var box = measureCharPrepared(cm, preparedMeasure, ch);
+ box.top += widgetHeight$$1; box.bottom += widgetHeight$$1;
+ if (!boxIsAfter(box, x, y, false)) { return false }
+ if (box.top <= y && box.left <= x) {
+ chAround = ch;
+ boxAround = box;
+ }
+ return true
+ }, begin, end);
+
+ var baseX, sticky, outside = false;
+ // If a box around the coordinates was found, use that
+ if (boxAround) {
+ // Distinguish coordinates nearer to the left or right side of the box
+ var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr;
+ ch = chAround + (atStart ? 0 : 1);
+ sticky = atStart ? "after" : "before";
+ baseX = atLeft ? boxAround.left : boxAround.right;
+ } else {
+ // (Adjust for extended bound, if necessary.)
+ if (!ltr && (ch == end || ch == begin)) { ch++; }
+ // To determine which side to associate with, get the box to the
+ // left of the character and compare it's vertical position to the
+ // coordinates
+ sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" :
+ (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight$$1 <= y) == ltr ?
+ "after" : "before";
+ // Now get accurate coordinates for this place, in order to get a
+ // base X position
+ var coords = cursorCoords(cm, Pos(lineNo$$1, ch, sticky), "line", lineObj, preparedMeasure);
+ baseX = coords.left;
+ outside = y < coords.top || y >= coords.bottom;
+ }
+
+ ch = skipExtendingChars(lineObj.text, ch, 1);
+ return PosWithInfo(lineNo$$1, ch, sticky, outside, x - baseX)
+ }
+
+ function coordsBidiPart(cm, lineObj, lineNo$$1, preparedMeasure, order, x, y) {
+ // Bidi parts are sorted left-to-right, and in a non-line-wrapping
+ // situation, we can take this ordering to correspond to the visual
+ // ordering. This finds the first part whose end is after the given
+ // coordinates.
+ var index = findFirst(function (i) {
+ var part = order[i], ltr = part.level != 1;
+ return boxIsAfter(cursorCoords(cm, Pos(lineNo$$1, ltr ? part.to : part.from, ltr ? "before" : "after"),
+ "line", lineObj, preparedMeasure), x, y, true)
+ }, 0, order.length - 1);
+ var part = order[index];
+ // If this isn't the first part, the part's start is also after
+ // the coordinates, and the coordinates aren't on the same line as
+ // that start, move one part back.
+ if (index > 0) {
+ var ltr = part.level != 1;
+ var start = cursorCoords(cm, Pos(lineNo$$1, ltr ? part.from : part.to, ltr ? "after" : "before"),
+ "line", lineObj, preparedMeasure);
+ if (boxIsAfter(start, x, y, true) && start.top > y)
+ { part = order[index - 1]; }
+ }
+ return part
+ }
+
+ function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) {
+ // In a wrapped line, rtl text on wrapping boundaries can do things
+ // that don't correspond to the ordering in our `order` array at
+ // all, so a binary search doesn't work, and we want to return a
+ // part that only spans one line so that the binary search in
+ // coordsCharInner is safe. As such, we first find the extent of the
+ // wrapped line, and then do a flat search in which we discard any
+ // spans that aren't on the line.
+ var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y);
+ var begin = ref.begin;
+ var end = ref.end;
+ if (/\s/.test(lineObj.text.charAt(end - 1))) { end--; }
+ var part = null, closestDist = null;
+ for (var i = 0; i < order.length; i++) {
+ var p = order[i];
+ if (p.from >= end || p.to <= begin) { continue }
+ var ltr = p.level != 1;
+ var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right;
+ // Weigh against spans ending before this, so that they are only
+ // picked if nothing ends after
+ var dist = endX < x ? x - endX + 1e9 : endX - x;
+ if (!part || closestDist > dist) {
+ part = p;
+ closestDist = dist;
+ }
+ }
+ if (!part) { part = order[order.length - 1]; }
+ // Clip the part to the wrapped line.
+ if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; }
+ if (part.to > end) { part = {from: part.from, to: end, level: part.level}; }
+ return part
+ }
+
+ var measureText;
+ // Compute the default text height.
+ 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
+ }
+
+ // Compute the default character width.
+ function charWidth(display) {
+ if (display.cachedCharWidth != null) { return display.cachedCharWidth }
+ var anchor = elt("span", "xxxxxxxxxx");
+ var pre = elt("pre", [anchor]);
+ removeChildrenAndAdd(display.measure, pre);
+ var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;
+ if (width > 2) { display.cachedCharWidth = width; }
+ return width || 10
+ }
+
+ // Do a bulk-read of the DOM positions and sizes needed to draw the
+ // view, so that we don't interleave reading and writing to the DOM.
+ function getDimensions(cm) {
+ var d = cm.display, left = {}, width = {};
+ var gutterLeft = d.gutters.clientLeft;
+ for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
+ var id = cm.display.gutterSpecs[i].className;
+ left[id] = n.offsetLeft + n.clientLeft + gutterLeft;
+ width[id] = n.clientWidth;
+ }
+ return {fixedPos: compensateForHScroll(d),
+ gutterTotalWidth: d.gutters.offsetWidth,
+ gutterLeft: left,
+ gutterWidth: width,
+ wrapperWidth: d.wrapper.clientWidth}
+ }
+
+ // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
+ // but using getBoundingClientRect to get a sub-pixel-accurate
+ // result.
+ function compensateForHScroll(display) {
+ return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left
+ }
+
+ // Returns a function that estimates the height of a line, to use as
+ // first approximation until the line becomes visible (and is thus
+ // properly measurable).
+ 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 }
+
+ var widgetsHeight = 0;
+ if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) {
+ if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height; }
+ } }
+
+ if (wrapping)
+ { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th }
+ else
+ { return widgetsHeight + 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); }
+ });
+ }
+
+ // Given a mouse event, find the corresponding position. If liberal
+ // is false, it checks whether a gutter or scrollbar was clicked,
+ // and returns null if it was. forRect is used by rectangular
+ // selections, and tries to estimate a character position even for
+ // coordinates beyond the right of the text.
+ function posFromMouse(cm, e, liberal, forRect) {
+ var display = cm.display;
+ if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null }
+
+ var x, y, space = display.lineSpace.getBoundingClientRect();
+ // Fails unpredictably on IE[67] when mouse is dragged around quickly.
+ try { x = e.clientX - space.left; y = e.clientY - space.top; }
+ catch (e) { return null }
+ var coords = coordsChar(cm, x, y), line;
+ if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
+ var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
+ coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));
+ }
+ return coords
+ }
+
+ // Find the view element corresponding to a given line. Return null
+ // when the line isn't visible.
+ function findViewIndex(cm, n) {
+ if (n >= cm.display.viewTo) { return null }
+ n -= cm.display.viewFrom;
+ if (n < 0) { return null }
+ var view = cm.display.view;
+ for (var i = 0; i < view.length; i++) {
+ n -= view[i].size;
+ if (n < 0) { return i }
+ }
+ }
+
+ // Updates the display.view data structure for a given change to the
+ // document. From and to are in pre-change coordinates. Lendiff is
+ // the amount of lines added or subtracted by the change. This is
+ // used for changes that span multiple lines, or change the way
+ // lines are divided into visual lines. regLineChange (below)
+ // registers single-line changes.
+ function regChange(cm, from, to, lendiff) {
+ if (from == null) { from = cm.doc.first; }
+ if (to == null) { to = cm.doc.first + cm.doc.size; }
+ if (!lendiff) { lendiff = 0; }
+
+ var display = cm.display;
+ if (lendiff && to < display.viewTo &&
+ (display.updateLineNumbers == null || display.updateLineNumbers > from))
+ { display.updateLineNumbers = from; }
+
+ cm.curOp.viewChanged = true;
+
+ if (from >= display.viewTo) { // Change after
+ if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
+ { resetView(cm); }
+ } else if (to <= display.viewFrom) { // Change before
+ if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
+ resetView(cm);
+ } else {
+ display.viewFrom += lendiff;
+ display.viewTo += lendiff;
+ }
+ } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
+ resetView(cm);
+ } else if (from <= display.viewFrom) { // Top overlap
+ var cut = viewCuttingPoint(cm, to, to + lendiff, 1);
+ if (cut) {
+ display.view = display.view.slice(cut.index);
+ display.viewFrom = cut.lineN;
+ display.viewTo += lendiff;
+ } else {
+ resetView(cm);
+ }
+ } else if (to >= display.viewTo) { // Bottom overlap
+ var cut$1 = viewCuttingPoint(cm, from, from, -1);
+ if (cut$1) {
+ display.view = display.view.slice(0, cut$1.index);
+ display.viewTo = cut$1.lineN;
+ } else {
+ resetView(cm);
+ }
+ } else { // Gap in the middle
+ var cutTop = viewCuttingPoint(cm, from, from, -1);
+ var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);
+ if (cutTop && cutBot) {
+ display.view = display.view.slice(0, cutTop.index)
+ .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
+ .concat(display.view.slice(cutBot.index));
+ display.viewTo += lendiff;
+ } else {
+ resetView(cm);
+ }
+ }
+
+ var ext = display.externalMeasured;
+ if (ext) {
+ if (to < ext.lineN)
+ { ext.lineN += lendiff; }
+ else if (from < ext.lineN + ext.size)
+ { display.externalMeasured = null; }
+ }
+ }
+
+ // Register a change to a single line. Type must be one of "text",
+ // "gutter", "class", "widget"
+ function regLineChange(cm, line, type) {
+ cm.curOp.viewChanged = true;
+ var display = cm.display, ext = cm.display.externalMeasured;
+ if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
+ { display.externalMeasured = null; }
+
+ if (line < display.viewFrom || line >= display.viewTo) { return }
+ var lineView = display.view[findViewIndex(cm, line)];
+ if (lineView.node == null) { return }
+ var arr = lineView.changes || (lineView.changes = []);
+ if (indexOf(arr, type) == -1) { arr.push(type); }
+ }
+
+ // Clear the view.
+ function resetView(cm) {
+ cm.display.viewFrom = cm.display.viewTo = cm.doc.first;
+ cm.display.view = [];
+ cm.display.viewOffset = 0;
+ }
+
+ function viewCuttingPoint(cm, oldN, newN, dir) {
+ var index = findViewIndex(cm, oldN), diff, view = cm.display.view;
+ if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
+ { return {index: index, lineN: newN} }
+ var n = cm.display.viewFrom;
+ for (var i = 0; i < index; i++)
+ { n += view[i].size; }
+ if (n != oldN) {
+ if (dir > 0) {
+ if (index == view.length - 1) { return null }
+ diff = (n + view[index].size) - oldN;
+ index++;
+ } else {
+ diff = n - oldN;
+ }
+ oldN += diff; newN += diff;
+ }
+ while (visualLineNo(cm.doc, newN) != newN) {
+ if (index == (dir < 0 ? 0 : view.length - 1)) { return null }
+ newN += dir * view[index - (dir < 0 ? 1 : 0)].size;
+ index += dir;
+ }
+ return {index: index, lineN: newN}
+ }
+
+ // Force the view to cover a given range, adding empty view element
+ // or clipping off existing ones as needed.
+ function adjustView(cm, from, to) {
+ var display = cm.display, view = display.view;
+ if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
+ display.view = buildViewArray(cm, from, to);
+ display.viewFrom = from;
+ } else {
+ if (display.viewFrom > from)
+ { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); }
+ else if (display.viewFrom < from)
+ { display.view = display.view.slice(findViewIndex(cm, from)); }
+ display.viewFrom = from;
+ if (display.viewTo < to)
+ { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); }
+ else if (display.viewTo > to)
+ { display.view = display.view.slice(0, findViewIndex(cm, to)); }
+ }
+ display.viewTo = to;
+ }
+
+ // Count the number of lines in the view whose DOM representation is
+ // out of date (or nonexistent).
+ function countDirtyView(cm) {
+ var view = cm.display.view, dirty = 0;
+ for (var i = 0; i < view.length; i++) {
+ var lineView = view[i];
+ if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; }
+ }
+ return dirty
+ }
+
+ function updateSelection(cm) {
+ cm.display.input.showSelection(cm.display.input.prepareSelection());
+ }
+
+ function prepareSelection(cm, primary) {
+ if ( primary === void 0 ) primary = true;
+
+ var doc = cm.doc, result = {};
+ var curFragment = result.cursors = document.createDocumentFragment();
+ var selFragment = result.selection = document.createDocumentFragment();
+
+ for (var i = 0; i < doc.sel.ranges.length; i++) {
+ if (!primary && i == doc.sel.primIndex) { continue }
+ var range$$1 = doc.sel.ranges[i];
+ if (range$$1.from().line >= cm.display.viewTo || range$$1.to().line < cm.display.viewFrom) { continue }
+ var collapsed = range$$1.empty();
+ if (collapsed || cm.options.showCursorWhenSelecting)
+ { drawSelectionCursor(cm, range$$1.head, curFragment); }
+ if (!collapsed)
+ { drawSelectionRange(cm, range$$1, selFragment); }
+ }
+ return result
+ }
+
+ // Draws a cursor for the given range
+ function drawSelectionCursor(cm, head, output) {
+ var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine);
+
+ var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
+ cursor.style.left = pos.left + "px";
+ cursor.style.top = pos.top + "px";
+ cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
+
+ if (pos.other) {
+ // Secondary cursor, shown when on a 'jump' in bi-directional text
+ var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
+ otherCursor.style.display = "";
+ otherCursor.style.left = pos.other.left + "px";
+ otherCursor.style.top = pos.other.top + "px";
+ otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
+ }
+ }
+
+ function cmpCoords(a, b) { return a.top - b.top || a.left - b.left }
+
+ // Draws the given range as a highlighted selection
+ function drawSelectionRange(cm, range$$1, output) {
+ var display = cm.display, doc = cm.doc;
+ var fragment = document.createDocumentFragment();
+ var padding = paddingH(cm.display), leftSide = padding.left;
+ var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right;
+ var docLTR = doc.direction == "ltr";
+
+ function add(left, top, width, bottom) {
+ if (top < 0) { top = 0; }
+ top = Math.round(top);
+ bottom = Math.round(bottom);
+ fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n 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)
+ }
+
+ function wrapX(pos, dir, side) {
+ var extent = wrappedLineExtentChar(cm, lineObj, null, pos);
+ var prop = (dir == "ltr") == (side == "after") ? "left" : "right";
+ var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1);
+ return coords(ch, prop)[prop]
+ }
+
+ var order = getOrder(lineObj, doc.direction);
+ iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) {
+ var ltr = dir == "ltr";
+ var fromPos = coords(from, ltr ? "left" : "right");
+ var toPos = coords(to - 1, ltr ? "right" : "left");
+
+ var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen;
+ var first = i == 0, last = !order || i == order.length - 1;
+ if (toPos.top - fromPos.top <= 3) { // Single line
+ var openLeft = (docLTR ? openStart : openEnd) && first;
+ var openRight = (docLTR ? openEnd : openStart) && last;
+ var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left;
+ var right = openRight ? rightSide : (ltr ? toPos : fromPos).right;
+ add(left, fromPos.top, right - left, fromPos.bottom);
+ } else { // Multiple lines
+ var topLeft, topRight, botLeft, botRight;
+ if (ltr) {
+ topLeft = docLTR && openStart && first ? leftSide : fromPos.left;
+ topRight = docLTR ? rightSide : wrapX(from, dir, "before");
+ botLeft = docLTR ? leftSide : wrapX(to, dir, "after");
+ botRight = docLTR && openEnd && last ? rightSide : toPos.right;
+ } else {
+ topLeft = !docLTR ? leftSide : wrapX(from, dir, "before");
+ topRight = !docLTR && openStart && first ? rightSide : fromPos.right;
+ botLeft = !docLTR && openEnd && last ? leftSide : toPos.left;
+ botRight = !docLTR ? rightSide : wrapX(to, dir, "after");
+ }
+ add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom);
+ if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); }
+ add(botLeft, toPos.top, botRight - botLeft, toPos.bottom);
+ }
+
+ if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; }
+ if (cmpCoords(toPos, start) < 0) { start = toPos; }
+ if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; }
+ if (cmpCoords(toPos, end) < 0) { end = toPos; }
+ });
+ return {start: start, end: end}
+ }
+
+ var sFrom = range$$1.from(), sTo = range$$1.to();
+ if (sFrom.line == sTo.line) {
+ drawForLine(sFrom.line, sFrom.ch, sTo.ch);
+ } else {
+ var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);
+ var singleVLine = visualLine(fromLine) == visualLine(toLine);
+ var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;
+ var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;
+ if (singleVLine) {
+ if (leftEnd.top < rightStart.top - 2) {
+ add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
+ add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);
+ } else {
+ add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
+ }
+ }
+ if (leftEnd.bottom < rightStart.top)
+ { add(leftSide, leftEnd.bottom, null, rightStart.top); }
+ }
+
+ output.appendChild(fragment);
+ }
+
+ // Cursor-blinking
+ function restartBlink(cm) {
+ if (!cm.state.focused) { return }
+ var display = cm.display;
+ clearInterval(display.blinker);
+ var on = true;
+ display.cursorDiv.style.visibility = "";
+ if (cm.options.cursorBlinkRate > 0)
+ { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; },
+ cm.options.cursorBlinkRate); }
+ else if (cm.options.cursorBlinkRate < 0)
+ { display.cursorDiv.style.visibility = "hidden"; }
+ }
+
+ function ensureFocus(cm) {
+ if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
+ }
+
+ function delayBlurEvent(cm) {
+ cm.state.delayingBlurEvent = true;
+ setTimeout(function () { if (cm.state.delayingBlurEvent) {
+ cm.state.delayingBlurEvent = false;
+ onBlur(cm);
+ } }, 100);
+ }
+
+ function onFocus(cm, e) {
+ if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false; }
+
+ if (cm.options.readOnly == "nocursor") { return }
+ if (!cm.state.focused) {
+ signal(cm, "focus", cm, e);
+ cm.state.focused = true;
+ addClass(cm.display.wrapper, "CodeMirror-focused");
+ // This test prevents this from firing when a context
+ // menu is closed (since the input reset would kill the
+ // select-all detection hack)
+ if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
+ cm.display.input.reset();
+ if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20); } // Issue #1730
+ }
+ cm.display.input.receivedFocus();
+ }
+ restartBlink(cm);
+ }
+ function onBlur(cm, e) {
+ if (cm.state.delayingBlurEvent) { return }
+
+ if (cm.state.focused) {
+ signal(cm, "blur", cm, e);
+ cm.state.focused = false;
+ rmClass(cm.display.wrapper, "CodeMirror-focused");
+ }
+ clearInterval(cm.display.blinker);
+ setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false; } }, 150);
+ }
+
+ // Read the actual heights of the rendered lines, and update their
+ // stored heights to match.
+ function updateHeightsInViewport(cm) {
+ var display = cm.display;
+ var prevBottom = display.lineDiv.offsetTop;
+ for (var i = 0; i < display.view.length; i++) {
+ var cur = display.view[i], wrapping = cm.options.lineWrapping;
+ var height = (void 0), width = 0;
+ if (cur.hidden) { continue }
+ if (ie && ie_version < 8) {
+ var bot = cur.node.offsetTop + cur.node.offsetHeight;
+ height = bot - prevBottom;
+ prevBottom = bot;
+ } else {
+ var box = cur.node.getBoundingClientRect();
+ height = box.bottom - box.top;
+ // Check that lines don't extend past the right of the current
+ // editor width
+ if (!wrapping && cur.text.firstChild)
+ { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; }
+ }
+ var diff = cur.line.height - height;
+ if (diff > .005 || diff < -.005) {
+ updateLineHeight(cur.line, height);
+ updateWidgetHeight(cur.line);
+ if (cur.rest) { for (var j = 0; j < cur.rest.length; j++)
+ { updateWidgetHeight(cur.rest[j]); } }
+ }
+ if (width > cm.display.sizerWidth) {
+ var chWidth = Math.ceil(width / charWidth(cm.display));
+ if (chWidth > cm.display.maxLineLength) {
+ cm.display.maxLineLength = chWidth;
+ cm.display.maxLine = cur.line;
+ cm.display.maxLineChanged = true;
+ }
+ }
+ }
+ }
+
+ // Read and store the height of line widgets associated with the
+ // given line.
+ function updateWidgetHeight(line) {
+ if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) {
+ var w = line.widgets[i], parent = w.node.parentNode;
+ if (parent) { w.height = parent.offsetHeight; }
+ } }
+ }
+
+ // Compute the lines that are visible in a given viewport (defaults
+ // the the current scroll position). viewport may contain top,
+ // height, and ensure (see op.scrollToPos) properties.
+ function visibleLines(display, doc, viewport) {
+ var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop;
+ top = Math.floor(top - paddingTop(display));
+ var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight;
+
+ var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);
+ // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
+ // forces those lines into the viewport (if possible).
+ if (viewport && viewport.ensure) {
+ var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line;
+ if (ensureFrom < from) {
+ from = ensureFrom;
+ to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight);
+ } else if (Math.min(ensureTo, doc.lastLine()) >= to) {
+ from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight);
+ to = ensureTo;
+ }
+ }
+ return {from: from, to: Math.max(to, from + 1)}
+ }
+
+ // SCROLLING THINGS INTO VIEW
+
+ // If an editor sits on the top or bottom of the window, partially
+ // scrolled out of view, this ensures that the cursor is visible.
+ function maybeScrollWindow(cm, rect) {
+ if (signalDOMEvent(cm, "scrollCursorIntoView")) { return }
+
+ var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
+ if (rect.top + box.top < 0) { doScroll = true; }
+ else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false; }
+ if (doScroll != null && !phantom) {
+ var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;"));
+ cm.display.lineSpace.appendChild(scrollNode);
+ scrollNode.scrollIntoView(doScroll);
+ cm.display.lineSpace.removeChild(scrollNode);
+ }
+ }
+
+ // Scroll a given position into view (immediately), verifying that
+ // it actually became visible (as line heights are accurately
+ // measured, the position of something may 'drift' during drawing).
+ function scrollPosIntoView(cm, pos, end, margin) {
+ if (margin == null) { margin = 0; }
+ var rect;
+ if (!cm.options.lineWrapping && pos == end) {
+ // Set pos and end to the cursor positions around the character pos sticks to
+ // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch
+ // If pos == Pos(_, 0, "before"), pos and end are unchanged
+ pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos;
+ end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos;
+ }
+ for (var limit = 0; limit < 5; limit++) {
+ var changed = false;
+ var coords = cursorCoords(cm, pos);
+ var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);
+ rect = {left: Math.min(coords.left, endCoords.left),
+ top: Math.min(coords.top, endCoords.top) - margin,
+ right: Math.max(coords.left, endCoords.left),
+ bottom: Math.max(coords.bottom, endCoords.bottom) + margin};
+ var scrollPos = calculateScrollPos(cm, rect);
+ var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
+ if (scrollPos.scrollTop != null) {
+ updateScrollTop(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) { break }
+ }
+ return rect
+ }
+
+ // Scroll a given set of coordinates into view (immediately).
+ function scrollIntoView(cm, rect) {
+ var scrollPos = calculateScrollPos(cm, rect);
+ if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); }
+ if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); }
+ }
+
+ // Calculate a new scroll position needed to scroll the given
+ // rectangle into view. Returns an object with scrollTop and
+ // scrollLeft properties. When these are undefined, the
+ // vertical/horizontal position does not need to be adjusted.
+ function calculateScrollPos(cm, rect) {
+ var display = cm.display, snapMargin = textHeight(cm.display);
+ if (rect.top < 0) { rect.top = 0; }
+ var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;
+ var screen = displayHeight(cm), result = {};
+ if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen; }
+ var docBottom = cm.doc.height + paddingVert(display);
+ var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin;
+ if (rect.top < screentop) {
+ result.scrollTop = atTop ? 0 : rect.top;
+ } else if (rect.bottom > screentop + screen) {
+ var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen);
+ if (newTop != screentop) { result.scrollTop = newTop; }
+ }
+
+ var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;
+ var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0);
+ var tooWide = rect.right - rect.left > screenw;
+ if (tooWide) { rect.right = rect.left + screenw; }
+ if (rect.left < 10)
+ { result.scrollLeft = 0; }
+ else if (rect.left < screenleft)
+ { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)); }
+ else if (rect.right > screenw + screenleft - 3)
+ { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; }
+ return result
+ }
+
+ // Store a relative adjustment to the scroll position in the current
+ // operation (to be applied when the operation finishes).
+ function addToScrollTop(cm, top) {
+ if (top == null) { return }
+ resolveScrollToPos(cm);
+ cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top;
+ }
+
+ // Make sure that at the end of the operation the current cursor is
+ // shown.
+ function ensureCursorVisible(cm) {
+ resolveScrollToPos(cm);
+ var cur = cm.getCursor();
+ cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin};
+ }
+
+ function scrollToCoords(cm, x, y) {
+ if (x != null || y != null) { resolveScrollToPos(cm); }
+ if (x != null) { cm.curOp.scrollLeft = x; }
+ if (y != null) { cm.curOp.scrollTop = y; }
+ }
+
+ function scrollToRange(cm, range$$1) {
+ resolveScrollToPos(cm);
+ cm.curOp.scrollToPos = range$$1;
+ }
+
+ // When an operation has its scrollToPos property set, and another
+ // scroll action is applied before the end of the operation, this
+ // 'simulates' scrolling that position into view in a cheap way, so
+ // that the effect of intermediate scroll commands is not ignored.
+ function resolveScrollToPos(cm) {
+ var range$$1 = cm.curOp.scrollToPos;
+ if (range$$1) {
+ cm.curOp.scrollToPos = null;
+ var from = estimateCoords(cm, range$$1.from), to = estimateCoords(cm, range$$1.to);
+ scrollToCoordsRange(cm, from, to, range$$1.margin);
+ }
+ }
+
+ function scrollToCoordsRange(cm, from, to, margin) {
+ var sPos = calculateScrollPos(cm, {
+ left: Math.min(from.left, to.left),
+ top: Math.min(from.top, to.top) - margin,
+ right: Math.max(from.right, to.right),
+ bottom: Math.max(from.bottom, to.bottom) + margin
+ });
+ scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop);
+ }
+
+ // Sync the scrollable area and scrollbars, ensure the viewport
+ // covers the visible area.
+ function updateScrollTop(cm, val) {
+ if (Math.abs(cm.doc.scrollTop - val) < 2) { return }
+ if (!gecko) { updateDisplaySimple(cm, {top: val}); }
+ setScrollTop(cm, val, true);
+ if (gecko) { updateDisplaySimple(cm); }
+ startWorker(cm, 100);
+ }
+
+ function setScrollTop(cm, val, forceScroll) {
+ val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val);
+ if (cm.display.scroller.scrollTop == val && !forceScroll) { return }
+ cm.doc.scrollTop = val;
+ cm.display.scrollbars.setScrollTop(val);
+ if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val; }
+ }
+
+ // Sync scroller and scrollbar, ensure the gutter elements are
+ // aligned.
+ function setScrollLeft(cm, val, isScroller, forceScroll) {
+ val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
+ if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return }
+ cm.doc.scrollLeft = val;
+ alignHorizontally(cm);
+ if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; }
+ cm.display.scrollbars.setScrollLeft(val);
+ }
+
+ // SCROLLBARS
+
+ // Prepare DOM reads needed to update the scrollbars. Done in one
+ // shot to minimize update/measure roundtrips.
+ function measureForScrollbars(cm) {
+ var d = cm.display, gutterW = d.gutters.offsetWidth;
+ var docH = Math.round(cm.doc.height + paddingVert(cm.display));
+ return {
+ clientHeight: d.scroller.clientHeight,
+ viewHeight: d.wrapper.clientHeight,
+ scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,
+ viewWidth: d.wrapper.clientWidth,
+ barLeft: cm.options.fixedGutter ? gutterW : 0,
+ docHeight: docH,
+ scrollHeight: docH + scrollGap(cm) + d.barHeight,
+ nativeBarWidth: d.nativeBarWidth,
+ gutterWidth: gutterW
+ }
+ }
+
+ var NativeScrollbars = function(place, scroll, cm) {
+ this.cm = cm;
+ var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
+ var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
+ vert.tabIndex = horiz.tabIndex = -1;
+ place(vert); place(horiz);
+
+ on(vert, "scroll", function () {
+ if (vert.clientHeight) { scroll(vert.scrollTop, "vertical"); }
+ });
+ on(horiz, "scroll", function () {
+ if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal"); }
+ });
+
+ this.checkedZeroWidth = false;
+ // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
+ if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; }
+ };
+
+ NativeScrollbars.prototype.update = function (measure) {
+ var needsH = measure.scrollWidth > measure.clientWidth + 1;
+ var needsV = measure.scrollHeight > measure.clientHeight + 1;
+ var sWidth = measure.nativeBarWidth;
+
+ if (needsV) {
+ this.vert.style.display = "block";
+ this.vert.style.bottom = needsH ? sWidth + "px" : "0";
+ var totalHeight = measure.viewHeight - (needsH ? sWidth : 0);
+ // A bug in IE8 can cause this value to be negative, so guard it.
+ this.vert.firstChild.style.height =
+ Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px";
+ } else {
+ this.vert.style.display = "";
+ this.vert.firstChild.style.height = "0";
+ }
+
+ if (needsH) {
+ this.horiz.style.display = "block";
+ this.horiz.style.right = needsV ? sWidth + "px" : "0";
+ this.horiz.style.left = measure.barLeft + "px";
+ var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0);
+ this.horiz.firstChild.style.width =
+ Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px";
+ } else {
+ this.horiz.style.display = "";
+ this.horiz.firstChild.style.width = "0";
+ }
+
+ if (!this.checkedZeroWidth && measure.clientHeight > 0) {
+ if (sWidth == 0) { this.zeroWidthHack(); }
+ this.checkedZeroWidth = true;
+ }
+
+ return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}
+ };
+
+ NativeScrollbars.prototype.setScrollLeft = function (pos) {
+ if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; }
+ if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz"); }
+ };
+
+ NativeScrollbars.prototype.setScrollTop = function (pos) {
+ if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; }
+ if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert"); }
+ };
+
+ NativeScrollbars.prototype.zeroWidthHack = function () {
+ var w = mac && !mac_geMountainLion ? "12px" : "18px";
+ this.horiz.style.height = this.vert.style.width = w;
+ this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none";
+ this.disableHoriz = new Delayed;
+ this.disableVert = new Delayed;
+ };
+
+ NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) {
+ bar.style.pointerEvents = "auto";
+ function maybeDisable() {
+ // To find out whether the scrollbar is still visible, we
+ // check whether the element under the pixel in the bottom
+ // right corner of the scrollbar box is the scrollbar box
+ // itself (when the bar is still visible) or its filler child
+ // (when the bar is hidden). If it is still visible, we keep
+ // it enabled, if it's hidden, we disable pointer events.
+ var box = bar.getBoundingClientRect();
+ var elt$$1 = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2)
+ : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1);
+ if (elt$$1 != bar) { bar.style.pointerEvents = "none"; }
+ else { delay.set(1000, maybeDisable); }
+ }
+ delay.set(1000, maybeDisable);
+ };
+
+ NativeScrollbars.prototype.clear = function () {
+ var parent = this.horiz.parentNode;
+ parent.removeChild(this.horiz);
+ parent.removeChild(this.vert);
+ };
+
+ var NullScrollbars = function () {};
+
+ NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} };
+ NullScrollbars.prototype.setScrollLeft = function () {};
+ NullScrollbars.prototype.setScrollTop = function () {};
+ NullScrollbars.prototype.clear = function () {};
+
+ function updateScrollbars(cm, measure) {
+ if (!measure) { measure = measureForScrollbars(cm); }
+ var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight;
+ updateScrollbarsInner(cm, measure);
+ for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) {
+ if (startWidth != cm.display.barWidth && cm.options.lineWrapping)
+ { updateHeightsInViewport(cm); }
+ updateScrollbarsInner(cm, measureForScrollbars(cm));
+ startWidth = cm.display.barWidth; startHeight = cm.display.barHeight;
+ }
+ }
+
+ // Re-synchronize the fake scrollbars with the actual size of the
+ // content.
+ function updateScrollbarsInner(cm, measure) {
+ var d = cm.display;
+ var sizes = d.scrollbars.update(measure);
+
+ d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px";
+ d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px";
+ d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent";
+
+ if (sizes.right && sizes.bottom) {
+ d.scrollbarFiller.style.display = "block";
+ d.scrollbarFiller.style.height = sizes.bottom + "px";
+ d.scrollbarFiller.style.width = sizes.right + "px";
+ } else { d.scrollbarFiller.style.display = ""; }
+ if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
+ d.gutterFiller.style.display = "block";
+ d.gutterFiller.style.height = sizes.bottom + "px";
+ d.gutterFiller.style.width = measure.gutterWidth + "px";
+ } else { d.gutterFiller.style.display = ""; }
+ }
+
+ var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars};
+
+ function initScrollbars(cm) {
+ if (cm.display.scrollbars) {
+ cm.display.scrollbars.clear();
+ if (cm.display.scrollbars.addClass)
+ { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); }
+ }
+
+ cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) {
+ cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller);
+ // Prevent clicks in the scrollbars from killing focus
+ on(node, "mousedown", function () {
+ if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0); }
+ });
+ node.setAttribute("cm-not-content", "true");
+ }, function (pos, axis) {
+ if (axis == "horizontal") { setScrollLeft(cm, pos); }
+ else { updateScrollTop(cm, pos); }
+ }, cm);
+ if (cm.display.scrollbars.addClass)
+ { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); }
+ }
+
+ // Operations are used to wrap a series of changes to the editor
+ // state in such a way that each change won't have to update the
+ // cursor and display (which would be awkward, slow, and
+ // error-prone). Instead, display updates are batched and then all
+ // combined and executed at once.
+
+ var nextOpId = 0;
+ // Start a new operation.
+ function startOperation(cm) {
+ cm.curOp = {
+ cm: cm,
+ viewChanged: false, // Flag that indicates that lines might need to be redrawn
+ startHeight: cm.doc.height, // Used to detect need to update scrollbar
+ forceUpdate: false, // Used to force a redraw
+ updateInput: 0, // Whether to reset the input textarea
+ typing: false, // Whether this reset should be careful to leave existing text (for compositing)
+ changeObjs: null, // Accumulated changes, for firing change events
+ cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
+ cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
+ selectionChanged: false, // Whether the selection needs to be redrawn
+ updateMaxLine: false, // Set when the widest line needs to be determined anew
+ scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
+ scrollToPos: null, // Used to scroll to a specific position
+ focus: false,
+ id: ++nextOpId // Unique ID
+ };
+ pushOperation(cm.curOp);
+ }
+
+ // Finish an operation, updating the display and signalling delayed events
+ function endOperation(cm) {
+ var op = cm.curOp;
+ if (op) { finishOperation(op, function (group) {
+ for (var i = 0; i < group.ops.length; i++)
+ { group.ops[i].cm.curOp = null; }
+ endOperations(group);
+ }); }
+ }
+
+ // The DOM updates done when an operation finishes are batched so
+ // that the minimum number of relayouts are required.
+ function endOperations(group) {
+ var ops = group.ops;
+ for (var i = 0; i < ops.length; i++) // Read DOM
+ { endOperation_R1(ops[i]); }
+ for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe)
+ { endOperation_W1(ops[i$1]); }
+ for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM
+ { endOperation_R2(ops[i$2]); }
+ for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe)
+ { endOperation_W2(ops[i$3]); }
+ for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM
+ { endOperation_finish(ops[i$4]); }
+ }
+
+ function endOperation_R1(op) {
+ var cm = op.cm, display = cm.display;
+ maybeClipScrollbars(cm);
+ if (op.updateMaxLine) { findMaxLine(cm); }
+
+ op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
+ op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
+ op.scrollToPos.to.line >= display.viewTo) ||
+ display.maxLineChanged && cm.options.lineWrapping;
+ op.update = op.mustUpdate &&
+ new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);
+ }
+
+ function endOperation_W1(op) {
+ op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);
+ }
+
+ function endOperation_R2(op) {
+ var cm = op.cm, display = cm.display;
+ if (op.updatedDisplay) { updateHeightsInViewport(cm); }
+
+ op.barMeasure = measureForScrollbars(cm);
+
+ // If the max line changed since it was last measured, measure it,
+ // and ensure the document's width matches it.
+ // updateDisplay_W2 will use these properties to do the actual resizing
+ if (display.maxLineChanged && !cm.options.lineWrapping) {
+ op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;
+ cm.display.sizerWidth = op.adjustWidthTo;
+ op.barMeasure.scrollWidth =
+ Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth);
+ op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm));
+ }
+
+ if (op.updatedDisplay || op.selectionChanged)
+ { op.preparedSelection = display.input.prepareSelection(); }
+ }
+
+ function endOperation_W2(op) {
+ var cm = op.cm;
+
+ if (op.adjustWidthTo != null) {
+ cm.display.sizer.style.minWidth = op.adjustWidthTo + "px";
+ if (op.maxScrollLeft < cm.doc.scrollLeft)
+ { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); }
+ cm.display.maxLineChanged = false;
+ }
+
+ var takeFocus = op.focus && op.focus == activeElt();
+ if (op.preparedSelection)
+ { cm.display.input.showSelection(op.preparedSelection, takeFocus); }
+ if (op.updatedDisplay || op.startHeight != cm.doc.height)
+ { updateScrollbars(cm, op.barMeasure); }
+ if (op.updatedDisplay)
+ { setDocumentHeight(cm, op.barMeasure); }
+
+ if (op.selectionChanged) { restartBlink(cm); }
+
+ if (cm.state.focused && op.updateInput)
+ { cm.display.input.reset(op.typing); }
+ if (takeFocus) { ensureFocus(op.cm); }
+ }
+
+ function endOperation_finish(op) {
+ var cm = op.cm, display = cm.display, doc = cm.doc;
+
+ if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); }
+
+ // Abort mouse wheel delta measurement, when scrolling explicitly
+ if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))
+ { display.wheelStartX = display.wheelStartY = null; }
+
+ // Propagate the scroll position to the actual DOM scroller
+ if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); }
+
+ if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true); }
+ // If we need to scroll a specific position into view, do so.
+ if (op.scrollToPos) {
+ var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
+ clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);
+ maybeScrollWindow(cm, rect);
+ }
+
+ // Fire events for markers that are hidden/unidden by editing or
+ // undoing
+ 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$1 = 0; i$1 < unhidden.length; ++i$1)
+ { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide"); } } }
+
+ if (display.wrapper.offsetHeight)
+ { doc.scrollTop = cm.display.scroller.scrollTop; }
+
+ // Fire change events, and delayed event handlers
+ if (op.changeObjs)
+ { signal(cm, "changes", cm, op.changeObjs); }
+ if (op.update)
+ { op.update.finish(); }
+ }
+
+ // Run the given function in an operation
+ function runInOp(cm, f) {
+ if (cm.curOp) { return f() }
+ startOperation(cm);
+ try { return f() }
+ finally { endOperation(cm); }
+ }
+ // Wraps a function in an operation. Returns the wrapped function.
+ function operation(cm, f) {
+ return function() {
+ if (cm.curOp) { return f.apply(cm, arguments) }
+ startOperation(cm);
+ try { return f.apply(cm, arguments) }
+ finally { endOperation(cm); }
+ }
+ }
+ // Used to add methods to editor and doc instances, wrapping them in
+ // operations.
+ function methodOp(f) {
+ return function() {
+ if (this.curOp) { return f.apply(this, arguments) }
+ startOperation(this);
+ try { return f.apply(this, arguments) }
+ finally { endOperation(this); }
+ }
+ }
+ function docMethodOp(f) {
+ return function() {
+ var cm = this.cm;
+ if (!cm || cm.curOp) { return f.apply(this, arguments) }
+ startOperation(cm);
+ try { return f.apply(this, arguments) }
+ finally { endOperation(cm); }
+ }
+ }
+
+ // HIGHLIGHT WORKER
+
+ function startWorker(cm, time) {
+ if (cm.doc.highlightFrontier < cm.display.viewTo)
+ { cm.state.highlight.set(time, bind(highlightWorker, cm)); }
+ }
+
+ function highlightWorker(cm) {
+ var doc = cm.doc;
+ if (doc.highlightFrontier >= cm.display.viewTo) { return }
+ var end = +new Date + cm.options.workTime;
+ var context = getContextBefore(cm, doc.highlightFrontier);
+ var changedLines = [];
+
+ doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) {
+ if (context.line >= cm.display.viewFrom) { // Visible
+ var oldStyles = line.styles;
+ var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null;
+ var highlighted = highlightLine(cm, line, context, true);
+ if (resetState) { context.state = resetState; }
+ line.styles = highlighted.styles;
+ var oldCls = line.styleClasses, newCls = highlighted.classes;
+ if (newCls) { line.styleClasses = newCls; }
+ else if (oldCls) { line.styleClasses = null; }
+ var ischange = !oldStyles || oldStyles.length != line.styles.length ||
+ oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);
+ for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i]; }
+ if (ischange) { changedLines.push(context.line); }
+ line.stateAfter = context.save();
+ context.nextLine();
+ } else {
+ if (line.text.length <= cm.options.maxHighlightLength)
+ { processLine(cm, line.text, context); }
+ line.stateAfter = context.line % 5 == 0 ? context.save() : null;
+ context.nextLine();
+ }
+ if (+new Date > end) {
+ startWorker(cm, cm.options.workDelay);
+ return true
+ }
+ });
+ doc.highlightFrontier = context.line;
+ doc.modeFrontier = Math.max(doc.modeFrontier, context.line);
+ if (changedLines.length) { runInOp(cm, function () {
+ for (var i = 0; i < changedLines.length; i++)
+ { regLineChange(cm, changedLines[i], "text"); }
+ }); }
+ }
+
+ // DISPLAY DRAWING
+
+ var DisplayUpdate = function(cm, viewport, force) {
+ var display = cm.display;
+
+ this.viewport = viewport;
+ // Store some values that we'll need later (but don't want to force a relayout for)
+ this.visible = visibleLines(display, cm.doc, viewport);
+ this.editorIsHidden = !display.wrapper.offsetWidth;
+ this.wrapperHeight = display.wrapper.clientHeight;
+ this.wrapperWidth = display.wrapper.clientWidth;
+ this.oldDisplayWidth = displayWidth(cm);
+ this.force = force;
+ this.dims = getDimensions(cm);
+ this.events = [];
+ };
+
+ DisplayUpdate.prototype.signal = function (emitter, type) {
+ if (hasHandler(emitter, type))
+ { this.events.push(arguments); }
+ };
+ DisplayUpdate.prototype.finish = function () {
+ var this$1 = this;
+
+ for (var i = 0; i < this.events.length; i++)
+ { signal.apply(null, this$1.events[i]); }
+ };
+
+ function maybeClipScrollbars(cm) {
+ var display = cm.display;
+ if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
+ display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth;
+ display.heightForcer.style.height = scrollGap(cm) + "px";
+ display.sizer.style.marginBottom = -display.nativeBarWidth + "px";
+ display.sizer.style.borderRightWidth = scrollGap(cm) + "px";
+ display.scrollbarsClipped = true;
+ }
+ }
+
+ function selectionSnapshot(cm) {
+ if (cm.hasFocus()) { return null }
+ var active = activeElt();
+ if (!active || !contains(cm.display.lineDiv, active)) { return null }
+ var result = {activeElt: active};
+ if (window.getSelection) {
+ var sel = window.getSelection();
+ if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) {
+ result.anchorNode = sel.anchorNode;
+ result.anchorOffset = sel.anchorOffset;
+ result.focusNode = sel.focusNode;
+ result.focusOffset = sel.focusOffset;
+ }
+ }
+ return result
+ }
+
+ function restoreSelection(snapshot) {
+ if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return }
+ snapshot.activeElt.focus();
+ if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {
+ var sel = window.getSelection(), range$$1 = document.createRange();
+ range$$1.setEnd(snapshot.anchorNode, snapshot.anchorOffset);
+ range$$1.collapse(false);
+ sel.removeAllRanges();
+ sel.addRange(range$$1);
+ sel.extend(snapshot.focusNode, snapshot.focusOffset);
+ }
+ }
+
+ // Does the actual updating of the line display. Bails out
+ // (returning false) when there is nothing to be done and forced is
+ // false.
+ function updateDisplayIfNeeded(cm, update) {
+ var display = cm.display, doc = cm.doc;
+
+ if (update.editorIsHidden) {
+ resetView(cm);
+ return false
+ }
+
+ // Bail out if the visible area is already rendered and nothing changed.
+ if (!update.force &&
+ update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
+ (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
+ display.renderedView == display.view && countDirtyView(cm) == 0)
+ { return false }
+
+ if (maybeUpdateLineNumberWidth(cm)) {
+ resetView(cm);
+ update.dims = getDimensions(cm);
+ }
+
+ // Compute a suitable new viewport (from & to)
+ var end = doc.first + doc.size;
+ var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
+ var to = Math.min(end, update.visible.to + cm.options.viewportMargin);
+ if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); }
+ if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); }
+ if (sawCollapsedSpans) {
+ from = visualLineNo(cm.doc, from);
+ to = visualLineEndNo(cm.doc, to);
+ }
+
+ var different = from != display.viewFrom || to != display.viewTo ||
+ display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;
+ adjustView(cm, from, to);
+
+ display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
+ // Position the mover div to align with the current scroll position
+ cm.display.mover.style.top = display.viewOffset + "px";
+
+ var toUpdate = countDirtyView(cm);
+ if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
+ (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
+ { return false }
+
+ // For big changes, we hide the enclosing element during the
+ // update, since that speeds up the operations on most browsers.
+ var selSnapshot = selectionSnapshot(cm);
+ if (toUpdate > 4) { display.lineDiv.style.display = "none"; }
+ patchDisplay(cm, display.updateLineNumbers, update.dims);
+ if (toUpdate > 4) { display.lineDiv.style.display = ""; }
+ display.renderedView = display.view;
+ // There might have been a widget with a focused element that got
+ // hidden or updated, if so re-focus it.
+ restoreSelection(selSnapshot);
+
+ // Prevent selection and cursors from interfering with the scroll
+ // width and height.
+ removeChildren(display.cursorDiv);
+ removeChildren(display.selectionDiv);
+ display.gutters.style.height = display.sizer.style.minHeight = 0;
+
+ if (different) {
+ display.lastWrapHeight = update.wrapperHeight;
+ display.lastWrapWidth = update.wrapperWidth;
+ startWorker(cm, 400);
+ }
+
+ display.updateLineNumbers = null;
+
+ return true
+ }
+
+ function postUpdateDisplay(cm, update) {
+ var viewport = update.viewport;
+
+ for (var first = true;; first = false) {
+ if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {
+ // Clip forced viewport to actual scrollable area.
+ if (viewport && viewport.top != null)
+ { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; }
+ // Updated line heights might result in the drawn area not
+ // actually covering the viewport. Keep looping until it does.
+ update.visible = visibleLines(cm.display, cm.doc, viewport);
+ if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
+ { break }
+ }
+ if (!updateDisplayIfNeeded(cm, update)) { break }
+ updateHeightsInViewport(cm);
+ var barMeasure = measureForScrollbars(cm);
+ updateSelection(cm);
+ updateScrollbars(cm, barMeasure);
+ setDocumentHeight(cm, barMeasure);
+ update.force = false;
+ }
+
+ update.signal(cm, "update", cm);
+ if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
+ update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
+ cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo;
+ }
+ }
+
+ function updateDisplaySimple(cm, viewport) {
+ var update = new DisplayUpdate(cm, viewport);
+ if (updateDisplayIfNeeded(cm, update)) {
+ updateHeightsInViewport(cm);
+ postUpdateDisplay(cm, update);
+ var barMeasure = measureForScrollbars(cm);
+ updateSelection(cm);
+ updateScrollbars(cm, barMeasure);
+ setDocumentHeight(cm, barMeasure);
+ update.finish();
+ }
+ }
+
+ // Sync the actual display DOM structure with display.view, removing
+ // nodes for lines that are no longer in view, and creating the ones
+ // that are not there yet, and updating the ones that are out of
+ // date.
+ function patchDisplay(cm, updateNumbersFrom, dims) {
+ var display = cm.display, lineNumbers = cm.options.lineNumbers;
+ var container = display.lineDiv, cur = container.firstChild;
+
+ function rm(node) {
+ var next = node.nextSibling;
+ // Works around a throw-scroll bug in OS X Webkit
+ if (webkit && mac && cm.display.currentWheelTarget == node)
+ { node.style.display = "none"; }
+ else
+ { node.parentNode.removeChild(node); }
+ return next
+ }
+
+ var view = display.view, lineN = display.viewFrom;
+ // Loop over the elements in the view, syncing cur (the DOM nodes
+ // in display.lineDiv) with the view as we go.
+ for (var i = 0; i < view.length; i++) {
+ var lineView = view[i];
+ if (lineView.hidden) ; else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet
+ var node = buildLineElement(cm, lineView, lineN, dims);
+ container.insertBefore(node, cur);
+ } else { // Already drawn
+ while (cur != lineView.node) { cur = rm(cur); }
+ var updateNumber = lineNumbers && updateNumbersFrom != null &&
+ updateNumbersFrom <= lineN && lineView.lineNumber;
+ if (lineView.changes) {
+ if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false; }
+ updateLineForChanges(cm, lineView, lineN, dims);
+ }
+ if (updateNumber) {
+ removeChildren(lineView.lineNumber);
+ lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));
+ }
+ cur = lineView.node.nextSibling;
+ }
+ lineN += lineView.size;
+ }
+ while (cur) { cur = rm(cur); }
+ }
+
+ function updateGutterSpace(display) {
+ var width = display.gutters.offsetWidth;
+ display.sizer.style.marginLeft = width + "px";
+ }
+
+ function setDocumentHeight(cm, measure) {
+ cm.display.sizer.style.minHeight = measure.docHeight + "px";
+ cm.display.heightForcer.style.top = measure.docHeight + "px";
+ cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px";
+ }
+
+ // Re-align line numbers and gutter marks to compensate for
+ // horizontal scrolling.
+ function alignHorizontally(cm) {
+ var display = cm.display, view = display.view;
+ 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, left = comp + "px";
+ for (var i = 0; i < view.length; i++) { if (!view[i].hidden) {
+ if (cm.options.fixedGutter) {
+ if (view[i].gutter)
+ { view[i].gutter.style.left = left; }
+ if (view[i].gutterBackground)
+ { view[i].gutterBackground.style.left = left; }
+ }
+ var align = view[i].alignable;
+ if (align) { for (var j = 0; j < align.length; j++)
+ { align[j].style.left = left; } }
+ } }
+ if (cm.options.fixedGutter)
+ { display.gutters.style.left = (comp + gutterW) + "px"; }
+ }
+
+ // Used to ensure that the line number gutter is still the right
+ // size for the current document size. Returns true when an update
+ // is needed.
+ 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) + 1;
+ display.lineNumWidth = display.lineNumInnerWidth + padding;
+ display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
+ display.lineGutter.style.width = display.lineNumWidth + "px";
+ updateGutterSpace(cm.display);
+ return true
+ }
+ return false
+ }
+
+ function getGutters(gutters, lineNumbers) {
+ var result = [], sawLineNumbers = false;
+ for (var i = 0; i < gutters.length; i++) {
+ var name = gutters[i], style = null;
+ if (typeof name != "string") { style = name.style; name = name.className; }
+ if (name == "CodeMirror-linenumbers") {
+ if (!lineNumbers) { continue }
+ else { sawLineNumbers = true; }
+ }
+ result.push({className: name, style: style});
+ }
+ if (lineNumbers && !sawLineNumbers) { result.push({className: "CodeMirror-linenumbers", style: null}); }
+ return result
+ }
+
+ // Rebuild the gutter elements, ensure the margin to the left of the
+ // code matches their width.
+ function renderGutters(display) {
+ var gutters = display.gutters, specs = display.gutterSpecs;
+ removeChildren(gutters);
+ display.lineGutter = null;
+ for (var i = 0; i < specs.length; ++i) {
+ var ref = specs[i];
+ var className = ref.className;
+ var style = ref.style;
+ var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + className));
+ if (style) { gElt.style.cssText = style; }
+ if (className == "CodeMirror-linenumbers") {
+ display.lineGutter = gElt;
+ gElt.style.width = (display.lineNumWidth || 1) + "px";
+ }
+ }
+ gutters.style.display = specs.length ? "" : "none";
+ updateGutterSpace(display);
+ }
+
+ function updateGutters(cm) {
+ renderGutters(cm.display);
+ regChange(cm);
+ alignHorizontally(cm);
+ }
+
+ // The display handles the DOM integration, both for input reading
+ // and content drawing. It holds references to DOM nodes and
+ // display-related state.
+
+ function Display(place, doc, input, options) {
+ var d = this;
+ this.input = input;
+
+ // Covers bottom-right square when both scrollbars are present.
+ d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
+ d.scrollbarFiller.setAttribute("cm-not-content", "true");
+ // Covers bottom of gutter when coverGutterNextToScrollbar is on
+ // and h scrollbar is present.
+ d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
+ d.gutterFiller.setAttribute("cm-not-content", "true");
+ // Will contain the actual code, positioned to cover the viewport.
+ d.lineDiv = eltP("div", null, "CodeMirror-code");
+ // Elements are added to these to represent selection and cursors.
+ d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
+ d.cursorDiv = elt("div", null, "CodeMirror-cursors");
+ // A visibility: hidden element used to find the size of things.
+ d.measure = elt("div", null, "CodeMirror-measure");
+ // When lines outside of the viewport are measured, they are drawn in this.
+ d.lineMeasure = elt("div", null, "CodeMirror-measure");
+ // Wraps everything that needs to exist inside the vertically-padded coordinate system
+ d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
+ null, "position: relative; outline: none");
+ var lines = eltP("div", [d.lineSpace], "CodeMirror-lines");
+ // Moved around its parent to cover visible view.
+ d.mover = elt("div", [lines], null, "position: relative");
+ // Set to the height of the document, allowing scrolling.
+ d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
+ d.sizerWidth = null;
+ // Behavior of elts with overflow: auto and padding is
+ // inconsistent across browsers. This is used to ensure the
+ // scrollable area is big enough.
+ d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;");
+ // Will contain the gutters, if any.
+ d.gutters = elt("div", null, "CodeMirror-gutters");
+ d.lineGutter = null;
+ // Actual scrollable element.
+ 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.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
+
+ // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
+ if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
+ if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true; }
+
+ if (place) {
+ if (place.appendChild) { place.appendChild(d.wrapper); }
+ else { place(d.wrapper); }
+ }
+
+ // Current rendered range (may be bigger than the view window).
+ d.viewFrom = d.viewTo = doc.first;
+ d.reportedViewFrom = d.reportedViewTo = doc.first;
+ // Information about the rendered lines.
+ d.view = [];
+ d.renderedView = null;
+ // Holds info about a single rendered line when it was rendered
+ // for measurement, while not in view.
+ d.externalMeasured = null;
+ // Empty space (in pixels) above the view
+ d.viewOffset = 0;
+ d.lastWrapHeight = d.lastWrapWidth = 0;
+ d.updateLineNumbers = null;
+
+ d.nativeBarWidth = d.barHeight = d.barWidth = 0;
+ d.scrollbarsClipped = false;
+
+ // 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;
+ // Set to true when a non-horizontal-scrolling line widget is
+ // added. As an optimization, line widget aligning is skipped when
+ // this is false.
+ d.alignWidgets = false;
+
+ d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
+
+ // 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;
+
+ // True when shift is held down.
+ d.shift = false;
+
+ // Used to track whether anything happened since the context menu
+ // was opened.
+ d.selForContextMenu = null;
+
+ d.activeTouch = null;
+
+ d.gutterSpecs = getGutters(options.gutters, options.lineNumbers);
+ renderGutters(d);
+
+ input.init(d);
+ }
+
+ // 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 wheelEventDelta(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; }
+ return {x: dx, y: dy}
+ }
+ function wheelEventPixels(e) {
+ var delta = wheelEventDelta(e);
+ delta.x *= wheelPixelsPerUnit;
+ delta.y *= wheelPixelsPerUnit;
+ return delta
+ }
+
+ function onScrollWheel(cm, e) {
+ var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;
+
+ var display = cm.display, scroll = display.scroller;
+ // Quit if there's nothing to scroll here
+ var canScrollX = scroll.scrollWidth > scroll.clientWidth;
+ var canScrollY = scroll.scrollHeight > scroll.clientHeight;
+ if (!(dx && canScrollX || dy && canScrollY)) { 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) {
+ outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
+ for (var i = 0; i < view.length; i++) {
+ if (view[i].node == cur) {
+ cm.display.currentWheelTarget = cur;
+ break outer
+ }
+ }
+ }
+ }
+
+ // 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 && !presto && wheelPixelsPerUnit != null) {
+ if (dy && canScrollY)
+ { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)); }
+ setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit));
+ // Only prevent default scrolling if vertical scrolling is
+ // actually possible. Otherwise, it causes vertical scroll
+ // jitter on OSX trackpads when deltaX is small and deltaY
+ // is large (issue #3579)
+ if (!dy || (dy && canScrollY))
+ { e_preventDefault(e); }
+ display.wheelStartX = null; // Abort measurement, if in progress
+ return
+ }
+
+ // 'Project' the visible viewport to cover the area that is being
+ // scrolled into view (if we know enough to estimate it).
+ 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); }
+ updateDisplaySimple(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;
+ }
+ }
+ }
+
+ // Selection objects are immutable. A new one is created every time
+ // the selection changes. A selection is one or more non-overlapping
+ // (and non-touching) ranges, sorted, and an integer that indicates
+ // which one is the primary selection (the one that's scrolled into
+ // view, that getCursor returns, etc).
+ var Selection = function(ranges, primIndex) {
+ this.ranges = ranges;
+ this.primIndex = primIndex;
+ };
+
+ Selection.prototype.primary = function () { return this.ranges[this.primIndex] };
+
+ Selection.prototype.equals = function (other) {
+ var this$1 = this;
+
+ if (other == this) { return true }
+ if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false }
+ for (var i = 0; i < this.ranges.length; i++) {
+ var here = this$1.ranges[i], there = other.ranges[i];
+ if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false }
+ }
+ return true
+ };
+
+ Selection.prototype.deepCopy = function () {
+ var this$1 = this;
+
+ var out = [];
+ for (var i = 0; i < this.ranges.length; i++)
+ { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)); }
+ return new Selection(out, this.primIndex)
+ };
+
+ Selection.prototype.somethingSelected = function () {
+ var this$1 = this;
+
+ for (var i = 0; i < this.ranges.length; i++)
+ { if (!this$1.ranges[i].empty()) { return true } }
+ return false
+ };
+
+ Selection.prototype.contains = function (pos, end) {
+ var this$1 = this;
+
+ if (!end) { end = pos; }
+ for (var i = 0; i < this.ranges.length; i++) {
+ var range = this$1.ranges[i];
+ if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
+ { return i }
+ }
+ return -1
+ };
+
+ var Range = function(anchor, head) {
+ this.anchor = anchor; this.head = head;
+ };
+
+ Range.prototype.from = function () { return minPos(this.anchor, this.head) };
+ Range.prototype.to = function () { return maxPos(this.anchor, this.head) };
+ Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch };
+
+ // Take an unsorted, potentially overlapping set of ranges, and
+ // build a selection out of it. 'Consumes' ranges array (modifying
+ // it).
+ function normalizeSelection(cm, ranges, primIndex) {
+ var mayTouch = cm && cm.options.selectionsMayTouch;
+ var prim = ranges[primIndex];
+ ranges.sort(function (a, b) { return cmp(a.from(), b.from()); });
+ primIndex = indexOf(ranges, prim);
+ for (var i = 1; i < ranges.length; i++) {
+ var cur = ranges[i], prev = ranges[i - 1];
+ var diff = cmp(prev.to(), cur.from());
+ if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) {
+ var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());
+ var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;
+ if (i <= primIndex) { --primIndex; }
+ ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));
+ }
+ }
+ return new Selection(ranges, primIndex)
+ }
+
+ function simpleSelection(anchor, head) {
+ return new Selection([new Range(anchor, head || anchor)], 0)
+ }
+
+ // Compute the position of the end of a change (its 'to' property
+ // refers to the pre-change end).
+ function changeEnd(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))
+ }
+
+ // Adjust a position to refer to the post-change position of the
+ // same text, or the end of the change if the change covers it.
+ function adjustForChange(pos, change) {
+ if (cmp(pos, change.from) < 0) { return pos }
+ if (cmp(pos, change.to) <= 0) { return changeEnd(change) }
+
+ var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
+ if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch; }
+ return Pos(line, ch)
+ }
+
+ function computeSelAfterChange(doc, change) {
+ var out = [];
+ for (var i = 0; i < doc.sel.ranges.length; i++) {
+ var range = doc.sel.ranges[i];
+ out.push(new Range(adjustForChange(range.anchor, change),
+ adjustForChange(range.head, change)));
+ }
+ return normalizeSelection(doc.cm, out, doc.sel.primIndex)
+ }
+
+ function offsetPos(pos, old, nw) {
+ if (pos.line == old.line)
+ { return Pos(nw.line, pos.ch - old.ch + nw.ch) }
+ else
+ { return Pos(nw.line + (pos.line - old.line), pos.ch) }
+ }
+
+ // Used by replaceSelections to allow moving the selection to the
+ // start or around the replaced test. Hint may be "start" or "around".
+ function computeReplacedSel(doc, changes, hint) {
+ var out = [];
+ var oldPrev = Pos(doc.first, 0), newPrev = oldPrev;
+ for (var i = 0; i < changes.length; i++) {
+ var change = changes[i];
+ var from = offsetPos(change.from, oldPrev, newPrev);
+ var to = offsetPos(changeEnd(change), oldPrev, newPrev);
+ oldPrev = change.to;
+ newPrev = to;
+ if (hint == "around") {
+ var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0;
+ out[i] = new Range(inv ? to : from, inv ? from : to);
+ } else {
+ out[i] = new Range(from, from);
+ }
+ }
+ return new Selection(out, doc.sel.primIndex)
+ }
+
+ // Used to get the editor into a consistent state again when options change.
+
+ function loadMode(cm) {
+ cm.doc.mode = getMode(cm.options, cm.doc.modeOption);
+ resetModeState(cm);
+ }
+
+ function resetModeState(cm) {
+ cm.doc.iter(function (line) {
+ if (line.stateAfter) { line.stateAfter = null; }
+ if (line.styles) { line.styles = null; }
+ });
+ cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first;
+ startWorker(cm, 100);
+ cm.state.modeGen++;
+ if (cm.curOp) { regChange(cm); }
+ }
+
+ // DOCUMENT DATA STRUCTURE
+
+ // By default, updates that start and end at the beginning of a line
+ // are treated specially, in order to make the association of line
+ // widgets and marker elements with the text behave more intuitive.
+ function isWholeLineUpdate(doc, change) {
+ return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" &&
+ (!doc.cm || doc.cm.options.wholeLineUpdateBefore)
+ }
+
+ // Perform a change on the document data structure.
+ function updateDoc(doc, change, markedSpans, estimateHeight$$1) {
+ function spansFor(n) {return markedSpans ? markedSpans[n] : null}
+ function update(line, text, spans) {
+ updateLine(line, text, spans, estimateHeight$$1);
+ signalLater(line, "change", line, change);
+ }
+ function linesFor(start, end) {
+ var result = [];
+ for (var i = start; i < end; ++i)
+ { result.push(new Line(text[i], spansFor(i), estimateHeight$$1)); }
+ return result
+ }
+
+ 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;
+
+ // Adjust the line structure
+ if (change.full) {
+ doc.insert(0, linesFor(0, text.length));
+ doc.remove(text.length, doc.size - text.length);
+ } else if (isWholeLineUpdate(doc, change)) {
+ // This is a whole-line replace. Treated specially to make
+ // sure line objects move the way they are supposed to.
+ var added = linesFor(0, text.length - 1);
+ 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 {
+ var added$1 = linesFor(1, text.length - 1);
+ added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight$$1));
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
+ doc.insert(from.line + 1, added$1);
+ }
+ } 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);
+ var added$2 = linesFor(1, text.length - 1);
+ if (nlines > 1) { doc.remove(from.line + 1, nlines - 1); }
+ doc.insert(from.line + 1, added$2);
+ }
+
+ signalLater(doc, "change", doc, change);
+ }
+
+ // Call f for all linked documents.
+ 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);
+ }
+
+ // Attach a document to an editor.
+ 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);
+ setDirectionClass(cm);
+ if (!cm.options.lineWrapping) { findMaxLine(cm); }
+ cm.options.mode = doc.modeOption;
+ regChange(cm);
+ }
+
+ function setDirectionClass(cm) {
+ (cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl");
+ }
+
+ function directionChanged(cm) {
+ runInOp(cm, function () {
+ setDirectionClass(cm);
+ regChange(cm);
+ });
+ }
+
+ function History(startGen) {
+ // Arrays of change events and selections. Doing something adds an
+ // event to done and clears undo. Undoing moves events from done
+ // to undone, redoing moves them in the other direction.
+ this.done = []; this.undone = [];
+ this.undoDepth = Infinity;
+ // Used to track when changes can be merged into a single undo
+ // event
+ this.lastModTime = this.lastSelTime = 0;
+ this.lastOp = this.lastSelOp = null;
+ this.lastOrigin = this.lastSelOrigin = null;
+ // Used by the isClean() method
+ this.generation = this.maxGeneration = startGen || 1;
+ }
+
+ // Create a history change event from an updateDoc-style change
+ // object.
+ function historyChangeFromChange(doc, change) {
+ var histChange = {from: copyPos(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) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true);
+ return histChange
+ }
+
+ // Pop all selection events off the end of a history array. Stop at
+ // a change event.
+ function clearSelectionEvents(array) {
+ while (array.length) {
+ var last = lst(array);
+ if (last.ranges) { array.pop(); }
+ else { break }
+ }
+ }
+
+ // Find the top change event in the history. Pop off selection
+ // events that are in the way.
+ function lastChangeEvent(hist, force) {
+ if (force) {
+ clearSelectionEvents(hist.done);
+ return lst(hist.done)
+ } else if (hist.done.length && !lst(hist.done).ranges) {
+ return lst(hist.done)
+ } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {
+ hist.done.pop();
+ return lst(hist.done)
+ }
+ }
+
+ // Register a change in the history. Merges changes that are within
+ // a single operation, or are close together with an origin that
+ // allows merging (starting with "+") into a single event.
+ function addChangeToHistory(doc, change, selAfter, opId) {
+ var hist = doc.history;
+ hist.undone.length = 0;
+ var time = +new Date, cur;
+ var last;
+
+ if ((hist.lastOp == opId ||
+ hist.lastOrigin == change.origin && change.origin &&
+ ((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) ||
+ change.origin.charAt(0) == "*")) &&
+ (cur = lastChangeEvent(hist, hist.lastOp == opId))) {
+ // Merge this change into the last event
+ last = lst(cur.changes);
+ if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {
+ // 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));
+ }
+ } else {
+ // Can not be merged, start a new event.
+ var before = lst(hist.done);
+ if (!before || !before.ranges)
+ { pushSelectionToHistory(doc.sel, hist.done); }
+ cur = {changes: [historyChangeFromChange(doc, change)],
+ generation: hist.generation};
+ hist.done.push(cur);
+ while (hist.done.length > hist.undoDepth) {
+ hist.done.shift();
+ if (!hist.done[0].ranges) { hist.done.shift(); }
+ }
+ }
+ hist.done.push(selAfter);
+ hist.generation = ++hist.maxGeneration;
+ hist.lastModTime = hist.lastSelTime = time;
+ hist.lastOp = hist.lastSelOp = opId;
+ hist.lastOrigin = hist.lastSelOrigin = change.origin;
+
+ if (!last) { signal(doc, "historyAdded"); }
+ }
+
+ function selectionEventCanBeMerged(doc, origin, prev, sel) {
+ var ch = origin.charAt(0);
+ return ch == "*" ||
+ ch == "+" &&
+ prev.ranges.length == sel.ranges.length &&
+ prev.somethingSelected() == sel.somethingSelected() &&
+ new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500)
+ }
+
+ // Called whenever the selection changes, sets the new selection as
+ // the pending selection in the history, and pushes the old pending
+ // selection into the 'done' array when it was significantly
+ // different (in number of selected ranges, emptiness, or time).
+ function addSelectionToHistory(doc, sel, opId, options) {
+ var hist = doc.history, origin = options && options.origin;
+
+ // A new event is started when the previous origin does not match
+ // the current, or the origins don't allow matching. Origins
+ // starting with * are always merged, those starting with + are
+ // merged when similar and close together in time.
+ if (opId == hist.lastSelOp ||
+ (origin && hist.lastSelOrigin == origin &&
+ (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
+ selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
+ { hist.done[hist.done.length - 1] = sel; }
+ else
+ { pushSelectionToHistory(sel, hist.done); }
+
+ hist.lastSelTime = +new Date;
+ hist.lastSelOrigin = origin;
+ hist.lastSelOp = opId;
+ if (options && options.clearRedo !== false)
+ { clearSelectionEvents(hist.undone); }
+ }
+
+ function pushSelectionToHistory(sel, dest) {
+ var top = lst(dest);
+ if (!(top && top.ranges && top.equals(sel)))
+ { dest.push(sel); }
+ }
+
+ // Used to store marked span information in the history.
+ 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;
+ });
+ }
+
+ // When un/re-doing restores text containing marked spans, those
+ // that have been explicitly cleared should not be restored.
+ function removeClearedSpans(spans) {
+ if (!spans) { return null }
+ var out;
+ for (var i = 0; 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
+ }
+
+ // Retrieve and filter the old marked spans stored in a change event.
+ function getOldSpans(doc, change) {
+ var found = change["spans_" + doc.id];
+ if (!found) { return null }
+ var nw = [];
+ for (var i = 0; i < change.text.length; ++i)
+ { nw.push(removeClearedSpans(found[i])); }
+ return nw
+ }
+
+ // Used for un/re-doing changes from the history. Combines the
+ // result of computing the existing spans with the set of spans that
+ // existed in the history (so that deleting around a span and then
+ // undoing brings back the span).
+ 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
+ }
+
+ // 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, instantiateSel) {
+ var copy = [];
+ for (var i = 0; i < events.length; ++i) {
+ var event = events[i];
+ if (event.ranges) {
+ copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event);
+ continue
+ }
+ var changes = event.changes, newChanges = [];
+ copy.push({changes: newChanges});
+ for (var j = 0; j < changes.length; ++j) {
+ var change = changes[j], m = (void 0);
+ 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
+ }
+
+ // The 'scroll' parameter given to many of these indicated whether
+ // the new cursor position should be scrolled into view after
+ // modifying the selection.
+
+ // If shift is held or the extend flag is set, extends a range to
+ // include a given position (and optionally a second position).
+ // Otherwise, simply returns the range between the given positions.
+ // Used for cursor motion and such.
+ function extendRange(range, head, other, extend) {
+ if (extend) {
+ var anchor = range.anchor;
+ if (other) {
+ var posBefore = cmp(head, anchor) < 0;
+ if (posBefore != (cmp(other, anchor) < 0)) {
+ anchor = head;
+ head = other;
+ } else if (posBefore != (cmp(head, other) < 0)) {
+ head = other;
+ }
+ }
+ return new Range(anchor, head)
+ } else {
+ return new Range(other || head, head)
+ }
+ }
+
+ // Extend the primary selection range, discard the rest.
+ function extendSelection(doc, head, other, options, extend) {
+ if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend); }
+ setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options);
+ }
+
+ // Extend all selections (pos is an array of selections with length
+ // equal the number of selections)
+ function extendSelections(doc, heads, options) {
+ var out = [];
+ var extend = doc.cm && (doc.cm.display.shift || doc.extend);
+ for (var i = 0; i < doc.sel.ranges.length; i++)
+ { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); }
+ var newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex);
+ setSelection(doc, newSel, options);
+ }
+
+ // Updates a single range in the selection.
+ function replaceOneSelection(doc, i, range, options) {
+ var ranges = doc.sel.ranges.slice(0);
+ ranges[i] = range;
+ setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options);
+ }
+
+ // Reset the selection to a single range.
+ function setSimpleSelection(doc, anchor, head, options) {
+ setSelection(doc, simpleSelection(anchor, head), options);
+ }
+
+ // Give beforeSelectionChange handlers a change to influence a
+ // selection update.
+ function filterSelectionChange(doc, sel, options) {
+ var obj = {
+ ranges: sel.ranges,
+ update: function(ranges) {
+ var this$1 = this;
+
+ this.ranges = [];
+ for (var i = 0; i < ranges.length; i++)
+ { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
+ clipPos(doc, ranges[i].head)); }
+ },
+ origin: options && options.origin
+ };
+ signal(doc, "beforeSelectionChange", doc, obj);
+ if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj); }
+ if (obj.ranges != sel.ranges) { return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) }
+ else { return sel }
+ }
+
+ function setSelectionReplaceHistory(doc, sel, options) {
+ var done = doc.history.done, last = lst(done);
+ if (last && last.ranges) {
+ done[done.length - 1] = sel;
+ setSelectionNoUndo(doc, sel, options);
+ } else {
+ setSelection(doc, sel, options);
+ }
+ }
+
+ // Set a new selection.
+ function setSelection(doc, sel, options) {
+ setSelectionNoUndo(doc, sel, options);
+ addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);
+ }
+
+ function setSelectionNoUndo(doc, sel, options) {
+ if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
+ { sel = filterSelectionChange(doc, sel, options); }
+
+ var bias = options && options.bias ||
+ (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
+ setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
+
+ if (!(options && options.scroll === false) && doc.cm)
+ { ensureCursorVisible(doc.cm); }
+ }
+
+ function setSelectionInner(doc, sel) {
+ if (sel.equals(doc.sel)) { return }
+
+ doc.sel = sel;
+
+ if (doc.cm) {
+ doc.cm.curOp.updateInput = 1;
+ doc.cm.curOp.selectionChanged = true;
+ signalCursorActivity(doc.cm);
+ }
+ signalLater(doc, "cursorActivity", doc);
+ }
+
+ // Verify that the selection does not partially select any atomic
+ // marked ranges.
+ function reCheckSelection(doc) {
+ setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false));
+ }
+
+ // Return a selection that does not partially select any atomic
+ // ranges.
+ function skipAtomicInSelection(doc, sel, bias, mayClear) {
+ var out;
+ for (var i = 0; i < sel.ranges.length; i++) {
+ var range = sel.ranges[i];
+ var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];
+ var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);
+ var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);
+ if (out || newAnchor != range.anchor || newHead != range.head) {
+ if (!out) { out = sel.ranges.slice(0, i); }
+ out[i] = new Range(newAnchor, newHead);
+ }
+ }
+ return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel
+ }
+
+ function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
+ var line = getLine(doc, pos.line);
+ if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {
+ var sp = line.markedSpans[i], m = sp.marker;
+
+ // Determine if we should prevent the cursor being placed to the left/right of an atomic marker
+ // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it
+ // is with selectLeft/Right
+ var preventCursorLeft = ("selectLeft" in m) ? !m.selectLeft : m.inclusiveLeft;
+ var preventCursorRight = ("selectRight" in m) ? !m.selectRight : m.inclusiveRight;
+
+ if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
+ (sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) {
+ if (mayClear) {
+ signal(m, "beforeCursorEnter");
+ if (m.explicitlyCleared) {
+ if (!line.markedSpans) { break }
+ else {--i; continue}
+ }
+ }
+ if (!m.atomic) { continue }
+
+ if (oldPos) {
+ var near = m.find(dir < 0 ? 1 : -1), diff = (void 0);
+ if (dir < 0 ? preventCursorRight : preventCursorLeft)
+ { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); }
+ if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
+ { return skipAtomicInner(doc, near, pos, dir, mayClear) }
+ }
+
+ var far = m.find(dir < 0 ? -1 : 1);
+ if (dir < 0 ? preventCursorLeft : preventCursorRight)
+ { far = movePos(doc, far, dir, far.line == pos.line ? line : null); }
+ return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null
+ }
+ } }
+ return pos
+ }
+
+ // Ensure a given position is not inside an atomic range.
+ function skipAtomic(doc, pos, oldPos, bias, mayClear) {
+ var dir = bias || 1;
+ var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
+ (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
+ skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
+ (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));
+ if (!found) {
+ doc.cantEdit = true;
+ return Pos(doc.first, 0)
+ }
+ return found
+ }
+
+ function movePos(doc, pos, dir, line) {
+ if (dir < 0 && pos.ch == 0) {
+ if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) }
+ else { return null }
+ } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
+ if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) }
+ else { return null }
+ } else {
+ return new Pos(pos.line, pos.ch + dir)
+ }
+ }
+
+ function selectAll(cm) {
+ cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);
+ }
+
+ // UPDATING
+
+ // Allow "beforeChange" event handlers to influence a change
+ function filterChange(doc, change, update) {
+ var obj = {
+ canceled: false,
+ from: change.from,
+ to: change.to,
+ text: change.text,
+ origin: change.origin,
+ cancel: function () { return obj.canceled = true; }
+ };
+ if (update) { obj.update = function (from, to, text, origin) {
+ if (from) { obj.from = clipPos(doc, from); }
+ if (to) { obj.to = clipPos(doc, to); }
+ if (text) { obj.text = text; }
+ if (origin !== undefined) { obj.origin = origin; }
+ }; }
+ signal(doc, "beforeChange", doc, obj);
+ if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj); }
+
+ if (obj.canceled) {
+ if (doc.cm) { doc.cm.curOp.updateInput = 2; }
+ return null
+ }
+ return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}
+ }
+
+ // Apply a change to a document, and add it to the document's
+ // history, and propagating it to all linked documents.
+ function makeChange(doc, change, ignoreReadOnly) {
+ if (doc.cm) {
+ if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, 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 >= 0; --i)
+ { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}); }
+ } else {
+ makeChangeInner(doc, change);
+ }
+ }
+
+ function makeChangeInner(doc, change) {
+ if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return }
+ var selAfter = computeSelAfterChange(doc, change);
+ addChangeToHistory(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));
+ });
+ }
+
+ // Revert a change stored in a document's history.
+ function makeChangeFromHistory(doc, type, allowSelectionOnly) {
+ var suppress = doc.cm && doc.cm.state.suppressEdits;
+ if (suppress && !allowSelectionOnly) { return }
+
+ var hist = doc.history, event, selAfter = doc.sel;
+ var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done;
+
+ // Verify that there is a useable event (so that ctrl-z won't
+ // needlessly clear selection events)
+ var i = 0;
+ for (; i < source.length; i++) {
+ event = source[i];
+ if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)
+ { break }
+ }
+ if (i == source.length) { return }
+ hist.lastOrigin = hist.lastSelOrigin = null;
+
+ for (;;) {
+ event = source.pop();
+ if (event.ranges) {
+ pushSelectionToHistory(event, dest);
+ if (allowSelectionOnly && !event.equals(doc.sel)) {
+ setSelection(doc, event, {clearRedo: false});
+ return
+ }
+ selAfter = event;
+ } else if (suppress) {
+ source.push(event);
+ return
+ } else { break }
+ }
+
+ // Build up a reverse change object to add to the opposite history
+ // stack (redo when undoing, and vice versa).
+ var antiChanges = [];
+ pushSelectionToHistory(selAfter, dest);
+ dest.push({changes: antiChanges, generation: hist.generation});
+ hist.generation = event.generation || ++hist.maxGeneration;
+
+ var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
+
+ var loop = function ( i ) {
+ var change = event.changes[i];
+ change.origin = type;
+ if (filter && !filterChange(doc, change, false)) {
+ source.length = 0;
+ return {}
+ }
+
+ antiChanges.push(historyChangeFromChange(doc, change));
+
+ var after = i ? computeSelAfterChange(doc, change) : lst(source);
+ makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
+ if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); }
+ var rebased = [];
+
+ // Propagate to the linked documents
+ 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));
+ });
+ };
+
+ for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) {
+ var returned = loop( i$1 );
+
+ if ( returned ) return returned.v;
+ }
+ }
+
+ // Sub-views need their line numbers shifted when text is added
+ // above or below them in the parent document.
+ function shiftDoc(doc, distance) {
+ if (distance == 0) { return }
+ doc.first += distance;
+ doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range(
+ Pos(range.anchor.line + distance, range.anchor.ch),
+ Pos(range.head.line + distance, range.head.ch)
+ ); }), doc.sel.primIndex);
+ if (doc.cm) {
+ regChange(doc.cm, doc.first, doc.first - distance, distance);
+ for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)
+ { regLineChange(doc.cm, l, "gutter"); }
+ }
+ }
+
+ // More lower-level change function, handling only a single document
+ // (not linked ones).
+ 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); }
+ if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); }
+ else { updateDoc(doc, change, spans); }
+ setSelectionNoUndo(doc, selAfter, sel_dontScroll);
+ }
+
+ // Handle the interaction of a change to a document with the editor
+ // that this document is part of.
+ function makeChangeSingleDocInEditor(cm, change, spans) {
+ 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(getLine(doc, from.line)));
+ doc.iter(checkWidthStart, to.line + 1, function (line) {
+ if (line == display.maxLine) {
+ recomputeMaxLength = true;
+ return true
+ }
+ });
+ }
+
+ if (doc.sel.contains(change.from, change.to) > -1)
+ { signalCursorActivity(cm); }
+
+ updateDoc(doc, change, spans, estimateHeight(cm));
+
+ if (!cm.options.lineWrapping) {
+ doc.iter(checkWidthStart, from.line + change.text.length, function (line) {
+ var len = lineLength(line);
+ if (len > display.maxLineLength) {
+ display.maxLine = line;
+ display.maxLineLength = len;
+ display.maxLineChanged = true;
+ recomputeMaxLength = false;
+ }
+ });
+ if (recomputeMaxLength) { cm.curOp.updateMaxLine = true; }
+ }
+
+ retreatFrontier(doc, from.line);
+ startWorker(cm, 400);
+
+ var lendiff = change.text.length - (to.line - from.line) - 1;
+ // Remember that these lines changed, for updating the display
+ if (change.full)
+ { regChange(cm); }
+ else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
+ { regLineChange(cm, from.line, "text"); }
+ else
+ { regChange(cm, from.line, to.line + 1, lendiff); }
+
+ var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change");
+ if (changeHandler || changesHandler) {
+ var obj = {
+ from: from, to: to,
+ text: change.text,
+ removed: change.removed,
+ origin: change.origin
+ };
+ if (changeHandler) { signalLater(cm, "change", cm, obj); }
+ if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); }
+ }
+ cm.display.selForContextMenu = null;
+ }
+
+ function replaceRange(doc, code, from, to, origin) {
+ var assign;
+
+ if (!to) { to = from; }
+ if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); }
+ if (typeof code == "string") { code = doc.splitLines(code); }
+ makeChange(doc, {from: from, to: to, text: code, origin: origin});
+ }
+
+ // Rebasing/resetting history to deal with externally-sourced changes
+
+ function rebaseHistSelSingle(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;
+ if (sub.ranges) {
+ if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; }
+ for (var j = 0; j < sub.ranges.length; j++) {
+ rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff);
+ rebaseHistSelSingle(sub.ranges[j].head, from, to, diff);
+ }
+ continue
+ }
+ for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) {
+ var cur = sub.changes[j$1];
+ if (to < cur.from.line) {
+ cur.from = Pos(cur.from.line + diff, cur.from.ch);
+ cur.to = Pos(cur.to.line + diff, cur.to.ch);
+ } else if (from <= cur.to.line) {
+ ok = false;
+ break
+ }
+ }
+ if (!ok) {
+ array.splice(0, i + 1);
+ i = 0;
+ }
+ }
+ }
+
+ 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);
+ }
+
+ // Utility for applying a change to a line by handle or number,
+ // returning the number and optionally registering the line as
+ // changed.
+ function changeLine(doc, handle, changeType, op) {
+ var no = handle, line = handle;
+ if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)); }
+ else { no = lineNo(handle); }
+ if (no == null) { return null }
+ if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType); }
+ return line
+ }
+
+ // The document is represented as a BTree consisting of leaves, with
+ // chunk of lines in them, and branches, with up to ten leaves or
+ // other branch nodes below them. The top node is always a branch
+ // node, and is the document object itself (meaning it has
+ // additional methods and properties).
+ //
+ // All nodes have parent links. The tree is used both to go from
+ // line numbers to line objects, and to go from objects to numbers.
+ // It also indexes by height, and is used to convert between height
+ // and line object, and to find the total height of the document.
+ //
+ // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
+
+ function LeafChunk(lines) {
+ var this$1 = this;
+
+ this.lines = lines;
+ this.parent = null;
+ var height = 0;
+ for (var i = 0; i < lines.length; ++i) {
+ lines[i].parent = this$1;
+ height += lines[i].height;
+ }
+ this.height = height;
+ }
+
+ LeafChunk.prototype = {
+ chunkSize: function() { return this.lines.length },
+
+ // Remove the n lines at offset 'at'.
+ removeInner: function(at, n) {
+ var this$1 = this;
+
+ for (var i = at, e = at + n; i < e; ++i) {
+ var line = this$1.lines[i];
+ this$1.height -= line.height;
+ cleanUpLine(line);
+ signalLater(line, "delete");
+ }
+ this.lines.splice(at, n);
+ },
+
+ // Helper used to collapse a small branch into a single leaf.
+ collapse: function(lines) {
+ lines.push.apply(lines, this.lines);
+ },
+
+ // Insert the given array of lines at offset 'at', count them as
+ // having the given height.
+ insertInner: function(at, lines, height) {
+ var this$1 = this;
+
+ this.height += height;
+ this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
+ for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1; }
+ },
+
+ // Used to iterate over a part of the tree.
+ iterN: function(at, n, op) {
+ var this$1 = this;
+
+ for (var e = at + n; at < e; ++at)
+ { if (op(this$1.lines[at])) { return true } }
+ }
+ };
+
+ function BranchChunk(children) {
+ var this$1 = this;
+
+ this.children = children;
+ var size = 0, height = 0;
+ for (var i = 0; i < children.length; ++i) {
+ var ch = children[i];
+ size += ch.chunkSize(); height += ch.height;
+ ch.parent = this$1;
+ }
+ this.size = size;
+ this.height = height;
+ this.parent = null;
+ }
+
+ BranchChunk.prototype = {
+ chunkSize: function() { return this.size },
+
+ removeInner: function(at, n) {
+ var this$1 = this;
+
+ this.size -= n;
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this$1.children[i], sz = child.chunkSize();
+ if (at < sz) {
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
+ child.removeInner(at, rm);
+ this$1.height -= oldHeight - child.height;
+ if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null; }
+ if ((n -= rm) == 0) { break }
+ at = 0;
+ } else { at -= sz; }
+ }
+ // If the result is smaller than 25 lines, ensure that it is a
+ // single leaf node.
+ if (this.size - n < 25 &&
+ (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {
+ var lines = [];
+ this.collapse(lines);
+ this.children = [new LeafChunk(lines)];
+ this.children[0].parent = this;
+ }
+ },
+
+ collapse: function(lines) {
+ var this$1 = this;
+
+ for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines); }
+ },
+
+ insertInner: function(at, lines, height) {
+ var this$1 = this;
+
+ this.size += lines.length;
+ this.height += height;
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this$1.children[i], sz = child.chunkSize();
+ if (at <= sz) {
+ child.insertInner(at, lines, height);
+ if (child.lines && child.lines.length > 50) {
+ // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced.
+ // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest.
+ var remaining = child.lines.length % 25 + 25;
+ for (var pos = remaining; pos < child.lines.length;) {
+ var leaf = new LeafChunk(child.lines.slice(pos, pos += 25));
+ child.height -= leaf.height;
+ this$1.children.splice(++i, 0, leaf);
+ leaf.parent = this$1;
+ }
+ child.lines = child.lines.slice(0, remaining);
+ this$1.maybeSpill();
+ }
+ break
+ }
+ at -= sz;
+ }
+ },
+
+ // When a node has grown, check whether it should be split.
+ 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) {
+ var this$1 = this;
+
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this$1.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; }
+ }
+ }
+ };
+
+ // Line widgets are block elements displayed above or below a line.
+
+ var LineWidget = function(doc, node, options) {
+ var this$1 = this;
+
+ if (options) { for (var opt in options) { if (options.hasOwnProperty(opt))
+ { this$1[opt] = options[opt]; } } }
+ this.doc = doc;
+ this.node = node;
+ };
+
+ LineWidget.prototype.clear = function () {
+ var this$1 = this;
+
+ var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
+ if (no == null || !ws) { return }
+ for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1); } }
+ if (!ws.length) { line.widgets = null; }
+ var height = widgetHeight(this);
+ updateLineHeight(line, Math.max(0, line.height - height));
+ if (cm) {
+ runInOp(cm, function () {
+ adjustScrollWhenAboveVisible(cm, line, -height);
+ regLineChange(cm, no, "widget");
+ });
+ signalLater(cm, "lineWidgetCleared", cm, this, no);
+ }
+ };
+
+ LineWidget.prototype.changed = function () {
+ var this$1 = this;
+
+ var oldH = this.height, cm = this.doc.cm, line = this.line;
+ this.height = null;
+ var diff = widgetHeight(this) - oldH;
+ if (!diff) { return }
+ if (!lineIsHidden(this.doc, line)) { updateLineHeight(line, line.height + diff); }
+ if (cm) {
+ runInOp(cm, function () {
+ cm.curOp.forceUpdate = true;
+ adjustScrollWhenAboveVisible(cm, line, diff);
+ signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line));
+ });
+ }
+ };
+ eventMixin(LineWidget);
+
+ function adjustScrollWhenAboveVisible(cm, line, diff) {
+ if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
+ { addToScrollTop(cm, diff); }
+ }
+
+ function addLineWidget(doc, handle, node, options) {
+ var widget = new LineWidget(doc, node, options);
+ var cm = doc.cm;
+ if (cm && widget.noHScroll) { cm.display.alignWidgets = true; }
+ changeLine(doc, handle, "widget", function (line) {
+ var widgets = line.widgets || (line.widgets = []);
+ if (widget.insertAt == null) { widgets.push(widget); }
+ else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); }
+ widget.line = line;
+ if (cm && !lineIsHidden(doc, line)) {
+ var aboveVisible = heightAtLine(line) < doc.scrollTop;
+ updateLineHeight(line, line.height + widgetHeight(widget));
+ if (aboveVisible) { addToScrollTop(cm, widget.height); }
+ cm.curOp.forceUpdate = true;
+ }
+ return true
+ });
+ if (cm) { signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)); }
+ return widget
+ }
+
+ // TEXTMARKERS
+
+ // Created with markText and setBookmark methods. A TextMarker is a
+ // handle that can be used to clear or find a marked position in the
+ // document. Line objects hold arrays (markedSpans) containing
+ // {from, to, marker} object pointing to such marker objects, and
+ // indicating that such a marker is present on that line. Multiple
+ // lines may point to the same marker when it spans across lines.
+ // The spans will have null for their from/to properties when the
+ // marker continues beyond the start/end of the line. Markers have
+ // links back to the lines they currently touch.
+
+ // Collapsed markers have unique ids, in order to be able to order
+ // them, which is needed for uniquely determining an outer marker
+ // when they overlap (they may nest, but not partially overlap).
+ var nextMarkerId = 0;
+
+ var TextMarker = function(doc, type) {
+ this.lines = [];
+ this.type = type;
+ this.doc = doc;
+ this.id = ++nextMarkerId;
+ };
+
+ // Clear the marker.
+ TextMarker.prototype.clear = function () {
+ var this$1 = this;
+
+ if (this.explicitlyCleared) { return }
+ var cm = this.doc.cm, withOp = cm && !cm.curOp;
+ if (withOp) { startOperation(cm); }
+ if (hasHandler(this, "clear")) {
+ var found = this.find();
+ if (found) { signalLater(this, "clear", found.from, found.to); }
+ }
+ var min = null, max = null;
+ for (var i = 0; i < this.lines.length; ++i) {
+ var line = this$1.lines[i];
+ var span = getMarkedSpanFor(line.markedSpans, this$1);
+ if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text"); }
+ else if (cm) {
+ if (span.to != null) { max = lineNo(line); }
+ if (span.from != null) { min = lineNo(line); }
+ }
+ line.markedSpans = removeMarkedSpan(line.markedSpans, span);
+ if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm)
+ { updateLineHeight(line, textHeight(cm.display)); }
+ }
+ if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) {
+ var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual);
+ if (len > cm.display.maxLineLength) {
+ cm.display.maxLine = visual;
+ cm.display.maxLineLength = len;
+ cm.display.maxLineChanged = true;
+ }
+ } }
+
+ if (min != null && cm && this.collapsed) { 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.doc); }
+ }
+ if (cm) { signalLater(cm, "markerCleared", cm, this, min, max); }
+ if (withOp) { endOperation(cm); }
+ if (this.parent) { this.parent.clear(); }
+ };
+
+ // Find the position of the marker in the document. Returns a {from,
+ // to} object by default. Side can be passed to get a specific side
+ // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
+ // Pos objects returned contain a line object, rather than a line
+ // number (used to prevent looking up the same line twice).
+ TextMarker.prototype.find = function (side, lineObj) {
+ var this$1 = this;
+
+ if (side == null && this.type == "bookmark") { side = 1; }
+ var from, to;
+ for (var i = 0; i < this.lines.length; ++i) {
+ var line = this$1.lines[i];
+ var span = getMarkedSpanFor(line.markedSpans, this$1);
+ if (span.from != null) {
+ from = Pos(lineObj ? line : lineNo(line), span.from);
+ if (side == -1) { return from }
+ }
+ if (span.to != null) {
+ to = Pos(lineObj ? line : lineNo(line), span.to);
+ if (side == 1) { return to }
+ }
+ }
+ return from && {from: from, to: to}
+ };
+
+ // Signals that the marker's widget changed, and surrounding layout
+ // should be recomputed.
+ TextMarker.prototype.changed = function () {
+ var this$1 = this;
+
+ var pos = this.find(-1, true), widget = this, cm = this.doc.cm;
+ if (!pos || !cm) { return }
+ runInOp(cm, function () {
+ var line = pos.line, lineN = lineNo(pos.line);
+ var view = findViewForLine(cm, lineN);
+ if (view) {
+ clearLineMeasurementCacheFor(view);
+ cm.curOp.selectionChanged = cm.curOp.forceUpdate = true;
+ }
+ cm.curOp.updateMaxLine = true;
+ if (!lineIsHidden(widget.doc, line) && widget.height != null) {
+ var oldHeight = widget.height;
+ widget.height = null;
+ var dHeight = widgetHeight(widget) - oldHeight;
+ if (dHeight)
+ { updateLineHeight(line, line.height + dHeight); }
+ }
+ signalLater(cm, "markerChanged", cm, this$1);
+ });
+ };
+
+ 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);
+ }
+ };
+ eventMixin(TextMarker);
+
+ // Create a marker, wire it up to the right lines, and
+ function markText(doc, from, to, options, type) {
+ // Shared markers (across linked documents) are handled separately
+ // (markTextShared will call out to this again, once per
+ // document).
+ if (options && options.shared) { return markTextShared(doc, from, to, options, type) }
+ // Ensure we are in an operation.
+ if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) }
+
+ var marker = new TextMarker(doc, type), diff = cmp(from, to);
+ if (options) { copyObj(options, marker, false); }
+ // Don't connect empty markers unless clearWhenEmpty is false
+ if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
+ { return marker }
+ if (marker.replacedWith) {
+ // Showing up as a widget implies collapsed (widget replaces text)
+ marker.collapsed = true;
+ marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget");
+ if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true"); }
+ if (options.insertLeft) { marker.widgetNode.insertLeft = true; }
+ }
+ if (marker.collapsed) {
+ if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
+ from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
+ { throw new Error("Inserting collapsed marker partially overlapping an existing one") }
+ seeCollapsedSpans();
+ }
+
+ if (marker.addToHistory)
+ { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); }
+
+ var curLine = from.line, cm = doc.cm, updateMaxLine;
+ doc.iter(curLine, to.line + 1, function (line) {
+ if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)
+ { updateMaxLine = true; }
+ if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); }
+ addMarkedSpan(line, new MarkedSpan(marker,
+ curLine == from.line ? from.ch : null,
+ curLine == to.line ? to.ch : null));
+ ++curLine;
+ });
+ // lineIsHidden depends on the presence of the spans, so needs a second pass
+ 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 () { return marker.clear(); }); }
+
+ if (marker.readOnly) {
+ seeReadOnlySpans();
+ if (doc.history.done.length || doc.history.undone.length)
+ { doc.clearHistory(); }
+ }
+ if (marker.collapsed) {
+ marker.id = ++nextMarkerId;
+ marker.atomic = true;
+ }
+ if (cm) {
+ // Sync editor state
+ if (updateMaxLine) { cm.curOp.updateMaxLine = true; }
+ if (marker.collapsed)
+ { regChange(cm, from.line, to.line + 1); }
+ else if (marker.className || marker.startStyle || marker.endStyle || marker.css ||
+ marker.attributes || marker.title)
+ { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text"); } }
+ if (marker.atomic) { reCheckSelection(cm.doc); }
+ signalLater(cm, "markerAdded", cm, marker);
+ }
+ return marker
+ }
+
+ // SHARED TEXTMARKERS
+
+ // A shared marker spans multiple linked documents. It is
+ // implemented as a meta-marker-object controlling multiple normal
+ // markers.
+ var SharedTextMarker = function(markers, primary) {
+ var this$1 = this;
+
+ this.markers = markers;
+ this.primary = primary;
+ for (var i = 0; i < markers.length; ++i)
+ { markers[i].parent = this$1; }
+ };
+
+ SharedTextMarker.prototype.clear = function () {
+ var this$1 = this;
+
+ if (this.explicitlyCleared) { return }
+ this.explicitlyCleared = true;
+ for (var i = 0; i < this.markers.length; ++i)
+ { this$1.markers[i].clear(); }
+ signalLater(this, "clear");
+ };
+
+ SharedTextMarker.prototype.find = function (side, lineObj) {
+ return this.primary.find(side, lineObj)
+ };
+ eventMixin(SharedTextMarker);
+
+ 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.widgetNode;
+ linkedDocs(doc, function (doc) {
+ if (widget) { options.widgetNode = 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)
+ }
+
+ function findSharedMarkers(doc) {
+ return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; })
+ }
+
+ function copySharedMarkers(doc, markers) {
+ for (var i = 0; i < markers.length; i++) {
+ var marker = markers[i], pos = marker.find();
+ var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to);
+ if (cmp(mFrom, mTo)) {
+ var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type);
+ marker.markers.push(subMark);
+ subMark.parent = marker;
+ }
+ }
+ }
+
+ function detachSharedMarkers(markers) {
+ var loop = function ( i ) {
+ var marker = markers[i], linked = [marker.primary.doc];
+ linkedDocs(marker.primary.doc, function (d) { return linked.push(d); });
+ for (var j = 0; j < marker.markers.length; j++) {
+ var subMarker = marker.markers[j];
+ if (indexOf(linked, subMarker.doc) == -1) {
+ subMarker.parent = null;
+ marker.markers.splice(j--, 1);
+ }
+ }
+ };
+
+ for (var i = 0; i < markers.length; i++) loop( i );
+ }
+
+ var nextDocId = 0;
+ var Doc = function(text, mode, firstLine, lineSep, direction) {
+ if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) }
+ if (firstLine == null) { firstLine = 0; }
+
+ BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
+ this.first = firstLine;
+ this.scrollTop = this.scrollLeft = 0;
+ this.cantEdit = false;
+ this.cleanGeneration = 1;
+ this.modeFrontier = this.highlightFrontier = firstLine;
+ var start = Pos(firstLine, 0);
+ this.sel = simpleSelection(start);
+ this.history = new History(null);
+ this.id = ++nextDocId;
+ this.modeOption = mode;
+ this.lineSep = lineSep;
+ this.direction = (direction == "rtl") ? "rtl" : "ltr";
+ this.extend = false;
+
+ if (typeof text == "string") { text = this.splitLines(text); }
+ updateDoc(this, {from: start, to: start, text: text});
+ setSelection(this, simpleSelection(start), sel_dontScroll);
+ };
+
+ Doc.prototype = createObj(BranchChunk.prototype, {
+ constructor: Doc,
+ // Iterate over the document. Supports two forms -- with only one
+ // argument, it calls that for each line in the document. With
+ // three, it iterates over the range given by the first two (with
+ // the second being non-inclusive).
+ 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); }
+ },
+
+ // Non-public interface for adding and removing lines.
+ insert: function(at, lines) {
+ var height = 0;
+ for (var i = 0; i < lines.length; ++i) { height += lines[i].height; }
+ this.insertInner(at - this.first, lines, height);
+ },
+ remove: function(at, n) { this.removeInner(at - this.first, n); },
+
+ // From here, the methods are part of the public interface. Most
+ // are also available from CodeMirror (editor) instances.
+
+ getValue: function(lineSep) {
+ var lines = getLines(this, this.first, this.first + this.size);
+ if (lineSep === false) { return lines }
+ return lines.join(lineSep || this.lineSeparator())
+ },
+ setValue: docMethodOp(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: this.splitLines(code), origin: "setValue", full: true}, true);
+ if (this.cm) { scrollToCoords(this.cm, 0, 0); }
+ setSelection(this, simpleSelection(top), sel_dontScroll);
+ }),
+ 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 || this.lineSeparator())
+ },
+
+ getLine: function(line) {var l = this.getLineHandle(line); return l && l.text},
+
+ getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }},
+ getLineNumber: function(line) {return lineNo(line)},
+
+ getLineHandleVisualStart: function(line) {
+ if (typeof line == "number") { line = getLine(this, line); }
+ return visualLine(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 range$$1 = this.sel.primary(), pos;
+ if (start == null || start == "head") { pos = range$$1.head; }
+ else if (start == "anchor") { pos = range$$1.anchor; }
+ else if (start == "end" || start == "to" || start === false) { pos = range$$1.to(); }
+ else { pos = range$$1.from(); }
+ return pos
+ },
+ listSelections: function() { return this.sel.ranges },
+ somethingSelected: function() {return this.sel.somethingSelected()},
+
+ setCursor: docMethodOp(function(line, ch, options) {
+ setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options);
+ }),
+ setSelection: docMethodOp(function(anchor, head, options) {
+ setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options);
+ }),
+ extendSelection: docMethodOp(function(head, other, options) {
+ extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
+ }),
+ extendSelections: docMethodOp(function(heads, options) {
+ extendSelections(this, clipPosArray(this, heads), options);
+ }),
+ extendSelectionsBy: docMethodOp(function(f, options) {
+ var heads = map(this.sel.ranges, f);
+ extendSelections(this, clipPosArray(this, heads), options);
+ }),
+ setSelections: docMethodOp(function(ranges, primary, options) {
+ var this$1 = this;
+
+ if (!ranges.length) { return }
+ var out = [];
+ for (var i = 0; i < ranges.length; i++)
+ { out[i] = new Range(clipPos(this$1, ranges[i].anchor),
+ clipPos(this$1, ranges[i].head)); }
+ if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); }
+ setSelection(this, normalizeSelection(this.cm, out, primary), options);
+ }),
+ addSelection: docMethodOp(function(anchor, head, options) {
+ var ranges = this.sel.ranges.slice(0);
+ ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)));
+ setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options);
+ }),
+
+ getSelection: function(lineSep) {
+ var this$1 = this;
+
+ var ranges = this.sel.ranges, lines;
+ for (var i = 0; i < ranges.length; i++) {
+ var sel = getBetween(this$1, ranges[i].from(), ranges[i].to());
+ lines = lines ? lines.concat(sel) : sel;
+ }
+ if (lineSep === false) { return lines }
+ else { return lines.join(lineSep || this.lineSeparator()) }
+ },
+ getSelections: function(lineSep) {
+ var this$1 = this;
+
+ var parts = [], ranges = this.sel.ranges;
+ for (var i = 0; i < ranges.length; i++) {
+ var sel = getBetween(this$1, ranges[i].from(), ranges[i].to());
+ if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()); }
+ parts[i] = sel;
+ }
+ return parts
+ },
+ replaceSelection: function(code, collapse, origin) {
+ var dup = [];
+ for (var i = 0; i < this.sel.ranges.length; i++)
+ { dup[i] = code; }
+ this.replaceSelections(dup, collapse, origin || "+input");
+ },
+ replaceSelections: docMethodOp(function(code, collapse, origin) {
+ var this$1 = this;
+
+ var changes = [], sel = this.sel;
+ for (var i = 0; i < sel.ranges.length; i++) {
+ var range$$1 = sel.ranges[i];
+ changes[i] = {from: range$$1.from(), to: range$$1.to(), text: this$1.splitLines(code[i]), origin: origin};
+ }
+ var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse);
+ for (var i$1 = changes.length - 1; i$1 >= 0; i$1--)
+ { makeChange(this$1, changes[i$1]); }
+ if (newSel) { setSelectionReplaceHistory(this, newSel); }
+ else if (this.cm) { ensureCursorVisible(this.cm); }
+ }),
+ undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
+ redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
+ undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
+ redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}),
+
+ setExtending: function(val) {this.extend = val;},
+ getExtending: function() {return this.extend},
+
+ historySize: function() {
+ var hist = this.history, done = 0, undone = 0;
+ for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done; } }
+ for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone; } }
+ return {undo: done, redo: undone}
+ },
+ clearHistory: function() {this.history = new History(this.history.maxGeneration);},
+
+ markClean: function() {
+ this.cleanGeneration = this.changeGeneration(true);
+ },
+ changeGeneration: function(forceSplit) {
+ if (forceSplit)
+ { this.history.lastOp = this.history.lastSelOp = 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 = new History(this.history.maxGeneration);
+ hist.done = copyHistoryArray(histData.done.slice(0), null, true);
+ hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);
+ },
+
+ setGutterMarker: docMethodOp(function(line, gutterID, value) {
+ return changeLine(this, line, "gutter", function (line) {
+ var markers = line.gutterMarkers || (line.gutterMarkers = {});
+ markers[gutterID] = value;
+ if (!value && isEmpty(markers)) { line.gutterMarkers = null; }
+ return true
+ })
+ }),
+
+ clearGutter: docMethodOp(function(gutterID) {
+ var this$1 = this;
+
+ this.iter(function (line) {
+ if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
+ changeLine(this$1, line, "gutter", function () {
+ line.gutterMarkers[gutterID] = null;
+ if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null; }
+ return true
+ });
+ }
+ });
+ }),
+
+ lineInfo: function(line) {
+ var n;
+ if (typeof line == "number") {
+ if (!isLine(this, line)) { return null }
+ n = line;
+ line = getLine(this, line);
+ if (!line) { return null }
+ } else {
+ 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}
+ },
+
+ addLineClass: docMethodOp(function(handle, where, cls) {
+ return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) {
+ var prop = where == "text" ? "textClass"
+ : where == "background" ? "bgClass"
+ : where == "gutter" ? "gutterClass" : "wrapClass";
+ if (!line[prop]) { line[prop] = cls; }
+ else if (classTest(cls).test(line[prop])) { return false }
+ else { line[prop] += " " + cls; }
+ return true
+ })
+ }),
+ removeLineClass: docMethodOp(function(handle, where, cls) {
+ return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) {
+ var prop = where == "text" ? "textClass"
+ : where == "background" ? "bgClass"
+ : where == "gutter" ? "gutterClass" : "wrapClass";
+ var cur = line[prop];
+ if (!cur) { return false }
+ else if (cls == null) { line[prop] = null; }
+ else {
+ var found = cur.match(classTest(cls));
+ 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: docMethodOp(function(handle, node, options) {
+ return addLineWidget(this, handle, node, options)
+ }),
+ removeLineWidget: function(widget) { widget.clear(); },
+
+ markText: function(from, to, options) {
+ return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range")
+ },
+ setBookmark: function(pos, options) {
+ var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
+ insertLeft: options && options.insertLeft,
+ clearWhenEmpty: false, shared: options && options.shared,
+ handleMouseEvents: options && options.handleMouseEvents};
+ 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
+ },
+ findMarks: function(from, to, filter) {
+ from = clipPos(this, from); to = clipPos(this, to);
+ var found = [], lineNo$$1 = from.line;
+ this.iter(from.line, to.line + 1, function (line) {
+ var spans = line.markedSpans;
+ if (spans) { for (var i = 0; i < spans.length; i++) {
+ var span = spans[i];
+ if (!(span.to != null && lineNo$$1 == from.line && from.ch >= span.to ||
+ span.from == null && lineNo$$1 != from.line ||
+ span.from != null && lineNo$$1 == to.line && span.from >= to.ch) &&
+ (!filter || filter(span.marker)))
+ { found.push(span.marker.parent || span.marker); }
+ } }
+ ++lineNo$$1;
+ });
+ return found
+ },
+ 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$$1 = this.first, sepSize = this.lineSeparator().length;
+ this.iter(function (line) {
+ var sz = line.text.length + sepSize;
+ if (sz > off) { ch = off; return true }
+ off -= sz;
+ ++lineNo$$1;
+ });
+ return clipPos(this, Pos(lineNo$$1, ch))
+ },
+ indexFromPos: function (coords) {
+ coords = clipPos(this, coords);
+ var index = coords.ch;
+ if (coords.line < this.first || coords.ch < 0) { return 0 }
+ var sepSize = this.lineSeparator().length;
+ this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value
+ index += line.text.length + sepSize;
+ });
+ return index
+ },
+
+ copy: function(copyHistory) {
+ var doc = new Doc(getLines(this, this.first, this.first + this.size),
+ this.modeOption, this.first, this.lineSep, this.direction);
+ doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
+ doc.sel = this.sel;
+ doc.extend = false;
+ 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, this.lineSep, this.direction);
+ 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}];
+ copySharedMarkers(copy, findSharedMarkers(this));
+ return copy
+ },
+ unlinkDoc: function(other) {
+ var this$1 = this;
+
+ if (other instanceof CodeMirror) { other = other.doc; }
+ if (this.linked) { for (var i = 0; i < this.linked.length; ++i) {
+ var link = this$1.linked[i];
+ if (link.doc != other) { continue }
+ this$1.linked.splice(i, 1);
+ other.unlinkDoc(this$1);
+ detachSharedMarkers(findSharedMarkers(this$1));
+ break
+ } }
+ // If the histories were shared, split them again
+ if (other.history == this.history) {
+ var splitIds = [other.id];
+ linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true);
+ other.history = new History(null);
+ 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},
+
+ splitLines: function(str) {
+ if (this.lineSep) { return str.split(this.lineSep) }
+ return splitLinesAuto(str)
+ },
+ lineSeparator: function() { return this.lineSep || "\n" },
+
+ setDirection: docMethodOp(function (dir) {
+ if (dir != "rtl") { dir = "ltr"; }
+ if (dir == this.direction) { return }
+ this.direction = dir;
+ this.iter(function (line) { return line.order = null; });
+ if (this.cm) { directionChanged(this.cm); }
+ })
+ });
+
+ // Public alias.
+ Doc.prototype.eachLine = Doc.prototype.iter;
+
+ // 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;
+ clearDragCursor(cm);
+ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
+ { return }
+ e_preventDefault(e);
+ if (ie) { lastDrop = +new Date; }
+ var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
+ if (!pos || cm.isReadOnly()) { return }
+ // Might be a file drop, in which case we simply extract the text
+ // and insert it.
+ if (files && files.length && window.FileReader && window.File) {
+ var n = files.length, text = Array(n), read = 0;
+ var loadFile = function (file, i) {
+ if (cm.options.allowDropFileTypes &&
+ indexOf(cm.options.allowDropFileTypes, file.type) == -1)
+ { return }
+
+ var reader = new FileReader;
+ reader.onload = operation(cm, function () {
+ var content = reader.result;
+ if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = ""; }
+ text[i] = content;
+ if (++read == n) {
+ pos = clipPos(cm.doc, pos);
+ var change = {from: pos, to: pos,
+ text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())),
+ origin: "paste"};
+ makeChange(cm.doc, change);
+ setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
+ }
+ });
+ reader.readAsText(file);
+ };
+ for (var i = 0; i < n; ++i) { loadFile(files[i], i); }
+ } else { // Normal drop
+ // Don't do a replace if the drop happened inside of the selected text.
+ if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
+ cm.state.draggingText(e);
+ // Ensure the editor is re-focused
+ setTimeout(function () { return cm.display.input.focus(); }, 20);
+ return
+ }
+ try {
+ var text$1 = e.dataTransfer.getData("Text");
+ if (text$1) {
+ var selected;
+ if (cm.state.draggingText && !cm.state.draggingText.copy)
+ { selected = cm.listSelections(); }
+ setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
+ if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1)
+ { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag"); } }
+ cm.replaceSelection(text$1, "around", "paste");
+ cm.display.input.focus();
+ }
+ }
+ 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 }
+
+ e.dataTransfer.setData("Text", cm.getSelection());
+ e.dataTransfer.effectAllowed = "copyMove";
+
+ // 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;");
+ img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
+ if (presto) {
+ 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 (presto) { img.parentNode.removeChild(img); }
+ }
+ }
+
+ function onDragOver(cm, e) {
+ var pos = posFromMouse(cm, e);
+ if (!pos) { return }
+ var frag = document.createDocumentFragment();
+ drawSelectionCursor(cm, pos, frag);
+ if (!cm.display.dragCursor) {
+ cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors");
+ cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv);
+ }
+ removeChildrenAndAdd(cm.display.dragCursor, frag);
+ }
+
+ function clearDragCursor(cm) {
+ if (cm.display.dragCursor) {
+ cm.display.lineSpace.removeChild(cm.display.dragCursor);
+ cm.display.dragCursor = null;
+ }
+ }
+
+ // These must be handled carefully, because naively registering a
+ // handler for each editor will cause the editors to never be
+ // garbage collected.
+
+ function forEachCodeMirror(f) {
+ if (!document.getElementsByClassName) { return }
+ var byClass = document.getElementsByClassName("CodeMirror"), editors = [];
+ for (var i = 0; i < byClass.length; i++) {
+ var cm = byClass[i].CodeMirror;
+ if (cm) { editors.push(cm); }
+ }
+ if (editors.length) { editors[0].operation(function () {
+ for (var i = 0; i < editors.length; i++) { f(editors[i]); }
+ }); }
+ }
+
+ var globalsRegistered = false;
+ function ensureGlobalHandlers() {
+ if (globalsRegistered) { return }
+ registerGlobalHandlers();
+ globalsRegistered = true;
+ }
+ function registerGlobalHandlers() {
+ // When the window resizes, we need to refresh active editors.
+ var resizeTimer;
+ on(window, "resize", function () {
+ if (resizeTimer == null) { resizeTimer = setTimeout(function () {
+ resizeTimer = null;
+ forEachCodeMirror(onResize);
+ }, 100); }
+ });
+ // When the window loses focus, we want to show the editor as blurred
+ on(window, "blur", function () { return forEachCodeMirror(onBlur); });
+ }
+ // Called when the window resizes
+ function onResize(cm) {
+ var d = cm.display;
+ // Might be a text scaling operation, clear size caches.
+ d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
+ d.scrollbarsClipped = false;
+ cm.setSize();
+ }
+
+ var keyNames = {
+ 3: "Pause", 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: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod",
+ 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 145: "ScrollLock",
+ 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
+ 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
+ 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"
+ };
+
+ // Number keys
+ for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i); }
+ // Alphabetic keys
+ for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1); }
+ // Function keys
+ for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2; }
+
+ var keyMap = {};
+
+ keyMap.basic = {
+ "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
+ "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
+ "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
+ "Tab": "defaultTab", "Shift-Tab": "indentAuto",
+ "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
+ "Esc": "singleSelection"
+ };
+ // Note that the save and find-related commands aren't defined by
+ // default. User code or addons can define them. 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", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown",
+ "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",
+ "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
+ "fallthrough": "basic"
+ };
+ // Very basic readline/emacs-style bindings, which are standard on Mac.
+ 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",
+ "Ctrl-O": "openLine"
+ };
+ keyMap.macDefault = {
+ "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
+ "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
+ "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "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": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight",
+ "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
+ "fallthrough": ["basic", "emacsy"]
+ };
+ keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
+
+ // KEYMAP DISPATCH
+
+ function normalizeKeyName(name) {
+ var parts = name.split(/-(?!$)/);
+ name = parts[parts.length - 1];
+ var alt, ctrl, shift, cmd;
+ for (var i = 0; i < parts.length - 1; i++) {
+ var mod = parts[i];
+ if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; }
+ else if (/^a(lt)?$/i.test(mod)) { alt = true; }
+ else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; }
+ else if (/^s(hift)?$/i.test(mod)) { shift = true; }
+ else { throw new Error("Unrecognized modifier name: " + mod) }
+ }
+ if (alt) { name = "Alt-" + name; }
+ if (ctrl) { name = "Ctrl-" + name; }
+ if (cmd) { name = "Cmd-" + name; }
+ if (shift) { name = "Shift-" + name; }
+ return name
+ }
+
+ // This is a kludge to keep keymaps mostly working as raw objects
+ // (backwards compatibility) while at the same time support features
+ // like normalization and multi-stroke key bindings. It compiles a
+ // new normalized keymap, and then updates the old object to reflect
+ // this.
+ function normalizeKeyMap(keymap) {
+ var copy = {};
+ for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) {
+ var value = keymap[keyname];
+ if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue }
+ if (value == "...") { delete keymap[keyname]; continue }
+
+ var keys = map(keyname.split(" "), normalizeKeyName);
+ for (var i = 0; i < keys.length; i++) {
+ var val = (void 0), name = (void 0);
+ if (i == keys.length - 1) {
+ name = keys.join(" ");
+ val = value;
+ } else {
+ name = keys.slice(0, i + 1).join(" ");
+ val = "...";
+ }
+ var prev = copy[name];
+ if (!prev) { copy[name] = val; }
+ else if (prev != val) { throw new Error("Inconsistent bindings for " + name) }
+ }
+ delete keymap[keyname];
+ } }
+ for (var prop in copy) { keymap[prop] = copy[prop]; }
+ return keymap
+ }
+
+ function lookupKey(key, map$$1, handle, context) {
+ map$$1 = getKeyMap(map$$1);
+ var found = map$$1.call ? map$$1.call(key, context) : map$$1[key];
+ if (found === false) { return "nothing" }
+ if (found === "...") { return "multi" }
+ if (found != null && handle(found)) { return "handled" }
+
+ if (map$$1.fallthrough) {
+ if (Object.prototype.toString.call(map$$1.fallthrough) != "[object Array]")
+ { return lookupKey(key, map$$1.fallthrough, handle, context) }
+ for (var i = 0; i < map$$1.fallthrough.length; i++) {
+ var result = lookupKey(key, map$$1.fallthrough[i], handle, context);
+ if (result) { return result }
+ }
+ }
+ }
+
+ // Modifier key presses don't count as 'real' key presses for the
+ // purpose of keymap fallthrough.
+ function isModifierKey(value) {
+ var name = typeof value == "string" ? value : keyNames[value.keyCode];
+ return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"
+ }
+
+ function addModifierNames(name, event, noShift) {
+ var base = name;
+ if (event.altKey && base != "Alt") { name = "Alt-" + name; }
+ if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name; }
+ if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name; }
+ if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name; }
+ return name
+ }
+
+ // Look up the name of a key as indicated by an event object.
+ function keyName(event, noShift) {
+ if (presto && event.keyCode == 34 && event["char"]) { return false }
+ var name = keyNames[event.keyCode];
+ if (name == null || event.altGraphKey) { return false }
+ // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause,
+ // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+)
+ if (event.keyCode == 3 && event.code) { name = event.code; }
+ return addModifierNames(name, event, noShift)
+ }
+
+ function getKeyMap(val) {
+ return typeof val == "string" ? keyMap[val] : val
+ }
+
+ // Helper for deleting text near the selection(s), used to implement
+ // backspace, delete, and similar functionality.
+ function deleteNearSelection(cm, compute) {
+ var ranges = cm.doc.sel.ranges, kill = [];
+ // Build up a set of ranges to kill first, merging overlapping
+ // ranges.
+ for (var i = 0; i < ranges.length; i++) {
+ var toKill = compute(ranges[i]);
+ while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {
+ var replaced = kill.pop();
+ if (cmp(replaced.from, toKill.from) < 0) {
+ toKill.from = replaced.from;
+ break
+ }
+ }
+ kill.push(toKill);
+ }
+ // Next, remove those actual ranges.
+ runInOp(cm, function () {
+ for (var i = kill.length - 1; i >= 0; i--)
+ { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); }
+ ensureCursorVisible(cm);
+ });
+ }
+
+ function moveCharLogically(line, ch, dir) {
+ var target = skipExtendingChars(line.text, ch + dir, dir);
+ return target < 0 || target > line.text.length ? null : target
+ }
+
+ function moveLogically(line, start, dir) {
+ var ch = moveCharLogically(line, start.ch, dir);
+ return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before")
+ }
+
+ function endOfLine(visually, cm, lineObj, lineNo, dir) {
+ if (visually) {
+ var order = getOrder(lineObj, cm.doc.direction);
+ if (order) {
+ var part = dir < 0 ? lst(order) : order[0];
+ var moveInStorageOrder = (dir < 0) == (part.level == 1);
+ var sticky = moveInStorageOrder ? "after" : "before";
+ var ch;
+ // With a wrapped rtl chunk (possibly spanning multiple bidi parts),
+ // it could be that the last bidi part is not on the last visual line,
+ // since visual lines contain content order-consecutive chunks.
+ // Thus, in rtl, we are looking for the first (content-order) character
+ // in the rtl chunk that is on the last line (that is, the same line
+ // as the last (content-order) character).
+ if (part.level > 0 || cm.doc.direction == "rtl") {
+ var prep = prepareMeasureForLine(cm, lineObj);
+ ch = dir < 0 ? lineObj.text.length - 1 : 0;
+ var targetTop = measureCharPrepared(cm, prep, ch).top;
+ ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch);
+ if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1); }
+ } else { ch = dir < 0 ? part.to : part.from; }
+ return new Pos(lineNo, ch, sticky)
+ }
+ }
+ return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after")
+ }
+
+ function moveVisually(cm, line, start, dir) {
+ var bidi = getOrder(line, cm.doc.direction);
+ if (!bidi) { return moveLogically(line, start, dir) }
+ if (start.ch >= line.text.length) {
+ start.ch = line.text.length;
+ start.sticky = "before";
+ } else if (start.ch <= 0) {
+ start.ch = 0;
+ start.sticky = "after";
+ }
+ var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos];
+ if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) {
+ // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines,
+ // nothing interesting happens.
+ return moveLogically(line, start, dir)
+ }
+
+ var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); };
+ var prep;
+ var getWrappedLineExtent = function (ch) {
+ if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} }
+ prep = prep || prepareMeasureForLine(cm, line);
+ return wrappedLineExtentChar(cm, line, prep, ch)
+ };
+ var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch);
+
+ if (cm.doc.direction == "rtl" || part.level == 1) {
+ var moveInStorageOrder = (part.level == 1) == (dir < 0);
+ var ch = mv(start, moveInStorageOrder ? 1 : -1);
+ if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) {
+ // Case 2: We move within an rtl part or in an rtl editor on the same visual line
+ var sticky = moveInStorageOrder ? "before" : "after";
+ return new Pos(start.line, ch, sticky)
+ }
+ }
+
+ // Case 3: Could not move within this bidi part in this visual line, so leave
+ // the current bidi part
+
+ var searchInVisualLine = function (partPos, dir, wrappedLineExtent) {
+ var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder
+ ? new Pos(start.line, mv(ch, 1), "before")
+ : new Pos(start.line, ch, "after"); };
+
+ for (; partPos >= 0 && partPos < bidi.length; partPos += dir) {
+ var part = bidi[partPos];
+ var moveInStorageOrder = (dir > 0) == (part.level != 1);
+ var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1);
+ if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) }
+ ch = moveInStorageOrder ? part.from : mv(part.to, -1);
+ if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) }
+ }
+ };
+
+ // Case 3a: Look for other bidi parts on the same visual line
+ var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent);
+ if (res) { return res }
+
+ // Case 3b: Look for other bidi parts on the next visual line
+ var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1);
+ if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) {
+ res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh));
+ if (res) { return res }
+ }
+
+ // Case 4: Nowhere to move
+ return null
+ }
+
+ // Commands are parameter-less actions that can be performed on an
+ // editor, mostly used for keybindings.
+ var commands = {
+ selectAll: selectAll,
+ singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); },
+ killLine: function (cm) { return deleteNearSelection(cm, function (range) {
+ if (range.empty()) {
+ var len = getLine(cm.doc, range.head.line).text.length;
+ if (range.head.ch == len && range.head.line < cm.lastLine())
+ { return {from: range.head, to: Pos(range.head.line + 1, 0)} }
+ else
+ { return {from: range.head, to: Pos(range.head.line, len)} }
+ } else {
+ return {from: range.from(), to: range.to()}
+ }
+ }); },
+ deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({
+ from: Pos(range.from().line, 0),
+ to: clipPos(cm.doc, Pos(range.to().line + 1, 0))
+ }); }); },
+ delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({
+ from: Pos(range.from().line, 0), to: range.from()
+ }); }); },
+ delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) {
+ var top = cm.charCoords(range.head, "div").top + 5;
+ var leftPos = cm.coordsChar({left: 0, top: top}, "div");
+ return {from: leftPos, to: range.from()}
+ }); },
+ delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) {
+ var top = cm.charCoords(range.head, "div").top + 5;
+ var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
+ return {from: range.from(), to: rightPos }
+ }); },
+ undo: function (cm) { return cm.undo(); },
+ redo: function (cm) { return cm.redo(); },
+ undoSelection: function (cm) { return cm.undoSelection(); },
+ redoSelection: function (cm) { return cm.redoSelection(); },
+ goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); },
+ goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); },
+ goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); },
+ {origin: "+move", bias: 1}
+ ); },
+ goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); },
+ {origin: "+move", bias: 1}
+ ); },
+ goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); },
+ {origin: "+move", bias: -1}
+ ); },
+ goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) {
+ var top = cm.cursorCoords(range.head, "div").top + 5;
+ return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
+ }, sel_move); },
+ goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) {
+ var top = cm.cursorCoords(range.head, "div").top + 5;
+ return cm.coordsChar({left: 0, top: top}, "div")
+ }, sel_move); },
+ goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) {
+ var top = cm.cursorCoords(range.head, "div").top + 5;
+ var pos = cm.coordsChar({left: 0, top: top}, "div");
+ if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) }
+ return pos
+ }, sel_move); },
+ goLineUp: function (cm) { return cm.moveV(-1, "line"); },
+ goLineDown: function (cm) { return cm.moveV(1, "line"); },
+ goPageUp: function (cm) { return cm.moveV(-1, "page"); },
+ goPageDown: function (cm) { return cm.moveV(1, "page"); },
+ goCharLeft: function (cm) { return cm.moveH(-1, "char"); },
+ goCharRight: function (cm) { return cm.moveH(1, "char"); },
+ goColumnLeft: function (cm) { return cm.moveH(-1, "column"); },
+ goColumnRight: function (cm) { return cm.moveH(1, "column"); },
+ goWordLeft: function (cm) { return cm.moveH(-1, "word"); },
+ goGroupRight: function (cm) { return cm.moveH(1, "group"); },
+ goGroupLeft: function (cm) { return cm.moveH(-1, "group"); },
+ goWordRight: function (cm) { return cm.moveH(1, "word"); },
+ delCharBefore: function (cm) { return cm.deleteH(-1, "char"); },
+ delCharAfter: function (cm) { return cm.deleteH(1, "char"); },
+ delWordBefore: function (cm) { return cm.deleteH(-1, "word"); },
+ delWordAfter: function (cm) { return cm.deleteH(1, "word"); },
+ delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); },
+ delGroupAfter: function (cm) { return cm.deleteH(1, "group"); },
+ indentAuto: function (cm) { return cm.indentSelection("smart"); },
+ indentMore: function (cm) { return cm.indentSelection("add"); },
+ indentLess: function (cm) { return cm.indentSelection("subtract"); },
+ insertTab: function (cm) { return cm.replaceSelection("\t"); },
+ insertSoftTab: function (cm) {
+ var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize;
+ for (var i = 0; i < ranges.length; i++) {
+ var pos = ranges[i].from();
+ var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);
+ spaces.push(spaceStr(tabSize - col % tabSize));
+ }
+ cm.replaceSelections(spaces);
+ },
+ defaultTab: function (cm) {
+ if (cm.somethingSelected()) { cm.indentSelection("add"); }
+ else { cm.execCommand("insertTab"); }
+ },
+ // Swap the two chars left and right of each selection's head.
+ // Move cursor behind the two swapped characters afterwards.
+ //
+ // Doesn't consider line feeds a character.
+ // Doesn't scan more than one line above to find a character.
+ // Doesn't do anything on an empty line.
+ // Doesn't do anything with non-empty selections.
+ transposeChars: function (cm) { return runInOp(cm, function () {
+ var ranges = cm.listSelections(), newSel = [];
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) { continue }
+ var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;
+ if (line) {
+ if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1); }
+ if (cur.ch > 0) {
+ cur = new Pos(cur.line, cur.ch + 1);
+ cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
+ Pos(cur.line, cur.ch - 2), cur, "+transpose");
+ } else if (cur.line > cm.doc.first) {
+ var prev = getLine(cm.doc, cur.line - 1).text;
+ if (prev) {
+ cur = new Pos(cur.line, 1);
+ cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +
+ prev.charAt(prev.length - 1),
+ Pos(cur.line - 1, prev.length - 1), cur, "+transpose");
+ }
+ }
+ }
+ newSel.push(new Range(cur, cur));
+ }
+ cm.setSelections(newSel);
+ }); },
+ newlineAndIndent: function (cm) { return runInOp(cm, function () {
+ var sels = cm.listSelections();
+ for (var i = sels.length - 1; i >= 0; i--)
+ { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input"); }
+ sels = cm.listSelections();
+ for (var i$1 = 0; i$1 < sels.length; i$1++)
+ { cm.indentLine(sels[i$1].from().line, null, true); }
+ ensureCursorVisible(cm);
+ }); },
+ openLine: function (cm) { return cm.replaceSelection("\n", "start"); },
+ toggleOverwrite: function (cm) { return cm.toggleOverwrite(); }
+ };
+
+
+ function lineStart(cm, lineN) {
+ var line = getLine(cm.doc, lineN);
+ var visual = visualLine(line);
+ if (visual != line) { lineN = lineNo(visual); }
+ return endOfLine(true, cm, visual, lineN, 1)
+ }
+ function lineEnd(cm, lineN) {
+ var line = getLine(cm.doc, lineN);
+ var visual = visualLineEnd(line);
+ if (visual != line) { lineN = lineNo(visual); }
+ return endOfLine(true, cm, line, lineN, -1)
+ }
+ function lineStartSmart(cm, pos) {
+ var start = lineStart(cm, pos.line);
+ var line = getLine(cm.doc, start.line);
+ var order = getOrder(line, cm.doc.direction);
+ if (!order || order[0].level == 0) {
+ var firstNonWS = Math.max(0, line.text.search(/\S/));
+ var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;
+ return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky)
+ }
+ return start
+ }
+
+ // Run a handler that was bound to a key.
+ 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
+ cm.display.input.ensurePolled();
+ var prevShift = cm.display.shift, done = false;
+ try {
+ if (cm.isReadOnly()) { cm.state.suppressEdits = true; }
+ if (dropShift) { cm.display.shift = false; }
+ done = bound(cm) != Pass;
+ } finally {
+ cm.display.shift = prevShift;
+ cm.state.suppressEdits = false;
+ }
+ return done
+ }
+
+ function lookupKeyForEditor(cm, name, handle) {
+ for (var i = 0; i < cm.state.keyMaps.length; i++) {
+ var result = lookupKey(name, cm.state.keyMaps[i], handle, cm);
+ if (result) { return result }
+ }
+ return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm))
+ || lookupKey(name, cm.options.keyMap, handle, cm)
+ }
+
+ // Note that, despite the name, this function is also used to check
+ // for bound mouse clicks.
+
+ var stopSeq = new Delayed;
+
+ function dispatchKey(cm, name, e, handle) {
+ var seq = cm.state.keySeq;
+ if (seq) {
+ if (isModifierKey(name)) { return "handled" }
+ if (/\'$/.test(name))
+ { cm.state.keySeq = null; }
+ else
+ { stopSeq.set(50, function () {
+ if (cm.state.keySeq == seq) {
+ cm.state.keySeq = null;
+ cm.display.input.reset();
+ }
+ }); }
+ if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true }
+ }
+ return dispatchKeyInner(cm, name, e, handle)
+ }
+
+ function dispatchKeyInner(cm, name, e, handle) {
+ var result = lookupKeyForEditor(cm, name, handle);
+
+ if (result == "multi")
+ { cm.state.keySeq = name; }
+ if (result == "handled")
+ { signalLater(cm, "keyHandled", cm, name, e); }
+
+ if (result == "handled" || result == "multi") {
+ e_preventDefault(e);
+ restartBlink(cm);
+ }
+
+ return !!result
+ }
+
+ // Handle a key from the keydown event.
+ function handleKeyBinding(cm, e) {
+ var name = keyName(e, true);
+ if (!name) { return false }
+
+ if (e.shiftKey && !cm.state.keySeq) {
+ // 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-'.
+ return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); })
+ || dispatchKey(cm, name, e, function (b) {
+ if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
+ { return doHandleBinding(cm, b) }
+ })
+ } else {
+ return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); })
+ }
+ }
+
+ // Handle a key from the keypress event
+ function handleCharBinding(cm, e, ch) {
+ return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); })
+ }
+
+ var lastStoppedKey = null;
+ function onKeyDown(e) {
+ var cm = this;
+ cm.curOp.focus = activeElt();
+ if (signalDOMEvent(cm, e)) { return }
+ // IE does strange things with escape.
+ if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; }
+ var code = e.keyCode;
+ cm.display.shift = code == 16 || e.shiftKey;
+ var handled = handleKeyBinding(cm, e);
+ if (presto) {
+ 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("", null, "cut"); }
+ }
+
+ // Turn mouse into crosshair when Alt is held on Mac.
+ if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className))
+ { showCrossHair(cm); }
+ }
+
+ function showCrossHair(cm) {
+ var lineDiv = cm.display.lineDiv;
+ addClass(lineDiv, "CodeMirror-crosshair");
+
+ function up(e) {
+ if (e.keyCode == 18 || !e.altKey) {
+ rmClass(lineDiv, "CodeMirror-crosshair");
+ off(document, "keyup", up);
+ off(document, "mouseover", up);
+ }
+ }
+ on(document, "keyup", up);
+ on(document, "mouseover", up);
+ }
+
+ function onKeyUp(e) {
+ if (e.keyCode == 16) { this.doc.sel.shift = false; }
+ signalDOMEvent(this, e);
+ }
+
+ function onKeyPress(e) {
+ var cm = this;
+ if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return }
+ var keyCode = e.keyCode, charCode = e.charCode;
+ if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return}
+ if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return }
+ var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
+ // Some browsers fire keypress events for backspace
+ if (ch == "\x08") { return }
+ if (handleCharBinding(cm, e, ch)) { return }
+ cm.display.input.onKeyPress(e);
+ }
+
+ var DOUBLECLICK_DELAY = 400;
+
+ var PastClick = function(time, pos, button) {
+ this.time = time;
+ this.pos = pos;
+ this.button = button;
+ };
+
+ PastClick.prototype.compare = function (time, pos, button) {
+ return this.time + DOUBLECLICK_DELAY > time &&
+ cmp(pos, this.pos) == 0 && button == this.button
+ };
+
+ var lastClick, lastDoubleClick;
+ function clickRepeat(pos, button) {
+ var now = +new Date;
+ if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) {
+ lastClick = lastDoubleClick = null;
+ return "triple"
+ } else if (lastClick && lastClick.compare(now, pos, button)) {
+ lastDoubleClick = new PastClick(now, pos, button);
+ lastClick = null;
+ return "double"
+ } else {
+ lastClick = new PastClick(now, pos, button);
+ lastDoubleClick = null;
+ return "single"
+ }
+ }
+
+ // A mouse down can be a single click, double click, triple click,
+ // start of selection drag, start of text drag, new cursor
+ // (ctrl-click), rectangle drag (alt-drag), or xwin
+ // middle-click-paste. Or it might be a click on something we should
+ // not interfere with, such as a scrollbar or widget.
+ function onMouseDown(e) {
+ var cm = this, display = cm.display;
+ if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return }
+ display.input.ensurePolled();
+ display.shift = e.shiftKey;
+
+ if (eventInWidget(display, e)) {
+ if (!webkit) {
+ // Briefly turn off draggability, to allow widgets to do
+ // normal dragging things.
+ display.scroller.draggable = false;
+ setTimeout(function () { return display.scroller.draggable = true; }, 100);
+ }
+ return
+ }
+ if (clickInGutter(cm, e)) { return }
+ var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single";
+ window.focus();
+
+ // #3261: make sure, that we're not starting a second selection
+ if (button == 1 && cm.state.selectingText)
+ { cm.state.selectingText(e); }
+
+ if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return }
+
+ if (button == 1) {
+ if (pos) { leftButtonDown(cm, pos, repeat, e); }
+ else if (e_target(e) == display.scroller) { e_preventDefault(e); }
+ } else if (button == 2) {
+ if (pos) { extendSelection(cm.doc, pos); }
+ setTimeout(function () { return display.input.focus(); }, 20);
+ } else if (button == 3) {
+ if (captureRightClick) { cm.display.input.onContextMenu(e); }
+ else { delayBlurEvent(cm); }
+ }
+ }
+
+ function handleMappedButton(cm, button, pos, repeat, event) {
+ var name = "Click";
+ if (repeat == "double") { name = "Double" + name; }
+ else if (repeat == "triple") { name = "Triple" + name; }
+ name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name;
+
+ return dispatchKey(cm, addModifierNames(name, event), event, function (bound) {
+ if (typeof bound == "string") { bound = commands[bound]; }
+ if (!bound) { return false }
+ var done = false;
+ try {
+ if (cm.isReadOnly()) { cm.state.suppressEdits = true; }
+ done = bound(cm, pos) != Pass;
+ } finally {
+ cm.state.suppressEdits = false;
+ }
+ return done
+ })
+ }
+
+ function configureMouse(cm, repeat, event) {
+ var option = cm.getOption("configureMouse");
+ var value = option ? option(cm, repeat, event) : {};
+ if (value.unit == null) {
+ var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey;
+ value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line";
+ }
+ if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey; }
+ if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey; }
+ if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); }
+ return value
+ }
+
+ function leftButtonDown(cm, pos, repeat, event) {
+ if (ie) { setTimeout(bind(ensureFocus, cm), 0); }
+ else { cm.curOp.focus = activeElt(); }
+
+ var behavior = configureMouse(cm, repeat, event);
+
+ var sel = cm.doc.sel, contained;
+ if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&
+ repeat == "single" && (contained = sel.contains(pos)) > -1 &&
+ (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) &&
+ (cmp(contained.to(), pos) > 0 || pos.xRel < 0))
+ { leftButtonStartDrag(cm, event, pos, behavior); }
+ else
+ { leftButtonSelect(cm, event, pos, behavior); }
+ }
+
+ // Start a text drag. When it ends, see if any dragging actually
+ // happen, and treat as a click if it didn't.
+ function leftButtonStartDrag(cm, event, pos, behavior) {
+ var display = cm.display, moved = false;
+ var dragEnd = operation(cm, function (e) {
+ if (webkit) { display.scroller.draggable = false; }
+ cm.state.draggingText = false;
+ off(display.wrapper.ownerDocument, "mouseup", dragEnd);
+ off(display.wrapper.ownerDocument, "mousemove", mouseMove);
+ off(display.scroller, "dragstart", dragStart);
+ off(display.scroller, "drop", dragEnd);
+ if (!moved) {
+ e_preventDefault(e);
+ if (!behavior.addNew)
+ { extendSelection(cm.doc, pos, null, null, behavior.extend); }
+ // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)
+ if (webkit || ie && ie_version == 9)
+ { setTimeout(function () {display.wrapper.ownerDocument.body.focus(); display.input.focus();}, 20); }
+ else
+ { display.input.focus(); }
+ }
+ });
+ var mouseMove = function(e2) {
+ moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10;
+ };
+ var dragStart = function () { return moved = true; };
+ // Let the drag handler handle this.
+ if (webkit) { display.scroller.draggable = true; }
+ cm.state.draggingText = dragEnd;
+ dragEnd.copy = !behavior.moveOnDrag;
+ // IE's approach to draggable
+ if (display.scroller.dragDrop) { display.scroller.dragDrop(); }
+ on(display.wrapper.ownerDocument, "mouseup", dragEnd);
+ on(display.wrapper.ownerDocument, "mousemove", mouseMove);
+ on(display.scroller, "dragstart", dragStart);
+ on(display.scroller, "drop", dragEnd);
+
+ delayBlurEvent(cm);
+ setTimeout(function () { return display.input.focus(); }, 20);
+ }
+
+ function rangeForUnit(cm, pos, unit) {
+ if (unit == "char") { return new Range(pos, pos) }
+ if (unit == "word") { return cm.findWordAt(pos) }
+ if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) }
+ var result = unit(cm, pos);
+ return new Range(result.from, result.to)
+ }
+
+ // Normal selection, as opposed to text dragging.
+ function leftButtonSelect(cm, event, start, behavior) {
+ var display = cm.display, doc = cm.doc;
+ e_preventDefault(event);
+
+ var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges;
+ if (behavior.addNew && !behavior.extend) {
+ ourIndex = doc.sel.contains(start);
+ if (ourIndex > -1)
+ { ourRange = ranges[ourIndex]; }
+ else
+ { ourRange = new Range(start, start); }
+ } else {
+ ourRange = doc.sel.primary();
+ ourIndex = doc.sel.primIndex;
+ }
+
+ if (behavior.unit == "rectangle") {
+ if (!behavior.addNew) { ourRange = new Range(start, start); }
+ start = posFromMouse(cm, event, true, true);
+ ourIndex = -1;
+ } else {
+ var range$$1 = rangeForUnit(cm, start, behavior.unit);
+ if (behavior.extend)
+ { ourRange = extendRange(ourRange, range$$1.anchor, range$$1.head, behavior.extend); }
+ else
+ { ourRange = range$$1; }
+ }
+
+ if (!behavior.addNew) {
+ ourIndex = 0;
+ setSelection(doc, new Selection([ourRange], 0), sel_mouse);
+ startSel = doc.sel;
+ } else if (ourIndex == -1) {
+ ourIndex = ranges.length;
+ setSelection(doc, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex),
+ {scroll: false, origin: "*mouse"});
+ } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) {
+ setSelection(doc, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0),
+ {scroll: false, origin: "*mouse"});
+ startSel = doc.sel;
+ } else {
+ replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
+ }
+
+ var lastPos = start;
+ function extendTo(pos) {
+ if (cmp(lastPos, pos) == 0) { return }
+ lastPos = pos;
+
+ if (behavior.unit == "rectangle") {
+ var ranges = [], tabSize = cm.options.tabSize;
+ var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);
+ var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);
+ var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);
+ for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));
+ line <= end; line++) {
+ var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);
+ if (left == right)
+ { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); }
+ else if (text.length > leftPos)
+ { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); }
+ }
+ if (!ranges.length) { ranges.push(new Range(start, start)); }
+ setSelection(doc, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),
+ {origin: "*mouse", scroll: false});
+ cm.scrollIntoView(pos);
+ } else {
+ var oldRange = ourRange;
+ var range$$1 = rangeForUnit(cm, pos, behavior.unit);
+ var anchor = oldRange.anchor, head;
+ if (cmp(range$$1.anchor, anchor) > 0) {
+ head = range$$1.head;
+ anchor = minPos(oldRange.from(), range$$1.anchor);
+ } else {
+ head = range$$1.anchor;
+ anchor = maxPos(oldRange.to(), range$$1.head);
+ }
+ var ranges$1 = startSel.ranges.slice(0);
+ ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head));
+ setSelection(doc, normalizeSelection(cm, ranges$1, ourIndex), sel_mouse);
+ }
+ }
+
+ var editorSize = display.wrapper.getBoundingClientRect();
+ // 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, behavior.unit == "rectangle");
+ if (!cur) { return }
+ if (cmp(cur, lastPos) != 0) {
+ cm.curOp.focus = activeElt();
+ extendTo(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) {
+ cm.state.selectingText = false;
+ counter = Infinity;
+ // If e is null or undefined we interpret this as someone trying
+ // to explicitly cancel the selection rather than the user
+ // letting go of the mouse button.
+ if (e) {
+ e_preventDefault(e);
+ display.input.focus();
+ }
+ off(display.wrapper.ownerDocument, "mousemove", move);
+ off(display.wrapper.ownerDocument, "mouseup", up);
+ doc.history.lastSelOrigin = null;
+ }
+
+ var move = operation(cm, function (e) {
+ if (e.buttons === 0 || !e_button(e)) { done(e); }
+ else { extend(e); }
+ });
+ var up = operation(cm, done);
+ cm.state.selectingText = up;
+ on(display.wrapper.ownerDocument, "mousemove", move);
+ on(display.wrapper.ownerDocument, "mouseup", up);
+ }
+
+ // Used when mouse-selecting to adjust the anchor to the proper side
+ // of a bidi jump depending on the visual position of the head.
+ function bidiSimplify(cm, range$$1) {
+ var anchor = range$$1.anchor;
+ var head = range$$1.head;
+ var anchorLine = getLine(cm.doc, anchor.line);
+ if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range$$1 }
+ var order = getOrder(anchorLine);
+ if (!order) { return range$$1 }
+ var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index];
+ if (part.from != anchor.ch && part.to != anchor.ch) { return range$$1 }
+ var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1);
+ if (boundary == 0 || boundary == order.length) { return range$$1 }
+
+ // Compute the relative visual position of the head compared to the
+ // anchor (<0 is to the left, >0 to the right)
+ var leftSide;
+ if (head.line != anchor.line) {
+ leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0;
+ } else {
+ var headIndex = getBidiPartAt(order, head.ch, head.sticky);
+ var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1);
+ if (headIndex == boundary - 1 || headIndex == boundary)
+ { leftSide = dir < 0; }
+ else
+ { leftSide = dir > 0; }
+ }
+
+ var usePart = order[boundary + (leftSide ? -1 : 0)];
+ var from = leftSide == (usePart.level == 1);
+ var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before";
+ return anchor.ch == ch && anchor.sticky == sticky ? range$$1 : new Range(new Pos(anchor.line, ch, sticky), head)
+ }
+
+
+ // Determines whether an event happened in the gutter, and fires the
+ // handlers for the corresponding event.
+ function gutterEvent(cm, e, type, prevent) {
+ var mX, mY;
+ if (e.touches) {
+ mX = e.touches[0].clientX;
+ mY = e.touches[0].clientY;
+ } else {
+ try { mX = e.clientX; mY = e.clientY; }
+ catch(e) { return false }
+ }
+ if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false }
+ if (prevent) { e_preventDefault(e); }
+
+ var display = cm.display;
+ var lineBox = display.lineDiv.getBoundingClientRect();
+
+ if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) }
+ mY -= lineBox.top - display.viewOffset;
+
+ for (var i = 0; i < cm.display.gutterSpecs.length; ++i) {
+ var g = display.gutters.childNodes[i];
+ if (g && g.getBoundingClientRect().right >= mX) {
+ var line = lineAtHeight(cm.doc, mY);
+ var gutter = cm.display.gutterSpecs[i];
+ signal(cm, type, cm, line, gutter.className, e);
+ return e_defaultPrevented(e)
+ }
+ }
+ }
+
+ function clickInGutter(cm, e) {
+ return gutterEvent(cm, e, "gutterClick", true)
+ }
+
+ // CONTEXT MENU HANDLING
+
+ // To make the context menu work, we need to briefly unhide the
+ // textarea (making it as unobtrusive as possible) to let the
+ // right-click take effect on it.
+ function onContextMenu(cm, e) {
+ if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return }
+ if (signalDOMEvent(cm, e, "contextmenu")) { return }
+ if (!captureRightClick) { cm.display.input.onContextMenu(e); }
+ }
+
+ function contextMenuInGutter(cm, e) {
+ if (!hasHandler(cm, "gutterContextMenu")) { return false }
+ return gutterEvent(cm, e, "gutterContextMenu", false)
+ }
+
+ 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);
+ }
+
+ var Init = {toString: function(){return "CodeMirror.Init"}};
+
+ var defaults = {};
+ var optionHandlers = {};
+
+ function defineOptions(CodeMirror) {
+ var optionHandlers = CodeMirror.optionHandlers;
+
+ 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; }
+ }
+
+ CodeMirror.defineOption = option;
+
+ // Passed to option handlers when there is no old value.
+ CodeMirror.Init = 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) { return 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) {
+ resetModeState(cm);
+ clearCaches(cm);
+ regChange(cm);
+ }, true);
+
+ option("lineSeparator", null, function (cm, val) {
+ cm.doc.lineSep = val;
+ if (!val) { return }
+ var newBreaks = [], lineNo = cm.doc.first;
+ cm.doc.iter(function (line) {
+ for (var pos = 0;;) {
+ var found = line.text.indexOf(val, pos);
+ if (found == -1) { break }
+ pos = found + val.length;
+ newBreaks.push(Pos(lineNo, found));
+ }
+ lineNo++;
+ });
+ for (var i = newBreaks.length - 1; i >= 0; i--)
+ { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); }
+ });
+ option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, function (cm, val, old) {
+ cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
+ if (old != Init) { cm.refresh(); }
+ });
+ option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true);
+ option("electricChars", true);
+ option("inputStyle", mobile ? "contenteditable" : "textarea", function () {
+ throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME
+ }, true);
+ option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true);
+ option("autocorrect", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true);
+ option("autocapitalize", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true);
+ option("rtlMoveVisually", !windows);
+ option("wholeLineUpdateBefore", true);
+
+ option("theme", "default", function (cm) {
+ themeChanged(cm);
+ updateGutters(cm);
+ }, true);
+ option("keyMap", "default", function (cm, val, old) {
+ var next = getKeyMap(val);
+ var prev = old != Init && getKeyMap(old);
+ if (prev && prev.detach) { prev.detach(cm, next); }
+ if (next.attach) { next.attach(cm, prev || null); }
+ });
+ option("extraKeys", null);
+ option("configureMouse", null);
+
+ option("lineWrapping", false, wrappingChanged, true);
+ option("gutters", [], function (cm, val) {
+ cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers);
+ updateGutters(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, function (cm) { return updateScrollbars(cm); }, true);
+ option("scrollbarStyle", "native", function (cm) {
+ initScrollbars(cm);
+ updateScrollbars(cm);
+ cm.display.scrollbars.setScrollTop(cm.doc.scrollTop);
+ cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft);
+ }, true);
+ option("lineNumbers", false, function (cm, val) {
+ cm.display.gutterSpecs = getGutters(cm.options.gutters, val);
+ updateGutters(cm);
+ }, true);
+ option("firstLineNumber", 1, updateGutters, true);
+ option("lineNumberFormatter", function (integer) { return integer; }, updateGutters, true);
+ option("showCursorWhenSelecting", false, updateSelection, true);
+
+ option("resetSelectionOnContextMenu", true);
+ option("lineWiseCopyCut", true);
+ option("pasteLinesPerSelection", true);
+ option("selectionsMayTouch", false);
+
+ option("readOnly", false, function (cm, val) {
+ if (val == "nocursor") {
+ onBlur(cm);
+ cm.display.input.blur();
+ }
+ cm.display.input.readOnlyChanged(val);
+ });
+ option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset(); }}, true);
+ option("dragDrop", true, dragDropChanged);
+ option("allowDropFileTypes", null);
+
+ option("cursorBlinkRate", 530);
+ option("cursorScrollMargin", 0);
+ option("cursorHeight", 1, updateSelection, true);
+ option("singleCursorHeightPerLine", true, updateSelection, true);
+ option("workTime", 100);
+ option("workDelay", 100);
+ option("flattenSpans", true, resetModeState, true);
+ option("addModeClass", false, resetModeState, true);
+ option("pollInterval", 100);
+ option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; });
+ option("historyEventDelay", 1250);
+ option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true);
+ option("maxHighlightLength", 10000, resetModeState, true);
+ option("moveInputWithCursor", true, function (cm, val) {
+ if (!val) { cm.display.input.resetPosition(); }
+ });
+
+ option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; });
+ option("autofocus", null);
+ option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true);
+ option("phrases", null);
+ }
+
+ function dragDropChanged(cm, value, old) {
+ var wasOn = old && old != Init;
+ if (!value != !wasOn) {
+ var funcs = cm.display.dragFunctions;
+ var toggle = value ? on : off;
+ toggle(cm.display.scroller, "dragstart", funcs.start);
+ toggle(cm.display.scroller, "dragenter", funcs.enter);
+ toggle(cm.display.scroller, "dragover", funcs.over);
+ toggle(cm.display.scroller, "dragleave", funcs.leave);
+ toggle(cm.display.scroller, "drop", funcs.drop);
+ }
+ }
+
+ function wrappingChanged(cm) {
+ if (cm.options.lineWrapping) {
+ addClass(cm.display.wrapper, "CodeMirror-wrap");
+ cm.display.sizer.style.minWidth = "";
+ cm.display.sizerWidth = null;
+ } else {
+ rmClass(cm.display.wrapper, "CodeMirror-wrap");
+ findMaxLine(cm);
+ }
+ estimateLineHeights(cm);
+ regChange(cm);
+ clearCaches(cm);
+ setTimeout(function () { return updateScrollbars(cm); }, 100);
+ }
+
+ // A CodeMirror instance represents an editor. This is the object
+ // that user code is usually dealing with.
+
+ function CodeMirror(place, options) {
+ var this$1 = this;
+
+ if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) }
+
+ this.options = options = options ? copyObj(options) : {};
+ // Determine effective options based on given values and defaults.
+ copyObj(defaults, options, false);
+
+ var doc = options.value;
+ if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); }
+ else if (options.mode) { doc.modeOption = options.mode; }
+ this.doc = doc;
+
+ var input = new CodeMirror.inputStyles[options.inputStyle](this);
+ var display = this.display = new Display(place, doc, input, options);
+ display.wrapper.CodeMirror = this;
+ themeChanged(this);
+ if (options.lineWrapping)
+ { this.display.wrapper.className += " CodeMirror-wrap"; }
+ initScrollbars(this);
+
+ this.state = {
+ keyMaps: [], // stores maps added by addKeyMap
+ overlays: [], // highlighting overlays, as added by addOverlay
+ modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
+ overwrite: false,
+ delayingBlurEvent: false,
+ focused: false,
+ suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
+ pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll
+ selectingText: false,
+ draggingText: false,
+ highlight: new Delayed(), // stores highlight worker timeout
+ keySeq: null, // Unfinished key sequence
+ specialChars: null
+ };
+
+ if (options.autofocus && !mobile) { display.input.focus(); }
+
+ // Override magic textarea content restore that IE sometimes does
+ // on our hidden textarea on reload
+ if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20); }
+
+ registerEventHandlers(this);
+ ensureGlobalHandlers();
+
+ startOperation(this);
+ this.curOp.forceUpdate = true;
+ attachDoc(this, doc);
+
+ if ((options.autofocus && !mobile) || this.hasFocus())
+ { setTimeout(bind(onFocus, this), 20); }
+ else
+ { onBlur(this); }
+
+ for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt))
+ { optionHandlers[opt](this$1, options[opt], Init); } }
+ maybeUpdateLineNumberWidth(this);
+ if (options.finishInit) { options.finishInit(this); }
+ for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1); }
+ endOperation(this);
+ // Suppress optimizelegibility in Webkit, since it breaks text
+ // measuring on line wrapping boundaries.
+ if (webkit && options.lineWrapping &&
+ getComputedStyle(display.lineDiv).textRendering == "optimizelegibility")
+ { display.lineDiv.style.textRendering = "auto"; }
+ }
+
+ // The default configuration options.
+ CodeMirror.defaults = defaults;
+ // Functions to run when options are changed.
+ CodeMirror.optionHandlers = optionHandlers;
+
+ // Attach the necessary event handlers when initializing the editor
+ function registerEventHandlers(cm) {
+ var d = cm.display;
+ on(d.scroller, "mousedown", operation(cm, onMouseDown));
+ // Older IE's will not fire a second mousedown for a double click
+ if (ie && ie_version < 11)
+ { 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 = cm.findWordAt(pos);
+ extendSelection(cm.doc, word.anchor, word.head);
+ })); }
+ else
+ { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }); }
+ // Some browsers fire contextmenu *after* opening the menu, at
+ // which point we can't mess with it anymore. Context menu is
+ // handled in onMouseDown for these browsers.
+ on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); });
+
+ // Used to suppress mouse event handling when a touch happens
+ var touchFinished, prevTouch = {end: 0};
+ function finishTouch() {
+ if (d.activeTouch) {
+ touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000);
+ prevTouch = d.activeTouch;
+ prevTouch.end = +new Date;
+ }
+ }
+ function isMouseLikeTouchEvent(e) {
+ if (e.touches.length != 1) { return false }
+ var touch = e.touches[0];
+ return touch.radiusX <= 1 && touch.radiusY <= 1
+ }
+ function farAway(touch, other) {
+ if (other.left == null) { return true }
+ var dx = other.left - touch.left, dy = other.top - touch.top;
+ return dx * dx + dy * dy > 20 * 20
+ }
+ on(d.scroller, "touchstart", function (e) {
+ if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) {
+ d.input.ensurePolled();
+ clearTimeout(touchFinished);
+ var now = +new Date;
+ d.activeTouch = {start: now, moved: false,
+ prev: now - prevTouch.end <= 300 ? prevTouch : null};
+ if (e.touches.length == 1) {
+ d.activeTouch.left = e.touches[0].pageX;
+ d.activeTouch.top = e.touches[0].pageY;
+ }
+ }
+ });
+ on(d.scroller, "touchmove", function () {
+ if (d.activeTouch) { d.activeTouch.moved = true; }
+ });
+ on(d.scroller, "touchend", function (e) {
+ var touch = d.activeTouch;
+ if (touch && !eventInWidget(d, e) && touch.left != null &&
+ !touch.moved && new Date - touch.start < 300) {
+ var pos = cm.coordsChar(d.activeTouch, "page"), range;
+ if (!touch.prev || farAway(touch, touch.prev)) // Single tap
+ { range = new Range(pos, pos); }
+ else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap
+ { range = cm.findWordAt(pos); }
+ else // Triple tap
+ { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); }
+ cm.setSelection(range.anchor, range.head);
+ cm.focus();
+ e_preventDefault(e);
+ }
+ finishTouch();
+ });
+ on(d.scroller, "touchcancel", finishTouch);
+
+ // Sync scrolling between fake scrollbars and real scrollable
+ // area, ensure viewport is updated when scrolling.
+ on(d.scroller, "scroll", function () {
+ if (d.scroller.clientHeight) {
+ updateScrollTop(cm, d.scroller.scrollTop);
+ setScrollLeft(cm, d.scroller.scrollLeft, true);
+ signal(cm, "scroll", cm);
+ }
+ });
+
+ // Listen to wheel events in order to try and update the viewport on time.
+ on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); });
+ on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); });
+
+ // Prevent wrapper from ever scrolling
+ on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
+
+ d.dragFunctions = {
+ enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e); }},
+ over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }},
+ start: function (e) { return onDragStart(cm, e); },
+ drop: operation(cm, onDrop),
+ leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }}
+ };
+
+ var inp = d.input.getField();
+ on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); });
+ on(inp, "keydown", operation(cm, onKeyDown));
+ on(inp, "keypress", operation(cm, onKeyPress));
+ on(inp, "focus", function (e) { return onFocus(cm, e); });
+ on(inp, "blur", function (e) { return onBlur(cm, e); });
+ }
+
+ var initHooks = [];
+ CodeMirror.defineInitHook = function (f) { return initHooks.push(f); };
+
+ // Indent the given line. The how parameter can be "smart",
+ // "add"/null, "subtract", or "prev". When aggressive is false
+ // (typically set to true for forced single-line indents), empty
+ // lines are not indented, and places where the mode returns Pass
+ // are left alone.
+ function indentLine(cm, n, how, aggressive) {
+ var doc = cm.doc, state;
+ if (how == null) { how = "add"; }
+ if (how == "smart") {
+ // Fall back to "prev" when the mode doesn't have an indentation
+ // method.
+ if (!doc.mode.indent) { how = "prev"; }
+ else { state = getContextBefore(cm, n).state; }
+ }
+
+ var tabSize = cm.options.tabSize;
+ var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
+ if (line.stateAfter) { line.stateAfter = null; }
+ var curSpaceString = line.text.match(/^\s*/)[0], indentation;
+ if (!aggressive && !/\S/.test(line.text)) {
+ indentation = 0;
+ how = "not";
+ } else if (how == "smart") {
+ indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
+ if (indentation == Pass || indentation > 150) {
+ 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(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
+ line.stateAfter = null;
+ return true
+ } else {
+ // Ensure that, if the cursor was in the whitespace at the start
+ // of the line, it is moved to the end of that space.
+ for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) {
+ var range = doc.sel.ranges[i$1];
+ if (range.head.line == n && range.head.ch < curSpaceString.length) {
+ var pos$1 = Pos(n, curSpaceString.length);
+ replaceOneSelection(doc, i$1, new Range(pos$1, pos$1));
+ break
+ }
+ }
+ }
+ }
+
+ // This will be set to a {lineWise: bool, text: [string]} object, so
+ // that, when pasting, we know what kind of selections the copied
+ // text was made out of.
+ var lastCopied = null;
+
+ function setLastCopied(newLastCopied) {
+ lastCopied = newLastCopied;
+ }
+
+ function applyTextInput(cm, inserted, deleted, sel, origin) {
+ var doc = cm.doc;
+ cm.display.shift = false;
+ if (!sel) { sel = doc.sel; }
+
+ var recent = +new Date - 200;
+ var paste = origin == "paste" || cm.state.pasteIncoming > recent;
+ var textLines = splitLinesAuto(inserted), multiPaste = null;
+ // When pasting N lines into N selections, insert one line per selection
+ if (paste && sel.ranges.length > 1) {
+ if (lastCopied && lastCopied.text.join("\n") == inserted) {
+ if (sel.ranges.length % lastCopied.text.length == 0) {
+ multiPaste = [];
+ for (var i = 0; i < lastCopied.text.length; i++)
+ { multiPaste.push(doc.splitLines(lastCopied.text[i])); }
+ }
+ } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) {
+ multiPaste = map(textLines, function (l) { return [l]; });
+ }
+ }
+
+ var updateInput = cm.curOp.updateInput;
+ // Normal behavior is to insert the new text into every selection
+ for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) {
+ var range$$1 = sel.ranges[i$1];
+ var from = range$$1.from(), to = range$$1.to();
+ if (range$$1.empty()) {
+ if (deleted && deleted > 0) // Handle deletion
+ { from = Pos(from.line, from.ch - deleted); }
+ else if (cm.state.overwrite && !paste) // Handle overwrite
+ { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); }
+ else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted)
+ { from = to = Pos(from.line, 0); }
+ }
+ var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines,
+ origin: origin || (paste ? "paste" : cm.state.cutIncoming > recent ? "cut" : "+input")};
+ makeChange(cm.doc, changeEvent);
+ signalLater(cm, "inputRead", cm, changeEvent);
+ }
+ if (inserted && !paste)
+ { triggerElectric(cm, inserted); }
+
+ ensureCursorVisible(cm);
+ if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; }
+ cm.curOp.typing = true;
+ cm.state.pasteIncoming = cm.state.cutIncoming = -1;
+ }
+
+ function handlePaste(e, cm) {
+ var pasted = e.clipboardData && e.clipboardData.getData("Text");
+ if (pasted) {
+ e.preventDefault();
+ if (!cm.isReadOnly() && !cm.options.disableInput)
+ { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }); }
+ return true
+ }
+ }
+
+ function triggerElectric(cm, inserted) {
+ // When an 'electric' character is inserted, immediately trigger a reindent
+ if (!cm.options.electricChars || !cm.options.smartIndent) { return }
+ var sel = cm.doc.sel;
+
+ for (var i = sel.ranges.length - 1; i >= 0; i--) {
+ var range$$1 = sel.ranges[i];
+ if (range$$1.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range$$1.head.line)) { continue }
+ var mode = cm.getModeAt(range$$1.head);
+ var indented = false;
+ if (mode.electricChars) {
+ for (var j = 0; j < mode.electricChars.length; j++)
+ { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
+ indented = indentLine(cm, range$$1.head.line, "smart");
+ break
+ } }
+ } else if (mode.electricInput) {
+ if (mode.electricInput.test(getLine(cm.doc, range$$1.head.line).text.slice(0, range$$1.head.ch)))
+ { indented = indentLine(cm, range$$1.head.line, "smart"); }
+ }
+ if (indented) { signalLater(cm, "electricInput", cm, range$$1.head.line); }
+ }
+ }
+
+ function copyableRanges(cm) {
+ var text = [], ranges = [];
+ for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
+ var line = cm.doc.sel.ranges[i].head.line;
+ var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
+ ranges.push(lineRange);
+ text.push(cm.getRange(lineRange.anchor, lineRange.head));
+ }
+ return {text: text, ranges: ranges}
+ }
+
+ function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) {
+ field.setAttribute("autocorrect", autocorrect ? "" : "off");
+ field.setAttribute("autocapitalize", autocapitalize ? "" : "off");
+ field.setAttribute("spellcheck", !!spellcheck);
+ }
+
+ function hiddenTextarea() {
+ var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none");
+ var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
+ // The textarea is kept positioned near the cursor to prevent the
+ // fact that it'll be scrolled into view on input from scrolling
+ // our fake cursor out of view. On webkit, when wrap=off, paste is
+ // very slow. So make the area wide instead.
+ if (webkit) { te.style.width = "1000px"; }
+ else { te.setAttribute("wrap", "off"); }
+ // If border: 0; -- iOS fails to open keyboard (issue #1287)
+ if (ios) { te.style.border = "1px solid black"; }
+ disableBrowserMagic(te);
+ return div
+ }
+
+ // The publicly visible API. Note that methodOp(f) means
+ // 'wrap f in an operation, performed on its `this` parameter'.
+
+ // This is not the complete set of editor methods. Most of the
+ // methods defined on the Doc type are also injected into
+ // CodeMirror.prototype, for backwards compatibility and
+ // convenience.
+
+ function addEditorMethods(CodeMirror) {
+ var optionHandlers = CodeMirror.optionHandlers;
+
+ var helpers = CodeMirror.helpers = {};
+
+ CodeMirror.prototype = {
+ constructor: CodeMirror,
+ focus: function(){window.focus(); this.display.input.focus();},
+
+ 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); }
+ signal(this, "optionChange", this, option);
+ },
+
+ getOption: function(option) {return this.options[option]},
+ getDoc: function() {return this.doc},
+
+ addKeyMap: function(map$$1, bottom) {
+ this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map$$1));
+ },
+ removeKeyMap: function(map$$1) {
+ var maps = this.state.keyMaps;
+ for (var i = 0; i < maps.length; ++i)
+ { if (maps[i] == map$$1 || maps[i].name == map$$1) {
+ maps.splice(i, 1);
+ return true
+ } }
+ },
+
+ addOverlay: methodOp(function(spec, options) {
+ var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
+ if (mode.startState) { throw new Error("Overlays may not be stateful.") }
+ insertSorted(this.state.overlays,
+ {mode: mode, modeSpec: spec, opaque: options && options.opaque,
+ priority: (options && options.priority) || 0},
+ function (overlay) { return overlay.priority; });
+ this.state.modeGen++;
+ regChange(this);
+ }),
+ removeOverlay: methodOp(function(spec) {
+ var this$1 = this;
+
+ 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$1.state.modeGen++;
+ regChange(this$1);
+ return
+ }
+ }
+ }),
+
+ indentLine: methodOp(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: methodOp(function(how) {
+ var this$1 = this;
+
+ var ranges = this.doc.sel.ranges, end = -1;
+ for (var i = 0; i < ranges.length; i++) {
+ var range$$1 = ranges[i];
+ if (!range$$1.empty()) {
+ var from = range$$1.from(), to = range$$1.to();
+ var start = Math.max(end, from.line);
+ end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
+ for (var j = start; j < end; ++j)
+ { indentLine(this$1, j, how); }
+ var newRanges = this$1.doc.sel.ranges;
+ if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
+ { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); }
+ } else if (range$$1.head.line > end) {
+ indentLine(this$1, range$$1.head.line, how, true);
+ end = range$$1.head.line;
+ if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1); }
+ }
+ }
+ }),
+
+ // 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) {
+ return takeToken(this, pos, precise)
+ },
+
+ getLineTokens: function(line, precise) {
+ return takeToken(this, Pos(line), precise, true)
+ },
+
+ 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;
+ var type;
+ if (ch == 0) { type = styles[2]; }
+ else { 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 { type = styles[mid * 2 + 2]; break }
+ } }
+ var cut = type ? type.indexOf("overlay ") : -1;
+ return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1)
+ },
+
+ getModeAt: function(pos) {
+ var mode = this.doc.mode;
+ if (!mode.innerMode) { return mode }
+ return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode
+ },
+
+ getHelper: function(pos, type) {
+ return this.getHelpers(pos, type)[0]
+ },
+
+ getHelpers: function(pos, type) {
+ var this$1 = this;
+
+ var found = [];
+ if (!helpers.hasOwnProperty(type)) { return found }
+ var help = helpers[type], mode = this.getModeAt(pos);
+ if (typeof mode[type] == "string") {
+ if (help[mode[type]]) { found.push(help[mode[type]]); }
+ } else if (mode[type]) {
+ for (var i = 0; i < mode[type].length; i++) {
+ var val = help[mode[type][i]];
+ if (val) { found.push(val); }
+ }
+ } else if (mode.helperType && help[mode.helperType]) {
+ found.push(help[mode.helperType]);
+ } else if (help[mode.name]) {
+ found.push(help[mode.name]);
+ }
+ for (var i$1 = 0; i$1 < help._global.length; i$1++) {
+ var cur = help._global[i$1];
+ if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1)
+ { found.push(cur.val); }
+ }
+ return found
+ },
+
+ getStateAfter: function(line, precise) {
+ var doc = this.doc;
+ line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
+ return getContextBefore(this, line + 1, precise).state
+ },
+
+ cursorCoords: function(start, mode) {
+ var pos, range$$1 = this.doc.sel.primary();
+ if (start == null) { pos = range$$1.head; }
+ else if (typeof start == "object") { pos = clipPos(this.doc, start); }
+ else { pos = start ? range$$1.from() : range$$1.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, includeWidgets) {
+ var end = false, lineObj;
+ if (typeof line == "number") {
+ var 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; }
+ lineObj = getLine(this.doc, line);
+ } else {
+ lineObj = line;
+ }
+ return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top +
+ (end ? this.doc.height - heightAtLine(lineObj) : 0)
+ },
+
+ defaultTextHeight: function() { return textHeight(this.display) },
+ defaultCharWidth: function() { return charWidth(this.display) },
+
+ getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}},
+
+ 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";
+ node.setAttribute("cm-ignore-events", "true");
+ this.display.input.setUneditable(node);
+ 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: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}); }
+ },
+
+ triggerOnKeyDown: methodOp(onKeyDown),
+ triggerOnKeyPress: methodOp(onKeyPress),
+ triggerOnKeyUp: onKeyUp,
+ triggerOnMouseDown: methodOp(onMouseDown),
+
+ execCommand: function(cmd) {
+ if (commands.hasOwnProperty(cmd))
+ { return commands[cmd].call(null, this) }
+ },
+
+ triggerElectric: methodOp(function(text) { triggerElectric(this, text); }),
+
+ findPosH: function(from, amount, unit, visually) {
+ var this$1 = this;
+
+ var dir = 1;
+ if (amount < 0) { dir = -1; amount = -amount; }
+ var cur = clipPos(this.doc, from);
+ for (var i = 0; i < amount; ++i) {
+ cur = findPosH(this$1.doc, cur, dir, unit, visually);
+ if (cur.hitSide) { break }
+ }
+ return cur
+ },
+
+ moveH: methodOp(function(dir, unit) {
+ var this$1 = this;
+
+ this.extendSelectionsBy(function (range$$1) {
+ if (this$1.display.shift || this$1.doc.extend || range$$1.empty())
+ { return findPosH(this$1.doc, range$$1.head, dir, unit, this$1.options.rtlMoveVisually) }
+ else
+ { return dir < 0 ? range$$1.from() : range$$1.to() }
+ }, sel_move);
+ }),
+
+ deleteH: methodOp(function(dir, unit) {
+ var sel = this.doc.sel, doc = this.doc;
+ if (sel.somethingSelected())
+ { doc.replaceSelection("", null, "+delete"); }
+ else
+ { deleteNearSelection(this, function (range$$1) {
+ var other = findPosH(doc, range$$1.head, dir, unit, false);
+ return dir < 0 ? {from: other, to: range$$1.head} : {from: range$$1.head, to: other}
+ }); }
+ }),
+
+ findPosV: function(from, amount, unit, goalColumn) {
+ var this$1 = this;
+
+ var dir = 1, x = goalColumn;
+ if (amount < 0) { dir = -1; amount = -amount; }
+ var cur = clipPos(this.doc, from);
+ for (var i = 0; i < amount; ++i) {
+ var coords = cursorCoords(this$1, cur, "div");
+ if (x == null) { x = coords.left; }
+ else { coords.left = x; }
+ cur = findPosV(this$1, coords, dir, unit);
+ if (cur.hitSide) { break }
+ }
+ return cur
+ },
+
+ moveV: methodOp(function(dir, unit) {
+ var this$1 = this;
+
+ var doc = this.doc, goals = [];
+ var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected();
+ doc.extendSelectionsBy(function (range$$1) {
+ if (collapse)
+ { return dir < 0 ? range$$1.from() : range$$1.to() }
+ var headPos = cursorCoords(this$1, range$$1.head, "div");
+ if (range$$1.goalColumn != null) { headPos.left = range$$1.goalColumn; }
+ goals.push(headPos.left);
+ var pos = findPosV(this$1, headPos, dir, unit);
+ if (unit == "page" && range$$1 == doc.sel.primary())
+ { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top); }
+ return pos
+ }, sel_move);
+ if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++)
+ { doc.sel.ranges[i].goalColumn = goals[i]; } }
+ }),
+
+ // Find the word at the given position (as returned by coordsChar).
+ findWordAt: function(pos) {
+ var doc = this.doc, line = getLine(doc, pos.line).text;
+ var start = pos.ch, end = pos.ch;
+ if (line) {
+ var helper = this.getHelper(pos, "wordChars");
+ if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end; }
+ var startChar = line.charAt(start);
+ var check = isWordChar(startChar, helper)
+ ? function (ch) { return isWordChar(ch, helper); }
+ : /\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 new Range(Pos(pos.line, start), Pos(pos.line, end))
+ },
+
+ toggleOverwrite: function(value) {
+ if (value != null && value == this.state.overwrite) { return }
+ if (this.state.overwrite = !this.state.overwrite)
+ { addClass(this.display.cursorDiv, "CodeMirror-overwrite"); }
+ else
+ { rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); }
+
+ signal(this, "overwriteToggle", this, this.state.overwrite);
+ },
+ hasFocus: function() { return this.display.input.getField() == activeElt() },
+ isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) },
+
+ scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }),
+ getScrollInfo: function() {
+ var scroller = this.display.scroller;
+ return {left: scroller.scrollLeft, top: scroller.scrollTop,
+ height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight,
+ width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth,
+ clientHeight: displayHeight(this), clientWidth: displayWidth(this)}
+ },
+
+ scrollIntoView: methodOp(function(range$$1, margin) {
+ if (range$$1 == null) {
+ range$$1 = {from: this.doc.sel.primary().head, to: null};
+ if (margin == null) { margin = this.options.cursorScrollMargin; }
+ } else if (typeof range$$1 == "number") {
+ range$$1 = {from: Pos(range$$1, 0), to: null};
+ } else if (range$$1.from == null) {
+ range$$1 = {from: range$$1, to: null};
+ }
+ if (!range$$1.to) { range$$1.to = range$$1.from; }
+ range$$1.margin = margin || 0;
+
+ if (range$$1.from.line != null) {
+ scrollToRange(this, range$$1);
+ } else {
+ scrollToCoordsRange(this, range$$1.from, range$$1.to, range$$1.margin);
+ }
+ }),
+
+ setSize: methodOp(function(width, height) {
+ var this$1 = this;
+
+ var interpret = function (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); }
+ if (this.options.lineWrapping) { clearLineMeasurementCache(this); }
+ var lineNo$$1 = this.display.viewFrom;
+ this.doc.iter(lineNo$$1, this.display.viewTo, function (line) {
+ if (line.widgets) { for (var i = 0; i < line.widgets.length; i++)
+ { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo$$1, "widget"); break } } }
+ ++lineNo$$1;
+ });
+ this.curOp.forceUpdate = true;
+ signal(this, "refresh", this);
+ }),
+
+ operation: function(f){return runInOp(this, f)},
+ startOperation: function(){return startOperation(this)},
+ endOperation: function(){return endOperation(this)},
+
+ refresh: methodOp(function() {
+ var oldHeight = this.display.cachedTextHeight;
+ regChange(this);
+ this.curOp.forceUpdate = true;
+ clearCaches(this);
+ scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop);
+ updateGutterSpace(this.display);
+ if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
+ { estimateLineHeights(this); }
+ signal(this, "refresh", this);
+ }),
+
+ swapDoc: methodOp(function(doc) {
+ var old = this.doc;
+ old.cm = null;
+ // Cancel the current text selection if any (#5821)
+ if (this.state.selectingText) { this.state.selectingText(); }
+ attachDoc(this, doc);
+ clearCaches(this);
+ this.display.input.reset();
+ scrollToCoords(this, doc.scrollLeft, doc.scrollTop);
+ this.curOp.forceScroll = true;
+ signalLater(this, "swapDoc", this, old);
+ return old
+ }),
+
+ phrase: function(phraseText) {
+ var phrases = this.options.phrases;
+ return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText
+ },
+
+ getInputField: function(){return this.display.input.getField()},
+ getWrapperElement: function(){return this.display.wrapper},
+ getScrollerElement: function(){return this.display.scroller},
+ getGutterElement: function(){return this.display.gutters}
+ };
+ eventMixin(CodeMirror);
+
+ CodeMirror.registerHelper = function(type, name, value) {
+ if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []}; }
+ helpers[type][name] = value;
+ };
+ CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {
+ CodeMirror.registerHelper(type, name, value);
+ helpers[type]._global.push({pred: predicate, val: value});
+ };
+ }
+
+ // Used for horizontal relative motion. Dir is -1 or 1 (left or
+ // right), unit can be "char", "column" (like char, but doesn't
+ // cross line boundaries), "word" (across next word), or "group" (to
+ // the start of next group of word or non-word-non-whitespace
+ // chars). The visually param controls whether, in right-to-left
+ // text, direction 1 means to move towards the next index in the
+ // string, or towards the character to the right of the current
+ // position. The resulting position will have a hitSide=true
+ // property if it reached the end of the document.
+ function findPosH(doc, pos, dir, unit, visually) {
+ var oldPos = pos;
+ var origDir = dir;
+ var lineObj = getLine(doc, pos.line);
+ function findNextLine() {
+ var l = pos.line + dir;
+ if (l < doc.first || l >= doc.first + doc.size) { return false }
+ pos = new Pos(l, pos.ch, pos.sticky);
+ return lineObj = getLine(doc, l)
+ }
+ function moveOnce(boundToLine) {
+ var next;
+ if (visually) {
+ next = moveVisually(doc.cm, lineObj, pos, dir);
+ } else {
+ next = moveLogically(lineObj, pos, dir);
+ }
+ if (next == null) {
+ if (!boundToLine && findNextLine())
+ { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir); }
+ else
+ { return false }
+ } else {
+ pos = 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";
+ var helper = doc.cm && doc.cm.getHelper(pos, "wordChars");
+ for (var first = true;; first = false) {
+ if (dir < 0 && !moveOnce(!first)) { break }
+ var cur = lineObj.text.charAt(pos.ch) || "\n";
+ var type = isWordChar(cur, helper) ? "w"
+ : group && cur == "\n" ? "n"
+ : !group || /\s/.test(cur) ? null
+ : "p";
+ if (group && !first && !type) { type = "s"; }
+ if (sawType && sawType != type) {
+ if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after";}
+ break
+ }
+
+ if (type) { sawType = type; }
+ if (dir > 0 && !moveOnce(!first)) { break }
+ }
+ }
+ var result = skipAtomic(doc, pos, oldPos, origDir, true);
+ if (equalCursorPos(oldPos, result)) { result.hitSide = true; }
+ return result
+ }
+
+ // For relative vertical movement. Dir may be -1 or 1. Unit can be
+ // "page" or "line". The resulting position will have a hitSide=true
+ // property if it reached the end of the document.
+ 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);
+ var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3);
+ y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount;
+
+ } else if (unit == "line") {
+ y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
+ }
+ var target;
+ for (;;) {
+ 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
+ }
+
+ // CONTENTEDITABLE INPUT STYLE
+
+ var ContentEditableInput = function(cm) {
+ this.cm = cm;
+ this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;
+ this.polling = new Delayed();
+ this.composing = null;
+ this.gracePeriod = false;
+ this.readDOMTimeout = null;
+ };
+
+ ContentEditableInput.prototype.init = function (display) {
+ var this$1 = this;
+
+ var input = this, cm = input.cm;
+ var div = input.div = display.lineDiv;
+ disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize);
+
+ on(div, "paste", function (e) {
+ if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }
+ // IE doesn't fire input events, so we schedule a read for the pasted content in this way
+ if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20); }
+ });
+
+ on(div, "compositionstart", function (e) {
+ this$1.composing = {data: e.data, done: false};
+ });
+ on(div, "compositionupdate", function (e) {
+ if (!this$1.composing) { this$1.composing = {data: e.data, done: false}; }
+ });
+ on(div, "compositionend", function (e) {
+ if (this$1.composing) {
+ if (e.data != this$1.composing.data) { this$1.readFromDOMSoon(); }
+ this$1.composing.done = true;
+ }
+ });
+
+ on(div, "touchstart", function () { return input.forceCompositionEnd(); });
+
+ on(div, "input", function () {
+ if (!this$1.composing) { this$1.readFromDOMSoon(); }
+ });
+
+ function onCopyCut(e) {
+ if (signalDOMEvent(cm, e)) { return }
+ if (cm.somethingSelected()) {
+ setLastCopied({lineWise: false, text: cm.getSelections()});
+ if (e.type == "cut") { cm.replaceSelection("", null, "cut"); }
+ } else if (!cm.options.lineWiseCopyCut) {
+ return
+ } else {
+ var ranges = copyableRanges(cm);
+ setLastCopied({lineWise: true, text: ranges.text});
+ if (e.type == "cut") {
+ cm.operation(function () {
+ cm.setSelections(ranges.ranges, 0, sel_dontScroll);
+ cm.replaceSelection("", null, "cut");
+ });
+ }
+ }
+ if (e.clipboardData) {
+ e.clipboardData.clearData();
+ var content = lastCopied.text.join("\n");
+ // iOS exposes the clipboard API, but seems to discard content inserted into it
+ e.clipboardData.setData("Text", content);
+ if (e.clipboardData.getData("Text") == content) {
+ e.preventDefault();
+ return
+ }
+ }
+ // Old-fashioned briefly-focus-a-textarea hack
+ var kludge = hiddenTextarea(), te = kludge.firstChild;
+ cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
+ te.value = lastCopied.text.join("\n");
+ var hadFocus = document.activeElement;
+ selectInput(te);
+ setTimeout(function () {
+ cm.display.lineSpace.removeChild(kludge);
+ hadFocus.focus();
+ if (hadFocus == div) { input.showPrimarySelection(); }
+ }, 50);
+ }
+ on(div, "copy", onCopyCut);
+ on(div, "cut", onCopyCut);
+ };
+
+ ContentEditableInput.prototype.prepareSelection = function () {
+ var result = prepareSelection(this.cm, false);
+ result.focus = this.cm.state.focused;
+ return result
+ };
+
+ ContentEditableInput.prototype.showSelection = function (info, takeFocus) {
+ if (!info || !this.cm.display.view.length) { return }
+ if (info.focus || takeFocus) { this.showPrimarySelection(); }
+ this.showMultipleSelections(info);
+ };
+
+ ContentEditableInput.prototype.getSelection = function () {
+ return this.cm.display.wrapper.ownerDocument.getSelection()
+ };
+
+ ContentEditableInput.prototype.showPrimarySelection = function () {
+ var sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary();
+ var from = prim.from(), to = prim.to();
+
+ if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) {
+ sel.removeAllRanges();
+ return
+ }
+
+ var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);
+ var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset);
+ if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&
+ cmp(minPos(curAnchor, curFocus), from) == 0 &&
+ cmp(maxPos(curAnchor, curFocus), to) == 0)
+ { return }
+
+ var view = cm.display.view;
+ var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) ||
+ {node: view[0].measure.map[2], offset: 0};
+ var end = to.line < cm.display.viewTo && posToDOM(cm, to);
+ if (!end) {
+ var measure = view[view.length - 1].measure;
+ var map$$1 = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map;
+ end = {node: map$$1[map$$1.length - 1], offset: map$$1[map$$1.length - 2] - map$$1[map$$1.length - 3]};
+ }
+
+ if (!start || !end) {
+ sel.removeAllRanges();
+ return
+ }
+
+ var old = sel.rangeCount && sel.getRangeAt(0), rng;
+ try { rng = range(start.node, start.offset, end.offset, end.node); }
+ catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
+ if (rng) {
+ if (!gecko && cm.state.focused) {
+ sel.collapse(start.node, start.offset);
+ if (!rng.collapsed) {
+ sel.removeAllRanges();
+ sel.addRange(rng);
+ }
+ } else {
+ sel.removeAllRanges();
+ sel.addRange(rng);
+ }
+ if (old && sel.anchorNode == null) { sel.addRange(old); }
+ else if (gecko) { this.startGracePeriod(); }
+ }
+ this.rememberSelection();
+ };
+
+ ContentEditableInput.prototype.startGracePeriod = function () {
+ var this$1 = this;
+
+ clearTimeout(this.gracePeriod);
+ this.gracePeriod = setTimeout(function () {
+ this$1.gracePeriod = false;
+ if (this$1.selectionChanged())
+ { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }); }
+ }, 20);
+ };
+
+ ContentEditableInput.prototype.showMultipleSelections = function (info) {
+ removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);
+ removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);
+ };
+
+ ContentEditableInput.prototype.rememberSelection = function () {
+ var sel = this.getSelection();
+ this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset;
+ this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset;
+ };
+
+ ContentEditableInput.prototype.selectionInEditor = function () {
+ var sel = this.getSelection();
+ if (!sel.rangeCount) { return false }
+ var node = sel.getRangeAt(0).commonAncestorContainer;
+ return contains(this.div, node)
+ };
+
+ ContentEditableInput.prototype.focus = function () {
+ if (this.cm.options.readOnly != "nocursor") {
+ if (!this.selectionInEditor())
+ { this.showSelection(this.prepareSelection(), true); }
+ this.div.focus();
+ }
+ };
+ ContentEditableInput.prototype.blur = function () { this.div.blur(); };
+ ContentEditableInput.prototype.getField = function () { return this.div };
+
+ ContentEditableInput.prototype.supportsTouch = function () { return true };
+
+ ContentEditableInput.prototype.receivedFocus = function () {
+ var input = this;
+ if (this.selectionInEditor())
+ { this.pollSelection(); }
+ else
+ { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); }
+
+ function poll() {
+ if (input.cm.state.focused) {
+ input.pollSelection();
+ input.polling.set(input.cm.options.pollInterval, poll);
+ }
+ }
+ this.polling.set(this.cm.options.pollInterval, poll);
+ };
+
+ ContentEditableInput.prototype.selectionChanged = function () {
+ var sel = this.getSelection();
+ return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
+ sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset
+ };
+
+ ContentEditableInput.prototype.pollSelection = function () {
+ if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return }
+ var sel = this.getSelection(), cm = this.cm;
+ // On Android Chrome (version 56, at least), backspacing into an
+ // uneditable block element will put the cursor in that element,
+ // and then, because it's not editable, hide the virtual keyboard.
+ // Because Android doesn't allow us to actually detect backspace
+ // presses in a sane way, this code checks for when that happens
+ // and simulates a backspace press in this case.
+ if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) {
+ this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs});
+ this.blur();
+ this.focus();
+ return
+ }
+ if (this.composing) { return }
+ this.rememberSelection();
+ var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);
+ var head = domToPos(cm, sel.focusNode, sel.focusOffset);
+ if (anchor && head) { runInOp(cm, function () {
+ setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll);
+ if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true; }
+ }); }
+ };
+
+ ContentEditableInput.prototype.pollContent = function () {
+ if (this.readDOMTimeout != null) {
+ clearTimeout(this.readDOMTimeout);
+ this.readDOMTimeout = null;
+ }
+
+ var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary();
+ var from = sel.from(), to = sel.to();
+ if (from.ch == 0 && from.line > cm.firstLine())
+ { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); }
+ if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine())
+ { to = Pos(to.line + 1, 0); }
+ if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false }
+
+ var fromIndex, fromLine, fromNode;
+ if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {
+ fromLine = lineNo(display.view[0].line);
+ fromNode = display.view[0].node;
+ } else {
+ fromLine = lineNo(display.view[fromIndex].line);
+ fromNode = display.view[fromIndex - 1].node.nextSibling;
+ }
+ var toIndex = findViewIndex(cm, to.line);
+ var toLine, toNode;
+ if (toIndex == display.view.length - 1) {
+ toLine = display.viewTo - 1;
+ toNode = display.lineDiv.lastChild;
+ } else {
+ toLine = lineNo(display.view[toIndex + 1].line) - 1;
+ toNode = display.view[toIndex + 1].node.previousSibling;
+ }
+
+ if (!fromNode) { return false }
+ var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine));
+ var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length));
+ while (newText.length > 1 && oldText.length > 1) {
+ if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; }
+ else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; }
+ else { break }
+ }
+
+ var cutFront = 0, cutEnd = 0;
+ var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length);
+ while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))
+ { ++cutFront; }
+ var newBot = lst(newText), oldBot = lst(oldText);
+ var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),
+ oldBot.length - (oldText.length == 1 ? cutFront : 0));
+ while (cutEnd < maxCutEnd &&
+ newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))
+ { ++cutEnd; }
+ // Try to move start of change to start of selection if ambiguous
+ if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) {
+ while (cutFront && cutFront > from.ch &&
+ newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) {
+ cutFront--;
+ cutEnd++;
+ }
+ }
+
+ newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "");
+ newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "");
+
+ var chFrom = Pos(fromLine, cutFront);
+ var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0);
+ if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {
+ replaceRange(cm.doc, newText, chFrom, chTo, "+input");
+ return true
+ }
+ };
+
+ ContentEditableInput.prototype.ensurePolled = function () {
+ this.forceCompositionEnd();
+ };
+ ContentEditableInput.prototype.reset = function () {
+ this.forceCompositionEnd();
+ };
+ ContentEditableInput.prototype.forceCompositionEnd = function () {
+ if (!this.composing) { return }
+ clearTimeout(this.readDOMTimeout);
+ this.composing = null;
+ this.updateFromDOM();
+ this.div.blur();
+ this.div.focus();
+ };
+ ContentEditableInput.prototype.readFromDOMSoon = function () {
+ var this$1 = this;
+
+ if (this.readDOMTimeout != null) { return }
+ this.readDOMTimeout = setTimeout(function () {
+ this$1.readDOMTimeout = null;
+ if (this$1.composing) {
+ if (this$1.composing.done) { this$1.composing = null; }
+ else { return }
+ }
+ this$1.updateFromDOM();
+ }, 80);
+ };
+
+ ContentEditableInput.prototype.updateFromDOM = function () {
+ var this$1 = this;
+
+ if (this.cm.isReadOnly() || !this.pollContent())
+ { runInOp(this.cm, function () { return regChange(this$1.cm); }); }
+ };
+
+ ContentEditableInput.prototype.setUneditable = function (node) {
+ node.contentEditable = "false";
+ };
+
+ ContentEditableInput.prototype.onKeyPress = function (e) {
+ if (e.charCode == 0 || this.composing) { return }
+ e.preventDefault();
+ if (!this.cm.isReadOnly())
+ { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); }
+ };
+
+ ContentEditableInput.prototype.readOnlyChanged = function (val) {
+ this.div.contentEditable = String(val != "nocursor");
+ };
+
+ ContentEditableInput.prototype.onContextMenu = function () {};
+ ContentEditableInput.prototype.resetPosition = function () {};
+
+ ContentEditableInput.prototype.needsContentAttribute = true;
+
+ function posToDOM(cm, pos) {
+ var view = findViewForLine(cm, pos.line);
+ if (!view || view.hidden) { return null }
+ var line = getLine(cm.doc, pos.line);
+ var info = mapFromLineView(view, line, pos.line);
+
+ var order = getOrder(line, cm.doc.direction), side = "left";
+ if (order) {
+ var partPos = getBidiPartAt(order, pos.ch);
+ side = partPos % 2 ? "right" : "left";
+ }
+ var result = nodeAndOffsetInLineMap(info.map, pos.ch, side);
+ result.offset = result.collapse == "right" ? result.end : result.start;
+ return result
+ }
+
+ function isInGutter(node) {
+ for (var scan = node; scan; scan = scan.parentNode)
+ { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } }
+ return false
+ }
+
+ function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos }
+
+ function domTextBetween(cm, from, to, fromLine, toLine) {
+ var text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false;
+ function recognizeMarker(id) { return function (marker) { return marker.id == id; } }
+ function close() {
+ if (closing) {
+ text += lineSep;
+ if (extraLinebreak) { text += lineSep; }
+ closing = extraLinebreak = false;
+ }
+ }
+ function addText(str) {
+ if (str) {
+ close();
+ text += str;
+ }
+ }
+ function walk(node) {
+ if (node.nodeType == 1) {
+ var cmText = node.getAttribute("cm-text");
+ if (cmText) {
+ addText(cmText);
+ return
+ }
+ var markerID = node.getAttribute("cm-marker"), range$$1;
+ if (markerID) {
+ var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID));
+ if (found.length && (range$$1 = found[0].find(0)))
+ { addText(getBetween(cm.doc, range$$1.from, range$$1.to).join(lineSep)); }
+ return
+ }
+ if (node.getAttribute("contenteditable") == "false") { return }
+ var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName);
+ if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return }
+
+ if (isBlock) { close(); }
+ for (var i = 0; i < node.childNodes.length; i++)
+ { walk(node.childNodes[i]); }
+
+ if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; }
+ if (isBlock) { closing = true; }
+ } else if (node.nodeType == 3) {
+ addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " "));
+ }
+ }
+ for (;;) {
+ walk(from);
+ if (from == to) { break }
+ from = from.nextSibling;
+ extraLinebreak = false;
+ }
+ return text
+ }
+
+ function domToPos(cm, node, offset) {
+ var lineNode;
+ if (node == cm.display.lineDiv) {
+ lineNode = cm.display.lineDiv.childNodes[offset];
+ if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) }
+ node = null; offset = 0;
+ } else {
+ for (lineNode = node;; lineNode = lineNode.parentNode) {
+ if (!lineNode || lineNode == cm.display.lineDiv) { return null }
+ if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break }
+ }
+ }
+ for (var i = 0; i < cm.display.view.length; i++) {
+ var lineView = cm.display.view[i];
+ if (lineView.node == lineNode)
+ { return locateNodeInLineView(lineView, node, offset) }
+ }
+ }
+
+ function locateNodeInLineView(lineView, node, offset) {
+ var wrapper = lineView.text.firstChild, bad = false;
+ if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) }
+ if (node == wrapper) {
+ bad = true;
+ node = wrapper.childNodes[offset];
+ offset = 0;
+ if (!node) {
+ var line = lineView.rest ? lst(lineView.rest) : lineView.line;
+ return badPos(Pos(lineNo(line), line.text.length), bad)
+ }
+ }
+
+ var textNode = node.nodeType == 3 ? node : null, topNode = node;
+ if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
+ textNode = node.firstChild;
+ if (offset) { offset = textNode.nodeValue.length; }
+ }
+ while (topNode.parentNode != wrapper) { topNode = topNode.parentNode; }
+ var measure = lineView.measure, maps = measure.maps;
+
+ function find(textNode, topNode, offset) {
+ for (var i = -1; i < (maps ? maps.length : 0); i++) {
+ var map$$1 = i < 0 ? measure.map : maps[i];
+ for (var j = 0; j < map$$1.length; j += 3) {
+ var curNode = map$$1[j + 2];
+ if (curNode == textNode || curNode == topNode) {
+ var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]);
+ var ch = map$$1[j] + offset;
+ if (offset < 0 || curNode != textNode) { ch = map$$1[j + (offset ? 1 : 0)]; }
+ return Pos(line, ch)
+ }
+ }
+ }
+ }
+ var found = find(textNode, topNode, offset);
+ if (found) { return badPos(found, bad) }
+
+ // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems
+ for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {
+ found = find(after, after.firstChild, 0);
+ if (found)
+ { return badPos(Pos(found.line, found.ch - dist), bad) }
+ else
+ { dist += after.textContent.length; }
+ }
+ for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) {
+ found = find(before, before.firstChild, -1);
+ if (found)
+ { return badPos(Pos(found.line, found.ch + dist$1), bad) }
+ else
+ { dist$1 += before.textContent.length; }
+ }
+ }
+
+ // TEXTAREA INPUT STYLE
+
+ var TextareaInput = function(cm) {
+ this.cm = cm;
+ // See input.poll and input.reset
+ this.prevInput = "";
+
+ // Flag that indicates whether we expect input to appear real soon
+ // now (after some event like 'keypress' or 'input') and are
+ // polling intensively.
+ this.pollingFast = false;
+ // Self-resetting timeout for the poller
+ this.polling = new Delayed();
+ // Used to work around IE issue with selection being forgotten when focus moves away from textarea
+ this.hasSelection = false;
+ this.composing = null;
+ };
+
+ TextareaInput.prototype.init = function (display) {
+ var this$1 = this;
+
+ var input = this, cm = this.cm;
+ this.createField(display);
+ var te = this.textarea;
+
+ display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild);
+
+ // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)
+ if (ios) { te.style.width = "0px"; }
+
+ on(te, "input", function () {
+ if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; }
+ input.poll();
+ });
+
+ on(te, "paste", function (e) {
+ if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }
+
+ cm.state.pasteIncoming = +new Date;
+ input.fastPoll();
+ });
+
+ function prepareCopyCut(e) {
+ if (signalDOMEvent(cm, e)) { return }
+ if (cm.somethingSelected()) {
+ setLastCopied({lineWise: false, text: cm.getSelections()});
+ } else if (!cm.options.lineWiseCopyCut) {
+ return
+ } else {
+ var ranges = copyableRanges(cm);
+ setLastCopied({lineWise: true, text: ranges.text});
+ if (e.type == "cut") {
+ cm.setSelections(ranges.ranges, null, sel_dontScroll);
+ } else {
+ input.prevInput = "";
+ te.value = ranges.text.join("\n");
+ selectInput(te);
+ }
+ }
+ if (e.type == "cut") { cm.state.cutIncoming = +new Date; }
+ }
+ on(te, "cut", prepareCopyCut);
+ on(te, "copy", prepareCopyCut);
+
+ on(display.scroller, "paste", function (e) {
+ if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }
+ if (!te.dispatchEvent) {
+ cm.state.pasteIncoming = +new Date;
+ input.focus();
+ return
+ }
+
+ // Pass the `paste` event to the textarea so it's handled by its event listener.
+ var event = new Event("paste");
+ event.clipboardData = e.clipboardData;
+ te.dispatchEvent(event);
+ });
+
+ // Prevent normal selection in the editor (we handle our own)
+ on(display.lineSpace, "selectstart", function (e) {
+ if (!eventInWidget(display, e)) { e_preventDefault(e); }
+ });
+
+ on(te, "compositionstart", function () {
+ var start = cm.getCursor("from");
+ if (input.composing) { input.composing.range.clear(); }
+ input.composing = {
+ start: start,
+ range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"})
+ };
+ });
+ on(te, "compositionend", function () {
+ if (input.composing) {
+ input.poll();
+ input.composing.range.clear();
+ input.composing = null;
+ }
+ });
+ };
+
+ TextareaInput.prototype.createField = function (_display) {
+ // Wraps and hides input textarea
+ this.wrapper = hiddenTextarea();
+ // The semihidden textarea that is focused when the editor is
+ // focused, and receives input.
+ this.textarea = this.wrapper.firstChild;
+ };
+
+ TextareaInput.prototype.prepareSelection = function () {
+ // Redraw the selection and/or cursor
+ var cm = this.cm, display = cm.display, doc = cm.doc;
+ var result = prepareSelection(cm);
+
+ // Move the hidden textarea near the cursor to prevent scrolling artifacts
+ if (cm.options.moveInputWithCursor) {
+ var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
+ var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
+ result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
+ headPos.top + lineOff.top - wrapOff.top));
+ result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
+ headPos.left + lineOff.left - wrapOff.left));
+ }
+
+ return result
+ };
+
+ TextareaInput.prototype.showSelection = function (drawn) {
+ var cm = this.cm, display = cm.display;
+ removeChildrenAndAdd(display.cursorDiv, drawn.cursors);
+ removeChildrenAndAdd(display.selectionDiv, drawn.selection);
+ if (drawn.teTop != null) {
+ this.wrapper.style.top = drawn.teTop + "px";
+ this.wrapper.style.left = drawn.teLeft + "px";
+ }
+ };
+
+ // Reset the input to correspond to the selection (or to be empty,
+ // when not typing and nothing is selected)
+ TextareaInput.prototype.reset = function (typing) {
+ if (this.contextMenuPending || this.composing) { return }
+ var cm = this.cm;
+ if (cm.somethingSelected()) {
+ this.prevInput = "";
+ var content = cm.getSelection();
+ this.textarea.value = content;
+ if (cm.state.focused) { selectInput(this.textarea); }
+ if (ie && ie_version >= 9) { this.hasSelection = content; }
+ } else if (!typing) {
+ this.prevInput = this.textarea.value = "";
+ if (ie && ie_version >= 9) { this.hasSelection = null; }
+ }
+ };
+
+ TextareaInput.prototype.getField = function () { return this.textarea };
+
+ TextareaInput.prototype.supportsTouch = function () { return false };
+
+ TextareaInput.prototype.focus = function () {
+ if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) {
+ try { this.textarea.focus(); }
+ catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
+ }
+ };
+
+ TextareaInput.prototype.blur = function () { this.textarea.blur(); };
+
+ TextareaInput.prototype.resetPosition = function () {
+ this.wrapper.style.top = this.wrapper.style.left = 0;
+ };
+
+ TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); };
+
+ // Poll for input changes, using the normal rate of polling. This
+ // runs as long as the editor is focused.
+ TextareaInput.prototype.slowPoll = function () {
+ var this$1 = this;
+
+ if (this.pollingFast) { return }
+ this.polling.set(this.cm.options.pollInterval, function () {
+ this$1.poll();
+ if (this$1.cm.state.focused) { this$1.slowPoll(); }
+ });
+ };
+
+ // When an event has just come in that is likely to add or change
+ // something in the input textarea, we poll faster, to ensure that
+ // the change appears on the screen quickly.
+ TextareaInput.prototype.fastPoll = function () {
+ var missed = false, input = this;
+ input.pollingFast = true;
+ function p() {
+ var changed = input.poll();
+ if (!changed && !missed) {missed = true; input.polling.set(60, p);}
+ else {input.pollingFast = false; input.slowPoll();}
+ }
+ input.polling.set(20, p);
+ };
+
+ // Read input from the textarea, and update the document to match.
+ // When something is selected, it is present in the textarea, and
+ // selected (unless it is huge, in which case a placeholder is
+ // used). When nothing is selected, the cursor sits after previously
+ // seen text (can be empty), which is stored in prevInput (we must
+ // not reset the textarea when typing, because that breaks IME).
+ TextareaInput.prototype.poll = function () {
+ var this$1 = this;
+
+ var cm = this.cm, input = this.textarea, prevInput = this.prevInput;
+ // Since this is called a *lot*, try to bail out as cheaply as
+ // possible when it is clear that nothing happened. hasSelection
+ // will be the case when there is a lot of text in the textarea,
+ // in which case reading its value would be expensive.
+ if (this.contextMenuPending || !cm.state.focused ||
+ (hasSelection(input) && !prevInput && !this.composing) ||
+ cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
+ { return false }
+
+ var text = input.value;
+ // If nothing changed, bail.
+ if (text == prevInput && !cm.somethingSelected()) { return false }
+ // Work around nonsensical selection resetting in IE9/10, and
+ // inexplicable appearance of private area unicode characters on
+ // some key combos in Mac (#2689).
+ if (ie && ie_version >= 9 && this.hasSelection === text ||
+ mac && /[\uf700-\uf7ff]/.test(text)) {
+ cm.display.input.reset();
+ return false
+ }
+
+ if (cm.doc.sel == cm.display.selForContextMenu) {
+ var first = text.charCodeAt(0);
+ if (first == 0x200b && !prevInput) { prevInput = "\u200b"; }
+ if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") }
+ }
+ // Find the part of the input that is actually new
+ var same = 0, l = Math.min(prevInput.length, text.length);
+ while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same; }
+
+ runInOp(cm, function () {
+ applyTextInput(cm, text.slice(same), prevInput.length - same,
+ null, this$1.composing ? "*compose" : null);
+
+ // Don't leave long text in the textarea, since it makes further polling slow
+ if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = ""; }
+ else { this$1.prevInput = text; }
+
+ if (this$1.composing) {
+ this$1.composing.range.clear();
+ this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"),
+ {className: "CodeMirror-composing"});
+ }
+ });
+ return true
+ };
+
+ TextareaInput.prototype.ensurePolled = function () {
+ if (this.pollingFast && this.poll()) { this.pollingFast = false; }
+ };
+
+ TextareaInput.prototype.onKeyPress = function () {
+ if (ie && ie_version >= 9) { this.hasSelection = null; }
+ this.fastPoll();
+ };
+
+ TextareaInput.prototype.onContextMenu = function (e) {
+ var input = this, cm = input.cm, display = cm.display, te = input.textarea;
+ if (input.contextMenuPending) { input.contextMenuPending(); }
+ var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
+ if (!pos || presto) { return } // Opera is difficult.
+
+ // Reset the current text selection only if the click is done outside of the selection
+ // and 'resetSelectionOnContextMenu' option is true.
+ var reset = cm.options.resetSelectionOnContextMenu;
+ if (reset && cm.doc.sel.contains(pos) == -1)
+ { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); }
+
+ var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText;
+ var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect();
+ input.wrapper.style.cssText = "position: static";
+ te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
+ var oldScrollY;
+ if (webkit) { oldScrollY = window.scrollY; } // Work around Chrome issue (#2712)
+ display.input.focus();
+ if (webkit) { window.scrollTo(null, oldScrollY); }
+ display.input.reset();
+ // Adds "Select all" to context menu in FF
+ if (!cm.somethingSelected()) { te.value = input.prevInput = " "; }
+ input.contextMenuPending = rehide;
+ display.selForContextMenu = cm.doc.sel;
+ clearTimeout(display.detectingSelectAll);
+
+ // Select-all will be greyed out if there's nothing to select, so
+ // this adds a zero-width space so that we can later check whether
+ // it got selected.
+ function prepareSelectAllHack() {
+ if (te.selectionStart != null) {
+ var selected = cm.somethingSelected();
+ var extval = "\u200b" + (selected ? te.value : "");
+ te.value = "\u21da"; // Used to catch context-menu undo
+ te.value = extval;
+ input.prevInput = selected ? "" : "\u200b";
+ te.selectionStart = 1; te.selectionEnd = extval.length;
+ // Re-set this, in case some other handler touched the
+ // selection in the meantime.
+ display.selForContextMenu = cm.doc.sel;
+ }
+ }
+ function rehide() {
+ if (input.contextMenuPending != rehide) { return }
+ input.contextMenuPending = false;
+ input.wrapper.style.cssText = oldWrapperCSS;
+ te.style.cssText = oldCSS;
+ if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); }
+
+ // Try to detect the user choosing select-all
+ if (te.selectionStart != null) {
+ if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack(); }
+ var i = 0, poll = function () {
+ if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&
+ te.selectionEnd > 0 && input.prevInput == "\u200b") {
+ operation(cm, selectAll)(cm);
+ } else if (i++ < 10) {
+ display.detectingSelectAll = setTimeout(poll, 500);
+ } else {
+ display.selForContextMenu = null;
+ display.input.reset();
+ }
+ };
+ display.detectingSelectAll = setTimeout(poll, 200);
+ }
+ }
+
+ if (ie && ie_version >= 9) { prepareSelectAllHack(); }
+ if (captureRightClick) {
+ e_stop(e);
+ var mouseup = function () {
+ off(window, "mouseup", mouseup);
+ setTimeout(rehide, 20);
+ };
+ on(window, "mouseup", mouseup);
+ } else {
+ setTimeout(rehide, 50);
+ }
+ };
+
+ TextareaInput.prototype.readOnlyChanged = function (val) {
+ if (!val) { this.reset(); }
+ this.textarea.disabled = val == "nocursor";
+ };
+
+ TextareaInput.prototype.setUneditable = function () {};
+
+ TextareaInput.prototype.needsContentAttribute = false;
+
+ function fromTextArea(textarea, options) {
+ options = options ? copyObj(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 = activeElt();
+ options.autofocus = hasFocus == textarea ||
+ textarea.getAttribute("autofocus") != null && hasFocus == document.body;
+ }
+
+ function save() {textarea.value = cm.getValue();}
+
+ var realSubmit;
+ 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) {}
+ }
+ }
+
+ options.finishInit = function (cm) {
+ cm.save = save;
+ cm.getTextArea = function () { return textarea; };
+ cm.toTextArea = function () {
+ cm.toTextArea = isNaN; // Prevent this from being ran twice
+ 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; }
+ }
+ };
+ };
+
+ textarea.style.display = "none";
+ var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); },
+ options);
+ return cm
+ }
+
+ function addLegacyProps(CodeMirror) {
+ CodeMirror.off = off;
+ CodeMirror.on = on;
+ CodeMirror.wheelEventPixels = wheelEventPixels;
+ CodeMirror.Doc = Doc;
+ CodeMirror.splitLines = splitLinesAuto;
+ CodeMirror.countColumn = countColumn;
+ CodeMirror.findColumn = findColumn;
+ CodeMirror.isWordChar = isWordCharBasic;
+ CodeMirror.Pass = Pass;
+ CodeMirror.signal = signal;
+ CodeMirror.Line = Line;
+ CodeMirror.changeEnd = changeEnd;
+ CodeMirror.scrollbarModel = scrollbarModel;
+ CodeMirror.Pos = Pos;
+ CodeMirror.cmpPos = cmp;
+ CodeMirror.modes = modes;
+ CodeMirror.mimeModes = mimeModes;
+ CodeMirror.resolveMode = resolveMode;
+ CodeMirror.getMode = getMode;
+ CodeMirror.modeExtensions = modeExtensions;
+ CodeMirror.extendMode = extendMode;
+ CodeMirror.copyState = copyState;
+ CodeMirror.startState = startState;
+ CodeMirror.innerMode = innerMode;
+ CodeMirror.commands = commands;
+ CodeMirror.keyMap = keyMap;
+ CodeMirror.keyName = keyName;
+ CodeMirror.isModifierKey = isModifierKey;
+ CodeMirror.lookupKey = lookupKey;
+ CodeMirror.normalizeKeyMap = normalizeKeyMap;
+ CodeMirror.StringStream = StringStream;
+ CodeMirror.SharedTextMarker = SharedTextMarker;
+ CodeMirror.TextMarker = TextMarker;
+ CodeMirror.LineWidget = LineWidget;
+ CodeMirror.e_preventDefault = e_preventDefault;
+ CodeMirror.e_stopPropagation = e_stopPropagation;
+ CodeMirror.e_stop = e_stop;
+ CodeMirror.addClass = addClass;
+ CodeMirror.contains = contains;
+ CodeMirror.rmClass = rmClass;
+ CodeMirror.keyNames = keyNames;
+ }
+
+ // EDITOR CONSTRUCTOR
+
+ defineOptions(CodeMirror);
+
+ addEditorMethods(CodeMirror);
+
+ // Set up methods on CodeMirror's prototype to redirect to the editor's document.
+ var dontDelegate = "iter insert remove copy getEditor constructor".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]); } }
+
+ eventMixin(Doc);
+ CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput};
+
+ // Extra arguments are stored as the mode's dependencies, which is
+ // used by (legacy) mechanisms like loadmode.js to automatically
+ // load a mode. (Preferred mechanism is the require/define calls.)
+ CodeMirror.defineMode = function(name/*, mode, …*/) {
+ if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name; }
+ defineMode.apply(this, arguments);
+ };
+
+ CodeMirror.defineMIME = defineMIME;
+
+ // Minimal default mode.
+ CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); });
+ CodeMirror.defineMIME("text/plain", "null");
+
+ // EXTENSIONS
+
+ CodeMirror.defineExtension = function (name, func) {
+ CodeMirror.prototype[name] = func;
+ };
+ CodeMirror.defineDocExtension = function (name, func) {
+ Doc.prototype[name] = func;
+ };
+
+ CodeMirror.fromTextArea = fromTextArea;
+
+ addLegacyProps(CodeMirror);
+
+ CodeMirror.version = "5.48.2";
+
return CodeMirror;
-})();
+
+})));
diff --git a/scripts/codemirror/lib/util/closetag.js b/scripts/codemirror/lib/util/closetag.js
deleted file mode 100644
index 656e93c..0000000
--- a/scripts/codemirror/lib/util/closetag.js
+++ /dev/null
@@ -1,164 +0,0 @@
-/**
- * Tag-closer extension for CodeMirror.
- *
- * This extension adds a "closeTag" utility function that can be used with key bindings to
- * insert a matching end tag after the ">" character of a start tag has been typed. It can
- * also complete "" if a matching start tag is found. It will correctly ignore signal
- * characters for empty tags, comments, CDATA, etc.
- *
- * The function depends on internal parser state to identify tags. It is compatible with the
- * following CodeMirror modes and will ignore all others:
- * - htmlmixed
- * - xml
- *
- * See demos/closetag.html for a usage example.
- *
- * @author Nathan Williams
- * Contributed under the same license terms as CodeMirror.
- */
-(function() {
- /** Option that allows tag closing behavior to be toggled. Default is true. */
- CodeMirror.defaults['closeTagEnabled'] = true;
-
- /** Array of tag names to add indentation after the start tag for. Default is the list of block-level html tags. */
- CodeMirror.defaults['closeTagIndent'] = ['applet', 'blockquote', 'body', 'button', 'div', 'dl', 'fieldset', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'iframe', 'layer', 'legend', 'object', 'ol', 'p', 'select', 'table', 'ul'];
-
- /** Array of tag names where an end tag is forbidden. */
- CodeMirror.defaults['closeTagVoid'] = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
-
- /**
- * Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass.
- * - cm: The editor instance.
- * - ch: The character being processed.
- * - indent: Optional. An array of tag names to indent when closing. Omit or pass true to use the default indentation tag list defined in the 'closeTagIndent' option.
- * Pass false to disable indentation. Pass an array to override the default list of tag names.
- * - vd: Optional. An array of tag names that should not be closed. Omit to use the default void (end tag forbidden) tag list defined in the 'closeTagVoid' option. Ignored in xml mode.
- */
- CodeMirror.defineExtension("closeTag", function(cm, ch, indent, vd) {
- if (!cm.getOption('closeTagEnabled')) {
- throw CodeMirror.Pass;
- }
-
- var mode = cm.getOption('mode');
-
- if (mode == 'text/html' || mode == 'xml') {
-
- /*
- * Relevant structure of token:
- *
- * htmlmixed
- * className
- * state
- * htmlState
- * type
- * tagName
- * context
- * tagName
- * mode
- *
- * xml
- * className
- * state
- * tagName
- * type
- */
-
- var pos = cm.getCursor();
- var tok = cm.getTokenAt(pos);
- var state = tok.state;
-
- if (state.mode && state.mode != 'html') {
- throw CodeMirror.Pass; // With htmlmixed, we only care about the html sub-mode.
- }
-
- if (ch == '>') {
- var type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml
-
- if (tok.className == 'tag' && type == 'closeTag') {
- throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag.
- }
-
- cm.replaceSelection('>'); // Mode state won't update until we finish the tag.
- pos = {line: pos.line, ch: pos.ch + 1};
- cm.setCursor(pos);
-
- tok = cm.getTokenAt(cm.getCursor());
- state = tok.state;
- type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml
-
- if (tok.className == 'tag' && type != 'selfcloseTag') {
- var tagName = state.htmlState ? state.htmlState.tagName : state.tagName; // htmlmixed : xml
- if (tagName.length > 0 && shouldClose(cm, vd, tagName)) {
- insertEndTag(cm, indent, pos, tagName);
- }
- return;
- }
-
- // Undo the '>' insert and allow cm to handle the key instead.
- cm.setSelection({line: pos.line, ch: pos.ch - 1}, pos);
- cm.replaceSelection("");
-
- } else if (ch == '/') {
- if (tok.className == 'tag' && tok.string == '<') {
- var tagName = state.htmlState ? (state.htmlState.context ? state.htmlState.context.tagName : '') : (state.context ? state.context.tagName : ''); // htmlmixed : xml
- if (tagName.length > 0) {
- completeEndTag(cm, pos, tagName);
- return;
- }
- }
- }
-
- }
-
- throw CodeMirror.Pass; // Bubble if not handled
- });
-
- function insertEndTag(cm, indent, pos, tagName) {
- if (shouldIndent(cm, indent, tagName)) {
- cm.replaceSelection('\n\n' + tagName + '>', 'end');
- cm.indentLine(pos.line + 1);
- cm.indentLine(pos.line + 2);
- cm.setCursor({line: pos.line + 1, ch: cm.getLine(pos.line + 1).length});
- } else {
- cm.replaceSelection('' + tagName + '>');
- cm.setCursor(pos);
- }
- }
-
- function shouldIndent(cm, indent, tagName) {
- if (typeof indent == 'undefined' || indent == null || indent == true) {
- indent = cm.getOption('closeTagIndent');
- }
- if (!indent) {
- indent = [];
- }
- return indexOf(indent, tagName.toLowerCase()) != -1;
- }
-
- function shouldClose(cm, vd, tagName) {
- if (cm.getOption('mode') == 'xml') {
- return true; // always close xml tags
- }
- if (typeof vd == 'undefined' || vd == null) {
- vd = cm.getOption('closeTagVoid');
- }
- if (!vd) {
- vd = [];
- }
- return indexOf(vd, tagName.toLowerCase()) == -1;
- }
-
- // C&P from codemirror.js...would be nice if this were visible to utilities.
- 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 completeEndTag(cm, pos, tagName) {
- cm.replaceSelection('/' + tagName + '>');
- cm.setCursor({line: pos.line, ch: pos.ch + tagName.length + 2 });
- }
-
-})();
diff --git a/scripts/codemirror/lib/util/dialog.js b/scripts/codemirror/lib/util/dialog.js
deleted file mode 100644
index 7aad7ea..0000000
--- a/scripts/codemirror/lib/util/dialog.js
+++ /dev/null
@@ -1,70 +0,0 @@
-// Open simple dialogs on top of an editor. Relies on dialog.css.
-
-(function() {
- function dialogDiv(cm, template) {
- var wrap = cm.getWrapperElement();
- var dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild);
- dialog.className = "CodeMirror-dialog";
- dialog.innerHTML = '
' + template + '
';
- return dialog;
- }
-
- CodeMirror.defineExtension("openDialog", function(template, callback) {
- var dialog = dialogDiv(this, template);
- var closed = false, me = this;
- function close() {
- if (closed) return;
- closed = true;
- dialog.parentNode.removeChild(dialog);
- }
- var inp = dialog.getElementsByTagName("input")[0], button;
- if (inp) {
- CodeMirror.connect(inp, "keydown", function(e) {
- if (e.keyCode == 13 || e.keyCode == 27) {
- CodeMirror.e_stop(e);
- close();
- me.focus();
- if (e.keyCode == 13) callback(inp.value);
- }
- });
- inp.focus();
- CodeMirror.connect(inp, "blur", close);
- } else if (button = dialog.getElementsByTagName("button")[0]) {
- CodeMirror.connect(button, "click", function() {
- close();
- me.focus();
- });
- button.focus();
- CodeMirror.connect(button, "blur", close);
- }
- return close;
- });
-
- CodeMirror.defineExtension("openConfirm", function(template, callbacks) {
- var dialog = dialogDiv(this, template);
- var buttons = dialog.getElementsByTagName("button");
- var closed = false, me = this, blurring = 1;
- function close() {
- if (closed) return;
- closed = true;
- dialog.parentNode.removeChild(dialog);
- me.focus();
- }
- buttons[0].focus();
- for (var i = 0; i < buttons.length; ++i) {
- var b = buttons[i];
- (function(callback) {
- CodeMirror.connect(b, "click", function(e) {
- CodeMirror.e_preventDefault(e);
- close();
- if (callback) callback(me);
- });
- })(callbacks[i]);
- CodeMirror.connect(b, "blur", function() {
- --blurring;
- setTimeout(function() { if (blurring <= 0) close(); }, 200);
- });
- CodeMirror.connect(b, "focus", function() { ++blurring; });
- }
- });
-})();
\ No newline at end of file
diff --git a/scripts/codemirror/lib/util/foldcode.js b/scripts/codemirror/lib/util/foldcode.js
deleted file mode 100644
index 02cfb50..0000000
--- a/scripts/codemirror/lib/util/foldcode.js
+++ /dev/null
@@ -1,196 +0,0 @@
-// the tagRangeFinder function is
-// Copyright (C) 2011 by Daniel Glazman
-// released under the MIT license (../../LICENSE) like the rest of CodeMirror
-CodeMirror.tagRangeFinder = function(cm, line, hideEnd) {
- var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
- var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
- var xmlNAMERegExp = new RegExp("^[" + nameStartChar + "][" + nameChar + "]*");
-
- var lineText = cm.getLine(line);
- var found = false;
- var tag = null;
- var pos = 0;
- while (!found) {
- pos = lineText.indexOf("<", pos);
- if (-1 == pos) // no tag on line
- return;
- if (pos + 1 < lineText.length && lineText[pos + 1] == "/") { // closing tag
- pos++;
- continue;
- }
- // ok we weem to have a start tag
- if (!lineText.substr(pos + 1).match(xmlNAMERegExp)) { // not a tag name...
- pos++;
- continue;
- }
- var gtPos = lineText.indexOf(">", pos + 1);
- if (-1 == gtPos) { // end of start tag not in line
- var l = line + 1;
- var foundGt = false;
- var lastLine = cm.lineCount();
- while (l < lastLine && !foundGt) {
- var lt = cm.getLine(l);
- var gt = lt.indexOf(">");
- if (-1 != gt) { // found a >
- foundGt = true;
- var slash = lt.lastIndexOf("/", gt);
- if (-1 != slash && slash < gt) {
- var str = lineText.substr(slash, gt - slash + 1);
- if (!str.match( /\/\s*\>/ )) { // yep, that's the end of empty tag
- if (hideEnd === true) l++;
- return l;
- }
- }
- }
- l++;
- }
- found = true;
- }
- else {
- var slashPos = lineText.lastIndexOf("/", gtPos);
- if (-1 == slashPos) { // cannot be empty tag
- found = true;
- // don't continue
- }
- else { // empty tag?
- // check if really empty tag
- var str = lineText.substr(slashPos, gtPos - slashPos + 1);
- if (!str.match( /\/\s*\>/ )) { // finally not empty
- found = true;
- // don't continue
- }
- }
- }
- if (found) {
- var subLine = lineText.substr(pos + 1);
- tag = subLine.match(xmlNAMERegExp);
- if (tag) {
- // we have an element name, wooohooo !
- tag = tag[0];
- // do we have the close tag on same line ???
- if (-1 != lineText.indexOf("" + tag + ">", pos)) // yep
- {
- found = false;
- }
- // we don't, so we have a candidate...
- }
- else
- found = false;
- }
- if (!found)
- pos++;
- }
-
- if (found) {
- var startTag = "(\\<\\/" + tag + "\\>)|(\\<" + tag + "\\>)|(\\<" + tag + "\\s)|(\\<" + tag + "$)";
- var startTagRegExp = new RegExp(startTag, "g");
- var endTag = "" + tag + ">";
- var depth = 1;
- var l = line + 1;
- var lastLine = cm.lineCount();
- while (l < lastLine) {
- lineText = cm.getLine(l);
- var match = lineText.match(startTagRegExp);
- if (match) {
- for (var i = 0; i < match.length; i++) {
- if (match[i] == endTag)
- depth--;
- else
- depth++;
- if (!depth) {
- if (hideEnd === true) l++;
- return l;
- }
- }
- }
- l++;
- }
- return;
- }
-};
-
-CodeMirror.braceRangeFinder = function(cm, line, hideEnd) {
- var lineText = cm.getLine(line), at = lineText.length, startChar, tokenType;
- for (;;) {
- var found = lineText.lastIndexOf("{", at);
- if (found < 0) break;
- tokenType = cm.getTokenAt({line: line, ch: found}).className;
- if (!/^(comment|string)/.test(tokenType)) { startChar = found; break; }
- at = found - 1;
- }
- if (startChar == null || lineText.lastIndexOf("}") > startChar) return;
- var count = 1, lastLine = cm.lineCount(), end;
- outer: for (var i = line + 1; i < lastLine; ++i) {
- var text = cm.getLine(i), pos = 0;
- for (;;) {
- var nextOpen = text.indexOf("{", pos), nextClose = text.indexOf("}", pos);
- if (nextOpen < 0) nextOpen = text.length;
- if (nextClose < 0) nextClose = text.length;
- pos = Math.min(nextOpen, nextClose);
- if (pos == text.length) break;
- if (cm.getTokenAt({line: i, ch: pos + 1}).className == tokenType) {
- if (pos == nextOpen) ++count;
- else if (!--count) { end = i; break outer; }
- }
- ++pos;
- }
- }
- if (end == null || end == line + 1) return;
- if (hideEnd === true) end++;
- return end;
-};
-
-CodeMirror.indentRangeFinder = function(cm, line) {
- var tabSize = cm.getOption("tabSize");
- var myIndent = cm.getLineHandle(line).indentation(tabSize), last;
- for (var i = line + 1, end = cm.lineCount(); i < end; ++i) {
- var handle = cm.getLineHandle(i);
- if (!/^\s*$/.test(handle.text)) {
- if (handle.indentation(tabSize) <= myIndent) break;
- last = i;
- }
- }
- if (!last) return null;
- return last + 1;
-};
-
-CodeMirror.newFoldFunction = function(rangeFinder, markText, hideEnd) {
- var folded = [];
- if (markText == null) markText = '
▼
%N%';
-
- function isFolded(cm, n) {
- for (var i = 0; i < folded.length; ++i) {
- var start = cm.lineInfo(folded[i].start);
- if (!start) folded.splice(i--, 1);
- else if (start.line == n) return {pos: i, region: folded[i]};
- }
- }
-
- function expand(cm, region) {
- cm.clearMarker(region.start);
- for (var i = 0; i < region.hidden.length; ++i)
- cm.showLine(region.hidden[i]);
- }
-
- return function(cm, line) {
- cm.operation(function() {
- var known = isFolded(cm, line);
- if (known) {
- folded.splice(known.pos, 1);
- expand(cm, known.region);
- } else {
- var end = rangeFinder(cm, line, hideEnd);
- if (end == null) return;
- var hidden = [];
- for (var i = line + 1; i < end; ++i) {
- var handle = cm.hideLine(i);
- if (handle) hidden.push(handle);
- }
- var first = cm.setMarker(line, markText);
- var region = {start: first, hidden: hidden};
- cm.onDeleteLine(first, function() { expand(cm, region); });
- folded.push(region);
- }
- });
- };
-};
diff --git a/scripts/codemirror/lib/util/formatting.js b/scripts/codemirror/lib/util/formatting.js
deleted file mode 100644
index 22c943f..0000000
--- a/scripts/codemirror/lib/util/formatting.js
+++ /dev/null
@@ -1,299 +0,0 @@
-// ============== Formatting extensions ============================
-// A common storage for all mode-specific formatting features
-if (!CodeMirror.modeExtensions) CodeMirror.modeExtensions = {};
-
-// Returns the extension of the editor's current mode
-CodeMirror.defineExtension("getModeExt", function () {
- var mname = CodeMirror.resolveMode(this.getOption("mode")).name;
- var ext = CodeMirror.modeExtensions[mname];
- if (!ext) throw new Error("No extensions found for mode " + mname);
- return ext;
-});
-
-// If the current mode is 'htmlmixed', returns the extension of a mode located at
-// the specified position (can be htmlmixed, css or javascript). Otherwise, simply
-// returns the extension of the editor's current mode.
-CodeMirror.defineExtension("getModeExtAtPos", function (pos) {
- var token = this.getTokenAt(pos);
- if (token && token.state && token.state.mode)
- return CodeMirror.modeExtensions[token.state.mode == "html" ? "htmlmixed" : token.state.mode];
- else
- return this.getModeExt();
-});
-
-// Comment/uncomment the specified range
-CodeMirror.defineExtension("commentRange", function (isComment, from, to) {
- var curMode = this.getModeExtAtPos(this.getCursor());
- if (isComment) { // Comment range
- var commentedText = this.getRange(from, to);
- this.replaceRange(curMode.commentStart + this.getRange(from, to) + curMode.commentEnd
- , from, to);
- if (from.line == to.line && from.ch == to.ch) { // An empty comment inserted - put cursor inside
- this.setCursor(from.line, from.ch + curMode.commentStart.length);
- }
- }
- else { // Uncomment range
- var selText = this.getRange(from, to);
- var startIndex = selText.indexOf(curMode.commentStart);
- var endIndex = selText.lastIndexOf(curMode.commentEnd);
- if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) {
- // Take string till comment start
- selText = selText.substr(0, startIndex)
- // From comment start till comment end
- + selText.substring(startIndex + curMode.commentStart.length, endIndex)
- // From comment end till string end
- + selText.substr(endIndex + curMode.commentEnd.length);
- }
- this.replaceRange(selText, from, to);
- }
-});
-
-// Applies automatic mode-aware indentation to the specified range
-CodeMirror.defineExtension("autoIndentRange", function (from, to) {
- var cmInstance = this;
- this.operation(function () {
- for (var i = from.line; i <= to.line; i++) {
- cmInstance.indentLine(i, "smart");
- }
- });
-});
-
-// Applies automatic formatting to the specified range
-CodeMirror.defineExtension("autoFormatRange", function (from, to) {
- var absStart = this.indexFromPos(from);
- var absEnd = this.indexFromPos(to);
- // Insert additional line breaks where necessary according to the
- // mode's syntax
- var res = this.getModeExt().autoFormatLineBreaks(this.getValue(), absStart, absEnd);
- var cmInstance = this;
-
- // Replace and auto-indent the range
- this.operation(function () {
- cmInstance.replaceRange(res, from, to);
- var startLine = cmInstance.posFromIndex(absStart).line;
- var endLine = cmInstance.posFromIndex(absStart + res.length).line;
- for (var i = startLine; i <= endLine; i++) {
- cmInstance.indentLine(i, "smart");
- }
- });
-});
-
-// Define extensions for a few modes
-
-CodeMirror.modeExtensions["css"] = {
- commentStart: "/*",
- commentEnd: "*/",
- wordWrapChars: [";", "\\{", "\\}"],
- autoFormatLineBreaks: function (text, startPos, endPos) {
- text = text.substring(startPos, endPos);
- return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2");
- }
-};
-
-CodeMirror.modeExtensions["javascript"] = {
- commentStart: "/*",
- commentEnd: "*/",
- wordWrapChars: [";", "\\{", "\\}"],
-
- getNonBreakableBlocks: function (text) {
- var nonBreakableRegexes = [
- new RegExp("for\\s*?\\(([\\s\\S]*?)\\)"),
- new RegExp("\\\\\"([\\s\\S]*?)(\\\\\"|$)"),
- new RegExp("\\\\\'([\\s\\S]*?)(\\\\\'|$)"),
- new RegExp("'([\\s\\S]*?)('|$)"),
- new RegExp("\"([\\s\\S]*?)(\"|$)"),
- new RegExp("//.*([\r\n]|$)")
- ];
- var nonBreakableBlocks = new Array();
- for (var i = 0; i < nonBreakableRegexes.length; i++) {
- var curPos = 0;
- while (curPos < text.length) {
- var m = text.substr(curPos).match(nonBreakableRegexes[i]);
- if (m != null) {
- nonBreakableBlocks.push({
- start: curPos + m.index,
- end: curPos + m.index + m[0].length
- });
- curPos += m.index + Math.max(1, m[0].length);
- }
- else { // No more matches
- break;
- }
- }
- }
- nonBreakableBlocks.sort(function (a, b) {
- return a.start - b.start;
- });
-
- return nonBreakableBlocks;
- },
-
- autoFormatLineBreaks: function (text, startPos, endPos) {
- text = text.substring(startPos, endPos);
- var curPos = 0;
- var reLinesSplitter = new RegExp("(;|\\{|\\})([^\r\n;])", "g");
- var nonBreakableBlocks = this.getNonBreakableBlocks(text);
- if (nonBreakableBlocks != null) {
- var res = "";
- for (var i = 0; i < nonBreakableBlocks.length; i++) {
- if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block
- res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2");
- curPos = nonBreakableBlocks[i].start;
- }
- if (nonBreakableBlocks[i].start <= curPos
- && nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block
- res += text.substring(curPos, nonBreakableBlocks[i].end);
- curPos = nonBreakableBlocks[i].end;
- }
- }
- if (curPos < text.length - 1) {
- res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2");
- }
- return res;
- }
- else {
- return text.replace(reLinesSplitter, "$1\n$2");
- }
- }
-};
-
-CodeMirror.modeExtensions["xml"] = {
- commentStart: "",
- wordWrapChars: [">"],
-
- autoFormatLineBreaks: function (text, startPos, endPos) {
- text = text.substring(startPos, endPos);
- var lines = text.split("\n");
- var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)");
- var reOpenBrackets = new RegExp("<", "g");
- var reCloseBrackets = new RegExp("(>)([^\r\n])", "g");
- for (var i = 0; i < lines.length; i++) {
- var mToProcess = lines[i].match(reProcessedPortion);
- if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces
- lines[i] = mToProcess[1]
- + mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2")
- + mToProcess[3];
- continue;
- }
- }
-
- return lines.join("\n");
- }
-};
-
-CodeMirror.modeExtensions["htmlmixed"] = {
- commentStart: "",
- wordWrapChars: [">", ";", "\\{", "\\}"],
-
- getModeInfos: function (text, absPos) {
- var modeInfos = new Array();
- modeInfos[0] =
- {
- pos: 0,
- modeExt: CodeMirror.modeExtensions["xml"],
- modeName: "xml"
- };
-
- var modeMatchers = new Array();
- modeMatchers[0] =
- {
- regex: new RegExp("]*>|$)", "i"),
- modeExt: CodeMirror.modeExtensions["css"],
- modeName: "css"
- };
- modeMatchers[1] =
- {
- regex: new RegExp("]*>|$)", "i"),
- modeExt: CodeMirror.modeExtensions["javascript"],
- modeName: "javascript"
- };
-
- var lastCharPos = (typeof (absPos) !== "undefined" ? absPos : text.length - 1);
- // Detect modes for the entire text
- for (var i = 0; i < modeMatchers.length; i++) {
- var curPos = 0;
- while (curPos <= lastCharPos) {
- var m = text.substr(curPos).match(modeMatchers[i].regex);
- if (m != null) {
- if (m.length > 1 && m[1].length > 0) {
- // Push block begin pos
- var blockBegin = curPos + m.index + m[0].indexOf(m[1]);
- modeInfos.push(
- {
- pos: blockBegin,
- modeExt: modeMatchers[i].modeExt,
- modeName: modeMatchers[i].modeName
- });
- // Push block end pos
- modeInfos.push(
- {
- pos: blockBegin + m[1].length,
- modeExt: modeInfos[0].modeExt,
- modeName: modeInfos[0].modeName
- });
- curPos += m.index + m[0].length;
- continue;
- }
- else {
- curPos += m.index + Math.max(m[0].length, 1);
- }
- }
- else { // No more matches
- break;
- }
- }
- }
- // Sort mode infos
- modeInfos.sort(function sortModeInfo(a, b) {
- return a.pos - b.pos;
- });
-
- return modeInfos;
- },
-
- autoFormatLineBreaks: function (text, startPos, endPos) {
- var modeInfos = this.getModeInfos(text);
- var reBlockStartsWithNewline = new RegExp("^\\s*?\n");
- var reBlockEndsWithNewline = new RegExp("\n\\s*?$");
- var res = "";
- // Use modes info to break lines correspondingly
- if (modeInfos.length > 1) { // Deal with multi-mode text
- for (var i = 1; i <= modeInfos.length; i++) {
- var selStart = modeInfos[i - 1].pos;
- var selEnd = (i < modeInfos.length ? modeInfos[i].pos : endPos);
-
- if (selStart >= endPos) { // The block starts later than the needed fragment
- break;
- }
- if (selStart < startPos) {
- if (selEnd <= startPos) { // The block starts earlier than the needed fragment
- continue;
- }
- selStart = startPos;
- }
- if (selEnd > endPos) {
- selEnd = endPos;
- }
- var textPortion = text.substring(selStart, selEnd);
- if (modeInfos[i - 1].modeName != "xml") { // Starting a CSS or JavaScript block
- if (!reBlockStartsWithNewline.test(textPortion)
- && selStart > 0) { // The block does not start with a line break
- textPortion = "\n" + textPortion;
- }
- if (!reBlockEndsWithNewline.test(textPortion)
- && selEnd < text.length - 1) { // The block does not end with a line break
- textPortion += "\n";
- }
- }
- res += modeInfos[i - 1].modeExt.autoFormatLineBreaks(textPortion);
- }
- }
- else { // Single-mode text
- res = modeInfos[0].modeExt.autoFormatLineBreaks(text.substring(startPos, endPos));
- }
-
- return res;
- }
-};
diff --git a/scripts/codemirror/lib/util/javascript-hint.js b/scripts/codemirror/lib/util/javascript-hint.js
deleted file mode 100644
index ff15adb..0000000
--- a/scripts/codemirror/lib/util/javascript-hint.js
+++ /dev/null
@@ -1,134 +0,0 @@
-(function () {
- function forEach(arr, f) {
- for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
- }
-
- function arrayContains(arr, item) {
- if (!Array.prototype.indexOf) {
- var i = arr.length;
- while (i--) {
- if (arr[i] === item) {
- return true;
- }
- }
- return false;
- }
- return arr.indexOf(item) != -1;
- }
-
- function scriptHint(editor, keywords, getToken) {
- // Find the token at the cursor
- var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
- // If it's not a 'word-style' token, ignore the token.
- if (!/^[\w$_]*$/.test(token.string)) {
- token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
- className: token.string == "." ? "property" : null};
- }
- // If it is a property, find out what it is a property of.
- while (tprop.className == "property") {
- tprop = getToken(editor, {line: cur.line, ch: tprop.start});
- if (tprop.string != ".") return;
- tprop = getToken(editor, {line: cur.line, ch: tprop.start});
- if (tprop.string == ')') {
- var level = 1;
- do {
- tprop = getToken(editor, {line: cur.line, ch: tprop.start});
- switch (tprop.string) {
- case ')': level++; break;
- case '(': level--; break;
- default: break;
- }
- } while (level > 0);
- tprop = getToken(editor, {line: cur.line, ch: tprop.start});
- if (tprop.className == 'variable')
- tprop.className = 'function';
- else return; // no clue
- }
- if (!context) var context = [];
- context.push(tprop);
- }
- return {list: getCompletions(token, context, keywords),
- from: {line: cur.line, ch: token.start},
- to: {line: cur.line, ch: token.end}};
- }
-
- CodeMirror.javascriptHint = function(editor) {
- return scriptHint(editor, javascriptKeywords,
- function (e, cur) {return e.getTokenAt(cur);});
- };
-
- function getCoffeeScriptToken(editor, cur) {
- // This getToken, it is for coffeescript, imitates the behavior of
- // getTokenAt method in javascript.js, that is, returning "property"
- // type and treat "." as indepenent token.
- var token = editor.getTokenAt(cur);
- if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
- token.end = token.start;
- token.string = '.';
- token.className = "property";
- }
- else if (/^\.[\w$_]*$/.test(token.string)) {
- token.className = "property";
- token.start++;
- token.string = token.string.replace(/\./, '');
- }
- return token;
- }
-
- CodeMirror.coffeescriptHint = function(editor) {
- return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken);
- };
-
- var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
- "toUpperCase toLowerCase split concat match replace search").split(" ");
- var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
- "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
- var funcProps = "prototype apply call bind".split(" ");
- var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " +
- "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
- var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
- "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
-
- function getCompletions(token, context, keywords) {
- var found = [], start = token.string;
- function maybeAdd(str) {
- if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
- }
- function gatherCompletions(obj) {
- if (typeof obj == "string") forEach(stringProps, maybeAdd);
- else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
- else if (obj instanceof Function) forEach(funcProps, maybeAdd);
- for (var name in obj) maybeAdd(name);
- }
-
- if (context) {
- // If this is a property, see if it belongs to some object we can
- // find in the current environment.
- var obj = context.pop(), base;
- if (obj.className == "variable")
- base = window[obj.string];
- else if (obj.className == "string")
- base = "";
- else if (obj.className == "atom")
- base = 1;
- else if (obj.className == "function") {
- if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
- (typeof window.jQuery == 'function'))
- base = window.jQuery();
- else if (window._ != null && (obj.string == '_') && (typeof window._ == 'function'))
- base = window._();
- }
- while (base != null && context.length)
- base = base[context.pop().string];
- if (base != null) gatherCompletions(base);
- }
- else {
- // If not, just look in the window object and any local scope
- // (reading into JS mode internals to get at the local variables)
- for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
- gatherCompletions(window);
- forEach(keywords, maybeAdd);
- }
- return found;
- }
-})();
diff --git a/scripts/codemirror/lib/util/match-highlighter.js b/scripts/codemirror/lib/util/match-highlighter.js
deleted file mode 100644
index 59098ff..0000000
--- a/scripts/codemirror/lib/util/match-highlighter.js
+++ /dev/null
@@ -1,44 +0,0 @@
-// Define match-highlighter commands. Depends on searchcursor.js
-// Use by attaching the following function call to the onCursorActivity event:
- //myCodeMirror.matchHighlight(minChars);
-// And including a special span.CodeMirror-matchhighlight css class (also optionally a separate one for .CodeMirror-focused -- see demo matchhighlighter.html)
-
-(function() {
- var DEFAULT_MIN_CHARS = 2;
-
- function MatchHighlightState() {
- this.marked = [];
- }
- function getMatchHighlightState(cm) {
- return cm._matchHighlightState || (cm._matchHighlightState = new MatchHighlightState());
- }
-
- function clearMarks(cm) {
- var state = getMatchHighlightState(cm);
- for (var i = 0; i < state.marked.length; ++i)
- state.marked[i].clear();
- state.marked = [];
- }
-
- function markDocument(cm, className, minChars) {
- clearMarks(cm);
- minChars = (typeof minChars !== 'undefined' ? minChars : DEFAULT_MIN_CHARS);
- if (cm.somethingSelected() && cm.getSelection().replace(/^\s+|\s+$/g, "").length >= minChars) {
- var state = getMatchHighlightState(cm);
- var query = cm.getSelection();
- cm.operation(function() {
- if (cm.lineCount() < 2000) { // This is too expensive on big documents.
- for (var cursor = cm.getSearchCursor(query); cursor.findNext();) {
- //Only apply matchhighlight to the matches other than the one actually selected
- if (!(cursor.from().line === cm.getCursor(true).line && cursor.from().ch === cm.getCursor(true).ch))
- state.marked.push(cm.markText(cursor.from(), cursor.to(), className));
- }
- }
- });
- }
- }
-
- CodeMirror.defineExtension("matchHighlight", function(className, minChars) {
- markDocument(this, className, minChars);
- });
-})();
diff --git a/scripts/codemirror/lib/util/multiplex.js b/scripts/codemirror/lib/util/multiplex.js
deleted file mode 100644
index 755588b..0000000
--- a/scripts/codemirror/lib/util/multiplex.js
+++ /dev/null
@@ -1,81 +0,0 @@
-CodeMirror.multiplexingMode = function(outer /*, others */) {
- // Others should be {open, close, mode [, delimStyle]} objects
- var others = Array.prototype.slice.call(arguments, 1);
- var n_others = others.length;
-
- function indexOf(string, pattern, from) {
- if (typeof pattern == "string") return string.indexOf(pattern, from);
- var m = pattern.exec(from ? string.slice(from) : string);
- return m ? m.index + from : -1;
- }
-
- return {
- startState: function() {
- return {
- outer: CodeMirror.startState(outer),
- innerActive: null,
- inner: null
- };
- },
-
- copyState: function(state) {
- return {
- outer: CodeMirror.copyState(outer, state.outer),
- innerActive: state.innerActive,
- inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner)
- };
- },
-
- token: function(stream, state) {
- if (!state.innerActive) {
- var cutOff = Infinity, oldContent = stream.string;
- for (var i = 0; i < n_others; ++i) {
- var other = others[i];
- var found = indexOf(oldContent, other.open, stream.pos);
- if (found == stream.pos) {
- stream.match(other.open);
- state.innerActive = other;
- state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0);
- return other.delimStyle;
- } else if (found != -1 && found < cutOff) {
- cutOff = found;
- }
- }
- if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff);
- var outerToken = outer.token(stream, state.outer);
- if (cutOff != Infinity) stream.string = oldContent;
- return outerToken;
- } else {
- var curInner = state.innerActive, oldContent = stream.string;
- var found = indexOf(oldContent, curInner.close, stream.pos);
- if (found == stream.pos) {
- stream.match(curInner.close);
- state.innerActive = state.inner = null;
- return curInner.delimStyle;
- }
- if (found > -1) stream.string = oldContent.slice(0, found);
- var innerToken = curInner.mode.token(stream, state.inner);
- if (found > -1) stream.string = oldContent;
- var cur = stream.current(), found = cur.indexOf(curInner.close);
- if (found > -1) stream.backUp(cur.length - found);
- return innerToken;
- }
- },
-
- indent: function(state, textAfter) {
- var mode = state.innerActive ? state.innerActive.mode : outer;
- if (!mode.indent) return CodeMirror.Pass;
- return mode.indent(state.innerActive ? state.inner : state.outer, textAfter);
- },
-
- compareStates: function(a, b) {
- if (a.innerActive != b.innerActive) return false;
- var mode = a.innerActive || outer;
- if (!mode.compareStates) return CodeMirror.Pass;
- return mode.compareStates(a.innerActive ? a.inner : a.outer,
- b.innerActive ? b.inner : b.outer);
- },
-
- electricChars: outer.electricChars
- };
-};
diff --git a/scripts/codemirror/lib/util/overlay.js b/scripts/codemirror/lib/util/overlay.js
deleted file mode 100644
index 1d5df6c..0000000
--- a/scripts/codemirror/lib/util/overlay.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Utility function that allows modes to be combined. The mode given
-// as the base argument takes care of most of the normal mode
-// functionality, but a second (typically simple) mode is used, which
-// can override the style of text. Both modes get to parse all of the
-// text, but when both assign a non-null style to a piece of code, the
-// overlay wins, unless the combine argument was true, in which case
-// the styles are combined.
-
-// overlayParser is the old, deprecated name
-CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, combine) {
- return {
- startState: function() {
- return {
- base: CodeMirror.startState(base),
- overlay: CodeMirror.startState(overlay),
- basePos: 0, baseCur: null,
- overlayPos: 0, overlayCur: null
- };
- },
- copyState: function(state) {
- return {
- base: CodeMirror.copyState(base, state.base),
- overlay: CodeMirror.copyState(overlay, state.overlay),
- basePos: state.basePos, baseCur: null,
- overlayPos: state.overlayPos, overlayCur: null
- };
- },
-
- token: function(stream, state) {
- if (stream.start == state.basePos) {
- state.baseCur = base.token(stream, state.base);
- state.basePos = stream.pos;
- }
- if (stream.start == state.overlayPos) {
- stream.pos = stream.start;
- state.overlayCur = overlay.token(stream, state.overlay);
- state.overlayPos = stream.pos;
- }
- stream.pos = Math.min(state.basePos, state.overlayPos);
- if (stream.eol()) state.basePos = state.overlayPos = 0;
-
- if (state.overlayCur == null) return state.baseCur;
- if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur;
- else return state.overlayCur;
- },
-
- indent: base.indent && function(state, textAfter) {
- return base.indent(state.base, textAfter);
- },
- electricChars: base.electricChars
- };
-};
diff --git a/scripts/codemirror/lib/util/pig-hint.js b/scripts/codemirror/lib/util/pig-hint.js
deleted file mode 100644
index 08e0dbf..0000000
--- a/scripts/codemirror/lib/util/pig-hint.js
+++ /dev/null
@@ -1,123 +0,0 @@
-(function () {
- function forEach(arr, f) {
- for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
- }
-
- function arrayContains(arr, item) {
- if (!Array.prototype.indexOf) {
- var i = arr.length;
- while (i--) {
- if (arr[i] === item) {
- return true;
- }
- }
- return false;
- }
- return arr.indexOf(item) != -1;
- }
-
- function scriptHint(editor, keywords, getToken) {
- // Find the token at the cursor
- var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
- // If it's not a 'word-style' token, ignore the token.
-
- if (!/^[\w$_]*$/.test(token.string)) {
- token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
- className: token.string == ":" ? "pig-type" : null};
- }
-
- if (!context) var context = [];
- context.push(tprop);
-
- var completionList = getCompletions(token, context);
- completionList = completionList.sort();
- //prevent autocomplete for last word, instead show dropdown with one word
- if(completionList.length == 1) {
- completionList.push(" ");
- }
-
- return {list: completionList,
- from: {line: cur.line, ch: token.start},
- to: {line: cur.line, ch: token.end}};
- }
-
- CodeMirror.pigHint = function(editor) {
- return scriptHint(editor, pigKeywordsU, function (e, cur) {return e.getTokenAt(cur);});
- };
-
- function toTitleCase(str) {
- return str.replace(/(?:^|\s)\w/g, function(match) {
- return match.toUpperCase();
- });
- }
-
- var pigKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP "
- + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL "
- + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE "
- + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE "
- + "NEQ MATCHES TRUE FALSE";
- var pigKeywordsU = pigKeywords.split(" ");
- var pigKeywordsL = pigKeywords.toLowerCase().split(" ");
-
- var pigTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP";
- var pigTypesU = pigTypes.split(" ");
- var pigTypesL = pigTypes.toLowerCase().split(" ");
-
- var pigBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL "
- + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS "
- + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG "
- + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN "
- + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER "
- + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS "
- + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA "
- + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE "
- + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG "
- + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER";
- var pigBuiltinsU = pigBuiltins.split(" ").join("() ").split(" ");
- var pigBuiltinsL = pigBuiltins.toLowerCase().split(" ").join("() ").split(" ");
- var pigBuiltinsC = ("BagSize BinStorage Bloom BuildBloom ConstantSize CubeDimensions DoubleAbs "
- + "DoubleAvg DoubleBase DoubleMax DoubleMin DoubleRound DoubleSum FloatAbs FloatAvg FloatMax "
- + "FloatMin FloatRound FloatSum GenericInvoker IntAbs IntAvg IntMax IntMin IntSum "
- + "InvokeForDouble InvokeForFloat InvokeForInt InvokeForLong InvokeForString Invoker "
- + "IsEmpty JsonLoader JsonMetadata JsonStorage LongAbs LongAvg LongMax LongMin LongSum MapSize "
- + "MonitoredUDF Nondeterministic OutputSchema PigStorage PigStreaming StringConcat StringMax "
- + "StringMin StringSize TextLoader TupleSize Utf8StorageConverter").split(" ").join("() ").split(" ");
-
- function getCompletions(token, context) {
- var found = [], start = token.string;
- function maybeAdd(str) {
- if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
- }
-
- function gatherCompletions(obj) {
- if(obj == ":") {
- forEach(pigTypesL, maybeAdd);
- }
- else {
- forEach(pigBuiltinsU, maybeAdd);
- forEach(pigBuiltinsL, maybeAdd);
- forEach(pigBuiltinsC, maybeAdd);
- forEach(pigTypesU, maybeAdd);
- forEach(pigTypesL, maybeAdd);
- forEach(pigKeywordsU, maybeAdd);
- forEach(pigKeywordsL, maybeAdd);
- }
- }
-
- if (context) {
- // If this is a property, see if it belongs to some object we can
- // find in the current environment.
- var obj = context.pop(), base;
-
- if (obj.className == "pig-word")
- base = obj.string;
- else if(obj.className == "pig-type")
- base = ":" + obj.string;
-
- while (base != null && context.length)
- base = base[context.pop().string];
- if (base != null) gatherCompletions(base);
- }
- return found;
- }
-})();
diff --git a/scripts/codemirror/lib/util/runmode.js b/scripts/codemirror/lib/util/runmode.js
deleted file mode 100644
index 6723927..0000000
--- a/scripts/codemirror/lib/util/runmode.js
+++ /dev/null
@@ -1,53 +0,0 @@
-CodeMirror.runMode = function(string, modespec, callback, options) {
- function esc(str) {
- return str.replace(/[<&]/, function(ch) { return ch == "<" ? "<" : "&"; });
- }
-
- var mode = CodeMirror.getMode(CodeMirror.defaults, modespec);
- var isNode = callback.nodeType == 1;
- var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
- if (isNode) {
- var node = callback, accum = [], col = 0;
- callback = function(text, style) {
- if (text == "\n") {
- accum.push(" ");
- col = 0;
- return;
- }
- var escaped = "";
- // HTML-escape and replace tabs
- for (var pos = 0;;) {
- var idx = text.indexOf("\t", pos);
- if (idx == -1) {
- escaped += esc(text.slice(pos));
- col += text.length - pos;
- break;
- } else {
- col += idx - pos;
- escaped += esc(text.slice(pos, idx));
- var size = tabSize - col % tabSize;
- col += size;
- for (var i = 0; i < size; ++i) escaped += " ";
- pos = idx + 1;
- }
- }
-
- if (style)
- accum.push("" + escaped + "");
- else
- accum.push(escaped);
- };
- }
- var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode);
- for (var i = 0, e = lines.length; i < e; ++i) {
- if (i) callback("\n");
- var stream = new CodeMirror.StringStream(lines[i]);
- while (!stream.eol()) {
- var style = mode.token(stream, state);
- callback(stream.current(), style, i, stream.start);
- stream.start = stream.pos;
- }
- }
- if (isNode)
- node.innerHTML = accum.join("");
-};
diff --git a/scripts/codemirror/lib/util/search.js b/scripts/codemirror/lib/util/search.js
deleted file mode 100644
index 356283a..0000000
--- a/scripts/codemirror/lib/util/search.js
+++ /dev/null
@@ -1,118 +0,0 @@
-// Define search commands. Depends on dialog.js or another
-// implementation of the openDialog method.
-
-// Replace works a little oddly -- it will do the replace on the next
-// Ctrl-G (or whatever is bound to findNext) press. You prevent a
-// replace by making sure the match is no longer selected when hitting
-// Ctrl-G.
-
-(function() {
- function SearchState() {
- this.posFrom = this.posTo = this.query = null;
- this.marked = [];
- }
- function getSearchState(cm) {
- return cm._searchState || (cm._searchState = new SearchState());
- }
- function getSearchCursor(cm, query, pos) {
- // Heuristic: if the query string is all lowercase, do a case insensitive search.
- return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase());
- }
- function dialog(cm, text, shortText, f) {
- if (cm.openDialog) cm.openDialog(text, f);
- else f(prompt(shortText, ""));
- }
- function confirmDialog(cm, text, shortText, fs) {
- if (cm.openConfirm) cm.openConfirm(text, fs);
- else if (confirm(shortText)) fs[0]();
- }
- function parseQuery(query) {
- var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
- return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
- }
- var queryDialog =
- 'Search: (Use /re/ syntax for regexp search)';
- function doSearch(cm, rev) {
- var state = getSearchState(cm);
- if (state.query) return findNext(cm, rev);
- dialog(cm, queryDialog, "Search for:", function(query) {
- cm.operation(function() {
- if (!query || state.query) return;
- state.query = parseQuery(query);
- if (cm.lineCount() < 2000) { // This is too expensive on big documents.
- for (var cursor = getSearchCursor(cm, state.query); cursor.findNext();)
- state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching"));
- }
- state.posFrom = state.posTo = cm.getCursor();
- findNext(cm, rev);
- });
- });
- }
- function findNext(cm, rev) {cm.operation(function() {
- var state = getSearchState(cm);
- var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
- if (!cursor.find(rev)) {
- cursor = getSearchCursor(cm, state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0});
- if (!cursor.find(rev)) return;
- }
- cm.setSelection(cursor.from(), cursor.to());
- state.posFrom = cursor.from(); state.posTo = cursor.to();
- });}
- function clearSearch(cm) {cm.operation(function() {
- var state = getSearchState(cm);
- if (!state.query) return;
- state.query = null;
- for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear();
- state.marked.length = 0;
- });}
-
- var replaceQueryDialog =
- 'Replace: (Use /re/ syntax for regexp search)';
- var replacementQueryDialog = 'With: ';
- var doReplaceConfirm = "Replace? ";
- function replace(cm, all) {
- dialog(cm, replaceQueryDialog, "Replace:", function(query) {
- if (!query) return;
- query = parseQuery(query);
- dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
- if (all) {
- cm.compoundChange(function() { cm.operation(function() {
- for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
- if (typeof query != "string") {
- var match = cm.getRange(cursor.from(), cursor.to()).match(query);
- cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];}));
- } else cursor.replace(text);
- }
- });});
- } else {
- clearSearch(cm);
- var cursor = getSearchCursor(cm, query, cm.getCursor());
- function advance() {
- var start = cursor.from(), match;
- if (!(match = cursor.findNext())) {
- cursor = getSearchCursor(cm, query);
- if (!(match = cursor.findNext()) ||
- (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
- }
- cm.setSelection(cursor.from(), cursor.to());
- confirmDialog(cm, doReplaceConfirm, "Replace?",
- [function() {doReplace(match);}, advance]);
- }
- function doReplace(match) {
- cursor.replace(typeof query == "string" ? text :
- text.replace(/\$(\d)/, function(w, i) {return match[i];}));
- advance();
- }
- advance();
- }
- });
- });
- }
-
- CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
- CodeMirror.commands.findNext = doSearch;
- CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
- CodeMirror.commands.clearSearch = clearSearch;
- CodeMirror.commands.replace = replace;
- CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
-})();
diff --git a/scripts/codemirror/lib/util/searchcursor.js b/scripts/codemirror/lib/util/searchcursor.js
deleted file mode 100644
index 970af89..0000000
--- a/scripts/codemirror/lib/util/searchcursor.js
+++ /dev/null
@@ -1,119 +0,0 @@
-(function(){
- function SearchCursor(cm, query, pos, caseFold) {
- this.atOccurrence = false; this.cm = cm;
- if (caseFold == null && typeof query == "string") caseFold = false;
-
- pos = pos ? cm.clipPos(pos) : {line: 0, ch: 0};
- this.pos = {from: pos, to: pos};
-
- // The matches method is filled in based on the type of query.
- // It takes a position and a direction, and returns an object
- // describing the next occurrence of the query, or null if no
- // more matches were found.
- if (typeof query != "string") { // Regexp match
- if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
- this.matches = function(reverse, pos) {
- if (reverse) {
- query.lastIndex = 0;
- var line = cm.getLine(pos.line).slice(0, pos.ch), match = query.exec(line), start = 0;
- while (match) {
- start += match.index;
- line = line.slice(match.index);
- query.lastIndex = 0;
- var newmatch = query.exec(line);
- if (newmatch) match = newmatch;
- else break;
- start++;
- }
- } else {
- query.lastIndex = pos.ch;
- var line = cm.getLine(pos.line), match = query.exec(line),
- start = match && match.index;
- }
- if (match)
- return {from: {line: pos.line, ch: start},
- to: {line: pos.line, ch: start + match[0].length},
- match: match};
- };
- } else { // String query
- if (caseFold) query = query.toLowerCase();
- var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
- var target = query.split("\n");
- // Different methods for single-line and multi-line queries
- if (target.length == 1)
- this.matches = function(reverse, pos) {
- var line = fold(cm.getLine(pos.line)), len = query.length, match;
- if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
- : (match = line.indexOf(query, pos.ch)) != -1)
- return {from: {line: pos.line, ch: match},
- to: {line: pos.line, ch: match + len}};
- };
- else
- this.matches = function(reverse, pos) {
- var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln));
- var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
- if (reverse ? offsetA >= pos.ch || offsetA != match.length
- : offsetA <= pos.ch || offsetA != line.length - match.length)
- return;
- for (;;) {
- if (reverse ? !ln : ln == cm.lineCount() - 1) return;
- line = fold(cm.getLine(ln += reverse ? -1 : 1));
- match = target[reverse ? --idx : ++idx];
- if (idx > 0 && idx < target.length - 1) {
- if (line != match) return;
- else continue;
- }
- var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
- if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
- return;
- var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB};
- return {from: reverse ? end : start, to: reverse ? start : end};
- }
- };
- }
- }
-
- SearchCursor.prototype = {
- findNext: function() {return this.find(false);},
- findPrevious: function() {return this.find(true);},
-
- find: function(reverse) {
- var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to);
- function savePosAndFail(line) {
- var pos = {line: line, ch: 0};
- self.pos = {from: pos, to: pos};
- self.atOccurrence = false;
- return false;
- }
-
- for (;;) {
- if (this.pos = this.matches(reverse, pos)) {
- this.atOccurrence = true;
- return this.pos.match || true;
- }
- if (reverse) {
- if (!pos.line) return savePosAndFail(0);
- pos = {line: pos.line-1, ch: this.cm.getLine(pos.line-1).length};
- }
- else {
- var maxLine = this.cm.lineCount();
- if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
- pos = {line: pos.line+1, ch: 0};
- }
- }
- },
-
- from: function() {if (this.atOccurrence) return this.pos.from;},
- to: function() {if (this.atOccurrence) return this.pos.to;},
-
- replace: function(newText) {
- var self = this;
- if (this.atOccurrence)
- self.pos.to = this.cm.replaceRange(newText, self.pos.from, self.pos.to);
- }
- };
-
- CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
- return new SearchCursor(this, query, pos, caseFold);
- });
-})();
diff --git a/scripts/codemirror/lib/util/simple-hint.css b/scripts/codemirror/lib/util/simple-hint.css
deleted file mode 100644
index 4387cb9..0000000
--- a/scripts/codemirror/lib/util/simple-hint.css
+++ /dev/null
@@ -1,16 +0,0 @@
-.CodeMirror-completions {
- position: absolute;
- z-index: 10;
- overflow: hidden;
- -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
- -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
- box-shadow: 2px 3px 5px rgba(0,0,0,.2);
-}
-.CodeMirror-completions select {
- background: #fafafa;
- outline: none;
- border: none;
- padding: 0;
- margin: 0;
- font-family: monospace;
-}
diff --git a/scripts/codemirror/lib/util/simple-hint.js b/scripts/codemirror/lib/util/simple-hint.js
deleted file mode 100644
index 8e481c3..0000000
--- a/scripts/codemirror/lib/util/simple-hint.js
+++ /dev/null
@@ -1,97 +0,0 @@
-(function() {
- CodeMirror.simpleHint = function(editor, getHints, givenOptions) {
- // Determine effective options based on given values and defaults.
- var options = {}, defaults = CodeMirror.simpleHint.defaults;
- for (var opt in defaults)
- if (defaults.hasOwnProperty(opt))
- options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
-
- function collectHints(previousToken) {
- // We want a single cursor position.
- if (editor.somethingSelected()) return;
-
- var tempToken = editor.getTokenAt(editor.getCursor());
-
- // Don't show completions if token has changed and the option is set.
- if (options.closeOnTokenChange && previousToken != null &&
- (tempToken.start != previousToken.start || tempToken.className != previousToken.className)) {
- return;
- }
-
- var result = getHints(editor);
- if (!result || !result.list.length) return;
- var completions = result.list;
- function insert(str) {
- editor.replaceRange(str, result.from, result.to);
- }
- // When there is only one completion, use it directly.
- if (completions.length == 1) {insert(completions[0]); return true;}
-
- // Build the select widget
- var complete = document.createElement("div");
- complete.className = "CodeMirror-completions";
- var sel = complete.appendChild(document.createElement("select"));
- // Opera doesn't move the selection when pressing up/down in a
- // multi-select, but it does properly support the size property on
- // single-selects, so no multi-select is necessary.
- if (!window.opera) sel.multiple = true;
- for (var i = 0; i < completions.length; ++i) {
- var opt = sel.appendChild(document.createElement("option"));
- opt.appendChild(document.createTextNode(completions[i]));
- }
- sel.firstChild.selected = true;
- sel.size = Math.min(10, completions.length);
- var pos = editor.cursorCoords();
- complete.style.left = pos.x + "px";
- complete.style.top = pos.yBot + "px";
- document.body.appendChild(complete);
- // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
- var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
- if(winW - pos.x < sel.clientWidth)
- complete.style.left = (pos.x - sel.clientWidth) + "px";
- // Hack to hide the scrollbar.
- if (completions.length <= 10)
- complete.style.width = (sel.clientWidth - 1) + "px";
-
- var done = false;
- function close() {
- if (done) return;
- done = true;
- complete.parentNode.removeChild(complete);
- }
- function pick() {
- insert(completions[sel.selectedIndex]);
- close();
- setTimeout(function(){editor.focus();}, 50);
- }
- CodeMirror.connect(sel, "blur", close);
- CodeMirror.connect(sel, "keydown", function(event) {
- var code = event.keyCode;
- // Enter
- if (code == 13) {CodeMirror.e_stop(event); pick();}
- // Escape
- else if (code == 27) {CodeMirror.e_stop(event); close(); editor.focus();}
- else if (code != 38 && code != 40) {
- close(); editor.focus();
- // Pass the event to the CodeMirror instance so that it can handle things like backspace properly.
- editor.triggerOnKeyDown(event);
- // Don't show completions if the code is backspace and the option is set.
- if (!options.closeOnBackspace || code != 8) {
- setTimeout(function(){collectHints(tempToken);}, 50);
- }
- }
- });
- CodeMirror.connect(sel, "dblclick", pick);
-
- sel.focus();
- // Opera sometimes ignores focusing a freshly created node
- if (window.opera) setTimeout(function(){if (!done) sel.focus();}, 100);
- return true;
- }
- return collectHints();
- };
- CodeMirror.simpleHint.defaults = {
- closeOnBackspace: true,
- closeOnTokenChange: false
- };
-})();
diff --git a/scripts/codemirror/lib/util/xml-hint.js b/scripts/codemirror/lib/util/xml-hint.js
deleted file mode 100644
index 816e3b4..0000000
--- a/scripts/codemirror/lib/util/xml-hint.js
+++ /dev/null
@@ -1,137 +0,0 @@
-
-(function() {
-
- CodeMirror.xmlHints = [];
-
- CodeMirror.xmlHint = function(cm, simbol) {
-
- if(simbol.length > 0) {
- var cursor = cm.getCursor();
- cm.replaceSelection(simbol);
- cursor = {line: cursor.line, ch: cursor.ch + 1};
- cm.setCursor(cursor);
- }
-
- // dirty hack for simple-hint to receive getHint event on space
- var getTokenAt = editor.getTokenAt;
-
- editor.getTokenAt = function() { return 'disabled'; };
- CodeMirror.simpleHint(cm, getHint);
-
- editor.getTokenAt = getTokenAt;
- };
-
- var getHint = function(cm) {
-
- var cursor = cm.getCursor();
-
- if (cursor.ch > 0) {
-
- var text = cm.getRange({line: 0, ch: 0}, cursor);
- var typed = '';
- var simbol = '';
- for(var i = text.length - 1; i >= 0; i--) {
- if(text[i] == ' ' || text[i] == '<') {
- simbol = text[i];
- break;
- }
- else {
- typed = text[i] + typed;
- }
- }
-
- text = text.slice(0, text.length - typed.length);
-
- var path = getActiveElement(cm, text) + simbol;
- var hints = CodeMirror.xmlHints[path];
-
- if(typeof hints === 'undefined')
- hints = [''];
- else {
- hints = hints.slice(0);
- for (var i = hints.length - 1; i >= 0; i--) {
- if(hints[i].indexOf(typed) != 0)
- hints.splice(i, 1);
- }
- }
-
- return {
- list: hints,
- from: { line: cursor.line, ch: cursor.ch - typed.length },
- to: cursor
- };
- };
- };
-
- var getActiveElement = function(codeMirror, text) {
-
- var element = '';
-
- if(text.length >= 0) {
-
- var regex = new RegExp('<([^!?][^\\s/>]*).*?>', 'g');
-
- var matches = [];
- var match;
- while ((match = regex.exec(text)) != null) {
- matches.push({
- tag: match[1],
- selfclose: (match[0].slice(match[0].length - 2) === '/>')
- });
- }
-
- for (var i = matches.length - 1, skip = 0; i >= 0; i--) {
-
- var item = matches[i];
-
- if (item.tag[0] == '/')
- {
- skip++;
- }
- else if (item.selfclose == false)
- {
- if (skip > 0)
- {
- skip--;
- }
- else
- {
- element = '<' + item.tag + '>' + element;
- }
- }
- }
-
- element += getOpenTag(text);
- }
-
- return element;
- };
-
- var getOpenTag = function(text) {
-
- var open = text.lastIndexOf('<');
- var close = text.lastIndexOf('>');
-
- if (close < open)
- {
- text = text.slice(open);
-
- if(text != '<') {
-
- var space = text.indexOf(' ');
- if(space < 0)
- space = text.indexOf('\t');
- if(space < 0)
- space = text.indexOf('\n');
-
- if (space < 0)
- space = text.length;
-
- return text.slice(0, space);
- }
- }
-
- return '';
- };
-
-})();
diff --git a/scripts/codemirror/mode/apl/apl.js b/scripts/codemirror/mode/apl/apl.js
new file mode 100644
index 0000000..b1955f6
--- /dev/null
+++ b/scripts/codemirror/mode/apl/apl.js
@@ -0,0 +1,174 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.defineMode("apl", function() {
+ var builtInOps = {
+ ".": "innerProduct",
+ "\\": "scan",
+ "/": "reduce",
+ "⌿": "reduce1Axis",
+ "⍀": "scan1Axis",
+ "¨": "each",
+ "⍣": "power"
+ };
+ var builtInFuncs = {
+ "+": ["conjugate", "add"],
+ "−": ["negate", "subtract"],
+ "×": ["signOf", "multiply"],
+ "÷": ["reciprocal", "divide"],
+ "⌈": ["ceiling", "greaterOf"],
+ "⌊": ["floor", "lesserOf"],
+ "∣": ["absolute", "residue"],
+ "⍳": ["indexGenerate", "indexOf"],
+ "?": ["roll", "deal"],
+ "⋆": ["exponentiate", "toThePowerOf"],
+ "⍟": ["naturalLog", "logToTheBase"],
+ "○": ["piTimes", "circularFuncs"],
+ "!": ["factorial", "binomial"],
+ "⌹": ["matrixInverse", "matrixDivide"],
+ "<": [null, "lessThan"],
+ "≤": [null, "lessThanOrEqual"],
+ "=": [null, "equals"],
+ ">": [null, "greaterThan"],
+ "≥": [null, "greaterThanOrEqual"],
+ "≠": [null, "notEqual"],
+ "≡": ["depth", "match"],
+ "≢": [null, "notMatch"],
+ "∈": ["enlist", "membership"],
+ "⍷": [null, "find"],
+ "∪": ["unique", "union"],
+ "∩": [null, "intersection"],
+ "∼": ["not", "without"],
+ "∨": [null, "or"],
+ "∧": [null, "and"],
+ "⍱": [null, "nor"],
+ "⍲": [null, "nand"],
+ "⍴": ["shapeOf", "reshape"],
+ ",": ["ravel", "catenate"],
+ "⍪": [null, "firstAxisCatenate"],
+ "⌽": ["reverse", "rotate"],
+ "⊖": ["axis1Reverse", "axis1Rotate"],
+ "⍉": ["transpose", null],
+ "↑": ["first", "take"],
+ "↓": [null, "drop"],
+ "⊂": ["enclose", "partitionWithAxis"],
+ "⊃": ["diclose", "pick"],
+ "⌷": [null, "index"],
+ "⍋": ["gradeUp", null],
+ "⍒": ["gradeDown", null],
+ "⊤": ["encode", null],
+ "⊥": ["decode", null],
+ "⍕": ["format", "formatByExample"],
+ "⍎": ["execute", null],
+ "⊣": ["stop", "left"],
+ "⊢": ["pass", "right"]
+ };
+
+ var isOperator = /[\.\/⌿⍀¨⍣]/;
+ var isNiladic = /⍬/;
+ var isFunction = /[\+−×÷⌈⌊∣⍳\?⋆⍟○!⌹<≤=>≥≠≡≢∈⍷∪∩∼∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢]/;
+ var isArrow = /←/;
+ var isComment = /[⍝#].*$/;
+
+ var stringEater = function(type) {
+ var prev;
+ prev = false;
+ return function(c) {
+ prev = c;
+ if (c === type) {
+ return prev === "\\";
+ }
+ return true;
+ };
+ };
+ return {
+ startState: function() {
+ return {
+ prev: false,
+ func: false,
+ op: false,
+ string: false,
+ escape: false
+ };
+ },
+ token: function(stream, state) {
+ var ch, funcName;
+ if (stream.eatSpace()) {
+ return null;
+ }
+ ch = stream.next();
+ if (ch === '"' || ch === "'") {
+ stream.eatWhile(stringEater(ch));
+ stream.next();
+ state.prev = true;
+ return "string";
+ }
+ if (/[\[{\(]/.test(ch)) {
+ state.prev = false;
+ return null;
+ }
+ if (/[\]}\)]/.test(ch)) {
+ state.prev = true;
+ return null;
+ }
+ if (isNiladic.test(ch)) {
+ state.prev = false;
+ return "niladic";
+ }
+ if (/[¯\d]/.test(ch)) {
+ if (state.func) {
+ state.func = false;
+ state.prev = false;
+ } else {
+ state.prev = true;
+ }
+ stream.eatWhile(/[\w\.]/);
+ return "number";
+ }
+ if (isOperator.test(ch)) {
+ return "operator apl-" + builtInOps[ch];
+ }
+ if (isArrow.test(ch)) {
+ return "apl-arrow";
+ }
+ if (isFunction.test(ch)) {
+ funcName = "apl-";
+ if (builtInFuncs[ch] != null) {
+ if (state.prev) {
+ funcName += builtInFuncs[ch][1];
+ } else {
+ funcName += builtInFuncs[ch][0];
+ }
+ }
+ state.func = true;
+ state.prev = false;
+ return "function " + funcName;
+ }
+ if (isComment.test(ch)) {
+ stream.skipToEnd();
+ return "comment";
+ }
+ if (ch === "∘" && stream.peek() === ".") {
+ stream.next();
+ return "function jot-dot";
+ }
+ stream.eatWhile(/[\w\$_]/);
+ state.prev = true;
+ return "keyword";
+ }
+ };
+});
+
+CodeMirror.defineMIME("text/apl", "apl");
+
+});
diff --git a/scripts/codemirror/mode/apl/index.html b/scripts/codemirror/mode/apl/index.html
new file mode 100644
index 0000000..56ab02f
--- /dev/null
+++ b/scripts/codemirror/mode/apl/index.html
@@ -0,0 +1,72 @@
+
+
+CodeMirror: APL mode
+
+
+
+
+
+
+
+
+
Simple mode that tries to handle APL as well as it can.
+
It attempts to label functions/operators based upon
+ monadic/dyadic usage (but this is far from fully fleshed out).
+ This means there are meaningful classnames so hover states can
+ have popups etc.