Shofel2_T124_python/venv/lib/python3.10/site-packages/asciimatics/widgets/filebrowser.py

163 lines
6.8 KiB
Python

# -*- coding: utf-8 -*-
"""This module defines a file browser selection"""
from __future__ import division
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
from re import compile as re_compile
import os
import unicodedata
from collections import namedtuple
from asciimatics.utilities import readable_timestamp, readable_mem
from asciimatics.widgets.multicolumnlistbox import MultiColumnListBox
class FileBrowser(MultiColumnListBox):
"""
A FileBrowser is a widget for finding a file on the local disk.
"""
def __init__(self, height, root, name=None, on_select=None, on_change=None, file_filter=None):
r"""
:param height: The desired height for this widget.
:param root: The starting root directory to display in the widget.
:param name: The name of this widget.
:param on_select: Optional function that gets called when user selects a file (by pressing
enter or double-clicking).
:param on_change: Optional function that gets called on any movement of the selection.
:param file_filter: Optional RegEx string that can be passed in to filter the files to be displayed.
Most people will want to use a filter to finx files with a particular extension. In this case,
you must use a regex that matches to the end of the line - e.g. use ".*\.txt$" to find files ending
with ".txt". This ensures that you don't accidentally pick up files containing the filter.
"""
super(FileBrowser, self).__init__(
height,
[0, ">8", ">14"],
[],
titles=["Filename", "Size", "Last modified"],
name=name,
on_select=self._on_selection,
on_change=on_change)
# Remember the on_select handler for external notification. This allows us to wrap the
# normal on_select notification with a function that will open new sub-directories as
# needed.
self._external_notification = on_select
self._root = root
self._in_update = False
self._initialized = False
self._file_filter = None if file_filter is None else re_compile(file_filter)
def update(self, frame_no):
# Defer initial population until we first display the widget in order to avoid race
# conditions in the Frame that may be using this widget.
if not self._initialized:
self._populate_list(self._root)
self._initialized = True
super(FileBrowser, self).update(frame_no)
def _on_selection(self):
"""
Internal function to handle directory traversal or bubble notifications up to user of the
Widget as needed.
"""
if self.value and os.path.isdir(self.value):
self._populate_list(self.value)
elif self._external_notification:
self._external_notification()
def clone(self, new_widget):
# Copy the data into the new widget. Notes:
# 1) I don't really want to expose these methods, so am living with the protected access.
# 2) I need to populate the list and then assign the values to ensure that we get the
# right selection on re-sizing.
# pylint: disable=protected-access
new_widget._populate_list(self._root)
new_widget._start_line = self._start_line
new_widget._root = self._root
new_widget.value = self.value
def _populate_list(self, value):
"""
Populate the current multi-column list with the contents of the selected directory.
:param value: The new value to use.
"""
# Nothing to do if the value is rubbish.
if value is None:
return
# Stop any recursion - no more returns from here to end of fn please!
if self._in_update:
return
self._in_update = True
# We need to update the tree view.
self._root = os.path.abspath(value if os.path.isdir(value) else os.path.dirname(value))
# The absolute expansion of "/" or "\" is the root of the disk, so is a cross-platform
# way of spotting when to insert ".." or not.
tree_view = []
if len(self._root) > len(os.path.abspath(os.sep)):
tree_view.append((["|-+ .."], os.path.abspath(os.path.join(self._root, ".."))))
tree_dirs = []
tree_files = []
try:
files = os.listdir(self._root)
except OSError:
# Can fail on Windows due to access permissions
files = []
for my_file in files:
full_path = os.path.join(self._root, my_file)
try:
details = os.lstat(full_path)
except OSError:
# Can happen on Windows due to access permissions
details = namedtuple("stat_type", "st_size st_mtime")
details.st_size = 0
details.st_mtime = 0
name = "|-- {}".format(my_file)
tree = tree_files
if os.path.isdir(full_path):
tree = tree_dirs
if os.path.islink(full_path):
# Show links separately for directories
real_path = os.path.realpath(full_path)
name = "|-+ {} -> {}".format(my_file, real_path)
else:
name = "|-+ {}".format(my_file)
elif self._file_filter and not self._file_filter.match(my_file):
# Skip files that don't match the filter (if present)
continue
elif os.path.islink(full_path):
# Check if link target exists and if it does, show statistics of the
# linked file, otherwise just display the link
try:
real_path = os.path.realpath(full_path)
except OSError:
# Can fail on Linux prof file system.
real_path = None
if real_path and os.path.exists(real_path):
details = os.stat(real_path)
name = "|-- {} -> {}".format(my_file, real_path)
else:
# Both broken directory and file links fall to this case.
# Actually using the files will cause a FileNotFound exception
name = "|-- {} -> {}".format(my_file, real_path)
# Normalize names for MacOS and then add to the list.
tree.append(([unicodedata.normalize("NFC", name),
readable_mem(details.st_size),
readable_timestamp(details.st_mtime)], full_path))
tree_view.extend(sorted(tree_dirs))
tree_view.extend(sorted(tree_files))
self.options = tree_view
self._titles[0] = self._root
# We're out of the function - unset recursion flag.
self._in_update = False