summary refs log tree commit diff stats
path: root/README
blob: 0b0cf9a7830677e0094600ef7b20cce002c3461a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
ranger v.1.5.5
==============
ranger is a console file manager with VI key bindings.  It provides a
minimalistic and nice curses interface with a view on the directory hierarchy.
The secondary task of ranger is to figure out which program you want to use to
open your files with.

This file describes ranger and how to get it to run.  For instructions on the
usage, please read the man page.  See doc/HACKING for development specific
information.  For configuration, check the files in ranger/config/.  They
are usually installed to /usr/lib/python*/site-packages/ranger/config/
and can be obtained with ranger's --copy-config option.

A note to packagers:  Versions meant for packaging are listed in the changelog
on the website.


About
-----
* Authors:     Check the copyright notices in each source file
* License:     GNU General Public License Version 3

* Website:     http://ranger.nongnu.org/
* Download:    http://ranger.nongnu.org/ranger-stable.tar.gz
* Bug reports: https://savannah.nongnu.org/bugs/?group=ranger&func=additem
* git clone    http://git.sv.gnu.org/r/ranger.git


Design Goals
------------
* An easily maintainable file manager in a high level language
* A quick way to switch directories and browse the file system
* Keep it small but useful, do one thing and do it well
* Console based, with smooth integration into the unix shell


Features
--------
* UTF-8 Support  (if your python copy supports it)
* Multi-column display
* Preview of the selected file/directory
* Common file operations (create/chmod/copy/delete/...)
* Renaming multiple files at once
* VIM-like console and hotkeys
* Automatically determine file types and run them with correct programs
* Change the directory of your shell after exiting ranger
* Tabs, Bookmarks, Mouse support


Dependencies
------------
* Python (tested with version 2.6, 2.7, 3.1, 3.2) with support for ncurses
  and (optionally) wide-unicode.
* A pager ("less" by default)

Optional:
* The "file" program for determining file types
* The python module "chardet", in case of encoding detection problems
* "sudo" to use the "run as root"-feature

Optional, for enhanced file previews (with "scope.sh"):
* img2txt (from caca-utils) for previewing images
* highlight for syntax highlighting of code
* atool for previews of archives
* lynx, w3m or elinks for previews of html pages
* pdftotext for pdf previews
* transmission-show for viewing bit-torrent information
* mediainfo for viewing information about media files


Installing
----------
Use the package manager of your operating system to install ranger.
Note that ranger can be started without installing by simply running ranger.py.

To install ranger manually:
    sudo make install

This translates roughly to:
    sudo python setup.py install --optimize=1 --record=install_log.txt

This also saves a list of all installed files to install_log.txt, which you can
use to uninstall ranger.


Getting Started
---------------
After starting ranger, you can use the Arrow Keys (or hjkl) to navigate, Enter
to open a file or type Q to quit.  The third column shows a preview of the
current file.  The second is the main column and the first shows the parent
directory.

Ranger can automatically copy default configuration files to ~/.config/ranger
if you run it with the switch --copy-config. (see ranger --help for a
description of that switch.)  Also check ranger/config/ for the default
configuration.
a id='n358' href='#n358'>358 359 360 361 362 363 364 365 366
//: Clean syntax to manipulate and check the screen in scenarios.
//: Instructions 'assume-screen' and 'screen-should-contain' implicitly create
//: a variable called 'screen' that is accessible inside other 'run'
//: instructions in the scenario. 'screen-should-contain' can check unicode
//: characters in the fake screen

:(scenarios run_mu_scenario)
:(scenario screen_in_scenario)
scenario screen-in-scenario [
#?   $start-tracing #? 2
  assume-screen 5:literal/width, 3:literal/height
  run [
    screen:address <- print-character screen:address, 97:literal  # 'a'
  ]
  screen-should-contain [
  #  01234
    .a    .
    .     .
    .     .
  ]
#?   $exit #? 1
]

:(scenario screen_in_scenario_unicode)
scenario screen-in-scenario-unicode-color [
  assume-screen 5:literal/width, 3:literal/height
  run [
    screen:address <- print-character screen:address, 955:literal/greek-small-lambda, 1:literal/red
    screen:address <- print-character screen:address, 97:literal/a
  ]
  screen-should-contain [
  #  01234
    .λa   .
    .     .
    .     .
  ]
#?   $exit
]

:(scenario screen_in_scenario_color)
# screen-should-contain can check unicode characters in the fake screen
scenario screen-in-scenario-color [
  assume-screen 5:literal/width, 3:literal/height
  run [
    screen:address <- print-character screen:address, 955:literal/greek-small-lambda, 1:literal/red
    screen:address <- print-character screen:address, 97:literal/a, 7:literal/white
  ]
  # screen-should-contain shows everything
  screen-should-contain [
  #  01234
    .λa   .
    .     .
    .     .
  ]
  # screen-should-contain-in-color filters out everything except the given
  # color, all you see is the 'a' in white.
  screen-should-contain-in-color 7:literal/white, [
  #  01234
    . a   .
    .     .
    .     .
  ]
  # ..and the λ in red.
  screen-should-contain-in-color 1:literal/red, [
  #  01234
    .λ    .
    .     .
    .     .
  ]
#?   $exit
]

:(scenario screen_in_scenario_error)
% Scenario_testing_scenario = true;
% Hide_warnings = true;
scenario screen-in-scenario-error [
  assume-screen 5:literal/width, 3:literal/height
  run [
    screen:address <- print-character screen:address, 97:literal  # 'a'
  ]
  screen-should-contain [
  #  01234
    .b    .
    .     .
    .     .
  ]
]
+warn: expected screen location (0, 0) to contain 98 ('b') instead of 97 ('a')

:(scenario screen_in_scenario_color_error)
% Scenario_testing_scenario = true;
% Hide_warnings = true;
# screen-should-contain can check unicode characters in the fake screen
scenario screen-in-scenario-color [
  assume-screen 5:literal/width, 3:literal/height
  run [
    screen:address <- print-character screen:address, 97:literal/a, 1:literal/red
  ]
  screen-should-contain-in-color 2:literal/green, [
  #  01234
    .a    .
    .     .
    .     .
  ]
]
+warn: expected screen location (0, 0) to be in color 2 instead of 1

//: allow naming just for 'screen'
:(before "End is_special_name Cases")
if (s == "screen") return true;

:(scenarios run)
:(scenario convert_names_does_not_warn_when_mixing_special_names_and_numeric_locations)
% Scenario_testing_scenario = true;
% Hide_warnings = true;
recipe main [
  screen:number <- copy 1:number
]
-warn: mixing variable names and numeric addresses in main
$warn: 0
:(scenarios run_mu_scenario)

:(before "End Globals")
// Scenarios may not define default-space, so they should fit within the
// initial area of memory reserved for tests. We'll put the predefined
// variables available to them at the end of that region.
const long long int Max_variables_in_scenarios = Reserved_for_tests-100;
long long int Next_predefined_global_for_scenarios = Max_variables_in_scenarios;
:(before "End Setup")
assert(Next_predefined_global_for_scenarios < Reserved_for_tests);
:(after "transform_all()" following "case RUN:")
// There's a restriction on the number of variables 'run' can use, so that
// it can avoid colliding with the dynamic allocator in case it doesn't
// initialize a default-space.
assert(Name[tmp_recipe.at(0)][""] < Max_variables_in_scenarios);

:(before "End Globals")
// Scenario Globals.
const long long int SCREEN = Next_predefined_global_for_scenarios++;
// End Scenario Globals.
:(before "End Special Scenario Variable Names(r)")
Name[r]["screen"] = SCREEN;

:(before "End Rewrite Instruction(curr)")
// rewrite `assume-screen width, height` to
// `screen:address <- new-fake-screen width, height`
//? cout << "before: " << curr.to_string() << '\n'; //? 1
if (curr.name == "assume-screen") {
  curr.operation = Recipe_ordinal["new-fake-screen"];
  curr.name = "new-fake-screen";
  assert(curr.operation);
  assert(curr.products.empty());
  curr.products.push_back(reagent("screen:address"));
  curr.products.at(0).set_value(SCREEN);
//? cout << "after: " << curr.to_string() << '\n'; //? 1
//? cout << "AAA " << Recipe_ordinal["new-fake-screen"] << '\n'; //? 1
}

//: screen-should-contain is a regular instruction
:(before "End Primitive Recipe Declarations")
SCREEN_SHOULD_CONTAIN,
:(before "End Primitive Recipe Numbers")
Recipe_ordinal["screen-should-contain"] = SCREEN_SHOULD_CONTAIN;
:(before "End Primitive Recipe Implementations")
case SCREEN_SHOULD_CONTAIN: {
  if (!Passed) break;
  check_screen(current_instruction().ingredients.at(0).name, -1);
  break;
}

:(before "End Primitive Recipe Declarations")
SCREEN_SHOULD_CONTAIN_IN_COLOR,
:(before "End Primitive Recipe Numbers")
Recipe_ordinal["screen-should-contain-in-color"] = SCREEN_SHOULD_CONTAIN_IN_COLOR;
:(before "End Primitive Recipe Implementations")
case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
  if (!Passed) break;
  assert(scalar(ingredients.at(0)));
  check_screen(current_instruction().ingredients.at(1).name, ingredients.at(0).at(0));
  break;
}

:(before "End Types")
// scan an array of characters in a unicode-aware, bounds-checked manner
struct raw_string_stream {
  long long int index;
  const long long int max;
  const char* buf;

  raw_string_stream(const string&);
  uint32_t get();  // unicode codepoint
  uint32_t peek();  // unicode codepoint
  bool at_end() const;
  void skip_whitespace_and_comments();
};

:(code)
void check_screen(const string& expected_contents, const int color) {
//?   cerr << "Checking screen for color " << color << "\n"; //? 2
  assert(!Current_routine->calls.front().default_space);  // not supported
  long long int screen_location = Memory[SCREEN];
  int data_offset = find_element_name(Type_ordinal["screen"], "data");
  assert(data_offset >= 0);
  long long int screen_data_location = screen_location+data_offset;  // type: address:array:character
  long long int screen_data_start = Memory[screen_data_location];  // type: array:character
  int width_offset = find_element_name(Type_ordinal["screen"], "num-columns");
  long long int screen_width = Memory[screen_location+width_offset];
  int height_offset = find_element_name(Type_ordinal["screen"], "num-rows");
  long long int screen_height = Memory[screen_location+height_offset];
  raw_string_stream cursor(expected_contents);
  // todo: too-long expected_contents should fail
  long long int addr = screen_data_start+1;  // skip length
//?   cerr << "screen height " << screen_height << '\n'; //? 1
  for (long long int row = 0; row < screen_height; ++row) {
//?     cerr << "row: " << row << '\n'; //? 3
//?     cerr << "contents: " << cursor.buf+cursor.index << "$\n"; //? 1
    cursor.skip_whitespace_and_comments();
    if (cursor.at_end()) break;
//?     cerr << "row2\n"; //? 2
    assert(cursor.get() == '.');
    for (long long int column = 0;  column < screen_width;  ++column, addr+= /*size of screen-cell*/2) {
      const int cell_color_offset = 1;
      uint32_t curr = cursor.get();
//?       cerr << "col: " << column << '\n'; //? 1
      if (Memory[addr] == 0 && isspace(curr)) continue;
//?       cerr << color << " vs " << Memory[addr+1] << '\n'; //? 1
      if (curr == ' ' && color != -1 && color != Memory[addr+cell_color_offset]) {
        // filter out other colors
        continue;
      }
//?       cerr << "col3 " << column << ": " << Memory[addr] << " " << curr << '\n'; //? 1
      if (Memory[addr] != 0 && Memory[addr] == curr) {
//?         cerr << "col4\n"; //? 1
        if (color == -1 || color == Memory[addr+cell_color_offset]) continue;
//?         cerr << "col5: " << column << '\n'; //? 1
        // contents match but color is off
        if (Current_scenario && !Scenario_testing_scenario) {
          // genuine test in a mu file
          raise << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ", address " << addr << ", value " << Memory[addr] << ") to be in color " << color << " instead of " << Memory[addr+cell_color_offset] << "\n";
        }
        else {
          // just testing check_screen
          raise << "expected screen location (" << row << ", " << column << ") to be in color " << color << " instead of " << Memory[addr+cell_color_offset] << '\n';
        }
        if (!Scenario_testing_scenario) {
          Passed = false;
          ++Num_failures;
        }
        return;
      }

//?       cerr << "col6 " << column << ": " << Memory[addr] << " " << curr << '\n'; //? 1
      // really a mismatch
      // can't print multi-byte unicode characters in warnings just yet. not very useful for debugging anyway.
      char expected_pretty[10] = {0};
      if (curr < 256 && !iscntrl(curr)) {
        // " ('<curr>')"
        expected_pretty[0] = ' ', expected_pretty[1] = '(', expected_pretty[2] = '\'', expected_pretty[3] = static_cast<unsigned char>(curr), expected_pretty[4] = '\'', expected_pretty[5] = ')', expected_pretty[6] = '\0';
      }
      char actual_pretty[10] = {0};
      if (Memory[addr] < 256 && !iscntrl(Memory[addr])) {
        // " ('<curr>')"
        actual_pretty[0] = ' ', actual_pretty[1] = '(', actual_pretty[2] = '\'', actual_pretty[3] = static_cast<unsigned char>(Memory[addr]), actual_pretty[4] = '\'', actual_pretty[5] = ')', actual_pretty[6] = '\0';
      }

      if (Current_scenario && !Scenario_testing_scenario) {
        // genuine test in a mu file
        raise << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << " instead of " << Memory[addr] << actual_pretty << '\n';
        dump_screen();
      }
      else {
        // just testing check_screen
        raise << "expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << " instead of " << Memory[addr] << actual_pretty << '\n';
      }
      if (!Scenario_testing_scenario) {
        Passed = false;
        ++Num_failures;
      }
      return;
    }
    assert(cursor.get() == '.');
  }
  cursor.skip_whitespace_and_comments();
  assert(cursor.at_end());
}

raw_string_stream::raw_string_stream(const string& backing) :index(0), max(backing.size()), buf(backing.c_str()) {}

bool raw_string_stream::at_end() const {
//?   cerr << index << ' ' << max << '\n'; //? 1
//?   cerr << buf << "$\n"; //? 1
  if (index >= max) return true;
  if (tb_utf8_char_length(buf[index]) > max-index) {
    raise << "unicode string seems corrupted at index "<< index << " character " << static_cast<int>(buf[index]) << '\n';
    return true;
  }
  return false;
}

uint32_t raw_string_stream::get() {
  assert(index < max);  // caller must check bounds before calling 'get'
  uint32_t result = 0;
  int length = tb_utf8_char_to_unicode(&result, &buf[index]);
  assert(length != TB_EOF);
  index += length;
  return result;
}

uint32_t raw_string_stream::peek() {
  assert(index < max);  // caller must check bounds before calling 'get'
  uint32_t result = 0;
  int length = tb_utf8_char_to_unicode(&result, &buf[index]);
  assert(length != TB_EOF);
  return result;
}

void raw_string_stream::skip_whitespace_and_comments() {
  while (!at_end()) {
    if (isspace(peek())) get();
    else if (peek() == '#') {
      // skip comment
      get();
      while (peek() != '\n') get();  // implicitly also handles CRLF
    }
    else break;
  }
}

:(before "End Primitive Recipe Declarations")
_DUMP_SCREEN,
:(before "End Primitive Recipe Numbers")
Recipe_ordinal["$dump-screen"] = _DUMP_SCREEN;
:(before "End Primitive Recipe Implementations")
case _DUMP_SCREEN: {
  dump_screen();
  break;
}

:(code)
void dump_screen() {
  assert(!Current_routine->calls.front().default_space);  // not supported
  long long int screen_location = Memory[SCREEN];
  int width_offset = find_element_name(Type_ordinal["screen"], "num-columns");
  long long int screen_width = Memory[screen_location+width_offset];
  int height_offset = find_element_name(Type_ordinal["screen"], "num-rows");
  long long int screen_height = Memory[screen_location+height_offset];
  int data_offset = find_element_name(Type_ordinal["screen"], "data");
  assert(data_offset >= 0);
  long long int screen_data_location = screen_location+data_offset;  // type: address:array:character
  long long int screen_data_start = Memory[screen_data_location];  // type: array:character
//?   cerr << "data start: " << screen_data_start << '\n'; //? 1
  assert(Memory[screen_data_start] == screen_width*screen_height);
  long long int curr = screen_data_start+1;  // skip length
  for (long long int row = 0; row < screen_height; ++row) {
//?     cerr << curr << ":\n"; //? 2
    cerr << '.';
    for (long long int col = 0; col < screen_width; ++col) {
      if (Memory[curr])
        cerr << to_unicode(Memory[curr]);
      else
        cerr << ' ';
      curr += /*size of screen-cell*/2;
    }
    cerr << ".\n";
  }
}