diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ac495c4..9c128b1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## [2.50.5](https://github.com/rokucommunity/vscode-brightscript-language/compare/v2.50.4...v2.50.5) - 2024-11-06 +### Changed + - Show multi-line lsp progress details ([#600](https://github.com/rokucommunity/vscode-brightscript-language/pull/600)) +### Fixed + - Fix bug with m.top colorization ([#601](https://github.com/rokucommunity/vscode-brightscript-language/pull/601)) + + + ## [2.50.4](https://github.com/rokucommunity/vscode-brightscript-language/compare/v2.50.3...v2.50.4) - 2024-10-18 ### Added - ability to set a device as the current active device via the Deive tree view. ([#597](https://github.com/rokucommunity/vscode-brightscript-language/pull/597)) diff --git a/package-lock.json b/package-lock.json index 925683fc..37cff692 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "brightscript", - "version": "2.50.4", + "version": "2.50.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "brightscript", - "version": "2.50.4", + "version": "2.50.5", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 22a12278..ad36dfc0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "brightscript", "displayName": "BrightScript Language", - "version": "2.50.4", + "version": "2.50.5", "publisher": "RokuCommunity", "description": "Language support for Roku's BrightScript language.", "author": { @@ -177,9 +177,15 @@ "onCommand:extension.brightscript.pressVolumeDown", "onCommand:extension.brightscript.pressVolumeMute", "onCommand:extension.brightscript.pressVolumeUp", + "onCommand:extension.brightscript.setVolume", "onCommand:extension.brightscript.pressPowerOff", "onCommand:extension.brightscript.pressChannelUp", "onCommand:extension.brightscript.pressChannelDown", + "onCommand:extension.brightscript.pressBlue", + "onCommand:extension.brightscript.pressGreen", + "onCommand:extension.brightscript.pressRed", + "onCommand:extension.brightscript.pressYellow", + "onCommand:extension.brightscript.pressExit", "onCommand:extension.brightscript.changeTvInput", "onCommand:extension.brightscript.sendRemoteText", "onCommand:brighterscript.showPreview", @@ -2857,6 +2863,12 @@ "title": "Press the VolumeUp button on the Roku remote", "category": "Brightscript" }, + { + "command": "extension.brightscript.setVolume", + "title": "Set the volume on a volume-enabled Roku device to the specified value (0-100)", + "shortTitle": "Set Volume target volume level", + "category": "Brightscript" + }, { "command": "extension.brightscript.pressPowerOff", "title": "Press the PowerOff button on the Roku remote", @@ -2877,6 +2889,31 @@ "title": "Press the ChannelDown button on the Roku remote", "category": "Brightscript" }, + { + "command": "extension.brightscript.pressBlue", + "title": "Press the Blue button on the Roku remote", + "category": "Brightscript" + }, + { + "command": "extension.brightscript.pressGreen", + "title": "Press the Green button on the Roku remote", + "category": "Brightscript" + }, + { + "command": "extension.brightscript.pressRed", + "title": "Press the Red button on the Roku remote", + "category": "Brightscript" + }, + { + "command": "extension.brightscript.pressYellow", + "title": "Press the Yellow button on the Roku remote", + "category": "Brightscript" + }, + { + "command": "extension.brightscript.pressExit", + "title": "Press the Exit button on the Roku remote", + "category": "Brightscript" + }, { "command": "extension.brightscript.changeTvInput", "title": "Provides a list of possible inputs to change to", diff --git a/src/BrightScriptCommands.ts b/src/BrightScriptCommands.ts index 28772369..da85f028 100644 --- a/src/BrightScriptCommands.ts +++ b/src/BrightScriptCommands.ts @@ -177,7 +177,47 @@ export class BrightScriptCommands { }); this.registerCommand('pressVolumeUp', async () => { - await this.sendRemoteCommand('FindVolumeUp'); + await this.sendRemoteCommand('VolumeUp'); + }); + + this.registerCommand('setVolume', async () => { + let result = await vscode.window.showInputBox({ + placeHolder: 'The target volume level (0-100)', + value: '', + validateInput: (text: string) => { + const num = Number(text); + if (isNaN(num)) { + return 'Value must be a number'; + } else if (num < 0 || num > 100) { + return 'Please enter a number between 0 and 100'; + } + return null; + } + }); + const targetVolume = Number(result); + + if (!isNaN(targetVolume)) { + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Setting volume' + }, async (progress) => { + const totalCommands = 100 + targetVolume; + const incrementValue = 100 / totalCommands; + let executedCommands = 0; + + for (let i = 0; i < 100; i++) { + await this.sendRemoteCommand('VolumeDown'); + executedCommands++; + progress.report({ increment: incrementValue, message: `decreasing volume - ${Math.round((executedCommands / totalCommands) * 100)}%` }); + } + + for (let i = 0; i < targetVolume; i++) { + await this.sendRemoteCommand('VolumeUp'); + executedCommands++; + progress.report({ increment: incrementValue, message: `increasing volume - ${Math.round((executedCommands / totalCommands) * 100)}%` }); + } + }); + } }); this.registerCommand('pressPowerOff', async () => { @@ -196,6 +236,26 @@ export class BrightScriptCommands { await this.sendRemoteCommand('ChannelDown'); }); + this.registerCommand('pressBlue', async () => { + await this.sendRemoteCommand('Blue'); + }); + + this.registerCommand('pressGreen', async () => { + await this.sendRemoteCommand('Green'); + }); + + this.registerCommand('pressRed', async () => { + await this.sendRemoteCommand('Red'); + }); + + this.registerCommand('pressYellow', async () => { + await this.sendRemoteCommand('Yellow'); + }); + + this.registerCommand('pressExit', async () => { + await this.sendRemoteCommand('Exit'); + }); + this.registerCommand('changeTvInput', async (host?: string) => { const selectedInput = await vscode.window.showQuickPick([ 'InputHDMI1', diff --git a/src/grammar/brightscript.tmLanguage.spec.ts b/src/grammar/brightscript.tmLanguage.spec.ts index aa31da12..0620b1f0 100644 --- a/src/grammar/brightscript.tmLanguage.spec.ts +++ b/src/grammar/brightscript.tmLanguage.spec.ts @@ -6,6 +6,73 @@ import { standardizePath as s } from 'brighterscript'; const brightscriptTmlanguagePath = s`${__dirname}/../../syntaxes/brightscript.tmLanguage.json`; describe('brightscript.tmlanguage.json', () => { + it('colors numerics correctly', async () => { + await testGrammar(` + var = 1 + ' ^ constant.numeric.brs + '^^^ entity.name.variable.local.brs + `); + + await testGrammar(` + var = 1.1 + ' ^ constant.numeric.brs + ' ^ constant.numeric.brs + '^^^ entity.name.variable.local.brs + `); + + await testGrammar(` + var = .1 + ' ^ constant.numeric.brs + '^^^ entity.name.variable.local.brs + `); + + await testGrammar(` + var = 0x2 + ' ^^^ constant.numeric.brs + '^^^ entity.name.variable.local.brs + `); + + await testGrammar(` + var = 1.8e+308 + ' ^^^^^^^^ constant.numeric.brs + '^^^ entity.name.variable.local.brs + `); + + await testGrammar(` + var = 0x2 + ' ^^^ constant.numeric.brs + '^^^ entity.name.variable.local.brs + `); + + await testGrammar(` + var = 0% + ' ^ source.brs + ' ^ constant.numeric.brs + '^^^ entity.name.variable.local.brs + `); + + await testGrammar(` + var = 0! + ' ^ source.brs + ' ^ constant.numeric.brs + '^^^ entity.name.variable.local.brs + `); + + await testGrammar(` + var = 0# + ' ^ source.brs + ' ^ constant.numeric.brs + '^^^ entity.name.variable.local.brs + `); + + await testGrammar(` + var = 0& + ' ^ source.brs + ' ^ constant.numeric.brs + '^^^ entity.name.variable.local.brs + `); + }); + it('colors m, m.top, m.global, and super correctly', async () => { await testGrammar(` super.doSomething() @@ -31,6 +98,27 @@ describe('brightscript.tmlanguage.json', () => { `); }); + it('does not color `top` as a variable name', async () => { + await testGrammar(` + top = true + '^^^ entity.name.variable.local.brs + `); + }); + + it('does not color `top` when part of another variable name', async () => { + await testGrammar(` + m.top1 = true + ' ^^^^ variable.other.object.property.brs + '^ keyword.other.this.brs + `); + + await testGrammar(` + m.1top = true + ' ^^^^ variable.other.object.property.brs + '^ keyword.other.this.brs + `); + }); + it('colors alias statement properly', async () => { await testGrammar(` alias alpha = beta @@ -447,6 +535,246 @@ describe('brightscript.tmlanguage.json', () => { `); }); + it('handles named function declarations', async () => { + await testGrammar(` + sub write() + ' ^^^^^ entity.name.function.brs + '^^^ keyword.declaration.function.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + sub write () + ' ^^^^^ entity.name.function.brs + '^^^ keyword.declaration.function.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + sub write() as string + ' ^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^ entity.name.function.brs + '^^^ keyword.declaration.function.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + sub write(param as function) as string + ' ^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^ entity.name.variable.local.brs + ' ^^^^^ entity.name.function.brs + '^^^ keyword.declaration.function.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + }); + + it('handles named public/protected/private function declarations', async () => { + await testGrammar(` + public sub write() + ' ^^^^^ entity.name.function.brs + ' ^^^ keyword.declaration.function.brs + '^^^^^^ storage.modifier.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + protected sub write() + ' ^^^^^ entity.name.function.brs + ' ^^^ keyword.declaration.function.brs + '^^^^^^^^^ storage.modifier.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + private sub write() + ' ^^^^^ entity.name.function.brs + ' ^^^ keyword.declaration.function.brs + '^^^^^^^ storage.modifier.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + public sub write() as string + ' ^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^ entity.name.function.brs + ' ^^^ keyword.declaration.function.brs + '^^^^^^ storage.modifier.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + public sub write(param as function) as string + ' ^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^ entity.name.variable.local.brs + ' ^^^^^ entity.name.function.brs + ' ^^^ keyword.declaration.function.brs + '^^^^^^ storage.modifier.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + }); + + it('handles named public/protected/private with override function declarations', async () => { + await testGrammar(` + public override sub write() + ' ^^^^^ entity.name.function.brs + ' ^^^ keyword.declaration.function.brs + ' ^^^^^^^^ storage.modifier.brs + '^^^^^^ storage.modifier.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + protected override sub write() + ' ^^^^^ entity.name.function.brs + ' ^^^ keyword.declaration.function.brs + ' ^^^^^^^^ storage.modifier.brs + '^^^^^^^^^ storage.modifier.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + private override sub write() + ' ^^^^^ entity.name.function.brs + ' ^^^ keyword.declaration.function.brs + ' ^^^^^^^^ storage.modifier.brs + '^^^^^^^ storage.modifier.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + public override sub write() as string + ' ^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^ entity.name.function.brs + ' ^^^ keyword.declaration.function.brs + ' ^^^^^^^^ storage.modifier.brs + '^^^^^^ storage.modifier.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + public override sub write(param as function) as string + ' ^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^ entity.name.variable.local.brs + ' ^^^^^ entity.name.function.brs + ' ^^^ keyword.declaration.function.brs + ' ^^^^^^^^ storage.modifier.brs + '^^^^^^ storage.modifier.brs + + end sub + '^^^^^^^ keyword.declaration.function.brs + `); + }); + + it('handles anon function declarations', async () => { + await testGrammar(` + var = function () + ' ^^^^^^^^ keyword.declaration.function.brs + '^^^ entity.name.variable.local.brs + + end function + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + var = function() + ' ^^^^^^^^ keyword.declaration.function.brs + '^^^ entity.name.variable.local.brs + + end function + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + var = function() as string + ' ^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^^^^ keyword.declaration.function.brs + '^^^ entity.name.variable.local.brs + + end function + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + var = function(param as function) as string + ' ^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^ entity.name.variable.local.brs + ' ^^^^^^^^ keyword.declaration.function.brs + '^^^ entity.name.variable.local.brs + + end function + '^^^^^^^ keyword.declaration.function.brs + `); + + await testGrammar(` + var = { + name: function() as string + ' ^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^^^^ keyword.declaration.function.brs + + end function + '^^^^^^^ keyword.declaration.function.brs + } + `); + + await testGrammar(` + var = { + name: function(param as function) as string + ' ^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^^^^ storage.type.brs + ' ^^ keyword.control.brs + ' ^^^^^ entity.name.variable.local.brs + ' ^^^^^^^^ keyword.declaration.function.brs + + end function + '^^^^^^^ keyword.declaration.function.brs + } + `); + }); + it.skip('colorizes class fields properly', async () => { //TODO the properties have the wrong scope...this should get fixed when we improve the class textmate scope flow await testGrammar(` @@ -465,6 +793,101 @@ describe('brightscript.tmlanguage.json', () => { end class `); }); + + it('colorizes class_roku_builtin correctly', async () => { + async function testRokuClass (className: string) { + return testGrammar(` + var = createObject("${className}") + ' ${'^'.repeat(className.length)} support.class.brs + ' ^^^^^^^^^^^^ entity.name.function.brs + '^^^ entity.name.variable.local.brs + `); + } + + await testRokuClass('roAppInfo'); + await testRokuClass('roAppManager'); + await testRokuClass('roAppMemoryMonitor'); + await testRokuClass('roAppMemoryMonitorEvent'); + await testRokuClass('roArray'); + await testRokuClass('roAssociativeArray'); + await testRokuClass('roAudioGuide'); + await testRokuClass('roAudioMetadata'); + await testRokuClass('roAudioPlayer'); + await testRokuClass('roAudioPlayerEvent'); + await testRokuClass('roAudioResource'); + await testRokuClass('roBitmap'); + await testRokuClass('roBoolean'); + await testRokuClass('roByteArray'); + await testRokuClass('roCECStatus'); + await testRokuClass('roCECStatusEvent'); + await testRokuClass('roChannelStore'); + await testRokuClass('roChannelStoreEvent'); + await testRokuClass('roCompositor'); + await testRokuClass('roDataGramSocket'); + await testRokuClass('roDateTime'); + await testRokuClass('roDeviceCrypto'); + await testRokuClass('roDeviceInfo'); + await testRokuClass('roDeviceInfoEvent'); + await testRokuClass('roDouble'); + await testRokuClass('roDsa'); + await testRokuClass('roEVPCipher'); + await testRokuClass('roEVPDigest'); + await testRokuClass('roFileSystem'); + await testRokuClass('roFileSystemEvent'); + await testRokuClass('roFloat'); + await testRokuClass('roFont'); + await testRokuClass('roFontRegistry'); + await testRokuClass('roFunction'); + await testRokuClass('roHdmiStatus'); + await testRokuClass('roHdmiStatusEvent'); + await testRokuClass('roHMAC'); + await testRokuClass('roHttpAgent'); + await testRokuClass('roImageMetaData'); + await testRokuClass('roInput'); + await testRokuClass('roInputEvent'); + await testRokuClass('roInt'); + await testRokuClass('roInvalid'); + await testRokuClass('roList'); + await testRokuClass('roLocalization'); + await testRokuClass('roLongInteger'); + await testRokuClass('roMessagePort'); + await testRokuClass('roMicrophone'); + await testRokuClass('roMicrophoneEvent'); + await testRokuClass('roPath'); + await testRokuClass('roProgramGuide'); + await testRokuClass('roRegex'); + await testRokuClass('roRegion'); + await testRokuClass('roRegistry'); + await testRokuClass('roRegistrySection'); + await testRokuClass('roRemoteInfo'); + await testRokuClass('roRSA'); + await testRokuClass('roScreen'); + await testRokuClass('roSGNode'); + await testRokuClass('roSGNodeEvent'); + await testRokuClass('roSGScreen'); + await testRokuClass('roSGScreenEvent'); + await testRokuClass('roSocketAddress'); + await testRokuClass('roSocketEvent'); + await testRokuClass('roSprite'); + await testRokuClass('roStreamSocket'); + await testRokuClass('roString'); + await testRokuClass('roSystemlog'); + await testRokuClass('roSystemLogEvent'); + await testRokuClass('roTextToSpeech'); + await testRokuClass('roTextToSpeechEvent'); + await testRokuClass('roTextureManager'); + await testRokuClass('roTextureRequest'); + await testRokuClass('roTextureRequestEvent'); + await testRokuClass('roTimespan'); + await testRokuClass('roUniversalControlEvent'); + await testRokuClass('roUrlEvent'); + await testRokuClass('roUrlTransfer'); + await testRokuClass('roVideoPlayer'); + await testRokuClass('roVideoPlayerEvent'); + await testRokuClass('roXMLElement'); + await testRokuClass('roXMLList'); + + }); }); const registries = new Cache(); diff --git a/syntaxes/brightscript.tmLanguage.json b/syntaxes/brightscript.tmLanguage.json index b52646a7..cd28c268 100644 --- a/syntaxes/brightscript.tmLanguage.json +++ b/syntaxes/brightscript.tmLanguage.json @@ -82,6 +82,9 @@ { "include": "#function_call" }, + { + "include": "#numeric_literal" + }, { "include": "#object_properties" }, @@ -507,7 +510,7 @@ "name": "entity.name.function.brs" } }, - "match": "(?i:(?:(public|protected|private)\\s+)?(?:(override)\\s+)?((?:sub|function)[^\\w])(?:\\s+([a-z_][a-z0-9_]*))?)" + "match": "(?i:(?:(public|protected|private)\\s+)?(?:(override)\\s+)?((?:sub|function))(?:\\s+(?:\\(|([a-z_][a-z0-9_]*))))" }, "field": { "match": "(?i:(public|protected|private)\\s+([a-z0-9_]+))", @@ -581,8 +584,7 @@ ] }, { - "match": "\\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f)?\\b", - "name": "constant.numeric.brs" + "include": "#numeric_literal" }, { "patterns": [ @@ -606,6 +608,14 @@ } ] }, + "numeric_literal": { + "patterns": [ + { + "match": "\\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f)?\\b", + "name": "constant.numeric.brs" + } + ] + }, "comment": { "patterns": [ { @@ -657,7 +667,7 @@ "name": "keyword.other.this.brs" } }, - "match": "(?i:(?