Part of #201
Adds the "Promote to input" section for named ranges and external refs. Defined names appear in the section with default-off Promote toggles. LAMBDA names are excluded (they're function calls, not inputs). External refs move from PR 1's auto-extracted behaviour to default-off promotable.
Scope
- New
IWorkbookContext interface: IReadOnlyDictionary<string, string> WorkbookNames mapping name → RefersTo. Lookup is OrdinalIgnoreCase. The live adapter (in RefactorCommand) populates this from workbook.Names AND the active sheet's worksheet.Names (unioned) once when the dialog opens.
- LAMBDA-name detection: a name with
RefersTo starting with =LAMBDA( (via LambdaSignatureParser.IsLambdaFormula) is excluded from the promotable list. The engine treats those identifiers as function calls and leaves them inline. Applies regardless of whether the formula text has a trailing ( after the identifier.
- Identifier tokenizer: new helper that walks formula text (skipping strings, mirroring
CellRefExtractor's SkipString rule) and yields (identifier, position) for each bare identifier that isn't a function call (not immediately followed by (). Lookbehind / trailing guards match CellRefExtractor's identifier-boundary rules to avoid splitting Help?Foo style names.
- For each unique candidate identifier present in
WorkbookNames and not a LAMBDA, emit a RefactorPromotableRow(kind: NamedRange, token: identifier, occurrences: N, promote: false).
- External refs: change the engine so refs where
FormulaRef.IsExternal == true emit as RefactorPromotableRow(kind: ExternalRef, token: original-text, occurrences: N, promote: false) instead of as inputs (PR 1 behaviour). Authors can still promote them.
- Dialog gains a "Promote to input" section below the inputs (and above the calc-binding section). Each promotable row shows token text, occurrences count, and a Promote checkbox. Promoting moves the row into the inputs section with an auto-name and editable name field; un-promoting moves it back.
- Rewrite step: when a named range is promoted, the rewrite pass adds the identifier to a separate rewrite map that swaps the bare name for the binding name. Fall back to a second-pass identifier rewriter rather than extending
CellRefExtractor.Rewrite (which is tightly coupled to A1-shaped patterns).
Files
New:
- Identifier tokenizer (either standalone class or internal helper in
RefactorEngine.cs)
Modified:
addin/lambda-boss/RefactorEngine.cs
addin/lambda-boss/RefactorTypes.cs (add IWorkbookContext, RefactorPromotableRow)
addin/lambda-boss/Commands/RefactorCommand.cs (live IWorkbookContext adapter)
addin/lambda-boss/UI/RefactorToLetWindow.xaml / .xaml.cs (promotable section)
addin/lambda-boss.Tests/RefactorEngineTests.cs
Tests
- workbook-scoped named range promoted (rewrites everywhere; binding RHS is the original identifier text)
- worksheet-scoped named range promoted
- LAMBDA name excluded from promotables (with and without trailing
()
- named range left un-promoted (stays inline, no binding)
- external ref defaults to promotable
- external ref promoted produces a binding
- identifier-boundary correctness (
Help?Foo not split)
Manual Excel test
Define a workbook name Tax_Rate = 0.2. Type =A1 * Tax_Rate + Tax_Rate. Run /Refactor. Verify Tax_Rate appears in the promotable section with occurrences = 2. Promote it. Save. Confirm the LET evaluates.
Reference
Plan: plans/0008-refactor-to-let.md § PR 3
Part of #201
Adds the "Promote to input" section for named ranges and external refs. Defined names appear in the section with default-off Promote toggles. LAMBDA names are excluded (they're function calls, not inputs). External refs move from PR 1's auto-extracted behaviour to default-off promotable.
Scope
IWorkbookContextinterface:IReadOnlyDictionary<string, string> WorkbookNamesmapping name →RefersTo. Lookup isOrdinalIgnoreCase. The live adapter (inRefactorCommand) populates this fromworkbook.NamesAND the active sheet'sworksheet.Names(unioned) once when the dialog opens.RefersTostarting with=LAMBDA((viaLambdaSignatureParser.IsLambdaFormula) is excluded from the promotable list. The engine treats those identifiers as function calls and leaves them inline. Applies regardless of whether the formula text has a trailing(after the identifier.CellRefExtractor'sSkipStringrule) and yields(identifier, position)for each bare identifier that isn't a function call (not immediately followed by(). Lookbehind / trailing guards matchCellRefExtractor's identifier-boundary rules to avoid splittingHelp?Foostyle names.WorkbookNamesand not a LAMBDA, emit aRefactorPromotableRow(kind: NamedRange, token: identifier, occurrences: N, promote: false).FormulaRef.IsExternal == trueemit asRefactorPromotableRow(kind: ExternalRef, token: original-text, occurrences: N, promote: false)instead of as inputs (PR 1 behaviour). Authors can still promote them.CellRefExtractor.Rewrite(which is tightly coupled to A1-shaped patterns).Files
New:
RefactorEngine.cs)Modified:
addin/lambda-boss/RefactorEngine.csaddin/lambda-boss/RefactorTypes.cs(addIWorkbookContext,RefactorPromotableRow)addin/lambda-boss/Commands/RefactorCommand.cs(liveIWorkbookContextadapter)addin/lambda-boss/UI/RefactorToLetWindow.xaml/.xaml.cs(promotable section)addin/lambda-boss.Tests/RefactorEngineTests.csTests
()Help?Foonot split)Manual Excel test
Define a workbook name
Tax_Rate = 0.2. Type=A1 * Tax_Rate + Tax_Rate. Run/Refactor. VerifyTax_Rateappears in the promotable section with occurrences = 2. Promote it. Save. Confirm the LET evaluates.Reference
Plan: plans/0008-refactor-to-let.md § PR 3