diff --git a/docs/docs/configuration/settings.md b/docs/docs/configuration/settings.md index cbc55751fa..bf776e488a 100644 --- a/docs/docs/configuration/settings.md +++ b/docs/docs/configuration/settings.md @@ -179,8 +179,6 @@ The configuration file, `configuration.json` is in the Oni2 directory, whose loc - `workbench.sideBar.visible` __(_bool_ default: `true`)__ - Controls the visibility of the sidebar. -- `workbench.statusBar.visible` __(_bool_ default: `true`)__ - Controls the visibility of the status bar. - - `window.menuBarVisibility` __(_"visible" | "hidden"_ default: `"visible"`)__ - Controls the visibility of the menu bar. - `window.titleBarStyle` __(_"native" | "custom"_ default: `"native"` on Windows, `"custom"` otherwise)__ - Controls whether the titlebar is custom-rendered. @@ -191,6 +189,51 @@ The configuration file, `configuration.json` is in the Oni2 directory, whose loc - `oni.layout.singleTabMode` __(_bool_ default: `false`)__ - When `true`, groups will only hold a single editor, and closing this editor will always close the group. It will also hide the editor tabs, and therefore essentially hide the concept of editor groups. +#### Status Bar + +- `workbench.statusBar.visible` __(_bool_ default: `true`)__ - Controls the visibility of the status bar. + +##### Status Bar Items +- `workbench.statusBar.items` - Controls the position and visibility of the individual items on the status bar. + +```JSON + "workbench.statusBar.items": { + "start": ["notificationCount", "macro", "...", "diagnosticCount", "git"], + "end": ["...", "lineEndings", "indentation", "fileType", "position", "modeIndicator"], + "showOnNotification": ["modeIndicator", "notificationCount"], + "hidden": [], + "notificationMode": "default", + } +``` + +- `start` __(_[ __items__ ]_ default: `["notificationCount", "macro", "...", "diagnosticCount", "git"]`)__ - Defines the first group of items that appear. + +- `end` __(_[ __items__ ]_ default: `["...", "lineEndings", "indentation", "fileType", "position", "modeIndicator"]`)__ - Defines the group of items that appears at the end of the status bar. + +- `showOnNotification` __(_[ __items__ ]_ default: `["notificationCount", "modeIndicator"]`)__ - Defines the group of items that are hidden by the notification popup text. Only works on `notificationMode` `default|keepPosition` + +- `hidden` __(_[ __items__ ]_ default: `[ ]`)__ - Defines the group of items that are always hiden. + +- Possible __Items__: + - _"notificationCount"_ - Notification Count icon and counter + - _"macro"_ - The vim macro indicator + - _"leftItems/rightItems"_ - Items that generated based on other factors, like extensions + - _"diagnosticCount"_ - Problem icon and counter + - _"git"_ - Source control information + - _"lineEndings"_ - Line endings information + - _"indentation"_ - Indentation information + - _"fileType"_ - File type information + - _"position"_ - Position information + - _"modeIndicator"_ - Vim mode indicator + - _"..."_ - The rest of the items in that group, as by the default, that are not defined in other groups + - _Extension Item ID_ - Any known extension item ID can be used to as an item. + +- `notificationMode` __(_"default | keepPosition | compact | compact+"_ default: `[ ]`)__ - Defines how the notification popup. + - _"default"_ - The notification popup hides the __items__ on the `showOnNotification`, and reorganizes the items to be together based on if they are on `showOnNotification`. + - _"keepPosition"_ - The notification popup hides the __items__ on the `showOnNotification`. + - _"compact"_ - The notification popups take the minimum space available, but notifications can stack up on the status bar. + - _"compact+"_ - The same as `compact` but notification popups do not stack up. + ### Proxy Onivim 2 can be configured to send requests through an HTTP/HTTPs proxy with the following configuration: diff --git a/src/Core/ConfigurationDefaults.re b/src/Core/ConfigurationDefaults.re index 8ee04db60a..d50debceda 100644 --- a/src/Core/ConfigurationDefaults.re +++ b/src/Core/ConfigurationDefaults.re @@ -36,6 +36,12 @@ let getDefaultConfigString = configName => "workbench.sideBar.location": "left", "workbench.sideBar.visible": true, "workbench.statusBar.visible": true, + "workbench.statusBar.items": { + "start": ["notificationCount", "macro", "...", "diagnosticCount", "git"], + "showOnNotification": ["modeIndicator", "notificationCount"], + "end": ["...", "lineEndings", "indentation", "fileType", "position", "modeIndicator"], + "hidden": [], + }, "workbench.tree.indent": 2, "vim.useSystemClipboard": ["yank"] } diff --git a/src/Feature/Notification/Feature_Notification.re b/src/Feature/Notification/Feature_Notification.re index 4f02fdf320..d4f9fb262b 100644 --- a/src/Feature/Notification/Feature_Notification.re +++ b/src/Feature/Notification/Feature_Notification.re @@ -392,6 +392,15 @@ module View = { transform(Transform.[TranslateY(yOffset)]), ]; + let containerCompact = (~background, ~yOffset) => [ + position(`Relative), + backgroundColor(background), + flexDirection(`Row), + alignItems(`Center), + paddingHorizontal(10), + transform(Transform.[TranslateY(yOffset)]), + ]; + let text = (~foreground) => [ textWrap(TextWrapping.NoWrap), marginLeft(6), @@ -413,6 +422,8 @@ module View = { ~background, ~foreground, ~font: UiFont.t, + ~onlyAnimation: bool, + ~compact: bool, (), ) => { let yOffset = model.yOffset; @@ -433,16 +444,24 @@ module View = { | None => React.empty }; - - - - - ; + onlyAnimation + ? + : + + + + ; }; }; // LIST diff --git a/src/Feature/Notification/Feature_Notification.rei b/src/Feature/Notification/Feature_Notification.rei index cf3831465d..1a0bbf77e5 100644 --- a/src/Feature/Notification/Feature_Notification.rei +++ b/src/Feature/Notification/Feature_Notification.rei @@ -99,6 +99,8 @@ module View: { ~background: Color.t, ~foreground: Color.t, ~font: UiFont.t, + ~onlyAnimation: bool, + ~compact: bool, unit ) => React.element(React.node); diff --git a/src/Feature/StatusBar/Feature_StatusBar.re b/src/Feature/StatusBar/Feature_StatusBar.re index 7e8bf2a69e..42d224fb79 100644 --- a/src/Feature/StatusBar/Feature_StatusBar.re +++ b/src/Feature/StatusBar/Feature_StatusBar.re @@ -40,6 +40,229 @@ module Item = { }; }; +module ConfigurationItems = { + [@deriving show] + type notificationMode = + | Default + | KeepPosition + | Compact + | CompactPlus; + + [@deriving show] + type t = { + startItems: list(string), + endItems: list(string), + hidden: list(string), + showOnNotification: list(string), + notificationMode, + }; + + let decode = + Json.Decode.( + obj(({field, _}) => + { + startItems: field.withDefault("start", ["..."], list(string)), + endItems: field.withDefault("end", ["..."], list(string)), + hidden: field.withDefault("hidden", [], list(string)), + showOnNotification: + field.withDefault( + "showOnNotification", + ["notificationCount", "modeIndicator"], + list(string), + ), + notificationMode: + field.withDefault( + "notificationMode", + Default, + string + |> map(String.lowercase_ascii) + |> and_then( + fun + | "default" => succeed(Default) + | "keepposition" => succeed(KeepPosition) + | "compact" => succeed(Compact) + | "compact+" => succeed(CompactPlus) + | invalid => + fail("Invalid notification mode: " ++ invalid), + ), + ), + } + ) + ); + + let encode = configurationItems => + Json.Encode.( + obj([ + ("start", configurationItems.startItems |> list(string)), + ( + "showOnNotification", + configurationItems.showOnNotification |> list(string), + ), + ("end", configurationItems.endItems |> list(string)), + ("hidden", configurationItems.hidden |> list(string)), + ( + "notificationMode", + configurationItems.notificationMode + |> ( + fun + | Default => "default" + | Compact => "compact" + | CompactPlus => "compact+" + | KeepPosition => "keepPosition" + ) + |> string, + ), + ]) + ); + + let codec = Config.Schema.DSL.custom(~decode, ~encode); + + let startItemsDef = [ + "notificationCount", + "macro", + "leftItems", + "diagnosticCount", + "git", + "notificationPopup", + ]; + + let endItemsDef = [ + "rightItems", + "lineEndings", + "indentation", + "fileType", + "position", + "modeIndicator", + ]; + + let extendItem = "..."; + + let preProcess = (t, statusBarItems) => { + //Helper funcions + let removeFromList = (listToRemove, list) => + list |> List.filter(a => !List.mem(a, listToRemove)); + + let process = (def, alignment, list) => + list + |> List.map(str => + if (str == extendItem) { + def; + } else { + [str]; + } + ) + |> List.flatten + |> List.map(str => + ( + str, + ( + t.notificationMode == Default + || t.notificationMode == KeepPosition + ) + && !List.mem(str, t.showOnNotification), + ) + ) + |> ( + t.notificationMode != Default + ? List.fold_left( + (a, item) => { + let (toAdd, notificationToAdd) = item; + let (head, notification) = a |> List.hd; + + notificationToAdd == notification + ? [(head @ [toAdd], notification)] @ (a |> List.tl) + : [([toAdd], notificationToAdd)] @ a; + }, + [([], false)], + ) + //Merge all Items that are to be hidden notificaion and those that arent into + //separate positions + : List.fold_left( + (a, item) => { + let (toAdd, notificationToAdd) = item; + let (head, _) = a |> List.hd; + let (tail, _) = a |> List.tl |> List.hd; + + let isRight = alignment == Right; + + if (isRight) { + !notificationToAdd + ? [(head, true), (tail @ [toAdd], false)] + : [(head @ [toAdd], true), (tail, false)]; + } else { + notificationToAdd + ? [(head, false), (tail @ [toAdd], true)] + : [(head @ [toAdd], false), (tail, true)]; + }; + }, + [([], false), ([], false)], + ) + ); + + let allItems = + t.startItems @ t.endItems |> List.filter(a => a != extendItem); + + //Get if `...` if its, on the rigth and left + let extendStart = List.mem(extendItem, t.startItems); + let extendEnd = List.mem(extendItem, t.endItems); + + /* + if x has `...` and !x doesn't then add them all + else if x can extended then do + else then no default + */ + let startItemsPDef = + ( + if (extendStart && !extendEnd) { + endItemsDef @ startItemsDef; + } else if (extendStart) { + startItemsDef; + } else { + []; + } + ) + |> removeFromList(allItems @ t.hidden); + + let endItemsPDef = + ( + if (extendEnd && !extendStart) { + startItemsDef @ endItemsDef; + } else if (extendEnd) { + endItemsDef; + } else { + []; + } + ) + |> removeFromList(allItems @ t.hidden); + + let getItemsFromAlign = align => + statusBarItems + |> List.filter((item: Item.t) => + item.alignment == align + && !( + ( + switch (item.command) { + | Some(command) => List.mem(command, allItems @ t.hidden) + | None => false + } + ) + || List.mem(item.id, allItems @ t.hidden) + ) + ); + + ( + process(startItemsPDef, Right, t.startItems), + process(endItemsPDef, Left, t.endItems), + List.mem("center", t.showOnNotification) + || t.notificationMode != Default + && t.notificationMode != KeepPosition, + getItemsFromAlign(Right), + getItemsFromAlign(Left), + t.notificationMode, + ); + }; +}; + // MSG [@deriving show] @@ -198,7 +421,8 @@ module Styles = { transform(Transform.[TranslateY(yOffset)]), ]; - let sectionGroup = [ + let sectionGroup = background => [ + backgroundColor(background), position(`Relative), flexDirection(`Row), justifyContent(`SpaceBetween), @@ -236,8 +460,8 @@ let positionToString = ) | None => ""; -let sectionGroup = (~children, ()) => - children ; +let sectionGroup = (~background, ~children, ()) => + children ; let section = (~children=React.empty, ~align, ()) => children ; @@ -396,6 +620,7 @@ module View = { ~theme, ~dispatch, ~workingDirectory: string, + ~items: ConfigurationItems.t, (), ) => { let activeNotifications = Feature_Notification.active(notifications); @@ -405,6 +630,8 @@ module View = { Feature_Notification.statusBarForeground(~theme, notifications); let defaultForeground = Colors.StatusBar.foreground.from(theme); + let defaultBackground = + Feature_Theme.Colors.StatusBar.background.from(theme); let yOffset = 0.; @@ -444,21 +671,6 @@ module View = { viewOrTooltip ; }; - let leftItems = - statusBar.items - |> List.filter((item: Item.t) => item.alignment == Left) - |> List.map( - ({command, label, color, tooltip, backgroundColor, _}: Item.t) => - toStatusBarElement( - ~command?, - ~backgroundColor?, - ~color?, - ~tooltip?, - label, - ) - ) - |> React.listToElement; - let scmItems = scm |> Feature_SCM.statusBarCommands(~workingDirectory) @@ -471,14 +683,6 @@ module View = { ) |> React.listToElement; - let rightItems = - statusBar.items - |> List.filter((item: Item.t) => item.alignment == Right) - |> List.map(({command, label, color, tooltip, _}: Item.t) => - toStatusBarElement(~command?, ~color?, ~tooltip?, label) - ) - |> React.listToElement; - let indentation = () => { let text = indentationSettings |> indentationToString; @@ -553,50 +757,153 @@ module View = { ; }; - let notificationPopups = () => + let macroElement = + recordingMacro + |> Option.map(register => ) + |> Option.value(~default=React.empty); + + let ( + startItems, + endItems, + center, + rightItems, + leftItems, + notificationMode, + ) = + ConfigurationItems.preProcess(items, statusBar.items); + + let notificationPopups = (~onlyAnimation, ~compact, ()) => activeNotifications |> List.rev - |> List.map(model => - + |> ( + list => + ( + notificationMode == CompactPlus && list |> List.length > 0 + ? [list |> List.hd] : list + ) + |> List.map(model => + + ) + |> React.listToElement + ); + + let rightItems = + rightItems + |> List.map(({command, label, color, tooltip, _}: Item.t) => + toStatusBarElement(~command?, ~color?, ~tooltip?, label) ) |> React.listToElement; - let macroElement = - recordingMacro - |> Option.map(register => ) - |> Option.value(~default=React.empty); + let leftItems = + leftItems + |> List.map(({command, label, color, tooltip, _}: Item.t) => + toStatusBarElement(~command?, ~color?, ~tooltip?, label) + ) + |> React.listToElement; - -
- -
- -
macroElement
-
leftItems
-
- - scmItems -
-
-
rightItems
-
- - - - -
- - -
- -
+ let itemsToElement = list => + list + |> List.rev_map(item => { + let (list, noti) = item; + let onlyAnimation = !List.mem("notificationPopup", list); + let list = + list + |> List.map(str => + switch (str) { + | "modeIndicator" => + + | "notificationCount" => + + | "diagnosticCount" => + + | "lineEndings" => + | "indentation" => + | "fileType" => + | "position" => + | "macro" => macroElement + | "leftItems" => leftItems + | "git" => scmItems + | "rightItems" => rightItems + | "notificationPopup" => + notificationMode != Default + ? + : React.empty + | str => + statusBar.items + |> List.filter((item: Item.t) => + item.id == str + || ( + switch (item.command) { + | Some(command) => command == str + | None => false + } + ) + ) + |> List.map( + ({command, label, color, tooltip, _}: Item.t) => + toStatusBarElement( + ~command?, + ~color?, + ~tooltip?, + label, + ) + ) + |> React.listToElement + } + ); + let reactList = list |> React.listToElement; + + let count = + list + |> List.fold_left((a, b) => b != React.empty ? a + 1 : a, 0); + + if (noti && count > 0) { + +
reactList
+ +
; + } else if (noti) { + + + ; + } else { + +
reactList
+
; + }; + }) + |> React.listToElement; + + let startItems = startItems |> itemsToElement; + let endItems = endItems |> itemsToElement; + let center = + center + ? React.empty : ; + //Feature_Theme.Colors.StatusBar.background.from(theme) + +
startItems
+
center
+
endItems
; }; }; @@ -604,8 +911,20 @@ module View = { module Configuration = { open Config.Schema; let visible = setting("workbench.statusBar.visible", bool, ~default=true); + let items = + setting( + "workbench.statusBar.items", + ConfigurationItems.codec, + ~default={ + startItems: ["..."], + endItems: ["..."], + showOnNotification: ["notificationCount", "modeIndicator"], + hidden: [], + notificationMode: Default, + }, + ); }; module Contributions = { - let configuration = Configuration.[visible.spec]; + let configuration = Configuration.[visible.spec, items.spec]; }; diff --git a/src/Feature/StatusBar/Feature_StatusBar.rei b/src/Feature/StatusBar/Feature_StatusBar.rei index ac4a60bb43..432777d98d 100644 --- a/src/Feature/StatusBar/Feature_StatusBar.rei +++ b/src/Feature/StatusBar/Feature_StatusBar.rei @@ -17,6 +17,21 @@ module Item: { t; }; +module ConfigurationItems: { + type notificationMode = + | Default + | KeepPosition + | Compact + | CompactPlus; + + type t = { + startItems: list(string), + endItems: list(string), + hidden: list(string), + showOnNotification: list(string), + notificationMode, + }; +}; // MODEL [@deriving show] @@ -62,6 +77,7 @@ module View: { ~theme: ColorTheme.Colors.t, ~dispatch: msg => unit, ~workingDirectory: string, + ~items: ConfigurationItems.t, unit ) => Revery.UI.element; @@ -69,7 +85,10 @@ module View: { // CONFIGURATION -module Configuration: {let visible: Config.Schema.setting(bool);}; +module Configuration: { + let visible: Config.Schema.setting(bool); + let items: Config.Schema.setting(ConfigurationItems.t); +}; // CONTRIBUTIONS diff --git a/src/UI/Root.re b/src/UI/Root.re index 69b2030f98..85d4ac7cfb 100644 --- a/src/UI/Root.re +++ b/src/UI/Root.re @@ -162,6 +162,7 @@ let make = (~dispatch, ~state: State.t, ()) => { workingDirectory={Feature_Workspace.workingDirectory( state.workspace, )} + items={Feature_StatusBar.Configuration.items.get(config)} /> ; } else {