diff --git a/src/Definition/Info.elm b/src/Definition/Info.elm index 8a5d5b2..8bfc99b 100644 --- a/src/Definition/Info.elm +++ b/src/Definition/Info.elm @@ -15,7 +15,7 @@ import List.Nonempty as NEL type alias Info = { name : String - , namespace : Maybe String + , namespace : Maybe FQN , otherNames : List FQN } @@ -33,7 +33,7 @@ makeInfo name_ allFqns = -- Helpers -namespaceAndOtherNames : String -> NEL.Nonempty FQN -> ( Maybe String, List FQN ) +namespaceAndOtherNames : String -> NEL.Nonempty FQN -> ( Maybe FQN, List FQN ) namespaceAndOtherNames suffixName fqns = let fqnWithin = @@ -46,4 +46,4 @@ namespaceAndOtherNames suffixName fqns = |> NEL.toList |> ListE.filterNot (FQN.equals fqnWithin) in - ( FQN.namespaceOf suffixName fqnWithin, fqnsWithout ) + ( FQN.namespace fqnWithin, fqnsWithout ) diff --git a/src/FullyQualifiedName.elm b/src/FullyQualifiedName.elm index d5cbc04..a871a5c 100644 --- a/src/FullyQualifiedName.elm +++ b/src/FullyQualifiedName.elm @@ -11,6 +11,7 @@ module FullyQualifiedName exposing , isSuffixOf , isValidSegmentChar , isValidUrlSegmentChar + , namespace , namespaceOf , segments , toString @@ -21,6 +22,7 @@ module FullyQualifiedName exposing ) import Json.Decode as Decode +import List.Extra as ListE import List.Nonempty as NEL import String.Extra as StringE import Url @@ -144,6 +146,19 @@ unqualifiedName (FQN nameParts) = NEL.last nameParts +namespace : FQN -> Maybe FQN +namespace (FQN segments_) = + case segments_ |> NEL.toList |> ListE.init of + Nothing -> + Nothing + + Just [] -> + Nothing + + Just segments__ -> + Just (fromList segments__) + + equals : FQN -> FQN -> Bool equals a b = toString a == toString b diff --git a/src/UI/Icon.elm b/src/UI/Icon.elm index f8b8a0e..79322c7 100644 --- a/src/UI/Icon.elm +++ b/src/UI/Icon.elm @@ -14,6 +14,8 @@ import Svg.Attributes as Attrs , r , rx , stroke + , strokeLinecap + , strokeLinejoin , strokeWidth , viewBox , width @@ -232,9 +234,17 @@ chevronUp = folder : Icon msg folder = Icon - "folder" + "folder " [] - [ path [ stroke "currentColor", d "M.5 3V2a.5.5 0 01.5-.5h4a.5.5 0 01.4.2l.9 1.2.4-.3-.4.3a1.5 1.5 0 001.2.6H13a.5.5 0 01.5.5v8a.5.5 0 01-.5.5H1a.5.5 0 01-.5-.5V3z" ] [] ] + [ path [ fill "currentColor", fillRule "evenodd", d "M2 2C1.44772 2 1 2.44772 1 3V11.5C1 12.0523 1.44772 12.5 2 12.5H12C12.5523 12.5 13 12.0523 13 11.5V4.5C13 3.94772 12.5523 3.5 12 3.5H7.83333C7.61696 3.5 7.40643 3.42982 7.23333 3.3L5.76667 2.2C5.59357 2.07018 5.38304 2 5.16667 2H4.5H2Z" ] [] ] + + +folderOutlined : Icon msg +folderOutlined = + Icon + "folder-outlined" + [] + [ path [ fill "currentColor", fillRule "evenodd", d "M2 2C1.44772 2 1 2.44772 1 3V11.5C1 12.0523 1.44772 12.5 2 12.5H12C12.5523 12.5 13 12.0523 13 11.5V4.5C13 3.94772 12.5523 3.5 12 3.5H7.83333C7.61696 3.5 7.40643 3.42982 7.23333 3.3L5.76667 2.2C5.59357 2.07018 5.38304 2 5.16667 2H4.5H2ZM2.5 4.5C2.22386 4.5 2 4.72386 2 5V11C2 11.2761 2.22386 11.5 2.5 11.5H11.5C11.7761 11.5 12 11.2761 12 11V5C12 4.72386 11.7761 4.5 11.5 4.5H2.5Z" ] [] ] hash : Icon msg @@ -242,7 +252,7 @@ hash = Icon "hash" [] - [ path [ fill "currentColor", d "M7.629 9.197L7.082 12h.978l.546-2.803h1.723v-.936H8.784l.472-2.42h1.586v-.95H9.44l.561-2.844h-.978l-.56 2.844H6.658l.56-2.844h-.977l-.56 2.844H3.732v.95h1.764l-.472 2.42H3.213v.936h1.634L4.3 12h.977l.547-2.803H7.63zm-1.627-.936l.472-2.42h1.804l-.471 2.42H6.002z" ] [] ] + [ path [ fill "currentColor", d "M6.55414 9.62088C7.18994 9.62088 7.66431 10.2064 7.53237 10.8284L7.23223 12.2432C7.14954 12.633 7.44684 13 7.84532 13C8.14133 13 8.39698 12.7929 8.45841 12.5033L8.90178 10.4134C8.99979 9.95133 9.4077 9.62088 9.88001 9.62088H10.7634C11.0752 9.62088 11.328 9.36812 11.328 9.05632C11.328 8.74452 11.0752 8.49176 10.7634 8.49176H10.4215C9.84528 8.49176 9.41532 7.96113 9.53478 7.39741L9.75316 6.36686C9.8511 5.90474 10.2591 5.57418 10.7314 5.57418H11.4272C11.7435 5.57418 12 5.31772 12 5.00137C12 4.68502 11.7435 4.42857 11.4272 4.42857H11.1789C10.6561 4.42857 10.2664 3.94653 10.3759 3.43535L10.7355 1.75773C10.819 1.36775 10.5217 1 10.1229 1C9.82752 1 9.57227 1.20635 9.51037 1.49518L9.05112 3.63812C8.95231 4.09918 8.54485 4.42857 8.07332 4.42857H7.75314C7.11649 4.42857 6.64193 3.84154 6.77534 3.21902L7.08851 1.75773C7.17208 1.36775 6.8748 1 6.47596 1C6.18057 1 5.92531 1.20635 5.86341 1.49518L5.40417 3.63812C5.30536 4.09918 4.8979 4.42857 4.42637 4.42857H3.25381C2.93746 4.42857 2.681 4.68502 2.681 5.00137C2.681 5.31772 2.93746 5.57418 3.25381 5.57418H3.75871C4.39443 5.57418 4.86878 6.15958 4.73698 6.78149L4.54253 7.69907C4.4446 8.1612 4.03665 8.49176 3.56426 8.49176H2.56456C2.25276 8.49176 2 8.74452 2 9.05632C2 9.36812 2.25276 9.62088 2.56456 9.62088H2.95846C3.56785 9.62088 4.02252 10.1821 3.89605 10.7782L3.58528 12.2432C3.50258 12.633 3.79989 13 4.19837 13C4.49438 13 4.75003 12.7929 4.81146 12.5033L5.25482 10.4134C5.35284 9.95133 5.76075 9.62088 6.23305 9.62088H6.55414ZM6.89004 8.49176C6.25432 8.49176 5.77997 7.90635 5.91176 7.28445L6.10621 6.36686C6.20414 5.90474 6.6121 5.57418 7.08449 5.57418H7.40566C8.04138 5.57418 8.51573 6.15958 8.38394 6.78149L8.18949 7.69907C8.09156 8.1612 7.6836 8.49176 7.21121 8.49176H6.89004Z" ] [] ] plus : Icon msg @@ -286,3 +296,22 @@ list = , rect [ Attrs.x "3", y "6.5", width "8", height "1", rx "0.5", fill "currentColor" ] [] , rect [ Attrs.x "3", y "3.5", width "8", height "1", rx "0.5", fill "currentColor" ] [] ] + + +tags : Icon msg +tags = + Icon "tags" + [] + [ path [ fill "currentColor", fillRule "evenodd", d "M0.5 2.5V6.33693C0.5 6.61411 0.615047 6.87886 0.817682 7.06798L6.32182 12.2052C6.73954 12.595 7.39844 12.5563 7.76752 12.1201L11.3047 7.93974C11.6499 7.53182 11.6134 6.9246 11.2218 6.561L6.05924 1.76721C5.87428 1.59545 5.6312 1.5 5.37879 1.5H1.5C0.947714 1.5 0.5 1.94772 0.5 2.5ZM3.13574 5.45362C3.86358 5.45362 4.45361 4.86359 4.45361 4.13575C4.45361 3.40791 3.86358 2.81788 3.13574 2.81788C2.4079 2.81788 1.81787 3.40791 1.81787 4.13575C1.81787 4.86359 2.4079 5.45362 3.13574 5.45362Z" ] [] + , path [ fill "transparent", stroke "currentColor", d "M9 2.5L13.2987 6.3688C13.6924 6.72318 13.7415 7.32313 13.4106 7.73679L10 12", strokeLinecap "round", strokeLinejoin "round" ] [] + ] + + +tagsOutlined : Icon msg +tagsOutlined = + Icon "tags-outlined" + [] + [ path [ fill "currentColor", fillRule "evenodd", d "M2 2.5C1.72386 2.5 1.5 2.72386 1.5 3V6.11965C1.5 6.25824 1.55752 6.39062 1.65884 6.48518L6.62037 11.1159C6.82923 11.3109 7.15868 11.2915 7.34322 11.0734L10.2329 7.65828C10.4055 7.45432 10.3872 7.15071 10.1915 6.96891L5.52267 2.6336C5.43019 2.54773 5.30865 2.5 5.18244 2.5H2ZM0.5 2.5V6.33693C0.5 6.61411 0.615047 6.87886 0.817682 7.06798L6.32182 12.2052C6.73954 12.595 7.39844 12.5563 7.76752 12.1201L11.3047 7.93974C11.6499 7.53181 11.6134 6.9246 11.2218 6.561L6.05924 1.76721C5.87428 1.59545 5.6312 1.5 5.37879 1.5H1.5C0.947714 1.5 0.5 1.94772 0.5 2.5Z" ] [] + , path [ fill "currentColor", fillRule "evenodd", d "M8.62836 2.16552C8.81309 1.96026 9.12923 1.94362 9.33449 2.12835L13.6332 5.99715C14.2238 6.52872 14.2974 7.42865 13.801 8.04914L10.3904 12.3123C10.2179 12.528 9.90329 12.5629 9.68766 12.3904C9.47203 12.2179 9.43707 11.9033 9.60957 11.6877L13.0201 7.42444C13.1856 7.21761 13.1611 6.91763 12.9642 6.74045L8.66552 2.87165C8.46027 2.68692 8.44363 2.37077 8.62836 2.16552Z" ] [] + , circle [ cx "4", cy "5", r "1.5", stroke "currentColor", fill "transparent" ] [] + ] diff --git a/src/Workspace/WorkspaceItem.elm b/src/Workspace/WorkspaceItem.elm index c937dd0..ef4e09d 100644 --- a/src/Workspace/WorkspaceItem.elm +++ b/src/Workspace/WorkspaceItem.elm @@ -5,15 +5,15 @@ import Definition.AbilityConstructor exposing (AbilityConstructor(..), AbilityCo import Definition.Category as Category exposing (Category) import Definition.DataConstructor exposing (DataConstructor(..), DataConstructorDetail, DataConstructorSource(..)) import Definition.Doc as Doc exposing (Doc(..), DocFoldToggles) -import Definition.Info as Info +import Definition.Info as Info exposing (Info) import Definition.Reference as Reference exposing (Reference(..)) import Definition.Source as Source import Definition.Term as Term exposing (Term(..), TermCategory, TermDetail, TermSignature(..), TermSource(..)) import Definition.Type as Type exposing (Type(..), TypeCategory, TypeDetail, TypeSource(..)) import FullyQualifiedName as FQN exposing (FQN) -import Hash +import Hash exposing (Hash) import HashQualified as HQ exposing (HashQualified(..)) -import Html exposing (Attribute, Html, a, div, h3, header, section, span, strong, text) +import Html exposing (Attribute, Html, a, div, h3, header, label, section, span, strong, text) import Html.Attributes exposing (class, classList, id, title) import Html.Events exposing (onClick) import Http @@ -22,7 +22,7 @@ import List.Nonempty as NEL import Maybe.Extra as MaybeE import String.Extra exposing (pluralize) import UI -import UI.Icon as Icon +import UI.Icon as Icon exposing (Icon) import UI.Tooltip as Tooltip import Util import Workspace.Zoom exposing (Zoom(..)) @@ -136,22 +136,18 @@ viewBuiltin item = UI.nothing -{-| TODO: Some of this that isn't Workspace specific might be moved into Definition.Info --} -viewNames : - msg - -> { a | name : String, namespace : Maybe String, otherNames : List FQN } - -> Category - -> Html msg -viewNames onClick_ info category = +viewMenuItem : Icon msg -> String -> Html msg +viewMenuItem icon label_ = + div [ class "menu-item" ] [ Icon.view icon, label [] [ text label_ ] ] + + +viewMenuItems : Hash -> Info -> Html msg +viewMenuItems hash_ info = let namespace = case info.namespace of Just ns -> - div [ class "namespace" ] - [ span [ class "separator in" ] [ text "in" ] - , text ns - ] + viewMenuItem Icon.folderOutlined (FQN.toString ns) Nothing -> UI.nothing @@ -166,21 +162,35 @@ viewNames onClick_ info category = div [] (List.map (\n -> div [] [ text (FQN.toString n) ]) info.otherNames) otherNamesLabel = - text (pluralize "other name..." "other names..." numOtherNames) + pluralize "other name..." "other names..." numOtherNames in - div [] - [ span [ class "separator" ] [ text "•" ] - , span [ class "other-names" ] [ Tooltip.tooltip otherNamesLabel otherNamesTooltipContent |> Tooltip.withArrow Tooltip.TopLeft |> Tooltip.view ] - ] + Tooltip.tooltip (viewMenuItem Icon.tagsOutlined otherNamesLabel) otherNamesTooltipContent + |> Tooltip.withArrow Tooltip.TopLeft + |> Tooltip.view else UI.nothing + + formatHash h = + h |> Hash.toString |> String.dropLeft 1 |> String.left 8 + + hash = + Tooltip.tooltip (viewMenuItem Icon.hash (formatHash hash_)) (text (Hash.toString hash_)) + |> Tooltip.withArrow Tooltip.TopLeft + |> Tooltip.view in - div [ class "names", onClick onClick_ ] - [ Icon.view Icon.caretRight - , Icon.view (Category.icon category) - , h3 [ class "name" ] [ text info.name ] - , div [ class "info" ] [ namespace, otherNames ] + div [ class "menu-items" ] [ hash, namespace, otherNames ] + + +viewInfo : msg -> Hash -> Info -> Category -> Html msg +viewInfo onClick_ hash info category = + div [ class "info" ] + [ a [ class "toggle-zoom", onClick onClick_ ] + [ Icon.view Icon.caretRight + , Icon.view (Category.icon category) + , h3 [ class "name" ] [ text info.name ] + ] + , viewMenuItems hash info ] @@ -267,44 +277,44 @@ viewItem closeMsg toOpenReferenceMsg toUpdateZoomMsg toggleFoldMsg ref data isFo |> Maybe.withDefault UI.nothing in case data.item of - TermItem (Term _ category detail) -> + TermItem (Term h category detail) -> viewClosableRow closeMsg ref attrs - (viewNames docZoomMsg detail.info (Category.Term category)) + (viewInfo docZoomMsg h detail.info (Category.Term category)) [ ( UI.nothing, viewDoc_ detail.doc ) , ( UI.nothing, viewBuiltin data.item ) , viewSource toOpenReferenceMsg data.item ] - TypeItem (Type _ category detail) -> + TypeItem (Type h category detail) -> viewClosableRow closeMsg ref attrs - (viewNames docZoomMsg detail.info (Category.Type category)) + (viewInfo docZoomMsg h detail.info (Category.Type category)) [ ( UI.nothing, viewDoc_ detail.doc ) , ( UI.nothing, viewBuiltin data.item ) , viewSource toOpenReferenceMsg data.item ] - DataConstructorItem (DataConstructor _ detail) -> + DataConstructorItem (DataConstructor h detail) -> viewClosableRow closeMsg ref attrs - (viewNames docZoomMsg detail.info (Category.Type Type.DataType)) + (viewInfo docZoomMsg h detail.info (Category.Type Type.DataType)) [ ( UI.nothing, viewBuiltin data.item ) , viewSource toOpenReferenceMsg data.item ] - AbilityConstructorItem (AbilityConstructor _ detail) -> + AbilityConstructorItem (AbilityConstructor h detail) -> viewClosableRow closeMsg ref attrs - (viewNames docZoomMsg detail.info (Category.Type Type.AbilityType)) + (viewInfo docZoomMsg h detail.info (Category.Type Type.AbilityType)) [ ( UI.nothing, viewBuiltin data.item ) , viewSource toOpenReferenceMsg data.item ] diff --git a/src/css/themes/unison/light.css b/src/css/themes/unison/light.css index e780eea..55080c2 100644 --- a/src/css/themes/unison/light.css +++ b/src/css/themes/unison/light.css @@ -65,7 +65,7 @@ --color-workspace-item-link: var(--color-blue-1); --color-workspace-item-link-active: var(--color-pink-1); --color-workspace-item-link-hover: var(--color-pink-2); - --color-workspace-item-subtle-fg: var(--color-gray-lighten-40); + --color-workspace-item-subtle-fg: var(--color-gray-lighten-30); --color-workspace-item-subtle-fg-em: var(--color-gray-lighten-20); --color-workspace-item-subtle-bg: var(--color-gray-lighten-60); --color-workspace-item-em-bg: var(--color-gray-lighten-55); diff --git a/src/css/workspace.css b/src/css/workspace.css index 783249e..82c6dea 100644 --- a/src/css/workspace.css +++ b/src/css/workspace.css @@ -135,58 +135,82 @@ color: var(--color-workspace-item-fg); } -.definition-row header .names { +.definition-row header .info { display: flex; flex-direction: row; align-items: center; - cursor: pointer; + height: 1.5rem; } -.definition-row header .names .icon { +.definition-row header .toggle-zoom { + display: flex; + flex-direction: row; + align-items: center; + height: 1.5rem; +} + +.definition-row header .toggle-zoom:hover { + text-decoration: none; +} +.definition-row header .toggle-zoom:hover .icon.caret-right { + color: var(--color-workspace-item-subtle-fg-em); +} + +.definition-row header .info .toggle-zoom .icon { margin-right: 0.375rem; } -.definition-row header .names .icon.caret-right { +.definition-row header .info .toggle-zoom .icon.caret-right { margin-right: 0.25rem; } -.definition-row header .names:hover .icon.caret-right { - color: var(--color-workspace-item-focus-subtle-fg); +.definition-row header .icon.caret-right:hover { + color: red; } -.definition-row header .names .name { +.definition-row header .info .name { color: var(--color-workspace-item-fg); font-size: var(--font-size-base); transition: color 0.2s; font-weight: bold; } -.definition-row header .names .separator { - color: var(--color-workspace-item-subtle-fg); - margin: 0 0.375rem; - cursor: default; -} - -.definition-row header .names .info { +.definition-row header .info .menu-items { font-size: var(--font-size-small); display: flex; flex-direction: row; - align-self: flex-end; - margin-bottom: 0.1rem; + align-items: flex-end; + color: var(--color-workspace-item-subtle-fg); + height: 1.5rem; + margin-left: 0.25rem; + margin-top: 1px; } -.definition-row header .names .namespace .in { - color: var(--color-workspace-item-subtle-fg-em); +.definition-row header .info .menu-items .icon { + color: var(--color-workspace-item-subtle-fg); + margin-right: 0.25rem; + margin-top: -1px; } -.definition-row header .names .other-names { - height: 1rem; - color: var(--color-workspace-item-subtle-fg-em); - transition: color 0.2s; - cursor: pointer; +.definition-row header .info .menu-items .menu-item { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 0 0.5rem; + margin-right: 0.5rem; + height: 1.5rem; + line-height: 0.875rem; + transition: 0.2s all; + border-radius: var(--border-radius-base); +} + +.definition-row header .info .menu-items .menu-item:hover { + color: var(--color-workspace-item-fg); + background: var(--color-workspace-item-em-bg); } -.definition-row header .names .other-names:hover { +.definition-row header .info .menu-items .menu-item:hover .icon { color: var(--color-workspace-item-fg); } @@ -290,7 +314,7 @@ height: 0; } -.definition-row.zoom-level-medium .names .icon.caret-right { +.definition-row.zoom-level-medium .info .icon.caret-right { transform: rotate(90deg); } diff --git a/tests/FullyQualifiedNameTests.elm b/tests/FullyQualifiedNameTests.elm index 0e74f73..da93128 100644 --- a/tests/FullyQualifiedNameTests.elm +++ b/tests/FullyQualifiedNameTests.elm @@ -196,6 +196,26 @@ namespaceOf = ] +namepsace : Test +namepsace = + describe "FullyQualifiedName.namepsace" + [ test "removes qualified name" <| + \_ -> + let + fqn = + FQN.fromString "base.List.map" + in + Expect.equal (Just (FQN.fromString "base.List")) (FQN.namespace fqn) + , test "with an FQN of only 1 segment, it returns Nothing" <| + \_ -> + let + fqn = + FQN.fromString "map" + in + Expect.equal Nothing (FQN.namespace fqn) + ] + + -- HELPERS