mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-27 08:37:58 +03:00
Add syntax highlighting for CSV and TSV files.
This commit is contained in:
@@ -11,6 +11,148 @@ enum LanguageStandard {
|
||||
// Helper functions (needed by the logic, inferred from original code)
|
||||
namespace { // Use an anonymous namespace to keep helpers local to this translation unit
|
||||
|
||||
/**
|
||||
* @brief Parses a single field in CSV/TSV, handling quoted content and including trailing separator
|
||||
*
|
||||
* @param input The input string
|
||||
* @param pos Current position (will be updated)
|
||||
* @param length Total length of input
|
||||
* @param delimiter Field delimiter (',' for CSV, '\t' for TSV)
|
||||
* @param startPos Output: start position of the field content
|
||||
* @param endPos Output: end position of the field content (including separator if present)
|
||||
* @return true if a field was successfully parsed
|
||||
*/
|
||||
inline bool parseCSVField( const char* input, int& pos, int length, char delimiter, int& startPos,
|
||||
int& endPos ) {
|
||||
if ( pos >= length ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
startPos = pos;
|
||||
bool inQuotes = false;
|
||||
bool hasQuotes = false;
|
||||
|
||||
// Check if field starts with quote
|
||||
if ( input[pos] == '"' ) {
|
||||
hasQuotes = true;
|
||||
inQuotes = true;
|
||||
pos++; // Skip opening quote
|
||||
}
|
||||
|
||||
while ( pos < length ) {
|
||||
char c = input[pos];
|
||||
|
||||
if ( hasQuotes && inQuotes ) {
|
||||
if ( c == '"' ) {
|
||||
// Check for escaped quote (double quote)
|
||||
if ( pos + 1 < length && input[pos + 1] == '"' ) {
|
||||
pos += 2; // Skip both quotes
|
||||
continue;
|
||||
} else {
|
||||
// End of quoted field
|
||||
pos++; // Skip closing quote
|
||||
inQuotes = false;
|
||||
|
||||
// Skip any whitespace until delimiter or end of line
|
||||
while ( pos < length && input[pos] != delimiter && input[pos] != '\n' &&
|
||||
input[pos] != '\r' &&
|
||||
std::isspace( static_cast<unsigned char>( input[pos] ) ) ) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Include delimiter if present
|
||||
if ( pos < length && input[pos] == delimiter ) {
|
||||
endPos = pos + 1; // Include the delimiter
|
||||
pos++; // Move past delimiter
|
||||
} else {
|
||||
endPos = pos; // End of line or end of input
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if ( c == '\n' || c == '\r' ) {
|
||||
// Unescaped newline in quoted field - invalid
|
||||
return false;
|
||||
} else {
|
||||
pos++;
|
||||
}
|
||||
} else {
|
||||
// Not in quotes or unquoted field
|
||||
if ( c == delimiter ) {
|
||||
// Found delimiter - include it in the field
|
||||
endPos = pos + 1; // Include the delimiter
|
||||
pos++; // Move past delimiter
|
||||
return true;
|
||||
} else if ( c == '\n' || c == '\r' ) {
|
||||
// End of line - don't include newline
|
||||
endPos = pos;
|
||||
return true;
|
||||
} else if ( c == '"' && !hasQuotes ) {
|
||||
// Quote in middle of unquoted field - treat as regular character
|
||||
pos++;
|
||||
} else {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reached end of input
|
||||
endPos = pos;
|
||||
return startPos < endPos; // Return true if we have any content
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses a complete CSV/TSV line and creates match groups for each column
|
||||
*
|
||||
* @param input The input string
|
||||
* @param startOffset Starting offset in the string
|
||||
* @param length Total length of input
|
||||
* @param delimiter Field delimiter (',' for CSV, '\t' for TSV)
|
||||
* @param matchList Output array for match ranges
|
||||
* @param maxMatches Maximum number of matches to store
|
||||
* @return Number of columns found
|
||||
*/
|
||||
inline size_t parseCSVLine( const char* input, int startOffset, int length, char delimiter,
|
||||
PatternMatcher::Range* matchList, size_t maxMatches ) {
|
||||
if ( startOffset < 0 || startOffset >= length || !matchList ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pos = startOffset;
|
||||
size_t columnCount = 0;
|
||||
|
||||
// Find end of current line
|
||||
int lineEnd = pos;
|
||||
while ( lineEnd < length && input[lineEnd] != '\n' && input[lineEnd] != '\r' ) {
|
||||
lineEnd++;
|
||||
}
|
||||
|
||||
// If line is empty, return 0
|
||||
if ( pos >= lineEnd ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Set the first match to cover the entire line
|
||||
matchList[0].start = startOffset;
|
||||
matchList[0].end = lineEnd;
|
||||
columnCount = 1;
|
||||
|
||||
// Parse individual fields/columns
|
||||
while ( pos < lineEnd && columnCount < maxMatches ) {
|
||||
int fieldStart, fieldEnd;
|
||||
|
||||
if ( parseCSVField( input, pos, lineEnd, delimiter, fieldStart, fieldEnd ) ) {
|
||||
// Add column match (group index starts from 1, 0 is the whole line)
|
||||
matchList[columnCount].start = fieldStart;
|
||||
matchList[columnCount].end = fieldEnd;
|
||||
columnCount++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return columnCount;
|
||||
}
|
||||
|
||||
inline bool isBinaryDigit( char c ) {
|
||||
return c == '0' || c == '1';
|
||||
}
|
||||
@@ -1049,6 +1191,22 @@ void ParserMatcherManager::registerBaseParsers() {
|
||||
return isJavaScriptRegEx( stringSearch, stringStartOffset, matchList, stringLength );
|
||||
} );
|
||||
|
||||
// CSV Parser - detects CSV fields and creates match groups for each column
|
||||
registerParser( "csv_parser", []( const char* stringSearch, int stringStartOffset,
|
||||
PatternMatcher::Range* matchList, size_t stringLength ) {
|
||||
const size_t MAX_CSV_COLUMNS = 63;
|
||||
return parseCSVLine( stringSearch, stringStartOffset, stringLength, ',', matchList,
|
||||
std::min( MAX_CSV_COLUMNS, stringLength ) );
|
||||
} );
|
||||
|
||||
// TSV Parser - detects TSV fields and creates match groups for each column
|
||||
registerParser( "tsv_parser", []( const char* stringSearch, int stringStartOffset,
|
||||
PatternMatcher::Range* matchList, size_t stringLength ) {
|
||||
const size_t MAX_TSV_COLUMNS = 63;
|
||||
return parseCSVLine( stringSearch, stringStartOffset, stringLength, '\t', matchList,
|
||||
std::min( MAX_TSV_COLUMNS, stringLength ) );
|
||||
} );
|
||||
|
||||
mRegisteredBaseParsers = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#include <eepp/ui/doc/languages/d.hpp>
|
||||
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
|
||||
|
||||
namespace EE { namespace UI { namespace Doc { namespace Language {
|
||||
|
||||
#define CSV_COLORING_PATTERN \
|
||||
"keyword", "type", "parameter", "string", "operator", "number", "literal", "function",
|
||||
|
||||
SyntaxDefinition& addCSV() {
|
||||
return SyntaxDefinitionManager::instance()->add(
|
||||
{ "CSV",
|
||||
{ "%.csv$" },
|
||||
{ { { "csv_parser" },
|
||||
{ "string", CSV_COLORING_PATTERN CSV_COLORING_PATTERN CSV_COLORING_PATTERN
|
||||
CSV_COLORING_PATTERN CSV_COLORING_PATTERN CSV_COLORING_PATTERN
|
||||
CSV_COLORING_PATTERN CSV_COLORING_PATTERN },
|
||||
"",
|
||||
SyntaxPatternMatchType::Parser } },
|
||||
{} } );
|
||||
}
|
||||
|
||||
SyntaxDefinition& addTSV() {
|
||||
return SyntaxDefinitionManager::instance()->add(
|
||||
{ "TSV",
|
||||
{ "%.tsv$" },
|
||||
{ { { "tsv_parser" },
|
||||
{ "string", CSV_COLORING_PATTERN CSV_COLORING_PATTERN CSV_COLORING_PATTERN
|
||||
CSV_COLORING_PATTERN CSV_COLORING_PATTERN CSV_COLORING_PATTERN
|
||||
CSV_COLORING_PATTERN CSV_COLORING_PATTERN },
|
||||
"",
|
||||
SyntaxPatternMatchType::Parser } },
|
||||
{} } );
|
||||
}
|
||||
|
||||
}}}} // namespace EE::UI::Doc::Language
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef EE_UI_DOC_CSV
|
||||
#define EE_UI_DOC_CSV
|
||||
|
||||
#include <eepp/ui/doc/syntaxdefinition.hpp>
|
||||
|
||||
namespace EE { namespace UI { namespace Doc { namespace Language {
|
||||
|
||||
extern SyntaxDefinition& addCSV();
|
||||
|
||||
extern SyntaxDefinition& addTSV();
|
||||
|
||||
}}}} // namespace EE::UI::Doc::Language
|
||||
|
||||
#endif
|
||||
@@ -24,7 +24,7 @@ SyntaxDefinition& addShellScript() {
|
||||
{ { "%f[%S][%+%-][%w%-_]+%f[=]" }, "function" },
|
||||
{ { "(%s%-%a[%w_%-]*%s+)(%d[%d%.]+)" }, { "normal", "function", "number" } },
|
||||
{ { "(%s%-%a[%w_%-]*%s+)(%a[%a%-_:=]+)" }, { "normal", "function", "symbol" } },
|
||||
{ { "[_%a][%w_]+%f[%+=]" }, "type" },
|
||||
{ { "[_%a][%w_-]+%f[%+=]" }, "type" },
|
||||
{ { "${.-}" }, "type" },
|
||||
{ { "$[%d$%a_@*][%w_]*" }, "type" },
|
||||
{ { "[%a_%-][%w_%-]*[%s]*%f[(]" }, "function" },
|
||||
|
||||
+19
-6
@@ -19,6 +19,7 @@
|
||||
#include <eepp/ui/doc/languages/covscript.hpp>
|
||||
#include <eepp/ui/doc/languages/crystal.hpp>
|
||||
#include <eepp/ui/doc/languages/csharp.hpp>
|
||||
#include <eepp/ui/doc/languages/csv.hpp>
|
||||
#include <eepp/ui/doc/languages/curry.hpp>
|
||||
#include <eepp/ui/doc/languages/d.hpp>
|
||||
#include <eepp/ui/doc/languages/dart.hpp>
|
||||
@@ -244,6 +245,18 @@ void LanguagesSyntaxHighlighting::load() {
|
||||
{ "%.cr$" },
|
||||
} );
|
||||
|
||||
sdm->addPreDefinition( {
|
||||
"Curry",
|
||||
[]() -> SyntaxDefinition& { return addCurry(); },
|
||||
{ "%.curry$" },
|
||||
} );
|
||||
|
||||
sdm->addPreDefinition( {
|
||||
"CSV",
|
||||
[]() -> SyntaxDefinition& { return addCSV(); },
|
||||
{ "%.csv$" },
|
||||
} );
|
||||
|
||||
sdm->addPreDefinition( {
|
||||
"C#",
|
||||
[]() -> SyntaxDefinition& { return addCSharp(); },
|
||||
@@ -252,12 +265,6 @@ void LanguagesSyntaxHighlighting::load() {
|
||||
"csharp",
|
||||
} );
|
||||
|
||||
sdm->addPreDefinition( {
|
||||
"Curry",
|
||||
[]() -> SyntaxDefinition& { return addCurry(); },
|
||||
{ "%.curry$" },
|
||||
} );
|
||||
|
||||
sdm->addPreDefinition( {
|
||||
"eC",
|
||||
[]() -> SyntaxDefinition& { return addeC(); },
|
||||
@@ -726,6 +733,12 @@ void LanguagesSyntaxHighlighting::load() {
|
||||
{},
|
||||
"typescriptreact" } );
|
||||
|
||||
sdm->addPreDefinition( {
|
||||
"TSV",
|
||||
[]() -> SyntaxDefinition& { return addTSV(); },
|
||||
{ "%.tsv$" },
|
||||
} );
|
||||
|
||||
sdm->addPreDefinition(
|
||||
{ "Typst", []() -> SyntaxDefinition& { return addTypst(); }, { "%.typ$" } } );
|
||||
|
||||
|
||||
Reference in New Issue
Block a user