Skip to content

Commit

Permalink
(GH-465) Align hash rocket as you type
Browse files Browse the repository at this point in the history
This commit adds an auto-aligner for the hash rocket (=>) for .pp files.  This
feature is hidden behind the 'hashrocket' featureflag as it may have unintended
consequences and reformat user's files unexpectedly.

The auto-align triggers on the `>` character.
* The provider then determines if the user is in a hashrocket.
* It then looks at the preceeding and proceeding lines to determine the block
  of hash rockets to consider
* Once it has block of lines, it determines the smallest hash rocket location
  and edits the text file appropriately.
  • Loading branch information
glennsarti committed Jul 15, 2019
1 parent a32e71e commit 3e1a537
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 0 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,22 @@ To use a custom install path for the Puppet-Agent, set the `puppet.installDirect
}
```

#### Turning on experimental features

To assist in trying out new features the Puppet extension can turn on feature flags, which by default are turned off. Features are enabled by configuring the `puppet.editorService.featureFlags` setting. The available feature flags are:

##### `hashrocket`

Turns on the [Format As You Type](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#incrementally-format-code-as-the-user-types) feature and auto aligns hash rockets after you type ` =>`

Note - This feature requires `"editor.formatOnType": true` to be enabled

``` json
{
"puppet.editorService.featureFlags": ["hashrocket"]
}
```

## Experience a Problem?

### Puppet Agent Install
Expand Down
2 changes: 2 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as fs from 'fs';
import * as vscode from 'vscode';
import { CreateAggregrateConfiguration, IAggregateConfiguration } from './configuration';
import { IFeature } from './feature';
import { AlignHashRocketsFeature } from './feature/AlignHashRocketsFeature';
import { BoltFeature } from './feature/BoltFeature';
import { DebuggingFeature } from './feature/DebuggingFeature';
import { FormatDocumentFeature } from './feature/FormatDocumentFeature';
Expand Down Expand Up @@ -93,6 +94,7 @@ export function activate(context: vscode.ExtensionContext) {
extensionFeatures.push(new NodeGraphFeature(puppetLangID, connectionHandler, logger, extContext));
extensionFeatures.push(new PuppetResourceFeature(extContext, connectionHandler, logger));
extensionFeatures.push(new DebuggingFeature(debugType, configSettings, extContext, logger));
extensionFeatures.push(new AlignHashRocketsFeature(puppetLangID, configSettings, extContext, logger));
}

export function deactivate() {
Expand Down
125 changes: 125 additions & 0 deletions src/feature/AlignHashRocketsFeature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { IFeature } from '../feature';
import * as vscode from "vscode";
import { ILogger } from "../logging";
import { IAggregateConfiguration } from "../configuration";

class HashRocketLocation {
public rocketIndex: number;
public preceedingTextIndex: number;
}

class AlignHashRocketsFormatter implements vscode.OnTypeFormattingEditProvider{
private logger: ILogger;

constructor(logger: ILogger) {
this.logger = logger;
}

private extractHashRocketLocation(document: vscode.TextDocument, line: number): HashRocketLocation {
if (line < 0 || line > document.lineCount) { return null; }

var textLine = document.lineAt(line);
// Find the first hash-rocket `=>`
var hashRocket = textLine.text.indexOf(' =>');
if (hashRocket === -1) { return null; }

// Now find where the preceeding text ends
var textIndex = textLine.text.substr(0, hashRocket).trimRight().length;
// key => value
// | | <--- this is hashRocket
// | <--- this is textIndex
var result: HashRocketLocation = {
rocketIndex: hashRocket + 1,
preceedingTextIndex: textIndex
};

return result;
}

public provideOnTypeFormattingEdits(
document: vscode.TextDocument,
position: vscode.Position,
ch: string,
options: vscode.FormattingOptions,
token: vscode.CancellationToken):
Thenable<vscode.TextEdit[]>
{
//TODO: Check options for tabs vs spaces

// Check if we're at the end of an expected hash rocket
if (document.getText(new vscode.Range(position.line, position.character - 3, position.line, position.character)) !== ' =>') {
return null;
}

var logger = this.logger;
var extractor = this.extractHashRocketLocation;

return new Promise<vscode.TextEdit[]>(function (resolve) {
var result: vscode.TextEdit[] = [];
var rocketLocations: Map<number, HashRocketLocation> = new Map<number, HashRocketLocation>();
// Get the hash rocket location for the current position
var location = extractor(document, position.line);
// If we couldn't find one, or it's different towhat we expect, pull the ripcord!
if (location === null) { return null; }
if (location.rocketIndex !== position.character - 2) { return null; } // -2 becuase we need to go back the two characters in the hash rocket
rocketLocations.set(position.line, location);

// Find the hash rocket lines after this one
var lineNum = position.line;
do {
lineNum--;
location = extractor(document, lineNum);
if (location !== null) { rocketLocations.set(lineNum, location); }
} while (location !== null);

// Find the hash rocket lines before this one
lineNum = position.line;
do {
lineNum++;
location = extractor(document, lineNum);
if (location !== null) { rocketLocations.set(lineNum, location); }
} while (location !== null);

// Find the largest preceeding text and then add 1, this is where the hash rocket _should_ be.
var correctIndex:number = -1;
rocketLocations.forEach( (value) => {
if (value.preceedingTextIndex > correctIndex) { correctIndex = value.preceedingTextIndex; }
});
correctIndex++;

// Construct required TextEdits
rocketLocations.forEach((value, key) => {
// Only create a TextEdit if the hash rocket is in the wrong location
if (value.rocketIndex !== correctIndex) {
var te = new vscode.TextEdit(
new vscode.Range(key, value.preceedingTextIndex, key, value.rocketIndex),
' '.repeat(correctIndex - value.preceedingTextIndex)
);
logger.debug(`[${key}] = ` + JSON.stringify(value));
result.push(te);
}
});
resolve(result);
});
}
}

export class AlignHashRocketsFeature implements IFeature {
private provider: vscode.OnTypeFormattingEditProvider;

constructor(
langID: string,
config: IAggregateConfiguration,
context: vscode.ExtensionContext,
logger: ILogger
) {
// Check if the hashrocket feature flah has been set
if (!config.workspace.editorService.featureFlags.includes('hashrocket')) { return; }
this.provider = new AlignHashRocketsFormatter(logger);
context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider(langID, this.provider, '>'));
logger.debug("Registered On Type Formatting Edit provider");

}

public dispose(): any { return undefined; }
}

0 comments on commit 3e1a537

Please sign in to comment.