feat(mime): support SAS ABAP (closes #7851)

This commit is contained in:
Elian Doran 2025-11-24 18:39:34 +02:00
parent 29f9d0c1cd
commit 84890fd5ad
No known key found for this signature in database
5 changed files with 211 additions and 0 deletions

View File

@ -0,0 +1,140 @@
import { StreamParser, StringStream } from "@codemirror/language";
const KEYWORD_WORDS =
'ABSTRACT ADD ADD-CORRESPONDING ADJACENT ALIAS ALIASES ALL AND APPEND APPENDING AS ASCENDING ASSERT ASSIGN ASSIGNED ASSIGNING ASSOCIATION AT AUTHORITY-CHECK BACK BEGIN BINARY BLOCK BOUND BREAK-POINT BY BYTE CALL CASE CAST CATCH CHANGING CHARACTER CHECK CLASS CLASS-DATA CLASS-METHOD CLASS-METHODS CLASS-POOL CLEAR CLOSE CNT COLLECT COMMIT COMMUNICATION COMPONENT COMPUTE CONCATENATE COND CONDENSE CONSTANTS CONTINUE CONTROLS CONV CONVERT CORRESPONDING COUNT CREATE CURRENCY DATA DEFAULT DEFERRED DEFINE DEFINITION DELETE DELETING DESCENDING DESCRIBE DESTINATION DETAIL DISPLAY DISPLAY-MODE DIVIDE DIVIDE-CORRESPONDING DO DUPLICATES DURATION EDITOR-CALL ELSE ELSEIF EMPTY END END-OF-DEFINITION END-OF-PAGE END-OF-SELECTION END-TEST-INJECTION END-TEST-SEAM ENDAT ENDCASE ENDCLASS ENDDO ENDEXEC ENDFORM ENDFUNCTION ENDIF ENDING ENDINTERFACE ENDLOOP ENDMETHOD ENDMODULE ENDON ENDPROVIDE ENDSELECT ENDTRY ENDWHILE ENUM EQ EVENT EVENTS EXCEPTION EXCEPTIONS EXEC EXIT EXIT-COMMAND EXPORT EXPORTING EXTRACT FETCH FIELD FIELD-GROUPS FIELD-SYMBOL FIELD-SYMBOLS FIELDS FINAL FIND FIRST FOR FORM FORMAT FOUND FRAME FREE FRIENDS FROM FUNCTION FUNCTION-POOL GE GENERATE GET HANDLE HARMLESS HASHED HEADER HIDE ID IF IMPLEMENTATION IMPLEMENTED IMPORT IMPORTING IN INCLUDE INDEX INFOTYPES INHERITING INIT INITIAL INITIALIZATION INPUT INSERT INSTANCE INTERFACE INTERFACE-POOL INTERFACES INTO IS KEY LEADING LEAVE LEFT LEFT-JUSTIFIED LENGTH LEVEL LIKE LINE LINE-COUNT LINE-SIZE LOAD LOCAL LOG-POINT LOOP LOWER MATCH MATCHCODE MESH MESSAGE MESSAGE-ID METHOD METHODS MODIFY MODULE MOVE MOVE-CORRESPONDING MULTIPLY MULTIPLY-CORRESPONDING NEW NEW-LINE NEW-PAGE NEW-SECTION NEXT NO NO-GAP NO-GAPS NO-SIGN NO-ZERO NON-UNIQUE NOT NUMBER OBJECT OBLIGATORY OCCURRENCE OCCURRENCES OCCURS OF OFFSET ON OPTIONAL OPTIONS OTHERS OUTPUT OVERLAY PACK PARAMETERS PARTIALLY PERFORM PLACES POSITION PRINT-CONTROL PRIVATE PROGRAM PROTECTED PROVIDE PUBLIC PUT RADIOBUTTON RAISE RAISING RANGE RANGES READ READ-ONLY RECEIVE RECEIVING REDEFINITION REDUCE REF REFERENCE REFRESH REGEX REJECT REPLACE REPORT REQUESTED RESERVE RESTORE RESULT RESULTS RETURN RETURNING RIGHT-JUSTIFIED RISK ROLLBACK RP-PROVIDE-FROM-LAST RUN SCAN SCREEN SCROLL SEARCH SECONDARY SECTION SELECT SELECT-OPTIONS SELECTION-SCREEN SEPARATED SET SHIFT SHORT SINGLE SKIP SORT SORTED SOURCE SPLIT STAMP STANDARD START-OF-SELECTION STARTING STATICS STEP STOP STRUCTURE SUBKEY SUBMATCHES SUBMIT SUBTRACT SUBTRACT-CORRESPONDING SUM SUMMARY SUPPLIED SUPPRESS SWITCH SYNTAX-CHECK SYNTAX-TRACE SYSTEM-CALL TABLE TABLES TASK TEST-INJECTION TEST-SEAM TESTING THEN TIME TIMES TITLE TITLEBAR TO TOP-OF-PAGE TRAILING TRANSFER TRANSFORMATION TRANSLATE TRANSPORTING TRY TYPE TYPE-POOL TYPE-POOLS TYPES ULINE UNASSIGN UNIQUE UNPACK UPDATE UPPER USING VALUE WHEN WHERE WHILE WINDOW WITH WORK WRITE';
const OPERATORS_SYMBOLS = '?= = > <> < <= >= + - * ** / & &&';
const OPERATOR_WORDS =
'EQ NE LT GT GE CS CP NP CO CN DIV MOD BIT-AND BIT-OR BIT-XOR BIT-NOT NOT OR AND XOR BETWEEN EQUIV BYTE-CO, BYTE-CN, BYTE-CA BYTE-NA BYTE-CS BYTE-NS';
const KEYWORDS = KEYWORD_WORDS.split(' ');
const OPERATORS = OPERATORS_SYMBOLS.concat(OPERATOR_WORDS, ' ').split(
' ',
);
const COMMENT = 'comment';
const KEYWORD = 'keyword';
const NUMBER = 'number';
const OPERATOR = 'operator';
const STRING = 'string';
interface Keywords {
[key: string]: boolean;
}
type CheckMatchCallback = (input: string) => boolean;
const composeKeywords = (words: string[]): Keywords =>
words.reduce(
(result, word) => ({
...result,
[word]: true,
}),
{},
);
const keywords = composeKeywords(KEYWORDS);
const checkMatch = (
stream: StringStream,
separators: string | string[],
callback: CheckMatchCallback,
): boolean => {
let next = stream.next();
let back = 0;
while (true) {
if (!next) {
break;
} else if (separators.includes(next)) {
stream.backUp(1);
break;
} else {
back++;
}
next = stream.next();
}
const toCheck = stream.current().toUpperCase();
const match = callback(toCheck);
if (match === false) {
stream.backUp(back);
}
return match;
};
const isKeyword = (stream: StringStream): boolean => {
const checkKeyword: CheckMatchCallback = (input: string) =>
keywords.propertyIsEnumerable(input);
const KEYWORD_SEPARATORS = '(.,: ';
return checkMatch(stream, KEYWORD_SEPARATORS, checkKeyword);
};
const isOperator = (stream: StringStream): boolean => {
const checkOperator: CheckMatchCallback = (input: string) =>
OPERATORS.includes(input);
return checkMatch(stream, ' ', checkOperator);
};
export const abapMode: StreamParser<unknown> = {
token: (stream: StringStream, state: any) => {
if (stream.eatSpace()) {
return null;
}
if (isKeyword(stream)) {
return KEYWORD;
} else if (stream.match(/^\d+( |\.|$)/, false)) {
stream.match(/^\d+/);
return NUMBER;
} else if (stream.match(/^##\w+/)) {
// pragmas
return COMMENT;
}
const char = stream.next();
let peek = stream.peek();
if (peek === undefined) {
peek = '';
}
if ((char === '*' && stream.column() === 0) || char === '"') {
stream.skipToEnd();
return COMMENT;
} else if (isOperator(stream)) {
return OPERATOR;
} else if (char === "'") {
let next;
next = '';
while (next !== undefined) {
if (next === "'") {
state.mode = false;
break;
}
next = stream.next();
}
return STRING;
} else if (char === '|') {
let next;
next = '';
while (next !== undefined) {
if (next === '|') {
state.mode = false;
break;
}
next = stream.next();
}
return STRING;
} else {
stream.eatWhile(/(\w|<|>)/);
return null;
}
},
startState: () => ({
mode: false,
}),
};

View File

@ -64,6 +64,7 @@ const byMimeType: Record<string, (() => Promise<StreamParser<unknown> | Language
"text/vnd.mermaid": async () => buildMermaid(), "text/vnd.mermaid": async () => buildMermaid(),
"text/mermaid": async () => buildMermaid(), "text/mermaid": async () => buildMermaid(),
"text/x-asm-mips": null, "text/x-asm-mips": null,
"text/x-abap": async () => (await import('./languages/abap.js')).abapMode,
"text/x-asterisk": async () => (await import('@codemirror/legacy-modes/mode/asterisk')).asterisk, "text/x-asterisk": async () => (await import('@codemirror/legacy-modes/mode/asterisk')).asterisk,
"text/x-brainfuck": async () => (await import('@codemirror/legacy-modes/mode/brainfuck')).brainfuck, "text/x-brainfuck": async () => (await import('@codemirror/legacy-modes/mode/brainfuck')).brainfuck,
"text/x-c++src": async () => (await import('@codemirror/legacy-modes/mode/clike')).cpp, "text/x-c++src": async () => (await import('@codemirror/legacy-modes/mode/clike')).cpp,

View File

@ -37,6 +37,7 @@ export const MIME_TYPES_DICT: readonly MimeTypeDefinition[] = Object.freeze([
{ title: "Plain text", mime: "text/plain", mdLanguageCode: "plaintext", default: true }, { title: "Plain text", mime: "text/plain", mdLanguageCode: "plaintext", default: true },
// Keep sorted alphabetically. // Keep sorted alphabetically.
{ title: "ABAP (SAP)", mime: "text/x-abap", mdLanguageCode: "abap" },
{ title: "APL", mime: "text/apl" }, { title: "APL", mime: "text/apl" },
{ title: "ASN.1", mime: "text/x-ttcn-asn" }, { title: "ASN.1", mime: "text/x-ttcn-asn" },
{ title: "ASP.NET", mime: "application/x-aspx" }, { title: "ASP.NET", mime: "application/x-aspx" },

View File

@ -0,0 +1,68 @@
// Source: https://github.com/highlightjs/highlightjs-sap-abap/blob/main/src/abap.js
/*
Language: ABAP
Author: Cassio Binkowski <cassioiks@live.com>
Description: SAP ABAP language description
*/
import { HLJSApi, Language } from "highlight.js";
export default function (hljs: HLJSApi): Language {
return {
case_insensitive: true,
aliases: ['sap-abap', 'abap'],
keywords: {
keyword: 'ABBREVIATED ABS ABSTRACT ABSTRACTFINAL ACCEPT ACCEPTING ACCORDING ACOS ACTUAL ADD|0 ADD-CORRESPONDING ADDITIONS ADJACENT AFTER|0 '
+ 'ALIASES ALL|0 ALLOCATE ANALYZER AND|0 APPEND APPENDING AS|0 ASCENDING DESCENDING ASIN ASSIGN ASSIGNING ATAN ATTRIBUTE AUTHORITY-CHECK '
+ 'AVG|0 BACK|0 BACKGOUND BEFORE BETWEEN BINARY BIT BLANK|0 BLOCK BREAK-POINT BUFFER BY|0 BYPASSING BYTE|0 BYTECHARACTER CALL|0 '
+ 'CASTING CEIL|0 CENTERED CHANGE CHANGING CHARACTER CHECK CHECKBOX CLASS-DATA CLASS-EVENTS CLASS-METHODS CLEANUP CLEAR|0 '
+ 'CLASS ENDCLASS CLIENT CLOCK|0 CLOSE|0 COL_BACKGROUND COL_HEADING COL_NORMAL COL_TOTAL COLLECT|0 COLOR|0 COLUMN COMMENT COMMIT COMMON COMMUNICATION COMPARING '
+ 'COMPONENT COMPONENTS COMPUTE CONCATENATE CONDENSE CONSTANTS CONTEXT CONTEXTS CONTINUE|0 CONTROL CONTROLS CONVERSION CONVERT COS COSH COUNT|0 COUNTRY '
+ 'COUNTY CREATE CURRENCY CURRENT CURSOR CUSTOMER-FUNCTION DATA DATABASE DATASET DATE DEALLOCATE DECIMALS DEFAULT DEFERRED '
+ 'DEFINE DEFINING DEFINITION DELETE DELETING DEMAND DESCENDING DESCRIBE DESTINATION DIALOG DIRECTORY DISTANCE DISTINCT DIVIDE DIVIDE-CORRESPONDING '
+ 'DUPLICATE DUPLICATES DURING DYNAMIC EDIT EDITOR-CALL ELSE ELSEIF ENCODING ENDING ENDON ENTRIES ERRORS EVENT EVENTS EXCEPTION EXCEPTIONS EXCEPTION-TABLE '
+ 'EXCLUDE EXCLUDING EXIT EXIT-COMMAND EXPORT EXPORTING EXTENDED EXTENSION EXTRACT FETCH FIELD FIELD-GROUPS FIELDSNO FIELD-SYMBOLS FILTER FINAL FIND|0 '
+ 'FIRST FLOOR FOR|0 FORMAT FORWARDBACKWARD FOUND FRAC FRAME FREE|0 FRIENDS FROM FUNCTION-POOL GET|0 GIVING GROUP HANDLER HASHED HAVING HEADER HEADING '
+ 'HELP-ID HIDE|0 HIGHLOW HOLD|0 HOTSPOT ICON IGNORING IMMEDIATELY IMPLEMENTATION IMPORT IMPORTING IN INCLUDE|0 INCREMENT INDEX|0 INDEX-LINE INHERITING '
+ 'INIT INITIAL INITIALIZATION INNER INNERLEFT INSERT INSTANCES INTENSIFIED INTERFACES INTERVALS INTO INVERTED-DATE IS|0 ITAB JOIN KEEPING '
+ 'KEY|0 KEYS KIND LANGUAGE LAST|0 LEADING LEAVE LEFT LEFT-JUSTIFIED LEFTRIGHT LEFTRIGHTCIRCULAR LEGACY LENGTH LIKE LINE LINE-COUNT LINES LINE-SELECTION '
+ 'LINE-SIZE LIST LIST-PROCESSING LOAD LOAD-OF-PROGRAM LOCAL LOCALE LOG LOG10 LOWER '
+ 'MARGIN MARK MASK MATCH MAX MAXIMUM MEMORY|0 MESSAGE MESSAGE-ID MESSAGES METHODS MIN MOD MODE MODEIN MODIF MODIFIER MODIFY MOVE MOVE-CORRESPONDING '
+ 'MULTIPLY MULTIPLY-CORRESPONDING NEW|0 NEW-LINE NEW-PAGE NEXT|0 NODES NODETABLE NO-DISPLAY NO-GAP NO-GAPS NO-HEADINGWITH-HEADING NO-SCROLLING '
+ 'NO-SCROLLINGSCROLLING NOT|0 NO-TITLE WITH-TITLE NO-ZERO NP NS NUMBER OBJECT|0 OBLIGATORY OCCURENCE OCCURENCES OCCURS OF|0 OFF|0 OFFSET ON|0 ONLY|0 OPEN '
+ 'OPTION OPTIONAL OR|0 ORDER OTHERS|0 OUTER OUTPUT-LENGTH OVERLAY PACK PACKAGE PAGE PAGELAST PAGEOF PAGEPAGE PAGES PARAMETER PARAMETERS PARAMETER-TABLE '
+ 'PART PERFORM PERFORMING PFN PF-STATUS PLACES POS_HIGH POS_LOW POSITION POSITIONS PRIMARY PRINT PRINT-CONTROL PRIVATE PROCESS PROGRAM PROPERTY '
+ 'PROTECTED PUBLIC PUSHBUTTON PUT QUICKINFO RADIOBUTTON RAISE|0 RAISING RANGE RANGES READ RECEIVE RECEIVING REDEFINITION '
+ 'REF REFERENCE REFRESH REJECT RENAMING REPLACE REPLACEMENT REPORT RESERVE RESET RESOLUTION RESULTS RETURN|0 RETURNING RIGHT RIGHT-JUSTIFIED '
+ 'ROLLBACK ROWS RUN SCAN SCREEN SCREEN-GROUP1 SCREEN-GROUP2 SCREEN-GROUP3 SCREEN-GROUP4 SCREEN-GROUP5 SCREEN-INPUT SCREEN-INTENSIFIED SCROLL '
+ 'SCROLL-BOUNDARY SEARCH SECTION SELECT SELECTION SELECTIONS SELECTION-SCREEN SELECTION-SET SELECTION-TABLE SELECT-OPTIONS SEND|0 SEPARATED SET|0 '
+ 'SHARED SHIFT SIGN SIN SINGLE SINGLEDISTINCT SINH SIZE|0 SKIP SORT|0 SORTABLE SPECIFIED SPLIT SQL|0 SQRT STABLE STAMP STANDARD|0 START|0 STARTING '
+ 'STATICS STEP-LOOP STOP STRLEN STRUCTURE|0 SUBMIT SUBTRACT SUBTRACT-CORRESPONDING SUFFIX SUM SUPPLY SUPPRESS SYMBOLS SYSTEM-EXCEPTIONS TABLE|0 TABLENAME '
+ 'TABLES TABLEVIEW TAN TANH TASK TEXT THEN|0 TIME|0 TIMES TITLE TITLEBAR TO TOPIC TOP-OF-PAGE TRAILING TRANSACTION TRANSFER TRANSLATE TRUNC TYPE '
+ 'TYPELIKE TYPE-POOL TYPE-POOLS TYPES ULINE UNION UNIQUE UNIT UNTIL|0 UP|0 UPDATE|0 UPPER UPPERLOWER USER-COMMAND USING VALUE|0 VALUES VARY VARYING '
+ 'VERSION VIA WAIT WHEN WHERE WINDOW WITH|0 WORK|0 WRITE|0 XSTRLEN ZONE '
+ 'CA CN CO CP CS EQ GE GT LE LT NA NE '
+ 'START-OF-SELECTION START-OF-PAGE END-OF-PAGE END-OF-SELECTION AT ENDAT '
+ 'EQUIV BOUND ASSIGNED SUPPLIED INSTANCE VALUE COND CONV CAST SWITCH',
literal: 'abap_true abap_false abap_undefined',
built_in: 'DO FORM IF LOOP MODULE START-OF_FILE DEFINE WHILE BEGIN ENDDO ENDFORM|10 ENDIF ENDLOOP ENDMODULE END-OF_FILE END-OF-DEFINITION ENDWHILE END'
+ ' METHOD ENDMETHOD|10 CHAIN ENDCHAIN CASE ENDCASE FUNCTION ENDFUNCTION ELSEIF ELSE TRY ENDTRY|10 CATCH '
},
contains: [
hljs.APOS_STRING_MODE,
hljs.NUMBER_MODE,
{
className: 'comment',
begin: '^[*]',
relevance: 0,
end: '\n'
},
{
className: 'comment',
begin: '\b*"',
relevance: 0,
end: '\n'
}
]
};
};

View File

@ -43,6 +43,7 @@ export const byMimeType: MimeRecord = {
"text/velocity": null, "text/velocity": null,
"text/vnd.mermaid": null, "text/vnd.mermaid": null,
"text/mermaid": null, "text/mermaid": null,
"text/x-abap": () => import("./languages/abap.js"),
"text/x-asm-mips": () => import("highlight.js/lib/languages/mipsasm"), "text/x-asm-mips": () => import("highlight.js/lib/languages/mipsasm"),
"text/x-asterisk": null, "text/x-asterisk": null,
"text/x-brainfuck": () => import("highlight.js/lib/languages/brainfuck"), "text/x-brainfuck": () => import("highlight.js/lib/languages/brainfuck"),