Skip to content

Commit a82df05

Browse files
committed
Fix bug where variable renaming would fail to update all occurrences of the variable in the presence of character code notation (0'Char)
1 parent 12bcdfe commit a82df05

5 files changed

Lines changed: 116 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [0.85.4]
4+
5+
* Fix bug where variable renaming would fail to update all occurrences of the variable in the presence of character code notation (0'Char)
6+
37
## [0.85.3]
48

59
* Fix bug where predicate/non-terminal argument refactoring would fail to update all clauses/rules

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "logtalk-for-vscode",
33
"displayName": "Logtalk for VSCode",
44
"description": "Logtalk programming support",
5-
"version": "0.85.3",
5+
"version": "0.85.4",
66
"publisher": "LogtalkDotOrg",
77
"icon": "images/logtalk.png",
88
"license": "MIT",
@@ -1338,7 +1338,7 @@
13381338
"compile": "tsc -watch -p ./",
13391339
"test": "tsc ./tests/runTest.ts",
13401340
"vsix:make": "vsce package --baseImagesUrl https://raw.githubusercontent.com/llvm/llvm-project/master/clang-tools-extra/clangd/clients/clangd-vscode/",
1341-
"vsix:install": "code --install-extension logtalk-for-vscode-0.85.3.vsix"
1341+
"vsix:install": "code --install-extension logtalk-for-vscode-0.85.4.vsix"
13421342
},
13431343
"devDependencies": {
13441344
"@types/bluebird": "^3.5.38",

src/features/renameProvider.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ export class LogtalkRenameProvider implements RenameProvider {
7171
}
7272

7373
// Check if we're in a string literal
74+
// Use the helper method that properly handles character code notation (0'Char)
7475
const beforeCursor = lineText.substring(0, position.character);
75-
const singleQuotes = (beforeCursor.match(/'/g) || []).length;
76+
const singleQuotes = this.countSingleQuotesExcludingCharCodes(beforeCursor);
7677
const doubleQuotes = (beforeCursor.match(/"/g) || []).length;
7778
if (singleQuotes % 2 !== 0 || doubleQuotes % 2 !== 0) {
7879
return null;
@@ -932,6 +933,35 @@ export class LogtalkRenameProvider implements RenameProvider {
932933
return workspaceEdit;
933934
}
934935

936+
/**
937+
* Counts single quotes in a string, excluding those that are part of character code notation (0'Char)
938+
* @param text The text to count quotes in
939+
* @returns The count of single quotes that are not part of character code notation
940+
*/
941+
private countSingleQuotesExcludingCharCodes(text: string): number {
942+
let count = 0;
943+
for (let i = 0; i < text.length; i++) {
944+
if (text[i] === "'") {
945+
// Check if this quote is part of character code notation (preceded by '0')
946+
if (i > 0 && text[i - 1] === '0') {
947+
// This is character code notation like 0'x, skip this quote
948+
// Also skip the next character (the char being represented)
949+
if (i + 1 < text.length) {
950+
// Handle escape sequences like 0'\n, 0'\\, etc.
951+
if (text[i + 1] === '\\' && i + 2 < text.length) {
952+
i += 2; // Skip the backslash and the escaped character
953+
} else {
954+
i += 1; // Skip the single character
955+
}
956+
}
957+
} else {
958+
count++;
959+
}
960+
}
961+
}
962+
return count;
963+
}
964+
935965
/**
936966
* Validates if a variable occurrence is in a valid context (not in string)
937967
* Note: We DO want to rename variables in comments to keep them accurate
@@ -943,7 +973,7 @@ export class LogtalkRenameProvider implements RenameProvider {
943973
private isValidVariableContextInLine(lineText: string, startPos: number, endPos: number): boolean {
944974
// Check if this is in a string literal
945975
const beforeMatch = lineText.substring(0, startPos);
946-
const singleQuotes = (beforeMatch.match(/'/g) || []).length;
976+
const singleQuotes = this.countSingleQuotesExcludingCharCodes(beforeMatch);
947977
const doubleQuotes = (beforeMatch.match(/"/g) || []).length;
948978
if (singleQuotes % 2 !== 0 || doubleQuotes % 2 !== 0) {
949979
return false;

tests/renameProvider.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,60 @@ suite('LogtalkRenameProvider Test Suite', () => {
307307
// Should NOT find 'member(+term, ?list)' because it's looking for indicator format
308308
assert.strictEqual(ranges.length, 0);
309309
});
310+
311+
// Tests for character code notation handling (0'Char)
312+
test('countSingleQuotesExcludingCharCodes - no quotes', () => {
313+
const count = (renameProvider as any).countSingleQuotesExcludingCharCodes('hello world');
314+
assert.strictEqual(count, 0);
315+
});
316+
317+
test('countSingleQuotesExcludingCharCodes - regular quoted atom', () => {
318+
const count = (renameProvider as any).countSingleQuotesExcludingCharCodes("'hello'");
319+
assert.strictEqual(count, 2);
320+
});
321+
322+
test('countSingleQuotesExcludingCharCodes - character code notation 0\'0', () => {
323+
// Character code notation should not count the quote
324+
const count = (renameProvider as any).countSingleQuotesExcludingCharCodes("Code >= 0'0, Code =< 0'9");
325+
assert.strictEqual(count, 0);
326+
});
327+
328+
test('countSingleQuotesExcludingCharCodes - character code with escape sequence', () => {
329+
// Character code with escape like 0'\n should not count the quote
330+
const count = (renameProvider as any).countSingleQuotesExcludingCharCodes("Code = 0'\\n");
331+
assert.strictEqual(count, 0);
332+
});
333+
334+
test('countSingleQuotesExcludingCharCodes - mixed quotes and char codes', () => {
335+
// Mix of quoted atoms and character codes
336+
const count = (renameProvider as any).countSingleQuotesExcludingCharCodes("'atom', 0'x, 'another'");
337+
assert.strictEqual(count, 4); // 2 for 'atom' + 2 for 'another', 0 for 0'x
338+
});
339+
340+
test('isValidVariableContextInLine - variable after character code notation', () => {
341+
// Variable Code after 0'0 should be in valid context (not inside quotes)
342+
const isValid = (renameProvider as any).isValidVariableContextInLine(
343+
"hex_digit(Code, Value) :- Code >= 0'0, Code =< 0'9, !, Value is Code - 0'0.",
344+
61, 65 // Position of the last "Code" before "- 0'0"
345+
);
346+
assert.strictEqual(isValid, true);
347+
});
348+
349+
test('isValidVariableContextInLine - variable between character codes', () => {
350+
// Variable Code between 0'0 and 0'9 should be in valid context
351+
const isValid = (renameProvider as any).isValidVariableContextInLine(
352+
"hex_digit(Code, Value) :- Code >= 0'0, Code =< 0'9, !, Value is Code - 0'0.",
353+
39, 43 // Position of "Code" in "Code =< 0'9"
354+
);
355+
assert.strictEqual(isValid, true);
356+
});
357+
358+
test('isValidVariableContextInLine - variable inside actual quoted atom should be invalid', () => {
359+
// Variable inside a quoted atom should not be valid
360+
const isValid = (renameProvider as any).isValidVariableContextInLine(
361+
"test('Code is here', Code).",
362+
6, 10 // Position of "Code" inside the quoted atom
363+
);
364+
assert.strictEqual(isValid, false);
365+
});
310366
});

tests/variable-rename-test.lgt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,27 @@
9696
% Value is a test variable
9797
write(Value). % Print Value
9898

99+
% Test 16: Variable with character code notation (0'Char)
100+
% All occurrences of Code should be renamed, not just the first two
101+
% The single quotes in 0'0 and 0'9 are character code notation, not quoted atoms
102+
hex_digit(Code, Value) :-
103+
Code >= 0'0,
104+
Code =< 0'9,
105+
!,
106+
Value is Code - 0'0.
107+
108+
% Test 17: Multiple character codes in same clause
109+
is_ascii_letter(Code) :-
110+
( Code >= 0'a, Code =< 0'z
111+
; Code >= 0'A, Code =< 0'Z
112+
).
113+
114+
% Test 18: Character code with escape sequences
115+
is_whitespace(Code) :-
116+
( Code = 0'
117+
; Code = 0'\t
118+
; Code = 0'\n
119+
).
120+
99121
:- end_object.
100122

0 commit comments

Comments
 (0)