├── .gitignore ├── tests └── codemirrorModeElixir.test.js ├── src ├── main.js ├── index.html ├── components │ ├── SiteFooter.vue │ ├── GithubIcon.vue │ ├── SiteHeader.vue │ └── Home.vue ├── code.js └── App.vue ├── .editorconfig ├── CHANGELOG.md ├── LICENSE ├── README.md ├── webpack.config.js ├── package.json └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | site 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /tests/codemirrorModeElixir.test.js: -------------------------------------------------------------------------------- 1 | test('TODO', () => { 2 | 3 | }) 4 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | /* eslint-disable no-new */ 5 | new Vue(App).$mount('#app') 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | codemirror-mode-elixir 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes to codemirror-mode-elixir 2 | 3 | ## v1.1.2 4 | 5 | * [#3: Make codemirror a peerDependency](https://github.com/ianwalter/codemirror-mode-elixir/pull/3) 6 | 7 | ## v1.1.1 8 | 9 | * [#2: Register MIME type (required for mode auto-loading)](https://github.com/ianwalter/codemirror-mode-elixir/pull/2) 10 | -------------------------------------------------------------------------------- /src/components/SiteFooter.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/components/GithubIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/components/SiteHeader.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2016 Marijn Haverbekem, Ian Walter, and others 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # codemirror-mode-elixir 2 | > A CodeMirror mode for the Elixir language 3 | 4 | ## Installation 5 | 6 | ```console 7 | npm install codemirror codemirror-mode-elixir --save 8 | ``` 9 | 10 | ## Usage 11 | 12 | 1. Include `codemirror-mode-elixir` into your project. 13 | 14 | ```html 15 | 16 | 17 | 18 | ``` 19 | 20 | or 21 | 22 | ```js 23 | // If you're using frontend build tools like Webpack and Babel, 24 | // you can simply import the module and register the mode: 25 | import CodeMirror from 'codemirror' 26 | import registerElixirMode from 'codemirror-mode-elixir' 27 | ``` 28 | 29 | 1. Set 'elixir' as the mode when creating the CodeMirror editor. 30 | 31 | ```js 32 | CodeMirror.fromTextArea(document.getElementById('code'), { mode: 'elixir' }) 33 | ``` 34 | 35 | ## License 36 | 37 | MIT - See [LICENSE][licenseUrl] 38 | 39 |   40 | 41 | Created by [Ian Walter](https://iankwalter.com) 42 | 43 |   44 | 45 | 46 | 47 | 48 | 49 | [licenseUrl]: https://github.com/ianwalter/codemirror-mode-elixir/blob/master/LICENSE 50 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 4 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 5 | const HtmlWebpackPlugin = require('html-webpack-plugin') 6 | 7 | const isProduction = process.env.NODE_ENV === 'production' 8 | const src = join(__dirname, 'src') 9 | 10 | module.exports = { 11 | mode: isProduction ? 'production' : 'development', 12 | entry: join(__dirname, 'src/main.js'), 13 | output: { 14 | path: join(__dirname, 'site'), 15 | filename: 'js/[name].bundle.js', 16 | chunkFilename: 'js/[id].[chunkhash].js' 17 | }, 18 | resolve: { 19 | extensions: ['.js', '.vue', '.json'], 20 | alias: { 21 | '@': src 22 | } 23 | }, 24 | module: { 25 | rules: [ 26 | { test: /\.js$/, include: src, loader: 'babel-loader' }, 27 | { test: /\.vue$/, include: src, loader: 'vue-loader' }, 28 | { 29 | test: /\.s?css$/, 30 | include: src, 31 | use: [ 32 | isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader', 33 | { 34 | loader: 'css-loader', 35 | options: { importLoaders: 1 } 36 | }, 37 | 'sass-loader' 38 | ] 39 | } 40 | ] 41 | }, 42 | plugins: [ 43 | new VueLoaderPlugin(), 44 | new HtmlWebpackPlugin({ template: './src/index.html' }), 45 | ...(isProduction ? [new MiniCssExtractPlugin()] : []) 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /src/code.js: -------------------------------------------------------------------------------- 1 | export const code = `# Some code examples borrowed from https://learnxinyminutes.com/docs/elixir/ 2 | 3 | # Module 4 | defmodule MyModule do 5 | @moduledoc """ 6 | This is a built-in attribute on a example module. 7 | """ 8 | 9 | # Struct 10 | defstruct name: nil, age: 0, height: 0 11 | 12 | # Function 13 | def function_one do 14 | {:ok, "one"} 15 | end 16 | 17 | # Pattern matching 18 | def function_two do 19 | {:ok, msg} = function_one 20 | IO.puts msg 21 | end 22 | 23 | # Try/Rescue block 24 | def function_three do 25 | try do 26 | raise "some error" 27 | rescue 28 | RuntimeError -> "rescued a runtime error" 29 | _error -> "this will rescue any error" 30 | end 31 | end 32 | 33 | # Pipes 34 | def function_four(start, finish) do 35 | Range.new(start, finish) 36 | |> Enum.map(fn x -> x * x end) 37 | |> Enum.filter(fn x -> rem(x, 2) == 0 end) 38 | end 39 | end 40 | ` 41 | 42 | export const htmlScript = ` 43 | 44 | ` 45 | 46 | export const jsImport = `// If you're using frontend build tools like Webpack and Babel, 47 | // you can simply import the module and register the mode: 48 | import CodeMirror from 'codemirror' 49 | import 'codemirror-mode-elixir'` 50 | 51 | export const create = `CodeMirror.fromTextArea(document.getElementById('code'), { mode: 'elixir' })` 52 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codemirror-mode-elixir", 3 | "version": "1.1.2", 4 | "description": "A CodeMirror mode for the Elixir language", 5 | "keywords": [ 6 | "codemirror", 7 | "elixir", 8 | "mode" 9 | ], 10 | "files": [ 11 | "dist" 12 | ], 13 | "main": "dist/codemirror-mode-elixir.js", 14 | "umd:main": "dist/codemirror-mode-elixir.umd.js", 15 | "module": "dist/codemirror-mode-elixir.m.js", 16 | "scripts": { 17 | "build": "microbundle build index.js --compress false", 18 | "watch": "microbundle watch index.js --compress false", 19 | "site": "webpack", 20 | "start": "webpack-dev-server", 21 | "test": "jest", 22 | "lint": "eslint --ext .js,.vue src index.js" 23 | }, 24 | "repository": { 25 | "url": "git@github.com:optick/codemirror-mode-elixir.git", 26 | "type": "git" 27 | }, 28 | "author": "Ian Walter ", 29 | "license": "MIT", 30 | "peerDependencies": { 31 | "codemirror": "^5.20.2" 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.1.2", 35 | "@babel/preset-env": "^7.1.5", 36 | "babel-core": "^7.0.0-bridge.0", 37 | "babel-jest": "^23.6.0", 38 | "babel-loader": "^8.0.4", 39 | "codemirror": "^5.41.0", 40 | "css-loader": "^0.25.0", 41 | "eslint": "^3.9.1", 42 | "eslint-config-standard": "^6.2.1", 43 | "eslint-friendly-formatter": "^2.0.5", 44 | "eslint-loader": "^1.5.0", 45 | "eslint-plugin-html": "^1.5.5", 46 | "eslint-plugin-promise": "^3.3.0", 47 | "eslint-plugin-standard": "^2.0.1", 48 | "html-webpack-plugin": "^3.2.0", 49 | "jest": "^23.6.0", 50 | "microbundle": "^0.7.0", 51 | "mini-css-extract-plugin": "^0.4.4", 52 | "node-sass": "^4.9.4", 53 | "np": "^3.0.4", 54 | "sass-loader": "^7.1.0", 55 | "vue": "^2.5.17", 56 | "vue-loader": "^15.4.2", 57 | "vue-style-loader": "^4.1.2", 58 | "vue-template-compiler": "^2.5.17", 59 | "webpack": "^4.25.1", 60 | "webpack-cli": "^3.1.2", 61 | "webpack-dev-server": "^3.1.10" 62 | }, 63 | "browserslist": [ 64 | "> 0.25%" 65 | ], 66 | "babel": { 67 | "presets": [ 68 | "@babel/preset-env" 69 | ], 70 | "env": { 71 | "test": { 72 | "presets": [ 73 | [ 74 | "@babel/preset-env", 75 | { 76 | "targets": { 77 | "node": "current" 78 | } 79 | } 80 | ] 81 | ] 82 | } 83 | } 84 | }, 85 | "eslintConfig": { 86 | "root": true, 87 | "extends": [ 88 | "standard" 89 | ] 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 131 | 132 | 200 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // CodeMirror Mode Elixir, copyright (c) by Marijn Haverbeke, Ian Walter, and 2 | // others. Distributed under an MIT license: http://codemirror.net/LICENSE. 3 | import CodeMirror from 'codemirror' 4 | 5 | CodeMirror.defineMode('elixir', config => { 6 | const wordObj = words => { 7 | let o = {} 8 | for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true 9 | return o 10 | } 11 | 12 | const keywords = wordObj([ 13 | 'alias', 'case', 'cond', 'def', 'defmodule', 'defp', 'defstruct', 14 | 'defprotocol', 'defimpl', 'defmacro', 'quote', 'unquote', 'receive', 'fn', 15 | 'do', 'else', 'else if', 'end', 'false', 'if', 'in', 'next', 'rescue', 16 | 'for', 'true', 'unless', 'when', 'nil', 'raise', 'throw', 'try', 'catch', 17 | 'after', 'with', 'require', 'use', '__MODULE__', '__FILE__', '__DIR__', 18 | '__ENV__', '__CALLER__' 19 | ]) 20 | const indentWords = wordObj([ 21 | 'def', 'defmodule', 'defp', 'case', 'cond', 'rescue', 'try', 'catch', '->' 22 | ]) 23 | const dedentWords = wordObj(['end']) 24 | const matching = {'[': ']', '{': '}', '(': ')'} 25 | 26 | let curPunc 27 | 28 | const chain = (newtok, stream, state) => { 29 | state.tokenize.push(newtok) 30 | return newtok(stream, state) 31 | } 32 | 33 | const tokenBase = (stream, state) => { 34 | if (stream.sol() && stream.match('"""') && stream.eol()) { 35 | state.tokenize.push(readBlockComment) 36 | return 'comment' 37 | } 38 | 39 | if (stream.eatSpace()) { 40 | return null 41 | } 42 | 43 | let ch = stream.next() 44 | let m 45 | 46 | if (ch === '\'' || ch === '"') { 47 | return chain(readQuoted(ch, 'string', ch === '"'), stream, state) 48 | } else if (ch === '/') { 49 | let currentIndex = stream.current().length 50 | if (stream.skipTo('/')) { 51 | let searchTill = stream.current().length 52 | let balance = 0 // balance brackets 53 | 54 | stream.backUp(stream.current().length - currentIndex) 55 | 56 | while (stream.current().length < searchTill) { 57 | const chchr = stream.next() 58 | if (chchr === '(') { 59 | balance += 1 60 | } else if (chchr === ')') { 61 | balance -= 1 62 | } 63 | if (balance < 0) { 64 | break 65 | } 66 | } 67 | 68 | stream.backUp(stream.current().length - currentIndex) 69 | 70 | if (balance === 0) { 71 | return chain(readQuoted(ch, 'string-2', true), stream, state) 72 | } 73 | } 74 | 75 | return 'operator' 76 | } else if (ch === '%') { 77 | let style = 'string' 78 | let embed = true 79 | 80 | if (stream.eat('s')) { 81 | style = 'atom' 82 | } else if (stream.eat(/[WQ]/)) { 83 | style = 'string' 84 | } else if (stream.eat(/[r]/)) { 85 | style = 'string-2' 86 | } else if (stream.eat(/[wxq]/)) { 87 | style = 'string' 88 | embed = false 89 | } 90 | 91 | let delim = stream.eat(/[^\w\s=]/) 92 | 93 | if (!delim) { 94 | return 'operator' 95 | } 96 | 97 | if (matching.propertyIsEnumerable(delim)) { 98 | delim = matching[delim] 99 | } 100 | 101 | return chain(readQuoted(delim, style, embed, true), stream, state) 102 | } else if (ch === '#') { 103 | stream.skipToEnd() 104 | return 'comment' 105 | } else if ( 106 | ch === '<' && 107 | (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/)) 108 | ) { 109 | return chain(readHereDoc(m[1]), stream, state) 110 | } else if (ch === '0') { 111 | if (stream.eat('x')) { 112 | stream.eatWhile(/[\da-fA-F]/) 113 | } else if (stream.eat('b')) { 114 | stream.eatWhile(/[01]/) 115 | } else { 116 | stream.eatWhile(/[0-7]/) 117 | } 118 | return 'number' 119 | } else if (/\d/.test(ch)) { 120 | stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/) 121 | return 'number' 122 | } else if (ch === '?') { 123 | while (stream.match(/^\\[CM]-/)) {} 124 | 125 | if (stream.eat('\\')) { 126 | stream.eatWhile(/\w/) 127 | } else { 128 | stream.next() 129 | } 130 | return 'string' 131 | } else if (ch === ':') { 132 | if (stream.eat('\'')) { 133 | return chain(readQuoted('\'', 'atom', false), stream, state) 134 | } 135 | if (stream.eat('"')) { 136 | return chain(readQuoted('"', 'atom', true), stream, state) 137 | } 138 | 139 | // :> :>> :< :<< are valid symbols 140 | if (stream.eat(/[\<\>]/)) { 141 | stream.eat(/[\<\>]/) 142 | return 'atom' 143 | } 144 | 145 | // :+ :- :/ :* :| :& :! are valid symbols 146 | if (stream.eat(/[\+\-\*\/\&\|\:\!]/)) { 147 | return 'atom' 148 | } 149 | 150 | // Symbols can't start by a digit 151 | if (stream.eat(/[a-zA-Z$@_\xa1-\uffff]/)) { 152 | stream.eatWhile(/[\w$\xa1-\uffff]/) 153 | // Only one ? ! = is allowed and only as the last character 154 | stream.eat(/[\?\!\=]/) 155 | return 'atom' 156 | } 157 | 158 | return 'operator' 159 | } else if (ch === '@' && stream.match(/^@?[a-zA-Z_\xa1-\uffff]/)) { 160 | stream.eat('@') 161 | stream.eatWhile(/[\w\xa1-\uffff]/) 162 | return 'variable-2' 163 | } else if (ch === '$') { 164 | if (stream.eat(/[a-zA-Z_]/)) { 165 | stream.eatWhile(/[\w]/) 166 | } else if (stream.eat(/\d/)) { 167 | stream.eat(/\d/) 168 | } else { 169 | stream.next() // Must be a special global like $: or $! 170 | } 171 | return 'variable-3' 172 | } else if (/[a-zA-Z_\xa1-\uffff]/.test(ch)) { 173 | stream.eatWhile(/[\w\xa1-\uffff]/) 174 | stream.eat(/[\?\!]/) 175 | if (stream.eat(':')) { 176 | return 'atom' 177 | } 178 | return 'ident' 179 | } else if ( 180 | ch === '|' && 181 | (state.varList || state.lastTok === '{' || state.lastTok === 'do') 182 | ) { 183 | curPunc = '|' 184 | return null 185 | } else if (/[\(\)\[\]{}\\;]/.test(ch)) { 186 | curPunc = ch 187 | return null 188 | } else if (ch === '-' && stream.eat('>')) { 189 | return 'arrow' 190 | } else if (ch === '|' && stream.eat('>')) { 191 | return 'pipe' 192 | } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) { 193 | if (ch === '.' && !stream.eatWhile(/[=+\-\/*:\.^%<>~|]/)) { 194 | curPunc = '.' 195 | } 196 | return 'operator' 197 | } else { 198 | return null 199 | } 200 | } 201 | 202 | const tokenBaseUntilBrace = depth => { 203 | if (!depth) { 204 | depth = 1 205 | } 206 | 207 | return (stream, state) => { 208 | if (stream.peek() === '}') { 209 | if (depth === 1) { 210 | state.tokenize.pop() 211 | return state.tokenize[state.tokenize.length - 1](stream, state) 212 | } else { 213 | state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth - 1) 214 | } 215 | } else if (stream.peek() === '{') { 216 | state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth + 1) 217 | } 218 | return tokenBase(stream, state) 219 | } 220 | } 221 | 222 | const tokenBaseOnce = () => { 223 | let alreadyCalled = false 224 | return (stream, state) => { 225 | if (alreadyCalled) { 226 | state.tokenize.pop() 227 | return state.tokenize[state.tokenize.length - 1](stream, state) 228 | } 229 | alreadyCalled = true 230 | return tokenBase(stream, state) 231 | } 232 | } 233 | 234 | const readQuoted = (quote, style, embed, unescaped) => { 235 | return (stream, state) => { 236 | let escaped = false 237 | let ch 238 | 239 | if (state.context.type === 'read-quoted-paused') { 240 | state.context = state.context.prev 241 | stream.eat('}') 242 | } 243 | 244 | while ((ch = stream.next()) != null) { // eslint-disable-line 245 | if (ch === quote && (unescaped || !escaped)) { 246 | state.tokenize.pop() 247 | break 248 | } 249 | 250 | if (embed && ch === '#' && !escaped) { 251 | if (stream.eat('{')) { 252 | if (quote === '}') { 253 | state.context = {prev: state.context, type: 'read-quoted-paused'} 254 | } 255 | state.tokenize.push(tokenBaseUntilBrace()) 256 | break 257 | } else if (/[@\$]/.test(stream.peek())) { 258 | state.tokenize.push(tokenBaseOnce()) 259 | break 260 | } 261 | } 262 | 263 | escaped = !escaped && ch === '\\' 264 | } 265 | 266 | return style 267 | } 268 | } 269 | 270 | const readHereDoc = phrase => { 271 | return (stream, state) => { 272 | if (stream.match(phrase)) { 273 | state.tokenize.pop() 274 | } else { 275 | stream.skipToEnd() 276 | } 277 | return 'string' 278 | } 279 | } 280 | 281 | const readBlockComment = (stream, state) => { 282 | if (stream.sol() && stream.match('"""') && stream.eol()) { 283 | state.tokenize.pop() 284 | } 285 | stream.skipToEnd() 286 | return 'comment' 287 | } 288 | 289 | return { 290 | startState: () => { 291 | return { 292 | tokenize: [tokenBase], 293 | indented: 0, 294 | context: {type: 'top', indented: -config.indentUnit}, 295 | continuedLine: false, 296 | lastTok: null, 297 | varList: false 298 | } 299 | }, 300 | token: (stream, state) => { 301 | curPunc = null 302 | 303 | // if (stream.sol()) { 304 | // state.indented = stream.indentation() 305 | // } 306 | 307 | let style = state.tokenize[state.tokenize.length - 1](stream, state) 308 | let kwtype 309 | let thisTok = curPunc 310 | 311 | if (style === 'ident') { 312 | let word = stream.current() 313 | 314 | style = state.lastTok === '.' ? 'property' 315 | : keywords.propertyIsEnumerable(stream.current()) ? 'keyword' 316 | : /^[A-Z]/.test(word) ? 'tag' 317 | : (state.lastTok === 'def' || state.lastTok === 'class' || state.varList) ? 'def' 318 | : 'variable' 319 | 320 | const isColumnIndent = stream.column() === stream.indentation() 321 | if (style === 'keyword') { 322 | thisTok = word 323 | if (indentWords.propertyIsEnumerable(word)) { 324 | kwtype = 'indent' 325 | } else if (dedentWords.propertyIsEnumerable(word)) { 326 | kwtype = 'dedent' 327 | } else if ((word === 'if' || word === 'unless') && isColumnIndent) { 328 | kwtype = 'indent' 329 | } else if (word === 'do' && state.context.indented < state.indented) { 330 | kwtype = 'indent' 331 | } 332 | } 333 | } 334 | 335 | if (curPunc || (style && style !== 'comment')) { 336 | state.lastTok = thisTok 337 | } 338 | 339 | if (curPunc === '|') { 340 | state.varList = !state.varList 341 | } 342 | 343 | if (kwtype === 'indent' || /[\(\[\{]/.test(curPunc)) { 344 | state.context = { 345 | prev: state.context, 346 | type: curPunc || style, 347 | indented: state.indented 348 | } 349 | } else if ( 350 | (kwtype === 'dedent' || /[\)\]\}]/.test(curPunc)) && 351 | state.context.prev 352 | ) { 353 | state.context = state.context.prev 354 | } 355 | 356 | if (stream.eol()) { 357 | state.continuedLine = (curPunc === '\\' || style === 'operator') 358 | } 359 | 360 | return style 361 | }, 362 | // indent: (state, textAfter) => { 363 | // if (state.tokenize[state.tokenize.length - 1] !== tokenBase) { 364 | // return 0 365 | // } 366 | // let firstChar = textAfter && textAfter.charAt(0) 367 | // let ct = state.context 368 | // let closing = ct.type === matching[firstChar] || 369 | // ct.type === 'keyword' && /^(?:end|until|else|else if|when|rescue)\b/.test(textAfter) 370 | // return ct.indented + (closing ? 0 : config.indentUnit) + 371 | // (state.continuedLine ? config.indentUnit : 0) 372 | // }, 373 | electricInput: /^\s*(?:end|rescue|else if|else|catch\})$/, 374 | lineComment: '#' 375 | } 376 | }) 377 | 378 | CodeMirror.defineMIME('text/x-elixir', 'elixir') 379 | --------------------------------------------------------------------------------