├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── client ├── .gitignore ├── package-lock.json ├── package.json ├── src │ ├── extension.ts │ └── formatter.ts └── tsconfig.json ├── extension.README.md ├── language-configuration.json ├── package-lock.json ├── package.json ├── res └── img │ └── logo.png ├── server ├── .gitignore ├── package-lock.json ├── package.json ├── src │ ├── auto-complete.ts │ ├── completion-shaderlab.ts │ ├── grammar-cg.ts │ ├── grammar-tree.txt │ ├── grammar.ts │ ├── server.ts │ ├── shaderlab.grammar.ts │ └── structure-shaderlb.ts └── tsconfig.json ├── snippets └── shaderlab.json ├── syntaxes └── shaderlab.tmLanguage.json ├── test ├── pattern-match.test.ts └── test.shader └── vsc-extension-quickstart.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.vsix -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension 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 | { 10 | "type": "node", 11 | "request": "attach", 12 | "name": "Attach to Server", 13 | "port": 6009, 14 | "address": "localhost", 15 | "protocol": "inspector", 16 | "sourceMaps": true, 17 | "outFiles": [ 18 | "${workspaceRoot}/server/out/**/*.js" 19 | ], 20 | "timeout": 2000 21 | }, 22 | { 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "name": "Run Extension", 26 | "runtimeExecutable": "${execPath}", 27 | "stopOnEntry": false, 28 | "sourceMaps": true, 29 | "args": [ 30 | "--extensionDevelopmentPath=${workspaceFolder}" 31 | ], 32 | "outFiles": [ 33 | "${workspaceFolder}/client/out/**/*.js" 34 | ] 35 | } 36 | ], 37 | "compounds": [ 38 | { 39 | "name": "Run & Attach", 40 | "configurations": ["Run Extension","Attach to Server"] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "watch", 8 | "dependsOn":[ 9 | { 10 | "type": "npm", 11 | "script": "watch:client" 12 | }, 13 | { 14 | "type": "npm", 15 | "script": "watch:server" 16 | } 17 | ], 18 | "problemMatcher":"$tsc", 19 | "group": { 20 | "kind": "build", 21 | "isDefault": true 22 | } 23 | }, 24 | { 25 | "label": "watch:client", 26 | "type": "npm", 27 | "script": "watch:client" 28 | }, 29 | { 30 | "label": "watch:server", 31 | "type": "npm", 32 | "script": "watch:server" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitignore 4 | vsc-extension-quickstart.md 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "unityshader-intellisense" extension will be documented in this file. 3 | 4 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 5 | 6 | ## [Unreleased] 7 | 8 | ## [0.3.1] - 2018-10-3 9 | ### Changed 10 | - Fix bugs of space spliting. 11 | 12 | ## [0.3.0] - 2018-9-6 13 | ### Added 14 | - Code formatter. 15 | 16 | ### Changed 17 | - Fix bugs. 18 | 19 | ## [0.2.5] - 2018-9-6 20 | ### Changed 21 | - Fix bugs. 22 | 23 | ## [0.2.4] - 2018-8-22 24 | ### Changed 25 | - Update project settings 26 | - Fix bugs 27 | - Change version format 28 | 29 | ## [0.2.1] - 2018-8-22 30 | ### Added 31 | - Language server 32 | - Auto-completion 33 | 34 | ### Changed 35 | - Update .tmLanguage 36 | 37 | ## [0.1.0] - 2018-8-16 38 | ### Changed 39 | - Update README 40 | 41 | ## [0.0.2] - 2018-8-16 42 | ### Changed 43 | - Fix bugs. 44 | 45 | ## [0.0.1] - 2018-8-16 46 | ### Added 47 | - Add syntax hightlight support for ShaderLab. 48 | - Add some snippets. 49 | 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 SardineFish 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Shader Support for VS Code 2 | 3 | An extension for VS Code to provide language support for Unity Shader 4 | 5 | *still in development* 6 | 7 | **I'm now rewriting the core part of this extension in order to provide stable grammar analysing. ** 8 | 9 | ## Requirements 10 | - VS Code 1.26.0 11 | 12 | ## Features 13 | - **Code formatter** 14 | - **Auto-completion** 15 | - **Syntax highlight** support 16 | - **Snippets** 17 | 18 | ### Code format 19 | ![](https://cdn-img.sardinefish.com/NTk4NjIw) 20 | 21 | ### Auto completion in properties 22 | ![](https://cdn-img.sardinefish.com/NTkyMzQw) 23 | 24 | ### Auto completion in tags & render setup 25 | ![](https://cdn-img.sardinefish.com/NTk1NTE0) 26 | 27 | ### Auto completion in Cg Code 28 | ![](https://cdn-img.sardinefish.com/NTk0NDc0) 29 | 30 | ### Code snippets 31 | ![](https://cdn-img.sardinefish.com/NTk2NTc5) 32 | 33 | ## TODO 34 | - Fix BUUUUUUUUUGs. 35 | - Code format 36 | - Diagnosis 37 | - Shader preview support (maybe impossible :) -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | out/ -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unityshader-intellisence-client", 3 | "version": "1.0.0", 4 | "description": "The Client of language server", 5 | "scripts": { 6 | "build": "tsc", 7 | "build:watch": "tsc -w", 8 | "update-vscode": "node ./node_modules/vscode/bin/install", 9 | "postinstall": "node ./node_modules/vscode/bin/install" 10 | }, 11 | "engines": { 12 | "vscode": "^1.26.0" 13 | }, 14 | "author": "SardineFish", 15 | "license": "MIT", 16 | "devDependencies": {}, 17 | "dependencies": { 18 | "linq": "^3.1.0", 19 | "vscode": "^1.1.21", 20 | "vscode-languageclient": "^5.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import * as path from 'path'; 3 | import { workspace, ExtensionContext } from "vscode"; 4 | import * as vscode from "vscode"; 5 | import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from "vscode-languageclient"; 6 | import { Formatter } from './formatter'; 7 | 8 | let client: LanguageClient; 9 | 10 | export function activate(context: ExtensionContext) 11 | { 12 | // Language Server 13 | console.log("Activated"); 14 | let serverModule = context.asAbsolutePath(path.join("server", "out", "server.js")); 15 | let debugOption = { execArgv: ["--nolazy", "--inspect=6009"] }; 16 | 17 | let serverOptions: ServerOptions = { 18 | run: { module: serverModule, transport: TransportKind.ipc }, 19 | debug: { 20 | module: serverModule, 21 | transport: TransportKind.ipc, 22 | options: debugOption 23 | } 24 | }; 25 | 26 | let clientOption: LanguageClientOptions = { 27 | documentSelector: [{ scheme: "file", language: "shaderlab" }], 28 | synchronize: { 29 | fileEvents: workspace.createFileSystemWatcher("**/.clientrc") 30 | } 31 | }; 32 | 33 | 34 | client = new LanguageClient("ShaderLab Language Server", serverOptions, clientOption); 35 | 36 | client.start(); 37 | console.log("Client started"); 38 | 39 | // Formatter 40 | vscode.languages.registerDocumentFormattingEditProvider("shaderlab", { 41 | provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions): vscode.TextEdit[] 42 | { 43 | return new Formatter(document, options).format(); 44 | } 45 | }); 46 | vscode.languages.registerOnTypeFormattingEditProvider("shaderlab", { 47 | provideOnTypeFormattingEdits(document: vscode.TextDocument, position: vscode.Position, ch: string, options: vscode.FormattingOptions, token: vscode.CancellationToken): vscode.TextEdit[] 48 | { 49 | 50 | return new Formatter(document, options).format(ch, position); 51 | } 52 | }, ";", "\n", "{", "}"); 53 | console.log("Registered formatter."); 54 | } -------------------------------------------------------------------------------- /client/src/formatter.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument, Position, Range, TextEdit, FormattingOptions } from "vscode"; 2 | import * as linq from "linq" 3 | type LineState = { line: number, indent: number, missSemicolon?:boolean, gaps?:Gap[] }; 4 | type GapHandler = (gap: Gap, state: LineState) => void; 5 | class DocRange extends Range 6 | { 7 | doc: TextDocument; 8 | get text() { return this.doc.getText(this); } 9 | get startOffset() { return this.doc.offsetAt(this.start); } 10 | get endOffset() { return this.doc.offsetAt(this.end); } 11 | get length() { return this.endOffset - this.startOffset;} 12 | constructor(doc: TextDocument, start: Position | number, end: Position | number) 13 | { 14 | if (start instanceof Position && end instanceof Position) 15 | { 16 | super(start, end); 17 | } 18 | else 19 | { 20 | super(doc.positionAt(start), doc.positionAt(end)); 21 | } 22 | this.doc = doc; 23 | } 24 | 25 | contains(range: Range | Position): boolean 26 | { 27 | if (range instanceof Range) 28 | return range.start.compareTo(this.start) >= 0 && range.start.compareTo(this.end) < 0 29 | && 30 | range.end.compareTo(this.start) > 0 && range.end.compareTo(this.end) <= 0; 31 | else 32 | { 33 | return this.start.compareTo(range) <= 0 && this.end.compareTo(range) > 0; 34 | } 35 | } 36 | } 37 | class CommentBlock extends DocRange 38 | { 39 | } 40 | class StringBlock extends DocRange 41 | { 42 | } 43 | class Gap extends DocRange 44 | { 45 | get prevCh() { return this.doc.getText(new Range(this.doc.positionAt(this.startOffset - 1), this.start)); } 46 | get latterCh() { return this.doc.getText(new Range(this.end, this.doc.positionAt(this.endOffset + 1)));} 47 | } 48 | 49 | function removeComment(doc: TextDocument): [string, CommentBlock[]] 50 | { 51 | let text = doc.getText(); 52 | const reg = /(?:\/\*(?!\/)(?:.|\s)*?\*\/)|\/\/.*[\r]?[\n]?/g; 53 | let comments: CommentBlock[] = [] 54 | text = text.replace(reg, (match, offset) => 55 | { 56 | comments.push(new CommentBlock(doc, offset, offset + match.length)); 57 | return linq.repeat(" ", match.length).toArray().join(""); 58 | }); 59 | return [text, comments]; 60 | } 61 | 62 | export class Formatter 63 | { 64 | indentUnit: string; 65 | ignoreRanges: DocRange[]; 66 | emptyRanges: DocRange[]; 67 | cgRanges: Range[]; 68 | doc: TextDocument; 69 | private isEmpty = (range: Range | Position) => this.emptyRanges.some(r => r.contains(range)); 70 | private ignoreFormmat = (range: Range | Position) => this.ignoreRanges.some(r => r.contains(range)); 71 | private containsComments = (range: Range) => this.ignoreRanges.filter(r => r instanceof CommentBlock).some(r => range.contains(r)); 72 | private genIndent = (count: number) => linq.repeat(this.indentUnit, count).toArray().join(""); 73 | private inCG = (range: Range | Position) => this.cgRanges.some(cg => cg.contains(range)); 74 | constructor(doc: TextDocument, options: FormattingOptions) 75 | { 76 | this.doc = doc; 77 | this.indentUnit = options.insertSpaces ? linq.repeat(" ", options.tabSize).toArray().join("") : "\t"; 78 | this.ignoreRanges = this.findIgnoreRange(); 79 | this.cgRanges = this.findCGCode(); 80 | this.emptyRanges = this.findEmptyRanges(); 81 | } 82 | 83 | private findCGCode(): Range[] 84 | { 85 | let text = this.doc.getText(); 86 | let regBegin = /CGPROGRAM/g; 87 | let regEnd = /ENDCG/g; 88 | let cgRanges = []; 89 | for (let matchBegin = regBegin.exec(text); matchBegin; matchBegin = regBegin.exec(text)) 90 | { 91 | if (this.ignoreRanges.some(ignore => ignore.contains(this.doc.positionAt(matchBegin.index)))) 92 | continue; 93 | let matchEnd: RegExpExecArray = null; 94 | do 95 | { 96 | matchEnd = regEnd.exec(text); 97 | if (!matchEnd) 98 | break; 99 | } 100 | while (matchEnd && matchEnd.index < matchBegin.index && !this.ignoreRanges.some(ignore => ignore.contains(this.doc.positionAt(matchEnd.index)))); 101 | if (!matchEnd) 102 | return cgRanges; 103 | 104 | cgRanges.push(new Range(this.doc.positionAt(matchBegin.index), this.doc.positionAt(matchEnd.index + matchEnd.length))); 105 | } 106 | return cgRanges; 107 | } 108 | 109 | private findIgnoreRange(): DocRange[] 110 | { 111 | let ranges = []; 112 | const regCommentBlock = /(\/\*(?!\/).*?\*\/)/; 113 | const regCommentLine = /\/\/.*[\r]?[\n]?/; 114 | const regString = /"([^\\"]|\\\S|\\")*"/; 115 | const reg = /(\/\*(?!\/).*?\*\/)|\/\/.*[\r]?[\n]?|"([^\\"]|\\\S|\\")*"/g; 116 | let startIdx = 0; 117 | let totalText = this.doc.getText(); 118 | for (let match = reg.exec(totalText); match; match = reg.exec(totalText)) 119 | { 120 | if (match[0].charAt(0) === '"') 121 | { 122 | ranges.push(new StringBlock(this.doc, match.index, match.index + match[0].length)); 123 | } 124 | else 125 | ranges.push(new CommentBlock(this.doc, match.index, match.index + match[0].length)); 126 | } 127 | return ranges; 128 | } 129 | 130 | private findEmptyRanges(): DocRange[] 131 | { 132 | let ranges = []; 133 | const reg = /(\s|(\/\*(?!\/).*?\*\/)|\/\/.*[\r]?[\n]?)+/g; 134 | let totalText = this.doc.getText(); 135 | for (let match = reg.exec(totalText); match; match = reg.exec(totalText)) 136 | { 137 | if (match[0].length > 0) 138 | ranges.push(new DocRange(this.doc, match.index, match.index + match[0].length)); 139 | } 140 | return ranges; 141 | } 142 | 143 | private splitGap(line: number): Gap[] 144 | { 145 | let text = this.doc.lineAt(line).text; 146 | let empties: Gap[] = []; 147 | 148 | const reg = /((?:(?:\+\+|--)?[_0-9a-zA-Z]+(?:\+\+|--)?)|(?:(?:<|>|<=|>=|==|\!=|\|\||&&)|(?:(?:\+|-|\*|\/|%|=|&|\||\^|<<|>>)=?)|(?:\.|\?|\:|~|,|;|\(|\)|\[|\]|{|}))|(?:"(?:[^\\"]|\\\S|\\")*"))(\s+)?/g; 149 | 150 | let headerSpace = /\s+/.exec(text); 151 | empties.push(new Gap(this.doc, new Position(line, 0), new Position(line, headerSpace ? headerSpace[0].length : 0))); 152 | for (let match = reg.exec(text); match; match = reg.exec(text)) 153 | { 154 | let range = new Gap(this.doc, new Position(line, match.index + match[1].length), new Position(line, match.index + match[0].length)); 155 | empties.push(range); 156 | } 157 | return empties; 158 | } 159 | 160 | private insertSpaceCg(gap: Gap): boolean 161 | { 162 | const regWord = /[a-zA-Z0-9_]|"/; 163 | const regOperator = /(?:<|>|<=|>=|==|\!=|\|\||&&|\?|\:)|(?:(?:\+|-|\*|\/|%|=|&|\||\^|<<|>>)=?)/; 164 | const regDelimiter = /,|;/; 165 | if (regWord.test(gap.prevCh) && regWord.test(gap.latterCh)) 166 | { 167 | return true; 168 | } 169 | else if (regOperator.test(gap.prevCh) || regOperator.test(gap.latterCh)) 170 | return true; 171 | else if (gap.prevCh === ",") 172 | return true; 173 | else if (gap.prevCh === ";" && gap.latterCh.length > 0) 174 | return true; 175 | else if (gap.prevCh.length > 0 && gap.latterCh === "{") 176 | return true; 177 | return false; 178 | } 179 | 180 | private insertSpaceShaderLab(gap: Gap): boolean 181 | { 182 | const regWord = /[a-zA-Z0-9_]|"/; 183 | const regOperator = /(?:<|>|<=|>=|==|\!=|\|\||&&|\?|\:)|(?:(?:\+|-|\*|\/|%|=|&|\||\^|<<|>>)=?)/; 184 | const regDelimiter = /,|;/; 185 | if (regWord.test(gap.prevCh) && regWord.test(gap.latterCh)) 186 | { 187 | return true; 188 | } 189 | else if (gap.prevCh === '"' && gap.latterCh === '"') 190 | return true; 191 | else if (regOperator.test(gap.prevCh) || regOperator.test(gap.latterCh)) 192 | return true; 193 | else if (regDelimiter.test(gap.prevCh)) 194 | return true; 195 | else if (gap.prevCh.length > 0 && gap.latterCh === "{") 196 | return true; 197 | return false; 198 | 199 | } 200 | 201 | getFormatRange(pos: Position): Range 202 | { 203 | let offset = this.doc.offsetAt(pos); 204 | let text = this.doc.getText(); 205 | let braceDepth = 0; 206 | const regEmpty = /\s/; 207 | 208 | let empty = linq.from(this.emptyRanges).where(r => r.contains(this.doc.positionAt(offset))).firstOrDefault(); 209 | if (empty) 210 | offset = empty.startOffset - 1; 211 | for (let i = offset; i >= 0; i--) 212 | { 213 | if (text.charAt(i) === "}") 214 | { 215 | braceDepth++; 216 | for (i--; i >= 0; i--) 217 | { 218 | if (text.charAt(i) === "}" && !this.ignoreFormmat(this.doc.positionAt(i))) 219 | braceDepth++; 220 | else if (text.charAt(i) === "{" && !this.ignoreFormmat(this.doc.positionAt(i))) 221 | braceDepth--; 222 | if (braceDepth === 0) 223 | { 224 | i--; 225 | break; 226 | } 227 | } 228 | } 229 | let empty = linq.from(this.emptyRanges).where(r => r.contains(this.doc.positionAt(i))).firstOrDefault(); 230 | if (empty) 231 | i = empty.startOffset - 1; 232 | if (text.charAt(i) === ";" && this.inCG(this.doc.positionAt(i))) 233 | { 234 | return new Range(this.doc.positionAt(i + 1), pos); 235 | } 236 | let startPos = this.doc.positionAt(i); 237 | startPos = new Position(startPos.line, 0); 238 | return new Range(startPos, pos); 239 | } 240 | } 241 | 242 | formatInRange(range: Range, indent = 0):TextEdit[] 243 | { 244 | enum FormatState { FormatShaderLab, FormatCG, MissSemicolon }; 245 | let state: FormatState = FormatState.FormatShaderLab; 246 | let editList = []; 247 | for (let lineIdx = range.start.line; lineIdx <= range.end.line; lineIdx++) 248 | { 249 | let line = this.doc.lineAt(lineIdx); 250 | let spaces = this.splitGap(lineIdx); 251 | if (state !== FormatState.MissSemicolon) 252 | { 253 | if (this.inCG(line.range)) 254 | state = FormatState.FormatCG; 255 | else 256 | state = FormatState.FormatShaderLab; 257 | } 258 | 259 | for (let i = 0; i < spaces.length; i++) 260 | { 261 | if (this.ignoreFormmat(spaces[i]) || this.containsComments(spaces[i])) 262 | continue; 263 | 264 | if (spaces[i].prevCh === "{") 265 | indent++; 266 | if (spaces[i].latterCh === "}") 267 | indent--; 268 | 269 | // Handle indent 270 | if (i === 0) 271 | { 272 | let indentCount = indent; 273 | if (state === FormatState.MissSemicolon) 274 | { 275 | if (!/{/.test(spaces[i].latterCh)) 276 | indentCount++; 277 | state = FormatState.FormatCG; 278 | } 279 | let indentReplace = 280 | TextEdit.replace( 281 | new Range( 282 | new Position(lineIdx, 0), 283 | new Position(lineIdx, line.firstNonWhitespaceCharacterIndex)), 284 | this.genIndent(indentCount)); 285 | editList.push(indentReplace); 286 | continue; 287 | } 288 | 289 | if (state === FormatState.FormatShaderLab) 290 | { 291 | let space = this.insertSpaceShaderLab(spaces[i]); 292 | if (space) 293 | { 294 | editList.push(TextEdit.replace(spaces[i], " ")); 295 | } 296 | else if (spaces[i].length > 0) 297 | { 298 | editList.push(TextEdit.delete(spaces[i])); 299 | } 300 | } 301 | else if (state === FormatState.FormatCG) 302 | { 303 | if (i === spaces.length - 1 && !/{|}|;/.test(spaces[i].prevCh)) 304 | { 305 | state = FormatState.MissSemicolon; 306 | } 307 | let space = this.insertSpaceCg(spaces[i]); 308 | if (space) 309 | { 310 | editList.push(TextEdit.replace(spaces[i], " ")); 311 | } 312 | else if (spaces[i].length > 0) 313 | { 314 | editList.push(TextEdit.delete(spaces[i])); 315 | } 316 | } 317 | } 318 | } 319 | return editList; 320 | } 321 | 322 | format(trigger: string = "", pos: Position = null): TextEdit[] 323 | { 324 | if (trigger === "") 325 | return this.formatInRange(new Range(new Position(0, 0), this.doc.positionAt(this.doc.getText().length))); 326 | let formatRange: Range; 327 | switch (trigger) 328 | { 329 | case "": 330 | formatRange = new Range(new Position(0, 0), this.doc.positionAt(this.doc.getText().length)); 331 | break; 332 | case "\n": 333 | formatRange = new Range(new Position(pos.line - 1, 0), pos); 334 | break; 335 | case "{": 336 | formatRange = new Range(new Position(pos.line, 0), pos); 337 | break; 338 | case "}": 339 | formatRange = this.getFormatRange(pos); 340 | break; 341 | case ";": 342 | formatRange = this.getFormatRange(this.doc.positionAt(this.doc.offsetAt(pos) - 2)); 343 | break; 344 | } 345 | 346 | let line = this.doc.lineAt(formatRange.start.line); 347 | let spaceCount = line.firstNonWhitespaceCharacterIndex; 348 | let indent = linq.range(0, spaceCount).select(i => line.text.charAt(i)).where(chr => chr === "\t").count(); 349 | console.log(this.doc.getText(formatRange)); 350 | // To fix bug cause by indent decending 351 | if (this.splitGap(formatRange.start.line)[0].latterCh === "}") 352 | indent++; 353 | return this.formatInRange(formatRange, indent); 354 | } 355 | } -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "lib": ["es6"], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | "sourceMap": true, /* Generates corresponding '.map' file. */ 12 | // "outFile": "./", /* Concatenate and emit output to single file. */ 13 | "outDir": "out", /* Redirect output structure to the directory. */ 14 | "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 15 | // "removeComments": true, /* Do not emit comments to output. */ 16 | // "noEmit": true, /* Do not emit outputs. */ 17 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 18 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 19 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 20 | 21 | /* Strict Type-Checking Options */ 22 | "strict": true, /* Enable all strict type-checking options. */ 23 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 24 | "strictNullChecks": false, /* Enable strict null checks. */ 25 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 26 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 27 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 28 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 29 | 30 | /* Additional Checks */ 31 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 32 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 33 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 34 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 35 | 36 | /* Module Resolution Options */ 37 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 38 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 39 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 40 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 41 | // "typeRoots": [], /* List of folders to include type definitions from. */ 42 | // "types": [], /* Type declaration files to be included in compilation. */ 43 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 44 | // "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 45 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 46 | 47 | /* Source Map Options */ 48 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 49 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 50 | // "inlineSourceMap": false, /* Emit a single file with source maps instead of having a separate file. */ 51 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 52 | 53 | /* Experimental Options */ 54 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 55 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 56 | }, 57 | "include": [ 58 | "src" 59 | ], 60 | "exclude": [ 61 | "node_modules" 62 | ] 63 | } -------------------------------------------------------------------------------- /extension.README.md: -------------------------------------------------------------------------------- 1 | # unityshader-intellisense README 2 | 3 | This is the README for your extension "unityshader-intellisense". After writing up a brief description, we recommend including the following sections. 4 | 5 | ## Features 6 | 7 | Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file. 8 | 9 | For example if there is an image subfolder under your extension project workspace: 10 | 11 | \!\[feature X\]\(images/feature-x.png\) 12 | 13 | > Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow. 14 | 15 | ## Requirements 16 | 17 | If you have any requirements or dependencies, add a section describing those and how to install and configure them. 18 | 19 | ## Extension Settings 20 | 21 | Include if your extension adds any VS Code settings through the `contributes.configuration` extension point. 22 | 23 | For example: 24 | 25 | This extension contributes the following settings: 26 | 27 | * `myExtension.enable`: enable/disable this extension 28 | * `myExtension.thing`: set to `blah` to do something 29 | 30 | ## Known Issues 31 | 32 | Calling out known issues can help limit users opening duplicate issues against your extension. 33 | 34 | ## Release Notes 35 | 36 | Users appreciate release notes as you update your extension. 37 | 38 | ### 1.0.0 39 | 40 | Initial release of ... 41 | 42 | ### 1.0.1 43 | 44 | Fixed issue #. 45 | 46 | ### 1.1.0 47 | 48 | Added features X, Y, and Z. 49 | 50 | ----------------------------------------------------------------------------------------------------------- 51 | 52 | ## Working with Markdown 53 | 54 | **Note:** You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts: 55 | 56 | * Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux) 57 | * Toggle preview (`Shift+CMD+V` on macOS or `Shift+Ctrl+V` on Windows and Linux) 58 | * Press `Ctrl+Space` (Windows, Linux) or `Cmd+Space` (macOS) to see a list of Markdown snippets 59 | 60 | ### For more information 61 | 62 | * [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown) 63 | * [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/) 64 | 65 | **Enjoy!** 66 | -------------------------------------------------------------------------------- /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 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments 6 | "blockComment": [ "/*", "*/" ] 7 | }, 8 | // symbols used as brackets 9 | "brackets": [ 10 | ["{", "}"], 11 | ["[", "]"], 12 | ["(", ")"] 13 | ], 14 | // symbols that are auto closed when typing 15 | "autoClosingPairs": [ 16 | ["{", "}"], 17 | ["[", "]"], 18 | ["(", ")"], 19 | ["\"", "\""], 20 | ["'", "'"], 21 | { 22 | "open": "/*", 23 | "close": "*/", 24 | "notIn": ["string"] 25 | } 26 | ], 27 | // symbols that that can be used to surround a selection 28 | "surroundingPairs": [ 29 | ["{", "}"], 30 | ["[", "]"], 31 | ["(", ")"], 32 | ["\"", "\""], 33 | ["'", "'"], 34 | [""] 35 | ], 36 | "folding": { 37 | "markers": { 38 | "start": "CGPROGRAM", 39 | "end": "ENDCG" 40 | } 41 | }, 42 | "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)" 43 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unityshader-intellisense", 3 | "version": "0.3.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "10.7.1", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.7.1.tgz", 10 | "integrity": "sha512-EGoI4ylB/lPOaqXqtzAyL8HcgOuCtH2hkEaLmkueOYufsTFWBn4VCvlCDC2HW8Q+9iF+QVC3sxjDKQYjHQeZ9w==", 11 | "dev": true 12 | }, 13 | "ansi-regex": { 14 | "version": "2.1.1", 15 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 16 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 17 | "dev": true 18 | }, 19 | "ansi-styles": { 20 | "version": "2.2.1", 21 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 22 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 23 | "dev": true 24 | }, 25 | "argparse": { 26 | "version": "1.0.10", 27 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 28 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 29 | "dev": true, 30 | "requires": { 31 | "sprintf-js": "~1.0.2" 32 | } 33 | }, 34 | "babel-code-frame": { 35 | "version": "6.26.0", 36 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", 37 | "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", 38 | "dev": true, 39 | "requires": { 40 | "chalk": "^1.1.3", 41 | "esutils": "^2.0.2", 42 | "js-tokens": "^3.0.2" 43 | }, 44 | "dependencies": { 45 | "chalk": { 46 | "version": "1.1.3", 47 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 48 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 49 | "dev": true, 50 | "requires": { 51 | "ansi-styles": "^2.2.1", 52 | "escape-string-regexp": "^1.0.2", 53 | "has-ansi": "^2.0.0", 54 | "strip-ansi": "^3.0.0", 55 | "supports-color": "^2.0.0" 56 | } 57 | } 58 | } 59 | }, 60 | "balanced-match": { 61 | "version": "1.0.0", 62 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 63 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 64 | "dev": true 65 | }, 66 | "boolbase": { 67 | "version": "1.0.0", 68 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 69 | "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", 70 | "dev": true 71 | }, 72 | "brace-expansion": { 73 | "version": "1.1.11", 74 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 75 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 76 | "dev": true, 77 | "requires": { 78 | "balanced-match": "^1.0.0", 79 | "concat-map": "0.0.1" 80 | } 81 | }, 82 | "buffer-crc32": { 83 | "version": "0.2.13", 84 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 85 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", 86 | "dev": true 87 | }, 88 | "builtin-modules": { 89 | "version": "1.1.1", 90 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 91 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", 92 | "dev": true 93 | }, 94 | "chalk": { 95 | "version": "2.4.1", 96 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 97 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 98 | "dev": true, 99 | "requires": { 100 | "ansi-styles": "^3.2.1", 101 | "escape-string-regexp": "^1.0.5", 102 | "supports-color": "^5.3.0" 103 | }, 104 | "dependencies": { 105 | "ansi-styles": { 106 | "version": "3.2.1", 107 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 108 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 109 | "dev": true, 110 | "requires": { 111 | "color-convert": "^1.9.0" 112 | } 113 | }, 114 | "supports-color": { 115 | "version": "5.4.0", 116 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 117 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 118 | "dev": true, 119 | "requires": { 120 | "has-flag": "^3.0.0" 121 | } 122 | } 123 | } 124 | }, 125 | "cheerio": { 126 | "version": "1.0.0-rc.2", 127 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", 128 | "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", 129 | "dev": true, 130 | "requires": { 131 | "css-select": "~1.2.0", 132 | "dom-serializer": "~0.1.0", 133 | "entities": "~1.1.1", 134 | "htmlparser2": "^3.9.1", 135 | "lodash": "^4.15.0", 136 | "parse5": "^3.0.1" 137 | } 138 | }, 139 | "color-convert": { 140 | "version": "1.9.2", 141 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", 142 | "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", 143 | "dev": true, 144 | "requires": { 145 | "color-name": "1.1.1" 146 | } 147 | }, 148 | "color-name": { 149 | "version": "1.1.1", 150 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", 151 | "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", 152 | "dev": true 153 | }, 154 | "commander": { 155 | "version": "2.17.1", 156 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", 157 | "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", 158 | "dev": true 159 | }, 160 | "concat-map": { 161 | "version": "0.0.1", 162 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 163 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 164 | "dev": true 165 | }, 166 | "core-util-is": { 167 | "version": "1.0.2", 168 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 169 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 170 | "dev": true 171 | }, 172 | "css-select": { 173 | "version": "1.2.0", 174 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", 175 | "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", 176 | "dev": true, 177 | "requires": { 178 | "boolbase": "~1.0.0", 179 | "css-what": "2.1", 180 | "domutils": "1.5.1", 181 | "nth-check": "~1.0.1" 182 | } 183 | }, 184 | "css-what": { 185 | "version": "2.1.0", 186 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", 187 | "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", 188 | "dev": true 189 | }, 190 | "denodeify": { 191 | "version": "1.2.1", 192 | "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", 193 | "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", 194 | "dev": true 195 | }, 196 | "diff": { 197 | "version": "3.5.0", 198 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 199 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 200 | "dev": true 201 | }, 202 | "dom-serializer": { 203 | "version": "0.1.0", 204 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", 205 | "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", 206 | "dev": true, 207 | "requires": { 208 | "domelementtype": "~1.1.1", 209 | "entities": "~1.1.1" 210 | }, 211 | "dependencies": { 212 | "domelementtype": { 213 | "version": "1.1.3", 214 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", 215 | "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", 216 | "dev": true 217 | } 218 | } 219 | }, 220 | "domelementtype": { 221 | "version": "1.3.0", 222 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", 223 | "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", 224 | "dev": true 225 | }, 226 | "domhandler": { 227 | "version": "2.4.2", 228 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", 229 | "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", 230 | "dev": true, 231 | "requires": { 232 | "domelementtype": "1" 233 | } 234 | }, 235 | "domutils": { 236 | "version": "1.5.1", 237 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 238 | "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", 239 | "dev": true, 240 | "requires": { 241 | "dom-serializer": "0", 242 | "domelementtype": "1" 243 | } 244 | }, 245 | "entities": { 246 | "version": "1.1.1", 247 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", 248 | "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", 249 | "dev": true 250 | }, 251 | "escape-string-regexp": { 252 | "version": "1.0.5", 253 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 254 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 255 | "dev": true 256 | }, 257 | "esprima": { 258 | "version": "4.0.1", 259 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 260 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 261 | "dev": true 262 | }, 263 | "esutils": { 264 | "version": "2.0.2", 265 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 266 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 267 | "dev": true 268 | }, 269 | "fd-slicer": { 270 | "version": "1.1.0", 271 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 272 | "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", 273 | "dev": true, 274 | "requires": { 275 | "pend": "~1.2.0" 276 | } 277 | }, 278 | "fs.realpath": { 279 | "version": "1.0.0", 280 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 281 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 282 | "dev": true 283 | }, 284 | "glob": { 285 | "version": "7.1.2", 286 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 287 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 288 | "dev": true, 289 | "requires": { 290 | "fs.realpath": "^1.0.0", 291 | "inflight": "^1.0.4", 292 | "inherits": "2", 293 | "minimatch": "^3.0.4", 294 | "once": "^1.3.0", 295 | "path-is-absolute": "^1.0.0" 296 | } 297 | }, 298 | "has-ansi": { 299 | "version": "2.0.0", 300 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 301 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 302 | "dev": true, 303 | "requires": { 304 | "ansi-regex": "^2.0.0" 305 | } 306 | }, 307 | "has-flag": { 308 | "version": "3.0.0", 309 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 310 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 311 | "dev": true 312 | }, 313 | "htmlparser2": { 314 | "version": "3.9.2", 315 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", 316 | "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", 317 | "dev": true, 318 | "requires": { 319 | "domelementtype": "^1.3.0", 320 | "domhandler": "^2.3.0", 321 | "domutils": "^1.5.1", 322 | "entities": "^1.1.1", 323 | "inherits": "^2.0.1", 324 | "readable-stream": "^2.0.2" 325 | } 326 | }, 327 | "inflight": { 328 | "version": "1.0.6", 329 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 330 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 331 | "dev": true, 332 | "requires": { 333 | "once": "^1.3.0", 334 | "wrappy": "1" 335 | } 336 | }, 337 | "inherits": { 338 | "version": "2.0.3", 339 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 340 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 341 | "dev": true 342 | }, 343 | "isarray": { 344 | "version": "1.0.0", 345 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 346 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 347 | "dev": true 348 | }, 349 | "js-tokens": { 350 | "version": "3.0.2", 351 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 352 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 353 | "dev": true 354 | }, 355 | "js-yaml": { 356 | "version": "3.12.0", 357 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", 358 | "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", 359 | "dev": true, 360 | "requires": { 361 | "argparse": "^1.0.7", 362 | "esprima": "^4.0.0" 363 | } 364 | }, 365 | "linkify-it": { 366 | "version": "2.0.3", 367 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz", 368 | "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=", 369 | "dev": true, 370 | "requires": { 371 | "uc.micro": "^1.0.1" 372 | } 373 | }, 374 | "lodash": { 375 | "version": "4.17.10", 376 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 377 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", 378 | "dev": true 379 | }, 380 | "markdown-it": { 381 | "version": "8.4.2", 382 | "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", 383 | "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", 384 | "dev": true, 385 | "requires": { 386 | "argparse": "^1.0.7", 387 | "entities": "~1.1.1", 388 | "linkify-it": "^2.0.0", 389 | "mdurl": "^1.0.1", 390 | "uc.micro": "^1.0.5" 391 | } 392 | }, 393 | "mdurl": { 394 | "version": "1.0.1", 395 | "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", 396 | "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", 397 | "dev": true 398 | }, 399 | "mime": { 400 | "version": "1.6.0", 401 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 402 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 403 | "dev": true 404 | }, 405 | "minimatch": { 406 | "version": "3.0.4", 407 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 408 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 409 | "dev": true, 410 | "requires": { 411 | "brace-expansion": "^1.1.7" 412 | } 413 | }, 414 | "mute-stream": { 415 | "version": "0.0.7", 416 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", 417 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", 418 | "dev": true 419 | }, 420 | "nth-check": { 421 | "version": "1.0.1", 422 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", 423 | "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", 424 | "dev": true, 425 | "requires": { 426 | "boolbase": "~1.0.0" 427 | } 428 | }, 429 | "once": { 430 | "version": "1.4.0", 431 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 432 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 433 | "dev": true, 434 | "requires": { 435 | "wrappy": "1" 436 | } 437 | }, 438 | "os-homedir": { 439 | "version": "1.0.2", 440 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 441 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", 442 | "dev": true 443 | }, 444 | "os-tmpdir": { 445 | "version": "1.0.2", 446 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 447 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 448 | "dev": true 449 | }, 450 | "osenv": { 451 | "version": "0.1.5", 452 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", 453 | "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", 454 | "dev": true, 455 | "requires": { 456 | "os-homedir": "^1.0.0", 457 | "os-tmpdir": "^1.0.0" 458 | } 459 | }, 460 | "parse-semver": { 461 | "version": "1.1.1", 462 | "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", 463 | "integrity": "sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg=", 464 | "dev": true, 465 | "requires": { 466 | "semver": "^5.1.0" 467 | } 468 | }, 469 | "parse5": { 470 | "version": "3.0.3", 471 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", 472 | "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", 473 | "dev": true, 474 | "requires": { 475 | "@types/node": "*" 476 | } 477 | }, 478 | "path-is-absolute": { 479 | "version": "1.0.1", 480 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 481 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 482 | "dev": true 483 | }, 484 | "path-parse": { 485 | "version": "1.0.6", 486 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 487 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 488 | "dev": true 489 | }, 490 | "pend": { 491 | "version": "1.2.0", 492 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 493 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", 494 | "dev": true 495 | }, 496 | "process-nextick-args": { 497 | "version": "2.0.0", 498 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 499 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", 500 | "dev": true 501 | }, 502 | "q": { 503 | "version": "1.5.1", 504 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", 505 | "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", 506 | "dev": true 507 | }, 508 | "read": { 509 | "version": "1.0.7", 510 | "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", 511 | "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", 512 | "dev": true, 513 | "requires": { 514 | "mute-stream": "~0.0.4" 515 | } 516 | }, 517 | "readable-stream": { 518 | "version": "2.3.6", 519 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 520 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 521 | "dev": true, 522 | "requires": { 523 | "core-util-is": "~1.0.0", 524 | "inherits": "~2.0.3", 525 | "isarray": "~1.0.0", 526 | "process-nextick-args": "~2.0.0", 527 | "safe-buffer": "~5.1.1", 528 | "string_decoder": "~1.1.1", 529 | "util-deprecate": "~1.0.1" 530 | } 531 | }, 532 | "resolve": { 533 | "version": "1.8.1", 534 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", 535 | "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", 536 | "dev": true, 537 | "requires": { 538 | "path-parse": "^1.0.5" 539 | } 540 | }, 541 | "safe-buffer": { 542 | "version": "5.1.2", 543 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 544 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 545 | "dev": true 546 | }, 547 | "semver": { 548 | "version": "5.5.0", 549 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", 550 | "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", 551 | "dev": true 552 | }, 553 | "sprintf-js": { 554 | "version": "1.0.3", 555 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 556 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 557 | "dev": true 558 | }, 559 | "string_decoder": { 560 | "version": "1.1.1", 561 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 562 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 563 | "dev": true, 564 | "requires": { 565 | "safe-buffer": "~5.1.0" 566 | } 567 | }, 568 | "strip-ansi": { 569 | "version": "3.0.1", 570 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 571 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 572 | "dev": true, 573 | "requires": { 574 | "ansi-regex": "^2.0.0" 575 | } 576 | }, 577 | "supports-color": { 578 | "version": "2.0.0", 579 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 580 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 581 | "dev": true 582 | }, 583 | "tmp": { 584 | "version": "0.0.29", 585 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", 586 | "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", 587 | "dev": true, 588 | "requires": { 589 | "os-tmpdir": "~1.0.1" 590 | } 591 | }, 592 | "tslib": { 593 | "version": "1.9.3", 594 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", 595 | "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", 596 | "dev": true 597 | }, 598 | "tslint": { 599 | "version": "5.11.0", 600 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", 601 | "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", 602 | "dev": true, 603 | "requires": { 604 | "babel-code-frame": "^6.22.0", 605 | "builtin-modules": "^1.1.1", 606 | "chalk": "^2.3.0", 607 | "commander": "^2.12.1", 608 | "diff": "^3.2.0", 609 | "glob": "^7.1.1", 610 | "js-yaml": "^3.7.0", 611 | "minimatch": "^3.0.4", 612 | "resolve": "^1.3.2", 613 | "semver": "^5.3.0", 614 | "tslib": "^1.8.0", 615 | "tsutils": "^2.27.2" 616 | } 617 | }, 618 | "tsutils": { 619 | "version": "2.29.0", 620 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", 621 | "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", 622 | "dev": true, 623 | "requires": { 624 | "tslib": "^1.8.1" 625 | } 626 | }, 627 | "tunnel": { 628 | "version": "0.0.4", 629 | "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", 630 | "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=", 631 | "dev": true 632 | }, 633 | "typed-rest-client": { 634 | "version": "0.9.0", 635 | "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-0.9.0.tgz", 636 | "integrity": "sha1-92jMDcP06VDwbgSCXDaz54NKofI=", 637 | "dev": true, 638 | "requires": { 639 | "tunnel": "0.0.4", 640 | "underscore": "1.8.3" 641 | }, 642 | "dependencies": { 643 | "underscore": { 644 | "version": "1.8.3", 645 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", 646 | "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", 647 | "dev": true 648 | } 649 | } 650 | }, 651 | "typescript": { 652 | "version": "3.0.1", 653 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz", 654 | "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==", 655 | "dev": true 656 | }, 657 | "uc.micro": { 658 | "version": "1.0.5", 659 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz", 660 | "integrity": "sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==", 661 | "dev": true 662 | }, 663 | "underscore": { 664 | "version": "1.9.1", 665 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", 666 | "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", 667 | "dev": true 668 | }, 669 | "url-join": { 670 | "version": "1.1.0", 671 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", 672 | "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=", 673 | "dev": true 674 | }, 675 | "util-deprecate": { 676 | "version": "1.0.2", 677 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 678 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 679 | "dev": true 680 | }, 681 | "vsce": { 682 | "version": "1.46.0", 683 | "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.46.0.tgz", 684 | "integrity": "sha512-cNQru5mXBPUtMDgwRNoespaR0gjdL09hV1KWktT5wkmTZfv0dSaAqqGAfr+2UI0aJTGttCcO3xKFQqtIcJpczA==", 685 | "dev": true, 686 | "requires": { 687 | "cheerio": "^1.0.0-rc.1", 688 | "commander": "^2.8.1", 689 | "denodeify": "^1.2.1", 690 | "glob": "^7.0.6", 691 | "lodash": "^4.17.10", 692 | "markdown-it": "^8.3.1", 693 | "mime": "^1.3.4", 694 | "minimatch": "^3.0.3", 695 | "osenv": "^0.1.3", 696 | "parse-semver": "^1.1.1", 697 | "read": "^1.0.7", 698 | "semver": "^5.1.0", 699 | "tmp": "0.0.29", 700 | "url-join": "^1.1.0", 701 | "vso-node-api": "6.1.2-preview", 702 | "yauzl": "^2.3.1", 703 | "yazl": "^2.2.2" 704 | } 705 | }, 706 | "vso-node-api": { 707 | "version": "6.1.2-preview", 708 | "resolved": "https://registry.npmjs.org/vso-node-api/-/vso-node-api-6.1.2-preview.tgz", 709 | "integrity": "sha1-qrNUbfJFHs2JTgcbuZtd8Zxfp48=", 710 | "dev": true, 711 | "requires": { 712 | "q": "^1.0.1", 713 | "tunnel": "0.0.4", 714 | "typed-rest-client": "^0.9.0", 715 | "underscore": "^1.8.3" 716 | } 717 | }, 718 | "wrappy": { 719 | "version": "1.0.2", 720 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 721 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 722 | "dev": true 723 | }, 724 | "yauzl": { 725 | "version": "2.10.0", 726 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 727 | "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", 728 | "dev": true, 729 | "requires": { 730 | "buffer-crc32": "~0.2.3", 731 | "fd-slicer": "~1.1.0" 732 | } 733 | }, 734 | "yazl": { 735 | "version": "2.4.3", 736 | "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.4.3.tgz", 737 | "integrity": "sha1-7CblzIfVYBud+EMtvdPNLlFzoHE=", 738 | "dev": true, 739 | "requires": { 740 | "buffer-crc32": "~0.2.3" 741 | } 742 | } 743 | } 744 | } 745 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unityshader-intellisense", 3 | "displayName": "UnityShader Support", 4 | "description": "Language support for Unity Shader", 5 | "version": "0.3.1", 6 | "publisher": "SardineFish", 7 | "license": "MIT", 8 | "engines": { 9 | "vscode": "^1.26.0" 10 | }, 11 | "repository": "https://github.com/SardineFish/UnityShaderSupport.git", 12 | "categories": [ 13 | "Programming Languages" 14 | ], 15 | "scripts": { 16 | "postinstall": "cd client && npm install && cd ../server && npm install && cd ..", 17 | "vscode:prepublish": "cd client && npm run update-vscode && cd .. && npm run build", 18 | "build:client": "tsc -p ./client/tsconfig.json", 19 | "build:server": "tsc -p ./server/tsconfig.json", 20 | "build": "npm run build:client && npm run build:server", 21 | "prestart": "npm run build", 22 | "watch:client": "tsc -w -p ./client/tsconfig.json", 23 | "watch:server": "tsc -w -p ./server/tsconfig.json", 24 | "watch": "npm run watch:client && npm run watch:server" 25 | }, 26 | "icon": "res/img/logo.png", 27 | "main": "./client/out/extension", 28 | "activationEvents": [ 29 | "onLanguage:shaderlab" 30 | ], 31 | "contributes": { 32 | "languages": [ 33 | { 34 | "id": "shaderlab", 35 | "aliases": [ 36 | "ShaderLab", 37 | "shaderlab" 38 | ], 39 | "extensions": [ 40 | ".shader" 41 | ], 42 | "configuration": "./language-configuration.json" 43 | } 44 | ], 45 | "grammars": [ 46 | { 47 | "language": "shaderlab", 48 | "scopeName": "source.shaderlab", 49 | "path": "./syntaxes/shaderlab.tmLanguage.json" 50 | } 51 | ], 52 | "snippets": [ 53 | { 54 | "language": "shaderlab", 55 | "path": "./snippets/shaderlab.json" 56 | } 57 | ] 58 | }, 59 | "devDependencies": { 60 | "@types/node": "^10.7.1", 61 | "tslint": "^5.11.0", 62 | "typescript": "^3.0.1", 63 | "vsce": "^1.46.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /res/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SardineFish/UnityShaderSupport/48cf8a131180ddf80a800a8275765dd8eb30b7bc/res/img/logo.png -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | out/ -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unityshader-intellisence-server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "linq": { 8 | "version": "3.1.0", 9 | "resolved": "https://registry.npmjs.org/linq/-/linq-3.1.0.tgz", 10 | "integrity": "sha512-fekT1De4dRt0rviLiLdlICWv/m7I2Z2RUizIuT2VKzx+2dvgDn+6tVng7Yth9AysO0np2JF6YxugLY9s4D4CUg==" 11 | }, 12 | "vscode-jsonrpc": { 13 | "version": "3.6.2", 14 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.6.2.tgz", 15 | "integrity": "sha512-T24Jb5V48e4VgYliUXMnZ379ItbrXgOimweKaJshD84z+8q7ZOZjJan0MeDe+Ugb+uqERDVV8SBmemaGMSMugA==" 16 | }, 17 | "vscode-languageserver": { 18 | "version": "5.0.3", 19 | "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-5.0.3.tgz", 20 | "integrity": "sha512-itOImvZfDueQXMhy4pm2SwPKa3AShZvILXFcK/5X3ruiYdZozmx3OeD5Y92dVBt0OzTdbVD9MZcEelH4E7Eu3g==", 21 | "requires": { 22 | "vscode-languageserver-protocol": "^3.10.3", 23 | "vscode-uri": "^1.0.5" 24 | } 25 | }, 26 | "vscode-languageserver-protocol": { 27 | "version": "3.11.0", 28 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.11.0.tgz", 29 | "integrity": "sha512-qUcGFE1clXLnJvJsv+7spFzwdd0+3/j6VXQE1tdmyhsCt62MMqT4nO6j2OJFk9x44lMVTcVzEqIaAsPwtGmEtA==", 30 | "requires": { 31 | "vscode-jsonrpc": "^3.6.2", 32 | "vscode-languageserver-types": "^3.11.0" 33 | } 34 | }, 35 | "vscode-languageserver-types": { 36 | "version": "3.11.0", 37 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.11.0.tgz", 38 | "integrity": "sha512-5epFKsT2MEw++UlXGz7FjpQiVeffoCjbqpcWKgTRGx7r70SkbUetl4OI9TkRYtMINR0IT7BhgsF5bLmXFui9GA==" 39 | }, 40 | "vscode-uri": { 41 | "version": "1.0.6", 42 | "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.6.tgz", 43 | "integrity": "sha512-sLI2L0uGov3wKVb9EB+vIQBl9tVP90nqRvxSoJ35vI3NjxE8jfsE5DSOhWgSunHSZmKS4OCi2jrtfxK7uyp2ww==" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unityshader-intellisence-server", 3 | "version": "1.0.0", 4 | "description": "The server of a language server", 5 | "scripts": { 6 | "build": "tsc", 7 | "build:watch": "tsc -w" 8 | }, 9 | "engines": { 10 | "node": "*" 11 | }, 12 | "author": "SardineFish", 13 | "license": "MIT", 14 | "dependencies": { 15 | "linq": "^3.1.0", 16 | "vscode-languageserver": "^5.0.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/src/auto-complete.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument, CompletionItem } from "vscode-languageserver"; 2 | 3 | export function getCompletion(doc: TextDocument): CompletionItem[] 4 | { 5 | return []; 6 | } -------------------------------------------------------------------------------- /server/src/completion-shaderlab.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItem, CodeActionKind, CompletionItemKind } from "vscode-languageserver"; 2 | import { MatchResult, PatternMatchResult, ScopeMatchResult, UnMatchedPattern } from "./grammar"; 3 | import { CgFunction, CgContext, CgVariable, CgGlobalContext, CgType, toCgVariableCompletions, toCgFunctionCompletions, toCgFieldCompletions, toCompletions, propertyTypes, cgBuildInTypesCompletion, preprocessors, CgArray } from "./grammar-cg"; 4 | import { Shader, ShaderProperty, SubShader, Pass, Tag, subShaderTags, passTags, getKeys, renderSetups } from "./structure-shaderlb"; 5 | 6 | function getMatchedProps(match: PatternMatchResult|UnMatchedPattern, name: string, defaultValue: string = null) 7 | { 8 | if (!match) 9 | return defaultValue; 10 | let value = match.getMatch(name)[0]; 11 | if (!value) 12 | return defaultValue; 13 | return value.text; 14 | } 15 | function onFunctionMatch(match: PatternMatchResult) 16 | { 17 | let type = getMatchedProps(match, "type"); 18 | let name = getMatchedProps(match, "name"); 19 | let returnSemantics = getMatchedProps(match, "semantics"); 20 | let context = match.matchedScope.state as CgGlobalContext; 21 | let func = new CgFunction(context.getType(type), name, returnSemantics); 22 | context.addFunction(func); 23 | match.state = func; 24 | } 25 | function onParamsDeclare(match: PatternMatchResult) 26 | { 27 | let type = getMatchedProps(match, "type"); 28 | let name = getMatchedProps(match, "name"); 29 | let semantics = getMatchedProps(match, "semantics"); 30 | let func = match.matchedPattern.state as CgFunction; 31 | func.addParameter(new CgVariable(func.globalContext.getType(type), name, semantics)); 32 | } 33 | function onBlockMatch(scope: ScopeMatchResult) 34 | { 35 | scope.state = new CgContext(); 36 | (scope.matchedScope.state as CgContext).addContext(scope.state as CgContext); 37 | if (scope.matchedPattern.state instanceof CgFunction) 38 | { 39 | scope.matchedPattern.state.setFunctionContext(scope.state); 40 | } 41 | } 42 | function onStructDeclare(match: PatternMatchResult) 43 | { 44 | let name = getMatchedProps(match, "name"); 45 | let context = match.matchedScope.state as CgGlobalContext; 46 | let type = new CgType(name, context); 47 | context.addCustomType(type); 48 | match.state = type; 49 | } 50 | function onStructMemberDeclare(match: PatternMatchResult) 51 | { 52 | let type = getMatchedProps(match, "type"); 53 | let name = getMatchedProps(match, "name"); 54 | let semantics = getMatchedProps(match, "semantics"); 55 | let struct = match.matchedPattern.state as CgType; 56 | let member = new CgVariable(struct.context.getType(type), name, semantics); 57 | struct.addMember(member); 58 | } 59 | function getObjectType(match: MatchResult,context:CgContext): CgType 60 | { 61 | const reg = /([_a-zA-Z][_a-zA-Z0-9]*)(\[.*\])?/; 62 | let name = ""; 63 | if (match.children.length === 2) 64 | { 65 | if (match.children[0].text === ".") 66 | { 67 | let prevIdx = match.parent.children.indexOf(match) - 1; 68 | let prevMatch = match.parent.children[prevIdx]; 69 | let t = getObjectType(prevMatch, context); 70 | let name = match.children[1].text; 71 | let result = reg.exec(name); 72 | if (result[2]) 73 | { 74 | return (t.getMember(result[1]).type as CgArray).elementType; 75 | } 76 | return t.getMember(result[1]).type; 77 | } 78 | name = match.children[1].text; 79 | } 80 | else 81 | { 82 | name = match.text; 83 | } 84 | let result = reg.exec(name); 85 | if (result[2]) 86 | { 87 | return (context.getVariable(result[1]).type as CgArray).elementType; 88 | } 89 | return context.getVariable(result[1]).type; 90 | 91 | } 92 | function onExpressionComplete(match: MatchResult): CompletionItem[] 93 | { 94 | let context = match.matchedScope.state as CgContext; 95 | if (match.patternName === "expr-unit") 96 | { 97 | return toCgVariableCompletions(context.getAllVariables()) 98 | .concat(toCgFunctionCompletions(context.global.functions)); 99 | } 100 | else if (match.patternName === "operator") 101 | { 102 | if (match.text === ".") 103 | { 104 | let context = match.matchedScope.state as CgContext; 105 | let prevIdx = match.parent.parent.children.indexOf(match.parent) - 1; 106 | let prevMatch = match.parent.parent.children[prevIdx]; 107 | let type = getObjectType(prevMatch,context); 108 | if (type.orderedMenber) 109 | { 110 | let base = 1000; 111 | return type.members.map((member, idx) => 112 | { 113 | return { 114 | label: member.name, 115 | detail: member.toString(), 116 | sortText: (base+idx).toString(), 117 | kind: CompletionItemKind.Field 118 | } 119 | }); 120 | } 121 | else 122 | { 123 | return type.members.map((member) => 124 | { 125 | return { 126 | label: member.name, 127 | detail: member.toString(), 128 | kind: CompletionItemKind.Field 129 | } 130 | }); 131 | } 132 | } 133 | } 134 | return []; 135 | } 136 | 137 | function onPropertiesCompletion(match: MatchResult):CompletionItem[] 138 | { 139 | if (match.patternName === "propType") 140 | return toCompletions(propertyTypes, CompletionItemKind.TypeParameter); 141 | else if (match.patternName === "propertyValue") 142 | { 143 | let typeName = match._unmatchedPattern.getMatch("propType")[0].text; 144 | if (/Color|Vector/i.test(typeName)) 145 | return toCompletions(["(0, 0, 0, 0)"], CompletionItemKind.Snippet); 146 | else if (/2D|Cube|3D/i.test(typeName)) 147 | { 148 | return [ 149 | { 150 | label: '"defaulttexture" {}', 151 | kind: CompletionItemKind.Snippet 152 | } 153 | ] 154 | } 155 | } 156 | return []; 157 | } 158 | function onShaderDeclare(match: ScopeMatchResult) 159 | { 160 | let name = getMatchedProps(match.matchedPattern, "string"); 161 | let shader = new Shader(name); 162 | match.state = shader; 163 | match.matchedPattern.state = shader; 164 | } 165 | function onPropertiesDeclare(match: PatternMatchResult) 166 | { 167 | let name = getMatchedProps(match, "identifier"); 168 | let displayName = getMatchedProps(match, "displayName").replace(/"/, ""); 169 | let type = getMatchedProps(match, "propType"); 170 | let defaultValue = getMatchedProps(match, "propertyValue"); 171 | let shader = match.matchedScope.matchedScope.state as Shader; 172 | shader.addProperty(new ShaderProperty(name, displayName, type, defaultValue)); 173 | } 174 | function onSubShaderDeclare(match: ScopeMatchResult) 175 | { 176 | let subShader = new SubShader(); 177 | match.state = subShader; 178 | let shader = match.matchedScope.state as Shader; 179 | shader.addSubShader(subShader); 180 | } 181 | function onPassDeclare(match: ScopeMatchResult) 182 | { 183 | let subShader = match.matchedScope.state as SubShader; 184 | let pass = new Pass(); 185 | subShader.addPass(pass); 186 | match.state = pass; 187 | } 188 | function onTagsMatch(match: PatternMatchResult) 189 | { 190 | let parent = match.matchedScope.matchedScope.state; 191 | let tagName = getMatchedProps(match, "tag").replace(/"/g, ""); 192 | let value = getMatchedProps(match, "value").replace(/"/g, ""); 193 | if (parent instanceof SubShader || parent instanceof Pass) 194 | { 195 | parent.addTag(new Tag(tagName, value)); 196 | } 197 | } 198 | function onTagCompletion(match: MatchResult): CompletionItem[] 199 | { 200 | let scope = match.matchedScope.matchedScope.state; 201 | let tagName: string; 202 | if (!match.patternName) 203 | { 204 | if (match instanceof UnMatchedPattern && match.getMatch("tag")) 205 | { 206 | tagName = getMatchedProps(match, "tag").replace(/"/g, "");; 207 | scope = match.matchedScope.matchedScope.state; 208 | } 209 | else 210 | scope = match.matchedScope.state; 211 | } 212 | let tags = scope instanceof SubShader ? subShaderTags : 213 | (scope instanceof Pass ? passTags : {}); 214 | 215 | if (tagName || match.patternName === "value") 216 | { 217 | if (!tagName) 218 | { 219 | let patternMatch = match._unmatchedPattern || match.matchedPattern; 220 | tagName = getMatchedProps(patternMatch, "tag").replace(/"/g, ""); 221 | } 222 | let tagsValues = tags[tagName]; 223 | if (!tagsValues) 224 | return []; 225 | return tagsValues.map(value => 226 | { 227 | return { 228 | label: value, 229 | insertText: `"${value}"`, 230 | kind: CompletionItemKind.EnumMember 231 | }; 232 | }); 233 | } 234 | else if (!match.patternName || match.patternName === "tag") 235 | { 236 | return getKeys(tags).map(tagName => 237 | { 238 | return { 239 | label: tagName, 240 | insertText: `"${tagName}"`, 241 | kind: CompletionItemKind.Property 242 | }; 243 | }); 244 | } 245 | return []; 246 | } 247 | 248 | function onRenderSetupCompletion(match: MatchResult): CompletionItem[] 249 | { 250 | let stateName = getMatchedProps(match._unmatchedPattern, "stateName"); 251 | if (!stateName) 252 | stateName = getMatchedProps(match.matchedPattern, "stateName"); 253 | if (match instanceof UnMatchedPattern) 254 | stateName = getMatchedProps(match, "stateName"); 255 | let values = renderSetups[stateName]; 256 | if (!stateName || !values) 257 | { 258 | return toCompletions(getKeys(renderSetups), CompletionItemKind.Property) 259 | .concat([ 260 | { 261 | label: "Tags", 262 | insertText: "Tags { }", 263 | kind: CompletionItemKind.Keyword 264 | }, 265 | { 266 | label: "CGPROGRAM", 267 | insertText: "CGPROGRAM\r\n\r\nENDCG", 268 | kind: CompletionItemKind.Snippet 269 | } 270 | ]) 271 | .concat(match.matchedScope.state instanceof SubShader ? 272 | [{ 273 | label: "Pass", 274 | insertText: "Pass {\r\n\r\n}", 275 | kind: CompletionItemKind.Snippet 276 | }] : []); 277 | } 278 | return toCompletions(values, CompletionItemKind.EnumMember); 279 | } 280 | function cgGlobalCompletion(match:MatchResult): CompletionItem[] 281 | { 282 | if (match.text.length <= 1) 283 | { 284 | return cgBuildInTypesCompletion 285 | .concat(toCompletions(["struct", "#pragma"], CompletionItemKind.Keyword)); 286 | } 287 | else if (match.patternName ==="cgProgram") 288 | { 289 | return cgBuildInTypesCompletion 290 | .concat(toCompletions(["struct", "#pragma"], CompletionItemKind.Keyword)); 291 | } 292 | return []; 293 | 294 | } 295 | function cgPreprocessorCompletion(match: MatchResult): CompletionItem[] 296 | { 297 | if (match instanceof UnMatchedPattern) 298 | { 299 | let cmd = getMatchedProps(match, "cmd"); 300 | if (!cmd) 301 | return toCompletions(preprocessors, CompletionItemKind.Keyword); 302 | } 303 | else if (match instanceof PatternMatchResult) 304 | { 305 | let name = getMatchedProps(match, "name"); 306 | let cmd = getMatchedProps(match, "cmd"); 307 | if (name) 308 | { 309 | return []; 310 | } 311 | else if (cmd) 312 | { 313 | if (["vertex", "fragment", "geometry", "hull", "domain", "surface"].indexOf(cmd) >= 0) 314 | { 315 | let context = match.matchedScope.state as CgGlobalContext; 316 | return toCgFunctionCompletions(context.functions); 317 | } 318 | } 319 | 320 | } 321 | return []; 322 | } 323 | function onVariableDeclare(match: MatchResult): CompletionItem[] 324 | { 325 | if (match.patternName === "type") 326 | { 327 | return cgBuildInTypesCompletion; 328 | } 329 | else if (match.patternName === "name") 330 | { 331 | let context = match.matchedScope.state; 332 | if (context instanceof CgGlobalContext) 333 | { 334 | 335 | return context.shader.properties.map(prop => 336 | { 337 | return { 338 | label: prop.identifier, 339 | detail: prop.toString(), 340 | kind: CompletionItemKind.Unit 341 | }; 342 | }) 343 | } 344 | } 345 | return []; 346 | } 347 | export 348 | { 349 | onFunctionMatch, 350 | onParamsDeclare, 351 | onBlockMatch, 352 | onStructDeclare, 353 | onStructMemberDeclare, 354 | onExpressionComplete, 355 | onPropertiesCompletion, 356 | onPropertiesDeclare, 357 | onShaderDeclare, 358 | onSubShaderDeclare, 359 | onPassDeclare, 360 | onTagsMatch, 361 | onTagCompletion, 362 | onRenderSetupCompletion, 363 | cgGlobalCompletion, 364 | cgPreprocessorCompletion, 365 | onVariableDeclare 366 | } -------------------------------------------------------------------------------- /server/src/grammar-cg.ts: -------------------------------------------------------------------------------- 1 | import linq from "linq" 2 | import { CompletionItem, CompletionItemKind } from "vscode-languageserver"; 3 | import { Shader, SubShader, Pass } from "./structure-shaderlb"; 4 | const scalarTypes = ["float", "half", "int", "fixed"]; 5 | const otherTypes = ["bool", "void"]; 6 | const vectorTypes = concat(scalarTypes.map(t => [1, 2, 3, 4].map(n => `${t}${n}`))); 7 | const matrixTypes = concat(scalarTypes.map(t => [1, 2, 3, 4].map(n => `${t}${n}x${n}`))); 8 | const samplerTypes = concat(["sampler"].map(t => ["1D", "2D", "3D", "CUBE", "RECT"].map(d => `${t}${d}`))); 9 | const keyWords = ["if", "for", "while", "do"]; 10 | const propertyTypes = ["Range", "Float", "Int", "Color", "Vector", "2D", "Cube", "3D"]; 11 | const preprocessors = ["vertex", "fragment", "geometry", "hull", "domain", "target", "only_renderers", "exclude_renderers", "multi_compile", "enable_d3d11_debug_symbols", "hardware_tier_variants"]; 12 | 13 | 14 | function concat(list: T[][]):T[] 15 | { 16 | let result: T[] = []; 17 | list.forEach(subList => result = result.concat(subList)); 18 | return result; 19 | } 20 | const cgBuildInTypes = scalarTypes.concat(otherTypes, vectorTypes, matrixTypes, samplerTypes); 21 | const cgBuildInTypesCompletion: CompletionItem[] 22 | = cgBuildInTypes.map(type => 23 | { 24 | return { 25 | label: type, 26 | kind:CompletionItemKind.Struct 27 | } 28 | }); 29 | const cgBuildInKeywordsCompletion: CompletionItem[] 30 | = keyWords.map(keyword => 31 | { 32 | return { 33 | label: keyword, 34 | kind: CompletionItemKind.Keyword 35 | } 36 | }); 37 | class CgType 38 | { 39 | name: string; 40 | members: CgVariable[] = []; 41 | context: CgGlobalContext; 42 | orderedMenber: boolean = false; 43 | constructor(name: string, context: CgGlobalContext, members: CgVariable[] = []) 44 | { 45 | this.name = name; 46 | this.context = context; 47 | this.members = members; 48 | } 49 | addMember(v: CgVariable) 50 | { 51 | v.context = null; 52 | this.members.push(v); 53 | } 54 | getMember(name: string): CgVariable 55 | { 56 | return linq.from(this.members) 57 | .where(member => member.name === name) 58 | .firstOrDefault(); 59 | } 60 | } 61 | class CgArray extends CgType 62 | { 63 | elementTypeName: string; 64 | get elementType() { return this.context.getType(this.elementTypeName); } 65 | constructor(element: string, context: CgGlobalContext) 66 | { 67 | super(`${element}[]`, context); 68 | this.elementTypeName = element; 69 | } 70 | } 71 | class CgVector extends CgArray 72 | { 73 | scalarTypeName: string; 74 | dimension: number; 75 | get scalarType() { return this.context.getType(this.scalarTypeName); } 76 | constructor(scalarType: string, dimension: number,context:CgGlobalContext) 77 | { 78 | super(scalarType, context); 79 | this.name = `${scalarType}${dimension}`; 80 | this.scalarTypeName = scalarType; 81 | this.dimension = dimension; 82 | this.orderedMenber = true; 83 | let d = [ 84 | ["x", "y", "z", "w"], 85 | ["r", "g", "b", "a"], 86 | ["s", "t", "p", "q"], 87 | ]; 88 | for (let i = 0; i < 3; i++) 89 | { 90 | for (let j = 0; j < dimension; j++) 91 | { 92 | this.addMember(new CgVariable(this.scalarType, d[i][j])); 93 | } 94 | } 95 | } 96 | } 97 | class CgMatrix extends CgArray 98 | { 99 | scalarTypeName: string; 100 | row: number; 101 | column: number; 102 | constructor(scalarType:string, column:number, row:number,context:CgGlobalContext) 103 | { 104 | super(`${scalarType}${column}`, context); 105 | this.column = column; 106 | this.row = row; 107 | this.name = `${scalarType}${row}x${column}`; 108 | } 109 | 110 | } 111 | class CgFunction 112 | { 113 | type: CgType; 114 | name: string; 115 | semantics: string = null; 116 | parameters: CgVariable[] = []; 117 | globalContext: CgGlobalContext; 118 | functionContext: CgContext; 119 | constructor(type: CgType, name: string, semantics: string = null, params: CgVariable[] = []) 120 | { 121 | this.type = type; 122 | this.name = name; 123 | this.semantics = semantics; 124 | this.parameters = params; 125 | } 126 | addParameter(param: CgVariable) 127 | { 128 | this.parameters.push(param); 129 | if (this.functionContext) 130 | this.functionContext.addVariable(param); 131 | } 132 | setFunctionContext(context: CgContext) 133 | { 134 | this.functionContext = context; 135 | this.globalContext.addContext(context); 136 | this.parameters.forEach(param => context.addVariable(param)); 137 | } 138 | toString() 139 | { 140 | return `${this.type.name} ${this.name}(${this.parameters.map(param => param.toString()).join(", ")})${this.semantics ? ` :${this.semantics}` : ""}`; 141 | } 142 | } 143 | class CgVariable 144 | { 145 | type: CgType; 146 | name: string; 147 | semantics: string = null; 148 | context: CgContext; 149 | constructor(type: CgType, name: string, semantics: string = null) 150 | { 151 | this.type = type; 152 | this.name = name; 153 | this.semantics = semantics; 154 | } 155 | toString() 156 | { 157 | return `${this.type.name} ${this.name}${this.semantics ? ` :${this.semantics}` : ""}`; 158 | } 159 | } 160 | class CgContext 161 | { 162 | upper: CgContext; 163 | contexts: CgContext[] = []; 164 | variables: CgVariable[] = []; 165 | global: CgGlobalContext; 166 | 167 | addContext(context: CgContext) 168 | { 169 | this.contexts.push(context); 170 | context.upper = this; 171 | context.global = this.global; 172 | } 173 | addVariable(variable: CgVariable) 174 | { 175 | this.variables.push(variable); 176 | variable.context = this; 177 | } 178 | getVariable(name: string): CgVariable 179 | { 180 | let v = linq.from(this.variables).where(variable => variable.name === name).firstOrDefault(); 181 | if (!v) 182 | { 183 | return this.upper ? this.upper.getVariable(name) : null; 184 | } 185 | return v; 186 | } 187 | getType(name: string): CgType 188 | { 189 | const reg = /(float|double|half|int|fixed)([1-4])?(x[1-4])?/; 190 | let match = reg.exec(name); 191 | if (match) 192 | { 193 | if (match[3]) 194 | return new CgMatrix(match[1], parseInt(match[2]), parseInt(match[3]), this.global); 195 | else if (match[2]) 196 | return new CgVector(match[1], parseInt(match[2]), this.global); 197 | return new CgType(match[1], this.global); 198 | } 199 | else if (cgBuildInTypes.indexOf(name) >= 0) 200 | return new CgType(name, this.global); 201 | 202 | let t = linq.from(this.global.declaredTypes).where(t => t.name === name).firstOrDefault(); 203 | return t ? t : new CgType(`${name}?`, this.global); 204 | } 205 | protected internalGetAllVariables(): Map 206 | { 207 | let varMap = new Map(); 208 | if (this.upper) 209 | varMap = this.upper.internalGetAllVariables(); 210 | for (let i = 0; i < this.variables.length; i++) 211 | { 212 | varMap.set(this.variables[i].name, this.variables[i]); 213 | } 214 | return varMap; 215 | } 216 | getAllVariables(): CgVariable[] 217 | { 218 | let varMap = this.internalGetAllVariables(); 219 | let varList:CgVariable[] = []; 220 | for (let v of varMap.values()) { 221 | varList.push(v); 222 | } 223 | return varList; 224 | } 225 | } 226 | class CgGlobalContext extends CgContext 227 | { 228 | shader: Shader; 229 | declaredTypes: CgType[] = []; 230 | functions: CgFunction[] = []; 231 | constructor() 232 | { 233 | super(); 234 | this.global = this; 235 | this.upper = null; 236 | this.declaredTypes = cgBuildInTypes.map(t => new CgType(t, this)); 237 | } 238 | addContext(context: CgContext) 239 | { 240 | super.addContext(context); 241 | context.global = this; 242 | } 243 | addCustomType(type: CgType) 244 | { 245 | this.declaredTypes.push(type); 246 | type.context = this; 247 | } 248 | addFunction(func: CgFunction) 249 | { 250 | this.functions.push(func); 251 | func.globalContext = this; 252 | } 253 | } 254 | 255 | function toCgVariableCompletions(varList: CgVariable[]): CompletionItem[] 256 | { 257 | return varList.map(v => 258 | { 259 | return { 260 | label: v.name, 261 | kind: CompletionItemKind.Variable, 262 | detail: v.toString() 263 | } 264 | }); 265 | } 266 | 267 | function toCgFunctionCompletions(funcList: CgFunction[]): CompletionItem[] 268 | { 269 | return funcList.map(func => 270 | { 271 | return { 272 | label: func.name, 273 | kind: CompletionItemKind.Function, 274 | detail: func.toString() 275 | } 276 | }); 277 | } 278 | 279 | function toCgFieldCompletions(fields: CgVariable[]): CompletionItem[] 280 | { 281 | return fields.map(field => 282 | { 283 | return { 284 | label: field.name, 285 | kind: CompletionItemKind.Field, 286 | detail: field.toString() 287 | } 288 | }); 289 | } 290 | function toCompletions(labels: string[], kind: CompletionItemKind): CompletionItem[] 291 | { 292 | return labels.map(label => 293 | { 294 | return { 295 | label: label, 296 | kind: kind 297 | } 298 | }); 299 | } 300 | export 301 | { 302 | cgBuildInTypes, 303 | cgBuildInTypesCompletion, 304 | cgBuildInKeywordsCompletion, 305 | CgType, 306 | CgVariable, 307 | CgContext, 308 | CgGlobalContext, 309 | toCgVariableCompletions, 310 | CgFunction, 311 | toCgFunctionCompletions, 312 | toCgFieldCompletions, 313 | toCompletions, 314 | propertyTypes, 315 | preprocessors, 316 | CgArray 317 | }; 318 | -------------------------------------------------------------------------------- /server/src/grammar-tree.txt: -------------------------------------------------------------------------------- 1 | Shader 2 | { 3 | Properties 4 | { 5 | (, ) = 6 | } 7 | } 8 | 9 | 10 | 11 | <(> 12 | - 13 | <,> 14 | ---- 15 | [] ---- <(> 16 | 17 | [...] --------- <,> 18 | 19 | <)> 20 | <)> 21 | <=> 22 | [] 23 | [] 24 | <{> 25 | 26 | <}> 27 | [] <(> 28 | 29 | <,> 30 | 31 | <,> 32 | 33 | <,> 34 | 35 | <)> 36 | 37 | 38 | 39 | 40 | [nest] 41 | 42 | 43 | 44 | <{> 45 | [nest] 46 | 47 | 48 | <{> 49 | [nest] 50 | 51 | <(> 52 | 53 | <,> 54 | 55 | <[_a-zA-Z0-9]+> 56 | [nest] 57 | <(> 58 | 59 | [nest] 60 | <,> 61 | 62 | <)> 63 | <)> 64 | <=> 65 | 66 | [number] 67 | [nest] 68 | 69 | <{> 70 | [identifier] 71 | <}> 72 | [nest] 73 | <(> 74 | 75 | <,> 76 | 77 | <,> 78 | 79 | <,> 80 | 81 | <)> 82 | <}> 83 | [nest] 84 | 85 | 86 | <{> 87 | [nest] 88 | 89 | 90 | <{> 91 | [nest] 92 | 93 | <=> 94 | 95 | <}> 96 | [nest] 97 | 98 | 99 | 100 | [identifier] 101 | [string] 102 | [number] 103 | [nest] 104 | 105 | 106 | <{> 107 | [nest] 108 | 109 | 110 | <{> 111 | [nest] 112 | 113 | <=> 114 | 115 | <}> 116 | [nest] 117 | 118 | 119 | 120 | [identifier] 121 | [string] 122 | [number] 123 | [Scope] 124 | 125 | [optional] 126 | [nest] 127 | <#pragma> 128 | 129 | 130 | 131 | 132 | [number] 133 | [identifier] 134 | [nest] 135 | 136 | 137 | [nest] 138 | <#pragma> 139 | 140 | 141 | [nest] 142 | 143 | 144 | [nest] 145 | <#pragma> 146 | 147 | 148 | [nest] 149 | 150 | 151 | 152 | [nest] 153 | <:> 154 | 155 | [nest] 156 | <=> 157 | 158 | <;> 159 | [nest] 160 | 161 | 162 | <(> 163 | [nest] 164 | 165 | 166 | 167 | [nest] 168 | <:> 169 | 170 | [nest] 171 | <,> 172 | 173 | 174 | 175 | 176 | [nest] 177 | <:> 178 | 179 | <)> 180 | [nest] 181 | <:> 182 | 183 | 184 | <}> 185 | [Scope] 186 | 187 | [optional] 188 | [nest] 189 | <#pragma> 190 | 191 | 192 | 193 | 194 | [number] 195 | [identifier] 196 | [nest] 197 | 198 | 199 | [nest] 200 | <#pragma> 201 | 202 | 203 | [nest] 204 | 205 | 206 | [nest] 207 | <#pragma> 208 | 209 | 210 | [nest] 211 | 212 | 213 | 214 | [nest] 215 | <:> 216 | 217 | [nest] 218 | <=> 219 | 220 | <;> 221 | [nest] 222 | 223 | 224 | <(> 225 | [nest] 226 | 227 | 228 | 229 | [nest] 230 | <:> 231 | 232 | [nest] 233 | <,> 234 | 235 | 236 | 237 | 238 | [nest] 239 | <:> 240 | 241 | <)> 242 | [nest] 243 | <:> 244 | 245 | 246 | <}> 247 | <}> -------------------------------------------------------------------------------- /server/src/grammar.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument, Position, CompletionItem, Diagnostic } from "vscode-languageserver"; 2 | import linq from "linq"; 3 | //import sourceShaderLab from "./shaderlab.grammar"; 4 | 5 | type DocumentCompletionCallback = (match:MatchResult) => CompletionItem[]; 6 | type PatternMatchedCallback = (patternMatch: PatternMatchResult) => void; 7 | type ScopeMatchedCallback = (scopeMatch: ScopeMatchResult) => void; 8 | type DocumentDiagnoseCallback = (unMatched: UnMatchedText) => Diagnostic[]; 9 | type PatternItemDictionary = { [key: string]: (pattern: GrammarPattern) => PatternItem }; 10 | 11 | abstract class PatternItem 12 | { 13 | name: string = "pattern"; 14 | parent?: OrderedPatternSet; 15 | strict: boolean = false; 16 | ignorable: boolean = false; 17 | multi: boolean = false; 18 | pattern: GrammarPattern; 19 | abstract match(doc: TextDocument, startOffset: number): MatchResult; 20 | constructor(pattern: GrammarPattern, ignorable = false) 21 | { 22 | this.pattern = pattern; 23 | this.ignorable = ignorable; 24 | } 25 | toString(): string 26 | { 27 | return this.ignorable ? `[${this.name}]` : `<${this.name}>`; 28 | } 29 | } 30 | class NamedPattern extends PatternItem 31 | { 32 | patternItem: PatternItem; 33 | constructor(pattern: GrammarPattern, name: string, patternItem: PatternItem) 34 | { 35 | super(pattern, patternItem.ignorable); 36 | this.name = name; 37 | this.patternItem = patternItem; 38 | } 39 | match(doc: TextDocument, startOffset: number): MatchResult 40 | { 41 | let match = this.patternItem.match(doc, startOffset); 42 | match.patternName = this.name; 43 | //match.pattern = this.pattern; 44 | return match; 45 | } 46 | 47 | } 48 | class EmptyPattern extends PatternItem 49 | { 50 | name = "space"; 51 | constructor(pattern: GrammarPattern, ignorable: boolean = false) 52 | { 53 | super(pattern, ignorable); 54 | } 55 | static skipEmpty(doc: TextDocument, startOffset: number, crossLine = false): RegExpExecArray 56 | { 57 | const reg = crossLine ? 58 | /(((?:\s|\/\*(?!\/).*?\*\/)*)(\/\/.*[\r]?[\n]?)?)*/ 59 | : 60 | /((?:[ \t]|\/\*(?!\/).*?\*\/)*)?/; 61 | const text = doc.getText().substr(startOffset); 62 | let match = reg.exec(text); 63 | if (match.index > 0) 64 | return null; 65 | return match; 66 | } 67 | static isEmpty(text: string,crossLine:boolean = false) 68 | { 69 | const reg = crossLine ? 70 | /^(((?:\s|\/\*(?!\/).*?\*\/)*)(\/\/.*[\r]?[\n]?)?)*$/ 71 | : 72 | /^((?:[ \t]|\/\*(?!\/).*?\*\/)*)?$/; 73 | return reg.test(text); 74 | } 75 | match(doc: TextDocument, startOffset: number): MatchResult 76 | { 77 | let empty = EmptyPattern.skipEmpty(doc, startOffset, this.pattern.crossLine); 78 | let match = new MatchResult(doc, this); 79 | match.startOffset = startOffset; 80 | if (empty && empty[0].length > 0) 81 | { 82 | match.endOffset = startOffset + empty[0].length; 83 | match.matched = true; 84 | } 85 | else 86 | { 87 | match.endOffset = startOffset; 88 | match.matched = false; 89 | } 90 | return match; 91 | } 92 | } 93 | class RegExpPattern extends PatternItem 94 | { 95 | name = "regExp"; 96 | regExp: RegExp; 97 | constructor(pattern: GrammarPattern, reg: RegExp, ignorable: boolean = false) 98 | { 99 | super(pattern, ignorable); 100 | this.regExp = reg; 101 | } 102 | match(doc: TextDocument, startOffset: number): MatchResult 103 | { 104 | let skip = EmptyPattern.skipEmpty(doc, startOffset, this.pattern.crossLine); 105 | if (skip) 106 | startOffset += skip[0].length; 107 | let text = doc.getText().substr(startOffset); 108 | let regMatch = this.regExp.exec(text); 109 | let match = new MatchResult(doc, this); 110 | match.startOffset = startOffset; 111 | if (!regMatch || regMatch.index !== 0) 112 | { 113 | match.endOffset = startOffset; 114 | match.matched = false; 115 | } 116 | else 117 | { 118 | match.endOffset = startOffset + regMatch[0].length; 119 | match.matched = true; 120 | } 121 | return match; 122 | } 123 | } 124 | class TextPattern extends RegExpPattern 125 | { 126 | text: string; 127 | currentIdx: number = 0; 128 | get ignoreCase() { return this.regExp.ignoreCase; } 129 | constructor(pattern: GrammarPattern, text: string, ignorable = false, ignoreCase = false) 130 | { 131 | super(pattern, new RegExp(text.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'), ignoreCase ? "i" : ""), ignorable); 132 | this.text = text; 133 | this.name = text; 134 | } 135 | } 136 | class StringPattern extends RegExpPattern 137 | { 138 | name = "string"; 139 | begin: boolean = false; 140 | slash: boolean = false; 141 | end: boolean = false; 142 | constructor(pattern: GrammarPattern, ignorable = false) 143 | { 144 | super(pattern, /"([^\\"]|\\\S|\\")*"/, ignorable); 145 | } 146 | //toString = () => this.ignorable ? "[string]" : ""; 147 | } 148 | class NumberPattern extends RegExpPattern 149 | { 150 | name = "number"; 151 | constructor(pattern: GrammarPattern, ignorable = false) 152 | { 153 | super(pattern, /[+-]?[0-9]+\.?[0-9]*/, ignorable); 154 | } 155 | } 156 | class IdentifierPattern extends RegExpPattern 157 | { 158 | name = "identifier"; 159 | constructor(pattern: GrammarPattern, ignorable = false) 160 | { 161 | super(pattern, /[_a-zA-Z][_a-zA-Z0-9]*/, ignorable); 162 | } 163 | } 164 | 165 | 166 | class OrderedPatternSet extends PatternItem 167 | { 168 | name = "nest"; 169 | subPatterns: PatternItem[] = []; 170 | currentIdx: number = 0; 171 | get count() { return this.subPatterns.length; } 172 | addSubPattern(patternItem: PatternItem) 173 | { 174 | this.subPatterns.push(patternItem); 175 | } 176 | toString() 177 | { 178 | let str = super.toString() + "\r\n" + this.subPatterns.map(pattern => pattern.toString()).join("\r\n").split(/\r\n/g).map(str => "\t" + str).join("\r\n"); 179 | return str; 180 | } 181 | match(doc: TextDocument, startOffset: number): MatchResult 182 | { 183 | let match = new MatchResult(doc, this); 184 | match.startOffset = startOffset; 185 | try 186 | { 187 | for (let i = 0; i < this.subPatterns.length; i++) 188 | { 189 | let subMatch = this.subPatterns[i].match(doc, startOffset); 190 | if (!subMatch.matched) 191 | { 192 | if (this.strict && !EmptyPattern.isEmpty(subMatch.text)) 193 | { 194 | match.addChildren(subMatch); 195 | match.endOffset = subMatch.endOffset; 196 | match.matched = false; 197 | return match; 198 | } 199 | if (this.subPatterns[i].ignorable) 200 | continue; 201 | match.addChildren(subMatch); 202 | match.endOffset = subMatch.endOffset; 203 | match.matched = false; 204 | return match; 205 | } 206 | match.addChildren(subMatch); 207 | startOffset = subMatch.endOffset; 208 | if (this.subPatterns[i].multi) 209 | i--; 210 | } 211 | if (match.children.length === 0) 212 | { 213 | match.endOffset = match.startOffset + 1; 214 | match.matched = false; 215 | } 216 | else 217 | { 218 | match.endOffset = match.children[match.children.length - 1].endOffset; 219 | match.startOffset = match.children[0].startOffset; 220 | match.matched = true; 221 | } 222 | } 223 | catch (ex) 224 | { 225 | console.error(ex); 226 | } 227 | return match; 228 | } 229 | } 230 | 231 | /** 232 | * Set of PatternItem created by compiled GrammarPattern 233 | */ 234 | class OptionalPatternSet extends OrderedPatternSet 235 | { 236 | name = "optional"; 237 | 238 | match(doc: TextDocument, startOffset: number): MatchResult 239 | { 240 | let match = new PatternMatchResult(doc, this); 241 | match.startOffset = startOffset; 242 | let failedMatches: MatchResult[] = []; 243 | for (let i = 0; i < this.subPatterns.length; i++) 244 | { 245 | let subMatch = this.subPatterns[i].match(doc, startOffset); 246 | if (!subMatch.matched) 247 | { 248 | failedMatches.push(subMatch); 249 | continue; 250 | } 251 | match.addChildren(subMatch); 252 | break; 253 | } 254 | if (match.children.length === 0) 255 | { 256 | match.endOffset = match.startOffset + 1; 257 | match.matched = false; 258 | let unMatched = new UnMatchedPattern(doc, this, failedMatches); 259 | unMatched.startOffset = startOffset; 260 | unMatched.endOffset = linq.from(failedMatches).max(match => match.endOffset); 261 | return unMatched; 262 | } 263 | else 264 | { 265 | match.endOffset = match.children[match.children.length - 1].endOffset; 266 | match.startOffset = match.children[0].startOffset; 267 | match.matched = true; 268 | } 269 | return match; 270 | } 271 | } 272 | class ScopePattern extends OrderedPatternSet 273 | { 274 | name = "scope"; 275 | scope: GrammarScope; 276 | 277 | constructor(pattern: GrammarPattern, scope: GrammarScope) 278 | { 279 | super(pattern, false); 280 | this.scope = scope; 281 | } 282 | match(doc: TextDocument, startOffset: number): MatchResult 283 | { 284 | function cleanSpace() 285 | { 286 | let skip = EmptyPattern.skipEmpty(doc, startOffset, true); 287 | if (skip) 288 | startOffset += skip[0].length; 289 | } 290 | let match = new ScopeMatchResult(doc, this); 291 | match.startOffset = startOffset; 292 | try 293 | { 294 | cleanSpace(); 295 | // Match first pattern 296 | let subMatch = this.subPatterns[0].match(doc, startOffset); 297 | match.beginMatch = subMatch; 298 | match.endOffset = startOffset = subMatch.endOffset; 299 | if (!subMatch.matched) 300 | { 301 | match.matched = false; 302 | return match; 303 | } 304 | else 305 | cleanSpace(); 306 | 307 | let hasMatched = false; 308 | let failedMatches: MatchResult[] = []; 309 | for (let i = 1; i < this.subPatterns.length; i++) 310 | { 311 | let subMatch = this.subPatterns[i].match(doc, startOffset); 312 | if (!subMatch.matched) 313 | { 314 | failedMatches.push(subMatch); 315 | if (i < this.subPatterns.length - 1) 316 | continue; 317 | } 318 | else 319 | { 320 | failedMatches = []; 321 | if (i === this.subPatterns.length - 1) 322 | { 323 | match.endMatch = subMatch; 324 | break; 325 | } 326 | match.addChildren(subMatch); 327 | match.endOffset = startOffset = subMatch.endOffset; 328 | hasMatched = true; 329 | 330 | cleanSpace(); 331 | } 332 | 333 | // Skip a line and continue matching 334 | if (!hasMatched) 335 | { 336 | let unMatched = new UnMatchedText(doc, this.scope, failedMatches); 337 | failedMatches = []; 338 | unMatched.startOffset = startOffset; 339 | match.addChildren(unMatched); 340 | if (!this.scope.skipMode || this.scope.skipMode === "line") 341 | { 342 | let pos = doc.positionAt(startOffset); 343 | pos.line++; 344 | pos.character = 0; 345 | startOffset = doc.offsetAt(pos); 346 | unMatched.endOffset = startOffset - 1; 347 | // Chceck if reach end 348 | let pos2 = doc.positionAt(startOffset); 349 | if (pos2.line !== pos.line) 350 | { 351 | match.matched = false; 352 | return match; 353 | } 354 | } 355 | else 356 | { 357 | startOffset = linq.from(unMatched.allMatches).max(match => match.endOffset); 358 | unMatched.endOffset = startOffset; 359 | } 360 | cleanSpace(); 361 | } 362 | i = 0; 363 | hasMatched = false; 364 | } 365 | if (!match.endMatch) 366 | { 367 | match.startOffset = match.beginMatch.startOffset; 368 | match.matched = false; 369 | return match; 370 | } 371 | else 372 | { 373 | match.startOffset = match.beginMatch.startOffset; 374 | match.endOffset = match.endMatch.endOffset; 375 | match.matched = true; 376 | } 377 | } 378 | catch (ex) 379 | { 380 | console.error(ex); 381 | } 382 | return match; 383 | } 384 | } 385 | class Grammar extends ScopePattern 386 | { 387 | name = "grammar"; 388 | grammar: LanguageGrammar; 389 | constructor(grammar: LanguageGrammar) 390 | { 391 | super(null, null); 392 | this.grammar = grammar; 393 | } 394 | match(doc: TextDocument, startOffset: number = 0): GrammarMatchResult 395 | { 396 | function cleanSpace() 397 | { 398 | let skip = EmptyPattern.skipEmpty(doc, startOffset, true); 399 | if (skip) 400 | startOffset += skip[0].length; 401 | } 402 | let match = new GrammarMatchResult(doc, this); 403 | let end = doc.getText().length; 404 | match.startOffset = 0; 405 | while (startOffset != end) 406 | { 407 | let hasMatched = false; 408 | let failedMathes: MatchResult[] = []; 409 | for (let i = 0; i < this.subPatterns.length; i++) 410 | { 411 | let subMatch = this.subPatterns[i].match(doc, startOffset); 412 | if (!subMatch.matched) 413 | { 414 | failedMathes.push(subMatch); 415 | continue; 416 | } 417 | failedMathes = []; 418 | hasMatched = true; 419 | match.addChildren(subMatch); 420 | match.endOffset = startOffset = subMatch.endOffset; 421 | cleanSpace(); 422 | break; 423 | } 424 | 425 | if (!hasMatched) 426 | { 427 | let unMatched = new UnMatchedText(doc, this.scope, failedMathes); 428 | failedMathes = []; 429 | unMatched.startOffset = startOffset; 430 | match.addChildren(unMatched); 431 | 432 | let pos = doc.positionAt(startOffset); 433 | pos.line++; 434 | pos.character = 0; 435 | startOffset = doc.offsetAt(pos); 436 | unMatched.endOffset = startOffset - 1; 437 | // Chceck if reach end 438 | let pos2 = doc.positionAt(startOffset); 439 | if (pos2.line !== pos.line) 440 | { 441 | break; 442 | } 443 | cleanSpace(); 444 | } 445 | } 446 | match.endOffset = end; 447 | match.matched = true; 448 | 449 | match.processSubMatches(); 450 | 451 | return match; 452 | } 453 | } 454 | class MatchResult 455 | { 456 | document: TextDocument; 457 | patternItem: PatternItem; 458 | patternName: string = null; 459 | startOffset: number; 460 | endOffset: number; 461 | matched: boolean = true; 462 | scope: GrammarScope; 463 | state: any = null; 464 | parent: MatchResult = null; 465 | children: MatchResult[] = []; 466 | matchedScope: ScopeMatchResult; 467 | matchedPattern: PatternMatchResult; 468 | _unmatchedPattern: UnMatchedPattern; 469 | private _pattern: GrammarPattern = null; 470 | get start() { return this.document.positionAt(this.startOffset); } 471 | get end() { return this.document.positionAt(this.endOffset); } 472 | get length() { return this.endOffset - this.startOffset; } 473 | get text() { return this.document.getText({ start: this.start, end: this.end }); } 474 | get pattern() { return this._pattern ? this._pattern : this.patternItem.pattern; } 475 | set pattern(value) { this._pattern = value; } 476 | constructor(doc: TextDocument, patternItem: PatternItem) 477 | { 478 | this.document = doc; 479 | this.patternItem = patternItem; 480 | } 481 | toString(): string 482 | { 483 | return this.text; 484 | } 485 | addChildren(match :MatchResult) 486 | { 487 | match.parent = this; 488 | this.children.push(match); 489 | } 490 | locateMatchAtPosition(pos: Position): MatchResult 491 | { 492 | let offset = this.document.offsetAt(pos); 493 | if (offset < this.startOffset || this.endOffset < offset) 494 | return null; 495 | if (this.children.length <= 0) 496 | return this; 497 | let child = linq.from(this.children).where(child => child.startOffset <= offset && offset <= child.endOffset).firstOrDefault(); 498 | if (!child) 499 | return this; 500 | return child.locateMatchAtPosition(pos); 501 | } 502 | } 503 | class PatternMatchResult extends MatchResult 504 | { 505 | private matchesDict: Map = new Map(); 506 | private _matchesList: MatchResult[] = null; 507 | private get allMathches(): MatchResult[] 508 | { 509 | if (this.children.length <= 0) 510 | return []; 511 | if (!this._matchesList) 512 | { 513 | let list = [this.children[0]]; 514 | for (let i = 0; i < list.length; i++) 515 | { 516 | if (list[i] instanceof PatternMatchResult || list[i] instanceof ScopeMatchResult || list[i] instanceof UnMatchedText) 517 | continue; 518 | if (!list[i]) 519 | console.log(list[i]); 520 | list = list.concat(list[i].children); 521 | 522 | } 523 | this._matchesList = list; 524 | } 525 | return this._matchesList; 526 | } 527 | getMatch(name: string): MatchResult[] 528 | { 529 | if (this.children.length <= 0) 530 | return null; 531 | /*if (this.matchesDict.get(name)) 532 | return this.matchesDict.get(name);*/ 533 | return linq.from(this.allMathches).where(match => match.patternName === name).toArray(); 534 | 535 | } 536 | processSubMatches() 537 | { 538 | if (this.pattern.onMatched) 539 | this.pattern.onMatched(this); 540 | 541 | this.allMathches.forEach(match => 542 | { 543 | if (match != this) 544 | { 545 | match.matchedScope = this.matchedScope; 546 | match.matchedPattern = this; 547 | } 548 | if (match instanceof ScopeMatchResult) 549 | { 550 | match.processSubMatches(); 551 | } 552 | else if (match instanceof PatternMatchResult) 553 | { 554 | match.processSubMatches(); 555 | } 556 | else if (match instanceof UnMatchedText) 557 | { 558 | match.processSubMatches(); 559 | } 560 | }); 561 | } 562 | } 563 | class ScopeMatchResult extends MatchResult 564 | { 565 | beginMatch: MatchResult; 566 | endMatch: MatchResult; 567 | constructor(doc: TextDocument, scope: ScopePattern) 568 | { 569 | super(doc, scope); 570 | this.scope = scope.scope; 571 | } 572 | processSubMatches() 573 | { 574 | if (this.scope && this.scope.onMatched) 575 | this.scope.onMatched(this); 576 | 577 | let matchList: MatchResult[] = [this]; 578 | for (let i = 0; i < matchList.length; i++) 579 | { 580 | let subMatch = matchList[i]; 581 | if (i > 0) 582 | { 583 | subMatch.matchedScope = this; 584 | subMatch.matchedPattern = this.matchedPattern; 585 | if (subMatch instanceof ScopeMatchResult) 586 | { 587 | subMatch.processSubMatches(); 588 | continue; 589 | } 590 | else if (subMatch instanceof PatternMatchResult) 591 | { 592 | subMatch.processSubMatches(); 593 | continue; 594 | } 595 | else if (subMatch instanceof UnMatchedText) 596 | { 597 | subMatch.processSubMatches(); 598 | continue; 599 | } 600 | } 601 | matchList = matchList.concat(subMatch.children); 602 | 603 | } 604 | } 605 | } 606 | class GrammarMatchResult extends ScopeMatchResult 607 | { 608 | grammar: LanguageGrammar; 609 | constructor(doc: TextDocument, grammar: Grammar) 610 | { 611 | super(doc, grammar); 612 | this.grammar = grammar.grammar; 613 | } 614 | requestCompletion(pos: Position): CompletionItem[] 615 | { 616 | let completions: CompletionItem[] = []; 617 | let match = this.locateMatchAtPosition(pos); 618 | if (!match) 619 | return []; 620 | if (match instanceof UnMatchedPattern) 621 | { 622 | completions = completions.concat(match.requestCompletion(pos)); 623 | } 624 | if (match instanceof UnMatchedText) 625 | { 626 | completions = completions.concat(match.requestCompletion(pos)); 627 | } 628 | else if (match instanceof GrammarMatchResult) 629 | { 630 | completions = match.grammar.onCompletion ? 631 | completions.concat(match.grammar.onCompletion(match)) : 632 | completions; 633 | } 634 | else if (match instanceof ScopeMatchResult) 635 | { 636 | completions = (match.scope && match.scope.onCompletion) ? 637 | completions.concat(match.scope.onCompletion(match)) : 638 | completions; 639 | } 640 | for (let matchP = match; matchP != null; matchP = matchP.parent) 641 | { 642 | if (matchP instanceof PatternMatchResult && matchP.pattern.onCompletion) 643 | { 644 | completions = completions.concat(matchP.pattern.onCompletion(matchP)); 645 | } 646 | if (!matchP.patternName) 647 | continue; 648 | if (matchP.matchedPattern && matchP.matchedPattern.pattern.onCompletion) 649 | { 650 | let comps = matchP.matchedPattern.pattern.onCompletion(matchP); 651 | completions = completions.concat(comps); 652 | } 653 | if (matchP.matchedScope && matchP.matchedScope.scope.onCompletion) 654 | { 655 | let comps = matchP.matchedScope.scope.onCompletion(matchP); 656 | completions = completions.concat(comps); 657 | } 658 | } 659 | // Remove same 660 | 661 | completions = linq.from(completions) 662 | .where(item => item !== undefined) 663 | .distinct(comp=>comp.label) 664 | .toArray(); 665 | return completions; 666 | } 667 | } 668 | class UnMatchedText extends MatchResult 669 | { 670 | allMatches: MatchResult[]; 671 | matched = false; 672 | protected _matchesList: MatchResult[] = null; 673 | protected get allSubMatches(): MatchResult[] 674 | { 675 | if (this.allMatches.length <= 0) 676 | return []; 677 | if (!this._matchesList) 678 | { 679 | let list = this.allMatches; 680 | for (let i = 0; i < list.length; i++) 681 | { 682 | if (list[i] instanceof PatternMatchResult || list[i] instanceof ScopeMatchResult || list[i] instanceof UnMatchedText) 683 | continue; 684 | list = list.concat(list[i].children); 685 | } 686 | this._matchesList = list; 687 | } 688 | return this._matchesList; 689 | } 690 | constructor(doc: TextDocument, scope: GrammarScope, matches: MatchResult[]) 691 | { 692 | super(doc, null); 693 | this.scope = scope; 694 | this.allMatches = matches; 695 | } 696 | processSubMatches() 697 | { 698 | this.allMatches.forEach(match => match.parent = this); 699 | this.allSubMatches.forEach(match => 700 | { 701 | match.matchedPattern = this.matchedPattern; 702 | match.matchedScope = this.matchedScope; 703 | if (match instanceof PatternMatchResult || match instanceof ScopeMatchResult || match instanceof UnMatchedText) 704 | { 705 | match.processSubMatches(); 706 | } 707 | }); 708 | //console.log(this.text); 709 | } 710 | requestCompletion(pos: Position): CompletionItem[] 711 | { 712 | let completions: CompletionItem[] = []; 713 | this.allMatches.forEach(match => 714 | { 715 | let endMatch = match.locateMatchAtPosition(pos); 716 | if (!endMatch) 717 | return; 718 | if (match instanceof UnMatchedPattern) 719 | { 720 | completions = completions.concat(match.requestCompletion(pos)); 721 | } 722 | if (endMatch instanceof UnMatchedText) 723 | { 724 | completions = completions.concat(endMatch.requestCompletion(pos)); 725 | } 726 | for (let matchP = endMatch; matchP != this; matchP = matchP.parent) 727 | { 728 | if (!matchP.patternName) 729 | continue; 730 | if (matchP._unmatchedPattern && matchP._unmatchedPattern.pattern.onCompletion) 731 | { 732 | let comps = matchP._unmatchedPattern.pattern.onCompletion(matchP); 733 | completions = completions.concat(comps); 734 | } 735 | else 736 | { 737 | if (matchP.matchedPattern && matchP.matchedPattern.pattern.onCompletion) 738 | { 739 | let comps = matchP.matchedPattern.pattern.onCompletion(matchP); 740 | completions = completions.concat(comps); 741 | } 742 | if (matchP.matchedScope && matchP.matchedScope.scope.onCompletion) 743 | { 744 | let comps = matchP.matchedScope.scope.onCompletion(matchP); 745 | completions = completions.concat(comps); 746 | } 747 | } 748 | 749 | } 750 | }); 751 | completions = (this.matchedScope && this.matchedScope.scope && this.matchedScope.scope.onCompletion) ? 752 | completions.concat(this.matchedScope.scope.onCompletion(this)) : 753 | completions; 754 | return completions; 755 | } 756 | addChildren(match: MatchResult) 757 | { 758 | match.parent = this; 759 | this.allMatches.push(match); 760 | } 761 | } 762 | class UnMatchedPattern extends UnMatchedText 763 | { 764 | constructor(doc: TextDocument, patternItem: PatternItem, matches: MatchResult[]) 765 | { 766 | super(doc, patternItem.pattern._scope, matches); 767 | this.patternItem = patternItem; 768 | } 769 | processSubMatches() 770 | { 771 | this.allMatches.forEach(match => match.parent = this); 772 | this.allSubMatches.forEach(match => 773 | { 774 | match._unmatchedPattern = this; 775 | match.matchedPattern = this.matchedPattern; 776 | match.matchedScope = this.matchedScope; 777 | if (match instanceof PatternMatchResult || match instanceof ScopeMatchResult || match instanceof UnMatchedText) 778 | { 779 | match.processSubMatches(); 780 | } 781 | }); 782 | //console.log(this.text); 783 | } 784 | requestCompletion(pos: Position) 785 | { 786 | let completions: CompletionItem[] = []; 787 | if (this.pattern.onCompletion) 788 | completions = completions.concat(this.pattern.onCompletion(this)); 789 | return completions.concat(super.requestCompletion(pos)); 790 | } 791 | getMatch(name: string): MatchResult[] 792 | { 793 | return linq.from(this.allSubMatches).where(match => match.patternName === name).toArray(); 794 | } 795 | } 796 | class PatternDictionary 797 | { 798 | [key: string]: GrammarPattern; 799 | } 800 | class ScopeDictionary 801 | { 802 | [key: string]: GrammarScope; 803 | } 804 | class GrammarScope 805 | { 806 | begin: string; 807 | end: string; 808 | skipMode?: "line" | "space"; 809 | patterns?: GrammarPattern[]; 810 | scopes?: GrammarScope[]; 811 | name?: string; 812 | ignore?: GrammarPattern; 813 | pairMatch?: string[][]; 814 | onMatched?: ScopeMatchedCallback; 815 | onCompletion?: DocumentCompletionCallback; 816 | _compiledPattern?: ScopePattern; 817 | _grammar?: LanguageGrammar 818 | } 819 | class GrammarPattern 820 | { 821 | static String: GrammarPattern = { patterns: [""], name: "String" }; 822 | static Number: GrammarPattern = { patterns: [''], name: "Number" }; 823 | static Identifier: GrammarPattern = { patterns: [''], name: "Identifier" }; 824 | patterns: string[]; 825 | caseInsensitive?: boolean = false; 826 | dictionary?: PatternDictionary; 827 | keepSpace?: boolean = false; 828 | name?: string; 829 | id?: string; 830 | strict?: boolean = false; 831 | crossLine?: boolean = false; 832 | scopes?: ScopeDictionary; 833 | recursive?: boolean = false; 834 | 835 | onMatched?: PatternMatchedCallback; 836 | onDiagnostic?: DocumentDiagnoseCallback; 837 | onCompletion?: DocumentCompletionCallback; 838 | _parent?: GrammarPattern; 839 | _nameInParent?: string; 840 | _scope?: GrammarScope; 841 | _compiledPattern?: PatternItem; 842 | _grammar?: LanguageGrammar; 843 | _compiling?: boolean = false; 844 | } 845 | class LanguageGrammar 846 | { 847 | patterns?: GrammarPattern[]; 848 | name?: string; 849 | ignore?: GrammarPattern; 850 | stringDelimiter?: string[]; 851 | pairMatch?: string[][]; 852 | patternRepository?: PatternDictionary; 853 | scopeRepository?: ScopeDictionary; 854 | onCompletion?: DocumentCompletionCallback; 855 | } 856 | function analyseBracketItem(item: string, pattern: GrammarPattern): PatternItem 857 | { 858 | const buildInPattern: PatternItemDictionary = { 859 | "string": (pt: GrammarPattern) => new StringPattern(pt), 860 | "number": (pt: GrammarPattern) => new NumberPattern(pt), 861 | "identifier": (pt: GrammarPattern) => new IdentifierPattern(pt), 862 | " ": (pt: GrammarPattern) => new EmptyPattern(pt), 863 | } 864 | if (item[0] === "<" && item[item.length - 1] === ">") 865 | { 866 | let subPattern: PatternItem; 867 | let name = item.substr(1, item.length - 2); 868 | if (buildInPattern[name]) 869 | subPattern = buildInPattern[name](pattern); 870 | else if (pattern.dictionary && pattern.dictionary[name]) 871 | { 872 | pattern.dictionary[name]._grammar = pattern._grammar; 873 | subPattern = compilePattern(pattern.dictionary[name]); 874 | subPattern 875 | } 876 | else if (pattern._grammar.patternRepository && pattern._grammar.patternRepository[name]) 877 | { 878 | pattern._grammar.patternRepository[name]._grammar = pattern._grammar; 879 | subPattern = compilePattern(pattern._grammar.patternRepository[name]); 880 | } 881 | else 882 | subPattern = new IdentifierPattern(pattern); 883 | subPattern.ignorable = false; 884 | return new NamedPattern(pattern, name, subPattern); 885 | } 886 | else if (item[0] === "[" && item[item.length - 1] === "]") 887 | { 888 | item = item.substr(1, item.length - 2); 889 | let multi = false; 890 | if (item.endsWith("...")) 891 | { 892 | multi = true; 893 | item = item.substr(0, item.length - 3); 894 | } 895 | let subPattern = analysePatternItem(item, pattern); 896 | subPattern.ignorable = true; 897 | subPattern.multi = multi; 898 | return subPattern; 899 | } 900 | else if (item[0] === "{" && item[item.length - 1] === "}") 901 | { 902 | let name = item.substr(1, item.length - 2); 903 | let scope: GrammarScope; 904 | if (pattern.scopes && pattern.scopes[name]) 905 | scope = pattern.scopes[name]; 906 | else if (pattern._grammar.scopeRepository && pattern._grammar.scopeRepository[name]) 907 | scope = pattern._grammar.scopeRepository[name]; 908 | 909 | if (!scope) 910 | throw new Error("Pattern undefined."); 911 | scope._grammar = pattern._grammar; 912 | return compileScope(scope, pattern); 913 | } 914 | else if (item.startsWith("/") && item.endsWith("/")) 915 | { 916 | let reg = item.substr(1, item.length - 2); 917 | let subPattern = new RegExpPattern(pattern, new RegExp(reg, pattern.caseInsensitive ? "i" : ""), false); 918 | subPattern.name = reg; 919 | return subPattern; 920 | } 921 | throw new Error("Syntax Error."); 922 | } 923 | function analysePatternItem(item: string, pattern: GrammarPattern): PatternItem 924 | { 925 | const bracketStart = ["<", "[", "{", "/"]; 926 | const bracketEnd = [">", "]", "}", "/"]; 927 | const spaceChars = [" "]; 928 | const isBracketStart = (chr: string): boolean => bracketStart.indexOf(chr) >= 0; 929 | const isBracketEnd = (chr: string): boolean => bracketEnd.indexOf(chr) >= 0; 930 | const isSpace = (chr: string): boolean => spaceChars.indexOf(chr) >= 0; 931 | enum State { CollectWords, MatchBracket }; 932 | 933 | let patternItem: OrderedPatternSet = new OrderedPatternSet(pattern, false); 934 | let state: State = State.CollectWords; 935 | let bracketDepth = 0; 936 | let startBracket = ""; 937 | let words = ""; 938 | 939 | for (let i = 0; i < item.length; i++) 940 | { 941 | if (state === State.CollectWords) 942 | { 943 | if (item[i] === "\\") 944 | { 945 | words += item[++i]; 946 | continue; 947 | } 948 | if (isBracketStart(item[i])) 949 | { 950 | if (words !== "") 951 | patternItem.addSubPattern(new TextPattern(pattern, words)); 952 | words = item[i]; 953 | state = State.MatchBracket; 954 | bracketDepth++; 955 | continue; 956 | } 957 | else if (isSpace(item[i])) 958 | { 959 | if (words !== "") 960 | patternItem.addSubPattern(new TextPattern(pattern, words)); 961 | words = ""; 962 | if (pattern.keepSpace) 963 | patternItem.addSubPattern(new EmptyPattern(pattern, false)); 964 | continue; 965 | } 966 | else if (isBracketEnd(item[i])) 967 | throw new Error("Syntax error."); 968 | else 969 | { 970 | words += item[i]; 971 | continue; 972 | } 973 | } 974 | else if (state === State.MatchBracket) 975 | { 976 | if (item[i] === "\\") 977 | { 978 | words += (item[i] + item[++i]); 979 | continue; 980 | } 981 | words += item[i]; 982 | if (isBracketEnd(item[i])) 983 | { 984 | bracketDepth--; 985 | if (bracketDepth === 0) 986 | { 987 | patternItem.addSubPattern(analyseBracketItem(words, pattern)); 988 | words = ""; 989 | state = State.CollectWords; 990 | continue; 991 | } 992 | } 993 | else if (isBracketStart(item[i])) 994 | bracketDepth++; 995 | } 996 | } 997 | 998 | if (state === State.CollectWords && words !== "") 999 | patternItem.addSubPattern(new TextPattern(pattern, words, false, pattern.caseInsensitive)); 1000 | else if (state === State.MatchBracket && bracketDepth > 0) 1001 | throw new Error("Syntax error."); 1002 | 1003 | if (patternItem.subPatterns.length === 0) 1004 | throw new Error("No pattern."); 1005 | else if (patternItem.subPatterns.length === 1) 1006 | { 1007 | patternItem.subPatterns[0].ignorable = patternItem.ignorable; 1008 | return patternItem.subPatterns[0]; 1009 | } 1010 | return patternItem; 1011 | } 1012 | function compilePattern(pattern: GrammarPattern): PatternItem 1013 | { 1014 | if (pattern === GrammarPattern.String) 1015 | return new StringPattern(pattern); 1016 | if (pattern._compiledPattern) 1017 | return pattern._compiledPattern; 1018 | pattern._compiling = true; 1019 | let patternList: OptionalPatternSet = new OptionalPatternSet(pattern, true); 1020 | pattern._compiledPattern = patternList; 1021 | pattern.patterns.forEach(pt => 1022 | { 1023 | let subPattern = analysePatternItem(pt, pattern); 1024 | subPattern.strict = pattern.strict ? true : false; 1025 | subPattern.ignorable = true; 1026 | patternList.addSubPattern(subPattern); 1027 | }); 1028 | if (patternList.count === 0) 1029 | throw new Error("No pattern."); 1030 | if (patternList.count === 1) 1031 | { 1032 | if (patternList.subPatterns[0] == patternList) 1033 | throw new Error("Looped."); 1034 | if (!(pattern.id || pattern.onMatched || pattern.onCompletion || pattern.onDiagnostic)) 1035 | { 1036 | patternList.subPatterns[0].ignorable = true; 1037 | pattern._compiledPattern = patternList.subPatterns[0]; 1038 | return patternList.subPatterns[0]; 1039 | } 1040 | } 1041 | return patternList; 1042 | } 1043 | function compileScope(scope: GrammarScope, pattern: GrammarPattern): ScopePattern 1044 | { 1045 | if (scope._compiledPattern) 1046 | return scope._compiledPattern; 1047 | let patternList = new ScopePattern(pattern, scope); 1048 | patternList.addSubPattern(new TextPattern(pattern, scope.begin, false)); 1049 | scope._compiledPattern = patternList; 1050 | scope.patterns.forEach(pt => 1051 | { 1052 | pt._grammar = pattern._grammar; 1053 | let subPattern = compilePattern(pt); 1054 | subPattern.ignorable = true; 1055 | subPattern.multi = true; 1056 | patternList.addSubPattern(subPattern); 1057 | }); 1058 | patternList.addSubPattern(new TextPattern(pattern, scope.end, false)); 1059 | patternList.name = scope.name ? scope.name : "Scope"; 1060 | return patternList; 1061 | } 1062 | function compileGrammar(grammarDeclare: LanguageGrammar): Grammar 1063 | { 1064 | let grammar = new Grammar(grammarDeclare); 1065 | grammarDeclare.patterns.forEach(pattern => 1066 | { 1067 | pattern._grammar = grammarDeclare; 1068 | let pt = compilePattern(pattern); 1069 | grammar.addSubPattern(pt) 1070 | }); 1071 | return grammar; 1072 | } 1073 | 1074 | function matchGrammar(grammar: Grammar, doc: TextDocument): GrammarMatchResult 1075 | { 1076 | return grammar.match(doc, 0); 1077 | } 1078 | function includePattern(patternName: string): GrammarPattern 1079 | { 1080 | return { patterns: [`<${patternName}>`] }; 1081 | } 1082 | function namedPattern(patternName: string): GrammarPattern 1083 | { 1084 | return { patterns: [`<${patternName}>`] }; 1085 | } 1086 | export 1087 | { 1088 | LanguageGrammar, 1089 | GrammarPattern, 1090 | compileGrammar, 1091 | matchGrammar, 1092 | GrammarScope, 1093 | includePattern, 1094 | namedPattern, 1095 | MatchResult, 1096 | PatternMatchResult, 1097 | ScopeMatchResult, 1098 | UnMatchedPattern 1099 | }; -------------------------------------------------------------------------------- /server/src/server.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { createConnection, ProposedFeatures, TextDocuments, InitializeParams, DidChangeConfigurationNotification, TextDocument, TextDocumentPositionParams, CompletionItem, CompletionParams, CompletionItemKind } from "vscode-languageserver"; 4 | import { compileGrammar, matchGrammar } from "./grammar"; 5 | import grammarShaderLab from "./shaderlab.grammar"; 6 | 7 | 8 | const connection = createConnection(ProposedFeatures.all); 9 | 10 | const documents: TextDocuments = new TextDocuments(); 11 | 12 | const compiledGrammarShaderLab = compileGrammar(grammarShaderLab); 13 | 14 | let documentList = new Map(); 15 | 16 | connection.onInitialize((params: InitializeParams) => 17 | { 18 | connection.console.log("Init Server"); 19 | return { 20 | capabilities: { 21 | textDocumentSync: documents.syncKind, 22 | completionProvider: { 23 | resolveProvider: true, 24 | triggerCharacters: ["."," ","="] 25 | }, 26 | } 27 | }; 28 | }); 29 | 30 | documents.onDidOpen(e => 31 | { 32 | documentList.set(e.document.uri, e.document); 33 | }); 34 | 35 | documents.onDidClose(e => 36 | { 37 | documentList.delete(e.document.uri); 38 | }); 39 | 40 | function getDocument(uri: string) 41 | { 42 | return documentList.get(uri); 43 | } 44 | 45 | 46 | connection.onCompletion((docPos: CompletionParams): CompletionItem[] => 47 | { 48 | try 49 | { 50 | let startTime = new Date().getTime(); 51 | let match = matchGrammar(compiledGrammarShaderLab, getDocument(docPos.textDocument.uri)); 52 | let completions = match.requestCompletion(docPos.position); 53 | let endTime = new Date().getTime(); 54 | console.log(`Complete in ${endTime - startTime}ms. `); 55 | return completions; 56 | } 57 | catch (ex) 58 | { 59 | console.error(ex); 60 | } 61 | }); 62 | 63 | documents.listen(connection); 64 | connection.listen(); -------------------------------------------------------------------------------- /server/src/shaderlab.grammar.ts: -------------------------------------------------------------------------------- 1 | import { LanguageGrammar, GrammarPattern, includePattern, namedPattern } from "./grammar"; 2 | import { CompletionItemKind } from "vscode-languageserver"; 3 | import { cgBuildInTypesCompletion, cgBuildInKeywordsCompletion, CgContext, CgGlobalContext, CgVariable, toCgVariableCompletions, toCgFunctionCompletions, toCompletions } from "./grammar-cg"; 4 | import { onFunctionMatch, onParamsDeclare, onBlockMatch, onStructDeclare, onStructMemberDeclare, onExpressionComplete, onPropertiesCompletion, onPropertiesDeclare, onShaderDeclare, onSubShaderDeclare, onTagsMatch, onTagCompletion, onPassDeclare, onRenderSetupCompletion, cgGlobalCompletion, cgPreprocessorCompletion, onVariableDeclare } from "./completion-shaderlab"; 5 | import { getKeys, renderSetups, Pass, SubShader } from "./structure-shaderlb"; 6 | const grammarShaderLab: LanguageGrammar = { 7 | stringDelimiter: ["\""], 8 | pairMatch: [ 9 | ["/*", "*/"], 10 | ["\"", "\""], 11 | ["'", "'"], 12 | ["(", ")"], 13 | ["{", "}"], 14 | ["[", "]"], 15 | ], 16 | ignore: { 17 | patterns: [ 18 | "/\* * \*/", 19 | "//*" 20 | ] 21 | }, 22 | patterns: [ 23 | { 24 | name: "Shader", 25 | id:"shader-declare", 26 | patterns: [ 27 | "Shader {shaderScope}", 28 | ], 29 | caseInsensitive: true, 30 | crossLine: true, 31 | scopes: { 32 | "shaderScope": { 33 | name: "Shader Scope", 34 | begin: "{", 35 | end: "}", 36 | patterns: [ 37 | { patterns: [""] }, 38 | { patterns: [""] } 39 | ], 40 | onCompletion: (match) => 41 | { 42 | if (match.patternName != "propertiesPattern" && match.patternName != "subShaderPattern") 43 | { 44 | return toCompletions(["Properties", "SubShader"], CompletionItemKind.Keyword) 45 | .concat(toCompletions(["Fallback"], CompletionItemKind.Property)); 46 | } 47 | }, 48 | onMatched: onShaderDeclare 49 | } 50 | }, 51 | 52 | } 53 | ], 54 | onCompletion: () => toCompletions(["Shader"], CompletionItemKind.Keyword), 55 | patternRepository: { 56 | "propertiesPattern": 57 | { 58 | name: "Properties", 59 | patterns: ["Properties {propScope}"], 60 | caseInsensitive: true, 61 | crossLine: true, 62 | scopes: { 63 | "propScope": { 64 | begin: "{", 65 | end: "}", 66 | patterns: [ 67 | { 68 | name: "Property Declare", 69 | patterns: [" (, ) = "], 70 | dictionary: { 71 | "displayName": GrammarPattern.String 72 | }, 73 | onCompletion: onPropertiesCompletion, 74 | onMatched:onPropertiesDeclare 75 | } 76 | ] 77 | } 78 | } 79 | }, 80 | "propertyValue": { 81 | name: "Property Value", 82 | patterns: [ 83 | "", 84 | " \\{[]\\}", 85 | "(, , , )" 86 | ] 87 | }, 88 | "propType": { 89 | name: "Property Type", 90 | patterns: ["[([, ...])]"], 91 | dictionary: { 92 | "typeName": { 93 | patterns: [ 94 | "/[_a-zA-Z0-9]+/" 95 | ] 96 | } 97 | } 98 | }, 99 | "subShaderPattern": { 100 | name: "SubShader", 101 | patterns: ["SubShader {subShaderScope}"], 102 | caseInsensitive: true, 103 | crossLine: true, 104 | scopes: { 105 | "subShaderScope": { 106 | begin: "{", 107 | end: "}", 108 | patterns: [ 109 | includePattern("tagsPattern"), 110 | includePattern("renderSetupPattern"), 111 | includePattern("pass"), 112 | includePattern("cgProgram") 113 | ], 114 | onMatched:onSubShaderDeclare 115 | } 116 | } 117 | }, 118 | "pass": { 119 | name: "Pass", 120 | patterns: ["Pass {pass}"], 121 | caseInsensitive: true, 122 | crossLine: true, 123 | scopes: { 124 | "pass": { 125 | begin: "{", 126 | end: "}", 127 | patterns: [ 128 | includePattern("tagsPattern"), 129 | includePattern("renderSetupPattern"), 130 | includePattern("cgProgram") 131 | ], 132 | onMatched: onPassDeclare, 133 | } 134 | } 135 | }, 136 | "tagsPattern": { 137 | name: "Tags", 138 | patterns: ["Tags {tagScope}"], 139 | crossLine: true, 140 | caseInsensitive: true, 141 | scopes: { 142 | "tagScope": { 143 | begin: "{", 144 | end: "}", 145 | skipMode: "space", 146 | patterns: [ 147 | { 148 | id:"tag", 149 | patterns: [" = "], 150 | dictionary: { 151 | "tag": {patterns:["",""]}, 152 | "value": {patterns:["",""]} 153 | }, 154 | onCompletion: onTagCompletion 155 | } 156 | ], 157 | onCompletion: onTagCompletion 158 | } 159 | }, 160 | }, 161 | "renderSetupPattern": { 162 | name: "Render Setup", 163 | patterns: [" < > [[,] < > ...]"], 164 | dictionary: { 165 | "stateName": GrammarPattern.Identifier, 166 | "value": { 167 | name: "Render Setup Value", 168 | patterns: ["", "", "", "{value-scope}"], 169 | scopes: { 170 | "value-scope": { 171 | begin: "{", 172 | end: "}", 173 | patterns:[] 174 | } 175 | } 176 | } 177 | }, 178 | onCompletion:onRenderSetupCompletion 179 | }, 180 | "cgProgram": { 181 | name: "Cg Program", 182 | patterns: ["{cgProgram}"], 183 | scopes: { 184 | "cgProgram": { 185 | begin: "CGPROGRAM", 186 | end: "ENDCG", 187 | patterns: [ 188 | { 189 | id:"preprocessor", 190 | name: "Preprocessor", 191 | patterns: [ 192 | "#pragma [ ...]", 193 | "#pragma [ ...]", 194 | "#pragma " 195 | ], 196 | keepSpace: true, 197 | dictionary: { 198 | "name": { 199 | patterns: ["", ""] 200 | } 201 | }, 202 | onCompletion:cgPreprocessorCompletion 203 | }, 204 | includePattern("variableDeclare"), 205 | includePattern("functionDefinition"), 206 | includePattern("structDeclare") 207 | ], 208 | onMatched: (match) => 209 | { 210 | match.state = new CgGlobalContext(); 211 | if (match.matchedScope.state instanceof Pass) 212 | { 213 | match.matchedScope.state.setCgCode(match.state); 214 | } 215 | else if (match.matchedScope.state instanceof SubShader) 216 | { 217 | match.matchedScope.state.setCgCode(match.state); 218 | } 219 | }, 220 | onCompletion:cgGlobalCompletion 221 | } 222 | } 223 | }, 224 | "functionDefinition": { 225 | name: "Function Definition", 226 | patterns: [" ([][,...])[:]{body-block}"], 227 | dictionary: { 228 | "type": GrammarPattern.Identifier, 229 | "name": GrammarPattern.Identifier, 230 | "semantics": GrammarPattern.Identifier 231 | }, 232 | onMatched: onFunctionMatch, 233 | onCompletion: (match) => 234 | { 235 | if (match.patternName === "type") 236 | { 237 | return cgBuildInTypesCompletion; 238 | } 239 | return []; 240 | } 241 | }, 242 | "paramsDeclare": { 243 | name: "Params Declare", 244 | patterns: ["[] < > [:]"], 245 | dictionary: { 246 | "type": GrammarPattern.Identifier, 247 | "name": GrammarPattern.Identifier, 248 | "semantics": GrammarPattern.Identifier, 249 | "decorator": { 250 | patterns: ["in ", "out ", "inout "], 251 | keepSpace: true 252 | } 253 | }, 254 | onMatched: onParamsDeclare 255 | }, 256 | "structDeclare": { 257 | name: "Struct", 258 | patterns: ["struct {struct-body};"], 259 | scopes: { 260 | "struct-body": { 261 | begin: "{", 262 | end: "}", 263 | patterns: [{ 264 | name: "Member Declare", 265 | id:"member-declare", 266 | patterns: [" < > [:];"], 267 | onMatched: onStructMemberDeclare 268 | }] 269 | } 270 | }, 271 | onMatched:onStructDeclare 272 | }, 273 | "variableDeclare": { 274 | name: "Variable Declare", 275 | patterns: [" < > [:] [= ] [, [:] [= ] ...];"], 276 | dictionary: { 277 | "type": GrammarPattern.Identifier, 278 | "name": GrammarPattern.Identifier, 279 | "semantics": GrammarPattern.Identifier 280 | }, 281 | onMatched: (match) => 282 | { 283 | let type = match.getMatch("type")[0].text; 284 | let name = match.getMatch("name")[0].text; 285 | let semantics = match.getMatch("semantics")[0] ? match.getMatch("semantics")[0].text : ""; 286 | let context = match.matchedScope.state as CgContext; 287 | context.addVariable(new CgVariable(context.getType(type), name, semantics)); 288 | }, 289 | onCompletion: onVariableDeclare 290 | }, 291 | "expression": { 292 | name: "Expression", 293 | patterns: [ 294 | " [ ...]" 295 | ], 296 | strict: true, 297 | dictionary: { 298 | "expr-unit": { 299 | name: "Expression Unit with Operator", 300 | patterns: ["[ ...][]"], 301 | dictionary: { 302 | "unit": { 303 | name: "Expression Unit", 304 | patterns: [ 305 | "", 306 | "", 307 | "", 308 | "", 309 | ] 310 | }, 311 | "unary-operator": { 312 | name: "Unary Operator", 313 | patterns: ["!", "+", "-", "~","++","--","*","&","()","sizeof< >"] 314 | }, 315 | "postfix": { 316 | name: "Postfix Operator", 317 | patterns: ["++", "--","\\[\\]"] 318 | } 319 | } 320 | }, 321 | "operator": { 322 | name: "Operator", 323 | patterns: ["/(((\\+|-|\\*|\\/|%|=|&|\\||\\^|<<|>>)=?)|(<|>|<=|>=|==|\\!=|\\|\\||&&)|(\\.|\\?|\\:|~|,))/"] 324 | } 325 | }, 326 | onCompletion: onExpressionComplete 327 | }, 328 | "bracket": { 329 | name: "Bracket", 330 | patterns: ["()"] 331 | }, 332 | "func-call": { 333 | name: "Function Call", 334 | patterns: [" ( [, ...])"] 335 | } 336 | }, 337 | scopeRepository: { 338 | "body-block": { 339 | name: "Block", 340 | begin: "{", 341 | end: "}", 342 | patterns: [ 343 | includePattern("variableDeclare"), 344 | { 345 | name: "Statement", 346 | id:"statement", 347 | patterns: [";"] 348 | }, 349 | { 350 | name: "If", 351 | id:"if-structure", 352 | patterns: ["if () {body-block} "] 353 | }, 354 | { 355 | name: "For Loop", 356 | id:"for-structure", 357 | patterns: ["for (;;) {body-block}"] 358 | }, 359 | { 360 | name: "While Loop", 361 | id:"while-structure", 362 | patterns: ["while () {body-block}"] 363 | }, 364 | { 365 | name: "Do-While Loop", 366 | id:"do-while-structure", 367 | patterns: ["do {body-block} while ();"], 368 | crossLine: true 369 | }, 370 | { 371 | name: "No sense code", 372 | patterns: [""], 373 | dictionary: { 374 | "no-sense": { 375 | patterns:["/[_a-zA-Z0-9]+\\r\\n/"] 376 | } 377 | } 378 | } 379 | ], 380 | onMatched: onBlockMatch, 381 | onCompletion: (match) => 382 | { 383 | let context = match.matchedScope.state as CgContext; 384 | if (match.patternName === "no-sense") 385 | return cgBuildInKeywordsCompletion 386 | .concat(cgBuildInTypesCompletion) 387 | .concat(toCgVariableCompletions(context.getAllVariables())) 388 | .concat(toCgFunctionCompletions(context.global.functions)); 389 | return []; 390 | } 391 | } 392 | } 393 | }; 394 | 395 | export default grammarShaderLab; -------------------------------------------------------------------------------- /server/src/structure-shaderlb.ts: -------------------------------------------------------------------------------- 1 | import { CgGlobalContext } from "./grammar-cg"; 2 | 3 | const booleanValue: string[] = ["True", "False"]; 4 | const subShaderTags: Dictionary = { 5 | "Queue": ["Background", "Geometry", "AlphaTest", "Transparent", "Overlay"], 6 | "RenderType": ["Opaque", "Transparent", "TransparentCutout", "Background", "Overlay", "TreeOpaque", "TreeTransparentCutout", "TreeBillboard", "Grass", "GrassBillboard"], 7 | "RequireOptions": ["SoftVegetation"], 8 | "DisableBatching": ["True", "False", "LODFading"], 9 | "ForceNoShadowCasting": booleanValue, 10 | "IgnoreProjector": booleanValue, 11 | "CanUseSpriteAtlas": booleanValue, 12 | "PreviewType": ["Sphere", "Plane", "Skybod", "Cube"] 13 | }; 14 | const passTags: Dictionary = { 15 | "LightMode": ["Always", "ForwardBase", "ForwardAdd", "Deferred", "ShadowCaster", "PrepassBase", "PrepassFinal", "Vertex", "VertexLMRGBM", "VertexLM"], 16 | "PassFlags": ["OnlyDirectional"], 17 | "RequireOptions": ["SoftVegetation"] 18 | }; 19 | const renderSetups: Dictionary = { 20 | "Cull": ["Back", "Front", "Off"], 21 | "ZTest": ["Less ", "Greater", "LEqual", "GEqual", "Equal", "NotEqual ", "Always"], 22 | "ZWrite": ["On", "Off"], 23 | "Blend": ["SourceBlendMode DestBlendMode", "AlphaSourceBlendMode AlphaDestBlendMode"], 24 | "ColorMask": ["R", "G", "B", "A", "RGBA", "0"], 25 | "Offset": ["OffsetFactor", "OffsetUnits"], 26 | "Lighting": ["On", "Off"], 27 | "Material": [], 28 | "SeparateSpecular": ["On", "Off"], 29 | "Color": [], 30 | "ColorMaterial": ["AmbientAndDiffuse", "Emission"], 31 | "AlphaTest": ["Less", "Greater", "LEqual", "GEqual", "Equal", "NotEqual", "Always"], 32 | "SetTexture": [], 33 | "Fog": [], 34 | "LOD":[] 35 | }; 36 | 37 | class Dictionary 38 | { 39 | [key: string]: TValue; 40 | } 41 | function getKeys(dict: Dictionary):string[] 42 | { 43 | let keys: string[] = []; 44 | for (const key in dict) { 45 | if (dict.hasOwnProperty(key)) { 46 | keys.push(key); 47 | } 48 | } 49 | return keys; 50 | } 51 | 52 | class Shader 53 | { 54 | name: string; 55 | properties: ShaderProperty[] = []; 56 | subShaders: SubShader[] = []; 57 | fallback: string; 58 | constructor(name: string) 59 | { 60 | this.name = name; 61 | } 62 | addProperty(prop: ShaderProperty) 63 | { 64 | this.properties.push(prop); 65 | prop.shader = this; 66 | } 67 | addSubShader(subShader: SubShader) 68 | { 69 | this.subShaders.push(subShader); 70 | subShader.shader = this; 71 | } 72 | } 73 | class ShaderProperty 74 | { 75 | shader: Shader; 76 | identifier: string; 77 | displayName: string; 78 | type: string; 79 | defaultValue: string; 80 | constructor(id: string, displayName: string, type: string, defaultValue: string) 81 | { 82 | this.identifier = id; 83 | this.displayName = displayName; 84 | this.type = type; 85 | this.defaultValue = defaultValue; 86 | } 87 | toString() 88 | { 89 | return `${this.identifier} ("${this.displayName}", ${this.type}) = ${this.defaultValue}`; 90 | } 91 | } 92 | class SubShader 93 | { 94 | shader: Shader; 95 | tags: Tag[] = []; 96 | renderSetups: RenderSetup[] = []; 97 | passes: Pass[] = []; 98 | cgCode: CgGlobalContext; 99 | addTag(tag: Tag) 100 | { 101 | this.tags.push(tag); 102 | } 103 | addPass(pass: Pass) 104 | { 105 | this.passes.push(pass); 106 | pass.subShader = this; 107 | } 108 | addRenderSetup(renderSetup: RenderSetup) 109 | { 110 | this.renderSetups.push(renderSetup); 111 | } 112 | setCgCode(cg: CgGlobalContext) 113 | { 114 | this.cgCode = cg; 115 | cg.shader = this.shader; 116 | } 117 | } 118 | class Pass 119 | { 120 | subShader: SubShader; 121 | tags: Tag[] = []; 122 | renderSetups: RenderSetup[] = []; 123 | cgCode: CgGlobalContext; 124 | addTag(tag: Tag) 125 | { 126 | this.tags.push(tag); 127 | } 128 | addRenderSetup(renderSetup: RenderSetup) 129 | { 130 | this.renderSetups.push(renderSetup); 131 | } 132 | setCgCode(cg: CgGlobalContext) 133 | { 134 | this.cgCode = cg; 135 | cg.shader = this.subShader.shader; 136 | } 137 | } 138 | class KeyValuePare 139 | { 140 | key: TKey; 141 | value: TValue; 142 | constructor(key: TKey, value: TValue) 143 | { 144 | this.key = key; 145 | this.value = value; 146 | } 147 | } 148 | class Tag extends KeyValuePare 149 | { 150 | get tagName() { return this.key; } 151 | set tagName(value) { this.key = value;} 152 | } 153 | class RenderSetup extends KeyValuePare 154 | { 155 | get stateName() { return this.key; } 156 | set stateName(value) { this.key = value; } 157 | } 158 | export 159 | { 160 | Shader, 161 | SubShader, 162 | ShaderProperty, 163 | Pass, 164 | Tag, 165 | RenderSetup, 166 | subShaderTags, 167 | passTags, 168 | renderSetups, 169 | getKeys 170 | } -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", 5 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 6 | "module": "commonjs", 7 | /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 8 | "lib": ["es6"], 9 | /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | "sourceMap": true, 15 | /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "out", 18 | /* Redirect output structure to the directory. */ 19 | "rootDir": "src", 20 | /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, 29 | /* Enable all strict type-checking options. */ 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | "strictNullChecks": false, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, 52 | /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": false, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | }, 65 | "include": [ 66 | "src" 67 | ], 68 | "exclude": [ 69 | "node_modules" 70 | ] 71 | } -------------------------------------------------------------------------------- /snippets/shaderlab.json: -------------------------------------------------------------------------------- 1 | { 2 | "Shader":{ 3 | "prefix": "Shader", 4 | "body": [ 5 | "Shader \"${1:Custom/NewShader}\" {", 6 | "\tProperties {", 7 | "\t\t", 8 | "\t}", 9 | "\tSubShader {", 10 | "\t\t$0", 11 | "\t}", 12 | "\tFallBack \"Diffuse\"", 13 | "\t", 14 | "}" 15 | ], 16 | "description": "Shader Structure" 17 | }, 18 | "SubShader":{ 19 | "prefix":"SubShader", 20 | "body":[ 21 | "SubShader {", 22 | "\t$0", 23 | "}" 24 | ] 25 | } 26 | } -------------------------------------------------------------------------------- /syntaxes/shaderlab.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "ShaderLab", 4 | "scopeName": "source.shaderlab", 5 | "patterns": [{ 6 | "include": "#shader-def" 7 | }, { 8 | "include": "#comment" 9 | }, 10 | { 11 | "name": "keyword.other", 12 | "match": "Shader" 13 | } 14 | ], 15 | "repository": { 16 | "keywords": { 17 | "patterns": [{ 18 | "name": "keyword.other.shaderlab", 19 | "match": "\\b(CGPROGRAM|ENDCG|in|out|inout)\\b" 20 | }] 21 | }, 22 | "buildin-props": { 23 | "patterns": [{ 24 | "name": "support.type.property-name.shaderlab", 25 | "match": "\\b((?i)Name|Tags|Fallback|CustomEditor|Cull|ZWrite|ZTest|Offset|Blend|BlendOp|ColorMask|AlphaToMask|LOD|Lighting|Stencil|Ref|ReadMask|WriteMask|Comp|CompBack|CompFront|Fail|ZFail|UsePass|GrabPass|Dependency|Material|Diffuse|Ambient|Shininess|Specular|Emission|Fog|Mode|Density|SeparateSpecular|SetTexture|Combine|ConstantColor|Matrix|AlphaTest|ColorMaterial|BindChannels|Bind)\\b" 26 | }] 27 | }, 28 | "buildin-tags": { 29 | "patterns": [{ 30 | "name": "support.variable.shaderlab", 31 | "match": "\\b((?i)Queue|RenderType|DisableBatching|ForceNoShadowCasting|IgnoreProjector|CanUseSpriteAtlas|PreviewType|LightMode|PassFlags|RquireOptions)\\b" 32 | }] 33 | }, 34 | "buildin-tag-values": { 35 | "patterns": [{ 36 | "name": "keyword.other.unit.shaderlab", 37 | "match": "\\b((?i)Background|Geometry|AlphaTest|Transparent|Overlay|Opaque|Transparent|TransparentCutout|Background|Overlay|TreeOpaque|TreeTransparentCutout|TreeBillboard|Grass|GrassBillboard|True|False|LODFading|Plane|SkyBox|Sphere|Always|ForwardBase|FowardAdd|Deferred|ShadowCaster|PrepassBase|PrepassFinal|Vertex|VertexLMRGBM|VertexLM|OnlyDirectional|SoftVegetation)\\b" 38 | }] 39 | }, 40 | "render-setup-values": { 41 | "patterns": [{ 42 | "include": "#numeric" 43 | }, 44 | { 45 | "name": "keyword.other.unit.shaderlab", 46 | "match": "\\b((?i)Back|Front|Off|Less|Greater|LEqual|GEqual|Equal|NotEqual|Always|On|Off|RGBA|RGB|OffsetFactor|OffsetUnits)\\b" 47 | }, 48 | { 49 | "include": "#strings" 50 | } 51 | ] 52 | }, 53 | "buildin-types": { 54 | "patterns": [{ 55 | "name": "storage.type.shaderlab", 56 | "match": "\\b(void|float[1-4]?|half[1-4]?|fixed[1-4]?|int[1-4]?|sampler(2D|3D)?)\\b" 57 | }] 58 | }, 59 | "struct-keyword": { 60 | "patterns": [{ 61 | "name": "keyword.other.shaderlab", 62 | "match": "\\b((?i)Shader|Properties|SubShader|Pass|Category)\\b" 63 | }] 64 | }, 65 | "comment": { 66 | "patterns": [{ 67 | "name": "comment.block.documentation.shaderlab", 68 | "begin": "/\\*(?!/)", 69 | "beginCaptures": { 70 | "0": { 71 | "name": "punctuation.definition.comment.shaderlab" 72 | } 73 | }, 74 | "end": "\\*/", 75 | "endCaptures": { 76 | "0": { 77 | "name": "punctuation.definition.comment.shaderlab" 78 | } 79 | } 80 | }, 81 | { 82 | "name": "comment.line.double-slash", 83 | "match": "//.*$" 84 | } 85 | ] 86 | }, 87 | "strings": { 88 | "name": "string.quoted.double.shaderlab", 89 | "begin": "\"", 90 | "end": "\"", 91 | "patterns": [{ 92 | "name": "constant.character.escape.shaderlab", 93 | "match": "\\\\." 94 | }] 95 | }, 96 | "shader-def": { 97 | "patterns": [{ 98 | "name": "meta.namespace.shaderlab", 99 | "begin": "(\\b(?i)Shader\\b)((?:\\s|/\\*(?!/).*\\*/)*)(\".*\")?((?:\\s|/\\*(?!/).*\\*/)*)", 100 | "end": "}", 101 | "beginCaptures": { 102 | "1": { 103 | "patterns": [{ 104 | "include": "#struct-keyword" 105 | }] 106 | }, 107 | "2": { 108 | "patterns": [{ 109 | "include": "#comment" 110 | }] 111 | }, 112 | "3": { 113 | "patterns": [{ 114 | "include": "#strings" 115 | }] 116 | }, 117 | "4": { 118 | "patterns": [{ 119 | "include": "#comment" 120 | }] 121 | } 122 | }, 123 | "patterns": [{ 124 | "include": "#shader-props" 125 | }, 126 | { 127 | "include": "#sub-shader" 128 | }, 129 | { 130 | "include": "#fallback" 131 | }, 132 | { 133 | "include": "#comment" 134 | } 135 | ] 136 | }] 137 | }, 138 | "shader-props": { 139 | "patterns": [{ 140 | "name": "meta.struct.shaderlab", 141 | "begin": "((?i)Properties)((?:\\s|/\\*(?!/).*\\*/)*)", 142 | "end": "}", 143 | "beginCaptures": { 144 | "1": { 145 | "patterns": [{ 146 | "include": "#struct-keyword" 147 | }] 148 | }, 149 | "2": { 150 | "patterns": [{ 151 | "include": "#comment" 152 | }] 153 | } 154 | }, 155 | "patterns": [{ 156 | "include": "#prop-def" 157 | }] 158 | }] 159 | }, 160 | "sub-shader": { 161 | "patterns": [{ 162 | "begin": "((?i)SubShader)((?:\\s|/\\*(?!/).*\\*/)*)", 163 | "end": "}", 164 | "beginCaptures": { 165 | "1": { 166 | "patterns": [{ 167 | "include": "#struct-keyword" 168 | }] 169 | }, 170 | "2": { 171 | "patterns": [{ 172 | "include": "#comment" 173 | }] 174 | } 175 | }, 176 | "patterns": [{ 177 | "include": "#comment" 178 | }, 179 | { 180 | "include": "#tags" 181 | }, 182 | { 183 | "include": "#cg-block" 184 | }, 185 | { 186 | "include": "#pass" 187 | }, 188 | { 189 | "include": "#render-setup" 190 | } 191 | ] 192 | }] 193 | }, 194 | "tags": { 195 | "patterns": [{ 196 | "name": "meta.struct.tags.shaderlab", 197 | "begin": "((?i)Tags)", 198 | "end": "$", 199 | "beginCaptures": { 200 | "1": { 201 | "patterns": [{ 202 | "include": "#buildin-props" 203 | }] 204 | }, 205 | "2": { 206 | "patterns": [{ 207 | "include": "#comment" 208 | }] 209 | } 210 | }, 211 | "patterns": [{ 212 | "include": "#comment" 213 | }, 214 | { 215 | "name": "meta.block.shaderlab", 216 | "begin": "{", 217 | "end": "}", 218 | "patterns": [{ 219 | "include": "#comment" 220 | }, 221 | { 222 | "name": "support.property-value", 223 | "match": "(\"(?:[^/]|/(?![*/]))*?\")((?:\\s|/\\*(?!/).*\\*/)*)=((?:\\s|/\\*(?!/).*\\*/)*)(\"(?:[^/]|/(?![*/]))*?\")", 224 | "captures": { 225 | "1": { 226 | "patterns": [{ 227 | "name": "string.quoted.double", 228 | "begin": "\"", 229 | "end": "\"", 230 | "patterns": [{ 231 | "include": "#buildin-tags" 232 | }] 233 | }] 234 | }, 235 | "2": { 236 | "patterns": [{ 237 | "include": "#comment" 238 | }] 239 | }, 240 | "3": { 241 | "patterns": [{ 242 | "include": "#comment" 243 | }] 244 | }, 245 | "4": { 246 | "patterns": [{ 247 | "name": "string.quoted.double", 248 | "begin": "\"", 249 | "end": "\"", 250 | "patterns": [{ 251 | "include": "#buildin-tag-values" 252 | }] 253 | }] 254 | } 255 | } 256 | } 257 | ] 258 | } 259 | ] 260 | }] 261 | }, 262 | "render-setup": { 263 | "patterns": [{ 264 | "name": "support.property-value.render-setup.shaderlab", 265 | "begin": "([_a-zA-Z][_a-zA-Z0-9]*)", 266 | "end": "$", 267 | "beginCaptures": { 268 | "1": { 269 | "patterns": [{ 270 | "include": "#buildin-props" 271 | }] 272 | } 273 | }, 274 | "patterns": [{ 275 | "include": "#comment" 276 | }, 277 | { 278 | "include": "#render-setup-values" 279 | } 280 | ] 281 | }] 282 | }, 283 | "pass": { 284 | "patterns": [{ 285 | "name": "meta.struct.pass.shaderlab", 286 | "begin": "((?i)Pass)((?:\\s|/\\*(?!/).*\\*/)*)", 287 | "end": "}", 288 | "beginCaptures": { 289 | "1": { 290 | "patterns": [{ 291 | "include": "#struct-keyword" 292 | }] 293 | }, 294 | "2": { 295 | "patterns": [{ 296 | "include": "#comment" 297 | }] 298 | } 299 | }, 300 | "patterns": [{ 301 | "include": "#comment" 302 | }, 303 | { 304 | "include": "#tags" 305 | }, 306 | { 307 | "include": "#cg-block" 308 | }, 309 | { 310 | "include": "#render-setup" 311 | } 312 | ] 313 | }] 314 | }, 315 | "cg-block": { 316 | "patterns": [{ 317 | "name": "meta.block.cg.shaderlab", 318 | "begin": "CGPROGRAM", 319 | "end": "ENDCG", 320 | "beginCaptures": { 321 | "0": { 322 | "patterns": [{ 323 | "include": "#keywords" 324 | }] 325 | } 326 | }, 327 | "endCaptures": { 328 | "0": { 329 | "patterns": [{ 330 | "include": "#keywords" 331 | }] 332 | } 333 | }, 334 | "patterns": [{ 335 | "include": "#code-global" 336 | }] 337 | }] 338 | }, 339 | "code-global": { 340 | "patterns": [{ 341 | "include": "#comment" 342 | }, 343 | { 344 | "include": "#func-call" 345 | }, 346 | { 347 | "include": "#declare" 348 | } 349 | ] 350 | }, 351 | "fallback": { 352 | "patterns": [{ 353 | "name": "support.property-value", 354 | "begin": "((?i)FallBack)", 355 | "end": "$", 356 | "beginCaptures": { 357 | "0": { 358 | "patterns": [{ 359 | "include": "#buildin-props" 360 | }] 361 | } 362 | }, 363 | "patterns": [{ 364 | "include": "#strings" 365 | }] 366 | }] 367 | }, 368 | "prop-def": { 369 | "name": "meta.definition.property.shaderlab", 370 | "patterns": [{ 371 | "include": "#comment" 372 | }, 373 | { 374 | "include": "#prop" 375 | }, 376 | { 377 | "include": "#prop-type-dec" 378 | }, 379 | { 380 | "include": "#prop-assignment" 381 | } 382 | ] 383 | }, 384 | "prop": { 385 | "patterns": [{ 386 | "name": "variable.other.property.shaderlab", 387 | "match": "\\b[_a-zA-Z][_a-zA-Z0-9]*\\b" 388 | }] 389 | }, 390 | "identifier": { 391 | "patterns": [{ 392 | "name": "entity.name.shaderlab", 393 | "match": "\\b[_a-zA-Z][_a-zA-Z0-9]*\\b" 394 | }] 395 | }, 396 | "prop-type-dec": { 397 | "patterns": [{ 398 | "name": "meta.declare.shaderlab", 399 | "begin": "\\(", 400 | "end": "\\)", 401 | "patterns": [{ 402 | "include": "#strings" 403 | }, 404 | { 405 | "include": "#prop-type" 406 | }, 407 | { 408 | "include": "#comment" 409 | } 410 | ] 411 | }] 412 | }, 413 | "prop-type": { 414 | "patterns": [{ 415 | "name": "entity.name.type.shaderlab", 416 | "match": "\\b(Int|Float|Color|Vector|2D|Cube|3D|(Range\\(.+\\)))\\b" 417 | }, 418 | { 419 | "begin": "(Range)(\\()", 420 | "end": "\\)", 421 | "beginCaptures": { 422 | "1": { 423 | "name": "entity.name.type.shaderlab" 424 | } 425 | }, 426 | "patterns": [{ 427 | "include": "#numeric" 428 | }, 429 | { 430 | "include": "#comment" 431 | } 432 | ] 433 | } 434 | ] 435 | }, 436 | "numeric": { 437 | "patterns": [{ 438 | "name": "constant.numeric.shaderlab", 439 | "match": "\\b([0-9]+\\.?[0-9]*)\\b" 440 | }] 441 | }, 442 | "prop-assignment": { 443 | "patterns": [{ 444 | "name": "keyword.operator.prop-assignment.shaderlab", 445 | "match": "=(.+)", 446 | "captures": { 447 | "1": { 448 | "patterns": [{ 449 | "include": "#prop-expr" 450 | }, 451 | { 452 | "include": "#comment" 453 | } 454 | ] 455 | } 456 | } 457 | }, 458 | { 459 | "include": "#comment" 460 | } 461 | ] 462 | }, 463 | "prop-expr": { 464 | "patterns": [{ 465 | "include": "#vector" 466 | }, 467 | { 468 | "include": "#numeric" 469 | }, 470 | { 471 | "include": "#strings" 472 | }, 473 | { 474 | "name": "meta.block.shaderlab", 475 | "begin": "{", 476 | "end": "}", 477 | "patterns": [{ 478 | "include": "#prop-expr" 479 | }] 480 | }, 481 | { 482 | "include": "#comment" 483 | } 484 | ] 485 | }, 486 | "vector": { 487 | "patterns": [{ 488 | "begin": "\\(", 489 | "end": "\\)", 490 | "patterns": [{ 491 | "include": "#numeric" 492 | }, 493 | { 494 | "include": "#comment" 495 | } 496 | ] 497 | }] 498 | }, 499 | "declare": { 500 | "patterns": [{ 501 | "include": "#compile-statement" 502 | }, 503 | { 504 | "include": "#struct-declare" 505 | }, 506 | { 507 | "include": "#func-declare" 508 | }, 509 | { 510 | "include": "#var-declare" 511 | } 512 | ] 513 | }, 514 | "compile-statement": { 515 | "patterns": [{ 516 | "name": "meta.preprocessor.shaderlab", 517 | "begin": "#pragma", 518 | "end": "$", 519 | "beginCaptures": { 520 | "0": { 521 | "name": "keyword.control.pragma.shaderlab" 522 | } 523 | }, 524 | "patterns": [{ 525 | "include": "#comment" 526 | }, 527 | { 528 | "begin": "(surface|target|vertex|fragment)", 529 | "end": "$", 530 | "beginCaptures": { 531 | "0": { 532 | "name": "keyword.other.unit.shaderlab" 533 | } 534 | }, 535 | "patterns": [{ 536 | "include": "#comment" 537 | }, 538 | { 539 | "begin": "\\b[_a-zA-Z][_a-zA-Z0-9]*\\b", 540 | "end": "$", 541 | "beginCaptures": { 542 | "0": { 543 | "name": "entity.name.function" 544 | } 545 | }, 546 | "patterns": [{ 547 | "include": "#comment" 548 | }, 549 | { 550 | "match": "\\b[_a-zA-Z][_a-zA-Z0-9]*\\b" 551 | } 552 | ] 553 | }, 554 | { 555 | "include": "#numeric" 556 | } 557 | ] 558 | } 559 | ] 560 | }] 561 | }, 562 | "struct-declare": { 563 | "patterns": [{ 564 | "name": "meta.class-struct-block.shaderlab", 565 | "begin": "\\bstruct\\b", 566 | "end": "$", 567 | "beginCaptures": { 568 | "0": { 569 | "name": "storage.type.shaderlab" 570 | } 571 | }, 572 | "patterns": [{ 573 | "include": "#comment" 574 | }, 575 | { 576 | "include": "#declare-block" 577 | } 578 | ] 579 | }] 580 | }, 581 | "var-declare": { 582 | "patterns": [{ 583 | "name": "meta.var.declare", 584 | "match": "(\\b[_a-zA-Z][_a-zA-Z0-9]*\\b)((?:\\s|/\\*(?!/).*\\*/)+)(\\b[_a-zA-Z][_a-zA-Z0-9]*\\b)((?:\\s|/\\*(?!/).*\\*/)*)(\\:(?:[_a-zA-Z][_a-zA-Z0-9]*))?", 585 | "captures": { 586 | "1": { 587 | "patterns": [{ 588 | "include": "#type" 589 | }] 590 | }, 591 | "2": { 592 | "patterns": [{ 593 | "include": "#comment" 594 | }] 595 | }, 596 | "3": { 597 | "patterns": [{ 598 | "include": "#identifier" 599 | }] 600 | }, 601 | "4": { 602 | "patterns": [{ 603 | "include": "#comment" 604 | }] 605 | }, 606 | "5": { 607 | "patterns": [{ 608 | "include": "#semantics" 609 | }] 610 | } 611 | } 612 | }] 613 | }, 614 | "macro-func-call": { 615 | "patterns": [{ 616 | "name": "meta.function-call.shaderlab", 617 | "begin": "\\b([_a-zA-Z][_a-zA-Z0-9]*)\\b(?:\\s*)\\(", 618 | "end": "\\)", 619 | "beginCaptures": { 620 | "1": { 621 | "name": "entity.name.function" 622 | } 623 | }, 624 | "patterns": [{ 625 | "include": "#ref" 626 | }] 627 | }] 628 | }, 629 | "ref": { 630 | "patterns": [{ 631 | "include": "#identifier" 632 | }] 633 | }, 634 | "type": { 635 | "patterns": [{ 636 | "include": "#buildin-types" 637 | }, { 638 | "name": "entity.name.type.shaderlab", 639 | "match": "\\b[_a-zA-Z][_a-zA-Z0-9]*\\b" 640 | }] 641 | }, 642 | "declare-block": { 643 | "patterns": [{ 644 | "begin": "{", 645 | "end": "}", 646 | "patterns": [{ 647 | "include": "#comment" 648 | }, 649 | { 650 | "include": "#var-declare" 651 | } 652 | ] 653 | }] 654 | }, 655 | "semantics": { 656 | "patterns": [{ 657 | "begin": "\\:", 658 | "end": "_a-zA-Z0-9", 659 | "patterns": [{ 660 | "include": "#comment" 661 | }, 662 | { 663 | "include": "#type" 664 | } 665 | ] 666 | }] 667 | }, 668 | "func-declare": { 669 | "patterns": [{ 670 | "name": "meta.function.shaderlab", 671 | "begin": "([_a-zA-Z][_a-zA-Z0-9]*)((?:\\s|/\\*(?!/).*\\*/)+)([_a-zA-Z][_a-zA-Z0-9]*)((?:\\s|/\\*(?!/).*\\*/)*)(\\((?:(?:[^/]|/(?![*/]))*)(?:(?:/\\*.*?\\*/)(?:(?:[^/]|/(?!\\*))*))*\\))((?:\\s|/\\*(?!/).*\\*/)*)(\\:(?:[_a-zA-Z][_a-zA-Z0-9]*))?", 672 | "end": "$", 673 | "captures": { 674 | "1": { 675 | "patterns": [{ 676 | "include": "#type" 677 | }] 678 | }, 679 | "2": { 680 | "patterns": [{ 681 | "include": "#comment" 682 | }] 683 | }, 684 | "3": { 685 | "name": "entity.name.function.shaderlab", 686 | "patterns": [{ 687 | "include": "#identifier" 688 | }] 689 | }, 690 | "4": { 691 | "patterns": [{ 692 | "include": "#comment" 693 | }] 694 | }, 695 | "5": { 696 | "patterns": [{ 697 | "include": "#params-declare" 698 | }] 699 | }, 700 | "6": { 701 | "patterns": [{ 702 | "include": "#comment" 703 | }] 704 | }, 705 | "7": { 706 | "patterns": [{ 707 | "include": "#semantics" 708 | }] 709 | } 710 | }, 711 | "patterns": [{ 712 | "include": "#blocks" 713 | }, 714 | { 715 | "include": "#comment" 716 | } 717 | ] 718 | }] 719 | }, 720 | "params-declare": { 721 | "patterns": [{ 722 | "name": "meta.function.parameters", 723 | "begin": "\\(", 724 | "end": "\\)", 725 | "patterns": [{ 726 | "include": "#keywords" 727 | }, 728 | { 729 | "include": "#var-declare" 730 | } 731 | ] 732 | }] 733 | }, 734 | "blocks": { 735 | "patterns": [{ 736 | "name": "meta.block.shaderlab", 737 | "begin": "{", 738 | "end": "}", 739 | "patterns": [{ 740 | "include": "#comment" 741 | }, 742 | { 743 | "include": "#var-declare" 744 | }, 745 | { 746 | "include": "#blocks" 747 | }, 748 | { 749 | "include": "#keywords-control" 750 | }, 751 | { 752 | "include": "#expr" 753 | } 754 | ] 755 | } 756 | 757 | ] 758 | }, 759 | "expr": { 760 | "patterns": [{ 761 | "include": "#comment" 762 | }, 763 | { 764 | "include": "#numeric" 765 | }, 766 | { 767 | "include": "#func-call" 768 | }, 769 | { 770 | "include": "#identifier" 771 | }, 772 | { 773 | "include": "#operators" 774 | } 775 | ] 776 | }, 777 | "keywords-control": { 778 | "patterns": [{ 779 | "name": "keyword.control.shaderlab", 780 | "match": "\\b(if|while|for|else|break|continue|do)\\b" 781 | }] 782 | }, 783 | "func-call": { 784 | "patterns": [{ 785 | "match": "([_a-zA-Z][_a-zA-Z0-9]*)((?:\\s|/\\*(?!/).*\\*/)*)(\\((([^/]|/(?!\\*))*)((/\\*.*?\\*/)(([^/]|/(?![*/]))*))*\\))", 786 | "captures": { 787 | "1": { 788 | "name": "entity.name.function.shaderlab", 789 | "patterns": [{ 790 | "include": "#buildin-types" 791 | }] 792 | }, 793 | "2": { 794 | "patterns": [{ 795 | "include": "#comment" 796 | }] 797 | }, 798 | "3": { 799 | "name": "meta.function.parameters.shaderlab", 800 | "patterns": [{ 801 | "include": "#params" 802 | }] 803 | } 804 | } 805 | }] 806 | }, 807 | "params": { 808 | "patterns": [{ 809 | "include": "#expr" 810 | }] 811 | }, 812 | "operators": { 813 | "patterns": [{ 814 | "name": "keyword.operator.shaderlab", 815 | "match": "[+\\-\\*/<>?!.]" 816 | }] 817 | } 818 | 819 | } 820 | } -------------------------------------------------------------------------------- /test/pattern-match.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SardineFish/UnityShaderSupport/48cf8a131180ddf80a800a8275765dd8eb30b7bc/test/pattern-match.test.ts -------------------------------------------------------------------------------- /test/test.shader: -------------------------------------------------------------------------------- 1 | Shader "Shader" { 2 | SubShader { 3 | Tags { "RenderType"="Opaque" "Queue"="Geometry"} 4 | ColorMask 0 5 | ZWrite Off 6 | 7 | Stencil 8 | { 9 | Ref 1 10 | Comp always 11 | Pass replace 12 | } 13 | Pass { 14 | cull Back 15 | 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /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 language support and define 6 | the location of the grammar file that has been copied into your extension. 7 | * `syntaxes/shaderlab.tmLanguage.json` - this is the Text mate grammar file that is used for tokenization. 8 | * `language-configuration.json` - this the language configuration, defining the tokens that are used for 9 | comments and brackets. 10 | 11 | ## Get up and running straight away 12 | * Make sure the language configuration settings in `language-configuration.json` are accurate. 13 | * Press `F5` to open a new window with your extension loaded. 14 | * Create a new file with a file name suffix matching your language. 15 | * Verify that syntax highlighting works and that the language configuration settings are working. 16 | 17 | ## Make changes 18 | * You can relaunch the extension from the debug toolbar after making changes to the files listed above. 19 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 20 | 21 | ## Add more language features 22 | * To add features such as intellisense, hovers and validators check out the VS Code extenders documentation at 23 | https://code.visualstudio.com/docs 24 | 25 | ## Install your extension 26 | * To start using your extension with Visual Studio Code copy it into the `/.vscode/extensions` folder and restart Code. 27 | * To share your extension with the world, read on https://code.visualstudio.com/docs about publishing an extension. 28 | --------------------------------------------------------------------------------