Skip to content

Commit 1f611bc

Browse files
committed
Rename the "Renumber variables" refactoring to "Increment numbered variables" and add a corresponding "Decrement numbered variables" refactoring
1 parent 9209e75 commit 1f611bc

File tree

5 files changed

+153
-27
lines changed

5 files changed

+153
-27
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [0.76.0]
44

5+
* Rename the "Renumber variables" refactoring to "Increment numbered variables" and add a corresponding "Decrement numbered variables" refactoring
56
* Update the "Wrap file contents as an object" refactoring to insert the object opening directive after any comments at the beginning of the file
67
* Update the "Open parent file" command to show a message if no parent file is found
78

@@ -10,7 +11,8 @@
1011
* Add support for renaming variables within the scope of a predicate clause, grammar rule, or directive
1112
* Add support for renaming parameter variables within the scope of an entity
1213
* Add "Extract predicate/non-terminal" refactoring support for selected code in predicate clauses and grammar rules
13-
* Add "Renumber variables" refactoring support for variables ending with numbers within the scope of a predicate clause or grammar rule
14+
* Add "Increment numbered variables" refactoring support for variables ending with numbers within the scope of a predicate clause or grammar rule
15+
* Add "Decrement numbered variables" refactoring support for variables ending with numbers within the scope of a predicate clause or grammar rule
1416
* Add "Unify with new variable" refactoring support for selected terms in predicate rules and grammar rules
1517
* Add "Inline variable" refactoring support for replacing variable unification goals in predicate rules and grammar rules
1618
* Add "Wrap file contents as an object" refactoring support for converting plain Prolog files to Logtalk objects

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -329,18 +329,18 @@ Several refactoring operations are supported. Users should commit their work bef
329329

330330
When a refactoring results in changes to multiple files, use the "Save All" command to save all modified files so that a single "Make - Reload" command can be used to reload the modified code. Note that this command can be called automatically when saving files using the `logtalk.make.onSave` setting.
331331

332-
#### Renumber variables
332+
#### Increment and decrement numbered variables
333333

334-
A "Renumber variables" refactoring operation is available when the user right-clicks on a variable ending with a number in a predicate rule or in a grammar rule and uses the "Refactor" context menu item or the "Refactor" command palette item. The refactoring renumbers all variables with the same prefix and a number equal or greater than the selected variable number in the rule to consecutive numbers. The selected variable can be anywhere in the rule (e.g., in the head, in the body, or in a comment).
334+
The "Increment numbered variables" and "Decrement numbered variables" refactoring operations are available when the user right-clicks on a variable ending with a number in a predicate rule or in a grammar rule and uses the "Refactor" context menu items or the "Refactor" command palette items. These refactorings increment or decrement all variables with the same prefix and a number equal or greater than the selected variable number in the rule. The selected variable can be anywhere in the rule (e.g., in the head, in the body, or in a comment).
335335

336-
#### Variable inlining
336+
#### Variable inlining and variable introduction
337337

338338
An "Inline variable" refactoring operation is available when the user selects a unification goal in a rule body and uses the "Refactor" context menu item or the "Refactor" command palette item. The refactoring replaces all occurrences of the variable in the rule with its unified term. The unification goal should be the only goal in the line.
339339

340-
#### Code extraction
341-
342340
A "Unify with new variable" refactoring operation is available when the user selects a complete term in a predicate rule or in a grammar rule (e.g., a head argument) and uses the "Refactor" context menu item or the "Refactor" command palette item. The refactoring asks the user for the name of the new variable, replaces the selected term with the new variable, and adds a unification goal to the rule body (after the rule head when the term is a head argument, before the line of the selected term otherwise).
343341

342+
#### Code extraction
343+
344344
A "Replace magic number with predicate call" refactoring operation is available when the user selects a number in a rule body and uses the "Refactor" context menu item or the "Refactor" command palette item. The user is asked to enter the name of the predicate to be created and its scope. The predicate is created with the number as its single argument and added to the entity. The selected number is replaced with a variable derived from the predicate name and the rule body is updated with a call to the new predicate inserted after the clause head (note that when compiling the code in optimal mode, the call to the new predicate is inlined).
345345

346346
An "Extract predicate/non-terminal" refactoring operation is available when the user selects one ot more lines of code with complete goals in a predicate rule or in a grammar rule and uses the "Refactor" context menu item or the "Refactor" command palette item. The refactoring asks the user for the name of the new predicate (or non-terminal) and adds it after all clauses of the current predicate (or non-terminal). The selected code is replaced with a call to the new predicate (or non-terminal).
@@ -352,12 +352,13 @@ Four other code extraction refactoring operations are supported when the user se
352352
- "Extract to Logtalk entity" (the user is asked to select the target entity)
353353
- "Extract to new Logtalk entity" (the user is asked to select the entity type, entity name, file name, and file location)
354354
- "Extract to new Logtalk file" (the user is asked to select the file name and file location)
355-
- "Replace with include/1 directive" (the user is asked to select the file name and file location)
356355

357-
#### Resolve include/1 directive
356+
#### Resolving and introducing include/1 directives
358357

359358
When the user selects a region of code that contains an `include/1` directive, the "Refactor" context menu item or the "Refactor" command palette item provides a "Replace include/1 directive with file contents" action. The included file is resolved if it's a relative or absolute path, with or without a common Logtalk or Prolog extension. The included file contents are indented to match the indentation of the `include/1` directive.
360359

360+
The "Replace with include/1 directive" refactoring operation is available when the user selects a region of code and uses the "Refactor" context menu item or the "Refactor" command palette item. The user is asked to select the new file name and file location. The selected code is extracted to the new file and the selection is replaced with an `include/1` directive.
361+
361362
#### Symbol renaming
362363

363364
Entity, predicate, non-terminal, and variable rename support is available:

src/extension.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,9 +584,17 @@ export async function activate(context: ExtensionContext) {
584584
);
585585

586586
context.subscriptions.push(
587-
commands.registerCommand('logtalk.refactor.renumberVariables', async (document, selection) => {
587+
commands.registerCommand('logtalk.refactor.incrementNumberedVariables', async (document, selection) => {
588588
if (refactorProvider) {
589-
await refactorProvider.renumberVariables(document, selection);
589+
await refactorProvider.incrementNumberedVariables(document, selection);
590+
}
591+
})
592+
);
593+
594+
context.subscriptions.push(
595+
commands.registerCommand('logtalk.refactor.decrementNumberedVariables', async (document, selection) => {
596+
if (refactorProvider) {
597+
await refactorProvider.decrementNumberedVariables(document, selection);
590598
}
591599
})
592600
);

src/features/refactorProvider.ts

Lines changed: 130 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -505,19 +505,30 @@ export class LogtalkRefactorProvider implements CodeActionProvider {
505505
}
506506
}
507507

508-
// Renumber variables - works with cursor position (empty or non-empty selection)
508+
// Increment/Decrement numbered variables - works with cursor position (empty or non-empty selection)
509509
const variableAtCursor = this.getNumberedVariableAtPosition(document, selection.active);
510510
if (variableAtCursor && !this.isSelectionInDirective(document, selection)) {
511-
const renumberVariablesAction = new CodeAction(
512-
"Renumber variables",
511+
const incrementVariablesAction = new CodeAction(
512+
"Increment numbered variables",
513513
CodeActionKind.RefactorRewrite
514514
);
515-
renumberVariablesAction.command = {
516-
command: "logtalk.refactor.renumberVariables",
517-
title: "Renumber variables",
515+
incrementVariablesAction.command = {
516+
command: "logtalk.refactor.incrementNumberedVariables",
517+
title: "Increment numbered variables",
518518
arguments: [document, selection]
519519
};
520-
actions.push(renumberVariablesAction);
520+
actions.push(incrementVariablesAction);
521+
522+
const decrementVariablesAction = new CodeAction(
523+
"Decrement numbered variables",
524+
CodeActionKind.RefactorRewrite
525+
);
526+
decrementVariablesAction.command = {
527+
command: "logtalk.refactor.decrementNumberedVariables",
528+
title: "Decrement numbered variables",
529+
arguments: [document, selection]
530+
};
531+
actions.push(decrementVariablesAction);
521532
}
522533

523534
// Sort files by dependencies - for logtalk_load/1-2 predicates with list of atoms
@@ -2984,10 +2995,10 @@ export class LogtalkRefactorProvider implements CodeActionProvider {
29842995
}
29852996

29862997
/**
2987-
* Renumber variables in a rule
2988-
* Finds all variables with the same prefix ending with numbers and renumbers them
2998+
* Increment numbered variables in a rule
2999+
* Finds all variables with the same prefix ending with numbers and increments them
29893000
*/
2990-
public async renumberVariables(document: TextDocument, selection: Selection): Promise<void> {
3001+
public async incrementNumberedVariables(document: TextDocument, selection: Selection): Promise<void> {
29913002
try {
29923003
// Try to get variable at cursor position first
29933004
let variableInfo = this.getNumberedVariableAtPosition(document, selection.active);
@@ -3042,7 +3053,7 @@ export class LogtalkRefactorProvider implements CodeActionProvider {
30423053
// Sort numbers in descending order to avoid conflicts when renaming
30433054
const sortedNumbers = Array.from(numberedVariables.keys()).sort((a, b) => b - a);
30443055

3045-
// Renumber variables starting from the selected one
3056+
// Increment variables starting from the selected one
30463057
for (const num of sortedNumbers) {
30473058
if (num >= selectedNumber) {
30483059
const oldVarName = numberedVariables.get(num)!;
@@ -3070,13 +3081,117 @@ export class LogtalkRefactorProvider implements CodeActionProvider {
30703081
const success = await workspace.applyEdit(edit);
30713082
if (success) {
30723083
const count = sortedNumbers.filter(n => n >= selectedNumber).length;
3073-
window.showInformationMessage(`Renumbered ${count} variable${count !== 1 ? 's' : ''}.`);
3084+
window.showInformationMessage(`Incremented ${count} variable${count !== 1 ? 's' : ''}.`);
3085+
} else {
3086+
window.showErrorMessage("Failed to increment variables.");
3087+
}
3088+
} catch (error) {
3089+
this.logger.error(`Error in incrementNumberedVariables: ${error}`);
3090+
window.showErrorMessage(`Error incrementing variables: ${error.message}`);
3091+
}
3092+
}
3093+
3094+
/**
3095+
* Decrement numbered variables in a rule
3096+
* Finds all variables with the same prefix ending with numbers and decrements them
3097+
*/
3098+
public async decrementNumberedVariables(document: TextDocument, selection: Selection): Promise<void> {
3099+
try {
3100+
// Try to get variable at cursor position first
3101+
let variableInfo = this.getNumberedVariableAtPosition(document, selection.active);
3102+
3103+
// If no variable at cursor, try parsing the selection
3104+
if (!variableInfo && !selection.isEmpty) {
3105+
const selectedText = document.getText(selection).trim();
3106+
const parsed = this.parseNumberedVariable(selectedText);
3107+
if (parsed) {
3108+
variableInfo = {
3109+
...parsed,
3110+
range: selection
3111+
};
3112+
}
3113+
}
3114+
3115+
if (!variableInfo) {
3116+
window.showErrorMessage("Cursor is not on a variable ending with a number.");
3117+
return;
3118+
}
3119+
3120+
const { prefix, number: selectedNumber } = variableInfo;
3121+
3122+
// Find the complete rule range
3123+
const ruleRange = this.findCompleteRuleRange(document, selection.start.line);
3124+
if (!ruleRange) {
3125+
window.showErrorMessage("Could not find the complete rule.");
3126+
return;
3127+
}
3128+
3129+
// Get the rule text
3130+
const ruleText = document.getText(ruleRange);
3131+
3132+
// Find all variables with the same prefix ending with numbers
3133+
const variableRegex = new RegExp(`\\b${prefix}(\\d+)\\b`, 'g');
3134+
3135+
// Collect all unique numbered variables with this prefix
3136+
const numberedVariables = new Map<number, string>(); // number -> variable name
3137+
let match: RegExpExecArray | null;
3138+
while ((match = variableRegex.exec(ruleText)) !== null) {
3139+
const num = parseInt(match[1], 10);
3140+
const varName = `${prefix}${num}`;
3141+
numberedVariables.set(num, varName);
3142+
}
3143+
3144+
// Check if decrementing would result in a number less than 0
3145+
const minNumber = Math.min(...Array.from(numberedVariables.keys()).filter(n => n >= selectedNumber));
3146+
if (minNumber - 1 < 0) {
3147+
window.showErrorMessage("Cannot decrement: would result in negative variable number.");
3148+
return;
3149+
}
3150+
3151+
// Calculate the decrement amount (how much to subtract from each number)
3152+
const decrement = 1;
3153+
3154+
// Create workspace edit
3155+
const edit = new WorkspaceEdit();
3156+
3157+
// Sort numbers in ascending order to avoid conflicts when renaming
3158+
const sortedNumbers = Array.from(numberedVariables.keys()).sort((a, b) => a - b);
3159+
3160+
// Decrement variables starting from the selected one
3161+
for (const num of sortedNumbers) {
3162+
if (num >= selectedNumber) {
3163+
const oldVarName = numberedVariables.get(num)!;
3164+
const newNum = num - decrement;
3165+
const newVarName = `${prefix}${newNum}`;
3166+
3167+
// Find and replace all occurrences of this variable in the rule
3168+
for (let lineNum = ruleRange.start.line; lineNum <= ruleRange.end.line; lineNum++) {
3169+
const lineText = document.lineAt(lineNum).text;
3170+
const lineMatches = [...lineText.matchAll(new RegExp(`\\b${oldVarName}\\b`, 'g'))];
3171+
3172+
for (const lineMatch of lineMatches) {
3173+
const startChar = lineMatch.index!;
3174+
const endChar = startChar + oldVarName.length;
3175+
const replaceRange = new Range(
3176+
new Position(lineNum, startChar),
3177+
new Position(lineNum, endChar)
3178+
);
3179+
edit.replace(document.uri, replaceRange, newVarName);
3180+
}
3181+
}
3182+
}
3183+
}
3184+
3185+
const success = await workspace.applyEdit(edit);
3186+
if (success) {
3187+
const count = sortedNumbers.filter(n => n >= selectedNumber).length;
3188+
window.showInformationMessage(`Decremented ${count} variable${count !== 1 ? 's' : ''}.`);
30743189
} else {
3075-
window.showErrorMessage("Failed to renumber variables.");
3190+
window.showErrorMessage("Failed to decrement variables.");
30763191
}
30773192
} catch (error) {
3078-
this.logger.error(`Error in renumberVariables: ${error}`);
3079-
window.showErrorMessage(`Error renumbering variables: ${error.message}`);
3193+
this.logger.error(`Error in decrementNumberedVariables: ${error}`);
3194+
window.showErrorMessage(`Error decrementing variables: ${error.message}`);
30803195
}
30813196
}
30823197

tests/test_renumber_variables.lgt renamed to tests/test_increment_decrement_numbered_variables.lgt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
:- object(test_renumber_variables).
1+
:- object(test_increment_decrement_numbered_variables).
22

33
:- info([
44
version is 1:0:0,
55
author is 'Paulo Moura',
66
date is 2025-11-15,
7-
comment is 'Test cases for the "Renumber variables" refactoring.'
7+
comment is 'Test cases for the "Increment numbered variables" and "Decrement numbered variables" refactorings.'
88
]).
99

1010
:- public(example1/2).

0 commit comments

Comments
 (0)