about summary refs log tree commit diff stats
path: root/code/fm.rb
blob: 307866f8ffa1770d622376ab47596dc613d76ee7 (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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
OPTIONS = {
	'hidden' => false,
}

module Fm
	def self.initialize
		@buffer = ''
		@pwd = ''
		@copy = []
		@ignore_until = nil
		@trash = File.expand_path('~/.trash')
		pwd = Dir.getwd

		@memory = {
			'`' => pwd,
			'\'' => pwd
		}

		@fmrc = File.expand_path('~/.fmrc')
		if (File.exists?(@fmrc))
			loaded = Marshal.load(File.read(@fmrc))
			if Hash === loaded
				@memory.update(loaded)
			end
		end

		@memory['0'] = pwd

		@dirs = Hash.new() do |hash, key|
			hash[key] = Directory.new(key)
		end

		@path = [@dirs['/']]
		enter_dir(Dir.pwd)
	end
	attr_reader(:dirs, :pwd)

	VI = "vi -c 'map h :quit<CR>' -c 'map q :quit<CR>' -c 'map H :unmap h<CR>:unmap H<CR>' %s"

	def self.dump
		remember_dir
		dumped = Marshal.dump(@memory)
		File.open(@fmrc, 'w') do |f|
			f.write(dumped)
		end
	end

	def self.rescue_me
		@buffer = ''
		sleep 0.2
	end

	def self.main_loop
		while true
			begin
				draw()
			rescue Interrupt
				rescue_me
			end
			begin
				press(geti)
			rescue Interrupt
				rescue_me
			end
		end
	end

	def self.current_path() @pwd.path end

	def self.enter_dir(dir)
		dir = File.expand_path(dir)

		oldpath = @path.dup

		# NOTE: @dirs[unknown] is not nil but Directory.new(unknown)
		@path = [@dirs['/']]
		unless dir == '/'
			dir.slice(0)
			accumulated = '/'
			for part in dir.split('/')
				unless part.empty?
					accumulated = File.join(accumulated, part)
					@path << @dirs[accumulated]
				end
			end
		end
		@pwd = @path.last
		set_title "fm: #{@pwd.path}"

		if @path.size < oldpath.size
			@pwd.pos = @pwd.files.index(oldpath.last.path) || 0
		end

		i = 0

		@path.each_with_index do |p, i|
			p.refresh
			unless i == @path.size - 1
				p.pointed_file = @path[i+1].path
			end
		end

		Dir.chdir(@pwd.path)
	end

	def self.currentfile
		@dirs[@currentdir][1][@dirs[@currentdir][0] || 0]
	end
	def self.currentfile() @pwd.files.at(@pwd.pos) 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

	COLUMNS = 4

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

		when 2
			q = cols / 4
			w = @path.last.width.limit(cols/2, cols/8) + 1
			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.put_directory(c, d)
		l = 0
		if d
			infos = (c == COLUMNS - 2)
			left, wid = get_boundaries(c)

			offset = get_offset(d, lines)
			(lines - 1).times do |l|
				lpo = l + offset
				bg = -1
				break if (f = d.files[lpo]) == nil

				dir = false
				if File.symlink?(f)
					bld = true
					if File.exists?(f)
						clr = [6, bg]
					else
						clr = [1, bg]
					end
					dir = File.directory?(f)
				elsif File.directory?(f)
					bld = true
					dir = true
					clr = [4, bg]
				elsif File.executable?(f)
					bld = true
					clr = [2, bg]
				else
					bld = false
					clr = [7, bg]
				end

				fn = File.basename(f)
				if infos
					myinfo = " #{d.infos[lpo]}  "
					str = fn[0, wid-1].ljust(wid)
					if str.size > myinfo.size
						str[-myinfo.size..-1] = myinfo
						yes = true
					else
						yes = false
					end
					puti l+1, left, str
					if dir and yes
						args = l+1, left+wid-myinfo.size, myinfo.size, *clr
						color_bold_at(*args)
					end
				else
					puti l+1, left, fn[0, wid-1].ljust(wid+1)
				end

				args = l+1, left, fn.size.limit(wid-1), *clr

				if d.pos == lpo
					color_reverse_at(*args)
				else
					if bld then color_bold_at(*args) else color_at(*args) end
				end
			end
		end

		column_clear(c, l)
	end

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

	def self.column_put_file(n, file)
		m = lines - 2
		i = 0
		color 7
		bold false
		File.open(file, '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
		column_clear(n, i)
	end

	def self.draw
		bold false
		@cur_y = get_boundaries(COLUMNS-2)[0]
		@pwd.get_file_infos

		s1 = "  "
		s2 = "#{@path.last.path}#{"/" unless @path.size == 1}"
		f = currentfile
		s3 = "#{f ? File.basename(f) : ''}"
		
		puti 0, (s1 + s2 + s3).ljust(cols)

		bg = -1
		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

		f = currentfile
		begin
			if File.directory?(f)
				put_directory(3, @dirs[currentfile])
			else
				column_put_file(3, currentfile)
			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
		color -1, -1
		puti -1, "#@buffer    #{@pwd.pos+1},#{@pwd.size},#{@path.size}    ".rjust(cols)
		more = ''
		if File.symlink?(currentfile)
			more = "#{File.readlink(currentfile)}"
		end
		puti -1, "  #{Time.now.strftime("%H:%M:%S %a %b %d")}  #{File.modestr(currentfile)} #{more}"

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

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

	def self.enter_dir_safely(dir)
		dir = File.expand_path(dir)
		if File.exists?(dir) and File.directory?(dir)
			olddir = @pwd.path
			begin
				enter_dir(dir)
				return true
			rescue
				enter_dir(olddir)
				return false
			end
		end
	end

	def self.move_to_trash!(fn)
		unless File.exists?(@trash)
			Dir.mkdir(@trash)
		end
		new_path = File.join(@trash, File.basename(fn))

		closei
		system('mv','-v', fn, new_path)
		starti

		return new_path
	end

	def self.in_trash?(fn)
		fn[0,@trash.size] == @trash
	end

	def self.move_to_trash(fn)
		if fn and File.exists?(fn)
			if File.directory?(fn)
				if !in_trash?(fn) and Dir.entries(fn).size > 2
					return move_to_trash!(fn)
				else
					Dir.rmdir(fn) rescue nil
				end
			elsif File.symlink?(fn)
				File.delete(fn)
			else
				if !in_trash?(fn) and File.size?(fn)
					return move_to_trash!(fn)
				else
					File.delete(fn)
				end
			end
		end
		return nil
	end
end