1 # Copyright (C) 2009, 2010, 2011 Roman Zimbelmann <romanz@lavabit.com>
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 from os
.path
import join
, isdir
, realpath
23 from os
import link
, symlink
, getcwd
24 from inspect
import cleandoc
27 from ranger
.ext
.direction
import Direction
28 from ranger
.ext
.relative_symlink
import relative_symlink
29 from ranger
.ext
.keybinding_parser
import key_to_string
, construct_keybinding
30 from ranger
.ext
.shell_escape
import shell_quote
31 from ranger
.core
.shared
import FileManagerAware
, EnvironmentAware
, \
33 from ranger
.fsobject
import File
34 from ranger
.core
.loader
import CommandLoader
36 MACRO_FAIL
= "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>"
38 class _MacroTemplate(string
.Template
):
39 """A template for substituting macros in commands"""
40 delimiter
= ranger
.MACRO_DELIMITER
42 class Actions(FileManagerAware
, EnvironmentAware
, SettingsAware
):
43 search_method
= 'ctime'
45 # --------------------------
47 # --------------------------
50 """Exit the program"""
54 """Reset the filemanager, clearing the directory buffer"""
55 old_path
= self
.env
.cwd
.path
57 self
.env
.garbage_collect(-1, self
.tabs
)
58 self
.enter_dir(old_path
)
68 def notify(self
, text
, duration
=4, bad
=False):
69 if isinstance(text
, Exception):
74 self
.log
.appendleft(text
)
75 if self
.ui
and self
.ui
.is_on
:
76 self
.ui
.status
.notify(" ".join(text
.split("\n")),
77 duration
=duration
, bad
=bad
)
83 item
= self
.loader
.queue
[0]
85 self
.notify("Type Q or :quit<Enter> to exit ranger")
87 self
.notify("Aborting: " + item
.get_description())
88 self
.loader
.remove(index
=0)
90 def redraw_window(self
):
91 """Redraw the window"""
92 self
.ui
.redraw_window()
94 def open_console(self
, string
='', prompt
=None, position
=None):
95 """Open the console if the current UI supports that"""
96 self
.ui
.open_console(string
, prompt
=prompt
, position
=position
)
98 def execute_console(self
, string
='', wildcards
=[], quantifier
=None):
99 """Execute a command for the console"""
100 command_name
= string
.split()[0]
101 cmd_class
= self
.commands
.get_command(command_name
, abbrev
=False)
102 if cmd_class
is None:
103 self
.notify("Command not found: `%s'" % command_name
, bad
=True)
105 cmd
= cmd_class(string
)
106 if cmd
.resolve_macros
and _MacroTemplate
.delimiter
in string
:
107 macros
= dict(('any%d'%i, key_to_string(char
)) \
108 for i
, char
in enumerate(wildcards
))
110 macros
['any'] = macros
['any0']
112 string
= self
.substitute_macros(string
, additional
=macros
,
113 escape
=cmd
.escape_macros_for_shell
)
114 except ValueError as e
:
118 return self
.notify(e
)
120 cmd_class(string
, quantifier
=quantifier
).execute()
121 except Exception as e
:
127 def substitute_macros(self
, string
, additional
=dict(), escape
=False):
128 macros
= self
._get
_macros
()
129 macros
.update(additional
)
131 for key
, value
in macros
.items():
132 if isinstance(value
, list):
133 macros
[key
] = " ".join(shell_quote(s
) for s
in value
)
134 elif value
!= MACRO_FAIL
:
135 macros
[key
] = shell_quote(value
)
137 for key
, value
in macros
.items():
138 if isinstance(value
, list):
139 macros
[key
] = " ".join(value
)
140 result
= _MacroTemplate(string
).safe_substitute(macros
)
141 if MACRO_FAIL
in result
:
142 raise ValueError("Could not apply macros to `%s'" % string
)
145 def _get_macros(self
):
148 macros
['rangerdir'] = ranger
.RANGERDIR
151 macros
['f'] = self
.fm
.env
.cf
.basename
153 macros
['f'] = MACRO_FAIL
155 if self
.fm
.env
.get_selection
:
156 macros
['s'] = [fl
.basename
for fl
in self
.fm
.env
.get_selection()]
158 macros
['s'] = MACRO_FAIL
161 macros
['c'] = [fl
.path
for fl
in self
.fm
.env
.copy
]
163 macros
['c'] = MACRO_FAIL
165 if self
.fm
.env
.cwd
.files
:
166 macros
['t'] = [fl
.basename
for fl
in self
.fm
.env
.cwd
.files
167 if fl
.realpath
in (self
.fm
.tags
or [])]
169 macros
['t'] = MACRO_FAIL
172 macros
['d'] = self
.fm
.env
.cwd
.path
176 # define d/f/s macros for each tab
177 for i
in range(1,10):
179 tab_dir_path
= self
.fm
.tabs
[i
]
182 tab_dir
= self
.fm
.env
.get_directory(tab_dir_path
)
184 macros
[i
+ 'd'] = tab_dir_path
185 if tab_dir
.get_selection():
186 macros
[i
+ 's'] = [fl
.path
for fl
in tab_dir
.get_selection()]
188 macros
[i
+ 's'] = MACRO_FAIL
189 if tab_dir
.pointed_obj
:
190 macros
[i
+ 'f'] = tab_dir
.pointed_obj
.path
192 macros
[i
+ 'f'] = MACRO_FAIL
194 # define D/F/S for the next tab
195 found_current_tab
= False
198 for tab
in self
.fm
.tabs
:
201 if found_current_tab
:
202 next_tab_path
= self
.fm
.tabs
[tab
]
204 if self
.fm
.current_tab
== tab
:
205 found_current_tab
= True
206 if found_current_tab
and not next_tab_path
:
207 next_tab_path
= self
.fm
.tabs
[first_tab
]
208 next_tab
= self
.fm
.env
.get_directory(next_tab_path
)
211 macros
['D'] = str(next_tab
.path
)
212 if next_tab
.pointed_obj
:
213 macros
['F'] = next_tab
.pointed_obj
.path
215 macros
['F'] = MACRO_FAIL
216 if next_tab
.get_selection():
217 macros
['S'] = [fl
.path
for fl
in next_tab
.get_selection()]
219 macros
['S'] = MACRO_FAIL
221 macros
['D'] = MACRO_FAIL
222 macros
['F'] = MACRO_FAIL
223 macros
['S'] = MACRO_FAIL
227 def source(self
, filename
):
228 filename
= os
.path
.expanduser(filename
)
229 for line
in open(filename
, 'r'):
230 line
= line
.rstrip("\r\n")
231 if line
.startswith("#") or not line
.strip():
234 self
.execute_console(line
)
235 except Exception as e
:
239 self
.notify('Error in line `%s\':\n %s' %
240 (line
, str(e
)), bad
=True)
242 def execute_file(self
, files
, **kw
):
244 app is the name of a method in Applications, without the "app_"
245 flags is a string consisting of runner.ALLOWED_FLAGS
246 mode is a positive integer.
247 Both flags and mode specify how the program is run."""
249 # ranger can act as a file chooser when running with --choosefile=...
250 if ranger
.arg
.choosefile
:
251 open(ranger
.arg
.choosefile
, 'w').write(self
.fm
.env
.cf
.path
)
254 if isinstance(files
, set):
256 elif type(files
) not in (list, tuple):
260 from ranger
.core
.runner
import Context
261 context
= Context(files
=list(files
), flags
=kw
['flags'])
262 context
.squash_flags()
263 if 'c' in context
.flags
:
264 files
= [self
.fm
.env
.cf
]
266 self
.signal_emit('execute.before', keywords
=kw
)
268 return self
.run(files
=list(files
), **kw
)
270 self
.signal_emit('execute.after')
272 # --------------------------
274 # --------------------------
276 def move(self
, narg
=None, **kw
):
278 A universal movement method.
280 Accepts these parameters:
281 (int) down, (int) up, (int) left, (int) right, (int) to,
282 (bool) absolute, (bool) relative, (bool) pages,
285 to=X is translated to down=X, absolute=True
288 self.move(down=4, pages=True) # moves down by 4 pages.
289 self.move(to=2, pages=True) # moves to page 2.
290 self.move(to=1, percentage=True) # moves to 80%
293 direction
= Direction(kw
)
294 if 'left' in direction
or direction
.left() > 0:
295 steps
= direction
.left()
299 directory
= os
.path
.join(*(['..'] * steps
))
302 self
.env
.enter_dir(directory
)
303 if cwd
and cwd
.accessible
and cwd
.content_loaded
:
304 if 'right' in direction
:
309 selection
= self
.env
.get_selection()
310 if not self
.env
.enter_dir(cf
) and selection
:
311 if self
.execute_file(selection
, mode
=mode
) is False:
312 self
.open_console('open_with ')
313 elif direction
.vertical() and cwd
.files
:
314 newpos
= direction
.move(
315 direction
=direction
.down(),
319 pagesize
=self
.ui
.browser
.hei
)
322 def move_parent(self
, n
, narg
=None):
325 parent
= self
.env
.at_level(-1)
326 if parent
is not None:
327 if parent
.pointer
+ n
< 0:
328 n
= 0 - parent
.pointer
330 self
.env
.enter_dir(parent
.files
[parent
.pointer
+n
])
334 def history_go(self
, relative
):
335 """Move back and forth in the history"""
336 self
.env
.history_go(int(relative
))
338 def scroll(self
, relative
):
339 """Scroll down by <relative> lines"""
340 if self
.ui
.browser
and self
.ui
.browser
.main_column
:
341 self
.ui
.browser
.main_column
.scroll(relative
)
342 self
.env
.cf
= self
.env
.cwd
.pointed_obj
344 def enter_dir(self
, path
, remember
=False, history
=True):
345 """Enter the directory at the given path"""
348 result
= self
.env
.enter_dir(path
, history
=history
)
349 self
.bookmarks
.remember(cwd
)
351 return self
.env
.enter_dir(path
, history
=history
)
353 def cd(self
, path
, remember
=True):
354 """enter the directory at the given path, remember=True"""
355 self
.enter_dir(path
, remember
=remember
)
360 if cf
is not None and cf
.is_directory
:
361 self
.enter_dir(cf
.path
)
362 elif cwd
.pointer
>= len(cwd
) - 1:
366 if cwd
.pointer
< len(cwd
) - 1:
376 # --------------------------
377 # -- Shortcuts / Wrappers
378 # --------------------------
380 def pager_move(self
, narg
=None, **kw
):
381 self
.ui
.browser
.pager
.move(narg
=narg
, **kw
)
383 def taskview_move(self
, narg
=None, **kw
):
384 self
.ui
.taskview
.move(narg
=narg
, **kw
)
386 def pager_close(self
):
387 if self
.ui
.pager
.visible
:
388 self
.ui
.close_pager()
389 if self
.ui
.browser
.pager
.visible
:
390 self
.ui
.close_embedded_pager()
392 def taskview_open(self
):
393 self
.ui
.open_taskview()
395 def taskview_close(self
):
396 self
.ui
.close_taskview()
398 def execute_command(self
, cmd
, **kw
):
399 return self
.run(cmd
, **kw
)
401 def edit_file(self
, file=None):
402 """Calls execute_file with the current file and app='editor'"""
405 elif isinstance(file, str):
406 file = File(os
.path
.expanduser(file))
409 self
.execute_file(file, app
= 'editor')
411 def toggle_option(self
, string
):
412 """Toggle a boolean option named <string>"""
413 if isinstance(self
.env
.settings
[string
], bool):
414 self
.env
.settings
[string
] ^
= True
416 def set_option(self
, optname
, value
):
417 """Set the value of an option named <optname>"""
418 self
.env
.settings
[optname
] = value
420 def sort(self
, func
=None, reverse
=None):
421 if reverse
is not None:
422 self
.env
.settings
['sort_reverse'] = bool(reverse
)
425 self
.env
.settings
['sort'] = str(func
)
427 def set_filter(self
, fltr
):
429 self
.env
.cwd
.filter = fltr
433 def mark_files(self
, all
=False, toggle
=False, val
=None, movedown
=None, narg
=1):
435 A wrapper for the directory.mark_xyz functions.
438 all - change all files of the current directory at once?
439 toggle - toggle the marked-status?
440 val - mark or unmark?
443 if self
.env
.cwd
is None:
448 if not cwd
.accessible
:
454 if val
is None and toggle
is False:
459 cwd
.toggle_all_marks()
463 for i
in range(cwd
.pointer
, min(cwd
.pointer
+ narg
, len(cwd
))):
467 cwd
.toggle_mark(item
)
469 cwd
.mark_item(item
, val
)
474 self
.ui
.redraw_main_column()
475 self
.ui
.status
.need_redraw
= True
477 def mark_in_direction(self
, val
=True, dirarg
=None):
479 direction
= Direction(dirarg
)
480 pos
, selected
= direction
.select(lst
=cwd
.files
, current
=cwd
.pointer
,
481 pagesize
=self
.env
.termsize
[0])
483 cwd
.correct_pointer()
484 for item
in selected
:
485 cwd
.mark_item(item
, val
)
487 # --------------------------
489 # --------------------------
491 def search_file(self
, text
, offset
=1, regexp
=True):
492 if isinstance(text
, str) and regexp
:
494 text
= re
.compile(text
, re
.L | re
.U | re
.I
)
497 self
.env
.last_search
= text
498 self
.search_next(order
='search', offset
=offset
)
500 def search_next(self
, order
=None, offset
=1, forward
=True):
501 original_order
= order
504 order
= self
.search_method
506 self
.set_search_method(order
=order
)
508 if order
in ('search', 'tag'):
509 if order
== 'search':
510 arg
= self
.env
.last_search
513 if hasattr(arg
, 'search'):
514 fnc
= lambda x
: arg
.search(x
.basename
)
516 fnc
= lambda x
: arg
in x
.basename
518 fnc
= lambda x
: x
.realpath
in self
.tags
520 return self
.env
.cwd
.search_fnc(fnc
=fnc
, offset
=offset
, forward
=forward
)
522 elif order
in ('size', 'mimetype', 'ctime', 'mtime', 'atime'):
524 if original_order
is not None or not cwd
.cycle_list
:
525 lst
= list(cwd
.files
)
527 fnc
= lambda item
: -item
.size
528 elif order
== 'mimetype':
529 fnc
= lambda item
: item
.mimetype
530 elif order
== 'ctime':
531 fnc
= lambda item
: -int(item
.stat
and item
.stat
.st_ctime
)
532 elif order
== 'atime':
533 fnc
= lambda item
: -int(item
.stat
and item
.stat
.st_atime
)
534 elif order
== 'mtime':
535 fnc
= lambda item
: -int(item
.stat
and item
.stat
.st_mtime
)
537 cwd
.set_cycle_list(lst
)
538 return cwd
.cycle(forward
=None)
540 return cwd
.cycle(forward
=forward
)
542 def set_search_method(self
, order
, forward
=True):
543 if order
in ('search', 'tag', 'size', 'mimetype', 'ctime'):
544 self
.search_method
= order
546 # --------------------------
548 # --------------------------
549 # Tags are saved in ~/.config/ranger/tagged and simply mark if a
550 # file is important to you in any context.
552 def tag_toggle(self
, paths
=None, value
=None, movedown
=None, tag
=None):
556 tags
= tuple(x
.realpath
for x
in self
.env
.get_selection())
558 tags
= [realpath(path
) for path
in paths
]
560 self
.tags
.add(*tags
, tag
=tag
or self
.tags
.default_tag
)
562 self
.tags
.remove(*tags
)
564 self
.tags
.toggle(*tags
, tag
=tag
or self
.tags
.default_tag
)
567 movedown
= len(tags
) == 1 and paths
is None
571 self
.ui
.redraw_main_column()
573 def tag_remove(self
, paths
=None, movedown
=None):
574 self
.tag_toggle(paths
=paths
, value
=False, movedown
=movedown
)
576 def tag_add(self
, paths
=None, movedown
=None):
577 self
.tag_toggle(paths
=paths
, value
=True, movedown
=movedown
)
579 # --------------------------
581 # --------------------------
582 # Using ranger.container.bookmarks.
584 def enter_bookmark(self
, key
):
585 """Enter the bookmark with the name <key>"""
587 self
.bookmarks
.update_if_outdated()
588 destination
= self
.bookmarks
[str(key
)]
590 if destination
.path
!= cwd
.path
:
591 self
.bookmarks
.enter(str(key
))
592 self
.bookmarks
.remember(cwd
)
596 def set_bookmark(self
, key
):
597 """Set the bookmark with the name <key> to the current directory"""
598 self
.bookmarks
.update_if_outdated()
599 self
.bookmarks
[str(key
)] = self
.env
.cwd
601 def unset_bookmark(self
, key
):
602 """Delete the bookmark with the name <key>"""
603 self
.bookmarks
.update_if_outdated()
604 self
.bookmarks
.delete(str(key
))
606 def draw_bookmarks(self
):
607 self
.ui
.browser
.draw_bookmarks
= True
609 def hide_bookmarks(self
):
610 self
.ui
.browser
.draw_bookmarks
= False
612 # --------------------------
614 # --------------------------
615 # These commands open the built-in pager and set specific sources.
617 def display_command_help(self
, console_widget
):
619 command
= console_widget
._get
_cmd
_class
()
621 self
.notify("Feature not available!", bad
=True)
625 self
.notify("Command not found!", bad
=True)
628 if not command
.__doc
__:
629 self
.notify("Command has no docstring. Try using python without -OO",
633 pager
= self
.ui
.open_pager()
634 lines
= cleandoc(command
.__doc
__).split('\n')
635 pager
.set_source(lines
)
637 def display_help(self
):
638 manualpath
= self
.relpath('../doc/ranger.1')
639 if os
.path
.exists(manualpath
):
640 process
= self
.run(['man', manualpath
])
641 if process
.poll() != 16:
643 process
= self
.run(['man', 'ranger'])
644 if process
.poll() == 16:
645 self
.notify("Could not find manpage.", bad
=True)
647 def display_log(self
):
648 pager
= self
.ui
.open_pager()
650 pager
.set_source(["Message Log:"] + list(self
.log
))
652 pager
.set_source(["Message Log:", "No messages!"])
654 def display_file(self
):
655 if not self
.env
.cf
or not self
.env
.cf
.is_file
:
658 pager
= self
.ui
.open_embedded_pager()
659 pager
.set_source(self
.env
.cf
.get_preview_source(pager
.wid
, pager
.hei
))
661 # --------------------------
663 # --------------------------
664 def update_preview(self
, path
):
666 del self
.previews
[path
]
667 self
.ui
.need_redraw
= True
671 def get_preview(self
, path
, width
, height
):
672 if self
.settings
.preview_script
and self
.settings
.use_preview_script
:
673 # self.previews is a 2 dimensional dict:
674 # self.previews['/tmp/foo.jpg'][(80, 24)] = "the content..."
675 # self.previews['/tmp/foo.jpg']['loading'] = False
676 # A -1 in tuples means "any"; (80, -1) = wid. of 80 and any hei.
677 # The key 'foundpreview' is added later. Values in (True, False)
679 data
= self
.previews
[path
]
681 data
= self
.previews
[path
] = {'loading': False}
686 found
= data
.get((-1, -1), data
.get((width
, -1),
687 data
.get((-1, height
), data
.get((width
, height
), False))))
689 data
['loading'] = True
690 loadable
= CommandLoader(args
=[self
.settings
.preview_script
,
691 path
, str(width
), str(height
)], read
=True,
692 silent
=True, descr
="Getting preview of %s" % path
)
693 def on_after(signal
):
694 exit
= signal
.process
.poll()
695 content
= signal
.loader
.stdout_buffer
696 data
['foundpreview'] = True
698 data
[(width
, height
)] = content
700 data
[(-1, height
)] = content
702 data
[(width
, -1)] = content
704 data
[(-1, -1)] = content
706 data
[(-1, -1)] = None
707 data
['foundpreview'] = False
709 f
= codecs
.open(path
, 'r', errors
='ignore')
711 data
[(-1, -1)] = f
.read(1024 * 32)
712 except UnicodeDecodeError:
714 f
= codecs
.open(path
, 'r', encoding
='latin-1',
716 data
[(-1, -1)] = f
.read(1024 * 32)
719 data
[(-1, -1)] = None
720 if self
.env
.cf
.realpath
== path
:
721 self
.ui
.browser
.need_redraw
= True
722 data
['loading'] = False
723 pager
= self
.ui
.browser
.pager
724 if self
.env
.cf
and self
.env
.cf
.is_file
:
725 pager
.set_source(self
.env
.cf
.get_preview_source(
726 pager
.wid
, pager
.hei
))
727 def on_destroy(signal
):
729 del self
.previews
[path
]
732 loadable
.signal_bind('after', on_after
)
733 loadable
.signal_bind('destroy', on_destroy
)
734 self
.loader
.add(loadable
)
740 return codecs
.open(path
, 'r', errors
='ignore')
744 # --------------------------
746 # --------------------------
747 # This implementation of tabs is very simple and keeps track of
748 # directory paths only.
750 def tab_open(self
, name
, path
=None):
751 do_emit_signal
= name
!= self
.current_tab
752 self
.current_tab
= name
753 if path
or (name
in self
.tabs
):
754 self
.enter_dir(path
or self
.tabs
[name
])
756 self
._update
_current
_tab
()
758 self
.signal_emit('tab.change')
760 def tab_close(self
, name
=None):
762 name
= self
.current_tab
763 if name
== self
.current_tab
:
764 direction
= -1 if name
== self
._get
_tab
_list
()[-1] else 1
765 previous
= self
.current_tab
766 self
.tab_move(direction
)
767 if previous
== self
.current_tab
:
768 return # can't close last tab
769 if name
in self
.tabs
:
772 def tab_move(self
, offset
):
773 assert isinstance(offset
, int)
774 tablist
= self
._get
_tab
_list
()
775 current_index
= tablist
.index(self
.current_tab
)
776 newtab
= tablist
[(current_index
+ offset
) % len(tablist
)]
777 if newtab
!= self
.current_tab
:
778 self
.tab_open(newtab
)
780 def tab_new(self
, path
=None):
781 for i
in range(1, 10):
782 if not i
in self
.tabs
:
783 self
.tab_open(i
, path
)
786 def _get_tab_list(self
):
787 assert len(self
.tabs
) > 0, "There must be >=1 tabs at all times"
788 return sorted(self
.tabs
)
790 def _update_current_tab(self
):
791 self
.tabs
[self
.current_tab
] = self
.env
.cwd
.path
793 # --------------------------
794 # -- Overview of internals
795 # --------------------------
797 def dump_keybindings(self
, *contexts
):
799 contexts
= 'browser', 'console', 'pager', 'taskview'
801 temporary_file
= tempfile
.NamedTemporaryFile()
803 temporary_file
.write(string
.encode('utf-8'))
805 def recurse(before
, pointer
):
806 for key
, value
in pointer
.items():
807 keys
= before
+ [key
]
808 if isinstance(value
, dict):
811 write("%12s %s\n" % (construct_keybinding(keys
), value
))
813 for context
in contexts
:
814 write("Keybindings in `%s'\n" % context
)
815 if context
in self
.env
.keymaps
:
816 recurse([], self
.env
.keymaps
[context
])
821 temporary_file
.flush()
822 self
.run(app
='pager', files
=[File(temporary_file
.name
)])
824 def dump_commands(self
):
825 temporary_file
= tempfile
.NamedTemporaryFile()
827 temporary_file
.write(string
.encode('utf-8'))
830 for cmd_name
in sorted(self
.commands
.commands
):
831 cmd
= self
.commands
.commands
[cmd_name
]
832 if hasattr(cmd
, '__doc__') and cmd
.__doc
__:
833 write(cleandoc(cmd
.__doc
__))
834 write("\n\n" + "-" * 60 + "\n")
836 undocumented
.append(cmd
)
839 write("Undocumented commands:\n\n")
840 for cmd
in undocumented
:
841 write(" :%s\n" % cmd
.get_name())
843 temporary_file
.flush()
844 self
.run(app
='pager', files
=[File(temporary_file
.name
)])
846 def dump_settings(self
):
847 from ranger
.container
.settingobject
import ALLOWED_SETTINGS
848 temporary_file
= tempfile
.NamedTemporaryFile()
850 temporary_file
.write(string
.encode('utf-8'))
852 for setting
in sorted(ALLOWED_SETTINGS
):
853 write("%30s = %s\n" % (setting
, getattr(self
.settings
, setting
)))
855 temporary_file
.flush()
856 self
.run(app
='pager', files
=[File(temporary_file
.name
)])
858 # --------------------------
859 # -- File System Operations
860 # --------------------------
863 self
.env
.copy
= set()
865 self
.ui
.browser
.main_column
.request_redraw()
867 def copy(self
, mode
='set', narg
=None, dirarg
=None):
868 """Copy the selected items. Modes are: 'set', 'add', 'remove'."""
869 assert mode
in ('set', 'add', 'remove')
871 if not narg
and not dirarg
:
872 selected
= (f
for f
in self
.env
.get_selection() if f
in cwd
.files
)
874 if not dirarg
and narg
:
875 direction
= Direction(down
=1)
878 direction
= Direction(dirarg
)
880 pos
, selected
= direction
.select(
881 override
=narg
, lst
=cwd
.files
, current
=cwd
.pointer
,
882 pagesize
=self
.env
.termsize
[0], offset
=offset
)
884 cwd
.correct_pointer()
886 self
.env
.copy
= set(selected
)
888 self
.env
.copy
.update(set(selected
))
889 elif mode
== 'remove':
890 self
.env
.copy
.difference_update(set(selected
))
892 self
.ui
.browser
.main_column
.request_redraw()
894 def cut(self
, mode
='set', narg
=None, dirarg
=None):
895 self
.copy(mode
=mode
, narg
=narg
, dirarg
=dirarg
)
897 self
.ui
.browser
.main_column
.request_redraw()
899 def paste_symlink(self
, relative
=False):
900 copied_files
= self
.env
.copy
901 for f
in copied_files
:
904 relative_symlink(f
.path
, join(getcwd(), f
.basename
))
906 symlink(f
.path
, join(getcwd(), f
.basename
))
907 except Exception as x
:
910 def paste_hardlink(self
):
911 for f
in self
.env
.copy
:
913 link(f
.path
, join(getcwd(), f
.basename
))
914 except Exception as x
:
917 def paste(self
, overwrite
=False):
918 """Paste the selected items into the current directory"""
919 copied_files
= tuple(self
.env
.copy
)
925 cwd
= self
.env
.get_directory(original_path
)
929 original_path
= cwd
.path
930 one_file
= copied_files
[0]
932 cp_flags
= ['-af', '--']
933 mv_flags
= ['-f', '--']
935 cp_flags
= ['--backup=numbered', '-a', '--']
936 mv_flags
= ['--backup=numbered', '--']
939 self
.env
.copy
.clear()
941 if len(copied_files
) == 1:
942 descr
= "moving: " + one_file
.path
944 descr
= "moving files from: " + one_file
.dirname
945 obj
= CommandLoader(args
=['mv'] + mv_flags \
946 + [f
.path
for f
in copied_files
] \
947 + [cwd
.path
], descr
=descr
)
949 if len(copied_files
) == 1:
950 descr
= "copying: " + one_file
.path
952 descr
= "copying files from: " + one_file
.dirname
953 if not overwrite
and len(copied_files
) == 1 \
954 and one_file
.dirname
== cwd
.path
:
956 # copying a file onto itself -> create a backup
957 obj
= CommandLoader(args
=['cp', '-f'] + cp_flags \
958 + [one_file
.path
, one_file
.path
], descr
=descr
)
960 obj
= CommandLoader(args
=['cp'] + cp_flags \
961 + [f
.path
for f
in copied_files
] \
962 + [cwd
.path
], descr
=descr
)
964 obj
.signal_bind('after', refresh
)
968 # XXX: warn when deleting mount points/unseen marked files?
969 self
.notify("Deleting!")
970 selected
= self
.env
.get_selection()
971 self
.env
.copy
-= set(selected
)
974 if isdir(f
.path
) and not os
.path
.islink(f
.path
):
976 shutil
.rmtree(f
.path
)
977 except OSError as err
:
982 except OSError as err
:
984 self
.env
.ensure_correct_pointer()
986 def mkdir(self
, name
):
988 os
.mkdir(os
.path
.join(self
.env
.cwd
.path
, name
))
989 except OSError as err
:
992 def rename(self
, src
, dest
):
993 if hasattr(src
, 'path'):
997 os
.renames(src
, dest
)
998 except OSError as err
: