1 // =================================
  2 // Copyright (c) 2022 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 
  9 void PrintHelp()
 10 {
 11     Console.WriteLine("Usage: less [options] [FILE]...");
 12     Console.WriteLine("View paginated output.");
 13     Console.WriteLine("Options:");
 14     Console.WriteLine("--help | -h");
 15     Console.WriteLine("  Print help and exit.");
 16 }
 17 
 18 int MaxWidth(const List<ustring>& lines)
 19 {
 20     int maxWidth = 0;
 21     for (const ustring& line : lines)
 22     {
 23         if (line.Length() > maxWidth)
 24         {
 25             maxWidth = cast<int>(line.Length());
 26         }
 27     }
 28     return maxWidth;
 29 }
 30 
 31 void ViewScreen(const List<ustring>& linesint offsetYint screenWidthint screenHeight)
 32 {
 33     ClearScreen(screenWidthscreenHeight);
 34     int n = Min(cast<int>(lines.Count())screenHeight - 1);
 35     for (int i = 0; i < n; ++i;)
 36     {
 37         SetCursorPos(0i);
 38         const ustring& line = lines[i + offsetY];
 39         Terminal.Out() << line;
 40     }
 41     SetCursorPos(0screenHeight - 1);
 42     Terminal.Out() << ":";
 43 }
 44 
 45 void Start(int& offsetY)
 46 {
 47     offsetY = 0;
 48 }
 49 
 50 void End(const List<ustring>& linesint& offsetYint screenHeight)
 51 {
 52     offsetY = Max(cast<int>(0)cast<int>(lines.Count()) - screenHeight);
 53 }
 54 
 55 void LineUp(int& offsetY)
 56 {
 57     if (offsetY > 0)
 58     {
 59         --offsetY;
 60     }
 61 }
 62 
 63 void LineDown(const List<ustring>& linesint& offsetYint screenHeight)
 64 {
 65     if (offsetY < lines.Count() - screenHeight)
 66     {
 67         ++offsetY;
 68     }
 69 }
 70 
 71 void PageUp(int& offsetYint screenHeight)
 72 {
 73     offsetY = Max(cast<int>(0)offsetY - screenHeight);
 74 }
 75 
 76 void PageDown(const List<ustring>& linesint& offsetYint screenHeight)
 77 {
 78     offsetY = Max(cast<int>(0)Min(offsetY + screenHeightcast<int>(lines.Count()) - screenHeight));
 79 }
 80 
 81 void View(List<ustring>& lines)
 82 {
 83     SetRaw(Terminal.Descriptor());
 84     SetEcho(Terminal.Descriptor()false);
 85     int maxWidth = MaxWidth(lines);
 86     int screenWidth = TerminalWindowWidth();
 87     int screenHeight = TerminalWindowHeight();
 88     List<ustring> lns;
 89     if (maxWidth > screenWidth)
 90     {
 91         for (const ustring& line : lines)
 92         {
 93             if (line.Length() > screenWidth)
 94             {
 95                 lns.Add(line.Substring(0screenWidth));
 96                 lns.Add(line.Substring(screenWidth));
 97             }
 98             else
 99             {
100                 lns.Add(line);
101             }
102         }
103         Swap(lineslns);
104     }
105     int offsetY = 0;
106     ViewScreen(linesoffsetYscreenWidthscreenHeight);
107     uchar key = ReadKey(Terminal.Descriptor());
108     while (key != keyEscape)
109     {
110         int prevOffsetY = offsetY;
111         switch (key)
112         {
113             case keyHome:
114             {
115                 Start(offsetY);
116                 break;
117             }
118             case keyEnd:
119             {
120                 End(linesoffsetYscreenHeight - 1);
121                 break;
122             }
123             case keyUp:
124             {
125                 LineUp(offsetY);
126                 break;
127             }
128             case keyDown:
129             {
130                 LineDown(linesoffsetYscreenHeight - 1);
131                 break;
132             }
133             case keyPgUp:
134             {
135                 PageUp(offsetYscreenHeight - 1);
136                 break;
137             }
138             case keyPgDown:
139             {
140                 PageDown(linesoffsetYscreenHeight - 1);
141                 break;
142             }
143         }
144         if (prevOffsetY != offsetY)
145         {
146             ViewScreen(linesoffsetYscreenWidthscreenHeight);
147         }
148         key = ReadKey(Terminal.Descriptor());
149     }
150     ClearScreen(screenWidthscreenHeight);
151     SetCursorPos(00);
152 }
153 
154 void ViewFiles(const List<string>& files)
155 {
156     List<ustring> lines;
157     for (const string& file : files)
158     {
159         StreamReader reader(SharedPtr<Stream>());
160         if (!file.IsEmpty())
161         {
162             reader = File.OpenRead(file);
163         }
164         else
165         {
166             if (IsConsole(0))
167             {
168                 reader = StreamReader(SharedPtr<Stream>(new FileStream(0)));
169             }
170             else
171             {
172                 reader = StreamReader(SharedPtr<Stream>(new BufferedStream(SharedPtr<Stream>(new FileStream(0)))));
173             }
174         }
175         while (!reader.EndOfStream())
176         {
177             string line = reader.ReadLine();
178             lines.Add(ToUtf32(line));
179         }
180     }
181     if (!IsConsole(1))
182     {
183         for (const ustring& line : lines)
184         {
185             Console.Out() << line << endl();
186         }
187     }
188     else
189     {
190         View(lines);
191     }
192 }
193 
194 int main(int argcconst char** argv)
195 {
196     try
197     {
198         List<string> files;
199         for (int i = 1; i < argc; ++i;)
200         {
201             string arg = argv[i];
202             if (arg.StartsWith("--"))
203             {
204                 if (arg == "--help")
205                 {
206                     PrintHelp();
207                     return 1;
208                 }
209                 else
210                 {
211                     throw Exception("unknown option '" + arg + "'");
212                 }
213             }
214             else if (arg.StartsWith("-"))
215             {
216                 string options = arg.Substring(1);
217                 for (char o : options)
218                 {
219                     bool unknown = false;
220                     string uo;
221                     switch (o)
222                     {
223                         case 'h':
224                         {
225                             PrintHelp();
226                             return 1;
227                         }
228                         default:
229                         {
230                             uo.Append(o);
231                             unknown = true;
232                             break;
233                         }
234                     }
235                     if (unknown)
236                     {
237                         throw Exception("unknown option '-" + uo + "'");
238                     }
239                 }
240             }
241             else
242             {
243                 files.Add(arg);
244             }
245         }
246         if (files.IsEmpty())
247         {
248             files.Add(string());
249         }
250         ViewFiles(files);
251     }
252     catch (const Exception& ex)
253     {
254         Console.Error() << ex.ToString() << endl();
255         return 1;
256     }
257     SetCooked(Terminal.Descriptor());
258     SetEcho(Terminal.Descriptor()true);
259     return 0;
260 }