├── .gitignore ├── InteractivePrompt.sln ├── InteractivePrompt ├── InteractivePrompt.cs ├── InteractivePrompt.csproj ├── InteractivePrompt.nuspec └── Properties │ ├── AssemblyInfo.cs │ └── InteractivePrompt.png ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | 10 | # Build results 11 | FileVerifierExe/FileVerifier 12 | [Dd]ebug/ 13 | [Rr]elease/ 14 | x64/ 15 | build/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | packages/ 19 | 20 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 21 | !packages/*/build/ 22 | 23 | # MSTest test Results 24 | [Tt]est[Rr]esult*/ 25 | [Bb]uild[Ll]og.* 26 | 27 | *.nupkg 28 | *_i.c 29 | *_p.c 30 | *.ilk 31 | *.meta 32 | *.obj 33 | *.pch 34 | *.pdb 35 | *.pgc 36 | *.pgd 37 | *.rsp 38 | *.sbr 39 | *.tlb 40 | *.tli 41 | *.tlh 42 | *.tmp 43 | *.tmp_proj 44 | *.log 45 | *.vspscc 46 | *.vssscc 47 | .builds 48 | *.pidb 49 | *.log 50 | *.scc 51 | *.userprefs 52 | *.suo 53 | 54 | # Visual C++ cache files 55 | ipch/ 56 | *.aps 57 | *.ncb 58 | *.opensdf 59 | *.sdf 60 | *.cachefile 61 | 62 | # Visual Studio profiler 63 | *.psess 64 | *.vsp 65 | *.vspx 66 | 67 | # Guidance Automation Toolkit 68 | *.gpState 69 | 70 | # ReSharper is a .NET coding add-in 71 | _ReSharper*/ 72 | *.[Rr]e[Ss]harper 73 | 74 | # TeamCity is a build add-in 75 | _TeamCity* 76 | 77 | # DotCover is a Code Coverage Tool 78 | *.dotCover 79 | 80 | # NCrunch 81 | *.ncrunch* 82 | .*crunch*.local.xml 83 | 84 | # Installshield output folder 85 | [Ee]xpress/ 86 | 87 | # DocProject is a documentation generator add-in 88 | DocProject/buildhelp/ 89 | DocProject/Help/*.HxT 90 | DocProject/Help/*.HxC 91 | DocProject/Help/*.hhc 92 | DocProject/Help/*.hhk 93 | DocProject/Help/*.hhp 94 | DocProject/Help/Html2 95 | DocProject/Help/html 96 | 97 | # Click-Once directory 98 | publish/ 99 | 100 | # Publish Web Output 101 | *.Publish.xml 102 | 103 | # NuGet Packages Directory 104 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 105 | #packages/ 106 | 107 | # Windows Azure Build Output 108 | csx 109 | *.build.csdef 110 | 111 | # Windows Store app package directory 112 | AppPackages/ 113 | 114 | # Others 115 | sql/ 116 | *.Cache 117 | ClientBin/ 118 | [Ss]tyle[Cc]op.* 119 | ~$* 120 | *~ 121 | *.dbmdl 122 | *.[Pp]ublish.xml 123 | *.pfx 124 | *.publishsettings 125 | 126 | # RIA/Silverlight projects 127 | Generated_Code/ 128 | 129 | # Backup & report files from converting an old project file to a newer 130 | # Visual Studio version. Backup files are not needed, because we have git ;-) 131 | _UpgradeReport_Files/ 132 | Backup*/ 133 | UpgradeLog*.XML 134 | UpgradeLog*.htm 135 | 136 | # SQL Server files 137 | App_Data/*.mdf 138 | App_Data/*.ldf 139 | 140 | 141 | #LightSwitch generated files 142 | GeneratedArtifacts/ 143 | _Pvt_Extensions/ 144 | ModelManifest.xml 145 | 146 | # ========================= 147 | # Windows detritus 148 | # ========================= 149 | 150 | # Windows image file caches 151 | Thumbs.db 152 | ehthumbs.db 153 | 154 | # Folder config file 155 | Desktop.ini 156 | 157 | # Recycle Bin used on file shares 158 | $RECYCLE.BIN/ 159 | 160 | # Mac desktop service store files 161 | .DS_Store 162 | -------------------------------------------------------------------------------- /InteractivePrompt.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteractivePrompt", "InteractivePrompt\InteractivePrompt.csproj", "{6A899A98-571E-4F01-838C-2292C1627520}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6A899A98-571E-4F01-838C-2292C1627520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6A899A98-571E-4F01-838C-2292C1627520}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6A899A98-571E-4F01-838C-2292C1627520}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6A899A98-571E-4F01-838C-2292C1627520}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /InteractivePrompt/InteractivePrompt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | 8 | namespace Cintio 9 | { 10 | public static class InteractivePrompt 11 | { 12 | private static string _prompt; 13 | private static int startingCursorLeft; 14 | private static int startingCursorTop; 15 | private static ConsoleKeyInfo key, lastKey; 16 | 17 | private static bool InputIsOnNewLine(List input, int inputPosition) 18 | { 19 | return (inputPosition + _prompt.Length > Console.BufferWidth - 1); 20 | } 21 | private static int GetCurrentLineForInput(List input, int inputPosition) 22 | { 23 | int currentLine = 0; 24 | for (int i = 0; i < input.Count; i++) 25 | { 26 | if (input[i] == '\n') 27 | currentLine += 1; 28 | if (i == inputPosition) 29 | break; 30 | } 31 | return currentLine; 32 | } 33 | /// 34 | /// Gets the cursor position relative to the current line it is on 35 | /// 36 | /// 37 | /// 38 | /// 39 | private static Tuple GetCursorRelativePosition(List input, int inputPosition) 40 | { 41 | int currentPos = 0; 42 | int currentLine = 0; 43 | for (int i = 0; i < input.Count; i++) 44 | { 45 | if (input[i] == '\n') 46 | { 47 | currentLine += 1; 48 | currentPos = 0; 49 | } 50 | if (i == inputPosition) 51 | { 52 | if (currentLine == 0) 53 | { 54 | currentPos += _prompt.Length; 55 | } 56 | break; 57 | } 58 | currentPos++; 59 | } 60 | return Tuple.Create(currentPos, currentLine); 61 | } 62 | private static int mod(int x, int m) 63 | { 64 | return (x % m + m) % m; 65 | } 66 | private static void ClearLine(List input, int inputPosition) 67 | { 68 | int cursorLeft = InputIsOnNewLine(input, inputPosition) ? 0 : _prompt.Length; 69 | Console.SetCursorPosition(cursorLeft, Console.CursorTop); 70 | Console.Write(new string(' ', input.Count + 5)); 71 | } 72 | 73 | /// 74 | /// A hacktastic way to scroll the buffer - WriteLine 75 | /// 76 | /// 77 | private static void ScrollBuffer(int lines = 0) 78 | { 79 | for (int i = 0; i <= lines; i++) 80 | Console.WriteLine(""); 81 | Console.SetCursorPosition(0, Console.CursorTop - lines); 82 | startingCursorTop = Console.CursorTop - lines; 83 | } 84 | 85 | /// 86 | /// RewriteLine will rewrite every character in the input List, and given the inputPosition 87 | /// will determine whether or not to continue to the next line 88 | /// 89 | /// The input buffer 90 | /// Current character position in the List 91 | private static void RewriteLine(List input, int inputPosition) 92 | { 93 | int cursorTop = 0; 94 | 95 | try 96 | { 97 | Console.SetCursorPosition(startingCursorLeft, startingCursorTop); 98 | var coords = GetCursorRelativePosition(input, inputPosition); 99 | cursorTop = startingCursorTop; 100 | int cursorLeft = 0; 101 | 102 | if (GetCurrentLineForInput(input, inputPosition) == 0) 103 | { 104 | cursorTop += (inputPosition + _prompt.Length) / Console.BufferWidth; 105 | cursorLeft = inputPosition + _prompt.Length; 106 | } 107 | else 108 | { 109 | cursorTop += coords.Item2; 110 | cursorLeft = coords.Item1 - 1; 111 | } 112 | 113 | // if the new vertical cursor position is going to exceed the buffer height (i.e., we are 114 | // at the bottom of console) then we need to scroll the buffer however much we are about to exceed by 115 | if (cursorTop >= Console.BufferHeight) 116 | { 117 | ScrollBuffer(cursorTop - Console.BufferHeight + 1); 118 | RewriteLine(input, inputPosition); 119 | return; 120 | } 121 | 122 | Console.Write(String.Concat(input)); 123 | Console.SetCursorPosition(mod(cursorLeft, Console.BufferWidth), cursorTop); 124 | } 125 | catch (Exception e) 126 | { 127 | Console.WriteLine(e.Message); 128 | } 129 | } 130 | private static IEnumerable GetMatch(List s, string input) 131 | { 132 | s.Add(input); 133 | int direction = (key.Modifiers == ConsoleModifiers.Shift) ? -1 : 1; 134 | for (int i = -1; i < s.Count; ) 135 | { 136 | direction = (key.Modifiers == ConsoleModifiers.Shift) ? -1 : 1; 137 | i = mod((i + direction), s.Count); 138 | 139 | if (Regex.IsMatch(s[i], ".*(?:" + input + ").*", RegexOptions.IgnoreCase)) 140 | { 141 | yield return s[i]; 142 | } 143 | } 144 | } 145 | 146 | static Tuple HandleMoveLeft(List input, int inputPosition) 147 | { 148 | var coords = GetCursorRelativePosition(input, inputPosition); 149 | int cursorLeftPosition = coords.Item1; 150 | int cursorTopPosition = Console.CursorTop; 151 | 152 | if (GetCurrentLineForInput(input, inputPosition) == 0) 153 | cursorLeftPosition = (coords.Item1) % Console.BufferWidth ; 154 | 155 | if (Console.CursorLeft == 0) 156 | cursorTopPosition = Console.CursorTop - 1; 157 | 158 | return Tuple.Create(cursorLeftPosition, cursorTopPosition); 159 | } 160 | 161 | static Tuple HandleMoveRight(List input, int inputPosition) 162 | { 163 | var coords = GetCursorRelativePosition(input, inputPosition); 164 | int cursorLeftPosition = coords.Item1; 165 | int cursorTopPosition = Console.CursorTop; 166 | if (Console.CursorLeft + 1 >= Console.BufferWidth || input[inputPosition] == '\n') 167 | { 168 | cursorLeftPosition = 0; 169 | cursorTopPosition = Console.CursorTop + 1; 170 | } 171 | return Tuple.Create(cursorLeftPosition % Console.BufferWidth, cursorTopPosition); 172 | } 173 | 174 | public static void ForEach(this IEnumerable source, Action action) 175 | { 176 | foreach (T item in source) { action(item); } 177 | } 178 | 179 | /// 180 | /// Run will start an interactive prompt 181 | /// 182 | /// This func is provided for the user to handle the input. Input is provided in both string and List<char>. A return response is provided as a string. 183 | /// The prompt for the interactive shell 184 | /// Startup msg to display to user 185 | public static void Run(Func, List, string> lambda, string prompt, string startupMsg, List completionList = null) 186 | { 187 | _prompt = prompt; 188 | Console.WriteLine(startupMsg); 189 | List> inputHistory = new List>(); 190 | IEnumerator wordIterator = null; 191 | 192 | while (true) 193 | { 194 | string completion = null; 195 | List input = new List(); 196 | startingCursorLeft = _prompt.Length; 197 | startingCursorTop = Console.CursorTop; 198 | int inputPosition = 0; 199 | int inputHistoryPosition = inputHistory.Count; 200 | 201 | key = lastKey = new ConsoleKeyInfo(); 202 | Console.Write(prompt); 203 | do 204 | { 205 | key = Console.ReadKey(true); 206 | if (key.Key == ConsoleKey.LeftArrow) 207 | { 208 | if (inputPosition > 0) 209 | { 210 | inputPosition--; 211 | var pos = HandleMoveLeft(input, inputPosition); 212 | Console.SetCursorPosition(pos.Item1, pos.Item2); 213 | } 214 | } 215 | else if (key.Key == ConsoleKey.RightArrow) 216 | { 217 | if (inputPosition < input.Count) 218 | { 219 | var pos = HandleMoveRight(input, inputPosition++); 220 | Console.SetCursorPosition(pos.Item1, pos.Item2); 221 | } 222 | } 223 | 224 | else if (key.Key == ConsoleKey.Tab && completionList != null && completionList.Count > 0) 225 | { 226 | int tempPosition = inputPosition; 227 | List word = new List(); 228 | while (tempPosition-- > 0 && !string.IsNullOrWhiteSpace(input[tempPosition].ToString())) 229 | word.Insert(0, input[tempPosition]); 230 | 231 | if (lastKey.Key == ConsoleKey.Tab) 232 | { 233 | wordIterator.MoveNext(); 234 | if (completion != null) 235 | { 236 | ClearLine(input, inputPosition); 237 | for (var i = 0; i < completion.Length; i++) 238 | { 239 | input.RemoveAt(--inputPosition); 240 | } 241 | RewriteLine(input, inputPosition); 242 | } 243 | else 244 | { 245 | ClearLine(input, inputPosition); 246 | for (var i = 0; i < string.Concat(word).Length; i++) 247 | { 248 | input.RemoveAt(--inputPosition); 249 | } 250 | RewriteLine(input, inputPosition); 251 | } 252 | } 253 | else 254 | { 255 | ClearLine(input, inputPosition); 256 | for (var i = 0; i < string.Concat(word).Length; i++) 257 | { 258 | input.RemoveAt(--inputPosition); 259 | } 260 | RewriteLine(input, inputPosition); 261 | wordIterator = GetMatch(completionList, string.Concat(word)).GetEnumerator(); 262 | while (wordIterator.Current == null) 263 | wordIterator.MoveNext(); 264 | } 265 | 266 | completion = wordIterator.Current; 267 | ClearLine(input, inputPosition); 268 | foreach (var c in completion.ToCharArray()) 269 | { 270 | input.Insert(inputPosition++, c); 271 | } 272 | RewriteLine(input, inputPosition); 273 | 274 | } 275 | else if (key.Key == ConsoleKey.Home || (key.Key == ConsoleKey.H && key.Modifiers == ConsoleModifiers.Control)) 276 | { 277 | inputPosition = 0; 278 | Console.SetCursorPosition(prompt.Length, startingCursorTop); 279 | } 280 | 281 | else if (key.Key == ConsoleKey.End || (key.Key == ConsoleKey.E && key.Modifiers == ConsoleModifiers.Control)) 282 | { 283 | inputPosition = input.Count; 284 | var cursorLeft = 0; 285 | int cursorTop = startingCursorTop; 286 | if ((inputPosition + _prompt.Length) / Console.BufferWidth > 0) 287 | { 288 | cursorTop += (inputPosition + _prompt.Length) / Console.BufferWidth; 289 | cursorLeft = (inputPosition + _prompt.Length) % Console.BufferWidth; 290 | } 291 | Console.SetCursorPosition(cursorLeft, cursorTop); 292 | } 293 | 294 | else if (key.Key == ConsoleKey.Delete) 295 | { 296 | if (inputPosition < input.Count) 297 | { 298 | input.RemoveAt(inputPosition); 299 | ClearLine(input, inputPosition); 300 | RewriteLine(input, inputPosition); 301 | } 302 | } 303 | 304 | else if (key.Key == ConsoleKey.UpArrow) 305 | { 306 | if (inputHistoryPosition > 0) 307 | { 308 | inputHistoryPosition -= 1; 309 | ClearLine(input, inputPosition); 310 | 311 | // ToList() so we make a copy and don't use the reference in the list 312 | input = inputHistory[inputHistoryPosition].ToList(); 313 | RewriteLine(input, input.Count); 314 | inputPosition = input.Count; 315 | } 316 | } 317 | else if (key.Key == ConsoleKey.DownArrow) 318 | { 319 | if (inputHistoryPosition < inputHistory.Count - 1) 320 | { 321 | inputHistoryPosition += 1; 322 | ClearLine(input, inputPosition); 323 | 324 | // ToList() so we make a copy and don't use the reference in the list 325 | input = inputHistory[inputHistoryPosition].ToList(); 326 | RewriteLine(input, input.Count); 327 | inputPosition = input.Count; 328 | } 329 | else 330 | { 331 | inputHistoryPosition = inputHistory.Count; 332 | ClearLine(input, inputPosition); 333 | Console.SetCursorPosition(prompt.Length, Console.CursorTop); 334 | input = new List(); 335 | inputPosition = 0; 336 | } 337 | } 338 | else if (key.Key == ConsoleKey.Backspace) 339 | { 340 | if (inputPosition > 0) 341 | { 342 | inputPosition--; 343 | input.RemoveAt(inputPosition); 344 | ClearLine(input, inputPosition); 345 | RewriteLine(input, inputPosition); 346 | } 347 | } 348 | 349 | else if (key.Key == ConsoleKey.Escape) 350 | { 351 | if (lastKey.Key == ConsoleKey.Escape) 352 | Environment.Exit(0); 353 | else 354 | Console.WriteLine("Press Escape again to exit."); 355 | } 356 | 357 | else if (key.Key == ConsoleKey.Enter && (key.Modifiers == ConsoleModifiers.Shift || key.Modifiers == ConsoleModifiers.Alt)) 358 | { 359 | input.Insert(inputPosition++, '\n'); 360 | RewriteLine(input, inputPosition); 361 | } 362 | 363 | // multiline paste event 364 | else if (key.Key == ConsoleKey.Enter && Console.KeyAvailable == true) 365 | { 366 | input.Insert(inputPosition++, '\n'); 367 | RewriteLine(input, inputPosition); 368 | } 369 | 370 | else if (key.Key != ConsoleKey.Enter) 371 | { 372 | 373 | input.Insert(inputPosition++, key.KeyChar); 374 | RewriteLine(input, inputPosition); 375 | } 376 | 377 | lastKey = key; 378 | } while (!(key.Key == ConsoleKey.Enter && Console.KeyAvailable == false) 379 | // If Console.KeyAvailable = true then we have a multiline paste event 380 | || (key.Key == ConsoleKey.Enter && (key.Modifiers == ConsoleModifiers.Shift || key.Modifiers == ConsoleModifiers.Alt))); 381 | 382 | int newlines = (input.Where(a => a == '\n').Count() > (input.Count / Console.BufferWidth)) 383 | ? input.Where(a => a == '\n').Count() 384 | : (input.Count / Console.BufferWidth); 385 | Console.SetCursorPosition(prompt.Length, startingCursorTop + newlines + 1); 386 | Enumerable.Range(0, newlines).ForEach(x => Console.WriteLine()); 387 | Console.SetCursorPosition(prompt.Length, Console.CursorTop); 388 | 389 | 390 | var cmd = string.Concat(input); 391 | if (String.IsNullOrWhiteSpace(cmd)) 392 | continue; 393 | 394 | if (!inputHistory.Contains(input)) 395 | inputHistory.Add(input); 396 | 397 | Console.Write(lambda(cmd, input, completionList)); 398 | 399 | } 400 | } 401 | } 402 | 403 | } 404 | -------------------------------------------------------------------------------- /InteractivePrompt/InteractivePrompt.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6A899A98-571E-4F01-838C-2292C1627520} 8 | Library 9 | Properties 10 | InteractivePrompt 11 | InteractivePrompt 12 | v4.0 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | -------------------------------------------------------------------------------- /InteractivePrompt/InteractivePrompt.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://github.com/sorrell/InteractivePrompt/blob/master/LICENSE 10 | https://github.com/sorrell/InteractivePrompt 11 | https://raw.githubusercontent.com/sorrell/InteractivePrompt/master/InteractivePrompt/Properties/InteractivePrompt.png 12 | false 13 | The RPL for your REPL. It's a REPL for Console Apps that features a command history, word completion, and basic shell nav. Just add your own prompt, startup message, and callback for handling the user input. More info at https://github.com/sorrell/InteractivePrompt 14 | Fixed crash when buffer carried into new line 15 | Copyright 2017 16 | Shell Repl Prompt Interactive 17 | 18 | 19 | -------------------------------------------------------------------------------- /InteractivePrompt/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("InteractivePrompt")] 9 | [assembly: AssemblyDescription("The RPL for your REPL - it's for building advanced Console apps, with a session command history via up/down arrows.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Cintio Ltd.")] 12 | [assembly: AssemblyProduct("InteractivePrompt")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6a899a98-571e-4f01-838c-2292c1627520")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.5.0")] 36 | [assembly: AssemblyFileVersion("1.0.5.0")] 37 | -------------------------------------------------------------------------------- /InteractivePrompt/Properties/InteractivePrompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorrell/InteractivePrompt/f1aea8fbf6280b800d3eedda074f9f8f92660aa9/InteractivePrompt/Properties/InteractivePrompt.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nick Sorrell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InteractivePrompt - The RPL for your REPL 2 | 3 | This is a very small library to help implement your own REPL. It takes care of the Read, Print, and Loop of the REPL, and also features a command history with use of the up/down arrows, word completion via tab, and ability to edit previous entries. The standard console doesn't provide enough functionality, and this solution provides a better way to create a fast repl / user input dialog. **You just provide the Eval.** 4 | 5 | ![image](http://cint.io/interactiveprompt.gif) 6 | 7 | ## Features 8 | 9 | - Word completion via Tab button (you provide the `List`) 10 | - Command history (up / down arrows) 11 | - Standard shell navigation (left/right, Home/End, Ctrl+E, [Ctrl+H was subbed for Ctrl+A], Esc) 12 | - Cross-platform: no Windows specific code 13 | - Fast, ready-to-go REPL 14 | 15 | ## Installation 16 | Add the library from [NuGet](https://www.nuget.org/packages/InteractivePrompt/), or simply extend what's in this repo. 17 | 18 | ## Example 19 | The gif above is based on the code below. Simply provide the prompt, startup message, and function to handle the input, and you're off! 20 | 21 | ```c# 22 | static void Main(string[] args) 23 | { 24 | var prompt = "cool> "; 25 | var startupMsg = "Welcome to my interactive Prompt!"; 26 | List completionList = new List { "contracts", "contractearnings", "cancels", "cancellationInfo", "cantankerous" }; 27 | InteractivePrompt.Run( 28 | ((strCmd, listCmd) => 29 | { 30 | var handleInput = "(((--> " + strCmd + " <--)))"; 31 | return handleInput + Environment.NewLine; 32 | }), prompt, startupMsg, completionList); 33 | } 34 | ``` 35 | ## Word Completion 36 | 37 | ### Predefined Completions 38 | 39 | The code example above will also give you the ability to tab through the provided list, as seen below. 40 | 41 | ![image](http://cint.io/codecompletion.gif) 42 | 43 | ### Runtime Completions 44 | 45 | The code example below shows runtime completions in action - see [AnotherCSharpRepl](https://github.com/sorrell/AnotherCSharpRepl) for working example. 46 | 47 | ```c# 48 | class Program 49 | { 50 | static void Main(string[] args) 51 | { 52 | var prompt = "c#> "; 53 | List compList = new List(); 54 | CSharpEvaluator eval = new CSharpEvaluator(); 55 | var startupMsg = "Another C# REPL v 1.0.0"; 56 | InteractivePrompt.Run( 57 | ((strCmd, listCmd, completions) => 58 | { 59 | foreach (var c in strCmd.Split(' ')) 60 | if (!completions.Contains(c)) 61 | completions.Add(c); 62 | return eval.HandleCmd(strCmd) + Environment.NewLine; 63 | }), prompt, startupMsg, compList); 64 | } 65 | } 66 | ``` 67 | 68 | ## How it's done 69 | Some very simple usage of `Cursor` position and rewriting the current line allows us to create an editable command history. 70 | --------------------------------------------------------------------------------