1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
const init = () => {
/* based on editors/sc-ide/core/sc_lexer.cpp */
CodeMirror.defineSimpleMode('scd', {
start: [
{ regex: /^\s+/, token: 'whitespace' },
{ regex: /^(?:arg|classvar|const|super|this|var)\b/, token: 'keyword' },
{ regex: /^(?:false|inf|nil|true|thisFunction|thisFunctionDef|thisMethod|thisProcess|thisThread|currentEnvironment|topEnvironment)\b/, token: 'built-in' },
{ regex: /^\b\d+r[0-9a-zA-Z]*(\.[0-9A-Z]*)?/, token: 'number radix-float' },
{ regex: /^\b\d+(s+|b+|[sb]\d+)\b/, token: 'number scale-degree' },
{ regex: /^\b((\d+(\.\d+)?([eE][-+]?\d+)?(pi)?)|pi)\b/, token: 'number float' },
{ regex: /^\b0x(\d|[a-f]|[A-F])+/, token: 'number hex-int' },
{ regex: /^\b[A-Za-z_]\w*\:/, token: 'symbol symbol-arg' },
{ regex: /^[a-z]\w*/, token: 'text name' },
{ regex: /^\b[A-Z]\w*/, token: 'class' },
{ regex: /^\b_\w+/, token: 'primitive' },
{ regex: /^\\\w*/, token: 'symbol' },
{ regex: /'(?:[^\\]|\\.)*?(?:'|$)/, token: 'symbol' },
{ regex: /^\$\\?./, token: 'char' },
{ regex: /^~\w+/, token: 'env-var' },
{ regex: /^\/\/[^\r\n]*/, token: 'comment single-line-comment' },
{ regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: 'string' },
{ regex: /^[-.,;#()\[\]{}]/, token: 'text punctuation' },
{ regex: /\/\*/, push: 'comment', token: 'comment multi-line-comment' },
{ regex: /^[+\-*/&\|\^%<>=!?]+/, token: 'text operator' },
],
comment: [
{ regex: /\*\//, pop: true, token: 'comment multi-line-comment' },
{ regex: /./, token: 'comment multi-line-comment' }
]
})
let textareas = Array.from(document.querySelectorAll('textarea'))
textareas.forEach(textarea => {
let code = textarea.value
textarea.editor = CodeMirror.fromTextArea(textarea, {
mode: 'scd',
value: code,
lineWrapping: true,
viewportMargin: Infinity,
extraKeys: {
// noop: prevent both codemirror and the browser to handle Shift-Enter
'Shift-Enter': ()=>{},
// prevent only codemirror to handle Ctrl+D
'Ctrl-D': false
}
})
textarea.editor.on('dblclick', editor => {
let cursor = editor.getCursor()
let parenMatch = editor.getLine(cursor.line)
.slice(cursor.ch-1,cursor.ch).match(/[()]/)
if (parenMatch) {
editor.undoSelection()
selectRegion({ flash: false })
}
})
textarea.editor.on('blur', editor => {
editor.setSelection(editor.getCursor(), null, { scroll: false })
})
})
}
/* returns the code selection, line or region */
const selectRegion = (options = { flash: true }) => {
let range = window.getSelection().getRangeAt(0)
let textarea = range.startContainer.parentNode.previousSibling
if (!textarea) return
let editor = textarea.editor
if (editor.somethingSelected())
return selectLine(options)
const findLeftParen = cursor => {
let cursorLeft = editor.findPosH(cursor, -1, 'char')
let token = editor.getTokenTypeAt(cursor) || ''
if (cursorLeft.hitSide)
return cursorLeft
let ch = editor.getLine(cursorLeft.line)
.slice(cursorLeft.ch, cursorLeft.ch+1)
if (token.match(/^(comment|string|symbol|char)/))
return findLeftParen(cursorLeft)
if (ch === ')')
return findLeftParen(findLeftParen(cursorLeft))
if (ch === '(')
return cursorLeft
return findLeftParen(cursorLeft)
}
const findRightParen = cursor => {
let cursorRight = editor.findPosH(cursor, 1, 'char')
let token = editor.getTokenTypeAt(cursor) || ''
if (cursorRight.hitSide)
return cursorRight
let ch = editor.getLine(cursorRight.line)
.slice(cursorRight.ch-1, cursorRight.ch)
if (ch === '(')
return findRightParen(findRightParen(cursorRight))
if (ch === ')')
return cursorRight
if (token.match(/^(comment|string|symbol|char)/))
return findRightParen(cursorRight)
return findRightParen(cursorRight)
}
let cursor = editor.getCursor()
if (editor.getLine(cursor.line).slice(cursor.ch,cursor.ch+1) === '(')
editor.setCursor(Object.assign(cursor, { ch: cursor.ch+1 }))
if (editor.getLine(cursor.line).slice(cursor.ch-1,cursor.ch) === ')')
editor.setCursor(Object.assign(cursor, { ch: cursor.ch-1 }))
let parenPairs = []
let leftCursor = findLeftParen(cursor)
let rightCursor = findRightParen(cursor)
while (!leftCursor.hitSide || !rightCursor.hitSide) {
parenPairs.push([leftCursor, rightCursor])
leftCursor = findLeftParen(leftCursor)
rightCursor = findRightParen(rightCursor)
}
/* no parens found */
if (parenPairs.length === 0)
return selectLine(options)
let pair = parenPairs.pop()
leftCursor = pair[0]
rightCursor = pair[1]
/* parens are inline */
if (leftCursor.ch > 0)
return selectLine(options)
/* parens are a region */
if (options.flash === false) {
editor.addSelection(leftCursor, rightCursor)
return editor.getSelection()
} else {
let marker = editor.markText(leftCursor, rightCursor, { className: 'text-flash' })
setTimeout(() => marker.clear(), 300)
return editor.getRange(leftCursor, rightCursor)
}
}
// Returns the code selection or line
const selectLine = (options = { flash: true }) => {
let range = window.getSelection().getRangeAt(0)
let textarea = range.startContainer.parentNode.previousSibling
if (!textarea) return
let editor = textarea.editor
let cursor = editor.getCursor()
if (editor.somethingSelected()) {
from = editor.getCursor('start')
to = editor.getCursor('end')
} else {
from = { line: cursor.line, ch: 0 }
to = { line: cursor.line, ch: editor.getLine(cursor.line).length }
}
if (!options.flash)
return editor.getRange(from, to)
let marker = editor.markText(from, to, { className: 'text-flash' })
setTimeout(() => marker.clear(), 300)
return editor.getRange(from, to)
}
init()
|