Skip to content

Commit 66f3310

Browse files
committed
Complete known required arguments
1 parent 2be2b05 commit 66f3310

10 files changed

Lines changed: 1213 additions & 45 deletions

example.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,3 +2221,58 @@ function getTimeout(): int
22212221
if (MAX_RETRIES > 0) { // → jumps to define('MAX_RETRIES', ...)
22222222
echo APP_VERSION; // → jumps to define('APP_VERSION', ...)
22232223
}
2224+
2225+
// ─── Callable Snippet Insertion ─────────────────────────────────────────────
2226+
//
2227+
// When completing methods, functions, or `new ClassName`, the LSP inserts
2228+
// a snippet with parentheses and tab-stops for each **required** parameter.
2229+
// Optional and variadic parameters are omitted — use signature help to
2230+
// fill those in.
2231+
//
2232+
// Method examples:
2233+
// $user->setName(|) — setName(string $name) → snippet: setName(${1:\$name})
2234+
// $user->toArray() — toArray() has no params → snippet: toArray()
2235+
// $user->addRoles(|) — addRoles(string ...$roles) variadic → snippet: addRoles()
2236+
//
2237+
// Function examples:
2238+
// createUser(|) — createUser(string $name, …) → snippet: createUser(${1:\$name})
2239+
// Built-in stubs like json_decode get empty parens: json_decode()
2240+
//
2241+
// `new ClassName` examples (same-namespace classes get full constructor params):
2242+
// new User(|,|) — __construct(string $name, string $email) → User(${1:\$name}, ${2:\$email})
2243+
// new Response(|) — __construct(int $statusCode, …) → Response(${1:\$statusCode})
2244+
// Classes from classmap/stubs just get empty parens: new DateTime()
2245+
2246+
class CallableSnippetDemo
2247+
{
2248+
public function methodSnippets(User $user, Response $response): void
2249+
{
2250+
// Try completing after `->` — each inserts a snippet with required params:
2251+
$user->setName($name); // Inserted: setName(${1:$name}) — 1 required param
2252+
$user->toArray(); // Inserted: toArray() — no params
2253+
$user->addRoles(); // Inserted: addRoles() — variadic only
2254+
$response->getBody(); // Inserted: getBody() — no params
2255+
}
2256+
2257+
public function staticSnippets(): void
2258+
{
2259+
// Static method calls also get snippets:
2260+
User::findByEmail($email); // Inserted: findByEmail(${1:$email}) — 1 required param
2261+
Model::find($id); // Inserted: find(${1:$id}) — 1 required param
2262+
}
2263+
2264+
public function newSnippets(): void
2265+
{
2266+
// `new` inserts class name + constructor params as snippet:
2267+
$u = new User($name, $email) // Inserted: User(${1:$name}, ${2:$email}) — 2 required
2268+
$r = new Response($statusCode); // Inserted: Response(${1:$statusCode}) — 1 required
2269+
$a = new AdminUser($name, $email); // Inserted: AdminUser(${1:$name}, ${2:$email}, ${3:$role})
2270+
}
2271+
2272+
public function parentConstructorSnippet(): void
2273+
{
2274+
// parent::__construct also gets snippets when inside a subclass.
2275+
// See AdminUser::__construct for an example:
2276+
// parent::__construct(${1:$name}, ${2:$email})
2277+
}
2278+
}

src/completion/builder.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,43 @@ use crate::Backend;
1212
use crate::types::Visibility;
1313
use crate::types::*;
1414

15+
/// Build an LSP snippet string for a callable (function, method, or constructor).
16+
///
17+
/// Required parameters are included as numbered tab stops with their
18+
/// PHP variable name as placeholder text. Optional and variadic
19+
/// parameters are omitted — they can be filled in via signature help.
20+
///
21+
/// The returned string uses LSP snippet syntax and **must** be paired
22+
/// with `InsertTextFormat::SNIPPET` on the `CompletionItem`.
23+
///
24+
/// # Examples
25+
///
26+
/// | call | result |
27+
/// |--------------------------------------------|-------------------------------------|
28+
/// | `("reset", &[])` | `"reset()$0"` |
29+
/// | `("makeText", &[req($text), opt($long)])` | `"makeText(${1:\\$text})$0"` |
30+
/// | `("add", &[req($a), req($b)])` | `"add(${1:\\$a}, ${2:\\$b})$0"` |
31+
pub(crate) fn build_callable_snippet(name: &str, params: &[ParameterInfo]) -> String {
32+
let required: Vec<&ParameterInfo> = params.iter().filter(|p| p.is_required).collect();
33+
34+
if required.is_empty() {
35+
format!("{name}()$0")
36+
} else {
37+
let placeholders: Vec<String> = required
38+
.iter()
39+
.enumerate()
40+
.map(|(i, p)| {
41+
// Escape `$` in parameter names so it is treated as a
42+
// literal character rather than a snippet tab-stop /
43+
// variable reference.
44+
let escaped_name = p.name.replace('$', "\\$");
45+
format!("${{{}:{}}}", i + 1, escaped_name)
46+
})
47+
.collect();
48+
format!("{name}({})$0", placeholders.join(", "))
49+
}
50+
}
51+
1552
// Re-export use-statement helpers so existing `use crate::completion::builder::{…}`
1653
// imports continue to work.
1754
pub(crate) use super::use_edit::{build_use_edit, find_use_insert_position};
@@ -169,7 +206,8 @@ impl Backend {
169206
label,
170207
kind: Some(CompletionItemKind::METHOD),
171208
detail: Some(format!("Class: {}", target_class.name)),
172-
insert_text: Some(method.name.clone()),
209+
insert_text: Some(build_callable_snippet(&method.name, &method.parameters)),
210+
insert_text_format: Some(InsertTextFormat::SNIPPET),
173211
filter_text: Some(method.name.clone()),
174212
deprecated: if method.is_deprecated {
175213
Some(true)

0 commit comments

Comments
 (0)