Skip to content

Commit def5fd6

Browse files
authored
Merge pull request #256 from krvajal/feature/formatting
Support for Formatting
2 parents d48a111 + 3dec046 commit def5fd6

8 files changed

+253
-5
lines changed

CHANGELOG.md

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

88
## [Unreleased]
99

10+
## [2.5.0]
11+
12+
### Added
13+
14+
- Adds support for formatting with `findent` and `fprettify`
15+
([#29](https://github.com/krvajal/vscode-fortran-support/issues/29))
16+
1017
## [2.4.3]
1118

1219
### Changed
@@ -22,13 +29,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2229
([#257](https://github.com/krvajal/vscode-fortran-support/issues/257))
2330
- Linting is now operational for `FortranFixedForm`
2431
([#258](https://github.com/krvajal/vscode-fortran-support/issues/258))
32+
- Fixes dummy variable list erroneous syntax highlighting
33+
([#264](https://github.com/krvajal/vscode-fortran-support/issues/264))
2534

2635
### Changed
2736

2837
- Renamed the Fixed Format Format language from `fortran_fixed-form` to
2938
`FortranFixedForm`, an alias has been added for backwards compatibility
3039
([#259](https://github.com/krvajal/vscode-fortran-support/issues/259))
3140

41+
### Added
42+
43+
- Adds prompts for installing Fortran IntelliSense and fortran-language-server
44+
3245
### Removed
3346

3447
- Removes `paths.js` for detecting binaries in favour of `which`
@@ -306,7 +319,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
306319

307320
- Initial release
308321

309-
[unreleased]: https://github.com/krvajal/vscode-fortran-support/compare/v2.4.3...HEAD
322+
[unreleased]: https://github.com/krvajal/vscode-fortran-support/compare/v2.5.0...HEAD
323+
[2.5.0]: https://github.com/krvajal/vscode-fortran-support/compare/v2.4.3...v2.5.0
310324
[2.4.3]: https://github.com/krvajal/vscode-fortran-support/compare/v2.4.2...v2.4.3
311325
[2.4.2]: https://github.com/krvajal/vscode-fortran-support/compare/v2.4.1...v2.4.2
312326
[2.4.1]: https://github.com/krvajal/vscode-fortran-support/compare/v2.4.0...v2.4.1

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- Code autocompletion (beta)
1818
- Symbols provider
1919
- Debugger, uses Microsoft's [C/C++ extension](https://github.com/Microsoft/vscode-cpptools)
20+
- Formatting with [findent](https://github.com/gnikit/findent-pypi) or [fprettify](https://github.com/pseewald/fprettify)
2021

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

@@ -137,6 +138,44 @@ More details about how to setup the debugger can be found in Microsoft's website
137138
}
138139
```
139140

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

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

images/findent-demo.gif

487 KB
Loading

images/fprettify-demo.gif

514 KB
Loading

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "linter-gfortran",
33
"displayName": "Modern Fortran",
44
"description": "Modern Fortran language support, including syntax highlighting and error detection.",
5-
"version": "2.4.0",
5+
"version": "2.5.0",
66
"publisher": "krvajalm",
77
"license": "MIT",
88
"author": {
@@ -36,7 +36,8 @@
3636
"Programming Languages",
3737
"Snippets",
3838
"Linters",
39-
"Debuggers"
39+
"Debuggers",
40+
"Formatters"
4041
],
4142
"activationEvents": [
4243
"onLanguage:FortranFreeForm",
@@ -159,6 +160,26 @@
159160
],
160161
"description": "Specify additional options to use when calling the gfortran compiler"
161162
},
163+
"fortran.formatting.formatter": {
164+
"type": "string",
165+
"default": "findent",
166+
"enum": [
167+
"findent",
168+
"fprettify",
169+
"Disabled"
170+
],
171+
"description": "Fortran formatter, currently supports findent and fprettify"
172+
},
173+
"fortran.formatting.args": {
174+
"type": "array",
175+
"default": [],
176+
"description": "Additional arguments for the formatter"
177+
},
178+
"fortran.formatting.path": {
179+
"type": "string",
180+
"default": "",
181+
"description": "Specify the full path of where the formatter is installed"
182+
},
162183
"fortran.provideSymbols": {
163184
"type": "boolean",
164185
"default": true,

src/extension.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { FORTRAN_DOCUMENT_SELECTOR, EXTENSION_ID, promptForMissingTool } from '.
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();
@@ -29,6 +30,17 @@ export function activate(context: vscode.ExtensionContext) {
2930
loggingService.logInfo('Linter is not enabled');
3031
}
3132

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

src/features/formatting-provider.ts

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

0 commit comments

Comments
 (0)