-
-
Notifications
You must be signed in to change notification settings - Fork 399
unescape printable characters #3140
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
Changes from 6 commits
d225651
dcf0d6a
bc9ff9d
3e45c23
ae498b6
1bd5305
db0a19b
504c302
ac5ea88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ module Ide.PluginUtils | |
handleMaybe, | ||
handleMaybeM, | ||
throwPluginError, | ||
unescape, | ||
) | ||
where | ||
|
||
|
@@ -43,10 +44,11 @@ import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE) | |
import Data.Algorithm.Diff | ||
import Data.Algorithm.DiffOutput | ||
import Data.Bifunctor (Bifunctor (first)) | ||
import Data.Char (isPrint, showLitChar) | ||
import qualified Data.HashMap.Strict as H | ||
import Data.List (find) | ||
import Data.String (IsString (fromString)) | ||
import qualified Data.Text as T | ||
import Data.Void (Void) | ||
import Ide.Plugin.Config | ||
import Ide.Plugin.Properties | ||
import Ide.Types | ||
|
@@ -57,6 +59,9 @@ import Language.LSP.Types hiding | |
SemanticTokensEdit (_start)) | ||
import qualified Language.LSP.Types as J | ||
import Language.LSP.Types.Capabilities | ||
import qualified Text.Megaparsec as P | ||
import qualified Text.Megaparsec.Char as P | ||
import qualified Text.Megaparsec.Char.Lexer as P | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
|
@@ -255,3 +260,35 @@ pluginResponse :: Monad m => ExceptT String m a -> m (Either ResponseError a) | |
pluginResponse = | ||
fmap (first (\msg -> ResponseError InternalError (fromString msg) Nothing)) | ||
. runExceptT | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
type TextParser = P.Parsec Void T.Text | ||
|
||
-- | Unescape printable escape sequences within double quotes. | ||
-- This is useful if you have to call 'show' indirectly, and it escapes some characters which you would prefer to | ||
-- display as is. | ||
unescape :: T.Text -> T.Text | ||
unescape input = | ||
case P.runParser escapedTextParser "inline" input of | ||
Left _ -> input | ||
Right strs -> T.pack strs | ||
|
||
-- | Parser for a string that contains double quotes. Returns unescaped string. | ||
escapedTextParser :: TextParser String | ||
escapedTextParser = do | ||
xs <- P.many (P.try stringLiteral) | ||
x <- P.manyTill P.anySingle P.eof -- consume characters after the final double quote | ||
pure $ concat xs ++ x | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels a little weird that you handle the content before the opening quote in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I rewrote |
||
where | ||
stringLiteral :: TextParser String | ||
stringLiteral = do | ||
before <- P.manyTill P.anySingle (P.char '"') -- include any character before the first double quote | ||
inside <- P.manyTill P.charLiteral (P.char '"') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, from the tests it looks like you made this work but I'm not sure why it works... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
let f '"' = "\\\"" -- double quote should still be escaped | ||
-- Despite the docs, 'showLitChar' and 'showLitString' from 'Data.Char' DOES ESCAPE unicode printable | ||
-- characters. So we need to call 'isPrint' from 'Data.Char' manually. | ||
f ch = if isPrint ch then [ch] else showLitChar ch "" | ||
inside' = concatMap f inside | ||
|
||
pure $ before <> "\"" <> inside' <> "\"" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,35 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
|
||
module Ide.PluginUtilsTest | ||
( tests | ||
) where | ||
|
||
import Ide.PluginUtils (positionInRange) | ||
import Data.Char (isPrint) | ||
import qualified Data.Text as T | ||
import Ide.PluginUtils (positionInRange, unescape) | ||
import Language.LSP.Types (Position (Position), Range (Range)) | ||
import Test.Tasty | ||
import Test.Tasty.HUnit | ||
|
||
tests :: TestTree | ||
tests = testGroup "PluginUtils" | ||
[ | ||
[ unescapeTest | ||
] | ||
|
||
unescapeTest :: TestTree | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 |
||
unescapeTest = testGroup "unescape" | ||
[ testCase "no double quote" $ | ||
unescape "hello世界" @?= "hello世界" | ||
, testCase "whole string quoted" $ | ||
unescape "\"hello\\19990\\30028\"" @?= "\"hello世界\"" | ||
, testCase "text before quotes should not be unescaped" $ | ||
unescape "\\19990a\"hello\\30028\"" @?= "\\19990a\"hello界\"" | ||
, testCase "some text after quotes" $ | ||
unescape "\"hello\\19990\\30028\"abc" @?= "\"hello世界\"abc" | ||
, testCase "many pairs of quote" $ | ||
unescape "oo\"hello\\19990\\30028\"abc\"\1087\1088\1080\1074\1077\1090\"hh" @?= "oo\"hello世界\"abc\"привет\"hh" | ||
, testCase "double quote itself should not be unescaped" $ | ||
unescape "\"\\\"o\"" @?= "\"\\\"o\"" | ||
, testCase "control characters should not be escaped" $ | ||
unescape "\"\\n\\t\"" @?= "\"\\n\\t\"" | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe also include the fact that it unescapes characters? That's quite an important difference from other outputable functions. In fact... maybe we should use hlint to ban use of other outputable functions apart from this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
TBH I don't know if this is configurable. I think the scheme is to use
printOutputable
by default, and also allow other print functions to be used when needed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, we can use
hlint
to ban functions, so we could do it. I don't know if it's a good idea, though.