2026-05-11 04:06:51 -05:00
import { cmdlets , builtinCmdlets , CmdletDefinition , variableTypes , scriptTypeValues } from "./PowerShellCompletions.g" ;
// Merge LANCommander cmdlets (shown first) with built-in PowerShell cmdlets
const allCmdlets : CmdletDefinition [ ] = [ . . . cmdlets , . . . builtinCmdlets ] ;
2026-05-08 02:02:33 -05:00
declare const monaco : any ;
2026-05-08 02:13:36 -05:00
// Build a lookup from type name to its properties
const typeMap = new Map ( variableTypes . map ( ( t ) = > [ t . name , t ] ) ) ;
2026-05-08 02:02:33 -05:00
interface VariableDefinition {
name : string ;
2026-05-08 02:13:36 -05:00
type : string ;
2026-05-08 02:02:33 -05:00
description : string ;
2026-05-10 19:32:27 -05:00
scriptTypes? : string [ ] ;
2026-05-08 02:02:33 -05:00
}
2026-05-10 19:32:27 -05:00
// Variables available across all client-side game script types
2026-05-26 19:59:06 -05:00
const gameClientScriptTypes = [ "Install" , "Uninstall" , "BeforeStart" , "AfterStop" , "NameChange" , "KeyChange" , "SaveUpload" , "SaveDownload" , "DetectInstall" , "RunWrapper" ] ;
2026-05-10 19:32:27 -05:00
2026-05-08 02:02:33 -05:00
const variables : VariableDefinition [ ] = [
2026-05-10 19:32:27 -05:00
{ name : "$InstallDirectory" , type : "string" , description : "Root install directory for the game" , scriptTypes : gameClientScriptTypes } ,
2026-05-08 02:13:36 -05:00
{ name : "$WorkingDirectory" , type : "string" , description : "Current working directory for the script" } ,
2026-05-10 19:32:27 -05:00
{ name : "$ServerAddress" , type : "string" , description : "Address of the LANCommander server" , scriptTypes : gameClientScriptTypes } ,
{ name : "$DefaultInstallDirectory" , type : "string" , description : "Default installation directory" , scriptTypes : gameClientScriptTypes } ,
2026-05-26 19:59:06 -05:00
{ name : "$GameManifest" , type : "GameManifest" , description : "The game's manifest object containing metadata such as title, ID, sort title, description, notes, and related collections" , scriptTypes : [ "Install" , "Uninstall" , "BeforeStart" , "AfterStop" , "NameChange" , "KeyChange" , "SaveUpload" , "SaveDownload" , "DetectInstall" , "RunWrapper" ] } ,
2026-05-10 19:32:27 -05:00
{ name : "$PlayerAlias" , type : "string" , description : "Current player's alias/display name" , scriptTypes : [ "BeforeStart" , "AfterStop" ] } ,
{ name : "$NewPlayerAlias" , type : "string" , description : "New player alias (name change scripts)" , scriptTypes : [ "NameChange" ] } ,
{ name : "$OldPlayerAlias" , type : "string" , description : "Previous player alias (name change scripts)" , scriptTypes : [ "NameChange" ] } ,
{ name : "$AllocatedKey" , type : "string" , description : "The product/serial key allocated to the current player for this game (key allocation scripts)" , scriptTypes : [ "KeyChange" ] } ,
{ name : "$ScriptType" , type : "ScriptType" , description : "Type of the script being executed" } ,
{ name : "$Server" , type : "Server" , description : "Server model object with properties: Id, Name, Host, Port, Game, and WorkingDirectory" , scriptTypes : [ "BeforeStart" , "AfterStop" , "GameStarted" , "GameStopped" ] } ,
{ name : "$Game" , type : "Game" , description : "Game model object with properties: Id, Title, SortTitle, Description, and Notes" , scriptTypes : [ "Package" , "GameStarted" , "GameStopped" ] } ,
{ name : "$User" , type : "User" , description : "User model object with properties: Id, UserName, and Alias" , scriptTypes : [ "GameStarted" , "GameStopped" , "UserRegistration" , "UserLogin" ] } ,
{ name : "$ToolManifest" , type : "ToolManifest" , description : "Tool manifest object containing metadata for the current tool" , scriptTypes : [ "Install" , "BeforeStart" , "AfterStop" , "DetectInstall" ] } ,
{ name : "$Tool" , type : "Tool" , description : "Tool model object with properties: Id, Name, and Description" , scriptTypes : [ "Package" ] } ,
2026-05-26 19:59:06 -05:00
{ name : "$RedistributableManifest" , type : "RedistributableManifest" , description : "Redistributable manifest object containing metadata for the current redistributable" , scriptTypes : [ "Install" , "BeforeStart" , "AfterStop" , "NameChange" , "DetectInstall" , "RunWrapper" ] } ,
{ name : "$ExecutablePath" , type : "string" , description : "Resolved path to the game executable" , scriptTypes : [ "RunWrapper" ] } ,
{ name : "$Arguments" , type : "string" , description : "Resolved command-line arguments for the executable" , scriptTypes : [ "RunWrapper" ] } ,
2026-05-10 19:32:27 -05:00
{ name : "$Redistributable" , type : "Redistributable" , description : "Redistributable model object with properties: Id, Name, and Description" , scriptTypes : [ "Package" ] } ,
{ name : "$DisplayWidth" , type : "string" , description : "Primary display width in pixels" , scriptTypes : gameClientScriptTypes } ,
{ name : "$DisplayHeight" , type : "string" , description : "Primary display height in pixels" , scriptTypes : gameClientScriptTypes } ,
{ name : "$DisplayRefreshRate" , type : "string" , description : "Primary display refresh rate in Hz" , scriptTypes : gameClientScriptTypes } ,
{ name : "$DisplayBitDepth" , type : "string" , description : "Primary display bit depth (bits per pixel)" , scriptTypes : gameClientScriptTypes } ,
{ name : "$IPXRelayHost" , type : "string" , description : "IPX relay server hostname (when IPX relay is configured)" , scriptTypes : gameClientScriptTypes } ,
{ name : "$IPXRelayPort" , type : "string" , description : "IPX relay server port (when IPX relay is configured)" , scriptTypes : gameClientScriptTypes } ,
{ name : "$ServerId" , type : "string" , description : "Server GUID identifier" , scriptTypes : [ "BeforeStart" , "AfterStop" , "GameStarted" , "GameStopped" ] } ,
{ name : "$ServerName" , type : "string" , description : "Server display name" , scriptTypes : [ "BeforeStart" , "AfterStop" , "GameStarted" , "GameStopped" ] } ,
{ name : "$ServerHost" , type : "string" , description : "Server hostname" , scriptTypes : [ "BeforeStart" , "AfterStop" , "GameStarted" , "GameStopped" ] } ,
{ name : "$ServerPort" , type : "string" , description : "Server port number" , scriptTypes : [ "BeforeStart" , "AfterStop" , "GameStarted" , "GameStopped" ] } ,
{ name : "$GameTitle" , type : "string" , description : "Game title" , scriptTypes : [ "BeforeStart" , "AfterStop" , "GameStarted" , "GameStopped" ] } ,
{ name : "$GameId" , type : "string" , description : "Game GUID identifier" , scriptTypes : [ "BeforeStart" , "AfterStop" , "GameStarted" , "GameStopped" ] } ,
2026-05-08 02:02:33 -05:00
] ;
let registered = false ;
2026-05-10 19:32:27 -05:00
let currentScriptType : string | null = null ;
2026-05-08 02:02:33 -05:00
export function registerPowerShellCompletions ( ) : void {
if ( registered || typeof monaco === "undefined" ) return ;
registered = true ;
registerCompletionProvider ( ) ;
registerHoverProvider ( ) ;
}
2026-05-10 19:32:27 -05:00
export function setScriptType ( scriptType : string | null ) : void {
currentScriptType = scriptType ;
}
function getFilteredVariables ( ) : VariableDefinition [ ] {
if ( ! currentScriptType ) return variables ;
return variables . filter ( ( v ) = > {
// Variables without scriptTypes are always available (e.g. $WorkingDirectory, $ScriptType)
if ( ! v . scriptTypes ) return true ;
return v . scriptTypes . includes ( currentScriptType ! ) ;
} ) ;
}
2026-05-08 02:02:33 -05:00
function registerCompletionProvider ( ) : void {
monaco . languages . registerCompletionItemProvider ( "powershell" , {
2026-05-08 02:13:36 -05:00
triggerCharacters : [ "-" , "$" , "." ] ,
2026-05-08 02:02:33 -05:00
provideCompletionItems ( model : any , position : any ) {
const word = model . getWordUntilPosition ( position ) ;
const range = {
startLineNumber : position.lineNumber ,
endLineNumber : position.lineNumber ,
startColumn : word.startColumn ,
endColumn : word.endColumn ,
} ;
const lineContent : string = model . getLineContent ( position . lineNumber ) ;
const textBefore = lineContent . substring ( 0 , position . column - 1 ) ;
const suggestions : any [ ] = [ ] ;
2026-05-10 19:32:27 -05:00
const filtered = getFilteredVariables ( ) ;
2026-05-08 02:02:33 -05:00
2026-05-08 02:13:36 -05:00
// Member access completions: triggered by . after a variable
const memberMatch = textBefore . match ( /\$(\w+)\.(\w*)$/ ) ;
if ( memberMatch ) {
const varName = "$" + memberMatch [ 1 ] ;
2026-05-10 19:32:27 -05:00
const variable = filtered . find ( ( v ) = > v . name === varName ) ;
2026-05-08 02:13:36 -05:00
if ( variable ) {
const typeDef = typeMap . get ( variable . type ) ;
if ( typeDef ) {
for ( const prop of typeDef . properties ) {
suggestions . push ( {
label : prop.name ,
kind : monaco.languages.CompletionItemKind.Field ,
insertText : prop.name ,
detail : ` [ ${ prop . type } ] ` ,
documentation : ` Property of ${ variable . type } ` ,
range ,
} ) ;
}
return { suggestions } ;
}
}
}
2026-05-08 02:02:33 -05:00
// Variable completions: triggered by $
if ( /\$\w*$/ . test ( textBefore ) ) {
const varRange = {
. . . range ,
startColumn : textBefore.lastIndexOf ( "$" ) + 1 ,
} ;
2026-05-10 19:32:27 -05:00
for ( const v of filtered ) {
2026-05-08 02:02:33 -05:00
suggestions . push ( {
label : v.name ,
kind : monaco.languages.CompletionItemKind.Variable ,
insertText : v.name.substring ( 1 ) ,
2026-05-08 02:13:36 -05:00
detail : ` [ ${ v . type } ] ` ,
2026-05-08 02:02:33 -05:00
documentation : v.description ,
range : varRange ,
} ) ;
}
}
// Parameter completions: triggered by - after a cmdlet name
const paramMatch = textBefore . match ( /-(\w*)$/ ) ;
if ( paramMatch ) {
const cmdlet = findCmdletOnLine ( textBefore ) ;
if ( cmdlet ) {
const dashCol = textBefore . lastIndexOf ( "-" ) + 1 ;
const paramRange = {
. . . range ,
startColumn : dashCol ,
} ;
for ( const p of cmdlet . parameters ) {
const detail = ` [ ${ p . type } ] ${ p . mandatory ? " (Required)" : "" } ` ;
suggestions . push ( {
label : ` - ${ p . name } ` ,
kind : monaco.languages.CompletionItemKind.Property ,
insertText : ` - ${ p . name } ` ,
detail ,
documentation : p.helpMessage || undefined ,
range : paramRange ,
sortText : p.mandatory ? ` 0_ ${ p . name } ` : ` 1_ ${ p . name } ` ,
} ) ;
for ( const alias of p . aliases ) {
suggestions . push ( {
label : ` - ${ alias } ` ,
kind : monaco.languages.CompletionItemKind.Property ,
insertText : ` - ${ alias } ` ,
detail : ` ${ detail } (alias for - ${ p . name } ) ` ,
documentation : p.helpMessage || undefined ,
range : paramRange ,
sortText : ` 2_ ${ alias } ` ,
} ) ;
}
}
}
}
// Cmdlet name completions
2026-05-08 02:13:36 -05:00
if ( ! paramMatch && ! memberMatch ) {
2026-05-11 04:06:51 -05:00
for ( let i = 0 ; i < allCmdlets . length ; i ++ ) {
const c = allCmdlets [ i ] ;
const isBuiltin = i >= cmdlets . length ;
2026-05-08 02:02:33 -05:00
suggestions . push ( {
label : c.name ,
kind : monaco.languages.CompletionItemKind.Function ,
insertText : c.name ,
2026-05-11 04:06:51 -05:00
detail : c.outputType ? ` -> ${ c . outputType } ` : isBuiltin ? "PowerShell Cmdlet" : "Cmdlet" ,
2026-05-08 02:02:33 -05:00
documentation : buildCmdletDocumentation ( c ) ,
range ,
2026-05-11 04:06:51 -05:00
sortText : isBuiltin ? ` 1_ ${ c . name } ` : ` 0_ ${ c . name } ` ,
2026-05-08 02:02:33 -05:00
} ) ;
}
}
return { suggestions } ;
} ,
} ) ;
}
function registerHoverProvider ( ) : void {
monaco . languages . registerHoverProvider ( "powershell" , {
provideHover ( model : any , position : any ) {
const word = model . getWordAtPosition ( position ) ;
if ( ! word ) return null ;
const lineContent : string = model . getLineContent ( position . lineNumber ) ;
2026-05-10 19:32:27 -05:00
const filtered = getFilteredVariables ( ) ;
2026-05-08 02:02:33 -05:00
// Check for cmdlet hover (Verb-Noun pattern)
const cmdletPattern = /[\w]+-[\w]+/g ;
let match ;
while ( ( match = cmdletPattern . exec ( lineContent ) ) !== null ) {
const start = match . index + 1 ;
const end = start + match [ 0 ] . length ;
if ( position . column >= start && position . column <= end ) {
2026-05-11 04:06:51 -05:00
const cmdlet = allCmdlets . find ( ( c ) = > c . name === match ! [ 0 ] ) ;
2026-05-08 02:02:33 -05:00
if ( cmdlet ) {
return {
range : {
startLineNumber : position.lineNumber ,
endLineNumber : position.lineNumber ,
startColumn : start ,
endColumn : end ,
} ,
contents : [
{ value : ` ** ${ cmdlet . name } ** ` } ,
{ value : buildCmdletHoverMarkdown ( cmdlet ) } ,
] ,
} ;
}
}
}
2026-05-08 02:13:36 -05:00
// Check for variable property hover ($Var.Property)
const varPropPattern = /\$(\w+)\.(\w+)/g ;
while ( ( match = varPropPattern . exec ( lineContent ) ) !== null ) {
const fullStart = match . index + 1 ;
const fullEnd = fullStart + match [ 0 ] . length ;
if ( position . column >= fullStart && position . column <= fullEnd ) {
const varName = "$" + match [ 1 ] ;
const propName = match [ 2 ] ;
2026-05-10 19:32:27 -05:00
const variable = filtered . find ( ( v ) = > v . name === varName ) ;
2026-05-08 02:13:36 -05:00
if ( variable ) {
const typeDef = typeMap . get ( variable . type ) ;
if ( typeDef ) {
const prop = typeDef . properties . find ( ( p ) = > p . name === propName ) ;
if ( prop ) {
return {
range : {
startLineNumber : position.lineNumber ,
endLineNumber : position.lineNumber ,
startColumn : fullStart ,
endColumn : fullEnd ,
} ,
contents : [
{ value : ` ** ${ varName } . ${ prop . name } ** [ ${ prop . type } ] ` } ,
{ value : ` Property of ${ variable . type } ` } ,
] ,
} ;
}
}
}
}
}
2026-05-08 02:02:33 -05:00
// Check for variable hover
const varPattern = /\$[\w]+/g ;
while ( ( match = varPattern . exec ( lineContent ) ) !== null ) {
const start = match . index + 1 ;
const end = start + match [ 0 ] . length ;
if ( position . column >= start && position . column <= end ) {
2026-05-10 19:32:27 -05:00
const variable = filtered . find ( ( v ) = > v . name === match ! [ 0 ] ) ;
2026-05-08 02:02:33 -05:00
if ( variable ) {
2026-05-08 02:13:36 -05:00
const typeDef = typeMap . get ( variable . type ) ;
const contents : { value : string } [ ] = [
{ value : ` ** ${ variable . name } ** [ ${ variable . type } ] ` } ,
{ value : variable.description } ,
] ;
if ( typeDef ) {
const propList = typeDef . properties
. map ( ( p ) = > ` - \` ${ p . name } \` [ ${ p . type } ] ` )
. join ( "\n" ) ;
contents . push ( { value : ` **Properties:** \ n ${ propList } ` } ) ;
}
2026-05-08 02:02:33 -05:00
return {
range : {
startLineNumber : position.lineNumber ,
endLineNumber : position.lineNumber ,
startColumn : start ,
endColumn : end ,
} ,
2026-05-08 02:13:36 -05:00
contents ,
2026-05-08 02:02:33 -05:00
} ;
}
}
}
// Check for parameter hover (-ParameterName)
const paramPattern = /-(\w+)/g ;
while ( ( match = paramPattern . exec ( lineContent ) ) !== null ) {
const start = match . index + 1 ;
const end = start + match [ 0 ] . length ;
if ( position . column >= start && position . column <= end ) {
const paramName = match [ 1 ] ;
const cmdlet = findCmdletOnLine (
lineContent . substring ( 0 , match . index )
) ;
if ( cmdlet ) {
const param = cmdlet . parameters . find (
( p ) = >
p . name === paramName ||
p . aliases . includes ( paramName )
) ;
if ( param ) {
const parts = [
{ value : ` **- ${ param . name } ** [ ${ param . type } ] ${ param . mandatory ? " *(Required)*" : "" } ` } ,
] ;
if ( param . helpMessage ) {
parts . push ( { value : param.helpMessage } ) ;
}
if ( param . aliases . length > 0 ) {
parts . push ( {
value : ` Aliases: ${ param . aliases . map ( ( a ) = > ` - ${ a } ` ) . join ( ", " ) } ` ,
} ) ;
}
return {
range : {
startLineNumber : position.lineNumber ,
endLineNumber : position.lineNumber ,
startColumn : start ,
endColumn : end ,
} ,
contents : parts ,
} ;
}
}
}
}
return null ;
} ,
} ) ;
}
function findCmdletOnLine ( text : string ) : CmdletDefinition | null {
const matches = text . match ( /(\w+-\w+)/g ) ;
if ( ! matches ) return null ;
// Return the last cmdlet found on the line (closest to cursor)
for ( let i = matches . length - 1 ; i >= 0 ; i -- ) {
2026-05-11 04:06:51 -05:00
const cmdlet = allCmdlets . find ( ( c ) = > c . name === matches [ i ] ) ;
2026-05-08 02:02:33 -05:00
if ( cmdlet ) return cmdlet ;
}
return null ;
}
function buildCmdletDocumentation ( c : CmdletDefinition ) : string {
const parts : string [ ] = [ ] ;
if ( c . description ) parts . push ( c . description ) ;
const requiredParams = c . parameters . filter ( ( p ) = > p . mandatory ) ;
if ( requiredParams . length > 0 ) {
parts . push (
"Required: " +
requiredParams . map ( ( p ) = > ` - ${ p . name } ` ) . join ( ", " )
) ;
}
return parts . join ( "\n\n" ) ;
}
function buildCmdletHoverMarkdown ( c : CmdletDefinition ) : string {
const lines : string [ ] = [ ] ;
if ( c . description ) lines . push ( c . description ) ;
if ( c . outputType ) lines . push ( ` **Output:** ${ c . outputType } ` ) ;
if ( c . parameters . length > 0 ) {
lines . push ( "" ) ;
lines . push ( "**Parameters:**" ) ;
for ( const p of c . parameters ) {
const req = p . mandatory ? " *(Required)*" : "" ;
lines . push ( ` - \` - ${ p . name } \` [ ${ p . type } ] ${ req } ` ) ;
}
}
return lines . join ( "\n" ) ;
}
2026-05-10 19:32:27 -05:00
// --- Snippet insertion ---
export function insertSnippet ( snippetText : string ) : void {
if ( typeof monaco === "undefined" ) return ;
// Use the last focused editor (there's typically only one in a dialog)
const editors = monaco . editor . getEditors ( ) ;
const editor = editors [ editors . length - 1 ] ;
if ( ! editor ) return ;
const controller = editor . getContribution ( "snippetController" ) ;
if ( controller ) {
editor . focus ( ) ;
controller . insert ( snippetText ) ;
} else {
// Fallback: insert as plain text
const selection = editor . getSelection ( ) ;
editor . executeEdits ( "snippet" , [ {
range : selection ,
text : snippetText ,
forceMoveMarkers : true ,
} ] ) ;
}
}
// --- Script templates ---
const scriptTemplates : Record < string , string > = {
Package : [
"# Package Script" ,
"# Prepare the game files and create a package for distribution." ,
"" ,
"$StagingPath = \"\"" ,
"" ,
"# Copy or prepare files in $StagingPath" ,
"" ,
"New-Package -Path $StagingPath -Version \"1.0.0\" -Changelog \"Initial release\"" ,
] . join ( "\n" ) ,
Install : [
"# Install Script" ,
"# Runs after the game archive has been extracted." ,
"" ,
] . join ( "\n" ) ,
Uninstall : [
"# Uninstall Script" ,
"# Runs when the game is being uninstalled." ,
"" ,
] . join ( "\n" ) ,
NameChange : [
"# Name Change Script" ,
"# Runs when a player changes their alias." ,
"" ,
] . join ( "\n" ) ,
KeyChange : [
"# Key Change Script" ,
"# Runs when a product key is allocated or changed." ,
"" ,
] . join ( "\n" ) ,
BeforeStart : [
"# Before Start Script" ,
"# Runs before the game process starts." ,
"" ,
] . join ( "\n" ) ,
AfterStop : [
"# After Stop Script" ,
"# Runs after the game process stops." ,
"" ,
] . join ( "\n" ) ,
2026-05-26 19:59:06 -05:00
RunWrapper : [
"# Run Wrapper Script" ,
"# Controls how the game executable is launched." ,
"# This script runs instead of the normal process launch." ,
"" ,
"# Launch the game" ,
"Start-Process -FilePath $ExecutablePath -ArgumentList $Arguments -WorkingDirectory $WorkingDirectory -Wait" ,
] . join ( "\n" ) ,
2026-05-10 19:32:27 -05:00
} ;
export function getScriptTemplate ( scriptType : string ) : string | null {
return scriptTemplates [ scriptType ] ? ? null ;
}
// --- Script validation / diagnostics ---
const allKnownVarNames = new Set ( variables . map ( ( v ) = > v . name ) ) ;
export function validateScript ( ) : void {
if ( typeof monaco === "undefined" ) return ;
// Validate all PowerShell models
const models = monaco . editor . getModels ( ) . filter ( ( m : any ) = > {
const lang = m . getLanguageId ? . ( ) ? ? m . getModeId ? . ( ) ;
return lang === "powershell" ;
} ) ;
for ( const model of models ) {
validateModel ( model ) ;
}
}
function validateModel ( model : any ) : void {
const markers : any [ ] = [ ] ;
const content = model . getValue ( ) ;
const lines = content . split ( "\n" ) ;
const filtered = getFilteredVariables ( ) ;
const filteredNames = new Set ( filtered . map ( ( v ) = > v . name ) ) ;
// Check for missing New-Package in Package scripts
if ( currentScriptType === "Package" ) {
const hasNewPackage = /New-Package\b/ . test ( content ) ;
const hasReturnAssignment = /\$Return\s*=/ . test ( content ) ;
if ( ! hasNewPackage && ! hasReturnAssignment ) {
markers . push ( {
severity : monaco.MarkerSeverity.Warning ,
message : "Package scripts should call New-Package to set the return value with Path, Version, and Changelog." ,
startLineNumber : 1 ,
startColumn : 1 ,
endLineNumber : 1 ,
endColumn : 1 ,
source : "LANCommander" ,
} ) ;
}
}
// Check for variables not available in current script type
if ( currentScriptType ) {
const varPattern = /\$(\w+)/g ;
for ( let i = 0 ; i < lines . length ; i ++ ) {
const line = lines [ i ] ;
let match ;
// Skip comment lines
if ( line . trimStart ( ) . startsWith ( "#" ) ) continue ;
varPattern . lastIndex = 0 ;
while ( ( match = varPattern . exec ( line ) ) !== null ) {
const varName = "$" + match [ 1 ] ;
// Skip PowerShell built-in/common variables and user-assigned variables
if ( ! allKnownVarNames . has ( varName ) ) continue ;
// It's a known LANCommander variable but not available for this script type
if ( ! filteredNames . has ( varName ) ) {
const col = match . index + 1 ;
markers . push ( {
severity : monaco.MarkerSeverity.Warning ,
message : ` ${ varName } is not available in ${ currentScriptType } scripts. ` ,
startLineNumber : i + 1 ,
startColumn : col ,
endLineNumber : i + 1 ,
endColumn : col + match [ 0 ] . length ,
source : "LANCommander" ,
} ) ;
}
}
}
}
monaco . editor . setModelMarkers ( model , "LANCommander" , markers ) ;
}