Skip to content

Commit e9705cf

Browse files
committed
Adds formatting support
Adds support for `findent` and `fprettify`. Fixes #29.
1 parent 34836cd commit e9705cf

File tree

5 files changed

+224
-1
lines changed

5 files changed

+224
-1
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Adds support for formatting with `findent` and `fprettify`
13+
([#29](https://github.com/krvajal/vscode-fortran-support/issues/29))
14+
- Adds prompts for installing Fortran IntelliSense and fortran-language-server
15+
16+
### Removed
17+
18+
- Removes `paths.js` for detecting binaries in favour of `which`
19+
1020
## [2.3.1]
1121

1222
### Fixed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- Code autocompletion (beta)
1717
- Symbols provider
1818
- Debugger, uses Microsoft's [C/C++ extension](https://github.com/Microsoft/vscode-cpptools)
19+
- Formatting with [findent](https://sourceforge.net/projects/findent/) or [fprettify](https://github.com/pseewald/fprettify)
1920

2021
![symbol_nav](./doc/symbol_nav.png)
2122

@@ -139,6 +140,41 @@ More details about how to setup the debugger can be found in Microsoft's website
139140
}
140141
```
141142

143+
## Formatting
144+
145+
Two formatters are supported [`findent`](https://sourceforge.net/projects/findent/)
146+
and [`fprettify`](https://github.com/pseewald/fprettify). `fprettify` can be installed
147+
automatically through the extension using `pip`, while `findent` must be installed
148+
by the user manually.
149+
150+
The formatter is controlled by the user option
151+
152+
```json
153+
{
154+
"fortran.formatting.formatter": "Disabled" | "findent" | "fprettify",
155+
}
156+
```
157+
158+
Additional arguments to the formatter can be input using
159+
160+
```json
161+
{
162+
"fortran.formatting.args":, ["-Cn", "-Rr"]
163+
}
164+
```
165+
166+
If the formatter is not present in the `PATH` its location can be input with
167+
168+
```json
169+
{
170+
"fortran.formattting.path": "/custom-path-to-formatter-binary",
171+
}
172+
```
173+
174+
### NOTE: About `findent`
175+
176+
`findent` can also be used to generate dependency files for a project.
177+
142178
## Requirements
143179

144180
For the linter to work you need to have `gfortran` on your path, or wherever you configure it to be.

package.json

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"Programming Languages",
3737
"Snippets",
3838
"Linters",
39-
"Debuggers"
39+
"Debuggers",
40+
"Formatters"
4041
],
4142
"activationEvents": [
4243
"onLanguage:FortranFreeForm"
@@ -156,6 +157,26 @@
156157
],
157158
"description": "Specify additional options to use when calling the gfortran compiler"
158159
},
160+
"fortran.formatting.formatter": {
161+
"type": "string",
162+
"default": "Disabled",
163+
"enum": [
164+
"findent",
165+
"fprettify",
166+
"Disabled"
167+
],
168+
"description": "Fortran formatter, currently supports findent and fprettify"
169+
},
170+
"fortran.formatting.args": {
171+
"type": "array",
172+
"default": [],
173+
"description": "Additional arguments for the formatter"
174+
},
175+
"fortran.formatting.path": {
176+
"type": "string",
177+
"default": "",
178+
"description": "Specify the full path of where the formatter is installed"
179+
},
159180
"fortran.provideSymbols": {
160181
"type": "boolean",
161182
"default": true,

src/extension.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { FORTRAN_FREE_FORM_ID, EXTENSION_ID, promptForMissingTool } from './lib/
1212
import { LoggingService } from './services/logging-service'
1313
import * as pkg from '../package.json'
1414
import { LANG_SERVER_TOOL_ID } from './lib/tools'
15+
import { FortranFormattingProvider } from './features/formatting-provider'
1516

1617
export function activate(context: vscode.ExtensionContext) {
1718
const loggingService = new LoggingService()
@@ -28,6 +29,18 @@ export function activate(context: vscode.ExtensionContext) {
2829
loggingService.logInfo('Linter is not enabled')
2930
}
3031

32+
if (extensionConfig.get('formatter') !== 'Disabled') {
33+
let disposable: vscode.Disposable =
34+
vscode.languages.registerDocumentFormattingEditProvider(
35+
FORTRAN_FREE_FORM_ID,
36+
new FortranFormattingProvider(loggingService)
37+
);
38+
context.subscriptions.push(disposable);
39+
}
40+
else {
41+
loggingService.logInfo('Formatting is disabled')
42+
}
43+
3144
if (extensionConfig.get('provideCompletion', true)) {
3245
let completionProvider = new FortranCompletionProvider(loggingService)
3346
vscode.languages.registerCompletionItemProvider(

src/features/formatting-provider.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
'use strict';
2+
3+
import * as fs from 'fs';
4+
import * as path from 'path';
5+
import * as which from 'which';
6+
import * as vscode from 'vscode';
7+
import * as cp from 'child_process';
8+
9+
import { FORMATTERS } from '../lib/tools';
10+
import { LoggingService } from '../services/logging-service';
11+
import { EXTENSION_ID, promptForMissingTool } from '../lib/helper';
12+
13+
14+
export class FortranFormattingProvider
15+
implements vscode.DocumentFormattingEditProvider {
16+
17+
constructor(private logger: LoggingService) { }
18+
19+
public provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken): vscode.ProviderResult<vscode.TextEdit[]> {
20+
21+
let formatterName: string = this.getFormatter();
22+
23+
if (formatterName === 'fprettify') {
24+
this.doFormatFprettify(document);
25+
}
26+
else if (formatterName === 'findent') {
27+
this.doFormatFindent(document);
28+
}
29+
else {
30+
this.logger.logError('Cannot format document with formatter set to Disabled')
31+
}
32+
33+
return
34+
}
35+
36+
/**
37+
* Use `fprettyfy` to format a Fortran file.
38+
*
39+
* @param document vscode.TextDocument document to operate on
40+
*/
41+
private doFormatFprettify(document: vscode.TextDocument) {
42+
43+
const formatterName: string = 'fprettify';
44+
let formatterPath: string = this.getFormatterPath();
45+
// If no formatter path is present check that formatter is present in $PATH
46+
if (!formatterPath) {
47+
if (!which.sync(formatterName, { nothrow: true })) {
48+
this.logger.logWarning(`Formatter: ${formatterName} not detected in your system.
49+
Attempting to install now.`);
50+
let msg = `Installing ${formatterName} through pip with --user option`;
51+
promptForMissingTool(formatterName, msg, 'Python');
52+
}
53+
}
54+
let formatter: string = path.join(formatterPath, formatterName);
55+
56+
let args: string[] = [document.fileName, ...this.getFormatterArgs()];
57+
// args.push('--silent'); // TODO: pass?
58+
59+
// Get current file (name rel to path), run extension can be in a shell??
60+
let process = cp.spawn(formatter, args);
61+
62+
// if the findent then capture the output from that and parse it back to the file
63+
process.stdout.on('data', (data) => { this.logger.logInfo(`formatter stdout: ${data}`) });
64+
process.stderr.on('data', (data) => { this.logger.logError(`formatter stderr: ${data}`) });
65+
process.on('close', (code: number) => { if (code !== 0) this.logger.logInfo(`formatter exited with code: ${code}`) });
66+
process.on('error', (code) => { this.logger.logInfo(`formatter exited with code: ${code}`) });
67+
68+
}
69+
70+
/**
71+
* Use `findent` to format a Fortran file.
72+
* Creates a temporary file where the output is placed and then deleted
73+
*
74+
* @param document vscode.TextDocument document to operate on
75+
*/
76+
private doFormatFindent(document: vscode.TextDocument) {
77+
78+
const formatterName: string = 'findent';
79+
let formatterPath: string = this.getFormatterPath();
80+
let formatter: string = path.join(formatterPath, formatterName);
81+
82+
// Annoyingly findent only outputs to a file and not to a stream so
83+
// let us go and create a temporary file
84+
let out = document.uri.path + '.findent.tmp';
85+
let args: string = ['< ' + document.fileName + ' >', out, ...this.getFormatterArgs()].join(' ');
86+
formatter = formatter + ' ' + args;
87+
88+
// @note It is wise to have all IO operations being synchronous we don't
89+
// want to copy or delete the temp file before findent has completed.
90+
// I cannot forsee a situation where a performance bottleneck is created
91+
// since findent performs something like ~100k lines per second
92+
cp.execSync(formatter, { stdio: 'inherit' });
93+
fs.copyFileSync(out, document.fileName);
94+
fs.unlinkSync(out);
95+
96+
}
97+
98+
/**
99+
* Get the formatter type
100+
* Currently supporting: `findent` and `fprettify`
101+
*
102+
* Formatters are defined in FORMATTERS (./lib/tools.ts)
103+
*
104+
* @returns {string} formatter name or `Disabled`
105+
*/
106+
private getFormatter(): string {
107+
108+
let config = vscode.workspace.getConfiguration(EXTENSION_ID)
109+
const formatter: string = config.get('formatting.formatter', 'Disabled')
110+
111+
if (!FORMATTERS.includes(formatter)) {
112+
this.logger.logError(`Unsupported formatter: ${formatter}`)
113+
}
114+
return formatter
115+
}
116+
117+
/**
118+
* Read in any custom arguments for the formatter
119+
*
120+
* @returns {string[]} list of additional arguments
121+
*/
122+
private getFormatterArgs(): string[] {
123+
let config = vscode.workspace.getConfiguration(EXTENSION_ID)
124+
const args: string[] = config.get('formatting.args', [])
125+
126+
return args
127+
}
128+
129+
/**
130+
* Installation directory for formatter (if not in PATH)
131+
*
132+
* @returns {string} path of formatter
133+
*/
134+
private getFormatterPath(): string {
135+
let config = vscode.workspace.getConfiguration(EXTENSION_ID)
136+
const formatterPath: string = config.get('formatting.path', '')
137+
if (formatterPath !== '') {
138+
this.logger.logInfo(`Formatter located in: ${formatterPath}`)
139+
}
140+
141+
return formatterPath
142+
}
143+
}

0 commit comments

Comments
 (0)