-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHeddleLanguageService.cs
More file actions
184 lines (164 loc) · 8.03 KB
/
Copy pathHeddleLanguageService.cs
File metadata and controls
184 lines (164 loc) · 8.03 KB
1
2
3
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using Heddle.LanguageServices.Completion;
using Heddle.Runtime.Expressions;
namespace Heddle.LanguageServices
{
/// <summary>
/// Document manager and analysis entry point (phase 6). One instance per workspace; thread-safe: public
/// members may be called from any thread, analyses are serialized per document, results are immutable
/// snapshots. Runs the engine pipeline directly (D9) and projects it (D10); model typing and host-registration
/// knowledge come from the configured assemblies (D14 model ALC, D23 extension scan, D24 function exports).
/// </summary>
public sealed class HeddleLanguageService : IDisposable
{
private readonly HeddleLanguageServiceOptions _options;
private readonly DocumentAnalyzer _analyzer;
private readonly ModelAssemblyManager _modelManager = new ModelAssemblyManager();
private readonly ConcurrentDictionary<string, DocumentAnalysis> _analyses =
new ConcurrentDictionary<string, DocumentAnalysis>(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks =
new ConcurrentDictionary<string, SemaphoreSlim>(StringComparer.Ordinal);
private readonly object _writerGate = new object();
private FunctionRegistry _functions;
private IReadOnlyList<System.Reflection.Assembly> _retainedHandles = Array.Empty<System.Reflection.Assembly>();
private bool _disposed;
public HeddleLanguageService(HeddleLanguageServiceOptions options)
{
_options = options ?? new HeddleLanguageServiceOptions();
_analyzer = new DocumentAnalyzer(_options);
InitializeWorkspace();
}
/// <summary>Optional sink for operational/user-actionable messages (D20). Set by the server or tests.</summary>
internal Action<string> LogSink { get; set; }
/// <summary>The current workspace function registry (the D24 scan result, or null = Default).</summary>
internal FunctionRegistry Functions => _functions;
/// <summary>A weak reference to the last-unloaded model context (D14 collection check).</summary>
internal WeakReference LastUnloadedModelContext => _modelManager.LastUnloaded;
private void InitializeWorkspace()
{
lock (_writerGate)
{
if (_options.AssemblyPaths != null && _options.AssemblyPaths.Count > 0)
{
_modelManager.Load(_options.AssemblyPaths);
_retainedHandles = ExtensionRegistrar.ScanOnce(_options.AssemblyPaths, Log);
}
else
{
// A workspace with no configured assemblies contributes no exports of its own — it stays on
// the default registry (bare-host parity), independent of any other workspace's scan.
_retainedHandles = System.Array.Empty<System.Reflection.Assembly>();
}
_functions = FunctionExportRegistrar.BuildRegistry(_retainedHandles, Log);
}
}
/// <summary>Analyzes a document version; returns the immutable analysis. Cancellation is honored between
/// pipeline stages (D8).</summary>
public DocumentAnalysis Analyze(string path, string text, int version,
CancellationToken cancellationToken = default)
{
if (path == null) throw new ArgumentNullException(nameof(path));
if (text == null) throw new ArgumentNullException(nameof(text));
var gate = _locks.GetOrAdd(path, _ => new SemaphoreSlim(1, 1));
gate.Wait(cancellationToken);
try
{
FunctionRegistry functions;
lock (_writerGate)
functions = _functions;
var analysis = _analyzer.Analyze(path, text, version, functions, cancellationToken);
_analyses[path] = analysis;
return analysis;
}
finally
{
gate.Release();
}
}
/// <summary>The latest completed analysis for the path, or null.</summary>
public DocumentAnalysis GetAnalysis(string path)
{
return path != null && _analyses.TryGetValue(path, out var analysis) ? analysis : null;
}
/// <summary>Releases the document's analysis state.</summary>
public void Close(string path)
{
if (path == null)
return;
_analyses.TryRemove(path, out _);
_locks.TryRemove(path, out _);
}
/// <summary>Completion items for the UTF-16 offset (D12/D13 semantics).</summary>
public CompletionResult GetCompletions(string path, int offset,
CancellationToken cancellationToken = default)
{
var analysis = GetAnalysis(path);
if (analysis == null)
return CompletionResult.Empty;
FunctionRegistry functions;
lock (_writerGate)
functions = _functions;
// Completion runs against a repaired copy of the buffer so the enclosing body parses and records its
// narrowed model type (the offset is unchanged). This is the synchronous, request-forced analysis D8
// describes — it is not cached and never republished.
var (repairedText, repairedOffset) = CompletionText.Repair(analysis.Text, offset);
DocumentAnalysis completionAnalysis;
if (string.Equals(repairedText, analysis.Text, StringComparison.Ordinal))
completionAnalysis = analysis;
else
completionAnalysis = _analyzer.Analyze(path, repairedText, analysis.Version, functions,
cancellationToken);
return CompletionProvider.GetCompletions(completionAnalysis, repairedOffset, functions);
}
/// <summary>Hover content for the offset, or null (D15).</summary>
public HoverResult GetHover(string path, int offset)
{
var analysis = GetAnalysis(path);
if (analysis == null)
return null;
FunctionRegistry functions;
lock (_writerGate)
functions = _functions;
return HoverProvider.GetHover(analysis, offset, functions);
}
/// <summary>Definition target for the offset, or null (D16).</summary>
public DefinitionTarget GetDefinition(string path, int offset)
{
var analysis = GetAnalysis(path);
return analysis == null ? null : DefinitionProvider.GetDefinition(analysis, offset);
}
/// <summary>Runs the D14 reload protocol; invalidates existing analyses — open documents must be
/// re-analyzed by the caller.</summary>
public void ReloadModelAssemblies()
{
lock (_writerGate)
{
_analyses.Clear(); // drop every type-derived cache (D14 step 2)
_modelManager.Unload(); // unregister + Unload (steps 3–4)
if (_options.AssemblyPaths != null && _options.AssemblyPaths.Count > 0)
_modelManager.Load(_options.AssemblyPaths); // load the new generation (step 5)
// The extension registry is process-append-only and untouched; the function registry re-applies
// from the retained scan handles without rescanning (D24).
_functions = FunctionExportRegistrar.BuildRegistry(_retainedHandles, Log);
}
}
private void Log(string message)
{
LogSink?.Invoke(message);
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
lock (_writerGate)
{
_analyses.Clear();
_modelManager.Unload();
}
}
}
}