Skip to content

Commit 60c393a

Browse files
kriskclaude
andcommitted
fix(search): deduplicate and merge overlapping match indices
When a search pattern exceeds MAX_BITS (32 chars), it is split into chunks. Each chunk produces its own match indices, which were concatenated without deduplication, resulting in duplicate and overlapping index ranges. This broke highlighting implementations. Now indices from multiple chunks are sorted and merged into non-overlapping ranges before being returned. Closes #735 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 58c7c73 commit 60c393a

14 files changed

Lines changed: 157 additions & 13 deletions

dist/fuse.basic.cjs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,23 @@ var stripDiacritics = String.prototype.normalize ? function (str) {
800800
return str;
801801
};
802802

803+
function mergeIndices(indices) {
804+
if (indices.length <= 1) return indices;
805+
indices.sort(function (a, b) {
806+
return a[0] - b[0] || a[1] - b[1];
807+
});
808+
var merged = [indices[0]];
809+
for (var i = 1, len = indices.length; i < len; i += 1) {
810+
var last = merged[merged.length - 1];
811+
var curr = indices[i];
812+
if (curr[0] <= last[1] + 1) {
813+
last[1] = Math.max(last[1], curr[1]);
814+
} else {
815+
merged.push(curr);
816+
}
817+
}
818+
return merged;
819+
}
803820
var BitapSearch = /*#__PURE__*/function () {
804821
function BitapSearch(pattern) {
805822
var _this = this;
@@ -927,7 +944,7 @@ var BitapSearch = /*#__PURE__*/function () {
927944
score: hasMatches ? totalScore / this.chunks.length : 1
928945
};
929946
if (hasMatches && includeMatches) {
930-
result.indices = allIndices;
947+
result.indices = mergeIndices(allIndices);
931948
}
932949
return result;
933950
}

dist/fuse.basic.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,23 @@
804804
return str;
805805
};
806806

807+
function mergeIndices(indices) {
808+
if (indices.length <= 1) return indices;
809+
indices.sort(function (a, b) {
810+
return a[0] - b[0] || a[1] - b[1];
811+
});
812+
var merged = [indices[0]];
813+
for (var i = 1, len = indices.length; i < len; i += 1) {
814+
var last = merged[merged.length - 1];
815+
var curr = indices[i];
816+
if (curr[0] <= last[1] + 1) {
817+
last[1] = Math.max(last[1], curr[1]);
818+
} else {
819+
merged.push(curr);
820+
}
821+
}
822+
return merged;
823+
}
807824
var BitapSearch = /*#__PURE__*/function () {
808825
function BitapSearch(pattern) {
809826
var _this = this;
@@ -931,7 +948,7 @@
931948
score: hasMatches ? totalScore / this.chunks.length : 1
932949
};
933950
if (hasMatches && includeMatches) {
934-
result.indices = allIndices;
951+
result.indices = mergeIndices(allIndices);
935952
}
936953
return result;
937954
}

dist/fuse.basic.min.cjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

dist/fuse.basic.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/fuse.basic.min.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

dist/fuse.basic.mjs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,26 @@ const stripDiacritics = String.prototype.normalize
715715
? ((str) => str.normalize('NFD').replace(/[\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08D3-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09FE\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C04\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D00-\u0D03\u0D3B\u0D3C\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u1885\u1886\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ABE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF7-\u1CF9\u1DC0-\u1DF9\u1DFB-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F]/g, ''))
716716
: ((str) => str);
717717

718+
function mergeIndices(indices) {
719+
if (indices.length <= 1) return indices
720+
721+
indices.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
722+
723+
const merged = [indices[0]];
724+
725+
for (let i = 1, len = indices.length; i < len; i += 1) {
726+
const last = merged[merged.length - 1];
727+
const curr = indices[i];
728+
if (curr[0] <= last[1] + 1) {
729+
last[1] = Math.max(last[1], curr[1]);
730+
} else {
731+
merged.push(curr);
732+
}
733+
}
734+
735+
return merged
736+
}
737+
718738
class BitapSearch {
719739
constructor(
720740
pattern,
@@ -843,7 +863,7 @@ class BitapSearch {
843863
};
844864

845865
if (hasMatches && includeMatches) {
846-
result.indices = allIndices;
866+
result.indices = mergeIndices(allIndices);
847867
}
848868

849869
return result

dist/fuse.cjs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,23 @@ var stripDiacritics = String.prototype.normalize ? function (str) {
867867
return str;
868868
};
869869

870+
function mergeIndices(indices) {
871+
if (indices.length <= 1) return indices;
872+
indices.sort(function (a, b) {
873+
return a[0] - b[0] || a[1] - b[1];
874+
});
875+
var merged = [indices[0]];
876+
for (var i = 1, len = indices.length; i < len; i += 1) {
877+
var last = merged[merged.length - 1];
878+
var curr = indices[i];
879+
if (curr[0] <= last[1] + 1) {
880+
last[1] = Math.max(last[1], curr[1]);
881+
} else {
882+
merged.push(curr);
883+
}
884+
}
885+
return merged;
886+
}
870887
var BitapSearch = /*#__PURE__*/function () {
871888
function BitapSearch(pattern) {
872889
var _this = this;
@@ -994,7 +1011,7 @@ var BitapSearch = /*#__PURE__*/function () {
9941011
score: hasMatches ? totalScore / this.chunks.length : 1
9951012
};
9961013
if (hasMatches && includeMatches) {
997-
result.indices = allIndices;
1014+
result.indices = mergeIndices(allIndices);
9981015
}
9991016
return result;
10001017
}

dist/fuse.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,23 @@
871871
return str;
872872
};
873873

874+
function mergeIndices(indices) {
875+
if (indices.length <= 1) return indices;
876+
indices.sort(function (a, b) {
877+
return a[0] - b[0] || a[1] - b[1];
878+
});
879+
var merged = [indices[0]];
880+
for (var i = 1, len = indices.length; i < len; i += 1) {
881+
var last = merged[merged.length - 1];
882+
var curr = indices[i];
883+
if (curr[0] <= last[1] + 1) {
884+
last[1] = Math.max(last[1], curr[1]);
885+
} else {
886+
merged.push(curr);
887+
}
888+
}
889+
return merged;
890+
}
874891
var BitapSearch = /*#__PURE__*/function () {
875892
function BitapSearch(pattern) {
876893
var _this = this;
@@ -998,7 +1015,7 @@
9981015
score: hasMatches ? totalScore / this.chunks.length : 1
9991016
};
10001017
if (hasMatches && includeMatches) {
1001-
result.indices = allIndices;
1018+
result.indices = mergeIndices(allIndices);
10021019
}
10031020
return result;
10041021
}

dist/fuse.min.cjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

dist/fuse.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)