├── .eslintignore ├── .github ├── CODEOWNERS ├── workflows │ ├── bump-version.yml │ └── tests.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── images └── batch.png ├── doc ├── rename-var.gif ├── rename-label.gif ├── goto-definition.gif ├── peek-references-var.gif └── peek-references-label.gif ├── scripts ├── sample.bat ├── sample.ps1 └── sample.sh ├── .gitattributes ├── .vscodeignore ├── .vscode ├── extensions.json ├── settings.json ├── launch.json └── tasks.json ├── CHANGELOG.md ├── .eslintrc.js ├── README.md ├── bat.configuration.json ├── LICENSE.txt ├── src ├── lsp │ ├── BatchReferencesFinder.ts │ ├── BatchDeclarationFinder.ts │ ├── client.ts │ ├── BatchReferencesProvider.ts │ └── server.ts ├── extension.ts ├── ConflictingExtensionsChecker.ts ├── SnippetsGenerator.ts ├── test │ └── lsp │ │ └── BatchReferencesProvider.test.ts └── bat │ └── TabStopper.ts ├── tsconfig.json ├── vsc-extension-quickstart.md ├── package.json ├── snippets ├── bat-community.json └── bat.json └── syntaxes └── batchfile.tmLanguage.json /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @gustavosimon @Lucasspaniol @zLianK @GabrielPdaC -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | PsPadBK 6 | tsconfig.tsbuildinfo 7 | -------------------------------------------------------------------------------- /images/batch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RechInformatica/rech-editor-batch/HEAD/images/batch.png -------------------------------------------------------------------------------- /doc/rename-var.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RechInformatica/rech-editor-batch/HEAD/doc/rename-var.gif -------------------------------------------------------------------------------- /doc/rename-label.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RechInformatica/rech-editor-batch/HEAD/doc/rename-label.gif -------------------------------------------------------------------------------- /doc/goto-definition.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RechInformatica/rech-editor-batch/HEAD/doc/goto-definition.gif -------------------------------------------------------------------------------- /doc/peek-references-var.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RechInformatica/rech-editor-batch/HEAD/doc/peek-references-var.gif -------------------------------------------------------------------------------- /scripts/sample.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Sample bat to make tests about Rech Editor Batch's Extension 4 | echo Hello World -------------------------------------------------------------------------------- /doc/peek-references-label.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RechInformatica/rech-editor-batch/HEAD/doc/peek-references-label.gif -------------------------------------------------------------------------------- /scripts/sample.ps1: -------------------------------------------------------------------------------- 1 | # Sample powershell script to make tests about Rech Editor Batch's Extension 2 | Write-Host 'Hello, world!' -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | package.json text eol=lf 4 | package-lock.json text eol=lf 5 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | out/**/*.map 5 | src/** 6 | .gitignore 7 | tsconfig.json 8 | vsc-extension-quickstart.md 9 | tslint.json -------------------------------------------------------------------------------- /scripts/sample.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Sample shell script to make tests about Rech Editor Batch's Extension 4 | # If you are in a Windows, you can run this script in WSL to test 5 | echo Hello World -------------------------------------------------------------------------------- /.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 | "dbaeumer.vscode-eslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /.github/workflows/bump-version.yml: -------------------------------------------------------------------------------- 1 | name: Bump version via shared workflow 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | bump: 10 | uses: RechInformatica/extensions-workflow/.github/workflows/bump-version.yml@master 11 | with: 12 | bump: patch 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: Feature request 5 | label: backlog 6 | --- 7 | 8 | ## Suggestion 9 | 10 | 11 | 12 | ## Use Cases 13 | 14 | 18 | 19 | ## Examples 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 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.0.21] - 2022-12-15 2 | 3 | ### Added 4 | * `rech.batch.snippetsType` - Snippets configuration that can change the source of snippets. There are two options: 5 | * ***Rech Internal*** - Focused on people who work at Rech (*Portuguese*). 6 | * ***Community*** - Focused on community (*English*). 7 | * `rech.batch.initialTabAligment` - Initial tab aligment configuration that can set the first tab size. 8 | 9 | ### Changed 10 | * Tab sizes became dynamic according to `editor.tabSize` configuration. 11 | 12 | ### Removed 13 | * Fixed ruler removed. To continue using the previous configuration, the user should add this line of code at the settings.json file: 14 | > "editor.rulers": [120] 15 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "prettier" 8 | ], 9 | "parser": "@typescript-eslint/parser", 10 | "parserOptions": { 11 | "project": ["./tsconfig.json"], 12 | "sourceType": "module" 13 | }, 14 | "plugins": [ 15 | "eslint-plugin-import", 16 | "@typescript-eslint" 17 | ], 18 | "root": true, 19 | "rules": { 20 | "@typescript-eslint/no-floating-promises": "error", 21 | "import/no-deprecated": "warn", 22 | "no-duplicate-case": "error", 23 | "no-duplicate-imports": "error", 24 | "no-magic-numbers": ["warn", {ignore: [-1, 0, 1, 2, 3]}], 25 | "prefer-const": "warn" 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | about: Create a report to help us improve 3 | 4 | --- 5 | 6 | **Describe the bug** 7 | A clear and concise description of what the bug is. 8 | 9 | **To Reproduce** 10 | Steps to reproduce the behavior: 11 | 1. Go to '...' 12 | 2. Click on '....' 13 | 3. Scroll down to '....' 14 | 4. See error 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Environments (please complete the following information as much as possible):** 20 | - OS: [e.g. Windows 10/Linux/OS X] 21 | - VS Code version: [e.g. 1.73.1] 22 | - Extension version [e.g. 0.20.0] 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Additional context** 28 | Add any other context about the problem here. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Edit Batch files with Visual Studio Code 2 | 3 | This extension provides Language Server for Windows batch scripts. 4 | Besides, it also provides useful *snippets* for these files. 5 | 6 | ## Go to/peek definition 7 | 8 | It's possible to peek definition of a label or go directly to it's declaration. 9 | 10 | !['Label definition' Label definition](doc/goto-definition.gif) 11 | 12 | ## Peek references 13 | 14 | You can peek all references of a label or a variable. The search scope is only the current file. In other words: it does not look for a variable on other batch scripts. 15 | 16 | Label: 17 | !['Label references' Label references](doc/peek-references-label.gif) 18 | 19 | Variable: 20 | !['Variable references' Variable references](doc/peek-references-var.gif) 21 | 22 | ## Rename 23 | 24 | It's also possible to rename labels and variables. 25 | 26 | Label: 27 | !['Label rename' Label rename](doc/rename-label.gif) 28 | 29 | Variable: 30 | !['Variable rename' Variable rename](doc/rename-var.gif) 31 | 32 | -------------------------------------------------------------------------------- /bat.configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "rem" 4 | }, 5 | "brackets": [ 6 | [ 7 | "(", 8 | ")" 9 | ] 10 | ], 11 | "autoClosingPairs": [ 12 | { 13 | "open": "(", 14 | "close": ")" 15 | }, 16 | { 17 | "open": "\"", 18 | "close": "\"" 19 | }, 20 | { 21 | "open": "'", 22 | "close": "'" 23 | }, 24 | { 25 | "open": "%", 26 | "close": "%" 27 | }, 28 | { 29 | "open": "!", 30 | "close": "!" 31 | } 32 | ], 33 | "surroundingPairs": [ 34 | [ 35 | "{", 36 | "}" 37 | ], 38 | [ 39 | "[", 40 | "]" 41 | ], 42 | [ 43 | "(", 44 | ")" 45 | ], 46 | [ 47 | "\"", 48 | "\"" 49 | ], 50 | [ 51 | "'", 52 | "'" 53 | ], 54 | [ 55 | "%", 56 | "%" 57 | ], 58 | [ 59 | "!", 60 | "!" 61 | ] 62 | ], 63 | "folding": { 64 | "markers": { 65 | "start": "^(?!rem)\\s*\\$region", 66 | "end": "^(?!rem)\\s*\\$end-region" 67 | } 68 | }, 69 | "wordPattern": "[a-zA-Z0-9ÇéâäàçêëèïîÄôöòûùÖÜáíóúñÑÁÂÀãÃÊËÈÍÎÏÌÓÔÒõÕÚÛÙüÉì_\\-]+" 70 | } -------------------------------------------------------------------------------- /.github/workflows/tests.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: windows-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: Installs node dependencies 26 | run: npm i 27 | 28 | - name: Compiles source code 29 | run: npm run compile 30 | 31 | - name: Run tests 32 | run: npm run test 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 rechinformatica 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 | -------------------------------------------------------------------------------- /src/lsp/BatchReferencesFinder.ts: -------------------------------------------------------------------------------- 1 | import { Location, Position, Range } from "vscode-languageserver"; 2 | import { BatchReferencesProvider, BatchElementPosition } from "./BatchReferencesProvider"; 3 | 4 | /** 5 | * Class to find Batch references 6 | */ 7 | export class BatchReferencesFinder { 8 | 9 | /** 10 | * Constructor of Find 11 | * 12 | * @param text editor text 13 | */ 14 | constructor(private text: string) {} 15 | 16 | /** 17 | * Find the declaration of the term 18 | * 19 | * @param term Term to find 20 | * @param uri current source uri 21 | */ 22 | public findReferences(term: string, uri: string): Promise { 23 | return new Promise((resolve, reject) => { 24 | new BatchReferencesProvider() 25 | .findReferences(this.text, term) 26 | .then((positions) => { 27 | const result = this.convertBatchPositionsToLocations(positions, uri); 28 | resolve(result); 29 | }) 30 | .catch(() => reject()); 31 | }); 32 | } 33 | 34 | private convertBatchPositionsToLocations(positions: BatchElementPosition[], uri: string): Location[] { 35 | const result: Location[] = []; 36 | positions.forEach(position => { 37 | const range = Range.create( 38 | Position.create(position.line, position.column), 39 | Position.create(position.line, position.column) 40 | ); 41 | result.push({ uri: uri, range: range }); 42 | }); 43 | return result; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/lsp/BatchDeclarationFinder.ts: -------------------------------------------------------------------------------- 1 | import { Location, Position, Range } from "vscode-languageserver"; 2 | import { Scan } from "rech-ts-commons"; 3 | 4 | /** 5 | * Class to find Batch declarations 6 | */ 7 | export class BatchDeclarationFinder { 8 | 9 | /** 10 | * Constructor of Find 11 | * 12 | * @param buffer editor text 13 | */ 14 | constructor(private buffer: string) {} 15 | 16 | /** 17 | * Find the declaration of the term 18 | * 19 | * @param label label declaration to find 20 | * @param uri current source uri 21 | */ 22 | public findDeclaration(label: string, uri: string): Promise { 23 | return new Promise((resolve, reject) => { 24 | const regexText = "^\\:" + label + "$"; 25 | const regex = new RegExp(regexText, "gm"); 26 | let declaration: Location | undefined = undefined; 27 | new Scan(this.buffer).scan(regex, (iterator: any) => { 28 | const firstCharRange = Range.create( 29 | Position.create(iterator.row, 1), 30 | Position.create(iterator.row, 1) 31 | ); 32 | declaration = Location.create(uri, firstCharRange); 33 | iterator.stop(); 34 | 35 | }); 36 | if (declaration) { 37 | return resolve(declaration) 38 | } else { 39 | return reject(); 40 | } 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", 5 | "module": "commonjs", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 10 | "sourceMap": true, /* Generates corresponding '.map' file. */ 11 | "outDir": "./out", /* Redirect output structure to the directory. */ 12 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 13 | "composite": true, /* Enable project compilation */ 14 | /* Strict Type-Checking Options */ 15 | "strict": true, /* Enable all strict type-checking options. */ 16 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 17 | /* Additional Checks */ 18 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 19 | "noImplicitReturns": false, /* Report error when not all code paths in function return a value. */ 20 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 21 | /* Module Resolution Options */ 22 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 23 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 24 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 25 | } 26 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // The module 'vscode' contains the VS Code extensibility API 3 | // Import the module and reference it with the alias vscode in your code below 4 | import { commands, ExtensionContext } from 'vscode'; 5 | import { Client } from './lsp/client'; 6 | import { TabStopper } from './bat/TabStopper'; 7 | import { ConflictingExtensionsChecker } from './ConflictingExtensionsChecker'; 8 | import { SnippetsGenerator } from './SnippetsGenerator'; 9 | 10 | // this method is called when your extension is activated 11 | // your extension is activated the very first time the command is executed 12 | export function activate(_context: any) { 13 | 14 | new ConflictingExtensionsChecker().check(); 15 | new SnippetsGenerator().generate(); 16 | 17 | const context = _context; 18 | Client.startServerAndEstablishCommunication(context); 19 | // 20 | // The command has been defined in the package.json file 21 | // Now provide the implementation of the command with registerCommand 22 | // The commandId parameter must match the command field in package.json 23 | // 24 | context.subscriptions.push(commands.registerCommand('rech.editor.batch.batchInsertCommentLine', async () => { 25 | await commands.executeCommand('editor.action.insertLineBefore'); 26 | await commands.executeCommand('editor.action.trimTrailingWhitespace'); 27 | await commands.executeCommand('editor.action.commentLine'); 28 | })); 29 | context.subscriptions.push(commands.registerCommand('rech.editor.batch.tab', () => { 30 | new TabStopper().processTabKey(true); 31 | })); 32 | context.subscriptions.push(commands.registerCommand('rech.editor.batch.revtab', () => { 33 | new TabStopper().processTabKey(false); 34 | })); 35 | } 36 | 37 | // this method is called when your extension is deactivated 38 | export function deactivate() { 39 | } 40 | -------------------------------------------------------------------------------- /.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 | "type": "extensionHost", 10 | "request": "launch", 11 | "name": "Launch Client", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceRoot}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceRoot}/client/out/**/*.js" 18 | ], 19 | "preLaunchTask": "npm: watch:client" 20 | }, 21 | { 22 | "type": "node", 23 | "request": "attach", 24 | "name": "Attach to Server", 25 | "port": 10999, 26 | "restart": true, 27 | "outFiles": [ 28 | "${workspaceRoot}/server/out/**/*.js" 29 | ] 30 | }, 31 | { 32 | "name": "Language Server E2E Test", 33 | "type": "extensionHost", 34 | "request": "launch", 35 | "runtimeExecutable": "${execPath}", 36 | "args": [ 37 | "--extensionDevelopmentPath=${workspaceRoot}", 38 | "--extensionTestsPath=${workspaceRoot}/client/out/test", 39 | "${workspaceRoot}/client/testFixture" 40 | ], 41 | "outFiles": [ 42 | "${workspaceRoot}/client/out/test/**/*.js" 43 | ] 44 | }, 45 | { 46 | "type": "node", 47 | "request": "launch", 48 | "name": "Mocha All", 49 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 50 | "args": [ 51 | "-r", 52 | "ts-node/register", 53 | "--timeout", 54 | "999999", 55 | "--colors", 56 | "${workspaceFolder}/src/test/**/*.ts", 57 | ], 58 | "console": "integratedTerminal", 59 | "internalConsoleOptions": "neverOpen", 60 | "protocol": "inspector" 61 | } 62 | ], 63 | "compounds": [ 64 | { 65 | "name": "Client + Server", 66 | "configurations": [ 67 | "Launch Client", 68 | "Attach to Server" 69 | ] 70 | } 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /src/lsp/client.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from 'vscode'; 2 | import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; 3 | import * as path from 'path'; 4 | 5 | /** 6 | * Language Server Provider client 7 | */ 8 | export class Client { 9 | 10 | /** Client instance of the Language Server Provider (LSP) */ 11 | private static client: LanguageClient | undefined; 12 | 13 | /** 14 | * Starts the LSP server and establishes communication between them 15 | */ 16 | public static startServerAndEstablishCommunication(context: ExtensionContext) { 17 | // The server is implemented in node 18 | const serverModule = context.asAbsolutePath( 19 | path.join('out', 'lsp', 'server.js') 20 | ); 21 | // The debug options for the server 22 | // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging 23 | const debugOptions = { execArgv: ['--nolazy', '--inspect=10999'] }; 24 | // If the extension is launched in debug mode then the debug server options are used 25 | // Otherwise the run options are used 26 | const serverOptions: ServerOptions = { 27 | run: { module: serverModule, transport: TransportKind.ipc }, 28 | debug: { 29 | module: serverModule, 30 | transport: TransportKind.ipc, 31 | options: debugOptions 32 | } 33 | }; 34 | // Options to control the language client 35 | const clientOptions: LanguageClientOptions = { 36 | // Register the server for Batch documents 37 | documentSelector: [{ scheme: 'file', language: 'bat' }] 38 | }; 39 | // Create the language client and start the client. 40 | Client.client = new LanguageClient( 41 | 'batchLanguageServer', 42 | 'Batch Language Server', 43 | serverOptions, 44 | clientOptions 45 | ); 46 | // Start the client. This will also launch the server 47 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 48 | Client.client.start(); 49 | } 50 | 51 | 52 | /** 53 | * Stops the LSP client if it has ben previously started 54 | */ 55 | public static stopClient() { 56 | if (!Client.client) { 57 | return undefined; 58 | } 59 | return Client.client.stop(); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | * This folder contains all of the files necessary for your extension. 5 | * `package.json` - this is the manifest file in which you declare your extension and command. 6 | The sample plugin registers a command and defines its title and command name. With this information 7 | 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 10 | activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 11 | We pass the function containing the implementation of the command as the second parameter to 12 | `registerCommand`. 13 | 14 | ## Get up and running straight away 15 | * Press `F5` to open a new window with your extension loaded. 16 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 17 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 18 | * Find output from your extension in the debug console. 19 | 20 | ## Make changes 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 | ## Explore the API 25 | * You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`. 26 | 27 | ## Run tests 28 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests`. 29 | * Press `F5` to run the tests in a new window with your extension loaded. 30 | * See the output of the test result in the debug console. 31 | * Make changes to `test/extension.test.ts` or create new test files inside the `test` folder. 32 | * By convention, the test runner will only consider files matching the name pattern `**.test.ts`. 33 | * You can create folders inside the `test` folder to structure your tests any way you want. 34 | -------------------------------------------------------------------------------- /.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": "compile:client", 9 | "group": "build", 10 | "presentation": { 11 | "panel": "dedicated", 12 | "reveal": "never" 13 | }, 14 | "problemMatcher": [ 15 | "$tsc" 16 | ] 17 | }, 18 | { 19 | "type": "npm", 20 | "script": "compile:server", 21 | "group": "build", 22 | "presentation": { 23 | "panel": "dedicated", 24 | "reveal": "never" 25 | }, 26 | "problemMatcher": [ 27 | "$tsc" 28 | ] 29 | }, 30 | { 31 | "label": "watch", 32 | "dependsOn": [ 33 | { 34 | "type": "npm", 35 | "script": "watch:client" 36 | }, 37 | { 38 | "type": "npm", 39 | "script": "watch:server" 40 | } 41 | ], 42 | "group": { 43 | "kind": "build", 44 | "isDefault": true 45 | }, 46 | "problemMatcher": [] 47 | }, 48 | { 49 | "type": "npm", 50 | "script": "watch:client", 51 | "isBackground": true, 52 | "group": "build", 53 | "presentation": { 54 | "panel": "dedicated", 55 | "reveal": "never" 56 | }, 57 | "problemMatcher": [ 58 | "$tsc-watch" 59 | ] 60 | }, 61 | { 62 | "type": "npm", 63 | "script": "watch:server", 64 | "isBackground": true, 65 | "group": "build", 66 | "presentation": { 67 | "panel": "dedicated", 68 | "reveal": "never" 69 | }, 70 | "problemMatcher": [ 71 | "$tsc-watch" 72 | ] 73 | }, 74 | { 75 | "type": "npm", 76 | "script": "lint", 77 | "problemMatcher": ["$eslint-stylish"], 78 | "label": "npm: lint", 79 | "detail": "eslint -c .eslintrc.js --ext .ts ./src" 80 | } 81 | ] 82 | } -------------------------------------------------------------------------------- /src/ConflictingExtensionsChecker.ts: -------------------------------------------------------------------------------- 1 | import { extensions, window, commands, workspace, WorkspaceConfiguration, ConfigurationTarget } from "vscode"; 2 | 3 | const EXTENSION_SETTINGS_GROUP = 'rech.batch'; 4 | const ALERT_CONFLICTING_EXTENSIONS = 'alertConflictingExtensions'; 5 | 6 | /** 7 | * Class to check for extensions which are installed and conflict with Rech Bath extension 8 | */ 9 | export class ConflictingExtensionsChecker { 10 | 11 | check(): void { 12 | if (this.shouldAlert()) { 13 | const extensionName = `'Windows Bat Language Basics'`; 14 | const extensionId = 'vscode.bat'; 15 | const vscodeBatExtension = extensions.getExtension(extensionId); 16 | if (vscodeBatExtension) { 17 | const yesButton = 'Yes'; 18 | const dontAskAnymoreButton = `Don't ask anymore`; 19 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 20 | window.showInformationMessage(`The built-in extension ${extensionName} is enabled along with Rech Batch, which may lead to misbehavior while inserting 'rem' comments in lowercase. Would you like to manually disable the built-in extension ${extensionName}?`, yesButton, 'Not now', dontAskAnymoreButton) 21 | .then(selected => { 22 | switch (selected) { 23 | case yesButton: 24 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 25 | commands.executeCommand('workbench.extensions.action.showExtensionsWithIds', [extensionId]); 26 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 27 | window.showInformationMessage(`Please click on the gear icon and manually disable ${extensionName} extension.`); 28 | break; 29 | case dontAskAnymoreButton: 30 | this.disableAlertSetting(); 31 | break; 32 | } 33 | }); 34 | } 35 | } 36 | }; 37 | 38 | private shouldAlert(): boolean { 39 | const shouldAlert = settingsGroup().get(ALERT_CONFLICTING_EXTENSIONS, true); 40 | return shouldAlert; 41 | } 42 | 43 | private disableAlertSetting(): void { 44 | const newValue = false; 45 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 46 | settingsGroup().update(ALERT_CONFLICTING_EXTENSIONS, newValue, ConfigurationTarget.Global); 47 | } 48 | 49 | } 50 | 51 | function settingsGroup(): WorkspaceConfiguration { 52 | return workspace.getConfiguration(EXTENSION_SETTINGS_GROUP); 53 | } 54 | -------------------------------------------------------------------------------- /src/SnippetsGenerator.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItem, CompletionItemKind, languages, MarkdownString, SnippetString, workspace, WorkspaceConfiguration } from "vscode"; 2 | 3 | /** 4 | * Class to generate snippets 5 | */ 6 | export class SnippetsGenerator { 7 | 8 | /** 9 | * Generate all the snippets completion items and provide them 10 | */ 11 | generate(): void { 12 | languages.registerCompletionItemProvider( 13 | { scheme: "file", language: "bat" }, 14 | { 15 | provideCompletionItems: async () => { 16 | const snippets: CompletionItem[] = []; 17 | const json = this.getSnippetsJson(); 18 | Object.entries(json).forEach(([_key, value]) => { 19 | const completionItem = new CompletionItem(value.prefix, CompletionItemKind.Snippet); 20 | completionItem.documentation = this.snippetTextToMarkdown(value.body); 21 | completionItem.detail = value.description; 22 | completionItem.insertText = new SnippetString(value.body); 23 | snippets.push(completionItem); 24 | }); 25 | return snippets; 26 | } 27 | } 28 | ); 29 | }; 30 | 31 | /** 32 | * Get the json from a json file according to the snippetsType configuration 33 | * 34 | * @returns {Object} Snippets json 35 | */ 36 | private getSnippetsJson(): Object { 37 | var filename = "../snippets/bat.json"; 38 | if (this.getSnippetsType() == "Community") { 39 | filename = "../snippets/bat-community.json" 40 | } 41 | return require(filename); 42 | } 43 | 44 | /** 45 | * Returns the value of the snippetsType configuration 46 | * 47 | * @returns {string} 48 | */ 49 | private getSnippetsType(): string { 50 | return this.settingsGroup().get("snippetsType", "Rech Internal"); 51 | } 52 | 53 | /** 54 | * Returns rech.batch settings group 55 | * 56 | * @returns {WorkspaceCofiguration} 57 | */ 58 | private settingsGroup(): WorkspaceConfiguration { 59 | return workspace.getConfiguration("rech.batch"); 60 | } 61 | 62 | /** 63 | * Convert snippet text to markdown code block 64 | * 65 | * @param snippetText Text to convert 66 | * @returns {MarkdownString} Markdown with the code block inside 67 | */ 68 | private snippetTextToMarkdown(snippetText: string): MarkdownString { 69 | const markdownText = snippetText.replace(/\$[0-9]+|\${|}/g, ""); 70 | const markdown = new MarkdownString(); 71 | markdown.appendCodeblock(markdownText); 72 | return markdown; 73 | } 74 | } -------------------------------------------------------------------------------- /src/lsp/BatchReferencesProvider.ts: -------------------------------------------------------------------------------- 1 | import { Scan } from "rech-ts-commons"; 2 | 3 | /** Minimum word size */ 4 | const MIN_WORD_SIZE = 3; 5 | 6 | /** 7 | * Class which provide references for Batch elements. 8 | * 9 | * This class do not have any dependence with VSCode API so 10 | * unit tests can be run. 11 | */ 12 | export class BatchReferencesProvider { 13 | 14 | /** 15 | * Find the declaration of the term 16 | * 17 | * @param text buffer text in which ter will be searched 18 | * @param term Term to find 19 | */ 20 | public findReferences(text: string, term: string): Promise { 21 | return new Promise((resolve, reject) => { 22 | // If the word is too small 23 | if (term.length < MIN_WORD_SIZE) { 24 | return reject(); 25 | } 26 | const result: BatchElementPosition[] = []; 27 | const regexText = '([\\s\\.\\%\\!\\:\\,\\)\\(])?(' + term + ')([\\s\\t\\n\\r\\.\\%\\!\\:\\=\\,\\)\\(])?'; 28 | const elementUsage = new RegExp(regexText, "img"); 29 | new Scan(text).scan(elementUsage, (iterator: any) => { 30 | if (!this.shouldIgnoreElement(term, iterator.lineContent, iterator.column)) { 31 | result.push({ line: iterator.row, column: this.shouldMoveColumnCursor(iterator) }); 32 | } 33 | }); 34 | return resolve(result); 35 | }); 36 | } 37 | 38 | private shouldMoveColumnCursor(iterator: any): number { 39 | if (iterator.column != 0 || iterator.match.includes(":")) { 40 | return iterator.column + 1; 41 | } else { 42 | return iterator.column; 43 | } 44 | } 45 | 46 | private shouldIgnoreElement(term: string, lineText: string, column: number): boolean { 47 | const enclosed = this.isEnclosedInQuotes(lineText, column); 48 | const variable = this.isVariableReference(term, lineText, column); 49 | return enclosed && !variable; 50 | } 51 | 52 | private isEnclosedInQuotes(lineText: string, column: number): boolean { 53 | let insideQuotes = false; 54 | for (let i = 0; i < lineText.length && i < column; i++) { 55 | const currentChar = lineText[i]; 56 | if (currentChar === "\"") { 57 | insideQuotes = !insideQuotes; 58 | } 59 | } 60 | return insideQuotes; 61 | } 62 | 63 | private isVariableReference(term: string, lineText: string, column: number): boolean { 64 | if (column == 0 || column == lineText.length) { 65 | return false; 66 | } 67 | const symbols = ["%", "!"]; 68 | let variable = false; 69 | for (let i = 0; i < symbols.length && !variable; i++) { 70 | const symbol = symbols[i]; 71 | const indexBeforeElement = column; 72 | var indexAfterElement = column + term.length; 73 | indexAfterElement++; 74 | if (lineText[indexBeforeElement] == symbol && lineText[indexAfterElement] == symbol) { 75 | variable = true; 76 | } 77 | } 78 | return variable; 79 | } 80 | 81 | } 82 | 83 | export interface BatchElementPosition { 84 | line: number, 85 | column: number 86 | } 87 | -------------------------------------------------------------------------------- /src/test/lsp/BatchReferencesProvider.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import 'mocha'; 3 | import { BatchReferencesProvider, BatchElementPosition } from '../../lsp/BatchReferencesProvider'; 4 | 5 | describe('Find batch references', () => { 6 | 7 | it('Finds label references with a string containing same label name. Issue #4', async () => { 8 | const buffer = [ 9 | ":home", 10 | "", 11 | "", 12 | "set str=\" home \"", 13 | "", 14 | "goto home", 15 | "", 16 | ]; 17 | const expected: BatchElementPosition[] = [ 18 | { line: 0, column: 1 }, 19 | { line: 5, column: 5 }, 20 | ] 21 | const result = await new BatchReferencesProvider().findReferences(buffer.join("\n"), 'home'); 22 | assertReferences(expected, result); 23 | }); 24 | 25 | it('Finds variable references within string containing multiple references of the same variable', async () => { 26 | const buffer = [ 27 | "echo \"containing teste!myVar!%myVar%\"\n", 28 | ]; 29 | const expected: BatchElementPosition[] = [ 30 | { line: 0, column: 23 }, 31 | { line: 0, column: 30 }, 32 | ] 33 | const result = await new BatchReferencesProvider().findReferences(buffer.join("\n"), 'myVar'); 34 | assertReferences(expected, result); 35 | }); 36 | 37 | it('Finds command references with multiple references of the same command. Issue #17', async () => { 38 | const buffer = [ 39 | "set variable=value", 40 | "rem some other code", 41 | "set another_variable=another_value" 42 | ]; 43 | const expected: BatchElementPosition[] = [ 44 | { line: 0, column: 0 }, 45 | { line: 2, column: 0 }, 46 | ] 47 | const result = await new BatchReferencesProvider().findReferences(buffer.join("\n"), 'set'); 48 | assertReferences(expected, result); 49 | }); 50 | 51 | it('Finds variable references within string containing same variable name', async () => { 52 | const buffer = [ 53 | "set myVar=S", 54 | "", 55 | "echo \"this text containing myVar must be ignored\"", 56 | "echo %myVar%", 57 | "echo \"this text containing %myVar% must not be ignored\"", 58 | "echo \"this text containing teste!myVar!%myVar%%myVar%myVar must not be ignored either\"", 59 | "echo \"set myVar=ABC\"", 60 | "echo set myVar=ABC", 61 | "", 62 | "goto :eof", 63 | "", 64 | ]; 65 | const expected: BatchElementPosition[] = [ 66 | { line: 0, column: 4 }, 67 | { line: 3, column: 6 }, 68 | { line: 4, column: 28 }, 69 | { line: 5, column: 33 }, 70 | { line: 5, column: 40 }, 71 | { line: 5, column: 47 }, 72 | { line: 7, column: 9 }, 73 | ] 74 | const result = await new BatchReferencesProvider().findReferences(buffer.join("\n"), 'myVar'); 75 | assertReferences(expected, result); 76 | }); 77 | 78 | 79 | it('Finds inexisting variable reference', async () => { 80 | const buffer = [ 81 | "set myVar=S", 82 | "", 83 | "echo \"this text containing myVar must be ignored\"", 84 | "echo %myVar%", 85 | "echo \"this text containing %myVar% must not be ignored\"", 86 | "echo \"this text containing !myVar! must not be ignored either\"", 87 | "echo \"set myVar=ABC\"", 88 | "echo set myVar=ABC", 89 | "", 90 | "goto :eof", 91 | "", 92 | ]; 93 | const expected: BatchElementPosition[] = []; 94 | const result = await new BatchReferencesProvider().findReferences(buffer.join("\n"), 'inexistentVar'); 95 | assertReferences(expected, result); 96 | }); 97 | }); 98 | 99 | function assertReferences(expected: BatchElementPosition[], result: BatchElementPosition[]): void { 100 | expect(expected.length).to.equal(result.length); 101 | for (let i = 0; i < result.length; i++) { 102 | expect(expected[i].line).to.equal(result[i].line); 103 | expect(expected[i].column).to.equal(result[i].column); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rech-editor-batch", 3 | "displayName": "Rech Batch", 4 | "description": "Edit Batch files with Visual Studio Code", 5 | "version": "0.0.23", 6 | "publisher": "rechinformatica", 7 | "engines": { 8 | "vscode": "^1.73.1" 9 | }, 10 | "icon": "images/batch.png", 11 | "license": "SEE LICENSE IN LICENSE.txt", 12 | "bugs": { 13 | "url": "https://github.com/RechInformatica/rech-editor-batch/issues", 14 | "email": "infra@rech.com.br" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/RechInformatica/rech-editor-batch.git" 19 | }, 20 | "categories": [ 21 | "Programming Languages", 22 | "Snippets", 23 | "Other" 24 | ], 25 | "activationEvents": [ 26 | "onLanguage:bat", 27 | "onLanguage:shellscript", 28 | "onLanguage:powershell" 29 | ], 30 | "main": "./out/extension.js", 31 | "contributes": { 32 | "configurationDefaults": { 33 | "[bat]": { 34 | "files.encoding": "windows1252", 35 | "editor.tabSize": 3, 36 | "editor.detectIndentation": true, 37 | "editor.trimAutoWhitespace": true, 38 | "editor.useTabStops": false, 39 | "editor.guides.indentation": false 40 | } 41 | }, 42 | "languages": [ 43 | { 44 | "id": "bat", 45 | "extensions": [ 46 | ".bat", 47 | ".cmd" 48 | ], 49 | "configuration": "./bat.configuration.json" 50 | } 51 | ], 52 | "grammars": [ 53 | { 54 | "language": "bat", 55 | "scopeName": "source.batchfile", 56 | "path": "./syntaxes/batchfile.tmLanguage.json" 57 | } 58 | ], 59 | "commands": [ 60 | { 61 | "command": "rech.editor.batch.batchInsertCommentLine", 62 | "title": "Rech Batch: Insert commentary line above" 63 | }, 64 | { 65 | "command": "rech.editor.batch.tab", 66 | "title": "Rech Batch: Performs a tab and sets the cursor on the most appropriate batch column" 67 | }, 68 | { 69 | "command": "rech.editor.batch.revtab", 70 | "title": "Rech Batch: Performs a reverse-tab and sets the cursor on the most appropriate batch column" 71 | } 72 | ], 73 | "keybindings": [ 74 | { 75 | "command": "rech.editor.batch.batchInsertCommentLine", 76 | "key": "alt+n", 77 | "when": "editorLangId == bat" 78 | }, 79 | { 80 | "command": "rech.editor.batch.tab", 81 | "key": "tab", 82 | "when": "editorLangId == bat && !inSnippetMode && !suggestWidgetVisible" 83 | }, 84 | { 85 | "command": "rech.editor.batch.revtab", 86 | "key": "shift+tab", 87 | "when": "editorLangId == bat && !inSnippetMode" 88 | } 89 | ], 90 | "configuration": { 91 | "title": "Rech Batch", 92 | "properties": { 93 | "rech.batch.alertConflictingExtensions": { 94 | "type": "boolean", 95 | "description": "Controls whether Rech Batch extension should alert when conflicting batch extensions are detected.", 96 | "default": true 97 | }, 98 | "rech.batch.initialTabAlignment": { 99 | "type": "string", 100 | "description": "Controls the value of the initial tab size.", 101 | "default": "4", 102 | "enum": [ 103 | "off", 104 | "1", 105 | "2", 106 | "3", 107 | "4", 108 | "5", 109 | "6", 110 | "7", 111 | "8" 112 | ] 113 | }, 114 | "rech.batch.snippetsType": { 115 | "type": "string", 116 | "default": "Community", 117 | "description": "Switch to different snippets", 118 | "enum": [ 119 | "Rech Internal", 120 | "Community" 121 | ] 122 | } 123 | } 124 | } 125 | }, 126 | "scripts": { 127 | "vscode:prepublish": "npm run compile", 128 | "package": "vsce package --out F:/DIV/VSCode/extension/market/rech-editor-batch/rech-editor-batch.vsix", 129 | "compile": "npm run lint && tsc -p ./", 130 | "compile:client": "tsc -p ./", 131 | "compile:server": "tsc -p ./", 132 | "watch": "tsc -watch -p ./", 133 | "test": "npm run compile && mocha -r ts-node/register ./src/test/**/*.test.ts", 134 | "lint": "eslint -c .eslintrc.js --ext .ts ./src" 135 | }, 136 | "dependencies": { 137 | "@types/chai": "4.1.6", 138 | "@types/q": "^1.0.0", 139 | "chai": "4.2.0", 140 | "iconv-lite": "^0.4.24", 141 | "q": "^1.4.1", 142 | "rech-ts-commons": "^1.0.3", 143 | "ts-node": "^10.9.1", 144 | "vscode-languageclient": "^8.0.2", 145 | "vscode-languageserver": "^8.0.2", 146 | "vscode-languageserver-textdocument": "^1.0.7" 147 | }, 148 | "devDependencies": { 149 | "@types/mocha": "^2.2.48", 150 | "@types/node": "^8.10.25", 151 | "@types/vscode": "^1.73.1", 152 | "@typescript-eslint/eslint-plugin": "^5.45.0", 153 | "@typescript-eslint/parser": "^5.45.0", 154 | "@vscode/test-electron": "^2.2.0", 155 | "@vscode/vsce": "^2.15.0", 156 | "eslint": "^8.28.0", 157 | "eslint-config-prettier": "^8.5.0", 158 | "eslint-plugin-import": "^2.26.0", 159 | "mocha": "^10.1.0", 160 | "typescript": "^4.9.3" 161 | }, 162 | "__metadata": { 163 | "id": "55c51b52-73f6-4664-a02d-fb57088919cc", 164 | "publisherDisplayName": "rechinformatica", 165 | "publisherId": "bca70ba0-6bb8-40cf-9663-39e27de4febf" 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /snippets/bat-community.json: -------------------------------------------------------------------------------- 1 | { 2 | "if": { 3 | "prefix": "if", 4 | "body": "if \"%$1%\"==\"$2\" (\n $3\n) else (\n $4\n)", 5 | "description": "Conditional with else clause" 6 | }, 7 | "ifc": { 8 | "prefix": "ifc", 9 | "body": "if /i \"%COMPUTERNAME%\"==\"$1\" (\n $2\n)", 10 | "description": "Check computer's name of the user who is executing the Batch" 11 | }, 12 | "ife": { 13 | "prefix": "ife", 14 | "body": "if exist \"$1\" (\n $2\n) else (\n $3\n)", 15 | "description": "Check if a file exists" 16 | }, 17 | "ifeq": { 18 | "prefix": "ifeq", 19 | "body": "if $1 EQU (\n $2\n)", 20 | "description": "Check equality" 21 | }, 22 | "ifi": { 23 | "prefix": "ifi", 24 | "body": "if /i \"%$1%\"==\"$2\" (\n $3\n) else (\n $4\n)", 25 | "description": "Check equality ignoring case" 26 | }, 27 | "ifin": { 28 | "prefix": "ifin", 29 | "body": "if /i not \"%$1%\"==\"$2\" (\n $3\n) else (\n $4\n)", 30 | "description": "Check negative equality ignoring case" 31 | }, 32 | "ifg": { 33 | "prefix": "ifg", 34 | "body": "if $1 GTR $2 (\n $3\n)", 35 | "description": "If greater than" 36 | }, 37 | "ifge": { 38 | "prefix": "ifge", 39 | "body": "if $1 GEQ $2 (\n $3\n)", 40 | "description": "If greater than or equal to" 41 | }, 42 | "ifl": { 43 | "prefix": "ifl", 44 | "body": "if $1 LSS $2 (\n $3\n)", 45 | "description": "If less than" 46 | }, 47 | "iflq": { 48 | "prefix": "iflq", 49 | "body": "if $1 LEQ $2 (\n $3\n)", 50 | "description": "If less than or equal to" 51 | }, 52 | "ifn": { 53 | "prefix": "ifn", 54 | "body": "if not \"%$1%\"==\"$2\" (\n $3\n) else (\n $4\n)", 55 | "description": "Check negative equality" 56 | }, 57 | "ifne": { 58 | "prefix": "ifne", 59 | "body": "if not exist \"$1\" (\n $2\n) else (\n $3\n)", 60 | "description": "Check if a file does not exists" 61 | }, 62 | "ifnq": { 63 | "prefix": "ifnq", 64 | "body": "if $1 NEQ $2 (\n $3\n)", 65 | "description": "Check inequality" 66 | }, 67 | "ifs": { 68 | "prefix": "ifs", 69 | "body": "if \"%$1%\"==\"$2\" (\n $3\n)", 70 | "description": "Simple conditional (without else clause)" 71 | }, 72 | "ifu": { 73 | "prefix": "ifu", 74 | "body": "if /i \"%USERNAME%\"==\"${CURRENT_USERNAME}\" (\n $1\n)", 75 | "description": "Check the username of the user who is executing the Batch" 76 | }, 77 | "for": { 78 | "prefix": "for", 79 | "body": "for %%i in ($1) do (\n $2\n)", 80 | "description": "Simple loop" 81 | }, 82 | "fora": { 83 | "prefix": "fora", 84 | "body": "for /f %%i in ($1) do (\n $2\n)", 85 | "description": "Loop reading line by line from a file" 86 | }, 87 | "forc": { 88 | "prefix": "forc", 89 | "body": "for /f %%i in ('$1') do (\n $2\n)", 90 | "description": "Iterates over a command output" 91 | }, 92 | "ford": { 93 | "prefix": "ford", 94 | "body": "for /d %%i in ($1,,) do (\n $2\n)", 95 | "description": "Loop through a comma separated list" 96 | }, 97 | "fore": { 98 | "prefix": "fore", 99 | "body": "for /f \"tokens=$1 delims=,\" %%i in () do (\n $2\n)", 100 | "description": "Loop reading line by line from a file with options" 101 | }, 102 | "fori": { 103 | "prefix": "fori", 104 | "body": "for /l %%i in (1,1,$1) do (\n $2\n)", 105 | "description": "Incremental for loop" 106 | }, 107 | "forl": { 108 | "prefix": "forl", 109 | "body": "for /l %%i in (0,0,0) do (\n $1\n)", 110 | "description": "Infinite loop" 111 | }, 112 | "forn": { 113 | "prefix": "forn", 114 | "body": "for /f \"skip=$1 tokens= delims=,\" %%i in () do (\n $2\n)", 115 | "description": "Loop reading line by line from a file with options, ignoring the first n lines" 116 | }, 117 | "foro": { 118 | "prefix": "foro", 119 | "body": "for %%a in (\"$1\" \"\" \"\" \"\") do (\n if /i \"%1\"==%%a (\n $2\n )\n)", 120 | "description": "Loop for simulating a logical OR" 121 | }, 122 | "forv": { 123 | "prefix": "forv", 124 | "body": "for /f %%i in ('$1') do set VARIABLE=%%i", 125 | "description": "Set a variable with the content of a command output" 126 | }, 127 | "goeof": { 128 | "prefix": "goeof", 129 | "body": "goto :eof", 130 | "description": "Command goto to go out of a label (goto :eof)" 131 | }, 132 | "delay": { 133 | "prefix": "delay", 134 | "body": "setlocal EnableDelayedExpansion", 135 | "description": "Enable the delayed environment variable expansion mode" 136 | }, 137 | "enable": { 138 | "prefix": "enable", 139 | "body": "setlocal EnableDelayedExpansion", 140 | "description": "Enable the delayed environment variable expansion mode" 141 | }, 142 | "replace": { 143 | "prefix": "replace", 144 | "body": " set VAR=%VAR:$1=%", 145 | "description": "Change the content of a variable" 146 | }, 147 | "substring": { 148 | "prefix": "substring", 149 | "body": ":~d,n", 150 | "description": "Substring skipping (d) characters and extracting the next (n)" 151 | }, 152 | "substringd": { 153 | "prefix": "substringd", 154 | "body": ":~d", 155 | "description": "Substring skipping (d) characters and extracting until the end" 156 | } 157 | } -------------------------------------------------------------------------------- /src/lsp/server.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | "use strict"; 6 | 7 | import { 8 | createConnection, 9 | TextDocuments, 10 | ProposedFeatures, 11 | DidChangeConfigurationNotification, 12 | Location, 13 | InitializeParams, 14 | ReferenceParams, 15 | RenameParams, 16 | TextDocumentPositionParams, 17 | CompletionItem, 18 | ResponseError, 19 | ErrorCodes, 20 | WorkspaceEdit, 21 | TextEdit, 22 | Range, 23 | Position, 24 | TextDocumentSyncKind 25 | } from "vscode-languageserver/node"; 26 | 27 | import { TextDocument } from 'vscode-languageserver-textdocument'; 28 | 29 | import { BufferSplitter, WordFinder } from "rech-ts-commons"; 30 | import { BatchDeclarationFinder } from "./BatchDeclarationFinder"; 31 | import { BatchReferencesFinder } from "./BatchReferencesFinder"; 32 | 33 | // Create a connection for the server. The connection uses Node's IPC as a transport. 34 | // Also include all preview / proposed LSP features. 35 | const connection = createConnection(ProposedFeatures.all); 36 | const documents = new TextDocuments(TextDocument); 37 | 38 | connection.onInitialize(async (_params: InitializeParams) => { 39 | return { 40 | capabilities: { 41 | textDocumentSync: TextDocumentSyncKind.Incremental, 42 | definitionProvider: true, 43 | referencesProvider: true, 44 | renameProvider: true, 45 | } 46 | }; 47 | }); 48 | 49 | /** 50 | * Retrun if the character that has been typed is a enter 51 | * 52 | * @param ch 53 | */ 54 | export function hasTypedEnter(ch: string) { 55 | return ch == "\n"; 56 | } 57 | 58 | connection.onInitialized(() => { 59 | // Register for all configuration changes. 60 | void connection.client.register( 61 | DidChangeConfigurationNotification.type, 62 | undefined 63 | ); 64 | }); 65 | 66 | connection.onDefinition((params: TextDocumentPositionParams): Thenable | undefined> => { 67 | return new Promise((resolve, reject) => { 68 | const fullDocument = documents.get(params.textDocument.uri); 69 | if (fullDocument) { 70 | const text = fullDocument.getText(); 71 | const word = getLineText(text, params.position.line, params.position.character); 72 | new BatchDeclarationFinder(text) 73 | .findDeclaration(word, params.textDocument.uri) 74 | .then((location) => resolve(location)) 75 | .catch(() => resolve(undefined)); 76 | } else { 77 | reject(new ResponseError(ErrorCodes.UnknownErrorCode, "Error to find declaration")); 78 | } 79 | }) 80 | }); 81 | 82 | connection.onReferences((params: ReferenceParams): Thenable | undefined> => { 83 | return new Promise((resolve, reject) => { 84 | const fullDocument = documents.get(params.textDocument.uri); 85 | if (fullDocument) { 86 | const text = fullDocument.getText(); 87 | const word = getLineText(text, params.position.line, params.position.character); 88 | new BatchReferencesFinder(text) 89 | .findReferences(word, params.textDocument.uri) 90 | .then((locations) => resolve(locations)) 91 | .catch(() => reject(undefined)); 92 | } else { 93 | return reject(new ResponseError(ErrorCodes.UnknownErrorCode, "Error to find references")); 94 | } 95 | }); 96 | }); 97 | 98 | connection.onRenameRequest((params: RenameParams): Thenable | undefined> => { 99 | return new Promise((resolve, reject) => { 100 | const fullDocument = documents.get(params.textDocument.uri); 101 | if (fullDocument) { 102 | const text = fullDocument.getText(); 103 | const word = getLineText(text, params.position.line, params.position.character); 104 | new BatchReferencesFinder(text) 105 | .findReferences(word, params.textDocument.uri) 106 | .then((locations) => { 107 | const textEdits: TextEdit[] = convertLocationsToTextEdits(locations, word, params.newName); 108 | resolve({ changes: { [params.textDocument.uri]: textEdits } }) 109 | }).catch(() => resolve(undefined)); 110 | } else { 111 | reject(new ResponseError(ErrorCodes.UnknownErrorCode, "Error to rename")); 112 | } 113 | }); 114 | 115 | }); 116 | 117 | /** 118 | * Converts the specified Location array into an TextEdit array 119 | * 120 | * @param locations locations to be converted 121 | */ 122 | export function convertLocationsToTextEdits(locations: Location[], oldName: string, newName: string): TextEdit[] { 123 | const textEdits: TextEdit[] = []; 124 | locations.forEach((currentLocation) => { 125 | const line = currentLocation.range.start.line; 126 | const column = currentLocation.range.start.character; 127 | textEdits.push({ 128 | newText: newName, 129 | range: Range.create( 130 | Position.create(line, column), 131 | Position.create(line, column + oldName.length)) 132 | }); 133 | }); 134 | return textEdits; 135 | } 136 | 137 | /** 138 | * Returns the specified line within the document text 139 | * 140 | * @param documentText document text 141 | * @param line line 142 | * @param column column 143 | */ 144 | export function getLineText( 145 | documentText: string, 146 | line: number, 147 | column: number 148 | ) { 149 | const currentLine = BufferSplitter.split(documentText)[line]; 150 | const batchRegEx = /([a-zA-Z0-9_\-])+/g 151 | const word = WordFinder.findWordWithRegex(currentLine, column, batchRegEx); 152 | return word; 153 | } 154 | 155 | 156 | // Make the text document manager listen on the connection 157 | // for open, change and close text document events 158 | documents.listen(connection); 159 | // Listen on the connection 160 | connection.listen(); 161 | 162 | // This handler resolve additional information for the item selected in 163 | // the completion list. 164 | connection.onCompletionResolve( 165 | (item: CompletionItem): CompletionItem => { 166 | return item; 167 | } 168 | ); 169 | -------------------------------------------------------------------------------- /src/bat/TabStopper.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { Position, Range, TextDocument, TextEditor, TextEditorEdit, Selection, window, WorkspaceConfiguration, workspace } from 'vscode'; 4 | 5 | /** 6 | * Class used to modify the Tab Stop tipically used with Batch files. 7 | * 8 | * Originally extracted from https://github.com/spgennard/vscode_cobol/blob/ae519156bf569742b4cd0e81e5ed252369c89ecd/src/tabstopper.ts 9 | */ 10 | export class TabStopper { 11 | 12 | /** 13 | * Processes the Tab or Reverse-tab with the specified stops 14 | * 15 | * @param inserting true if needs to insert tab 16 | */ 17 | public processTabKey(inserting: boolean) { 18 | const editor = window.activeTextEditor; 19 | if (editor) { 20 | const doc = editor.document; 21 | const sel = editor.selections; 22 | this.executeTab(editor, doc, sel, inserting); 23 | } 24 | } 25 | 26 | /** 27 | * Return the first two tab stops according to the configuration and default values 28 | * 29 | * @return {number[]} 30 | */ 31 | private getTabs(): number[] { 32 | return [0, this.getInitialTabAlignment()]; 33 | } 34 | 35 | /** 36 | * Executes the tab insertion or removal 37 | * 38 | * @param editor text editor 39 | * @param doc current document 40 | * @param sel selection 41 | * @param inserting boolean indicating whether the editor is inserting or removing a tab 42 | */ 43 | private executeTab(editor: TextEditor, doc: TextDocument, sel: readonly Selection[], inserting: boolean) { 44 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 45 | editor.edit(edit => { 46 | for (var x = 0; x < sel.length; x++) { 47 | if (sel[x].start.line === sel[x].end.line) { 48 | const position = sel[x].start; 49 | if (inserting) { 50 | this.singleSelectionTab(edit, position); 51 | } else { 52 | this.singleSelectionUnTab(edit, doc, position); 53 | } 54 | } else { 55 | if (inserting) { 56 | this.multipleSelectionTab(edit, sel[x]); 57 | } else { 58 | this.multipleSelectionUnTab(edit, doc, sel[x]); 59 | } 60 | } 61 | } 62 | }); 63 | } 64 | 65 | /** 66 | * Inserts a single selection tab 67 | * 68 | * @param edit text editor 69 | * @param pos position to insert the tab 70 | */ 71 | private singleSelectionTab(edit: TextEditorEdit, pos: Position) { 72 | const size = this.tabSize(pos.character); 73 | edit.insert(pos, ' '.repeat(size)); 74 | } 75 | 76 | /** 77 | * Get the value of the active text editor's tab size 78 | * 79 | * @return {number} Tab size 80 | */ 81 | private getEditorTabValue(): number { 82 | const codeTabSizeConfiguration = window.activeTextEditor?.options.tabSize; 83 | var codeTabSize: number; 84 | if (codeTabSizeConfiguration == undefined) { 85 | codeTabSize = 3 86 | } else { 87 | codeTabSize = Number(codeTabSizeConfiguration); 88 | } 89 | return codeTabSize; 90 | } 91 | 92 | /** 93 | * Removes a single selecton tab 94 | * 95 | * @param edit text editor 96 | * @param doc current document 97 | * @param pos position to insert the tab 98 | */ 99 | private singleSelectionUnTab(edit: TextEditorEdit, doc: TextDocument, pos: Position) { 100 | const size = this.unTabSize(pos.character); 101 | const range = new Range(pos.line, pos.character - size, pos.line, pos.character); 102 | const txt = doc.getText(range); 103 | if (txt === ' '.repeat(size)) { 104 | edit.delete(range); 105 | } 106 | } 107 | 108 | /** 109 | * Performs multiple tab selecton 110 | * 111 | * @param edit editor 112 | * @param sel selection 113 | */ 114 | private multipleSelectionTab(edit: TextEditorEdit, sel: Selection) { 115 | for (let line = sel.start.line; line <= sel.end.line; line++) { 116 | const pos = new Position(line, sel.start.character); 117 | this.singleSelectionTab(edit, pos); 118 | } 119 | } 120 | 121 | /** 122 | * Performs ubtab with multiple selecions 123 | * 124 | * @param edit current text editor 125 | * @param doc text document 126 | * @param selection selection 127 | */ 128 | private multipleSelectionUnTab(edit: TextEditorEdit, doc: TextDocument, selection: Selection) { 129 | for (let line = selection.start.line; line <= selection.end.line; line++) { 130 | var charpos = selection.start.character; 131 | if (charpos === 0) { 132 | const pttrn = /^\s*/; 133 | const selline = doc.getText(selection); 134 | if (selline !== null) { 135 | const match = selline.match(pttrn); 136 | if (match !== null) { 137 | charpos = match[0].length; 138 | } 139 | } 140 | } 141 | const pos = new Position(line, charpos); 142 | this.singleSelectionUnTab(edit, doc, pos); 143 | } 144 | } 145 | 146 | /** 147 | * Returns the tab size 148 | * 149 | * @param pos current position 150 | * @return {number} 151 | */ 152 | private tabSize(pos: number): number { 153 | const tabs = this.getTabs(); 154 | var tab = 0; 155 | for (var index = 0; index < tabs.length; index++) { 156 | tab = tabs[index]; 157 | 158 | if (tab > pos) { 159 | return tab - pos; 160 | } 161 | } 162 | // outside range? 163 | const tabValue = this.getEditorTabValue(); 164 | return tabValue - ((pos - tabs[tabs.length - 1]) % tabValue); 165 | } 166 | 167 | /** 168 | * Get the value of the initial tab alignment according to the configuration or the active editor tab value 169 | * 170 | * @return {number} Initial tab alignment 171 | */ 172 | private getInitialTabAlignment(): number { 173 | const tabConfigString = this.settingsGroup().get("initialTabAlignment", "4"); 174 | if (tabConfigString == "off") { 175 | return this.getEditorTabValue(); 176 | } 177 | return Number(tabConfigString); 178 | } 179 | 180 | /** 181 | * Returns the untab size 182 | * 183 | * @param pos current position 184 | * @return {number} 185 | */ 186 | private unTabSize(pos: number): number { 187 | const tabs = this.getTabs(); 188 | if (pos > tabs[tabs.length - 1]) { 189 | const tabSize = this.getEditorTabValue(); 190 | if ((pos - tabs[tabs.length - 1]) % tabSize === 0) { 191 | return tabSize; 192 | } 193 | return (pos - tabs[tabs.length - 1]) % tabSize; 194 | } 195 | for (var index = tabs.length - 1; index > -1; index--) { 196 | const tab = tabs[index]; 197 | if (tab < pos) { 198 | return pos - tab; 199 | } 200 | } 201 | return 0; 202 | } 203 | 204 | /** 205 | * Return the settings group of Rech Batch extension 206 | * 207 | * @return {WorkspaceConfiguration} 208 | */ 209 | private settingsGroup(): WorkspaceConfiguration { 210 | return workspace.getConfiguration("rech.batch"); 211 | } 212 | 213 | } 214 | 215 | -------------------------------------------------------------------------------- /snippets/bat.json: -------------------------------------------------------------------------------- 1 | { 2 | "if": { 3 | "prefix": "if", 4 | "body": "if \"%$1%\"==\"$2\" (\n $3\n) else (\n $4\n)", 5 | "description": "Teste com else" 6 | }, 7 | "ifc": { 8 | "prefix": "ifc", 9 | "body": "if /i \"%COMPUTERNAME%\"==\"$1\" (\n $2\n)", 10 | "description": "Teste do nome do computador que está executando o Batch" 11 | }, 12 | "ife": { 13 | "prefix": "ife", 14 | "body": "if exist \"$1\" (\n $2\n) else (\n $3\n)", 15 | "description": "Teste de existência de arquivo" 16 | }, 17 | "ifeq": { 18 | "prefix": "ifeq", 19 | "body": "if $1 EQU (\n $2\n)", 20 | "description": "Teste de igualdade" 21 | }, 22 | "ifi": { 23 | "prefix": "ifi", 24 | "body": "if /i \"%$1%\"==\"$2\" (\n $3\n) else (\n $4\n)", 25 | "description": "Teste ignorando maiúsculas/minúsculas" 26 | }, 27 | "ifin": { 28 | "prefix": "ifin", 29 | "body": "if /i not \"%$1%\"==\"$2\" (\n $3\n) else (\n $4\n)", 30 | "description": "Teste com negação ignorando maiúsculas/minúsculas" 31 | }, 32 | "ifg": { 33 | "prefix": "ifg", 34 | "body": "if $1 GTR $2 (\n $3\n)", 35 | "description": "Teste se maior que" 36 | }, 37 | "ifge": { 38 | "prefix": "ifge", 39 | "body": "if $1 GEQ $2 (\n $3\n)", 40 | "description": "Teste se maior ou igual que" 41 | }, 42 | "ifl": { 43 | "prefix": "ifl", 44 | "body": "if $1 LSS $2 (\n $3\n)", 45 | "description": "Teste de menor que" 46 | }, 47 | "iflq": { 48 | "prefix": "iflq", 49 | "body": "if $1 LEQ $2 (\n $3\n)", 50 | "description": "Teste de menor ou igual que" 51 | }, 52 | "ifn": { 53 | "prefix": "ifn", 54 | "body": "if not \"%$1%\"==\"$2\" (\n $3\n) else (\n $4\n)", 55 | "description": "Teste com negação" 56 | }, 57 | "ifne": { 58 | "prefix": "ifne", 59 | "body": "if not exist \"$1\" (\n $2\n) else (\n $3\n)", 60 | "description": "Teste de não existência de arquivo" 61 | }, 62 | "ifnq": { 63 | "prefix": "ifnq", 64 | "body": "if $1 NEQ $2 (\n $3\n)", 65 | "description": "Teste de desigualdade" 66 | }, 67 | "ifs": { 68 | "prefix": "ifs", 69 | "body": "if \"%$1%\"==\"$2\" (\n $3\n)", 70 | "description": "Teste simples (sem o else)" 71 | }, 72 | "ifu": { 73 | "prefix": "ifu", 74 | "body": "if /i \"%USERNAME%\"==\"${CURRENT_USERNAME}\" (\n $1\n)", 75 | "description": "Teste do nome do usuário que está executando o Batch" 76 | }, 77 | "ini": { 78 | "prefix": "ini", 79 | "body": "@echo off\nrem\nrem ####################################################################################################################\nrem *** $1\nrem --------------------------------------------------------------------------------------------------------------------\n setlocal", 80 | "description": "Comentários e comandos iniciais do Batch" 81 | }, 82 | "fin": { 83 | "prefix": "fin", 84 | "body": "rem\nrem Finalização.........................................................................................................\n:Fim\n endlocal\n", 85 | "description": "Label de finalização do Batch" 86 | }, 87 | "for": { 88 | "prefix": "for", 89 | "body": "for %%i in ($1) do (\n $2\n)", 90 | "description": "Laço padrão" 91 | }, 92 | "fora": { 93 | "prefix": "fora", 94 | "body": "for /f %%i in ($1) do (\n $2\n)", 95 | "description": "Laço para leitura linha a linha de arquivo" 96 | }, 97 | "forc": { 98 | "prefix": "forc", 99 | "body": "for /f %%i in ('$1') do (\n $2\n)", 100 | "description": "Varre o resultado da saída de um comando" 101 | }, 102 | "ford": { 103 | "prefix": "ford", 104 | "body": "for /d %%i in ($1,,) do (\n $2\n)", 105 | "description": "Laço em lista delimitada por vírgula" 106 | }, 107 | "fore": { 108 | "prefix": "fore", 109 | "body": "for /f \"tokens=$1 delims=,\" %%i in () do (\n $2\n)", 110 | "description": "Laço para leitura linha a linha de arquivo com opções" 111 | }, 112 | "fori": { 113 | "prefix": "fori", 114 | "body": "for /l %%i in (1,1,$1) do (\n $2\n)", 115 | "description": "Laço utilizando incremento" 116 | }, 117 | "forl": { 118 | "prefix": "forl", 119 | "body": "for /l %%i in (0,0,0) do (\n $1\n)", 120 | "description": "Laço infinito" 121 | }, 122 | "forn": { 123 | "prefix": "forn", 124 | "body": "for /f \"skip=$1 tokens= delims=,\" %%i in () do (\n $2\n)", 125 | "description": "Laço para leitura linha a linha de arquivo com opções, ignorando as primeiras n linhas" 126 | }, 127 | "foro": { 128 | "prefix": "foro", 129 | "body": "for %%a in (\"$1\" \"\" \"\" \"\") do (\n if /i \"%1\"==%%a (\n $2\n )\n)", 130 | "description": "Laço para simular teste com OR" 131 | }, 132 | "forv": { 133 | "prefix": "forv", 134 | "body": "for /f %%i in ('$1') do set VARIAVEL=%%i", 135 | "description": "Resultado da saída de um comando em uma variável" 136 | }, 137 | "goeof": { 138 | "prefix": "goeof", 139 | "body": "goto :eof", 140 | "description": "Faz goto para sair de um label (goto :eof)" 141 | }, 142 | "gofim": { 143 | "prefix": "gofim", 144 | "body": "goto :Fim", 145 | "description": "Faz goto para o fim do batch (goto :Fim)" 146 | }, 147 | "delay": { 148 | "prefix": "delay", 149 | "body": "setlocal EnableDelayedExpansion", 150 | "description": "Habilita a expansão de variáveis de ambiente atrasada" 151 | }, 152 | "enable": { 153 | "prefix": "enable", 154 | "body": "setlocal EnableDelayedExpansion", 155 | "description": "Habilita a expansão de variáveis de ambiente atrasada" 156 | }, 157 | "log": { 158 | "prefix": "log", 159 | "body": "call GravaLog.bat %0 %*", 160 | "description": "Grava log de execução do batch" 161 | }, 162 | "replace": { 163 | "prefix": "replace", 164 | "body": " set VAR=%VAR:$1=%", 165 | "description": "Faz troca de conteúdo em uma variável" 166 | }, 167 | "substring": { 168 | "prefix": "substring", 169 | "body": ":~d,n", 170 | "description": "Substring de um deslocamento (d) tantos caracteres (n)" 171 | }, 172 | "substringd": { 173 | "prefix": "substringd", 174 | "body": ":~d", 175 | "description": "Substring de um deslocamento (d) até o fim da String" 176 | }, 177 | "thelp": { 178 | "prefix": "thelp", 179 | "body": "rem Opções de exibição de ajuda\n for %%a in (\"\" \"/H\" \"/Help\" \"Help\") do if /i \"%~1\"==%%a (goto :Help)", 180 | "description": "Teste opções de ajuda" 181 | }, 182 | "help": { 183 | "prefix": "Help", 184 | "body": "rem\nrem Exibe Ajuda.........................................................................................................\n:Help\n echo Insira aqui a descrição do que faz o batch\n echo.\n echo Sintaxe: NOME_BATCH VERSAO OUTROS_PARAMETROS_OBRIGATORIOS [PAR_OPCIONAL_1] [PAR_OPCIONAL_2]\n echo.\n echo VERSAO = Número, Sigla ou Abreviação da Versão do SIGER\n echo OUTROS_PARAMETROS_1 = Ajuda do parâmetro 1\n echo OUTROS_PARAMETROS_2 = Ajuda do parâmetro 2\n echo PAR_OPCIONAL_1 = Ajuda do parâmetro opcional 1\n echo PAR_OPCIONAL_2 = Ajuda do parâmetro opcional 2\n echo.\n echo Exemplos:\n echo -----------\n echo NOME_BATCH PARAMETROS\n echo Explicação do que faz o comando\n echo.\n echo NOME_BATCH PARAMETROS\n echo Explicação do que faz o comando\n echo.\n goto :Fim", 185 | "description": "Exibe ajuda" 186 | } 187 | } -------------------------------------------------------------------------------- /syntaxes/batchfile.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "information_for_contributors": [ 3 | "This file has been converted from https://github.com/mmims/language-batchfile/blob/master/grammars/batchfile.cson", 4 | "If you want to provide a fix or improvement, please create a pull request against the original repository.", 5 | "Once accepted there, we are happy to receive an update request." 6 | ], 7 | "version": "https://github.com/mmims/language-batchfile/commit/95ea8c699f7a8296b15767069868532d52631c46", 8 | "name": "Batch File", 9 | "scopeName": "source.batchfile", 10 | "patterns": [ 11 | { 12 | "include": "#commands" 13 | }, 14 | { 15 | "include": "#comments" 16 | }, 17 | { 18 | "include": "#constants" 19 | }, 20 | { 21 | "include": "#controls" 22 | }, 23 | { 24 | "include": "#escaped_characters" 25 | }, 26 | { 27 | "include": "#labels" 28 | }, 29 | { 30 | "include": "#numbers" 31 | }, 32 | { 33 | "include": "#operators" 34 | }, 35 | { 36 | "include": "#parens" 37 | }, 38 | { 39 | "include": "#strings" 40 | }, 41 | { 42 | "include": "#variables" 43 | } 44 | ], 45 | "repository": { 46 | "commands": { 47 | "patterns": [ 48 | { 49 | "match": "(?<=^|[\\s@])(?i:adprep|append|arp|assoc|at|atmadm|attrib|auditpol|autochk|autoconv|autofmt|bcdboot|bcdedit|bdehdcfg|bitsadmin|bootcfg|brea|cacls|cd|certreq|certutil|change|chcp|chdir|chglogon|chgport|chgusr|chkdsk|chkntfs|choice|cipher|clip|cls|clscluadmin|cluster|cmd|cmdkey|cmstp|color|comp|compact|convert|copy|cprofile|cscript|csvde|date|dcdiag|dcgpofix|dcpromo|defra|del|dfscmd|dfsdiag|dfsrmig|diantz|dir|dirquota|diskcomp|diskcopy|diskpart|diskperf|diskraid|diskshadow|dispdiag|doin|dnscmd|doskey|driverquery|dsacls|dsadd|dsamain|dsdbutil|dsget|dsmgmt|dsmod|dsmove|dsquery|dsrm|edit|endlocal|eraseesentutl|eventcreate|eventquery|eventtriggers|evntcmd|expand|extract|fc|filescrn|find|findstr|finger|flattemp|fonde|forfiles|format|freedisk|fsutil|ftp|ftype|fveupdate|getmac|gettype|gpfixup|gpresult|gpupdate|graftabl|hashgen|hep|helpctr|hostname|icacls|iisreset|inuse|ipconfig|ipxroute|irftp|ismserv|jetpack|klist|ksetup|ktmutil|ktpass|label|ldifd|ldp|lodctr|logman|logoff|lpq|lpr|macfile|makecab|manage-bde|mapadmin|md|mkdir|mklink|mmc|mode|more|mount|mountvol|move|mqbup|mqsvc|mqtgsvc|msdt|msg|msiexec|msinfo32|mstsc|nbtstat|net computer|net group|net localgroup|net print|net session|net share|net start|net stop|net use|net user|net view|net|netcfg|netdiag|netdom|netsh|netstat|nfsadmin|nfsshare|nfsstat|nlb|nlbmgr|nltest|nslookup|ntackup|ntcmdprompt|ntdsutil|ntfrsutl|openfiles|pagefileconfig|path|pathping|pause|pbadmin|pentnt|perfmon|ping|pnpunatten|pnputil|popd|powercfg|powershell|powershell_ise|print|prncnfg|prndrvr|prnjobs|prnmngr|prnport|prnqctl|prompt|pubprn|pushd|pushprinterconnections|pwlauncher|qappsrv|qprocess|query|quser|qwinsta|rasdial|rcp|rd|rdpsign|regentc|recover|redircmp|redirusr|reg|regini|regsvr32|relog|ren|rename|rendom|repadmin|repair-bde|replace|reset session|rxec|risetup|rmdir|robocopy|route|rpcinfo|rpcping|rsh|runas|rundll32|rwinsta|scp|sc|schtasks|scwcmd|secedit|serverceipoptin|servrmanagercmd|serverweroptin|setspn|setx|sfc|shadow|shift|showmount|shutdown|sort|ssh|start|storrept|subst|sxstrace|ysocmgr|systeminfo|takeown|tapicfg|taskkill|tasklist|tcmsetup|telnet|tftp|time|timeout|title|tlntadmn|tpmvscmgr|tpmvscmgr|tacerpt|tracert|tree|tscon|tsdiscon|tsecimp|tskill|tsprof|type|typeperf|tzutil|uddiconfig|umount|unlodctr|ver|verifier|verif|vol|vssadmin|w32tm|waitfor|wbadmin|wdsutil|wecutil|wevtutil|where|whoami|winnt|winnt32|winpop|winrm|winrs|winsat|wlbs|mic|wscript|xcopy)(?=$|\\s)", 50 | "name": "keyword.command.batchfile" 51 | }, 52 | { 53 | "begin": "(?i)(?<=^|[\\s@])(echo)(?:(?=$|\\.|:)|\\s+(?:(on|off)(?=\\s*$))?)", 54 | "beginCaptures": { 55 | "1": { 56 | "name": "keyword.command.batchfile" 57 | }, 58 | "2": { 59 | "name": "keyword.other.special-method.batchfile" 60 | } 61 | }, 62 | "end": "(?=$\\n|[&|><)])", 63 | "patterns": [ 64 | { 65 | "include": "#escaped_characters" 66 | }, 67 | { 68 | "include": "#variables" 69 | }, 70 | { 71 | "include": "#numbers" 72 | }, 73 | { 74 | "include": "#strings" 75 | } 76 | ] 77 | }, 78 | { 79 | "match": "(?i)(?<=^|[\\s@])(setlocal)(?:\\s*$|\\s+(EnableExtensions|DisableExtensions|EnableDelayedExpansion|DisableDelayedExpansion)(?=\\s*$))", 80 | "captures": { 81 | "1": { 82 | "name": "keyword.command.batchfile" 83 | }, 84 | "2": { 85 | "name": "keyword.other.special-method.batchfile" 86 | } 87 | } 88 | }, 89 | { 90 | "include": "#command_set" 91 | } 92 | ] 93 | }, 94 | "command_set": { 95 | "patterns": [ 96 | { 97 | "begin": "(?<=^|[\\s@])(?i:SET)(?=$|\\s)", 98 | "beginCaptures": { 99 | "0": { 100 | "name": "keyword.command.batchfile" 101 | } 102 | }, 103 | "end": "(?=$\\n|[&|><)])", 104 | "patterns": [ 105 | { 106 | "include": "#command_set_inside" 107 | } 108 | ] 109 | } 110 | ] 111 | }, 112 | "command_set_inside": { 113 | "patterns": [ 114 | { 115 | "include": "#escaped_characters" 116 | }, 117 | { 118 | "include": "#variables" 119 | }, 120 | { 121 | "include": "#numbers" 122 | }, 123 | { 124 | "include": "#parens" 125 | }, 126 | { 127 | "include": "#command_set_strings" 128 | }, 129 | { 130 | "include": "#strings" 131 | }, 132 | { 133 | "begin": "([^ ][^=]*)(=)", 134 | "beginCaptures": { 135 | "1": { 136 | "name": "variable.other.readwrite.batchfile" 137 | }, 138 | "2": { 139 | "name": "keyword.operator.assignment.batchfile" 140 | } 141 | }, 142 | "end": "(?=$\\n|[&|><)])", 143 | "patterns": [ 144 | { 145 | "include": "#escaped_characters" 146 | }, 147 | { 148 | "include": "#variables" 149 | }, 150 | { 151 | "include": "#numbers" 152 | }, 153 | { 154 | "include": "#parens" 155 | }, 156 | { 157 | "include": "#strings" 158 | } 159 | ] 160 | }, 161 | { 162 | "begin": "\\s+/[aA]\\s+", 163 | "end": "(?=$\\n|[&|><)])", 164 | "name": "meta.expression.set.batchfile", 165 | "patterns": [ 166 | { 167 | "begin": "\"", 168 | "beginCaptures": { 169 | "0": { 170 | "name": "punctuation.definition.string.begin.batchfile" 171 | } 172 | }, 173 | "end": "\"", 174 | "endCaptures": { 175 | "0": { 176 | "name": "punctuation.definition.string.end.batchfile" 177 | } 178 | }, 179 | "name": "string.quoted.double.batchfile", 180 | "patterns": [ 181 | { 182 | "include": "#command_set_inside_arithmetic" 183 | }, 184 | { 185 | "include": "#command_set_group" 186 | }, 187 | { 188 | "include": "#variables" 189 | } 190 | ] 191 | }, 192 | { 193 | "include": "#command_set_inside_arithmetic" 194 | }, 195 | { 196 | "include": "#command_set_group" 197 | } 198 | ] 199 | }, 200 | { 201 | "begin": "\\s+/[pP]\\s+", 202 | "end": "(?=$\\n|[&|><)])", 203 | "patterns": [ 204 | { 205 | "include": "#command_set_strings" 206 | }, 207 | { 208 | "begin": "([^ ][^=]*)(=)", 209 | "beginCaptures": { 210 | "1": { 211 | "name": "variable.other.readwrite.batchfile" 212 | }, 213 | "2": { 214 | "name": "keyword.operator.assignment.batchfile" 215 | } 216 | }, 217 | "end": "(?=$\\n|[&|><)])", 218 | "name": "meta.prompt.set.batchfile", 219 | "patterns": [ 220 | { 221 | "include": "#strings" 222 | } 223 | ] 224 | } 225 | ] 226 | } 227 | ] 228 | }, 229 | "command_set_group": { 230 | "patterns": [ 231 | { 232 | "begin": "\\(", 233 | "beginCaptures": { 234 | "0": { 235 | "name": "punctuation.section.group.begin.batchfile" 236 | } 237 | }, 238 | "end": "\\)", 239 | "endCaptures": { 240 | "0": { 241 | "name": "punctuation.section.group.end.batchfile" 242 | } 243 | }, 244 | "patterns": [ 245 | { 246 | "include": "#command_set_inside_arithmetic" 247 | } 248 | ] 249 | } 250 | ] 251 | }, 252 | "command_set_inside_arithmetic": { 253 | "patterns": [ 254 | { 255 | "include": "#command_set_operators" 256 | }, 257 | { 258 | "include": "#numbers" 259 | }, 260 | { 261 | "match": ",", 262 | "name": "punctuation.separator.batchfile" 263 | } 264 | ] 265 | }, 266 | "command_set_operators": { 267 | "patterns": [ 268 | { 269 | "match": "([^ ]*)(\\+\\=|\\-\\=|\\*\\=|\\/\\=|%%\\=|&\\=|\\|\\=|\\^\\=|<<\\=|>>\\=)", 270 | "captures": { 271 | "1": { 272 | "name": "variable.other.readwrite.batchfile" 273 | }, 274 | "2": { 275 | "name": "keyword.operator.assignment.augmented.batchfile" 276 | } 277 | } 278 | }, 279 | { 280 | "match": "\\+|\\-|/|\\*|%%|\\||&|\\^|<<|>>|~", 281 | "name": "keyword.operator.arithmetic.batchfile" 282 | }, 283 | { 284 | "match": "!", 285 | "name": "keyword.operator.logical.batchfile" 286 | }, 287 | { 288 | "match": "([^ ][^=]*)(=)", 289 | "captures": { 290 | "1": { 291 | "name": "variable.other.readwrite.batchfile" 292 | }, 293 | "2": { 294 | "name": "keyword.operator.assignment.batchfile" 295 | } 296 | } 297 | } 298 | ] 299 | }, 300 | "command_set_strings": { 301 | "patterns": [ 302 | { 303 | "begin": "(\")\\s*([^ ][^=]*)(=)", 304 | "beginCaptures": { 305 | "1": { 306 | "name": "punctuation.definition.string.begin.batchfile" 307 | }, 308 | "2": { 309 | "name": "variable.other.readwrite.batchfile" 310 | }, 311 | "3": { 312 | "name": "keyword.operator.assignment.batchfile" 313 | } 314 | }, 315 | "end": "\"", 316 | "endCaptures": { 317 | "0": { 318 | "name": "punctuation.definition.string.end.batchfile" 319 | } 320 | }, 321 | "name": "string.quoted.double.batchfile", 322 | "patterns": [ 323 | { 324 | "include": "#variables" 325 | }, 326 | { 327 | "include": "#numbers" 328 | }, 329 | { 330 | "include": "#escaped_characters" 331 | } 332 | ] 333 | } 334 | ] 335 | }, 336 | "comments": { 337 | "patterns": [ 338 | { 339 | "begin": "(?:^|(&))\\s*(?=((?::[+=,;: ])))", 340 | "beginCaptures": { 341 | "1": { 342 | "name": "keyword.operator.conditional.batchfile" 343 | } 344 | }, 345 | "end": "\\n", 346 | "patterns": [ 347 | { 348 | "begin": "((?::[+=,;: ]))", 349 | "beginCaptures": { 350 | "1": { 351 | "name": "punctuation.definition.comment.batchfile" 352 | } 353 | }, 354 | "end": "(?=\\n)", 355 | "name": "comment.line.colon.batchfile" 356 | } 357 | ] 358 | }, 359 | { 360 | "begin": "(?<=^|[\\s@])(?i)(REM)(\\.)", 361 | "beginCaptures": { 362 | "1": { 363 | "name": "keyword.command.rem.batchfile" 364 | }, 365 | "2": { 366 | "name": "punctuation.separator.batchfile" 367 | } 368 | }, 369 | "end": "(?=$\\n|[&|><)])", 370 | "name": "comment.line.rem.batchfile" 371 | }, 372 | { 373 | "begin": "(?<=^|[\\s@])(?i:rem)\\b", 374 | "beginCaptures": { 375 | "0": { 376 | "name": "keyword.command.rem.batchfile" 377 | } 378 | }, 379 | "end": "\\n", 380 | "name": "comment.line.rem.batchfile", 381 | "patterns": [ 382 | { 383 | "match": "[><|]", 384 | "name": "invalid.illegal.unexpected-character.batchfile" 385 | } 386 | ] 387 | } 388 | ] 389 | }, 390 | "constants": { 391 | "patterns": [ 392 | { 393 | "match": "\\b(?i:NUL)\\b", 394 | "name": "constant.language.batchfile" 395 | } 396 | ] 397 | }, 398 | "controls": { 399 | "patterns": [ 400 | { 401 | "match": "(?i)(?<=^|\\s)(?:call|exit(?=$|\\s)|goto(?=$|\\s|:))", 402 | "name": "keyword.control.statement.batchfile" 403 | }, 404 | { 405 | "match": "(?<=^|\\s)(?i)(if)\\s+(?:(not)\\s+)?(exist|defined|errorlevel|cmdextversion)(?=\\s)", 406 | "captures": { 407 | "1": { 408 | "name": "keyword.control.conditional.batchfile" 409 | }, 410 | "2": { 411 | "name": "keyword.operator.logical.batchfile" 412 | }, 413 | "3": { 414 | "name": "keyword.other.special-method.batchfile" 415 | } 416 | } 417 | }, 418 | { 419 | "match": "(?<=^|\\s)(?i)(?:if|else)(?=$|\\s)", 420 | "name": "keyword.control.conditional.batchfile" 421 | }, 422 | { 423 | "match": "(?<=^|\\s)(?i)for(?=\\s)", 424 | "name": "keyword.control.repeat.batchfile" 425 | } 426 | ] 427 | }, 428 | "escaped_characters": { 429 | "patterns": [ 430 | { 431 | "match": "%%|\\^\\^!|\\^(?=.)|\\^\\n", 432 | "name": "constant.character.escape.batchfile" 433 | } 434 | ] 435 | }, 436 | "labels": { 437 | "patterns": [ 438 | { 439 | "match": "(?i)(?:^\\s*|(?<=goto)\\s*)(:)([^+=,;:\\s].*)$", 440 | "captures": { 441 | "1": { 442 | "name": "punctuation.separator.batchfile" 443 | }, 444 | "2": { 445 | "name": "keyword.other.special-method.batchfile" 446 | } 447 | } 448 | } 449 | ] 450 | }, 451 | "numbers": { 452 | "patterns": [ 453 | { 454 | "match": "(?<=^|\\s|=)(0[xX][0-9A-Fa-f]*|[+-]?\\d+)(?=$|\\s|<|>)", 455 | "name": "constant.numeric.batchfile" 456 | } 457 | ] 458 | }, 459 | "operators": { 460 | "patterns": [ 461 | { 462 | "match": "@(?=\\S)", 463 | "name": "keyword.operator.at.batchfile" 464 | }, 465 | { 466 | "match": "(?<=\\s)(?i:EQU|NEQ|LSS|LEQ|GTR|GEQ)(?=\\s)|==", 467 | "name": "keyword.operator.comparison.batchfile" 468 | }, 469 | { 470 | "match": "(?<=\\s)(?i)(NOT)(?=\\s)", 471 | "name": "keyword.operator.logical.batchfile" 472 | }, 473 | { 474 | "match": "(?[&>]?", 483 | "name": "keyword.operator.redirection.batchfile" 484 | } 485 | ] 486 | }, 487 | "parens": { 488 | "patterns": [ 489 | { 490 | "begin": "\\(", 491 | "beginCaptures": { 492 | "0": { 493 | "name": "punctuation.section.group.begin.batchfile" 494 | } 495 | }, 496 | "end": "\\)", 497 | "endCaptures": { 498 | "0": { 499 | "name": "punctuation.section.group.end.batchfile" 500 | } 501 | }, 502 | "name": "meta.group.batchfile", 503 | "patterns": [ 504 | { 505 | "match": ",|;", 506 | "name": "punctuation.separator.batchfile" 507 | }, 508 | { 509 | "include": "$self" 510 | } 511 | ] 512 | } 513 | ] 514 | }, 515 | "strings": { 516 | "patterns": [ 517 | { 518 | "begin": "\"", 519 | "beginCaptures": { 520 | "0": { 521 | "name": "punctuation.definition.string.begin.batchfile" 522 | } 523 | }, 524 | "end": "(\")|(\\n)", 525 | "endCaptures": { 526 | "1": { 527 | "name": "punctuation.definition.string.end.batchfile" 528 | }, 529 | "2": { 530 | "name": "invalid.illegal.newline.batchfile" 531 | } 532 | }, 533 | "name": "string.quoted.double.batchfile", 534 | "patterns": [ 535 | { 536 | "match": "%%", 537 | "name": "constant.character.escape.batchfile" 538 | }, 539 | { 540 | "include": "#variables" 541 | } 542 | ] 543 | } 544 | ] 545 | }, 546 | "variables": { 547 | "patterns": [ 548 | { 549 | "match": "(%)((~([fdpnxsatz]|\\$PATH:)*)?\\d|\\*)", 550 | "captures": { 551 | "1": { 552 | "name": "punctuation.definition.variable.batchfile" 553 | }, 554 | "2": { 555 | "name": "variable.parameter.batchfile" 556 | } 557 | } 558 | }, 559 | { 560 | "include": "#variable" 561 | }, 562 | { 563 | "include": "#variable_delayed_expansion" 564 | } 565 | ] 566 | }, 567 | "variable": { 568 | "patterns": [ 569 | { 570 | "begin": "%(?=[^%]+%)", 571 | "beginCaptures": { 572 | "0": { 573 | "name": "punctuation.definition.variable.begin.batchfile" 574 | } 575 | }, 576 | "end": "(%)|\\n", 577 | "endCaptures": { 578 | "1": { 579 | "name": "punctuation.definition.variable.end.batchfile" 580 | } 581 | }, 582 | "name": "variable.other.readwrite.batchfile", 583 | "patterns": [ 584 | { 585 | "begin": ":~", 586 | "beginCaptures": { 587 | "0": { 588 | "name": "punctuation.separator.batchfile" 589 | } 590 | }, 591 | "end": "(?=%|\\n)", 592 | "name": "meta.variable.substring.batchfile", 593 | "patterns": [ 594 | { 595 | "include": "#variable_substring" 596 | } 597 | ] 598 | }, 599 | { 600 | "begin": ":", 601 | "beginCaptures": { 602 | "0": { 603 | "name": "punctuation.separator.batchfile" 604 | } 605 | }, 606 | "end": "(?=%|\\n)", 607 | "name": "meta.variable.substitution.batchfile", 608 | "patterns": [ 609 | { 610 | "include": "#variable_replace" 611 | }, 612 | { 613 | "begin": "=", 614 | "beginCaptures": { 615 | "0": { 616 | "name": "punctuation.separator.batchfile" 617 | } 618 | }, 619 | "end": "(?=%|\\n)", 620 | "patterns": [ 621 | { 622 | "include": "#variable_delayed_expansion" 623 | }, 624 | { 625 | "match": "[^%]+", 626 | "name": "string.unquoted.batchfile" 627 | } 628 | ] 629 | } 630 | ] 631 | } 632 | ] 633 | } 634 | ] 635 | }, 636 | "variable_delayed_expansion": { 637 | "patterns": [ 638 | { 639 | "begin": "!(?=[^!]+!)", 640 | "beginCaptures": { 641 | "0": { 642 | "name": "punctuation.definition.variable.begin.batchfile" 643 | } 644 | }, 645 | "end": "(!)|\\n", 646 | "endCaptures": { 647 | "1": { 648 | "name": "punctuation.definition.variable.end.batchfile" 649 | } 650 | }, 651 | "name": "variable.other.readwrite.batchfile", 652 | "patterns": [ 653 | { 654 | "begin": ":~", 655 | "beginCaptures": { 656 | "0": { 657 | "name": "punctuation.separator.batchfile" 658 | } 659 | }, 660 | "end": "(?=!|\\n)", 661 | "name": "meta.variable.substring.batchfile", 662 | "patterns": [ 663 | { 664 | "include": "#variable_substring" 665 | } 666 | ] 667 | }, 668 | { 669 | "begin": ":", 670 | "beginCaptures": { 671 | "0": { 672 | "name": "punctuation.separator.batchfile" 673 | } 674 | }, 675 | "end": "(?=!|\\n)", 676 | "name": "meta.variable.substitution.batchfile", 677 | "patterns": [ 678 | { 679 | "include": "#escaped_characters" 680 | }, 681 | { 682 | "include": "#variable_replace" 683 | }, 684 | { 685 | "include": "#variable" 686 | }, 687 | { 688 | "begin": "=", 689 | "beginCaptures": { 690 | "0": { 691 | "name": "punctuation.separator.batchfile" 692 | } 693 | }, 694 | "end": "(?=!|\\n)", 695 | "patterns": [ 696 | { 697 | "include": "#variable" 698 | }, 699 | { 700 | "match": "[^!]+", 701 | "name": "string.unquoted.batchfile" 702 | } 703 | ] 704 | } 705 | ] 706 | } 707 | ] 708 | } 709 | ] 710 | }, 711 | "variable_replace": { 712 | "patterns": [ 713 | { 714 | "match": "[^=%!\\n]+", 715 | "name": "string.unquoted.batchfile" 716 | } 717 | ] 718 | }, 719 | "variable_substring": { 720 | "patterns": [ 721 | { 722 | "match": "([+-]?\\d+)(?:(,)([+-]?\\d+))?", 723 | "captures": { 724 | "1": { 725 | "name": "constant.numeric.batchfile" 726 | }, 727 | "2": { 728 | "name": "punctuation.separator.batchfile" 729 | }, 730 | "3": { 731 | "name": "constant.numeric.batchfile" 732 | } 733 | } 734 | } 735 | ] 736 | } 737 | } 738 | } --------------------------------------------------------------------------------