Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dosbox-staging/dosbox-staging.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFeralChild64 <>2022-11-07 22:37:45 +0300
committerFeralChild64 <>2022-11-13 20:31:03 +0300
commitc4e2fc4670497dc25afbe86819c74dadf1d5c7ab (patch)
tree79c6b883c25ec600faab8e7ce617c263becf55c0
parent49a0c73bdb25e5b1bd0c0d082ef81d629d11eafe (diff)
Use MORE to display help stringsfc/extended-more-1
-rw-r--r--include/shell.h3
-rw-r--r--src/dos/meson.build1
-rw-r--r--src/dos/program_autotype.cpp7
-rw-r--r--src/dos/program_boot.cpp5
-rw-r--r--src/dos/program_imgmount.cpp80
-rw-r--r--src/dos/program_intro.cpp9
-rw-r--r--src/dos/program_keyb.cpp5
-rw-r--r--src/dos/program_loadfix.cpp5
-rw-r--r--src/dos/program_loadrom.cpp9
-rw-r--r--src/dos/program_mem.cpp5
-rw-r--r--src/dos/program_more.cpp355
-rw-r--r--src/dos/program_more.h36
-rw-r--r--src/dos/program_more_output.cpp530
-rw-r--r--src/dos/program_more_output.h136
-rw-r--r--src/dos/program_mount.cpp13
-rw-r--r--src/dos/program_mousectl.cpp5
-rw-r--r--src/dos/program_placeholder.cpp7
-rw-r--r--src/dos/program_rescan.cpp5
-rw-r--r--src/dos/program_serial.cpp6
-rw-r--r--src/misc/programs.cpp29
-rw-r--r--src/shell/shell.cpp5
-rw-r--r--src/shell/shell_cmds.cpp89
-rw-r--r--vs/dosbox.vcxproj1
-rw-r--r--vs/dosbox.vcxproj.filters3
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 &param : params)
+ for (const auto &param : 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> &params)
+bool MORE::FindInputFiles(MoreOutputFiles &output,
+ const std::vector<std::string> &params)
{
- 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 &param : 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> &params)
// 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> &params)
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> &params)
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> &params)
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> &params);
-
- 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> &params);
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>