[MORE]: Rewrite almost completely the MORE command, taking advantage of the console pager functionality of the ConUtils library (see CORE-10504 and commit r73024).

- When displaying files, display the percentage of file read so far in the "Continue" prompt. Otherwise (when being piped or feeded from StdIn), just display the "Continue" prompt without percentage.
- Try to detect the text file encoding (ANSI, UTF16-BE/BE w/ or w/o BOM; UTF-8 to do!!) before displaying it. See the code for more details.

NOTE that the other functionalities of more (being able to scroll one line at a time, other command-line switches, etc...) are still not implemented yet.

svn path=/trunk/; revision=73025
This commit is contained in:
Hermès Bélusca-Maïto 2016-10-22 22:07:28 +00:00
parent eb198b854d
commit 6f5d03b414
25 changed files with 670 additions and 222 deletions

View file

@ -1,6 +1,11 @@
PROJECT(more)
include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/conutils)
add_executable(more more.c more.rc)
set_module_type(more win32cui)
set_module_type(more win32cui UNICODE)
target_link_libraries(more conutils_pager ${PSEH_LIB})
add_importlibs(more advapi32 user32 msvcrt kernel32)
set_target_properties(more PROPERTIES SUFFIX ".com")
add_importlibs(more user32 msvcrt kernel32)
add_cd_file(TARGET more DESTINATION reactos/system32 FOR all)

View file

@ -9,6 +9,7 @@ BEGIN
[Устройство:][Път]Файлово име Файл, чието съдържание да бъде показано.\n\
Команда\t\t Команда, чийто изход да бъде показан.\n\n\
При въпроса ""-- Продължаване --"" натиснете произволен клавиш, за показаване на следващата страница.\n"
IDS_CONTINUE " -- Продължаване (100%) -- "
IDS_FILE_ACCESS "Няма достъп до файл %s."
IDS_CONTINUE "-- Продължаване --"
IDS_CONTINUE_PROGRESS "-- Продължаване (%d%%) --"
IDS_FILE_ACCESS "Няма достъп до файл %s.\n"
END

View file

@ -9,6 +9,7 @@ BEGIN
[Unitat:][Ruta]Nom del fitxer Un fitxer, el contingut del qual serà mostrat.\n\
Instrucció\t\t Una instrucció, el resultat de la qual serà mostrada.\n\n\
Al visualitzar ""-- Continua --"" heu de premer qualsevol tecla per visualitzar la següent pàgina.\n"
IDS_CONTINUE " -- Continua (100%) -- "
IDS_FILE_ACCESS "No puc accedir al fitxer %s."
IDS_CONTINUE "-- Continua --"
IDS_CONTINUE_PROGRESS "-- Continua (%d%%) --"
IDS_FILE_ACCESS "No puc accedir al fitxer %s.\n"
END

View file

@ -16,6 +16,7 @@ BEGIN
Příkaz\t\t Příkaz, jehož výstup bude zobrazen.\n\n\
Při výzvě ""-- Pokračovat --"" lze stisknout libovolnou klávesu\n\
pro zobrazení další stránky.\n"
IDS_CONTINUE " -- Pokračovat (100%) -- "
IDS_FILE_ACCESS "Nelze získat přístup k souboru %s."
IDS_CONTINUE "-- Pokračovat --"
IDS_CONTINUE_PROGRESS "-- Pokračovat (%d%%) --"
IDS_FILE_ACCESS "Nelze získat přístup k souboru %s.\n"
END

View file

@ -10,6 +10,7 @@ BEGIN
Befehl\t\t Ein Befehl, dessen Ausgabe angezeigt werden soll.\n\n\
An der Eingabeaufforderung ""-- Fortsetzung --"" kann eine beliebige\n\
Taste gedrückt werden, um die nächste Seite anzuzeigen.\n"
IDS_CONTINUE " -- Fortsetzung (100%) -- "
IDS_FILE_ACCESS "Auf die Datei %s kann nicht zugegriffen werden."
IDS_CONTINUE "-- Fortsetzung --"
IDS_CONTINUE_PROGRESS "-- Fortsetzung (%d%%) --"
IDS_FILE_ACCESS "Auf die Datei %s kann nicht zugegriffen werden.\n"
END

View file

@ -9,6 +9,7 @@ BEGIN
[Δίσκος:][Μονοπάτι]Όνομα αρχείου Το αρχείο, τα δεδομένα του οποίου θα εμφανιστούν.\n\
Εντολή\t\t Η εντολή, της οποίας η έξοδος θα εμφανιστεί.\n\n\
Στη γραμμή εντολών ""-- Συνέχεια --"" μπορείτε να πατήσετε οποιοδήποτε κουμπί για να δείτε την επόμενη σελίδα.\n"
IDS_CONTINUE " -- Συνέχεια (100%) -- "
IDS_FILE_ACCESS "Δεν ήταν δυνατή η προσπέλαση του αρχείου %s."
IDS_CONTINUE "-- Συνέχεια --"
IDS_CONTINUE_PROGRESS "-- Συνέχεια (%d%%) --"
IDS_FILE_ACCESS "Δεν ήταν δυνατή η προσπέλαση του αρχείου %s.\n"
END

View file

@ -9,6 +9,7 @@ BEGIN
[Drive:][Path]File name A file, whose content shall be displayed.\n\
Command\t\t A command, whose output shall be displayed.\n\n\
At the prompt ""-- Continue --"" you can press any key to show the next page.\n"
IDS_CONTINUE " -- Continue (100%) -- "
IDS_FILE_ACCESS "Cannot access the file %s."
IDS_CONTINUE "-- Continue --"
IDS_CONTINUE_PROGRESS "-- Continue (%d%%) --"
IDS_FILE_ACCESS "Cannot access the file %s.\n"
END

View file

@ -9,6 +9,7 @@ BEGIN
[Unidad:][Ruta]Nombre del archivo Un archivo, cuyo contenido pueda ser mostrado.\n\
Comando\t\t Un comando, cuya salida pueda ser mostrada.\n\n\
Al visualizar ""-- Continuar --"" presione cualquier tecla para mostrar la siguiente página.\n"
IDS_CONTINUE " -- Continuar (100%) -- "
IDS_FILE_ACCESS "No se puede acceder al fichero %s."
IDS_CONTINUE "-- Continuar --"
IDS_CONTINUE_PROGRESS "-- Continuar (%d%%) --"
IDS_FILE_ACCESS "No se puede acceder al fichero %s.\n"
END

View file

@ -9,6 +9,7 @@ BEGIN
[Lecteur:][Chemin]Nom du fichier Un fichier, dont le contenu sera affiché.\n\
Commande\t\t Une commande, dont la sortie sera affichée.\n\n\
À l'invite ""-- Continuer --"" Vous pouvez appuyer sur n'importe quelle touche pour afficher la page suivante.\n"
IDS_CONTINUE " -- Continuer (100%) -- "
IDS_FILE_ACCESS "Impossible d'accéder au fichier %s."
IDS_CONTINUE "-- Continuer --"
IDS_CONTINUE_PROGRESS "-- Continuer (%d %%) --"
IDS_FILE_ACCESS "Impossible d'accéder au fichier %s.\n"
END

View file

@ -9,6 +9,7 @@ BEGIN
[Drive:][Path]File Il file da visualizzare.\n\
Comando\t\t Il comando di cui l'output dev'essere visualizzato.\n\n\
Alla richiesta ""-- Continua --"" premere un tasto qualsiasi per visualizzare la pagina successiva.\n"
IDS_CONTINUE " -- Continua (100%) -- "
IDS_FILE_ACCESS "Impossibile accedere al file %s."
IDS_CONTINUE "-- Continua --"
IDS_CONTINUE_PROGRESS "-- Continua (%d%%) --"
IDS_FILE_ACCESS "Impossibile accedere al file %s.\n"
END

View file

@ -21,6 +21,7 @@ BEGIN
būti atvaizduotas.\n\n\
Pasirodžius raginimui ""-- Toliau --"" spauskite bet kurį klavišą, kad\n\
pamatytumėte sekantį puslapį.\n"
IDS_CONTINUE " -- Toliau (100%) -- "
IDS_FILE_ACCESS "Nepavyko atverti bylos %s."
IDS_CONTINUE "-- Toliau --"
IDS_CONTINUE_PROGRESS "-- Toliau (%d%%) --"
IDS_FILE_ACCESS "Nepavyko atverti bylos %s.\n"
END

View file

@ -9,6 +9,7 @@ BEGIN
[Stasjon:][Mappe]Filnavn En fil, Velg innhold som skal vises.\n\
Command\t\t En kommando, Velg output som skal vises.\n\n\
At the prompt ""-- Fortsett --"" du kan trykke en tast for å vise neste side.\n"
IDS_CONTINUE " -- Fortsett (100%) -- "
IDS_FILE_ACCESS "Får ikke tilgang til filen %s."
IDS_CONTINUE "-- Fortsett --"
IDS_CONTINUE_PROGRESS "-- Fortsett (%d%%) --"
IDS_FILE_ACCESS "Får ikke tilgang til filen %s.\n"
END

View file

@ -17,6 +17,7 @@ BEGIN
[Napęd:][Ścieżka]Nazwa pliku Plik, którego zawartość ma być wyświetlona.\n\
Polecenie\t\t Polecenie, którego wynik ma być wyświetlony.\n\n\
Po wyświetleniu ""-- Kontynuuj --"" możesz nacisnąć dowolny klawisz, aby przejść do następnej strony.\n"
IDS_CONTINUE " -- Kontynuuj (100%) -- "
IDS_FILE_ACCESS "Brak dostępu do pliku: %s."
IDS_CONTINUE "-- Kontynuuj --"
IDS_CONTINUE_PROGRESS "-- Kontynuuj (%d%%) --"
IDS_FILE_ACCESS "Brak dostępu do pliku: %s.\n"
END

View file

@ -18,6 +18,7 @@ BEGIN
Comandă Comanda a cărei ieșire va fi afișată.\n\n\
La vederea sugestiei ""-- Continuă --"" veți apăsa o tastă pentru afișarea\n\
următoarei pagini.\n"
IDS_CONTINUE " -- Continuă (100%) -- "
IDS_FILE_ACCESS "Fișierul «%s» nu poate fi accesat!"
IDS_CONTINUE "-- Continuă --"
IDS_CONTINUE_PROGRESS "-- Continuă (%d%%) --"
IDS_FILE_ACCESS "Fișierul «%s» nu poate fi accesat!\n"
END

View file

@ -17,6 +17,7 @@ BEGIN
[диск:][путь]имя_файла Файл, отображаемый по фрагментам.\n\
имя_команды Команда, вывод которой отображается на экране.\n\n\
При запросе ""-- Продолжить --"" вы можете нажать любую клавишу для отображения следующего экрана.\n"
IDS_CONTINUE " -- Продолжить (100%) -- "
IDS_FILE_ACCESS "Нет доступа к файлу %s."
IDS_CONTINUE "-- Продолжить --"
IDS_CONTINUE_PROGRESS "-- Продолжить (%d%%) --"
IDS_FILE_ACCESS "Нет доступа к файлу %s.\n"
END

View file

@ -14,6 +14,7 @@ BEGIN
Príkaz\t\t Príkaz, ktorého výstup má byť zobrazený.\n\n\
Pri výzve ""-- Pokračujte --"" môžete stlačiť ľubovoľný kláves\n\
k zobrazeniu nasledujúcej stránky.\n"
IDS_CONTINUE " -- Pokračujte (100%) -- "
IDS_FILE_ACCESS "Neviem získať prístup k súboru %s."
IDS_CONTINUE "-- Pokračujte --"
IDS_CONTINUE_PROGRESS "-- Pokračujte (%d%%) --"
IDS_FILE_ACCESS "Neviem získať prístup k súboru %s.\n"
END

View file

@ -13,6 +13,7 @@ BEGIN
[Drive:][Path]File name Nje dokument, përmbajtja e të cilit do të shfaqet.\n\
Komandë\t\t Nje komande, nxjerrja e te cilit do të shfaqet.\n\n\
At the prompt ""-- Vazhdo --"" ju mund të shtypni çfarëdo butoni për të vazhduar në faqen tjetër.\n"
IDS_CONTINUE " -- Vazhdo (100%) -- "
IDS_FILE_ACCESS "Nuk mund të aksesoj dokumentin %s."
IDS_CONTINUE "-- Vazhdo --"
IDS_CONTINUE_PROGRESS "-- Vazhdo (%d%%) --"
IDS_FILE_ACCESS "Nuk mund të aksesoj dokumentin %s.\n"
END

View file

@ -16,6 +16,7 @@ BEGIN
[Enhet:][Mapp]Filnamn En fil, Välj innehåll som skall visas.\n\
Kommando\t\t Ett kommando, Välj vad som skall visas.\n\n\
vid prompten ""-- Fortsätt --"" du kan trycka valfri knapp för att visa nästa sida.\n"
IDS_CONTINUE " -- Fortsätt (100%) -- "
IDS_FILE_ACCESS "Får inte tillgång till filen %s."
IDS_CONTINUE "-- Fortsätt --"
IDS_CONTINUE_PROGRESS "-- Fortsätt (%d%%) --"
IDS_FILE_ACCESS "Får inte tillgång till filen %s.\n"
END

View file

@ -11,6 +11,7 @@ BEGIN
[Sürücü:][Yol]Kütük adı İçeriği görüntülenecek bir kütük.\n\
Komut\t\t Çıkışı görüntülenecek bir komut.\n\n\
İstemde ""-- Sürdür --"" ile bir sonraki sayfayı göstermek için rastgele bir düğmeye basabilirsiniz.\n"
IDS_CONTINUE " -- Sürdür (%100) -- "
IDS_FILE_ACCESS "%s kütüğüne erişilemiyor."
IDS_CONTINUE "-- Sürdür --"
IDS_CONTINUE_PROGRESS "-- Sürdür (%%%d) --"
IDS_FILE_ACCESS "%s kütüğüne erişilemiyor.\n"
END

View file

@ -17,6 +17,7 @@ BEGIN
[Диск:][Шлях]Ім'я_файлу Файл, вмiст якого треба вивести на екран.\n\
Command\t\t Команда, результат роботи якої треба вивести на екран.\n\n\
На запрошення ""-- Далi --"" Ви можете натиснути будь-яку клавiшу щоб побачити наступну сторiнку.\n"
IDS_CONTINUE " -- Далi (100%) -- "
IDS_FILE_ACCESS "Не можу отримати доступ до файла %s."
IDS_CONTINUE "-- Далi --"
IDS_CONTINUE_PROGRESS "-- Далi (%d%%) --"
IDS_FILE_ACCESS "Не можу отримати доступ до файла %s.\n"
END

View file

@ -11,6 +11,7 @@ BEGIN
[驱动器:][路径]文件名 一个将要被如此显示内容的文件。\n\
命令\t\t 一个输出将要被如此显示的命令。\n\n\
在 ""-- 继续 --"" 的提示出现时你可以按任意键来显示下一页。\n"
IDS_CONTINUE " -- 继续 (100%) -- "
IDS_FILE_ACCESS "无法访问文件 %s。"
IDS_CONTINUE "-- 继续 --"
IDS_CONTINUE_PROGRESS "-- 继续 (%d%%) --"
IDS_FILE_ACCESS "无法访问文件 %s。\n"
END

View file

@ -11,6 +11,7 @@ BEGIN
[磁碟機:][路徑]檔案名 一個將要被如此顯示內容的檔案。\n\
命令\t\t 一個輸出將要被如此顯示的命令。\n\n\
在 ""-- 繼續 --"" 的提示出現時你可以按任意鍵來顯示下一頁。\n"
IDS_CONTINUE " -- 繼續 (100%) -- "
IDS_FILE_ACCESS "無法訪問檔案 %s。"
IDS_CONTINUE "-- 繼續 --"
IDS_CONTINUE_PROGRESS "-- 繼續 (%d%%) --"
IDS_FILE_ACCESS "無法訪問檔案 %s。\n"
END

View file

@ -1,3 +1,14 @@
/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS More Command
* FILE: base/applications/cmdutils/more/more.c
* PURPOSE: Displays text stream from STDIN or from an arbitrary number
* of files to STDOUT, with screen capabilities (more than CAT,
* but less than LESS ^^).
* PROGRAMMERS: Paolo Pantaleo
* Timothy Schepens
* Hermes Belusca-Maito (hermes.belusca@sfr.fr)
*/
/*
* MORE.C - external command.
*
@ -5,209 +16,616 @@
*
* 26 Sep 1999 - Paolo Pantaleo <paolopan@freemail.it>
* started
*
* Oct 2003 - Timothy Schepens <tischepe at fastmail dot fm>
* use window size instead of buffer size.
*/
#include <stdio.h>
#include <malloc.h>
#include <tchar.h>
#include <windef.h>
#include <winbase.h>
#include <winnls.h>
#include <winuser.h>
#include <wincon.h>
#include <conutils.h>
#include "resource.h"
static TCHAR szCont[128];
static DWORD szContLength;
static HINSTANCE hApp;
/* PagePrompt statistics for the current file */
DWORD dwFileSize; // In bytes
DWORD dwSumReadBytes, dwSumReadChars;
// The average number of bytes per character is equal to
// dwSumReadBytes / dwSumReadChars. Note that dwSumReadChars
// will never be == 0 when ConWritePaging (and possibly PagePrompt)
// is called.
/*handle for file and console*/
HANDLE hStdIn;
HANDLE hStdOut;
HANDLE hStdErr;
/* Handles for file and console */
HANDLE hFile = INVALID_HANDLE_VALUE;
HANDLE hStdIn, hStdOut;
HANDLE hKeyboard;
static VOID
GetScreenSize (PSHORT maxx, PSHORT maxy)
static BOOL
__stdcall
PagePrompt(PCON_PAGER Pager, DWORD Done, DWORD Total)
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
HANDLE hInput = ConStreamGetOSHandle(StdIn);
DWORD dwMode;
KEY_EVENT_RECORD KeyEvent;
GetConsoleScreenBufferInfo (hStdOut, &csbi);
*maxx = (csbi.srWindow.Right - csbi.srWindow.Left) + 1;
*maxy = (csbi.srWindow.Bottom - csbi.srWindow.Top) - 4;
}
static
VOID ConOutPuts (LPTSTR szText)
{
DWORD dwWritten;
WriteFile (GetStdHandle (STD_OUTPUT_HANDLE), szText, _tcslen(szText), &dwWritten, NULL);
WriteFile (GetStdHandle (STD_OUTPUT_HANDLE), "\n", 1, &dwWritten, NULL);
}
static VOID
ConInKey (VOID)
{
INPUT_RECORD ir;
DWORD dwRead;
do
{
ReadConsoleInput (hKeyboard, &ir, 1, &dwRead);
if ((ir.EventType == KEY_EVENT) &&
(ir.Event.KeyEvent.bKeyDown == TRUE))
return;
}
while (TRUE);
}
static VOID
WaitForKey (VOID)
{
DWORD dwWritten;
WriteFile (hStdErr, szCont , szContLength, &dwWritten, NULL);
ConInKey();
WriteFile (hStdErr, _T("\n"), 1, &dwWritten, NULL);
// FlushConsoleInputBuffer (hConsoleIn);
}
//INT CommandMore (LPTSTR cmd, LPTSTR param)
int main (int argc, char **argv)
{
SHORT maxx,maxy;
SHORT line_count=0,ch_count=0;
DWORD i, last;
HANDLE hFile = INVALID_HANDLE_VALUE;
TCHAR szFullPath[MAX_PATH];
TCHAR szMsg[1024];
/*reading/writing buffer*/
TCHAR *buff;
/*bytes written by WriteFile and ReadFile*/
DWORD dwRead,dwWritten;
/*ReadFile() return value*/
BOOL bRet;
hStdIn = GetStdHandle(STD_INPUT_HANDLE);
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
hStdErr = GetStdHandle(STD_ERROR_HANDLE);
hApp = GetModuleHandle(NULL);
buff=malloc(4096);
if (!buff)
{
ConOutPuts(_T("Error: no memory"));
return 1;
}
if (argc > 1 && _tcsncmp (argv[1], _T("/?"), 2) == 0)
{
if (LoadString(hApp, IDS_USAGE, buff, 4096 / sizeof(TCHAR)) < (int)(4096 / sizeof(TCHAR)))
{
CharToOem(buff, buff);
ConOutPuts(buff);
}
free(buff);
return 0;
}
hKeyboard = CreateFile (_T("CONIN$"), GENERIC_READ|GENERIC_WRITE,
0,NULL,OPEN_ALWAYS,0,0);
GetScreenSize(&maxx,&maxy);
FlushConsoleInputBuffer (hKeyboard);
if(argc > 1)
{
GetFullPathNameA(argv[1], MAX_PATH, szFullPath, NULL);
hFile = CreateFile (szFullPath,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
0,
0);
if (hFile == INVALID_HANDLE_VALUE)
{
if (LoadString(hApp, IDS_FILE_ACCESS, szMsg, sizeof(szMsg) / sizeof(TCHAR)) < (int)(sizeof(szMsg) / sizeof(TCHAR)))
{
_stprintf(buff, szMsg, szFullPath);
CharToOem(buff, buff);
ConOutPuts(buff);
}
free(buff);
return 0;
}
}
else
{
hFile = hStdIn;
}
if (!LoadString(hApp, IDS_CONTINUE, szCont, sizeof(szCont) / sizeof(TCHAR)))
/*
* Just use the simple prompt if the file being displayed is the STDIN,
* otherwise use the prompt with progress percentage.
*
* The progress percentage is evaluated as follows.
* So far we have read a total of 'dwSumReadBytes' bytes from the file.
* Amongst those is the latest read chunk of 'dwReadBytes' bytes, to which
* correspond a number of 'dwReadChars' characters with which we have called
* ConWritePaging who called PagePrompt. We then have: Total == dwReadChars.
* During this ConWritePaging call the PagePrompt was called after 'Done'
* number of characters over 'Total'.
* It should be noted that for 'dwSumReadBytes' number of bytes read it
* *roughly* corresponds 'dwSumReadChars' number of characters. This is
* because there may be some failures happening during the conversion of
* the bytes read to the character string for a given encoding.
* Therefore the number of characters displayed on screen is equal to:
* dwSumReadChars - Total + Done ,
* but the best corresponding approximed number of bytes would be:
* dwSumReadBytes - (Total - Done) * (dwSumReadBytes / dwSumReadChars) ,
* where the ratio is the average number of bytes per character.
* The percentage is then computed relative to the total file size.
*/
if (hFile == hStdIn)
{
/* Shouldn't happen, so exit */
free(buff);
ConResPuts(Pager->Screen->Stream, IDS_CONTINUE);
}
else
{
ConResPrintf(Pager->Screen->Stream, IDS_CONTINUE_PROGRESS,
// (dwSumReadChars - Total + Done) * 100 / dwFileSize
(dwSumReadBytes - (Total - Done) *
(dwSumReadBytes / dwSumReadChars)) * 100 / dwFileSize
);
}
// TODO: Implement prompt read line!
// FIXME: Does not support TTY yet!
/* RemoveBreakHandler */
SetConsoleCtrlHandler(NULL, TRUE);
/* ConInDisable */
GetConsoleMode(hInput, &dwMode);
dwMode &= ~ENABLE_PROCESSED_INPUT;
SetConsoleMode(hInput, dwMode);
do
{
// FIXME: Does not support TTY yet!
// ConInKey(&KeyEvent);
INPUT_RECORD ir;
DWORD dwRead;
do
{
ReadConsoleInput(hInput, &ir, 1, &dwRead);
}
while ((ir.EventType != KEY_EVENT) || (!ir.Event.KeyEvent.bKeyDown));
/* Got our key, return to caller */
KeyEvent = ir.Event.KeyEvent;
}
while ((KeyEvent.wVirtualKeyCode == VK_SHIFT) ||
(KeyEvent.wVirtualKeyCode == VK_MENU) ||
(KeyEvent.wVirtualKeyCode == VK_CONTROL));
/* AddBreakHandler */
SetConsoleCtrlHandler(NULL, FALSE);
/* ConInEnable */
GetConsoleMode(hInput, &dwMode);
dwMode |= ENABLE_PROCESSED_INPUT;
SetConsoleMode(hInput, dwMode);
/*
* Erase the full line where the cursor is, and move
* the cursor back to the beginning of the line.
*/
ConClearLine(Pager->Screen->Stream);
if ((KeyEvent.wVirtualKeyCode == VK_ESCAPE) ||
((KeyEvent.wVirtualKeyCode == L'C') &&
(KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))))
{
/* We break, output a newline */
WCHAR ch = L'\n';
ConStreamWrite(Pager->Screen->Stream, &ch, 1);
return FALSE;
}
return TRUE;
}
/*
* See base/applications/cmdutils/clip/clip.c!IsDataUnicode()
* and base/applications/notepad/text.c!ReadText() for more details.
* Also some good code example can be found at:
* https://github.com/AutoIt/text-encoding-detect
*/
typedef enum
{
ENCODING_ANSI = 0,
ENCODING_UTF16LE = 1,
ENCODING_UTF16BE = 2,
ENCODING_UTF8 = 3
} ENCODING;
static BOOL
IsDataUnicode(
IN PVOID Buffer,
IN DWORD BufferSize,
OUT ENCODING* Encoding OPTIONAL,
OUT PDWORD SkipBytes OPTIONAL)
{
PBYTE pBytes = Buffer;
ENCODING encFile = ENCODING_ANSI;
DWORD dwPos = 0;
/*
* See http://archives.miloush.net/michkap/archive/2007/04/22/2239345.html
* for more details about the algorithm and the pitfalls behind it.
* Of course it would be actually great to make a nice function that
* would work, once and for all, and put it into a library.
*/
/* Look for Byte Order Marks */
if ((BufferSize >= 2) && (pBytes[0] == 0xFF) && (pBytes[1] == 0xFE))
{
encFile = ENCODING_UTF16LE;
dwPos = 2;
}
else if ((BufferSize >= 2) && (pBytes[0] == 0xFE) && (pBytes[1] == 0xFF))
{
encFile = ENCODING_UTF16BE;
dwPos = 2;
}
else if ((BufferSize >= 3) && (pBytes[0] == 0xEF) && (pBytes[1] == 0xBB) && (pBytes[2] == 0xBF))
{
encFile = ENCODING_UTF8;
dwPos = 3;
}
else
{
/*
* Try using statistical analysis. Do not rely on the return value of
* IsTextUnicode as we can get FALSE even if the text is in UTF-16 BE
* (i.e. we have some of the IS_TEXT_UNICODE_REVERSE_MASK bits set).
* Instead, set all the tests we want to perform, then just check
* the passed tests and try to deduce the string properties.
*/
/*
* This mask contains the 3 highest bits from IS_TEXT_UNICODE_NOT_ASCII_MASK
* and the 1st highest bit from IS_TEXT_UNICODE_NOT_UNICODE_MASK.
*/
#define IS_TEXT_UNKNOWN_FLAGS_MASK ((7 << 13) | (1 << 11))
/* Flag out the unknown flags here, the passed tests will not have them either */
INT Tests = (IS_TEXT_UNICODE_NOT_ASCII_MASK |
IS_TEXT_UNICODE_NOT_UNICODE_MASK |
IS_TEXT_UNICODE_REVERSE_MASK | IS_TEXT_UNICODE_UNICODE_MASK)
& ~IS_TEXT_UNKNOWN_FLAGS_MASK;
INT Results;
IsTextUnicode(Buffer, BufferSize, &Tests);
Results = Tests;
/*
* As the IS_TEXT_UNICODE_NULL_BYTES or IS_TEXT_UNICODE_ILLEGAL_CHARS
* flags are expected to be potentially present in the result without
* modifying our expectations, filter them out now.
*/
Results &= ~(IS_TEXT_UNICODE_NULL_BYTES | IS_TEXT_UNICODE_ILLEGAL_CHARS);
/*
* NOTE: The flags IS_TEXT_UNICODE_ASCII16 and
* IS_TEXT_UNICODE_REVERSE_ASCII16 are not reliable.
*
* NOTE2: Check for potential "bush hid the facts" effect by also
* checking the original results (in 'Tests') for the absence of
* the IS_TEXT_UNICODE_NULL_BYTES flag, as we may presumably expect
* that in UTF-16 text there will be at some point some NULL bytes.
* If not, fall back to ANSI. This shows the limitations of using the
* IsTextUnicode API to perform such tests, and the usage of a more
* improved encoding detection algorithm would be really welcome.
*/
if (!(Results & IS_TEXT_UNICODE_NOT_UNICODE_MASK) &&
!(Results & IS_TEXT_UNICODE_REVERSE_MASK) &&
(Results & IS_TEXT_UNICODE_UNICODE_MASK) &&
(Tests & IS_TEXT_UNICODE_NULL_BYTES))
{
encFile = ENCODING_UTF16LE;
dwPos = (Results & IS_TEXT_UNICODE_SIGNATURE) ? 2 : 0;
}
else
if (!(Results & IS_TEXT_UNICODE_NOT_UNICODE_MASK) &&
!(Results & IS_TEXT_UNICODE_UNICODE_MASK) &&
(Results & IS_TEXT_UNICODE_REVERSE_MASK) &&
(Tests & IS_TEXT_UNICODE_NULL_BYTES))
{
encFile = ENCODING_UTF16BE;
dwPos = (Results & IS_TEXT_UNICODE_REVERSE_SIGNATURE) ? 2 : 0;
}
else
{
/*
* Either 'Results' has neither of those masks set, as it can be
* the case for UTF-8 text (or ANSI), or it has both as can be the
* case when analysing pure binary data chunk. This is therefore
* invalid and we fall back to ANSI encoding.
* FIXME: In case of failure, assume ANSI (as long as we do not have
* correct tests for UTF8, otherwise we should do them, and at the
* very end, assume ANSI).
*/
encFile = ENCODING_ANSI; // ENCODING_UTF8;
dwPos = 0;
}
}
if (Encoding)
*Encoding = encFile;
if (SkipBytes)
*SkipBytes = dwPos;
return (encFile != ENCODING_ANSI);
}
/*
* Adapted from base/shell/cmd/misc.c!FileGetString(), but with correct
* text encoding support. Also please note that similar code should be
* also used in the CMD.EXE 'TYPE' command.
* Contrary to CMD's FileGetString() we do not stop at new-lines.
*
* Read text data from a file and convert it from a given encoding to UTF-16.
*
* IN OUT PVOID pCacheBuffer and IN DWORD CacheBufferLength :
* Implementation detail so that the function uses an external user-provided
* buffer to store the data temporarily read from the file. The function
* could have used an internal buffer instead. The length is in number of bytes.
*
* IN OUT PWSTR* pBuffer and IN OUT PDWORD pnBufferLength :
* Reallocated buffer containing the string data converted to UTF-16.
* In input, contains a pointer to the original buffer and its length.
* In output, contains a pointer to the reallocated buffer and its length.
* The length is in number of characters.
*
* At first call to this function, pBuffer can be set to NULL, in which case
* when the function returns the pointer will point to a valid buffer.
* After the last call to this function, free the pBuffer pointer with:
* HeapFree(GetProcessHeap(), 0, *pBuffer);
*
* If Encoding is set to ENCODING_UTF16LE or ENCODING_UTF16BE, since we are
* compiled in UNICODE, no extra conversion is performed and therefore
* pBuffer is unused (remains unallocated) and one can directly use the
* contents of pCacheBuffer as it is expected to contain valid UTF-16 text.
*
* OUT PDWORD pdwReadBytes : Number of bytes read from the file (optional).
* OUT PDWORD pdwReadChars : Corresponding number of characters read (optional).
*/
static BOOL
FileGetString(
IN HANDLE hFile,
IN ENCODING Encoding,
IN OUT PVOID pCacheBuffer,
IN DWORD CacheBufferLength,
IN OUT PWCHAR* pBuffer,
IN OUT PDWORD pnBufferLength,
OUT PDWORD pdwReadBytes OPTIONAL,
OUT PDWORD pdwReadChars OPTIONAL)
{
BOOL Success;
UINT CodePage = (UINT)-1;
DWORD dwReadBytes;
INT len;
// ASSERT(pCacheBuffer && (CacheBufferLength > 0));
// ASSERT(CacheBufferLength % 2 == 0); // Cache buffer length MUST BE even!
// ASSERT(pBuffer && pnBufferLength);
/* Always reset the retrieved number of bytes/characters */
if (pdwReadBytes) *pdwReadBytes = 0;
if (pdwReadChars) *pdwReadChars = 0;
Success = ReadFile(hFile, pCacheBuffer, CacheBufferLength, &dwReadBytes, NULL);
if (!Success || dwReadBytes == 0)
return FALSE;
if (pdwReadBytes) *pdwReadBytes = dwReadBytes;
if ((Encoding == ENCODING_ANSI) || (Encoding == ENCODING_UTF8))
{
/* Conversion is needed */
if (Encoding == ENCODING_ANSI)
CodePage = GetConsoleCP(); // CP_ACP; // FIXME: Cache GetConsoleCP() value.
else // if (Encoding == ENCODING_UTF8)
CodePage = CP_UTF8;
/* Retrieve the needed buffer size */
len = MultiByteToWideChar(CodePage, 0, pCacheBuffer, dwReadBytes,
NULL, 0);
if (len == 0)
{
/* Failure, bail out */
return FALSE;
}
/* Initialize the conversion buffer if needed... */
if (*pBuffer == NULL)
{
*pnBufferLength = len;
*pBuffer = HeapAlloc(GetProcessHeap(), 0, *pnBufferLength * sizeof(WCHAR));
if (*pBuffer == NULL)
{
// *pBuffer = NULL;
*pnBufferLength = 0;
// WARN("DEBUG: Cannot allocate memory for *pBuffer!\n");
// ConErrFormatMessage(GetLastError());
return FALSE;
}
}
/* ... or reallocate only if the new length is greater than the old one */
else if (len > *pnBufferLength)
{
PWSTR OldBuffer = *pBuffer;
*pnBufferLength = len;
*pBuffer = HeapReAlloc(GetProcessHeap(), 0, *pBuffer, *pnBufferLength * sizeof(WCHAR));
if (*pBuffer == NULL)
{
/* Do not leak old buffer */
HeapFree(GetProcessHeap(), 0, OldBuffer);
// *pBuffer = NULL;
*pnBufferLength = 0;
// WARN("DEBUG: Cannot reallocate memory for *pBuffer!\n");
// ConErrFormatMessage(GetLastError());
return FALSE;
}
}
/* Now perform the conversion proper */
len = MultiByteToWideChar(CodePage, 0, pCacheBuffer, dwReadBytes,
*pBuffer, len);
dwReadBytes = len;
}
else
{
/*
* No conversion needed, just convert from big to little endian if needed.
* pBuffer and pnBufferLength are left untouched and pCacheBuffer can be
* directly used.
*/
PWCHAR pWChars = pCacheBuffer;
DWORD i;
dwReadBytes /= sizeof(WCHAR);
if (Encoding == ENCODING_UTF16BE)
{
for (i = 0; i < dwReadBytes; i++)
{
/* Equivalent to RtlUshortByteSwap: reverse high/low bytes */
pWChars[i] = MAKEWORD(HIBYTE(pWChars[i]), LOBYTE(pWChars[i]));
}
}
// else if (Encoding == ENCODING_UTF16LE), we are good, nothing to do.
}
/* Return the number of characters (dwReadBytes is converted) */
if (pdwReadChars) *pdwReadChars = dwReadBytes;
return TRUE;
}
// INT CommandMore(LPTSTR cmd, LPTSTR param)
int wmain(int argc, WCHAR* argv[])
{
// FIXME this stuff!
CON_SCREEN Screen = {StdOut};
CON_PAGER Pager = {&Screen, 0};
int i;
BOOL bRet, bContinue;
ENCODING Encoding;
DWORD SkipBytes = 0;
#define FileCacheBufferSize 4096
PVOID FileCacheBuffer = NULL;
PWCHAR StringBuffer = NULL;
DWORD StringBufferLength = 0;
DWORD dwReadBytes, dwReadChars;
TCHAR szFullPath[MAX_PATH];
hStdIn = GetStdHandle(STD_INPUT_HANDLE);
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
/* Initialize the Console Standard Streams */
ConStreamInit(StdIn , GetStdHandle(STD_INPUT_HANDLE) , UTF8Text, INVALID_CP);
ConStreamInit(StdOut, GetStdHandle(STD_OUTPUT_HANDLE), UTF8Text, INVALID_CP);
ConStreamInit(StdErr, GetStdHandle(STD_ERROR_HANDLE) , UTF8Text, INVALID_CP);
/*
* Bad usage (too much options) or we use the /? switch.
* Display help for the MORE command.
*/
if (argc > 1 && wcscmp(argv[1], L"/?") == 0)
{
ConResPuts(StdOut, IDS_USAGE);
return 0;
}
// FIXME: Parse all the remaining parameters.
// Then the file list can be found at the very end.
// FIXME2: Use the PARSER api that can be found in EVENTCREATE.
// NOTE: We might try to duplicate the ConOut for read access... ?
hKeyboard = CreateFileW(L"CONIN$", GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
FlushConsoleInputBuffer(hKeyboard);
ConStreamSetOSHandle(StdIn, hKeyboard);
FileCacheBuffer = HeapAlloc(GetProcessHeap(), 0, FileCacheBufferSize);
if (!FileCacheBuffer)
{
ConPuts(StdErr, L"Error: no memory\n");
CloseHandle(hKeyboard);
return 1;
}
szContLength = _tcslen(szCont);
/* Special case where we run 'MORE' without any argument: we use STDIN */
if (argc <= 1)
{
/*
* Assign STDIN handle to hFile so that the page prompt function will
* know the data comes from STDIN, and will take different actions.
*/
hFile = hStdIn;
/* Update the statistics for PagePrompt */
dwFileSize = 0;
dwSumReadBytes = dwSumReadChars = 0;
do
{
bRet = ReadFile(hFile,buff,4096,&dwRead,NULL);
/* We suppose we read text from the file */
for(last=i=0;i<dwRead && bRet;i++)
{
ch_count++;
if(buff[i] == _T('\n') || ch_count == maxx)
{
ch_count=0;
line_count++;
if (line_count == maxy)
/* For STDIN we always suppose we are in ANSI mode */
// SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
Encoding = ENCODING_ANSI; // ENCODING_UTF8;
bContinue = ConPutsPaging(&Pager, PagePrompt, TRUE, L"");
if (!bContinue)
goto Quit;
do
{
bRet = FileGetString(hFile, Encoding,
FileCacheBuffer, FileCacheBufferSize,
&StringBuffer, &StringBufferLength,
&dwReadBytes, &dwReadChars);
if (!bRet || dwReadBytes == 0 || dwReadChars == 0)
{
line_count = 0;
WriteFile(hStdOut,&buff[last], i-last+1, &dwWritten, NULL);
last=i+1;
FlushFileBuffers (hStdOut);
WaitForKey ();
/* We failed at reading the file, bail out */
break;
}
}
}
if (last<dwRead && bRet)
WriteFile(hStdOut,&buff[last], dwRead-last, &dwWritten, NULL);
}
while(dwRead>0 && bRet);
/* Update the statistics for PagePrompt */
dwSumReadBytes += dwReadBytes;
dwSumReadChars += dwReadChars;
free (buff);
CloseHandle (hKeyboard);
if (hFile != hStdIn)
CloseHandle (hFile);
bContinue = ConWritePaging(&Pager, PagePrompt, FALSE,
StringBuffer, dwReadChars);
/* If we Ctrl-C/Ctrl-Break, stop everything */
if (!bContinue)
goto Quit;
}
while (bRet && dwReadBytes > 0);
goto Quit;
}
return 0;
/* We have files: read them and output them to STDOUT */
for (i = 1; i < argc; i++)
{
GetFullPathNameW(argv[i], ARRAYSIZE(szFullPath), szFullPath, NULL);
hFile = CreateFileW(szFullPath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0, // FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
ConResPrintf(StdErr, IDS_FILE_ACCESS, szFullPath);
continue;
}
/* We currently do not support files too big */
dwFileSize = GetFileSize(hFile, NULL);
if (dwFileSize == INVALID_FILE_SIZE)
{
ConPuts(StdErr, L"ERROR: Invalid file size!\n");
CloseHandle(hFile);
continue;
}
/* We suppose we read text from the file */
/* Check whether the file is UNICODE and retrieve its encoding */
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
bRet = ReadFile(hFile, FileCacheBuffer, FileCacheBufferSize, &dwReadBytes, NULL);
IsDataUnicode(FileCacheBuffer, dwReadBytes, &Encoding, &SkipBytes);
SetFilePointer(hFile, SkipBytes, NULL, FILE_BEGIN);
/* Update the statistics for PagePrompt */
dwSumReadBytes = dwSumReadChars = 0;
bContinue = ConPutsPaging(&Pager, PagePrompt, TRUE, L"");
if (!bContinue)
{
CloseHandle(hFile);
goto Quit;
}
do
{
bRet = FileGetString(hFile, Encoding,
FileCacheBuffer, FileCacheBufferSize,
&StringBuffer, &StringBufferLength,
&dwReadBytes, &dwReadChars);
if (!bRet || dwReadBytes == 0 || dwReadChars == 0)
{
/*
* We failed at reading the file, bail out and
* continue with the other files.
*/
break;
}
/* Update the statistics for PagePrompt */
dwSumReadBytes += dwReadBytes;
dwSumReadChars += dwReadChars;
if ((Encoding == ENCODING_UTF16LE) || (Encoding == ENCODING_UTF16BE))
{
bContinue = ConWritePaging(&Pager, PagePrompt, FALSE,
FileCacheBuffer, dwReadChars);
}
else
{
bContinue = ConWritePaging(&Pager, PagePrompt, FALSE,
StringBuffer, dwReadChars);
}
/* If we Ctrl-C/Ctrl-Break, stop everything */
if (!bContinue)
{
CloseHandle(hFile);
goto Quit;
}
}
while (bRet && dwReadBytes > 0);
CloseHandle(hFile);
}
Quit:
if (StringBuffer) HeapFree(GetProcessHeap(), 0, StringBuffer);
HeapFree(GetProcessHeap(), 0, FileCacheBuffer);
CloseHandle(hKeyboard);
return 0;
}
/* EOF */

View file

@ -2,13 +2,16 @@
#include "resource.h"
#define REACTOS_STR_FILE_DESCRIPTION "ReactOS More Command"
#define REACTOS_STR_INTERNAL_NAME "more"
#define REACTOS_STR_ORIGINAL_FILENAME "more.com"
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
#define REACTOS_STR_FILE_DESCRIPTION "ReactOS More Command"
#define REACTOS_STR_INTERNAL_NAME "more"
#define REACTOS_STR_ORIGINAL_FILENAME "more.com"
#include <reactos/version.rc>
/* UTF-8 */
#pragma code_page(65001)
#ifdef LANGUAGE_BG_BG
#include "lang/bg-BG.rc"
#endif
@ -71,4 +74,4 @@
#endif
#ifdef LANGUAGE_ZH_TW
#include "lang/zh-TW.rc"
#endif
#endif

View file

@ -2,4 +2,5 @@
#define IDS_USAGE 100
#define IDS_CONTINUE 101
#define IDS_FILE_ACCESS 102
#define IDS_CONTINUE_PROGRESS 102
#define IDS_FILE_ACCESS 103