Skip to content

Commit

Permalink
Merge pull request #94 from jan-dolejsi/formatter
Browse files Browse the repository at this point in the history
PDDL formatter; v2.21.0
  • Loading branch information
jan-dolejsi authored Jan 24, 2021
2 parents 8e04946 + f3d5036 commit 75314e1
Show file tree
Hide file tree
Showing 11 changed files with 417 additions and 65 deletions.
36 changes: 35 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
# PDDL support - What's new?

## 2.21.0

### PDDL Formatter

Right-click on PDDL document and you will see two more options: _Format Document_ and _Format Selection_.
It re-formats the white space to enhance readability of the domain/problem.\
The formatter does not modify most of your line breaks, but modifies most of the indentation.

Even more helpful is the on-type formatter, which automatically suggests indentation when you press Enter in the document.

![On-type formatter](https://raw.githubusercontent.com/wiki/jan-dolejsi/vscode-pddl/img/on-type-formatter.gif)

The on-type formatter must however be first enabled using this setting:

```json
"editor.formatOnType": true
```

You can selectively enable the on-type formatting just for PDDL documents:

```json
"[pddl]": {
"editor.formatOnType": true
}
```

The above configuration can be now easily inserted from the _PDDL: Overview Page_.

The Document and Document Selection formatter (along with the separately enabled on-type formatter) can be disabled (in case of clash with another extension) using the `pddl.formatter` setting.

This was one of the oldest standing feature request is now addressed.
This work was started by our intern more than 2 years ago, but I only had time to finish it now - _thanks_ to C-19 isolation.

## 2.20.4

- Overview alerts use codicons to look correct on MacOS
Expand Down Expand Up @@ -1257,7 +1290,8 @@ Note for open source contributors: all notable changes to the "pddl" extension w

Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

[Unreleased]: https://github.com/jan-dolejsi/vscode-pddl/compare/v2.20.0...HEAD
[Unreleased]: https://github.com/jan-dolejsi/vscode-pddl/compare/v2.21.0...HEAD
[2.21.0]:https://github.com/jan-dolejsi/vscode-pddl/compare/v2.20.0...v2.21.0
[2.20.0]:https://github.com/jan-dolejsi/vscode-pddl/compare/v2.19.0...v2.20.0
[2.19.0]:https://github.com/jan-dolejsi/vscode-pddl/compare/v2.18.0...v2.19.0
[2.18.0]:https://github.com/jan-dolejsi/vscode-pddl/compare/v2.17.1...v2.18.0
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Planning Domain Description Language support",
"author": "Jan Dolejsi",
"license": "MIT",
"version": "2.20.4",
"version": "2.21.0",
"publisher": "jan-dolejsi",
"engines": {
"vscode": "^1.50.0",
Expand Down Expand Up @@ -874,8 +874,8 @@
},
"pddl.formatter": {
"type": "boolean",
"default": false,
"description": "Enable PDDL formatter (default is 'false'). Warning: this is an experimental feature - work in progress."
"default": true,
"description": "Enable/disable PDDL formatter (default is 'true')."
},
"pddl.modelHierarchy": {
"type": "boolean",
Expand Down Expand Up @@ -1005,7 +1005,7 @@
"jsonc-parser": "^2.2.1",
"open": "^7.0.2",
"pddl-gantt": "^1.5.3",
"pddl-workspace": "^6.0.3",
"pddl-workspace": "^6.1.0",
"request": "^2.88.2",
"semver": "^7.1.3",
"tree-kill": "^1.2.2",
Expand Down
13 changes: 13 additions & 0 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const PLANNER_VAL_STEP_PATH = CONF_PDDL + "." + VAL_STEP_PATH;
export const PLANNER_VALUE_SEQ_PATH = CONF_PDDL + "." + VALUE_SEQ_PATH;
export const PDDL_CONFIGURE_COMMAND = CONF_PDDL + "." + "configure";
export const DEFAULT_EPSILON = 1e-3;
const EDITOR_FORMAT_ON_TYPE = "editor.formatOnType";

export class PddlConfiguration {

Expand Down Expand Up @@ -69,6 +70,18 @@ export class PddlConfiguration {
return workspace.getConfiguration().get(PLANNER_EPSILON_TIMESTEP, DEFAULT_EPSILON);
}

getEditorFormatOnType(): boolean {
return workspace.getConfiguration('', { languageId: CONF_PDDL }).get<boolean>(EDITOR_FORMAT_ON_TYPE, false);
}

setEditorFormatOnType(newValue: boolean, options: { forPddlOnly: boolean }): void {
if (options.forPddlOnly) {
workspace.getConfiguration(undefined, { languageId: CONF_PDDL }).update(EDITOR_FORMAT_ON_TYPE, newValue, true, true);
} else {
workspace.getConfiguration().update(EDITOR_FORMAT_ON_TYPE, newValue, true);
}
}

getParserPath(workspaceFolder?: WorkspaceFolder): string | undefined {
const configuredPath = workspace.getConfiguration(PDDL_PARSER, workspaceFolder).get<string>(EXECUTABLE_OR_SERVICE);
return ensureAbsoluteGlobalStoragePath(configuredPath, this.context);
Expand Down
6 changes: 4 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,10 @@ function createAuthentication(pddlConfiguration: PddlConfiguration): Authenticat
function registerDocumentFormattingProvider(context: ExtensionContext, pddlWorkspace: CodePddlWorkspace): boolean {
if (workspace.getConfiguration("pddl").get<boolean>("formatter") && !formattingProvider) {
formattingProvider = new PddlFormatProvider(pddlWorkspace);
// const formattingProviderDisposable = languages.registerDocumentFormattingEditProvider(PDDL, formattingProvider);
// context.subscriptions.push(formattingProviderDisposable);
const formattingProviderDisposable = languages.registerDocumentFormattingEditProvider(PDDL, formattingProvider);
context.subscriptions.push(formattingProviderDisposable);
const rangeFormattingProviderDisposable = languages.registerDocumentRangeFormattingEditProvider(PDDL, formattingProvider);
context.subscriptions.push(rangeFormattingProviderDisposable);

const onTypeFormattingProviderDisposable = languages.registerOnTypeFormattingEditProvider(PDDL, new PddlOnTypeFormatter(pddlWorkspace), '\n');
context.subscriptions.push(onTypeFormattingProviderDisposable);
Expand Down
142 changes: 126 additions & 16 deletions src/formatting/PddlFormatProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,46 @@
* ------------------------------------------------------------------------------------------ */
'use strict';

import { DomainInfo, parser, PddlLanguage, ProblemInfo } from 'pddl-workspace';
import {TextDocument, CancellationToken, DocumentFormattingEditProvider, FormattingOptions, TextEdit } from 'vscode';
import { DomainInfo, FileInfo, parser, PddlLanguage, ProblemInfo } from 'pddl-workspace';
import {TextDocument, CancellationToken, DocumentFormattingEditProvider, FormattingOptions, TextEdit, DocumentRangeFormattingEditProvider, Range, Position } from 'vscode';
import { nodeToRange } from '../utils';
import { CodePddlWorkspace } from '../workspace/CodePddlWorkspace';
import { PddlOnTypeFormatter } from './PddlOnTypeFormatter';

export class PddlFormatProvider implements DocumentFormattingEditProvider {
export class PddlFormatProvider implements DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider {

constructor(private pddlWorkspace?: CodePddlWorkspace) {
}

async provideDocumentFormattingEdits(document: TextDocument, _options: FormattingOptions, token: CancellationToken): Promise<TextEdit[] | undefined> {
async provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): Promise<TextEdit[] | undefined> {
const fileInfo = await this.pddlWorkspace?.upsertAndParseFile(document);
if (token.isCancellationRequested) { return undefined; }

if (fileInfo && (fileInfo.getLanguage() !== PddlLanguage.PDDL)) {
return undefined;
}

const tree: parser.PddlSyntaxTree = this.getSyntaxTree(fileInfo, document);

return new PddlFormatter(document, range, options, token).format(tree.getRootNode());
}

async provideDocumentFormattingEdits(document: TextDocument, options: FormattingOptions, token: CancellationToken): Promise<TextEdit[] | undefined> {
const fileInfo = await this.pddlWorkspace?.upsertAndParseFile(document);
if (token.isCancellationRequested) { return undefined; }

if (fileInfo && (fileInfo.getLanguage() !== PddlLanguage.PDDL)) {
return undefined;
}

const tree: parser.PddlSyntaxTree = this.getSyntaxTree(fileInfo, document);

const fullRange = document.validateRange(new Range(new Position(0, 0), new Position(document.lineCount, Number.MAX_VALUE)));

return new PddlFormatter(document, fullRange, options, token).format(tree.getRootNode());
}

private getSyntaxTree(fileInfo: FileInfo | undefined, document: TextDocument): parser.PddlSyntaxTree {
let tree: parser.PddlSyntaxTree;
if (fileInfo && (fileInfo instanceof DomainInfo)) {
tree = (fileInfo as DomainInfo).syntaxTree;
Expand All @@ -31,19 +53,107 @@ export class PddlFormatProvider implements DocumentFormattingEditProvider {
}
else {
tree = new parser.PddlSyntaxTreeBuilder(document.getText()).getTree();
}
const edits: TextEdit[] = [];
this.format(tree.getRootNode(), edits, document);
}
return tree;
}
}

return edits;
}
class PddlFormatter {

format(node: parser.PddlSyntaxNode, edits: TextEdit[], document: TextDocument): void {
if (node.getToken().type === parser.PddlTokenType.Whitespace) {
edits.push(TextEdit.replace(nodeToRange(document, node), ' '));
}
private readonly edits: TextEdit[] = [];
private readonly firstOffset: number;
private readonly lastOffset: number;

node.getChildren().forEach(child => this.format(child, edits, document));
}
constructor(private readonly document: TextDocument, range: Range, private readonly options: FormattingOptions, private token: CancellationToken) {
this.firstOffset = document.offsetAt(range.start);
this.lastOffset = document.offsetAt(range.end);
}

format(node: parser.PddlSyntaxNode): TextEdit[] {
if (node.getStart() > this.lastOffset || this.token.isCancellationRequested) {
return this.edits;
}

if (node.getStart() >= this.firstOffset || node.includesIndex(this.firstOffset)) {
if (node.getToken().type === parser.PddlTokenType.Whitespace) {

const nextSibling = node.getFollowingSibling()
?? node.getParent()?.getFollowingSibling(undefined, node);

if (node.getParent() && ['(increase', '(decrease', '(assign'].includes(node.getParent()!.getToken().tokenText)) {
if ((node.getParent()?.length ?? 0) > 50) {
this.breakAndIndent(node);
} else {
this.replace(node, ' ');
}
} else if (node.getParent() && ['(:types', '(:objects'].includes(node.getParent()!.getToken().tokenText)) {
// todo: format type inheritance
} else if (nextSibling === undefined) {
this.replace(node, '');
} else if (nextSibling.isType(parser.PddlTokenType.CloseBracket)) {
if (node.getText().includes('\n')) {
this.breakAndIndent(node, -1);
} else {
this.replace(node, '');
}
} else if (nextSibling.isType(parser.PddlTokenType.Comment)) {
if (node.getText().includes('\n')) {
this.breakAndIndent(node);
} else {
this.replace(node, ' ');
}
} else if (nextSibling.isAnyOf([parser.PddlTokenType.Dash, parser.PddlTokenType.Other, parser.PddlTokenType.Parameter])) {
this.replace(node, ' ');
} else if (nextSibling.isAnyOf([parser.PddlTokenType.OpenBracket, parser.PddlTokenType.OpenBracketOperator, parser.PddlTokenType.Keyword])) {
if (nextSibling.isType(parser.PddlTokenType.Keyword)) {
if (node.getParent()
&& ['(:requirements'].includes(node.getParent()!.getToken().tokenText)) {
this.replace(node, ' ');
} else {
this.breakAndIndent(node);
}
} else if (node.getParent()?.isNumericExpression() || node.getParent()?.isLogicalExpression() || node.getParent()?.isTemporalExpression()) {
if (node.getText().includes('\n')) {
this.breakAndIndent(node);
} else {
this.replace(node, ' ');
}
} else if (['(domain', '(problem'].includes(nextSibling.getToken().tokenText)) {
this.replace(node, ' ');
} else {
if (node.getParent()
&& [':parameters', ':duration', ':precondition', ':condition', ':effect'].includes(node.getParent()!.getToken().tokenText)) {
this.replace(node, ' ');
} else {
this.breakAndIndent(node);
}
}
}
}
}

node.getChildren().forEach(child => this.format(child));

return this.edits;
}

breakAndIndent(node: parser.PddlSyntaxNode, offset = 0): void {
const level = node.getAncestors([parser.PddlTokenType.OpenBracket, parser.PddlTokenType.OpenBracketOperator]).length;
this.replace(node, this.ends(node.getText(), 1) + PddlOnTypeFormatter.createIndent('', level + offset, this.options));
}

replace(node: parser.PddlSyntaxNode, newText: string): void {
this.edits.push(TextEdit.replace(nodeToRange(this.document, node), newText));
}

/** @returns the endline characters only, but at least the `min` count */
ends(text: string, min: number): string {
const endls = text.replace(/[^\n\r]/g, '');
const endlCount = (endls.match(/\n/g) || []).length;
if (endlCount < min) {
return endls + '\n'.repeat(min - endlCount);
} else {
return endls;
}
}
}
10 changes: 5 additions & 5 deletions src/formatting/PddlOnTypeFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export class PddlOnTypeFormatter implements OnTypeFormattingEditProvider {

if (ch === '\n' && currentNode.isType(parser.PddlTokenType.Whitespace)) {
const insertBeforeText = previousIndent !== null ?
this.createIndent(previousIndent, 0, options) :
this.createIndent(parentIndent, +1, options);
const insertAfterText = '\n' + this.createIndent(previousIndent ?? parentIndent, 0, options);
PddlOnTypeFormatter.createIndent(previousIndent, 0, options) :
PddlOnTypeFormatter.createIndent(parentIndent, +1, options);
const insertAfterText = '\n' + PddlOnTypeFormatter.createIndent(previousIndent ?? parentIndent, 0, options);
// todo: use createEolString(document)
const trailingText = document.getText(rangeAfter).trim();
if (trailingText && !trailingText.match(/\w/)) {
Expand All @@ -69,7 +69,7 @@ export class PddlOnTypeFormatter implements OnTypeFormattingEditProvider {
else if (ch === '(-disabled') { // disabled, because it cancels out the auto-completion pop-up
const leadingText = document.getText(rangeBefore);
if (leadingText.trim() === '(') {
const insertBeforeText = this.createIndent(parentIndent, +1, options) + '(';
const insertBeforeText = PddlOnTypeFormatter.createIndent(parentIndent, +1, options) + '(';
return [
TextEdit.replace(rangeBefore, insertBeforeText)
];
Expand Down Expand Up @@ -120,7 +120,7 @@ export class PddlOnTypeFormatter implements OnTypeFormattingEditProvider {
return lineOfPrevious.text.substr(0, firstNonWhitespaceCharacter);
}

createIndent(parentIndentText: string, levelIncrement: number, options: FormattingOptions): string {
static createIndent(parentIndentText: string, levelIncrement: number, options: FormattingOptions): string {
const singleIndent = options.insertSpaces ? " ".repeat(options.tabSize) : "\t";
return parentIndentText +
singleIndent.repeat(levelIncrement);
Expand Down
7 changes: 6 additions & 1 deletion src/init/OverviewPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ export class OverviewPage {
const options: ValDownloadOptions = { bypassConsent: message.informedDecision };
await commands.executeCommand(VAL_DOWNLOAD_COMMAND, options);
break;
case 'enableFormatOnType':
this.pddlConfiguration.setEditorFormatOnType(true, { forPddlOnly: message.forPddlOnly as boolean});
break;
default:
if (message.command?.startsWith('command:')) {
const command = message.command as string;
Expand Down Expand Up @@ -358,7 +361,8 @@ export class OverviewPage {
showInstallIconsAlert: !this.iconsInstalled,
showEnableIconsAlert: this.iconsInstalled && workspace.getConfiguration().get<string>("workbench.iconTheme") !== "vscode-icons",
downloadValAlert: !this.pddlConfiguration.getValidatorPath(this.workspaceFolder) || !(await this.val.isInstalled()),
updateValAlert: await this.val.isNewValVersionAvailable()
updateValAlert: await this.val.isNewValVersionAvailable(),
showEnableFormatterAlert: !this.pddlConfiguration.getEditorFormatOnType()
// todo: workbench.editor.revealIfOpen
};
return this?.webViewPanel?.webview?.postMessage(message) ?? false;
Expand Down Expand Up @@ -389,6 +393,7 @@ interface OverviewConfiguration {
showEnableIconsAlert: boolean;
downloadValAlert: boolean;
updateValAlert: boolean;
showEnableFormatterAlert: boolean;
}

interface WireWorkspaceFolder {
Expand Down
Loading

0 comments on commit 75314e1

Please sign in to comment.