diff options
author | FeralChild64 <> | 2022-11-07 22:37:45 +0300 |
---|---|---|
committer | FeralChild64 <> | 2022-11-13 20:31:03 +0300 |
commit | c4e2fc4670497dc25afbe86819c74dadf1d5c7ab (patch) | |
tree | 79c6b883c25ec600faab8e7ce617c263becf55c0 | |
parent | 49a0c73bdb25e5b1bd0c0d082ef81d629d11eafe (diff) |
Use MORE to display help stringsfc/extended-more-1
-rw-r--r-- | include/shell.h | 3 | ||||
-rw-r--r-- | src/dos/meson.build | 1 | ||||
-rw-r--r-- | src/dos/program_autotype.cpp | 7 | ||||
-rw-r--r-- | src/dos/program_boot.cpp | 5 | ||||
-rw-r--r-- | src/dos/program_imgmount.cpp | 80 | ||||
-rw-r--r-- | src/dos/program_intro.cpp | 9 | ||||
-rw-r--r-- | src/dos/program_keyb.cpp | 5 | ||||
-rw-r--r-- | src/dos/program_loadfix.cpp | 5 | ||||
-rw-r--r-- | src/dos/program_loadrom.cpp | 9 | ||||
-rw-r--r-- | src/dos/program_mem.cpp | 5 | ||||
-rw-r--r-- | src/dos/program_more.cpp | 355 | ||||
-rw-r--r-- | src/dos/program_more.h | 36 | ||||
-rw-r--r-- | src/dos/program_more_output.cpp | 530 | ||||
-rw-r--r-- | src/dos/program_more_output.h | 136 | ||||
-rw-r--r-- | src/dos/program_mount.cpp | 13 | ||||
-rw-r--r-- | src/dos/program_mousectl.cpp | 5 | ||||
-rw-r--r-- | src/dos/program_placeholder.cpp | 7 | ||||
-rw-r--r-- | src/dos/program_rescan.cpp | 5 | ||||
-rw-r--r-- | src/dos/program_serial.cpp | 6 | ||||
-rw-r--r-- | src/misc/programs.cpp | 29 | ||||
-rw-r--r-- | src/shell/shell.cpp | 5 | ||||
-rw-r--r-- | src/shell/shell_cmds.cpp | 89 | ||||
-rw-r--r-- | vs/dosbox.vcxproj | 1 | ||||
-rw-r--r-- | vs/dosbox.vcxproj.filters | 3 |
24 files changed, 871 insertions, 478 deletions
diff --git a/include/shell.h b/include/shell.h index 58afb82ff..56d0d7a4f 100644 --- a/include/shell.h +++ b/include/shell.h @@ -62,6 +62,7 @@ public: }; class AutoexecEditor; +class MoreOutputStrings; struct SHELL_Cmd { void (DOS_Shell::*handler)(char *args) = nullptr; // Handler for this command @@ -72,7 +73,7 @@ struct SHELL_Cmd { class DOS_Shell : public Program { private: - void PrintHelpForCommands(HELP_Filter req_filter); + void PrintHelpForCommands(MoreOutputStrings &output, HELP_Filter req_filter); void AddShellCmdsToHelpList(); bool WriteHelp(const std::string &command, char* args); diff --git a/src/dos/meson.build b/src/dos/meson.build index da69c78cc..118f91ad4 100644 --- a/src/dos/meson.build +++ b/src/dos/meson.build @@ -35,6 +35,7 @@ libdos_sources = files( 'program_ls.cpp', 'program_mem.cpp', 'program_more.cpp', + 'program_more_output.cpp', 'program_mount.cpp', 'program_mount_common.cpp', 'program_mousectl.cpp', diff --git a/src/dos/program_autotype.cpp b/src/dos/program_autotype.cpp index f1725a0b0..69b75744f 100644 --- a/src/dos/program_autotype.cpp +++ b/src/dos/program_autotype.cpp @@ -28,9 +28,10 @@ #include <string> #include <sstream> +#include "dosbox.h" #include "mapper.h" #include "math_utils.h" -#include "dosbox.h" +#include "program_more_output.h" #include "programs.h" // Prints the key-names for the mapper's currently-bound events. @@ -121,7 +122,9 @@ void AUTOTYPE::Run() // Usage if (!cmd->GetCount() || HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_AUTOTYPE_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_AUTOTYPE_HELP_LONG")); + output.Display(); return; } diff --git a/src/dos/program_boot.cpp b/src/dos/program_boot.cpp index 04b16236d..6f955aaf4 100644 --- a/src/dos/program_boot.cpp +++ b/src/dos/program_boot.cpp @@ -31,6 +31,7 @@ #include "drives.h" #include "mapper.h" #include "mouse.h" +#include "program_more_output.h" #include "regs.h" #include "string_utils.h" @@ -166,7 +167,9 @@ void BOOT::Run(void) } if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_BOOT_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_BOOT_HELP_LONG")); + output.Display(); return; } if (cmd->GetCount() == 1) { diff --git a/src/dos/program_imgmount.cpp b/src/dos/program_imgmount.cpp index feb78a944..7be1d614d 100644 --- a/src/dos/program_imgmount.cpp +++ b/src/dos/program_imgmount.cpp @@ -24,18 +24,19 @@ #include <vector> +#include "../ints/int10.h" #include "bios_disk.h" +#include "cdrom.h" #include "control.h" #include "cross.h" #include "drives.h" #include "fs_utils.h" #include "ide.h" #include "mapper.h" +#include "program_more_output.h" #include "program_mount_common.h" #include "shell.h" -#include "cdrom.h" #include "string_utils.h" -#include "../ints/int10.h" void IMGMOUNT::ListImgMounts(void) { @@ -96,8 +97,10 @@ void IMGMOUNT::Run(void) { } // Usage if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_IMGMOUNT_HELP_LONG"), PRIMARY_MOD_NAME); - return; + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_IMGMOUNT_HELP_LONG"), PRIMARY_MOD_NAME); + output.Display(); + return; } /* In secure mode don't allow people to change imgmount points. @@ -492,44 +495,45 @@ void IMGMOUNT::Run(void) { void IMGMOUNT::AddMessages() { AddCommonMountMessages(); - MSG_Add("PROGRAM_IMGMOUNT_HELP_LONG", - "Mount a CD-ROM, floppy, or disk image to a drive letter.\n" - "\n" - "Usage:\n" - " [color=green]imgmount[reset] [color=white]DRIVE[reset] [color=cyan]CDROM-SET[reset] [-fs iso] [-ide] -t cdrom|iso\n" - " [color=green]imgmount[reset] [color=white]DRIVE[reset] [color=cyan]IMAGEFILE[reset] [IMAGEFILE2 [..]] [-fs fat] -t hdd|floppy -ro\n" - " [color=green]imgmount[reset] [color=white]DRIVE[reset] [color=cyan]BOOTIMAGE[reset] [-fs fat|none] -t hdd -size GEOMETRY -ro\n" - " [color=green]imgmount[reset] -u [color=white]DRIVE[reset] (unmounts the [color=white]DRIVE[reset]'s image)\n" - "\n" - "Where:\n" - " [color=white]DRIVE[reset] is the drive letter where the image will be mounted: a, c, d, ...\n" - " [color=cyan]CDROM-SET[reset] is an ISO, CUE+BIN, CUE+ISO, or CUE+ISO+FLAC/OPUS/OGG/MP3/WAV\n" - " [color=cyan]IMAGEFILE[reset] is a hard drive or floppy image in FAT16 or FAT12 format\n" - " [color=cyan]BOOTIMAGE[reset] is a bootable disk image with specified -size GEOMETRY:\n" - " bytes-per-sector,sectors-per-head,heads,cylinders\n" - "Notes:\n" - " - %s+F4 swaps & mounts the next [color=cyan]CDROM-SET[reset] or [color=cyan]BOOTIMAGE[reset], if provided.\n" - " - The -ro flag mounts the disk image in read-only (write-protected) mode.\n" - " - The -ide flag emulates an IDE controller with attached IDE CD drive, useful\n" - " for CD-based games that need a real DOS environment via bootable HDD image.\n" - "Examples:\n" + MSG_Add("PROGRAM_IMGMOUNT_HELP_LONG", + "Mount a CD-ROM, floppy, or disk image to a drive letter.\n" + "\n" + "Usage:\n" + " [color=green]imgmount[reset] [color=white]DRIVE[reset] [color=cyan]CDROM-SET[reset] [-fs iso] [-ide] -t cdrom|iso\n" + " [color=green]imgmount[reset] [color=white]DRIVE[reset] [color=cyan]IMAGEFILE[reset] [IMAGEFILE2 [..]] [-fs fat] -t hdd|floppy -ro\n" + " [color=green]imgmount[reset] [color=white]DRIVE[reset] [color=cyan]BOOTIMAGE[reset] [-fs fat|none] -t hdd -size GEOMETRY -ro\n" + " [color=green]imgmount[reset] -u [color=white]DRIVE[reset] (unmounts the [color=white]DRIVE[reset]'s image)\n" + "\n" + "Where:\n" + " [color=white]DRIVE[reset] is the drive letter where the image will be mounted: a, c, d, ...\n" + " [color=cyan]CDROM-SET[reset] is an ISO, CUE+BIN, CUE+ISO, or CUE+ISO+FLAC/OPUS/OGG/MP3/WAV\n" + " [color=cyan]IMAGEFILE[reset] is a hard drive or floppy image in FAT16 or FAT12 format\n" + " [color=cyan]BOOTIMAGE[reset] is a bootable disk image with specified -size GEOMETRY:\n" + " bytes-per-sector,sectors-per-head,heads,cylinders\n" + "Notes:\n" + " - %s+F4 swaps & mounts the next [color=cyan]CDROM-SET[reset] or [color=cyan]BOOTIMAGE[reset], if provided.\n" + " - The -ro flag mounts the disk image in read-only (write-protected) mode.\n" + " - The -ide flag emulates an IDE controller with attached IDE CD drive, useful\n" + " for CD-based games that need a real DOS environment via bootable HDD image.\n" + "\n" + "Examples:\n" #if defined(WIN32) - " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]C:\\games\\doom.iso[reset] -t cdrom\n" - " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]cd/quake1.cue[reset] -t cdrom\n" - " [color=green]imgmount[reset] [color=white]A[reset] [color=cyan]floppy1.img floppy2.img floppy3.img[reset] -t floppy -ro\n" - " [color=green]imgmount[reset] [color=white]C[reset] [color=cyan]bootable.img[reset] -t hdd -fs none -size 512,63,32,1023\n" + " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]C:\\games\\doom.iso[reset] -t cdrom\n" + " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]cd/quake1.cue[reset] -t cdrom\n" + " [color=green]imgmount[reset] [color=white]A[reset] [color=cyan]floppy1.img floppy2.img floppy3.img[reset] -t floppy -ro\n" + " [color=green]imgmount[reset] [color=white]C[reset] [color=cyan]bootable.img[reset] -t hdd -fs none -size 512,63,32,1023\n" #elif defined(MACOSX) - " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]/Users/USERNAME/games/doom.iso[reset] -t cdrom\n" - " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]cd/quake1.cue[reset] -t cdrom\n" - " [color=green]imgmount[reset] [color=white]A[reset] [color=cyan]floppy1.img floppy2.img floppy3.img[reset] -t floppy -ro\n" - " [color=green]imgmount[reset] [color=white]C[reset] [color=cyan]bootable.img[reset] -t hdd -fs none -size 512,63,32,1023\n" + " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]/Users/USERNAME/games/doom.iso[reset] -t cdrom\n" + " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]cd/quake1.cue[reset] -t cdrom\n" + " [color=green]imgmount[reset] [color=white]A[reset] [color=cyan]floppy1.img floppy2.img floppy3.img[reset] -t floppy -ro\n" + " [color=green]imgmount[reset] [color=white]C[reset] [color=cyan]bootable.img[reset] -t hdd -fs none -size 512,63,32,1023\n" #else - " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]/home/USERNAME/games/doom.iso[reset] -t cdrom\n" - " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]cd/quake1.cue[reset] -t cdrom\n" - " [color=green]imgmount[reset] [color=white]A[reset] [color=cyan]floppy1.img floppy2.img floppy3.img[reset] -t floppy -ro\n" - " [color=green]imgmount[reset] [color=white]C[reset] [color=cyan]bootable.img[reset] -t hdd -fs none -size 512,63,32,1023\n" + " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]/home/USERNAME/games/doom.iso[reset] -t cdrom\n" + " [color=green]imgmount[reset] [color=white]D[reset] [color=cyan]cd/quake1.cue[reset] -t cdrom\n" + " [color=green]imgmount[reset] [color=white]A[reset] [color=cyan]floppy1.img floppy2.img floppy3.img[reset] -t floppy -ro\n" + " [color=green]imgmount[reset] [color=white]C[reset] [color=cyan]bootable.img[reset] -t hdd -fs none -size 512,63,32,1023\n" #endif - ); + ); MSG_Add("PROGRAM_IMGMOUNT_SPECIFY_DRIVE", "Must specify drive letter to mount image at.\n"); diff --git a/src/dos/program_intro.cpp b/src/dos/program_intro.cpp index 2d6a1ffd8..2f72aaed7 100644 --- a/src/dos/program_intro.cpp +++ b/src/dos/program_intro.cpp @@ -22,6 +22,7 @@ #include "program_intro.h" #include "mapper.h" +#include "program_more_output.h" void INTRO::WriteOutProgramIntroSpecial() { @@ -67,9 +68,11 @@ void INTRO::DisplayMount(void) { void INTRO::Run(void) { // Usage if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_INTRO_HELP")); - WriteOut("\n"); - WriteOut(MSG_Get("PROGRAM_INTRO_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_INTRO_HELP")); + output.AddString("\n"); + output.AddString(MSG_Get("PROGRAM_INTRO_HELP_LONG")); + output.Display(); return; } /* Only run if called from the first shell (Xcom TFTD runs any intro file in the path) */ diff --git a/src/dos/program_keyb.cpp b/src/dos/program_keyb.cpp index 1d72ef088..fceb8c12f 100644 --- a/src/dos/program_keyb.cpp +++ b/src/dos/program_keyb.cpp @@ -21,6 +21,7 @@ #include "program_keyb.h" #include "dos_keyboard_layout.h" +#include "program_more_output.h" #include "string_utils.h" void KEYB::Run(void) { @@ -59,7 +60,9 @@ void KEYB::Run(void) { // One argument: asked for help if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_KEYB_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_KEYB_HELP_LONG")); + output.Display(); return; } diff --git a/src/dos/program_loadfix.cpp b/src/dos/program_loadfix.cpp index 88889ef38..fe098822e 100644 --- a/src/dos/program_loadfix.cpp +++ b/src/dos/program_loadfix.cpp @@ -21,13 +21,16 @@ #include "program_loadfix.h" #include "dosbox.h" +#include "program_more_output.h" #include "shell.h" #include "string_utils.h" void LOADFIX::Run(void) { if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_LOADFIX_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_LOADFIX_HELP_LONG")); + output.Display(); return; } uint16_t commandNr = 1; diff --git a/src/dos/program_loadrom.cpp b/src/dos/program_loadrom.cpp index f11c8d4b7..341364b26 100644 --- a/src/dos/program_loadrom.cpp +++ b/src/dos/program_loadrom.cpp @@ -24,8 +24,9 @@ #include <stdio.h> -#include "drives.h" #include "callback.h" +#include "drives.h" +#include "program_more_output.h" #include "regs.h" void LOADROM::Run(void) { @@ -34,8 +35,10 @@ void LOADROM::Run(void) { return; } if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_LOADROM_HELP_LONG")); - return; + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_LOADROM_HELP_LONG")); + output.Display(); + return; } uint8_t drive; char fullname[DOS_PATHLENGTH]; diff --git a/src/dos/program_mem.cpp b/src/dos/program_mem.cpp index c0c1cd1e7..ad09187de 100644 --- a/src/dos/program_mem.cpp +++ b/src/dos/program_mem.cpp @@ -21,11 +21,14 @@ #include "program_mem.h" #include "callback.h" +#include "program_more_output.h" #include "regs.h" void MEM::Run(void) { if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_MEM_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_MEM_HELP_LONG")); + output.Display(); return; } /* Show conventional Memory */ diff --git a/src/dos/program_more.cpp b/src/dos/program_more.cpp index 76d6015a9..e2b7309d1 100644 --- a/src/dos/program_more.cpp +++ b/src/dos/program_more.cpp @@ -20,6 +20,8 @@ #include "program_more.h" +#include "program_more_output.h" + #include "../ints/int10.h" #include "callback.h" #include "checks.h" @@ -32,52 +34,26 @@ CHECK_NARROWING(); -// ASCII control characters -constexpr char code_ctrl_c = 0x03; // end of text -constexpr char code_lf = 0x0a; // line feed -constexpr char code_cr = 0x0d; // carriage return +extern unsigned int result_errorcode; void MORE::Run() { // Handle command line if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_MORE_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_MORE_HELP_LONG")); + output.Display(); return; } - if (!ParseCommandLine() || shutdown_requested) - return; - - // Retrieve screen size, prepare limits - constexpr uint16_t min_lines = 10; - constexpr uint16_t min_columns = 40; - max_lines = std::max(min_lines, INT10_GetTextRows()); - max_columns = std::max(min_columns, INT10_GetTextColumns()); - // The prompt at the bottom will cause scrolling, - // so reduce the maximum number of lines accordingly - max_lines = static_cast<uint16_t>(max_lines - 1); - - line_counter = 0; - // Show STDIN or input file(s) content - if (input_files.empty()) { - DisplayInputStream(); - } else { - DisplayInputFiles(); - - // End message and command prompt is going to appear; ensure the - // scrolling won't make top lines disappear before user reads them - const int free_rows_threshold = 2; - if (max_lines - line_counter < free_rows_threshold) - PromptUser(); - - WriteOut(MSG_Get("PROGRAM_MORE_END")); - WriteOut("\n"); + MoreOutputFiles output(*this); + if (!ParseCommandLine(output) || shutdown_requested) { + return; } - - WriteOut("\n"); + output.Display(); } -bool MORE::ParseCommandLine() +bool MORE::ParseCommandLine(MoreOutputFiles &output) { // Put all the parameters into vector std::vector<std::string> params; @@ -90,39 +66,42 @@ bool MORE::ParseCommandLine() (param.length() == 3) && (param.back() >= '1') && (param.back() <= '9')) { // FreeDOS extension - custom TAB size - tab_size = static_cast<uint8_t>(param.back() - '0'); + output.SetTabSize(static_cast<uint8_t>(param.back() - '0')); params.erase(params.begin()); } } // Make sure no other switches are supplied - for (const auto ¶m : params) + for (const auto ¶m : params) { if (starts_with("/", param)) { + result_errorcode = DOSERR_FUNCTION_NUMBER_INVALID; WriteOut(MSG_Get("SHELL_ILLEGAL_SWITCH"), param.c_str()); return false; } + } // Create list of input files - return FindInputFiles(params); + return FindInputFiles(output, params); } -bool MORE::FindInputFiles(const std::vector<std::string> ¶ms) +bool MORE::FindInputFiles(MoreOutputFiles &output, + const std::vector<std::string> ¶ms) { - input_files.clear(); if (params.empty()) return true; - constexpr auto search_attr = UINT16_MAX & ~DOS_ATTR_DIRECTORY & - ~DOS_ATTR_VOLUME; + constexpr auto search_attr = UINT16_MAX & ~DOS_ATTR_DIRECTORY & ~DOS_ATTR_VOLUME; RealPt save_dta = dos.dta(); dos.dta(dos.tables.tempdta); + bool found = false; for (const auto ¶m : params) { // Retrieve path to current file/pattern char path[DOS_PATHLENGTH]; - if (!DOS_Canonicalize(param.c_str(), path)) + if (!DOS_Canonicalize(param.c_str(), path)) { continue; + } char *const end = strrchr(path, '\\') + 1; assert(end); *end = 0; @@ -130,11 +109,12 @@ bool MORE::FindInputFiles(const std::vector<std::string> ¶ms) // Search for the first file from pattern if (!DOS_FindFirst(param.c_str(), static_cast<uint16_t>(search_attr))) { - LOG_WARNING("DOS: MORE.COM - no match for pattern '%s'", + LOG_WARNING("DOS: MORE - no match for pattern '%s'", param.c_str()); continue; } + found = true; while (!shutdown_requested) { CALLBACK_Idle(); @@ -142,21 +122,17 @@ bool MORE::FindInputFiles(const std::vector<std::string> ¶ms) uint32_t size = 0; uint16_t time = 0; uint16_t date = 0; - uint8_t attr = 0; + uint8_t attr = 0; DOS_DTA dta(dos.dta()); dta.GetResult(name, size, date, time, attr); assert(name); - input_files.emplace_back(); - auto &entry = input_files.back(); - - if (attr & DOS_ATTR_DEVICE) { - entry.is_device = true; - entry.name = std::string(name); + const bool is_device = attr & DOS_ATTR_DEVICE; + if (is_device) { + output.AddFile(std::string(name), is_device); } else { - entry.is_device = false; - entry.name = std::string(path) + std::string(name); + output.AddFile(std::string(path) + std::string(name), is_device); } if (!DOS_FindNext()) { @@ -167,7 +143,8 @@ bool MORE::FindInputFiles(const std::vector<std::string> ¶ms) dos.dta(save_dta); - if (!shutdown_requested && input_files.empty()) { + if (!shutdown_requested && !found) { + result_errorcode = DOSERR_FILE_NOT_FOUND; WriteOut(MSG_Get("PROGRAM_MORE_NO_FILE")); WriteOut("\n"); return false; @@ -176,276 +153,6 @@ bool MORE::FindInputFiles(const std::vector<std::string> ¶ms) return true; } -std::string MORE::GetShortName(const std::string &file_name, const char *msg_id) -{ - assert(msg_id); - - // The shortest name we should be able to display is: - // - 3 dots - // - 1 path separator - // - 8 characters of name - // - 1 dot - // - 3 characters of extension - // This gives 16 characters. - // We need to keep the last column free (reduces max length by 1). - // Format string contains '%s' (increases max length by 2). - constexpr size_t min = 16; - const auto max_len = std::max(min, max_columns - std::strlen(MSG_Get(msg_id)) + 1); - - // Nothing to do if file name maches the constraint - if (file_name.length() <= max_len) - return file_name; - - // We need to shorten the name - try to strip part of the path - auto shortened = file_name; - while (shortened.length() > max_len && - std::count(shortened.begin(), shortened.end(), '\\') > 1) { - // Strip one level of path at a time - const auto pos = shortened.find('\\', shortened.find('\\') + 1); - shortened = std::string("...") + shortened.substr(pos); - } - - // If still too long, just cut away the beginning - const auto len = shortened.length(); - if (len > max_len) - shortened = std::string("...") + shortened.substr(len - max_len + 3); - - return shortened; -} - -void MORE::DisplayInputFiles() -{ - WriteOut("\n"); - - bool first = true; - for (const auto &input_file : input_files) { - if (!first && UserDecision::Cancel == PromptUser()) - break; - first = false; - - if (!DOS_OpenFile(input_file.name.c_str(), 0, &input_handle)) { - LOG_WARNING("DOS: MORE.COM - could not open '%s'", - input_file.name.c_str()); - const auto short_name = GetShortName(input_file.name, - "PROGRAM_MORE_OPEN_ERROR"); - WriteOut(MSG_Get("PROGRAM_MORE_OPEN_ERROR"), short_name.c_str()); - WriteOut("\n"); - ++line_counter; - continue; - } - - if (input_file.is_device) { - const auto short_name = GetShortName(input_file.name, - "PROGRAM_MORE_NEW_DEVICE"); - WriteOut(MSG_Get("PROGRAM_MORE_NEW_DEVICE"), short_name.c_str()); - } else { - const auto short_name = GetShortName(input_file.name, - "PROGRAM_MORE_NEW_FILE"); - WriteOut(MSG_Get("PROGRAM_MORE_NEW_FILE"), short_name.c_str()); - } - WriteOut("\n"); - ++line_counter; - - // If input from a device, CTRL+C shall quit - ctrl_c_enable = input_file.is_device; - - const auto decision = DisplaySingleStream(); - DOS_CloseFile(input_handle); - if (decision == UserDecision::Cancel) { - break; - } - } -} - -void MORE::DisplayInputStream() -{ - // We need to be able to read STDIN for key presses, but it is most - // likely redirected - so clone the handle, and reconstruct real STDIN - // from STDERR (idea from FreeDOS implementation, - // https://github.com/FDOS/more/blob/master/src/more.c) - if (!DOS_DuplicateEntry(STDIN, &input_handle) || - !DOS_ForceDuplicateEntry(STDERR, STDIN)) { - LOG_ERR("DOS: Unable to prepare handles in MORE.COM"); - return; - } - - WriteOut("\n"); - - // Since this CAN be STDIN input (there is no way to check), - // CTRL+C shall quit - ctrl_c_enable = true; - DisplaySingleStream(); -} - -UserDecision MORE::DisplaySingleStream() -{ - auto previous_column = GetCurrentColumn(); - - tabs_remaining = 0; - skip_next_cr = false; - skip_next_lf = false; - - auto decision = UserDecision::Next; - while (true) { - if (shutdown_requested) { - decision = UserDecision::Cancel; - break; - } - - // Read character - char code = 0; - if (!GetCharacter(code)) { - decision = UserDecision::Next; // end of current file - break; - } - - // A trick to make it more resistant to ANSI cursor movements - const auto current_row = GetCurrentRow(); - if (line_counter > current_row) - line_counter = current_row; - - // Handle new line characters - bool new_line = false; - if (code == code_cr) { - skip_next_lf = true; - new_line = true; - } else if (code == code_lf) { - skip_next_cr = true; - new_line = true; - } else { - skip_next_cr = false; - skip_next_lf = false; - } - - // Duplicate character on the output - if (new_line) - code = '\n'; - WriteOut("%c", code); - - // Detect 'new line' due to character passing the last column - const auto current_column = GetCurrentColumn(); - if (!current_column && previous_column) { - new_line = true; - } - previous_column = current_column; - - // Update new line counter, decide if pause needed - if (new_line && current_row) { - ++line_counter; - } - if (line_counter < max_lines) { - continue; - } - - // New line occured just enough times for a pause - decision = PromptUser(); - if (decision == UserDecision::Cancel || - decision == UserDecision::Next) { - break; - } - } - - if (GetCurrentColumn()) { - ++line_counter; - WriteOut("\n"); - } - - return decision; -} - -UserDecision MORE::PromptUser() -{ - line_counter = 0; - const bool multiple_files = input_files.size() > 1; - - if (GetCurrentColumn()) - WriteOut("\n"); - - if (multiple_files) - WriteOut(MSG_Get("PROGRAM_MORE_PROMPT_MULTI")); - else - WriteOut(MSG_Get("PROGRAM_MORE_PROMPT_SINGLE")); - - auto decision = UserDecision::Cancel; - - if (multiple_files) - decision = DOS_WaitForCancelContinueNext(); - else - decision = DOS_WaitForCancelContinue(); - - if (decision == UserDecision::Cancel || decision == UserDecision::Next) { - WriteOut(" "); - WriteOut(MSG_Get("PROGRAM_MORE_TERMINATE")); - WriteOut("\n"); - ++line_counter; - } else { - // We are going to continue - erase the prompt - WriteOut("\033[M"); // clear line - auto counter = GetCurrentColumn(); - while (counter--) - WriteOut("\033[D"); // cursor one position back - } - - return decision; -} - -bool MORE::GetCharacter(char &code) -{ - if (!tabs_remaining) { - while (true) { - // Retrieve character from input stream - uint16_t count = 1; - DOS_ReadFile(input_handle, - reinterpret_cast<uint8_t *>(&code), - &count); - - if (!count) { - return false; // end of stream - } - - if (ctrl_c_enable && code == code_ctrl_c) { - if (input_files.empty()) { - WriteOut("^C"); - } - return false; // quit by CTRL+C - } - - // Skip CR/LF characters if requested - if (skip_next_cr && code == code_cr) { - skip_next_cr = false; - } else if (skip_next_lf && code == code_lf) { - skip_next_lf = false; - } else { - break; - } - } - - // If TAB found, replace it with given number of spaces - if (code == '\t') { - tabs_remaining = tab_size; - } - } - - if (tabs_remaining) { - --tabs_remaining; - code = ' '; - } - - return true; -} - -uint8_t MORE::GetCurrentColumn() -{ - const auto page = real_readb(BIOSMEM_SEG, BIOSMEM_CURRENT_PAGE); - return CURSOR_POS_COL(page); -} - -uint8_t MORE::GetCurrentRow() -{ - const auto page = real_readb(BIOSMEM_SEG, BIOSMEM_CURRENT_PAGE); - return CURSOR_POS_ROW(page); -} - void MORE::AddMessages() { MSG_Add("PROGRAM_MORE_HELP_LONG", diff --git a/src/dos/program_more.h b/src/dos/program_more.h index ff3431087..ab57a783d 100644 --- a/src/dos/program_more.h +++ b/src/dos/program_more.h @@ -23,7 +23,7 @@ #include "programs.h" -#include <vector> +#include "program_more_output.h" class MORE final : public Program { public: @@ -38,39 +38,11 @@ public: void Run(); private: - bool ParseCommandLine(); - bool FindInputFiles(const std::vector<std::string> ¶ms); - - void DisplayInputFiles(); - void DisplayInputStream(); - UserDecision DisplaySingleStream(); - UserDecision PromptUser(); - - std::string GetShortName(const std::string &file_name, const char *msg_id); - static uint8_t GetCurrentColumn(); - static uint8_t GetCurrentRow(); - bool GetCharacter(char &code); + bool ParseCommandLine(MoreOutputFiles &output); + bool FindInputFiles(MoreOutputFiles &output, + const std::vector<std::string> ¶ms); void AddMessages(); - - struct InputFile { - std::string name = ""; // file name with path - bool is_device = false; - }; - - std::vector<InputFile> input_files = {}; - - uint16_t max_lines = 0; - uint16_t max_columns = 0; - uint16_t line_counter = 0; - - uint8_t tab_size = 8; - uint8_t tabs_remaining = 0; - bool skip_next_cr = false; - bool skip_next_lf = false; - - uint16_t input_handle = 0; // DOS handle of the input stream - bool ctrl_c_enable = false; // if CTRL+C in the input stream should quit }; #endif diff --git a/src/dos/program_more_output.cpp b/src/dos/program_more_output.cpp new file mode 100644 index 000000000..ee4cee812 --- /dev/null +++ b/src/dos/program_more_output.cpp @@ -0,0 +1,530 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2022-2022 The DOSBox Staging Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "program_more_output.h" + +#include "../ints/int10.h" +#include "callback.h" +#include "checks.h" +#include "dos_inc.h" +#include "string_utils.h" + +#include <algorithm> +#include <array> +#include <cctype> + +CHECK_NARROWING(); + +// ASCII control characters +constexpr char code_ctrl_c = 0x03; // end of text +constexpr char code_lf = 0x0a; // line feed +constexpr char code_cr = 0x0d; // carriage return + +// *************************************************************************** +// Base class, only for internal usage +// *************************************************************************** + +MoreOutputBase::MoreOutputBase(Program &program) : program(program) +{ + // Retrieve screen size, prepare limits + constexpr uint16_t min_lines = 10; + constexpr uint16_t min_columns = 40; + max_lines = std::max(min_lines, INT10_GetTextRows()); + max_columns = std::max(min_columns, INT10_GetTextColumns()); + // The prompt at the bottom will cause scrolling, + // so reduce the maximum number of lines accordingly + max_lines = static_cast<uint16_t>(max_lines - 1); +} + +void MoreOutputBase::SetTabSize(const uint8_t new_tab_size) +{ + assert(new_tab_size > 0); + tab_size = new_tab_size; +} + +uint16_t MoreOutputBase::GetMaxLines() const +{ + return max_lines; +} + +uint16_t MoreOutputBase::GetMaxColumns() const +{ + return max_columns; +} + +void MoreOutputBase::PrepareInternals() +{ + line_counter = 0; + + has_multiple_files = false; + should_end_on_ctrl_c = false; + should_print_ctrl_c = false; + was_prompt_recently = false; + + tabs_remaining = 0; + + skip_next_cr = false; + skip_next_lf = false; +} + +UserDecision MoreOutputBase::DisplaySingleStream() +{ + auto previous_column = GetCurrentColumn(); + auto decision = UserDecision::Next; + + tabs_remaining = 0; + skip_next_cr = false; + skip_next_lf = false; + + was_prompt_recently = false; + + while (true) { + if (shutdown_requested) { + decision = UserDecision::Cancel; + break; + } + + // Read character + char code = 0; + bool is_definitely_last = false; + if (!GetCharacter(code, is_definitely_last)) { + decision = UserDecision::Next; // end of current file + break; + } + + // A trick to make it more resistant to ANSI cursor movements + const auto current_row = GetCurrentRow(); + if (line_counter > current_row) { + line_counter = current_row; + } + + // Handle new line characters + bool new_line = false; + if (code == code_cr) { + skip_next_lf = true; + new_line = true; + } else if (code == code_lf) { + skip_next_cr = true; + new_line = true; + } else { + skip_next_cr = false; + skip_next_lf = false; + } + + // Duplicate character on the output + if (new_line) { + code = '\n'; + } + WriteOut("%c", code); + + // Detect 'new line' due to character passing the last column + const auto current_column = GetCurrentColumn(); + if (!current_column && previous_column && code != code_cr && + code != code_lf) { + // The cursor just moved to new line due to too small + // screen width. If this is followed by new line, ignore + // it, so that it is possible to i. e. nicely display up + // to 80-character lines on a standard 80 column screen + skip_next_cr = true; + skip_next_lf = true; + new_line = true; + } + previous_column = current_column; + + // Update new line counter, decide if pause needed + if (new_line && current_row) { + ++line_counter; + } + if (code != '\n') { + was_prompt_recently = false; + } + if (is_definitely_last) { + // Skip further processing (including possible user prompt) + // if we know no data is left + decision = UserDecision::Next; + break; + } + if (line_counter < max_lines) { + continue; + } + + // New line occured just enough times for a pause + decision = PromptUser(); + if (decision == UserDecision::Cancel || + decision == UserDecision::Next) { + break; + } + } + + if (GetCurrentColumn()) { + ++line_counter; + WriteOut("\n"); + } + + return decision; +} + +UserDecision MoreOutputBase::PromptUser() +{ + line_counter = 0; + + if (GetCurrentColumn()) { + WriteOut("\n"); + } + + if (has_multiple_files) { + WriteOut(MSG_Get("PROGRAM_MORE_PROMPT_MULTI")); + } else { + WriteOut(MSG_Get("PROGRAM_MORE_PROMPT_SINGLE")); + } + + auto decision = UserDecision::Cancel; + + if (has_multiple_files) { + decision = DOS_WaitForCancelContinueNext(); + } else { + decision = DOS_WaitForCancelContinue(); + } + + if (decision == UserDecision::Cancel || decision == UserDecision::Next) { + WriteOut(" "); + WriteOut(MSG_Get("PROGRAM_MORE_TERMINATE")); + WriteOut("\n"); + ++line_counter; + } else { + // We are going to continue - erase the prompt + WriteOut("\033[M"); // clear line + auto counter = GetCurrentColumn(); + while (counter--) { + WriteOut("\033[D"); // cursor one position back + } + } + + was_prompt_recently = true; + return decision; +} + +bool MoreOutputBase::GetCharacter(char &code, bool &is_definitely_last) +{ + is_definitely_last = false; + if (!tabs_remaining) { + while (true) { + if (!GetCharacterRaw(code, is_definitely_last)) { + return false; // end of data + } + + if (should_end_on_ctrl_c && code == code_ctrl_c) { + if (should_print_ctrl_c) { + WriteOut("^C"); + } + return false; // end by CTRL+C + } + + // Skip CR/LF characters if requested + if (skip_next_cr && code == code_cr) { + skip_next_cr = false; + } else if (skip_next_lf && code == code_lf) { + skip_next_lf = false; + } else { + break; + } + } + + // If TAB found, replace it with given number of spaces + if (code == '\t') { + tabs_remaining = tab_size; + is_tab_definitely_last = is_definitely_last; + is_definitely_last = false; + } + } + + if (tabs_remaining) { + code = ' '; + --tabs_remaining; + is_definitely_last = is_tab_definitely_last && !tabs_remaining; + } + + return true; +} + +uint8_t MoreOutputBase::GetCurrentColumn() +{ + const auto page = real_readb(BIOSMEM_SEG, BIOSMEM_CURRENT_PAGE); + return CURSOR_POS_COL(page); +} + +uint8_t MoreOutputBase::GetCurrentRow() +{ + const auto page = real_readb(BIOSMEM_SEG, BIOSMEM_CURRENT_PAGE); + return CURSOR_POS_ROW(page); +} + +// *************************************************************************** +// Output file/device/stream content via MORE +// *************************************************************************** + +MoreOutputFiles::MoreOutputFiles(Program &program) : MoreOutputBase(program) {} + +void MoreOutputFiles::AddFile(const std::string &file_path, const bool is_device) +{ + input_files.emplace_back(); + auto &entry = input_files.back(); + + entry.path = file_path; + entry.is_device = is_device; +} + +void MoreOutputFiles::Display() +{ + if (SuppressWriteOut("")) { + input_files.clear(); + return; + } + + PrepareInternals(); + + has_multiple_files = input_files.size() > 1; + should_print_ctrl_c = input_files.empty(); + + // Show STDIN or input file(s) content + if (input_files.empty()) { + DisplayInputStream(); + } else { + DisplayInputFiles(); + } + + input_files.clear(); + WriteOut("\n"); +} + +std::string MoreOutputFiles::GetShortPath(const std::string &file_path, + const char *msg_id) +{ + assert(msg_id); + + // We need to make sure the path and file name fits within + // the designated space - if not, we have to shorten it. + + // The shortest name we should be able to display is: + // - 3 dots (ellipsis) + // - 1 path separator + // - 8 characters of name + // - 1 dot + // - 3 characters of extension + // This gives 16 characters. + // We need to keep the last column free (reduces max length by 1). + // Format string contains '%s' (increases max length by 2). + constexpr size_t min = 16; + const auto max = GetMaxColumns() - std::strlen(MSG_Get(msg_id)) + 1; + const auto max_len = std::max(min, max); + + // Nothing to do if file name matches the constraint + if (file_path.length() <= max_len) { + return file_path; + } + + // We need to shorten the name - try to strip part of the path + static const std::string ellipsis = "..."; + auto shortened = file_path; + while (shortened.length() > max_len && + std::count(shortened.begin(), shortened.end(), '\\') > 1) { + // Strip one level of path at a time + const auto pos = shortened.find('\\', shortened.find('\\') + 1); + shortened = ellipsis + shortened.substr(pos); + } + + // If still too long, just cut away the beginning + const auto len = shortened.length(); + if (len > max_len) { + shortened = ellipsis + shortened.substr(len - max_len + 3); + } + + return shortened; +} + +void MoreOutputFiles::DisplayInputStream() +{ + // We need to be able to read STDIN for key presses, but it is most + // likely redirected - so clone the handle, and reconstruct real STDIN + // from STDERR (idea from FreeDOS implementation, + // https://github.com/FDOS/more/blob/master/src/more.c) + if (!DOS_DuplicateEntry(STDIN, &input_handle) || + !DOS_ForceDuplicateEntry(STDERR, STDIN)) { + LOG_ERR("DOS: Unable to prepare handles in MORE"); + return; + } + + WriteOut("\n"); + + // Since this CAN be STDIN input (there is no way to check), + // CTRL+C shall quit + should_end_on_ctrl_c = true; + DisplaySingleStream(); +} + +void MoreOutputFiles::DisplayInputFiles() +{ + WriteOut("\n"); + + bool first = true; + for (const auto &input_file : input_files) { + if (!first && !was_prompt_recently && + UserDecision::Cancel == PromptUser()) { + break; + } + first = false; + + if (!DOS_OpenFile(input_file.path.c_str(), 0, &input_handle)) { + LOG_WARNING("DOS: MORE - could not open '%s'", + input_file.path.c_str()); + const auto short_path = GetShortPath(input_file.path, + "PROGRAM_MORE_OPEN_ERROR"); + WriteOut(MSG_Get("PROGRAM_MORE_OPEN_ERROR"), + short_path.c_str()); + WriteOut("\n"); + ++line_counter; + continue; + } + + if (input_file.is_device) { + const auto short_path = GetShortPath(input_file.path, + "PROGRAM_MORE_NEW_DEVICE"); + WriteOut(MSG_Get("PROGRAM_MORE_NEW_DEVICE"), + short_path.c_str()); + } else { + const auto short_path = GetShortPath(input_file.path, + "PROGRAM_MORE_NEW_FILE"); + WriteOut(MSG_Get("PROGRAM_MORE_NEW_FILE"), + short_path.c_str()); + } + WriteOut("\n"); + ++line_counter; + + // If input from a device, CTRL+C shall quit + should_end_on_ctrl_c = input_file.is_device; + + const auto decision = DisplaySingleStream(); + DOS_CloseFile(input_handle); + if (decision == UserDecision::Cancel) { + break; + } + } + + // End message and command prompt is going to appear; ensure the + // scrolling won't make top lines disappear before user reads them + const int free_rows_threshold = 2; + if (!was_prompt_recently && + GetMaxLines() - line_counter < free_rows_threshold) { + PromptUser(); + } + + WriteOut(MSG_Get("PROGRAM_MORE_END")); + WriteOut("\n"); +} + +bool MoreOutputFiles::GetCharacterRaw(char &code, bool &is_definitely_last) +{ + // Skip detecting if it is the last character for file/stream + // mode - this is often problematic (like with STDIN input) + // and wouldn't bring any user experience improvements due to + // our 'end of input' message displayed at the end. + is_definitely_last = false; + + uint16_t count = 1; + DOS_ReadFile(input_handle, reinterpret_cast<uint8_t *>(&code), &count); + + if (!count) { + return false; // end of stream + } + + return true; +} + +// *************************************************************************** +// Output string content via MORE +// *************************************************************************** + +MoreOutputStrings::MoreOutputStrings(Program &program) : MoreOutputBase(program) +{} + +void MoreOutputStrings::AddString(const char *format, ...) +{ + constexpr size_t buf_len = 16 * 1024; + + char buf[buf_len]; + va_list msg; + + va_start(msg, format); + vsnprintf(buf, buf_len - 1, format, msg); + va_end(msg); + + input_strings += std::string(buf); +} + +void MoreOutputStrings::Display() +{ + if (SuppressWriteOut("")) { + input_strings.clear(); + return; + } + + PrepareInternals(); + + input_position = 0; + + has_multiple_files = false; + should_end_on_ctrl_c = false; + + // Change the last CR/LF or LF/CR to a single + // end of the line symbol, so that 'is_definitely_last' + // can be calculated easilty + const auto length = input_strings.size(); + if (length >= 2) { + const auto code1 = input_strings[length - 2]; + const auto code2 = input_strings[length - 1]; + if ((code1 == code_lf && code2 == code_cr) || + (code1 == code_cr && code2 == code_lf)) { + input_strings.pop_back(); + } + } + + WriteOut("\n"); + DisplaySingleStream(); + + input_strings.clear(); + WriteOut("\n"); +} + +bool MoreOutputStrings::GetCharacterRaw(char &code, bool &is_definitely_last) +{ + is_definitely_last = false; + + if (input_position >= input_strings.size()) { + is_definitely_last = true; + return false; + } + + code = input_strings[input_position++]; + if (input_position == input_strings.size()) { + is_definitely_last = true; + } + + return true; +} diff --git a/src/dos/program_more_output.h b/src/dos/program_more_output.h new file mode 100644 index 000000000..e147eff2e --- /dev/null +++ b/src/dos/program_more_output.h @@ -0,0 +1,136 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2022-2022 The DOSBox Staging Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef DOSBOX_PROGRAM_MORE_OUTPUT_H +#define DOSBOX_PROGRAM_MORE_OUTPUT_H + +#include "programs.h" + +#include <string> + +// *************************************************************************** +// Base class, only for internal usage +// *************************************************************************** + +class MoreOutputBase { +public: + MoreOutputBase(Program &program); + virtual ~MoreOutputBase() = default; + + void SetTabSize(const uint8_t new_tab_size); + + virtual void Display() = 0; + +protected: + + uint16_t GetMaxLines() const; + uint16_t GetMaxColumns() const; + + void PrepareInternals(); + UserDecision DisplaySingleStream(); + UserDecision PromptUser(); + + virtual bool GetCharacterRaw(char &code, bool &is_definitely_last) = 0; + + uint16_t line_counter = 0; // how many lines printed out since last user prompt + + bool was_prompt_recently = false; // if next user prompt can be skipped + bool has_multiple_files = false; // if more than 1 file has to be displayed + bool should_end_on_ctrl_c = false; // reaction on CTRL+C in the input + bool should_print_ctrl_c = false; // if Ctrl+C on input should print '^C' + + // Wrappers for Program:: methods + + template <typename... Arguments> + void WriteOut(const char *format, Arguments... arguments) + { + program.WriteOut(format, arguments...); + } + + bool SuppressWriteOut(const char *format) + { + return program.SuppressWriteOut(format); + } + +private: + Program &program; + + static uint8_t GetCurrentColumn(); + static uint8_t GetCurrentRow(); + bool GetCharacter(char &code, bool &is_definitely_last); + + uint16_t max_lines = 0; // max number of lines to display between user prompts + uint16_t max_columns = 0; + + uint8_t tab_size = 8; // how many spaces to print for a TAB + uint8_t tabs_remaining = 0; // how many still to be printed for a current TAB + bool is_tab_definitely_last = false; + + bool skip_next_cr = false; + bool skip_next_lf = false; +}; + +// *************************************************************************** +// Output file/device/stream content via MORE +// *************************************************************************** + +class MoreOutputFiles final : public MoreOutputBase { +public: + MoreOutputFiles(Program &program); + + void AddFile(const std::string &file_path, const bool is_device); + void Display() override; + +private: + void DisplayInputFiles(); + void DisplayInputStream(); + + std::string GetShortPath(const std::string &file_path, const char *msg_id); + + bool GetCharacterRaw(char &code, bool &is_definitely_last) override; + + struct InputFile { + std::string path = ""; + bool is_device = false; // whether this is a regular file or a device + }; + + std::vector<InputFile> input_files = {}; + uint16_t input_handle = 0; // DOS handle of the input stream +}; + +// *************************************************************************** +// Output string content via MORE +// *************************************************************************** + +class MoreOutputStrings final : public MoreOutputBase { +public: + MoreOutputStrings(Program &program); + + void AddString(const char *format, ...); + void Display() override; + +private: + bool GetCharacterRaw(char &code, bool &is_definitely_last) override; + + std::string input_strings = {}; + size_t input_position = 0; +}; + +#endif diff --git a/src/dos/program_mount.cpp b/src/dos/program_mount.cpp index 7165d8840..d365a3060 100644 --- a/src/dos/program_mount.cpp +++ b/src/dos/program_mount.cpp @@ -23,14 +23,15 @@ #include "dosbox.h" +#include "../ints/int10.h" #include "bios_disk.h" +#include "cdrom.h" #include "control.h" #include "drives.h" #include "fs_utils.h" +#include "program_more_output.h" #include "shell.h" -#include "cdrom.h" #include "string_utils.h" -#include "../ints/int10.h" void MOUNT::Move_Z(char new_z) { @@ -137,7 +138,9 @@ void MOUNT::Run(void) { // a side effect of not being able to parse the correct // command line options. if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_MOUNT_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_MOUNT_HELP_LONG")); + output.Display(); return; } @@ -405,7 +408,9 @@ void MOUNT::Run(void) { if (type == "floppy") incrementFDD(); return; showusage: - WriteOut(MSG_Get("PROGRAM_MOUNT_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_MOUNT_HELP_LONG")); + output.Display(); return; } diff --git a/src/dos/program_mousectl.cpp b/src/dos/program_mousectl.cpp index 92759cc2c..c78bb4225 100644 --- a/src/dos/program_mousectl.cpp +++ b/src/dos/program_mousectl.cpp @@ -22,6 +22,7 @@ #include "ansi_code_markup.h" #include "checks.h" +#include "program_more_output.h" #include "string_utils.h" #include <set> @@ -31,7 +32,9 @@ CHECK_NARROWING(); void MOUSECTL::Run() { if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_MOUSECTL_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_MOUSECTL_HELP_LONG")); + output.Display(); return; } diff --git a/src/dos/program_placeholder.cpp b/src/dos/program_placeholder.cpp index f6859a041..9c5e75759 100644 --- a/src/dos/program_placeholder.cpp +++ b/src/dos/program_placeholder.cpp @@ -22,6 +22,8 @@ extern unsigned int result_errorcode; +#include "program_more_output.h" + void PLACEHOLDER::Run() { const auto command = cmd->GetFileName(); @@ -30,7 +32,10 @@ void PLACEHOLDER::Run() LOG_WARNING("%s: %s", command, MSG_Get("VISIT_FOR_MORE_HELP")); LOG_WARNING("%s: %s/%s", command, MSG_Get("WIKI_URL"), "Add-Utilities"); - WriteOut(MSG_Get("PROGRAM_PLACEHOLDER_HELP_LONG"), command); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_PLACEHOLDER_HELP_LONG"), command); + output.Display(); + WriteOut_NoParsing(MSG_Get("UTILITY_DRIVE_EXAMPLE_NO_TRANSLATE")); result_errorcode = dos.return_code; diff --git a/src/dos/program_rescan.cpp b/src/dos/program_rescan.cpp index a3ea36007..d20a964b5 100644 --- a/src/dos/program_rescan.cpp +++ b/src/dos/program_rescan.cpp @@ -20,6 +20,7 @@ #include "program_rescan.h" +#include "program_more_output.h" #include "string_utils.h" void RESCAN::Run(void) @@ -29,7 +30,9 @@ void RESCAN::Run(void) uint8_t drive = DOS_GetDefaultDrive(); if (HelpRequested()) { - WriteOut(MSG_Get("PROGRAM_RESCAN_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_RESCAN_HELP_LONG")); + output.Display(); return; } diff --git a/src/dos/program_serial.cpp b/src/dos/program_serial.cpp index 98ef983be..a03949179 100644 --- a/src/dos/program_serial.cpp +++ b/src/dos/program_serial.cpp @@ -28,6 +28,8 @@ #include "../hardware/serialport/nullmodem.h" #include "../hardware/serialport/serialmouse.h" +#include "program_more_output.h" + // Map the serial port type enums to printable names static std::map<SERIAL_PORT_TYPE, const std::string> serial_type_names = { {SERIAL_PORT_TYPE::DISABLED, "disabled"}, @@ -164,7 +166,9 @@ void SERIAL::Run() } // Show help. - WriteOut(MSG_Get("PROGRAM_SERIAL_HELP_LONG"), SERIAL_MAX_PORTS); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("PROGRAM_SERIAL_HELP_LONG")); + output.Display(); } void SERIAL::AddMessages() { diff --git a/src/misc/programs.cpp b/src/misc/programs.cpp index 6a9503bf2..a978e88c9 100644 --- a/src/misc/programs.cpp +++ b/src/misc/programs.cpp @@ -29,15 +29,16 @@ #include <cstring> #include <vector> +#include "../dos/program_more_output.h" #include "callback.h" -#include "regs.h" -#include "support.h" -#include "cross.h" #include "control.h" -#include "shell.h" +#include "cross.h" #include "hardware.h" #include "mapper.h" +#include "regs.h" +#include "shell.h" #include "string_utils.h" +#include "support.h" Bitu call_program; @@ -414,7 +415,13 @@ void CONFIG::Run(void) { P_WRITELANG, P_WRITELANG2, P_SECURE } presult = P_NOMATCH; - + + auto display_help = [this]() { + MoreOutputStrings output(*this); + output.AddString(MSG_Get("SHELL_CMD_CONFIG_HELP_LONG")); + output.Display(); + }; + bool first = true; std::vector<std::string> pvars; // Loop through the passed parameters @@ -499,15 +506,11 @@ void CONFIG::Run(void) { break; [[fallthrough]]; - case P_NOMATCH: - WriteOut(MSG_Get("SHELL_CMD_CONFIG_HELP_LONG")); - return; + case P_NOMATCH: display_help(); return; case P_HELP: case P_HELP2: case P_HELP3: { switch(pvars.size()) { - case 0: - WriteOut(MSG_Get("SHELL_CMD_CONFIG_HELP_LONG")); - return; + case 0: display_help(); return; case 1: { if (!strcasecmp("sections",pvars[0].c_str())) { // list the sections @@ -541,9 +544,7 @@ void CONFIG::Run(void) { } break; } - default: - WriteOut(MSG_Get("SHELL_CMD_CONFIG_HELP_LONG")); - return; + default: display_help(); return; } // if we have one value in pvars, it's a section // two values are section + property diff --git a/src/shell/shell.cpp b/src/shell/shell.cpp index d039f5531..e3572b78c 100644 --- a/src/shell/shell.cpp +++ b/src/shell/shell.cpp @@ -25,6 +25,7 @@ #include <stdlib.h> #include <string.h> +#include "../dos/program_more_output.h" #include "callback.h" #include "control.h" #include "fs_utils.h" @@ -486,7 +487,9 @@ void DOS_Shell::Run() char input_line[CMD_MAXLINE] = {0}; std::string line; if (cmd->FindExist("/?", false) || cmd->FindExist("-?", false)) { - WriteOut(MSG_Get("SHELL_CMD_COMMAND_HELP_LONG")); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("SHELL_CMD_COMMAND_HELP_LONG")); + output.Display(); return; } if (cmd->FindStringRemainBegin("/C",line)) { diff --git a/src/shell/shell_cmds.cpp b/src/shell/shell_cmds.cpp index 04210fda3..331130fc0 100644 --- a/src/shell/shell_cmds.cpp +++ b/src/shell/shell_cmds.cpp @@ -34,6 +34,8 @@ #include <string> #include <vector> +#include "../dos/program_more_output.h" +#include "../ints/int10.h" #include "ansi_code_markup.h" #include "bios.h" #include "callback.h" @@ -45,7 +47,6 @@ #include "string_utils.h" #include "support.h" #include "timer.h" -#include "../ints/int10.h" // clang-format off static const std::map<std::string, SHELL_Cmd> shell_cmds = { @@ -187,17 +188,19 @@ void DOS_Shell::DoCommand(char * line) { } bool DOS_Shell::WriteHelp(const std::string &command, char *args) { - if (!args || !ScanCMDBool(args, "?")) { + if (!args || !ScanCMDBool(args, "?")) return false; - } + + MoreOutputStrings output(*this); std::string short_key("SHELL_CMD_" + command + "_HELP"); - WriteOut("%s\n", MSG_Get(short_key.c_str())); + output.AddString("%s\n", MSG_Get(short_key.c_str())); std::string long_key("SHELL_CMD_" + command + "_HELP_LONG"); - if (MSG_Exists(long_key.c_str())) { - WriteOut("%s", MSG_Get(long_key.c_str())); - } else { - WriteOut("%s\n", command.c_str()); - } + if (MSG_Exists(long_key.c_str())) + output.AddString("%s", MSG_Get(long_key.c_str())); + else + output.AddString("%s\n", command.c_str()); + output.Display(); + return true; } @@ -260,21 +263,12 @@ void DOS_Shell::CMD_DELETE(char * args) { dos.dta(save_dta); } -void DOS_Shell::PrintHelpForCommands(HELP_Filter req_filter) +void DOS_Shell::PrintHelpForCommands(MoreOutputStrings &output, HELP_Filter req_filter) { - BIOS_NROWS; // macro creates 'nrows' queried from BIOS - nrows--; - int rows_printed = 0; - auto page_break_if_required = [&]() { - if (++rows_printed == nrows) { - CMD_PAUSE(empty_string); - rows_printed = 0; - } - }; - auto add_blank_line = [&]() { - WriteOut("\n"); - page_break_if_required(); - }; + static const auto format_header_str = convert_ansi_markup("[color=blue]%s[reset]\n"); + static const auto format_command_str = convert_ansi_markup(" [color=green]%-8s[reset] %s"); + static const auto format_header = format_header_str.c_str(); + static const auto format_command = format_command_str.c_str(); for (const auto &cat : {HELP_Category::Dosbox, HELP_Category::File, HELP_Category::Batch, HELP_Category::Misc}) { bool category_started = false; @@ -287,26 +281,16 @@ void DOS_Shell::PrintHelpForCommands(HELP_Filter req_filter) if (!category_started) { // Only add a newline to the first category heading when // displaying "common" help - if (cat == HELP_Category::Dosbox) { - if (req_filter == HELP_Filter::Common) { - add_blank_line(); - } - } else { - add_blank_line(); + if (cat != HELP_Category::Dosbox || req_filter == HELP_Filter::Common) { + output.AddString("\n"); } - auto header_pattern = convert_ansi_markup("[color=blue]%s[reset]\n"); - WriteOut(header_pattern.c_str(), HELP_CategoryHeading(cat)); + output.AddString(format_header, HELP_CategoryHeading(cat)); category_started = true; - page_break_if_required(); } std::string name(s.first); lowcase(name); - auto pattern = convert_ansi_markup(" [color=green]%-8s[reset] %s"); - WriteOut(pattern.c_str(), - name.c_str(), - HELP_GetShortHelp(s.second.name).c_str()); - - page_break_if_required(); + output.AddString(format_command, name.c_str(), + HELP_GetShortHelp(s.second.name).c_str()); } } } @@ -342,11 +326,15 @@ void DOS_Shell::CMD_HELP(char * args){ (this->*(shell_cmd.handler))(help_arg); } else if (ScanCMDBool(args, "A") || ScanCMDBool(args, "ALL")) { // Print help for all the commands - PrintHelpForCommands(HELP_Filter::All); + MoreOutputStrings output(*this); + PrintHelpForCommands(output, HELP_Filter::All); + output.Display(); } else { // Print help for just the common commands - WriteOut(MSG_Get("SHELL_CMD_HELP")); - PrintHelpForCommands(HELP_Filter::Common); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("SHELL_CMD_HELP")); + PrintHelpForCommands(output, HELP_Filter::Common); + output.Display(); } } @@ -1755,10 +1743,13 @@ void DOS_Shell::CMD_DATE(char *args) return; } if (ScanCMDBool(args, "?")) { - WriteOut(MSG_Get("SHELL_CMD_DATE_HELP")); - WriteOut("\n"); - WriteOut(MSG_Get("SHELL_CMD_DATE_HELP_LONG"), format, - format_date(2012, 10, 11)); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("SHELL_CMD_DATE_HELP")); + output.AddString("\n"); + output.AddString(MSG_Get("SHELL_CMD_DATE_HELP_LONG"), + format, + format_date(2012, 10, 11)); + output.Display(); return; } if (ScanCMDBool(args, "H")) { @@ -1839,9 +1830,11 @@ void DOS_Shell::CMD_TIME(char * args) { sprintf(format, "hh%cmm%css", time_separator, time_separator); sprintf(example, "13%c14%c15", time_separator, time_separator); if (ScanCMDBool(args, "?")) { - WriteOut(MSG_Get("SHELL_CMD_TIME_HELP")); - WriteOut("\n"); - WriteOut(MSG_Get("SHELL_CMD_TIME_HELP_LONG"), format, example); + MoreOutputStrings output(*this); + output.AddString(MSG_Get("SHELL_CMD_TIME_HELP")); + output.AddString("\n"); + output.AddString(MSG_Get("SHELL_CMD_TIME_HELP_LONG"), format, example); + output.Display(); return; } if (ScanCMDBool(args, "H")) { diff --git a/vs/dosbox.vcxproj b/vs/dosbox.vcxproj index 6e353d183..497376a9e 100644 --- a/vs/dosbox.vcxproj +++ b/vs/dosbox.vcxproj @@ -554,6 +554,7 @@ IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0</Command> <ClCompile Include="..\src\dos\program_ls.cpp" /> <ClCompile Include="..\src\dos\program_mem.cpp" /> <ClCompile Include="..\src\dos\program_more.cpp" /> + <ClCompile Include="..\src\dos\program_more_output.cpp" /> <ClCompile Include="..\src\dos\program_mount_common.cpp" /> <ClCompile Include="..\src\dos\program_mount.cpp" /> <ClCompile Include="..\src\dos\program_mousectl.cpp" /> diff --git a/vs/dosbox.vcxproj.filters b/vs/dosbox.vcxproj.filters index 310996485..1a6a49861 100644 --- a/vs/dosbox.vcxproj.filters +++ b/vs/dosbox.vcxproj.filters @@ -592,6 +592,9 @@ <ClCompile Include="..\src\dos\program_more.cpp"> <Filter>src\dos</Filter> </ClCompile> + <ClCompile Include="..\src\dos\program_more_output.cpp"> + <Filter>src\dos</Filter> + </ClCompile> <ClCompile Include="..\src\dos\program_mount.cpp"> <Filter>src\dos</Filter> </ClCompile> |