From 9fe249c0d24b46a4fd8381c7032f408581e7f5c5 Mon Sep 17 00:00:00 2001 From: Pier CeccoPierangioliEugenio Date: Sat, 9 Aug 2025 09:25:53 +0100 Subject: [PATCH] feat: allow multiple files to be selected and dragged (#438) --- apps/ttkode/ttkode/app/ttkode.py | 37 ++++++----- apps/ttkode/ttkode/plugins/_010/findwidget.py | 2 + tests/t.generic/test.stderr.001.py | 16 +++++ tests/t.generic/test.stderr.002.py | 63 +++++++++++++++++++ 4 files changed, 104 insertions(+), 14 deletions(-) create mode 100644 tests/t.generic/test.stderr.001.py create mode 100644 tests/t.generic/test.stderr.002.py diff --git a/apps/ttkode/ttkode/app/ttkode.py b/apps/ttkode/ttkode/app/ttkode.py index 2ea572b0..aca3171a 100644 --- a/apps/ttkode/ttkode/app/ttkode.py +++ b/apps/ttkode/ttkode/app/ttkode.py @@ -198,7 +198,7 @@ class TTKode(ttk.TTkGridLayout): helpMenu.addMenu("About ...").menuButtonClicked.connect(_showAbout) helpMenu.addMenu("About ttk").menuButtonClicked.connect(_showAboutTTk) - fileTree = ttk.TTkFileTree(path='.', dragDropMode=ttk.TTkK.DragDropMode.AllowDrag) + fileTree = ttk.TTkFileTree(path='.', dragDropMode=ttk.TTkK.DragDropMode.AllowDrag, selectionMode=ttk.TTkK.SelectionMode.MultiSelection) self._activityBar = TTKodeActivityBar() self._activityBar.addActivity(name="Explorer", icon=ttk.TTkString("╔██\n╚═╝"), widget=fileTree, select=True) @@ -344,8 +344,10 @@ class TTKode(ttk.TTkGridLayout): kt.tabButton(index).mergeStyle(doc.getTabButtonStyle()) kt.tabButton(index).setText(doc._tabText) - def _openFile(self, filePath, line:int=0, pos:int=0): + def _openFile(self, filePath, line:int=0, pos:int=0) -> None: filePath = os.path.realpath(filePath) + if not os.path.isfile(filePath): + return doc, tedit = self._getDocument(filePath=filePath) if tedit: self._kodeTab.setCurrentWidget(tedit) @@ -359,29 +361,36 @@ class TTKode(ttk.TTkGridLayout): def _dropEventProxyFile(self, evt:ttk.TTkDnDEvent): data = evt.data() - filePath = None + filePaths = [] + linenums = [] if ( issubclass(type(data), ttk.TTkTreeWidget._DropTreeData) and data.items ): if issubclass(type(data.items[0]), ttk.TTkFileTreeWidgetItem): - linenum:int = 0 - ftwi:ttk.TTkFileTreeWidgetItem = data.items[0] - filePath = os.path.realpath(ftwi.path()) + ftwis:List[ttk.TTkFileTreeWidgetItem] = data.items + filePaths = [os.path.realpath(_f.path()) for _f in ftwis] + linenums:List[int] = [0]*len(filePaths) elif issubclass(type(data.items[0]), TTKodeFileWidgetItem): kfwi:TTKodeFileWidgetItem = data.items[0] - linenum:int = kfwi.lineNumber() - filePath = os.path.realpath(kfwi.path()) + linenums:List[int] = [kfwi.lineNumber()] + filePaths = [os.path.realpath(kfwi.path())] - if filePath: + newData = [] + for filePath,linenum in zip(filePaths,linenums) : + if not os.path.isfile(filePath): + continue doc, _ = self._getDocument(filePath=filePath) tedit = _TextEdit(document=doc, readOnly=False, lineNumber=True) tedit.docFocussed.connect(self._handleDocFocussed) tedit.goToLine(linenum) - newData = _TTkNewTabWidgetDragData( - widget=tedit, - label=doc._tabText, - data=None, - closable=True + newData.append( + _TTkNewTabWidgetDragData( + widget=tedit, + label=doc._tabText, + data=None, + closable=True + ) ) + if newData: newEvt = evt.clone() newEvt.setData(newData) return newEvt diff --git a/apps/ttkode/ttkode/plugins/_010/findwidget.py b/apps/ttkode/ttkode/plugins/_010/findwidget.py index 5386a320..d9937ddb 100644 --- a/apps/ttkode/ttkode/plugins/_010/findwidget.py +++ b/apps/ttkode/ttkode/plugins/_010/findwidget.py @@ -83,6 +83,8 @@ def _custom_walk(directory:str, patterns:List[str]=[]) -> Generator[Tuple[str, s full_path = os.path.join(directory, entry) if _should_ignore(full_path, patterns): continue + if not os.path.exists(full_path): + continue if os.path.isdir(full_path): if entry == '.git': continue diff --git a/tests/t.generic/test.stderr.001.py b/tests/t.generic/test.stderr.001.py new file mode 100644 index 00000000..9b97163b --- /dev/null +++ b/tests/t.generic/test.stderr.001.py @@ -0,0 +1,16 @@ +import sys +from io import StringIO + +sys.stderr.write('Eugenio') + +old_stderr = sys.stderr +sys.stderr = mystderr = StringIO() + +sys.stderr.write('Eugenio2') + +sys.stderr = old_stderr + +print('Err:',mystderr.getvalue()) + +with open('jkhdkjdhkjdhkjdhd.txt','r') as f: + x = f.read() \ No newline at end of file diff --git a/tests/t.generic/test.stderr.002.py b/tests/t.generic/test.stderr.002.py new file mode 100644 index 00000000..d544b585 --- /dev/null +++ b/tests/t.generic/test.stderr.002.py @@ -0,0 +1,63 @@ +import sys +import contextlib + +class StderrHandler: + def __init__(self, callback=None): + """ + Create a handler for stderr output. + + Args: + callback: A function that will be called with each piece of text written to stderr + """ + self.buffer = "" + self.callback = callback + + def write(self, text): + self.buffer += text + if self.callback: + self.callback(text) + return len(text) + + def flush(self): + # Required to be a proper file-like object + pass + + def getvalue(self): + return self.buffer + + +@contextlib.contextmanager +def redirect_stderr_to_handler(callback=None): + """Context manager for temporarily redirecting stderr to our handler.""" + handler = StderrHandler(callback) + original_stderr = sys.stderr + sys.stderr = handler + try: + yield handler + finally: + sys.stderr = original_stderr + + +# Example 1: Using the context manager +def my_handler(text): + print(f"Captured stderr: {text!r}") + +with redirect_stderr_to_handler(my_handler): + # Any stderr output inside this block will be handled by my_handler + print("This is an error", file=sys.stderr) + raise Exception("This will also be captured") + +# Example 2: Manual redirection +handler = StderrHandler(lambda text: print(f"Got: {text}")) +original_stderr = sys.stderr +sys.stderr = handler + +try: + # Now stderr is redirected + print("Error message", file=sys.stderr) +finally: + # Always restore the original stderr + sys.stderr = original_stderr + +# Get the accumulated output +print(f"Captured content: {handler.getvalue()}") \ No newline at end of file