Skip to content

Commit

Permalink
feat(formatting): Initial range formatting (onivim#1960)
Browse files Browse the repository at this point in the history
* Initial range formatting scaffolding

* Add range formatter

* Add range formatting effect

* Factor format operation out

* Start wiring up selection

* Formatting

* More features tweaking

* Start wiring up selection from active editor

* Hook up range formatting

* Formatting
  • Loading branch information
bryphe authored Jun 20, 2020
1 parent 77381e8 commit b6d03f8
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 97 deletions.
226 changes: 151 additions & 75 deletions src/Feature/Formatting/Feature_Formatting.re
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,21 @@ type documentFormatter = {
type model = {
nextSessionId: int,
availableDocumentFormatters: list(documentFormatter),
availableRangeFormatters: list(documentFormatter),
activeSession: option(session),
};

let initial = {
nextSessionId: 0,
availableDocumentFormatters: [],
availableRangeFormatters: [],
activeSession: None,
};

module Internal = {
let clearSession = model => {...model, activeSession: None};

let startSession = (~sessionId, ~buffer, model) => {
...model,
nextSessionId: sessionId + 1,
activeSession:
Some({
sessionId,
bufferId: Oni_Core.Buffer.getId(buffer),
bufferVersion: Oni_Core.Buffer.getVersion(buffer),
}),
};
};

[@deriving show]
type command =
| FormatDocument;
| FormatDocument
| FormatRange;

[@deriving show]
type msg =
Expand All @@ -52,6 +40,11 @@ type msg =
selector: Exthost.DocumentSelector.t,
displayName: string,
})
| RangeFormatterAvailable({
handle: int,
selector: Exthost.DocumentSelector.t,
displayName: string,
})
| EditsReceived({
displayName: string,
sessionId: int,
Expand All @@ -75,23 +68,129 @@ type outmsg =
})
| FormatError(string);

let textToArray =
fun
| None => [||]
| Some(text) =>
text
|> Utility.StringEx.removeWindowsNewLines
|> Utility.StringEx.removeTrailingNewLine
|> Utility.StringEx.splitNewLines;

let extHostEditToVimEdit: Exthost.Edit.SingleEditOperation.t => Vim.Edit.t =
edit => {
range: edit.range |> Exthost.OneBasedRange.toRange,
text: textToArray(edit.text),
module Internal = {
let clearSession = model => {...model, activeSession: None};

let startSession = (~sessionId, ~buffer, model) => {
...model,
nextSessionId: sessionId + 1,
activeSession:
Some({
sessionId,
bufferId: Oni_Core.Buffer.getId(buffer),
bufferVersion: Oni_Core.Buffer.getVersion(buffer),
}),
};

let textToArray =
fun
| None => [||]
| Some(text) =>
text
|> Utility.StringEx.removeWindowsNewLines
|> Utility.StringEx.removeTrailingNewLine
|> Utility.StringEx.splitNewLines;

let extHostEditToVimEdit: Exthost.Edit.SingleEditOperation.t => Vim.Edit.t =
edit => {
range: edit.range |> Exthost.OneBasedRange.toRange,
text: textToArray(edit.text),
};

let runFormat =
(
~formatFn,
~model,
~configuration,
~matchingFormatters,
~buf,
~filetype,
~extHostClient,
) => {
let sessionId = model.nextSessionId;

let indentation =
Oni_Core.Indentation.getForBuffer(~buffer=buf, configuration);

let effects =
matchingFormatters
|> List.map(formatter =>
formatFn(
~handle=formatter.handle,
~uri=Oni_Core.Buffer.getUri(buf),
~options=
Exthost.FormattingOptions.{
tabSize: indentation.tabSize,
insertSpaces:
indentation.mode == Oni_Core.IndentationSettings.Spaces,
},
extHostClient,
res => {
switch (res) {
| Ok(edits) =>
EditsReceived({
displayName: formatter.displayName,
sessionId,
edits: List.map(extHostEditToVimEdit, edits),
})
| Error(msg) => EditRequestFailed({sessionId, msg})
}
})
)
|> Isolinear.Effect.batch;

if (matchingFormatters == []) {
(
model,
FormatError(
Printf.sprintf("No format providers available for %s", filetype),
),
);
} else {
(model |> startSession(~sessionId, ~buffer=buf), Effect(effects));
};
};
};

let update = (~configuration, ~maybeBuffer, ~extHostClient, model, msg) => {
let update =
(
~configuration,
~maybeSelection,
~maybeBuffer,
~extHostClient,
model,
msg,
) => {
switch (msg) {
| Command(FormatRange) =>
switch (maybeBuffer, maybeSelection) {
| (Some(buf), Some(range)) =>
let filetype =
buf
|> Oni_Core.Buffer.getFileType
|> Option.value(~default="plaintext");

let matchingFormatters =
model.availableRangeFormatters
|> List.filter(({selector, _}) =>
DocumentSelector.matches(~filetype, selector)
);

Internal.runFormat(
~formatFn=
Service_Exthost.Effects.LanguageFeatures.provideDocumentRangeFormattingEdits(
~range,
),
~model,
~configuration,
~matchingFormatters,
~buf,
~filetype,
~extHostClient,
);
| _ => (model, FormatError("No range selected."))
}

| Command(FormatDocument) =>
switch (maybeBuffer) {
| None => (model, Nothing)
Expand All @@ -106,51 +205,16 @@ let update = (~configuration, ~maybeBuffer, ~extHostClient, model, msg) => {
|> List.filter(({selector, _}) =>
DocumentSelector.matches(~filetype, selector)
);
let sessionId = model.nextSessionId;

let indentation =
Oni_Core.Indentation.getForBuffer(~buffer=buf, configuration);

let effects =
matchingFormatters
|> List.map(formatter =>
Service_Exthost.Effects.LanguageFeatures.provideDocumentFormattingEdits(
~handle=formatter.handle,
~uri=Oni_Core.Buffer.getUri(buf),
~options=
Exthost.FormattingOptions.{
tabSize: indentation.tabSize,
insertSpaces:
indentation.mode == Oni_Core.IndentationSettings.Spaces,
},
extHostClient,
res => {
switch (res) {
| Ok(edits) =>
EditsReceived({
displayName: formatter.displayName,
sessionId,
edits: List.map(extHostEditToVimEdit, edits),
})
| Error(msg) => EditRequestFailed({sessionId, msg})
}
})
)
|> Isolinear.Effect.batch;

if (matchingFormatters == []) {
(
model,
FormatError(
Printf.sprintf("No format providers available for %s", filetype),
),
);
} else {
(
model |> Internal.startSession(~sessionId, ~buffer=buf),
Effect(effects),
);
};

Internal.runFormat(
~formatFn=Service_Exthost.Effects.LanguageFeatures.provideDocumentFormattingEdits,
~model,
~configuration,
~matchingFormatters,
~buf,
~filetype,
~extHostClient,
);
}
| DocumentFormatterAvailable({handle, selector, displayName}) => (
{
Expand All @@ -162,6 +226,18 @@ let update = (~configuration, ~maybeBuffer, ~extHostClient, model, msg) => {
},
Nothing,
)

| RangeFormatterAvailable({handle, selector, displayName}) => (
{
...model,
availableRangeFormatters: [
{handle, selector, displayName},
...model.availableRangeFormatters,
],
},
Nothing,
)

| EditsReceived({displayName, sessionId, edits}) =>
switch (model.activeSession) {
| None => (model, Nothing)
Expand Down
10 changes: 9 additions & 1 deletion src/Feature/Formatting/Feature_Formatting.rei
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
open EditorCoreTypes;
open Oni_Core;

type model;
Expand All @@ -6,7 +7,8 @@ let initial: model;

[@deriving show]
type command =
| FormatDocument;
| FormatDocument
| FormatRange;

[@deriving show]
type msg =
Expand All @@ -16,6 +18,11 @@ type msg =
selector: Exthost.DocumentSelector.t,
displayName: string,
})
| RangeFormatterAvailable({
handle: int,
selector: Exthost.DocumentSelector.t,
displayName: string,
})
| EditsReceived({
displayName: string,
sessionId: int,
Expand All @@ -42,6 +49,7 @@ type outmsg =
let update:
(
~configuration: Oni_Core.Configuration.t,
~maybeSelection: option(Range.t),
~maybeBuffer: option(Oni_Core.Buffer.t),
~extHostClient: Exthost.Client.t,
model,
Expand Down
25 changes: 25 additions & 0 deletions src/Service/Exthost/Service_Exthost.re
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,31 @@ module Effects = {
});
};

let provideDocumentRangeFormattingEdits =
(~handle, ~uri, ~range, ~options, client, toMsg) => {
Isolinear.Effect.createWithDispatch(
~name="language.provideRangeFormattingEdits", dispatch => {
let promise =
Exthost.(
Request.LanguageFeatures.provideDocumentRangeFormattingEdits(
~handle,
~resource=uri,
~options,
~range=range |> OneBasedRange.ofRange,
client,
)
);

Lwt.on_success(
promise,
Option.iter(edits => dispatch(toMsg(Ok(edits)))),
);
Lwt.on_failure(promise, err =>
dispatch(toMsg(Error(Printexc.to_string(err))))
);
});
};

let provideHover = (~handle, ~uri, ~position, client, toMsg) => {
Isolinear.Effect.createWithDispatch(
~name="language.provideHover", dispatch => {
Expand Down
12 changes: 12 additions & 0 deletions src/Service/Exthost/Service_Exthost.rei
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
open EditorCoreTypes;
// EFFECTS
module Effects: {
module Documents: {
Expand Down Expand Up @@ -37,6 +38,17 @@ module Effects: {
) =>
Isolinear.Effect.t('msg);

let provideDocumentRangeFormattingEdits:
(
~handle: int,
~uri: Oni_Core.Uri.t,
~range: Range.t,
~options: Exthost.FormattingOptions.t,
Exthost.Client.t,
result(list(Exthost.Edit.SingleEditOperation.t), string) => 'msg
) =>
Isolinear.Effect.t('msg);

let provideHover:
(
~handle: int,
Expand Down
13 changes: 13 additions & 0 deletions src/Store/ExtensionClient.re
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,19 @@ let create = (~config, ~extensions, ~setup: Setup.t) => {
| LanguageFeatures(RegisterReferenceSupport({handle, selector})) =>
withClient(onRegisterReferencesProvider(handle, selector));
Lwt.return(Reply.okEmpty);
| LanguageFeatures(
RegisterRangeFormattingSupport({handle, selector, displayName, _}),
) =>
dispatch(
Formatting(
Feature_Formatting.RangeFormatterAvailable({
handle,
selector,
displayName,
}),
),
);
Lwt.return(Reply.okEmpty);
| LanguageFeatures(
RegisterDocumentFormattingSupport({handle, selector, displayName, _}),
) =>
Expand Down
Loading

0 comments on commit b6d03f8

Please sign in to comment.