-
Notifications
You must be signed in to change notification settings - Fork 437
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3906 from ChrisBr/feature/dockerfile
[webui] Enable Dockerfile mode for code mirror
- Loading branch information
Showing
5 changed files
with
311 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
src/api/app/assets/javascripts/webui/application/cm2/mode/dockerfile.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
// Distributed under an MIT license: http://codemirror.net/LICENSE | ||
|
||
(function(mod) { | ||
if (typeof exports == "object" && typeof module == "object") // CommonJS | ||
mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); | ||
else if (typeof define == "function" && define.amd) // AMD | ||
define(["../../lib/codemirror", "../../addon/mode/simple"], mod); | ||
else // Plain browser env | ||
mod(CodeMirror); | ||
})(function(CodeMirror) { | ||
"use strict"; | ||
|
||
// Collect all Dockerfile directives | ||
var instructions = ["from", "maintainer", "run", "cmd", "expose", "env", | ||
"add", "copy", "entrypoint", "volume", "user", | ||
"workdir", "onbuild"], | ||
instructionRegex = "(" + instructions.join('|') + ")", | ||
instructionOnlyLine = new RegExp(instructionRegex + "\\s*$", "i"), | ||
instructionWithArguments = new RegExp(instructionRegex + "(\\s+)", "i"); | ||
|
||
CodeMirror.defineSimpleMode("dockerfile", { | ||
start: [ | ||
// Block comment: This is a line starting with a comment | ||
{ | ||
regex: /#.*$/, | ||
token: "comment" | ||
}, | ||
// Highlight an instruction without any arguments (for convenience) | ||
{ | ||
regex: instructionOnlyLine, | ||
token: "variable-2" | ||
}, | ||
// Highlight an instruction followed by arguments | ||
{ | ||
regex: instructionWithArguments, | ||
token: ["variable-2", null], | ||
next: "arguments" | ||
}, | ||
{ | ||
regex: /./, | ||
token: null | ||
} | ||
], | ||
arguments: [ | ||
{ | ||
// Line comment without instruction arguments is an error | ||
regex: /#.*$/, | ||
token: "error", | ||
next: "start" | ||
}, | ||
{ | ||
regex: /[^#]+\\$/, | ||
token: null | ||
}, | ||
{ | ||
// Match everything except for the inline comment | ||
regex: /[^#]+/, | ||
token: null, | ||
next: "start" | ||
}, | ||
{ | ||
regex: /$/, | ||
token: null, | ||
next: "start" | ||
}, | ||
// Fail safe return to start | ||
{ | ||
token: null, | ||
next: "start" | ||
} | ||
], | ||
meta: { | ||
lineComment: "#" | ||
} | ||
}); | ||
|
||
CodeMirror.defineMIME("text/x-dockerfile", "dockerfile"); | ||
}); |
216 changes: 216 additions & 0 deletions
216
src/api/app/assets/javascripts/webui/application/cm2/mode/simple.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
// CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
// Distributed under an MIT license: http://codemirror.net/LICENSE | ||
|
||
(function(mod) { | ||
if (typeof exports == "object" && typeof module == "object") // CommonJS | ||
mod(require("../../lib/codemirror")); | ||
else if (typeof define == "function" && define.amd) // AMD | ||
define(["../../lib/codemirror"], mod); | ||
else // Plain browser env | ||
mod(CodeMirror); | ||
})(function(CodeMirror) { | ||
"use strict"; | ||
|
||
CodeMirror.defineSimpleMode = function(name, states) { | ||
CodeMirror.defineMode(name, function(config) { | ||
return CodeMirror.simpleMode(config, states); | ||
}); | ||
}; | ||
|
||
CodeMirror.simpleMode = function(config, states) { | ||
ensureState(states, "start"); | ||
var states_ = {}, meta = states.meta || {}, hasIndentation = false; | ||
for (var state in states) if (state != meta && states.hasOwnProperty(state)) { | ||
var list = states_[state] = [], orig = states[state]; | ||
for (var i = 0; i < orig.length; i++) { | ||
var data = orig[i]; | ||
list.push(new Rule(data, states)); | ||
if (data.indent || data.dedent) hasIndentation = true; | ||
} | ||
} | ||
var mode = { | ||
startState: function() { | ||
return {state: "start", pending: null, | ||
local: null, localState: null, | ||
indent: hasIndentation ? [] : null}; | ||
}, | ||
copyState: function(state) { | ||
var s = {state: state.state, pending: state.pending, | ||
local: state.local, localState: null, | ||
indent: state.indent && state.indent.slice(0)}; | ||
if (state.localState) | ||
s.localState = CodeMirror.copyState(state.local.mode, state.localState); | ||
if (state.stack) | ||
s.stack = state.stack.slice(0); | ||
for (var pers = state.persistentStates; pers; pers = pers.next) | ||
s.persistentStates = {mode: pers.mode, | ||
spec: pers.spec, | ||
state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state), | ||
next: s.persistentStates}; | ||
return s; | ||
}, | ||
token: tokenFunction(states_, config), | ||
innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; }, | ||
indent: indentFunction(states_, meta) | ||
}; | ||
if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop)) | ||
mode[prop] = meta[prop]; | ||
return mode; | ||
}; | ||
|
||
function ensureState(states, name) { | ||
if (!states.hasOwnProperty(name)) | ||
throw new Error("Undefined state " + name + " in simple mode"); | ||
} | ||
|
||
function toRegex(val, caret) { | ||
if (!val) return /(?:)/; | ||
var flags = ""; | ||
if (val instanceof RegExp) { | ||
if (val.ignoreCase) flags = "i"; | ||
val = val.source; | ||
} else { | ||
val = String(val); | ||
} | ||
return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags); | ||
} | ||
|
||
function asToken(val) { | ||
if (!val) return null; | ||
if (val.apply) return val | ||
if (typeof val == "string") return val.replace(/\./g, " "); | ||
var result = []; | ||
for (var i = 0; i < val.length; i++) | ||
result.push(val[i] && val[i].replace(/\./g, " ")); | ||
return result; | ||
} | ||
|
||
function Rule(data, states) { | ||
if (data.next || data.push) ensureState(states, data.next || data.push); | ||
this.regex = toRegex(data.regex); | ||
this.token = asToken(data.token); | ||
this.data = data; | ||
} | ||
|
||
function tokenFunction(states, config) { | ||
return function(stream, state) { | ||
if (state.pending) { | ||
var pend = state.pending.shift(); | ||
if (state.pending.length == 0) state.pending = null; | ||
stream.pos += pend.text.length; | ||
return pend.token; | ||
} | ||
|
||
if (state.local) { | ||
if (state.local.end && stream.match(state.local.end)) { | ||
var tok = state.local.endToken || null; | ||
state.local = state.localState = null; | ||
return tok; | ||
} else { | ||
var tok = state.local.mode.token(stream, state.localState), m; | ||
if (state.local.endScan && (m = state.local.endScan.exec(stream.current()))) | ||
stream.pos = stream.start + m.index; | ||
return tok; | ||
} | ||
} | ||
|
||
var curState = states[state.state]; | ||
for (var i = 0; i < curState.length; i++) { | ||
var rule = curState[i]; | ||
var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex); | ||
if (matches) { | ||
if (rule.data.next) { | ||
state.state = rule.data.next; | ||
} else if (rule.data.push) { | ||
(state.stack || (state.stack = [])).push(state.state); | ||
state.state = rule.data.push; | ||
} else if (rule.data.pop && state.stack && state.stack.length) { | ||
state.state = state.stack.pop(); | ||
} | ||
|
||
if (rule.data.mode) | ||
enterLocalMode(config, state, rule.data.mode, rule.token); | ||
if (rule.data.indent) | ||
state.indent.push(stream.indentation() + config.indentUnit); | ||
if (rule.data.dedent) | ||
state.indent.pop(); | ||
var token = rule.token | ||
if (token && token.apply) token = token(matches) | ||
if (matches.length > 2 && rule.token && typeof rule.token != "string") { | ||
state.pending = []; | ||
for (var j = 2; j < matches.length; j++) | ||
if (matches[j]) | ||
state.pending.push({text: matches[j], token: rule.token[j - 1]}); | ||
stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0)); | ||
return token[0]; | ||
} else if (token && token.join) { | ||
return token[0]; | ||
} else { | ||
return token; | ||
} | ||
} | ||
} | ||
stream.next(); | ||
return null; | ||
}; | ||
} | ||
|
||
function cmp(a, b) { | ||
if (a === b) return true; | ||
if (!a || typeof a != "object" || !b || typeof b != "object") return false; | ||
var props = 0; | ||
for (var prop in a) if (a.hasOwnProperty(prop)) { | ||
if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false; | ||
props++; | ||
} | ||
for (var prop in b) if (b.hasOwnProperty(prop)) props--; | ||
return props == 0; | ||
} | ||
|
||
function enterLocalMode(config, state, spec, token) { | ||
var pers; | ||
if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next) | ||
if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p; | ||
var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec); | ||
var lState = pers ? pers.state : CodeMirror.startState(mode); | ||
if (spec.persistent && !pers) | ||
state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates}; | ||
|
||
state.localState = lState; | ||
state.local = {mode: mode, | ||
end: spec.end && toRegex(spec.end), | ||
endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false), | ||
endToken: token && token.join ? token[token.length - 1] : token}; | ||
} | ||
|
||
function indexOf(val, arr) { | ||
for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true; | ||
} | ||
|
||
function indentFunction(states, meta) { | ||
return function(state, textAfter, line) { | ||
if (state.local && state.local.mode.indent) | ||
return state.local.mode.indent(state.localState, textAfter, line); | ||
if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1) | ||
return CodeMirror.Pass; | ||
|
||
var pos = state.indent.length - 1, rules = states[state.state]; | ||
scan: for (;;) { | ||
for (var i = 0; i < rules.length; i++) { | ||
var rule = rules[i]; | ||
if (rule.data.dedent && rule.data.dedentIfLineStart !== false) { | ||
var m = rule.regex.exec(textAfter); | ||
if (m && m[0]) { | ||
pos--; | ||
if (rule.next || rule.push) rules = states[rule.next || rule.push]; | ||
textAfter = textAfter.slice(m[0].length); | ||
continue scan; | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
return pos < 0 ? 0 : state.indent[pos]; | ||
}; | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters