diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 9f01b52f1aff3b..c29a235ce812b5 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -168,7 +168,6 @@ production systems where traditional profiling approaches would be too intrusive (Contributed by Pablo Galindo and László Kiss Kollár in :gh:`135953`.) - Other language changes ====================== @@ -354,6 +353,19 @@ tarfile (Contributed by Matt Prodani and Petr Viktorin in :gh:`112887` and :cve:`2025-4435`.) +tkinter +------- + +* The :meth:`!tkinter.Text.search` method now supports two additional + arguments: *nolinestop* which allows the search to + continue across line boundaries; + and *strictlimits* which restricts the search to within the specified range. + (Contributed by Rihaan Meher in :gh:`130848`) + +* A new method :meth:`!tkinter.Text.search_all` has been introduced. + This method allows for searching for all matches of a pattern + using Tcl's ``-all`` and ``-overlap`` options. + (Contributed by Rihaan Meher in :gh:`130848`) types ------ diff --git a/Lib/test/test_tkinter/test_text.py b/Lib/test/test_tkinter/test_text.py index b26956930d3402..ec1d2e06efac37 100644 --- a/Lib/test/test_tkinter/test_text.py +++ b/Lib/test/test_tkinter/test_text.py @@ -41,6 +41,47 @@ def test_search(self): self.assertEqual(text.search('-test', '1.0', 'end'), '1.2') self.assertEqual(text.search('test', '1.0', 'end'), '1.3') + text.delete('1.0', 'end') + text.insert('1.0', + 'This is a test. This is only a test.\n' + 'Another line.\n' + 'Yet another line.') + + result = text.search('line', '1.0', 'end', nolinestop=True, regexp=True) + self.assertEqual(result, '2.8') + + strict_res = text.search('test', '1.0', '1.20', strictlimits=True) + self.assertEqual(strict_res, '1.10') + + def test_search_all(self): + text = self.text + text.insert('1.0', + 'ababa ababa\n' + 'ababababa\n' + 'aba aba') + + all_res = text.search_all('aba', '1.0', 'end') + all_res_strs = [str(i) for i in all_res] + self.assertIsInstance(all_res, tuple) + self.assertGreaterEqual(len(all_res), 3) + self.assertEqual(str(all_res[0]), '1.0') + self.assertEqual(str(all_res[1]), '1.6') + + overlap_res = text.search_all('aba', '1.0', 'end', overlap=True) + overlap_res_strs = [str(i) for i in overlap_res] + self.assertIsInstance(overlap_res, tuple) + self.assertGreater(len(overlap_res), len(all_res)) + + # Check that overlap actually finds overlapping matches + self.assertIn('2.0', overlap_res_strs) + self.assertIn('2.2', overlap_res_strs) + self.assertIn('2.4', overlap_res_strs) + self.assertNotIn('2.2', all_res_strs) + + # Ensure all results are valid text indices + for i in overlap_res: + self.assertRegex(str(i), r'^\d+\.\d+$') + def test_count(self): text = self.text text.insert('1.0', @@ -94,6 +135,5 @@ def test_count(self): self.assertEqual(text.count('1.3', '1.3', 'update', return_ints=True), 0) self.assertEqual(text.count('1.3', '1.3', 'update'), None) - if __name__ == "__main__": unittest.main() diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index a693b04870b995..df3d1b49cf9d41 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -4039,8 +4039,9 @@ def scan_dragto(self, x, y): self.tk.call(self._w, 'scan', 'dragto', x, y) def search(self, pattern, index, stopindex=None, - forwards=None, backwards=None, exact=None, - regexp=None, nocase=None, count=None, elide=None): + forwards=None, backwards=None, exact=None, + regexp=None, nocase=None, count=None, + elide=None, nolinestop=None, strictlimits=None): """Search PATTERN beginning from INDEX until STOPINDEX. Return the index of the first character of a match or an empty string.""" @@ -4052,12 +4053,39 @@ def search(self, pattern, index, stopindex=None, if nocase: args.append('-nocase') if elide: args.append('-elide') if count: args.append('-count'); args.append(count) + if nolinestop: args.append('-nolinestop') + if strictlimits: args.append('-strictlimits') if pattern and pattern[0] == '-': args.append('--') args.append(pattern) args.append(index) if stopindex: args.append(stopindex) return str(self.tk.call(tuple(args))) + def search_all(self, pattern, index, stopindex=None, *, + forwards=None, backwards=None, exact=None, + regexp=None, nocase=None, count=None, + elide=None, nolinestop=None, overlap=None, + strictlimits=None): + """Search all occurrences of PATTERN from INDEX to STOPINDEX. + Return a tuple of indices where matches begin.""" + args = [self._w, 'search', '-all'] + if forwards: args.append('-forwards') + if backwards: args.append('-backwards') + if exact: args.append('-exact') + if regexp: args.append('-regexp') + if nocase: args.append('-nocase') + if elide: args.append('-elide') + if count: args.append('-count'); args.append(count) + if nolinestop: args.append('-nolinestop') + if overlap: args.append('-overlap') + if strictlimits: args.append('-strictlimits') + if pattern and pattern[0] == '-': args.append('--') + args.append(pattern) + args.append(index) + if stopindex: args.append(stopindex) + result = self.tk.call(tuple(args)) + return self.tk.splitlist(result) + def see(self, index): """Scroll such that the character at INDEX is visible.""" self.tk.call(self._w, 'see', index) diff --git a/Misc/NEWS.d/next/Library/2025-03-04-17-19-26.gh-issue-130693.Kv01r8.rst b/Misc/NEWS.d/next/Library/2025-03-04-17-19-26.gh-issue-130693.Kv01r8.rst new file mode 100644 index 00000000000000..b175ab7cad468a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-04-17-19-26.gh-issue-130693.Kv01r8.rst @@ -0,0 +1 @@ +Add support for ``-nolinestop``, and ``-strictlimits`` options to :meth:`!tkinter.Text.search`. Also add the :meth:`!tkinter.Text.search_all` method for ``-all`` and ``-overlap`` options.