149 changes: 63 additions & 86 deletions mode/css/test.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,19 @@
(function() {
var mode = CodeMirror.getMode({tabSize: 1}, "css");
var mode = CodeMirror.getMode({indentUnit: 2}, "css");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
function IT(name) { test.indentation(name, mode, Array.prototype.slice.call(arguments, 1)); }

// Requires at least one media query
MT("atMediaEmpty",
"[def @media] [error {] }");

MT("atMediaMultiple",
"[def @media] [keyword not] [attribute screen] [operator and] ([property color]), [keyword not] [attribute print] [operator and] ([property color]) { }");

MT("atMediaCheckStack",
"[def @media] [attribute screen] { } [tag foo] { }");

MT("atMediaCheckStack",
"[def @media] [attribute screen] ([property color]) { } [tag foo] { }");

MT("atMediaPropertyOnly",
"[def @media] ([property color]) { } [tag foo] { }");

MT("atMediaCheckStackInvalidAttribute",
"[def @media] [attribute&error foobarhello] { [tag foo] { } }");

MT("atMediaCheckStackInvalidAttribute",
"[def @media] [attribute&error foobarhello] { } [tag foo] { }");

// Error, because "and" is only allowed immediately preceding a media expression
MT("atMediaInvalidAttribute",
"[def @media] [attribute&error foobarhello] { }");

// Error, because "and" is only allowed immediately preceding a media expression
MT("atMediaInvalidAnd",
"[def @media] [error and] [attribute screen] { }");

// Error, because "not" is only allowed as the first item in each media query
MT("atMediaInvalidNot",
"[def @media] [attribute screen] [error not] ([error not]) { }");

// Error, because "only" is only allowed as the first item in each media query
MT("atMediaInvalidOnly",
"[def @media] [attribute screen] [error only] ([error only]) { }");

// Error, because "foobarhello" is neither a known type or property, but
// property was expected (after "and"), and it should be in parenthese.
MT("atMediaUnknownType",
"[def @media] [attribute screen] [operator and] [error foobarhello] { }");

// Error, because "color" is not a known type, but is a known property, and
// should be in parentheses.
MT("atMediaInvalidType",
"[def @media] [attribute screen] [operator and] [error color] { }");

// Error, because "print" is not a known property, but is a known type,
// and should not be in parenthese.
MT("atMediaInvalidProperty",
"[def @media] [attribute screen] [operator and] ([error print]) { }");
"[def @media] [attribute screen] [keyword and] [error foobarhello] { }");

// Soft error, because "foobarhello" is not a known property or type.
MT("atMediaUnknownProperty",
"[def @media] [attribute screen] [operator and] ([property&error foobarhello]) { }");
"[def @media] [attribute screen] [keyword and] ([error foobarhello]) { }");

// Make sure nesting works with media queries
MT("atMediaMaxWidthNested",
"[def @media] [attribute screen] [operator and] ([property max-width][operator :] [number 25px]) { [tag foo] { } }");
"[def @media] [attribute screen] [keyword and] ([property max-width]: [number 25px]) { [tag foo] { } }");

MT("tagSelector",
"[tag foo] { }");
Expand All @@ -74,69 +25,95 @@
"[builtin #foo] { [error #foo] }");

MT("tagSelectorUnclosed",
"[tag foo] { [property margin][operator :] [number 0] } [tag bar] { }");
"[tag foo] { [property margin]: [number 0] } [tag bar] { }");

MT("tagStringNoQuotes",
"[tag foo] { [property font-family][operator :] [variable-2 hello] [variable-2 world]; }");
"[tag foo] { [property font-family]: [variable hello] [variable world]; }");

MT("tagStringDouble",
"[tag foo] { [property font-family][operator :] [string \"hello world\"]; }");
"[tag foo] { [property font-family]: [string \"hello world\"]; }");

MT("tagStringSingle",
"[tag foo] { [property font-family][operator :] [string 'hello world']; }");
"[tag foo] { [property font-family]: [string 'hello world']; }");

MT("tagColorKeyword",
"[tag foo] {" +
"[property color][operator :] [keyword black];" +
"[property color][operator :] [keyword navy];" +
"[property color][operator :] [keyword yellow];" +
"}");
"[tag foo] {",
" [property color]: [keyword black];",
" [property color]: [keyword navy];",
" [property color]: [keyword yellow];",
"}");

MT("tagColorHex3",
"[tag foo] { [property background][operator :] [atom #fff]; }");
"[tag foo] { [property background]: [atom #fff]; }");

MT("tagColorHex6",
"[tag foo] { [property background][operator :] [atom #ffffff]; }");
"[tag foo] { [property background]: [atom #ffffff]; }");

MT("tagColorHex4",
"[tag foo] { [property background][operator :] [atom&error #ffff]; }");
"[tag foo] { [property background]: [atom&error #ffff]; }");

MT("tagColorHexInvalid",
"[tag foo] { [property background][operator :] [atom&error #ffg]; }");
"[tag foo] { [property background]: [atom&error #ffg]; }");

MT("tagNegativeNumber",
"[tag foo] { [property margin][operator :] [number -5px]; }");
"[tag foo] { [property margin]: [number -5px]; }");

MT("tagPositiveNumber",
"[tag foo] { [property padding][operator :] [number 5px]; }");
"[tag foo] { [property padding]: [number 5px]; }");

MT("tagVendor",
"[tag foo] { [meta -foo-][property box-sizing][operator :] [meta -foo-][string-2 border-box]; }");
"[tag foo] { [meta -foo-][property box-sizing]: [meta -foo-][atom border-box]; }");

MT("tagBogusProperty",
"[tag foo] { [property&error barhelloworld][operator :] [number 0]; }");
"[tag foo] { [property&error barhelloworld]: [number 0]; }");

MT("tagTwoProperties",
"[tag foo] { [property margin][operator :] [number 0]; [property padding][operator :] [number 0]; }");
"[tag foo] { [property margin]: [number 0]; [property padding]: [number 0]; }");

MT("tagTwoPropertiesURL",
"[tag foo] { [property background][operator :] [string-2 url]([string //example.com/foo.png]); [property padding][operator :] [number 0]; }");
"[tag foo] { [property background]: [atom url]([string //example.com/foo.png]); [property padding]: [number 0]; }");

MT("commentSGML",
"[comment <!--comment-->]");

IT("tagSelector",
"strong, em [1 { background][2 : rgba][3 (255, 255, 0, .2][2 )][1 ;]}");

IT("atMedia",
"[1 @media { foo ][2 { ][1 } ]}");

IT("comma",
"foo [1 { font-family][2 : verdana, sans-serif][1 ; ]}");

IT("parentheses",
"foo [1 { background][2 : url][3 (\"bar\"][2 )][1 ; ]}");

IT("pseudo",
"foo:before [1 { ]}");
MT("commentSGML2",
"[comment <!--comment]",
"[comment -->] [tag div] {}");

MT("indent_tagSelector",
"[tag strong], [tag em] {",
" [property background]: [atom rgba](",
" [number 255], [number 255], [number 0], [number .2]",
" );",
"}");

MT("indent_atMedia",
"[def @media] {",
" [tag foo] {",
" [property color]:",
" [keyword yellow];",
" }",
"}");

MT("indent_comma",
"[tag foo] {",
" [property font-family]: [variable verdana],",
" [atom sans-serif];",
"}");

MT("indent_parentheses",
"[tag foo]:[variable-3 before] {",
" [property background]: [atom url](",
"[string blahblah]",
"[string etc]",
"[string ]) [keyword !important];",
"}");

MT("font_face",
"[def @font-face] {",
" [property font-family]: [string 'myfont'];",
" [error nonsense]: [string 'abc'];",
" [property src]: [atom url]([string http://blah]),",
" [atom url]([string http://foo]);",
"}");
})();
481 changes: 302 additions & 179 deletions mode/erlang/erlang.js

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions mode/erlang/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ <h2>Erlang mode</h2>
case tuple_size(Tup) of
L when L < 1 -> Tup;
L ->
try Fields = M:rec_info(element(1,Tup)),
L = length(Fields)+1,
lists:zip(Fields,expand_recs(M,tl(tuple_to_list(Tup))))
catch _:_ ->
list_to_tuple(expand_recs(M,tuple_to_list(Tup)))
try
Fields = M:rec_info(element(1,Tup)),
L = length(Fields)+1,
lists:zip(Fields,expand_recs(M,tl(tuple_to_list(Tup))))
catch
_:_ -> list_to_tuple(expand_recs(M,tuple_to_list(Tup)))
end
end;
expand_recs(_,Term) ->
Expand Down
15 changes: 10 additions & 5 deletions mode/gfm/gfm.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CodeMirror.defineMode("gfm", function(config) {
CodeMirror.defineMode("gfm", function(config, modeConfig) {
var codeDepth = 0;
function blankLine(state) {
state.code = false;
Expand Down Expand Up @@ -75,7 +75,7 @@ CodeMirror.defineMode("gfm", function(config) {
return "link";
}
}
if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`!()\[\]{};:'".,<>?«»]))/i) &&
if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`!()\[\]{};:'".,<>?«»]))/i) &&
stream.string.slice(stream.start - 2, stream.start) != "](") {
// URLs
// Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
Expand All @@ -87,11 +87,16 @@ CodeMirror.defineMode("gfm", function(config) {
},
blankLine: blankLine
};
CodeMirror.defineMIME("gfmBase", {
name: "markdown",

var markdownConfig = {
underscoresBreakWords: false,
taskLists: true,
fencedCodeBlocks: true
});
};
for (var attr in modeConfig) {
markdownConfig[attr] = modeConfig[attr];
}
markdownConfig.name = "markdown";
CodeMirror.defineMIME("gfmBase", markdownConfig);
return CodeMirror.overlayMode(CodeMirror.getMode(config, "gfmBase"), gfmOverlay);
}, "markdown");
19 changes: 18 additions & 1 deletion mode/gfm/test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
(function() {
var mode = CodeMirror.getMode({tabSize: 4}, "gfm");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
var modeHighlightFormatting = CodeMirror.getMode({tabSize: 4}, {name: "gfm", highlightFormatting: true});
function FT(name) { test.mode(name, modeHighlightFormatting, Array.prototype.slice.call(arguments, 1)); }

FT("codeBackticks",
"[comment&formatting&formatting-code `][comment foo][comment&formatting&formatting-code `]");

FT("doubleBackticks",
"[comment&formatting&formatting-code ``][comment foo ` bar][comment&formatting&formatting-code ``]");

FT("codeBlock",
"[comment&formatting&formatting-code-block ```css]",
"[tag foo]",
"[comment&formatting&formatting-code-block ```]");

FT("taskList",
"[variable-2&formatting&formatting-list&formatting-list-ul - ][meta&formatting&formatting-task [ ]]][variable-2 foo]",
"[variable-2&formatting&formatting-list&formatting-list-ul - ][property&formatting&formatting-task [x]]][variable-2 foo]");

MT("emInWordAsterisk",
"foo[em *bar*]hello");
Expand Down Expand Up @@ -97,7 +114,7 @@

MT("notALink",
"[comment ```css]",
"[tag foo] {[property color][operator :][keyword black];}",
"[tag foo] {[property color]:[keyword black];}",
"[comment ```][link http://www.example.com/]");

MT("notALink",
Expand Down
95 changes: 45 additions & 50 deletions mode/gherkin/gherkin.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,40 @@ CodeMirror.defineMode("gherkin", function () {
startState: function () {
return {
lineNumber: 0,
tableHeaderLine: null,
tableHeaderLine: false,
allowFeature: true,
allowBackground: false,
allowScenario: false,
allowSteps: false,
allowPlaceholders: false,
inMultilineArgument: false,
allowMultilineArgument: false,
inMultilineString: false,
inMultilineTable: false
inMultilineTable: false,
inKeywordLine: false
};
},
token: function (stream, state) {
if (stream.sol()) {
state.lineNumber++;
state.inKeywordLine = false;
if (state.inMultilineTable) {
state.tableHeaderLine = false;
if (!stream.match(/\s*\|/, false)) {
state.allowMultilineArgument = false;
state.inMultilineTable = false;
}
}
}

stream.eatSpace();

// INSIDE OF MULTILINE ARGUMENTS
if (state.inMultilineArgument) {
if (state.allowMultilineArgument) {

// STRING
if (state.inMultilineString) {
if (stream.match('"""')) {
state.inMultilineString = false;
state.inMultilineArgument = false;
state.allowMultilineArgument = false;
} else {
stream.match(/.*/);
}
Expand All @@ -51,19 +60,11 @@ CodeMirror.defineMode("gherkin", function () {

// TABLE
if (state.inMultilineTable) {
// New table, assume first row is headers
if (state.tableHeaderLine === null) {
state.tableHeaderLine = state.lineNumber;
}

if (stream.match(/\|\s*/)) {
if (stream.eol()) {
state.inMultilineTable = false;
}
return "bracket";
} else {
stream.match(/[^\|]*/);
return state.tableHeaderLine === state.lineNumber ? "property" : "string";
return state.tableHeaderLine ? "header" : "string";
}
}

Expand All @@ -75,92 +76,86 @@ CodeMirror.defineMode("gherkin", function () {
} else if (stream.match("|")) {
// Table
state.inMultilineTable = true;
state.tableHeaderLine = true;
return "bracket";
} else {
// Or abort
state.inMultilineArgument = false;
state.tableHeaderLine = null;
}


return null;
}

// LINE COMMENT
if (stream.match(/#.*/)) {
return "comment";

// TAG
} else if (stream.match(/@\S+/)) {
return "def";
} else if (!state.inKeywordLine && stream.match(/@\S+/)) {
return "tag";

// FEATURE
} else if (state.allowFeature && stream.match(/Feature:/)) {
} else if (!state.inKeywordLine && state.allowFeature && stream.match(/(|||||||||| || |وِیژگی|خاصية|תכונה|Функціонал|Функция|Функционалност|Функционал|Үзенчәлеклелек|Свойство|Особина|Мөмкинлек|Могућност|Λειτουργία|Δυνατότητα|Właściwość|Vlastnosť|Trajto|Tính năng|Savybė|Pretty much|Požiadavka|Požadavek|Potrzeba biznesowa|Özellik|Osobina|Ominaisuus|Omadus|OH HAI|Mogućnost|Mogucnost|Jellemző|Hwæt|Hwaet|Funzionalità|Funktionalitéit|Funktionalität|Funkcja|Funkcionalnost|Funkcionalitāte|Funkcia|Fungsi|Functionaliteit|Funcționalitate|Funcţionalitate|Functionalitate|Funcionalitat|Funcionalidade|Fonctionnalité|Fitur|Fīča|Feature|Eiginleiki|Egenskap|Egenskab|Característica|Caracteristica|Business Need|Aspekt|Arwedd|Ahoy matey!|Ability):/)) {
state.allowScenario = true;
state.allowBackground = true;
state.allowPlaceholders = false;
state.allowSteps = false;
state.allowMultilineArgument = false;
state.inKeywordLine = true;
return "keyword";

// BACKGROUND
} else if (state.allowBackground && stream.match("Background:")) {
} else if (!state.inKeywordLine && state.allowBackground && stream.match(/(|||ಿ||ਿ|ि|زمینه|الخلفية|רקע|Тарих|Предыстория|Предистория|Позадина|Передумова|Основа|Контекст|Кереш|Υπόβαθρο|Założenia|Yo\-ho\-ho|Tausta|Taust|Situācija|Rerefons|Pozadina|Pozadie|Pozadí|Osnova|Latar Belakang|Kontext|Konteksts|Kontekstas|Kontekst|Háttér|Hannergrond|Grundlage|Geçmiş|Fundo|Fono|First off|Dis is what went down|Dasar|Contexto|Contexte|Context|Contesto|Cenário de Fundo|Cenario de Fundo|Cefndir|Bi cnh|Bakgrunnur|Bakgrunn|Bakgrund|Baggrund|Background|B4|Antecedents|Antecedentes|Ær|Aer|Achtergrond):/)) {
state.allowPlaceholders = false;
state.allowSteps = true;
state.allowBackground = false;
state.allowMultilineArgument = false;
state.inKeywordLine = true;
return "keyword";

// SCENARIO OUTLINE
} else if (state.allowScenario && stream.match("Scenario Outline:")) {
} else if (!state.inKeywordLine && state.allowScenario && stream.match(/(|||||||| |||ಿ|| | |ि |سيناريو مخطط|الگوی سناریو|תבנית תרחיש|Сценарийның төзелеше|Сценарий структураси|Структура сценарію|Структура сценария|Структура сценарија|Скица|Рамка на сценарий|Концепт|Περιγραφή Σεναρίου|Wharrimean is|Template Situai|Template Senario|Template Keadaan|Tapausaihio|Szenariogrundriss|Szablon scenariusza|Swa hwær swa|Swa hwaer swa|Struktura scenarija|Structură scenariu|Structura scenariu|Skica|Skenario konsep|Shiver me timbers|Senaryo taslağı|Schema dello scenario|Scenariomall|Scenariomal|Scenario Template|Scenario Outline|Scenario Amlinellol|Scenārijs pēc parauga|Scenarijaus šablonas|Reckon it's like|Raamstsenaarium|Plang vum Szenario|Plan du Scénario|Plan du scénario|Osnova scénáře|Osnova Scenára|Náčrt Scenáru|Náčrt Scénáře|Náčrt Scenára|MISHUN SRSLY|Menggariskan Senario|Lýsing Dæma|Lýsing Atburðarásar|Konturo de la scenaro|Koncept|Khung tình hung|Khung kch bn|Forgatókönyv vázlat|Esquema do Cenário|Esquema do Cenario|Esquema del escenario|Esquema de l'escenari|Esbozo do escenario|Delineação do Cenário|Delineacao do Cenario|All y'all|Abstrakt Scenario|Abstract Scenario):/)) {
state.allowPlaceholders = true;
state.allowSteps = true;
state.allowMultilineArgument = false;
state.inKeywordLine = true;
return "keyword";

// EXAMPLES
} else if (state.allowScenario && stream.match("Examples:")) {
} else if (state.allowScenario && stream.match(/(||||||||||نمونه ها|امثلة|דוגמאות|Үрнәкләр|Сценарији|Примеры|Примери|Приклади|Мисоллар|Мисаллар|Σενάρια|Παραδείγματα|You'll wanna|Voorbeelden|Variantai|Tapaukset|Se þe|Se the|Se ðe|Scenarios|Scenariji|Scenarijai|Przykłady|Primjeri|Primeri|Příklady|Príklady|Piemēri|Példák|Pavyzdžiai|Paraugs|Örnekler|Juhtumid|Exemplos|Exemples|Exemple|Exempel|EXAMPLZ|Examples|Esempi|Enghreifftiau|Ekzemploj|Eksempler|Ejemplos|D liu|Dead men tell no tales|Dæmi|Contoh|Cenários|Cenarios|Beispiller|Beispiele|Atburðarásir):/)) {
state.allowPlaceholders = false;
state.allowSteps = true;
state.allowBackground = false;
state.inMultilineArgument = true;
state.allowMultilineArgument = true;
return "keyword";

// SCENARIO
} else if (state.allowScenario && stream.match(/Scenario:/)) {
} else if (!state.inKeywordLine && state.allowScenario && stream.match(/(||||||||ి||ि|سيناريو|سناریو|תרחיש|Сценарій|Сценарио|Сценарий|Пример|Σενάριο|Tình hung|The thing of it is|Tapaus|Szenario|Swa|Stsenaarium|Skenario|Situai|Senaryo|Senario|Scenaro|Scenariusz|Scenariu|Scénario|Scenario|Scenarijus|Scenārijs|Scenarij|Scenarie|Scénář|Scenár|Primer|MISHUN|Kch bn|Keadaan|Heave to|Forgatókönyv|Escenario|Escenari|Cenário|Cenario|Awww, look mate|Atburðarás):/)) {
state.allowPlaceholders = false;
state.allowSteps = true;
state.allowBackground = false;
state.allowMultilineArgument = false;
state.inKeywordLine = true;
return "keyword";

// STEPS
} else if (state.allowSteps && stream.match(/(Given|When|Then|And|But)/)) {
} else if (!state.inKeywordLine && state.allowSteps && stream.match(/(||||||||||||||||||||||||||||| | | | | |ಿಿ | |ಿಿ | | |ి |ిి |ి | ిిి | | | | |ਿ ਿ | | |ि | | | | | | |ि |ि | | | |و |هنگامی |متى |لكن |عندما |ثم |بفرض |با فرض |اما |اذاً |آنگاه |כאשר |וגם |בהינתן |אזי |אז |אבל |Якщо |Һәм |Унда |Тоді |Тогда |То |Также |Та |Пусть |Припустимо, що |Припустимо |Онда |Но |Нехай |Нәтиҗәдә |Лекин |Ләкин |Коли |Когда |Когато |Када |Кад |К тому же |І |И |Задато |Задати |Задате |Если |Допустим |Дано |Дадено |Вә |Ва |Бирок |Әмма |Әйтик |Әгәр |Аммо |Али |Але |Агар |А також |А |Τότε |Όταν |Και |Δεδομένου |Αλλά |Þurh |Þegar |Þa þe |Þá |Þa |Zatati |Zakładając |Zadato |Zadate |Zadano |Zadani |Zadan |Za předpokladu |Za predpokladu |Youse know when youse got |Youse know like when |Yna |Yeah nah |Y'know |Y |Wun |Wtedy |When y'all |When |Wenn |WEN |wann |Ve |Và |Und |Un |ugeholl |Too right |Thurh |Thì |Then y'all |Then |Tha the |Tha |Tetapi |Tapi |Tak |Tada |Tad |Stel |Soit |Siis |Și |Şi |Si |Sed |Se |Så |Quando |Quand |Quan |Pryd |Potom |Pokud |Pokiaľ |Però |Pero |Pak |Oraz |Onda |Ond |Oletetaan |Og |Och |O zaman |Niin |Nhưng |När |Når |Mutta |Men |Mas |Maka |Majd |Mając |Mais |Maar |mä |Ma |Lorsque |Lorsqu'|Logo |Let go and haul |Kun |Kuid |Kui |Kiedy |Khi |Ketika |Kemudian |Keď |Když |Kaj |Kai |Kada |Kad |Jeżeli |Jeśli |Ja |It's just unbelievable |Ir |I CAN HAZ |I |Ha |Givun |Givet |Given y'all |Given |Gitt |Gegeven |Gegeben seien |Gegeben sei |Gdy |Gangway! |Fakat |Étant donnés |Etant donnés |Étant données |Etant données |Étant donnée |Etant donnée |Étant donné |Etant donné |Et |És |Entonces |Entón |Então |Entao |En |Eğer ki |Ef |Eeldades |E |Ðurh |Duota |Dun |Donitaĵo |Donat |Donada |Do |Diyelim ki |Diberi |Dengan |Den youse gotta |DEN |De |Dato |Dați fiind |Daţi fiind |Dati fiind |Dati |Date fiind |Date |Data |Dat fiind |Dar |Dann |dann |Dan |Dados |Dado |Dadas |Dada |Ða ðe |Ða |Cuando |Cho |Cando |Când |Cand |Cal |But y'all |But at the end of the day I reckon |BUT |But |Buh |Blimey! |Biết |Bet |Bagi |Aye |awer |Avast! |Atunci |Atesa |Atès |Apabila |Anrhegedig a |Angenommen |And y'all |And |AN |An |an |Amikor |Amennyiben |Ama |Als |Alors |Allora |Ali |Aleshores |Ale |Akkor |Ak |Adott |Ac |Aber |A zároveň |A tiež |A taktiež |A také |A |a |7 |\* )/)) {
state.inStep = true;
state.allowPlaceholders = true;
state.allowMultilineArgument = true;
state.inKeywordLine = true;
return "keyword";

// INLINE STRING
} else if (!state.inMultilineArgument && stream.match(/"/)) {
stream.match(/.*?"/);
} else if (stream.match(/"[^"]*"?/)) {
return "string";

// MULTILINE ARGUMENTS
} else if (state.allowSteps && stream.eat(":")) {
if (stream.match(/\s*$/)) {
state.inMultilineArgument = true;
return "keyword";
} else {
return null;
}

} else if (state.allowSteps && stream.match("<")) {
if (stream.match(/.*?>/)) {
return "property";
} else {
return null;
}
// PLACEHOLDER
} else if (state.allowPlaceholders && stream.match(/<[^>]*>?/)) {
return "variable";

// Fall through
} else {
stream.eatWhile(/[^":<]/);
stream.next();
stream.eatWhile(/[^@"<#]/);
return null;
}

return null;
}
};
});
Expand Down
4 changes: 0 additions & 4 deletions mode/haml/haml.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,6 @@
style = null;
}
return style;
},

indent: function(state) {
return state.indented;
}
};
}, "htmlmixed", "ruby");
Expand Down
2 changes: 1 addition & 1 deletion mode/haml/test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(function() {
var mode = CodeMirror.getMode({tabSize: 4}, "haml");
var mode = CodeMirror.getMode({tabSize: 4, indentUnit: 2}, "haml");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }

// Requires at least one media query
Expand Down
8 changes: 5 additions & 3 deletions mode/haskell/haskell.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ CodeMirror.defineMode("haskell", function(_config, modeConfig) {
// These should all be Unicode extended, as per the Haskell 2010 report
var smallRE = /[a-z_]/;
var largeRE = /[A-Z]/;
var digitRE = /[0-9]/;
var digitRE = /\d/;
var hexitRE = /[0-9A-Fa-f]/;
var octitRE = /[0-7]/;
var idRE = /[a-z_A-Z0-9']/;
Expand Down Expand Up @@ -76,9 +76,8 @@ CodeMirror.defineMode("haskell", function(_config, modeConfig) {
}
source.eatWhile(digitRE);
var t = "number";
if (source.eat('.')) {
if (source.match(/^\.\d+/)) {
t = "number";
source.eatWhile(digitRE); // should require at least 1
}
if (source.eat(/[eE]/)) {
t = "number";
Expand All @@ -88,6 +87,9 @@ CodeMirror.defineMode("haskell", function(_config, modeConfig) {
return t;
}

if (ch == "." && source.eat("."))
return "keyword";

if (symbolRE.test(ch)) {
if (ch == '-' && source.eat(/-/)) {
source.eatWhile(/-/);
Expand Down
2 changes: 0 additions & 2 deletions mode/htmlembedded/htmlembedded.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
};
},

electricChars: "/{}:",

innerMode: function(state) {
if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode};
else return {state: state.htmlState, mode: htmlMixedMode};
Expand Down
2 changes: 0 additions & 2 deletions mode/htmlmixed/htmlmixed.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
return CodeMirror.Pass;
},

electricChars: "/{}:",

innerMode: function(state) {
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
}
Expand Down
5 changes: 3 additions & 2 deletions mode/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ <h2>Language modes</h2>
<li><a href="eiffel/index.html">Eiffel</a></li>
<li><a href="erlang/index.html">Erlang</a></li>
<li><a href="fortran/index.html">Fortran</a></li>
<li><a href="mllike/index.html">F#</a></li>
<li><a href="gas/index.html">Gas</a> (AT&amp;T-style assembly)</li>
<li><a href="gherkin/index.html">Gherkin</a></li>
<li><a href="go/index.html">Go</a></li>
Expand All @@ -61,14 +62,14 @@ <h2>Language modes</h2>
<li><a href="javascript/index.html">JavaScript</a></li>
<li><a href="jinja2/index.html">Jinja2</a></li>
<li><a href="julia/index.html">Julia</a></li>
<li><a href="less/index.html">LESS</a></li>
<li><a href="css/less.html">LESS</a></li>
<li><a href="livescript/index.html">LiveScript</a></li>
<li><a href="lua/index.html">Lua</a></li>
<li><a href="markdown/index.html">Markdown</a> (<a href="gfm/index.html">GitHub-flavour</a>)</li>
<li><a href="mirc/index.html">mIRC</a></li>
<li><a href="nginx/index.html">Nginx</a></li>
<li><a href="ntriples/index.html">NTriples</a></li>
<li><a href="ocaml/index.html">OCaml</a></li>
<li><a href="mllike/index.html">OCaml</a></li>
<li><a href="octave/index.html">Octave</a> (MATLAB)</li>
<li><a href="pascal/index.html">Pascal</a></li>
<li><a href="pegjs/index.html">PEG.js</a></li>
Expand Down
73 changes: 43 additions & 30 deletions mode/javascript/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {

var jsKeywords = {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
Expand Down Expand Up @@ -54,14 +54,16 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {

var isOperatorChar = /[+\-*&%=<>!?|~^]/;

function nextUntilUnescaped(stream, end) {
var escaped = false, next;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (next == end && !escaped)
return false;
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
return escaped;
}

// Used as scratch variables to communicate multiple values without
Expand All @@ -83,7 +85,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>");
return ret("=>", "operator");
} else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
Expand All @@ -99,12 +101,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return ret("comment", "comment");
} else if (state.lastType == "operator" || state.lastType == "keyword c" ||
state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
nextUntilUnescaped(stream, "/");
readRegexp(stream);
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
return ret("regexp", "string-2");
} else {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
Expand All @@ -114,7 +116,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return ret("error", "error");
} else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
return ret("operator", "operator", stream.current());
} else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
Expand All @@ -125,8 +127,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {

function tokenString(quote) {
return function(stream, state) {
if (!nextUntilUnescaped(stream, quote))
state.tokenize = tokenBase;
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
Expand Down Expand Up @@ -304,7 +310,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == ";") return cont();
if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse);
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, poplex, statement, poplex);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
Expand All @@ -327,7 +333,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function expressionInner(type, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, commasep(pattern, ")"), expect("=>"), body, popcontext);
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}

Expand All @@ -337,8 +343,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), expressionNoComma, maybeArrayComprehension, poplex, maybeop);
if (type == "{") return cont(commasep(objprop, "}"), maybeop);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
return cont();
}
function maybeexpression(type) {
Expand All @@ -365,12 +371,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
if (type == "quasi") { cx.cc.push(me); return quasi(value); }
if (type == ";") return;
if (type == "(") return cont(commasep(expressionNoComma, ")", "call"), me);
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
}
function quasi(value) {
if (!value) debugger;
if (value.slice(value.length - 2) != "${") return cont();
return cont(expression, continueQuasi);
}
Expand Down Expand Up @@ -418,7 +423,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end, info) {
function commasep(what, end) {
function proceed(type) {
if (type == ",") {
var lex = cx.state.lexical;
Expand All @@ -430,10 +435,14 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
return function(type) {
if (type == end) return cont();
if (info === false) return pass(what, proceed);
return pass(pushlex(end, info), what, proceed, poplex);
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
Expand All @@ -449,8 +458,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
function pattern(type, value) {
if (type == "variable") { register(value); return cont(); }
if (type == "[") return cont(commasep(pattern, "]"));
if (type == "{") return cont(commasep(proppattern, "}"));
if (type == "[") return contCommasep(pattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
Expand All @@ -470,7 +479,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex);
}
function forspec(type) {
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"));
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, expect(";"), forspec2);
Expand All @@ -493,7 +502,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, commasep(funarg, ")"), statement, popcontext);
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type) {
if (type == "spread") return cont(funarg);
Expand All @@ -506,7 +515,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (value == "extends") return cont(expression);
}
function objlit(type) {
if (type == "{") return cont(commasep(objprop, "}"));
if (type == "{") return contCommasep(objprop, "}");
}
function afterModule(type, value) {
if (type == "string") return cont(statement);
Expand All @@ -522,17 +531,21 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return pass(importSpec, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return cont(commasep(importSpec, "}"));
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
return cont();
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(expressionNoComma, maybeArrayComprehension);
}
function maybeArrayComprehension(type) {
if (type == "for") return pass(comprehension);
if (type == ",") return cont(commasep(expressionNoComma, "]", false));
return pass(commasep(expressionNoComma, "]", false));
if (type == "for") return pass(comprehension, expect("]"));
if (type == ",") return cont(commasep(expressionNoComma, "]"));
return pass(commasep(expressionNoComma, "]"));
}
function comprehension(type) {
if (type == "for") return cont(forspec, comprehension);
Expand Down
67 changes: 54 additions & 13 deletions mode/javascript/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,29 @@
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }

MT("locals",
"[keyword function] [variable foo]([def a], [def b]) { [keyword var] [def c] = [number 10]; [keyword return] [variable-2 a] + [variable-2 c] + [variable d]; }");
"[keyword function] [variable foo]([def a], [def b]) { [keyword var] [def c] [operator =] [number 10]; [keyword return] [variable-2 a] [operator +] [variable-2 c] [operator +] [variable d]; }");

MT("comma-and-binop",
"[keyword function](){ [keyword var] [def x] = [number 1] + [number 2], [def y]; }");
"[keyword function](){ [keyword var] [def x] [operator =] [number 1] [operator +] [number 2], [def y]; }");

MT("destructuring",
"([keyword function]([def a], [[[def b], [def c] ]]) {",
" [keyword let] {[def d], [property foo]: [def c]=[number 10], [def x]} = [variable foo]([variable-2 a]);",
" [[[variable-2 c], [variable y] ]] = [variable-2 c];",
" [keyword let] {[def d], [property foo]: [def c][operator =][number 10], [def x]} [operator =] [variable foo]([variable-2 a]);",
" [[[variable-2 c], [variable y] ]] [operator =] [variable-2 c];",
"})();");

MT("class",
"[keyword class] [variable Point] [keyword extends] [variable SuperThing] {",
" [[ [string-2 /expr/] ]]: [number 24],",
" [property constructor]([def x], [def y]) {",
" [keyword super]([string 'something']);",
" [keyword this].[property x] = [variable-2 x];",
" [keyword this].[property x] [operator =] [variable-2 x];",
" }",
"}");

MT("module",
"[keyword module] [string 'foo'] {",
" [keyword export] [keyword let] [def x] = [number 42];",
" [keyword export] [keyword let] [def x] [operator =] [number 42];",
" [keyword export] [keyword *] [keyword from] [string 'somewhere'];",
"}");

Expand All @@ -38,22 +38,22 @@

MT("const",
"[keyword function] [variable f]() {",
" [keyword const] [[ [def a], [def b] ]] = [[ [number 1], [number 2] ]];",
" [keyword const] [[ [def a], [def b] ]] [operator =] [[ [number 1], [number 2] ]];",
"}");

MT("for/of",
"[keyword for]([keyword let] [variable of] [keyword of] [variable something]) {}");

MT("generator",
"[keyword function*] [variable repeat]([def n]) {",
" [keyword for]([keyword var] [def i] = [number 0]; [variable-2 i] < [variable-2 n]; ++[variable-2 i])",
" [keyword for]([keyword var] [def i] [operator =] [number 0]; [variable-2 i] [operator <] [variable-2 n]; [operator ++][variable-2 i])",
" [keyword yield] [variable-2 i];",
"}");

MT("fatArrow",
"[variable array].[property filter]([def a] => [variable-2 a] + [number 1]);",
"[variable array].[property filter]([def a] [operator =>] [variable-2 a] [operator +] [number 1]);",
"[variable a];", // No longer in scope
"[keyword let] [variable f] = ([[ [def a], [def b] ]], [def c]) => [variable-2 a] + [variable-2 c];",
"[keyword let] [variable f] [operator =] ([[ [def a], [def b] ]], [def c]) [operator =>] [variable-2 a] [operator +] [variable-2 c];",
"[variable c];");

MT("spread",
Expand All @@ -63,10 +63,51 @@

MT("comprehension",
"[keyword function] [variable f]() {",
" [[ [variable x] + [number 1] [keyword for] ([keyword var] [def x] [keyword in] [variable y]) [keyword if] [variable pred]([variable-2 x]) ]];",
" ([variable u] [keyword for] ([keyword var] [def u] [keyword of] [variable generateValues]()) [keyword if] ([variable-2 u].[property color] === [string 'blue']));",
" [[([variable x] [operator +] [number 1]) [keyword for] ([keyword var] [def x] [keyword in] [variable y]) [keyword if] [variable pred]([variable-2 x]) ]];",
" ([variable u] [keyword for] ([keyword var] [def u] [keyword of] [variable generateValues]()) [keyword if] ([variable-2 u].[property color] [operator ===] [string 'blue']));",
"}");

MT("quasi",
"[variable re][string-2 `fofdlakj${][variable x] + ([variable re][string-2 `foo`]) + [number 1][string-2 }fdsa`] + [number 2]");
"[variable re][string-2 `fofdlakj${][variable x] [operator +] ([variable re][string-2 `foo`]) [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]");

MT("indent_statement",
"[keyword var] [variable x] [operator =] [number 10]",
"[variable x] [operator +=] [variable y] [operator +]",
" [atom Infinity]",
"[keyword debugger];");

MT("indent_if",
"[keyword if] ([number 1])",
" [keyword break];",
"[keyword else] [keyword if] ([number 2])",
" [keyword continue];",
"[keyword else]",
" [number 10];",
"[keyword if] ([number 1]) {",
" [keyword break];",
"} [keyword else] [keyword if] ([number 2]) {",
" [keyword continue];",
"} [keyword else] {",
" [number 10];",
"}");

MT("indent_for",
"[keyword for] ([keyword var] [variable i] [operator =] [number 0];",
" [variable i] [operator <] [number 100];",
" [variable i][operator ++])",
" [variable doSomething]([variable i]);",
"[keyword debugger];");

MT("indent_c_style",
"[keyword function] [variable foo]()",
"{",
" [keyword debugger];",
"}");

MT("multilinestring",
"[keyword var] [variable x] [operator =] [string 'foo\\]",
"[string bar'];");

MT("scary_regexp",
"[string-2 /foo[[/]]bar/];");
})();
16 changes: 13 additions & 3 deletions mode/jinja2/jinja2.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
CodeMirror.defineMode("jinja2", function() {
var keywords = ["block", "endblock", "for", "endfor", "in", "true", "false",
"loop", "none", "self", "super", "if", "as", "not", "and",
"else", "import", "with", "without", "context"];
var keywords = ["and", "as", "block", "endblock", "by", "cycle", "debug", "else", "elif",
"extends", "filter", "endfilter", "firstof", "for",
"endfor", "if", "endif", "ifchanged", "endifchanged",
"ifequal", "endifequal", "ifnotequal",
"endifnotequal", "in", "include", "load", "not", "now", "or",
"parsed", "regroup", "reversed", "spaceless",
"endspaceless", "ssi", "templatetag", "openblock",
"closeblock", "openvariable", "closevariable",
"openbrace", "closebrace", "opencomment",
"closecomment", "widthratio", "url", "with", "endwith",
"get_current_language", "trans", "noop", "blocktrans",
"endblocktrans", "get_available_languages",
"get_current_language_bidi", "plural"];
keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b");

function tokenBase (stream, state) {
Expand Down
13 changes: 7 additions & 6 deletions mode/less/less.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ CodeMirror.defineMode("less", function(config) {
if(stream.peek() === " ")stream.eatSpace();
if(stream.peek() === ")" || type === ":")return ret("number", "unit");//rgba(0,0,0,.25);
else if(stream.current().length >1){
if(state.stack[state.stack.length-1] === "rule" && stream.peek().match(/{|,|\+|\(/) === null)return ret("number", "unit");
if(state.stack[state.stack.length-1] === "rule" && !stream.match(/^[{,+(]/, false)) return ret("number", "unit");
}
return ret("tag", "tag");
} else if (ch == "#") {
Expand Down Expand Up @@ -170,7 +170,7 @@ CodeMirror.defineMode("less", function(config) {
} else if(type == "compare" || type == "a" || type == "("){
return ret("string", "string");
} else if(type == "|" || stream.current() == "-" || type == "["){
if(type == "|" && stream.peek().match(/\]|=|\~/) !== null)return ret("number", stream.current());
if (type == "|" && stream.match(/^[\]=~]/, false)) return ret("number", stream.current());
else if(type == "|" )return ret("tag", "tag");
else if(type == "["){
stream.eatWhile(/\w\-/);
Expand Down Expand Up @@ -201,7 +201,7 @@ CodeMirror.defineMode("less", function(config) {

//else if((type === ")" && state.stack[state.stack.length-1] === "rule") || (state.stack[state.stack.length-2] === "{" && state.stack[state.stack.length-1] === "rule" && type === "variable"))return ret(null, stream.current());

else if(/\^|\$/.test(stream.current()) && stream.peek().match(/\~|=/) !== null)return ret("string", "string");//att^=val
else if (/\^|\$/.test(stream.current()) && stream.match(/^[~=]/, false)) return ret("string", "string");//att^=val

else if(type === "unit" && state.stack[state.stack.length-1] === "rule")return ret(null, "unit");
else if(type === "unit" && state.stack[state.stack.length-1] === ";")return ret(null, "unit");
Expand All @@ -210,12 +210,13 @@ CodeMirror.defineMode("less", function(config) {
//else if(type === "unit" && state.stack[state.stack.length-1] === "rule")return ret(null, stream.current());

else if((type === ";" || type === "}" || type === ",") && state.stack[state.stack.length-1] === ";")return ret("tag", stream.current());
else if((type === ";" && stream.peek() !== undefined && stream.peek().match(/{|./) === null) || (type === ";" && stream.eatSpace() && stream.peek().match(/{|./) === null))return ret("variable", stream.current());
else if((type === ";" && stream.peek() !== undefined && !stream.match(/^[{\.]/, false)) ||
(type === ";" && stream.eatSpace() && !stream.match(/^[{\.]/))) return ret("variable", stream.current());
else if((type === "@media" && state.stack[state.stack.length-1] === "@media") || type === "@namespace")return ret("tag", stream.current());

else if(type === "{" && state.stack[state.stack.length-1] === ";" && stream.peek() === "{")return ret("tag", "tag");
else if((type === "{" || type === ":") && state.stack[state.stack.length-1] === ";")return ret(null, stream.current());
else if((state.stack[state.stack.length-1] === "{" && stream.eatSpace() && stream.peek().match(/.|#/) === null) || type === "select-op" || (state.stack[state.stack.length-1] === "rule" && type === ",") )return ret("tag", "tag");
else if((state.stack[state.stack.length-1] === "{" && stream.eatSpace() && !stream.match(/^[\.#]/)) || type === "select-op" || (state.stack[state.stack.length-1] === "rule" && type === ",") )return ret("tag", "tag");
else if(type === "variable" && state.stack[state.stack.length-1] === "rule")return ret("tag", "tag");
else if((stream.eatSpace() && stream.peek() === "{") || stream.eol() || stream.peek() === "{")return ret("tag", "tag");
//this one messes up indentation
Expand All @@ -227,7 +228,7 @@ CodeMirror.defineMode("less", function(config) {
else if(stream.sol())return ret("tag", "tag");
else if((stream.eatSpace() && stream.peek() === "#") || stream.peek() === "#")return ret("tag", "tag");
else if(state.stack.length === 0)return ret("tag", "tag");
else if(type === ";" && stream.peek() !== undefined && stream.peek().match(/^[.|\#]/g) !== null)return ret("tag", "tag");
else if(type === ";" && stream.peek() !== undefined && stream.match(/^[\.|#]/g)) return ret("tag", "tag");

else if(type === ":"){stream.eatSpace();return ret(null, stream.current());}

Expand Down
276 changes: 231 additions & 45 deletions mode/markdown/markdown.js

Large diffs are not rendered by default.

94 changes: 77 additions & 17 deletions mode/markdown/test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,57 @@
(function() {
var mode = CodeMirror.getMode({tabSize: 4}, "markdown");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
var modeHighlightFormatting = CodeMirror.getMode({tabSize: 4}, {name: "markdown", highlightFormatting: true});
function FT(name) { test.mode(name, modeHighlightFormatting, Array.prototype.slice.call(arguments, 1)); }

FT("formatting_emAsterisk",
"[em&formatting&formatting-em *][em foo][em&formatting&formatting-em *]");

FT("formatting_emUnderscore",
"[em&formatting&formatting-em _][em foo][em&formatting&formatting-em _]");

FT("formatting_strongAsterisk",
"[strong&formatting&formatting-strong **][strong foo][strong&formatting&formatting-strong **]");

FT("formatting_strongUnderscore",
"[strong&formatting&formatting-strong __][strong foo][strong&formatting&formatting-strong __]");

FT("formatting_codeBackticks",
"[comment&formatting&formatting-code `][comment foo][comment&formatting&formatting-code `]");

FT("formatting_doubleBackticks",
"[comment&formatting&formatting-code ``][comment foo ` bar][comment&formatting&formatting-code ``]");

FT("formatting_atxHeader",
"[header&header1&formatting&formatting-header&formatting-header1 #][header&header1 foo # bar ][header&header1&formatting&formatting-header&formatting-header1 #]");

FT("formatting_setextHeader",
"foo",
"[header&header1&formatting&formatting-header&formatting-header1 =]");

FT("formatting_blockquote",
"[quote&quote-1&formatting&formatting-quote&formatting-quote-1 > ][quote&quote-1 foo]");

FT("formatting_list",
"[variable-2&formatting&formatting-list&formatting-list-ul - ][variable-2 foo]");
FT("formatting_list",
"[variable-2&formatting&formatting-list&formatting-list-ol 1. ][variable-2 foo]");

FT("formatting_link",
"[link&formatting&formatting-link [][link foo][link&formatting&formatting-link ]]][string&formatting&formatting-link-string (][string http://example.com/][string&formatting&formatting-link-string )]");

FT("formatting_linkReference",
"[link&formatting&formatting-link [][link foo][link&formatting&formatting-link ]]][string&formatting&formatting-link-string [][string bar][string&formatting&formatting-link-string ]]]",
"[link&formatting&formatting-link [][link bar][link&formatting&formatting-link ]]:] [string http://example.com/]");

FT("formatting_linkWeb",
"[link&formatting&formatting-link <][link http://example.com/][link&formatting&formatting-link >]");

FT("formatting_linkEmail",
"[link&formatting&formatting-link <][link user@example.com][link&formatting&formatting-link >]");

FT("formatting_escape",
"[formatting&formatting-escape \\]*");

MT("plainText",
"foo");
Expand Down Expand Up @@ -142,50 +193,50 @@

// Single-line blockquote with trailing space
MT("blockquoteSpace",
"[atom > foo]");
"[quote&quote-1 > foo]");

// Single-line blockquote
MT("blockquoteNoSpace",
"[atom >foo]");
"[quote&quote-1 >foo]");

// No blank line before blockquote
MT("blockquoteNoBlankLine",
"foo",
"[atom > bar]");
"[quote&quote-1 > bar]");

// Nested blockquote
MT("blockquoteSpace",
"[atom > foo]",
"[number > > foo]",
"[atom > > > foo]");
"[quote&quote-1 > foo]",
"[quote&quote-1 >][quote&quote-2 > foo]",
"[quote&quote-1 >][quote&quote-2 >][quote&quote-3 > foo]");

// Single-line blockquote followed by normal paragraph
MT("blockquoteThenParagraph",
"[atom >foo]",
"[quote&quote-1 >foo]",
"",
"bar");

// Multi-line blockquote (lazy mode)
MT("multiBlockquoteLazy",
"[atom >foo]",
"[atom bar]");
"[quote&quote-1 >foo]",
"[quote&quote-1 bar]");

// Multi-line blockquote followed by normal paragraph (lazy mode)
MT("multiBlockquoteLazyThenParagraph",
"[atom >foo]",
"[atom bar]",
"[quote&quote-1 >foo]",
"[quote&quote-1 bar]",
"",
"hello");

// Multi-line blockquote (non-lazy mode)
MT("multiBlockquote",
"[atom >foo]",
"[atom >bar]");
"[quote&quote-1 >foo]",
"[quote&quote-1 >bar]");

// Multi-line blockquote followed by normal paragraph (non-lazy mode)
MT("multiBlockquoteThenParagraph",
"[atom >foo]",
"[atom >bar]",
"[quote&quote-1 >foo]",
"[quote&quote-1 >bar]",
"",
"hello");

Expand Down Expand Up @@ -225,6 +276,11 @@
"1. bar",
"2. hello");

// List after header
MT("listAfterHeader",
"[header&header1 # foo]",
"[variable-2 - bar]");

// Formatting in lists (*)
MT("listAsteriskFormatting",
"[variable-2 * ][variable-2&em *foo*][variable-2 bar]",
Expand Down Expand Up @@ -310,7 +366,7 @@
"",
"[variable-2 * bar]",
"",
" [variable-2&atom > hello]");
" [variable-2&quote&quote-1 > hello]");

// Code block
MT("blockquoteCode",
Expand Down Expand Up @@ -368,7 +424,7 @@
"",
" [variable-3 + bar]",
"",
" [atom&variable-3 > hello]");
" [quote&quote-1&variable-3 > hello]");

MT("listCode",
"[variable-2 * foo]",
Expand Down Expand Up @@ -651,6 +707,10 @@
MT("doubleEscapeHash",
"\\\\# foo");

MT("escapeNewline",
"\\",
"[em *foo*]");


// Tests to make sure GFM-specific things aren't getting through

Expand Down
3 changes: 2 additions & 1 deletion mode/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ CodeMirror.modeInfo = [
{name: 'Eiffel', mime: 'text/x-eiffel', mode: 'eiffel'},
{name: 'Erlang', mime: 'text/x-erlang', mode: 'erlang'},
{name: 'Fortran', mime: 'text/x-fortran', mode: 'fortran'},
{name: 'F#', mime: 'text/x-fsharp', mode: 'mllike'},
{name: 'Gas', mime: 'text/x-gas', mode: 'gas'},
{name: 'Gherkin', mime: 'text/x-feature', mode: 'gherkin'},
{name: 'GitHub Flavored Markdown', mime: 'text/x-gfm', mode: 'gfm'},
Expand Down Expand Up @@ -45,7 +46,7 @@ CodeMirror.modeInfo = [
{name: 'mIRC', mime: 'text/mirc', mode: 'mirc'},
{name: 'Nginx', mime: 'text/x-nginx-conf', mode: 'nginx'},
{name: 'NTriples', mime: 'text/n-triples', mode: 'ntriples'},
{name: 'OCaml', mime: 'text/x-ocaml', mode: 'ocaml'},
{name: 'OCaml', mime: 'text/x-ocaml', mode: 'mllike'},
{name: 'Octave', mime: 'text/x-octave', mode: 'octave'},
{name: 'Pascal', mime: 'text/x-pascal', mode: 'pascal'},
{name: 'PEG.js', mime: null, mode: 'pegjs'},
Expand Down
49 changes: 41 additions & 8 deletions mode/ocaml/index.html → mode/mllike/index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<!doctype html>

<title>CodeMirror: OCaml mode</title>
<title>CodeMirror: ML-like mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">

<link rel=stylesheet href=../../lib/codemirror.css>
<script src=../../lib/codemirror.js></script>
<script src=../../addon/edit/matchbrackets.js></script>
<script src=ocaml.js></script>
<script src=mllike.js></script>
<style type=text/css>
.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}
</style>
Expand All @@ -21,15 +21,15 @@
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">OCaml</a>
<li><a class=active href="#">ML-like</a>
</ul>
</div>

<article>
<h2>OCaml mode</h2>


<textarea id=code>
<textarea id="ocamlCode">
(* Summing a list of integers *)
let rec sum xs =
match xs with
Expand Down Expand Up @@ -89,7 +89,7 @@ <h2>OCaml mode</h2>

and insert elem = function
| [] -&gt; [elem]
| x :: l -&gt;
| x :: l -&gt;
if elem &lt; x then elem :: x :: l else x :: insert elem l;;

(* Imperative features *)
Expand Down Expand Up @@ -134,13 +134,46 @@ <h2>OCaml mode</h2>
(* OCaml page on Wikipedia - http://en.wikipedia.org/wiki/OCaml *)
</textarea>

<h2>F# mode</h2>
<textarea id="fsharpCode">
module CodeMirror.FSharp

let rec fib = function
| 0 -> 0
| 1 -> 1
| n -> fib (n - 1) + fib (n - 2)

type Point =
{
x : int
y : int
}

type Color =
| Red
| Green
| Blue

[0 .. 10]
|> List.map ((+) 2)
|> List.fold (fun x y -> x + y) 0
|> printf "%i"
</textarea>


<script>
var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
mode: 'ocaml',
var ocamlEditor = CodeMirror.fromTextArea(document.getElementById('ocamlCode'), {
mode: 'text/x-ocaml',
lineNumbers: true,
matchBrackets: true
});

var fsharpEditor = CodeMirror.fromTextArea(document.getElementById('fsharpCode'), {
mode: 'text/x-fsharp',
lineNumbers: true,
matchBrackets: true
});
</script>

<p><strong>MIME types defined:</strong> <code>text/x-ocaml</code>.</p>
<p><strong>MIME types defined:</strong> <code>text/x-ocaml</code> (OCaml) and <code>text/x-fsharp</code> (F#).</p>
</article>
101 changes: 88 additions & 13 deletions mode/ocaml/ocaml.js → mode/mllike/mllike.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
CodeMirror.defineMode('ocaml', function() {
CodeMirror.defineMode('mllike', function(_config, parserConfig) {

var words = {
'true': 'atom',
'false': 'atom',
'let': 'keyword',
'rec': 'keyword',
'in': 'keyword',
'of': 'keyword',
'and': 'keyword',
'succ': 'keyword',
'if': 'keyword',
'then': 'keyword',
'else': 'keyword',
Expand All @@ -25,17 +22,19 @@ CodeMirror.defineMode('ocaml', function() {
'match': 'keyword',
'with': 'keyword',
'try': 'keyword',
'raise': 'keyword',
'begin': 'keyword',
'end': 'keyword',
'open': 'builtin',
'trace': 'builtin',
'ignore': 'builtin',
'exit': 'builtin',
'print_string': 'builtin',
'print_endline': 'builtin'
'begin': 'keyword',
'end': 'keyword'
};

var extraWords = parserConfig.extraWords || {};
for (var prop in extraWords) {
if (extraWords.hasOwnProperty(prop)) {
words[prop] = parserConfig.extraWords[prop];
}
}

function tokenBase(stream, state) {
var ch = stream.next();

Expand All @@ -58,6 +57,10 @@ CodeMirror.defineMode('ocaml', function() {
stream.eatWhile(/\w/);
return 'quote';
}
if (ch === '/' && parserConfig.slashComments && stream.eat('/')) {
stream.skipToEnd();
return 'comment';
}
if (/\d/.test(ch)) {
stream.eatWhile(/[\d]/);
if (stream.eat('.')) {
Expand Down Expand Up @@ -109,8 +112,80 @@ CodeMirror.defineMode('ocaml', function() {
},

blockCommentStart: "(*",
blockCommentEnd: "*)"
blockCommentEnd: "*)",
lineComment: parserConfig.slashComments ? "//" : null
};
});

CodeMirror.defineMIME('text/x-ocaml', 'ocaml');
CodeMirror.defineMIME('text/x-ocaml', {
name: 'mllike',
extraWords: {
'succ': 'keyword',
'trace': 'builtin',
'exit': 'builtin',
'print_string': 'builtin',
'print_endline': 'builtin',
'true': 'atom',
'false': 'atom',
'raise': 'keyword'
}
});

CodeMirror.defineMIME('text/x-fsharp', {
name: 'mllike',
extraWords: {
'abstract': 'keyword',
'as': 'keyword',
'assert': 'keyword',
'base': 'keyword',
'class': 'keyword',
'default': 'keyword',
'delegate': 'keyword',
'downcast': 'keyword',
'downto': 'keyword',
'elif': 'keyword',
'exception': 'keyword',
'extern': 'keyword',
'finally': 'keyword',
'global': 'keyword',
'inherit': 'keyword',
'inline': 'keyword',
'interface': 'keyword',
'internal': 'keyword',
'lazy': 'keyword',
'let!': 'keyword',
'member' : 'keyword',
'module': 'keyword',
'namespace': 'keyword',
'new': 'keyword',
'null': 'keyword',
'override': 'keyword',
'private': 'keyword',
'public': 'keyword',
'return': 'keyword',
'return!': 'keyword',
'select': 'keyword',
'static': 'keyword',
'struct': 'keyword',
'upcast': 'keyword',
'use': 'keyword',
'use!': 'keyword',
'val': 'keyword',
'when': 'keyword',
'yield': 'keyword',
'yield!': 'keyword',

'List': 'builtin',
'Seq': 'builtin',
'Map': 'builtin',
'Set': 'builtin',
'int': 'builtin',
'string': 'builtin',
'raise': 'builtin',
'failwith': 'builtin',
'not': 'builtin',
'true': 'builtin',
'false': 'builtin'
},
slashComments: true
});
14 changes: 5 additions & 9 deletions mode/pegjs/pegjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,11 @@ CodeMirror.defineMode("pegjs", function (config) {
}
return "comment";
} else if (state.inChracterClass) {
if (stream.match(/^[^\]\\]+/)) {
return;
} else if (stream.match(/^\\./)) {
return;
} else {
stream.next();
state.inChracterClass = false;
return 'bracket';
}
while (state.inChracterClass && !stream.eol()) {
if (!(stream.match(/^[^\]\\]+/) || stream.match(/^\\./))) {
state.inChracterClass = false;
}
}
} else if (stream.peek() === '[') {
stream.next();
state.inChracterClass = true;
Expand Down
13 changes: 6 additions & 7 deletions mode/php/php.js
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,26 @@

function dispatch(stream, state) {
var isPHP = state.curMode == phpMode;
if (stream.sol() && state.pending != '"') state.pending = null;
if (stream.sol() && state.pending && state.pending != '"' && state.pending != "'") state.pending = null;
if (!isPHP) {
if (stream.match(/^<\?\w*/)) {
state.curMode = phpMode;
state.curState = state.php;
return "meta";
}
if (state.pending == '"') {
while (!stream.eol() && stream.next() != '"') {}
if (state.pending == '"' || state.pending == "'") {
while (!stream.eol() && stream.next() != state.pending) {}
var style = "string";
} else if (state.pending && stream.pos < state.pending.end) {
stream.pos = state.pending.end;
var style = state.pending.style;
} else {
var style = htmlMode.token(stream, state.curState);
}
state.pending = null;
var cur = stream.current(), openPHP = cur.search(/<\?/);
if (state.pending) state.pending = null;
var cur = stream.current(), openPHP = cur.search(/<\?/), m;
if (openPHP != -1) {
if (style == "string" && /\"$/.test(cur) && !/\?>/.test(cur)) state.pending = '"';
if (style == "string" && (m = cur.match(/[\'\"]$/)) && !/\?>/.test(cur)) state.pending = m[0];
else state.pending = {end: stream.pos, style: style};
stream.backUp(cur.length - openPHP);
}
Expand Down Expand Up @@ -117,7 +117,6 @@
return state.curMode.indent(state.curState, textAfter);
},

electricChars: "/{}:",
blockCommentStart: "/*",
blockCommentEnd: "*/",
lineComment: "//",
Expand Down
1 change: 1 addition & 0 deletions mode/python/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ <h2>Configuration Options for Python mode:</h2>
<ul>
<li>version - 2/3 - The version of Python to recognize. Default is 2.</li>
<li>singleLineStringErrors - true/false - If you have a single-line string that is not terminated at the end of the line, this will show subsequent lines as errors if true, otherwise it will consider the newline as the end of the string. Default is false.</li>
<li>hangingIndent - int - If you want to write long arguments to a function starting on a new line, how much that line should be indented. Defaults to one normal indentation unit.</li>
</ul>
<h2>Advanced Configuration Options:</h2>
<p>Usefull for superset of python syntax like Enthought enaml, IPython magics and questionmark help</p>
Expand Down
6 changes: 6 additions & 0 deletions mode/python/python.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ CodeMirror.defineMode("python", function(conf, parserConf) {
var doubleDelimiters = parserConf.doubleDelimiters || new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
var tripleDelimiters = parserConf.tripleDelimiters || new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
var hangingIndent = parserConf.hangingIndent || parserConf.indentUnit;

var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
Expand Down Expand Up @@ -211,6 +212,11 @@ CodeMirror.defineMode("python", function(conf, parserConf) {
break;
}
}
} else if (stream.match(/\s*($|#)/, false)) {
// An open paren/bracket/brace with only space or comments after it
// on the line will indent the next line a fixed amount, to make it
// easier to put arguments, list items, etc. on their own lines.
indentUnit = stream.indentation() + hangingIndent;
} else {
indentUnit = stream.column() + stream.current().length;
}
Expand Down
6 changes: 5 additions & 1 deletion mode/r/r.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ CodeMirror.defineMode("r", function(config) {
var word = stream.current();
if (atoms.propertyIsEnumerable(word)) return "atom";
if (keywords.propertyIsEnumerable(word)) {
if (blockkeywords.propertyIsEnumerable(word)) curPunc = "block";
// Block keywords start new blocks, except 'else if', which only starts
// one new block for the 'if', no block for the 'else'.
if (blockkeywords.propertyIsEnumerable(word) &&
!stream.match(/\s*if(\s+|$)/, false))
curPunc = "block";
return "keyword";
}
if (builtins.propertyIsEnumerable(word)) return "builtin";
Expand Down
2 changes: 1 addition & 1 deletion mode/rpm/spec/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ CodeMirror.defineMode("spec", function() {
var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/;

var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/;
var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preun|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/;
var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/;
var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros
var control_flow_simple = /^%(else|endif)/; // rpm control flow macros
var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros
Expand Down
4 changes: 3 additions & 1 deletion mode/ruby/ruby.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ CodeMirror.defineMode("ruby", function(config) {
"caller", "lambda", "proc", "public", "protected", "private", "require", "load",
"require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__"
]);
var indentWords = wordObj(["def", "class", "case", "for", "while", "do", "module", "then",
var indentWords = wordObj(["def", "class", "case", "for", "while", "module", "then",
"catch", "loop", "proc", "begin"]);
var dedentWords = wordObj(["end", "until"]);
var matching = {"[": "]", "{": "}", "(": ")"};
Expand Down Expand Up @@ -214,6 +214,8 @@ CodeMirror.defineMode("ruby", function(config) {
else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent";
else if ((word == "if" || word == "unless") && stream.column() == stream.indentation())
kwtype = "indent";
else if (word == "do" && state.context.indented < state.indented)
kwtype = "indent";
}
if (curPunc || (style && style != "comment")) state.lastTok = word || curPunc || style;
if (curPunc == "|") state.varList = !state.varList;
Expand Down
6 changes: 4 additions & 2 deletions mode/smalltalk/smalltalk.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ CodeMirror.defineMode('smalltalk', function(config) {
stream.next();
token = nextSymbol(stream, new Context(nextSymbol, context));
} else {
stream.eatWhile(/[^ .\[\]()]/);
token.name = 'string-2';
if (stream.eatWhile(/[^ .{}\[\]()]/))
token.name = 'string-2';
else
token.name = 'meta';
}

} else if (aChar === '$') {
Expand Down
2 changes: 0 additions & 2 deletions mode/smartymixed/smartymixed.js
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,6 @@ CodeMirror.defineMode("smartymixed", function(config) {
return htmlMixedMode.indent(state.htmlMixedState, textAfter);
},

electricChars: "/{}:",

innerMode: function(state) {
return {
state: state.localState || state.htmlMixedState,
Expand Down
9 changes: 5 additions & 4 deletions mode/sql/sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,10 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {

indent: function(state, textAfter) {
var cx = state.context;
if (!cx) return CodeMirror.Pass;
if (cx.align) return cx.col + (textAfter.charAt(0) == cx.type ? 0 : 1);
else return cx.indent + config.indentUnit;
if (!cx) return 0;
var closing = textAfter.charAt(0) == cx.type;
if (cx.align) return cx.col + (closing ? 0 : 1);
else return cx.indent + (closing ? 0 : config.indentUnit);
},

blockCommentStart: "/*",
Expand Down Expand Up @@ -326,7 +327,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
name: "sql",
client: set("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"),
keywords: set("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"),
builtin: set("bfile blob character clob dec float int integer mlslabel natural naturaln nchar nclob number numeric nvarchar2 real rowtype signtype smallint string varchar varchar2 abs acos add_months ascii asin atan atan2 average bfilename ceil chartorowid chr concat convert cos cosh count decode deref dual dump dup_val_on_index empty error exp false floor found glb greatest hextoraw initcap instr instrb isopen last_day least lenght lenghtb ln lower lpad ltrim lub make_ref max min mod months_between new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null nvl others power rawtohex reftohex round rowcount rowidtochar rpad rtrim sign sin sinh soundex sqlcode sqlerrm sqrt stddev substr substrb sum sysdate tan tanh to_char to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid upper user userenv variance vsize"),
builtin: set("abs acos add_months ascii asin atan atan2 average bfile bfilename bit blob ceil character chartorowid chr clob concat convert cos cosh count dec decode deref dual dump dup_val_on_index empty error exp false float floor found glb greatest hextoraw initcap instr instrb int integer isopen last_day least lenght lenghtb ln lower lpad ltrim lub make_ref max min mlslabel mod months_between natural naturaln nchar nclob new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null number numeric nvarchar2 nvl others power rawtohex real reftohex round rowcount rowidtochar rowtype rpad rtrim sign signtype sin sinh smallint soundex sqlcode sqlerrm sqrt stddev string substr substrb sum sysdate tan tanh to_char text to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid upper user userenv varchar varchar2 variance varying vsize xml"),
operatorChars: /^[*+\-%<>!=~]/,
dateSQL: set("date time timestamp"),
support: set("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber")
Expand Down
187 changes: 87 additions & 100 deletions mode/xml/xml.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
var alignCDATA = parserConfig.alignCDATA;

// Return variables for tokenizers
var tagName, type;
var tagName, type, setStyle;

function inText(stream, state) {
function chain(parser) {
Expand Down Expand Up @@ -110,6 +110,8 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
return null;
} else if (ch == "<") {
state.tokenize = inText;
state.state = baseState;
state.tagName = state.tagStart = null;
var next = state.tokenize(stream, state);
return next ? next + " error" : "error";
} else if (/[\'\"]/.test(ch)) {
Expand Down Expand Up @@ -169,139 +171,124 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
};
}

var curState, curStream, setStyle;
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
function Context(state, tagName, startOfLine) {
this.prev = state.context;
this.tagName = tagName;
this.indent = state.indented;
this.startOfLine = startOfLine;
if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
this.noIndent = true;
}
function cont() {
pass.apply(null, arguments);
return true;
function popContext(state) {
if (state.context) state.context = state.context.prev;
}

function pushContext(tagName, startOfLine) {
var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
curState.context = {
prev: curState.context,
tagName: tagName,
indent: curState.indented,
startOfLine: startOfLine,
noIndent: noIndent
};
}
function popContext() {
if (curState.context) curState.context = curState.context.prev;
function maybePopContext(state, nextTagName) {
var parentTagName;
while (true) {
if (!state.context) {
return;
}
parentTagName = state.context.tagName.toLowerCase();
if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
!Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext(state);
}
}

function element(type) {
function baseState(type, stream, state) {
if (type == "openTag") {
curState.tagName = tagName;
curState.tagStart = curStream.column();
return cont(attributes, endtag(curState.startOfLine));
state.tagName = tagName;
state.tagStart = stream.column();
return attrState;
} else if (type == "closeTag") {
var err = false;
if (curState.context) {
if (curState.context.tagName != tagName) {
if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
popContext();
}
err = !curState.context || curState.context.tagName != tagName;
if (state.context) {
if (state.context.tagName != tagName) {
if (Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName.toLowerCase()))
popContext(state);
err = !state.context || state.context.tagName != tagName;
}
} else {
err = true;
}
if (err) setStyle = "error";
return cont(endclosetag(err));
return err ? closeStateErr : closeState;
} else {
return baseState;
}
return cont();
}
function endtag(startOfLine) {
return function(type) {
var tagName = curState.tagName;
curState.tagName = curState.tagStart = null;
if (type == "selfcloseTag" ||
(type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) {
maybePopContext(tagName.toLowerCase());
return cont();
}
if (type == "endTag") {
maybePopContext(tagName.toLowerCase());
pushContext(tagName, startOfLine);
return cont();
}
return cont();
};
}
function endclosetag(err) {
return function(type) {
if (err) setStyle = "error";
if (type == "endTag") { popContext(); return cont(); }
function closeState(type, _stream, state) {
if (type != "endTag") {
setStyle = "error";
return cont(arguments.callee);
};
}
function maybePopContext(nextTagName) {
var parentTagName;
while (true) {
if (!curState.context) {
return;
}
parentTagName = curState.context.tagName.toLowerCase();
if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
!Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext();
return closeState;
}
popContext(state);
return baseState;
}
function closeStateErr(type, stream, state) {
setStyle = "error";
return closeState(type, stream, state);
}

function attributes(type) {
if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
if (type == "endTag" || type == "selfcloseTag") return pass();
function attrState(type, _stream, state) {
if (type == "word") {
setStyle = "attribute";
return attrEqState;
} else if (type == "endTag" || type == "selfcloseTag") {
var tagName = state.tagName, tagStart = state.tagStart;
state.tagName = state.tagStart = null;
if (type == "selfcloseTag" ||
Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase())) {
maybePopContext(state, tagName.toLowerCase());
} else {
maybePopContext(state, tagName.toLowerCase());
state.context = new Context(state, tagName, tagStart == state.indented);
}
return baseState;
}
setStyle = "error";
return cont(attributes);
return attrState;
}
function attribute(type) {
if (type == "equals") return cont(attvalue, attributes);
function attrEqState(type, stream, state) {
if (type == "equals") return attrValueState;
if (!Kludges.allowMissing) setStyle = "error";
else if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
return attrState(type, stream, state);
}
function attvalue(type) {
if (type == "string") return cont(attvaluemaybe);
if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
function attrValueState(type, stream, state) {
if (type == "string") return attrContinuedState;
if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;}
setStyle = "error";
return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
return attrState(type, stream, state);
}
function attvaluemaybe(type) {
if (type == "string") return cont(attvaluemaybe);
else return pass();
function attrContinuedState(type, stream, state) {
if (type == "string") return attrContinuedState;
return attrState(type, stream, state);
}

return {
startState: function() {
return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, tagStart: null, context: null};
return {tokenize: inText,
state: baseState,
indented: 0,
tagName: null, tagStart: null,
context: null};
},

token: function(stream, state) {
if (!state.tagName && stream.sol()) {
state.startOfLine = true;
if (!state.tagName && stream.sol())
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;

setStyle = type = tagName = null;
if (stream.eatSpace()) return null;
tagName = type = null;
var style = state.tokenize(stream, state);
state.type = type;
if ((style || type) && style != "comment") {
curState = state; curStream = stream;
while (true) {
var comb = state.cc.pop() || element;
if (comb(type || style)) break;
}
setStyle = null;
state.state = state.state(type || style, stream, state);
if (setStyle)
style = setStyle == "error" ? style + " error" : setStyle;
}
state.startOfLine = false;
if (setStyle)
style = setStyle == "error" ? style + " error" : setStyle;
return style;
},

Expand All @@ -311,8 +298,8 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
if (state.tokenize.isInAttribute) {
return state.stringStartCol + 1;
}
if ((state.tokenize != inTag && state.tokenize != inText) ||
context && context.noIndent)
if (context && context.noIndent) return CodeMirror.Pass;
if (state.tokenize != inTag && state.tokenize != inText)
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
// Indent the starts of attribute names.
if (state.tagName) {
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
{
"name": "codemirror",
"version":"3.20.0",
"version":"3.21.0",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
"url": "http://codemirror.net/LICENSE"}],
"directories": {"lib": "./lib"},
"scripts": {"test": "node ./test/run.js"},
"devDependencies": {"node-static": "0.6.0"},
"devDependencies": {"node-static": "0.6.0",
"phantomjs": "1.9.2-5"},
"bugs": "http://github.com/marijnh/CodeMirror/issues",
"keywords": ["JavaScript", "CodeMirror", "Editor"],
"homepage": "http://codemirror.net",
"maintainers":[{"name": "Marijn Haverbeke",
"email": "marijnh@gmail.com",
"web": "http://marijnhaverbeke.nl"}],
"repository": {"type": "git",
"url": "http://marijnhaverbeke.nl/git/codemirror"}
"url": "https://github.com/marijnh/CodeMirror.git"}
}
12 changes: 12 additions & 0 deletions test/comment_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,16 @@ namespace = "comment_";
cm.setCursor(1);
cm.execCommand("toggleComment");
}, "a;\n\nb;", "a;\n// \nb;");

test("dontMessWithStrings", "javascript", function(cm) {
cm.execCommand("toggleComment");
}, "console.log(\"/*string*/\");", "// console.log(\"/*string*/\");");

test("dontMessWithStrings2", "javascript", function(cm) {
cm.execCommand("toggleComment");
}, "console.log(\"// string\");", "// console.log(\"// string\");");

test("dontMessWithStrings3", "javascript", function(cm) {
cm.execCommand("toggleComment");
}, "// console.log(\"// string\");", "console.log(\"// string\");");
})();
2 changes: 1 addition & 1 deletion test/doc_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
run.apply(null, editors);
successful = true;
} finally {
if ((debug && !successful) || verbose) {
if (!successful || verbose) {
place.style.visibility = "visible";
} else {
for (var i = 0; i < editors.length; ++i)
Expand Down
54 changes: 23 additions & 31 deletions test/driver.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var tests = [], debug = null, debugUsed = new Array(), allNames = [];
var tests = [], filters = [], allNames = [];

function Failure(why) {this.message = why;}
Failure.prototype.toString = function() { return this.message; };
Expand Down Expand Up @@ -32,7 +32,7 @@ function testCM(name, run, opts, expectedFail) {
run(cm);
successful = true;
} finally {
if ((debug && !successful) || verbose) {
if (!successful || verbose) {
place.style.visibility = "visible";
} else {
place.removeChild(cm.getWrapperElement());
Expand All @@ -42,39 +42,23 @@ function testCM(name, run, opts, expectedFail) {
}

function runTests(callback) {
if (debug) {
if (indexOf(debug, "verbose") === 0) {
verbose = true;
debug.splice(0, 1);
}
if (debug.length < 1) {
debug = null;
}
}
var totalTime = 0;
function step(i) {
if (i === tests.length){
running = false;
return callback("done");
}
}
var test = tests[i], expFail = test.expectedFail, startTime = +new Date;
if (debug !== null) {
var debugIndex = indexOf(debug, test.name);
if (debugIndex !== -1) {
// Remove from array for reporting incorrect tests later
debug.splice(debugIndex, 1);
} else {
var wildcardName = test.name.split("_")[0] + "_*";
debugIndex = indexOf(debug, wildcardName);
if (debugIndex !== -1) {
// Remove from array for reporting incorrect tests later
debug.splice(debugIndex, 1);
debugUsed.push(wildcardName);
} else {
debugIndex = indexOf(debugUsed, wildcardName);
if (debugIndex == -1) return step(i + 1);
if (filters.length) {
for (var j = 0; j < filters.length; j++) {
if (test.name.match(filters[j])) {
break;
}
}
if (j == filters.length) {
callback("skipped", test.name, message);
return step(i + 1);
}
}
var threw = false;
try {
Expand Down Expand Up @@ -127,13 +111,21 @@ function is(a, msg) {
}

function countTests() {
if (!debug) return tests.length;
if (!filters.length) return tests.length;
var sum = 0;
for (var i = 0; i < tests.length; ++i) {
var name = tests[i].name;
if (indexOf(debug, name) != -1 ||
indexOf(debug, name.split("_")[0] + "_*") != -1)
++sum;
for (var j = 0; j < filters.length; j++) {
if (name.match(filters[j])) {
++sum;
break;
}
}
}
return sum;
}

function parseTestFilter(s) {
if (/_\*$/.test(s)) return new RegExp("^" + s.slice(0, s.length - 2), "i");
else return new RegExp(s, "i");
}
3 changes: 3 additions & 0 deletions test/emacs_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@
sim("transposeExpr", "do foo[bar] dah",
Pos(0, 6), "Ctrl-Alt-T", txt("do [bar]foo dah"));

sim("clearMark", "abcde", Pos(0, 2), "Ctrl-Space", "Ctrl-F", "Ctrl-F",
"Ctrl-G", "Ctrl-W", txt("abcde"));

testCM("save", function(cm) {
var saved = false;
CodeMirror.commands.save = function(cm) { saved = cm.getValue(); };
Expand Down
41 changes: 25 additions & 16 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,13 @@ <h2>Test Suite</h2>
<script src="test.js"></script>
<script src="doc_test.js"></script>
<script src="comment_test.js"></script>
<script src="search_test.js"></script>
<script src="mode_test.js"></script>
<script src="../mode/javascript/test.js"></script>
<script src="../mode/css/css.js"></script>
<script src="../mode/css/test.js"></script>
<script src="../mode/css/scss_test.js"></script>
<script src="../mode/css/less_test.js"></script>
<script src="../mode/xml/xml.js"></script>
<script src="../mode/htmlmixed/htmlmixed.js"></script>
<script src="../mode/ruby/ruby.js"></script>
Expand Down Expand Up @@ -107,10 +109,11 @@ <h2>Test Suite</h2>
progressTotal = document.getElementById("progress_total").childNodes[0];
var count = 0,
failed = 0,
skipped = 0,
bad = "",
running = false, // Flag that states tests are running
quit = false, // Flag to quit tests ASAP
verbose = false; // Adds message for *every* test to output
quit = false, // Flag to quit tests ASAP
verbose = false; // Adds message for *every* test to output

function runHarness(){
if (running) {
Expand All @@ -119,19 +122,25 @@ <h2>Test Suite</h2>
setTimeout(function(){runHarness();}, 500);
return;
}
filters = [];
verbose = false;
if (window.location.hash.substr(1)){
debug = window.location.hash.substr(1).split(",");
} else {
debug = null;
var strings = window.location.hash.substr(1).split(",");
while (strings.length) {
var s = strings.shift();
if (s === "verbose")
verbose = true;
else
filters.push(parseTestFilter(decodeURIComponent(s)));;
}
}
quit = false;
running = true;
setStatus("Loading tests...");
count = 0;
failed = 0;
skipped = 0;
bad = "";
verbose = false;
debugUsed = Array();
totalTests = countTests();
progressTotal.nodeValue = " of " + totalTests;
progressRan.nodeValue = count;
Expand Down Expand Up @@ -164,12 +173,16 @@ <h2>Test Suite</h2>
}
function displayTest(type, name, customMessage) {
var message = "???";
if (type != "done") ++count;
if (type != "done" && type != "skipped") ++count;
progress.style.width = (count * (progress.parentNode.clientWidth - 2) / totalTests) + "px";
progressRan.nodeValue = count;
if (type == "ok") {
message = "Test '" + name + "' succeeded";
if (!verbose) customMessage = false;
} else if (type == "skipped") {
message = "Test '" + name + "' skipped";
++skipped;
if (!verbose) customMessage = false;
} else if (type == "expected") {
message = "Test '" + name + "' failed as expected";
if (!verbose) customMessage = false;
Expand All @@ -187,15 +200,11 @@ <h2>Test Suite</h2>
} else {
type += " ok";
message = "All passed";
if (skipped) {
message += " (" + skipped + " skipped)";
}
}
if (debug && debug.length) {
var bogusTests = totalTests - count;
message += " — " + bogusTests + " nonexistent test" +
(bogusTests > 1 ? "s" : "") + " requested by location.hash: " +
"`" + debug.join("`, `") + "`";
} else {
progressTotal.nodeValue = '';
}
progressTotal.nodeValue = '';
customMessage = true; // Hack to avoid adding to output
}
if (verbose && !customMessage) customMessage = message;
Expand Down
108 changes: 38 additions & 70 deletions test/mode_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,18 @@
return {tokens: tokens, plain: plain};
}

test.indentation = function(name, mode, tokens, modeName) {
var data = parseTokens(tokens);
return test((modeName || mode.name) + "_indent_" + name, function() {
return compare(data.plain, data.tokens, mode, true);
});
};

test.mode = function(name, mode, tokens, modeName) {
var data = parseTokens(tokens);
return test((modeName || mode.name) + "_" + name, function() {
return compare(data.plain, data.tokens, mode);
});
};

function compare(text, expected, mode, compareIndentation) {
function esc(str) {
return str.replace('&', '&amp;').replace('<', '&lt;');
}

function compare(text, expected, mode) {

var expectedOutput = [];
for (var i = 0; i < expected.length; i += 2) {
Expand All @@ -82,61 +79,49 @@
expectedOutput.push(sty, expected[i + 1]);
}

var observedOutput = highlight(text, mode, compareIndentation);

var pass, passStyle = "";
pass = highlightOutputsEqual(expectedOutput, observedOutput);
passStyle = pass ? 'mt-pass' : 'mt-fail';
var observedOutput = highlight(text, mode);

var s = '';
if (pass) {
s += '<div class="mt-test ' + passStyle + '">';
s += '<pre>' + text.replace('&', '&amp;').replace('<', '&lt;') + '</pre>';
s += '<div class="cm-s-default">';
s += prettyPrintOutputTable(observedOutput);
s += '</div>';
s += '</div>';
return s;
} else {
s += '<div class="mt-test ' + passStyle + '">';
s += '<pre>' + text.replace('&', '&amp;').replace('<', '&lt;') + '</pre>';
var s = "";
var diff = highlightOutputsDifferent(expectedOutput, observedOutput);
if (diff != null) {
s += '<div class="mt-test mt-fail">';
s += '<pre>' + esc(text) + '</pre>';
s += '<div class="cm-s-default">';
s += 'expected:';
s += prettyPrintOutputTable(expectedOutput);
s += prettyPrintOutputTable(expectedOutput, diff);
s += 'observed:';
s += prettyPrintOutputTable(observedOutput);
s += prettyPrintOutputTable(observedOutput, diff);
s += '</div>';
s += '</div>';
throw s;
}
if (observedOutput.indentFailures) {
for (var i = 0; i < observedOutput.indentFailures.length; i++)
s += "<div class='mt-test mt-fail'>" + esc(observedOutput.indentFailures[i]) + "</div>";
}
if (s) throw new Failure(s);
}

/**
* Emulation of CodeMirror's internal highlight routine for testing. Multi-line
* input is supported.
*
* @param string to highlight
*
* @param mode the mode that will do the actual highlighting
*
* @return array of [style, token] pairs
*/
function highlight(string, mode, compareIndentation) {
function highlight(string, mode) {
var state = mode.startState()

var lines = string.replace(/\r\n/g,'\n').split('\n');
var st = [], pos = 0;
for (var i = 0; i < lines.length; ++i) {
var line = lines[i], newLine = true;
if (mode.indent) {
var ws = line.match(/^\s*/)[0];
var indent = mode.indent(state, line.slice(ws.length));
if (indent != CodeMirror.Pass && indent != ws.length)
(st.indentFailures || (st.indentFailures = [])).push(
"Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")");
}
var stream = new CodeMirror.StringStream(line);
if (line == "" && mode.blankLine) mode.blankLine(state);
/* Start copied code from CodeMirror.highlight */
while (!stream.eol()) {
var compare = mode.token(stream, state), substr = stream.current();
if(compareIndentation) compare = mode.indent(state) || null;
else if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' ');

stream.start = stream.pos;
var compare = mode.token(stream, state), substr = stream.current();
if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' ');
stream.start = stream.pos;
if (pos && st[pos-2] == compare && !newLine) {
st[pos-1] += substr;
} else if (substr) {
Expand All @@ -154,39 +139,22 @@
return st;
}

/**
* Compare two arrays of output from highlight.
*
* @param o1 array of [style, token] pairs
*
* @param o2 array of [style, token] pairs
*
* @return boolean; true iff outputs equal
*/
function highlightOutputsEqual(o1, o2) {
if (o1.length != o2.length) return false;
for (var i = 0; i < o1.length; ++i)
if (o1[i] != o2[i]) return false;
return true;
function highlightOutputsDifferent(o1, o2) {
var minLen = Math.min(o1.length, o2.length);
for (var i = 0; i < minLen; ++i)
if (o1[i] != o2[i]) return i >> 1;
if (o1.length > minLen || o2.length > minLen) return minLen;
}

/**
* Print tokens and corresponding styles in a table. Spaces in the token are
* replaced with 'interpunct' dots (&middot;).
*
* @param output array of [style, token] pairs
*
* @return html string
*/
function prettyPrintOutputTable(output) {
function prettyPrintOutputTable(output, diffAt) {
var s = '<table class="mt-output">';
s += '<tr>';
for (var i = 0; i < output.length; i += 2) {
var style = output[i], val = output[i+1];
s +=
'<td class="mt-token">' +
'<span class="cm-' + String(style).replace(/ +/g, " cm-") + '">' +
val.replace(/ /g,'\xb7').replace('&', '&amp;').replace('<', '&lt;') +
'<td class="mt-token"' + (i == diffAt * 2 ? " style='background: pink'" : "") + '>' +
'<span class="cm-' + esc(String(style)) + '">' +
esc(val.replace(/ /g,'\xb7')) +
'</span>' +
'</td>';
}
Expand Down
34 changes: 19 additions & 15 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,30 @@ lint.checkDir("keymap");

var ok = lint.success();

var files = new (require('node-static').Server)('.');
var files = new (require('node-static').Server)();

var server = require('http').createServer(function (req, res) {
req.addListener('end', function () {
files.serve(req, res);
});
files.serve(req, res, function (err/*, result */) {
if (err) {
console.error(err);
process.exit(1);
}
});
}).resume();
}).addListener('error', function (err) {
throw err;
}).listen(3000, function () {
var child_process = require('child_process');
child_process.exec("which phantomjs", function (err) {
if (err) {
console.error("PhantomJS is not installed. Download from http://phantomjs.org");
process.exit(1);
}
var cmd = 'phantomjs test/phantom_driver.js';
child_process.exec(cmd, function (err, stdout) {
server.close();
console.log(stdout);
process.exit(err || !ok ? 1 : 0);
});
var childProcess = require('child_process');
var phantomjs = require("phantomjs");
var childArgs = [
require("path").join(__dirname, 'phantom_driver.js')
];
childProcess.execFile(phantomjs.path, childArgs, function (err, stdout, stderr) {
server.close();
console.log(stdout);
if (err) console.error(err);
if (stderr) console.error(stderr);
process.exit(err || stderr || !ok ? 1 : 0);
});
});
Loading