Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ime/#2781): Correctly position IME popup #3546

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Components/InputText/Model.re
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ module Internal = {
};

let handleInput = (~text, ~selection: Selection.t, key) => {
prerr_endline("Handle input: " ++ key);
switch (key, Selection.isCollapsed(selection)) {
// Arrow keys
| ("<LEFT>", true) => (
Expand Down
9 changes: 8 additions & 1 deletion src/Components/InputText/View.re
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ let%component make =
(),
) => {
let%hook textRef = Hooks.ref(None);
let%hook textBoundingBox = Hooks.ref(None);
let%hook scrollOffset = Hooks.ref(0);

let%hook () =
Expand Down Expand Up @@ -344,9 +345,15 @@ let%component make =
fontSize
/>;

switch (textBoundingBox^) {
| Some(bbox) when isFocused => Oni_Core.IME.set(Some(bbox))
| _ => ()
};

<Sneakable sneakId="text" onAnyClick=handleClick onSneak>
<View
style={Styles.box(~shadowOpacity, ~theme, ~focused=model.isFocused)}>
style={Styles.box(~shadowOpacity, ~theme, ~focused=model.isFocused)}
onBoundingBoxChanged={bbox => {textBoundingBox := Some(bbox)}}>
<View style=Styles.marginContainer>
<selectionView />
<cursor />
Expand Down
27 changes: 27 additions & 0 deletions src/Core/IME.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module BoundingBox2d = Revery.Math.BoundingBox2d;
let rect = (x, y, width, height) => {
Revery.Math.BoundingBox2d.create(
x |> float,
y |> float,
width |> float,
height |> float,
);
};

module MutableState = {
let current: ref(option(BoundingBox2d.t)) = ref(None);
};

let reset = () => MutableState.current := None;

let set = maybeBbox => MutableState.current := maybeBbox;

let textArea = _state => {
MutableState.current
// switch (FocusManager.current(state)) {
// | Editor => Some(rect(50, 50, 75, 75))
// | Extensions => None
// | _ => Some(rect(100, 100, 100, 100))
^;
// };
};
1 change: 1 addition & 0 deletions src/Core/Oni_Core.re
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module FontLigatures = FontLigatures;
module FontSmoothing = FontSmoothing;
module FontMeasurementCache = FontMeasurementCache;
module Glob = Glob;
module IME = IME;
module IconTheme = IconTheme;
module Indentation = Indentation;
module IndentationConverter = IndentationConverter;
Expand Down
12 changes: 12 additions & 0 deletions src/Feature/Editor/EditorSurface.re
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,18 @@ let%component make =
diffMarkers
/>;

let (pixelPosition: PixelPosition.t, _) =
Editor.bufferCharacterPositionToPixel(~position=cursorPosition, editor);
let left = Editor.pixelX(editor) +. pixelPosition.x +. gutterWidth;
let top = Editor.pixelY(editor) +. pixelPosition.y;
if (isActiveSplit && Vim.Mode.isInsert(mode)) {
Oni_Core.IME.set(
Some(
Revery.Math.BoundingBox2d.create(left, top, left +. 20., top +. 25.),
),
);
};

let hoverPopup = {
let maybeHover =
Feature_LanguageSupport.Hover.Popup.make(
Expand Down
86 changes: 66 additions & 20 deletions src/Feature/Input/Feature_Input.re
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module InnerIME = IME;
open Oni_Core;
open Utility;
module IME = InnerIME;
module Log = (val Log.withNamespace("Oni2.Feature.Input"));

module KeybindingsLoader = KeybindingsLoader;
Expand Down Expand Up @@ -164,22 +166,14 @@ module Configuration = {
CustomDecoders.timeout,
~default=Timeout(Revery.Time.seconds(1)),
);

module Debug = {
let showIMEFocus = setting("debug.ime.showFocus", bool, ~default=false);
};
};

// MSG

type outmsg =
| Nothing
| DebugInputShown
| ErrorNotifications(list(string))
| MapParseError({
fromKeys: string,
toKeys: string,
error: string,
})
| OpenFile(FpExp.t(FpExp.absolute))
| TimedOut;

type execute =
InputStateMachine.execute =
| NamedCommand({
Expand All @@ -198,6 +192,7 @@ type command =
[@deriving show]
type msg =
| Command(command)
| IME(IME.msg)
| KeybindingsUpdated([@opaque] list(Schema.resolvedKeybinding))
| KeybindingsReloaded({
bindings: [@opaque] list(Schema.resolvedKeybinding),
Expand All @@ -211,6 +206,19 @@ type msg =
| KeyDisplayer([@opaque] KeyDisplayer.msg)
| Timeout;

type outmsg =
| Nothing
| DebugInputShown
| Effect(Isolinear.Effect.t(msg))
| ErrorNotifications(list(string))
| MapParseError({
fromKeys: string,
toKeys: string,
error: string,
})
| OpenFile(FpExp.t(FpExp.absolute))
| TimedOut;

module Msg = {
let keybindingsUpdated = keybindings => KeybindingsUpdated(keybindings);
let vimMap = mapping => VimMap(mapping);
Expand All @@ -227,6 +235,16 @@ type model = {
// such that we can provide a unique id for the timer to flush on timeout.
inputTick: int,
keybindingLoader: KeybindingsLoader.t,
ime: IME.t,
};

let configurationChanged = (~config, model: model) => {
...model,
ime:
IME.setDebugView(
~enabled=Configuration.Debug.showIMEFocus.get(config),
model.ime,
),
};

type uniqueId = InputStateMachine.uniqueId;
Expand Down Expand Up @@ -280,6 +298,8 @@ let initial = (~loader, keybindings) => {
keyDisplayer: None,
inputTick: 0,
keybindingLoader: loader,

ime: IME.initial,
};
};

Expand All @@ -293,6 +313,13 @@ type effect =
})
| RemapRecursionLimitHit;

let imeEdit = (~candidateText, ~length, ~start, model) => {
...model,
ime: IME.setCandidateText(~candidateText, ~length, ~start, model.ime),
};

let isImeActive = ({ime, _}) => IME.isActive(ime);

let keyDown =
(
~config,
Expand Down Expand Up @@ -366,6 +393,7 @@ let text = (~text, ~time, {inputStateMachine, keyDisplayer, _} as model) => {
...model,
inputStateMachine: inputStateMachine',
keyDisplayer: keyDisplayer',
ime: IME.clear(model.ime),
}
|> incrementTick,
effects,
Expand Down Expand Up @@ -561,6 +589,14 @@ let update = (msg, model) => {
{...model, keyDisplayer: Some(KeyDisplayer.initial)},
Nothing,
)

| IME(imeMsg) =>
let (ime', imeEffect) = IME.update(imeMsg, model.ime);
(
{...model, ime: ime'},
Effect(imeEffect |> Isolinear.Effect.map(msg => IME(msg))),
);

| VimMap(mapping) =>
// When parsing Vim-style mappings, don't require a shift key.
// In other words - characters like 'J' should resolve to 'Shift+j'
Expand Down Expand Up @@ -692,8 +728,9 @@ module Commands = {

let sub =
(
~imeBoundingArea,
~config,
{keyDisplayer, inputTick, inputStateMachine, keybindingLoader, _},
{keyDisplayer, inputTick, inputStateMachine, keybindingLoader, ime, _},
) => {
let keyDisplayerSub =
switch (keyDisplayer) {
Expand All @@ -702,6 +739,9 @@ let sub =
KeyDisplayer.sub(kd) |> Isolinear.Sub.map(msg => KeyDisplayer(msg))
};

let imeSub =
IME.sub(~imeBoundingArea, ime) |> Isolinear.Sub.map(msg => IME(msg));

let timeoutSub =
switch (Configuration.timeout.get(config)) {
| NoTimeout => Isolinear.Sub.none
Expand All @@ -724,7 +764,7 @@ let sub =
KeybindingsReloaded({bindings, errors})
});

[keyDisplayerSub, timeoutSub, loaderSub] |> Isolinear.Sub.batch;
[keyDisplayerSub, imeSub, timeoutSub, loaderSub] |> Isolinear.Sub.batch;
};

module ContextKeys = {
Expand All @@ -743,7 +783,8 @@ module Contributions = {
openDefaultKeybindingsFile,
];

let configuration = Configuration.[leaderKey.spec, timeout.spec];
let configuration =
Configuration.[Debug.showIMEFocus.spec, leaderKey.spec, timeout.spec];

let contextKeys = model => {
WhenExpr.ContextKeys.(
Expand All @@ -761,11 +802,16 @@ module View = {

module Overlay = {
let make = (~input, ~uiFont, ~bottom, ~right, ()) => {
switch (input.keyDisplayer) {
| None => React.empty
| Some(keyDisplayer) =>
<KeyDisplayer model=keyDisplayer uiFont bottom right />
};
let keyDisplayerView =
switch (input.keyDisplayer) {
| None => React.empty
| Some(keyDisplayer) =>
<KeyDisplayer model=keyDisplayer uiFont bottom right />
};

let imeView = <IME.View ime={input.ime} />;

[keyDisplayerView, imeView] |> React.listToElement;
};
};

Expand Down
40 changes: 27 additions & 13 deletions src/Feature/Input/Feature_Input.rei
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,6 @@ open EditorInput;
// TODO: Move to Service_Input
module ReveryKeyConverter = ReveryKeyConverter;

type outmsg =
| Nothing
| DebugInputShown
| ErrorNotifications(list(string))
| MapParseError({
fromKeys: string,
toKeys: string,
error: string,
})
| OpenFile(FpExp.t(FpExp.absolute))
| TimedOut;

[@deriving show]
type command;

Expand Down Expand Up @@ -73,6 +61,19 @@ module KeybindingsLoader: {
[@deriving show]
type msg;

type outmsg =
| Nothing
| DebugInputShown
| Effect(Isolinear.Effect.t(msg))
| ErrorNotifications(list(string))
| MapParseError({
fromKeys: string,
toKeys: string,
error: string,
})
| OpenFile(FpExp.t(FpExp.absolute))
| TimedOut;

module Msg: {
let keybindingsUpdated: list(Schema.resolvedKeybinding) => msg;
let vimMap: Vim.Mapping.t => msg;
Expand Down Expand Up @@ -116,6 +117,11 @@ let timeout:
let text:
(~text: string, ~time: Revery.Time.t, model) => (model, list(effect));

let imeEdit:
(~candidateText: string, ~length: int, ~start: int, model) => model;

let isImeActive: model => bool;

let candidates:
(~config: Config.resolver, ~context: WhenExpr.ContextKeys.t, model) =>
list((EditorInput.Matcher.t, execute));
Expand Down Expand Up @@ -155,13 +161,21 @@ let disable: model => model;

let notifyFileSaved: (FpExp.t(FpExp.absolute), model) => model;

let configurationChanged: (~config: Config.resolver, model) => model;

// UPDATE

let update: (msg, model) => (model, outmsg);

// SUBSCRIPTION

let sub: (~config: Config.resolver, model) => Isolinear.Sub.t(msg);
let sub:
(
~imeBoundingArea: option(Revery.Math.BoundingBox2d.t),
~config: Config.resolver,
model
) =>
Isolinear.Sub.t(msg);

// CONTRIBUTIONS

Expand Down
Loading