Skip to content

Commit b46c205

Browse files
committed
implement helper functions and add more doc comments to explain ActiveArgument enum
1 parent c4af2fe commit b46c205

File tree

2 files changed

+74
-50
lines changed

2 files changed

+74
-50
lines changed

crates/pyrefly_types/src/callable.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,15 @@ impl Param {
660660
}
661661
}
662662

663+
pub fn name(&self) -> Option<&Name> {
664+
match self {
665+
Param::PosOnly(name, ..) | Param::VarArg(name, ..) | Param::Kwargs(name, ..) => {
666+
name.as_ref()
667+
}
668+
Param::Pos(name, ..) | Param::KwOnly(name, ..) => Some(name),
669+
}
670+
}
671+
663672
pub fn as_type(&self) -> &Type {
664673
match self {
665674
Param::PosOnly(_, ty, _)

pyrefly/lib/state/lsp.rs

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -477,8 +477,11 @@ pub struct FindDefinitionItem {
477477
/// The currently active argument in a function call for signature help.
478478
#[derive(Debug)]
479479
enum ActiveArgument {
480+
/// The cursor is within an existing positional argument at the given index.
480481
Positional(usize),
482+
/// The cursor is within a keyword argument whose name is provided.
481483
Keyword(Name),
484+
/// The cursor is in the argument list but not inside any argument expression yet.
482485
Next(usize),
483486
}
484487

@@ -857,36 +860,11 @@ impl<'a> Transaction<'a> {
857860
if let Expr::Call(call) = x
858861
&& call.arguments.range.contains_inclusive(find)
859862
{
860-
// Check positional arguments
861-
for (i, arg) in call.arguments.args.as_ref().iter().enumerate() {
862-
if arg.range().contains_inclusive(find) {
863-
Self::visit_finding_signature_range(arg, find, res);
864-
if res.is_some() {
865-
return;
866-
}
867-
*res = Some((
868-
call.func.range(),
869-
call.arguments.range,
870-
ActiveArgument::Positional(i),
871-
));
872-
return;
873-
}
863+
if Self::visit_positional_signature_args(call, find, res) {
864+
return;
874865
}
875-
// Check keyword arguments
876-
let kwarg_start_idx = call.arguments.args.len();
877-
for (j, kw) in call.arguments.keywords.iter().enumerate() {
878-
if kw.range.contains_inclusive(find) {
879-
Self::visit_finding_signature_range(&kw.value, find, res);
880-
if res.is_some() {
881-
return;
882-
}
883-
let active_argument = match kw.arg.as_ref() {
884-
Some(identifier) => ActiveArgument::Keyword(identifier.id.clone()),
885-
None => ActiveArgument::Positional(kwarg_start_idx + j),
886-
};
887-
*res = Some((call.func.range(), call.arguments.range, active_argument));
888-
return;
889-
}
866+
if Self::visit_keyword_signature_args(call, find, res) {
867+
return;
890868
}
891869
if res.is_none() {
892870
*res = Some((
@@ -900,6 +878,51 @@ impl<'a> Transaction<'a> {
900878
}
901879
}
902880

881+
fn visit_positional_signature_args(
882+
call: &ExprCall,
883+
find: TextSize,
884+
res: &mut Option<(TextRange, TextRange, ActiveArgument)>,
885+
) -> bool {
886+
for (i, arg) in call.arguments.args.as_ref().iter().enumerate() {
887+
if arg.range().contains_inclusive(find) {
888+
Self::visit_finding_signature_range(arg, find, res);
889+
if res.is_some() {
890+
return true;
891+
}
892+
*res = Some((
893+
call.func.range(),
894+
call.arguments.range,
895+
ActiveArgument::Positional(i),
896+
));
897+
return true;
898+
}
899+
}
900+
false
901+
}
902+
903+
fn visit_keyword_signature_args(
904+
call: &ExprCall,
905+
find: TextSize,
906+
res: &mut Option<(TextRange, TextRange, ActiveArgument)>,
907+
) -> bool {
908+
let kwarg_start_idx = call.arguments.args.len();
909+
for (j, kw) in call.arguments.keywords.iter().enumerate() {
910+
if kw.range.contains_inclusive(find) {
911+
Self::visit_finding_signature_range(&kw.value, find, res);
912+
if res.is_some() {
913+
return true;
914+
}
915+
let active_argument = match kw.arg.as_ref() {
916+
Some(identifier) => ActiveArgument::Keyword(identifier.id.clone()),
917+
None => ActiveArgument::Positional(kwarg_start_idx + j),
918+
};
919+
*res = Some((call.func.range(), call.arguments.range, active_argument));
920+
return true;
921+
}
922+
}
923+
false
924+
}
925+
903926
/// Finds the callable(s) (multiple if overloads exist) at position in document, returning them, chosen overload index, and arg index
904927
fn get_callables_from_call(
905928
&self,
@@ -910,6 +933,9 @@ impl<'a> Transaction<'a> {
910933
let mut res = None;
911934
mod_module.visit(&mut |x| Self::visit_finding_signature_range(x, position, &mut res));
912935
let (callee_range, call_args_range, mut active_argument) = res?;
936+
// When the cursor is in the argument list but not inside any argument yet,
937+
// estimate the would-be positional index by counting commas up to the cursor.
938+
// This keeps signature help useful even before the user starts typing the next arg.
913939
if let ActiveArgument::Next(index) = &mut active_argument
914940
&& let Some(next_index) =
915941
self.count_argument_separators_before(handle, call_args_range, position)
@@ -989,20 +1015,9 @@ impl<'a> Transaction<'a> {
9891015
ActiveArgument::Positional(index) | ActiveArgument::Next(index) => {
9901016
(*index < params.len()).then_some(*index)
9911017
}
992-
ActiveArgument::Keyword(name) => params.iter().position(|param| {
993-
Self::parameter_name(param).is_some_and(|param_name| param_name == name)
994-
}),
995-
}
996-
}
997-
998-
fn parameter_name(param: &Param) -> Option<&Name> {
999-
match param {
1000-
Param::PosOnly(Some(name), ..)
1001-
| Param::Pos(name, ..)
1002-
| Param::VarArg(Some(name), ..)
1003-
| Param::KwOnly(name, ..)
1004-
| Param::Kwargs(Some(name), ..) => Some(name),
1005-
_ => None,
1018+
ActiveArgument::Keyword(name) => params
1019+
.iter()
1020+
.position(|param| param.name().is_some_and(|param_name| param_name == name)),
10061021
}
10071022
}
10081023

@@ -1014,14 +1029,14 @@ impl<'a> Transaction<'a> {
10141029
) -> Option<usize> {
10151030
let module = self.get_module_info(handle)?;
10161031
let contents = module.contents();
1017-
let start = arguments_range.start().to_usize();
1018-
let end = arguments_range.end().to_usize().min(contents.len());
1019-
if start >= end {
1020-
return Some(0);
1021-
}
1032+
let len = contents.len();
1033+
let start = arguments_range.start().to_usize().min(len);
1034+
let end = arguments_range.end().to_usize().min(len);
10221035
let pos = position.to_usize().clamp(start, end);
1023-
let slice = &contents[start..pos];
1024-
Some(slice.bytes().filter(|&b| b == b',').count())
1036+
contents
1037+
.get(start..pos)
1038+
.map(|slice| slice.bytes().filter(|&b| b == b',').count())
1039+
.or(Some(0))
10251040
}
10261041

10271042
fn normalize_singleton_function_type_into_params(type_: Type) -> Option<Vec<Param>> {

0 commit comments

Comments
 (0)