From e7103c7b61850d44eda54aeb434a51a465151d32 Mon Sep 17 00:00:00 2001 From: Pier CeccoPierangioliEugenio Date: Sat, 9 Aug 2025 22:37:08 +0100 Subject: [PATCH] feat(FindPlugin): support include and exclude files (#443) --- apps/ttkode/ttkode/plugins/_010/findwidget.py | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/apps/ttkode/ttkode/plugins/_010/findwidget.py b/apps/ttkode/ttkode/plugins/_010/findwidget.py index d9937ddb..758eee75 100644 --- a/apps/ttkode/ttkode/plugins/_010/findwidget.py +++ b/apps/ttkode/ttkode/plugins/_010/findwidget.py @@ -70,26 +70,32 @@ def _load_gitignore_patterns(gitignore_path): return patterns return [] -def _should_ignore(path, patterns): - for pattern in patterns: - if fnmatch.fnmatch(path, pattern): - return True - return False - -def _custom_walk(directory:str, patterns:List[str]=[]) -> Generator[Tuple[str, str], None, None]: +def _glob_match_patterns(path, patterns) -> bool: + if path == '.': + check_path = '' + elif path.startswith('./'): + check_path = path[2:] + else: + check_path = path + return any(f"/{_p}/" in path for _p in patterns if _p) or any(fnmatch.fnmatch(check_path, _p) for _p in patterns if _p) + +def _custom_walk(directory:str, include_patterns:List[str]=[], exclude_patterns:List[str]=[]) -> Generator[Tuple[str, str], None, None]: gitignore_path = os.path.join(directory, '.gitignore') - patterns = patterns + _load_gitignore_patterns(gitignore_path) + exclude_patterns = exclude_patterns + _load_gitignore_patterns(gitignore_path) for entry in sorted(os.listdir(directory)): full_path = os.path.join(directory, entry) - if _should_ignore(full_path, patterns): + if _glob_match_patterns(full_path, exclude_patterns): continue if not os.path.exists(full_path): continue if os.path.isdir(full_path): if entry == '.git': continue - yield from _custom_walk(full_path, patterns) + yield from _custom_walk(full_path, include_patterns, exclude_patterns) else: + if include_patterns and not _glob_match_patterns(full_path, include_patterns): + # ttk.TTkLog.debug(f"{include_patterns=} {full_path=}") + continue yield directory, entry def _walk_with_gitignore(root): @@ -97,7 +103,7 @@ def _walk_with_gitignore(root): gitignore_path = os.path.join(dirpath, '.gitignore') patterns = _load_gitignore_patterns(gitignore_path) - filenames[:] = [f for f in filenames if not _should_ignore(os.path.join(dirpath, f), patterns)] + filenames[:] = [f for f in filenames if not _glob_match_patterns(os.path.join(dirpath, f), patterns)] yield dirpath, filenames @@ -182,6 +188,9 @@ class FindWidget(ttk.TTkContainer): btn_expand.clicked.connect(self._results_tree.expandAll) btn_collapse.clicked.connect(self._results_tree.collapseAll) search.returnPressed.connect(self._search) + replace.returnPressed.connect(self._search) + ft_incl.returnPressed.connect(self._search) + ft_excl.returnPressed.connect(self._search) res_tree.itemActivated.connect(self._activated) @ttk.pyTTkSlot(str) @@ -203,14 +212,17 @@ class FindWidget(ttk.TTkContainer): def _search(self): self._runId += 1 search_pattern = str(self._search_le.text()) + replace_pattern = str(self._replace_le.text()) + include_patterns = _s.split(',') if (_s:=str(self._files_inc_le.text())) else [] + exclude_patterns = _s.split(',') if (_s:=str(self._files_exc_le.text())) else [] if not search_pattern: return def _search_threading(): self._results_tree.clear() group = [] groupSize = 1 - for (file,root,matches) in self._search_files('.',str(search_pattern),self._runId): - ttk.TTkLog.debug((file,matches)) + for (file,root,matches) in self._search_files('.',str(search_pattern),self._runId,include_patterns,exclude_patterns): + # ttk.TTkLog.debug((file,matches)) item = ttk.TTkTreeWidgetItem([ ttk.TTkString( ttk.TTkCfg.theme.fileIcon.getIcon(file), @@ -222,13 +234,19 @@ class FindWidget(ttk.TTkContainer): line = line.lstrip(' ') # index = line.find(search_pattern) # outLine = - item.addChild( - _MatchTreeWidgetItem([ - ttk.TTkString(str(num)+" ",ttk.TTkColor.CYAN) + - ttk.TTkString(line.replace('\n','')).completeColor( + if replace_pattern: + _s = line.replace('\n','').split(search_pattern) + _j = ( + ttk.TTkString(search_pattern,ttk.TTkColor.RED + ttk.TTkColor.STRIKETROUGH) + + ttk.TTkString(replace_pattern,ttk.TTkColor.GREEN) + ttk.TTkColor.RST) + ttkLine = _j.join(_s) + else: + ttkLine = ttk.TTkString(line.replace('\n','')).completeColor( match=search_pattern, color=ttk.TTkColor.GREEN) - ], + + item.addChild( + _MatchTreeWidgetItem([ttk.TTkString(str(num)+" ",ttk.TTkColor.CYAN) + ttkLine] , match=line, lineNumber=num, path=os.path.join(root,file))) @@ -242,9 +260,8 @@ class FindWidget(ttk.TTkContainer): self._results_tree.addTopLevelItems(group) Thread(target=_search_threading).start() - def _search_files(self, root_folder, match, runId): - matches = [] - for root, file in _custom_walk(root_folder): + def _search_files(self, root_folder, match, runId, include_patterns, exclude_patterns): + for root, file in _custom_walk(root_folder,include_patterns,exclude_patterns): if runId != self._runId: return if True: # file.endswith('.py'): # file.endswith(file_extension):