├── .gitignore ├── screenshot.png ├── .vscodeignore ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── CHANGELOG.md ├── tslint.json ├── .github └── workflows │ ├── release.yml │ ├── ci.yml │ └── codeql-analysis.yml ├── src ├── test │ ├── suite │ │ ├── extension.test.ts │ │ └── index.ts │ └── runTest.ts └── extension.ts ├── README.md ├── tsconfig.json ├── language-configuration.json ├── LICENSE ├── vsc-extension-quickstart.md ├── package.json └── syntaxes └── rbs.tmLanguage.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soutaro/vscode-rbs-syntax/HEAD/screenshot.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/*.map 10 | **/*.ts -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.vscode-typescript-tslint-plugin" 6 | ] 7 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.1.0] - 2021-03-26 4 | 5 | * Rename to `RBS Syntax` ([\#5](https://github.com/soutaro/vscode-rbs-syntax/pull/5)) 6 | * Transfered from @hanachin to @soutaro 7 | 8 | ### [0.0.2] 9 | 10 | * Improve formatting of | 11 | 12 | ### [0.0.1] 13 | 14 | * Initial release of ruby-signature 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [ 9 | true, 10 | "always" 11 | ], 12 | "triple-equals": true 13 | }, 14 | "defaultSeverity": "warning" 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release to Marketplace 2 | 3 | on: 4 | release: 5 | types: [released] 6 | workflow_dispatch: {} 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | - run: 16 | npm ci 17 | - run: 18 | npx vsce publish 19 | env: 20 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 21 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.equal(-1, [1, 2, 3].indexOf(5)); 13 | assert.equal(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RBS Syntax 2 | 3 | RBS Syntax is a syntax support for [RBS](https://github.com/ruby/rbs) files. It implements basic syntax highlighting and indentation support. 4 | 5 | ![Screenshot](screenshot.png) 6 | 7 | RBS Syntax was originally developed by [@hanachin](https://github.com/hanachin) and transfered at Mar 25, 2021. The repository was `hanachin/vscode-ruby-signature` and the extension was distributed as [ruby-signature](https://marketplace.visualstudio.com/items?itemName=hanachin.ruby-signature). [@soutaro](https://github.com/soutaro) is the new maintainer. 8 | 9 | -------------------------------------------------------------------------------- /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 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "#" 5 | }, 6 | // symbols used as brackets 7 | "brackets": [ 8 | ["{", "}"], 9 | ["[", "]"], 10 | ["(", ")"] 11 | ], 12 | // symbols that are auto closed when typing 13 | "autoClosingPairs": [ 14 | ["{", "}"], 15 | ["[", "]"], 16 | ["(", ")"], 17 | ["\"", "\""], 18 | ["'", "'"] 19 | ], 20 | // symbols that that can be used to surround a selection 21 | "surroundingPairs": [ 22 | ["{", "}"], 23 | ["[", "]"], 24 | ["(", ")"], 25 | ["\"", "\""], 26 | ["'", "'"] 27 | ], 28 | 29 | } -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | }); 10 | mocha.useColors(true); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | e(err); 34 | } 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | 25 | - name: Install dependencies 26 | run: npm install 27 | 28 | - name: Build the package 29 | run: npm run package 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Seiei Miyagi and Soutaro Matsumoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--disable-extensions", 15 | "--extensionDevelopmentPath=${workspaceFolder}" 16 | ], 17 | "outFiles": [ 18 | "${workspaceFolder}/out/**/*.js" 19 | ], 20 | "preLaunchTask": "${defaultBuildTask}" 21 | }, 22 | { 23 | "name": "Extension Tests", 24 | "type": "extensionHost", 25 | "request": "launch", 26 | "runtimeExecutable": "${execPath}", 27 | "args": [ 28 | "--extensionDevelopmentPath=${workspaceFolder}", 29 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 30 | ], 31 | "outFiles": [ 32 | "${workspaceFolder}/out/test/**/*.js" 33 | ], 34 | "preLaunchTask": "${defaultBuildTask}" 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '18 23 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'javascript' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | 25 | ## Explore the API 26 | 27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 28 | 29 | ## Run tests 30 | 31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 32 | * Press `F5` to run the tests in a new window with your extension loaded. 33 | * See the output of the test result in the debug console. 34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 36 | * You can create folders inside the `test` folder to structure your tests any way you want. 37 | 38 | ## Go further 39 | 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rbs-syntax", 3 | "publisher": "soutaro", 4 | "displayName": "RBS Syntax", 5 | "description": "", 6 | "homepage": "https://github.com/soutaro/vscode-rbs-syntax", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/soutaro/vscode-rbs-syntax.git" 10 | }, 11 | "version": "0.3.1", 12 | "engines": { 13 | "vscode": "^1.39.0" 14 | }, 15 | "categories": [ 16 | "Programming Languages" 17 | ], 18 | "activationEvents": [ 19 | "onLanguage:ruby-signature", 20 | "onLanguage:rbs" 21 | ], 22 | "main": "./out/extension.js", 23 | "contributes": { 24 | "languages": [ 25 | { 26 | "id": "rbs", 27 | "aliases": [ 28 | "rbs", 29 | "ruby-signature" 30 | ], 31 | "extensions": [ 32 | ".rbs" 33 | ], 34 | "configuration": "./language-configuration.json" 35 | } 36 | ], 37 | "grammars": [ 38 | { 39 | "language": "rbs", 40 | "scopeName": "source.rbs", 41 | "path": "./syntaxes/rbs.tmLanguage.json" 42 | } 43 | ], 44 | "configurationDefaults": { 45 | "[rbs]": { 46 | "editor.tabSize": 2, 47 | "editor.insertSpaces": true, 48 | "editor.detectIndentation": false, 49 | "editor.formatOnType": true 50 | } 51 | }, 52 | "configuration": { 53 | "title": "RBS Syntax", 54 | "properties": { 55 | "rbs-syntax.enableDocumentSymbolProvider": { 56 | "type": "boolean", 57 | "default": true, 58 | "markdownDescription": "Enable this for outline view, breadcrumbs, and go-to-symbol navigation inside a file for RBS files." 59 | }, 60 | "rbs-syntax.enableOnTypeFormattingProvider": { 61 | "type": "boolean", 62 | "default": true, 63 | "markdownDescription": "Enable this for on-type formatting for RBS files." 64 | } 65 | } 66 | } 67 | }, 68 | "scripts": { 69 | "vscode:prepublish": "npm run compile", 70 | "compile": "tsc -p ./", 71 | "watch": "tsc -watch -p ./", 72 | "pretest": "npm run compile", 73 | "test": "node ./out/test/runTest.js", 74 | "package": "vsce package" 75 | }, 76 | "devDependencies": { 77 | "@types/glob": "^7.1.1", 78 | "@types/mocha": "^5.2.7", 79 | "@types/node": "^12.11.7", 80 | "@types/vscode": "^1.39.0", 81 | "@vscode/vsce": "^2.19.0", 82 | "glob": "^7.1.5", 83 | "mocha": "^11.1.0", 84 | "tslint": "^5.20.0", 85 | "typescript": "^5.8.0", 86 | "vscode-test": "^1.2.2" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /syntaxes/rbs.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "rbs", 4 | "patterns": [ 5 | { 6 | "include": "#comments" 7 | }, 8 | { 9 | "include": "#keywords" 10 | }, 11 | { 12 | "include": "#strings" 13 | } 14 | ], 15 | "repository": { 16 | "comments": { 17 | "name": "comment.line.number-sign", 18 | "begin": "#", 19 | "end": "\n" 20 | }, 21 | "keywords": { 22 | "patterns": [ 23 | { 24 | "name": "keyword.control.class.rbs", 25 | "match": "\\b(class)\\s+((::)?([A-Z]\\w*(::))*[A-Z]\\w*)", 26 | "captures": { 27 | "1": { 28 | "name": "keyword.control.class.rbs" 29 | }, 30 | "2": { 31 | "name": "entity.name.class" 32 | } 33 | } 34 | }, 35 | { 36 | "name": "keyword.control.type.rbs", 37 | "match": "\\b(type)\\b" 38 | }, 39 | { 40 | "name": "keyword.control.def.rbs", 41 | "match": "\\b(def)\\b([^:]+)", 42 | "captures": { 43 | "1": { 44 | "name": "keyword.control.def.rbs" 45 | }, 46 | "2": { 47 | "name": "entity.name.function.rbs" 48 | } 49 | } 50 | }, 51 | { 52 | "name": "keyword.control.self.rbs", 53 | "match": "\\b(self)\\b" 54 | }, 55 | { 56 | "name": "keyword.control.void.rbs", 57 | "match": "\\b(void)\\b" 58 | }, 59 | { 60 | "name": "keyword.control.untyped.rbs", 61 | "match": "\\b(untyped)\\b" 62 | }, 63 | { 64 | "name": "keyword.control.top.rbs", 65 | "match": "\\b(top)\\b" 66 | }, 67 | { 68 | "name": "keyword.control.bot.rbs", 69 | "match": "\\b(bot)\\b" 70 | }, 71 | { 72 | "name": "keyword.control.instance.rbs", 73 | "match": "\\b(instance)\\b" 74 | }, 75 | { 76 | "name": "keyword.control.bool.rbs", 77 | "match": "\\b(bool)\\b" 78 | }, 79 | { 80 | "name": "keyword.control.nil.rbs", 81 | "match": "\\b(nil)\\b" 82 | }, 83 | { 84 | "name": "keyword.control.singleton.rbs", 85 | "match": "\\b(singleton)\\b" 86 | }, 87 | { 88 | "name": "keyword.control.interface.rbs", 89 | "match": "\\b(interface)\\s+((::)?([A-Z]\\w*(::))*_[A-Z]\\w*)", 90 | "captures": { 91 | "1": { 92 | "name": "keyword.control.interface.rbs" 93 | }, 94 | "2": { 95 | "name": "entity.name.class" 96 | } 97 | } 98 | }, 99 | { 100 | "name": "keyword.control.end.rbs", 101 | "match": "\\b(end)\\b" 102 | }, 103 | { 104 | "name": "keyword.control.include.rbs", 105 | "match": "\\b(include)\\s+((::)?([A-Z]\\w*(::))*_?[A-Z]\\w*)", 106 | "captures": { 107 | "1": { 108 | "name": "keyword.control.include.rbs" 109 | }, 110 | "2": { 111 | "name": "variable.other.constant.rbs" 112 | } 113 | } 114 | }, 115 | { 116 | "name": "keyword.control.extend.rbs", 117 | "match": "\\b(extend)\\s+((::)?([A-Z]\\w*(::))*_?[A-Z]\\w*)", 118 | "captures": { 119 | "1": { 120 | "name": "keyword.control.extend.rbs" 121 | }, 122 | "2": { 123 | "name": "variable.other.constant.rbs" 124 | } 125 | } 126 | }, 127 | { 128 | "name": "keyword.control.prepend.rbs", 129 | "match": "\\b(prepend)\\s+((::)?([A-Z]\\w*(::))*[A-Z]\\w*)", 130 | "captures": { 131 | "1": { 132 | "name": "keyword.control.prepend.rbs" 133 | }, 134 | "2": { 135 | "name": "variable.other.constant.rbs" 136 | } 137 | } 138 | }, 139 | { 140 | "name": "keyword.control.module.rbs", 141 | "match": "\\b(module)\\s+((::)?([A-Z]\\w*(::))*[A-Z]\\w*)", 142 | "captures": { 143 | "1": { 144 | "name": "keyword.control.module.rbs" 145 | }, 146 | "2": { 147 | "name": "entity.name.class" 148 | } 149 | } 150 | }, 151 | { 152 | "name": "keyword.control.attr_reader.rbs", 153 | "match": "\\b(attr_reader)\\b" 154 | }, 155 | { 156 | "name": "keyword.control.attr_writer.rbs", 157 | "match": "\\b(attr_writer)\\b" 158 | }, 159 | { 160 | "name": "keyword.control.attr_accessor.rbs", 161 | "match": "\\b(attr_accessor)\\b" 162 | }, 163 | { 164 | "name": "keyword.control.public.rbs", 165 | "match": "\\b(public)\\b" 166 | }, 167 | { 168 | "name": "keyword.control.private.rbs", 169 | "match": "\\b(private)\\b" 170 | }, 171 | { 172 | "name": "keyword.control.alias.rbs", 173 | "match": "\\b(alias)\\b" 174 | }, 175 | { 176 | "name": "keyword.control.unchecked.rbs", 177 | "match": "\\b(unchecked)\\b" 178 | }, 179 | { 180 | "name": "keyword.control.out.rbs", 181 | "match": "\\b(out)\\b" 182 | }, 183 | { 184 | "name": "keyword.control.in.rbs", 185 | "match": "\\b(in)\\b" 186 | }, 187 | { 188 | "name": "keyword.other.use.rbs", 189 | "match": "\\b(use)\\b" 190 | }, 191 | { 192 | "name": "keyword.other.as.rbs", 193 | "match": "\\b(as)\\b" 194 | } 195 | ] 196 | }, 197 | "strings": { 198 | "name": "string.quoted.double.rbs", 199 | "begin": "\"", 200 | "end": "\"", 201 | "patterns": [ 202 | { 203 | "name": "constant.character.escape.rbs", 204 | "match": "\\\\." 205 | } 206 | ] 207 | } 208 | }, 209 | "scopeName": "source.rbs" 210 | } 211 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { 2 | languages, 3 | CancellationToken, 4 | ExtensionContext, 5 | FormattingOptions, 6 | TextDocument, 7 | TextEdit, 8 | OnTypeFormattingEditProvider, 9 | Range, 10 | Position, 11 | ProviderResult, 12 | TextLine, 13 | IndentAction, 14 | DocumentSymbolProvider, 15 | SymbolKind, 16 | DocumentSymbol, 17 | workspace, 18 | Disposable, 19 | } from 'vscode'; 20 | 21 | class NewLineIndentProvider implements OnTypeFormattingEditProvider { 22 | matchDefOrType(line: TextLine): RegExpMatchArray | null { 23 | return line.text.match(/^(\s*)((def [^:]+:)|(type [^=]+=))/) 24 | } 25 | 26 | matchEmptyLine(line: TextLine): RegExpMatchArray | null { 27 | return line.text.match(/^\s*$/) 28 | } 29 | 30 | matchBlockDelimiter(line: TextLine): RegExpMatchArray | null { 31 | return line.text.match(/^\s*end$/) || line.text.match(/^\s*(class|interface|module)\b/) 32 | } 33 | 34 | indentForDefOrType(document: TextDocument, position: Position): ProviderResult | null { 35 | const d = this.matchDefOrType(document.lineAt(position.line - 1)) 36 | if (!d) return 37 | 38 | const indentSize = d[2].length - 1; 39 | const insertSpace = TextEdit.insert(position, ' '.repeat(indentSize)) 40 | return Promise.resolve([insertSpace]) 41 | } 42 | 43 | outdentToMember(document: TextDocument, position: Position): ProviderResult | null { 44 | const prevLine = document.lineAt(position.line - 1) 45 | const empty = this.matchEmptyLine(prevLine) 46 | if (!empty) return 47 | 48 | let memberLine = position.line - 2 49 | while (memberLine > 0) { 50 | const line = document.lineAt(memberLine) 51 | 52 | if (this.matchBlockDelimiter(line)) { 53 | return 54 | } 55 | 56 | const match = this.matchDefOrType(line) 57 | if (match) { 58 | const leadingSpaces = match[1] 59 | 60 | if (leadingSpaces.length >= position.character) { 61 | return 62 | } 63 | 64 | const range = new Range(position.translate(undefined, leadingSpaces.length - position.character), position) 65 | const outdent = TextEdit.delete(range) 66 | 67 | return Promise.resolve([outdent]) 68 | } else { 69 | memberLine--; 70 | } 71 | } 72 | } 73 | 74 | provideOnTypeFormattingEdits(document: TextDocument, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): ProviderResult { 75 | if (document.lineAt(position.line).text.substring(position.character).length > 0) { 76 | return 77 | } 78 | 79 | return this.indentForDefOrType(document, position) || this.outdentToMember(document, position) 80 | } 81 | } 82 | 83 | class RbsDocumentSymbolProvider implements DocumentSymbolProvider { 84 | provideDocumentSymbols(document: TextDocument, token: CancellationToken): ProviderResult { 85 | const symbols: DocumentSymbol[] = [] 86 | const iter = this.eachLine(document) 87 | 88 | while (!iter.eof()) { 89 | const parsed = this.parse(iter) 90 | symbols.push(...parsed) 91 | } 92 | 93 | return symbols 94 | } 95 | 96 | parse(iter: { next: () => TextLine | null, eof: () => boolean, prev: () => TextLine }): DocumentSymbol[] { 97 | const symbols: DocumentSymbol[] = [] 98 | 99 | while (true) { 100 | const line = iter.next() 101 | if (line === null || line.text.match(/^\s*end\s*$/)) { 102 | break 103 | } 104 | 105 | let match: RegExpMatchArray | null 106 | if (match = line.text.match(/^(\s*)(class|module|interface)\s+(_?[A-Z]\w*)/)) { 107 | const indent = match[1] 108 | const kind = match[2] 109 | const name = match[3] 110 | 111 | const symKind = kind === "class" ? SymbolKind.Class : kind === "module" ? SymbolKind.Module : SymbolKind.Interface 112 | const selectionRange = new Range(line.range.start.translate(undefined, indent.length), line.range.end) 113 | const children = this.parse(iter) 114 | 115 | const range = new Range(line.range.start, iter.prev().range.end) 116 | const symbol = new DocumentSymbol(name, line.text, symKind, range, selectionRange) 117 | symbol.children = children 118 | symbols.push(symbol) 119 | } else if (match = line.text.match(/^(\s*)def\s*([^:]+):/)) { 120 | const indent = match[1] 121 | const name = match[2] 122 | 123 | const range = new Range(line.range.start.translate(undefined, indent.length), line.range.end) 124 | const symbol = new DocumentSymbol(name, line.text, SymbolKind.Method, range, range) 125 | symbols.push(symbol) 126 | } 127 | } 128 | 129 | return symbols 130 | } 131 | 132 | eachLine(document: TextDocument) { 133 | let i = 0 134 | let eof = false 135 | let prev: TextLine; 136 | 137 | return { 138 | next: () => { 139 | if (i < document.lineCount) { 140 | prev = document.lineAt(i++); 141 | return prev 142 | } else { 143 | eof = true; 144 | return null; 145 | } 146 | }, 147 | eof: () => eof, 148 | prev: () => prev, 149 | } 150 | } 151 | } 152 | 153 | const features = { 154 | documentSymbolProvider: undefined as Disposable | undefined, 155 | onTypeFormattingEditProvider: undefined as Disposable | undefined, 156 | } 157 | 158 | function updateFeature(enabled: boolean, key: keyof typeof features, start: () => Disposable) { 159 | if (enabled) { 160 | if (!features[key]) { 161 | console.log(`Starting ${key}...`) 162 | features[key] = start() 163 | } 164 | } else { 165 | const disposable = features[key] 166 | if (disposable) { 167 | console.log(`Shutting down ${key}...`) 168 | disposable.dispose() 169 | features[key] = undefined; 170 | } 171 | } 172 | } 173 | 174 | function updateDocumentSymbolProvider(enabled: boolean = workspace.getConfiguration("rbs-syntax").get("enableDocumentSymbolProvider", true)) { 175 | updateFeature( 176 | enabled, 177 | "documentSymbolProvider", 178 | () => languages.registerDocumentSymbolProvider("rbs", new RbsDocumentSymbolProvider()) 179 | ) 180 | } 181 | 182 | function updateOnTypeFormattingProvider(enabled: boolean = workspace.getConfiguration("rbs-syntax").get("enableOnTypeFormattingProvider", true)) { 183 | updateFeature( 184 | enabled, 185 | "onTypeFormattingEditProvider", 186 | () => languages.registerOnTypeFormattingEditProvider('rbs', new NewLineIndentProvider(), "\n") 187 | ) 188 | } 189 | 190 | 191 | export function activate(context: ExtensionContext) { 192 | context.subscriptions.push( 193 | workspace.onDidChangeConfiguration(event => { 194 | if (event.affectsConfiguration("rbs-syntax")) { 195 | updateDocumentSymbolProvider() 196 | updateOnTypeFormattingProvider() 197 | } 198 | }) 199 | ) 200 | 201 | updateDocumentSymbolProvider() 202 | updateOnTypeFormattingProvider() 203 | 204 | context.subscriptions.push( 205 | languages.setLanguageConfiguration( 206 | "rbs", 207 | { 208 | indentationRules: { 209 | increaseIndentPattern: /^(\s*)(class|module|interface)\b/, 210 | decreaseIndentPattern: /^(\s*)end/ 211 | }, 212 | onEnterRules: [ 213 | { 214 | beforeText: /^\s*#/, 215 | action: { indentAction: IndentAction.None, appendText: "# " } 216 | } 217 | ] 218 | } 219 | ) 220 | ) 221 | } 222 | 223 | export function deactivate() { 224 | updateDocumentSymbolProvider(false) 225 | updateOnTypeFormattingProvider(false) 226 | } 227 | --------------------------------------------------------------------------------