about summary refs log tree commit diff stats
path: root/HACKING.md
blob: e00e7d60a9366de88f8b475e29e8b3b7ddafcdd4 (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
97
98
99
100
101
Guidelines for Code Modification
================================

Coding Style
------------

* Use syntax compatible with Python `2.6+` and `3.1+`.
* Use docstrings with `pydoc` in mind
* Follow the PEP8 style guide: https://www.python.org/dev/peps/pep-0008/
* Always run `make test` before submitting a new PR. `pylint`, `flake8`,
  `pytest`, `doctest` and `shellcheck` need to be installed. (If you don't
  change any shell scripts you can run `make test_py` and you don't need the
  `shellcheck` dependency but it's an awesome tool, so check it out : )
* When breaking backward compatibility with old configuration files or plugins,
  please include a temporary workaround code that provides a compatibility
  layer and mark it with a comment that includes the word `COMPAT`. For
  examples, grep the code for the word `COMPAT`. :)


Patches
-------

Send patches, created with `git format-patch`, to the email address

    ranger-users@nongnu.org

or open a pull request on GitHub.

Please use PGP-encryption for security-relevant patches or messages. PGP key
IDs are shared along with releases on https://ranger.github.io and can be
attempted in reverse chronological order in case a maintainer is unresponsive.

Version Numbering
-----------------

Three numbers, `A.B.C`, where
* `A` changes on a rewrite
* `B` changes when major configuration incompatibilities occur
* `C` changes with each release


Starting Points
---------------

Good places to read about ranger internals are:

* `ranger/core/actions.py`
* `ranger/container/fsobject.py`

About the UI:

* `ranger/gui/widgets/browsercolumn.py`
* `ranger/gui/widgets/view_miller.py`
* `ranger/gui/ui.py`


Common Changes
==============

Adding options
--------------

* Add a default value in `rc.conf`, along with a comment that describes the option.
* Add the option to the `ALLOWED_SETTINGS` dictionary in the file
  `ranger/container/settings.py` in alphabetical order.
* Add an entry in the man page by editing `doc/ranger.pod`, then rebuild the man
  page by running `make man` in the ranger root directory

The setting is now accessible with `self.settings.my_option`, assuming self is a
subclass of `ranger.core.shared.SettingsAware`.


Adding colorschemes
-------------------

* Copy `ranger/colorschemes/default.py` to `ranger/colorschemes/myscheme.py`
  and modify it according to your needs. Alternatively, create a subclass of
  `ranger.colorschemes.default.Default` and override the `use` method, as it is
  done in the `Jungle` colorscheme.

* Add this line to your `~/.config/ranger/rc.conf`:
  `set colorscheme myscheme`


Change which programs start which file types
--------------------------------------------

Edit the configuration file `~/.config/ranger/rifle.conf`. The default one can
be obtained by running `ranger --copy-config rifle`.


Change which file extensions have which mime type
-------------------------------------------------

Modify `ranger/data/mime.types`. You may also add your own entries to `~/.mime.types`


Change which files are previewed in the auto preview
----------------------------------------------------

In `ranger/container/file.py`, change the constant `PREVIEW_BLACKLIST`
ber.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
require 'socket'

module Fm
	DONT_PREVIEW_THESE_FILES = /\.(avi|[mj]pe?g|iso|mp\d|og[gmv]|wm[av]|mkv|torrent|so|class|flv|png|bmp|vob|divx?)$/i

	def column_put_file(n, file)
		i = 0
		if OPTIONS['filepreview'] and file.path !~ DONT_PREVIEW_THESE_FILES
			m = lines - 2
			attr_set(Color.base)
			left, wid = get_boundaries(n)
			if file.ext =~ /(?:rar|zip|7z|tar|gz)$/ and file.size < 10485760
				text = `aunpack -l #{file.sh} 2>> /dev/null`
				text.each_line do |l|
					puti i+1, left, l[0, wid-1].ljust(wid)
					i += 1
					break if i == m
				end
			else
				File.open(file.path, 'r') do |f|
					check = true
					left, wid = get_boundaries(n)
					f.lines.each do |l|
						if check
							check = false
							break unless l.each_char.all? {|x| x[0] > 0 and x[0] < 128}
						end
						puti i+1, left, l.gsub("\t","   ")[0, wid-1].ljust(wid)
						i += 1
						break if i == m
					end
				end
			end
		end
		column_clear(n, i)
	end

	def put_directory(c, d)
		l = 1
		return column_clear(c, 0) unless d

		infos = (c == COLUMNS - 2)
		left, wid = get_boundaries(c)
		right = left + wid
		
		if not d.read?
			if (c == COLUMNS - 1) and @entering_directory
#				puti l, left, "reading...".ljust(wid+1)
				puti l, left, " " * (wid+1)
				column_clear(c, 1)
				@entering_directory = false
			end
			Scheduler << d
			return
		elsif d.read? and d.empty?
			puti l, left, 'empty'.ljust(wid+1)
			column_clear(c, 1)
			return
		end


		offset = get_offset(d, lines)
		(lines - 1).times do |l|
			lpo = l + offset
			l += 1

			break if (f = d.files[lpo]) == nil

			mycolor = if lpo == d.pos
				if infos
					Color.selected_current_row
				else
					Color.selected
				end
			elsif f.marked?
				Color.marked
			else
				Color.normal
			end

			dir = false

			clrname = if f.symlink?
				dir = f.dir?
				if f.broken_symlink?
					:badlink
				else
					:goodlink
				end
			elsif f.dir?
				dir = true
				:directory
			elsif f.movie?
				:video
			elsif f.executable?
				:executable
			else
				:file
			end

			fn = f.basename
			fn = "* #{fn}" if f.marked?


			if infos
				myinfo = " #{f.infostring}  "
				sz = myinfo.size
				str = fn[0, wid-1].ljust(wid+1)
				if str.size > sz
					str[-sz..-1] = myinfo
					yes = true
				else
					yes = false
				end
				puti l, left, str
				attr_at(l, right-sz, sz, Color.normal.send(clrname))
			else
				puti l, left, fn[0, wid-1].ljust(wid+1)
			end

			attr_at(l, left, fn.size.limit(wid-1), mycolor.send(clrname))
		end

		column_clear(c, l-1)
	end

	def self.column_clear(n, from=0)
		attr_set(Color.base)
		left, wid = get_boundaries(n)
		(from -1).upto(lines) do |l|
			puti l+2, left, ' ' * (wid+1)
		end
	end

	def self.get_offset(dir, max)
		pos = dir.pos
		len = dir.files.size
		max -= 2
		if len <= max or pos < max/2
			return 0
		elsif pos >= (len - max/2)
			return len - max
		else
			return pos - max/2
		end
	end

	def self.get_boundaries(column)
		cols = CLI.cols # to cache
		case column
		when 0
			return 0, cols / 8
			
		when 1
			q = cols / 8
			return q, q

		when 2
			q = cols / 4
			w = @path.last.width.limit(cols/2, cols/8)
			return q, w
			
		when 3
			l = cols / 4 + 1
			l += @path.last.width.limit(cols/2, cols/8)

			return l, cols - l
		end
	end

	def self.draw
		attr_set(Color.base)

		@cur_y = get_boundaries(COLUMNS-2)[0]

		if @buffer =~ /^block/
			screensaver
		elsif @buffer == '?'
			cleari
			puti 0, "      - - - Help - - -"
			puti 2, "   h/j/k/l: Movement    J/K: fast Movement"
			puti 3, "   H: Descend directory with respect to symlinks"
			puti 4, "   L: Wait for <Enter> after execution of a program"
			puti 6, "   t: Toggle Option     S: Change Sorting"
			puti 7, "   E: Edit file         s: Enter Shell"
			puti 8, "   rmdir: Remove whole dir  dD: Delete file or empty dir"
			puti 9, "   dd: Move file to ~/.trash and memorize it's new path"
			puti 10,"   yy: Memorize path    p: Copy memorized file here"
			puti 11,"   mv<place>: move file to place  mkdir<name>: obvious"
			puti 12,"   mX: Bookmark dir     'X: Enter bookmarked dir"
			puti 13,"   '': Enter last visited dir (note: ' and ` are equal)"
			puti 13,"   !<command> executes command"
			puti 15,"   To interrupt current operations: <Ctrl-C>"
			puti 16,"   To quit: q / ZZ / <Ctrl-D> / <Ctrl-C><Ctrl-C> (twice in a row)"
			puti 18,"   Press one of those keys for more information: g f"
		elsif @buffer == '?f'
			cleari
			puti 0, "      - - - Help - - -"
			puti 2, "   f<regexp> or /<regexp> searches for pattern and presses l"
			puti 3, "       when a matching file is found."
			puti 4, "       Pressing L in this mode is like pressing l outside"
			puti 6, "   F<regexp> like f but stay in this mode until <esc> is pressed"
		elsif @buffer == '?g'
			cleari
			puti 0, "      - - - Help - - -"
			puti 2, "   gg: go to top"
			puti 3, "   G:  go to bottom"
			puti 4, "   g0: go to /"
			puti 5, "   gu: go to /usr/"
			puti 6, "   gm: go to /media/"
			puti 7, "   ge: go to /etc/"
			puti 8, "   gh: go to ~/"
			puti 9, "   gt: go to ~/.trash/"
		else
			@pwd.recheck_stuff()
			cf = currentfile

			if cf and s0 = cf.mimetype
				puti 0, cols-s0.size, s0
			end

			s1 = ""
			s1 << Socket.gethostname
			s1 << ":"
			s2 = "#{@path.last.path}#{"/" unless @path.size == 1}"
			s3 = "#{cf ? cf.basename : ''}"
			
			if s0
				puti 0, (s1 + s2 + s3).ljust(cols-s0.size)
			else
				puti 0, (s1 + s2 + s3).ljust(cols)
			end

			bg = -1
			attr_at(0, 0, s1.size, *Color.hostname)
			attr_at(0, s1.size, s2.size, *Color.currentdir)
			attr_at(0, s1.size + s2.size, s3.size, *Color.currentfile)
#			color_at 0, 0, -1, 7, bg
#			color_at 0, 0, s1.size, 7, bg
#			color_at 0, s1.size, s2.size, 6, bg
#			color_at 0, s1.size + s2.size, s3.size, 5, bg

#			bold false

			begin
				if cf.dir?
					put_directory(3, @dirs[cf.path])
				elsif cf.file?
					column_put_file(3, cf)
				else
					column_clear(3)
				end
			rescue
				column_clear(3)
			end

			pos_constant = @path.size - COLUMNS + 1

			(COLUMNS - 1).times do |c|
				pos = pos_constant + c

				if pos >= 0
					put_directory(c, @path[pos])
				else
					column_clear(c)
				end
			end

#			bold false
			attr_set(Color.base)
#			color(-1, -1)
			btm = lines - 1

			case @buffer
			when 'S'
				puti btm, "Sort by (n)ame (s)ize (m)time (c)time (CAPITAL:reversed)"
			when 't'
				puti btm, "Toggle (h)idden_files (d)irs_first (c)olor (f)ilepreview"
			else
#				log(@pwd)
#				log "Buffer: #{@buffer}"
				puti btm, "#@buffer    #{@pwd.file_size.bytes(false)},#{@pwd.size},#{@pwd.pos+1}    ".rjust(cols)
				more = ''
				if cf.symlink?
					more = "#{cf.readlink}"
				end
				puti btm, "  #{Time.now.strftime("%H:%M:%S %a %b %d")}  #{cf.rights} #{more}"

#				color_at btm, 23, 10, (cf.writable? ? 6 : 5), -1
				if more
#					color_at btm, 34, more.size, (cf.exists? ? 6 : 1), -1
				end
			end

			draw_bars unless @bars.empty?

			movi(@pwd.pos + 1 - get_offset(@pwd, lines), @cur_y)
		end
		CLI.refresh
	end

	def self.draw_bars()
		@bars.each_with_index do |bar, ix|
			bar.update

			l = -ix - 1
			puti l, bar.text[0..cols-1].ljust(cols)
			done = bar.done
			c = (done * cols).to_i
			unless done == 0
#				color_at l, 0, c, 0, 4
			end
			unless done == cols
#				color_at l, c, -1, 0, 6
			end
		end
	end
end