using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; namespace codelanguage { /// /// Completion item for IntelliSense /// public class CompletionItem { public string Label { get; set; } public string InsertText { get; set; } public string Documentation { get; set; } public CompletionItemKind Kind { get; set; } public int SortOrder { get; set; } public CompletionItem(string label, string insert, string doc, CompletionItemKind kind, int sort = 0) { Label = label; InsertText = insert; Documentation = doc; Kind = kind; SortOrder = sort; } public override string ToString() { return $"{Label} ({Kind})"; } } public enum CompletionItemKind { Variable, Procedure, Type, Keyword, BuiltinProc, Field, Parameter, Snippet } /// /// Scope context for variable and procedure tracking /// public class ScopeContext { public Dictionary LocalVariables { get; set; } = new(); public Dictionary Parameters { get; set; } = new(); public HashSet AvailableProcedures { get; set; } = new(); public string? CurrentProcedureName { get; set; } public string? CurrentTypePath { get; set; } public void Clear() { LocalVariables.Clear(); Parameters.Clear(); AvailableProcedures.Clear(); CurrentProcedureName = null; CurrentTypePath = null; } } /// /// Code completion and IntelliSense service for DM/Craft++ /// Provides variable names, procedure names, type paths, and member access completion /// public class CodeCompletionService { private static readonly HashSet DmKeywords = new(StringComparer.OrdinalIgnoreCase) { "if", "else", "for", "while", "do", "switch", "case", "default", "break", "continue", "return", "spawn", "sleep", "var", "static", "const", "global", "tmp", "local", "proc", "verb", "set", "topic", "new", "del", "true", "false", "null", "in", "as", "to", "try", "catch", "throw", "goto", "label", "override" }; private static readonly HashSet DmTypes = new(StringComparer.OrdinalIgnoreCase) { "null", "num", "text", "path", "list", "icon", "matrix", "color", "savefile", "datum", "obj", "mob", "turf", "area", "sound", "image", "client", "regex", "world" }; private static readonly Dictionary BuiltinProcs = new(StringComparer.OrdinalIgnoreCase) { { "alert", "alert(Message, Title = \"Alert\", Buttons = \"Ok\") → text choice" }, { "output", "output(Message, Recipient = null) → null" }, { "input", "input(Message, Title = \"Input\", Type = \"\") → value" }, { "rand", "rand(Low, High) → num" }, { "sqrt", "sqrt(x) → num" }, { "abs", "abs(x) → num" }, { "round", "round(x, Digits = 0) → num" }, { "length", "length(Value) → num" }, { "copytext", "copytext(Text, Start, End) → text" }, { "findtext", "findtext(Haystack, Needle) → num" }, { "view", "view(Distance = 5, Center = usr) → list" }, { "range", "range(Distance, Center) → list" }, { "turn", "turn(Direction, Angle) → num" }, { "get_dir", "get_dir(A, B) → num" } }; private readonly ScopeContext _scope = new(); /// /// Get completion items for variable names in current scope /// public List GetVariableCompletions() { var items = new List(); foreach (var localVar in _scope.LocalVariables) { items.Add(new CompletionItem( localVar.Key, localVar.Key, $"Variable of type {localVar.Value}", CompletionItemKind.Variable, 50 )); } foreach (var param in _scope.Parameters) { items.Add(new CompletionItem( param.Key, param.Key, $"Parameter of type {param.Value}", CompletionItemKind.Parameter, 60 )); } return items.OrderByDescending(i => i.SortOrder).ToList(); } /// /// Get completion items for procedure names /// public List GetProcedureCompletions() { var items = new List(); foreach (var procName in _scope.AvailableProcedures) { items.Add(new CompletionItem( procName, procName + "()", $"Procedure: {procName}", CompletionItemKind.Procedure, 40 )); } return items; } /// /// Get completion items for type paths (e.g., /mob, /obj, /turf) /// public List GetTypePathCompletions() { var items = new List(); var basePaths = new[] { "/mob", "/obj", "/turf", "/area", "/datum" }; foreach (var typePath in basePaths) { items.Add(new CompletionItem( typePath, typePath, $"Type path: {typePath}", CompletionItemKind.Type, 30 )); } return items; } /// /// Get completion items for member access (after dot operator) /// Uses TypeRegistry to resolve the type and return its vars, procs, and verbs. /// public List GetMemberAccessCompletions(string objectType) { var items = new List(); var added = new HashSet(StringComparer.OrdinalIgnoreCase); // For src, use the current type path from scope analysis string? resolvedPath = null; if (string.Equals(objectType, "src", StringComparison.OrdinalIgnoreCase)) { resolvedPath = _scope.CurrentTypePath; } else if (string.Equals(objectType, "usr", StringComparison.OrdinalIgnoreCase)) { resolvedPath = "/mob"; } else { // Try to find matching type in TypeRegistry resolvedPath = ResolveTypePath(objectType); } if (resolvedPath != null) { AddTypeMembers(items, added, resolvedPath); } return items; } /// /// Get completion items for the src/self object's members in the current scope /// public List GetSrcMemberCompletions() { var items = new List(); var added = new HashSet(StringComparer.OrdinalIgnoreCase); if (_scope.CurrentTypePath != null) { AddTypeMembers(items, added, _scope.CurrentTypePath); } return items; } private void AddTypeMembers(List items, HashSet added, string typePath) { TypeRegistry.EnsureInitialized(); // Build the inheritance chain from root to most-derived var chain = new List(); string? current = typePath; var visited = new HashSet(); while (!string.IsNullOrEmpty(current) && visited.Add(current)) { var def = TypeRegistry.Get(current); if (def == null) break; chain.Add(def); current = def.ParentPath; } // Walk root → leaf so derived members override parents for (int i = chain.Count - 1; i >= 0; i--) { var def = chain[i]; foreach (var v in def.DefaultVars.Keys) { if (added.Add(v)) items.Add(new CompletionItem(v, v, $"var {v} — from {def.Path}", CompletionItemKind.Field, 50)); } foreach (var p in def.Procs.Keys) { if (added.Add(p)) items.Add(new CompletionItem(p, p + "()", $"proc {p}() — from {def.Path}", CompletionItemKind.Procedure, 40)); } foreach (var v in def.Verbs.Keys) { if (added.Add(v)) items.Add(new CompletionItem(v, v + "()", $"verb {v}() — from {def.Path}", CompletionItemKind.Procedure, 40)); } } } private static string? ResolveTypePath(string name) { foreach (var t in TypeRegistry.GetAllTypes()) { if (t.EndsWith("/" + name, StringComparison.OrdinalIgnoreCase) || string.Equals(t.TrimStart('/'), name, StringComparison.OrdinalIgnoreCase)) { return t; } } return null; } /// /// Get completion items for keywords /// public List GetKeywordCompletions() { var items = DmKeywords.Select(k => new CompletionItem(k, k, $"Keyword: {k}", CompletionItemKind.Keyword, 10) ).ToList(); return items; } /// /// Get completion items for built-in procedures /// public List GetBuiltinProcCompletions() { var items = new List(); foreach (var proc in BuiltinProcs) { items.Add(new CompletionItem( proc.Key, proc.Key, proc.Value, CompletionItemKind.BuiltinProc, 20 )); } return items; } /// /// Get all completions filtered by prefix /// public List GetCompletions(string prefix, CompletionContext context = CompletionContext.General) { var items = new List(); // Add different completion types based on context if (context.HasFlag(CompletionContext.Variables)) items.AddRange(GetVariableCompletions()); if (context.HasFlag(CompletionContext.Procedures)) items.AddRange(GetProcedureCompletions()); if (context.HasFlag(CompletionContext.Types)) items.AddRange(GetTypePathCompletions()); if (context.HasFlag(CompletionContext.Keywords)) items.AddRange(GetKeywordCompletions()); if (context.HasFlag(CompletionContext.Builtins)) items.AddRange(GetBuiltinProcCompletions()); // Filter by prefix if (!string.IsNullOrEmpty(prefix)) { items = items .Where(i => i.Label.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) .ToList(); } // Sort by relevance return items .OrderByDescending(i => i.SortOrder) .ThenBy(i => i.Label) .ToList(); } /// /// Parse code to extract scope information (variables, procedures, types) /// public void AnalyzeCode(string sourceCode) { _scope.Clear(); var lines = sourceCode.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); foreach (var line in lines) { // Extract variable declarations (both "var /type name" and "var name") var varMatch = Regex.Match(line, @"var\s+(?:/?([\w/]+)\s+)?(\w+)\s*=", RegexOptions.IgnoreCase); if (varMatch.Success) { string typeName = varMatch.Groups[1].Success ? varMatch.Groups[1].Value : "null"; string varName = varMatch.Groups[2].Value; _scope.LocalVariables[varName] = typeName; } // Extract procedure names var procMatch = Regex.Match(line, @"proc\s+(\w+)", RegexOptions.IgnoreCase); if (procMatch.Success) { _scope.AvailableProcedures.Add(procMatch.Groups[1].Value); } // Extract current procedure AND its enclosing type path // Matches: mob/Player/Login() or /mob/Player/Login() or just Login() var procDeclMatch = Regex.Match(line, @"^(?:/?([a-zA-Z_][\w/]*)/)?(\w+)\s*\(", RegexOptions.IgnoreCase); if (procDeclMatch.Success) { _scope.CurrentProcedureName = procDeclMatch.Groups[2].Value; string? typePath = procDeclMatch.Groups[1].Success ? "/" + procDeclMatch.Groups[1].Value.Trim('/') : null; _scope.CurrentTypePath = typePath; } // Extract procedure parameters var paramMatch = Regex.Match(line, @"\(([^)]*)\)"); if (paramMatch.Success && _scope.CurrentProcedureName != null) { var paramStr = paramMatch.Groups[1].Value; var parameters = paramStr.Split(','); foreach (var param in parameters) { var parts = param.Trim().Split('=')[0].Split(); if (parts.Length >= 2) { string paramType = parts[0]; string paramName = parts[parts.Length - 1]; if (!string.IsNullOrEmpty(paramName)) _scope.Parameters[paramName] = paramType; } } } } } /// /// Get documentation/signature for a completion item /// public string GetSignatureHelp(string name) { if (BuiltinProcs.ContainsKey(name)) return BuiltinProcs[name]; if (_scope.LocalVariables.ContainsKey(name)) return $"{name}: {_scope.LocalVariables[name]}"; if (_scope.Parameters.ContainsKey(name)) return $"{name}: {_scope.Parameters[name]} (parameter)"; return $"{name}"; } /// /// Get scope context /// public ScopeContext GetScope() => _scope; } [Flags] public enum CompletionContext { General = 0, Variables = 1, Procedures = 2, Types = 4, Keywords = 8, Builtins = 16, All = Variables | Procedures | Types | Keywords | Builtins } }