Skip to content

Commit 3a2b23e

Browse files
authored
Escape dollar signs in completion snippets (#4745)
* Escape dollar signs in completion snippets * added Snippet * Added comments
1 parent 4808791 commit 3a2b23e

File tree

2 files changed

+70
-12
lines changed

2 files changed

+70
-12
lines changed

‎ghcide/src/Development/IDE/Plugin/Completions/Logic.hs‎

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import Development.IDE hiding (line)
6969
importDevelopment.IDE.Spans.AtPoint (pointCommand)
7070

7171

72+
importqualifiedDevelopment.IDE.Plugin.Completions.TypesasC
7273
importGHC.Plugins (Depth (AllTheWay),
7374
mkUserStyle,
7475
neverQualify,
@@ -202,7 +203,7 @@ mkCompl
202203
_preselect =Nothing,
203204
_sortText =Nothing,
204205
_filterText =Nothing,
205-
_insertText =Just insertText,
206+
_insertText =Just$ snippetToText insertText,
206207
_insertTextFormat =JustInsertTextFormat_Snippet,
207208
_insertTextMode =Nothing,
208209
_textEdit =Nothing,
@@ -242,10 +243,9 @@ mkNameCompItem doc thingParent origName provenance isInfix !imp mod = CI{..}
242243
isTypeCompl = isTcOcc origName
243244
typeText =Nothing
244245
label = stripOccNamePrefix $ printOutputable origName
245-
insertText =case isInfix of
246+
insertText =snippetText $case isInfix of
246247
Nothing-> label
247248
JustLeftSide-> label <>"`"
248-
249249
JustSurrounded-> label
250250
additionalTextEdits =
251251
imp <&>\x ->
@@ -294,7 +294,7 @@ defaultCompletionItemWithLabel label =
294294
fromIdentInfo::Uri->IdentInfo->MaybeT.Text->CompItem
295295
fromIdentInfo doc identInfo@IdentInfo{..} q =CI
296296
{compKind= occNameToComKind name
297-
, insertText=rend
297+
, insertText= snippetText rend
298298
, provenance =DefinedInmod
299299
, label=rend
300300
, typeText =Nothing
@@ -458,10 +458,11 @@ localCompletionsForParsedModule uri pm@ParsedModule{pm_parsed_source = L _ HsMod
458458
]
459459

460460
mkLocalComp pos n ctyp ty =
461-
CI ctyp pn (Local pos) pn ty Nothing (ctyp `elem` [CompletionItemKind_Struct, CompletionItemKind_Interface]) Nothing (Just$NameDetails (ms_mod $ pm_mod_summary pm) occ) True
461+
CI ctyp sn (Local pos) pn ty Nothing (ctyp `elem` [CompletionItemKind_Struct, CompletionItemKind_Interface]) Nothing (Just$NameDetails (ms_mod $ pm_mod_summary pm) occ) True
462462
where
463463
occ = rdrNameOcc $ unLoc n
464464
pn = showForSnippet n
465+
sn = snippetText pn
465466

466467
findRecordCompl::Uri->Provenance->TyClDeclGhcPs-> [CompItem]
467468
findRecordCompl uri mn DataDecl{tcdLName, tcdDataDefn} = result
@@ -638,7 +639,7 @@ getCompletions
638639
dotFieldSelectorToCompl::T.Text->T.Text-> (Bool, CompItem)
639640
dotFieldSelectorToCompl recname label = (True, CI
640641
{compKind =CompletionItemKind_Field
641-
, insertText = label
642+
, insertText =snippetText label
642643
, provenance =DefinedIn recname
643644
, label = label
644645
, typeText =Nothing
@@ -667,7 +668,7 @@ getCompletions
667668
endLoc = upperRange oldPos
668669
localCompls =map (uncurry localBindsToCompItem) $ getFuzzyScope localBindings startLoc endLoc
669670
localBindsToCompItem::Name->MaybeType->CompItem
670-
localBindsToCompItem name typ =CI ctyp pn thisModName pn ty Nothing (not$ isValOcc occ) Nothing dets True
671+
localBindsToCompItem name typ =CI ctyp (snippetText pn) thisModName pn ty Nothing (not$ isValOcc occ) Nothing dets True
671672
where
672673
occ = nameOccName name
673674
ctyp = occNameToComKind occ
@@ -736,7 +737,8 @@ uniqueCompl candidate unique =
736737
-- filter global completions when we already have a local one
737738
||not(isLocalCompletion candidate) && isLocalCompletion unique
738739
thenEQ
739-
elsecompare (importedFrom candidate, insertText candidate) (importedFrom unique, insertText unique)
740+
elsecompare (importedFrom candidate) (importedFrom unique) <>
741+
snippetLexOrd (insertText candidate) (insertText unique)
740742
other -> other
741743
where
742744
importedFrom::CompItem->T.Text
@@ -805,9 +807,10 @@ mkRecordSnippetCompItem uri parent ctxStr compl importedFrom imp = r
805807
}
806808

807809
placeholder_pairs =zip compl ([1..]::[Int])
808-
snippet_parts =map (\(x, i) -> x <>"=${"<>T.pack (show i) <>":_"<> x <>"}") placeholder_pairs
809-
snippet =T.intercalate (T.pack ", ") snippet_parts
810-
buildSnippet = ctxStr <>"{"<> snippet <>"}"
810+
snippet_parts = placeholder_pairs <&>\(x, i) ->
811+
snippetText x <>"="<> snippetVariableDefault (T.pack $show i) (C.SText$"_"<> x)
812+
snippet =mconcat$ intersperse ", " snippet_parts
813+
buildSnippet = snippetText ctxStr <>"{"<> snippet <>"}"
811814

812815
getImportQual::LImportDeclGhcPs->MaybeT.Text
813816
getImportQual (L _ imp)

‎ghcide/src/Development/IDE/Plugin/Completions/Types.hs‎

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import qualified Data.Text as T
1414

1515
importData.Aeson
1616
importData.Aeson.Types
17+
importData.Function (on)
1718
importData.Hashable (Hashable)
19+
importqualifiedData.ListasL
20+
importData.List.NonEmpty (NonEmpty (..))
21+
importData.String (IsString (..))
1822
importData.Text (Text)
1923
importDevelopment.IDE.GHC.Compat
2024
importDevelopment.IDE.Graph (RuleResult)
@@ -81,9 +85,60 @@ data Provenance
8185
| LocalSrcSpan
8286
deriving (Eq, Ord, Show)
8387

88+
newtypeSnippet=Snippet [SnippetAny]
89+
deriving (Eq, Show)
90+
derivingnewtype (Semigroup, Monoid)
91+
92+
instanceIsStringSnippetwhere
93+
fromString = snippetText .T.pack
94+
95+
--| @SnippetAny@ can be used to construct sanitized snippets. See the LSP
96+
-- spec for more details.
97+
dataSnippetAny
98+
=STextText
99+
--^ Literal text
100+
| STabStopInt (MaybeSnippetAny)
101+
--^ Creates a tab stop, i.e. parts of the snippet that are meant to be
102+
-- filled in by the user and that can be jumped between using the tab key.
103+
-- The optional field can be used to provide a placeholder value.
104+
| SChoiceInt (NonEmptyText)
105+
--^ Presents a choice between the provided values to the user
106+
| SVariableText (MaybeSnippetAny)
107+
--^ Snippet variable. See the spec for possible values. The optional field
108+
-- can be used to provide a default value for when the variable is not set.
109+
deriving (Eq, Show)
110+
111+
snippetText::Text->Snippet
112+
snippetText =Snippet.L.singleton .SText
113+
114+
snippetVariable::Text->Snippet
115+
snippetVariable n =Snippet.L.singleton $SVariable n Nothing
116+
117+
snippetVariableDefault::Text->SnippetAny->Snippet
118+
snippetVariableDefault n d =Snippet.L.singleton .SVariable n $Just d
119+
120+
snippetToText::Snippet->Text
121+
snippetToText (Snippet l) =foldMap (snippetAnyToText False) l
122+
where
123+
snippetAnyToText isNested =\case
124+
SText t -> sanitizeText isNested t
125+
STabStop i ph ->"${"<>T.pack (show i) <>foldMap (\p ->":"<> snippetAnyToText True p) ph <>"}"
126+
SChoice i (c :| cs) ->"${"<>T.pack (show i) <>"|"<> c <>foldMap (","<>) cs <>"}"
127+
SVariable n md ->"${"<> n <>foldMap (\x ->":"<> snippetAnyToText True x) md <>"}"
128+
sanitizeText isNested =T.foldl' (sanitizeChar isNested) mempty
129+
sanitizeChar isNested t = (t <>) .\case
130+
'$'->"\\$"
131+
'\\'->"\\\\"
132+
','| isNested ->"\\,"
133+
'|'| isNested ->"\\|"
134+
c ->T.singleton c
135+
136+
snippetLexOrd::Snippet->Snippet->Ordering
137+
snippetLexOrd =compare`on` snippetToText
138+
84139
dataCompItem=CI
85140
{compKind::CompletionItemKind
86-
, insertText::T.Text--^ Snippet for the completion
141+
, insertText::Snippet--^ Snippet for the completion
87142
, provenance::Provenance--^ From where this item is imported from.
88143
, label::T.Text--^ Label to display to the user.
89144
, typeText::MaybeT.Text

0 commit comments

Comments
(0)