diff --git a/__tests__/__fixtures__/solidity/sample.sol b/__tests__/__fixtures__/solidity/sample.sol index 14c0f555..d07253a6 100644 --- a/__tests__/__fixtures__/solidity/sample.sol +++ b/__tests__/__fixtures__/solidity/sample.sol @@ -1,7 +1,156 @@ -pragma solidity ^0.8.9; +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.0 <0.9.0; +/// @title Voting with delegation. +contract Ballot { + // This declares a new complex type which will + // be used for variables later. + // It will represent a single voter. + struct Voter { + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal + } + + // This is a type for a single proposal. + struct Proposal { + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes + } + + address public chairperson; + + // This declares a state variable that + // stores a `Voter` struct for each possible address. + mapping(address => Voter) public voters; + + // A dynamically-sized array of `Proposal` structs. + Proposal[] public proposals; + + /// Create a new ballot to choose one of `proposalNames`. + constructor(bytes32[] memory proposalNames) { + chairperson = msg.sender; + voters[chairperson].weight = 1; + + // For each of the provided proposal names, + // create a new proposal object and add it + // to the end of the array. + for (uint i = 0; i < proposalNames.length; i++) { + // `Proposal({...})` creates a temporary + // Proposal object and `proposals.push(...)` + // appends it to the end of `proposals`. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + } + + // Give `voter` the right to vote on this ballot. + // May only be called by `chairperson`. + function giveRightToVote(address voter) external { + // If the first argument of `require` evaluates + // to `false`, execution terminates and all + // changes to the state and to Ether balances + // are reverted. + // This used to consume all gas in old EVM versions, but + // not anymore. + // It is often a good idea to use `require` to check if + // functions are called correctly. + // As a second argument, you can also provide an + // explanation about what went wrong. + require( + msg.sender == chairperson, + "Only chairperson can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; + } + + /// Delegate your vote to the voter `to`. + function delegate(address to) external { + // assigns reference + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "You have no right to vote"); + require(!sender.voted, "You already voted."); + + require(to != msg.sender, "Self-delegation is disallowed."); + + // Forward the delegation as long as + // `to` also delegated. + // In general, such loops are very dangerous, + // because if they run too long, they might + // need more gas than is available in a block. + // In this case, the delegation will not be executed, + // but in other situations, such loops might + // cause a contract to get "stuck" completely. + while (voters[to].delegate != address(0)) { + to = voters[to].delegate; + + // We found a loop in the delegation, not allowed. + require(to != msg.sender, "Found loop in delegation."); + } + + Voter storage delegate_ = voters[to]; + + // Voters cannot delegate to accounts that cannot vote. + require(delegate_.weight >= 1); + + // Since `sender` is a reference, this + // modifies `voters[msg.sender]`. + sender.voted = true; + sender.delegate = to; + + if (delegate_.voted) { + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate_.vote].voteCount += sender.weight; + } else { + // If the delegate did not vote yet, + // add to her weight. + delegate_.weight += sender.weight; + } + } + + /// Give your vote (including votes delegated to you) + /// to proposal `proposals[proposal].name`. + function vote(uint proposal) external { + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); + sender.voted = true; + sender.vote = proposal; + + // If `proposal` is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; + } + + /// @dev Computes the winning proposal taking all + /// previous votes into account. + function winningProposal() public view + returns (uint winningProposal_) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; + } + } + } -contract HelloWorld { - function render () public pure returns (string memory) { - return 'Hello World'; + // Calls winningProposal() function to get the index + // of the winner contained in the proposals array and then + // returns the name of the winner + function winnerName() external view + returns (bytes32 winnerName_) + { + winnerName_ = proposals[winningProposal()].name; } } diff --git a/__tests__/__snapshots__/codeMirror.test.js.snap b/__tests__/__snapshots__/codeMirror.test.js.snap index 904c93dd..853b1d76 100644 --- a/__tests__/__snapshots__/codeMirror.test.js.snap +++ b/__tests__/__snapshots__/codeMirror.test.js.snap @@ -345,11 +345,160 @@ exports[`Supported languages Shell should syntax highlight an example 1`] = ` `; exports[`Supported languages Solidity should syntax highlight an example 1`] = ` -"
pragma solidity ^0.8.9; +"
// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.0 <0.9.0; +/// @title Voting with delegation. +contract Ballot { + // This declares a new complex type which will + // be used for variables later. + // It will represent a single voter. + struct Voter { + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal + } + + // This is a type for a single proposal. + struct Proposal { + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes + } + + address public chairperson; + + // This declares a state variable that + // stores a \`Voter\` struct for each possible address. + mapping(address => Voter) public voters; + + // A dynamically-sized array of \`Proposal\` structs. + Proposal[] public proposals; + + /// Create a new ballot to choose one of \`proposalNames\`. + constructor(bytes32[] memory proposalNames) { + chairperson = msg.sender; + voters[chairperson].weight = 1; + + // For each of the provided proposal names, + // create a new proposal object and add it + // to the end of the array. + for (uint i = 0; i < proposalNames.length; i++) { + // \`Proposal({...})\` creates a temporary + // Proposal object and \`proposals.push(...)\` + // appends it to the end of \`proposals\`. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + } + + // Give \`voter\` the right to vote on this ballot. + // May only be called by \`chairperson\`. + function giveRightToVote(address voter) external { + // If the first argument of \`require\` evaluates + // to \`false\`, execution terminates and all + // changes to the state and to Ether balances + // are reverted. + // This used to consume all gas in old EVM versions, but + // not anymore. + // It is often a good idea to use \`require\` to check if + // functions are called correctly. + // As a second argument, you can also provide an + // explanation about what went wrong. + require( + msg.sender == chairperson, + "Only chairperson can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; + } + + /// Delegate your vote to the voter \`to\`. + function delegate(address to) external { + // assigns reference + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "You have no right to vote"); + require(!sender.voted, "You already voted."); + + require(to != msg.sender, "Self-delegation is disallowed."); + + // Forward the delegation as long as + // \`to\` also delegated. + // In general, such loops are very dangerous, + // because if they run too long, they might + // need more gas than is available in a block. + // In this case, the delegation will not be executed, + // but in other situations, such loops might + // cause a contract to get "stuck" completely. + while (voters[to].delegate != address(0)) { + to = voters[to].delegate; + + // We found a loop in the delegation, not allowed. + require(to != msg.sender, "Found loop in delegation."); + } + + Voter storage delegate_ = voters[to]; + + // Voters cannot delegate to accounts that cannot vote. + require(delegate_.weight >= 1); + + // Since \`sender\` is a reference, this + // modifies \`voters[msg.sender]\`. + sender.voted = true; + sender.delegate = to; + + if (delegate_.voted) { + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate_.vote].voteCount += sender.weight; + } else { + // If the delegate did not vote yet, + // add to her weight. + delegate_.weight += sender.weight; + } + } + + /// Give your vote (including votes delegated to you) + /// to proposal \`proposals[proposal].name\`. + function vote(uint proposal) external { + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); + sender.voted = true; + sender.vote = proposal; + + // If \`proposal\` is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; + } + + /// @dev Computes the winning proposal taking all + /// previous votes into account. + function winningProposal() public view + returns (uint winningProposal_) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; + } + } + } -contract HelloWorld { - function render () public pure returns (string memory) { - return 'Hello World'; + // Calls winningProposal() function to get the index + // of the winner contained in the proposals array and then + // returns the name of the winner + function winnerName() external view + returns (bytes32 winnerName_) + { + winnerName_ = proposals[winningProposal()].name; } }
" diff --git a/src/utils/cm-mode-imports.js b/src/utils/cm-mode-imports.js index b704aba1..6451bade 100644 --- a/src/utils/cm-mode-imports.js +++ b/src/utils/cm-mode-imports.js @@ -28,3 +28,4 @@ require('codemirror/mode/swift/swift'); require('codemirror/mode/toml/toml'); require('codemirror/mode/yaml/yaml'); require('codemirror-graphql/mode'); +require('./modes/solidity'); diff --git a/src/utils/modes.js b/src/utils/modes.js index 0f033975..7e78e88d 100644 --- a/src/utils/modes.js +++ b/src/utils/modes.js @@ -76,8 +76,8 @@ const modes = { scala: ['clike', 'text/x-scala'], scss: 'css', sh: 'shell', - sol: 'clike', - solidity: 'clike', + sol: 'solidity', + solidity: 'solidity', sql: ['sql', 'text/x-sql'], sqlite: ['sql', 'text/x-sqlite'], styl: 'css', diff --git a/src/utils/modes/solidity.js b/src/utils/modes/solidity.js new file mode 100644 index 00000000..f85fd7f5 --- /dev/null +++ b/src/utils/modes/solidity.js @@ -0,0 +1,599 @@ +/* eslint-disable operator-assignment */ +/* eslint-disable prefer-template */ +/* eslint-disable object-shorthand */ +/* eslint-disable no-return-assign */ +/* eslint-disable unicorn/no-unsafe-regex */ +/* eslint-disable no-unused-expressions */ +/* eslint-disable consistent-return */ +/* eslint-disable no-prototype-builtins */ +/* eslint-disable no-useless-escape */ +/* eslint-disable eqeqeq */ +/* eslint-disable no-use-before-define */ +/* eslint-disable global-require */ + +/** + * This mode has been adapted from the source of `codemirror-solidity` as the way that library is + * structured makes it impossible for us load as an isolated mode for our Codemirror needs. The + * source is mostly presented as-is with the exception of a few areas adapted into to our coding + * standards (such as using `const` instead of `let` for immutable data). + * + * @license MIT + * @see {@link https://github.com/alincode/codemirror-solidity} + */ +(function (mod) { + if (typeof exports === 'object' && typeof module === 'object') { + // CommonJS + mod(require('codemirror/lib/codemirror')); + // eslint-disable-next-line no-undef + } else if (typeof define === 'function' && define.amd) { + // AMD + define(['codemirror/lib/codemirror'], mod); // eslint-disable-line no-undef + } else { + // Plain browser env + mod(CodeMirror); // eslint-disable-line no-undef + } +})(function (CodeMirror) { + CodeMirror.defineMode('solidity', function (config) { + const indentUnit = config.indentUnit; + + // let functionKeyword = 'function' + // let functionNameKeyword = 'Name' + // let leftBracketSign = '(' + // let rightBracketSign = ')' + // let functionVariableName = 'variable' + // let keyWordContract = 'contract' + + const keywords = { + pragma: true, + solidity: true, + import: true, + as: true, + from: true, + contract: true, + constructor: true, + is: true, + function: true, + modifier: true, + // modifiers + pure: true, + view: true, + payable: true, + constant: true, + anonymous: true, + indexed: true, + returns: true, + return: true, + event: true, + struct: true, + mapping: true, + interface: true, + using: true, + library: true, + storage: true, + memory: true, + calldata: true, + public: true, + private: true, + external: true, + internal: true, + emit: true, + assembly: true, + abstract: true, + after: true, + catch: true, + final: true, + in: true, + inline: true, + let: true, + match: true, + null: true, + of: true, + relocatable: true, + static: true, + try: true, + typeof: true, + var: true, + }; + + const keywordsSpecial = { + pragma: true, + returns: true, + address: true, + contract: true, + function: true, + struct: true, + }; + + const keywordsEtherUnit = { + wei: true, + szabo: true, + finney: true, + ether: true, + }; + const keywordsTimeUnit = { + seconds: true, + minutes: true, + hours: true, + days: true, + weeks: true, + }; + const keywordsBlockAndTransactionProperties = { + block: ['coinbase', 'difficulty', 'gaslimit', 'number', 'timestamp'], + msg: ['data', 'sender', 'sig', 'value'], + tx: ['gasprice', 'origin'], + }; + const keywordsMoreBlockAndTransactionProperties = { + now: true, + gasleft: true, + blockhash: true, + }; + const keywordsErrorHandling = { + assert: true, + require: true, + revert: true, + throw: true, + }; + const keywordsMathematicalAndCryptographicFuctions = { + addmod: true, + mulmod: true, + keccak256: true, + sha256: true, + ripemd160: true, + ecrecover: true, + }; + const keywordsContractRelated = { + this: true, + selfdestruct: true, + super: true, + }; + const keywordsTypeInformation = { type: true }; + const keywordsContractList = {}; + + const keywordsControlStructures = { + if: true, + else: true, + while: true, + do: true, + for: true, + break: true, + continue: true, + switch: true, + case: true, + default: true, + }; + + const keywordsValueTypes = { + bool: true, + byte: true, + string: true, + enum: true, + address: true, + }; + + const keywordsV0505NewReserve = { + alias: true, + apply: true, + auto: true, + copyof: true, + define: true, + immutable: true, + implements: true, + macro: true, + mutable: true, + override: true, + partial: true, + promise: true, + reference: true, + sealed: true, + sizeof: true, + supports: true, + typedef: true, + unchecked: true, + }; + + const keywordsAbiEncodeDecodeFunctions = { + abi: ['decode', 'encodePacked', 'encodeWithSelector', 'encodeWithSignature', 'encode'], + }; + + const keywordsMembersOfAddressType = ['transfer', 'send', 'balance', 'call', 'delegatecall', 'staticcall']; + + const natSpecTags = ['title', 'author', 'notice', 'dev', 'param', 'return']; + + // let functionStructureStage = [{ + // function: ['function', 'returns'] + // }, + // leftBracketSign, + // parameterName, + // parameterValue, + // rightBracketSign, + // ]; + + const atoms = { + delete: true, + new: true, + true: true, + false: true, + // "iota": true, "nil": true, "append": true, + // "cap": true, "close": true, "complex": true, "copy": true, "imag": true, + // "make": true, "panic": true, "print": true, + // "println": true, "real": true, "recover": true + }; + + const isOperatorChar = /[+\-*&^%:=<>!|\/~]/; + const isNegativeChar = /[-]/; + + let curPunc; + + function tokenBase(stream, state) { + let ch = stream.next(); + + if (ch == '"' || ch == "'" || ch == '`') { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + + if (isVersion(stream, state)) return 'version'; + + if ( + ch == '.' && + keywordsMembersOfAddressType.some(function (item) { + return stream.match(`${item}`); + }) + ) + return 'addressFunction'; + + if (isNumber(ch, stream)) return 'number'; + + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + return updateGarmmer(ch, state); + } + + if (ch == '/') { + if (stream.eat('*')) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.match(/\/{2}/)) { + while ((ch = stream.next())) { + if (ch == '@') { + stream.backUp(1); + state.grammar = 'doc'; + break; + } + } + return 'doc'; + } + + if (stream.eat('/')) { + stream.skipToEnd(); + return 'comment'; + } + } + + if (isNegativeChar.test(ch)) { + if (isNumber(stream.peek(), stream)) return 'number'; + return 'operator'; + } + + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return 'operator'; + } + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + + const cur = stream.current(); + + if (state.grammar == 'doc') { + if ( + natSpecTags.some(function (item) { + return cur == `@${item}`; + }) + ) + return 'docReserve'; + return 'doc'; + } + + if (cur === 'solidity' && state.lastToken == 'pragma') { + state.lastToken = state.lastToken + ' ' + cur; + } + + if (keywords.propertyIsEnumerable(cur)) { + if (cur == 'case' || cur == 'default') curPunc = 'case'; + if (keywordsSpecial.propertyIsEnumerable(cur)) state.lastToken = cur; + // if (cur == 'function' && state.para == 'parameterMode') + return 'keyword'; + } + + if (keywordsEtherUnit.propertyIsEnumerable(cur)) return 'etherUnit'; + if (keywordsContractRelated.propertyIsEnumerable(cur)) return 'contractRelated'; + if ( + keywordsControlStructures.propertyIsEnumerable(cur) || + keywordsTypeInformation.propertyIsEnumerable(cur) || + keywordsV0505NewReserve.propertyIsEnumerable(cur) + ) + return 'keyword'; + if ( + keywordsValueTypes.propertyIsEnumerable(cur) || + keywordsTimeUnit.propertyIsEnumerable(cur) || + isValidInteger(cur) || + isValidBytes(cur) || + isValidFixed(cur) + ) { + state.lastToken = state.lastToken + 'variable'; + return 'keyword'; + } + + if (atoms.propertyIsEnumerable(cur)) return 'atom'; + if (keywordsErrorHandling.propertyIsEnumerable(cur)) return 'errorHandling'; + if (keywordsMathematicalAndCryptographicFuctions.propertyIsEnumerable(cur)) return 'mathematicalAndCryptographic'; + + if ( + keywordsMoreBlockAndTransactionProperties.propertyIsEnumerable(cur) || + (keywordsBlockAndTransactionProperties.hasOwnProperty(cur) && + keywordsBlockAndTransactionProperties[cur].some(function (item) { + return stream.match(`.${item}`); + })) + ) + return 'variable-2'; + + if ( + keywordsAbiEncodeDecodeFunctions.hasOwnProperty(cur) && + keywordsAbiEncodeDecodeFunctions[cur].some(function (item) { + return stream.match(`.${item}`); + }) + ) + return 'abi'; + + const style = updateHexLiterals(cur, stream); + if (style != null) return style; + + if ( + (state.lastToken == 'functionName(' || state.lastToken == 'returns(') && + keywordsContractList.propertyIsEnumerable(cur) + ) { + state.lastToken += 'variable'; + return 'variable'; + } + if (state.lastToken == 'function') { + state.lastToken = 'functionName'; + if (state.para == null) { + state.grammar = 'function'; + state.para = ''; + } + // state.parasMode = isNaN(state.parasMode) ? 1 : state.functionLayerCount++; + state.para += 'functionName'; + return 'functionName'; + } + + if (state.lastToken == 'functionName(variable') { + state.lastToken = 'functionName('; + return 'parameterValue'; + } + + if (state.lastToken == 'returns(variable') { + state.lastToken = 'returns('; + return 'parameterValue'; + } + + if (state.lastToken == 'address' && cur == 'payable') { + state.lastToken = 'address payable'; + } + if (state.lastToken == 'contract' || state.lastToken == 'struct') { + keywordsContractList[cur] = true; + state.lastToken = null; + } + if (state.grammar == 'function') { + return 'parameterValue'; + } + + return 'variable'; + } + + function tokenString(quote) { + return function (stream, state) { + let escaped = false; + let next; + let end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) { + end = true; + break; + } + escaped = !escaped && quote != '`' && next == '\\'; + } + if (end || !(escaped || quote == '`')) state.tokenize = tokenBase; + return 'string'; + }; + } + + function tokenComment(stream, state) { + let maybeEnd = false; + let ch; + while ((ch = stream.next())) { + if (ch == '/' && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = ch == '*'; + } + return 'comment'; + } + + function isVersion(stream, state) { + if (state.lastToken == 'pragma solidity') { + state.lastToken = null; + return ( + !state.startOfLine && + (stream.match(/[\^{0}][0-9\.]+/) || stream.match(/[\>\=]+?[\s]*[0-9\.]+[\s]*[\<]?[\s]*[0-9\.]+/)) + ); + } + } + + function isNumber(ch, stream) { + if (/[\d\.]/.test(ch)) { + if (ch == '.') { + stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); + } else if (ch == '0') { + stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); + } else { + stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); + } + return true; + } + } + + function isValidInteger(token) { + if (token.match(/^[u]?int/)) { + if (token.indexOf('t') + 1 == token.length) return true; + const numberPart = token.substr(token.indexOf('t') + 1, token.length); + return numberPart % 8 === 0 && numberPart <= 256; + } + } + + function isValidBytes(token) { + if (token.match(/^bytes/)) { + if (token.indexOf('s') + 1 == token.length) return true; + const bytesPart = token.substr(token.indexOf('s') + 1, token.length); + return bytesPart <= 32; + } + } + + function isValidFixed(token) { + if (token.match(/^[u]?fixed([0-9]+x[0-9]+)?/)) { + if (token.indexOf('d') + 1 == token.length) return true; + const numberPart = token.substr(token.indexOf('d') + 1, token.length).split('x'); + return numberPart[0] % 8 === 0 && numberPart[0] <= 256 && numberPart[1] <= 80; + } + } + + function updateHexLiterals(token, stream) { + if (token.match(/^hex/) && stream.peek() == '"') { + let maybeEnd = false; + let ch; + let hexValue = ''; + let stringAfterHex = ''; + while ((ch = stream.next())) { + stringAfterHex += ch; + if (ch == '"' && maybeEnd) { + hexValue = stringAfterHex.substring(1, stringAfterHex.length - 1); + if (hexValue.match(/^[0-9a-fA-F]+$/)) { + return 'number'; + } + + stream.backUp(stringAfterHex.length); + break; + } + maybeEnd = maybeEnd || ch == '"'; + } + } + } + + function updateGarmmer(ch, state) { + if (ch == ',' && state.para == 'functionName(variable') { + state.para = 'functionName('; + } + if (state.para != null && state.para.startsWith('functionName')) { + if (ch == ')') { + if (state.para.endsWith('(')) { + state.para = state.para.substr(0, state.para.length - 1); + if (state.para == 'functionName') state.grammar = ''; + } + } else if (ch == '(') { + state.para += ch; + } + } + + if (ch == '(' && state.lastToken == 'functionName') { + state.lastToken += ch; + } else if (ch == ')' && state.lastToken == 'functionName(') { + state.lastToken = null; + } else if (ch == '(' && state.lastToken == 'returns') { + state.lastToken += ch; + } else if (ch == ')' && (state.lastToken == 'returns(' || state.lastToken == 'returns(variable')) { + state.lastToken = null; + } + if (ch == '(' && state.lastToken == 'address') { + state.lastToken += ch; + } + curPunc = ch; + return null; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + return (state.context = new Context(state.indented, col, type, null, state.context)); + } + function popContext(state) { + if (!state.context.prev) return; + const t = state.context.type; + if (t == ')' || t == ']' || t == '}') state.indented = state.context.indented; + return (state.context = state.context.prev); + } + + // Interface + return { + startState: function (basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, 'top', false), + indented: 0, + startOfLine: true, + }; + }, + + token: function (stream, state) { + const ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + if (ctx.type == 'case') ctx.type = '}'; + if (state.grammar == 'doc') state.grammar = null; + } + if (stream.eatSpace()) return null; + curPunc = null; + const style = (state.tokenize || tokenBase)(stream, state); + + if (style == 'comment') return style; + if (ctx.align == null) ctx.align = true; + + if (curPunc == '{') pushContext(state, stream.column(), '}'); + else if (curPunc == '[') pushContext(state, stream.column(), ']'); + else if (curPunc == '(') pushContext(state, stream.column(), ')'); + else if (curPunc == 'case') ctx.type = 'case'; + else if (curPunc == '}' && ctx.type == '}') popContext(state); + else if (curPunc == ctx.type) popContext(state); + state.startOfLine = false; + return style; + }, + + indent: function (state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; + const ctx = state.context; + const firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == 'case' && /^(?:case|default)\b/.test(textAfter)) { + state.context.type = '}'; + return ctx.indented; + } + const closing = firstChar == ctx.type; + if (ctx.align) return ctx.column + (closing ? 0 : 1); + return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: '{}):', + closeBrackets: '()[]{}\'\'""``', + fold: 'brace', + blockCommentStart: '/*', + blockCommentEnd: '*/', + lineComment: '//', + }; + }); + + CodeMirror.defineMIME('text/x-solidity', 'solidity'); +});