pi-revit 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-revit",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Native Pi connector for Autodesk Revit. Run npx.cmd -y pi-revit for the full Windows install.",
5
5
  "author": "Ahmad Altahlawi",
6
6
  "license": "MIT",
@@ -21,16 +21,19 @@ namespace RevitBridge.Tools
21
21
  {
22
22
  private const int DefaultMaxResults = 10;
23
23
  private const int MaxResultsCap = 50;
24
+
25
+ // The Max*Chars caps apply to the model-visible markdown only; the index and
26
+ // the details payload keep the full documentation text.
24
27
  private const int MaxMarkdownChars = 6_000;
25
- private const int MaxSummaryChars = 600;
26
28
  private const int MaxRemarksChars = 600;
27
29
  private const int MaxReturnsChars = 300;
28
30
  private const int MaxParamDocChars = 240;
29
31
  private const int MaxParamsPerMember = 10;
32
+ private const int MaxExceptionsPerMember = 8;
30
33
 
31
34
  public string Name => "search_api_docs";
32
35
  public string Label => "Search API Docs";
33
- public string Description => "Search the offline Revit API documentation (the RevitAPI.xml and RevitAPIUI.xml files shipped with Revit) for types, methods, constructors, properties, fields, and events; every public API enum is fully searchable by value name (values the XML leaves undocumented are synthesized from the API assemblies). query is a single name or substring — e.g. 'FilteredElementCollector', 'Wall.Create', 'WALL_BASE_OFFSET' — ranked: exact name first, then prefix, then substring; dotted Type.Member queries match composites. Returns signatures with summary, remarks, parameter docs, return docs, and the Revit version a member was introduced in ('since'). Works with no document open. Use it to verify exact classes, members, and signatures before writing execute_csharp code. The first query builds the index (a few seconds); later queries are instant.";
36
+ public string Description => "Search the offline Revit API documentation (the RevitAPI.xml and RevitAPIUI.xml files shipped with Revit) for types, methods, constructors, properties, fields, and events; every public API enum is fully searchable by value name (values the XML leaves undocumented are synthesized from the API assemblies). query is a single name or substring — e.g. 'FilteredElementCollector', 'Wall.Create', 'WALL_BASE_OFFSET' — ranked: exact name first, then prefix, then substring; dotted Type.Member queries match composites, and same-named overloads rank simplest-first. To target one overload, continue the query past a parenthesis with parameter types, e.g. 'Wall.Create(Document, Curve'. Returns signatures with summary, remarks, parameter docs, return docs, exception docs, and the Revit version a member was introduced in ('since'); the top match shows its full docs inline. Works with no document open. Use it to verify exact classes, members, and signatures before writing execute_csharp code. The first query builds the index (a few seconds); later queries are instant.";
34
37
 
35
38
  public bool RequiresDocument => false;
36
39
 
@@ -97,6 +100,9 @@ namespace RevitBridge.Tools
97
100
  .Select(pair => new Dictionary<string, object?> { ["name"] = pair.Key, ["description"] = pair.Value })
98
101
  .ToList(),
99
102
  ["returns"] = member.Returns,
103
+ ["exceptions"] = member.Exceptions?
104
+ .Select(pair => new Dictionary<string, object?> { ["type"] = pair.Key, ["description"] = pair.Value })
105
+ .ToList(),
100
106
  });
101
107
  }
102
108
 
@@ -134,6 +140,7 @@ namespace RevitBridge.Tools
134
140
  var top = scored
135
141
  .OrderByDescending(entry => entry.Score)
136
142
  .ThenBy(entry => entry.Member.Composite.Length)
143
+ .ThenBy(entry => entry.Member.ParameterCount)
137
144
  .ThenBy(entry => entry.Member.FullName, StringComparer.Ordinal)
138
145
  .Take(maxResults)
139
146
  .Select(entry => entry.Member)
@@ -143,8 +150,15 @@ namespace RevitBridge.Tools
143
150
 
144
151
  private static int Score(ApiMember member, string q, string[] words)
145
152
  {
153
+ // A query with a parenthesis targets a specific overload by signature,
154
+ // e.g. 'wall.create(document, curve' — matched against the shortened
155
+ // signature text before any name-based ranking.
156
+ bool signatureQuery = q.Contains('(');
157
+
146
158
  int score;
147
159
  if (member.CompositeLower == q) score = 1000;
160
+ else if (signatureQuery && member.SignatureLower == q) score = 980;
161
+ else if (signatureQuery && member.SignatureLower.StartsWith(q, StringComparison.Ordinal)) score = 950;
148
162
  else if (member.ShortNameLower == q) score = 900;
149
163
  else if (member.CompositeLower.StartsWith(q, StringComparison.Ordinal)) score = 700;
150
164
  else if (member.ShortNameLower.StartsWith(q, StringComparison.Ordinal)) score = 650;
@@ -169,10 +183,22 @@ namespace RevitBridge.Tools
169
183
  else
170
184
  {
171
185
  markdown.Append(total == top.Count ? $"{total} match(es)." : $"top {top.Count} of {total} matches.");
186
+
187
+ // Same-named members from different namespaces (XYZ exists in
188
+ // Autodesk.Revit.DB and in helper namespaces) render identical
189
+ // signatures; disambiguate those lines with their full container path.
190
+ var ambiguous = top
191
+ .GroupBy(member => member.Signature, StringComparer.Ordinal)
192
+ .Where(group => group.Count() > 1)
193
+ .Select(group => group.Key)
194
+ .ToHashSet(StringComparer.Ordinal);
195
+
172
196
  for (int i = 0; i < top.Count; i++)
173
197
  {
174
198
  var member = top[i];
175
199
  string origin = member.Since is null ? member.Assembly : $"{member.Assembly}, since {member.Since}";
200
+ if (ambiguous.Contains(member.Signature))
201
+ origin += $", in {ContainingPath(member)}";
176
202
  string line = $"\n{i + 1}. **{member.Signature}** — {KindLabel(member)} ({origin}) — {FirstSentence(member.Summary) ?? "(no summary)"}";
177
203
  if (markdown.Length + line.Length > MaxMarkdownChars)
178
204
  {
@@ -181,7 +207,7 @@ namespace RevitBridge.Tools
181
207
  }
182
208
  markdown.Append(line);
183
209
  if (i == 0)
184
- AppendTopMatchDocs(markdown, member);
210
+ AppendTopMatchDocs(markdown, member, top.Count(other => other.CompositeLower == member.CompositeLower));
185
211
  }
186
212
  }
187
213
  foreach (string warning in index.Warnings)
@@ -190,18 +216,30 @@ namespace RevitBridge.Tools
190
216
  }
191
217
 
192
218
  /// <summary>The model sees only this markdown — details.payload never reaches it —
193
- /// so the top match carries its remarks, parameter, and return docs inline. Lines
194
- /// that would blow the markdown budget are dropped individually; narrowing the
195
- /// query promotes any other match to the top slot with its docs.</summary>
196
- private static void AppendTopMatchDocs(StringBuilder markdown, ApiMember member)
219
+ /// so the top match carries its remarks, parameter, return, and exception docs
220
+ /// inline (capped for display; the payload keeps full text). Lines that would blow
221
+ /// the markdown budget are dropped individually. When the top match is one of
222
+ /// several same-named overloads, a note says how to target another one.</summary>
223
+ private static void AppendTopMatchDocs(StringBuilder markdown, ApiMember member, int overloadCount)
197
224
  {
198
- var lines = new List<string>(3);
225
+ var lines = new List<string>(5);
199
226
  if (member.Remarks is { } remarks)
200
- lines.Add($"\n remarks: {remarks}");
227
+ lines.Add($"\n remarks: {Cap(remarks, MaxRemarksChars)}");
201
228
  if (member.Parameters is { Count: > 0 } parameters)
202
- lines.Add($"\n params: {string.Join("; ", parameters.Select(pair => $"{pair.Key} — {pair.Value}"))}");
229
+ lines.Add($"\n params: {string.Join("; ", parameters.Select(pair => $"{pair.Key} — {Cap(pair.Value, MaxParamDocChars)}"))}");
203
230
  if (member.Returns is { } returns)
204
- lines.Add($"\n returns: {returns}");
231
+ lines.Add($"\n returns: {Cap(returns, MaxReturnsChars)}");
232
+ if (member.Exceptions is { Count: > 0 } exceptions)
233
+ lines.Add($"\n throws: {string.Join("; ", exceptions.Select(pair => $"{pair.Key} — {Cap(pair.Value, MaxParamDocChars)}"))}");
234
+ if (overloadCount > 1)
235
+ {
236
+ // The targeting example must be the query form the signature matcher
237
+ // actually accepts, so it is cut from the displayed signature itself —
238
+ // 'new XYZ(' for constructors, 'Wall.Create(' for methods.
239
+ int paren = member.Signature.IndexOf('(');
240
+ string example = paren >= 0 ? member.Signature[..(paren + 1)] : member.Composite + "(";
241
+ lines.Add($"\n note: {member.Composite} has {overloadCount} overload(s) listed; full docs shown for the simplest top-ranked one. To target another, continue the query past a parenthesis with its parameter types, e.g. '{example}'.");
242
+ }
205
243
  foreach (string line in lines)
206
244
  {
207
245
  if (markdown.Length + line.Length > MaxMarkdownChars)
@@ -210,6 +248,16 @@ namespace RevitBridge.Tools
210
248
  }
211
249
  }
212
250
 
251
+ /// <summary>Namespace-qualified container for disambiguating display lines:
252
+ /// the type itself for types, the declaring type's full path for members.</summary>
253
+ private static string ContainingPath(ApiMember member)
254
+ {
255
+ if (member.Kind == 'T')
256
+ return member.FullName;
257
+ int dot = member.FullName.LastIndexOf('.');
258
+ return dot > 0 ? member.FullName[..dot] : member.FullName;
259
+ }
260
+
213
261
  private static string KindLabel(ApiMember member) => member.IsConstructor ? "constructor" : member.Kind switch
214
262
  {
215
263
  'T' => "type",
@@ -262,11 +310,18 @@ namespace RevitBridge.Tools
262
310
  public required string FullNameLower { get; init; }
263
311
  public required string CompositeLower { get; init; }
264
312
  public required string ShortNameLower { get; init; }
313
+ public required string SignatureLower { get; init; }
314
+
315
+ /// <summary>Top-level parameter count from the doc id; overload tie-breaks
316
+ /// rank the simplest overload first.</summary>
317
+ public required int ParameterCount { get; init; }
318
+
265
319
  public string? Summary { get; init; }
266
320
  public string? Remarks { get; init; }
267
321
  public string? Returns { get; init; }
268
322
  public string? Since { get; init; }
269
323
  public IReadOnlyList<KeyValuePair<string, string>>? Parameters { get; init; }
324
+ public IReadOnlyList<KeyValuePair<string, string>>? Exceptions { get; init; }
270
325
  }
271
326
 
272
327
  private sealed class DocIndex
@@ -364,6 +419,8 @@ namespace RevitBridge.Tools
364
419
  FullNameLower = fullName.ToLowerInvariant(),
365
420
  CompositeLower = composite.ToLowerInvariant(),
366
421
  ShortNameLower = name.ToLowerInvariant(),
422
+ SignatureLower = composite.ToLowerInvariant(),
423
+ ParameterCount = 0,
367
424
  Summary = enumType == typeof(BuiltInCategory)
368
425
  ? "BuiltInCategory enum value — usable as the 'category' argument of get_elements/get_element_types and with FilteredElementCollector.OfCategory in execute_csharp."
369
426
  : $"{typeName} enum value (not documented in the XML; synthesized from {assemblyName} metadata).",
@@ -481,12 +538,24 @@ namespace RevitBridge.Tools
481
538
  if (parameters is { Count: >= MaxParamsPerMember })
482
539
  break;
483
540
  string? name = param.Attribute("name")?.Value;
484
- string? text = Cap(CleanDocText(param), MaxParamDocChars);
541
+ string? text = CleanDocText(param);
485
542
  if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(text))
486
543
  continue;
487
544
  (parameters ??= new List<KeyValuePair<string, string>>()).Add(new KeyValuePair<string, string>(name, text));
488
545
  }
489
546
 
547
+ List<KeyValuePair<string, string>>? exceptions = null;
548
+ foreach (var exception in element.Elements("exception"))
549
+ {
550
+ if (exceptions is { Count: >= MaxExceptionsPerMember })
551
+ break;
552
+ string? cref = exception.Attribute("cref")?.Value;
553
+ string? text = CleanDocText(exception);
554
+ if (string.IsNullOrEmpty(cref) || string.IsNullOrEmpty(text))
555
+ continue;
556
+ (exceptions ??= new List<KeyValuePair<string, string>>()).Add(new KeyValuePair<string, string>(ShortCref(cref), text));
557
+ }
558
+
490
559
  return new ApiMember
491
560
  {
492
561
  Kind = kind,
@@ -498,14 +567,34 @@ namespace RevitBridge.Tools
498
567
  FullNameLower = path.ToLowerInvariant(),
499
568
  CompositeLower = composite.ToLowerInvariant(),
500
569
  ShortNameLower = shortName.ToLowerInvariant(),
501
- Summary = Cap(CleanDocText(element.Element("summary")), MaxSummaryChars),
502
- Remarks = Cap(CleanDocText(element.Element("remarks")), MaxRemarksChars),
503
- Returns = Cap(CleanDocText(element.Element("returns")), MaxReturnsChars),
570
+ SignatureLower = signature.ToLowerInvariant(),
571
+ ParameterCount = CountTopLevelParameters(paramText),
572
+ Summary = CleanDocText(element.Element("summary")),
573
+ Remarks = CleanDocText(element.Element("remarks")),
574
+ Returns = CleanDocText(element.Element("returns")),
504
575
  Since = CleanDocText(element.Element("since")),
505
576
  Parameters = parameters,
577
+ Exceptions = exceptions,
506
578
  };
507
579
  }
508
580
 
581
+ /// <summary>Parameter count of a doc-id parameter list; commas inside generic
582
+ /// braces (Dictionary{K,V}) do not separate parameters.</summary>
583
+ private static int CountTopLevelParameters(string? paramText)
584
+ {
585
+ if (string.IsNullOrEmpty(paramText))
586
+ return 0;
587
+ int count = 1;
588
+ int depth = 0;
589
+ foreach (char c in paramText)
590
+ {
591
+ if (c == '{') depth++;
592
+ else if (c == '}') depth--;
593
+ else if (c == ',' && depth == 0) count++;
594
+ }
595
+ return count;
596
+ }
597
+
509
598
  // ------------------------------------------------------------- text utils
510
599
 
511
600
  /// <summary>Flattens doc XML to plain text: see/seealso cref -> short type name,