lessの実装
Posted feedbacks - Nested
Flatten Hidden
コマンド f, b, /, n, N, q を受け付けます。
文字列のリストに全部ぶっこんでから表示してるだけ。
「巨大なファイルでも~」というのは置いといて、、、
(3万行くらいなら余裕ぽい)
文字列のリストに全部ぶっこんでから表示してるだけ。
「巨大なファイルでも~」というのは置いといて、、、
(3万行くらいなら余裕ぽい)
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 | using System;
using System.Collections.Generic;
using System.IO;
class MyLess
{
int height = 20;
List<string> lines = new List<string>();
int pos = 0;
string search = null;
MyLess(TextReader tr) {
string line;
while ((line = tr.ReadLine()) != null)
lines.Add(line);
tr.Close();
}
void Show() {
Console.Clear();
for (int i = 0 ; i < height && (i + pos) < lines.Count ; i++)
Console.WriteLine("{0,5}{1} {2}", i + pos + 1, i == 0 ? '>' : '|', lines[i + pos]);
}
void Forward() {
pos = pos + height > lines.Count ? lines.Count - 1 : pos + height;
}
void Back() {
pos = pos < height ? 0 : pos - height;
}
void Find(bool forward) {
if (search == null) return;
bool found = false;
if (forward) {
for (int i = pos + 1 ; i < lines.Count ; i++) {
if (lines[i].Contains(search)) {
found = true; pos = i; break;
}
}
} else {
for (int i = pos - 1 ; i >= 0 ; i--) {
if (lines[i].Contains(search)) {
found = true; pos = i; break;
}
}
}
Show();
if (found)
Console.Write("{0}{1}", forward ? '/' : '?', search);
else
Console.Write("見つかりません");
}
void Do() {
Show();
while (true) {
ConsoleKeyInfo key = Console.ReadKey(true);
switch (key.KeyChar) {
case 'q': return;
case 'f': Forward(); Show(); break;
case 'b': Back(); Show(); break;
case '/':
Console.Write('/');
search = Console.ReadLine().Replace("\n", "");
if (search.Length == 0)
search = null;
Find(true);
break;
case 'n': Find(true); break;
case 'N': Find(false); break;
default: break;
}
}
}
static void Main(string[] args) {
MyLess less;
if (args.Length == 0)
less = new MyLess(Console.In);
else
less = new MyLess(new StreamReader(args[0]));
less.Do();
}
}
|
なお、動かし方は
myless.exe ファイル名
です。
dir | myless.exe
のようにすると例外がでました。(ReadKeyがまずかったらしい)
myless.exe ファイル名
です。
dir | myless.exe
のようにすると例外がでました。(ReadKeyがまずかったらしい)
反則ですが。
1 | less <- function(f) file.show(f, pager="less")
|
Squeak Smalltalk で。
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 | | file font nLines cr sp goNextPage goPrevPage findNext prevLine |
file := FileStream fileNamed: 'in.txt'.
font := TextStyle defaultFont.
cr := Character cr.
sp := Character space.
nLines := Display height // (font height + font descent).
goNextPage := [nLines timesRepeat: [file nextLine]].
prevLine := [file binary. [file position = 0 or: [file back = 13]] whileFalse. file ascii].
goPrevPage := [nLines timesRepeat: [prevLine value]].
findNext := [:pattern | pattern notEmpty ifTrue: [file upToAll: pattern]. prevLine value].
[ | string |
string := String streamContents: [:ss |
| position |
position := file position.
nLines timesRepeat: [
file nextLine ifNotNilDo: [:line | ss nextPutAll: line].
file peek ifNotNil: [ss cr]].
file position: position].
Display fillWhite.
string asDisplayText display.
[Sensor keyboardPressed] whileFalse.
Sensor keyboard caseOf: {
[$q] -> [file close. ^World restoreDisplay].
[$g] -> [file reset].
[$G] -> [file setToEnd].
[sp] -> [goNextPage value].
[$f] -> [goNextPage value].
[$b] -> [goPrevPage value].
[cr] -> [file nextLine].
[$e] -> [file nextLine].
[$y] -> [prevLine value].
[$/] -> [findNext value: (FillInTheBlank request: 'pattern:')]
} otherwise: []
] repeat
|
環境に依存するかもしれませんが、書いてみました。
Gentoo Linuxで動作確認してます。
% ruby -v
ruby 1.8.6 (2008-06-20 patchlevel 230) [i686-linux]
less準拠でjk/qが使えます。
Gentoo Linuxで動作確認してます。
% ruby -v
ruby 1.8.6 (2008-06-20 patchlevel 230) [i686-linux]
less準拠でjk/qが使えます。
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 | #!/usr/bin/env ruby
require 'curses'
lines = ARGF.readlines
index = 0
LINES = 10
Curses::init_screen
def update(index, lines)
(index..index+LINES).each do |l|
Curses::stdscr.setpos(l - index, 0)
Curses::addstr lines[l]
end
end
def search(index, lines)
word = ''
while (ch = Curses::getch) != 0x0A do
word += ch.chr
end
(index..(lines.size)).each do |l|
if lines[l].match(word) then
return l
end
end
return nil
end
update(index, lines)
while ch = Curses::getch do
case ch
when ?j
index += 1 if index < lines.length - LINES
when ?k
index -= 1 if 0 < index
when ?/
index = search(index, lines) || index
when ?q
break
end
update(index, lines)
end
|
効率はともかく、巨大なファイルでも表示できるように作ってみました。
表示はcurses、ファイルの行インデックススキャンをバックグラウンドで行うためにpthreadを使用しています。
行インデックスもテンポラリファイルとして書き出しているので、fpos_tが32bitの環境でも2GB、64bitなら8EBまでいける(自信なし)はずです。
ただし、システム側に懲りすぎたので、タブやら一行の折り返しやらは手を抜いて全く手付かずです。
検索機能もUIが手抜きのためインクリメンタルサーチしかできません。
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 | /*
Large less
programed by M.Suzuki
ver 0.1 2008/8/4
key binding
n,j,^n scroll up
p,k,^p scroll down
/ i-search
ESC i-search cancel
TAB next search(i-search only)
q quit
*/
#include <stdio.h>
#include <string.h>
#include <curses.h>
#include <pthread.h>
#define LINE_MAX 256 /* file text width max */
#define False 0
#define True (!False)
static pthread_mutex_t file_mutex;
static FILE* fileFp;
static FILE* seekFp;
static fpos_t lineTop;
static fpos_t lineMax;
static bool readMaxFlag;
static bool abortFlag;
static void ScanWait()
{
pthread_mutex_lock(&file_mutex);
while( lineTop + LINES >= lineMax ){
static struct timespec time10ms = {0,10*1000*1000};
pthread_mutex_unlock(&file_mutex);
nanosleep(&time10ms,NULL);
pthread_mutex_lock(&file_mutex);
if( readMaxFlag ){
break;
}
} /* end of while */
pthread_mutex_unlock(&file_mutex);
}
static char* ReadLine(int y, char* buff)
{
char* text = NULL;
ScanWait();
pthread_mutex_lock(&file_mutex);
if( lineTop + y + 1 < lineMax ){
fpos_t offset = sizeof(fpos_t)*(lineTop+y);
fsetpos(seekFp,&offset);
fread(&offset,sizeof(fpos_t),1,seekFp);
fsetpos(fileFp,&offset);
if( fgets(buff,LINE_MAX,fileFp) ){
text = buff;
}
}
pthread_mutex_unlock(&file_mutex);
return text;
}
static void DrawLine(int y)
{
char* text;
char buff[LINE_MAX+1];
text = ReadLine(y,buff);
if( text == NULL ){
text = "~";
}
mvinsstr(y,0,text);
}
static void ViewAll()
{
int y;
erase();
for(y=0;y<LINES;y++){
DrawLine(y);
}
}
static void RollUp()
{
ScanWait();
pthread_mutex_lock(&file_mutex);
if( lineTop >= lineMax ){
if( readMaxFlag ){
pthread_mutex_unlock(&file_mutex);
return;
}
}
lineTop++;
pthread_mutex_unlock(&file_mutex);
move(0,0);
deleteln();
move(LINES-1,0);
DrawLine(LINES-1);
refresh();
}
static void RollDown()
{
pthread_mutex_lock(&file_mutex);
if( lineTop <= 0 ){
pthread_mutex_unlock(&file_mutex);
return;
}
lineTop--;
pthread_mutex_unlock(&file_mutex);
move(0,0);
insdelln(1);
DrawLine(0);
refresh();
}
static void Search()
{
char search[LINE_MAX];
int len = 0;
while(1){
int key = getch();
int y = 0;
if( key == 0x1b ){
break;
}
if( key == '\t' ){
y = 1;
} else {
if( len < LINE_MAX ){
search[len++] = key;
search[len] = '\0';
}
}
while(1){
char buff[LINE_MAX+1];
if( ReadLine(y,buff) == NULL ){
return;
}
if( strstr(buff,search) ){
pthread_mutex_lock(&file_mutex);
lineTop += y;
pthread_mutex_unlock(&file_mutex);
ViewAll();
break;
}
y++;
} /* end of while */
} /* end of while */
}
static void KeyLoop()
{
ViewAll();
while(1){
int key = getch();
if( key == 'q' ){
break;
}
switch(key){
case 'N'-'@':
case 'n':
case 'j':
RollUp();
break;
case 'P'-'@':
case 'p':
case 'k':
RollDown();
break;
case '/':
Search();
break;
} /* end of switch */
} /* end of while */
}
void* ScanThread(void* arg)
{
fpos_t filePos;
fgetpos(fileFp, &filePos);
while(1){
char buff[LINE_MAX+1];
fpos_t fpos = sizeof(fpos_t)*lineMax;
pthread_mutex_lock(&file_mutex);
fsetpos(seekFp,&fpos);
fwrite(&filePos,sizeof(fpos_t),1,seekFp);
lineMax++;
fsetpos(fileFp,&filePos);
if( fgets(buff,LINE_MAX,fileFp) == NULL ){
break;
}
fgetpos(fileFp,&filePos);
if( abortFlag ){
break;
}
pthread_mutex_unlock(&file_mutex);
} /* end of while */
readMaxFlag = True;
pthread_mutex_unlock(&file_mutex);
return NULL;
}
static void MainLoop()
{
pthread_t scanThread_id;
char tmpName[L_tmpnam];
tmpnam(tmpName);
if( (seekFp=fopen(tmpName,"w+b")) == NULL ){
perror(tmpName);
return;
}
pthread_mutex_init(&file_mutex,NULL);
lineTop = 0;
lineMax = 0;
if( pthread_create(&scanThread_id,NULL,ScanThread,NULL)!=0){
perror("ScanThread");
return;
}
initscr();
noecho();
raw();
cbreak();
KeyLoop();
nocbreak();
noraw();
echo();
endwin();
pthread_mutex_lock(&file_mutex);
abortFlag = True;
pthread_mutex_unlock(&file_mutex);
pthread_join(scanThread_id,NULL);
fclose(seekFp);
remove(tmpName);
}
int main(int argc, char* argv[])
{
char* fname = NULL;
int i;
for(i=1;i<argc;i++){
char* p = argv[i];
if( *p == '-' ){
/* option */
} else {
fname = argv[i];
}
} /* end of for */
if( fname != NULL ){
if( (fileFp=fopen(fname,"r")) == NULL ){
perror(fname);
return 1;
}
MainLoop();
fclose(fileFp);
}
return 0;
}
|




takeru #6817() Rating-7/7=-1.00
'less'を実装してください。
巨大なファイルでも効率的に動作するようにしてください。
最低限必要な機能は、
です。
[ reply ]