├── .nvmrc ├── examples └── test.cairo ├── .gitignore ├── documentation └── preview.gif ├── language-configuration.json ├── tsconfig.json ├── LICENSE ├── package.json ├── src └── extension.ts ├── README.md ├── snippets.json └── syntaxes └── cairo.tmLanguage.json /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.17.0 2 | -------------------------------------------------------------------------------- /examples/test.cairo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /out 3 | /package-lock.json 4 | /*.vsix 5 | -------------------------------------------------------------------------------- /documentation/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqdee/vscode-cairo-extension/HEAD/documentation/preview.gif -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // Symbol used for single line comment. 4 | "lineComment": "//", 5 | }, 6 | // Symbols used as brackets. 7 | "brackets": [ 8 | ["[", "]"] 9 | ], 10 | // Symbols that are auto closed when typing. 11 | "autoClosingPairs": [ 12 | ["[", "]"], 13 | ["%{", "%}"], 14 | ["(", ")"], 15 | ["{", "}"], 16 | ], 17 | // Symbols that that can be used to surround a selection. 18 | "surroundingPairs": [ 19 | ["%{", "%}"], 20 | ["(", ")"], 21 | ["{", "}"] 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | // Enable all strict type-checking options. 12 | "strict": true, 13 | // Additional checks. 14 | "noUnusedLocals": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noUnusedParameters": false 18 | }, 19 | "exclude": [ 20 | "node_modules", 21 | ".vscode-test" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 qd-qd 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cairo-unofficial", 3 | "displayName": "Cairo Unofficial", 4 | "description": "Unofficial support of the Cairo syntax", 5 | "version": "1.3.0", 6 | "engines": { 7 | "vscode": "^1.30.0" 8 | }, 9 | "dependencies": { 10 | "vscode-languageclient": "^5.0.0" 11 | }, 12 | "categories": [ 13 | "Programming Languages" 14 | ], 15 | "activationEvents": [ 16 | "onLanguage:cairo" 17 | ], 18 | "main": "./out/extension", 19 | "contributes": { 20 | "languages": [ 21 | { 22 | "id": "cairo", 23 | "aliases": [ 24 | "Cairo", 25 | "cairo" 26 | ], 27 | "extensions": [ 28 | ".cairo" 29 | ], 30 | "configuration": "./language-configuration.json" 31 | } 32 | ], 33 | "grammars": [ 34 | { 35 | "language": "cairo", 36 | "scopeName": "source.cairo", 37 | "path": "./syntaxes/cairo.tmLanguage.json", 38 | "embeddedLanguages": { 39 | "meta.embedded.block.python": "source.python" 40 | } 41 | } 42 | ], 43 | "snippets": [ 44 | { 45 | "language": "cairo", 46 | "path": "./snippets.json" 47 | } 48 | ], 49 | "configuration": [ 50 | { 51 | "title": "Cairo", 52 | "properties": { 53 | "cairo.cairoFormatPath": { 54 | "type": "string", 55 | "default": "cairo-format", 56 | "description": "Path to the cairo-format utility.", 57 | "scope": "window" 58 | } 59 | } 60 | } 61 | ], 62 | "configurationDefaults": { 63 | "[cairo]": { 64 | "editor.tabSize": 4, 65 | "editor.insertSpaces": true 66 | } 67 | } 68 | }, 69 | "publisher": "qdqd", 70 | "repository": "https://github.com/qd-qd/vscode-cairo-extension/", 71 | "scripts": { 72 | "vscode:prepublish": "npm run compile", 73 | "compile": "tsc -p ./", 74 | "watch": "tsc -watch -p ./", 75 | "postinstall": "node ./node_modules/vscode/bin/install", 76 | "package": "vsce package -o out", 77 | "deploy:local": "code --install-extension out/cairo-unofficial-*.vsix" 78 | }, 79 | "devDependencies": { 80 | "typescript": "^3.1.4", 81 | "vscode": "^1.1.25", 82 | "@types/node": "^8.10.25" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import cp = require('child_process'); 3 | 4 | type ExecFileSyncError = { 5 | error: Error, 6 | code: string, 7 | stderr: string | Buffer 8 | stdout: string | Buffer 9 | }; 10 | 11 | function getFormatterExecutablePath(config: vscode.WorkspaceConfiguration): string | undefined { 12 | let execPath = config.get('cairo.cairoFormatPath'); 13 | 14 | if (!execPath) return undefined; 15 | 16 | // TODO: Replace rootPath with workspaceFolders (https://github.com/microsoft/vscode/wiki/Adopting-Multi-Root-Workspace-APIs#eliminating-rootpath) 17 | // Replace placeholders, if present. 18 | if (vscode.workspace.rootPath) { 19 | execPath = execPath.replace(/\${workspaceFolder}/g, vscode.workspace.rootPath); 20 | } 21 | return execPath.replace(/\${cwd}/g, process.cwd()); 22 | } 23 | 24 | export function activate(/*context: vscode.ExtensionContext*/) { 25 | const config = vscode.workspace.getConfiguration(); 26 | vscode.languages.registerDocumentFormattingEditProvider('cairo', { 27 | provideDocumentFormattingEdits(document: vscode.TextDocument): vscode.TextEdit[] { 28 | const execPath = getFormatterExecutablePath(config); 29 | if (!execPath) { 30 | vscode.window.showInformationMessage('Unable to read cairo.cairoFormatPath.'); 31 | return []; 32 | } 33 | 34 | const opts = { input: document.getText(), encoding: 'utf-8', }; 35 | try { 36 | var replacementText = cp.execFileSync(execPath, ['-'], opts); 37 | } catch (e) { 38 | const { code, stderr } = e; 39 | if (code === 'ENOENT') { 40 | vscode.window.showInformationMessage( 41 | `Formatting failed. Executable ${execPath} not found. ` + 42 | 'Please check your cairo.cairoFormatPath user setting and ensure ' + 43 | 'it is installed.'); 44 | return []; 45 | } 46 | vscode.window.showInformationMessage(`Formatting failed: ${stderr.toString()}`); 47 | return []; 48 | } 49 | 50 | const firstLine = document.lineAt(0); 51 | const lastLine = document.lineAt(document.lineCount - 1); 52 | const wholeRange = new vscode.Range(0, 53 | firstLine.range.start.character, 54 | document.lineCount - 1, 55 | lastLine.range.end.character); 56 | return [vscode.TextEdit.replace(wholeRange, replacementText.toString())]; 57 | } 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cairo extension (unofficial) 2 | 3 | [![Extension version](https://vsmarketplacebadge.apphb.com/version/qdqd.cairo-unofficial.svg)](https://marketplace.visualstudio.com/items?itemName=qdqd.cairo-unofficial) [![Number of downloads](https://vsmarketplacebadge.apphb.com/downloads/qdqd.cairo-unofficial.svg)](https://marketplace.visualstudio.com/items?itemName=qdqd.cairo-unofficial) 4 | 5 | ## Introduction 6 | 7 | The Starkware team is doing a great job in developing the Cairo language and the Starknet layer2. At the time I create this repository, the team is focused on issues and improvements (regenesis, cairo v1...) much more important than the development of the vscode extension, which is absolutely fine. However, since the vscode extensions lives in the [cairo-lang](https://github.com/starkware-libs/cairo-lang) monorepo its non-priority development is slowed down. 8 | 9 | This repository is a way to relieve the Starkware teams by out-sourcing the development of the vscode extension. I don't think that the [cairo-lang](https://github.com/starkware-libs/cairo-lang) monorepo is the right place to host a product like this one whose development dynamics and code severity is different from the language itself. 10 | 11 | This extension is intended to support all the features developed by Starkware up to now, and to add to it features that make the development of programs written in Cairo smoother. 12 | 13 | ![preview of the extension](./documentation/preview.gif) 14 | 15 | ## Installation 16 | 17 | Make sure you installed [`vsce`](https://www.npmjs.com/package/vsce) globally and you installed the dependencies of this repository. These steps only need to be done once 18 | 19 | ```bash 20 | npm install -g vsce 21 | npm install 22 | ``` 23 | 24 | ## Install current version of the extension on vscode 25 | 26 | Run the following command to package the extension and install it locally 27 | 28 | ```bash 29 | npm run package && npm run deploy:local 30 | ``` 31 | 32 | ## Configuration 33 | 34 | If you have `cairo-format` installed globally (available in PATH), the value of 35 | `cairo.cairoFormatPath` should be `cairo-format` (the default). 36 | 37 | If you're working inside a StarkWare repository, and want the most up-to-date version, 38 | set the value of `cairo.cairoFormatPath` to 39 | 40 | ``` 41 | ${workspaceFolder}/src/starkware/cairo/lang/scripts/cairo-format 42 | ``` 43 | 44 | ## Comparison 45 | 46 | This extension supports all the features developed by Starkware, plus 47 | 48 | - A new snippet for the `@view` decorator 49 | - A new snippet for the `@constructor` decorator 50 | - A new snippet for the `@raw_input` decorator 51 | - A new snippet for the `@raw_output` decorator 52 | - A new snippet that let you create a function that uses both the `@raw_input` and `@raw_output` decorators 53 | - An improvement that allows you to write a `@storage_var` mapping more easily 54 | - A new snippet to write `struct` 55 | - A new snippet for the allocation point `ap` 56 | - It auto-close parenthesis/curly braces 57 | - It allows you to surround your selection with parenthesis/curly braces 58 | - A new snippet for the `@event` decorator 59 | - A new snippet for the `@l1_handler` decorator 60 | -------------------------------------------------------------------------------- /snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "Storage variable": { 3 | "prefix": [ 4 | "@storage_var" 5 | ], 6 | "body": [ 7 | "@storage_var", 8 | "func ${1:name}(${2}) -> (${3:res: felt}) {", 9 | "}", 10 | "$0" 11 | ], 12 | "description": "A StarkNet storage variable." 13 | }, 14 | "External function": { 15 | "prefix": [ 16 | "@external" 17 | ], 18 | "body": [ 19 | "@external", 20 | "func ${1:name}{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(", 21 | "\t${2:arguments}", 22 | ") {", 23 | "\t$0", 24 | "}" 25 | ], 26 | "description": "A StarkNet contract external function." 27 | }, 28 | "View function": { 29 | "prefix": [ 30 | "@view" 31 | ], 32 | "body": [ 33 | "@view", 34 | "func ${1:name}{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(", 35 | "\t${2:arguments}", 36 | ") -> (${3:return}) {", 37 | "\t$0", 38 | "}" 39 | ], 40 | "description": "A StarkNet contract view function." 41 | }, 42 | "Constructor function": { 43 | "prefix": [ 44 | "@constructor" 45 | ], 46 | "body": [ 47 | "@constructor", 48 | "func constructor{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(", 49 | "\t${2:arguments}", 50 | ") {", 51 | "\t$0", 52 | "}" 53 | ], 54 | "description": "A StarkNet contract constructor" 55 | }, 56 | "Raw input function": { 57 | "prefix": [ 58 | "@raw_input" 59 | ], 60 | "body": [ 61 | "@raw_input", 62 | "func ${1:name}{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(", 63 | "\tselector : felt, calldata_size : felt, calldata : felt*", 64 | ") {", 65 | "\t$0", 66 | "}" 67 | ], 68 | "description": "A StarkNet contract raw_input function." 69 | }, 70 | "Raw output function": { 71 | "prefix": [ 72 | "@raw_output" 73 | ], 74 | "body": [ 75 | "@raw_output", 76 | "func ${1:name}{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(", 77 | "\t${2:arguments}", 78 | ") -> (retdata_size : felt, retdata : felt*) {", 79 | "\t$0", 80 | "}" 81 | ], 82 | "description": "A StarkNet contract raw_output function." 83 | }, 84 | "Raw input/output function": { 85 | "prefix": [ 86 | "@raw_inout" 87 | ], 88 | "body": [ 89 | "@raw_input", 90 | "@raw_output", 91 | "func ${1:name}{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(", 92 | "\tselector : felt, calldata_size : felt, calldata : felt*", 93 | ") -> (retdata_size : felt, retdata : felt*) {", 94 | "\t$0", 95 | "}" 96 | ], 97 | "description": "A StarkNet contract raw_input and raw_output function." 98 | }, 99 | "StarkNet contract interface": { 100 | "prefix": [ 101 | "@contract_interface" 102 | ], 103 | "body": [ 104 | "@contract_interface", 105 | "namespace ${1:name} {", 106 | "\t$0", 107 | "}" 108 | ], 109 | "description": "A contract interface for another contract." 110 | }, 111 | "Struct": { 112 | "prefix": [ 113 | "struct" 114 | ], 115 | "body": [ 116 | "struct ${1:PascalCaseName} {", 117 | " ${2:first_member}: ${3:felt},", 118 | " ${4:second_member}: ${5:felt}", 119 | "}" 120 | ], 121 | "description": "A Cairo struct" 122 | }, 123 | "Allocation pointer": { 124 | "prefix": [ 125 | "app" 126 | ], 127 | "body": [ 128 | "[ap${1}] = ${2}, ap++;" 129 | ], 130 | "description": "A snippet for the allocation point statement" 131 | }, 132 | "Event": { 133 | "prefix": [ 134 | "@event" 135 | ], 136 | "body": [ 137 | "@event", 138 | "func ${1:name}(${2}) {", 139 | "}" 140 | ], 141 | "description": "A snippet for the events" 142 | }, 143 | "L1 handler": { 144 | "prefix": [ 145 | "@l1_handler" 146 | ], 147 | "body": [ 148 | "@l1_handler", 149 | "func ${1:name}{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(", 150 | "\t${2}", 151 | ") {", 152 | "\t${3}", 153 | "}" 154 | ], 155 | "description": "A function that receives message from the L1" 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /syntaxes/cairo.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "Cairo", 4 | "patterns": [ 5 | { 6 | "name": "meta.control.if", 7 | "begin": "\\b(if).*\\(", 8 | "beginCaptures": { 9 | "1": { 10 | "name": "keyword.control.if" 11 | }, 12 | "2": { 13 | "name": "entity.name.condition" 14 | } 15 | }, 16 | "contentName": "source.cairo", 17 | "end": "\\}", 18 | "patterns": [ 19 | { 20 | "include": "source.cairo" 21 | } 22 | ], 23 | "endCaptures": { 24 | "0": { 25 | "name": "keyword.control.end" 26 | } 27 | } 28 | }, 29 | { 30 | "name": "meta.control.with", 31 | "begin": "\\b(with)\\s+(.+)\\s*\\{", 32 | "beginCaptures": { 33 | "1": { 34 | "name": "keyword.control.with" 35 | }, 36 | "2": { 37 | "name": "entity.name.identifiers" 38 | } 39 | }, 40 | "contentName": "source.cairo", 41 | "end": "\\}", 42 | "patterns": [ 43 | { 44 | "include": "source.cairo" 45 | } 46 | ], 47 | "endCaptures": { 48 | "0": { 49 | "name": "keyword.control.end" 50 | } 51 | } 52 | }, 53 | { 54 | "name": "meta.control.with_attr", 55 | "begin": "\\b(with_attr)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*[({]", 56 | "beginCaptures": { 57 | "1": { 58 | "name": "keyword.control.with_attr" 59 | }, 60 | "2": { 61 | "name": "entity.name.function" 62 | } 63 | }, 64 | "contentName": "source.cairo", 65 | "end": "\\}", 66 | "patterns": [ 67 | { 68 | "include": "source.cairo" 69 | } 70 | ], 71 | "endCaptures": { 72 | "0": { 73 | "name": "keyword.control.end" 74 | } 75 | } 76 | }, 77 | { 78 | "name": "keyword.control.else", 79 | "match": "\\belse\\b" 80 | }, 81 | { 82 | "name": "keyword.other.opcode", 83 | "match": "\\b(call|jmp|ret|abs|rel|if)\\b" 84 | }, 85 | { 86 | "name": "keyword.other.register", 87 | "match": "\\b(ap|fp)\\b" 88 | }, 89 | { 90 | "name": "keyword.other.meta", 91 | "match": "\\b(const|let|local|tempvar|felt|as|from|import|static_assert|return|assert|cast|alloc_locals|with|with_attr|nondet|dw|codeoffset|new|using|and)\\b" 92 | }, 93 | { 94 | "name": "markup.italic", 95 | "match": "\\b(SIZEOF_LOCALS|SIZE)\\b" 96 | }, 97 | { 98 | "name": "comment.line.sharp", 99 | "match": "//[^\n]*\n" 100 | }, 101 | { 102 | "name": "entity.name.function", 103 | "match": "\\b[a-zA-Z_][a-zA-Z0-9_]*:\\s*$" 104 | }, 105 | { 106 | "name": "meta.function.cairo", 107 | "begin": "\\b(func)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*[({]", 108 | "beginCaptures": { 109 | "1": { 110 | "name": "storage.type.function.cairo" 111 | }, 112 | "2": { 113 | "name": "entity.name.function" 114 | } 115 | }, 116 | "contentName": "source.cairo", 117 | "end": "\\}", 118 | "patterns": [ 119 | { 120 | "include": "source.cairo" 121 | } 122 | ], 123 | "endCaptures": { 124 | "0": { 125 | "name": "storage.type.function.cairo" 126 | } 127 | } 128 | }, 129 | { 130 | "name": "meta.function.cairo", 131 | "begin": "\\b(struct|namespace)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\{", 132 | "beginCaptures": { 133 | "1": { 134 | "name": "storage.type.function.cairo" 135 | }, 136 | "2": { 137 | "name": "entity.name.function" 138 | } 139 | }, 140 | "contentName": "source.cairo", 141 | "end": "\\}", 142 | "patterns": [ 143 | { 144 | "include": "source.cairo" 145 | } 146 | ], 147 | "endCaptures": { 148 | "0": { 149 | "name": "storage.type.function.cairo" 150 | } 151 | } 152 | }, 153 | { 154 | "name": "constant.numeric.decimal", 155 | "match": "\\b[+-]?[0-9]+\\b" 156 | }, 157 | { 158 | "name": "constant.numeric.hexadecimal", 159 | "match": "\\b[+-]?0x[0-9a-fA-F]+\\b" 160 | }, 161 | { 162 | "name": "string.quoted.single", 163 | "match": "'[^']*'" 164 | }, 165 | { 166 | "name": "string.quoted.double", 167 | "match": "\"[^\"]*\"" 168 | }, 169 | { 170 | "begin": "%{", 171 | "beginCaptures": { 172 | "0": { 173 | "name": "punctuation.section.embedded.begin.python" 174 | } 175 | }, 176 | "end": "%}", 177 | "endCaptures": { 178 | "0": { 179 | "name": "punctuation.section.embedded.end.python" 180 | }, 181 | "1": { 182 | "name": "source.python" 183 | } 184 | }, 185 | "name": "meta.embedded.block.python", 186 | "contentName": "source.python", 187 | "patterns": [ 188 | { 189 | "include": "source.python" 190 | } 191 | ] 192 | } 193 | ], 194 | "scopeName": "source.cairo" 195 | } 196 | --------------------------------------------------------------------------------