separate ktxt2 parsing logic from VScode API

This commit is contained in:
2021-10-21 12:24:36 -04:00
parent fcece681e5
commit db01de062e
5 changed files with 145 additions and 107 deletions

View File

@@ -1,107 +0,0 @@
// Kiss-VSCode command functions for authoring ktxt2 files.
// ktxt2 files contain an original "SOURCE" file and a translated "OUTPUT" file
// which is authored semi-automatically with the help of these commands.
// A file called README.md.html.ktxt2 would be for converting README.md to README.html.
(var blockStartEnd "|||")
(var unlockedStart "|>|")
(var lockedStart "|!|")
(var outputStarts [unlockedStart lockedStart])
// Return [sourceFile outputFile]
(function :Array<String> splitFileContents []
(let [fileText (activeTextEditor.document.getText)
fileStream (kiss.Stream.fromString fileText)
&mut sourceText ""
&mut outputText ""]
(loop
(case (fileStream.takeUntilAndDrop blockStartEnd)
((Some _)
(let [sourceBlock (fileStream.expect "A chunk of source text followed by one of $outputStarts"
->(fileStream.takeUntilOneOf outputStarts))]
(+= sourceText "${sourceBlock}\n"))
(assert (apply = (for outputStart outputStarts outputStart.length)) "all output starts must be the same length!")
(fileStream.dropChars .length (first outputStarts))
(let [outputBlock (fileStream.expect "A chunk of output text followed by $blockStartEnd"
->(fileStream.takeUntilAndDrop blockStartEnd))]
(+= outputText "${outputBlock}\n")))
(None
(break))))
[sourceText outputText]))
(function exportKtxt2Files [&opt _]
(let [[sourceText outputText] (splitFileContents)
ktxt2FullFilename activeTextEditor.document.fileName
ktxt2Directory (haxe.io.Path.directory ktxt2FullFilename)
ktxt2Filename (haxe.io.Path.withoutDirectory ktxt2FullFilename)
[baseFilename sourceExt outputExt ktxt2Ext] (ktxt2Filename.split ".")
sourceFilename (joinPath ktxt2Directory "${baseFilename}.${sourceExt}")
outputFilename (joinPath ktxt2Directory "${baseFilename}.${outputExt}")]
// Use editors instead of File.saveContent for this, so the user can undo the export if
// it overwrites something!
(defMacro overwriteDocument [document content]
`(let [document ,document content ,content]
(awaitLet [editor (Vscode.window.showTextDocument document)]
(editor.edit
->e (e.replace (new Range (document.positionAt 0) (document.positionAt .length (document.getText))) content)))))
(function uriFor [filename]
(let [uri (Uri.file filename)]
(if (sys.FileSystem.exists filename)
uri
(uri.with (object scheme "untitled")))))
(awaitLet [sourceDocument
(Vscode.workspace.openTextDocument (uriFor sourceFilename))
sourceEditSuccess
(overwriteDocument sourceDocument sourceText)
outputDocument
(Vscode.workspace.openTextDocument (uriFor outputFilename))
outputEditSuccess
(overwriteDocument outputDocument outputText)]
(assert (and sourceEditSuccess outputEditSuccess))
(awaitLet [saveSourceSuccess
(sourceDocument.save)
saveOutputSuccess
(outputDocument.save)]
(assert (and saveSourceSuccess saveOutputSuccess))))))
(function streamPosToVscodePos [pos]
(new vscode.Position pos.line (- pos.column 1)))
(function rangeFromStartEnd [start end]
(new Range (streamPosToVscodePos start) (streamPosToVscodePos end)))
(function :Array<Dynamic> splitFileBlocks []
(let [fileText (activeTextEditor.document.getText)
fileStream (kiss.Stream.fromString fileText)
blocks []]
(loop
(let [block (object source "" sourceRange null output "" outputRange null outputLocked false)]
(case (fileStream.takeUntilAndDrop blockStartEnd)
((Some _)
(let [sourceStartPosition (fileStream.position)
sourceBlock (fileStream.expect "A chunk of source text followed by one of $outputStarts"
->(fileStream.takeUntilOneOf outputStarts))
sourceEndPosition (fileStream.position)]
(set block.source sourceBlock)
(set block.sourceRange (rangeFromStartEnd sourceStartPosition sourceEndPosition)))
(assert (apply = (for outputStart outputStarts outputStart.length)) "all output starts must be the same length!")
(set block.outputLocked
(case (fileStream.expect "One of $outputStarts" ->(fileStream.takeChars .length (first outputStarts)))
(lockedStart true)
(unlockedStart false)
(otherwise
(throw "Expected one of $outputStarts"))))
(let [outputStartPosition (fileStream.position)
outputBlock (fileStream.expect "A chunk of output text followed by $blockStartEnd"
->(fileStream.takeUntilOneOf [blockStartEnd]))
outputEndPosition (fileStream.position)]
(fileStream.dropChars blockStartEnd.length)
(set block.output outputBlock)
(set block.outputRange (rangeFromStartEnd outputStartPosition outputEndPosition))))
(None
(break)))
(blocks.push block)))
blocks))
(function splitBlocks [&opt _]
(let [blocks (splitFileBlocks)] (print blocks)))

View File

@@ -0,0 +1,23 @@
package ktxt2;
import kiss.Stream;
import kiss.Prelude;
typedef KTxt2Block = {
source:String,
output:String,
outputLocked:Bool,
// kiss.Stream.Positions:
sourceStart:Position,
sourceEnd:Position,
outputStart:Position,
outputEnd:Position
};
enum KTxt2Element {
Comment(content:String);
Block(block:KTxt2Block);
}
@:build(kiss.Kiss.build())
class KTxt2 {}

View File

@@ -0,0 +1,76 @@
// Functions for parsing and modifying ktxt2 files.
// ktxt2 files contain an original "SOURCE" file and a translated "OUTPUT" file
// which is authored semi-automatically with the help of a custom VSCode editor.
// A file called README.md.html.ktxt2 would be for converting README.md -> README.html.
(var blockStartEnd "|||")
(var unlockedStart "|>|")
(var lockedStart "|!|")
(var outputStarts [unlockedStart lockedStart])
// Return [sourceFile outputFile]
(function :Array<String> splitFileContents [file]
(let [fileStream (kiss.Stream.fromFile file)
&mut sourceText ""
&mut outputText ""]
(loop
(case (fileStream.takeUntilAndDrop blockStartEnd)
((Some _)
(let [sourceBlock (fileStream.expect "A chunk of source text followed by one of $outputStarts"
->(fileStream.takeUntilOneOf outputStarts))]
(+= sourceText "${sourceBlock}\n"))
(assert (apply = (for outputStart outputStarts outputStart.length)) "all output starts must be the same length!")
(fileStream.dropChars .length (first outputStarts))
(let [outputBlock (fileStream.expect "A chunk of output text followed by $blockStartEnd"
->(fileStream.takeUntilAndDrop blockStartEnd))]
(+= outputText "${outputBlock}\n")))
(None
(break))))
[sourceText outputText]))
(function :Array<KTxt2Element> splitFileElements [file]
(let [fileStream (kiss.Stream.fromFile file)
elements []]
(loop
(let [block
(object
source ""
sourceStart null
sourceEnd null
output ""
outputStart null
outputEnd null
outputLocked false)]
// Look for the start of a KTxt2 block
(case (fileStream.takeUntilAndDrop blockStartEnd)
((Some comment)
// Anything before the start of the block is a comment
(when comment
(elements.push (Comment comment)))
(let [sourceStartPosition (fileStream.position)
sourceBlock (fileStream.expect "A chunk of source text followed by one of $outputStarts"
->(fileStream.takeUntilOneOf outputStarts))
sourceEndPosition (fileStream.position)]
(set block.source sourceBlock)
(set block.sourceStart sourceStartPosition)
(set block.sourceEnd sourceEndPosition))
(assert (apply = (for outputStart outputStarts outputStart.length)) "all output starts must be the same length!")
(set block.outputLocked
(case (fileStream.expect "One of $outputStarts" ->(fileStream.takeChars .length (first outputStarts)))
(lockedStart true)
(unlockedStart false)
(otherwise
(throw "Expected one of $outputStarts"))))
(let [outputStartPosition (fileStream.position)
outputBlock (fileStream.expect "A chunk of output text followed by $blockStartEnd"
->(fileStream.takeUntilOneOf [blockStartEnd]))
outputEndPosition (fileStream.position)]
(fileStream.dropChars blockStartEnd.length)
(set block.output outputBlock)
(set block.outputStart outputStartPosition)
(set block.outputEnd outputEndPosition)))
(None
(break)))
(elements.push (Block block))))
elements))

View File

@@ -1,5 +1,6 @@
package ktxt2;
@:build(kiss.Kiss.build())
class KTxt2Editor {
public static function main() {
var vscode = EditorExterns.acquireVsCodeApi();

View File

@@ -0,0 +1,45 @@
(function exportSourceAndOutputFiles [document]
(let [[sourceText outputText] (splitFileContents document.fileName)
ktxt2FullFilename document.fileName
ktxt2Directory (haxe.io.Path.directory ktxt2FullFilename)
ktxt2Filename (haxe.io.Path.withoutDirectory ktxt2FullFilename)
[baseFilename sourceExt outputExt ktxt2Ext] (ktxt2Filename.split ".")
sourceFilename (joinPath ktxt2Directory "${baseFilename}.${sourceExt}")
outputFilename (joinPath ktxt2Directory "${baseFilename}.${outputExt}")]
// Use editors instead of File.saveContent for this, so the user can undo the export if
// it overwrites something!
(defMacro overwriteDocument [document content]
`(let [document ,document content ,content]
(awaitLet [editor (Vscode.window.showTextDocument document)]
(editor.edit
->e (e.replace (new Range (document.positionAt 0) (document.positionAt .length (document.getText))) content)))))
(function uriFor [filename]
(let [uri (Uri.file filename)]
(if (sys.FileSystem.exists filename)
uri
(uri.with (object scheme "untitled")))))
(awaitLet [sourceDocument
(Vscode.workspace.openTextDocument (uriFor sourceFilename))
sourceEditSuccess
(overwriteDocument sourceDocument sourceText)
outputDocument
(Vscode.workspace.openTextDocument (uriFor outputFilename))
outputEditSuccess
(overwriteDocument outputDocument outputText)]
(assert (and sourceEditSuccess outputEditSuccess))
(awaitLet [saveSourceSuccess
(sourceDocument.save)
saveOutputSuccess
(outputDocument.save)]
(assert (and saveSourceSuccess saveOutputSuccess))))))
(function streamPosToVscodePos [pos]
(new vscode.Position pos.line (- pos.column 1)))
(function rangeFromStartEnd [start end]
(new Range (streamPosToVscodePos start) (streamPosToVscodePos end)))
(function splitBlocks [&opt _]
(let [blocks (splitFileBlocks)] (print blocks)))