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
}
}