lfm-2.3/0000755000076400001440000000000011565710734012435 5ustar inigousers00000000000000lfm-2.3/setup.py0000755000076400001440000000371511565705675014170 0ustar inigousers00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Last File Manager is a powerful file manager for the UNIX console. Based in a curses interface, it's written in Python.""" from distutils.core import setup import sys DOC_FILES = ['COPYING', 'README', 'README.pyview', 'NEWS', 'TODO', 'ChangeLog'] MAN_FILES = ['lfm.1', 'pyview.1'] classifiers = """\ Development Status :: 5 - Production/Stable Environment :: Console :: Curses Intended Audience :: End Users/Desktop Intended Audience :: System Administrators License :: OSI Approved :: GNU General Public License (GPL) Natural Language :: English Operating System :: POSIX Operating System :: Unix Programming Language :: Python Topic :: Desktop Environment :: File Managers Topic :: System :: Filesystems Topic :: System :: Shells Topic :: System :: System Shells Topic :: Utilities """ doclines = __doc__.split("\n") print doclines if sys.version_info < (2, 3): _setup = setup def setup(**kwargs): if kwargs.has_key("classifiers"): del kwargs["classifiers"] _setup(**kwargs) setup(name = 'lfm', version = '2.3', license = 'GPL', description = doclines[0], long_description = '\n'.join(doclines[2:]), author = u'Inigo Serna', author_email = 'inigoserna@gmail.com', url = 'https://inigo.katxi.org/devel/lfm', platforms = 'POSIX', classifiers = filter(None, classifiers.split("\n")), py_modules = ['lfm/__init__', 'lfm/lfm', 'lfm/messages', 'lfm/files', 'lfm/actions', 'lfm/compress', 'lfm/utils', 'lfm/vfs', 'lfm/config', 'lfm/pyview'], scripts = ['lfm/lfm', 'lfm/pyview'], data_files = [('share/doc/lfm', DOC_FILES), ('share/man/man1', MAN_FILES)] # **addargs ) # import os, os.path, sys # from distutils.sysconfig import get_python_lib # os.symlink(os.path.join(get_python_lib(), 'lfm/lfm.py'), # os.path.join(sys.exec_prefix, 'bin/lfm')) lfm-2.3/PKG-INFO0000644000076400001440000000163311565710734013535 0ustar inigousers00000000000000Metadata-Version: 1.0 Name: lfm Version: 2.3 Summary: Last File Manager is a powerful file manager for the UNIX console. Home-page: https://inigo.katxi.org/devel/lfm Author: Inigo Serna Author-email: inigoserna@gmail.com License: GPL Description: UNKNOWN Platform: POSIX Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console :: Curses Classifier: Intended Audience :: End Users/Desktop Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Natural Language :: English Classifier: Operating System :: POSIX Classifier: Operating System :: Unix Classifier: Programming Language :: Python Classifier: Topic :: Desktop Environment :: File Managers Classifier: Topic :: System :: Filesystems Classifier: Topic :: System :: Shells Classifier: Topic :: System :: System Shells Classifier: Topic :: Utilities lfm-2.3/NEWS0000644000076400001440000004174111565704616013145 0ustar inigousers00000000000000Version 2.3 ("Wow, less than a year!") - 2011/05/21: + About the code - lfm needs python version 2.5 or upper now + New features - PowerCLI, an advanced command line interface with completion, persistent history, variable substitution and many other useful features. As this is a very powerful tool, read the documentation for examples - history . use different types of history lists: path, file, glob, grep, exec, cli for the different forms and actions . persistent history between sessions => ~/.lfm_history . controlled by a flag in configuration - find/grep . configuration options for ignorecase and regex . sort results . show results as FILE:lineno . much faster - show diff between xxx.orig and xxx files - tar files compress/uncompress - messages.EntryLine has been rewritten, with many new key shorcuts. This is the core behind most of the forms lfm shows when asking for anything. Consult the documentation + Minor changes - reorganize "un/compress file" and "compress directory xxx" in file_menu - config: sort entries when saving - improve load/save handling of new options not present in ~/.lfmrc - added new extensions - messages.error rewritten to offer better messages - added some new key shortcuts messages.SelectItem + Documentation - added a note about python v2.5+ is needed from now on - 'lfm' shell function: change "$*" to "$@" to properly handle paths containg spaces - FAQ: added information about fuse to mount ssh, ftp, smb and webdav - reorganized and fixed key bindings section - documented .lfmrc contents - added link to public BitBucket repository + lots of bugs fixed: - pyview: . last char is not shown if file size is small . last line and wrap: cursor_down or page_next . when number of lines == window height - ncurses v5.8 doesn't accept 0 as width or height - UI crashes: . time string could contain non-ascii characters (reported by Martin Steigerwald) . when filenane length is large in full pane mode . MenuWin, SelectItem: ellipsize entries if bigger than screen width - find or find&grep: . pass "-type f" to find as ".#filename" are temporary emacs files/links that break search . show wrong matches if results contain directories or files with spaces . file->goto_file: move to correct page - copy/move "/file" to "/anydir/anyplace" fails, trying to copy/move to "/" - executing non-ascii programname or args - convoluted issue with link to directory in corner cases (reported by Xin Wang) - rename/backup ".." crashes - we should not compress ".." - create_link, edit_link: don't show error if canceled - only store one copy of the same entry in history - tree: "disable" colors of active panel, "enable" at end - Config.save: work with unicode, only convert to encoding when saving Version 2.2 ("Approaching perfection") - 2010/05/22: + New features - use 2 progress bars in copy/move/delete dialog, one for files count and other for files size - added recursive chmod chown chgrp - faster cursor movement . Ctrl-l: center cursor in panel, so now edit-link is in 'L' . Ctrl-cursor_up, Ctrl-P: move cursor 1/4th of page up . Ctrl-cursor_down, Ctrl-N: move cursor 1/4th of page down . P: move cursor 1/4th of page up in other panel . N: move cursor 1/4th of page down in other panel - file_menu new feature: a -> backup file. You can specify the extension to use in .lfmrc - added support for .xz compressed files - Unicode & Encodings . rewrite all internals to use unicode strings, but employ terminal encoding (f.e. utf-8) to interact with the user or to display contents in ncurses functions or to run commands in shell . when lfm detects a file with invalid encoding name it asks the user to convert it (can be automatic with the proper option in the configuration, automatic_file_encoding_conversion, default 0 (ask)). If not converted, lfm will display the file but won't operate on it. . try more encodings when we get a filename with strange characters . lfm will check and require a valid encoding before running - Pyview: . completely rewritten. Code is shorter and more beautiful now Uses a new FileCache class to accelerate the retrieving of file lines . displays contents between 2 and 4 times faster . new command line flag -s/--stdin to force reading from stdin. Now pyview doesn't wait for stdin input by default, so it starts much faster. eg. $ ps efax | pyview -s + Minor changes - add color entries for directories and exe_files - expand ~ to user home - make Tree follow .dotfiles behaviour, new keybinding Ctrl-H - dialogs are bigger now - show filesystem info rewritten - show file info rewritten, now it shows correctly information from fuse-mounted volumes - added new "ebook" category, filetypes and formats + About the code - since python v2.6+, popen* is deprecated, so make lfm check python version and use popen* or subprocess accordingly - correct some python idioms - clean code + Documentation - Added "Files Name Encoding" and "FAQ" sections - Added information about keybindings in permissions window - Updated some other minor changes: wide char support, vfs, thanks + lots of bugs fixed: - file system information was not showed correctly sometimes - devices major and minor numbers were not showed correctly - crash in goto_dir if there aren't any historic entries - crash in a void EntryLine after pressing BACKSPACE on some platforms - unzip => overwrite files without prompting ("unzip -o" option) to avoid ethernal waiting, as messages can not be seen by user - fix make_dir error message - recompressing a vfs compressed file leave some garbage on temporary dir - don't try to copy fifo/socket/block-dev/char-dev files - crash when we don't have enough permissions to write to dest - show_dirs_size: don't show in stderr if we don't have perms for a dir - can't browse /home/ as root if .gvfs is present - EntryLine: non-ascii chars are not showed correctly - lfm crashes with invalid encoding filenames - increment owner and group space to avoid ugly look in 1-pane view - when moving files, don't delete source if some error or if we don't overwrite destination Version 2.1 ("What do you want for Christimas?") - 2008/12/21: + Ctrl-H now show/hide dot files + Ctrl-Y display directories history + It's now posible to move the cursor in the non-active pane Consult the documentation for available keys and actions This behaviour is de/activated with Ctrl-W + added support for .7z compressed files + swapped F2 and F12 keys, now F2 rename files and F12 show file menu + new key shortcuts in dialogs. Read docs + speed up cursor movement + lots of code cleaning and refactoring + and fixed lot of bugs, some of them: - setup.py: change IƱigo for Inigo to avoid problems when installing - sorting by None doesn't crash anymore - MenuWin dialog crashed when title length was greater than length of entries to show Version 2.0 ("Nine 1/2 weeks... ok, ok, and 3 years") - 2007/09/03: + tabs implemented + color files by extension [Andrey Skvortsov] + new IPC code and API; more flexible, powerful and stable + new un/compress vfs API, added support for .zip and .rar files + make sort mode per tab, not globally + support locale [Andrey Skvortsov] + speed up loading directory contents + speed cursor movement, don't waste much CPU + use logging module in lfm and pyview for debugging + overwrite_all_none: yes, all, no => new options: "none", "skip all"" + rewrite/refactor most of code to make lfm more robust and clean + preferences: - change file name preferences.py => config.py - use ConfigParser + use tempfile secure versions mkdtemp() and mkstemp() + added man pages [Sebastien Bacher] + use reST for documentation + check for python version 2.3 or higher in lfm and pyview + upgraded to GPL v3 license + and fixed lot of bugs, some of them: - general: . delete garbage if user stops action . run 'do_special_view_file' as dettached from lfm window . path expand in bookmarks ("~/") [Andrey Skvortsov] . an ugly traceback crash appears when user starts "lfm path" and has no permissions to enter. Show error message and default to current directory . lfm crashes when filename is not encoded with same codec than g_encoding utils.{decode|encode}. Needs curses module linked against ncursesw to work properly . sort_mix_cases = 1 performance degrades on larger dirs. Reported by Andrey Skvortsov . escape filenames with chars $ ". Reported by Andrey Skvortsov - user interface: . maximize/minimize window don't crash lfm anymore . dialogs appear at bad position after terminal is resized . handle window resize in Tree mode . refresh display after canceling completion dialog . "the size of the right pane does not fill the last column in terminal if their number is odd" [Andrey Skvortsov] . fix crash when "df" shows entries in two different lines (device name is too large, f.e. in linux lvm2 volumes) . if you try to enter a directory with insufficient permissions, after the error message is closed the cursorline refreshes to the first line - compress: . added -i flag (--ignore-zeros) flag to tar [Andrey Skvortsov] . standard tar needs - for flags - vfs: . vfs.py: regenerate_file, if user stops process, tempfile can't be deleted - find/grep: . escape special chars (- \ ( ) [ ]) in patterns . don't crash when find/grep returns no results . bug when matches occur in binary files - pyview: . goto line 0 in pyview showed a blank screen . crash in file info if filename is too long Version 1.0 was never publically released - 2006 Version 0.92 was never publically released - 2005 Version 0.91 ("It rocks... yeah!") - 2004/06/30: + quite stable and robust, doesn't crash + faster + new option: show_dotfiles flag + new option: detach_terminal_at_exec flag: useful f.e. if you want to run elinks as web browser attached to lfm terminal + file associations and applications can be configured in preferences + now each application has only 1 associated program, *breaking old .lfmrc* + perms dialog: users & groups sorted alphabetically + uncompress in other panel + resizing terminal works in lfm, pyview. Be careful with dialogs + columns size eliminated from preferences + 1-panel view redesigned + ESC closes dialogs, not lfm or pyview + Ctrl-D: select bookmark dialog + code reorganized: actions.py, vfs.py + added classifiers to setup.py script Version 0.90 was never publically released Version 0.9 ("...and the day arrived") - 2002/09/05: + ZIP files can be un/compressed now. ZIP vfs works too + added 'rebuild vfs' and 'rebuild_vfs' question / option. Configurable. There is no need to wait until vfs file is rebuilt + Applied some good patches from Bartosz Oler (liar AT furrynet DOT org): 1. colors customization: Now you can customize the colors lfm use in the configuration file, 'colors' section. Each color is defined by a string with its name. It looks like this: element foreground_color background_color 2. Allow preferences values to contain colons in the configuration file. 3. Lack of a bookmark's definition shouldn't be an error. + pyview: new features: - read from stdin - go to / set bookmarks - open shell + pyview doesn't show blank screen a the end of the file, now it shows last line + lfm works now with "from __future__ import division", prepared for Python 3.0 + set python2 as default interpreter for setup.py, lfm, lfm.py, pyview and pyview.py + my email address has changed + Bugs fixes. See ChangeLog for complete list - lfm: * create temporary files with mask 0066 and directories with perms 0700, so only owner can read/write them * avoid zombie processes when using 'fork' (now fork twice or threads) * show correct filename when an error occurs while uncompressing file * don't compress '..' directory * when lfm exits after checking command line options, make 'lfm' shell script don't show path error * when un/compressing files cursorbar must remain in the same file * when, at start, lfm can't enter into a dir due to directory permissions * crash if len(line) == width of cursorbar window * don't append '*' to historic in entries * fix cursor bar position after sorting - pyview: * when user hasn't permissions to read file, exit gracefully instead of crashing * last char in file is not showed Version 0.8 ("Close to Paradise") - 2002/03/04: + Implemented VFS feature to enter into .tar.gz and .tar.bz2 files + Panelize vfs option in find/grep implemented + Tree panel implemented + 'lfm' and 'pyview' are simple scripts now, not just a copy of the .py files. __init__.py contains global variables now + A new message window is used to show work in progress, in place of a message in status bar. 'run_thread' function has been cleaned too + Copy / move features now use my own function to walk trees, instead of 'shutil.copytree' which originates some problems and bugs + In ChangePermissions window, the cursor movement is circular now + Change copyright date to years 2001-2 + I don't need a crypt / uncrypt feature, so eliminated from TODO list + Many bugs fixes and functions rewrites. See ChangeLog for complete list - lfm: * if panel2 shows 'a' file in panel, and in panel1 'a' is moved or deleted, lfm crashes * after moving a file cursor goes to next directory as deletion does * catching an exception after not been able to copy => it crashed when trying to delete copied files * "messages.SelectItem, messages.FindfilesWin, messages.MenuWin, messages.ChangePerms: upperleft corner disappears" * findgrep: fix bug: if selected file has a ':' in name - pyview: * changing 'addch' by 'addstr' shows individual chars >= 0xA0 (meta chars) correctly, neither in reversed video or as 2 chars * "if wrap mode => fix prev/next page & up/down cursor". Now they move to screen lines, not to physical lines Version 0.7 ("Hello Darling, I'm here") - 2001/11/30: + 'pyview', a new pager/viewer for use with lfm or standalone, internally or externally. It is used as default pager too. Some features: Text / Hex view, backwards & forwards search, goto line/byte, un/wrap mode, documentation, ... + Rewrite 'show filesystems info' to use internal viewer + New 'run_thread' function in which almost every proccess is executed, so they can be stopped and there is a working signal too. 'do_something_on_file' does not use it + Implemented 'show file info' + Check errors when un/compressing + Support for .bz2 + Removed tar 'v' flag in un/compressing + Fixed completition, it should work perfectly now + Added Ctrl-D key to delete the whole content of the EntryLine + Implemented help: README, NEWS, TODO, ChangeLog or COPYING files + Added new function to show special files: html, graphics, ..., so we have defined new programs and file types too + Added new preference: show_output_after_exec, defaults to yes + Fix cursor position after deleting files + Default configuration is now saved inmediately + Many other bugs fixed again (see ChangeLog) Version 0.6 was never publically released Version 0.5 ("Last call to London") - 2001/08/07: + F2 file menu, added many functions + F9 general menu, added many functions + Implemented find and grep + File permissions, owner and group has a window now + Implemented preferences, edition has to be improved, of course, but loading and saving works + Added 'show filesystems info' feature + Now 'q' or F10 exits to current path, see proper README section + Default pager changed to 'less' + Documentation has been improved + 'setup.py' now installs docs + Many bug fixes and functions rewrites: - home and end keys work ok now - not all people use bash-type shells, so don't use 2>&1 - use popen2.popen3 instead of os.popen to catch messages - manage problems with move while files don't fit into destination - option 'b' does not exist in Solaris' 'du -s' command - fix a problem while moving files if destination has no enough space - many others Version 0.4 - 2001/07/19: + First public release lfm-2.3/README.pyview0000644000076400001440000000372611565705062014645 0ustar inigousers00000000000000================================= pyview - Last File Manager Viewer ================================= :Author: Ińigo Serna, inigoserna AT gmail DOT com :Version: 2.3, May 21th. 2011 :Home page: https://inigo.katxi.org/devel/lfm/ or http://www.terra.es/personal7/inigoserna/lfm/ :License: \(C\) 2001-11, Ińigo Serna This software has been realised under the `GPL License`__ version 3 or later, read the COPYING_ file that comes with this package for more information. There is NO WARRANTY. :Last update: Sat May 21 11:47:16 2011 .. contents:: Table of Contents Introduction ============ **pyview** is a pager (viewer) written in Python_. Though it was initially written to be used with lfm_, it can be used standalone as well. Since version 0.9 it can read from standard input too (eg. `$ ps efax | pyview -s`). Type `pyview -h` for more help. Keys ==== + **Movement** - cursor_up, k, K - cursor_down, j, J - previous page, backspace, Ctrl-B - next page, space, Ctrl-F - home, Ctrl-A: first line - end, Ctrl-E: last line - cursor_left - cursor_right + **Actions** - h, H, F1: help - w, W, F2: toggle un / wrap (only in text mode) - m, M, F4: toggle text / hex mode - g, G, F5: goto line / byte offset - /: find (new search) - F6: find previous or find - F7: find next or find - 0..9: go to bookmark # - b, B: set bookmark # - Ctrl-O: open shell 'sh'. Type 'exit' to return to pyview - q, Q, x, X, F3, F10, Ctrl-Q: exit Goto Line / Byte Offset ======================= Enter the line number / byte offset you want to show. If number / byte is preceded by `0x` it is interpreted as hexadecimal. You can scroll relative lines from the current position using '+' or '-' character. Find ==== Type the string to search. It ignores case. .. _python: http://www.python.org .. _COPYING: COPYING .. _GPL: http://www.gnu.org/licenses/licenses.html#GPL .. _lfm: README.html __ GPL_ lfm-2.3/lfm.10000644000076400001440000000275711565706554013315 0ustar inigousers00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH lfm "1" "May 21, 2011" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME \fBlfm\fR \- a powerful file manager for the UNIX console .SH SYNOPSIS .BI "lfm [ -h | --help ] " .br .BI " [ -d | --debug ]" .br .BI " [-1 | -2] [pathtodir1 [pathtodir2]]" .sp .SH DESCRIPTION .B Last File Manager is a powerful file manager for the UNIX console. It has a curses interface and it's written in Python. .SH OPTIONS .TP .B "\-1" start in 1-pane mode .TP .B "\-2" start in 2-panes mode (default) .TP .B "\-d, \-\-debug" create debug file .TP .B "\-h, \-\-help" show help .TP .B "pathtodir1" directory .TP .B "pathtodir2" directory to show in panel 2 .SH AUTHOR .B lfm was written by Ińigo Serna .PP This manual page was written by Sebastien Bacher for the Debian GNU/Linux system (but may be used by others). .SH SEE ALSO The full documentation which includes the keys descriptions is in /usr/share/doc/lfm/README. lfm-2.3/README0000644000076400001440000007546111565707661013337 0ustar inigousers00000000000000======================= lfm - Last File Manager ======================= :Author: IƱigo Serna, inigoserna AT gmail DOT com :Version: 2.3, May 21st. 2011 :Home page: https://inigo.katxi.org/devel/lfm/ or http://www.terra.es/personal7/inigoserna/lfm/ :License: \(C\) 2001-11, IƱigo Serna This software has been realised under the `GPL License`__ version 3 or later, read the COPYING_ file that comes with this package for more information. There is NO WARRANTY. :Last update: Sat May 21 11:46:07 2011 .. contents:: Table of Contents Introduction ============ **Last File Manager** is a powerful file manager for the UNIX console. It has a curses interface and it's written in Python. Some of the features you can find in *lfm*: - console-based file manager for UNIX platforms - 1-pane or 2-pane view - tabs - bookmarks - history - vfs for compressed files - dialogs with entry completion - PowerCLI, a command line interface with advanced features - fast access to the shell - direct integration of find/grep, df and other tools - color files by extension [Andrey Skvortsov] - support for different file names encodings - fast file viewer with text and binary modes - ...and many others From version 0.6 and up *lfm* package also contains **pyview**, a text / hex file viewer to be used with or without *lfm*. Read README.pyview_ for more info about it. Some screenshots: **lfm**: .. image:: lfm.png **pyview**: .. image:: pyview.png Type `lfm --help` or `pyview --help` for a complete list of options. When *lfm* starts the first time, it tries to discover the location of some apps in your system to configure itself automatically, but it's not perfect, so you should take a look to the configuration (`General Menu [F9] -> Edit Configuration [c]`) and change it according to your preferences. Consult `.lfmrc configuration file`_ section for in-depth knowledgement about all the settings and their meaning. Finally, take a look at TODO_ file to check known bugs and *not-implemented-yet* (tm) features. Requirements ============ *Lfm* and *Pyview* are written in Python_ and require curses module. It should run on Python v2.5 or higher, but as I'm only have v2.7 on my computers I haven't tested older versions. All modern UNIX flavours (Linux, \*BSD, Solaris, etc) should run it without problems. If they appear please notify me. Since version 0.90, *lfm* needs ncurses >= v5.x to handle terminal resizing. Python v2.5+ and ncurses v5.4+ to use wide characters. Note that python curses module should be linked against ncursesw library (instead of ncurses) to get wide characters support. This is the usual case in later versions of Linux distributions, but maybe not the case in older Linux or other UNIX platforms. Thus, expect problems when using multibyte file names (f.e. UTF-8 or latin-1 encoded) if your curses module isn't compiled against ncursesw. Anyway, I hope this issue will disappear with new releases of those platforms eventually. Consult `Files name encoding`_ section below for more information about support of different encodings. Development, Download, Installation =================================== .. sidebar:: **Files:** all releases :class: warning +---------+------------------+------------+ | Version | File | Date | +=========+==================+============+ | 2.3 | lfm-2.3.tar.gz_ | 2011/05/21 | +---------+------------------+------------+ | 2.2 | lfm-2.2.tar.gz_ | 2010/05/22 | +---------+------------------+------------+ | 2.1 | lfm-2.1.tar.gz_ | 2008/12/21 | +---------+------------------+------------+ | 2.0 | lfm-2.0.tar.gz_ | 2007/09/03 | +---------+------------------+------------+ | 1.0 | Never released | ~2006 | +---------+------------------+------------+ | 0.92 | Never released | ~2005 | +---------+------------------+------------+ | 0.91 | lfm-0.91.tar.gz_ | 2004/07/03 | +---------+------------------+------------+ | 0.90 | Never released | | +---------+------------------+------------+ | 0.9 | lfm-0.9.tar.gz_ | 2002/09/05 | +---------+------------------+------------+ | 0.8 | lfm-0.8.tar.gz_ | 2002/03/04 | +---------+------------------+------------+ | 0.7 | lfm-0.7.tar.gz_ | 2001/11/30 | +---------+------------------+------------+ | 0.6 | Never released | | +---------+------------------+------------+ | 0.5 | lfm-0.5.tar.gz_ | 2001/08/07 | +---------+------------------+------------+ | 0.4 | lfm-0.4.tar.gz_ | 2001/07/19 | +---------+------------------+------------+ Read about NEWS_ or ChangeLog_ **Last File Manager** development can be followed in the `BitBucket mercurial repository`__. 'lfm' is very easy to install, just keep next steps: 1. Download sources_ 2. Uncompress file 3. Build: `$ python setup.py build` 4. Install, as root: `# python setup.py install` 5. Run it: `$ lfm` 6. Edit settings: `General Menu [F9] -> Edit Configuration [c]` To let 'lfm' to change to panel's current directory after quiting with `q`, `Q` or `F10` keys, you must add next code to `/etc/bashrc` or to your `~/.bashrc`:: lfm() { /usr/bin/lfm "$@" # type here full path to lfm script LFMPATHFILE=/tmp/lfm-$$.path cd "`cat $LFMPATHFILE`" rm -f $LFMPATHFILE } If you don't use bash or csh shell, above lines could differ. Upgrading --------- If you upgrade from versions < 2.0, please remove first `~/.lfmrc` to regenerate a valid configuration as file format has changed. I advise you to make a backup copy before. Also, note that some keys have changed since previous versions. Read carefully following section. Key bindings ============ In this section you can find the complete list of key bindings. Global ------ + **Movement** - cursor_up, k - cursor_down, j - previous_page, backspace, Ctrl-B - next_page, space, Ctrl-F - home, Ctrl-A: first file - end, Ctrl-E: last file - cursor_left: upper dir - cursor_right: enter dir / vfs - Ctrl-S: go to file in current panel - Ctrl-L: center cursor in current panel - Ctrl-P, Ctrl-up: move cursor 1/4th of page upwards - Ctrl-N, Ctrl-down: move cursor 1/4th of page downwards + **Movement in non active pane** [#]_ - Alt/Shift-cursor_up, K - Alt/Shift-cursor_down, J - Alt/Shift-previous_page, B - Alt/Shift-next_page, F - Alt/Shift-home, A: first file - Alt/Shift-end, E: last file - Alt/Shift-cursor_left: upper dir - Alt/Shift-cursor_right: enter dir / vfs - P: move cursor 1/4th of page upwards - N: move cursor 1/4th of page downwards .. [#] Some key shorcuts combinations such as Alt or Shift + key may not work, as it depends on the capabilities of the terminal program you are using. F.e. it doesn't work in my computer console running Fedora 13 Linux, but it works under gnome-terminal in X. Your results could be different. Anyway, there are alternative shortcuts (K J B F A E), but not for everything. Consult your terminal emulation program documentation to check it. Also note that you can allow/disallow this navigation with Ctrl-W, being disabled by default. + **Changing directory** - g, G: go to directory - 0..9: go to bookmark # - Ctrl-D, Ctrl-\\: select bookmark # from menu - b: set bookmark # - Ctrl-Y: display directories history + **Panes** - tab: other pane - .: toggle display 1 or 2 panes - , Ctrl-U: change panes position (left->right, right->left) - =: show same directory in both panes + **Tabs** - :: new tab - !: close tab - <: go to left tab - >: go to right tab + **Selections** - insert: select item and go to next file - +: select group - -: deselect group - \*: invert selection + **Files / Directories operations** - t, T: touch file - l: create link - L: edit link - F2: rename file/dir/selection - F3: view file - F4: edit file - F5: copy file/dir/selection - F6: move file/dir/selection - F7: make directory - F8, del: delete file/dir/selection - enter: execute file, enter dir / vfs or view 'specially' depending on the extension of the regular file. It is executed in a thread that can be stopped and captures output - i, I: show file info + **Other** - #: show selected/all directories size - s, S: sort files - /: find/grep files - @: do something on file. Output is not captured - Ctrl-H: toggle show/hide dot files - Ctrl-W: toggle allow navigate in non-active pane - Ctrl-O: open shell. Type 'exit' or press Ctrl-D to return to lfm - Ctrl-X: toggle show/hide PowerCLI - Ctrl-T: tree - F12: file menu - @: do something on file(s) - i: file(s) info - p: change file permissions, owner, group - a: backup file. You can specify the extension to use in settings - d: diff file with backup. Can be unified, context or ndiff, configured in settings - z: Compress/uncompress file(s)... - g: gzip/gunzip - b: bzip2/bunzip2 - x: xz/unxz - x: uncompress .tar.gz, .tar.bz2, .tar.xz, .tar, .zip, .rar, .7z - u: uncompress .tar.gz, etc in other panel - c: compress directory to format... - g: .tar.gz - b: .tar.bz2 - x: .tar.xz - t: .tar - z: .zip - r: .rar - 7: .7z - F9: general menu - /: find/grep file - #: show directories size - s: sort files - t: tree - f: show filesystems info - o: open shell - c: edit configuration - r: regenerate programs - h: delete history - Ctrl-R: refresh screen - h, H, F1: help - q, Q, F10: exit changing to current path - Ctrl-Q: quit Dialogs ------- + ***EntryLine* window** - enter: return path or execute command in *PowerCLI* - Ctrl-C, ESC: quit - Ctrl-X: toggle show/hide in *PowerCLI* - insert, ... - special: - up, down: history - tab: change to next entry or button or complete in *PowerCLI* - Ctrl-T: complete - movement - home, Ctrl-A: move start of line - end, Ctrl-E: move end of line - left, Ctrl-B: move cursor left - right, Ctrl-F: move cursor right - Ctrl-P, Ctrl-left: move cursor previous special character - Ctrl-N, Ctrl-right: move cursor next special character - deletion - backspace, del - Ctrl-W: delete whole line - Ctrl-H: delete from start to position - Ctrl-K: delete from position to end of line - Ctrl-Q, Ctrl-Backspace: delete until previous special character - Ctrl-R, Ctrl-Del: delete until next special character - insertion - Ctrl-Z: restore original content (undo) - Ctrl-V: insert filename at position - Ctrl-S: insert path at position - Ctrl-O: insert other pane path at position - Ctrl-D, Ctrl-\: select bookmark at position - Ctrl-Y: select previous path at position - Ctrl-G: select historic (not PowerCLI) - Ctrl-G: select PowerCLI stored (from config) or history command (PowerCLI) + ***SelectItem* window** - up, k, K - down, j, J - previous page, backspace, Ctrl-B - next page, space, Ctrl-F - home, Ctrl-A - end, Ctrl-E - Ctrl-L: go to entry in the middle - 0..9: go to entry number # (0->10) - Ctrl-S: go to entry starting by... - enter: return entry - Ctrl-C, q, Q, ESC: quit + ***Permissions* window** - tab, cursor: move - in permissions: r, w, x, s, t to toggle read, write, exec, setuid or setgid, sticky bit - in user, group: space or enter to select - in recursive: space or enter to toggle - in buttons: space or enter to accept that action - everywhere: space or enter to accept, a to accept all, i to ignore and c, q, esc, Ctrl-c to cancel + ***Tree* panel** - down, j, K: down within current depth, without going out from directory - up, k, K: up within current depth, without going out from directory - previous page, backspace, Ctrl-B: same as up but page-size scroll - next page, space, Ctrl-F: same as down but page-size scroll - home, Ctrl-A: first directory - end, Ctrl-E: last directory - left: go out from directory - right: enter in directory - enter: return changing to directory - Ctrl-H: toggle show/hide dot files - Ctrl-C, q, Q, F10, ESC: quit Files name encoding =================== Since v2.0, *lfm* uses the encoding defined in the locale of your system if found, this will be UTF-8 likely. Since v2.2, *lfm* was rewritten to always use unicode strings internally, but employ terminal encoding (f.e. UTF-8) to interact with the user in input forms, to display contents, and to pass commands to run in shell. When *lfm* detects a file with invalid encoding name it asks the user to convert it (can be automatic with the proper option in the configuration). If not converted, *lfm* will display the file but won't operate on it. Please note there are some restrictions to support wide characters by now, as explained in the `Requirements`_ section. Virtual File Systems (VFS) ========================== You can navigate inside some special files (known as vfs files in *lfm*) just *entering into* them (press *enter* or *cursor_right* when the cursor bar is over one of these files). By now, supported types are `.tar.gz`, `.tar.bz2`, `.tar.xz`, `.zip`, `.rar`, and `.7z` files. The virtual directory name ('path_to_vfs_file#vfs/dir') is not propagated, so tmpdir (`/tmp/@6421.2/dir`) is showed in the copy/move/... dialogs or when view/edit/... a file, but this is just an estetic issue. When returning from one of such vfs files, a question dialog appears asking to allow you to regenerate the vfs file and update all changes (i.e., it is compressed again, so it could be slow in some machines), but `lfm` checks if it can do first, to avoid waste of time. This behaviour (rebuild or not rebuild, ask it or not) can be modified in the configuration file. By default the question is showed but it's set to *not regenerate vfs*. In case of `panelize` vfs type (after find/grep), deleted / moved files are not deleted / moved in real path. *lfm* doesn't implement remote vfs such as ssh, ftp, smb, webdav, ... This is a design criterion, we don't want to add external dependencies beyond python standard library. If you need to access remote file systems you could mount them using something like *fuse* and treat them as local directories from inside *lfm*. Look at the FAQ section to learn how. PowerCLI ======== *PowerCLI* is a command line interface with advanced features. To show it press Ctrl-X, and same again to hide, ENTER to run. Line contents are restored next time PowerCLI is showed. Some features: - uses *EntryLine*, so same key bindings are available. You can press Ctrl-V to paste file name for instance - completion (Ctrl-T or TAB key), both for system programs or path files and directories - loops to run the same command for all the selected files - variable substitution - can execute python code - persistent history between sessions - faster than opening a shell (Ctrl-O) *lfm* waits until the command is finished, showing output or error. You can stop the command if it seems to run forever. To run a command in background just add a "&" at the end of the command. This is useful to open a graphical program and come back to *lfm* quickly. But note you won't get any feedback about the command, even if it has been able to run or not. If the program you want to run needs the terminal (pyview, less, vim...), add "%" at the end of the command to let *lfm* know it must temporary free the terminal. Not passing it will fill your screen with garbage. Variables substitution ---------------------- They are a lot of variables you can use to simplify your command typing. Specially useful in loops to apply the same command to many files. - *$f*: file including extension - *$v*: same - *$E*: file without extension - *$e*: extension - *$F*: path/file.ext - *$d*: directory - *$o*: other panel directory - *$b#*: path in bookmark # - *$s*: all selected files, space-separated and enclosed between " - *$a*: all files, space-separated and enclosed between " - *$i*: loop index, starting at 1 - *$tm*: file modification time - *$ta*: file access time - *$tc*: file creation time - *$tn*: now Python execution ---------------- You can run a subset of python language code in a sandbox, but note this sandbox doesn't allow to import modules or access anything outside for security reasons. But DON'T TRUST IT'S SECURE. The sandbox is a very limited environment but powerful enough to satisfy common needs, even you can use the variables inside the code. Code must be enclosed between { }. Even you can use different code chunks in the same command. Consult the examples. Examples -------- * copy current file (or all selected files in a loop) to the other pane path:: cp $f $o * move selected files to path stored in bookmark #3 (no loop):: mv $s "$b3" We have enclosed $b3 between " here in case the path could contain spaces. * show all python files in a directory:: find /to/path -name "*.py" * open current file with gthumb in background and continue inmediately in *lfm*:: gthumb [Ctrl-v] & * find python files containing some special words in the background and redirect output to a file:: find . -name "*py" -print0 | xargs --null grep -EHcni "TODO|WARNING|FIXME|BUG" > output.txt & Note that if you run a command in the background you won't get any feedback by default, that's why we redirect the output to a file. * edit current file with vim in the console:: vim %F % Note you must end the line with a % if the command will use the terminal. * convert file (or all selected) to lowercase and change .bak extension to .orig. F.e., "FiLeFOO.bak" => "filefoo.orig":: mv $f {$f.lower().replace('.bak', '.orig')} * loop over selected files, copy to the other pane path and rename. F.e., if "/current/path/img1234.jpeg" is the 13th file in the selection and was created on 2010/07/22 at 19:43:22 => "/other/path/13. 20100722194322 - IMG1234.jpg":: cp $f "$o/{'%2.2d. %s - %s' % ($i, $tm.strftime('%Y%m%d%H%S'), $E.upper())}.jpg" Yes, a stupid convoluted example, but it clearly shows how powerful *PowerCLI* is. Also observe that as the target file name contain spaces, the whole destination must be surrounded with ". Random notes ------------ * Paths or filenames with spaces or special characters must be enclosed between ". Study last example above * Loops are only executed with selected files AND at least one of next variables present within the command: $f, $v, $F, $E, $i, $tm, $ta, $tc. Remember $a or $s never loop * Note the differences of running commands with trailing "&" vs. "%" vs. nothing * If cursor is at the beginning of line, completion will try system programs. If it is in any other position, it will try files or directories first and if nothing is found then programs * Although python code is executed inside a sandbox, it's not completely secure. Anyway, it's the same kind of security issues your system is exposed to when shell access is allowed .lfmrc configuration file ========================= Program preferences are saved in `~/.lfmrc` file. To configure *lfm* `General Menu [F9] -> Edit Configuration [c]`. To restore default configuration exit from all instances of *lfm* and delete `~/.lfmrc` file. In next subsections we will discuss the default configuration. Header ------ Always the same text. It is used to validate the configuration file:: ########## lfm - Last File Manager Configuration File ########## [Programs] ---------- Default programs *lfm* use for common file types:: audio: mplayer ebook: FBReader editor: vi graphics: gthumb pager: pyview pdf: evince shell: bash video: mplayer web: firefox [File Types] ------------ File extensions associated with default programs. See previous subsection:: audio: ogg, flac, mp3, wav, au, midi ebook: epub, chm, mobi, prc, azw, lit, fb2 graphics: png, jpeg, jpg, gif, tiff, tif, xpm, svg pdf: pdf, ps video: mpeg, mpg, avi, asf, ogv, flv, mkv web: html, htm [Bookmarks] ----------- User-defined 10 bookmarks. / by default:: 0: / 1: / 2: / 3: / 4: / 5: / 6: / 7: / 8: / 9: / [PowerCLI commands] ------------------- User-defined 10 favourites PowerCLI stored commands:: 0: mv "$f" "{$f.replace('', '')}" 1: pyview "$f" % 2: find "$d" -name "*" -print0 | xargs --null grep -EHcni "TODO|WARNING|FIXME|BUG" 3: find "$d" -name "*" -print0 | xargs --null grep -EHcni "TODO|WARNING|FIXME|BUG" >output.txt & 4: cp $s "$o" 5: 6: 7: 8: 9: [Colors] -------- User interface colors. Each entry represents a different entity. Allowed colors are: black, blue, cyan, green, magenta, red, white and yellow:: archive_files: yellow black buttons: yellow red cli_prompt: blue black cli_text: white black current_file: blue cyan current_file_otherpane: black white current_selected_file: yellow cyan current_selected_file_otherpane: yellow white data_files: magenta black directories: green black document_files: blue black error_messages1: white red error_messages2: black red exe_files: red black file_info: red black files: white black graphics_files: magenta black help: green black media_files: blue black messages: magenta cyan selected_file: yellow black source_files: cyan black tabs: white blue temp_files: white black title: yellow blue [Options] --------- Main settings:: # automatic_file_encoding_conversion: never = -1, ask = 0, always = 1 # sort: None = 0, byName = 1, byName_rev = 2, bySize = 3, # bySize_rev = 4, byDate = 5, byDate_rev = 6 automatic_file_encoding_conversion: 0 color_files: 1 detach_terminal_at_exec: 1 grep_ignorecase: 1 grep_regex: 1 manage_otherpane: 0 num_panes: 2 rebuild_vfs: 0 save_conf_at_exit: 1 save_history_at_exit: 1 show_dotfiles: 1 show_output_after_exec: 1 sort: 1 sort_mix_cases: 1 sort_mix_dirs: 0 * *automatic_file_encoding_conversion*: Automatically convert filenames when wrong encoding found? Default 1 (yes) * *color_files*: Colorize files by extension? Default 1 (yes) * *detach_terminal_at_exec*: Detach terminal at execute? Default 1 (yes) * *grep_ignorecase*: Ignore case in grep? Default 1 (yes) * *grep_regex*: Use regex as grep pattern? Default 1 (yes) * *manage_otherpane*: Allow cursor navigation for the non-active panel? Default 0 (no), but can be enabled with Ctrl-W * *num_panes*: Number of panels to show? Default 2 * *rebuild_vfs*: Rebuild vfs? Useful if automatic in confirmations->ask_rebuild_vfs. Default 0 (no) * *save_conf_at_exit*: Save configuration at exit? Default 1 (yes) * *save_history_at_exit*: Save history at exit for future sessions? Default 1 (yes) * *show_dotfiles*: Show .files? Default 1 (yes) * *show_output_after_exec*: Show output after exec? Default 1 (yes) * *sort*: Sort type. Default 1 (sort by name) * *sort_mix_cases*: Mix upper and lower case files in sort? Default 1 (yes) * *sort_mix_dirs*: Mix files and directories in sort? Default 0 (no) [Misc] ------ Settings which require a string value:: # diff_type: context, unified, ndiff backup_extension: .bak diff_type: unified * *backup_extension*: Backup file extensions? Default .bak * *diff_type*: Diff output format? Default unified [Confirmations] --------------- These settings indicate whether the user will be prompted in these actions:: ask_rebuild_vfs: 1 delete: 1 overwrite: 1 quit: 1 *ask_rebuild_vfs*: when abandoning compressed files, prompt if we should rebuild the file in case we've modified contents. [Files] ------- File extensions for different file types. Used to color them:: archive_files: .gz, .bz2, .xz, .tar, .tgz, .Z, .zip, .rar, .7z, .arj, .cab, .lzh, .lha, .zoo, .arc, .ark, .rpm, .deb data_files: .dta, .nc, .dbf, .mdn, .db, .mdb, .dat, .fox, .dbx, .mdx, .sql, .mssql, .msql, .ssql, .pgsql, .cdx, .dbi, .sqlite document_files: .txt, .text, .rtf, .odt, .odc, .odp, .abw, .gnumeric, .sxw, .sxc, .sxp, .sdw, .sdc, .sdp, .ps, .pdf, .djvu, .dvi, .bib, .tex, .epub, .chm, .prc, .mobi, .azw, .lit, .imp, .xml, .xsd, .xslt, .sgml, .dtd, .html, .shtml, .htm, .css, .mail, .msg, .letter, .ics, .vcs, .vcard, .lsm, .po, .man, .1, .info, .doc, .xls, .ppt, .pps graphics_files: .jpg, .jpeg, .gif, .png, .tif, .tiff, .pcx, .bmp, .xpm, .xbm, .eps, .pic, .rle, .ico, .wmf, .omf, .ai, .cdr, .xcf, .dwb, .dwg, .dxf, .svg, .dia media_files: .mp2, .mp3, .mpg, .ogg, .flac, .mpeg, .wav, .avi, .asf, .mov, .mol, .mpl, .xm, .med, .mid, .midi, .umx, .wma, .acc, .wmv, .swf, .flv, .ogv source_files: .c, .h, .cc, .hh, .cpp, .hpp, .py, .pl, .pm, .inc, .rb., .asm, .pas, .f, .f90, .pov, .m, .pas, .cgi, .php, .phps, .tcl, .tk, .js, .java, .jav, .jasm, .diff, .patch, .sh, .bash, .awk, .m4, .el, .st, .mak, .sl, .ada, .caml, .ml, .mli, .mly, .mll, .mlp, .prg temp_files: .tmp, .$$$, ~, .bak FAQ === + **How and why lfm born?** Everything is explained in next sections. `list.com` and `midnight commander` were the muses who guided. + **Isn't python slow? why develop lfm on python?** No. It's fast enough. And programming in python is funny. + **I've been reading the sources and you don't use newer python features like ternary operator, with statement, and many others** We want to mantain compatibility with python v2.4 by now. Btw, you can find some of these interesting new features in the TODO_ file. + **Does it work with Python v3.x?** No. We'll support Python v3.x when it is mainline (read, when my linux distribution of choice package it as default). + **lfm does not change to current directory after quiting** This can't be made inside the program, but you could get it using the shell tip mentioned in `Development, Download, Installation`_ section. + **Why doesn't lfm implement remote vfs such as ssh, ftp, smb, webdav, ...?** One of the design goals for *lfm* is simplicity, we don't want to add external dependencies beyond python standard library. Nevertheless you can use something like *fuse* to mount those remote volumes anyway. To use fuse with ssh you need *fuse* and *sshfs* packages installed on your system:: $ mkdir mount_point_for_ssh_server $ sshfs user@ip_or_hostname:/path mount_point_for_ssh_server For ftp you need *fuse* and *curlftpfs*:: $ mkdir mount_point_for_ftp_server $ curlftpfs ftp://user:password@ip_or_hostname mount_point_for_ftp_server For webdav you need *fuse* and *wdfs* or davfs2 (non fuse based):: $ mkdir mount_point_for_webdav_server $ wdfs https://user:password@server.org/webdav_dir mount_point_for_webdav_server For smb take a look at *fuse-smb*. And to umount:: $ fusermount -u mount_point $ rm -rf mount_point + **Request: add advanced file rename tool** Use *PowerCLI*, it's much... uhmmm... powerful! + **Keybindings customization?** Not for the near future. Anyway, you can modify `actions.py` in the sources if it's so important for you. + **Mouse support? UI to configure settings?** I'm afraid we speak different languages. + **When will be support for internationalization?** If we are talking about translating *lfm*, the answer is mostly never. Ncurses programming makes very difficult to control the length of every text for every possible language translation. If you mean support for file names in foreign languages and encodings then it's almost here already. + **Some Chinese, Japanese or Korean files make lfm crash** Known issue. The characters of these languages span over 2 cells, so it's not possible for *lfm* to guess the real width they need. We expect to solve this in a near future as we are studying different methods. + **[Any other question / feature request]** Consult if it's mentioned in the TODO_ file and/or send me an email. History ======= Many many years ago I began to write a program like this in C, but after some weeks of coding I never finished it... I'm too lazy, yes. Then I saw the light and I started writing `lfm` to learn python_. Code evolved and application got more and more features, used by many people around the world on different UNIX systems. But after the release of version 0.91 (June 2004) they were not more releases. Not that I had stopped working on *lfm*, new code was written, tested, rewritten again... silently... but different reasons made me to postpone public releases... code refactoring, a new essential feature, source cleaning, a wedding, a child, ahem... more code refactoring.... Anyway, from now on I'll do my best to release often. Thanks ====== Thanks are obviously due to the whole python community, specially to GvR (of course! ;-) and all the people who answered my questions in c.l.p. It's a great pleasure to code in a language like this. Alexei Gilchrist, for his cfm program from which I took some ideas. `Midnight Commander`__ developers, whose program was the guide. `Vernon D. Buerg's list.com`__, the best program ever coded (well, just after emacs ;-). And also to all the people who have contributed with ideas, reporting bugs and code over these years: Antoni Aloy, Sebastien Bacher, Grigory Bakunov, Luigi M. Bianchi, Hunter Blanks, Witold Bołt, Fabian Braennstroem, Jason Buberel, Ondrej Certik, Kevin Coyner, Tim Daneliuk, Mike Dean, ArnĆ„ DG, Christian Eichert, Steve Emms, Murat Erten, Daniel Echeverry, Luca Falavigna, Stephen R. Figgins, f1ufx, Francisco Gama, Vlad Glagolev, Ana Beatriz Guerrero Lopez, Kelly Hopkins, Tjabo Kloppenburg, Zoran Kolic, Max Kutny, Martin Lüethi, James Mills, Bartosz Oler, Piotr Ozarowski, Mikhail A. Pokidko, Jerome Prudent, Mikhail Ramendik, Rod, Daniel T. Schmitt, Chengqi Song, Robin Siebler, Andrey Skvortsov, Espartaco Smith, Jƶrg Sonnenberger, Martin Steigerwald, Joshua Tasker, Tim TerlegĆ„rd, Edd Thompson, Walter van den Broek, Jesper Vestergaard, Xin Wang, Alejandro Weil, Yellowprotoss, Hai Zaar and many others... You have made posible to run *lfm* in all those platforms! .. _sources: lfm-2.3.tar.gz .. _README.pyview: README.pyview.html .. _TODO: TODO .. _NEWS: NEWS .. _ChangeLog: ChangeLog .. _COPYING: COPYING .. _GPL: http://www.gnu.org/licenses/licenses.html#GPL .. _mc: http://www.ibiblio.org/mc/ .. _buerg: http://www.buerg.com/ .. _python: http://www.python.org .. _BITBUCKETREPO: https://bitbucket.org/inigoserna/lfm __ GPL_ __ BITBUCKETREPO_ __ mc_ __ buerg_ .. _lfm-2.3.tar.gz: lfm-2.3.tar.gz .. _lfm-2.2.tar.gz: lfm-2.2.tar.gz .. _lfm-2.1.tar.gz: lfm-2.1.tar.gz .. _lfm-2.0.tar.gz: lfm-2.0.tar.gz .. _lfm-0.91.tar.gz: lfm-0.91.tar.gz .. _lfm-0.9.tar.gz: lfm-0.9.tar.gz .. _lfm-0.8.tar.gz: lfm-0.8.tar.gz .. _lfm-0.7.tar.gz: lfm-0.7.tar.gz .. _lfm-0.5.tar.gz: lfm-0.5.tar.gz .. _lfm-0.4.tar.gz: lfm-0.4.tar.gz lfm-2.3/ChangeLog0000644000076400001440000035235511565710306014217 0ustar inigousers00000000000000Sat May 21 12:13:23 2011 IƱigo Serna * setup.py: updated to v2.3 * lfm/lfm.py: * lfm/pyview.py: * NEWS: * README: * README.pyview: * lfm.1: * pyview.1: Fri May 20 23:29:56 2011 IƱigo Serna * lfm/messages.py (SelectItem.manage_keys): new key shortcuts: Ctrl-L, 0..9 * README (Key bindings): update documentation Fri May 20 23:08:20 2011 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): Ctrl-G not PowerCLI: select historic * README (Key bindings): update documentation Fri May 20 22:52:24 2011 IƱigo Serna * lfm/messages.py (Entry.run): refactor "add text to history" code * lfm/messages.py (DoubleEntry.run): * lfm/messages.py (add_to_history): new function Fri May 20 22:32:42 2011 IƱigo Serna * lfm/lfm.py (Vfs.get_fileinfo_str_long): fixed bug: crash when filenane length is large Fri May 20 22:24:19 2011 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): fixed bug: don't allow duped entries in history Fri May 20 22:03:01 2011 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): delete old contents if bookmark or history selected, except in PowerCLI Fri May 20 22:02:44 2011 IƱigo Serna * lfm/messages.py (SelectItem.__init__): fixed bug: check w > 10 Fri May 20 21:03:22 2011 IƱigo Serna * README (.lfmrc configuration file): documented configuration file Mon May 16 21:58:06 2011 IƱigo Serna * lfm/config.py (Config.__init__): use unicode * lfm/config.py (Config.save): work with unicode, only convert to encoding when saving Mon May 16 21:51:41 2011 IƱigo Serna * lfm/messages.py (MenuWin.__init__): fix bug: ellipsize entries if bigger than screen width * lfm/messages.py (MenuWin.show): Mon May 16 21:34:50 2011 IƱigo Serna * lfm/messages.py (crop_line): new function Mon May 16 21:15:08 2011 IƱigo Serna * lfm/messages.py (SelectItem.__init__): fix bug: ellipsize entries if bigger than screen width * lfm/messages.py (SelectItem.show): Mon May 16 18:46:43 2011 IƱigo Serna * lfm/actions.py (tree): "disable" colors of active panel, "enable" at end Mon May 16 17:17:58 2011 IƱigo Serna * README: added forgotten reference to "delete history" Mon May 16 17:15:01 2011 IƱigo Serna * lfm/actions.py (general_menu): implemented: delete history Mon May 16 17:13:21 2011 IƱigo Serna * lfm/messages.py (win): changed message colors Mon May 16 17:12:21 2011 IƱigo Serna * lfm/messages.py (HISTORY_FILE): clean code * lfm/messages.py (DEFAULT_HISTORY): * lfm/lfm.py (lfm_start): * lfm/lfm.py (Lfm.quit_program): Mon May 16 16:25:51 2011 IƱigo Serna * lfm/actions.py (findgrep): find file->goto_file: move to correct page Mon May 16 15:50:18 201 1 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): PowerCLI: implemented insert stored (from config) or history command, key shortcut Ctrl-G * lfm/messages.py (Entry.run): * lfm/messages.py (DoubleEntry.run): Mon May 16 15:41:56 2011 IƱigo Serna * lfm/config.py: add PowerCLI stored favs commands * lfm/config.py: powercli_favs * lfm/config.py (Config.load): * lfm/config.py (Config.save): Mon May 16 15:35:49 2011 IƱigo Serna * lfm/config.py: use ConfigParser instead of SafeConfigParser * lfm/config.py (Config.load): * lfm/config.py (Config.load): load default config at start * lfm/config.py (Config.save): save default config if nothing configured Mon May 16 02:48:09 2011 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): use __insert_item instead of select_item * lfm/messages.py (EntryLine.manage_keys.__select_item): delete unused function Mon May 16 02:44:31 2011 IƱigo Serna * lfm/messages.py (SelectItem.__init__): accept title * lfm/messages.py (SelectItem.show): * lfm/messages.py (EntryLine.manage_keys.__insert_item): * lfm/messages.py (EntryLine.manage_keys): use title in SelectItem Mon May 16 01:19:37 2011 IƱigo Serna * lfm/lfm.py (PowerCLI.display): only store one copy of the same entry in history * lfm/messages.py (Entry.run): * lfm/messages.py (DoubleEntry.run): Mon May 16 00:27:24 2011 IƱigo Serna * lfm/lfm.py (HISTORY_FILE): history file * lfm/lfm.py (lfm_start): load history at start * lfm/lfm.py (Lfm.quit_program): save history at end * lfm/config.py: new option: save_history_at_exit Mon May 16 00:26:13 2011 IƱigo Serna * lfm/config.py (CONFIG_FILE): clean code * lfm/config.py (Config.__init__): Sun May 15 23:04:45 2011 IƱigo Serna * lfm/messages.py: use different types of history (path, file, glob, grep, exec, cli) for the different forms and actions * lfm/messages.py (EntryLine.__init__): * lfm/messages.py (EntryLine.manage_keys): * lfm/actions.py (doEntry): * lfm/actions.py (doDoubleEntry): * lfm/lfm.py (PowerCLI.display): use 'cli' history * lfm/actions.py (goto_dir): use 'path' history * lfm/actions.py (create_link): * lfm/actions.py (edit_link): * lfm/actions.py (copy): * lfm/actions.py (move): * lfm/actions.py (goto_file): use 'file' history * lfm/actions.py (touch_file): * lfm/actions.py (create_link): * lfm/utils.py (ProcessLoopRename.ask_confirmation): * lfm/actions.py (make_dir): * lfm/actions.py (findgrep): use 'glob' history * lfm/actions.py (select_group): * lfm/actions.py (deselect_group): * lfm/actions.py (findgrep): use 'grep' history * lfm/actions.py (do_execute_file): use 'exec' history * lfm/actions.py (do_something_on_file): Sun May 15 22:57:10 2011 IƱigo Serna * lfm/messages.py (Entry.__init__): clean code * lfm/messages.py (Entry.run): * lfm/messages.py (DoubleEntry.__init__): * lfm/messages.py (DoubleEntry.run): Sun May 15 22:48:57 2011 IƱigo Serna * lfm/lfm.py (MAX_TAB_HISTORY): clean code * lfm/lfm.py (Vfs.__init__): * lfm/lfm.py (Vfs.init_dir): * lfm/actions.py (select_history): Sun May 15 22:34:25 2011 IƱigo Serna * lfm/actions.py (create_link): fix bug: don't show error if canceled * lfm/actions.py (edit_link): fix bug: don't show error if canceled Sun May 15 18:41:46 2011 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): added insert bookmark at pos, key shortcuts Ctrl-D, Ctrl-\ * lfm/messages.py (EntryLine.manage_keys): added insert previous path at pos, key shortcut Ctrl-Y * lfm/messages.py (EntryLine.manage_keys): change insert path at pos, now Ctrl-S * lfm/messages.py (EntryLine.manage_keys.__select_item): new function * lfm/messages.py (EntryLine.manage_keys.__insert_item): new function * lfm/messages.py (EntryLine.manage_keys): clean code * README: update documentation Sat May 14 19:08:53 2011 IƱigo Serna * lfm/actions.py (keytable): change PowerCLI key shortcut, now Ctrl-X * lfm/messages.py (EntryLine:manage_keys): * README (Key bindings, PowerCLI): updated documentation Sat May 14 18:37:27 2011 IƱigo Serna * lfm/config.py (files_ext): added new extensions: pyw, vala, glade, ui, docx, xlsx, pptx Sat May 14 18:22:46 2011 IƱigo Serna * lfm/actions.py: change PowerCLI key shortcut, now Ctrl-G * lfm/messages.py (EntryLine.manage_keys): * README (Key bindings, PowerCLI): updated documentation Sat May 14 17:46:18 2011 IƱigo Serna * lfm/files.py (sort_dir): clean and optimize code * lfm/files.py (__do_sort): use python v2.4+ "key" arg in sorted Sat May 14 12:15:47 2011 IƱigo Serna * lfm/lfm.py (PowerCLI.__check_loop): update code to use python v2.5+ "any" Sat May 14 12:12:22 2011 IƱigo Serna * lfm/lfm.py (Lfm.run): update code to use python v2.5+ ternary operator * lfm/lfm.py (PowerCLI.display): * lfm/lfm.py (PowerCLI.__run): * lfm/lfm.py (Vfs.init_dir): * lfm/actions.py (toggle_manage_otherpane): * lfm/messages.py (EntryLine.manage_keys.__prev_step): * lfm/messages.py (EntryLine.manage_keys.__next_step): * lfm/files.py (__get_filetype): * lfm/utils.py (get_shell_output3_subprocess): * lfm/config.py (Config.check_progs): * lfm/pyview.py (InternalView.__validate_buf): Sun May 8 18:28:26 2011 IƱigo Serna * lfm/compress.py (PackagerTAR): implemented tar un/compress * lfm/compress.py (packagers): * lfm/compress.py (packagers_by_type): * lfm/compress.py (PackagerBase.build_compress_cmd): we need to check if .tar file * lfm/utils.py (do_compress_uncompress_file): same * lfm/actions.py (file_menu): ui changes for un/compress * README: update documentation Sun May 8 16:26:55 2011 IƱigo Serna * lfm/utils.py (do_find): fix bug: show wrong matches if results contain directories or files with spaces Sun May 8 16:02:24 2011 IƱigo Serna * lfm/utils.py (do_findgrep): fix bug: pass "-type f" to find as ".#filename" are temporary emacs files/links that break search Sun May 8 15:49:48 2011 IƱigo Serna * lfm/actions.py (do_show_fs_info): clean code Sun May 8 01:52:14 2011 IƱigo Serna * README (summary): added inigo.katxi.org as home page * README (requirements): mention lfm requires python v2.5+ now * README (download): added link to BitBucket repository * README (faq): added example for fuse + webdav Sun May 8 01:09:50 2011 IƱigo Serna * lfm/lfm.py (PowerCLI.execute): "except XXX as YYY" only valid for python v2.6+ Sun May 8 00:51:00 2011 IƱigo Serna * lfm/actions.py (file_menu): we should not compress '..' Sun May 8 00:34:56 2011 IƱigo Serna * lfm/actions.py (rename): fix bug: rename/backup '..' crashes * lfm/actions.py (backup_file): Sun May 8 00:08:19 2011 IƱigo Serna * lfm/messages.py (get_a_key): fix error in last commit Sat May 7 23:44:27 2011 IƱigo Serna * lfm/actions.py (enter): fix bug: dark and ugly bug regarding going up to a parent dir which is a link to another dir. Reported by Xin Wang Sat May 7 23:10:52 2011 IƱigo Serna * lfm/actions.py (do_execute_file): fix bugs: encoded or not encoded. Reported by Xin Wang * lfm/messages.py (BaseWindow.init_ui): be sure we use encoded strings * lfm/messages.py (get_a_key): Mon Mar 7 22:58:40 2011 IƱigo Serna * lfm/files.py (get_fileinfo): fix bug: time string could contain non-ascii chars (f.e. MƤrz for March) so we must convert to unicode * lfm/files.py (get_fileinfo_dict): use unicode instead of str * lfm/lfm.py (Vfs.get_fileinfo_str_short): * lfm/lfm.py (Vfs.get_fileinfo_str_long): Mon Mar 7 00:45:33 2011 IƱigo Serna * lfm/lfm.py (StatusBar.__init__): fix bug: ncurses 5.8 doesn't accept 0 as width or heigth * lfm/lfm.py (PowerCLI.__init__): * lfm/pyview.py (InternalView.init_curses): * lfm/pyview.py (FileView.init_curses): Thu Jul 29 22:59:14 2010 IƱigo Serna * lfm/config.py (filetypes): added .mkv files * lfm/config.py (files_ext): Thu Jul 29 21:09:48 2010 IƱigo Serna * lfm/lfm.py (PowerCLI.__run): run in background implemented * lfm/utils.py: add run_in_background_system and run_in_background_subprocess * lfm/utils.py (run_in_background_system): new function * lfm/utils.py (run_in_background_subprocess): * README (PowerCLI): added comments about running commands in background and a new example Tue Jul 27 00:06:36 2010 IƱigo Serna * lfm/lfm.py (PowerCLI.__check_loop): clean code: add python 2.5+ variant Mon Jul 26 17:42:33 2010 IƱigo Serna * README: 'lfm' shell function: change "$*" to "$@" to properly handle paths containing spaces * lfm/lfm.py (lfm_start): remove unneeded code Sat Jul 24 22:52:52 2010 IƱigo Serna * lfm/lfm.py (PowerCLI): PowerCLI beautiful implementation Sat Jul 24 22:51:09 2010 IƱigo Serna * lfm/lfm.py (Lfm.__init__): init cli * lfm/lfm.py (Lfm.init_ui): init cli colours * lfm/lfm.py (Lfm.display): check if statusbar or cli Sat Jul 24 22:48:28 2010 IƱigo Serna * lfm/utils.py (get_shell_output3_popen): new functions: get error from a command * lfm/utils.py (get_shell_output3_subprocess): * lfm/utils.py (run_on_current_file): add get_shell_output3_* Sat Jul 24 22:43:15 2010 IƱigo Serna * lfm/messages.py (historic_cli): new file variable * lfm/messages.py (EntryLine.__init__): accept cli flag, differentiate between Entry or PowerCLI * lfm/messages.py (EntryLine.show): different behaviours for Entry or PowerCLI * lfm/messages.py (EntryLine.manage_keys): Sat Jul 24 22:40:55 2010 IƱigo Serna * lfm/files.py (SYSTEM_PROGRAMS): new file variable * lfm/files.py (get_binary_programs): new function: get system programs * lfm/files.py (complete_programs): new function: complete system programs Sat Jul 24 22:40:15 2010 IƱigo Serna * lfm/config.py: added cli_prompt and cli_text colours Sat Jul 24 22:39:08 2010 IƱigo Serna * lfm/actions.py: added show_cli keybinding to Ctrl-C * lfm/actions.py (show_cli): new function Sat Jul 24 22:35:15 2010 IƱigo Serna * README (Introduction): add PowerCLI to features * README (PowerCLI): new section * README (Key bindings): reorganize and fixed some errors * README (FAQ): add an entry on advanced renaming tool Sat Jul 24 22:25:58 2010 IƱigo Serna * lfm/actions.py (open_shell): clean code * lfm/actions.py (file_menu): * lfm/actions.py (do_view_file): * lfm/utils.py (run_shell_popen): Fri Jul 16 17:08:18 2010 IƱigo Serna * lfm/pyview.py (InternalView.__validate_buf): fix bug: when number of lines == window height Thu Jul 15 00:36:17 2010 IƱigo Serna * lfm/pyview.py (FileView.move_down): fix bug: last line and wrap * lfm/pyview.py (FileView.move_pagenext): Wed Jul 14 23:35:35 2010 IƱigo Serna * lfm/lfm.py (Lfm.run): clean code Wed Jul 14 22:18:59 2010 IƱigo Serna * lfm/utils.py (do_findgrep): use a faster find/grep combination Wed Jul 14 00:20:38 2010 IƱigo Serna * lfm/utils.py (ProcessLoopCopy.prepare_args): fix bug: copy/move "/file" to "/anydir/anyplace" fails, trying to copy/move to "/" * lfm/utils.py (ProcessLoopCopy.process_response): Mon Jul 12 23:59:04 2010 IƱigo Serna * lfm/actions.py (do_show_file_info): removed unneeded "import stat" Mon Jul 12 22:42:33 2010 IƱigo Serna * lfm/config.py (files_ext): added .lua to source_files Mon Jul 12 22:30:57 2010 IƱigo Serna * lfm/config.py (Config.save): sort entries when saving Mon Jul 12 22:03:31 2010 IƱigo Serna * lfm/utils.py (do_findgrep): preference options for ignorecase and regex, sort results and show them as FILE:lineno * lfm/utils.py (do_find): * lfm/actions.py (findgrep): * lfm/config.py (options): new options: grep_ignorecase, grep_regex Mon Jul 12 01:25:41 2010 IƱigo Serna * lfm/lfm.py (Pane.__calculate_dims): use new messages.error syntax * lfm/lfm.py (TabVfsinit): * lfm/actions.py (set_bookmark): ... and improve error message * lfm/actions.py (select_historic): * lfm/actions.py (new_tab): * lfm/actions.py (close_tab): * lfm/actions.py (touch_file): * lfm/actions.py (create_link): * lfm/actions.py (edit_link): * lfm/actions.py (backup_file): * lfm/actions.py (diff_file): * lfm/actions.py (__copymove_helper): * lfm/actions.py (copy): * lfm/actions.py (move): * lfm/actions.py (make_dir): * lfm/actions.py (delete): * lfm/actions.py (do_special_view_file): * lfm/actions.py (do_execute_file): * lfm/actions.py (__do_change_perms): * lfm/actions.py (do_show_fs_info): * lfm/actions.py (findgrep): * lfm/utils.py (ProcessLoopBase.exec_file): * lfm/utils.py (ProcessLoopBase.run_pre): * lfm/utils.py (ProcessLoopUnCompress.process_response): * lfm/utils.py (ProcessLoopRename.process_response): * lfm/utils.py (ProcessLoopBackup.process_response): * lfm/utils.py (ProcessLoopBase_2.run): * lfm/utils.py (ProcessLoopCopy.process_response): * lfm/utils.py (ProcessLoopDelete.process_response): * lfm/utils.py (ProcessFunc.process_result): * lfm/utils.py (ProcessFunc.run): * lfm/vfs.py (init): * lfm/vfs.py (copy): * lfm/vfs.py (regenerate_file): * lfm/vfs.py (pan_init): * lfm/vfs.py (pan_copy): * lfm/vfs.py (pan_regenerate): * lfm/pyview.py (FileView.__init__): * lfm/pyview.py (FileView.__find): * lfm/pyview.py (FileView.__find_next): * lfm/pyview.py (FileView.__find_prev): * lfm/pyview.py (FileView.goto): Sun Jul 11 19:02:09 2010 IƱigo Serna * lfm/messages.py (error): rewritten * lfm/messages.py (CommonWindow.__init__): * lfm/messages.py (BaseWindow.init_ui): Wed Jun 23 01:34:00 2010 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): rewritten. New key bindings to move, delete and insert filename and paths Some old keybindings have changed * README: added new keybindings in EntryLine Sun Jun 20 23:21:12 2010 IƱigo Serna * README (FAQ): fix an error in curlftpfs documentation Sun Jun 20 19:18:38 2010 IƱigo Serna * lfm/pyview.py (FileCache.__init__): fix bug: last char is not shown Mon May 24 12:07:41 2010 IƱigo Serna * README (faq): added examples of using fuse + sshfs and curlftpfs Sat May 22 23:21:37 2010 IƱigo Serna * lfm/actions.py (file_menu): new action, diff between file and backup * lfm/actions.py (diff_file): new function * lfm/config.py (misc): new option diff_type * lfm/config.py (Config.load): * lfm/config.py (Config.save): * README: added info about diff action Sat May 22 19:43:08 2010 IƱigo Serna * lfm/utils.py: change code with python v2.4+ counterpart: sorted() * lfm/files.py (get_mount_points): * lfm/pyview.py (FileView.__find_prev): Sat May 22 18:01:30 2010 IƱigo Serna * lfm/actions.py (file_menu): reorganize "un/compress file" and "compress directory to format..." menu Sat May 22 17:46:53 2010 IƱigo Serna * lfm/messages.py (get_a_key): fix a typo Sat May 22 11:23:21 2010 IƱigo Serna * README: Released version 2.2 * README.pyview: * NEWS: Sat May 22 11:21:01 2010 IƱigo Serna * README: requires python 2.4 or up * lfm/lfm: * lfm/pyview: Fri May 21 23:20:19 2010 IƱigo Serna * lfm/__init__.py (sysprogs): added xz * lfm/config.py (files_ext): added .xz to archive_files * lfm/compress.py (packagers_by_type): added xz, txz * lfm/compress.py (PackagerBase.build_compress_cmd): support xz * lfm/compress.py (PackagerTXZ): new classes * lfm/compress.py (PackagerXZ): * lfm/compress.py (packagers): * lfm/actions.py (file_menu): added new 'h' and 'e' compress options h: un/xz, e: compress directory to .tar.xz * README: added info about xz * NEWS: Fri Aug 28 14:32:26 2009 IƱigo Serna * lfm/actions.py (move): fix bug: don't delete source files if error or if we don't overwrite destinations * lfm/files.py (PathContents.remove_files): new method: remove given files * lfm/utils.py (ProcessLoopCopy.process_response): add file with error or not overwritten to return list Fri Aug 28 13:07:59 2009 IƱigo Serna * README (FAQ): added question about python 3.x support Fri Aug 28 12:19:13 2009 IƱigo Serna * lfm/config.py: modified automatic_file_encoding_conversion behaviour Now it's under "options", not under "confirmations" never = -1, ask = 0, always = 1 * lfm/config.py (Config.load): * lfm/config.py (Config.save): * lfm/utils.py (ask_convert_invalid_encoding_filename): Fri Aug 28 12:02:27 2009 IƱigo Serna * lfm/__init__.py: fix bug: require valid encoding before running lfm Mon Aug 24 14:50:52 2009 IƱigo Serna * NEWS: update file with v2.2 information Mon Aug 24 14:12:37 2009 IƱigo Serna * lfm/config.py (defaultprogs): add new "ebook" category, deleted "ps" * lfm/config.py (filetypes): added ebook filetypes * lfm/config.py (files_ext): added new formats in document_files (epub, mobi, prc, azw, lit) and media_files (ogv, flv) Mon Aug 24 13:11:42 2009 IƱigo Serna * lfm/utils.py (ProcessLoopRename.process_response): fix bug: overwrite not triggered. Bug appeared with r58 when changed internals to work with unicode strings. Now we must compare the type of returned value with unicode, not str * lfm/utils.py (ProcessLoopBackup.process_response): * lfm/utils.py (ProcessLoopCopy.process_response): Mon Aug 24 11:50:47 2009 IƱigo Serna * lfm/lfm.py (Vfs.get_fileinfo_str_long): fix bug: increment owner and group space to avoid ugly look in 1-pane view Mon Aug 24 11:35:54 2009 IƱigo Serna * README: added new sections: Virtual File Systems, FAQ * README: some fixes and updates: wide char support, vfs, thanks... * README.pyview: some updates Sat Aug 22 22:14:17 2009 IƱigo Serna * lfm/utils.py (decode): try more encodings when we get a filename with strange characters. Use codecs_list * lfm/utils.py (codecs_list): build a list with common codecs, but put preferred encodings first not to slow down decoding Sat Aug 22 15:55:40 2009 IƱigo Serna * lfm/files.py (get_fileinfo): fix bug: returned owner and group should be strings here, not integers Sat Aug 22 15:37:16 2009 IƱigo Serna * lfm/files.py (PathContents.__init__): detect and raise UnicodeError if there are any filename with invalid encoding * lfm/actions.py (copy): show error message if filenames with invalid enconding * lfm/actions.py (move): * lfm/actions.py (delete): * lfm/actions.py (backup_file): * lfm/actions.py (__do_change_perms): Sat Aug 22 14:59:27 2009 IƱigo Serna * lfm/files.py (get_dir): ask to convert files with invalid encoding name fix bug: lfm crashes with invalid encoding filenames * lfm/files.py (convert_filename_encoding): new fuction * lfm/utils.py (ask_convert_invalid_encoding_filename): new function * lfm/config.py (confirmations): automatic_file_encoding_conversion, new confirmation option in config file, default 0 => ask * lfm/config.py (confirmations): quit option new default is 1 => ask Fri Aug 21 22:12:17 2009 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): fix bug: don't pass curses.KEY_* keystrokes to get_char Fri Aug 21 19:17:31 2009 IƱigo Serna * lfm/config.py: import codecs, g_encoding, encode * lfm/config.py: bookmarks should be unicode strings * lfm/config.py (Config.load): load config file with g_encoding * lfm/config.py (Config.save): save config file with g_encoding Fri Aug 21 18:42:55 2009 IƱigo Serna * lfm/vfs.py (pan_regenerate): encode params before run Fri Aug 21 18:27:04 2009 IƱigo Serna * lfm/utils.py (do_compress_uncompress_file): encode params before run * lfm/utils.py (do_uncompress_dir): * lfm/utils.py (do_compress_dir): Fri Aug 21 18:06:21 2009 IƱigo Serna * lfm/messages.py (FindfilesWin.manage_keys): move with cursor keys too Fri Aug 21 18:01:56 2009 IƱigo Serna * lfm/actions.py (findgrep): encode before running do_on_file * lfm/utils.py (do_find): encode params before run and decode returned files * lfm/utils.py (do_findgrep): Fri Aug 21 16:43:37 2009 IƱigo Serna * lfm/files.py: import decode * lfm/files.py (get_dir): bug in python: os.path.normpath(u'/') returns str instead of unicode, so convert to unicode Fri Aug 21 01:56:38 2009 IƱigo Serna * lfm/actions.py (do_execute_file): encode params before run * lfm/files.py (__get_size): encode params before run Fri Aug 21 01:44:36 2009 IƱigo Serna * lfm/utils.py (run_on_current_file): accept filename, not tab * lfm/actions.py (do_view_file): * lfm/actions.py (do_edit_file): Fri Aug 21 01:23:00 2009 IƱigo Serna * lfm/pyview.py: import encode & decode * lfm/pyview.py (main): decode before storing filename internally * lfm/pyview.py (FileView.show_title): encode before displaying * lfm/pyview.py (FileView.show_status): * lfm/pyview.py (FileView.__find): encode params before run * lfm/pyview.py (FileView.open_shell): Fri Aug 21 01:19:40 2009 IƱigo Serna * lfm/utils.py (get_escaped_filename): encode params * lfm/utils.py (get_escaped_command): Fri Aug 21 00:22:57 2009 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): fix bug: read correctly utf-8 (or with other encodings) characters from keyboard * lfm/messages.py (get_char): new functions * lfm/messages.py (get_char_raw): * lfm/messages.py (get_char_codec): * lfm/messages.py (get_char_ascii): * lfm/messages.py (get_char_codec): * lfm/messages.py (get_char_utf8): Fri Aug 21 00:21:03 2009 IƱigo Serna * lfm/utils.py (decode): return unicode always Thu Aug 20 16:54:36 2009 IƱigo Serna * lfm/messages.py: import utils * lfm/messages.py (BaseWindow.init_ui): encode before displaying * lfm/messages.py (FixSizeCommonWindow.__init__): * lfm/messages.py (ProgressBarWindowBase.show_common): * lfm/messages.py (ProgressBarWindowBase.update_common): * lfm/messages.py (get_a_key): * lfm/messages.py (confirm): * lfm/messages.py (confirm_all): * lfm/messages.py (confirm_all_none): * lfm/messages.py (EntryLine.show): * lfm/messages.py (Entry.__init__): * lfm/messages.py (DoubleEntry.__init__): * lfm/messages.py (SelectItem.show): * lfm/messages.py (FindfilesWin.show): * lfm/messages.py (MenuWin.show): * lfm/messages.py (ChangePerms.show): Thu Aug 20 10:59:38 2009 IƱigo Serna * lfm/actions.py (Tree.show_tree): encode before displaying * lfm/actions.py (Tree.display): Thu Aug 20 10:55:51 2009 IƱigo Serna * lfm/actions.py (do_show_file_info): encode with g_encoding before calling pyview Thu Aug 20 10:41:13 2009 IƱigo Serna * lfm/lfm.py (StatusBar.display): as paths are now unicode internally, we only have to encode to g_encoding when displaying * lfm/lfm.py (Pane.display_tabs): * lfm/lfm.py (Pane.display_files): * lfm/lfm.py (Pane.display_cursorbar): * lfm/lfm.py (Vfs.get_fileinfo_str_short): * lfm/lfm.py (Vfs.get_fileinfo_str_long): Thu Aug 20 10:39:47 2009 IƱigo Serna * lfm/lfm.py (lfm_start): store unicode paths internaly * lfm/lfm.py (Pane.load_tabs_with_paths): * lfm/lfm.py (lfm_exit): encode before saving path Wed Aug 19 22:12:18 2009 IƱigo Serna * lfm/files.py (get_fileinfo_extended): new function. Move code from actions.py to files.py * lfm/files.py (get_mount_points): new function. Use "mount" instead of "df" * lfm/files.py (get_mountpoint_for_file): new function * lfm/actions.py (do_show_file_info): rewritten, use get_fileinfo_extended and get_mountpoint_for_file Wed Aug 19 19:56:00 2009 IƱigo Serna * lfm/actions.py (do_show_fs_info): rewritten, use "df -h" directly instead of files.get_fs_info * lfm/files.py (get_fs_info): function removed, previously used in do_show_fs_info (actions.py) and do_show_file_info (actions.py) Wed Aug 19 18:26:56 2009 IƱigo Serna * lfm/files.py (__get_filetype): simplify code Wed Aug 19 18:15:50 2009 IƱigo Serna * lfm/files.py (get_fileinfo): fix bug: can't browse /home/ as root if ".gvfs" is present * lfm/files.py (FILETYPES): new FTYPE_UNKNOWN filetype Wed Aug 19 17:38:47 2009 IƱigo Serna * lfm/pyview.py (FileView.show_text_nowrap): do \t \n \r replacement here * lfm/pyview.py (FileView.show_text_wrap): * lfm/pyview.py (FileCache.__getitem__): not here Wed Aug 19 17:37:10 2009 IƱigo Serna * lfm/pyview.py (FileCache.__getitem__): fix bug: pyview doesn't show last char Wed Aug 19 17:10:51 2009 IƱigo Serna * lfm/pyview.py (InternalView.__validate_buf): fix bug: crash when buffer is large Wed Aug 19 13:26:13 2009 IƱigo Serna * lfm/files.py (do_backup): fix bug: backup dirs does not work Tue Aug 18 16:43:22 2009 IƱigo Serna * lfm/files.py (complete): fix bug: complete includes abs path. Bug was introduced in r49 Thu Jul 16 23:28:01 2009 IƱigo Serna * lfm/lfm.py (num2str): simplify code Thu Jul 16 23:27:29 2009 IƱigo Serna * lfm/lfm.py (Vfs.get_fileinfo_str_short): simplify code * lfm/lfm.py (Vfs.get_fileinfo_str_long): Thu Jul 16 23:27:11 2009 IƱigo Serna * lfm/lfm.py (StatusBar.display): clean code "cond and expr1 or expr2" * lfm/lfm.py (Pane.display_tabs): * lfm/lfm.py (Pane.display_files): * lfm/lfm.py (Vfs.restore): Thu Jul 16 18:10:23 2009 IƱigo Serna * lfm/pyview.py (FileView): rewritten completely. Clean code and refactor many methods. Actions have been moved from FileView.run to their own methods. Cleaned: __init__, init_curses, show_title, show_status, show, __find, __find_next, __find_prev Added: show_str_yx, __calc_hex_charsperline, __move_hex, move_*, goto_*, find_*, toggle_* Rewritten: show_text_nowrap, show_text_wrap, show_hex, run Removed: __get_file_info_, __move_lines, __get_lines_text, __get_prev_lines_text, __get_line_length, __get_1line, show_chr, __move_hex, __get_lines_hex * lfm/pyview.py (FileCache): new class to encapsulate and accelerate the access to lines of file * lfm/pyview.py (InternalView): clean code * lfm/pyview.py: moved help doc here instead of __init__.py Tue Jul 14 18:55:19 2009 IƱigo Serna * lfm/pyview.py (InternalView:run): fix bug: in page_next offset was wrong Tue Jul 14 00:44:08 2009 IƱigo Serna * lfm/pyview.py (PyView): added an -s/--stdin to force reading from stdin. Now pyview doesn't wait for stdin input by default, so it starts much faster * lfm/pyview.py (usage): add info about -s/--stdin option * lfm/pyview.py (read_stdin): wait 2 sec max for input from stdin * README.pyview: document -s/--stdin new flag Tue Jul 14 00:11:51 2009 IƱigo Serna * lfm/pyview.py: delete some imports not really used * lfm/pyview.py (FileView.show): clean code Mon Jul 13 23:53:12 2009 IƱigo Serna * lfm/pyview.py (FileView.show_text_nowrap): speed up displaying * lfm/pyview.py (FileView.show_text_wrap): * lfm/pyview.py (FileView.show_hex): Mon Jul 13 23:52:55 2009 IƱigo Serna * lfm/pyview.py (FileView.show_chr): optimize code to speed up displaying * lfm/pyview.py (FileView.show_str): optimize code to speed up displaying * lfm/pyview.py (FileView.__sanitize_char): new function Mon Jul 13 22:44:20 2009 IƱigo Serna * README: Add information about keybindings in permissions window * lfm/messages.py (ChangePerms.manage_keys): add some keybindings Mon Jul 13 22:24:18 2009 IƱigo Serna * lfm/lfm.py (lfm_start): clean code * lfm/pyview.py: * lfm/pyview.py (FileView.show_text_nowrap): * lfm/pyview.py (FileView.show_status): * lfm/pyview.py (FileView.__find): * lfm/pyview.py (FileView.__find_next): * lfm/pyview.py (FileView.__find_previous): * lfm/pyview.py (FileView.run): * lfm/pyview.py (PyView): Mon Jul 13 21:22:06 2009 IƱigo Serna * lfm/files.py (__get_size): clean code. As we can't use ternary operator because it wasn't introduced until v2.5, we use a trick: "result = check and a or b" * lfm/messages.py (error): * lfm/messages.py (EntryLine.show): * lfm/messages.py (DoubleEntry.__init__): * lfm/messages.py (ChangePerms.__init__): * lfm/messages.py (ChangePerms.show_btns): * lfm/messages.py (ChangePerms.show): * lfm/messages.py (ChangePerms.manage_keys): * lfm/messages.py (ChangePerms.manage_keys): Mon Jul 13 01:17:35 2009 IƱigo Serna * lfm/messages.py (ChangePerms): added recursive chmod chown chgrp * lfm/actions.py (do_change_perms): * lfm/actions.py (__do_change_perms): * lfm/files.py (set_perms): added recursive chmod * lfm/files.py (set_owner_group): added recursive chown chgrp Mon Jul 13 01:14:52 2009 IƱigo Serna * lfm/actions.py (__do_change_perms): fix bug: error messages were incorrect Sun Jul 12 23:41:02 2009 IƱigo Serna * lfm/messages.py (ProgressBarWindowBase.update_common): highlight file name Sun Jul 12 23:00:36 2009 IƱigo Serna * lfm/messages.py (Entry.__init__): make widgets bigger * lfm/messages.py (DoubleEntry.__init__): * lfm/messages.py (SelectItem.__init__): * lfm/messages.py (EntryLine.manage_keys): put SelectItem below entry Sun Jul 12 22:22:52 2009 IƱigo Serna * lfm/utils.py (ProcessLoopBase_1.show_win): show "file_idx/total" * lfm/utils.py (ProcessLoopBase_2.show_win): * lfm/messages.py (ProgressBarWindowBase.update_common): show "file_idx/total" after file name * lfm/messages.py (ProgressBarWindow.update): * lfm/messages.py (ProgressBarWindow.show): * lfm/messages.py (ProgressBarWindow2.show): * lfm/messages.py (ProgressBarWindow2.update):. Sun Jul 12 20:02:29 2009 IƱigo Serna * lfm/actions.py (backup_file): make backup operate over selected files * lfm/files.py (do_backup): new function * lfm/utils.py (ProcessLoopBackup): new class Sun Jul 12 20:02:09 2009 IƱigo Serna * lfm/actions.py (__rename_backup_helper): new helper function * lfm/actions.py (rename): use __rename_backup_helper Sun Jul 12 19:35:45 2009 IƱigo Serna * lfm/utils.py (get_shell_output_popen2): change function names * lfm/utils.py (get_shell_output_subprocess2): * lfm/utils.py: Sun Jul 12 19:31:07 2009 IƱigo Serna * lfm/utils.py (get_shell_output_subprocess2): fix bug: don't display anything in the screen as we don't want stderr Sun Jul 12 19:12:38 2009 IƱigo Serna * lfm/files.py (do_copy): fix bug: crash when we don't have enough permissions to write to dest Sun Jul 12 19:10:16 2009 IƱigo Serna * lfm/files.py (complete): fix bug: tab-completion don't work sometimes * lfm/messages.py (EntryLine:manage_keys): Sun Jul 12 18:09:19 2009 IƱigo Serna * lfm/files.py (do_copy): fix bug: don't try to copy fifo/socket/block-dev/char-dev files Sun Jul 12 15:21:20 2009 IƱigo Serna * lfm/utils.py (ProcessLoopBase): new base class for ProcessLoopBase_1 and ProcessLoopBase_2. Replaces old ProcessBaseLoop * lfm/utils.py (ProcessLoopBase.init_gui): use messages.ProgressBarWindow or messages.ProgressBarWindow2 depending on the concrete class * lfm/utils.py (ProcessLoopBase_1): new base class for actions which only needs 1 progress bar * lfm/utils.py (ProcessLoopDirSize): use ProcessLoopBase_1 * lfm/utils.py (ProcessLoopUnCompress): * lfm/utils.py (ProcessLoopRename): * lfm/utils.py (ProcessLoopBase_2:): new base class for actions which needs 2 progress bars * lfm/utils.py (ProcessLoopCopy): use ProcessLoopBase_2 * lfm/utils.py (ProcessLoopDelete): Sun Jul 12 14:59:20 2009 IƱigo Serna * lfm/messages.py (ProgressBarWindowBase): new base class for ProgressBarWindow and ProgressBarWindow2. Replaces old FixSizeProgressBarWindow * lfm/messages.py (ProgressBarWindow): for use in ProcessLoopBase_1 * lfm/messages.py (ProgressBarWindow2): for use in ProcessLoopBase_2 Sun Jul 12 14:56:27 2009 IƱigo Serna * lfm/messages.py (FixSizeCommonWindow.__init__): clean code * lfm/messages.py (EntryLine.show): clean code Sun Jul 10 14:45:51 2009 IƱigo Serna * lfm/files.py (copy_bulk): new functions. Replaces old "copy" and "delete" functions used in compress.py and vfs.py * lfm/files.py (delete_bulk): * lfm/actions.py (backup_file): use files.copy_bulk * lfm/compress.py (PackagerBase.delete_uncompress_temp): use files.delete_bulk * lfm/compress.py (PackagerBase.delete_compress_temp): * lfm/vfs.py (init): use files.delete_bulk * lfm/vfs.py (exit): * lfm/vfs.py (regenerate_file): * lfm/vfs.py (copy): use files.copy_bulk * lfm/vfs.py (regenerate_file): * lfm/vfs.py (pan_init): * lfm/vfs.py (pan_copy): * lfm/vfs.py (pan_regenerate): Sun Jul 10 14:38:05 2009 IƱigo Serna * lfm/files.py (do_copy): rewritten, old 'copy' function removed * lfm/files.py (do_delete): rewritten, old 'delete' function removed * lfm/files.py (do_rename): new function, old 'move' function removed and replace with this and with do_copy & do_delete * lfm/actions.py (rename): use ProcessLoopRename and files.do_rename * lfm/actions.py (show_dirs_size): use ProcessLoopDirSize * lfm/actions.py (copy): rewritten to use PathContents and ProcessLoopCopy & do_copy * lfm/actions.py (delete): rewritten to use PathContents and ProcessLoopDelete & do_delete * lfm/actions.py (move): rewritten to use PathContents and ProcessLoopCopy & do_copy and ProcessLoopDelete & do_delete * lfm/actions.py (__copymove_helper): new function: helper for copy and move actions Sun Jul 10 14:27:43 2009 IƱigo Serna * lfm/files.py (PathContents): new class Walks over a list of paths, gathering information about all the files and directories inside these paths Mon Jul 6 01:32:57 2009 IƱigo Serna * lfm/utils.py (get_shell_output_subprocess): reduce sleep * lfm/utils.py (get_shell_output_subprocess2): Mon Jul 6 01:23:18 2009 IƱigo Serna * lfm/vfs.py (regenerate_file): fix bug: some garbage was left on the temporary directory after recompressing a vfs compressed file Mon Jul 6 01:13:49 2009 IƱigo Serna * lfm/actions.py (make_dir): fix bug: error message was incorrect Sun Jul 5 12:03:51 2009 IƱigo Serna * lfm/actions.py (file_menu): fix bug: restore key for compressing to .bz2, now is back to 'd' key. 'j' is used to move cursor in widget. Previous change was on Fri Jun 19 23:42:14 2009 * README: update documentation Sun Jul 5 12:02:28 2009 IƱigo Serna * lfm/compress.py (PackagerZIP): fix bug: overwrite files without prompting ("unzip -o" option) to avoid ethernal waiting, as messages can not be seen by user Sat Jul 4 22:13:47 2009 IƱigo Serna * lfm/utils.py (IPC.send): disable waiting after sending data This should speed communications a lot * lfm/utils.py (IPC.receive): decrease timeout while waiting for data from socket Sat Jul 4 19:09:26 2009 IƱigo Serna * lfm/files.py (move): some cosmetic changes to string doc * lfm/files.py (copy): Mon Jun 29 00:17:05 2009 IƱigo Serna * lfm/actions.py (Tree.__get_dirs): follow show/hide .dotfiles behaviour * lfm/actions.py (Tree.run): new key: Ctrl-H toggle show/hide .dotfiles Sun Jun 28 18:31:37 2009 IƱigo Serna * lfm/actions.py (show_info): use "file -b" to simplify code, check if no information is returned Sun Jun 28 13:16:40 2009 IƱigo Serna * lfm/actions.py (keytable): new keybindings for new actions Ctrl-P, Ctrl-cursor_up: move cursor 1/4th of page up Ctrl-N, Ctrl-cursor_down: move cursor 1/4th of page down P: move cursor 1/4th of page up in other pane N: move cursor 1/4th of page down in other pane * lfm/actions.py (cursor_quarter_up): new function * lfm/actions.py (cursor_quarter_down): * lfm/actions.py (cursor_quarter_up_otherpane): * lfm/actions.py (cursor_quarter_down_otherpane): Fri Jun 26 14:58:52 2009 IƱigo Serna * lfm/messages.py (Entry.run): expand ~ to user home. Idea from Joshua Tasker * lfm/messages.py (DoubleEntry.run): Fri Jun 26 14:57:18 2009 IƱigo Serna * lfm/messages.py (EntryLine. manage_keys): fix bug: crash in a void EntryLine after pressing BACKSPACE on some platforms. Reported by Joshua Tasker Fri Jun 26 14:53:50 2009 IƱigo Serna * lfm/lfm.py (Vfs.init_dir): fix bug: crash in goto_dir if there aren't any historic entries. Reported by Joshua Tasker Sun Jun 21 23:06:17 2009 IƱigo Serna * lfm/config.py (Config.load): include code to validate sections Sun Jun 21 22:59:45 2009 IƱigo Serna * lfm/config.py (misc): new section in configuration file -> "Misc" Added "backup_extension" item * lfm/config.py (Config.load): updated to consider Misc section * lfm/config.py (Config.save): Sun Jun 21 22:58:03 2009 IƱigo Serna * lfm/actions.py (file_menu): new entry: a -> backup file * lfm/actions.py (backup_file): new function * README: update documentation Sun Jun 21 20:13:55 2009 IƱigo Serna * lfm/actions.py (keytable): Ctrl-l: center cursor in pane, so now edit-link is in 'L' * lfm/actions.py (cursor_center): new function Fri Jun 19 23:42:14 2009 IƱigo Serna * lfm/actions.py (file_menu): changed menu key from 'd' to 'j' to compress to .bz2 format, so make it similar to tar flags to reflect it * README: update documentation Fri Jun 19 23:17:49 2009 IƱigo Serna * lfm/files.py (get_fileinfo): fix bug: devices major and minor numbers were not showed properly * lfm/lfm.py (Vfs.get_fileinfo_str_short): prettify device numbers * lfm/lfm.py (Vfs.get_fileinfo_str_long): Fri Jun 19 22:51:02 2009 IƱigo Serna * lfm/utils.py: check if python version < 2.4, if so, use popen* for run_shell and get_shell_output else, use subprocess. As python v2.6, popen* is deprecated * lfm/utils.py (run_shell_subprocess): functions using subprocess * lfm/utils.py (get_shell_output_subprocess): * lfm/utils.py (get_shell_output_subprocess2): * lfm/utils.py (run_shell_popen): functions using popen* * lfm/utils.py (get_shell_output_popen): * lfm/utils.py (get_shell_output_popen2): * lfm/actions.py (touch_file): substitute os.popen4 * lfm/actions.py (do_show_file_info): substitute os.popen * lfm/files.py (get_rdev): substitute os.popen * lfm/files.py (get_fs_info): substitute os.popen * lfm/pyview.py (FileView.__find): substitute os.popen3 * lfm/vfs.py (pan_regenerate): substitute os.popen4 Fri Jun 19 22:50:22 2009 IƱigo Serna * lfm/actions.py (enter): use correct python idiom: "is None", not "== None" * lfm/actions.py (show_dirs_size): * lfm/actions.py (do_execute_file): * lfm/actions.py (findgrep): * lfm/actions.py (Tree.__get_node): * lfm/actions.py (do_show_fs_info): use correct python idiom: use "isinstance" * lfm/actions.py (doEntry): * lfm/actions.py (doDoubleEntry): * lfm/lfm.py (Pane.manage_keys): use correct python idiom: "is None", not "== None" * lfm/lfm.py (lfm_start): * lfm/messages.py (EntryLine.manage_keys): * lfm/pyview.py (FileView.__find): * lfm/utils.py (ProcessUnCompressLoop.process_response): use correct python idiom: use "isinstance" * lfm/utils.py (ProcessCopyMoveLoop.process_response): * lfm/utils.py (ProcessRenameLoop.process_response): * lfm/utils.py (ProcessDeleteLoop.process_response): * lfm/utils.py (IPC.receive): use correct python idiom: "is None", "== None" * lfm/utils.py (do_compress_uncompress_file): * lfm/utils.py (do_uncompress_dir): * lfm/utils.py (uncompress_dir): * lfm/utils.py (do_compress_dir): * lfm/utils.py (compress_dir): * lfm/utils.py (do_find): Fri Jun 19 22:49:07 2009 IƱigo Serna * lfm/files.py (get_fs_info): fix bug: file system information was not showed correctly sometimes Fri Jun 19 22:48:24 2009 IƱigo Serna * lfm/config.py (colors): added 2 new colors for directories and exe_files, instead of using general colors * lfm/lfm.py (Lfm.init_ui): * lfm/lfm.py (Pane.get_filetypecolorpair): Sat Jan 10 13:43:26 2009 IƱigo Serna * lfm/files.py (get_fs_info): fix bug: parsing fstab Sat Dec 20 23:14:48 2008 IƱigo Serna * lfm/__init__.py: version 2.1 * lfm/lfm.py: * setup.py: * PKG-INFO: * README: * README.pyview: Sat Dec 20 21:50:08 2008 IƱigo Serna * lfm/actions.py (keytable): new key shortcut C-y: display directories history * lfm/actions.py (select_historic): new function * lfm/lfm.py (Vfs.__init__): added historic list * lfm/lfm.py (Vfs.init_dir): save path history before changing * README: document this Sat Dec 20 18:12:23 2008 IƱigo Serna * lfm/messages.py (MenuWin.__init__): fix bug: crash when title length was greater than length of each entry Thu Dec 18 14:19:01 2008 IƱigo Serna * lfm/actions.py (keytable): new key shortcuts to allow navigating in non active pane * lfm/actions.py (cursor_up_otherpane): new functions * lfm/actions.py (cursor_down_otherpane): * lfm/actions.py (page_previous_otherpane): * lfm/actions.py (page_next_otherpane): * lfm/actions.py (home_otherpane): * lfm/actions.py (end_otherpane): * lfm/actions.py (cursor_left_otherpane): * lfm/actions.py (cursor_right_otherpane): * README: document this new features and key shorcuts Thu Dec 18 13:01:23 2008 IƱigo Serna * lfm/actions.py (keytable): new key shortcut C-w: toggle navigating in non active pane * lfm/actions.py (toggle_manage_otherpane): new function * lfm/config.py (options): new option: manage_otherpane * lfm/config.py: added new color schemas for non active pane cursor and selected files: current_file_otherpane, current_selected_file_otherpane * lfm/lfm.py (Pane.display_cursorbar): handle non active pane drawing Thu Dec 18 12:56:09 2008 IƱigo Serna * lfm/lfm.py (Pane.manage_keys): handle non-update and partial update for non active pane * lfm/lfm.py (Lfm.half_display_other): new method * lfm/__init__.py: added RET_NO_UPDATE, RET_HALF_UPDATE_OTHER Wed Nov 26 01:05:28 2008 IƱigo Serna * lfm/pyview.py: clean code Wed Nov 26 00:01:22 2008 IƱigo Serna * lfm/lfm.py: clean code Tue Nov 25 21:23:33 2008 IƱigo Serna * lfm/lfm.py (Lfm.init_ui): simplify colors handling Tue Nov 25 19:38:41 2008 IƱigo Serna * lfm/lfm.py (lfm_start): refactor code * lfm/lfm.py (add_path): Tue Nov 25 17:37:06 2008 IƱigo Serna * lfm/config.py (defaultprogs): change some default values * lfm/config.py (filetypes): * lfm/config.py (files_ext): Tue Nov 25 15:27:34 2008 IƱigo Serna * lfm/files.py (__do_sort): fix bug: sorting by None Tue Nov 25 14:59:51 2008 IƱigo Serna * lfm/actions.py: clean code Tue Nov 25 13:39:52 2008 IƱigo Serna * lfm/actions.py (doDoubleEntry): change order of parameters to simplify code * lfm/actions.py (findgrep): * lfm/actions.py (create_link): Tue Nov 25 12:41:01 2008 IƱigo Serna * lfm/lfm.py (Lfm.half_display): update only active pane and statusbar * lfm/lfm.py (Pane.manage_keys): allow partial update for some actions * lfm/__init__.py: * lfm/actions.py (cursor_up): update only active pane and statusbar * lfm/actions.py (cursor_down): * lfm/actions.py (page_previous): * lfm/actions.py (page_next): * lfm/actions.py (home): * lfm/actions.py (end): * lfm/actions.py (goto_dir): * lfm/actions.py (goto_file): * lfm/actions.py (goto_bookmark): Mon Nov 24 01:55:52 2008 IƱigo Serna * lfm/messages.py (BaseWindow): new base class * lfm/messages.py (CommonWindow): based on BaseWindow * lfm/messages.py (FixSizeCommonWindow): based on BaseWindow Sun Nov 23 17:56:50 2008 IƱigo Serna * lfm/messages.py (EntryLine): clean code * lfm/messages.py (EntryLine.manage_keys): new key shortcuts: . ctrl-w: delete whole entry . ctrl-k: delete line from position . ctrl-d: delete until next / . ctrl-z: recover original content (undo) . ctrl-b, ctrl-f: prev / next char . ctrl-p, ctrl-left: prev / . ctrl-n, crtl-right: next / * README: document these key shortcuts Sun Nov 23 14:18:58 2008 IƱigo Serna * lfm/messages.py (Entry.run): clean code * lfm/messages.py (DoubleEntry.run): Fri Nov 21 23:27:09 2008 IƱigo Serna * lfm/messages.py (confirm_all): handle left/right cursors * lfm/messages.py (confirm_all_none): Fri Nov 21 23:26:49 2008 IƱigo Serna * lfm/messages.py (confirm): clean code * lfm/messages.py (confirm_all): * lfm/messages.py (confirm_all_none): * lfm/messages.py (Yes_No_Buttons.show): * lfm/messages.py (Entry.run): * lfm/messages.py (DoubleEntry.run): Mon Nov 17 23:51:16 2008 IƱigo Serna * README: documentation updated: .7z support, Ctrl-H, swap F2 <=> F12 Mon Nov 17 23:37:01 2008 IƱigo Serna * lfm/compress.py (Packager7Z): added support for 7z compressor * lfm/compress.py (packagers): * lfm/compress.py (packagers_by_type): * lfm/__init__.py (sysprogs): added 7z compressor * lfm/config.py (files_ext): * lfm/actions.py (file_menu): added support for 7z Fri Nov 23 23:43:05 2007 IƱigo Serna * lfm/actions.py (keytable): Ctrl-H toggle show/hide dot files * lfm/actions.py (toggle_dotfiles): * lfm/actions.py (keytable): swapped F2 and F12 actions Now F2 => rename, F12 => file_menu * setup.py (setup): change IƱigo for Inigo to avoid setup problems Fri Sep 14 19:18:22 2007 IƱigo Serna * lfm/lfm.py (Vfs.regenerate): fix bug: endless loop if in "/" directory. Catch and fix by Murat Erten Fri Sep 14 19:01:41 2007 IƱigo Serna * lfm/pyview.py (FileView.__get_file_info): fix bug: crash when viewing a file with zero length. Catch and fix by Murat Erten Mon Sep 3 23:03:32 2007 IƱigo Serna * lfm/__init__.py: version 2.0 * lfm/lfm.py: * setup.py: * PKG-INFO: Mon Sep 3 22:59:56 2007 IƱigo Serna * README: update documentation to describe new features, changes, etc * README.pyview: * NEWS: * TODO: * lfm.1: * pyview.1: Sun Sep 2 20:12:56 2007 IƱigo Serna * lfm/config.py: added new file types extensions Sun Sep 2 17:23:01 2007 IƱigo Serna * lfm/actions.py (findgrep): panelize: clean code Sun Sep 2 17:13:43 2007 IƱigo Serna * lfm/actions.py (__do_change_perms): refactor: new function Sun Sep 2 17:05:56 2007 IƱigo Serna * lfm/actions.py (show_dirs_size): simplify code Sun Sep 2 16:02:15 2007 IƱigo Serna * lfm/pyview.py (PyView.run): fix bug: goto line/byte 0 Sat Sep 1 15:57:56 2007 IƱigo Serna * lfm/pyview.py (PyView): use standard lib logging module in debug messages Sat Sep 1 15:53:16 2007 IƱigo Serna * lfm/pyview.py (FileView.show_text_wrap): simplify and clean code * lfm/pyview.py (FileView.show_text_nowrap): * lfm/pyview.py (FileView.show_hex): * lfm/pyview.py (FileView.run): Sat Sep 1 13:36:40 2007 IƱigo Serna * lfm/pyview.py (FileView.show_str): fix bug: sometimes a blank space was added Fri Aug 31 23:53:11 2007 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): refresh app display after canceling completion Fri Aug 31 23:48:01 2007 IƱigo Serna * lfm/messages.py (cursor_show2): move from lfm.py * lfm/messages.py (cursor_show): * lfm/messages.py (cursor_hide): Fri Aug 31 13:23:46 2007 IƱigo Serna * lfm/files.py (__do_sort): fix bug: "sort_mix_cases = 1 performance degrades on larger dirs". Reported by Andrey Skvortsov. This speeds up loading directory contents quite a lot Fri Aug 31 10:32:45 2007 IƱigo Serna * lfm/files.py: move all constants to the top of the module Fri Aug 31 10:26:43 2007 IƱigo Serna * lfm/files.py (get_rdev): clean code Fri Aug 31 02:02:34 2007 IƱigo Serna * lfm/files.py (mkdtemp): use more secure tempfile.mkdtemp * lfm/vfs.py (init): use files.mkdtemp * lfm/vfs.py (copy): * lfm/vfs.py (pan_init): * lfm/vfs.py (pan_copy): * lfm/files.py (mktemp): use more secure tempfile.mkstemp * lfm/pyview.py (create_temp_for_stdin): * lfm/vfs.py (regenerate_file): as files.mktemp calls tempfile.mkstemp we don't need modify mask too Fri Aug 31 01:20:55 2007 IƱigo Serna * lfm/actions.py (do_change_perms): refactoring: new function Thu Aug 30 23:36:10 2007 IƱigo Serna * lfm/actions.py (cursor_right): now pressing cursor_right is the same as pressing enter Thu Aug 30 22:37:06 2007 IƱigo Serna * lfm/actions.py (Tree.run): fix bug: catch window resize * lfm/actions.py (Tree.__calculate_dims): refactor method Thu Aug 30 20:01:55 2007 IƱigo Serna * lfm/actions.py (touch_file): fix bug: escape filenames with chars $ ". Reported by Andrey Skvortsov * lfm/actions.py (open_shell): * lfm/actions.py (do_special_view_file): * lfm/actions.py (do_view_file): * lfm/actions.py (do_edit_file): * lfm/actions.py (do_execute_file): * lfm/actions.py (do_something_on_file): * lfm/actions.py (do_show_file_info.show_info): Thu Aug 30 18:30:26 2007 IƱigo Serna * lfm/lfm.py (Vfs.get_file): refactoring: new method * lfm/lfm.py (Vfs.get_fullpathfile): refactoring: new method * lfm/utils.py (get_escaped_filename): refactoring: new functions * lfm/utils.py (get_escaped_command): * lfm/utils.py (run_on_current_file): Thu Aug 30 16:41:43 2007 IƱigo Serna * lfm/pyview.py (FileView.__init__): fix bug: pyview messages.py couldn't access window dimensions so it crashed * lfm/pyview.py (InternalView.__init__): Thu Aug 30 11:35:32 2007 IƱigo Serna * lfm/lfm.py (usage): make error message more visible Thu Aug 30 11:30:15 2007 IƱigo Serna * lfm/lfm: check python version 2.3 or higher * lfm/pyview: check python version 2.3 or higher Thu Aug 30 11:13:49 2007 IƱigo Serna * lfm/lfm.py (Pane.load_tabs_with_paths): fix bug: don't show ugly traceback crash when user starts "lfm path" and has no permissions to enter. Show error message and default to current directory * lfm/lfm.py (TabVfs.init): propagate error back Thu Aug 30 10:20:44 2007 IƱigo Serna * lfm/lfm.py (lfm_start): modified the sequence in which tabs are created and filled with path contents. Prepare code to support sessions in a future * lfm/lfm.py (main): * lfm/lfm.py (Lfm.__init__): pane contents are not filled in the constructor anymore. Lfm.load_paths must be called explicitly * lfm/lfm.py (Lfm.load_paths): new method * lfm/lfm.py (Pane.load_tabs_with_paths): new method * lfm/lfm.py (Pane.__init__): * lfm/lfm.py (TabVfs.__init__): self.init is not called in the constructor anymore. Pane.load_tabs_with_paths must be called explicitly * lfm/lfm.py (Vfs.init_dir): the awful hack used here to get preferences the first time is not needed anymore * lfm/lfm.py (TabVfs.init): Thu Aug 30 01:56:17 2007 IƱigo Serna * lfm/utils.py (do_findgrep): escape '-' character in grep patterns Thu Aug 30 01:51:36 2007 IƱigo Serna * lfm/config.py: back to original '.lfmrc' configuration file name Thu Aug 30 01:28:11 2007 IƱigo Serna * lfm/lfm.py (Vfs.enter_dir): Refactor code from actions.py to these new methods * lfm/lfm.py (Vfs.exit_dir): * lfm/actions.py (cursor_left): * lfm/actions.py (cursor_right): * lfm/actions.py (enter): * lfm/lfm.py (Vfs.enter_dir): fix bug: "if you try to enter a directory with insufficient permissions, after the error message is closed the cursorline refreshes to the first line"" This bug was first observed by Andrey, fixed by me Thu Aug 30 01:13:13 2007 IƱigo Serna * lfm/lfm.py (Vfs.init): mark method as virtual Thu Aug 30 00:57:20 2007 IƱigo Serna * lfm/lfm.py (Vfs.init_dir): error dialog must be in UI class * lfm/lfm.py (TabVfs.init): Wed Aug 29 17:41:26 2007 IƱigo Serna * lfm/messages.py (CommonWindow.__init__): change curses.COLS and curses.LINES by app.maxw and app.maxh fixing the position of dialogs in maximized terminals. Curses.{COLS|LINES} are constants defined at curses library start and they don't modify their values when window resizes * lfm/messages.py (FixSizeCommonWindow.__init__): * lfm/messages.py (FixSizeProgressBarWindow.__init__): * lfm/messages.py (FixSizeProgressBarWindow.show): * lfm/messages.py (Yes_No_Buttons.__init__): * lfm/messages.py (Entry.__init__): * lfm/messages.py (get_a_key): * lfm/messages.py (confirm): * lfm/messages.py (confirm_all(:) * lfm/messages.py (confirm_all_none): * lfm/messages.py (DoubleEntry): * lfm/messages.py (SelectItem): * lfm/messages.py (FindfilesWin): * lfm/messages.py (MenuWin): * lfm/messages.py (ChangePerms): Mon Aug 27 23:07:24 2007 IƱigo Serna * lfm/lfm.py (StatusBar.display): fix crashes when filenames are not encoded with same codec than LC tells us * lfm/lfm.py (Pane.display_tabs): * lfm/lfm.py (Pane.display_files): * lfm/lfm.py (Pane.get_fileinfo_str_short): * lfm/lfm.py (Pane.get_fileinfo_str_long): * lfm/utils.py (encode): util functions to encode/decode filenames * lfm/utils.py (decode): Mon Aug 27 21:11:06 2007 IƱigo Serna * lfm/lfm.py (Pane.manage_keys): the most expected fix to the problems when maximizing windows. It was a stupid curses.doupdate() missed. Andrey Skvortsov did it again! Thanks, I ought you a beer. Mon Aug 27 20:56:52 2007 IƱigo Serna * lfm/__init__.py: support locale. Andrey Skvortsov * lfm/lfm.py (StatusBar.display): * lfm/lfm.py (Pane.display_tabs): * lfm/lfm.py (Pane.display_files): * lfm/lfm.py (Vfs.get_fileinfo_str_short): * lfm/lfm.py (Vfs.get_fileinfo_str_long): Mon Aug 27 20:03:50 2007 IƱigo Serna * lfm/lfm.py (Pane.__calculate_dims): right pane fill to the last column in terminal if their number is odd. Andrey Skvortsov * lfm/lfm.py (Pane.__calculate_columns): Mon Aug 27 19:58:02 2007 IƱigo Serna * lfm/lfm.py (Lfm.quit_program): refactoring: new method Mon Aug 27 17:52:18 2007 IƱigo Serna * lfm/actions.py (select_group): clean code. Andrey Skvortsov Mon Aug 27 17:20:02 2007 IƱigo Serna * lfm/lfm.py (Vfs.get_fileinfo_str_short): clean code * lfm/lfm.py (Vfs.get_fileinfo_str_long): Mon Aug 27 16:53:50 2007 IƱigo Serna * lfm/lfm.py (Pane.display_files): colorize by files extension. By Andrey Skvortsov * lfm/lfm.py (Pane.get_filetypecolorpair): new function * lfm/lfm.py (Lfm.init_ui): added colors for files by extension * lfm/config.py: added option color_files * lfm/config.py: added colors for fiels by extension Mon Aug 27 16:42:12 2007 IƱigo Serna * lfm/lfm.py (Pane.display_files): replace old code with Andrey's beautiful and clean code' * lfm/lfm.py (Pane.__calculate_scrollbar_dims): Mon Aug 27 13:47:36 2007 IƱigo Serna * lfm/config.py (load): consider path expand. Andrey Skvortsov Mon Aug 27 13:40:55 2007 IƱigo Serna * lfm/actions.py (new_tab): Correct English messages. Andrey Skvortsov * lfm/actions.py (close_tab): Mon Aug 27 12:41:26 2007 IƱigo Serna * lfm/compress.py (PackagerTBZ2): user -i (--ignore-zeros) flag to avoid spureous warnings. Andrey Skvortsov * lfm/compress.py (PackagerTGZ): Tue Jul 31 01:19:11 2007 IƱigo Serna * lfm/files.py (get_fs_info): fix crash when "df" shows entries in two different lines (dev is too large, f.e. in lvm2 volumes) Tue Jul 31 00:17:22 2007 IƱigo Serna * lfm/lfm.py (Vfs.__init__): fix a stupid bug Tue Jul 31 00:12:16 2007 IƱigo Serna * lfm/lfm.py (Lfm.run): simplify code * lfm/lfm.py (Pane.init_ui): * lfm/lfm.py (Pane.display_tabs): * lfm/lfm.py (Pane.display_cursorbar): Mon Jul 30 23:11:09 2007 IƱigo Serna * lfm/lfm.py (Lfm.init_ui): rewrite code to not repeat default colors in config.py Mon Jul 30 12:25:15 2007 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): added keycode 127, as konsole interprets backspace as such, again Andrey Skvortsov Mon Jul 30 11:57:39 2007 IƱigo Serna * lfm/lfm.py (Pane.manage_keys): python suggests to use while 1 to while True in speed sensitive routines. Nicely noticed by Andrey Skvortsov Mon Jul 30 11:54:53 2007 IƱigo Serna * lfm/compress.py (PackagerZIP): zip flag needs to be -q instead of -X, catched by Andrey Skvortsov Mon Jul 30 11:52:07 2007 IƱigo Serna * lfm/vfs.py (exit): 'ans' variable initilization was missing, catched by Andrey Skvortsov Sat May 14 00:10:45 2005 IƱigo Serna * lfm/actions.py (do_execute_file): fixed bug Fri May 13 00:32:59 2005 IƱigo Serna * lfm/utils.py (do_findgrep): fixed stupid bug, status was missing Fri May 13 00:24:46 2005 IƱigo Serna * lfm/utils.py (IPC): new InterProcessCommunication API * lfm/utils.py (ProcessBaseLoop): use IPC class, clean code * lfm/utils.py (ProcessFunc): use IPC class, clean code Thu May 12 23:50:37 2005 IƱigo Serna * lfm/actions.py (show_dirs_size): FIXED BUG: if stopped by user len(results) < len(dirs) Thu May 12 00:20:21 2005 IƱigo Serna * lfm/utils.py (ProcessBaseLoop.check_keys): fixed bug Mon May 9 00:21:39 2005 IƱigo Serna * lfm/utils.py (run_thread): old API deleted * lfm/utils.py (ProcessLoop): old API deleted Mon May 9 00:17:28 2005 IƱigo Serna * lfm/vfs.py (init): adapted to new ProcessFunc, code cleaned, improved error handling * lfm/vfs.py (regenerate_file): Mon May 9 00:14:00 2005 IƱigo Serna * lfm/utils.py (do_compress_uncompress_file): cleaned and rewritten to use ProcessUnCompressLoop * lfm/utils.py (compress_uncompress_file): * lfm/utils.py (do_uncompress_dir): * lfm/utils.py (uncompress_dir): * lfm/utils.py (do_compress_dir): * lfm/utils.py (compress_dir): Sun May 8 23:55:20 2005 IƱigo Serna * lfm/utils.py (do_findgrep): adapted to new ProcessFunc, code cleaned, improved error handling * lfm/utils.py (do_find): Sun May 8 23:49:47 2005 IƱigo Serna * lfm/actions.py (findgrep): improve errors handling Sun May 8 23:43:23 2005 IƱigo Serna * lfm/actions.py (do_execute_file): use new ProcessFunc Sun May 8 23:41:37 2005 IƱigo Serna * lfm/utils.py (ProcessFunc): cleaned and improved * lfm/utils.py (run_shell): cleaned and improved Sun May 8 23:40:11 2005 IƱigo Serna * lfm/actions.py (show_dirs_size): use ProcessDirSizeLoop * lfm/actions.py (do_show_dirs_size): Sun May 8 23:38:45 2005 IƱigo Serna * lfm/actions.py (copy): cleaned and rewritten to use Process*Loop API * lfm/actions.py (move): * lfm/actions.py (rename): * lfm/actions.py (delete): Sun May 8 23:01:29 2005 IƱigo Serna * lfm/files.py (copy): code cleaning and function arguments arranged * lfm/files.py (move): * lfm/files.py (delete): Sun May 8 22:27:22 2005 IƱigo Serna * lfm/utils.py (ProcessBaseLoop): new base virtual class * lfm/utils.py (ProcessDirSizeLoop: new API * lfm/utils.py (ProcessUnCompressLoop): * lfm/utils.py (ProcessCopyMoveLoop): * lfm/utils.py (ProcessRenameLoop): * lfm/utils.py (ProcessDeleteLoop): Sun May 1 16:42:41 2005 IƱigo Serna * lfm/utils.py (ProcessLoop.show_win): use progressbar window * lfm/utils.py (ProcessLoop.process_output): * lfm/messages.py (FixSizeProgressBarWindow): new dialog * lfm/messages.py (winprogress_nokey): wrapper for FixSizeProgressBarWindow Sun May 1 01:18:47 2005 IƱigo Serna * lfm/utils.py (do_uncompress_dir): * lfm/utils.py (do_compress_dir): FIXED BUG: delete garbage if user stops process * lfm/compress.py (PackagerBase.delete_uncompress_temp): * lfm/compress.py (PackagerBase.delete_compress_temp): * lfm/vfs.py (exit): * lfm/vfs.py (regenerate_file): Sun May 1 00:19:23 2005 IƱigo Serna * lfm/utils.py (ProcessFunc.check_keys): fixed typo Sat Apr 30 22:54:28 2005 IƱigo Serna * lfm/actions.py (do_special_view_file): use run_dettached and don't show crap messages from started app * lfm/utils.py (run_dettached): new function Sat Apr 30 16:55:14 2005 IƱigo Serna * lfm/utils.py (do_findgrep): FIXED BUG: special chars should be escaped, f.e. \ ( ) [ ] Sat Apr 30 16:25:36 2005 IƱigo Serna * lfm/actions.py (enter): FIXED BUG: "enter" tries to create a vfs from a simple bzipped or gzipped file Sat Apr 30 13:12:08 2005 IƱigo Serna * lfm/utils.py (do_find): FIXED BUG: crashed when no results * lfm/utils.py (do_findgrep): Fri Apr 29 00:55:47 2005 IƱigo Serna * lfm/actions.py (create_link): fixed a bug Fri Apr 1 00:02:14 2005 IƱigo Serna * lfm/utils.py (do_findgrep): FIXED BUG: when found a match in a binary file, the output text is not formatted in the way we wanted Wed Mar 30 22:42:44 2005 IƱigo Serna * lfm/files.py (get_fileinfo_dict): fixed a typo Wed Mar 30 21:40:13 2005 IƱigo Serna * lfm/utils.py (do_findgrep): using new ProcessFunc and run_shell * lfm/utils.py (do_find): * lfm/utils.py (do_compress_uncompress_file): using new ProcessFunc and run_shell * lfm/utils.py (do_uncompress_dir): * lfm/utils.py (do_compress_dir): * lfm/utils.py (compress_uncompress_file): using new ProcessLoop * lfm/utils.py (uncompress_dir): * lfm/utils.py (compress_dir): * lfm/vfs.py (init): using new utils.do_uncompress_dire * lfm/vfs.py (regenerate_file): using new ProcessFunc * lfm/actions.py (show_dirs_size): using new ProcessLoop * lfm/actions.py (do_execute_file): using new ProcessFunc Wed Mar 30 21:34:30 2005 IƱigo Serna * lfm/utils.py (ProcessLoop): rewritten * lfm/utils.py (ProcessFunc): rewritten * lfm/utils.py (run_shell): rewritten, to be executed inside ProcessFunc Wed Mar 30 21:31:51 2005 IƱigo Serna * lfm/utils.py (ProcessBase): removed * lfm/utils.py (ProcessShell): removed, run_shell inside ProcessFunc Wed Mar 30 21:27:20 2005 IƱigo Serna * lfm/actions.py (delete): cosmetic changes Tue Mar 29 23:28:07 2005 IƱigo Serna * lfm/messages.py (confirm_all): add a skip_all option * lfm/messages.py (confirm_all): fix a silly bug ord * lfm/messages.py (confirm): Mon Mar 28 03:26:25 2005 IƱigo Serna * lfm/utils.py (compress_uncompress_file): now use ProcessLoop * lfm/utils.py (uncompress_dir): * lfm/utils.py (compress_dir): Mon Mar 28 03:26:14 2005 IƱigo Serna * lfm/utils.py (do_compress_uncompress_file): now use ProcessShell * lfm/utils.py (do_uncompress_dir): * lfm/utils.py (do_compress_dir): * lfm/utils.py (do_findgrep): * lfm/utils.py (do_find): * lfm/actions.py (do_execute_file): * lfm/vfs.py (regenerate_file): Mon Mar 28 03:23:53 2005 IƱigo Serna * lfm/utils.py (ProcessBase.init_gui): obbey gui flag * lfm/utils.py (ProcessBase.end_gui): * lfm/utils.py (ProcessBase.show_win): * lfm/utils.py (ProcessBase.animate_cursor): * lfm/utils.py (ProcessBase.check_keys): Mon Mar 28 03:22:57 2005 IƱigo Serna * lfm/utils.py (ProcessShell): execute a shell command. Obsoletes run_shell function Thu Mar 24 01:31:47 2005 IƱigo Serna * lfm/actions.py (show_dirs_size): use ProcessLoop * lfm/actions.py (do_show_dirs_size): Thu Mar 24 01:29:06 2005 IƱigo Serna * lfm/utils.py (ProcessBase): abstract class to execute functions or commands in background. It will obsolete run_thread, run_shell and run_loop * lfm/utils.py (ProcessFunc): execute a function * lfm/utils.py (ProcessLoop): execute a loop Thu Mar 24 01:26:47 2005 IƱigo Serna * lfm/utils.py (loop_child_process): function name changed, "child_process" is now "loop_child_process" * lfm/utils.py (run_loop): use "loop_child_process" * lfm/utils.py (get_shell_output): delete old debug print * lfm/utils.py (get_shell_output2): delete old debug print Wed Mar 23 01:06:46 2005 IƱigo Serna * lfm/actions.py (do_execute_file): use run_shell * lfm/actions.py (do_do_execute_file): removed, not needed anymore Wed Mar 23 00:08:20 2005 IƱigo Serna * lfm/actions.py (show_dirs_size): function rewritten using run_loop, but don't work as child can't modify app data * lfm/actions.py (do_show_dirs_size): new function Tue Mar 22 22:03:17 2005 IƱigo Serna * lfm/actions.py (show_dirs_size): clean code * lfm/files.py (__get_size): use get_shell_output2 * lfm/files.py (get_fileinfo): pardir_flag and show_dirs_size are boolean now, not int * lfm/utils.py (get_shell_output2): new function get output from a command run in shell, no stderr Tue Mar 22 21:04:44 2005 IƱigo Serna * lfm/actions.py (findgrep): clean code * lfm/utils.py (do_findgrep): clean code, call run_shell now * lfm/utils.py (do_find): Tue Mar 22 21:01:03 2005 IƱigo Serna * lfm/utils.py (run_shell): added return_output flag if true return output, else return status Tue Mar 22 20:00:33 2005 IƱigo Serna * lfm/__init__.py (sysprogs): added xargs Tue Mar 22 17:49:16 2005 IƱigo Serna * lfm/files.py: moved 'findgrep' and 'find' functions to utils.py * lfm/utils.py (do_findgrep): moved function from files.py, name changed to avoid collision * lfm/utils.py (do_find): moved function from files.py, name changed to avoid collision * lfm/actions.py (findgrep): use functions from utils.py Tue Mar 22 17:30:57 2005 IƱigo Serna * lfm/utils.py (get_shell_output): moved from old files.py:exec_cmd2 * lfm/files.py (exec_cmd2): moved to utils.py:get_shell_output * lfm/files.py (exec_cmd): removed function * lfm/preferences.py (Preferences.check_progs): use utils.py:get_shell_output Tue Mar 22 17:10:01 2005 IƱigo Serna * lfm/__init__.py (sysprogs): added find, grep, egrep, which * lfm/__init__.py (defaultprogs): removed find, grep, egrep * lfm/actions.py (findgrep): use 'find' and 'egrep' from __init__.py:sysprogs Tue Mar 22 16:51:34 2005 IƱigo Serna * lfm/__init__.py (sysprogs): hardcoding is a bad idea "tar, bzip2, gzip, zip, unzip, rar, grep, find" defined here * lfm/compress.py: use __init__.py:sysprogs * lfm/pyview.py (FileView.__find): use __init__.py:sysprogs Tue Mar 22 14:30:13 2005 IƱigo Serna * lfm/pyview.py (FileView.__init__): grep is harcoded now, no need to look for it => faster start * lfm/pyview.py (FileView.__find): changed os.popen for os.popen3 to capture error if grep is not found * lfm/pyview.py (exec_cmd): removed, not needed anymore Tue Mar 22 13:23:20 2005 IƱigo Serna * lfm/vfs.py (init): FIXED BUG: make this work after last changes in run_shell * lfm/vfs.py (regenerate_file): Tue Mar 22 13:03:57 2005 IƱigo Serna * lfm/utils.py (run_shell): changes to run inside run_loop * lfm/utils.py (do_compress_uncompress_file): * lfm/utils.py (do_uncompress_dir): * lfm/utils.py (uncompress_dir): * lfm/utils.py (do_compress_dir): * lfm/utils.py (compress_dir): Tue Mar 22 13:03:09 2005 IƱigo Serna * lfm/utils.py (run_loop): new API to execute a function for each element in a list * lfm/utils.py (child_process): Tue Mar 22 13:02:53 2005 IƱigo Serna * lfm/utils.py (run_thread): some stylist changes Mon Mar 21 23:34:33 2005 IƱigo Serna * lfm/messages.py (EntryLine.show): FIXED BUG: EntryLine wasn't showed completely Mon Mar 21 23:24:41 2005 IƱigo Serna * lfm/pyview.py (InternalView.init_curses): FIXED BUG: crash in file info if long file name Mon Mar 21 22:59:26 2005 IƱigo Serna * lfm/lfm.py (LfmApp): FIXED BUG: "lfm path1 path2" didn't work because shell lfm function returned a string of args instead of a list which is what we were asking for Mon Mar 21 01:03:32 2005 IƱigo Serna * lfm/actions.py (general_menu): new option: Regenerate programs removed option: Save config, it's superfluous * lfm/preferences.py (Preferences.__init__): don't use defaultprogs directly, make a copy instead, so we could regenerate them Mon Mar 21 00:57:46 2005 IƱigo Serna * lfm/lfm.py (Lfm.__init__): don't check default programs at start so start is faster now Sun Mar 20 23:51:36 2005 IƱigo Serna * lfm/__init__.py (defaultprogs): tar, gzip, bzip2, zip, unzip removed, these are system programs and must exist, so we don't check for them Sun Mar 20 21:24:00 2005 IƱigo Serna * lfm/utils.py (run_shell): show error FIXED BUG: get error if program finishes prematuraly Sun Mar 20 19:38:55 2005 IƱigo Serna * setup.py (setup): added forgotten lfm/compress Sun Mar 20 18:04:30 2005 IƱigo Serna * lfm/utils.py (run_shell): new function to execute command in shell, run_thread will be obsoleted Sun Mar 20 17:59:19 2005 IƱigo Serna * lfm/vfs.py (init): updated to new compressing API, nicer code * lfm/vfs.py (exit): * lfm/vfs.py (regenerate_file): Sun Mar 20 17:50:59 2005 IƱigo Serna * lfm/actions.py (cursor_right): updated to new compression API * lfm/actions.py (enter): * lfm/actions.py (file_menu): added RAR un/compression Sun Mar 20 17:46:15 2005 IƱigo Serna * lfm/utils.py (do_compress_uncompress_file): update to new compression API * lfm/utils.py (compress_uncompress_file): * lfm/utils.py (do_uncompress_dir): * lfm/utils.py (uncompress_dir): * lfm/utils.py (do_compress_dir): * lfm/utils.py (compress_dir): Sun Mar 20 17:46:15 2005 IƱigo Serna * lfm/compress.py: new source file, un/compressing staff The new API is more modular and easier to use Supported packagers: TBZ2, BZ2, TGZ, GZ, ZIP, RAR Sun Mar 20 17:43:24 2005 IƱigo Serna * lfm/utils.py (run_thread): FIXED BUG: args passed to messages.win_nokey Sun Mar 20 17:42:04 2005 IƱigo Serna * lfm/__init__.py (DATE): updated Sat Mar 19 15:34:05 2005 IƱigo Serna * default.css: new file: style sheet to build html docs Thu Oct 14 00:45:57 2004 IƱigo Serna * lfm/vfs.py (init): FIXED BUG: stopping vfs creation, doesn't return to original dir Thu Oct 14 00:05:30 2004 IƱigo Serna * lfm/actions.py (general_menu): FIXED BUG: reload configuration after modifying it Thu Oct 14 00:00:55 2004 IƱigo Serna * lfm/messages.py (win): fixed a typo Wed Oct 13 23:59:23 2004 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): speed up cursor move and try not to waste much CPU Mon Aug 2 00:28:37 2004 IƱigo Serna * lfm/lfm.py.ok: deleted old files * patches/*: Mon Jul 26 18:20:06 2004 IƱigo Serna * setup.py: man pages must be installed in $PREFIX/share/man Wed Jul 21 19:26:38 2004 IƱigo Serna * lfm/files.py (fix_chars_in_filename): FIX for debian bug #260401: some chars in filename make lfm crash see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=260401 for more info * lfm/files.py (get_fileinfo_dict): use fix_chars_in_filename * lfm/lfm.py (Lfm.__show_bars): Sat Jul 3 11:45:28 2004 IƱigo Serna * lfm.1, pyview.1: man files, thanks to Sebastien Bacher for them * setup.py: more metadata for pypi, install man files Fri Jul 2 00:50:01 2004 IƱigo Serna * lfm/lfm.py (Panel.__get_fileinfo_str_long): limit length of owner and group strings Thu Jul 1 11:50:52 2004 IƱigo Serna * lfm/messages.py (MenuWin.__init__): fixed another crash because of fucking curses.cur_set() returning ERR Wed Jun 30 22:24:05 2004 IƱigo Serna * setup.py: upgrade to version 0.91 * PKG-INFO: * lfm/__init__.py: Wed Jun 30 22:21:26 2004 IƱigo Serna * README: documentation updated, use reST for easy generation of HTML * README.pyview: * TODO: * NEWS: Mon Jun 28 00:31:55 2004 IƱigo Serna * lfm/preferences.py (Preferences.__init__): new option: show_dotfiles flag * lfm/lfm.py (Panel.init_dir): * lfm/files.py (get_dir): Sun Jun 27 23:41:21 2004 IƱigo Serna * lfm/preferences.py (Preferences.__init__): new option: detach_terminal_at_exec flag * lfm/actions.py (do_special_view_file): use detach_terminal_at_exec Sun Jun 27 20:03:11 2004 IƱigo Serna * lfm/preferences.py (Preferences.load): apps and file types can be stored in configuration now * lfm/preferences.py (Preferences.save): Sun Jun 27 18:45:37 2004 IƱigo Serna * lfm/pyview.py (PyView): always write debug info to pyview-log.debug Sun Jun 27 18:27:49 2004 IƱigo Serna * lfm/__init__.py (defaultprogs): now each app only has one associated program, not a list of programs. WARNING: this breaks old .lfmrc, but speeds up start * lfm/lfm.py (Lfm.__init__): check for valid programs * lfm/preferences.py (Preferences.__init__): only loads default progs, but don't check them * lfm/preferences.py (Preferences.check_progs): changed function name from 'check_defaultprogs' * lfm/actions.py (do_special_view_file): warn when can't open special file and default to pager view Sun Jun 27 16:34:11 2004 IƱigo Serna * lfm/preferences.py (Preferences.load): BUG FIXED: didn't load sort mode Sun Jun 27 15:23:19 2004 IƱigo Serna * lfm/messages.py (ChangePerms.manage_keys): show users and groups sorted alphabetically Sun Jun 27 14:57:18 2004 IƱigo Serna * lfm/actions.py (file_menu): add support for uncompress in other panel * lfm/utils.py (uncompress_dir): * lfm/utils.py (do_uncompress_dir): Sun Jun 27 03:10:27 2004 IƱigo Serna * lfm/lfm.py: resizing implemented in lfm * lfm/actions.py (keytable): * lfm/actions.py (resize_window): * lfm/lfm.py (Lfm.resize): * lfm/lfm.py (Panel.do_resize): * lfm/lfm.py (Lfm.__show_bars): * lfm/lfm.py (Panel.show): * lfm/lfm.py (Panel.__showbar): * lfm/lfm.py (Panel.__calculate_columns): new useful functions * lfm/lfm.py (Panel.__calculate_dims): Sun Jun 27 02:10:06 2004 IƱigo Serna * lfm/lfm.py (Lfm.__init__): eliminated deleted references to columns positions * lfm/preferences.py (Preferences.__init__): * lfm/preferences.py (Preferences.load): * lfm/preferences.py (Preferences.save): Sat Jun 26 23:49:49 2004 IƱigo Serna * lfm/lfm.py (Panel.show): - 1-panel view changed, now filename has more space - file info formatting has been translated here * lfm/lfm.py (Panel.__showbar): file info formatting has been translated here * lfm/lfm.py (Panel.__get_fileinfo_str_long): get file info formatted string, 1-panel version * lfm/lfm.py (Panel.__get_fileinfo_str_short): get file info formatted string, 2-panel version * lfm/files.py (get_fileinfo_dict): - changed function name from 'get_fileinfo_str' - now this function only returns info, not a formatted string * lfm/files.py (get_fileinfostr_short): function eliminated, use 'get_fileinfo_dict' instead Sat Jun 26 21:18:00 2004 IƱigo Serna * pyview.py (FileView): - resizing implemented in pyview - amount of info is reduced in function of window size * pyview.py (FileView.show_hex): show as much bytes as window size allows Sat Jun 26 20:57:56 2004 IƱigo Serna * messages.py (get_a_key): ESC now closes dialogs * messages.py (confirm): * messages.py (confirm_all): * messages.py (Yes_No_Buttons.manage_keys): * messages.py (EntryLine.manage_keys): * messages.py (SelectItem.manage_keys): * messages.py (FindfilesWin.manage_keys): * messages.py (MenuWin.manage_keys): * messages.py (ChangePerms.manage_keys): * pyview.py (InternalView.run): Sat Jun 26 16:27:34 2004 IƱigo Serna * utils.py (compress_uncompress_file): FIXED BUG: 'app' wasn't passed to 'run_thread' * actions.py (Tree.run): FIXED BUG: Ctrl-T Ctrl-T crashes tree view Thu Dec 4 01:41:28 2003 IƱigo Serna * README: add " surrounding $* in bash function to allow lfm start at dirs with space characters in name Thu Dec 4 00:37:26 2003 IƱigo Serna * lfm/files.py (findgrep): fixed bug: grep with '-' char in pattern, it was interpreted as grep option Tue Dec 2 22:46:19 2003 IƱigo Serna * lfm/actions.py (select_bookmark): select bookmark from a menu. Function associated to key Ctrl-D Tue Dec 2 00:11:07 2003 IƱigo Serna * lfm/actions.py (rename): new feature Tue Dec 2 00:08:57 2003 IƱigo Serna * lfm/actions.py (swap_panels): fixed a bug introduced when lfm.py:panel.init_curses changed Tue Dec 2 00:04:51 2003 IƱigo Serna * lfm/lfm.py: moved keys actions to actions.py file * lfm/actions.py: keys actions * lfm/utils.py: run_thread, un/compress functions * lfm/vfs.py: vfs functions Sun Apr 27 02:30:46 2003 IƱigo Serna * lfm/files.py (get_fileinfostr_short): fixed bug when files > 1Gb size Sun Apr 27 00:26:09 2003 IƱigo Serna * lfm/__init__.py: coltbl is now here Sun Apr 27 00:20:55 2003 IƱigo Serna * lfm/lfm.py (cursor_show): wrappers over curses.curs_set() with * lfm/lfm.py (cursor_hide): try-except protection Sat Apr 26 23:10:05 2003 IƱigo Serna * setup.py (classifiers): added classifiers Sat Apr 26 23:08:37 2003 IƱigo Serna * *.py: added "# -*- coding: iso-8859-15 -*-" to conform with PEP 0263 Wed Sep 4 01:04:57 2002 IƱigo Serna * lfm/__init__.py (VERSION): released version 0.9 * lfm/setup.py: Mon Sep 2 17:24:04 2002 IƱigo Serna * lfm/messages.py (Entry.run): don't append '*' to historic * lfm/messages.py (DoubleEntry.run): Mon Sep 2 17:22:55 2002 IƱigo Serna * lfm/lfm.py (sort): Fix bug: fix cursor bar position after sorting Tue Aug 20 16:50:57 2002 IƱigo Serna * lfm/pyview.py (InternalView.__validate_buf): simplify code: if a: a = 0; else: a = 1 => a = not a * lfm/messages.py (confirm): * lfm/messages.py (EntryLine.manage_keys): * lfm/lfm.py (Lfm.run): * lfm/lfm.py (Panel.manage_keys): Tue Aug 20 16:37:23 2002 IƱigo Serna * lfm/pyview.py (FileView.__move_lines): now pyview only shows last line at the end of the file. It showed blank screen before. I've written code which changes this behaviour to show last 'screen' of lines, but it is commented because I don't like it Tue Aug 20 16:16:04 2002 IƱigo Serna * lfm/pyview.py (FileView.run): new feature: go to / set bookmarks * lfm/pyview.py (FileView.__init__): initialize bookmarks with -1 * lfm/pyview.py (FileView.run): new feature: shell Tue Aug 20 16:13:37 2002 IƱigo Serna * lfm/pyview.py (PyView): read from stdin implemented * lfm/pyview.py (FileView.__init__): * lfm/pyview.py (FileView.show): show 'STDIN' as file name and path when reading from stdin * lfm/pyview.py (read_stdin): new function * lfm/pyview.py (create_temp_for_stdin): new function Tue Aug 20 12:21:14 2002 IƱigo Serna * lfm/pyview.py (FileView.run): cosmetic changes in string * lfm/lfm.py (Panel.manage_keys): Tue Aug 20 12:18:38 2002 IƱigo Serna * lfm/pyview.py (FileView.show): move 'bytes' information one char left Tue Aug 20 12:11:00 2002 IƱigo Serna * lfm/pyview.py (FileView.__get_lines_text): Fix bug: last char in file is not showed. Erasing '\n\r' chars when loading lines with rstrip() instead of [:-1] * lfm/pyview.py (FileView.__get_prev_lines_text): * lfm/pyview.py (FileView.__get_line_length): * lfm/pyview.py (FileView.__get_1line): * lfm/pyview.py (FileView.__get_file_info): Fix bug: number of lines in file was +1. * lfm/pyview.py (FileView.run): modified cursor down, page next, end Sat Aug 17 19:33:33 2002 IƱigo Serna * lfm/lfm.py (run_thread): Fix bug: create temporary files with mask 0066, i.e. only owner can read/write them * lfm/lfm.py (do_compress_dir): * lfm/lfm.py (vfs_regenerate_file): Sat Aug 17 19:20:39 2002 IƱigo Serna * lfm/lfm.py (vfs_init): Fix bug: create directories with perms 0700 to avoid other people read them * lfm/lfm.py (vfs_copy): * lfm/lfm.py (vfs_pan_init): * lfm/lfm.py (vfs_pan_copy): Sat Aug 17 17:19:43 2002 IƱigo Serna * lfm/lfm.py (LfmApp): Fix bug: when lfm exits after checking command line options, make 'lfm' shell script don't show path error * lfm/lfm.py (lfm_exit): new function Sat Aug 17 15:37:48 2002 IƱigo Serna * lfm/lfm.py (compress_dir): don't compress '..' directory Sat Aug 17 15:34:47 2002 IƱigo Serna * lfm/lfm.py (compress_dir): Fix bug: don't check 'tar' if un/zip * lfm/lfm.py (vfs_init): Sat Aug 17 14:26:48 2002 IƱigo Serna * lfm/lfm.py (vfs_init): modified to allow zip-files vfs * lfm/lfm.py (vfs_regenerate_file): Sat Aug 17 14:17:41 2002 IƱigo Serna * lfm/lfm.py (compress_dir): modified to allow compress to zip-files * lfm/lfm.py (do_compress_dir): * lfm/lfm.py (uncompress_dir): modified to allow uncompress zip-files * lfm/lfm.py (do_uncompress_dir): * lfm/lfm.py (check_compressed_tarfile): * lfm/__init__.py (defaultprogs): zip and unzip programs added * lfm/lfm.py (Panel.manage_keys): added zip functionalities to file menu Sat Aug 17 12:32:38 2002 IƱigo Serna * setup.py: use python2 as default interpreter * lfm/lfm: * lfm/pyview: * lfm/lfm.py: * lfm/pyview.py: Fri Aug 16 13:37:34 2002 IƱigo Serna * lfm/lfm.py (vfs_init): show correct filename when an error occurs while uncompressing file Wed Aug 14 18:55:22 2002 IƱigo Serna * lfm/lfm.py (vfs_exit): added 'rebuild vfs' question / option * lfm/preferences.py (Preferences.__init__): added 'ask_rebuild_vfs' confirmation and 'rebuild_vfs' option flags * README: modified accordingly to new behaviour Wed Aug 14 18:44:40 2002 IƱigo Serna * lfm/__init__.py: email address changed: * lfm/pyview.py: * lfm/lfm.py: * setup.py: * README: * README.pyview:: Wed Aug 14 18:06:35 2002 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): Fix bug: when un/compressing files cursorbar must remain in the same file Wed Aug 14 17:11:02 2002 IƱigo Serna * lfm/lfm.py (do_special_view_file): fork twice to avoid zombie processes * lfm/files.py (exec_cmd): new threaded exec command to avoid zombie processes * lfm/preferences.py (Preferences.check_defaultprogs): use files.exec_cmd instead of os.popen4 * lfm/pyview.py (exec_cmd): idem * lfm/pyview.py (FileView.__init__): Wed Aug 14 17:10:19 2002 IƱigo Serna * setup.py: execute setup with python2 Mon Aug 12 23:00:10 2002 IƱigo Serna Applied some good patches from Bartosz Oler (liar@furrynet.org): 1. colors customization: Now you can customize the colors lfm use in the configuration file, 'colors' section. Each color is defined by a string with its name. It looks like this: element foreground_color background_color * lfm/lfm.py (Lfm.init_curses): Removed old static colors initialization * lfm/lfm.py (Lfm.run): Preferences loading has been moved from here * lfm/lfm.py (Lfm.__init__): ... to here * lfm/lfm.py (Lfm.set_color): New function that returns curses color if exists, otherwise return default. * lfm/preferences.py (Preferences.__init__): default colors * lfm/preferences.py (Preferences.load): read colors from configuration file or use default colors * lfm/preferences.py (Preferences.save): save used colors to configuration file 2. Allow preferences values to contain colons in the configuration file. * lfm/preferences.py (Preferences.load): Allow preferences values to contain colons. 3. Lack of a bookmark's definition shouldn't be an error. * lfm/preferences.py (Preferences.load): "Lack of a bookmark's definition shouldn't be an error. I.e. I have only few bookmarks and it annoyed me when lfm was printing: 'Bad bookmark blah, blah...'. Now, message is printed only if directory doesn't exist." Thu Mar 7 19:24:22 2002 IƱigo Serna * lfm/lfm.py (Panel.init_dir): Fix bug: when, at start, lfm can't enter into a dir due to directory permissions Thu Mar 7 18:57:31 2002 IƱigo Serna * lfm/pyview.py (FileView.__init__): Fix bug: when user hasn't permissions to read file, exit gracefully instead of crashing Wed Mar 6 13:57:56 2002 IƱigo Serna * lfm/messages.py (FixSizeCommonWindow.__init__): fix len(text) problem Tue Mar 5 13:43:57 2002 IƱigo Serna * README: added " to the 'cd' command in the lfm() shell function to allow changing to dir containing spaces Tue Mar 5 13:31:13 2002 IƱigo Serna * lfm/messages.py (SelectItem.show): Added win refresh to assure window refresh properly * lfm/messages.py (FindfilesWin.show): Tue Mar 5 13:20:41 2002 IƱigo Serna * lfm/messages.py (SelectItem.show): Fix bug: crash if len(line) == width of cursorbar window Tue Mar 5 13:00:26 2002 IƱigo Serna * lfm/lfm.py (do_execute_file): eliminate extra '"' Sun Mar 3 22:19:49 2002 IƱigo Serna * lfm/__init__.py (VERSION): release version 0.8 Sun Mar 3 22:17:21 2002 IƱigo Serna * lfm/lfm.py (do_uncompress_dir): well, it seems '-C' is not a valid option to specify output directory in not gnu tars, so change code Sun Mar 3 20:30:09 2002 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): eliminate 'y' feature (un/crypt) Sun Mar 3 00:24:12 2002 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): tree panel implemented * lfm/lfm.py (Tree): new class to implement a tree panel Sat Mar 2 13:36:17 2002 IƱigo Serna * lfm/pyview.py (FileView.run): fix bug: page up / down don't scroll correctly in wrap mode if line doesn't fix in screen Thu Feb 28 23:32:36 2002 IƱigo Serna * lfm/lfm: new scripts * lfm/pyview: * lfm/lfm.py: * lfm/pyview.py: * lfm/__init__.py: contain globals Thu Feb 28 20:32:31 2002 IƱigo Serna * lfm/files.py (do_copy): restore not to copy directory mode, times, owner and group attributes, because it fails when copying from not writable fs Thu Feb 28 19:06:43 2002 IƱigo Serna * lfm/messages.py (win_nokey): now uses FixSizeCommonWindow Thu Feb 28 19:06:32 2002 IƱigo Serna * lfm/messages.py (FixSizeCommonWindow): new message window class to show work in progress Thu Feb 28 18:25:39 2002 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): fix 'show same dir in two panels' with vfs Thu Feb 28 18:21:48 2002 IƱigo Serna * lfm/lfm.py (vfs_copy): new functions * lfm/lfm.py (vfs_pan_copy): Thu Feb 28 18:00:00 2002 IƱigo Serna * lfm/lfm.py (findgrep): panelize vfs implemented * lfm/lfm.py (vfs_regenerate_file): Thu Feb 28 17:59:21 2002 IƱigo Serna * lfm/lfm.py (vfs_pan_init): new functions * lfm/lfm.py (vfs_pan_regenerate): Thu Feb 28 15:36:40 2002 IƱigo Serna * lfm/lfm.py (findgrep): fix bug: if selected file has a ':' in name Thu Feb 28 14:32:27 2002 IƱigo Serna * lfm/lfm.py (vfs_regenerate_file): check if it can regenerate vfs file before doing it Thu Feb 28 13:42:27 2002 IƱigo Serna * lfm/files.py (move): fix bug: catching an exception after not been able to copy => it crashed when trying to delete copied files Thu Feb 28 13:33:25 2002 IƱigo Serna * lfm/messages.py (CommonWindow.__init__): fix bug: if #lines of text > dialog.width Thu Feb 28 12:22:49 2002 IƱigo Serna * lfm/lfm.py (run_thread): now message appear in a dialog, clean up function Thu Feb 28 11:41:17 2002 IƱigo Serna * lfm/messages.py (CommonWindow): added option not to wait for a key * lfm/messages.py (win_nokey): new function Thu Feb 28 01:41:09 2002 IƱigo Serna * lfm/lfm.py (Lfm.run): vfs implemented!!! * lfm/lfm.py (Lfm.show_bars): * lfm/lfm.py (Panel.show): * lfm/lfm.py (Panel.init_dir): * lfm/lfm.py (Panel.refresh_panel): * lfm/lfm.py (Panel.manage_keys): * lfm/lfm.py (Panel.show_info): Wed Feb 27 17:31:57 2002 IƱigo Serna * lfm/lfm.py (do_uncompress_dir): added tar '-C' option to specify output directory. Does it work in not gnu tars? * lfm/lfm.py (vfs_init): new functions * lfm/lfm.py (vfs_exit): * lfm/lfm.py (vfs_regenerate_file): * lfm/lfm.py (vfs_join): * lfm/files.py (mktemp): new function, wrapper to tempfile.mktemp Wed Feb 27 01:26:25 2002 IƱigo Serna * lfm/files.py (do_copy): change 'shutil.copytree' by own code when walking tree to fix next bug: "Overwrite dirs when moving or copying => walk dirs f.e. panel1: dir/a panel2: dir/b /b /c Now when moving dir from panel1 to panel2, the 'dir' destination is overwritten, so 'c'-file is lost. This is not the desired behaviour" Tue Feb 26 15:55:13 2002 IƱigo Serna * lfm/messages.py (SelectItem.show): fix bug: "messages.SelectItem, messages.FindfilesWin, messages.MenuWin, messages.ChangePerms: upperleft corner disappears" * lfm/messages.py (FindfilesWin.show): * lfm/messages.py (MenuWin.show): * lfm/messages.py (ChangePerms.show): Tue Feb 26 14:10:20 2002 IƱigo Serna * lfm/messages.py (ChangePerms.manage_keys): change cursor movement behaviour, now it's circular Tue Feb 26 13:43:17 2002 IƱigo Serna * lfm/pyview.py (FileView.show_chr): changing 'addch' by 'addstr' shows individual chars >= 0xA0 (meta chars) correctly, neither in reversed video or as 2 chars * lfm/pyview.py (FileView.show_str): * lfm/pyview.py (FileView.show_hex): Tue Feb 26 13:13:57 2002 IƱigo Serna * lfm/pyview.py (FileView.run): fix bug: "if wrap mode => fix prev/next page & up/down cursor". Now they move to screen lines, not to physical lines * lfm/pyview.py (FileView.show_text_wrap): new functions * lfm/pyview.py (FileView.show_text_nowrap): * lfm/pyview.py (FileView.__get_1line): * lfm/pyview.py (FileView.__get_line_length): * lfm/pyview.py (FileView.__get_prev_lines_text): Mon Feb 25 12:47:32 2002 IƱigo Serna * lfm/lfm.py (Panel.refresh_panel): fix bug: if panel2 shows 'a' directory and in panel1 'a' is moved or deleted, lfm crashes Mon Feb 25 12:10:48 2002 IƱigo Serna * lfm/lfm.py (DATE): change copyright date to years 2001-2 * lfm/pyview.py (DATE): Mon Feb 25 11:54:15 2002 IƱigo Serna * lfm/pyview.py (FileView.show): fix bug when filename length is too big * lfm/messages.py (CommonWindow.__init__): Sat Feb 23 16:21:46 2002 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): after moving a file cursor goes to next file in panel, as deletion does Sun Nov 25 19:40:19 2001 IƱigo Serna * lfm/lfm.py (VERSION): release of version 0.7 * lfm/pyview.py (VERSION): upgraded to version 0.3 Sun Nov 25 19:40:19 2001 IƱigo Serna * lfm/files.py (complete): fix complete code. It works ok now * lfm/lfm.py (doEntry): * lfm/lfm.py (doDoubleEntry): * lfm/messages.py (EntryLine): * lfm/messages.py (Entry): * lfm/messages.py (DoubleEntry): Sun Nov 25 15:48:34 2001 IƱigo Serna * lfm/lfm.py (do_something_on_file): change to initial behaviour: do_someting_on_file has not to capture output or been executed inside other thread. Old code remains comented Sun Nov 25 02:16:54 2001 IƱigo Serna * lfm/files.py (complete): fix a bug when trying to complete something that is not a valid path Sun Nov 25 01:49:29 2001 IƱigo Serna * lfm/lfm.py (do_something_on_file): capture output & option to show it * lfm/lfm.py (do_do_something_on_file): new function to be run inside run_thread Sun Nov 25 01:17:53 2001 IƱigo Serna * lfm/preferences.py (Preferences.__init__): added new preference: show_output_after_exec, defaults to yes Sun Nov 25 00:27:18 2001 IƱigo Serna * lfm/pyview.py (InternalView.__validate_buf): added option not to center Sat Nov 24 18:37:07 2001 IƱigo Serna * lfm/lfm.py (do_something_on_file): chdir to file path before doing something on file Sat Nov 24 18:24:39 2001 IƱigo Serna * lfm/lfm.py (do_uncompress_dir): remove tar 'v' flag when * lfm/lfm.py (do_compress_dir): un/compressing tar files Sun Nov 18 22:15:47 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): fix cursor position after deleting files Sun Nov 18 18:33:07 2001 IƱigo Serna * lfm/pyview.py (FileView.show_text): fixed other bug in wrapped mode Sun Nov 18 13:01:23 2001 IƱigo Serna * lfm/pyview.py (FileView.__init__): if file is empty exists gracefully Sun Nov 18 12:57:09 2001 IƱigo Serna * lfm/pyview.py (FileView.show): fix path printing if it's too big Thu Nov 1 15:30:06 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): now copy, move, delete can be stopped Thu Nov 1 13:56:23 2001 IƱigo Serna * lfm/files.py (__get_size): capture errors Thu Nov 1 13:01:31 2001 IƱigo Serna * lfm/lfm.py (compress_dir): now compress directories can be stopped * lfm/lfm.py (do_compress_dir): Thu Nov 1 12:54:33 2001 IƱigo Serna * lfm/messages.py (CommonWindow.__init__): fix a bug: when len(title) > len(text) Thu Nov 1 12:26:16 2001 IƱigo Serna * lfm/lfm.py (uncompress_dir): now uncompress tar files can be stopped * lfm/lfm.py (do_uncompress_dir): Thu Nov 1 12:10:14 2001 IƱigo Serna * lfm/lfm.py (run_thread.thread_quit_handler): added a 0.05 secs sleep to avoid wasting resources silly Thu Nov 1 11:55:24 2001 IƱigo Serna * lfm/lfm.py (compress_uncompress_file): now un/compress can be stopped * lfm/lfm.py (do_compress_uncompress_file): Mon Oct 29 00:50:03 2001 IƱigo Serna * lfm/lfm.py (compress_dir): fix bug: tar cvf - dir | gzip >dir.tar.gz when compressing directories did tar processes remain, it seems it was because of the '>' redirection awaiting eternally to completition Sun Oct 28 12:15:49 2001 IƱigo Serna * lfm/lfm.py (uncompress_dir): check errors * lfm/lfm.py (compress_dir): Sun Oct 28 11:50:24 2001 IƱigo Serna * lfm/lfm.py (compress_uncompress_file): check errors Sun Oct 28 11:20:42 2001 IƱigo Serna * lfm/files.py (findgrep): use popen3 instead of popen and forget * lfm/files.py (find): stderr output Sun Oct 28 02:29:38 2001 IƱigo Serna * lfm/lfm.py: change popen2.popen[34] by os.popen[34] functions * lfm/pyview.py: * lfm/preferences.py: Sat Oct 27 16:24:39 2001 IƱigo Serna * lfm/pyview.py (VERSION): upgraded to v0.2 Sat Oct 27 16:48:39 2001 IƱigo Serna * lfm/pyview.py (FileView.run): added find and find next / previous Sat Oct 27 16:23:25 2001 IƱigo Serna * lfm/pyview.py (FileView.__find): new functions * lfm/pyview.py (FileView.__find_next): * lfm/pyview.py (FileView.__find_previous): Sat Oct 27 15:45:57 2001 IƱigo Serna * lfm/pyview.py (FileView.__init__): check for 'grep' Sat Oct 27 15:45:16 2001 IƱigo Serna * README.pyview (Keys): added find information Sat Oct 27 12:35:31 2001 IƱigo Serna * lfm/pyview.py (PYVIEW_README): update help Sat Oct 20 23:27:00 2001 IƱigo Serna * lfm/pyview.py (FileView.show_str): don't show '\r' in dos text files Thu Aug 23 00:59:59 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): added compress directory to * lfm/lfm.py (compress_dir): .tar.bz2 Thu Aug 23 00:42:43 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): added README.pyview to help Thu Aug 23 00:34:09 2001 IƱigo Serna * lfm/pyview.py (InternalView): allow scrolling if buffer doesn't fit in body window Thu Aug 23 00:22:19 2001 IƱigo Serna * README.pyview: new file, documentation for pyview Wed Aug 22 18:28:34 2001 IƱigo Serna * lfm/lfm.py (Panel.show_info): fixed a small bug when showing info of a file on / Wed Aug 22 16:52:47 2001 IƱigo Serna * lfm/pyview.py (FileView.__get_file_info): speed up function, before it was very slow on large files Wed Aug 22 00:22:17 2001 IƱigo Serna * lfm/lfm.py (Panel.do_show_file_info): implemented 'show file info' Tue Aug 21 21:28:39 2001 IƱigo Serna * lfm/pyview.py (InternalView.__validate_buf): check for lines not to be wider than screen width Tue Aug 21 20:53:43 2001 IƱigo Serna * lfm/files.py (get_user_fullname): new function Tue Aug 21 20:11:30 2001 IƱigo Serna * lfm/pyview.py: 'import messages' must be inside a try clause to avoid raising an exception in lfm, I suppose because lfm imports messages module before Tue Aug 21 19:28:30 2001 IƱigo Serna * lfm/lfm.py (show_fs_info): new function * lfm/lfm.py (Panel.manage_keys): rewrite 'show filesystems info' to use internal viewer Tue Aug 21 01:42:15 2001 IƱigo Serna * lfm/pyview.py (InternalView): new viewer class to use internally Mon Aug 20 20:32:58 2001 IƱigo Serna * lfm/preferences.py (Preferences.check_defaultprogs): fixed a bug Mon Aug 20 20:31:37 2001 IƱigo Serna * lfm/lfm.py (Lfm.run): default configuration is now saved inmediately Mon Aug 20 20:25:36 2001 IƱigo Serna * lfm/lfm.py (defaultprogs): default pager changed to pyview because this is the best one ;-) Mon Aug 20 20:23:02 2001 IƱigo Serna * lfm/pyview.py (View.run): implemented 'goto line / byte' Sun Aug 19 16:19:10 2001 IƱigo Serna * lfm/pyview.py: start of the lfm viewer to be used both internally and externally as a standalone program -> 'pyview' Sun Aug 19 15:12:18 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): change help behaviour to allow to read README, NEWS, TODO, ChangeLog or COPYING files Sun Aug 19 14:17:19 2001 IƱigo Serna * lfm/lfm.py (do_special_view_file): new function to show special files: html, graphics, ... * lfm/lfm.py (defaultprogs): defined new programs * lfm/lfm.py (filetypes): defined new file types Sun Aug 19 12:22:11 2001 IƱigo Serna * lfm/messages.py (EntryLine.manage_keys): added Ctrl-D key to delete the whole content of the EntryLine Sat Aug 18 19:36:50 2001 IƱigo Serna * lfm/files.py (findgrep): fixed a bug around filename with " in grep call Sat Aug 18 19:31:01 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): implemented help Tue Aug 7 19:43:40 2001 IƱigo Serna * lfm/lfm.py (VERSION): release of version 0.5 Tue Aug 7 19:02:39 2001 IƱigo Serna * setup.py (DOC_FILES): setup.py must install docs Tue Aug 7 18:23:20 2001 IƱigo Serna * lfm/files.py (__get_size): 'b' is not a valid flag for 'du -s' in solaris, so get Kb and convert to bytes by hand Mon Aug 6 00:11:18 2001 IƱigo Serna * lfm/lfm.py (check_compressed_tarfile): new function splitted from uncompress_dir for future uses Sun Aug 5 20:52:36 2001 IƱigo Serna * lfm/messages.py (FindfilesWin.manage_keys): fixed what 'enter' returns Sun Aug 5 20:28:23 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): implemented 'show filesystems info' * lfm/messages.py (show_fs_info): new function * lfm/files.py (get_fs_info): new function Sun Aug 5 18:34:32 2001 IƱigo Serna * NEWS: added a new file with changes between versions Sun Aug 5 18:23:11 2001 IƱigo Serna * lfm/lfm.py (findgrep): fix a bug causing a crash when find and/or grep returns nothing Sun Aug 5 18:00:02 2001 IƱigo Serna * lfm/files.py (move): when a problem appears while copying files, then don't delete original file but do it with the destination, because it is only partially written Mon Jul 23 21:27:10 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): change x and q behaviour when exiting. Now x exits to dir. in panel, q not Mon Jul 23 20:56:18 2001 IƱigo Serna * lfm/lfm.py (do_something_on_file): * lfm/lfm.py (compress_uncompress_file): * lfm/lfm.py (uncompress_dir): * lfm/lfm.py (compress_dir): * lfm/lfm.py (show_dirs_size): * lfm/lfm.py (findgrep): * lfm/lfm.py (sort): new functions to simplify Panel.manage_keys Mon Jul 23 01:21:35 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): implement 'p' in file menu: change permissions, owner or group * lfm/messages.py (ChangePerms): new window class * lfm/files.py (get_owners): new functions * lfm/files.py (get_groups): * lfm/files.py (set_perms): * lfm/files.py (set_owner_group): Sun Jul 22 19:31:30 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): File and General menu File menu: @, g, b, x, c General menu: /, #, s, o, c, a Sun Jul 22 14:31:36 2001 IƱigo Serna * lfm/messages.py (MenuWin): new window class to show file and general menu Sun Jul 22 13:47:23 2001 IƱigo Serna * lfm/preferences.py (Preferences.check_defaultprogs): use popen2.popen3 instead of os.popen to avoid messages in stderr Sun Jul 22 13:45:55 2001 IƱigo Serna * lfm/files.py (findgrep): rewrite function not to show any error to stderr. 2>/dev/null only works in bash-type shells Sun Jul 22 03:34:40 2001 IƱigo Serna * README: added info about changing directory at exit time Sun Jul 22 01:40:27 2001 IƱigo Serna * lfm/lfm.py, lfm/files.py, lfm/messages.py,lfm/preferences.py: document modules Sun Jul 22 01:28:00 2001 IƱigo Serna * lfm/lfm.py (main): now lfm returns current path if exists via 'q' or F10 keys Sat Jul 21 23:24:54 2001 IƱigo Serna * lfm/preferences.py (Preferences): now preferences class has its own file * lfm/preferences.py (Preferences.check_defaultprogs): implemented * lfm/preferences.py (Preferences.load): implemented * lfm/preferences.py (Preferences.save): implemented * lfm/preferences.py (Preferences.edit): basic implementation using file edition Sat Jul 21 16:39:46 2001 IƱigo Serna * lfm/lfm.py (Preferences.__init__): default pager changed to 'less' Fri Jul 20 19:15:31 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): find/grep implemented, except panelize option * lfm/files.py (findgrep): new functions * lfm/files.py (find): * lfm/messages.py (FindfilesWin): new class to show find/grep results Thu Jul 19 20:38:11 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): manage home and end properly * lfm/messages.py (EntryLine.manage_keys): Thu Jul 19 18:14:00 2001 IƱigo Serna * lfm/lfm.py (VERSION): version 0.4 Thu Jul 19 18:00:16 2001 IƱigo Serna * lfm/messages.py (confirm_all): new function * lfm/lfm.py (Panel.manage_keys): overwrite all implemented in copy and move Thu Jul 19 01:33:21 2001 IƱigo Serna * lfm/files.py (complete): completition function. After a couple of days it works ok now * lfm/lfm.py (doEntry): * lfm/lfm.py (doDoubleEntry): new wrappers to parse paths to feed Entry and DoubleEntry classes with proper values for completition * lfm/messages.py (Entry): * lfm/messages.py (DoubleEntry): modified to support completition Tue Jul 17 17:20:43 2001 IƱigo Serna * lfm/lfm.py (Panel.show): scroll bars in panels (not interactive) Sun Jul 15 03:15:04 2001 IƱigo Serna * lfm/messages.py (Yes_no_Buttons): new class * lfm/messages.py (EntryLine): new class to ease entries with many entry lines * lfm/messages.py (Entry): rewrite to use upper classes * lfm/messages.py (DoubleEntry): new class Sat Jul 14 20:12:55 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): when some dirs are selected and 'show dirs size', only show size of the selected dirs Sat Jul 14 12:07:17 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): added 'special regards' ;-) Wed Jul 11 19:23:15 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): fix a problem with enter key Tue Jul 10 18:53:29 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): fixed a problem when using files which names contain spaces Tue Jul 10 01:36:07 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): #: show dirs size Tue Jul 10 00:49:02 2001 IƱigo Serna * setup.py: using distutils Tue Jul 10 00:47:56 2001 IƱigo Serna * lfm/lfm.py (Panel.manage_keys): create/edit link implemented Tue Jul 10 00:47:16 2001 IƱigo Serna * lfm/files.py (modify_link): * lfm/files.py (create_link): added functions Fri Jul 6 00:30:09 2001 IƱigo Serna * lfm.py (app): version 0.3 Thu Jul 5 19:38:17 2001 IƱigo Serna * messages.py (Entry): add historic support Thu Jul 5 18:37:29 2001 IƱigo Serna * lfm.py (Panel.manage_keys): do something on file implemented Tue Jul 3 01:38:47 2001 IƱigo Serna * messages.py (Entry): rewrite class using curses.pad Tue Jul 3 01:07:42 2001 IƱigo Serna * messages.py (confirm): rewrite function using curses.pad Tue Jul 3 01:05:27 2001 IƱigo Serna * messages.py (error): * messages.py (win): * messages.py (notyet): rewritten to use new CommonWindow class Tue Jul 3 01:06:31 2001 IƱigo Serna * messages.py (CommonWindow): new class Mon Jul 2 22:25:28 2001 IƱigo Serna * lfm.py (Panel.manage_keys): * files.py (copy): * files.py (move): added overwrite pref to copy and move Mon Jul 2 20:14:15 2001 IƱigo Serna * lfm.py (Panel.manage_keys): added confirmation pref to delete Mon Jul 2 19:55:30 2001 IƱigo Serna * lfm.py (Panel.manage_keys): touch file implemented Mon Jul 2 19:20:38 2001 IƱigo Serna * lfm.py (Panel.__init_dir): sort info saved in session Mon Jul 2 18:34:07 2001 IƱigo Serna * files.py (__do_sort): fix bug with size and date sorting Mon Jul 2 00:49:02 2001 IƱigo Serna * files.py (__do_sort): implement mix filename cases in sorting; implement reversed name, size and date sorting; fix bug in sorting by size or date when 2 or more files have the same size or date Mon Jul 2 00:47:56 2001 IƱigo Serna * files.py (sort_dir): implement mix files and dirs while sorting Mon Jul 2 00:46:16 2001 IƱigo Serna * messages.py (entry.manage_keys): added Ctrl-C key to quit window Sat Jun 30 13:57:44 2001 IƱigo Serna * lfm.py (Panel.manage_keys): go to file (Ctrl-S) implemented Sat Jun 30 13:43:11 2001 IƱigo Serna * lfm.py (Panel.manage_keys): go to / set bookmarks implemented Sat Jun 30 13:16:41 2001 IƱigo Serna * lfm.py (Preferences.__init__): start preferences support Fri Jun 29 00:43:19 2001 IƱigo Serna * lfm.py (Panel.manage_keys): implemented de/select group functions Thu Jun 28 23:36:58 2001 IƱigo Serna * The day of the first public release is coming, so I must start this ChangeLog file lfm-2.3/COPYING0000644000076400001440000010451311535256031013464 0ustar inigousers00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . lfm-2.3/lfm/0000755000076400001440000000000011565710734013213 5ustar inigousers00000000000000lfm-2.3/lfm/lfm.py0000755000076400001440000011747311565706266014370 0ustar inigousers00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2001-11 IƱigo Serna # Time-stamp: <2011-05-21 11:58:14 inigo> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . u"""lfm v2.3 - (C) 2001-11, by IƱigo Serna 'Last File Manager' is a file manager for UNIX console. It has a curses interface and it's written in Python. Released under GNU Public License, read COPYING file for more details. Usage:\tlfm [path1 [path2]] Arguments: path1 Directory to show in left pane path2 Directory to show in right pane Options: -1 Start in 1-pane mode -2 Start in 2-panes mode (default) -d, --debug Create debug file -h, --help Show help """ __author__ = u'IƱigo Serna' __revision__ = '2.3' import os, os.path import sys import time import datetime import getopt import logging import curses import curses.panel import cPickle as pickle from __init__ import * from config import Config, colors import files import actions import utils import vfs import messages import pyview ###################################################################### ##### Global variables LOG_FILE = os.path.join(os.getcwd(), 'lfm.log') MAX_TAB_HISTORY = 15 ###################################################################### ##### Lfm main class class Lfm(object): """Main application class""" def __init__(self, win, prefs): self.win = win # root window, needed for resizing self.prefs = prefs # preferences self.init_ui() self.statusbar = StatusBar(self.maxh, self) # statusbar self.cli = PowerCLI(self.maxh, self) # powercli self.lpane = Pane(PANE_MODE_LEFT, self) # left pane self.rpane = Pane(PANE_MODE_RIGHT, self) # right pane self.act_pane, self.noact_pane = self.lpane, self.rpane if self.prefs.options['num_panes'] == 1: self.lpane.mode = PANE_MODE_FULL self.lpane.init_ui() self.rpane.mode = PANE_MODE_HIDDEN self.rpane.init_ui() actions.app = messages.app = utils.app = vfs.app = pyview.app = self def load_paths(self, paths1, paths2): self.lpane.load_tabs_with_paths(paths1) self.rpane.load_tabs_with_paths(paths2) def init_ui(self): """initialize curses stuff: windows, colors...""" self.maxh, self.maxw = self.win.getmaxyx() curses.cbreak() curses.raw() self.win.leaveok(1) messages.cursor_hide() # colors if curses.has_colors(): # Translation table: color name -> curses color name colors_table = { 'black': curses.COLOR_BLACK, 'blue': curses.COLOR_BLUE, 'cyan': curses.COLOR_CYAN, 'green': curses.COLOR_GREEN, 'magenta': curses.COLOR_MAGENTA, 'red': curses.COLOR_RED, 'white': curses.COLOR_WHITE, 'yellow': curses.COLOR_YELLOW } # List of items to get color color_items = ['title', 'files', 'current_file', 'messages', 'help', 'file_info', 'error_messages1', 'error_messages2', 'buttons', 'selected_file', 'current_selected_file', 'tabs', 'temp_files', 'document_files', 'media_files', 'archive_files', 'source_files', 'graphics_files', 'data_files', 'current_file_otherpane', 'current_selected_file_otherpane', 'directories', 'exe_files', 'cli_prompt', 'cli_text'] # Initialize every color pair with user colors or with the defaults prefs_colors = self.prefs.colors for i, item_name in enumerate(color_items): pref_color_fg, pref_color_bg = prefs_colors[item_name] def_color_fg = colors_table[colors[item_name][0]] def_color_bg = colors_table[colors[item_name][1]] color_fg = colors_table.get(pref_color_fg, def_color_fg) color_bg = colors_table.get(pref_color_bg, def_color_bg) curses.init_pair(i+1, color_fg, color_bg) def resize(self): """resize windows""" h, w = self.win.getmaxyx() self.maxh, self.maxw = h, w if w == 0 or h == 2: return self.win.resize(h, w) self.lpane.do_resize(h, w) self.rpane.do_resize(h, w) self.statusbar.do_resize(h, w) self.cli.do_resize(h, w) self.regenerate() self.display() def display(self): """display/update both panes and status bar or powercli""" self.lpane.display() self.rpane.display() if self.cli.visible: self.cli.display() else: self.statusbar.display() def half_display(self): """display/update only active pane and status bar""" self.act_pane.display() self.statusbar.display() def half_display_other(self): """display/update only non-active pane and status bar""" self.noact_pane.display() self.statusbar.display() def regenerate(self): """Rebuild panes' directories""" self.lpane.regenerate() self.rpane.regenerate() def quit_program(self, icode): """save settings and prepare to quit""" for tab in self.lpane.tabs + self.rpane.tabs: if tab.vfs: vfs.exit(tab) if self.prefs.options['save_conf_at_exit']: self.prefs.save() if self.prefs.options['save_history_at_exit']: pickle.dump(messages.history, file(messages.HISTORY_FILE, 'w'), -1) if icode == -1: # change directory return self.act_pane.act_tab.path else: # exit, but don't change directory return def run(self): """run application""" while True: self.display() ret = self.act_pane.manage_keys() if ret < 0: return self.quit_program(ret) elif ret == RET_TOGGLE_PANE: if self.act_pane == self.lpane: self.act_pane, self.noact_pane = self.rpane, self.lpane else: self.act_pane, self.noact_pane = self.lpane, self.rpane elif ret == RET_TAB_NEW: tab = self.act_pane.act_tab path = tab.path if tab.vfs=='' else os.path.dirname(tab.vbase) idx = self.act_pane.tabs.index(tab) newtab = TabVfs(self.act_pane) newtab.init(path) self.act_pane.tabs.insert(idx+1, newtab) self.act_pane.act_tab = newtab elif ret == RET_TAB_CLOSE: tab = self.act_pane.act_tab idx = self.act_pane.tabs.index(tab) self.act_pane.act_tab = self.act_pane.tabs[idx-1] self.act_pane.tabs.remove(tab) del tab ###################################################################### ##### StatusBar class class StatusBar(object): """Status bar""" def __init__(self, maxh, app): self.app = app try: self.win = curses.newwin(1, app.maxw, maxh-1, 0) except curses.error: print 'Can\'t create StatusBar window' sys.exit(-1) if curses.has_colors(): self.win.bkgd(curses.color_pair(1)) def do_resize(self, h, w): self.win.resize(1, w) self.win.mvwin(h-1, 0) def display(self): """show status bar""" self.win.erase() adir = self.app.act_pane.act_tab maxw = self.app.maxw if len(adir.selections) > 0: if maxw >= 45: size = 0 for f in adir.selections: size += adir.files[f][files.FT_SIZE] self.win.addstr(' %s bytes in %d files' % \ (num2str(size), len(adir.selections))) else: if maxw >= 80: self.win.addstr('File: %4d of %-4d' % \ (adir.file_i + 1, adir.nfiles)) filename = adir.sorted[adir.file_i] if adir.vfs: realpath = os.path.join(vfs.join(self.app.act_pane.act_tab), filename) else: realpath = files.get_realpath(adir.path, filename, adir.files[filename][files.FT_TYPE]) path = (len(realpath)>maxw-35) and \ '~' + realpath[-(maxw-37):] or realpath self.win.addstr(0, 20, 'Path: ' + utils.encode(path)) if maxw > 10: try: self.win.addstr(0, maxw-8, 'F1=Help') except: pass self.win.refresh() ###################################################################### ##### PowerCLI class class PowerCLI(object): """The PowerCLI class is an advanced 1-line cli""" RUN_NORMAL, RUN_BACKGROUND, RUN_NEEDCURSESWIN = xrange(3) def __init__(self, maxh, app): self.app = app self.visible = False self.text = '' self.pos = 0 try: self.win = curses.newwin(1, app.maxw, maxh-1, 0) self.pwin = curses.panel.new_panel(self.win) self.pwin.top() except curses.error: print 'Can\'t create StatusBar window' sys.exit(-1) if curses.has_colors(): self.win.bkgd(curses.color_pair(25)) self.entry = None def do_resize(self, h, w): self.win.resize(1, w) self.win.mvwin(h-1, 0) if self.visible: self.display() def hide(self): self.visible = False self.pwin.bottom() messages.cursor_hide() self.app.statusbar.display() def display(self): self.visible = True tab = self.app.act_pane.act_tab path = vfs.join(tab) if tab.vfs else tab.path l = self.app.maxw/6 - 4 if len(path) > l: path = '~'+ path[-l-1:] useridchar = '#' if os.getuid()==0 else '$' self.win.erase() self.win.addstr('[%s]%s ' % (utils.encode(path), useridchar), curses.color_pair(24) | curses.A_BOLD) self.win.refresh() le = len(path) + 4 self.entry = messages.EntryLine(self.app.maxw-le+4, 1, self.app.maxh-1, le, self.text, 'cli', True, tab.path, cli=True) self.entry.pos = self.pos messages.cursor_show2() self.pwin.top() while True: ans = self.entry.manage_keys() if ans == -1: # Ctrl-C self.text, self.pos = self.entry.text, self.entry.pos cmd = '' break elif ans == 10: # return self.text, self.pos = '', 0 cmd = self.entry.text.strip() break self.hide() if cmd != '': self.execute(cmd, tab) if len(messages.history['cli']) >= messages.MAX_HISTORY: messages.history['cli'].remove(messages.history['cli'][0]) if messages.history['cli'].count(cmd) >= 1: messages.history['cli'].remove(cmd) messages.history['cli'].append(cmd) def __check_loop(self, cmd, selected): if not selected: return False return any((var in cmd) for var in \ ('$f', '$v', '$F', '$E', '$i', '$tm', '$ta', '$tc')) def __replace_python(self, cmd, lcls): lcls = dict([('__lfm_%s' % k, v) for k,v in lcls.items()]) # get chunks chunks, st = {}, 0 while True: i = cmd.find('{', st) if i == -1: break j = cmd.find('}', i+1) if j == -1: raise SyntaxError('{ at %d position has not ending }' % i) else: chunks[(i+1, j)] = cmd[i+1:j].replace('$', '__lfm_') st = j + 1 # evaluate if chunks == {}: return cmd buf, st = '', 0 for i, j in sorted(chunks.keys()): buf += cmd[st:i-1] try: translated = eval(chunks[(i, j)], {}, lcls) except Exception, msg: raise SyntaxError(str(msg).replace('__lfm_', '$')) buf += unicode(translated) st = j+1 buf += cmd[st:] return buf def __replace_variables(self, cmd, lcls): for k, v in lcls.items(): if k in ('i', 'tm', 'ta', 'tc', 'tn'): cmd = cmd.replace('$%s' % k, unicode(v)) elif k in ('s', 'a'): cmd = cmd.replace('$%s' % k, ' '.join(['"%s"' % f for f in v])) else: cmd = cmd.replace('$%s' % k, v) return cmd def __replace_cli(self, cmd, tab, selected, filename=None): # prepare vars if not filename: filename = tab.sorted[tab.file_i] cur_directory = tab.path other_directory = self.app.noact_pane.act_tab.path fullpath = os.path.join(tab.path, filename) filename_noext, ext = os.path.splitext(filename) if filename_noext.endswith('.tar'): filename_noext = filename_noext.replace('.tar', '') ext = '.tar' + ext all_selected = selected all_files = [f for f in tab.sorted if f is not os.pardir] try: selection_idx = selected.index(filename)+1 except ValueError: selection_idx = 0 tm = datetime.datetime.fromtimestamp(os.path.getmtime(fullpath)) ta = datetime.datetime.fromtimestamp(os.path.getatime(fullpath)) tc = datetime.datetime.fromtimestamp(os.path.getctime(fullpath)) tnow = datetime.datetime.now() # and replace, first python code, and then variables lcls = {'f': filename, 'v': filename, 'F': fullpath, 'E': filename_noext, 'e': ext, 'd': cur_directory, 'o': other_directory, 's': all_selected, 'a': all_files, 'i': selection_idx, 'tm': tm, 'ta': ta, 'tc': tc, 'tn': tnow } for i, bmk in enumerate(self.app.prefs.bookmarks): lcls['b%d' % i] = bmk cmd = self.__replace_python(cmd, lcls) cmd = self.__replace_variables(cmd, lcls) return cmd def __run(self, cmd, path, mode): if mode == PowerCLI.RUN_NEEDCURSESWIN: curses.endwin() try: msg = utils.get_shell_output3('cd "%s" && %s' % (path, cmd)) except KeyboardInterrupt: os.system('reset') msg = 'Stopped by user' st = -1 if msg else 0 elif mode == PowerCLI.RUN_BACKGROUND: utils.run_in_background(cmd, path) st, msg = 0, '' else: # PowerCLI.RUN_NORMAL st, msg = utils.ProcessFunc('Executing PowerCLI', cmd, utils.run_shell, cmd, path, True).run() if st == -1: messages.error('Cannot execute PowerCLI command:\n %s\n\n%s' % \ (cmd, str(msg))) elif st != -100 and msg is not None and msg != '': if self.app.prefs.options['show_output_after_exec']: curses.curs_set(0) if messages.confirm('Executing PowerCLI', 'Show output', 1): lst = [(l, 2) for l in msg.split('\n')] pyview.InternalView('Output of "%s"' % utils.encode(cmd), lst, center=0).run() return st def execute(self, cmd, tab): selected = [f for f in tab.sorted if f in tab.selections] loop = self.__check_loop(cmd, selected) if cmd[-1] == '&': mode = PowerCLI.RUN_BACKGROUND cmd_orig = cmd[:-1].strip() elif cmd[-1] == '%': mode = PowerCLI.RUN_NEEDCURSESWIN cmd_orig = cmd[:-1].strip() else: mode = PowerCLI.RUN_NORMAL cmd_orig = cmd if loop: for f in selected: try: cmd = self.__replace_cli(cmd_orig, tab, selected, f) # except Exception as msg: # python v2.6+ except Exception, msg: messages.error('Cannot execute PowerCLI command:\n %s\n\n%s' % \ (cmd_orig, str(msg))) st = -1 else: st = self.__run(cmd, tab.path, mode) if st == -1: self.app.lpane.display() self.app.rpane.display() if messages.confirm('Error running PowerCLI', 'Do you want to stop now?') == 1: break tab.selections = [] else: try: cmd = self.__replace_cli(cmd_orig, tab, selected) # except Exception as msg: # python v2.6+ except Exception, msg: messages.error('Cannot execute PowerCLI command:\n %s\n\n%s' % \ (cmd_orig, str(msg))) else: st = self.__run(cmd, tab.path, mode) ###################################################################### ##### Pane class class Pane(object): """The Pane class is like a notebook containing TabVfs""" def __init__(self, mode, app): self.app = app self.mode = mode self.dims = [0, 0, 0, 0] # h, w, y0, x0 self.maxh, self.maxw = app.maxh, app.maxw self.init_ui() self.tabs = [] def load_tabs_with_paths(self, paths): for path in paths: tab = TabVfs(self) err = tab.init(utils.decode(path)) if err: tab.init(os.path.abspath(u'.')) self.tabs.append(tab) self.act_tab = self.tabs[0] def init_ui(self): self.dims = self.__calculate_dims() try: self.win = curses.newwin(*self.dims) except curses.error: print 'Can\'t create Pane window' sys.exit(-1) self.win.leaveok(1) self.win.keypad(1) if curses.has_colors(): self.win.bkgd(curses.color_pair(2)) self.__calculate_columns() def __calculate_dims(self): if self.mode == PANE_MODE_HIDDEN: return (self.maxh-2, self.maxw, 0, 0) # h, w, y0, x0 elif self.mode == PANE_MODE_LEFT: return (self.maxh-2, int(self.maxw/2), 1, 0) elif self.mode == PANE_MODE_RIGHT: return (self.maxh-2, self.maxw-int(self.maxw/2), 1, int(self.maxw/2)) elif self.mode == PANE_MODE_FULL: return (self.maxh-2, self.maxw, 1, 0) # h, w, y0, x0 else: # error messages.error('Cannot initialize panes\nReport bug if you can see this.') return (self.maxh-2, int(self.maxw/2), 1, int(self.maxw/2)) def __calculate_columns(self): self.pos_col2 = self.dims[1] - 14 # sep between size and date self.pos_col1 = self.pos_col2 - 8 # sep between filename and size def do_resize(self, h, w): self.maxh, self.maxw = h, w self.dims = self.__calculate_dims() self.win.resize(self.dims[0], self.dims[1]) self.win.mvwin(self.dims[2], self.dims[3]) self.__calculate_columns() for tab in self.tabs: tab.fix_limits() def display(self): """display pane""" if self.mode == PANE_MODE_HIDDEN: return if self.maxw < 65: return self.display_tabs() self.display_files() self.display_cursorbar() def display_tabs(self): tabs = curses.newpad(1, self.dims[1]+1) tabs.bkgd(curses.color_pair(12)) tabs.erase() w = self.dims[1] / 4 if w < 10: w = 5 tabs.addstr(('[' + ' '*(w-2) + ']') * len(self.tabs)) for i, tab in enumerate(self.tabs): if w < 10: path = '[ %d ]' % (i+1, ) else: if tab.vfs: path = os.path.basename(tab.vbase.split('#')[0]) else: path = os.path.basename(tab.path) or os.path.dirname(tab.path) if len(path) > w - 2: path = '[%s~]' % path[:w-3] else: path = '[' + path + ' ' * (w-2-len(path)) + ']' attr = (tab==self.act_tab) and curses.color_pair(10) or curses.color_pair(1) tabs.addstr(0, i*w, utils.encode(path), attr) tabs.refresh(0, 0, 0, self.dims[3], 1, self.dims[3]+self.dims[1]-1) def get_filetypecolorpair(self, f, typ): if typ == files.FTYPE_DIR: return curses.color_pair(22) elif typ == files.FTYPE_EXE: return curses.color_pair(23) | curses.A_BOLD ext = os.path.splitext(f)[1].lower() files_ext = self.app.prefs.files_ext if ext in files_ext['temp_files']: return curses.color_pair(13) elif ext in files_ext['document_files']: return curses.color_pair(14) elif ext in files_ext['media_files']: return curses.color_pair(15) elif ext in files_ext['archive_files']: return curses.color_pair(16) elif ext in files_ext['source_files']: return curses.color_pair(17) elif ext in files_ext['graphics_files']: return curses.color_pair(18) elif ext in files_ext['data_files']: return curses.color_pair(19) else: return curses.color_pair(2) def display_files(self): tab = self.act_tab self.win.erase() # calculate pane width, height and vertical start position w = self.dims[1] if self.mode != PANE_MODE_FULL: h, y = self.maxh-5, 2 else: h, y = self.maxh-2, 0 # headers if self.mode != PANE_MODE_FULL: if self == self.app.act_pane: self.win.attrset(curses.color_pair(5)) attr = curses.color_pair(6) | curses.A_BOLD else: self.win.attrset(curses.color_pair(2)) attr = curses.color_pair(2) path = tab.vfs and vfs.join(tab) or tab.path path = (len(path)>w-5) and '~' + path[-w+5:] or path self.win.box() self.win.addstr(0, 2, utils.encode(path), attr) self.win.addstr(1, 1, 'Name'.center(self.pos_col1-2)[:self.pos_col1-2], curses.color_pair(2) | curses.A_BOLD) self.win.addstr(1, self.pos_col1+2, 'Size', curses.color_pair(2) | curses.A_BOLD) self.win.addstr(1, self.pos_col2+5, 'Date', curses.color_pair(2) | curses.A_BOLD) else: if tab.nfiles > h: self.win.vline(0, w-1, curses.ACS_VLINE, h) # files for i in xrange(tab.file_z - tab.file_a + 1): filename = tab.sorted[i+tab.file_a] # get file info res = files.get_fileinfo_dict(tab.path, filename, tab.files[filename]) # get file color if tab.selections.count(filename): attr = curses.color_pair(10) | curses.A_BOLD else: if self.app.prefs.options['color_files']: attr = self.get_filetypecolorpair(filename, tab.files[filename][files.FT_TYPE]) else: attr = curses.color_pair(2) # show if self.mode == PANE_MODE_FULL: buf = tab.get_fileinfo_str_long(res, w) self.win.addstr(i, 0, utils.encode(buf), attr) else: buf = tab.get_fileinfo_str_short(res, w, self.pos_col1) self.win.addstr(i+2, 1, utils.encode(buf), attr) # vertical separators if self.mode != PANE_MODE_FULL: self.win.vline(1, self.pos_col1, curses.ACS_VLINE, self.dims[0]-2) self.win.vline(1, self.pos_col2, curses.ACS_VLINE, self.dims[0]-2) # vertical scroll bar y0, n = self.__calculate_scrollbar_dims(h, tab.nfiles, tab.file_i) self.win.vline(y+y0, w-1, curses.ACS_CKBOARD, n) if tab.file_a != 0: self.win.vline(y, w-1, '^', 1) if (n == 1) and (y0 == 0): self.win.vline(y+1, w-1, curses.ACS_CKBOARD, n) if tab.nfiles > tab.file_a + h: self.win.vline(h+y-1, w-1, 'v', 1) if (n == 1) and (y0 == h-1): self.win.vline(h+y-2, w-1, curses.ACS_CKBOARD, n) self.win.refresh() def __calculate_scrollbar_dims(self, h, nels, i): """calculate scrollbar initial position and size""" if nels > h: n = max(int(h*h/nels), 1) y0 = min(max(int(int(i/h)*h*h/nels),0), h-n) else: y0 = n = 0 return y0, n def display_cursorbar(self): if self == self.app.act_pane: attr_noselected = curses.color_pair(3) attr_selected = curses.color_pair(11) | curses.A_BOLD else: if self.app.prefs.options['manage_otherpane']: attr_noselected = curses.color_pair(20) attr_selected = curses.color_pair(21) else: return if self.mode == PANE_MODE_FULL: cursorbar = curses.newpad(1, self.maxw) else: cursorbar = curses.newpad(1, self.dims[1]-1) cursorbar.bkgd(curses.color_pair(3)) cursorbar.erase() tab = self.act_tab filename = tab.sorted[tab.file_i] try: tab.selections.index(filename) except ValueError: attr = attr_noselected else: attr = attr_selected res = files.get_fileinfo_dict(tab.path, filename, tab.files[filename]) if self.mode == PANE_MODE_FULL: buf = tab.get_fileinfo_str_long(res, self.maxw) cursorbar.addstr(0, 0, utils.encode(buf), attr) cursorbar.refresh(0, 0, tab.file_i % self.dims[0] + 1, 0, tab.file_i % self.dims[0] + 1, self.maxw-2) else: buf = tab.get_fileinfo_str_short(res, self.dims[1], self.pos_col1) cursorbar.addstr(0, 0, utils.encode(buf), attr) cursorbar.addch(0, self.pos_col1-1, curses.ACS_VLINE, attr) cursorbar.addch(0, self.pos_col2-1, curses.ACS_VLINE, attr) row = tab.file_i % (self.dims[0]-3) + 3 if self.mode == PANE_MODE_LEFT: cursorbar.refresh(0, 0, row, 1, row, int(self.maxw/2)-2) else: cursorbar.refresh(0, 0, row, int(self.maxw/2)+1, row, self.maxw-2) def regenerate(self): """Rebuild tabs' directories, this is needed because panel could be changed""" for tab in self.tabs: tab.backup() tab.regenerate() tab.fix_limits() tab.restore() def manage_keys(self): self.win.nodelay(1) while True: ch = self.win.getch() if ch == -1: # no key pressed # curses.napms(1) time.sleep(0.05) curses.doupdate() continue # print 'key: \'%s\' <=> %c <=> 0x%X <=> %d' % \ # (curses.keyname(ch), ch & 255, ch, ch) # messages.win('Keyboard hitted:', # 'key: \'%s\' <=> %c <=> 0x%X <=> %d' % \ # (curses.keyname(ch), ch & 255, ch, ch)) ret = actions.do(self.act_tab, ch) if ret is None: self.app.display() elif ret == RET_NO_UPDATE: continue elif ret == RET_HALF_UPDATE: self.app.half_display() elif ret == RET_HALF_UPDATE_OTHER: self.app.half_display_other() else: return ret ###################################################################### ##### Vfs class class Vfs(object): """Vfs class contains files information in a directory""" def __init__(self): self.path = '' self.nfiles = 0 self.files = [] self.sorted = [] self.selections = [] self.sort_mode = 0 # vfs variables self.vfs = '' # vfs? if not -> blank string self.base = '' # tempdir basename self.vbase = self.path # virtual directory basename # history self.history = [] def init_dir(self, path): old_path = self.path if self.path and not self.vfs else '' try: app = self.pane.app self.nfiles, self.files = files.get_dir(path, app.prefs.options['show_dotfiles']) sortmode = app.prefs.options['sort'] sort_mix_dirs = app.prefs.options['sort_mix_dirs'] sort_mix_cases = app.prefs.options['sort_mix_cases'] self.sorted = files.sort_dir(self.files, sortmode, sort_mix_dirs, sort_mix_cases) self.sort_mode = sortmode self.path = os.path.abspath(path) self.selections = [] except (IOError, OSError), (errno, strerror): if len(self.history) > 0: self.history.pop() return (strerror, errno) # vfs variables self.vfs = '' self.base = '' self.vbase = self.path # history if old_path: if old_path in self.history: self.history.remove(old_path) self.history.append(old_path) self.history = self.history[-MAX_TAB_HISTORY:] def init(self, path, old_file = ''): raise NotImplementedError def enter_dir(self, filename): if self.vfs: if self.path == self.base and filename == os.pardir: vfs.exit(self) self.init(os.path.dirname(self.vbase), old_file=os.path.basename(self.vbase).replace('#vfs', '')) else: pvfs, base, vbase = self.vfs, self.base, self.vbase self.init(os.path.join(self.path, filename)) self.vfs, self.base, self.vbase = pvfs, base, vbase else: if filename == os.pardir: self.init(os.path.dirname(self.path), old_file=os.path.basename(self.path)) else: self.init(os.path.join(self.path, filename), old_file=self.sorted[self.file_i], check_oldfile=False) def exit_dir(self): if self.vfs: if self.path == self.base: vfs.exit(self) self.init(os.path.dirname(self.vbase), old_file=os.path.basename(self.vbase).replace('#vfs', '')) else: pvfs, base, vbase = self.vfs, self.base, self.vbase self.init(os.path.dirname(self.path), old_file=os.path.basename(self.path)) self.vfs, self.base, self.vbase = pvfs, base, vbase else: if self.path != os.sep: self.init(os.path.dirname(self.path), old_file=os.path.basename(self.path)) def backup(self): self.old_file = self.sorted[self.file_i] self.old_file_i = self.file_i self.old_vfs = self.vfs, self.base, self.vbase def restore(self): try: self.file_i = self.sorted.index(self.old_file) except ValueError: if self.old_file_i < len(self.sorted): self.file_i = self.old_file_i else: self.file_i = len(self.sorted) - 1 self.vfs, self.base, self.vbase = self.old_vfs del self.old_file del self.old_file_i del self.old_vfs def regenerate(self): """Rebuild tabs' directories""" path = self.path if path != os.sep and path[-1] == os.sep: path = path[:-1] while not os.path.exists(path): path = os.path.dirname(path) if path != self.path: self.path = path self.file_i = 0 pvfs, base, vbase = self.vfs, self.base, self.vbase self.init_dir(self.path) self.vfs, self.base, self.vbase = pvfs, base, vbase self.selections = [] else: filename_old = self.sorted[self.file_i] selections_old = self.selections[:] pvfs, base, vbase = self.vfs, self.base, self.vbase self.init_dir(self.path) self.vfs, self.base, self.vbase = pvfs, base, vbase try: self.file_i = self.sorted.index(filename_old) except ValueError: self.file_i = 0 self.selections = selections_old[:] for f in self.selections: if f not in self.sorted: self.selections.remove(f) def refresh(self): file_i_old = self.file_i file_old = self.sorted[self.file_i] self.pane.app.regenerate() try: self.file_i = self.sorted.index(file_old) except ValueError: self.file_i = file_i_old self.fix_limits() def get_fileinfo_str_short(self, res, maxw, pos_col1): filewidth = maxw - 24 fname = res['filename'] if len(fname) > filewidth: half = int(filewidth/2) fname = fname[:half+2] + '~' + fname[-half+3:] fname = fname.ljust(pos_col1-2)[:pos_col1-2] res['fname'] = fname if res['dev']: res['devs'] = '%3d,%d' % (res['maj_rdev'], res['min_rdev']) buf = u'%(type_chr)c%(fname)s %(devs)7s %(mtime2)12s' % res else: buf = u'%(type_chr)c%(fname)s %(size)7s %(mtime2)12s' % res return buf def get_fileinfo_str_long(self, res, maxw): filewidth = maxw - 62 fname = res['filename'] if len(fname) > filewidth: half = int(filewidth/2) fname = fname[:half+2] + '~' + fname[-half+2:] res['fname'] = fname res['owner'] = res['owner'][:10] res['group'] = res['group'][:10] if res['dev']: res['devs'] = '%3d,%d' % (res['maj_rdev'], res['min_rdev']) buf = u'%(type_chr)c%(perms)9s %(owner)-10s %(group)-10s %(devs)7s %(mtime)16s %(fname)s' % res else: buf = u'%(type_chr)c%(perms)9s %(owner)-10s %(group)-10s %(size)7s %(mtime)16s %(fname)s' % res return buf def get_file(self): """return pointed file""" return self.sorted[self.file_i] def get_fullpathfile(self): """return full path for pointed file""" return os.path.join(self.path, self.sorted[self.file_i]) ###################################################################### ##### TabVfs class class TabVfs(Vfs): """TabVfs class is the UI container for Vfs class""" def __init__(self, pane): Vfs.__init__(self) self.pane = pane def init(self, path, old_file='', check_oldfile=True): err = self.init_dir(path) if err: messages.error('Cannot change directory\n%s: %s (%d)' % (path, err[0], err[1])) if (check_oldfile and old_file) or (old_file and err): try: self.file_i = self.sorted.index(old_file) except ValueError: self.file_i = 0 else: self.file_i = 0 self.fix_limits() return err def fix_limits(self): self.file_i = max(0, min(self.file_i, self.nfiles-1)) if self.pane.mode == PANE_MODE_HIDDEN or \ self.pane.mode == PANE_MODE_FULL: height = self.pane.dims[0] else: height = self.pane.dims[0] - 3 self.file_a = int(self.file_i/height) * height self.file_z = min(self.file_a+height-1, self.nfiles-1) ###################################################################### ##### Utils def num2str(num): # Thanks to "Fatal" in #pys60 num = str(num) return (len(num) < 4) and num or (num2str(num[:-3])+","+num[-3:]) ###################################################################### ##### Main def usage(msg=''): if msg != "": print 'lfm:\tERROR: %s\n' % msg print __doc__ def lfm_exit(ret_code, ret_path=u'.'): f = open('/tmp/lfm-%s.path' % (os.getppid()), 'w') f.write(utils.encode(ret_path)) f.close() sys.exit(ret_code) def main(win, prefs, paths1, paths2): app = Lfm(win, prefs) app.load_paths(paths1, paths2) if app == OSError: sys.exit(-1) ret = app.run() return ret def add_path(arg, paths): buf = os.path.abspath(arg) if not os.path.isdir(buf): usage('<%s> is not a directory' % arg) lfm_exit(-1) paths.append(buf) def lfm_start(sysargs): # get configuration & preferences DEBUG = False paths1, paths2 = [], [] prefs = Config() ret = prefs.load() if ret == -1: print 'Config file does not exist, we\'ll use default values' prefs.save() time.sleep(1) elif ret == -2: print 'Config file looks corrupted, we\'ll use default values' prefs.save() time.sleep(1) # parse args try: opts, args = getopt.getopt(sysargs[1:], '12dh', ['debug', 'help']) except getopt.GetoptError: usage('Bad argument(s)') lfm_exit(-1) for o, a in opts: if o == '-1': prefs.options['num_panes'] = 1 if o == '-2': prefs.options['num_panes'] = 2 if o in ('-d', '--debug'): DEBUG = True if o in ('-h', '--help'): usage() lfm_exit(2) if len(args) == 0: paths1.append(os.path.abspath(u'.')) paths2.append(os.path.abspath(u'.')) elif len(args) == 1: add_path(args[0], paths1) paths2.append(os.path.abspath(u'.')) elif len(args) == 2: add_path(args[0], paths1) add_path(args[1], paths2) else: usage('Incorrect number of arguments') lfm_exit(-1) # history if prefs.options['save_history_at_exit']: try: messages.history = pickle.load(file(messages.HISTORY_FILE, 'r')) except: messages.history = messages.DEFAULT_HISTORY.copy() else: messages.history = messages.DEFAULT_HISTORY.copy() # logging if DEBUG: log_file = os.path.join(os.path.abspath(u'.'), LOG_FILE) logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s\t%(message)s', datefmt='%Y-%m-%d %H:%M:%S ', filename=log_file, filemode='w') logging.info('Starting Lfm...') # main app logging.info('Main application call') path = curses.wrapper(main, prefs, paths1, paths2) logging.info('End') # change to directory if path is not None: lfm_exit(0, path) else: lfm_exit(0) if __name__ == '__main__': lfm_start(sys.argv) ###################################################################### lfm-2.3/lfm/vfs.py0000644000076400001440000001405411564042146014361 0ustar inigousers00000000000000# -*- coding: utf-8 -*- """vfs.py This module supplies vfs functionality. """ import os, os.path from glob import glob import files import messages import utils import compress ###################################################################### ##### module variables app = None ###################################################################### ##### VFS # initialize vfs stuff def init(tab, filename, vfstype): """initiliaze vfs stuff""" tempdir = files.mkdtemp() # uncompress st, msg = utils.ProcessFunc('Creating vfs', filename, utils.do_uncompress_dir, filename, tab.path, tempdir, True).run() if st == -1: # error app.display() messages.error('Cannot create vfs (opening compressed file)\n' + msg) app.display() return # temppdir deleted by previous call, so we just return elif st == -100: # stopped by user try: files.delete_bulk(tempdir) except OSError: pass return # update vfs vars vpath = tab.path tab.init(tempdir) tab.vfs = vfstype tab.base = tempdir tab.vbase = os.path.join(vpath, filename) + '#vfs' # refresh the other panel app.regenerate() # copy vfs def copy(tab_org, tab_new): """copy vfs""" tempdir = files.mkdtemp() # copy contents dir_src = tab_org.base for f in glob(os.path.join(dir_src, '*')): f = os.path.basename(f) try: files.copy_bulk(os.path.join(dir_src, f), os.path.join(tempdir, f)) except (IOError, os.error), (errno, strerror): app.display() messages.error('Cannot copy vfs (compressed file)\n%s (%s)' % (strerror, errno)) # init vars tab_new.base = tempdir tab_new.vfs = tab_org.vfs tab_new.vbase = tab_org.vbase # exit from vfs, clean all def exit(tab): """exit from vfs, clean all""" ans = 0 rebuild = app.prefs.options['rebuild_vfs'] if app.prefs.confirmations['ask_rebuild_vfs']: ans = messages.confirm('Rebuild vfs file', 'Rebuild vfs file', rebuild) app.display() if ans: if tab.vfs == 'pan': return pan_regenerate(tab) else: regenerate_file(tab) files.delete_bulk(tab.base, ignore_errors=True) app.regenerate() # regenerate vfs file def regenerate_file(tab): """regenerate vfs file: compress new file""" vfs_file = tab.vbase.replace('#vfs', '') # compress file tmpfile = files.mktemp() c = compress.check_compressed_file(vfs_file) cmd = c.build_compressXXX_cmd('*', tmpfile) f = os.path.basename(vfs_file) st, buf = utils.ProcessFunc('Compressing Directory', '\'%s\'' % f, utils.run_shell, cmd, tab.base).run() if st == -1: # error app.display() messages.error('Creating vfs', buf) messages.error('Cannot regenerate vfs (closing compressed file)\n' + buf) app.display() try: files.delete_bulk(tmpfile) except OSError: pass elif st == -100: # stopped by user try: files.delete_bulk(tmpfile) except OSError: pass else: # compress process always create filename with extension, # so we have 2 files now tmpfile_ext = tmpfile + c.exts[0] try: files.copy_bulk(tmpfile_ext, vfs_file) except (IOError, os.error), (errno, strerror): files.delete_bulk(tmpfile_ext) files.delete_bulk(tmpfile) return '%s (%s)' % (strerror, errno) files.delete_bulk(tmpfile_ext, ignore_errors=True) files.delete_bulk(tmpfile, ignore_errors=True) # vfs path join def join(tab): if tab.base == tab.path: return tab.vbase else: return tab.vbase + tab.path.replace(tab.base, '') # initialize panelize vfs stuff def pan_init(tab, fs): """initiliaze panelize vfs stuff""" vfstype = 'pan' tempdir = files.mkdtemp() # copy files for f in fs: f_orig = os.path.join(tab.path, f) f_dest = os.path.join(tempdir, f) d = os.path.join(tempdir, os.path.dirname(f)) try: os.makedirs(d) except (IOError, os.error), (errno, strerror): pass try: if os.path.isfile(f_orig): files.copy_bulk(f_orig, f_dest) elif os.path.isdir(f_orig): os.mkdir(f_dest) except (IOError, os.error), (errno, strerror): messages.error('Cannot create vfs (starting panelize)\n%s (%s)' % (strerror, errno)) # update vfs vars vpath = tab.path tab.init(tempdir) tab.vfs = vfstype tab.base = tempdir tab.vbase = vpath + '#vfs' # copy pan vfs def pan_copy(tab_org, tab_new): """copy vfs""" tempdir = files.mkdtemp() # copy contents dir_src = tab_org.base for f in glob(os.path.join(dir_src, '*')): f = os.path.basename(f) try: files.copy_bulk(os.path.join(dir_src, f), os.path.join(tempdir, f)) except (IOError, os.error), (errno, strerror): app.display() messages.error('Cannot copy vfs (panelize subsystem)\n%s (%s)' % (strerror, errno)) # init vars tab_new.base = tempdir tab_new.vfs = tab_org.vfs tab_new.vbase = tab_org.vbase # regenerate vfs pan file def pan_regenerate(tab): """regenerate vfs pan file: copy files""" dir_src = tab.path dir_dest = tab.vbase.replace('#vfs', '') # check if can copy files out = utils.get_shell_output('touch ' + utils.encode(dir_dest)) if out: return ''.join(out.split(':')[1:])[1:] # copy files for f in glob(os.path.join(dir_src, '*')): f = os.path.basename(f) try: files.copy_bulk(os.path.join(dir_src, f), os.path.join(dir_dest, f)) except (IOError, os.error), (errno, strerror): app.display() messages.error('Cannot regenerating vfs (closing panelize)\n%s (%s)' % (strerror, errno)) ###################################################################### lfm-2.3/lfm/actions.py0000644000076400001440000016037711564252446015243 0ustar inigousers00000000000000# -*- coding: utf-8 -*- """actions.py This module contains the actions to execute when keys are pressed. """ import os, os.path import sys from glob import glob from time import tzname, ctime import datetime import difflib import curses from __init__ import * import files from utils import * import compress import vfs import messages import pyview ###################################################################### ##### module variables app = None ###################################################################### ##### keys <-> actions table keytable = { # movement ord('k'): 'cursor_up', curses.KEY_UP: 'cursor_up', ord('j'): 'cursor_down', curses.KEY_DOWN: 'cursor_down', curses.KEY_PPAGE: 'page_previous', curses.KEY_BACKSPACE: 'page_previous', 0x02: 'page_previous', # Ctrl-B curses.KEY_NPAGE: 'page_next', ord(' '): 'page_next', 0x06: 'page_next', # Ctrl-F curses.KEY_HOME: 'home', 0x01: 'home', # Ctrl-A curses.KEY_END: 'end', 0x05: 'end', # Ctrl-E 0x0C: 'cursor_center', # Ctrl-L 0x10: 'cursor_quarter_up', # Ctrl-P 0x231: 'cursor_quarter_up', # Ctrl-Cursor_up 0x0E: 'cursor_quarter_down', # Ctrl-N 0x208: 'cursor_quarter_down', # Ctrl-Cursor_down # movement other pane curses.KEY_SR: 'cursor_up_otherpane', # Shift-up 0x22f: 'cursor_up_otherpane', # Alt-up, kUP3 ord('K'): 'cursor_up_otherpane', # K curses.KEY_SF: 'cursor_down_otherpane', # Shift-down 0x206: 'cursor_down_otherpane', # Alt-down, kDN3 ord('J'): 'cursor_down_otherpane', # J 0x224: 'page_previous_otherpane', # Alt-pageup, kPRV3 ord('B'): 'page_previous_otherpane', # B 0x21f: 'page_next_otherpane', # Alt-pagedown, kNXT3 ord('F'): 'page_next_otherpane', # F ord('A'): 'home_otherpane', # A ord('E'): 'end_otherpane', # E curses.KEY_SLEFT: 'cursor_left_otherpane', # Shift-left 0x21a: 'cursor_left_otherpane', # Alt-left, kLFT3 curses.KEY_SRIGHT: 'cursor_right_otherpane', # Shift-right 0x229: 'cursor_right_otherpane', # Alt-right, kRIT3 ord('P'): 'cursor_quarter_up_otherpane', # P ord('N'): 'cursor_quarter_down_otherpane', # N # change dir curses.KEY_LEFT: 'cursor_left', curses.KEY_RIGHT: 'cursor_right', 10: 'enter', 13: 'enter', ord('g'): 'goto_dir', ord('G'): 'goto_dir', 0x13: 'goto_file', # Ctrl-S 0x14: 'tree', # Ctrl-T ord('0'): 'bookmark_0', ord('1'): 'bookmark_1', ord('2'): 'bookmark_2', ord('3'): 'bookmark_3', ord('4'): 'bookmark_4', ord('5'): 'bookmark_5', ord('6'): 'bookmark_6', ord('7'): 'bookmark_7', ord('8'): 'bookmark_8', ord('9'): 'bookmark_9', 0x04: 'select_bookmark', # Ctrl-D 0x1C: 'select_bookmark', # Ctrl-\ ord('b'): 'set_bookmark', 0x19: 'select_history', # Ctrl-Y # panels ord('\t'): 'toggle_active_pane', # tab ord('.'): 'toggle_panes', ord(','): 'swap_panes', 0x15: 'swap_panes', # Ctrl-U ord('='): 'same_tabs', ord(':'): 'new_tab', ord('!'): 'close_tab', ord('<'): 'left_tab', ord('>'): 'right_tab', # selections curses.KEY_IC: 'select_item', ord('+'): 'select_group', ord('-'): 'deselect_group', ord('*'): 'invert_select', # misc 0x08: 'toggle_dotfiles', # Ctrl-H 0x17: 'toggle_manage_otherpane', # Ctrl-W ord('#'): 'show_size', ord('s'): 'sort', ord('S'): 'sort', ord('i'): 'file_info', ord('I'): 'file_info', ord('@'): 'action_on_file', 0xF1: 'special_regards', # special regards ord('/'): 'find_grep', ord('t'): 'touch_file', ord('T'): 'touch_file', ord('l'): 'create_link', ord('L'): 'edit_link', 0x0F: 'open_shell', # Ctrl-O 0x18: 'show_cli', # Ctrl-X # main functions curses.KEY_F2: 'rename', curses.KEY_F3: 'view_file', curses.KEY_F4: 'edit_file', curses.KEY_F5: 'copy', curses.KEY_F6: 'move', curses.KEY_F7: 'make_dir', curses.KEY_DC: 'delete', curses.KEY_F8: 'delete', # menu ord('h'): 'show_help', ord('H'): 'show_help', curses.KEY_F1: 'show_help', curses.KEY_F9: 'general_menu', curses.KEY_F12: 'file_menu', # terminal resize: curses.KEY_RESIZE: 'resize_window', 0x12: 'refresh_screen', # Ctrl-R # quit & exit ord('q'): 'quit', ord('Q'): 'quit', curses.KEY_F10: 'quit', 0x11: 'exit' # Ctrl-Q } def do(tab, ch): try: act = 'ret = %s(tab)' % keytable[ch] except KeyError: curses.beep() else: exec(act) return ret ###################################################################### ##### action functions # movement def cursor_up(tab): tab.file_i -= 1 tab.fix_limits() return RET_HALF_UPDATE def cursor_down(tab): tab.file_i += 1 tab.fix_limits() return RET_HALF_UPDATE def page_previous(tab): tab.file_i -= tab.pane.dims[0] if tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): tab.file_i += 3 tab.fix_limits() return RET_HALF_UPDATE def page_next(tab): tab.file_i += tab.pane.dims[0] if tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): tab.file_i -= 3 tab.fix_limits() return RET_HALF_UPDATE def home(tab): tab.file_i = 0 tab.fix_limits() return RET_HALF_UPDATE def end(tab): tab.file_i = tab.nfiles - 1 tab.fix_limits() return RET_HALF_UPDATE def cursor_center(tab): tab.file_i = tab.file_a + int(tab.pane.dims[0]/2) if tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): tab.file_i -= 1 tab.fix_limits() return RET_HALF_UPDATE def cursor_quarter_up(tab): delta = int(tab.pane.dims[0]/4) tab.file_i -= delta tab.fix_limits() return RET_HALF_UPDATE def cursor_quarter_down(tab): delta = int(tab.pane.dims[0]/4) tab.file_i += delta tab.fix_limits() return RET_HALF_UPDATE # movement other pane def cursor_up_otherpane(tab): if app.prefs.options['manage_otherpane'] and \ tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): othertab = app.noact_pane.act_tab othertab.file_i -= 1 othertab.fix_limits() return RET_HALF_UPDATE_OTHER else: return RET_NO_UPDATE def cursor_down_otherpane(tab): if app.prefs.options['manage_otherpane'] and \ tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): othertab = app.noact_pane.act_tab othertab.file_i += 1 othertab.fix_limits() return RET_HALF_UPDATE_OTHER else: return RET_NO_UPDATE def page_previous_otherpane(tab): if app.prefs.options['manage_otherpane'] and \ tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): othertab = app.noact_pane.act_tab othertab.file_i -= othertab.pane.dims[0] - 3 othertab.fix_limits() return RET_HALF_UPDATE_OTHER else: return RET_NO_UPDATE def page_next_otherpane(tab): if app.prefs.options['manage_otherpane'] and \ tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): othertab = app.noact_pane.act_tab othertab.file_i += othertab.pane.dims[0] - 3 othertab.fix_limits() return RET_HALF_UPDATE_OTHER else: return RET_NO_UPDATE def home_otherpane(tab): if app.prefs.options['manage_otherpane'] and \ tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): othertab = app.noact_pane.act_tab othertab.file_i = 0 othertab.fix_limits() return RET_HALF_UPDATE_OTHER else: return RET_NO_UPDATE def end_otherpane(tab): if app.prefs.options['manage_otherpane'] and \ tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): othertab = app.noact_pane.act_tab othertab.file_i = othertab.nfiles - 1 othertab.fix_limits() return RET_HALF_UPDATE_OTHER else: return RET_NO_UPDATE def cursor_left_otherpane(tab): if app.prefs.options['manage_otherpane'] and \ tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): othertab = app.noact_pane.act_tab othertab.exit_dir() return RET_HALF_UPDATE_OTHER else: return RET_NO_UPDATE def cursor_right_otherpane(tab): if app.prefs.options['manage_otherpane'] and \ tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): othertab = app.noact_pane.act_tab enter(othertab, allowexec=False) return RET_HALF_UPDATE_OTHER def cursor_quarter_up_otherpane(tab): if app.prefs.options['manage_otherpane'] and \ tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): delta = int(tab.pane.dims[0]/4) othertab = app.noact_pane.act_tab othertab.file_i -= delta othertab.fix_limits() return RET_HALF_UPDATE_OTHER else: return RET_NO_UPDATE def cursor_quarter_down_otherpane(tab): if app.prefs.options['manage_otherpane'] and \ tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): delta = int(tab.pane.dims[0]/4) othertab = app.noact_pane.act_tab othertab.file_i += delta othertab.fix_limits() return RET_HALF_UPDATE_OTHER else: return RET_NO_UPDATE # change dir def cursor_left(tab): tab.exit_dir() return RET_HALF_UPDATE def cursor_right(tab): enter(tab) def enter(tab, allowexec=True): filename = tab.get_file() vfstype = compress.check_compressed_file_type(filename) typ = tab.files[filename][files.FT_TYPE] if vfstype in ('bz2', 'gz', 'xz'): vfstype = None if typ == files.FTYPE_DIR: tab.enter_dir(filename) elif typ == files.FTYPE_LNK2DIR: if filename == os.pardir: tab.enter_dir(os.path.normpath(os.path.join(tab.path, filename))) else: tab.init(files.get_linkpath(tab.path, filename)) elif typ in (files.FTYPE_REG, files.FTYPE_LNK) and vfstype is not None: vfs.init(tab, filename, vfstype) elif typ == files.FTYPE_EXE and allowexec: do_execute_file(tab) elif typ == files.FTYPE_REG and allowexec: do_special_view_file(tab) else: return def goto_dir(tab): todir = doEntry(tab.path, 'Go to directory', 'Type directory name', with_history='path') if todir: app.display() todir = os.path.join(tab.path, todir) if tab.vfs: vfs.exit(tab) tab.init(todir) def goto_file(tab): tofile = doEntry(tab.path, 'Go to file', 'Type how file name begins', with_history='file') if tofile: thefiles = tab.sorted[tab.file_i:] for f in thefiles: if f.find(tofile) == 0: break else: return tab.file_i = tab.sorted.index(f) tab.fix_limits() def tree(tab): if tab.vfs: return app.act_pane, app.noact_pane = app.noact_pane, app.act_pane tab.pane.display() ans = Tree(tab.path, tab.pane.mode).run() app.act_pane, app.noact_pane = app.noact_pane, app.act_pane if ans != -1: tab.init(ans) def bookmark_0(tab): goto_bookmark(tab, 0) def bookmark_1(tab): goto_bookmark(tab, 1) def bookmark_2(tab): goto_bookmark(tab, 2) def bookmark_3(tab): goto_bookmark(tab, 3) def bookmark_4(tab): goto_bookmark(tab, 4) def bookmark_5(tab): goto_bookmark(tab, 5) def bookmark_6(tab): goto_bookmark(tab, 6) def bookmark_7(tab): goto_bookmark(tab, 7) def bookmark_8(tab): goto_bookmark(tab, 8) def bookmark_9(tab): goto_bookmark(tab, 9) def select_bookmark(tab): ret = messages.MenuWin('Select Bookmark', app.prefs.bookmarks).run() if ret != -1: if tab.vfs: vfs.exit(tab) tab.init(ret) def set_bookmark(tab): if tab.vfs: messages.error('Cannot set bookmark inside vfs') return while True: ch = messages.get_a_key('Set bookmark', 'Press 0-9 to save the bookmark, Ctrl-C to quit') if ch == -1: # Ctrl-C break elif 0x30 <= ch <= 0x39: # 0..9 app.prefs.bookmarks[ch-0x30] = tab.path[:] break def select_history(tab): items = tab.history[::-1] if not items: messages.error('Cannot select directory from history\nNo entries yet') return ret = messages.MenuWin('Return to', items).run() if ret != -1: if tab.vfs: vfs.exit(tab) tab.init(ret) # panes and tabs def toggle_active_pane(tab): if tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): return RET_TOGGLE_PANE def toggle_panes(tab): if tab.pane.mode == PANE_MODE_FULL: # now => 2-panes mode app.lpane.mode, app.rpane.mode = PANE_MODE_LEFT, PANE_MODE_RIGHT else: # now => 1-pane mode app.act_pane.mode, app.noact_pane.mode = PANE_MODE_FULL, PANE_MODE_HIDDEN app.lpane.init_ui() app.rpane.init_ui() for tab in app.lpane.tabs + app.rpane.tabs: tab.fix_limits() def swap_panes(tab): if tab.pane.mode in (PANE_MODE_LEFT, PANE_MODE_RIGHT): app.lpane.mode, app.rpane.mode = app.rpane.mode, app.lpane.mode app.lpane.init_ui() app.rpane.init_ui() def same_tabs(tab): othertab = app.noact_pane.act_tab if not tab.vfs: if othertab.vfs: vfs.exit(othertab) othertab.init(tab.path) else: if tab.vfs == 'pan': vfs.pan_copy(tab, othertab) else: vfs.copy(tab, othertab) pvfs, base, vbase = othertab.vfs, othertab.base, othertab.vbase othertab.init(base + tab.path.replace(tab.base, '')) othertab.vfs, othertab.base, othertab.vbase = pvfs, base, vbase def new_tab(tab): if len(tab.pane.tabs) >= 4: messages.error('Cannot create more tabs') else: return RET_TAB_NEW def close_tab(tab): if len(tab.pane.tabs) == 1: messages.error('Cannot close last tab') else: if tab.vfs: vfs.exit(tab) return RET_TAB_CLOSE def left_tab(tab): tabs = tab.pane.tabs idx = tabs.index(tab) if idx > 0: tab.pane.act_tab = tabs[idx-1] def right_tab(tab): tabs = tab.pane.tabs idx = tabs.index(tab) if idx < len(tabs) - 1: # and idx < 3 tab.pane.act_tab = tabs[idx+1] # selections def select_item(tab): filename = tab.get_file() if filename != os.pardir: try: tab.selections.index(filename) except ValueError: tab.selections.append(filename) else: tab.selections.remove(filename) tab.file_i += 1 tab.fix_limits() def select_group(tab): pattern = doEntry(tab.path, 'Select group', 'Type pattern', '*', with_history='glob') if pattern: fullpath = os.path.join(tab.path, pattern) for f in [os.path.basename(f) for f in glob(fullpath)]: if f not in tab.selections: tab.selections.append(f) def deselect_group(tab): pattern = doEntry(tab.path, 'Deselect group', 'Type pattern', '*', with_history='glob') if pattern: fullpath = os.path.join(tab.path, pattern) for f in [os.path.basename(f) for f in glob(fullpath)]: if f in tab.selections: tab.selections.remove(f) def invert_select(tab): selections_old = tab.selections[:] tab.selections = [f for f in tab.sorted if f not in selections_old and \ f != os.pardir] # misc def toggle_dotfiles(tab): app.prefs.options['show_dotfiles'] = 1 if app.prefs.options['show_dotfiles']==0 else 0 app.regenerate() def toggle_manage_otherpane(tab): app.prefs.options['manage_otherpane'] = 1 if app.prefs.options['manage_otherpane'] == 0 else 0 return RET_HALF_UPDATE_OTHER def show_size(tab): show_dirs_size(tab) def sort(tab): do_sort(tab) def file_info(tab): do_show_file_info(tab) def action_on_file(tab): do_something_on_file(tab) def special_regards(tab): messages.win('Special Regards', ' Maite zaitut, Montse\n T\'estimo molt, Montse') def find_grep(tab): findgrep(tab) def touch_file(tab): newfile = doEntry(tab.path, 'Touch file', 'Type file name', with_history='file') if not newfile: return fullfilename = os.path.join(tab.path, newfile) cmd = 'touch \"%s\"' % get_escaped_filename(fullfilename) err = get_shell_output(cmd) if err: err = err.split(':')[-1:][0].strip() app.display() messages.error('Cannot touch file\n%s: %s' % (newfile, err)) else: curses.curs_set(0) app.regenerate() def create_link(tab): othertab = app.noact_pane.act_tab if tab.path != othertab.path: otherfile = os.path.join(othertab.path, othertab.get_file()) else: otherfile = othertab.get_file() newlink, pointto = doDoubleEntry(tab.path, 'Create link', 'Link name', '', 'Pointing to', otherfile, with_history1='file', with_history2='path') app.display() if newlink is None or pointto is None: return if newlink == '': messages.error('Cannot create link\nYou must specify the name for the new link') return if pointto == '': messages.error('Cannot create link\nYou must specify the file to link') return fullfilename = os.path.join(tab.path, newlink) ans = files.create_link(pointto, fullfilename) if ans: messages.error('Cannot create link\n%s (%s)' % (ans, tab.get_file())) else: app.regenerate() def edit_link(tab): fullfilename = tab.get_fullpathfile() if not os.path.islink(fullfilename): return pointto = doEntry(tab.path, 'Edit link', 'Link \'%s\' points to' % \ tab.get_file(), os.readlink(fullfilename), with_history='path') app.display() if pointto is None: return elif pointto == '': messages.error('Cannot edit link\nYou must specify the file to link') return if pointto != os.readlink(fullfilename): ans = files.modify_link(pointto, fullfilename) if ans: messages.error('Cannot edit link\n%s (%s)' % (ans, tab.getfile())) app.regenerate() def open_shell(tab): curses.endwin() os.system('cd "%s" && %s' % (get_escaped_filename(tab.path), app.prefs.progs['shell'])) curses.curs_set(0) tab.refresh() def show_cli(tab): app.cli.display() curses.curs_set(0) tab.refresh() app.regenerate() # main functions def __rename_backup_helper(tab): if tab.selections: fs = tab.selections[:] else: filename = tab.get_file() if filename == os.pardir: return fs = [filename] return fs def rename(tab): fs = __rename_backup_helper(tab) if fs is None: return res = ProcessLoopRename('Rename files', files.do_rename, fs, tab.path).run() tab.selections = [] tab.refresh() app.regenerate() def backup_file(tab): fs = __rename_backup_helper(tab) if fs is None: return try: # pc is not used, just to check files have valid encoding pc = files.PathContents(fs, tab.path) except UnicodeError: messages.error('Cannot backup files\nFiles with invalid encoding, convert first') return res = ProcessLoopBackup('Backup files', files.do_backup, fs, tab.path, app.prefs.misc['backup_extension']).run() tab.selections = [] tab.refresh() app.regenerate() def diff_file(tab): orig_ext = app.prefs.misc['backup_extension'] diff_type = app.prefs.misc['diff_type'] filename = tab.get_file() if filename.endswith(orig_ext): file_old = os.path.join(tab.path, filename) file_new = os.path.join(tab.path, filename[:-len(orig_ext)]) else: file_old = os.path.join(tab.path, filename + orig_ext) file_new = os.path.join(tab.path, filename) if not os.path.exists(file_old): messages.error('Cannot diff file\nBackup file does not exist') return if not os.path.exists(file_new): messages.error('Cannot diff file\nOnly backup file exists') return if not os.path.isfile(file_old) or not os.path.isfile(file_new): messages.error('Cannot diff file\nWe can only diff regular files') return buf0 = open(file_old).readlines() buf1 = open(file_new).readlines() d0 = datetime.datetime.fromtimestamp(os.stat(file_old).st_mtime) date_old = d0.strftime(' %Y-%m-%d %H:%M:%S.%f ') + tzname[0] d1 = datetime.datetime.fromtimestamp(os.stat(file_new).st_mtime) date_new = d1.strftime(' %Y-%m-%d %H:%M:%S.%f ') + tzname[0] if diff_type == 'context': diff = difflib.context_diff(buf0, buf1, file_old, file_new, date_old, date_new, n=3) elif diff_type == 'unified': diff = difflib.unified_diff(buf0, buf1, file_old, file_new, date_old, date_new, n=3) elif diff_type == 'ndiff': diff = difflib.ndiff(buf0, buf1) diff = list(diff) if diff == []: messages.error('Files are identical') return tmpfile = os.path.join(files.mkdtemp(), os.path.basename(file_old) + \ ' DIFF ' + os.path.basename(file_new)) f = open(tmpfile, 'w') f.writelines(diff) f.close() curses.endwin() os.system('%s \"%s\"' % (app.prefs.progs['pager'], tmpfile)) curses.curs_set(0) files.delete_bulk(os.path.dirname(tmpfile), True) def view_file(tab): do_view_file(tab) def edit_file(tab): do_edit_file(tab) def __copymove_helper(destdir, path, fs): if destdir[0] != os.sep: destdir = os.path.join(path, destdir) dest_is_file = os.path.isfile(destdir) dest_exists = os.path.exists(destdir) # special case: no copy/move multiple files or dir over an existing file if len(fs) > 1 and (dest_is_file or not dest_exists): messages.error('Cannot copy files\n' + '%s: Not a directory (20)' % destdir) return -1, None, None for f in fs: if os.path.isdir(os.path.join(path, f)) and dest_is_file: messages.error('Cannot copy files\n' + '%s: Not a directory (20)' % destdir) return -1, None, None # special case: copy/move a dir to same path with different name rename_dir = False for f in fs: if os.path.isdir(os.path.join(path, f)) and not dest_exists: rename_dir = True break # special case: no copy/move to "/non-existing-dir/not-existing-name" # but permit copy/move to "/not-existing-name" dir_destdir = os.path.dirname(destdir) # not (a and b) = not a or not b # if os.path.dirname(destdir) != path and \ # not (os.path.isdir(os.path.dirname(destdir)) and not dest_exists): if dir_destdir != path and (not os.path.isdir(dir_destdir) or dest_exists): for f in fs: dest = os.path.join(destdir, f) if not os.path.exists(dest) and not dest_exists: messages.error('Cannot copy files\n' + '%s: No such file or directory (2)' % destdir) return -1, None, None return 0, destdir, rename_dir def copy(tab): destdir = app.noact_pane.act_tab.path + os.sep if tab.selections: buf = 'Copy %d items to' % len(tab.selections) fs = tab.selections[:] else: filename = tab.get_file() if filename == os.pardir: return buf = 'Copy \'%s\' to' % filename fs = [filename] destdir = doEntry(tab.path, 'Copy', buf, destdir, with_history='path') if destdir: st, destdir, rename_dir = __copymove_helper(destdir, tab.path, fs) if st == -1: return try: pc = files.PathContents(fs, tab.path) except UnicodeError: app.display() messages.error('Cannot copy files\nFiles with invalid encoding, convert first') return res = ProcessLoopCopy('Copy files', files.do_copy, pc, destdir, rename_dir).run() tab.selections = [] app.regenerate() def move(tab): destdir = app.noact_pane.act_tab.path + os.sep if tab.selections: buf = 'Move %d items to' % len(tab.selections) fs = tab.selections[:] else: filename = tab.get_file() if filename == os.pardir: return buf = 'Move \'%s\' to' % filename fs = [filename] destdir = doEntry(tab.path, 'Move', buf, destdir, with_history='path') if destdir: st, destdir, rename_dir = __copymove_helper(destdir, tab.path, fs) if st == -1: return try: pc = files.PathContents(fs, tab.path) except UnicodeError: app.display() messages.error('Cannot move files\nFiles with invalid encoding, convert first') return res = ProcessLoopCopy('Move files', files.do_copy, pc, destdir, rename_dir).run() if res != -1: # stopped by user if isinstance(res, list): # don't delete files when not overwritten pc.remove_files(res) tmp_delete_all = app.prefs.confirmations['delete'] app.prefs.confirmations['delete'] = 0 res = ProcessLoopDelete('Move files', files.do_delete, pc).run() app.prefs.confirmations['delete'] = tmp_delete_all tab.selections = [] tab.refresh() def make_dir(tab): newdir = doEntry(tab.path, 'Make directory', 'Type directory name', with_history='file') if newdir: ans = files.mkdir(tab.path, newdir) if ans: app.display() messages.error('Cannot make directory\n' + newdir + ': %s (%s)' % ans) else: app.regenerate() def delete(tab): if tab.selections: fs = tab.selections[:] else: filename = tab.get_file() if filename == os.pardir: return fs = [filename] try: pc = files.PathContents(fs, tab.path) except UnicodeError: messages.error('Cannot delete files\nFiles with invalid encoding, convert first') return res = ProcessLoopDelete('Delete files', files.do_delete, pc).run() tab.selections = [] tab.refresh() # menu def show_help(tab): menu = [ 'r Readme', 'v Readme pyview', 'n News', 't Todo', 'c ChangeLog', 'l License' ] cmd = messages.MenuWin('Help Menu', menu).run() if cmd == -1: return cmd = cmd[0] curses.endwin() docdir = os.path.join(sys.exec_prefix, 'share/doc/lfm') docfile = { 'r': 'README', 'v': 'README.pyview', 'n': 'NEWS', 't': 'TODO', 'c': 'ChangeLog', 'l': 'COPYING' }.get(cmd) fullfilename = os.path.join(docdir, docfile) os.system('%s \"%s\"' % (app.prefs.progs['pager'], fullfilename)) curses.curs_set(0) def file_menu(tab): menu = [ '@ Do something on file(s)', 'i File info', 'p Change file permissions, owner, group', 'a Backup file', 'd Diff file with backup', 'z Compress/uncompress file(s)...', # u'\u2026' 'x Uncompress .tar.gz, .tar.bz2, .tar.xz, .tar, .zip, .rar, .7z', 'u Uncompress .tar.gz, etc in other panel', 'c Compress directory to format...' ] # u'\u2026' cmd = messages.MenuWin('File Menu', menu).run() if cmd == -1: return app.display() cmd = cmd[0] if cmd == '@': do_something_on_file(tab) elif cmd == 'i': do_show_file_info(tab) elif cmd == 'p': do_change_perms(tab) elif cmd == 'a': backup_file(tab) elif cmd == 'd': diff_file(tab) elif cmd == 'z': compress_fmts = {'g': 'gz', 'b': 'bz2', 'x': 'xz'} while True: ch = messages.get_a_key('Un/Compress file(s)', '(g)zip, (b)zip2, (x)z\n\n' ' Ctrl-C to quit') if ch == -1: # Ctrl-C break elif chr(ch) in compress_fmts.keys(): compress_uncompress_file(tab, compress_fmts[chr(ch)]) break elif cmd == 'x': uncompress_dir(tab, tab.path) elif cmd == 'u': uncompress_dir(tab, app.noact_pane.act_tab.path) elif cmd == 'c': if not tab.selections and os.path.basename(tab.sorted[tab.file_i])==os.pardir: return compress_fmts = {'g': 'tgz', 'b': 'tbz2', 'x': 'txz', 't': 'tar', 'z': 'zip', 'r': 'rar', '7': '7z'} while True: ch = messages.get_a_key('Compress directory to...', '.tar.(g)z, .tar.(b)z2, .tar.(x)z, .(t)ar, .(z)ip, .(r)ar, .(7)z\n\n' ' Ctrl-C to quit') if ch == -1: # Ctrl-C break elif chr(ch) in compress_fmts.keys(): compress_dir(tab, compress_fmts[chr(ch)]) break app.regenerate() def general_menu(tab): menu = [ '/ Find/grep file(s)', '# Show directories size', 's Sort files', 't Tree', 'f Show filesystems info', 'o Open shell', 'c Edit configuration', 'r Regenerate programs', 'h Delete history' ] cmd = messages.MenuWin('General Menu', menu).run() if cmd == -1: return app.display() cmd = cmd[0] if cmd == '/': findgrep(tab) elif cmd == '#': show_dirs_size(tab) elif cmd == 's': do_sort(tab) elif cmd == 't': tree(tab) elif cmd == 'f': do_show_fs_info() elif cmd == 'o': open_shell(tab) elif cmd == 'c': curses.endwin() os.system('%s \"%s\"' % (app.prefs.progs['editor'], app.prefs.file)) curses.curs_set(0) app.prefs.load() elif cmd == 'r': app.prefs.check_progs() app.prefs.save() app.prefs.load() messages.win('Configuration', 'Regenerating programs... OK') elif cmd == 'h': files.delete_bulk(messages.HISTORY_FILE) messages.history = messages.DEFAULT_HISTORY.copy() # window resize def resize_window(tab): app.resize() #refresh screen def refresh_screen(tab): app.regenerate() # exit def quit(tab): if app.prefs.confirmations['quit']: ans = messages.confirm('Last File Manager', 'Quit Last File Manager', 1) if ans == 1: return RET_QUIT else: return RET_QUIT def exit(tab): if app.prefs.confirmations['quit']: ans = messages.confirm('Last File Manager', 'Quit Last File Manager', 1) if ans == 1: return RET_EXIT else: return RET_EXIT ###################################################################### ##### Utils # bookmarks def goto_bookmark(tab, num): todir = app.prefs.bookmarks[num] if tab.vfs: vfs.exit(tab) tab.init(todir) return RET_HALF_UPDATE # show size def do_show_dirs_size(filename, path): return files.get_fileinfo(os.path.join(path, filename), False, True) def show_dirs_size(tab): if tab.selections: lst = tab.selections else: lst = tab.files.keys() dirs = [d for d in lst if tab.files[d][files.FT_TYPE] in \ (files.FTYPE_DIR, files.FTYPE_LNK2DIR) and d != os.pardir] res = ProcessLoopDirSize('Calculate Directories Size', do_show_dirs_size, dirs, tab.path).run() if res is not None and isinstance(res, list) and len(res): for i, d in enumerate(dirs): try: tab.files[d] = res[i] except IndexError: # if stopped by user len(res) < len(dirs) pass # sort def do_sort(tab): sorttypes = { 'o': files.SORTTYPE_None, 'O': files.SORTTYPE_None, 'n': files.SORTTYPE_byName, 'N': files.SORTTYPE_byName_rev, 's': files.SORTTYPE_bySize, 'S': files.SORTTYPE_bySize_rev, 'd': files.SORTTYPE_byDate, 'D': files.SORTTYPE_byDate_rev } while True: ch = messages.get_a_key('Sorting mode', 'N(o), by (n)ame, by (s)ize, by (d)ate,\nuppercase if reverse order, Ctrl-C to quit') if ch == -1: # Ctrl-C break elif 32 <= ch < 256 and chr(ch) in sorttypes.keys(): app.prefs.options['sort'] = sorttypes[chr(ch)] break old_filename = tab.get_file() old_selections = tab.selections[:] tab.init(tab.path) tab.file_i = tab.sorted.index(old_filename) tab.fix_limits() tab.selections = old_selections # do special view file def do_special_view_file(tab): fullfilename = tab.get_fullpathfile() ext = os.path.splitext(fullfilename)[1].lower()[1:] for typ, exts in app.prefs.filetypes.items(): if ext in exts: prog = app.prefs.progs[typ] if prog == '': messages.error('Cannot run program\n' + 'Can\'t start %s files, defaulting to view' % typ) do_view_file(tab) break sys.stdout = sys.stderr = '/dev/null' curses.endwin() if app.prefs.options['detach_terminal_at_exec']: run_dettached(prog, get_escaped_filename(fullfilename)) curses.curs_set(0) break else: # programs inside same terminal as lfm should use: cmd = get_escaped_command(prog, fullfilename) os.system(cmd) curses.curs_set(0) sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ break else: do_view_file(tab) app.regenerate() # do view file def do_view_file(tab): run_on_current_file('pager', tab.get_fullpathfile()) app.regenerate() # do edit file def do_edit_file(tab): run_on_current_file('editor', tab.get_fullpathfile()) app.regenerate() # do execute file def do_execute_file(tab): filename = tab.get_file() parms = doEntry(tab.path, 'Execute file', 'Enter arguments', with_history='exec') if parms is None: return elif parms == '': cmd = get_escaped_filename(filename) else: cmd = '%s %s' % (get_escaped_filename(filename), encode(parms)) curses.endwin() st, msg = ProcessFunc('Executing file', encode(filename), run_shell, cmd, encode(tab.path), True).run() if st == -1: messages.error('Cannot execute file\n' + decode(msg)) if st != -100 and msg is not None: if app.prefs.options['show_output_after_exec']: curses.curs_set(0) if messages.confirm('Executing file', 'Show output'): lst = [(l, 2) for l in msg.split('\n')] pyview.InternalView('Output of "%s"' % cmd, lst, center=0).run() curses.curs_set(0) tab.refresh() # do something on file def do_something_on_file(tab): cmd = doEntry(tab.path, 'Do something on file(s)', 'Enter command', with_history='exec') if cmd: curses.endwin() if len(tab.selections): for f in tab.selections: os.system('cd \"%s\"; %s' % (encode(tab.path), get_escaped_command(cmd, f))) tab.selections = [] else: os.system('cd \"%s\"; %s' % (encode(tab.path), get_escaped_command(cmd, tab.sorted[tab.file_i]))) curses.curs_set(0) tab.refresh() # show file info def do_show_file_info(tab): def show_info(tab, file): fullfilename = os.path.join(tab.path, file) fd = tab.files[file] fde = files.get_fileinfo_extended(fullfilename) buf = [] user = os.environ['USER'] username = files.get_user_fullname(user) so, host, ver, tmp, arch = os.uname() buf.append(('%s v%s executed by %s' % (LFM_NAME, VERSION, username), 5)) buf.append(('<%s@%s> on a %s %s [%s]' % (user, host, so, ver, arch), 5)) buf.append(('', 2)) cmd = get_escaped_command('file -b', fullfilename) fileinfo = get_shell_output2(cmd) if fileinfo is None: fileinfo = 'no type information' buf.append(('%s: %s (%s)' % (files.FILETYPES[fd[0]][1], encode(file), fileinfo), 6)) if not tab.vfs: path = tab.path else: path = vfs.join(tab) + ' [%s]' % tab.base buf.append(('Path: %s' % encode(path[-(curses.COLS-8):]), 6)) buf.append(('Size: %s bytes' % fd[files.FT_SIZE], 6)) buf.append(('Mode: %s (%4.4o)' % \ (files.perms2str(fd[files.FT_PERMS]), fd[files.FT_PERMS]), 6)) buf.append(('Links: %s' % fde[0], 6)) buf.append(('User ID: %s (%s) / Group ID: %s (%s)' % \ (fd[files.FT_OWNER], fde[1], fd[files.FT_GROUP], fde[2]), 6)) buf.append(('Last access: %s' % ctime(fde[3]), 6)) buf.append(('Last modification: %s' % ctime(fde[4]), 6)) buf.append(('Last change: %s' % ctime(fde[5]), 6)) buf.append(('Location: %d, %d / Inode: #%X (%Xh:%Xh)' % \ ((fde[6] >> 8) & 0x00FF, fde[6] & 0x00FF, fde[7], fde[6], fde[7]), 6)) mountpoint, device, fstype = files.get_mountpoint_for_file(fullfilename) buf.append(('File system: %s on %s (%s)' % \ (device, mountpoint, fstype), 6)) pyview.InternalView('Information about \'%s\'' % encode(file), buf).run() if tab.selections: for f in tab.selections: show_info(tab, f) tab.selections = [] else: show_info(tab, tab.sorted[tab.file_i]) # change permissions def __do_change_perms(filename, perms, owner, group, recursive): try: ans = files.set_perms(filename, perms, recursive) if ans: messages.error('Cannot chmod\n' + filename + ': %s (%s)' % ans) ans = files.set_owner_group(filename, owner, group, recursive) if ans: messages.error('Cannot chown\n' + filename + ': %s (%s)' % ans) except UnicodeError: app.display() messages.error('Cannot change permissions\n' + 'Files with invalid encoding, convert first') def do_change_perms(tab): if tab.selections: change_all = False for i, f in enumerate(tab.selections): if not change_all: ret = messages.ChangePerms(f, tab.files[f], i+1, len(tab.selections)).run() if ret == -1: break elif ret == 0: continue elif ret[4] == True: change_all = True filename = os.path.join(tab.path, f) __do_change_perms(filename, *ret[:-1]) tab.selections = [] else: filename = tab.get_file() if filename == os.pardir: return ret = messages.ChangePerms(filename, tab.files[filename]).run() if ret == -1: return filename = os.path.join(tab.path, filename) __do_change_perms(filename, *ret[:-1]) app.regenerate() # do show filesystems info def do_show_fs_info(): try: buf = get_shell_output('df -h') except (IOError, os.error), (errno, strerror): messages.error('Cannot show filesystems info\n%s (%s)' % (strerror, errno)) return if buf is None or buf == '': messages.error('Cannot show filesystems info\n%s (%s)' % ('Can\'t run "df" command', 0)) return hdr = buf.split('\n')[0].strip() fs = [l.strip() for l in buf.strip().split('\n')[1:]] lst = [(hdr, 6), ('-'*len(hdr), 6)] for l in fs: lst.append((l, 2)) pyview.InternalView('Show filesystems info', lst).run() # find and grep def findgrep(tab): # ask data fs, pat = doDoubleEntry(tab.path, 'Find files', 'Filename', '*', 'Content', '', with_complete2=False, with_history1='glob', with_history2='grep') if fs is None or fs == '': return path = os.path.dirname(fs) fs = os.path.basename(fs) if path is None or path == '': path = tab.path if path[0] != os.sep: path = os.path.join(tab.path, path) if pat: # findgrep st, m = do_findgrep(path, fs, pat) search_type = 'Find/Grep' else: # find st, m = do_find(path, fs) search_type = 'Find' # check returned value if st < 0: # error if st == -100: # stopped by user return else: # some other error app.display() messages.error('Cannot %s\n' % search_type.lower() + m) return if not m or m == []: app.display() messages.error('Cannot %s\n' % search_type.lower() + 'No files found') return # show matches find_quit = 0 par = '' while not find_quit: cmd, par = messages.FindfilesWin(m, par).run() if par: if pat: try: line = int(par.split(':')[-1]) except ValueError: line = 0 f = os.path.join(path, par) else: f = os.path.join(path, par[:par.rfind(':')]) else: line = 0 f = os.path.join(path, par) if os.path.isdir(f): todir = f tofile = None else: todir = os.path.dirname(f) tofile = os.path.basename(f) if cmd == 0: # goto file pvfs, base, vbase = tab.vfs, tab.base, tab.vbase tab.init(todir) tab.vfs, tab.base, tab.vbase = pvfs, base, vbase if tofile: tab.file_i = tab.sorted.index(tofile) tab.fix_limits() find_quit = 1 elif cmd == 1: # panelize if pat: fs = [f[f.find(':')+1:] for f in m if fs.count(f) == 0] else: fs = [f for f in m if fs.count(f) == 0] vfs.pan_init(tab, fs) find_quit = 1 elif cmd == 2: # view if tofile: curses.curs_set(0) curses.endwin() os.system('%s +%d \"%s\"' % (app.prefs.progs['pager'], line, get_escaped_filename(f))) curses.curs_set(0) else: messages.error('Cannot view file\n' + todir + ': Is a directory') elif cmd == 3: # edit if tofile: curses.endwin() if line > 0: os.system('%s +%d \"%s\"' % (app.prefs.progs['editor'], line, get_escaped_filename(f))) else: os.system('%s \"%s\"' % (app.prefs.progs['editor'], get_escaped_filename(f))) curses.curs_set(0) app.regenerate() else: messages.error('Cannot edit file\n' + todir + ': Is a directory') elif cmd == 4: # do something on file cmd2 = doEntry(tab.path, 'Do something on file', 'Enter command', with_history='exec') if cmd2: curses.endwin() os.system(get_escaped_command(cmd2, f)) curses.curs_set(0) app.regenerate() else: find_quit = 1 # entries def doEntry(tabpath, title, help, path='', with_history='', with_complete=True): while True: path = messages.Entry(title, help, path, with_history, with_complete, tabpath).run() if isinstance(path, list): path = path.pop() else: return path def doDoubleEntry(tabpath, title, help1, path1, help2, path2, with_history1='', with_complete1=True, with_history2='', with_complete2=True): tabpath1 = tabpath2 = tabpath while True: path = messages.DoubleEntry(title, help1, path1, with_history1, with_complete1, tabpath1, help2, path2, with_history2, with_complete2, tabpath2, active_entry=0).run() if not isinstance(path, list): return path else: active_entry = path.pop() path2 = path.pop() path1 = path.pop() ###################################################################### ##### Tree class Tree(object): """Tree class""" def __init__(self, path=os.sep, panemode=0): if not os.path.exists(path): raise ValueError, 'path does not exist' self.panemode = panemode self.__init_ui() if path[-1] == os.sep and path != os.sep: path = path[:-1] self.path = path self.tree = self.get_tree() self.pos = self.__get_curpos() def __get_dirs(self, path): """return a list of dirs in path""" ds = [] try: ds = [d for d in os.listdir(path) \ if os.path.isdir(os.path.join(path, d))] except OSError: pass if not app.prefs.options['show_dotfiles']: ds = [d for d in ds if d[0] != '.'] ds.sort() return ds def __get_graph(self, path): """return 2 dicts with tree structure""" tree_n = {} tree_dir = {} expanded = None while path: if path == os.sep and tree_dir.has_key(os.sep): break tree_dir[path] = (self.__get_dirs(path), expanded) expanded = os.path.basename(path) path = os.path.dirname(path) dir_keys = tree_dir.keys() dir_keys.sort() for n, d in enumerate(dir_keys): tree_n[n] = d return tree_n, tree_dir def __get_node(self, i, tn, td, base): """expand branch""" lst2 = [] node = tn[i] dirs, expanded_node = td[node] if not expanded_node: return [] for d in dirs: if d == expanded_node: lst2.append([d, i, os.path.join(base, d)]) lst3 = self.__get_node(i+1, tn, td, os.path.join(base, d)) if lst3 is not None: lst2.extend(lst3) else: lst2.append([d, i, os.path.join(base, d)]) return lst2 def get_tree(self): """return list with tree structure""" tn, td = self.__get_graph(self.path) tree = [[os.sep, -1, os.sep]] tree.extend(self.__get_node(0, tn, td, os.sep)) return tree def __get_curpos(self): """get position of current dir""" for i in xrange(len(self.tree)): if self.path == self.tree[i][2]: return i else: return -1 def regenerate_tree(self, newpos): """regenerate tree when changing to a new directory""" self.path = self.tree[newpos][2] self.tree = self.get_tree() self.pos = self.__get_curpos() def show_tree(self, a=0, z=-1): """show an ascii representation of the tree. Not used in lfm""" if z > len(self.tree) or z == -1: z = len(self.tree) for i in xrange(a, z): name, depth, fullname = self.tree[i] if fullname == self.path: name += ' <=====' if name == os.sep: print encode(' ' + name) else: print encode(' | ' * depth + ' +- ' + name) # GUI functions def __init_ui(self): """initialize curses stuff""" self.__calculate_dims() try: self.win = curses.newwin(*self.dims) except curses.error: print 'Can\'t create tree window' sys.exit(-1) self.win.keypad(1) if curses.has_colors(): self.win.bkgd(curses.color_pair(2)) def __calculate_dims(self): if self.panemode == PANE_MODE_LEFT: self.dims = (app.maxh-2, int(app.maxw/2), 1, int(app.maxw/2)) elif self.panemode in (PANE_MODE_RIGHT, PANE_MODE_FULL): self.dims = (app.maxh-2, int(app.maxw/2), 1, 0) else: # PANE_MODE_HIDDEN: self.dims = (app.maxh-2, 0, 0, 0) # h, w, y, x def display(self): """show tree panel""" self.win.erase() h = app.maxh - 4 n = len(self.tree) # box self.win.attrset(curses.color_pair(5)) self.win.box() self.win.addstr(0, 2, ' Tree ', curses.color_pair(6) | curses.A_BOLD) # tree self.win.attrset(curses.color_pair(2)) j = 0 a = int(self.pos/h) * h z = (int(self.pos/h) + 1) * h if z > n: a = max(n-h, 0); z = n for i in xrange(a, z): j += 1 name, depth, fullname = self.tree[i] if name == os.sep: self.win.addstr(j, 1, ' ') else: self.win.move(j, 1) for kk in xrange(depth): self.win.addstr(' ') self.win.addch(curses.ACS_VLINE) self.win.addstr(' ') self.win.addstr(' ') if i == n - 1: self.win.addch(curses.ACS_LLCORNER) elif depth > self.tree[i+1][1]: self.win.addch(curses.ACS_LLCORNER) else: self.win.addch(curses.ACS_LTEE) self.win.addch(curses.ACS_HLINE) self.win.addstr(' ') w = int(app.maxw / 2) - 2 wd = 3 * depth + 4 if fullname == self.path: self.win.addstr(encode(name[:w-wd-3]), curses.color_pair(3)) child_dirs = self.__get_dirs(self.path) if len(child_dirs) > 0: self.win.addstr(' ') self.win.addch(curses.ACS_HLINE) self.win.addch(curses.ACS_RARROW) else: self.win.addstr(encode(name[:w-wd])) # scrollbar if n > h: nn = max(int(h*h/n), 1) y0 = min(max(int(int(self.pos/h)*h*h/n), 0), h-nn) else: y0 = nn = 0 self.win.attrset(curses.color_pair(5)) self.win.vline(y0 + 2, int(app.maxw/2) - 1, curses.ACS_CKBOARD, nn) if a != 0: self.win.vline(1, int(app.maxw/2) - 1, '^', 1) if nn == 1 and (y0 + 2 == 2): self.win.vline(3, int(app.maxw/2) - 1, curses.ACS_CKBOARD, nn) if n - 1 > a + h - 1: self.win.vline(h, int(app.maxw/2) - 1, 'v', 1) if nn == 1 and (y0 + 2 == h + 1): self.win.vline(h, int(app.maxw/2) - 1, curses.ACS_CKBOARD, nn) # status app.statusbar.win.erase() wp = app.maxw - 8 if len(self.path) > wp: path = self.path[:int(wp/2) -1] + '~' + self.path[-int(wp/2):] else: path = self.path app.statusbar.win.addstr(' Path: %s' % encode(path)) app.statusbar.win.refresh() def run(self): """manage keys""" while True: self.display() chext = 0 ch = self.win.getch() # to avoid extra chars input if ch == 0x1B: chext = 1 ch = self.win.getch() ch = self.win.getch() # cursor up if ch in (ord('k'), ord('K'), curses.KEY_UP): if self.pos == 0: continue if self.tree[self.pos][1] != self.tree[self.pos-1][1]: continue newpos = self.pos - 1 # cursor down elif ch in (ord('j'), ord('j'), curses.KEY_DOWN): if self.pos == len(self.tree) - 1: continue if self.tree[self.pos][1] != self.tree[self.pos+1][1]: continue newpos = self.pos + 1 # page previous elif ch in (curses.KEY_PPAGE, curses.KEY_BACKSPACE, 0x02): # BackSpace, Ctrl-B depth = self.tree[self.pos][1] if self.pos - (app.maxh-4) >= 0: if depth == self.tree[self.pos - (app.maxh-4)][1]: newpos = self.pos - (app.maxh-4) else: newpos = self.pos while 1: if newpos - 1 < 0 or self.tree[newpos-1][1] != depth: break newpos -= 1 else: newpos = self.pos while True: if newpos - 1 < 0 or self.tree[newpos-1][1] != depth: break newpos -= 1 # page next elif ch in (curses.KEY_NPAGE, ord(' '), 0x06): # Ctrl-F depth = self.tree[self.pos][1] if self.pos + (app.maxh-4) <= len(self.tree) - 1: if depth == self.tree[self.pos + (app.maxh-4)][1]: newpos = self.pos + (app.maxh-4) else: newpos = self.pos while True: if newpos + 1 == len(self.tree) or \ self.tree[newpos+1][1] != depth: break newpos += 1 else: newpos = self.pos while True: if newpos + 1 == len(self.tree) or \ self.tree[newpos+1][1] != depth: break newpos += 1 # home elif (ch in (curses.KEY_HOME, 0x01)) or \ (chext == 1) and (ch == 72): # home, Ctrl-A newpos = 1 # end elif (ch in (curses.KEY_END, 0x05)) or \ (chext == 1) and (ch == 70): # end, Ctrl-E newpos = len(self.tree) - 1 # cursor left elif ch == curses.KEY_LEFT: if self.pos == 0: continue newdepth = self.tree[self.pos][1] - 1 for i in xrange(self.pos-1, -1, -1): if self.tree[i][1] == newdepth: break newpos = i # cursor right elif ch == curses.KEY_RIGHT: child_dirs = self.__get_dirs(self.path) if len(child_dirs) > 0: self.path = os.path.join(self.path, child_dirs[0]) self.tree = self.get_tree() self.pos = self.__get_curpos() continue # enter elif ch in (10, 13): return self.path # resize elif ch == curses.KEY_RESIZE: curses.doupdate() app.resize() self.__calculate_dims() self.win.resize(self.dims[0], self.dims[1]) self.win.mvwin(self.dims[2], self.dims[3]) continue # toggle .dot-files elif ch == 0x08: # Ctrl-H toggle_dotfiles(None) if self.pos != 0 and os.path.basename(self.path)[0] == '.': newdepth = self.tree[self.pos][1] - 1 for i in xrange(self.pos-1, -1, -1): if self.tree[i][1] == newdepth: break newpos = i else: newpos = self.pos # quit elif ch in (ord('q'), ord('Q'), curses.KEY_F10, 0x03): # Ctrl-C return -1 # else else: continue # update self.regenerate_tree(newpos) ###################################################################### lfm-2.3/lfm/compress.py0000644000076400001440000001514211564042146015415 0ustar inigousers00000000000000# -*- coding: utf-8 -*- """compress.py This module contains un/compress classes. """ import os, os.path from __init__ import sysprogs import files ###################################################################### def check_compressed_file(filename): for p in packagers: # Note: tbz2 must be before bz2 for e in p.exts: if filename.endswith(e): return p(filename) else: return None def check_compressed_file_type(filename): c = check_compressed_file(filename) if c: return c.type else: return None class PackagerBase(object): def __init__(self, filename): while filename[-1] == os.sep: filename = filename[:-1] self.fullname = filename self.dirname = os.path.dirname(filename) self.filename = os.path.basename(filename) def build_uncompress_cmd(self): return self.uncompress_cmd % self.fullname def build_compress_cmd(self): newfile = self.filename + self.exts[0] if os.path.isfile(self.fullname): if self.type in ('bz2', 'gz', 'xz'): return self.compress_cmd % self.filename elif self.type in ('tbz2', 'tgz', 'txz', 'tar'): return # Don't use tar, it's a file else: return self.compress_cmd % (self.filename, newfile) elif os.path.isdir(self.fullname): if self.type in ('bz2', 'gz', 'xz'): return # Don't compress without tar, it's a dir if self.need_tar: return self.compress_cmd % (self.filename, newfile) else: return self.compress_cmd % (newfile, self.filename) def build_compressXXX_cmd(self, src, dest): if not dest.endswith(self.exts[0]): dest += self.exts[0] if self.need_tar: return self.compressXXX_cmd % (src, dest) else: return self.compressXXX_cmd % (dest, src) def delete_uncompress_temp(self, path, is_tmp=False): if is_tmp: tmpfile = path else: for e in self.exts: if self.filename.endswith(e): dirname = self.filename[:-len(e)] break else: return tmpfile = os.path.join(path, dirname) files.delete_bulk(tmpfile, ignore_errors=True) def delete_compress_temp(self, path, is_tmp=False): if is_tmp: tmpfile = path else: tmpfile = os.path.join(path, self.filename + self.exts[0]) files.delete_bulk(tmpfile, ignore_errors=True) class PackagerTBZ2(PackagerBase): type = 'tbz2' exts = ('.tar.bz2', '.tbz2') need_tar = True uncompress_prog = compress_prog = sysprogs['bzip2'] uncompress_cmd = uncompress_prog + ' -d \"%s\" -c | ' + sysprogs['tar'] + ' xfi -' compress_cmd = sysprogs['tar'] + ' cf - \"%s\" | ' + compress_prog + ' > \"%s\"' compressXXX_cmd = sysprogs['tar'] + ' cf - %s | ' + compress_prog + ' > \"%s\"' class PackagerBZ2(PackagerBase): type = 'bz2' exts = ('.bz2', ) need_tar = False uncompress_prog = compress_prog = sysprogs['bzip2'] uncompress_cmd = uncompress_prog + ' -d \"%s\"' compress_cmd = compress_prog + ' \"%s\"' compressXXX_cmd = compress_prog + ' %s' class PackagerTGZ(PackagerBase): type = 'tgz' exts = ('.tar.gz', '.tgz', '.tar.Z') need_tar = True uncompress_prog = compress_prog = sysprogs['gzip'] uncompress_cmd = uncompress_prog + ' -d \"%s\" -c | ' + sysprogs['tar'] + ' xfi -' compress_cmd = sysprogs['tar'] + ' cf - \"%s\" | ' + compress_prog + ' > \"%s\"' compressXXX_cmd = sysprogs['tar'] + ' cf - %s | ' + compress_prog + ' > \"%s\"' class PackagerGZ(PackagerBase): type = 'gz' exts = ('.gz', ) need_tar = False uncompress_prog = compress_prog = sysprogs['gzip'] uncompress_cmd = uncompress_prog + ' -d \"%s\"' compress_cmd = compress_prog + ' \"%s\"' compressXXX_cmd = compress_prog + ' %s' class PackagerTXZ(PackagerBase): type = 'txz' exts = ('.tar.xz', '.txz') need_tar = True uncompress_prog = compress_prog = sysprogs['xz'] uncompress_cmd = uncompress_prog + ' -d \"%s\" -c | ' + sysprogs['tar'] + ' xfi -' compress_cmd = sysprogs['tar'] + ' cf - \"%s\" | ' + compress_prog + ' > \"%s\"' compressXXX_cmd = sysprogs['tar'] + ' cf - %s | ' + compress_prog + ' > \"%s\"' class PackagerXZ(PackagerBase): type = 'xz' exts = ('.xz', ) need_tar = False uncompress_prog = compress_prog = sysprogs['xz'] uncompress_cmd = uncompress_prog + ' -d \"%s\"' compress_cmd = compress_prog + ' \"%s\"' compressXXX_cmd = compress_prog + ' %s' class PackagerTAR(PackagerBase): type = 'tar' exts = ('.tar', ) need_tar = False uncompress_prog = compress_prog = sysprogs['tar'] uncompress_cmd = uncompress_prog + ' xf \"%s\"' compress_cmd = compress_prog + ' cf \"%s\" \"%s\"' compressXXX_cmd = compress_prog + ' cf \"%s\" %s' class PackagerZIP(PackagerBase): type = 'zip' exts = ('.zip', ) need_tar = False uncompress_prog = sysprogs['unzip'] uncompress_cmd = uncompress_prog + ' -o -q \"%s\"' compress_prog = sysprogs['zip'] compress_cmd = compress_prog + ' -qr \"%s\" \"%s\"' compressXXX_cmd = compress_prog + ' -qr \"%s\" %s' class PackagerRAR(PackagerBase): type = 'rar' exts = ('.rar', ) need_tar = False uncompress_prog = compress_prog = sysprogs['rar'] uncompress_cmd = uncompress_prog + ' x \"%s\"' compress_cmd = compress_prog + ' a \"%s\" \"%s\"' compressXXX_cmd = compress_prog + ' a \"%s\" %s' class Packager7Z(PackagerBase): type = '7z' exts = ('.7z', ) need_tar = False uncompress_prog = sysprogs['7z'] uncompress_cmd = uncompress_prog + ' x \"%s\"' compress_prog = sysprogs['7z'] compress_cmd = compress_prog + ' a \"%s\" \"%s\"' compressXXX_cmd = compress_prog + ' a \"%s\" %s' packagers = ( PackagerTBZ2, PackagerBZ2, PackagerTGZ, PackagerGZ, PackagerTXZ, PackagerXZ, PackagerTAR, PackagerZIP, PackagerRAR, Packager7Z ) packagers_by_type = { 'tbz2': PackagerTBZ2, 'bz2': PackagerBZ2, 'tgz': PackagerTGZ, 'gz': PackagerGZ, 'txz': PackagerTXZ, 'xz': PackagerXZ, 'tar': PackagerTAR, 'zip': PackagerZIP, 'rar': PackagerRAR, '7z': Packager7Z } ###################################################################### lfm-2.3/lfm/messages.py0000644000076400001440000017710111565556646015415 0ustar inigousers00000000000000# -*- coding: utf-8 -*- """messages.py This module contains some windows for lfm use. """ import sys import os.path import curses import curses.panel import files import utils ###################################################################### ##### module variables app = None ###################################################################### ##### history stuff HISTORY_FILE = os.path.abspath(os.path.expanduser('~/.lfm_history')) DEFAULT_HISTORY = {'path': [], 'file': [], 'glob': [], 'grep': [], 'exec': [], 'cli': []} MAX_HISTORY = 100 history = DEFAULT_HISTORY.copy() def add_to_history(hist, text): if text and text != '*': if text in hist: hist.remove(text) if len(hist) >= MAX_HISTORY: hist.remove(hist[0]) hist.append(text) ###################################################################### class BaseWindow(object): """Base class for CommonWindow and FixSizeWindow classes""" def init_ui(self, h, w, title, text, br_bg, br_att, bd_att, bd_bg): try: win = curses.newwin(h, w, int((app.maxh-h)/2), int((app.maxw-w)/2)) self.pwin = curses.panel.new_panel(win) self.pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.bkgd(br_bg, br_att) win.erase() win.box(0, 0) if len(title) > app.maxw - 14: title = title[:app.maxw-10] + '...' + '\'' win.addstr(0, int((w-len(title)-2)/2), ' %s ' % utils.encode(title), curses.A_BOLD) try: text = utils.encode(text) except UnicodeDecodeError: pass for i, l in enumerate(text.split('\n')): win.addstr(i+2, 2, l, bd_att) if self.waitkey: win.addstr(h-1, int((w-27)/2), ' Press any key to continue ', br_att) win.refresh() win.keypad(1) self.win = win def run(self): if self.waitkey: while not self.pwin.window().getch(): pass self.pwin.hide() class CommonWindow(BaseWindow): """A superclass for 'error' and 'win' windows""" def __init__(self, title, text, br_att, br_bg, bd_att, bd_bg, waitkey=True): self.waitkey = waitkey text = text.strip().replace('\t', ' ' * 2) lines = text.split('\n') w = max(max(map(len, lines))+4, 40) if w > app.maxw - 10: w = app.maxw - 10 newlines = [] for l in lines: while len(l) > w-4: newlines.append(l[:w-4]) l = l[w-4:] newlines.append(l) lines = newlines text = '\n'.join(lines) h = len(lines) + 4 if h > app.maxh - 6: h = app.maxh - 6 text = '\n'.join(lines[:app.maxh-10]) self.init_ui(h, w, title, text, br_bg, br_att, bd_att, bd_bg) class FixSizeCommonWindow(BaseWindow): """A superclass for messages, with fixed size""" def __init__(self, title, text, footer, br_att, br_bg, bd_att, bd_bg, waitkey=True): self.waitkey = waitkey text = text.replace('\t', ' ' * 4) w = app.maxw - 20 if len(title) > w - 4: title = title[:w-4] if len(text) > w - 4: text = text[:w-5] if len(footer) > w - 4: footer = footer[:w-4] self.init_ui(5, w, title, text, br_bg, br_att, bd_att, bd_bg) if footer and not self.waitkey: self.win.addstr(4, int((w-len(footer)-2)/2), ' %s ' % utils.encode(footer), br_att) self.win.refresh() def error(text): """show an error window""" CommonWindow('Error', text, curses.color_pair(8), curses.color_pair(8), curses.color_pair(7) | curses.A_BOLD, curses.color_pair(7)).run() def win(title, text): """show a message window and wait for a key""" CommonWindow(title, text, curses.color_pair(1), curses.color_pair(1), curses.color_pair(1), curses.color_pair(1)).run() def win_nokey(title, text, footer=''): """show a message window, does not wait for a key""" FixSizeCommonWindow(title, text, footer, curses.color_pair(1), curses.color_pair(1), curses.color_pair(1), curses.color_pair(1), waitkey = 0).run() def notyet(title): """show a not-yet-implemented message""" CommonWindow(title, 'Sorry, but this function\nis not implemented yet!', curses.color_pair(1) | curses.A_BOLD, curses.color_pair(1), curses.color_pair(4), curses.color_pair(4)).run() ###################################################################### class ProgressBarWindowBase(object): """Window with 1 or 2 ProgressBar""" def __init__(self, title, footer, bd_att, bd_bg, pb_att, pb_bg, waitkey=True, num_pb=1): w = app.maxw - 30 if len(title) > w - 4: title = title[:w-4] if len(footer) > w - 4: footer = footer[:w-4] if num_pb == 1: h, pb_width = 7, w-10 else: h, pb_width = 8, w-17 self.h, self.w = h, w self.bd_att, self.pb_att = bd_att, pb_att self.progressbars = [] try: win = curses.newwin(h, w, int((app.maxh-h)/2), int((app.maxw-w)/2)) for i in xrange(num_pb): self.progressbars.append(curses.newpad(1, pb_width)) self.pwin = curses.panel.new_panel(win) self.pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.keypad(1) win.nodelay(1) win.bkgd(bd_bg, bd_att) for pb in self.progressbars: pb.bkgd(' ', pb_bg) self.title = title self.footer = footer self.waitkey = waitkey self.ishidden = True self.y0 = int((app.maxh-self.h)/2) + 4 def finish(self): self.pwin.window().nodelay(0) def getch(self): return self.pwin.window().getch() def show_common(self): win = self.pwin.window() win.erase() win.box(0, 0) win.addstr(0, int((self.w-len(self.title)-2)/2), ' %s ' % utils.encode(self.title), curses.A_BOLD) win.addstr(2, 2, 'File: ') if self.waitkey: win.addstr(self.h-1, int((self.w-27)/2), ' Press any key to continue ') elif self.footer: win.addstr(self.h-1, int((self.w-len(self.footer)-2)/2), ' %s ' % utils.encode(self.footer)) self.ishidden = False return win def update_common(self, text, idx_str=''): win = self.pwin.window() maxlen = self.w - 23 # 11 + len("12345/67890") + 1 if len(text) > maxlen: text = text[:maxlen/2-1] + '~' + text[-maxlen/2:] else: text = text.ljust(maxlen) win.addstr(2, 9, utils.encode(text), curses.A_BOLD) y = self.w - len(idx_str) - 2 win.addstr(2, y, idx_str) return win class ProgressBarWindow(ProgressBarWindowBase): def __init__(self, title, footer, bd_att, bd_bg, pb_att, pb_bg, waitkey=True): super(ProgressBarWindow, self).__init__(title, footer, bd_att, bd_bg, pb_att, pb_bg, waitkey, 1) self.x0 = int((app.maxw-self.w)/2) + 2 self.x1 = self.x0 + self.w - 12 def show(self, text='', percent=0, idx_str=''): win = self.show_common() win.addstr(self.h-3, self.w-8, '[ %]') self.update(text, percent, idx_str) def update(self, text, percent, idx_str): win = self.update_common(text, idx_str) win.addstr(self.h-3, self.w-7, '%3d' % percent) w1 = percent * (self.w-11) / 100 pb = self.progressbars[0] pb.erase() pb.addstr(0, 0, ' ' * w1, self.pb_att | curses.A_BOLD) pb.refresh(0, 0, self.y0, self.x0, self.y0+1, self.x1) win.refresh() class ProgressBarWindow2(ProgressBarWindowBase): def __init__(self, title, footer, bd_att, bd_bg, pb_att, pb_bg, waitkey=True): super(ProgressBarWindow2, self).__init__(title, footer, bd_att, bd_bg, pb_att, pb_bg, waitkey, 2) self.x0 = int((app.maxw-self.w)/2) + 9 self.x1 = self.x0 + self.w - 19 def show(self, text='', percent1=0, percent2=0, idx_str=''): win = self.show_common() win.addstr(self.h-4, 2, 'Bytes' + ' '*(self.w-15) + '[ %]') win.addstr(self.h-3, 2, 'Count' + ' '*(self.w-15) + '[ %]') self.update(text, percent1, percent2, idx_str) def update(self, text, percent1, percent2, idx_str): win = self.update_common(text, idx_str) win.addstr(self.h-4, self.w-7, '%3d' % percent1) win.addstr(self.h-3, self.w-7, '%3d' % percent2) w1 = percent1 * (self.w-18) / 100 pb1 = self.progressbars[0] pb1.erase() pb1.addstr(0, 0, ' ' * w1, self.pb_att | curses.A_BOLD) pb1.refresh(0, 0, self.y0, self.x0, self.y0+1, self.x1) w2 = percent2 * (self.w-18) / 100 pb2 = self.progressbars[1] pb2.erase() pb2.addstr(0, 0, ' ' * w2, self.pb_att | curses.A_BOLD) pb2.refresh(0, 0, self.y0+1, self.x0, self.y0+2, self.x1) win.refresh() ###################################################################### def get_a_key(title, question): """show a window returning key pressed""" question = question.replace('\t', ' ' * 4) lines = question.split('\n') length = max(map(len, lines)) h = min(len(lines)+4, app.maxh-2) w = min(length+4, app.maxw-2) try: win = curses.newwin(h, w, int((app.maxh-h)/2), int((app.maxw-w)/2)) pwin = curses.panel.new_panel(win) pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.bkgd(curses.color_pair(1)) win.erase() win.box(0, 0) win.addstr(0, int((w-len(title)-2)/2), ' %s ' % utils.encode(title), curses.color_pair(1) | curses.A_BOLD) for row, l in enumerate(lines): win.addstr(row+2, 2, utils.encode(l)) win.refresh() win.keypad(1) while True: ch = win.getch() if ch in (0x03, 0x1B): # Ctrl-C, ESC pwin.hide() return -1 elif 0x01 <= ch <= 0xFF: pwin.hide() return ch else: curses.beep() ###################################################################### def confirm(title, question, default=0): """show a yes/no window, returning 1/0""" BTN_SELECTED = curses.color_pair(9) | curses.A_BOLD BTN_NO_SELECTED = curses.color_pair(1) | curses.A_BOLD h, w = 5, min(max(34, len(question)+5), app.maxw-2) try: win = curses.newwin(h, w, int((app.maxh-h)/2), int((app.maxw-w)/2)) pwin = curses.panel.new_panel(win) pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.bkgd(curses.color_pair(1)) win.erase() win.box(0, 0) win.addstr(0, int((w-len(title)-2)/2), ' %s' % \ utils.encode(title.capitalize()), curses.color_pair(1) | curses.A_BOLD) win.addstr(1, 2 , '%s?' % utils.encode(question)) win.refresh() row = int((app.maxh-h)/2) + 3 col = int((app.maxw-w)/2) col1 = col + int(w/5) + 1 col2 = col + int(w*4/5) - 6 win.keypad(1) answer = default while True: if answer == 1: attr_yes, attr_no = BTN_SELECTED, BTN_NO_SELECTED else: attr_yes, attr_no = BTN_NO_SELECTED, BTN_SELECTED btn = curses.newpad(1, 8) btn.addstr(0, 0, '[ Yes ]', attr_yes) btn.refresh(0, 0, row, col1, row + 1, col1 + 6) btn = curses.newpad(1, 7) btn.addstr(0, 0, '[ No ]', attr_no) btn.refresh(0, 0, row, col2, row + 1, col2 + 5) ch = win.getch() if ch in (curses.KEY_UP, curses.KEY_DOWN, curses.KEY_LEFT, curses.KEY_RIGHT, 9): answer = not answer elif ch in (ord('Y'), ord('y')): answer = 1 break elif ch in (ord('N'), ord('n')): answer = 0 break elif ch in (0x03, 0x1B): # Ctrl-C, ESC answer = -1 break elif ch in (10, 13): # enter break else: curses.beep() pwin.hide() return answer def confirm_all(title, question, default = 0): """show a yes/all/no/stop window, returning 1/2/0/-1""" BTN_SELECTED = curses.color_pair(9) | curses.A_BOLD BTN_NO_SELECTED = curses.color_pair(1) | curses.A_BOLD h, w = 5, min(max(45, len(question)+5), app.maxw-2) try: win = curses.newwin(h, w, int((app.maxh-h)/2), int((app.maxw-w)/2)) pwin = curses.panel.new_panel(win) pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.bkgd(curses.color_pair(1)) win.erase() win.box(0, 0) win.addstr(0, int((w-len(title)-2)/2), ' %s ' % utils.encode(title), curses.color_pair(1) | curses.A_BOLD) win.addstr(1, 2 , '%s?' % utils.encode(question)) win.refresh() row = int((app.maxh-h) / 2) + 3 col = int((app.maxw-w) / 2) x = (w-28) / 5 col1 = col + x + 1 col2 = col1 + 7 + x col3 = col2 + 7 + x col4 = col3 + 6 + x win.keypad(1) answer = default order = [1, 2, 0, -1] while True: attr_yes = attr_all = attr_no = attr_skipall = BTN_NO_SELECTED if answer == 1: attr_yes = BTN_SELECTED elif answer == 2: attr_all = BTN_SELECTED elif answer == 0: attr_no = BTN_SELECTED elif answer == -1: attr_skipall = BTN_SELECTED else: raise ValueError btn = curses.newpad(1, 8) btn.addstr(0, 0, '[ Yes ]', attr_yes) btn.refresh(0, 0, row, col1, row + 1, col1 + 6) btn = curses.newpad(1, 8) btn.addstr(0, 0, '[ All ]', attr_all) btn.refresh(0, 0, row, col2, row + 1, col2 + 6) btn = curses.newpad(1, 7) btn.addstr(0, 0, '[ No ]', attr_no) btn.refresh(0, 0, row, col3, row + 1, col3 + 5) btn = curses.newpad(1, 15) btn.addstr(0, 0, '[ Stop ]', attr_skipall) btn.refresh(0, 0, row, col4, row + 1, col4 + 7) ch = win.getch() if ch in (curses.KEY_UP, curses.KEY_LEFT): try: answer = order[order.index(answer) - 1] except IndexError: answer = order[len(order)] if ch in (curses.KEY_DOWN, curses.KEY_RIGHT, 9): try: answer = order[order.index(answer) + 1] except IndexError: answer = order[0] elif ch in (ord('Y'), ord('y')): answer = 1 break elif ch in (ord('A'), ord('a')): answer = 2 break elif ch in (ord('N'), ord('n')): answer = 0 break elif ch in (ord('S'), ord('s'), 0x03, 0x1B): # Ctrl-C, ESC answer = -1 break elif ch in (10, 13): # enter break else: curses.beep() pwin.hide() return answer def confirm_all_none(title, question, default = 0): """show a yes/all/no/none/stop window, returning 1/2/0/-2/-1""" BTN_SELECTED = curses.color_pair(9) | curses.A_BOLD BTN_NO_SELECTED = curses.color_pair(1) | curses.A_BOLD h, w = 5, min(max(50, len(question)+5), app.maxw-2) try: win = curses.newwin(h, w, int((app.maxh-h)/2), int((app.maxw-w)/2)) pwin = curses.panel.new_panel(win) pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.bkgd(curses.color_pair(1)) win.erase() win.box(0, 0) win.addstr(0, int((w-len(title)-2)/2), ' %s ' % utils.encode(title), curses.color_pair(1) | curses.A_BOLD) win.addstr(1, 2 , '%s?' % utils.encode(question)) win.refresh() row = int((app.maxh-h) / 2) + 3 col = int((app.maxw-w) / 2) x = (w-36) / 6 col1 = col + x + 1 col2 = col1 + 7 + x col3 = col2 + 7 + x col4 = col3 + 6 + x col5 = col4 + 8 + x win.keypad(1) answer = default order = [1, 2, 0, -2, -1] while True: attr_yes, attr_all, attr_no, attr_none, attr_skipall = [BTN_NO_SELECTED] * 5 if answer == 1: attr_yes = BTN_SELECTED elif answer == 2: attr_all = BTN_SELECTED elif answer == 0: attr_no = BTN_SELECTED elif answer == -2: attr_none = BTN_SELECTED elif answer == -1: attr_skipall = BTN_SELECTED else: raise ValueError btn = curses.newpad(1, 8) btn.addstr(0, 0, '[ Yes ]', attr_yes) btn.refresh(0, 0, row, col1, row + 1, col1 + 6) btn = curses.newpad(1, 8) btn.addstr(0, 0, '[ All ]', attr_all) btn.refresh(0, 0, row, col2, row + 1, col2 + 6) btn = curses.newpad(1, 7) btn.addstr(0, 0, '[ No ]', attr_no) btn.refresh(0, 0, row, col3, row + 1, col3 + 5) btn = curses.newpad(1, 9) btn.addstr(0, 0, '[ NOne ]', attr_none) btn.refresh(0, 0, row, col4, row + 1, col4 + 7) btn = curses.newpad(1, 9) btn.addstr(0, 0, '[ Stop ]', attr_skipall) btn.refresh(0, 0, row, col5, row + 1, col5 + 7) ch = win.getch() if ch in (curses.KEY_UP, curses.KEY_LEFT): try: answer = order[order.index(answer) - 1] except IndexError: answer = order[len(order)] if ch in (curses.KEY_DOWN, curses.KEY_RIGHT, 9): try: answer = order[order.index(answer) + 1] except IndexError: answer = order[0] elif ch in (ord('Y'), ord('y')): answer = 1 break elif ch in (ord('A'), ord('a')): answer = 2 break elif ch in (ord('N'), ord('n')): answer = 0 break elif ch in (ord('O'), ord('o')): answer = -2 break elif ch in (ord('S'), ord('s'), 0x03, 0x1B): # Ctrl-C, ESC answer = -1 break elif ch in (10, 13): # enter break else: curses.beep() pwin.hide() return answer ###################################################################### class Yes_No_Buttons(object): """Yes/No buttons""" def __init__(self, w, h, d): self.row = int((app.maxh-h) / 2) + 4 + d col = int((app.maxw-w) / 2) self.col1 = col + int(w/5) + 1 self.col2 = col + int(w*4/5) - 6 self.active = 0 def show(self): BTN_SELECTED = curses.color_pair(9) | curses.A_BOLD BTN_NO_SELECTED = curses.color_pair(1) | curses.A_BOLD if self.active == 0: attr1, attr2 = BTN_NO_SELECTED, BTN_NO_SELECTED elif self.active == 1: attr1, attr2 = BTN_SELECTED, BTN_NO_SELECTED else: attr1, attr2 = BTN_NO_SELECTED, BTN_SELECTED btn = curses.newpad(1, 8) btn.addstr(0, 0, '[]', attr1) btn.refresh(0, 0, self.row, self.col1, self.row + 1, self.col1 + 6) btn = curses.newpad(1, 7) btn.addstr(0, 0, '[ No ]', attr2) btn.refresh(0, 0, self.row, self.col2, self.row + 1, self.col2 + 5) def manage_keys(self): tmp = curses.newpad(1, 1) while True: ch = tmp.getch() if ch in (0x03, 0x1B): # Ctrl-C, ESC return -1 elif ch == ord('\t'): return ch elif ch in (10, 13): # enter if self.active == 1: return 10 else: return -1 else: curses.beep() ###################################################################### # helper functions def get_char_raw(win, first_byte): try: return unichr(first_byte) except: raise UnicodeError def get_char_ascii(win, first_byte): try: if first_byte < 127: # <= 127 return chr(first_byte).decode('ascii') else: raise UnicodeError except: raise UnicodeError def get_char_codec(win, first_byte, encoding): try: if first_byte <= 255: return chr(first_byte).decode(encoding) else: raise UnicodeError except: raise UnicodeError def get_char_utf8(win, first_byte): def get_check_next_byte(): c = win.getch() if 128 <= c <= 191: return c else: raise UnicodeError bytes = [] if first_byte <= 127: # 1 bytes bytes.append(first_byte) elif 194 <= first_byte <= 223: # 2 bytes bytes.append(first_byte) bytes.append(get_check_next_byte()) elif 224 <= first_byte <= 239: # 3 bytes bytes.append(first_byte) bytes.append(get_check_next_byte()) bytes.append(get_check_next_byte()) elif 240 <= first_byte <= 244: # 4 bytes bytes.append(first_byte) bytes.append(get_check_next_byte()) bytes.append(get_check_next_byte()) bytes.append(get_check_next_byte()) buf = ''.join([chr(b) for b in bytes]) return buf.decode('utf-8') def get_char(win, first_byte): from __init__ import g_encoding try: if g_encoding is None: return get_char_raw(win, first_byte) elif g_encoding == 'UTF-8': return get_char_utf8(win, first_byte) elif g_encoding == 'ASCII': return get_char_ascii(win, first_byte) else: return get_char_codec(win, first_byte, g_encoding) except UnicodeError: return unichr(first_byte) ###################################################################### class EntryLine(object): """An entry line to enter a dir. or file, a pattern, etc""" def __init__(self, w, h, x, y, path, with_history, with_complete, panelpath, cli=False): try: self.entry = curses.newwin(1, w-4+1, x, y) except curses.error: print 'Can\'t create window' sys.exit(-1) self.cli = cli self.with_complete = with_complete if with_history in history.keys(): self.history = history[with_history][:] self.history_i = len(self.history) else: self.history = None self.colour = curses.color_pair(25) if cli else curses.color_pair(11)|curses.A_BOLD self.entry.attrset(self.colour) self.entry.keypad(1) self.entry_width = w - 4 self.origtext = path self.text = path self.panelpath = panelpath self.pos = len(self.text) self.ins = True def show(self): text, pos, ew, ltext = self.text, self.pos, self.entry_width, len(self.text) if pos < ew: relpos = pos textstr = (ltext > ew) and text[:ew] or text.ljust(ew) else: if pos > ltext - (ew-1): relpos = ew-1 - (ltext-pos) textstr = text[ltext-ew+1:] + ' ' else: relpos = pos - int(pos/ew)*ew textstr = text[int(pos/ew)*ew:int(pos/ew)*ew+ew] self.entry.bkgd(curses.color_pair(1)) self.entry.erase() self.entry.addstr(utils.encode(textstr[:ew]), self.colour) self.entry.move(0, relpos) self.entry.refresh() def manage_keys(self): import string stepchars = string.whitespace + string.punctuation def __prev_step(pos): pos -= 1 while pos > 0 and self.text[pos] not in stepchars: pos -=1 return pos if pos > 0 else 0 def __next_step(pos): pos += 1 l = len(self.text) while pos < l and self.text[pos] not in stepchars: pos +=1 return pos if pos < l else l def __select_item(entries, pos0, title='', cli=False): if not entries: curses.beep() return elif len(entries) == 1: return entries.pop() else: y, x0 = self.entry.getbegyx() x = x0 + pos0 if x > 2*app.maxw/3 - 4: x = x0 + 2 if cli: y = app.maxh / 2 - 2 selected = SelectItem(entries, y+1, x-2, title=title).run() if cli: app.lpane.display() app.rpane.display() else: app.display() cursor_show2() return selected while True: self.show() ch = self.entry.getch() # print 'key: \'%s\' <=> %c <=> 0x%X <=> %d' % \ # (curses.keyname(ch), ch & 255, ch, ch) if ch in (0x03, 0x1B) and not self.cli: # Ctrl-C, ESC return -1 elif ch == 0x18 and self.cli: # Ctrl-X return -1 # history elif ch == curses.KEY_UP: if self.history is not None and self.history_i > 0: if self.history_i == len(self.history): if self.text is None: self.text = '' self.history.append(self.text) self.history_i -= 1 self.text = self.history[self.history_i] self.pos = len(self.text) elif ch == curses.KEY_DOWN: if self.history is not None and self.history_i < len(self.history)-1: self.history_i += 1 self.text = self.history[self.history_i] self.pos = len(self.text) # special elif ch == ord('\t') and not self.cli: # tab, no CLI return ch elif ch == 0x14 and not self.cli: # Ctrl-T, no CLI if self.with_complete: base, entries = files.complete(self.text, self.panelpath) selected = __select_item(entries, len(self.text), 'Complete', False) if selected is not None and selected != -1: self.text = os.path.join(base, selected) self.pos = len(self.text) return 0x14 else: continue elif ch in (ord('\t'), 0x14) and self.cli: # tab or Ctrl-T, in CLI if self.text.rfind(' "', 0, self.pos) != -1: pos1 = self.pos-1 if self.text[self.pos-1]=='"' else self.pos pos0 = self.text.rfind(' "', 0, pos1) + 2 else: pos0 = self.text.rfind(' ', 0, self.pos) + 1 pos1 = self.pos text = self.text[pos0:pos1] base, entries = files.complete(text, self.panelpath) if pos0 == 0 or not entries: base = None entries = files.complete_programs(text) selected = __select_item(entries, pos0, 'Complete', True) if selected is not None and selected != -1: if base is None: # system program selected += ' ' else: # file selected = os.path.join(base, selected) self.text = self.text[:pos0] + selected + self.text[pos1:] self.pos = len(self.text[:pos0]+selected) elif ch in (10, 13): # enter return 10 # movement elif ch in (curses.KEY_HOME, 0x01): # home, Ctrl-A self.pos = 0 elif ch in (curses.KEY_END, 0x05): # end, Ctrl-E self.pos = len(self.text) elif ch in (curses.KEY_LEFT, 0x02): # <-, Ctrl-B if self.pos > 0: self.pos -= 1 elif ch in (curses.KEY_RIGHT, 0x06): # ->, Ctr-F if self.pos < len(self.text): self.pos += 1 elif ch in (0x10, 0x21C): # Ctrl-P, Ctrl-Cursor_left self.pos = __prev_step(self.pos) elif ch in (0x0E, 0x22B): # Ctrl-N, Ctrl-Cursor_right self.pos = __next_step(self.pos) # deletion elif ch in (127, curses.KEY_BACKSPACE): # Backspace if len(self.text) > 0 and self.pos > 0: self.text = self.text[:self.pos-1] + self.text[self.pos:] self.pos -= 1 elif ch == curses.KEY_DC: # Del if self.pos < len(self.text): self.text = self.text[:self.pos] + self.text[self.pos+1:] elif ch == 0x17: # Ctrl-W self.text = '' self.pos = 0 elif ch == 0x08: # Ctrl-H self.text = self.text[self.pos:] self.pos = 0 elif ch == 0x0B: # Ctrl-K self.text = self.text[:self.pos] elif ch in (0x11, 0x107): # Ctrl-Q, Ctrl-Backspace pos = __prev_step(self.pos) self.text = self.text[:pos] + self.text[self.pos:] self.pos = pos elif ch in (0x12, 0x202): # Ctrl-R, Ctrl-Del pos = __next_step(self.pos) self.text = self.text[:self.pos] + self.text[pos:] # insertion elif ch == 0x1A: # Ctrl-Z self.text = self.origtext self.pos = len(self.text) elif ch == 0x16: # Ctrl-V buf = app.act_pane.act_tab.get_file() self.text = self.text[:self.pos] + buf + self.text[self.pos:] self.pos += len(buf) elif ch == 0x13: # Ctrl-S buf = app.act_pane.act_tab.path + os.sep self.text = self.text[:self.pos] + buf + self.text[self.pos:] self.pos += len(buf) elif ch == 0x0F: # Ctrl-O buf = app.noact_pane.act_tab.path + os.sep if self.cli: buf = '"' + buf + '"' self.text = self.text[:self.pos] + buf + self.text[self.pos:] self.pos += len(buf) elif ch in (0x04, 0x1C): # Ctrl-D, Ctrl-\ selected = __select_item(app.prefs.bookmarks, self.pos if self.cli else 0, 'Bookmarks', self.cli) if selected not in (None, -1, ''): if self.cli: self.text = self.text[:self.pos] + selected + self.text[self.pos:] self.pos = len(self.text) else: self.text, self.pos = selected, len(selected) return 0x1C # hack, to update screen elif ch == 0x19: # Ctrl-Y items = app.act_pane.act_tab.history[::-1] items.extend(reversed([h for h in app.noact_pane.act_tab.history if h not in items])) if items: selected = __select_item(items, self.pos if self.cli else 0, 'Previous paths', self.cli) if selected not in (None, -1, ''): if self.cli: self.text = self.text[:self.pos] + selected + self.text[self.pos:] self.pos = len(self.text) else: self.text, self.pos = selected, len(selected) return 0x19 # hack, to update screen elif ch == 0x07 and not self.cli: # Ctr-G, not CLI selected = __select_item(self.history[::-1], 0, 'Historic', False) if selected not in (None, -1, ''): self.text, self.pos = selected, len(selected) return 0x07 # hack, to update screen elif ch == 0x07 and self.cli: # Ctr-G, in CLI BOOKMARKS_STR, HISTORY_STR = '----- Stored: -----', '----- History: -----' entries = [BOOKMARKS_STR] entries.extend([c for c in app.prefs.powercli_favs if c]) entries.append(HISTORY_STR) entries.extend(history['cli'][:]) selected = __select_item(entries, 0, 'Commands', True) if selected not in (None, -1, '', BOOKMARKS_STR, HISTORY_STR): self.text, self.pos = selected, len(selected) return 0x07 # hack, to update screen # chars and edit keys elif ch == curses.KEY_IC: # insert self.ins = not self.ins elif len(self.text) < 255 and 32 <= ch <= 255: buf = get_char(self.entry, ch) if self.ins: self.text = self.text[:self.pos] + buf + self.text[self.pos:] self.pos += 1 else: self.text = self.text[:self.pos] + buf + self.text[self.pos+1:] self.pos += 1 else: curses.beep() ###################################################################### class Entry(object): """An entry window to enter a dir. or file, a pattern, ...""" def __init__(self, title, help='', path='', with_history='', with_complete=True, panelpath=''): h = 6 w = max(len(help)+5, app.maxw/2) try: win = curses.newwin(h, w, int((app.maxh-h)/2), int((app.maxw-w)/2)) self.entry = EntryLine(w, h, int((app.maxh-h)/2)+2, int((app.maxw-w+4)/2), path, with_history, with_complete, panelpath) self.btns = Yes_No_Buttons(w, h, 0) self.pwin = curses.panel.new_panel(win) self.pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.bkgd(curses.color_pair(1)) win.erase() win.box(0, 0) win.addstr(1, 2 , '%s:' % utils.encode(help)) win.addstr(0, int((w-len(title)-2)/2), ' %s ' % utils.encode(title), curses.color_pair(1) | curses.A_BOLD) win.refresh() self.with_history = with_history self.active_widget = self.entry def run(self): self.entry.entry.refresh() # needed to avoid a problem with blank paths self.entry.show() self.btns.show() cursor_show2() quit = False while not quit: self.btns.show() ans = self.active_widget.manage_keys() if ans == -1: # Ctrl-C quit = True answer = None elif ans == ord('\t'): # tab if self.active_widget == self.entry: self.active_widget = self.btns self.btns.active = 1 cursor_hide() answer = self.entry.text elif self.active_widget == self.btns and self.btns.active == 1: self.btns.active = 2 cursor_hide() answer = None else: self.active_widget = self.entry self.btns.active = 0 cursor_show2() elif ans in (0x07, 0x14, 0x19, 0x1C): # Ctrl-G, Ctrl-T, Ctrl-Y, Ctrl-\ # this is a hack, we need to return to refresh Entry return [self.entry.text] elif ans == 10: # return values quit = True answer = self.entry.text cursor_hide() if answer: answer = os.path.expanduser(answer) if self.with_history in history.keys(): add_to_history(history[self.with_history], self.entry.text) self.pwin.hide() return answer ###################################################################### class DoubleEntry(object): """An entry window to enter 2 dirs. or files, patterns, ...""" def __init__(self, title, help1='', path1='', with_history1='', with_complete1=True, panelpath1='', help2='', path2='', with_history2='', with_complete2=True, panelpath2='', active_entry=0): h = 9 w = max(len(help1)+5, len(help2)+5, app.maxw/2) try: win = curses.newwin(h, w, int((app.maxh-h)/2)-1, int((app.maxw-w)/2)) self.entry1 = EntryLine(w, h, int((app.maxh-h)/2) + 1, int((app.maxw-w+4) / 2), path1, with_history1, with_complete1, panelpath1) self.entry2 = EntryLine(w, h, int((app.maxh-h)/2) + 4, int((app.maxw-w+4) / 2), path2, with_history2, with_complete2, panelpath2) self.btns = Yes_No_Buttons(w, h, 2) self.pwin = curses.panel.new_panel(win) self.pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.bkgd(curses.color_pair(1)) win.erase() win.box(0, 0) win.addstr(1, 2 , '%s:' % utils.encode(help1)) win.addstr(4, 2 , '%s:' % utils.encode(help2)) win.addstr(0, int((w-len(title)-2)/2), ' %s ' % utils.encode(title), curses.color_pair(1) | curses.A_BOLD) win.refresh() self.with_history1, self.with_history2 = with_history1, with_history2 self.active_entry = (active_entry==0) and self.entry1 or self.entry2 self.active_entry_i = active_entry def run(self): # needed to avoid a problem with blank paths self.entry1.entry.refresh() self.entry2.entry.refresh() self.entry1.show() self.entry2.show() self.btns.show() cursor_show2() answer = True quit = False while not quit: self.btns.show() if self.active_entry_i in [0, 1]: ans = self.active_entry.manage_keys() else: ans = self.btns.manage_keys() if ans == -1: # Ctrl-C quit = True answer = False elif ans == ord('\t'): # tab self.active_entry_i += 1 if self.active_entry_i > 3: self.active_entry_i = 0 if self.active_entry_i == 0: self.active_entry = self.entry1 self.btns.active = 0 cursor_show2() elif self.active_entry_i == 1: self.active_entry = self.entry2 self.btns.active = 0 cursor_show2() elif self.active_entry_i == 2: self.btns.active = 1 cursor_hide() answer = True else: self.btns.active = 2 cursor_hide() answer = False elif ans in (0x07, 0x14, 0x19, 0x1C): # Ctrl-G, Ctrl-T, Ctrl-Y, Ctrl-\ # this is a hack, we need to return to refresh Entry return [self.entry1.text, self.entry2.text, self.active_entry_i] elif ans == 10: # return values quit = True answer = True cursor_hide() if answer: if self.with_history1 in history.keys(): add_to_history(history[self.with_history1], self.entry1.text) if self.with_history2 in history.keys(): add_to_history(history[self.with_history2], self.entry2.text) ans1 = os.path.expanduser(self.entry1.text) ans2 = os.path.expanduser(self.entry2.text) else: ans1, ans2 = None, None self.pwin.hide() return ans1, ans2 ###################################################################### class SelectItem(object): """A window to select an item""" def __init__(self, entries, y0, x0, entry_i='', title=''): h = (app.maxh-1) - (y0+1) + 1 w = min(max(max(map(len, entries)), len(title)+4, int(app.maxw/5))+4, app.maxw-8) if x0 + w >= app.maxw: w = max(10, w-x0) try: win = curses.newwin(h, w, y0, x0) self.pwin = curses.panel.new_panel(win) self.pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.keypad(1) cursor_hide() win.bkgd(curses.color_pair(4)) self.entries = entries try: self.entry_i = self.entries.index(entry_i) except: self.entry_i = 0 self.title = title def show(self): win = self.pwin.window() win.erase() win.refresh() win.box(0, 0) y, x = win.getbegyx() h, w = win.getmaxyx() h0, w0 = h - 2, w - 3 if self.title != '': win.addstr(0, int((w-len(self.title)-2)/2), ' %s ' % utils.encode(self.title), curses.color_pair(3)) nels = len(self.entries) entry_a = int(self.entry_i/h0) * h0 for i in xrange(h0): try: line = self.entries[entry_a+i] except IndexError: line = '' if line: win.addstr(i+1, 2, utils.encode(crop_line(line, w0)), curses.color_pair(4)) win.refresh() # cursor cursor = curses.newpad(1, w-1) cursor.bkgd(curses.color_pair(1)) cursor.erase() line = self.entries[self.entry_i] cursor.addstr(0, 1, utils.encode(crop_line(line, w0)), curses.color_pair(1) | curses.A_BOLD) y += 1; x += 1 cur_row = y + self.entry_i % h0 cursor.refresh(0, 0, cur_row, x, cur_row, x + w0) # scrollbar if nels > h0: n = max(int(h0*h0/nels), 1) y0 = min(max(int(int(self.entry_i/h0)*h0*h0/nels),0), h0 - n) else: y0 = n = 0 win.vline(y0+1, w-1, curses.ACS_CKBOARD, n) if entry_a != 0: win.vline(1, w-1, '^', 1) if n == 1 and (y0 + 1 == 1): win.vline(2, w-1, curses.ACS_CKBOARD, n) if nels - 1 > entry_a + h0 - 1: win.vline(h0, w-1, 'v', 1) if n == 1 and (y0 == h0 - 1): win.vline(h0-1, w-1, curses.ACS_CKBOARD, n) def manage_keys(self): h, w = self.pwin.window().getmaxyx() nels = len(self.entries) while True: self.show() ch = self.pwin.window().getch() if ch in (0x03, 0x1B, ord('q'), ord('Q')): # Ctrl-C, ESC return -1 elif ch in (curses.KEY_UP, ord('k'), ord('K')): if self.entry_i != 0: self.entry_i -= 1 elif ch in (curses.KEY_DOWN, ord('j'), ord('J')): if self.entry_i < nels - 1: self.entry_i += 1 elif ch in (curses.KEY_PPAGE, curses.KEY_BACKSPACE, 0x08, 0x02): if self.entry_i < h - 3: self.entry_i = 0 else: self.entry_i -= h - 2 elif ch in (curses.KEY_NPAGE, ord(' '), 0x06): if self.entry_i + (h-2) > nels - 1: self.entry_i = nels - 1 else: self.entry_i += h - 2 elif ch in (curses.KEY_HOME, 0x01): self.entry_i = 0 elif ch in (curses.KEY_END, 0x05): self.entry_i = nels - 1 elif ch == 0x0C: # Ctrl-L self.entry_i = int(len(self.entries)/2) elif ch == 0x30: # 0 self.entry_i = ch - 0x30 + 9 elif 0x31 <= ch <= 0x39: # 1..9 self.entry_i = ch - 0x30 - 1 elif ch == 0x13: # Ctrl-S theentries = self.entries[self.entry_i:] ch2 = self.pwin.window().getkey() for e in theentries: if e.find(ch2) == 0: break else: continue self.entry_i = self.entries.index(e) elif ch in (0x0A, 0x0D): # enter return self.entries[self.entry_i] else: curses.beep() def run(self): selected = self.manage_keys() self.pwin.below().top() self.pwin.hide() return selected ###################################################################### class FindfilesWin(object): """A window to select a file""" def __init__(self, entries, entry_i=''): y0 = 1 h = (app.maxh-1) - (y0+1) + 1 # w = max(map(len, entries)) + 4 w = 64 x0 = int((app.maxw-w) / 2) try: win = curses.newwin(h, w, y0, x0) self.pwin = curses.panel.new_panel(win) self.pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.keypad(1) cursor_hide() win.bkgd(curses.color_pair(4)) self.entries = entries try: self.entry_i = self.entries.index(entry_i) except: self.entry_i = 0 self.btn_active = 0 def show(self): win = self.pwin.window() win.erase() win.refresh() win.box(0, 0) y, x = win.getbegyx() h, w = win.getmaxyx() h0, w0 = h - 4, w - 3 nels = len(self.entries) entry_a = int(self.entry_i/h0) * h0 for i in xrange(h0): try: line = self.entries[entry_a+i] except IndexError: line = '' if len(line) >= w0: if w0 % 2 == 0: # even line = line[:int(w0/2)] + '~' + line[-int(w0/2)+3:] else: # odd line = line[:int(w0/2)+1] + '~' + line[-int(w0/2)+3:] if line: win.addstr(i+1, 2, utils.encode(line), curses.color_pair(4)) win.refresh() # cursor cursor = curses.newpad(1, w-2) cursor.attrset(curses.color_pair(1) | curses.A_BOLD) cursor.bkgdset(curses.color_pair(1)) cursor.erase() line = self.entries[self.entry_i] if len(line) >= w0: if (w - 2) % 2 == 0: # even line = line[:int((w-2)/2)] + '~' + line[-int((w-2)/2)+3:] else: # odd line = line[:int((w-2)/2)+1] + '~' + line[-int((w-2)/2)+3:] cursor.addstr(0, 1, utils.encode(line), curses.color_pair(1) | curses.A_BOLD) y += 1; x += 1 cursor.refresh(0, 0, y + self.entry_i % h0, x, y + self.entry_i % h0, x + w0) # scrollbar if nels > h0: n = max(int(h0*h0/nels), 1) y0 = min(max(int(int(self.entry_i/h0)*h0*h0/nels),0), h0 - n) else: y0 = n = 0 win.vline(y0+1, w-1, curses.ACS_CKBOARD, n) if entry_a != 0: win.vline(1, w-1, '^', 1) if n == 1 and (y0 + 1 == 1): win.vline(2, w-1, curses.ACS_CKBOARD, n) if nels - 1 > entry_a + h0 - 1: win.vline(h0, w-1, 'v', 1) if n == 1 and (y0 == h0 - 1): win.vline(h0-1, w-1, curses.ACS_CKBOARD, n) win.hline(h-3, 1, curses.ACS_HLINE, w-2) win.hline(h-3, 0, curses.ACS_LTEE, 1) win.hline(h-3, w-1, curses.ACS_RTEE, 1) win.addstr(h-2, 3, '[ Go ] [ Panelize ] [ View ] [ Edit ] [ Do ] [ Quit ]', curses.color_pair(4)) attr0 = attr1 = attr2 = attr3 = attr4 = attr5 = curses.color_pair(4) if self.btn_active == 0: attr0 = curses.color_pair(1) | curses.A_BOLD elif self.btn_active == 1: attr1 = curses.color_pair(1) | curses.A_BOLD elif self.btn_active == 2: attr2 = curses.color_pair(1) | curses.A_BOLD elif self.btn_active == 3: attr3 = curses.color_pair(1) | curses.A_BOLD elif self.btn_active == 4: attr4 = curses.color_pair(1) | curses.A_BOLD else: attr5 = curses.color_pair(1) | curses.A_BOLD win.addstr(h-2, 3, '[ Go ]', attr0) win.addstr(h-2, 11, '[ PAnelize ]', attr1) win.addstr(h-2, 25, '[ View ]', attr2) win.addstr(h-2, 35, '[ Edit ]', attr3) win.addstr(h-2, 45, '[ Do ]', attr4) win.addstr(h-2, 53, '[ Quit ]', attr5) win.refresh() def manage_keys(self): h, w = self.pwin.window().getmaxyx() nels = len(self.entries) while True: self.show() ch = self.pwin.window().getch() if ch in (0x03, 0x1B, ord('q'), ord('Q')): # Ctrl-C, ESC return -1, None elif ch in (curses.KEY_UP, ord('k'), ord('K')): if self.entry_i != 0: self.entry_i -= 1 elif ch in (curses.KEY_DOWN, ord('j'), ord('j')): if self.entry_i != nels - 1: self.entry_i += 1 elif ch in (curses.KEY_PPAGE, curses.KEY_BACKSPACE, 0x08, 0x02): if self.entry_i < (h - 5): self.entry_i = 0 else: self.entry_i -= (h - 4) elif ch in (curses.KEY_NPAGE, ord(' '), 0x06): if self.entry_i + (h-4) > nels - 1: self.entry_i = nels - 1 else: self.entry_i += (h - 4) elif ch in (curses.KEY_HOME, 0x01): self.entry_i = 0 elif ch in (curses.KEY_END, 0x05): self.entry_i = nels - 1 elif ch == 0x13: # Ctrl-S theentries = self.entries[self.entry_i:] ch2 = self.pwin.window().getkey() for e in theentries: if e.find(ch2) == 0: break else: continue self.entry_i = self.entries.index(e) elif ch in (curses.KEY_LEFT, ): if self.btn_active == 0: self.btn_active = 5 else: self.btn_active -= 1 elif ch in (0x09, curses.KEY_RIGHT): # tab if self.btn_active == 5: self.btn_active = 0 else: self.btn_active += 1 elif ch in (0x0A, 0x0D): # enter if self.btn_active == 0: return 0, self.entries[self.entry_i] elif self.btn_active == 1: return 1, None elif self.btn_active == 2: return 2, self.entries[self.entry_i] elif self.btn_active == 3: return 3, self.entries[self.entry_i] elif self.btn_active == 4: return 4, self.entries[self.entry_i] elif self.btn_active == 5: return -1, None elif ch in (ord('a'), ord('A')): return 1, None elif ch in (curses.KEY_F3, ord('v'), ord('V')): return 2, self.entries[self.entry_i] elif ch in (curses.KEY_F4, ord('e'), ord('E')): return 3, self.entries[self.entry_i] elif ch in (ord('@'), ord('d'), ord('D')): return 4, self.entries[self.entry_i] else: curses.beep() def run(self): selected = self.manage_keys() self.pwin.hide() return selected ###################################################################### class MenuWin(object): """A window to select a menu option""" def __init__(self, title, entries): h = len(entries) + 4 w = min(max(len(title)+2, max(map(len, entries)))+4, app.maxw-8) y0 = int((app.maxh-h) / 2) x0 = int((app.maxw-w) / 2) try: win = curses.newwin(h, w, y0, x0) self.pwin = curses.panel.new_panel(win) self.pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.keypad(1) cursor_hide() win.bkgd(curses.color_pair(3)) self.title = title self.entries = entries self.entry_i = 0 self.keys = [e[0] for e in entries] def show(self): win = self.pwin.window() win.erase() win.box(0, 0) y, x = win.getbegyx() h, w = win.getmaxyx() attr = curses.color_pair(7) win.addstr(0, int((w-len(self.title)-2)/2), ' %s ' % \ utils.encode(self.title), attr) for i in xrange(h-2): try: line = self.entries[i] except IndexError: line = '' if line: win.addstr(i+2, 2, utils.encode(crop_line(line, w-3)), curses.color_pair(3)) win.refresh() # cursor cursor = curses.newpad(1, w-2) cursor.bkgd(curses.color_pair(1)) cursor.erase() line = self.entries[self.entry_i] cursor.addstr(0, 1, utils.encode(crop_line(line, w-3)), curses.color_pair(1) | curses.A_BOLD) y += 1; x += 1 cursor.refresh(0, 0, y + self.entry_i % (h-4) + 1, x, y + self.entry_i % (h-4) + 1, x + w-3) def manage_keys(self): while True: self.show() ch = self.pwin.window().getch() if ch in (0x03, 0x1B, ord('q'), ord('Q')): # Ctrl-C, ESC return -1 elif ch in (curses.KEY_UP, ord('k'), ord('K')): if self.entry_i != 0: self.entry_i -= 1 elif ch in (curses.KEY_DOWN, ord('j'), ord('J')): if self.entry_i != len(self.entries) - 1: self.entry_i += 1 elif ch in (curses.KEY_HOME, 0x01, curses.KEY_PPAGE, 0x08, 0x02, curses.KEY_BACKSPACE): self.entry_i = 0 elif ch in (curses.KEY_END, 0x05, curses.KEY_NPAGE, ord(' '), 0x06): self.entry_i = len(self.entries) - 1 elif ch == 0x13: # Ctrl-S theentries = self.entries[self.entry_i:] ch2 = self.pwin.window().getkey() for e in theentries: if e.find(ch2) == 0: break else: continue self.entry_i = self.entries.index(e) elif ch in (0x0A, 0x0D): # enter return self.entries[self.entry_i] elif 0 <= ch <= 255 and chr(ch).lower() in self.keys: return self.entries[self.keys.index(chr(ch).lower())] else: curses.beep() def run(self): selected = self.manage_keys() self.pwin.hide() return selected ###################################################################### class ChangePerms(object): """A window to change permissions, owner or group""" def __init__(self, filename, fileinfo, i=0, n=0): h, w = 6+4, 64+4 x0, y0 = int((app.maxw-w)/2), int((app.maxh-h)/2) try: win = curses.newwin(h, w, y0, x0) self.pwin = curses.panel.new_panel(win) self.pwin.top() except curses.error: print 'Can\'t create window' sys.exit(-1) win.keypad(1) cursor_hide() win.bkgd(curses.color_pair(1)) self.file = filename self.perms_old = files.perms2str(fileinfo[files.FT_PERMS]) self.perms = [l for l in self.perms_old] self.owner = fileinfo[files.FT_OWNER] self.group = fileinfo[files.FT_GROUP] self.owner_old = self.owner[:] self.group_old = self.group[:] self.recursive = True self.i, self.n, self.entry_i, self.w = i, n, 0, w def show_btns(self): win = self.pwin.window() h, w = win.getmaxyx() attr1 = curses.color_pair(1) | curses.A_BOLD attr2 = curses.color_pair(9) | curses.A_BOLD win.addstr(h-2, w-21, '[]', (self.entry_i==11) and attr2 or attr1) win.addstr(h-2, w-13, '[ Cancel ]', (self.entry_i==12) and attr2 or attr1) if self.n > 0: win.addstr(h-2, 3, '[ All ]', (self.entry_i==13) and attr2 or attr1) win.addstr(h-2, 12, '[ Ignore ]', (self.entry_i==14) and attr2 or attr1) def show(self): win = self.pwin.window() win.getmaxyx() win.erase() win.box(0, 0) attr = curses.color_pair(1) | curses.A_BOLD title = 'Change permissions, owner or group' win.addstr(0, int((self.w-len(title)-2)/2), ' %s ' % title, attr) win.addstr(2, 2, 'File: ') win.addstr(2, 8, utils.encode(self.file), attr) if self.n > 0: win.addstr(2, self.w-12-2, '%4d of %-4d' % (self.i, self.n)) win.addstr(4, 7, 'owner group other owner group recursive') win.addstr(5, 2, 'new: [---] [---] [---] [----------] [----------] [ ]') win.addstr(6, 2, 'old: [---] [---] [---] [----------] [----------] [ ]') win.addstr(6, 8, self.perms_old[0:3]) win.addstr(6, 15, self.perms_old[3:6]) win.addstr(6, 22, self.perms_old[6:9]) l = len(self.owner_old) win.addstr(6, 30, (l > 10) and self.owner_old[:10] or self.owner_old+'-'*(10-l)) l = len(self.group_old) win.addstr(6, 44, (l > 10) and self.group_old[:10] or self.group_old+'-'*(10-l)) perms = ''.join(self.perms) win.addstr(5, 8, perms[0:3]) win.addstr(5, 15, perms[3:6]) win.addstr(5, 22, perms[6:9]) l = len(self.owner) owner = (l > 10) and self.owner[:10] or self.owner+'-'*(10-l) win.addstr(5, 30, owner) l = len(self.group) group = (l > 10) and self.group[:10] or self.group+'-'*(10-l) win.addstr(5, 44, group) recursive = self.recursive and 'X' or ' ' win.addstr(5, 61, recursive) if self.entry_i == 0: win.addstr(5, 8, perms[0:3], curses.color_pair(5) | curses.A_BOLD) elif self.entry_i == 1: win.addstr(5, 15, perms[3:6], curses.color_pair(5) | curses.A_BOLD) elif self.entry_i == 2: win.addstr(5, 22, perms[6:9], curses.color_pair(5) | curses.A_BOLD) elif self.entry_i == 3: win.addstr(5, 30, owner, curses.color_pair(5) | curses.A_BOLD) elif self.entry_i == 4: win.addstr(5, 44, group, curses.color_pair(5) | curses.A_BOLD) elif self.entry_i == 5: win.addstr(5, 61, recursive, curses.color_pair(5) | curses.A_BOLD) self.show_btns() win.refresh() def manage_keys(self): y, x = self.pwin.window().getbegyx() order = (self.n > 0) and [0, 1, 2, 3, 4, 5, 13, 14, 11, 12] or [0, 1, 2, 3, 4, 5, 11, 12] while True: self.show() ch = self.pwin.window().getch() if ch in (0x03, 0x1B, ord('c'), ord('C'), ord('q'), ord('Q')): return -1 elif ch in (ord('\t'), 0x09, curses.KEY_DOWN, curses.KEY_RIGHT): try: self.entry_i = order[order.index(self.entry_i)+1] except IndexError: self.entry_i = order[0] elif ch in (curses.KEY_UP, curses.KEY_LEFT): try: self.entry_i = order[order.index(self.entry_i)-1] except IndexError: self.entry_i = order[0] elif ch in (ord('r'), ord('R')): if not 0 <= self.entry_i <= 2: continue d = self.entry_i * 3 self.perms[d] = (self.perms[d]=='-') and 'r' or '-' elif ch in (ord('w'), ord('W')): if not 0 <= self.entry_i <= 2: continue d = 1 + self.entry_i * 3 self.perms[d] = (self.perms[d]=='-') and 'w' or '-' elif ch in (ord('x'), ord('X')): if not 0 <= self.entry_i <= 2: continue d = 2 + self.entry_i * 3 self.perms[d] = (self.perms[d]=='-') and 'x' or '-' elif ch in (ord('t'), ord('T')): if not self.entry_i == 2: continue self.perms[d] = (self.perms[8]=='t') and self.perms_old[8] or 't' elif ch in (ord('s'), ord('S')): if not 0 <= self.entry_i <= 1: continue d = 2 + self.entry_i * 3 self.perms[d] = (self.perms[d]=='s') and self.perms_old[d] or 's' elif ch in (ord(' '), 0x0A, 0x0D): if self.entry_i == 5: self.recursive = not self.recursive elif self.entry_i == 3: owners = files.get_owners() owners.sort() try: owners.index(self.owner) except: owners.append(self.owner) ret = SelectItem(owners, y+6, x+30, self.owner).run() if ret != -1: self.owner = ret app.display() elif self.entry_i == 4: groups = files.get_groups() groups.sort() try: groups.index(self.group) except: groups.append(self.group) ret = SelectItem(groups, y+6, x+30, self.group).run() if ret != -1: self.group = ret app.display() elif self.entry_i == 12: return -1 elif self.n > 0 and self.entry_i == 13: return self.perms, self.owner, self.group, self.recursive, True elif self.n > 0 and self.entry_i == 14: return 0 else: return self.perms, self.owner, self.group, self.recursive, False elif self.n > 0 and ch in (ord('i'), ord('I')): return 0 elif self.n > 0 and ch in (ord('a'), ord('A')): return self.perms, self.owner, self.group, self.recursive, True else: curses.beep() def run(self): selected = self.manage_keys() self.pwin.hide() return selected ###################################################################### ##### some utils def crop_line(line, w): if len(line) > w-1: if w % 2 == 0: # even return line[:int(w/2)] + '~' + line[-int(w/2)+2:] else: # odd return line[:int(w/2)+1] + '~' + line[-int(w/2)+2:] else: return line def cursor_show2(): try: # some terminals don't allow '2' curses.curs_set(2) except: cursor_show() def cursor_show(): try: curses.curs_set(1) except: pass def cursor_hide(): try: curses.curs_set(0) except: pass ###################################################################### lfm-2.3/lfm/files.py0000644000076400001440000005255411564042146014674 0ustar inigousers00000000000000# -*- coding: utf-8 -*- """files.py This module defines files utilities for lfm. """ import sys import os import os.path import stat import time import pwd import grp import shutil import tempfile from utils import get_shell_output, get_shell_output2, encode, decode, \ ask_convert_invalid_encoding_filename ######################################################################## ##### constants # File Type: dir, link to directory, link, nlink, char dev, # block dev, fifo, socket, executable, file (FTYPE_DIR, FTYPE_LNK2DIR, FTYPE_LNK, FTYPE_NLNK, FTYPE_CDEV, FTYPE_BDEV, FTYPE_FIFO, FTYPE_SOCKET, FTYPE_EXE, FTYPE_REG, FTYPE_UNKNOWN) = xrange(11) FILETYPES = { FTYPE_DIR: (os.sep, 'Directory'), FTYPE_LNK2DIR: ('~', 'Link to Directory'), FTYPE_LNK: ('@', 'Link'), FTYPE_NLNK: ('!', 'No Link'), FTYPE_CDEV: ('-', 'Char Device'), FTYPE_BDEV: ('+', 'Block Device'), FTYPE_FIFO: ('|', 'Fifo'), FTYPE_SOCKET: ('#', 'Socket'), FTYPE_EXE: ('*', 'Executable'), FTYPE_REG: (' ', 'File'), FTYPE_UNKNOWN: ('?', 'Unknown') } (FT_TYPE, FT_PERMS, FT_OWNER, FT_GROUP, FT_SIZE, FT_MTIME) = xrange(6) # Sort Type: None, byName, bySize, byDate, byType (SORTTYPE_None, SORTTYPE_byName, SORTTYPE_byName_rev, SORTTYPE_bySize, SORTTYPE_bySize_rev, SORTTYPE_byDate, SORTTYPE_byDate_rev) = xrange(7) SYSTEM_PROGRAMS = [] ######################################################################## ##### general functions # HACK: checks for st_rdev in stat_result, falling back to # "ls -la %s" hack if Python before 2.2 or without st_rdev try: os.stat_result.st_rdev except AttributeError: def get_rdev(f): """'ls -la' to get mayor and minor number of devices""" try: buf = get_shell_output('ls -la %s' % encode(f)) except: return 0 else: try: return int(buf[4][:-1]), int(buf[5]) except: # HACK: found 0xff.. encoded device numbers, ignore them... return 0, 0 else: def get_rdev(f): """mayor and minor number of devices""" r = os.stat(f).st_rdev return r >> 8, r & 255 def __get_size(f): """return the size of the directory or file via 'du -sk' command""" buf = get_shell_output2('du -sk \"%s\"' % encode(f)) return (buf==None) and 0 or int(buf.split()[0])*1024 def get_realpath(path, filename, filetype): """return absolute path or, if path is a link, pointed file""" if filetype in (FTYPE_LNK2DIR, FTYPE_LNK, FTYPE_NLNK): try: return '-> ' + os.readlink(os.path.join(path, filename)) except os.error: return os.path.join(path, filename) else: return os.path.join(path, filename) def get_linkpath(path, filename): """return absolute path to the destination of a link""" link_dest = os.readlink(os.path.join(path, filename)) return os.path.normpath(os.path.join(path, link_dest)) def join(directory, f): if not os.path.isdir(directory): directory = os.path.dirname(directory) return os.path.join(directory, f) def __get_filetype(lmode, f): """get the type of the file. See listed types above""" if stat.S_ISDIR(lmode): return FTYPE_DIR if stat.S_ISLNK(lmode): try: mode = os.stat(f)[stat.ST_MODE] except OSError: return FTYPE_NLNK else: return FTYPE_LNK2DIR if stat.S_ISDIR(mode) else FTYPE_LNK if stat.S_ISCHR(lmode): return FTYPE_CDEV if stat.S_ISBLK(lmode): return FTYPE_BDEV if stat.S_ISFIFO(lmode): return FTYPE_FIFO if stat.S_ISSOCK(lmode): return FTYPE_SOCKET if stat.S_ISREG(lmode) and (lmode & 0111): return FTYPE_EXE else: return FTYPE_REG # if no other type, regular file def get_fileinfo(f, pardir_flag=False, show_dirs_size=False): """return information about a file, with format: (filetype, perms, owner, group, size, mtime)""" f = os.path.abspath(f) try: st = os.lstat(f) except OSError: return (FTYPE_UNKNOWN, 0, 'root', 'root', 0, 0) typ = __get_filetype(st[stat.ST_MODE], f) if typ in (FTYPE_DIR, FTYPE_LNK2DIR) and not pardir_flag and show_dirs_size: size = __get_size(f) elif typ in (FTYPE_CDEV, FTYPE_BDEV): # HACK: it's too time consuming to calculate all files' rdevs # in a directory, so we just calculate what we need # at show time # maj_red, min_rdev = get_rdev(file) size = 0 else: size = st[stat.ST_SIZE] try: owner = pwd.getpwuid(st[stat.ST_UID])[0] except: owner = unicode(st[stat.ST_UID]) try: group = grp.getgrgid(st[stat.ST_GID])[0] except: group = unicode(st[stat.ST_GID]) return (typ, stat.S_IMODE(st[stat.ST_MODE]), owner, group, size, st[stat.ST_MTIME]) def get_fileinfo_extended(f): """return additional information about a file, with format: (num_links, uid, gid, atime, mtime, ctime, inode, dev)""" try: st = os.lstat(f) except OSError: return (0, 0, 0, 0, 0, 0, 0, 0) else: return (st[stat.ST_NLINK], st[stat.ST_UID], st[stat.ST_GID], st[stat.ST_ATIME], st[stat.ST_MTIME], st[stat.ST_CTIME], st[stat.ST_DEV], st[stat.ST_INO]) def perms2str(p): permis = ['x', 'w', 'r'] perms = ['-'] * 9 for i in xrange(9): if p & (0400 >> i): perms[i] = permis[(8-i) % 3] if p & 04000: perms[2] = 's' if p & 02000: perms[5] = 's' if p & 01000: perms[8] = 't' return ''.join(perms) def get_fileinfo_dict(path, filename, filevalues): """return a dict with file information""" res = {} res['filename'] = filename typ = filevalues[FT_TYPE] res['type_chr'] = FILETYPES[typ][0] if typ in (FTYPE_CDEV, FTYPE_BDEV): # HACK: it's too time consuming to calculate all files' rdevs # in a directory, so we just calculate needed ones here # at show time maj_rdev, min_rdev = get_rdev(os.path.join(path, filename)) res['size'] = 0 res['maj_rdev'] = maj_rdev res['min_rdev'] = min_rdev res['dev'] = 1 else: size = filevalues[FT_SIZE] if size >= 1000000000L: size = str(size/(1024*1024)) + 'M' elif size >= 10000000L: size = str(size/1024) + 'K' else: size = str(size) res['size'] = size res['maj_rdev'] = 0 res['min_rdev'] = 0 res['dev'] = 0 res['perms'] = perms2str(filevalues[1]) res['owner'] = filevalues[FT_OWNER] res['group'] = filevalues[FT_GROUP] if -15552000 < (time.time() - filevalues[FT_MTIME]) < 15552000: # filedate < 6 months from now, past or future mtime = time.strftime('%a %b %d %H:%M', time.localtime(filevalues[FT_MTIME])) mtime2 = time.strftime('%d %b %H:%M', time.localtime(filevalues[FT_MTIME])) else: mtime = time.strftime('%a %d %b %Y', time.localtime(filevalues[FT_MTIME])) mtime2 = time.strftime('%d %b %Y', time.localtime(filevalues[FT_MTIME])) res['mtime'] = decode(mtime) res['mtime2'] = decode(mtime2) return res def get_dir(path, show_dotfiles=1): """return a dict whose elements are formed by file name as key and a (filetype, perms, owner, group, size, mtime) tuple as value""" # bug in python: os.path.normpath(u'/') returns str instead of unicode, # so convert to unicode anyway path = decode(os.path.normpath(path)) files_dict = {} if path != os.sep: files_dict[os.pardir] = get_fileinfo(os.path.dirname(path), 1) files_list = os.listdir(path) if not show_dotfiles: files_list = [f for f in files_list if f[0] != '.'] for f in files_list: if not isinstance(f, unicode): newf = decode(f) if ask_convert_invalid_encoding_filename(newf): convert_filename_encoding(path, f, newf) f = newf files_dict[f] = get_fileinfo(os.path.join(path, f)) return len(files_dict), files_dict def get_owners(): """get a list with the users defined in the system""" return [e[0] for e in pwd.getpwall()] def get_user_fullname(user): """return the fullname of an user""" try: return pwd.getpwnam(user)[4] except KeyError: return '' def get_groups(): """get a list with the groups defined in the system""" return [e[0] for e in grp.getgrall()] def set_perms(f, perms, recursive=False): """set permissions to a file""" ps, i = 0, 8 for p in perms: if p == 'x': ps += 1 * 8 ** int(i/3) elif p == 'w': ps += 2 * 8 ** int(i/3) elif p == 'r': ps += 4 * 8 ** int(i/3) elif p == 't' and i == 0: ps += 1 * 8 ** 3 elif p == 's': if i == 6: ps += 4 * 8 ** 3 elif i == 3: ps += 2 * 8 ** 3 i -= 1 try: if recursive: pc = PathContents([f], os.path.dirname(f)) for e, s in pc.iter_walk(): if not os.path.isdir(e): os.chmod(e, ps) else: os.chmod(f, ps) except (IOError, os.error), (errno, strerror): return (strerror, errno) def set_owner_group(f, owner, group, recursive=False): """set owner and group to a file""" try: owner_n = pwd.getpwnam(owner)[2] except: owner_n = int(owner) try: group_n = grp.getgrnam(group)[2] except: group_n = int(group) try: if recursive: pc = PathContents([f], os.path.dirname(f)) for e, s in pc.iter_walk(): os.chown(e, owner_n, group_n) else: os.chown(f, owner_n, group_n) except (IOError, os.error), (errno, strerror): return (strerror, errno) def get_mount_points(): """return system mount points as list of (mountpoint, device, fstype). Compatible with linux and solaris""" try: buf = get_shell_output('mount') except (IOError, os.error), (errno, strerror): return (strerror, errno) if buf is None or buf == '': return ('Can\t run "mount" command', 0) lst = [] for e in buf.split('\n'): es = e.split() lst.append((es[2], es[0], es[4])) return sorted(lst, reverse=True) def get_mountpoint_for_file(f): # check mps mps = get_mount_points() for m, d, t in mps: if f.find(m) != -1: return (m, d, t) else: return ('/', '', '') def convert_filename_encoding(path, filename, newname): curpath = os.getcwd() os.chdir(path) os.rename(filename, newname) os.chdir(curpath) def get_binary_programs(): d = {} for p in os.getenv('PATH').split(':'): try: for prog in os.listdir(p): d.setdefault(prog) except OSError: pass return sorted(d.keys()) SYSTEM_PROGRAMS = get_binary_programs() ######################################################################## ##### temporary file def mktemp(): return tempfile.mkstemp()[1] def mkdtemp(): return tempfile.mkdtemp() ######################################################################## ##### sort def __do_sort(f_dict, sortmode, sort_mix_cases): if sortmode == SORTTYPE_None: names = f_dict.keys() elif sortmode in (SORTTYPE_byName, SORTTYPE_byName_rev): names = sorted(f_dict.keys(), key=lambda f: f.lower() if sort_mix_cases else f, reverse=sortmode==SORTTYPE_byName_rev) elif sortmode in (SORTTYPE_bySize, SORTTYPE_bySize_rev): names = sorted(f_dict.keys(), key=lambda f: f_dict[f][FT_SIZE], reverse=sortmode==SORTTYPE_bySize_rev) elif sortmode in (SORTTYPE_byDate, SORTTYPE_byDate_rev): names = sorted(f_dict.keys(), key=lambda f: f_dict[f][FT_MTIME], reverse=sortmode==SORTTYPE_byDate_rev) if names.count(os.pardir) != 0: # move pardir to top names.remove(os.pardir) names.insert(0, os.pardir) return names def sort_dir(files_dict, sortmode, sort_mix_dirs, sort_mix_cases): """return an array of files which are sorted by mode""" d, f = {}, {} if sort_mix_dirs: f = files_dict d1 = [] else: for k, v in files_dict.items(): if v[FT_TYPE] in (FTYPE_DIR, FTYPE_LNK2DIR): d[k] = v else: f[k] = v d1 = __do_sort(d, sortmode, sort_mix_cases) d2 = __do_sort(f, sortmode, sort_mix_cases) d1.extend(d2) return d1 ######################################################################## ##### complete def complete(entrypath, panelpath): if not entrypath: path = panelpath + os.sep base = '' elif entrypath[0] == os.sep: path = entrypath base = os.path.dirname(entrypath) else: path = os.path.join(panelpath, entrypath) base = os.path.dirname(entrypath) # get elements try: if path.endswith(os.sep) and os.path.isdir(path): basedir = path fs = os.listdir(path) else: basedir = os.path.dirname(path) start = os.path.basename(path) try: entries = os.listdir(basedir) except OSError: entries = [] fs = [f for f in entries if f.startswith(start)] except (IOError, os.error), (errno, strerror): fs = [] # sort files with dirs first d1, d2 = [], [] for f in fs: if os.path.isdir(os.path.join(basedir, f)): d1.append(f + os.sep) else: d2.append(f) d1.sort() d2.sort() d1.extend(d2) return base, d1 def complete_programs(text): return [prog for prog in SYSTEM_PROGRAMS if prog.startswith(text)] ######################################################################## ##### actions def do_create_link(pointto, link): os.symlink(pointto, link) def modify_link(pointto, linkname): try: os.unlink(linkname) do_create_link(pointto, linkname) except (IOError, os.error), (errno, strerror): return (strerror, errno) def create_link(pointto, linkname): try: do_create_link(pointto, linkname) except (IOError, os.error), (errno, strerror): return (strerror, errno) def copy_bulk(src, dest): if os.path.isdir(src): shutil.copytree(src, dest, symlinks=True) elif os.path.isfile(src): shutil.copy2(src, dest) def delete_bulk(path, ignore_errors=False): if os.path.isdir(path): shutil.rmtree(path, ignore_errors=ignore_errors) elif os.path.isfile(path): if ignore_errors: try: os.unlink(path) except: pass else: os.unlink(path) def do_copy(filename, basepath, dest, rename_dir=False, check_fileexists=True): src = os.path.join(basepath, filename) if os.path.exists(dest) and os.path.isdir(dest): if rename_dir: filename = os.sep.join(filename.split(os.sep)[1:]) dest = os.path.join(dest, filename) if os.path.exists(dest) and check_fileexists: return os.path.basename(dest) if os.path.islink(src): try: do_create_link(os.readlink(src), dest) except (IOError, os.error), (errno, strerror): return (strerror, errno) elif os.path.isdir(src): try: os.mkdir(dest) except (IOError, os.error), (errno, strerror): if errno != 17: # don't error if directory exists return (strerror, errno) else: # copy mode, times, owner and group try: st = os.lstat(src) os.chown(dest, st[stat.ST_UID], st[stat.ST_GID]) shutil.copymode(src, dest) shutil.copystat(src, dest) except (IOError, os.error), (errno, strerror): pass elif src == dest: return ('Source and destination are the same file', 0) else: if os.path.isfile(src): # stat.S_ISREG(os.lstat(src)[stat.ST_MODE]) try: shutil.copy2(src, dest) except (IOError, os.error), (errno, strerror): return (strerror, errno) else: return ('Special files can\'t be copied or moved', 0) def do_rename(f, path, newname, check_fileexists=True): src = os.path.join(path, f) if newname[0] != os.sep: dest = os.path.join(path, newname) else: dest = newname if src == dest: return ('Source and destination are the same file', 0) if os.path.dirname(dest) != path: return ('Can\'t rename to different directory', 0) if os.path.isfile(dest) and check_fileexists: return os.path.basename(newname) try: os.rename(src, dest) except (IOError, os.error), (errno, strerror): return (strerror, errno) def do_backup(f, path, backup_ext): src = os.path.join(path, f) dest = os.path.join(path, f+backup_ext) if os.path.exists(dest): return ('File exists', 0) try: copy_bulk(src, dest) except (IOError, os.error), (errno, strerror): return (strerror, errno) def do_delete(f): try: if os.path.islink(f): os.unlink(f) elif os.path.isdir(f): os.rmdir(f) else: os.unlink(f) except (IOError, os.error), (errno, strerror): return (strerror, errno) def mkdir(path, newdir): fullpath = os.path.join(path, newdir) try: os.makedirs(fullpath) except (IOError, os.error), (errno, strerror): return (strerror, errno) ######################################################################## ##### PathContents class PathContents(object): def __init__(self, fs, basepath=None): """fs must be a list with relative path to files""" if not isinstance(fs, list) or not len(fs) > 0: raise TypeError, "fs must be a list of files" if basepath is None: basepath = os.getcwd() self.basepath = os.path.abspath(basepath) if not os.path.isdir(self.basepath): raise TypeError, "basepath must be a valid directory or None" self.__entries = [] self.__errors = [] for f in fs: f = os.path.join(self.basepath, f) try: if os.path.islink(f): self.__entries.append((f, 0)) else: self.__entries.append((f, os.path.getsize(f))) except (IOError, os.error), (errno, strerror): self.__errors.append((f, (strerror, errno))) else: if os.path.isdir(f) and not os.path.islink(f): try: self.__fill_contents(f) except UnicodeDecodeError: raise UnicodeError self.length = len(fs) self.tlength = len(self.__entries) self.tsize = sum([f[1] for f in self.__entries]) or 1 def __fill_contents(self, path): for root, dirs, files in os.walk(path, topdown=False, onerror=self.__on_error): for d in dirs: fullpath = os.path.join(root, d) try: if os.path.islink(fullpath): self.__entries.append((fullpath, 0)) else: self.__entries.append((fullpath, os.path.getsize(fullpath))) except (IOError, os.error), (errno, strerror): self.__errors.append((fullpath, (strerror, errno))) for f in files: fullpath = os.path.join(root, f) try: if os.path.islink(fullpath): self.__entries.append((fullpath, 0)) else: self.__entries.append((fullpath, os.path.getsize(fullpath))) except (IOError, os.error), (errno, strerror): self.__errors.append((fullpath, (strerror, errno))) def __on_error(self, exc): # print '[Error %d] %s: %s' % (exc.errno, exc.strerror, exc.filename) fullpath = os.path.join(self.basepath, exc.filename) self.__errors.append((fullpath, (exc.strerror, exc.errno))) def __repr__(self): return u'PathContents[Base:"%s" with %d entries (Total: %d items, %.2f KB)]' % \ (self.basepath, self.length, self.tlength, self.tsize/1024) @property def entries(self, reverse=False): return sorted(self.__entries, reverse=reverse) @property def errors(self): return sorted(self.__errors) def iter_walk(self, reverse=False): for e in sorted(self.__entries, reverse=reverse): yield e def remove_files(self, fs): new = [] length = self.length for f, s in self.__entries: if f in fs: length -= 1 else: new.append((f, s)) self.__entries = new self.length = (length > 0) and length or 0 self.tlength = len(self.__entries) self.tsize = sum([f[1] for f in self.__entries]) or 1 ######################################################################## lfm-2.3/lfm/__init__.py0000644000076400001440000000351411564042146015321 0ustar inigousers00000000000000# -*- coding: utf-8 -*- """ Copyright (C) 2001-11, IƱigo Serna . All rights reserved. This software has been realised under the GPL License, see the COPYING file that comes with this package. There is NO WARRANTY. 'Last File Manager' is (tries to be) a simple 'midnight commander'-type application for UNIX console. """ ###################################################################### import locale from sys import exit try: locale.setlocale(locale.LC_ALL, '') except locale.Error: print 'lfm can\'t use the encoding defined in your system: "%s"' % \ '.'.join(locale.getdefaultlocale()) print 'Please configure before running lfm. Eg. $ export LANG=en_GB.UTF-8' exit(-1) g_encoding = locale.getpreferredencoding() if g_encoding is None or g_encoding == '': print 'lfm can\'t find a valid encoding for your terminal.' print 'Please configure before running lfm. Eg. $ export LANG=en_GB.UTF-8' exit(-1) ###################################################################### AUTHOR = u'IƱigo Serna' VERSION = '2.3' DATE = '2001-11' LFM_NAME = 'lfm - Last File Manager' ###################################################################### ##### lfm sysprogs = { 'tar': 'tar', 'bzip2': 'bzip2', 'gzip': 'gzip', 'zip': 'zip', 'unzip': 'unzip', 'rar': 'rar', '7z': '7z', 'xz': 'xz', 'grep': 'grep', 'find': 'find', 'which': 'which', 'xargs': 'xargs' } RET_QUIT, RET_EXIT = -1, -2 RET_TOGGLE_PANE, RET_TAB_NEW, RET_TAB_CLOSE, RET_NO_UPDATE, \ RET_HALF_UPDATE, RET_HALF_UPDATE_OTHER = xrange(1, 7) PANE_MODE_HIDDEN, PANE_MODE_LEFT, PANE_MODE_RIGHT, PANE_MODE_FULL = xrange(4) ###################################################################### lfm-2.3/lfm/utils.py0000644000076400001440000011011611564042146014717 0ustar inigousers00000000000000# -*- coding: utf-8 -*- """utils.py This module contains useful functions. """ import os import os.path import sys import time import signal import select import cPickle import curses import compress import messages from __init__ import sysprogs, g_encoding ###################################################################### ##### module variables app = None # first common codecs, to avoid slow down decoding codecs_list = [g_encoding, 'utf-8', 'latin-1', 'ascii'] # if program is too slow due to too many encodings to try, # comment out next lines or put the encodings you use first import encodings.aliases # Set is deprecated on python v2.6+, but set doesn't exist < v2.6 cds = {} for c in encodings.aliases.aliases.values(): cds[c] = 1 codecs_list += sorted(cds.keys()) ###################################################################### ##### InterProcess Communication class IPC(object): def __init__(self): pipe_r, pipe_w = os.pipe() self.rfd = os.fdopen(pipe_r, 'rb', 0) self.wfd = os.fdopen(pipe_w, 'wb', 0) def send(self, buf): cPickle.dump(buf, self.wfd) # time.sleep(0.01) cPickle.dump(None, self.wfd) def receive(self): ready = select.select([self.rfd], [], [], 0.001) # 0.01 if self.rfd in ready[0]: try: buf = cPickle.load(self.rfd) except: return -1, 'Error unmarshaling' if buf is None: return 0, None try: arg1, arg2 = buf except: return -1, 'Malformed response' return 1, buf return 0, None def close(self): self.rfd.close() self.wfd.close() ###################################################################### ##### Process Loop Base Class class ProcessLoopBase(object): """Run a function in background, so it can be stopped, continued, etc. There is also a graphical animation to show the program still runs and has not crashed.""" anim_char = ('|', '/', '-', '\\') def __init__(self, action='', func=None, *args): self.action = action # action label self.func = func # function to run in child self.args = args # additional args for func self.ret = [] # information to return self.filename = '' # current filename self.file_i = 0 # index to current item self.cursor_i = 0 # index to cursor animation step self.init_gui() def init_gui(self): self.cur_win = curses.newpad(1, 2) self.cur_win.bkgd(curses.color_pair(1)) if self.processloop_type == 1: dlg = messages.ProgressBarWindow elif self.processloop_type == 2: dlg = messages.ProgressBarWindow2 self.dlg = dlg(self.action, 'Press Ctrl-C to stop', curses.color_pair(1), curses.color_pair(1), curses.color_pair(20), curses.color_pair(4), waitkey=False) self.dlg.show() def end_gui(self): self.dlg.finish() self.show_parent() def show_parent(self): self.dlg.ishidden = True app.display() def show_win(self): raise NotImplementedError # in ProcessLoopBase_X class def animate_cursor(self): self.cur_win.erase() self.cur_win.addch(ProcessLoopBase.anim_char[self.cursor_i%4], curses.color_pair(1) | curses.A_BOLD) self.cur_win.refresh(0, 0, 0, app.maxw-2, 1, app.maxw-1) self.cursor_i += 1 if self.cursor_i > 3: self.cursor_i = 0 def check_keys(self): ch = self.dlg.getch() if ch == 0x03: os.kill(self.pid_child, signal.SIGSTOP) self.show_parent() ans = messages.confirm('Stop process', 'Stop \"%s\"' % self.action.lower(), 1) if ans: os.kill(self.pid_child, signal.SIGKILL) os.wait() return -100 else: os.kill(self.pid_child, signal.SIGCONT) return 1 return 0 def wait_for_answer(self): while True: # feedback from user status = self.check_keys() if status == -100: # stopped and ended by user return ('stopped_by_user', None) elif status == 1: # stopped and continued by user self.show_win() self.animate_cursor() # check response code, buf = self.c2p.receive() if code == 1: return buf elif code == -1: return ('internal_error', buf) def ask_confirmation(self): raise NotImplementedError # in final class def prepare_args(self): raise NotImplementedError # in final class def process_response(self, result): raise NotImplementedError # in final class def exec_file(self, args): # update progress dialog self.show_win() # send data to child self.p2c.send(('exec', args)) # wait for answer and process it ans, result = self.wait_for_answer() if ans == 'stopped_by_user': return -1 elif ans == 'internal_error': self.show_parent() messages.error('Cannot %s\n' % self.action.lower() + 'Parent: Internal Error: ' + result) return 0 elif ans == 'error': self.show_parent() messages.error('Cannot %s\n' % self.action.lower() + 'Parent: Error: ' + result) return 0 elif ans == 'result': return self.process_response(result) else: self.show_parent() messages.error('Cannot %s\n' % self.action.lower() + 'Parent: Bad response from child') return 0 def return_data(self): return self.ret def run_pre(self): self.p2c = IPC() self.c2p = IPC() self.pid_child = os.fork() if self.pid_child < 0: # error messages.error('Cannot %s\n' % self.action.lower() + 'Can\'t run function') return -1 elif self.pid_child == 0: # child self.child_process() os._exit(0) def run(self): raise NotImplementedError # in ProcessLoopBase_X class def run_post(self): self.p2c.send(('quit', None)) self.p2c.close() self.c2p.close() try: os.wait() except OSError: pass self.end_gui() def child_process(self): while True: # wait for command to execute while True: code, buf = self.p2c.receive() if code == 1: break elif code == -1: self.c2p.send(('error', 'Child: ' + buf)) continue else: continue cmd, args = buf # check command if cmd == 'quit': break elif cmd == 'exec': res = self.func(*args) self.c2p.send(('result', res)) continue else: result = ('error', 'Child: Bad command from parent') self.c2p.send(('result', result)) continue # end # time.sleep(.25) # time to let parent get return value os._exit(0) ###################################################################### ##### Process Loop Base Class, 1 progressbar class ProcessLoopBase_1(ProcessLoopBase): def __init__(self, action='', func=None, lst=[], *args): self.processloop_type = 1 super(ProcessLoopBase_1, self).__init__(action, func, *args) self.lst = lst self.length = len(lst) def show_win(self): filename = self.filename percent = 100 * self.file_i / self.length idx_str = '%d/%d' % (self.file_i, self.length) if self.dlg.ishidden: self.dlg.show(filename, percent, idx_str) else: self.dlg.update(filename, percent, idx_str) def run(self): if ProcessLoopBase.run_pre(self) == -1: return for self.filename in self.lst: ret = self.ask_confirmation() if ret == -1: break elif ret == 0: continue self.file_i += 1 args = self.prepare_args() ret = self.exec_file(args) if ret == -1: self.ret = -1 # stopped by user break ProcessLoopBase.run_post(self) return self.return_data() ##### Process Loop DirSize class ProcessLoopDirSize(ProcessLoopBase_1): def ask_confirmation(self): return 1 def prepare_args(self): return (self.filename, ) + self.args def process_response(self, result): self.ret.append(result) return 0 ##### Process Loop Un/Compress class ProcessLoopUnCompress(ProcessLoopBase_1): def ask_confirmation(self): return 1 def prepare_args(self): return (self.filename, ) + self.args def process_response(self, result): if isinstance(result, tuple): # error st, msg = result if st == -1: self.show_parent() messages.error('Cannot %s\n' % self.action.lower() + msg) return 0 ##### Process Loop Rename class ProcessLoopRename(ProcessLoopBase_1): def ask_confirmation(self): from actions import doEntry buf = 'Rename \'%s\' to' % self.filename tabpath = app.act_pane.act_tab.path self.show_parent() newname = doEntry(tabpath, 'Rename', buf, self.filename, with_history='file') if newname: self.newname = newname return 1 else: return 0 def prepare_args(self): return (self.filename, ) + self.args + (self.newname, ) def process_response(self, result): if isinstance(result, unicode) or isinstance(result, str): # overwrite? self.show_parent() ans = messages.confirm(self.action, 'Overwrite \'%s\'' % result, 1) if ans == -1: return -1 elif ans == 0: return 0 elif ans == 1: args = (self.filename, ) + self.args + (self.newname, False) self.dlg.show() return self.exec_file(args) elif isinstance(result, tuple): # error from child self.show_parent() messages.error('Cannot %s\n' % self.action.lower() + self.filename + ': %s (%s)' % result) return 0 else: return 0 ##### Process Loop Rename class ProcessLoopBackup(ProcessLoopBase_1): def ask_confirmation(self): return 1 def prepare_args(self): return (self.filename, ) + self.args def process_response(self, result): if isinstance(result, unicode) or isinstance(result, str): # overwrite? self.show_parent() ans = messages.confirm(self.action, 'Overwrite \'%s\'' % result, 1) if ans == -1: return -1 elif ans == 0: return 0 elif ans == 1: args = (self.filename, ) + self.args + (False, ) self.dlg.show() return self.exec_file(args) elif isinstance(result, tuple): # error from child self.show_parent() messages.error('Cannot %s\n' % self.action.lower() + self.filename + ': %s (%s)' % result) return 0 else: return 0 ###################################################################### ##### Process Loop Base Class, 2 progressbar class ProcessLoopBase_2(ProcessLoopBase): def __init__(self, action='', func=None, pc=None, *args): self.processloop_type = 2 super(ProcessLoopBase_2, self).__init__(action, func, *args) self.pc = pc # PathContents self.filesize_aggr = 0 # partial sum of processed files def show_win(self): filename = self.filename.replace(self.pc.basepath+os.sep, '') perc_size = 100 * self.filesize_aggr / self.pc.tsize perc_count = 100 * self.file_i / self.pc.tlength idx_str = '%d/%d' % (self.file_i, self.pc.tlength) if self.dlg.ishidden: self.dlg.show(filename, perc_size, perc_count, idx_str) else: self.dlg.update(filename, perc_size, perc_count, idx_str) def run(self): for filename, err in self.pc.errors: self.show_parent() messages.error('Cannot %s\n' % self.action.lower() + filename + ': %s (%s)' % err) if ProcessLoopBase.run_pre(self) == -1: return for self.filename, filesize in self.pc.iter_walk(reverse=self.rev): ret = self.ask_confirmation() if ret == -1: break elif ret == 0: continue self.file_i += 1 self.filesize_aggr += filesize args = self.prepare_args() ret = self.exec_file(args) if ret == -1: self.ret = -1 # stopped by user break ProcessLoopBase.run_post(self) return self.return_data() ##### Process Loop Copy class ProcessLoopCopy(ProcessLoopBase_2): def __init__(self, action='', func=None, pc=None, *args): super(ProcessLoopCopy, self).__init__(action, func, pc, *args) self.rev = False self.overwrite_all = not app.prefs.confirmations['overwrite'] self.overwrite_none = False def ask_confirmation(self): return 1 def prepare_args(self): if self.pc.basepath == os.sep: filename = self.filename.replace(self.pc.basepath, '') else: filename = self.filename.replace(self.pc.basepath+os.sep, '') if self.overwrite_all: return (filename, self.pc.basepath) + self.args + (False, ) else: return (filename, self.pc.basepath) + self.args def process_response(self, result): if isinstance(result, unicode) or isinstance(result, str): # overwrite? if self.overwrite_none: self.ret.append(self.filename) return 0 self.show_parent() ans = messages.confirm_all_none(self.action, 'Overwrite \'%s\'' % result, 1) if ans == -1: self.ret.append(self.filename) return -1 elif ans == -2: self.ret.append(self.filename) self.overwrite_none = True return 0 elif ans == 0: self.ret.append(self.filename) return 0 elif ans == 1: pass elif ans == 2: self.overwrite_all = True if self.pc.basepath == os.sep: filename = self.filename.replace(self.pc.basepath, '') else: filename = self.filename.replace(self.pc.basepath+os.sep, '') args = (filename, self.pc.basepath) + self.args + (False, ) return self.exec_file(args) elif isinstance(result, tuple): # error from child self.ret.append(self.filename) self.show_parent() messages.error('Cannot %s\n' % self.action.lower() + self.filename + ': %s (%s)' % result) return 0 else: return 0 ##### Process Loop Delete class ProcessLoopDelete(ProcessLoopBase_2): def __init__(self, action='', func=None, pc=None, *args): super(ProcessLoopDelete, self).__init__(action, func, pc, *args) self.rev = True self.delete_all = not app.prefs.confirmations['delete'] def ask_confirmation(self): if self.delete_all: return 2 if app.prefs.confirmations['delete']: self.show_parent() ans = messages.confirm_all('Delete', 'Delete \'%s\'' % self.filename, 1) if ans == 2: self.delete_all = True return ans def prepare_args(self): return (self.filename, ) + self.args def process_response(self, result): if isinstance(result, tuple): # error from child self.dlg.ishidden = True self.show_parent() messages.error('Cannot %s\n' % self.action.lower() + self.filename + ': %s (%s)' % result) return 0 ###################################################################### ##### Process Func class ProcessFunc(object): """Run a function in background, so it can be stopped, continued, etc. There is also a graphical animation to show the program still runs and has not crashed. Parameters: title: title of info window subtitle: subtitle of info window func: function to run *args: arguments to pass to the function Returns: (status, message)""" anim_char = ('|', '/', '-', '\\') def __init__(self, title='', subtitle='', func=None, *args): self.func = func self.args = args self.title = title[:app.maxw-14] self.subtitle = subtitle[:app.maxw-14] self.init_gui() self.cursor_i = 0 self.status = 0 self.output = [] self.ret = None def init_gui(self): self.cur_win = curses.newpad(1, 2) self.cur_win.bkgd(curses.color_pair(1)) app.statusbar.win.nodelay(1) self.show_parent() self.show_win() def end_gui(self): app.statusbar.win.nodelay(0) self.show_parent() def show_parent(self): app.display() def show_win(self): messages.win_nokey(self.title, self.subtitle, 'Press Ctrl-C to stop') def animate_cursor(self): self.cur_win.erase() self.cur_win.addch(ProcessFunc.anim_char[self.cursor_i%4], curses.color_pair(1) | curses.A_BOLD) self.cur_win.refresh(0, 0, 0, app.maxw-2, 1, app.maxw-1) self.cursor_i += 1 if self.cursor_i > 3: self.cursor_i = 0 def check_finish(self): (pid, status) = os.waitpid(self.pid_child, os.WNOHANG) if pid > 0: self.status = status >> 8 return True else: return False def process_result(self): code, buf = self.c2p.receive() if code == 1: self.ret = buf elif code == -1: self.show_parent() messages.error('Cannot %s\n' % self.action.lower() + 'Parent: ' + buf) self.show_parent() self.show_win() else: pass def check_keys(self): ch = app.statusbar.win.getch() if ch == 0x03: os.kill(self.pid_child, signal.SIGSTOP) self.show_parent() ans = messages.confirm('Stop process', '%s %s' % (self.title, self.subtitle), 1) if ans: os.kill(self.pid_child, signal.SIGKILL) os.wait() return -100 else: self.show_win() os.kill(self.pid_child, signal.SIGCONT) return 0 def run(self): self.c2p = IPC() self.pid_child = os.fork() if self.pid_child < 0: # error messages.error('Cannot run function') return elif self.pid_child == 0: # child self.child_process(self.func, *self.args) os._exit(0) # parent status = 0 while True: if self.check_finish(): break self.process_result() status = self.check_keys() if status == -100: # stopped by user self.status = status break self.animate_cursor() # finish and return self.c2p.close() try: os.wait() except OSError: pass self.end_gui() if self.status == -100: # stopped by user return -100, 'Stopped by user' try: st, buf = self.ret except: st, buf = 0, None return st, buf def child_process(self, func, *args): res = func(*args) self.c2p.send(res) os._exit(0) ###################################################################### ##### run_shell # run command via shell and optionally return output, popen version def run_shell_popen(cmd, path, return_output=False): if not cmd: return 0, '' cmd = 'cd "%s" && %s' % (path, cmd) p = popen2.Popen3(cmd, capturestderr=True) p.tochild.close() outfd, errfd = p.fromchild, p.childerr output, error = [], [] while True: # check if finished (pid, status) = os.waitpid(p.pid, os.WNOHANG) if pid > 0: status = status >> 8 o = p.fromchild.readline() while o: # get output before quit o = o.strip() if o: output.append(o) o = p.fromchild.readline() e = p.childerr.readline() while e: # get error before quit e = e.strip() if e: error.append(e) e = p.childerr.readline() break # check for output ready = select.select([outfd, errfd], [], [], .01) if outfd in ready[0]: o = p.fromchild.readline() if o: output.append(o) if errfd in ready[0]: e = p.childerr.readline() while e: # get the whole error message e = e.strip() if e: error.append(e) e = p.childerr.readline() status = p.wait() >> 8 break time.sleep(0.1) # extra time to update output in case execution is too fast # return p.fromchild.close() p.childerr.close() if status != 0: error.insert(0, 'Exit code: %d' % status) return -1, '\n'.join(error) if error != []: return -1, '\n'.join(error) if return_output: return 0, '\n'.join(output) else: return 0, '' # run in background, system version def run_in_background_system(cmd, path): pid = os.fork() if pid == 0: try: maxfd = os.sysconf("SC_OPEN_MAX") except (AttributeError, ValueError): maxfd = 256 # default maximum # os.closerange(0, maxfd) # python v2.6+ for fd in xrange(0, maxfd): try: os.close(fd) except OSError: # ERROR (ignore) pass # Redirect the standard file descriptors to /dev/null. os.open("/dev/null", os.O_RDONLY) # standard input (0) os.open("/dev/null", os.O_RDWR) # standard output (1) os.open("/dev/null", os.O_RDWR) # standard error (2) os.system('cd "%s" && %s' % (path, cmd)) os._exit(0) else: pass # don't wait # get output from a command run in shell, popen version def get_shell_output_popen(cmd): i, a = os.popen4(cmd) buf = a.read() i.close(), a.close() return buf.strip() # get output from a command run in shell, no stderr, popen version def get_shell_output2_popen(cmd): i, o, e = os.popen3(cmd) buf = o.read() i.close(), o.close(), e.close() if buf: return buf.strip() else: return '' # get error from a command run in shell, popen version def get_shell_output3_popen(cmd): i, o, e = os.popen3(cmd) buf = e.read() i.close(), o.close(), e.close() if buf: return buf.strip() else: return '' # run command via shell and optionally return output, subprocess version def run_shell_subprocess(cmd, path, return_output=False): if not cmd: return 0, '' p = Popen(cmd, cwd=path, shell=True, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=True) while p.wait() is None: time.sleep(0.2) output, error = p.stdout.read(), p.stderr.read() p.stdout.close(), p.stderr.close() if p.returncode < 0: error = 'Exit code: %d\n' % p.returncode + error return -1, error if error != '': return -1, error if return_output: return 0, output else: return 0, '' # run in background, subprocess version def run_in_background_subprocess(cmd, path): pid = os.fork() if pid == 0: p = Popen(cmd, cwd=path, shell=True, close_fds=True, stdin=None, stdout=open('/dev/null', 'w'), stderr=STDOUT) os._exit(0) else: pass # don't wait # get output from a command run in shell, subprocess version def get_shell_output_subprocess(cmd): p = Popen(cmd, shell=True, stdin=None, stdout=PIPE, stderr=STDOUT, close_fds=True) while p.wait() is None: time.sleep(0.1) buf = p.stdout.read() p.stdout.close() return buf.strip() if buf else None # get output from a command run in shell without stderr, subprocess version def get_shell_output2_subprocess(cmd): p = Popen(cmd, shell=True, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=True) p.stderr.close() while p.wait() is None: time.sleep(0.1) buf = p.stdout.read() p.stdout.close() return buf.strip() if buf else None # get error from a command run in shell, subprocess version def get_shell_output3_subprocess(cmd): p = Popen(cmd, shell=True, stdin=None, stdout=None, stderr=PIPE, close_fds=True) while p.wait() is None: time.sleep(0.1) buf = p.stderr.read() p.stderr.close() return buf.strip() if buf else None ###################################################################### ##### run_dettached def run_dettached(prog, *args): pid = os.fork() if pid == 0: os.setsid() os.chdir('/') try: maxfd = os.sysconf("SC_OPEN_MAX") except (AttributeError, ValueError): maxfd = 256 # default maximum # os.closerange(0, maxfd) # python v2.6+ for fd in xrange(0, maxfd): try: os.close(fd) except OSError: # ERROR (ignore) pass # Redirect the standard file descriptors to /dev/null. os.open("/dev/null", os.O_RDONLY) # standard input (0) os.open("/dev/null", os.O_RDWR) # standard output (1) os.open("/dev/null", os.O_RDWR) # standard error (2) pid2 = os.fork() if pid2 == 0: os.execlp(prog, prog, *args) else: os.waitpid(-1, os.P_NOWAIT) os._exit(0) else: os.wait() ###################################################################### ##### un/compress(ed) files # compress/uncompress file: gzip/gunzip, bzip2/bunzip2 def do_compress_uncompress_file(filename, path, typ): if os.path.isabs(filename): fullfile = filename filename = os.path.basename(filename) else: fullfile = os.path.join(path, filename) if not os.path.isfile(fullfile): return -1, '%s: is not a file' % filename c = compress.check_compressed_file(fullfile) if c is None or isinstance(c, compress.PackagerTAR): packager = compress.packagers_by_type[typ] c = packager(fullfile) cmd = c.build_compress_cmd() elif c.type == typ: cmd = c.build_uncompress_cmd() else: return -1, '%s: can\'t un/compress with %s' % \ (filename, compress.packagers_by_type[typ].compress_prog) st, msg = run_shell(encode(cmd), encode(path), return_output=True) return st, msg def compress_uncompress_file(tab, typ): if tab.selections: fs = tab.selections[:] else: fs = [tab.sorted[tab.file_i]] ProcessLoopUnCompress('Un/Compress file', do_compress_uncompress_file, fs, tab.path, typ).run() tab.selections = [] app.regenerate() # uncompress directory def do_uncompress_dir(filename, path, dest, is_tmp=False): if os.path.isabs(filename): fullfile = filename filename = os.path.basename(filename) else: fullfile = os.path.join(path, filename) if not os.path.isfile(fullfile): return -1, '%s: is not a file' % filename c = compress.check_compressed_file(fullfile) if c is None: return -1, '%s: can\'t uncompress' % filename cmd = c.build_uncompress_cmd() st, msg = run_shell(encode(cmd), encode(dest), return_output=True) if st < 0: # (-100, -1), # never reached if user stops (-100) because this process is killed c.delete_uncompress_temp(dest, is_tmp) return st, msg def uncompress_dir(tab, dest=None, is_tmp=False): """uncompress tarred file in path directory""" if dest is None: dest = tab.path if tab.selections: fs = tab.selections[:] else: fs = [tab.sorted[tab.file_i]] ProcessLoopUnCompress('Uncompress file', do_uncompress_dir, fs, tab.path, dest, is_tmp).run() tab.selections = [] # compress directory: tar and gzip, bzip2 def do_compress_dir(filename, path, typ, dest, is_tmp=False): if os.path.isabs(filename): fullfile = filename filename = os.path.basename(filename) else: fullfile = os.path.join(path, filename) if not os.path.isdir(fullfile): return -1, '%s: is not a directory' % filename c = compress.packagers_by_type[typ](fullfile) if c is None: return -1, '%s: can\'t compress' % filename cmd = c.build_compress_cmd() st, msg = run_shell(encode(cmd), encode(dest), return_output=True) if st < 0: # (-100, -1): # never reached if user stops (-100) because this process is killed c.delete_compress_temp(dest, is_tmp) return st, msg def compress_dir(tab, typ, dest=None, is_tmp=False): """compress directory to current path""" if dest is None: dest = tab.path if tab.selections: fs = tab.selections[:] else: fs = [tab.sorted[tab.file_i]] ProcessLoopUnCompress('Compress file', do_compress_dir, fs, tab.path, typ, dest, is_tmp).run() tab.selections = [] ###################################################################### ##### find / grep # find/grep def do_findgrep(path, files, pattern): # escape special chars pat_re = pattern.replace('\\', '\\\\\\\\').replace('-', '\\-') pat_re = pat_re.replace('(', '\\(').replace(')', '\\)') pat_re = pat_re.replace('[', '\\[').replace(']', '\\]') ign = app.prefs.options['grep_ignorecase'] and 'i' or '' rex = app.prefs.options['grep_regex'] and 'E' or '' # 1. find . -type f -iname "*.py" -exec grep -EHni PATTERN {} \; # the slowest, 10x # 2. find . -type f -iname "*py" -print0 | xargs --null grep -EHni PATTERN # maybe the best choice cmd = '%s "%s" -type f -iname "%s" -print0 | %s --null %s -%sHn%s \"%s\"' % \ (sysprogs['find'], path, files, sysprogs['xargs'], sysprogs['grep'], rex, ign, pat_re) # 3. grep -EHni PATTERN `find . -type f -iname "*.py"` # don't like ` # 4. grep -REHni PATTERN --include "*.py" . # the fastest, but non-POSIX, because of: -R, --include # cmd = '%s -R%sHn%s \"%s\" --include "%s" "%s"' % \ # (sysprogs['grep'], rex, ign, pat_re, files, path) st, ret = ProcessFunc('Searching', 'Searching for \"%s\" in \"%s\" files' % (pattern, files), run_shell, encode(cmd), path, True).run() if not ret: return 0, [] if st < 0: # (-100, -1) => error return st, ret elif st == 0: ret = [f.strip() for f in ret.split('\n') if f.strip() != ''] matches = [] if len(ret) > 0: # filename:linenumber:matching # note that filename could contain ':', so we have to parse for l in ret: if not l: continue lst = l.split(':') if len(lst) == 1: # binary file linenumber = 0 filename = lst[0].split(' ')[-2] # FIXME: filename can contain SPC else: i = len(lst) - 2 while True: filename = decode(':'.join(lst[:i])) if os.path.exists(filename): break else: i -= 1 try: linenumber = int(lst[i]) except ValueError: linenumber = 0 filename = filename.replace(path, '') if filename[0] == os.sep and path != os.sep: filename = filename[1:] matches.append((filename, linenumber)) matches = ['%s:%d' % (f, l) for f, l in sorted(matches)] return 0, matches # find def do_find(path, files): cmd = '%s %s -name \"%s\" -print' % (sysprogs['find'], path, files) st, ret = ProcessFunc('Searching', 'Searching for \"%s\" files' % files, run_shell, encode(cmd), path, True).run() if not ret: return 0, [] if st < 0: # (-100, -1) => error return st, ret elif st == 0: ret = [f.strip() for f in ret.split('\n') if f.strip() != ''] matches = [] if len(ret) > 0: for filename in ret: filename = decode(filename).strip().replace(path, '') if filename is not None and filename != '': if filename[0] == os.sep and path != os.sep: filename = filename[1:] matches.append(filename) return 0, sorted(matches) ###################################################################### ##### encode/decode strings def encode(buf): return buf.encode(g_encoding) def decode(buf): if isinstance(buf, unicode): return buf for c in codecs_list: try: buf = buf.decode(c) except UnicodeDecodeError: continue else: return buf else: return buf.decode('ascii', 'replace') def ask_convert_invalid_encoding_filename(filename): auto = app.prefs.options['automatic_file_encoding_conversion'] if auto == -1: return False elif auto == 1: return True elif auto == 0: ret = messages.confirm('Detected invalid encoding', 'In file <%s>, convert' % filename) try: app.display() except: pass return (ret == 1) else: raise ValueError ###################################################################### ##### useful functions def get_escaped_filename(filename): filename = filename.replace('$', '\\$') if filename.find('"') != -1: filename = filename.replace('"', '\\"') return encode(filename) def get_escaped_command(cmd, filename): filename = filename.replace('$', '\$') if filename.find('"') != -1: filename = filename.replace('"', '\\"') return '%s \'%s\'' % (encode(cmd), encode(filename)) else: return '%s \"%s\"' % (encode(cmd), encode(filename)) def run_on_current_file(program, filename): cmd = get_escaped_command(app.prefs.progs[program], filename) curses.endwin() os.system(cmd) curses.curs_set(0) ###################################################################### if sys.version_info[:2] < (2, 4): import popen2 run_shell = run_shell_popen run_in_background = run_in_background_system get_shell_output = get_shell_output_popen get_shell_output2 = get_shell_output2_popen get_shell_output3 = get_shell_output3_popen else: from subprocess import Popen, PIPE, STDOUT run_in_background = run_in_background_subprocess run_shell = run_shell_subprocess get_shell_output = get_shell_output_subprocess get_shell_output2 = get_shell_output2_subprocess get_shell_output3 = get_shell_output3_subprocess ###################################################################### lfm-2.3/lfm/pyview0000755000076400001440000000163411561353053014461 0ustar inigousers00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2001-11 IƱigo Serna # Time-stamp: <2011-05-08 01:30:50 inigo> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from lfm.pyview import PyView if sys.version_info < (2, 5): print "Python 2.5 or higher is required to run pyview." else: PyView(sys.argv) lfm-2.3/lfm/config.py0000644000076400001440000003354711564300725015040 0ustar inigousers00000000000000# -*- coding: utf-8 -*- """config.py This module contains the configuration class for lfm. """ import os, os.path import codecs from ConfigParser import ConfigParser from __init__ import LFM_NAME, sysprogs, g_encoding from files import SORTTYPE_byName from utils import get_shell_output, encode, decode ###################################################################### ##### Some variables and default values CONFIG_FILE = '~/.lfmrc' defaultprogs = { 'shell': 'bash', 'pager': 'pyview', 'editor': 'vi', 'web': 'firefox', 'audio': 'mplayer', 'video': 'mplayer', 'graphics': 'gthumb', 'pdf': 'evince', 'ebook': 'FBReader' } filetypes = { 'web': ('html', 'htm'), 'audio': ('ogg', 'flac', 'mp3', 'wav', 'au', 'midi'), 'video': ('mpeg', 'mpg', 'avi', 'asf', 'ogv', 'flv', 'mkv'), 'graphics': ('png', 'jpeg', 'jpg', 'gif', 'tiff', 'tif', 'xpm', 'svg'), 'pdf': ('pdf', 'ps'), 'ebook': ('epub', 'chm', 'mobi', 'prc', 'azw', 'lit', 'fb2') } bookmarks = [u'/'] * 10 powercli_favs = [u'mv "$f" "{$f.replace(\'\', \'\')}"', u'pyview "$f" %', u'find "$d" -name "*" -print0 | xargs --null grep -EHcni "TODO|WARNING|FIXME|BUG"', u'find "$d" -name "*" -print0 | xargs --null grep -EHcni "TODO|WARNING|FIXME|BUG" >output.txt &', u'cp $s "$o"', u'', u'', u'', u'', u''] colors = { 'title': ('yellow', 'blue'), 'files': ('white', 'black'), 'current_file': ('blue', 'cyan'), 'messages': ('magenta', 'cyan'), 'help': ('green', 'black'), 'file_info': ('red', 'black'), 'error_messages1': ('white', 'red'), 'error_messages2': ('black', 'red'), 'buttons': ('yellow', 'red'), 'selected_file': ('yellow', 'black'), 'current_selected_file': ('yellow', 'cyan'), 'tabs': ('white', 'blue'), 'temp_files': ( 'white', 'black'), 'document_files': ('blue', 'black'), 'media_files': ('blue', 'black'), 'archive_files': ('yellow', 'black'), 'source_files': ('cyan', 'black'), 'graphics_files': ('magenta', 'black'), 'data_files': ('magenta', 'black'), 'current_file_otherpane': ('black', 'white'), 'current_selected_file_otherpane': ('yellow', 'white'), 'directories': ('green', 'black'), 'exe_files': ('red', 'black'), 'cli_prompt': ('blue', 'black'), 'cli_text': ('white', 'black') } options = { 'save_conf_at_exit': 1, 'save_history_at_exit': 1, 'show_output_after_exec': 1, 'rebuild_vfs': 0, 'detach_terminal_at_exec': 1, 'show_dotfiles': 1, 'num_panes': 2, 'sort': SORTTYPE_byName, 'sort_mix_dirs': 0, 'sort_mix_cases': 1, 'color_files': 1, 'manage_otherpane': 0, 'automatic_file_encoding_conversion': 0, # ask 'grep_ignorecase': 1, 'grep_regex': 1 } misc = { 'backup_extension': '.bak', 'diff_type': 'unified' } confirmations = { 'delete': 1, 'overwrite': 1, 'quit': 1, 'ask_rebuild_vfs': 1 } files_ext = { 'temp_files': ('.tmp', '.$$$', '~', '.bak'), 'document_files': ('.txt', '.text', '.rtf', '.odt', '.odc', '.odp', '.abw', '.gnumeric', '.sxw', '.sxc', '.sxp', '.sdw', '.sdc', '.sdp', '.ps', '.pdf', '.djvu', '.dvi', '.bib', '.tex', '.epub', '.chm', '.prc', '.mobi', '.azw', '.lit', '.imp' '.fb2', '.xml', '.xsd', '.xslt', '.sgml', '.dtd', '.html', '.shtml', '.htm', '.css', '.mail', '.msg', '.letter', '.ics', '.vcs', '.vcard', '.lsm', '.po', '.man', '.1', '.info', '.doc', '.xls', '.ppt', '.pps', '.docx', '.xlsx', '.pptx'), 'media_files': ('.mp2', '.mp3', '.mpg', '.ogg', '.flac', '.mpeg', '.wav', '.avi', '.asf', '.mov', '.mol', '.mpl', '.xm', '.med', '.mid', '.midi', '.umx', '.wma', '.acc', '.wmv', '.swf', '.flv', '.ogv', '.mkv'), 'archive_files': ('.gz', '.bz2', '.xz', '.tar', '.tgz', '.Z', '.zip', '.rar', '.7z', '.arj', '.cab', '.lzh', '.lha', '.zoo', '.arc', '.ark', '.rpm', '.deb'), 'source_files': ('.c', '.h', '.cc', '.hh', '.cpp', '.hpp', '.py', '.pyw', '.lua', '.pl', '.pm', '.inc', '.rb', '.asm', '.pas', '.f', '.f90', '.pov', '.m', '.pas', '.cgi', '.php', '.phps', '.tcl', '.tk', '.js', '.java', '.jav', '.jasm', '.vala', '.glade', '.ui', '.diff', '.patch', '.sh', '.bash', '.awk', '.m4', '.el', '.st', '.mak', '.sl', '.ada', '.caml', '.ml', '.mli', '.mly', '.mll', '.mlp', '.prg'), 'graphics_files': ('.jpg', '.jpeg', '.gif', '.png', '.tif', '.tiff', '.pcx', '.bmp', '.xpm', '.xbm', '.eps', '.pic', '.rle', '.ico', '.wmf', '.omf', '.ai', '.cdr', '.xcf', '.dwb', '.dwg', '.dxf', '.svg', '.dia'), 'data_files': ('.dta', '.nc', '.dbf', '.mdn', '.db', '.mdb', '.dat', '.fox', '.dbx', '.mdx', '.sql', '.mssql', '.msql', '.ssql', '.pgsql', '.cdx', '.dbi', '.sqlite') } ###################################################################### ##### Configuration class Config(object): """Config class""" def __init__(self): self.file = os.path.abspath(os.path.expanduser(CONFIG_FILE)) self.file_start = u'#' * 10 + ' ' + LFM_NAME + ' ' + \ 'Configuration File' + ' ' + '#' * 10 self.progs = {} # make a copy for k, v in defaultprogs.items(): self.progs[k] = v self.filetypes = filetypes self.bookmarks = bookmarks self.colors = colors self.options = options self.misc = misc self.confirmations = confirmations self.files_ext = files_ext def check_progs(self): for k, v in defaultprogs.items(): r = get_shell_output('%s \"%s\"' % (sysprogs['which'], v)) self.progs[k] = v if r else '' def load(self): # check config file if not os.path.exists(self.file) or not os.path.isfile(self.file): return -1 f = codecs.open(self.file, encoding=g_encoding) title = f.readline()[:-1] f.close() if title and title != self.file_start: return -2 # load config and validate sections cfg = ConfigParser() cfg.read(self.file) for sect in ('Programs', 'File Types', 'Bookmarks', 'PowerCLI commands', 'Colors', 'Options', 'Misc', 'Confirmations', 'Files'): if not cfg.has_section(sect): print 'Section "%s" does not exist, creating' % sect cfg.add_section(sect) # programs self.progs = defaultprogs.copy() for typ, prog in cfg.items('Programs'): self.progs[typ] = prog # file types self.filetypes = filetypes.copy() for typ, exts in cfg.items('File Types'): lst = [t.strip() for t in exts.split(',')] self.filetypes[typ] = tuple(lst) # bookmarks self.bookmarks = bookmarks[:] for num, path in cfg.items('Bookmarks'): try: num = int(num) except ValueError: print 'Bad bookmark number:', num continue if 0 <= num <= 9: if os.path.isdir(os.path.expanduser(path)): self.bookmarks[num] = decode(path) elif not path: self.bookmarks[num] = bookmarks[num] else: print 'Incorrect directory in bookmark[%d]: %s' % \ (num, path) else: print 'Bad bookmark number:', num # PowerCLI stored commands self.powercli_favs = powercli_favs[:] for num, cmd in cfg.items('PowerCLI commands'): try: num = int(num) except ValueError: print 'Bad PowerCLI command number:', num continue if 0 <= num <= 9: self.powercli_favs[num] = cmd else: print 'Bad PowerCLI command number:', num # colours self.colors = colors.copy() for sec, color in cfg.items('Colors'): if not self.colors.has_key(sec): print 'Bad object name:', sec else: (fg, bg) = color.split(' ', 2) self.colors[sec.lower()] = (fg.lower(), bg.lower()) # options self.options = options.copy() for what, val in cfg.items('Options'): try: val = int(val) except ValueError: print 'Bad option value: %s => %s' % (what, val) else: if what not in self.options.keys(): print 'Bad option: %s => %s' % (what, val) else: self.options[what] = val if self.options['num_panes'] != 1 or self.options['num_panes'] != 2: self.options['num_panes'] = 2 # misc self.misc = misc.copy() for what, val in cfg.items('Misc'): if not isinstance(val, str): print 'Bad option value: %s => %s' % (what, val) else: if what not in self.misc.keys(): print 'Bad option: %s => %s' % (what, val) else: if what == 'diff_type' and \ val not in ('context', 'unified', 'ndiff'): print 'Bad option: %s => %s' % (what, val) continue self.misc[what] = val # confirmations self.confirmations = confirmations.copy() for what, val in cfg.items('Confirmations'): try: val = int(val) except ValueError: print 'Bad confirmation value: %s => %s' % (what, val) else: if what not in self.confirmations.keys(): print 'Bad confirmation option: %s => %s' % (what, val) elif val not in (0, 1): print 'Bad confirmation value: %s => %s' % (what, val) else: self.confirmations[what] = val # File types for color self.files_ext = files_ext.copy() for typ, exts in cfg.items('Files'): lst = [t.strip() for t in exts.split(',')] self.files_ext[typ] = tuple(lst) def save(self): # title buf = self.file_start + '\n' # progs buf += '\n[Programs]\n' args = self.progs if hasattr(self, 'progs') else defaultprogs for k, v in sorted(args.items()): buf += '%s: %s\n' % (k, v) # filetypes buf += '\n[File Types]\n' args = self.filetypes if hasattr(self, 'filetypes') else filetypes for k, vs in sorted(args.items()): buf += '%s: %s\n' % (k, ', '.join(vs)) # bookmarks buf += '\n[Bookmarks]\n' args = self.bookmarks if hasattr(self, 'bookmarks') else bookmarks for i, b in enumerate(args): buf += '%d: %s\n' % (i, b) # PowerCLI stored commands buf += '\n[PowerCLI commands]\n' args = self.powercli_favs if hasattr(self, 'power_favs') else powercli_favs for i, c in enumerate(args): buf += '%d: %s\n' % (i, c) # colours buf += '\n[Colors]\n' args = self.colors if hasattr(self, 'colors') else colors for k, v in sorted(args.items()): buf += '%s: %s %s\n' % (k, v[0], v[1]) # options buf += '\n[Options]\n' buf += '# automatic_file_encoding_conversion: never = -1, ask = 0, always = 1\n' buf += '# sort:\tNone = 0, byName = 1, byName_rev = 2, bySize = 3,\n' buf += '# \tbySize_rev = 4, byDate = 5, byDate_rev = 6\n' args = self.options if hasattr(self, 'options') else options for k, v in sorted(args.items()): buf += '%s: %s\n' % (k, v) # misc buf += '\n[Misc]\n' buf += '# diff_type: context, unified, ndiff\n' args = self.misc if hasattr(self, 'misc') else misc for k, v in sorted(args.items()): buf += '%s: %s\n' % (k, v) # confirmations buf += '\n[Confirmations]\n' args = self.confirmations if hasattr(self, 'confirmations') else confirmations for k, v in sorted(args.items()): buf += '%s: %s\n' % (k, v) # File types for color buf += '\n[Files]\n' args = self.files_ext if hasattr(self, 'files_ext') else files_ext for k, vs in sorted(args.items()): buf += '%s: %s\n' % (k, ', '.join(vs)) # write to file f = codecs.open(self.file, 'w', encoding=g_encoding) f.write(buf) f.close() ###################################################################### lfm-2.3/lfm/pyview.py0000755000076400001440000010205311564326706015115 0ustar inigousers00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2001-11 IƱigo Serna # Time-stamp: <2011-05-14 12:12:24 inigo> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ Copyright (C) 2001-11, IƱigo Serna . All rights reserved. This software has been released under the GPL License, see the COPYING file that comes with this package. There is NO WARRANTY. 'pyview' is a simple pager (viewer) to be used with Last File Manager. """ import os, os.path import sys import getopt import logging from time import time import curses, curses.ascii from __init__ import * import messages from utils import run_shell, encode, decode ######################################################################## ##### module definitions and variables PYVIEW_NAME = 'pyview' PYVIEW_README = """ %s is a pager (viewer) written in Python. Though initially it was written to be used with 'lfm', it can be used standalone too. Since version 0.9 it can read from standard input too (eg. $ ps efax | pyview -s) This software has been realised under the GPL License, see the COPYING file that comes with this package. There is NO WARRANTY. Keys: ===== + Movement - cursor_up, p, P - cursor_down, n, N - previous page, backspace, Ctrl-P - next page, space, Ctrl-N - home: first line - end: last line - cursor_left - cursor_right + Actions - h, H, F1: help - w, W, F2: toggle un / wrap (only in text mode) - m, M, F4: toggle text / hex mode - g, G, F5: goto line / byte offset - /: find (new search) - F6: find previous or find - F7: find next or find - 0..9: go to bookmark # - b, B: set bookmark # - Ctrl-O: open shell 'sh'. Type 'exit' to return to pyview - q, Q, x, X, F3, F10: exit Goto Line / Byte Offset ======================= Enter the line number / byte offset you want to show. If number / byte is preceded by '0x' it is interpreted as hexadecimal. You can scroll relative lines from the current position using '+' or '-' character. Find ==== Type the string to search. It ignores case. """ % PYVIEW_NAME MODE_TEXT, MODE_HEX = 0, 1 app = None LOG_FILE = os.path.join(os.getcwd(), 'pyview.log') ###################################################################### ##### Internal View class InternalView(object): """Internal View class""" def __init__(self, title, buf, center=True): self.title = title self.__validate_buf(buf, center) self.init_curses() def __validate_buf(self, buf, center): buf = [(l[0][:app.maxw-2], l[1] ) for l in buf] buf2 = [l[0] for l in buf] self.nlines = len(buf2) if self.nlines >= app.maxh - 2: self.large = True self.y0 = self.y = 0 else: self.large = False self.y0 = int(((app.maxh-2) - self.nlines)/2) if center: col_max = max(map(len, buf2)) self.x0 = int((app.maxw-col_max)/2) else: self.x0 = 1 self.y0 = 0 if self.large else 1 self.buf = buf def init_curses(self): curses.cbreak() curses.raw() curses.curs_set(0) try: self.win_title = curses.newwin(1, app.maxw, 0, 0) self.win_body = curses.newwin(app.maxh-2, app.maxw, 1, 0) # h, w, y, x self.win_status = curses.newwin(1, app.maxw, app.maxh-1, 0) except curses.error: print 'Can\'t create windows' sys.exit(-1) if curses.has_colors(): self.win_title.bkgd(curses.color_pair(1), curses.color_pair(1) | curses.A_BOLD) self.win_body.bkgd(curses.color_pair(2)) self.win_status.bkgd(curses.color_pair(1)) self.win_body.leaveok(1) self.win_body.keypad(1) self.win_title.erase() self.win_status.erase() title = self.title if len(title) - 4 > app.maxw: title = title[:app.maxw-12] + '~' + title[-7:] self.win_title.addstr(0, int((app.maxw-len(title))/2), title) if self.large: status = '' else: status = 'Press any key to continue' self.win_status.addstr(0, int((app.maxw-len(status))/2), status) self.win_title.refresh() self.win_status.refresh() def show(self): self.win_body.erase() buf = self.large and self.buf[self.y:self.y+app.maxh-2] or self.buf for i, (l, c) in enumerate(buf): self.win_body.addstr(self.y0+i, self.x0, l, curses.color_pair(c)) self.win_body.refresh() def run(self): if self.large: while True: self.show() ch = self.win_body.getch() if ch in (ord('k'), ord('K'), curses.KEY_UP): self.y = max(self.y-1, 0) if ch in (ord('j'), ord('J'), curses.KEY_DOWN): self. y = min(self.y+1, self.nlines-1) elif ch in (curses.KEY_HOME, 0x01): self.y = 0 elif ch in (curses.KEY_END, 0x05): self.y = self.nlines - 1 elif ch in (curses.KEY_PPAGE, 0x08, 0x02, curses.KEY_BACKSPACE): self.y = max(self.y-app.maxh+2, 0) elif ch in (curses.KEY_NPAGE, ord(' '), 0x06): self. y = min(self.y+app.maxh-2, self.nlines-1) elif ch in (0x1B, ord('q'), ord('Q'), ord('x'), ord('X'), curses.KEY_F3, curses.KEY_F10): break else: self.show() while not self.win_body.getch(): pass ###################################################################### ##### Utilities def read_stdin(): """Read from stdin with 2.0 sec timeout maximum. Returns text""" from select import select try: fd = select([sys.stdin], [], [], 2.0)[0][0] stdin = ''.join(fd.readlines()) # close stdin (pipe) and open terminal for reading os.close(0) sys.stdin = open(os.ttyname(1), 'r') except IndexError: stdin = '' return stdin def create_temp_for_stdin(buf): """Copy stdin in a temporary file. Returns file name""" from tempfile import mkstemp filename = mkstemp()[1] f = open(filename, 'w') f.write(buf) f.close() return filename class FileCache(object): def __init__(self, filename, maxsize=1000000): # cache self.lines = {} self.lines_age = {} self.size = 0 self.maxsize = maxsize # file self.filename = filename self.fd = open(filename, 'r') self.nbytes = os.path.getsize(filename) self.lines_pos = [-1, 0L] # make index start at 1, first line pos = 0 if self.nbytes != 0: pos = nline = 0L for line in self.fd: nline += 1 pos += len(line) self.lines_pos.append(pos) # prepolulate first 50 lines if nline <= 50: buf = line.replace('\t', ' '*4) self.lines[nline] = buf self.lines_age[nline] = time() self.size += len(buf) self.nlines = nline else: self.nlines = 0 def __del__(self): self.fd.close() def __len__(self): return len(self.lines) def __repr__(self): return u'FileCache [%s, %d of %d lines in cache]' % \ (self.filename, len(self), self.nlines) def isempty(self): return self.nbytes == 0 def __getitem__(self, lineno): """return line# contents""" if lineno < 1 or lineno > self.nlines: return None if lineno in self.lines.keys(): self.lines_age[lineno] = time() return self.lines[lineno] else: self.fd.seek(self.lines_pos[lineno]) line = self.fd.readline() self.lines[lineno] = line self.lines_age[lineno] = time() self.size += len(line) self.__ensure_size() return line def __ensure_size(self): if self.size > self.maxsize: ages = [(t, len(self.lines[lno]), lno) for lno, t in self.lines_age.items()] ages.sort(reverse=True) while self.size > self.maxsize: t, s, lno = ages.pop() del self.lines[lno] del self.lines_age[lno] self.size -= s def line_len(self, lineno): """return length of line#""" if lineno < 1 or lineno > self.nlines: return None return len(self[lineno]) def linecol2pos(self, lineno, col): """return absolute position for line# and col# in file""" if lineno < 1 or lineno > self.nlines or col < 0: return None return self.lines_pos[lineno] + col def pos2linecol(self, pos): """return line# and col# for absolute pos in file""" if pos < 0 or pos > self.nbytes: return None for i, p in enumerate(self.lines_pos): if pos == p: return i, 0 elif pos < p: return i-1, p-pos return i, p-pos def get_bytes(self, n, pos): """return a string with n bytes starting at pos""" self.fd.seek(pos) return self.fd.read(n) ###################################################################### ##### PyView class FileView(object): """Main application class""" def __init__(self, win, filename, line, mode, stdin_flag): global app app, messages.app = self, self self.win = win # root window, need for resizing self.mode = mode self.wrap = False self.stdin_flag = stdin_flag self.line, self.col, self.col_max, self.pos = 1, 0, 0, 0 self.pattern = '' self.matches = [] self.bookmarks = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1] self.init_curses() try: self.fc = FileCache(filename) except (IOError, os.error), (errno, strerror): messages.error('Cannot view file\n%s: %s (%s)' % (filename, strerror, errno)) sys.exit(-1) if self.fc.isempty(): messages.error('Cannot view file\n%s: File is empty' % filename) sys.exit(-1) if line != 0: if mode == MODE_TEXT: self.line = max(0, min(line, self.fc.nlines)) else: self.pos = max(0, min(line, self.fc.nbytes)) & 0xFFFFFFF0L def init_curses(self): self.maxh, self.maxw = self.win.getmaxyx() curses.cbreak() curses.raw() curses.curs_set(0) try: self.win_title = curses.newwin(1, self.maxw, 0, 0) self.win_file = curses.newwin(self.maxh-2, self.maxw, 1, 0) self.win_status = curses.newwin(1, self.maxw, self.maxh-1, 0) except curses.error: print 'Can\'t create windows' sys.exit(-1) if curses.has_colors(): curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_BLUE) # title curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK) # files curses.init_pair(3, curses.COLOR_BLUE, curses.COLOR_CYAN) # current file curses.init_pair(4, curses.COLOR_MAGENTA, curses.COLOR_CYAN) # messages curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK) # help curses.init_pair(6, curses.COLOR_RED, curses.COLOR_BLACK) # file info curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_RED) # error messages curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_RED) # error messages curses.init_pair(9, curses.COLOR_YELLOW, curses.COLOR_RED) # button in dialog curses.init_pair(10, curses.COLOR_YELLOW, curses.COLOR_BLACK) # file selected curses.init_pair(11, curses.COLOR_YELLOW, curses.COLOR_CYAN) # file selected and current self.win_title.bkgd(curses.color_pair(1), curses.color_pair(1) | curses.A_BOLD) self.win_file.bkgdset(curses.color_pair(2)) self.win_status.bkgdset(curses.color_pair(1)) self.win_file.leaveok(1) self.win_file.keypad(1) def resize_window(self): h, w = self.win.getmaxyx() self.maxh, self.maxw = h, w if w == 0 or h == 2: return self.win.resize(h, w) self.win_title.resize(1, w) self.win_file.resize(h-2, w) self.win_status.resize(1, w) self.win_status.mvwin(h-1, 0) self.show() def __sanitize_char(self, c): if curses.ascii.iscntrl(c) or ord(c) in range(0x7F, 0xA0): return '.' elif curses.ascii.isascii(c) or curses.ascii.ismeta(c): return c # curses.ascii.ascii(c) = c else: return '.' def show_str(self, w, line): buf = ''.join(map(self.__sanitize_char, line)) w.addstr(buf) def show_str_yx(self, y, x, w, line): buf = ''.join(map(self.__sanitize_char, line)) w.addstr(y, x, buf) def __calc_hex_charsperline(self): tbl = ((152, 32), (134, 28), (116, 24), (98, 20), (80, 16), (62, 12), (44, 8), (26,4)) for width, chars_per_line in tbl: if self.maxw >= width: return chars_per_line else: return -1 def __move_hex(self, n): self.pos += self.__calc_hex_charsperline() * n self.pos = min(self.fc.nbytes & 0xFFFFFFF0L, max(0, self.pos)) def show_text_nowrap(self): self.win_file.refresh() self.col_max = self.maxw lwin = curses.newpad(self.maxh-2, self.maxw+1) lwin.erase() for y in xrange(self.maxh-2): l = self.fc[self.line+y] if l is None: break l = l.replace('\n', '').replace('\r', '').replace('\t', ' '*4) largeline = (len(l)-self.col > self.maxw) and True or False buf = l[self.col:self.col+self.maxw] self.show_str_yx(y, 0, lwin, buf) if largeline: self.col_max = max(self.col_max, len(l)) lwin.addch(y, self.maxw-1, '>', curses.color_pair(2) | curses.A_BOLD) lwin.refresh(0, 0, 1, 0, self.maxh-2, self.maxw-1) def show_text_wrap(self): self.win_file.refresh() lwin = curses.newpad(self.maxh-2, self.maxw+1) lwin.erase() i = y = 0 dx = self.col while y < self.maxh - 2: l = self.fc[self.line+i] if l is None: break l = l.replace('\n', '').replace('\r', '').replace('\t', ' '*4) l = l[dx:] # remaining chars of line self.show_str_yx(y, 0, lwin, l[:self.maxw]) y += 1 if len(l) > self.maxw: dx += self.maxw else: i += 1; dx = 0 lwin.refresh(0, 0, 1, 0, self.maxh-2, self.maxw-1) def show_hex(self): self.win_file.erase() chars_per_line = self.__calc_hex_charsperline() if chars_per_line == -1: return n = chars_per_line * (self.maxh-2) bytes = self.fc.get_bytes(n, self.pos) if len(bytes) < n: bytes += '\0' * (n-len(bytes)) for y in xrange(self.maxh-2): pos = chars_per_line*y self.win_file.addstr(y, 0, '%8.8X ' % (self.pos+pos), curses.color_pair(2) | curses.A_BOLD) buf = ' '.join([' '.join(['%2.2X' % (ord(bytes[pos+4*i+j]) & 0xFF) \ for j in xrange(4)]) \ for i in xrange(chars_per_line/4)]) buf += ' ' + bytes[y*chars_per_line:(y+1)*chars_per_line] self.show_str(self.win_file, buf) for i in xrange(chars_per_line/4-1): self.win_file.vline(0, 21+14*i, curses.ACS_VLINE, self.maxh-2) self.win_file.refresh() def show_title(self): self.win_title.erase() if self.mode == MODE_TEXT: pos = self.fc.linecol2pos(self.line, self.wrap and self.col or 0) lineno, col = self.line, self.col else: pos = self.pos lineno, col = self.fc.pos2linecol(self.pos) if self.maxw > 20: title = self.stdin_flag and 'STDIN' or os.path.basename(self.fc.filename) if len(title) > self.maxw-52: title = title[:self.maxw-58] + '~' + title[-5:] self.win_title.addstr('File: %s' % encode(title)) if self.maxw >= 67: if (self.mode == MODE_TEXT) and (col != 0 or self.wrap): self.win_title.addstr(0, int(self.maxw/2)-14, 'Col: %d' % col) buf = 'Bytes: %d/%d' % (pos, self.fc.nbytes) self.win_title.addstr(0, int(self.maxw/2)-5, buf) buf = 'Lines: %d/%d' % (lineno, self.fc.nlines) self.win_title.addstr(0, int(self.maxw*3/4)-4, buf) if self.maxw > 5: percent = int(pos*100/self.fc.nbytes) self.win_title.addstr(0, self.maxw-5, '%3d%%' % percent) self.win_title.refresh() def show_status(self): self.win_status.erase() if self.maxw > 40: if self.stdin_flag: path = 'STDIN' else: path = os.path.dirname(self.fc.filename) if not path or path[0] != os.sep: path = os.path.join(os.getcwd(), path) if len(path) > self.maxw - 37: path = '~' + path[-(self.maxw-38):] self.win_status.addstr('Path: %s' % encode(path)) if self.maxw > 30: mode = (self.mode==MODE_TEXT) and 'TEXT' or 'HEX' self.win_status.addstr(0, self.maxw-30, 'View mode: %s' % mode) wrap = self.wrap and 'YES' or 'NO' if self.mode == MODE_TEXT: self.win_status.addstr(0, self.maxw-10, 'Wrap: %s' % wrap) self.win_status.refresh() def show(self): if self.maxh < 3: return self.show_title() if self.mode == MODE_TEXT: if self.wrap: self.show_text_wrap() else: self.show_text_nowrap() else: self.show_hex() self.show_status() def __find(self, title): self.pattern = messages.Entry(title, 'Type search string', '', True, False).run() if self.pattern is None or self.pattern == '': return -1 filename = os.path.abspath(self.fc.filename) mode = (self.mode==MODE_TEXT) and 'n' or 'b' try: cmd = '%s -i%c \"%s\" \"%s\"' % (sysprogs['grep'], mode, self.pattern, filename) st, buf = run_shell(encode(cmd), path=u'.', return_output=True) except OSError: self.show() messages.error('Find error: Can\'t open file') return -1 if st == -1: self.show() messages.error('Find error\n' + buf) self.matches = [] return -1 else: try: self.matches = [int(l.split(':')[0]) for l in buf.split('\n') if l] except (ValueError, TypeError): self.matches = [] return 0 def __find_next(self): pos = (self.mode==MODE_TEXT) and self.line+1 or \ self.pos+self.__calc_hex_charsperline() # start in next line for next in self.matches: if next >= pos: break else: self.show() messages.error('Cannot find "%s"\nNo more matches' % self.pattern) return if self.mode == MODE_TEXT: self.line, self.col = next, 0 else: self.pos = next def __find_prev(self): pos = (self.mode==MODE_TEXT) and self.line-1 or \ self.pos - self.__calc_hex_charsperline() # start in prev line for prev in sorted(self.matches, reverse=True): if prev <= pos: break else: self.show() messages.error('Cannot find "%s"\nNo more matches' % self.pattern) return if self.mode == MODE_TEXT: self.line, self.col = prev, 0 else: self.pos = prev # movement def move_up(self): if self.mode == MODE_TEXT: if self.wrap: if self.col > 0: self.col -= self.maxw else: self.line = max(1, self.line-1) f, r = divmod(self.fc.line_len(self.line), self.maxw) # self.col = ((r==0) and (f-1) or f) * self.maxw # Fails! if r == 0 and f > 0: f -= 1 self.col = f * self.maxw else: self.line = max(1, self.line-1) else: self.__move_hex(-1) def move_down(self): if self.mode == MODE_TEXT: if self.wrap: if self.col + self.maxw >= self.fc.line_len(self.line): if self.line == self.fc.nlines: return self.line = min(self.fc.nlines, self.line+1) self.col = 0 else: self.col += self.maxw else: self.line = min(self.fc.nlines, self.line+1) else: self.__move_hex(1) def move_pageprev(self): if self.mode == MODE_TEXT: if self.wrap: i, y = 0, self.maxh-2 if len(self.fc[self.line]) > self.maxw: y += int((len(self.fc[self.line])-self.col)/self.maxw) while y >= 0: l = self.fc[self.line+i] if l is None: break f, r = divmod(len(l), self.maxw) if r == 0 and f > 0: f -= 1 y -= f+1; i -= 1 if f != 0: self.col = -(y+1) * self.maxw else: self.col = 0 self.line = max(1, self.line+i+1) else: self.line = max(1, self.line-self.maxh+2) else: self.__move_hex(-(self.maxh-2)) def move_pagenext(self): if self.mode == MODE_TEXT: if self.wrap: i = y = 0 dx = old_col = self.col while y < self.maxh - 2: l = self.fc[self.line+i] if l is None: break f, r = divmod(len(l[dx:]), self.maxw) if r == 0 and f > 0: f -= 1 y += f+1; i += 1; dx = 0 if f != 0: i -= 1 self.col = dx + ((f+1)-(y-self.maxh+2)) * self.maxw if l is None or self.col >= len(l[dx:]): self.col = 0 i += 1 else: self.col = dx if self.line == self.fc.nlines: self.col = old_col return self.line = min(self.fc.nlines, self.line+i) else: self.line = min(self.fc.nlines, self.line+self.maxh-2) else: self.__move_hex(self.maxh-2) def move_home(self): if self.mode == MODE_TEXT: self.line, self.col = 1, 0 else: self.pos = 0 def move_end(self): if self.mode == MODE_TEXT: self.line, self.col = self.fc.nlines, 0 else: self.pos = self.fc.nbytes & 0xFFFFFFF0L def move_left(self): if self.mode == MODE_TEXT and not self.wrap: if self.col > 9: self.col -= 10 def move_right(self): if self.mode == MODE_TEXT and not self.wrap: if self.col_max > self.col+self.maxw: self.col += 10 # goto, bookmarks def goto(self): rel = 0 title = (self.mode==MODE_TEXT) and 'Goto line' or 'Type line number' help = (self.mode==MODE_TEXT) and 'Goto line' or 'Type byte offset' n = messages.Entry(title, help, '', True, False).run() if not n: return if n[0] in ('+', '-'): rel = 1 try: if n[rel:rel+2] == '0x': if rel != 0: n = long(n[0] + str(int(n[1:], 16))) else: n = long(n, 16) else: n = long(n) except ValueError: self.show() msg = 'Goto %s' % (self.mode==MODE_TEXT and 'line' or 'byte') messages.error(msg + '\nInvalid number: %s' % n) return if n == 0: return if self.mode == MODE_TEXT: line = (rel!=0) and (self.line+n) or n self.line = max(1, min(line, self.fc.nlines)) else: pos = (rel!=0) and (self.pos+n) or n self.pos = max(0, min(pos, self.fc.nbytes)) & 0xFFFFFFF0L def goto_bookmark(self, no): pos = self.bookmarks[no] if pos != -1: if self.mode == MODE_TEXT: self.line, self.col = self.fc.pos2linecol(pos), 0 else: self.pos = pos def set_bookmark(self): while True: ch = messages.get_a_key('Set bookmark', 'Press 0-9 to save bookmark, Ctrl-C to quit') if 0x30 <= ch <= 0x39: if self.mode == MODE_TEXT: pos = self.fc.linecol2pos(self.line, self.col) else: pos = self.pos self.bookmarks[ch-0x30] = pos break elif ch == -1: break def find(self): if self.__find('Find') != -1: self.__find_next() def find_prev(self): if not self.matches: if self.__find('Find Previous') == -1: return self.__find_prev() def find_next(self): if not self.matches: if self.__find('Find Next') == -1: return self.__find_next() # modes def toggle_wrap(self): if self.mode == MODE_TEXT: self.wrap = not self.wrap self.col = 0 def toggle_mode(self): if self.mode == MODE_TEXT: self.mode = MODE_HEX self.pos = self.fc.linecol2pos(self.line, self.col) & 0xFFFFFFF0L else: self.mode = MODE_TEXT self.line, self.col = self.fc.pos2linecol(self.pos)[0], 0 self.pos = 0 # other def open_shell(self): curses.endwin() if self.stdin_flag: os.system('sh') else: os.system('cd \"%s\"; sh' % encode(os.path.dirname(self.fc.filename))) curses.curs_set(0) def show_help(self): buf = [('', 2)] buf.append(('%s v%s (C) %s, by %s' % \ (PYVIEW_NAME, VERSION, DATE, AUTHOR), 5)) text = PYVIEW_README.split('\n') for l in text: buf.append((l, 6)) InternalView('Help for %s' % PYVIEW_NAME, buf).run() def run(self): while True: self.show() ch = self.win_file.getch() # movement if ch in (ord('k'), ord('K'), curses.KEY_UP): self.move_up() elif ch in (ord('j'), ord('J'), curses.KEY_DOWN): self.move_down() elif ch in (curses.KEY_PPAGE, curses.KEY_BACKSPACE, 0x08, 0x02): self.move_pageprev() elif ch in (curses.KEY_NPAGE, ord(' '), 0x06): self.move_pagenext() elif ch in (curses.KEY_HOME, 0x01): self.move_home() elif ch in (curses.KEY_END, 0x05): self.move_end() elif ch == curses.KEY_LEFT: self.move_left() elif ch == curses.KEY_RIGHT: self.move_right() # goto, bookmarks, find elif ch in (ord('g'), ord('G'), curses.KEY_F5): self.goto() elif 0x30 <= ch <= 0x39: self.goto_bookmark(ch-0x30) elif ch in (ord('b'), ord('B')): self.set_bookmark() elif ch == ord('/'): self.find() elif ch == curses.KEY_F6: self.find_prev() elif ch == curses.KEY_F7: self.find_next() # modes elif ch in (ord('w'), ord('W'), curses.KEY_F2): self.toggle_wrap() elif ch in (ord('m'), ord('M'), curses.KEY_F4): self.toggle_mode() # other elif ch == 0x0F: # Ctrl-O self.open_shell() elif ch in (ord('h'), ord('H'), curses.KEY_F1): self.show_help() elif ch == curses.KEY_RESIZE: self.resize_window() elif ch in (0x11, ord('q'), ord('Q'), ord('x'), ord('X'), # Ctrl-Q curses.KEY_F3, curses.KEY_F10): del self.fc return ###################################################################### ##### Main def usage(prog, msg=''): prog = os.path.basename(prog) if msg != '': print '%s:\t%s\n' % (prog, msg) print """\ %s v%s - (C) %s, by %s A simple pager (viewer) to be used with Last File Manager. Released under GNU Public License, read COPYING for more details. Usage:\t%s\t[-h | --help] \t\t[-s | --stdin] \t\t[-m text|hex | --mode=text|hex] \t\t[-d | --debug] \t\t[+n] \t\tpathtofile Options: -s, --stdin\t\tread from stdin -m, --mode\t\tstart in text or hexadecimal mode -d, --debug\t\tcreate debug file -h, --help\t\tshow help +n\t\t\tstart at line (text mode) or byte (hex mode), \t\t\tif n starts with '0x' is considered hexadecimal pathtofile\t\tfile to view """ % (PYVIEW_NAME, VERSION, DATE, AUTHOR, prog) def main(win, filename, line, mode, stdin_flag): app = FileView(win, decode(filename), line, mode, stdin_flag) if app == OSError: sys.exit(-1) return app.run() def PyView(sysargs): # defaults DEBUG = False filename = '' line = 0 stdin_flag = False mode = MODE_TEXT # args try: opts, args = getopt.getopt(sysargs[1:], 'dhsm:', ['debug', 'help', 'stdin', 'mode=']) except getopt.GetoptError: usage(sysargs[0], 'Bad argument(s)') sys.exit(-1) for o, a in opts: if o in ('-s', '--stdin'): stdin_flag = True elif o in ('-d', '--debug'): DEBUG = True elif o in ('-h', '--help'): usage(sysargs[0]) sys.exit(2) elif o in ('-m', '--mode'): if a == 'text': mode = MODE_TEXT elif a == 'hex': mode = MODE_HEX else: usage(sysargs[0], '<%s> is not a valid mode' % a) sys.exit(-1) if stdin_flag: stdin = read_stdin() if stdin == '': stdin_flag = False if len(args) > 2: usage(sysargs[0], 'Incorrect number of arguments') sys.exit(-1) while True: try: arg = args.pop() if arg[0] == '+': line = arg[1:] try: line = (line[:2]=='0x') and int(line, 16) or int(line) except ValueError: usage(sysargs[0], '<%s> is not a valid line number' % line) sys.exit(-1) else: filename = arg except IndexError: break if filename == '' and not stdin_flag: usage(sysargs[0], 'File is missing') sys.exit(-1) if stdin_flag: filename = create_temp_for_stdin(stdin) else: if not os.path.isfile(filename): usage(sysargs[0], '<%s> is not a valid file' % filename) sys.exit(-1) # logging if DEBUG: log_file = os.path.join(os.path.abspath(u'.'), LOG_FILE) logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s\t%(message)s', datefmt='%Y-%m-%d %H:%M:%S ', filename=log_file, filemode='w') logging.info('Starting PyView...') # main app logging.info('Main application call') curses.wrapper(main, filename, line, mode, stdin_flag) logging.info('End') if stdin_flag: os.unlink(filename) if __name__ == '__main__': PyView(sys.argv) lfm-2.3/lfm/lfm0000755000076400001440000000163411561353066013720 0ustar inigousers00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2001-11 IƱigo Serna # Time-stamp: <2011-05-08 01:31:02 inigo> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from lfm.lfm import lfm_start if sys.version_info < (2, 5): print "Python 2.5 or higher is required to run lfm." else: lfm_start(sys.argv) lfm-2.3/pyview.10000644000076400001440000000307311565705505014045 0ustar inigousers00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH pyview "1" "May 21, 2011" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME \fBpyview\fR \- a simple pager (viewer) to be used with Last File Manager. .SH SYNOPSIS .BI "pyview [ -h | --help ] " .br .BI " [ -d | --debug ]" .br .BI " [-m text|hex | --mode=text|hex]" .br .BI " [+n]" .br .BI " pathtofile" .sp .SH DESCRIPTION .B pyview is a simple pager (viewer) to be used with Last File Manager. .SH OPTIONS .TP .B "\-m, \-\-mode" start in text or hexadecimal mode .TP .B "\-d, \-\-debug" create debug file .TP .B "\-h, \-\-help" show help .TP .B "\+n" start at line (text mode) or byte (hex mode), if n starts with '0x' is considered hexadecimal .TP .B "pathtofile" file to view .SH AUTHOR .B pyview was written by Ińigo Serna .PP This manual page was written by Sebastien Bacher for the Debian GNU/Linux system (but may be used by others). .SH SEE ALSO The full documentation which includes the keys descriptions is in /usr/share/doc/lfm/README.pyview. lfm-2.3/TODO0000644000076400001440000000741111565704626013133 0ustar inigousers00000000000000============================================================================ Last update: Time-stamp: <2011-05-21 11:44:54 inigo> ============================================================================ General: ======== High Priority: + enhancements: - don't like much how recursive chmod/chown/chgrp currently works . split in 2 different features chmod and chown/chgrp ? - support for languages with wide chars . look at Xin Wang's patch . check the real length on screen of the utf8-encoded strings. http://bugs.python.org/issue6755 http://stackoverflow.com/questions/2476953/python-utf-8-howto-align-printout + documentation: - be sure version and release date is correct in all files Medium Priority: + enhancements: - substitute curses.color_pair(X) for COL_XXX + ui: - tab views: . 2 columns mode . customized: permissions instead of date . quick view . info view + new features: - pyview: . use mmap . tail mode: option -f, document - powercli: |: run as tail -f, substitute run sync by default, document - diff and sync dirs: use filecmp.dircmp or rsync Low Priority (maybe some day): + enhancements: - more than 10 bookmarks => use letters - find/grep . exclude files (f.e. *.o) in findgrep grep --exclude=XXX => NON-POSIX, GNU extension . checkboxes for ignorecase and regex flags + new features: - filter files (f.e. "*py" to only view python files) - crypt files => gnupg ? - new vfs: .rpm, .cpio, .deb, .jar, .xpi, .egg - show directory under cursor in the other panel: mc: Alt+O - keybindings customization - global copy/cut/paste between tabs or panes - background processes: copy/move/delete + other: - use mimetypes module - use pyinotify module - plugin system Very Low Priority (never): + remote vfs: ssh, ftp, smb, webdav => read FAQ entry about fuse integration + advanced file rename tool => use PowerCLI + i18n => curses ui programming is very hard when dealing with different languages because strings have different lengths so it's imposible to design a nice and working interface for every language + mouse support + UI to configure preferences + "save delete" / delete moving to ~/.Trash Known Bugs: =========== + general: - after renaming a file, cursor should be placed over the new file name, but this is not always posible because we don't know new file name - if an error happens while copying/moving/uncompressing/... => => some rubbish remain in destination + compress.py: - rar with password halts lfm, because process is waiting for a password => => timeout if not output and kill the spawned process - recursively chmod +w temp files before deleting + vfs.py: - .tar.gz inside a .tar.gz file (vfs in vfs) - tmpdir are showed in the copy/move/... dialogs or when view/edit/... a file, instead of vfs dir (this is just a minor estetic issue) - in case of 'panelize' vfs type (after find/grep), deleted / moved files are not deleted / moved in real path Python versions: ================ Upgrading python version, interesting new features by version + python 2.4 - sorted - sort, sorted: cmp -> key - subprocess - property - unicodetata.east_asian_width + python 2.5 - ternary operator - any, all - ctypes + python 2.6 - with - multiprocessing - subprocess.Popen.{send_signal,kill,terminate} - format - collections.namedtuple - os.closerange() - try: except Exception as + python 2.7 - subprocess.check_output - collections.OrderedDict + python 3.x - print() - has_key => in - collections.OrderedDict - no module cPickle ============================================================================ lfm-2.3/MANIFEST.in0000644000076400001440000000023011535256031014156 0ustar inigousers00000000000000global-exclude {arch} include setup.py lfm/*.py lfm.1 pyview.1 include lfm/lfm lfm/pyview README README.pyview NEWS COPYING ChangeLog TODO MANIFEST.in