File: editor.js

package info (click to toggle)
supercollider 1%3A3.11.2%2Brepack-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 71,152 kB
  • sloc: cpp: 387,846; lisp: 80,328; ansic: 76,515; sh: 22,779; python: 7,932; makefile: 2,333; perl: 1,123; javascript: 915; java: 677; xml: 582; yacc: 314; lex: 175; objc: 152; ruby: 136
file content (169 lines) | stat: -rw-r--r-- 6,533 bytes parent folder | download | duplicates (4)
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()