Skip to content

Commit

Permalink
Formatter (#63)
Browse files Browse the repository at this point in the history
* add formatting through redhat.vscode-xml

* Some progress

* get basic formatting working

* format basically working.

* clean up

* get formatting working
  • Loading branch information
oscarlevin authored Dec 12, 2023
1 parent b391748 commit e504f13
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"xml.fileAssociations": [
{
"pattern": "**/source/**.ptx",
"systemId": "C:\\Users\\oscar.levin\\.ptx\\schema\\pretext.rng"
"systemId": "C:\\Users\\oscar.levin\\.ptx\\schema\\pretext-dev.rng"
}
]
}
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [UNRELEASED]

### Added

- Formatting support using vs-code's built-in formatting engine. From the command palette, select "Format Document" or use the keyboard shortcut `CTRL+SHIFT+I`.

### Fixed

- Bug that prevent initialization if not in a pretext project folder.


## [0.13.0] - 2023-11-27

### Added
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
},
"categories": [
"Programming Languages",
"Snippets"
"Snippets",
"Formatters"
],
"activationEvents": [
"workspaceContains:project.ptx"
Expand Down
48 changes: 48 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Purpose: Contains constants used throughout the project.

export const tex2ptxBlocks: { [key: string]: string } = {
axiom: "axiom",
principle: "principle",
conjecture: "conjecture",
heuristic: "heuristic",
hypothesis: "hypothesis",
assumption: "assumption",
theorem: "theorem",
thm: "theorem",
lemma: "lemma",
lem: "lemma",
corollary: "corollary",
cor: "corollary",
claim: "claim",
proposition: "proposition",
prop: "proposition",
algorithm: "algorithm",
fact: "fact",
identity: "identity",
proof: "proof",
definition: "definition",
def: "definition",
defn: "definition",
remark: "remark",
note: "note",
warning: "warning",
convention: "convention",
observation: "observation",
insight: "insight",
example: "example",
question: "question",
problem: "problem",
solution: "solution",
hint: "hint",
answer: "answer",
exercise: "exercise",
aside: "aside",
activity: "activity",
project: "project",
investigation: "investigation",
exploration: "exploration",
figure: "figure",
table: "table",
enumerate: "ol",
itemize: "ul",
};
38 changes: 30 additions & 8 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as path from "path";
import * as vscode from "vscode";
import { convertToPretext } from "./pandocConvert";
import { latexToPretext } from "./latextopretext";
import { formatPTX } from "./formatter";

// Set up vscode elements
let pretextOutputChannel: vscode.OutputChannel;
Expand Down Expand Up @@ -201,14 +202,16 @@ function getTargets() {
.split(/\r?\n/)
.filter(Boolean);
// Set up dictionary for quickselect:
let targetSelection = [];
for (let target of targets) {
targetSelection.push({
label: target,
description: "Build source as " + target,
});
}
return targetSelection;
if (targets.length > 0) {
let targetSelection = [];
for (let target of targets) {
targetSelection.push({
label: target,
description: "Build source as " + target,
});
}
return targetSelection;
} else {return [{label: "No PreTeXt project found.", description: "Change to directory containing a project.ptx file."}];}
} catch (err) {
console.log("getTargets() Error: \n", err);
return [];
Expand Down Expand Up @@ -441,6 +444,7 @@ export function activate(context: vscode.ExtensionContext) {
console.log("Pretext is installed is:", ptxInstalled);

var targetSelection = getTargets();
console.log("targetSelection is:", targetSelection);
lastTarget = targetSelection[0].label;
pretextCommandList[0].label = "Build " + lastTarget;
console.log(
Expand All @@ -450,6 +454,15 @@ export function activate(context: vscode.ExtensionContext) {
})
);

// Formatter:
vscode.languages.registerDocumentFormattingEditProvider("pretext", {
provideDocumentFormattingEdits(
document: vscode.TextDocument
): vscode.TextEdit[] {
return formatPTX(document);
}
});

context.subscriptions.push(
vscode.commands.registerCommand("pretext-tools.showLog", () => {
pretextOutputChannel.show();
Expand Down Expand Up @@ -783,6 +796,15 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("pretext-tools.latexToPretext", () => {
const editor = vscode.window.activeTextEditor;
// const { document } = activeTextEditor;
// const firstLine = document.lineAt(0);

// if (firstLine.text !== '42') {
// const edit = new vscode.WorkspaceEdit();
// edit.insert(document.uri, firstLine.range.start, '42\n');

// return vscode.workspace.applyEdit(edit);
// }

if (editor) {
const selection = editor.selection;
Expand Down
152 changes: 152 additions & 0 deletions src/formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import * as vscode from 'vscode';

const docStructure = [
'pretext', 'mathbook', 'book', 'part', 'article',
'docinfo', 'macros',
'html', 'search', 'google', 'feedback', 'index',
'frontmatter', 'backmatter',
'appendix', 'solutions', 'references', 'biography', 'dedication',
'titlepage', 'preface', 'abstract', 'colophon', 'shortlicense',
'acknowledgement', 'credit', 'website', 'copyright',
'author', 'editor', 'contributor', 'contributors' ];

const docSecs = ['chapter', 'section', 'subsection', 'subsubsection',
'technology', 'worksheet',
'objectives', 'outcomes', 'paragraphs', 'task',
'sbsgroup', 'stack',
'introduction', 'conclusion', 'assemblage',
'prelude', 'postlude'];

const docEnvs = ['proof',
'project',
'theorem', 'proposition', 'lemma', 'conjecture',
'corollary', 'principle', 'algorithm',
'definition', 'axiom', 'example', 'insight', 'exploration',
'problem', 'exercise', 'question',
'statement', 'hint', 'solution', 'answer', 'case',
'note', 'blockquote',
'openconjecture', 'openproblem', 'openquestion',
'activity', 'remark', 'warning', 'table', 'tabular',
'list', 'listing', 'program', 'console', 'demonstration',
'images', 'image',
'fact', 'subtask', 'claim', 'biblio',
'poem', 'stanza'];

const docPieces = ['title', 'subtitle', 'cell', 'caption',
'address', 'attribution', 'location', 'edition',
'personname', 'date', 'email', 'department', 'institution',
'cd', 'line', 'cline',
'alert', 'url', 'q', 'pubtitle',
'role', 'entity', 'year', 'minilicense', 'holder',
'usage', 'description', 'journal', 'volume', 'number',
'mrow', 'intertext', 'initialism',
'set', 'pg-macros'];

// empty tags that should be on their own line
const docEmpty = ['cell', 'col', 'notation-list', 'brandlogo',
'cross-references', 'input', 'video', 'slate',
'webwork'];

const list_like = ['ol', 'ul', 'dl'];

const math_display = ['me', 'men', 'md', 'mdn'];

const footnote_like = ['fn'];

const nestable_tags = ["ul", "ol", "li", "p", "task", "figure", "sidebyside"];

// note that c is special, because it is inline verbatim
const verbatimTags = ['latex-image-preamble', 'latex-image', 'latex-preamble',
'slate', 'sage', 'sageplot', 'asymptote', 'macros',
'program', 'input', 'output', 'prompt', 'pre', 'pg-code',
'tikzpicture', 'tikz', 'code',
'c'];

const newlineTags = docStructure.concat(docSecs).concat(docEnvs).concat(nestable_tags);

const blockTags = newlineTags.concat(math_display);



export function formatPTX(document: vscode.TextDocument): vscode.TextEdit[] {
// let changes = [];
// const firstLine = document.lineAt(0);
// if (!firstLine.text.startsWith("<?xml")) {
// changes.push(
// vscode.TextEdit.insert(firstLine.range.start, '<?xml version="1.0" encoding="UTF-8"?>\n')
// );
// }

// First clean up document so that each line is a single tag when appropriate.
let allText = document.getText();

console.log("Getting ready to start formatting.")
for (let btag of blockTags) {
let startTag = new RegExp('<'+btag+'(>|(\s[^/]*?)>)', 'g');
let endTag = new RegExp('<\\/'+btag+'>(.?)', 'g');
allText = allText.replace(startTag, "\n$&\n");
allText = allText.replace(endTag, "\n$&\n");
}

let level = 0;
let verbatim = false;
let lines = allText.split(/\r\n|\r|\n/g);
console.log("Finished splitting lines. Now will process", lines.length, "lines.");
let fixedLines = [];
for (let line of lines) {
console.log("level is", level, "and verbatim is", verbatim, "and line is", line);
let trimmedLine = line.trim();
let openTagMatch = /^<(\w*?)(\s.*?|>)$/.exec(trimmedLine);
let closeTagMatch = /^<\/(\w*?)(\s.*?|>)(.?)$/.exec(trimmedLine);
let selfCloseTagMatch = /^<(\w*?)(\s.*?\/>|\/>)$/.exec(trimmedLine);
if (trimmedLine.length === 0) {
continue;
} else if (trimmedLine.startsWith("<?")) {
// It's the start line of the file:
fixedLines.push(trimmedLine);
} else if (trimmedLine.startsWith("<!--")) {
// It's a comment:
fixedLines.push("\t".repeat(level) + trimmedLine);
} else if (closeTagMatch) {
if (blockTags.includes(closeTagMatch[1])) {
level = Math.max(0, level-1);
fixedLines.push("\t".repeat(level) + trimmedLine);
} else if (verbatimTags.includes(closeTagMatch[1])) {
verbatim = false;
fixedLines.push("\t".repeat(level) + trimmedLine);
}
} else if (openTagMatch) {
if (blockTags.includes(openTagMatch[1])) {
fixedLines.push("\t".repeat(level) + trimmedLine);
level += 1;
} else if (verbatimTags.includes(openTagMatch[1])) {
verbatim = true;
fixedLines.push("\t".repeat(level) + trimmedLine);
}
} else if (verbatim) {
fixedLines.push(line);
} else {
fixedLines.push("\t".repeat(level) + trimmedLine);
}
}
// Second pass: add empty line between appropriate tags.
for (let i = 0; i < fixedLines.length-1; i++) {
if (fixedLines[i].trim().startsWith("</")){
for (let tag of newlineTags) {
let startTag = new RegExp('<'+tag+'(.*?)>', 'g');
if (startTag.test(fixedLines[i+1])) {
console.log("Adding newline between", fixedLines[i], "and", fixedLines[i+1]);
fixedLines[i] += "\n";
}
}
}
}
// Add document identifier line if missing:
if (!fixedLines[0].trim().startsWith('<?xml')) {
fixedLines.unshift('<?xml version="1.0" encoding="UTF-8" ?>');
}

allText = fixedLines.join("\n");

return [vscode.TextEdit.replace(document.validateRange(new vscode.Range(0, 0, document.lineCount, 0)), allText)];
}
33 changes: 7 additions & 26 deletions src/latextopretext.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,8 @@
// module
import * as constants from "./constants";

const blocks = constants.tex2ptxBlocks;

const BLOCKS: { [key: string]: string } = {
axiom: "axiom",
theorem: "theorem",
lemma: "lemma",
corollary: "corollary",
proposition: "proposition",
definition: "definition",
example: "example",
remark: "remark",
exercise: "exercise",
question: "question",
problem: "problem",
solution: "solution",
proof: "proof",
note: "note",
aside: "aside",
figure: "figure",
table: "table",
algorithm: "algorithm",
enumerate: "ol",
itemize: "ul",
};

// Paragraph element conversions /////////////////////////////////

Expand Down Expand Up @@ -220,8 +201,8 @@ function convertLines(lines: string[]) {
else if (line.startsWith("\\begin")) {
let env = line.match(/\\begin{(.*?)}/);
if (env !== null) {
if (env[1] in BLOCKS) {
result += "<" + BLOCKS[env[1]] + ">\n\t";
if (env[1] in blocks) {
result += "<" + blocks[env[1]] + ">\n\t";
} else {
result +=
"<!-- START " +
Expand All @@ -232,8 +213,8 @@ function convertLines(lines: string[]) {
} else if (line.startsWith("\\end")) {
let env = line.match(/\\end{(.*?)}/);
if (env !== null) {
if (env[1] in BLOCKS) {
result += "\n</" + BLOCKS[env[1]] + ">\n";
if (env[1] in blocks) {
result += "\n</" + blocks[env[1]] + ">\n";
} else {
result += "<!-- END " + env[1] + " environment -->\n";
}
Expand Down

0 comments on commit e504f13

Please sign in to comment.