diff --git a/notebooks/Test.ipynb b/notebooks/Test.ipynb
index 49bec2b4..50caafd6 100644
--- a/notebooks/Test.ipynb
+++ b/notebooks/Test.ipynb
@@ -36,7 +36,43 @@
"cell_type": "code",
"collapsed": false,
"input": [
- ":doc map"
+ "getStringTarget :: String -> String\n",
+ "getStringTarget = go \"\" . reverse\n",
+ " where\n",
+ " go acc rest = case rest of\n",
+ " '\"':'\\\\':rem -> go ('\"':acc) rem\n",
+ " '\"':rem -> acc\n",
+ " ' ':'\\\\':rem -> go (' ':acc) rem\n",
+ " ' ':rem -> acc\n",
+ " x:rem -> go (x:acc) rem\n",
+ " [] -> acc\n",
+ "\n",
+ "\" ~/archive/\n",
+ ":load \" ~/archive/"
+ ],
+ "language": "python",
+ "metadata": {
+ "hidden": false
+ },
+ "outputs": [
+ {
+ "html": [
+ "Parse error (line 12, column 13): lexical error in string/character literal at end of input"
+ ],
+ "metadata": {},
+ "output_type": "display_data",
+ "text": [
+ "Parse error (line 12, column 13): lexical error in string/character literal at end of input"
+ ]
+ }
+ ],
+ "prompt_number": 1
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "getStringTarget \"absdf\\\" he\\\\\\\"llo\""
],
"language": "python",
"metadata": {
@@ -45,59 +81,13 @@
"outputs": [
{
"metadata": {},
- "output_type": "display_data"
- },
- {
- "html": [
- "map ∷ (a → b) → [a] → [b](package base, module Prelude)
map f xs is the list obtained by applying f to each element of xs, i.e.,\n",
- "
\n",
- "
\n",
- "
> map f [x1, x2, ..., xn] == [f x1, f x2, ..., f xn]\n",
- "> map f [x1, x2, ...] == [f x1, f x2, ...] \n",
- "
\n",
- "
\n",
- "map ∷ (Char → Char) → ByteString → ByteString(package bytestring, module Data.ByteString.Char8)O(n) map f xs is the ByteString obtained by applying f to each element of xs \n",
- "
\n",
- "
\n",
- "map ∷ (Char → Char) → Text → Text(package text, module Data.Text)O(n) map f t is the Text obtained by applying f to each element of t. Subject to fusion. Performs replacement on invalid scalar values. \n",
- "
\n",
- "
\n",
- "map ∷ (Key → Key) → IntSet → IntSet(package containers, module Data.IntSet)O(n*min(n,W)). map f s is the set obtained by applying f to each element of s.\n",
- "
\n",
- "
\n",
- "
It's worth noting that the size of the result may be smaller if, for some (x,y), x /= y && f x == f y \n",
- "
\n",
- "
\n",
- "map ∷ (Word8 → Word8) → ByteString → ByteString(package bytestring, module Data.ByteString.Lazy)O(n) map f xs is the ByteString obtained by applying f to each element of xs. \n",
- "
\n",
- "
\n",
- "map ∷ (Word8 → Word8) → ByteString → ByteString(package bytestring, module Data.ByteString)O(n) map f xs is the ByteString obtained by applying f to each element of xs. This function is subject to array fusion. \n",
- "
\n",
- "
\n",
- "map ∷ (a → b) → IntMap a → IntMap b(package containers, module Data.IntMap.Strict)O(n). Map a function over all values in the map.\n",
- "
\n",
- "
\n",
- "
> map (++ \"x\") (fromList [(5,\"a\"), (3,\"b\")]) == fromList [(3, \"bx\"), (5, \"ax\")] \n",
- "
\n",
- "
\n",
- "map ∷ (a → b) → Map k a → Map k b(package containers, module Data.Map.Lazy)O(n). Map a function over all values in the map.\n",
- "
\n",
- "
\n",
- "
> map (++ \"x\") (fromList [(5,\"a\"), (3,\"b\")]) == fromList [(3, \"bx\"), (5, \"ax\")] \n",
- "
\n",
- "
\n",
- "map ∷ Ord b ⇒ (a → b) → Set a → Set b(package containers, module Data.Set)O(n*log n). map f s is the set obtained by applying f to each element of s.\n",
- "
\n",
- "
\n",
- "
It's worth noting that the size of the result may be smaller if, for some (x,y), x /= y && f x == f y \n",
- "
\n",
- "
\n"
- ],
- "metadata": {},
- "output_type": "display_data"
+ "output_type": "display_data",
+ "text": [
+ "\"he\\\"llo\""
+ ]
}
],
- "prompt_number": 4
+ "prompt_number": 13
},
{
"cell_type": "code",
diff --git a/src/IHaskell/Eval/Completion.hs b/src/IHaskell/Eval/Completion.hs
index 073a3cfb..49186b4e 100644
--- a/src/IHaskell/Eval/Completion.hs
+++ b/src/IHaskell/Eval/Completion.hs
@@ -69,14 +69,15 @@ complete line pos = do
moduleNames = nub $ concatMap getNames db
let target = completionTarget line pos
+ completion = completionType line pos target
- let matchedText = case completionType line pos target of
+ let matchedText = case completion of
HsFilePath _ match -> match
FilePath _ match -> match
otherwise -> intercalate "." target
options <-
- case completionType line pos target of
+ case completion of
Empty -> return []
Identifier candidate ->
@@ -175,10 +176,18 @@ completionType line loc target
-- If it's empty, no completion.
| null target
= Empty
+
+ -- When in a string, complete filenames.
+ | cursorInString line loc
+ = FilePath (getStringTarget lineUpToCursor) (getStringTarget lineUpToCursor)
+
+ -- Complete module names in imports and elsewhere.
| startswith "import" stripped && isModName
= ModuleName dotted candidate
| isModName && (not . null . init) target
= Qualified dotted candidate
+
+ -- Default to completing identifiers.
| otherwise
= Identifier candidate
where stripped = strip line
@@ -196,6 +205,26 @@ completionType line loc target
else []
Left _ -> Empty
+ cursorInString str loc = nquotes (take loc str) `mod` 2 /= 0
+
+ nquotes ('\\':'"':xs) = nquotes xs
+ nquotes ('"':xs) = 1 + nquotes xs
+ nquotes (_:xs) = nquotes xs
+ nquotes [] = 0
+
+ -- Get the bit of a string that might be a filename completion.
+ -- Logic is a bit convoluted, but basically go backwards from the
+ -- end, stopping at any quote or space, unless they are escaped.
+ getStringTarget :: String -> String
+ getStringTarget = go "" . reverse
+ where
+ go acc rest = case rest of
+ '"':'\\':rem -> go ('"':acc) rem
+ '"':rem -> acc
+ ' ':'\\':rem -> go (' ':acc) rem
+ ' ':rem -> acc
+ x:rem -> go (x:acc) rem
+ [] -> acc
-- | Get the word under a given cursor location.
completionTarget :: String -> Int -> [String]