1
1
mirror of https://github.com/github/semantic.git synced 2024-12-01 09:15:01 +03:00

Merge remote-tracking branch 'origin/master' into hold-on-to-your-butts

This commit is contained in:
Rob Rix 2017-02-14 14:16:19 -05:00
commit 80bb79f73c
23 changed files with 218 additions and 194 deletions

View File

@ -20,7 +20,7 @@ import Diff
import Info
import Patch
import Range
import Source hiding (break, fromList, uncons)
import Source hiding (break, fromList)
import SplitDiff
import Syntax
import Term
@ -38,11 +38,11 @@ hasChanges :: (Foldable f, Functor f) => SplitDiff f annotation -> Bool
hasChanges = or . (True <$)
-- | Align a Diff into a list of Join These SplitDiffs representing the (possibly blank) lines on either side.
alignDiff :: HasField fields Range => Both (Source Char) -> SyntaxDiff leaf fields -> [Join These (SplitSyntaxDiff leaf fields)]
alignDiff :: HasField fields Range => Both Source -> SyntaxDiff leaf fields -> [Join These (SplitSyntaxDiff leaf fields)]
alignDiff sources diff = iter (alignSyntax (runBothWith ((Join .) . These)) wrap getRange sources) (alignPatch sources <$> diff)
-- | Align the contents of a patch into a list of lines on the corresponding side(s) of the diff.
alignPatch :: forall fields leaf. HasField fields Range => Both (Source Char) -> Patch (SyntaxTerm leaf fields) -> [Join These (SplitSyntaxDiff leaf fields)]
alignPatch :: forall fields leaf. HasField fields Range => Both Source -> Patch (SyntaxTerm leaf fields) -> [Join These (SplitSyntaxDiff leaf fields)]
alignPatch sources patch = case patch of
Delete term -> fmap (pure . SplitDelete) <$> alignSyntax' this (fst sources) term
Insert term -> fmap (pure . SplitInsert) <$> alignSyntax' that (snd sources) term
@ -50,13 +50,13 @@ alignPatch sources patch = case patch of
(alignSyntax' this (fst sources) term1)
(alignSyntax' that (snd sources) term2)
where getRange = characterRange . extract
alignSyntax' :: (forall a. Identity a -> Join These a) -> Source Char -> SyntaxTerm leaf fields -> [Join These (SyntaxTerm leaf fields)]
alignSyntax' :: (forall a. Identity a -> Join These a) -> Source -> SyntaxTerm leaf fields -> [Join These (SyntaxTerm leaf fields)]
alignSyntax' side source term = hylo (alignSyntax side cofree getRange (Identity source)) runCofree (Identity <$> term)
this = Join . This . runIdentity
that = Join . That . runIdentity
-- | The Applicative instance f is either Identity or Both. Identity is for Terms in Patches, Both is for Diffs in unchanged portions of the diff.
alignSyntax :: (Applicative f, HasField fields Range) => (forall a. f a -> Join These a) -> (SyntaxTermF leaf fields term -> term) -> (term -> Range) -> f (Source Char) -> TermF (Syntax leaf) (f (Record fields)) [Join These term] -> [Join These term]
alignSyntax :: (Applicative f, HasField fields Range) => (forall a. f a -> Join These a) -> (SyntaxTermF leaf fields term -> term) -> (term -> Range) -> f Source -> TermF (Syntax leaf) (f (Record fields)) [Join These term] -> [Join These term]
alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = catMaybes $ case syntax of
Leaf s -> wrapInBranch (const (Leaf s)) <$> alignBranch getRange [] bothRanges
Syntax.Comment a -> wrapInBranch (const (Syntax.Comment a)) <$> alignBranch getRange [] bothRanges

View File

@ -284,10 +284,10 @@ pqGramDecorator getLabel p q = cata algebra
-- | Computes a unit vector of the specified dimension from a hash.
unitVector :: Int -> Int -> FeatureVector
unitVector d hash = fmap (/ magnitude) uniform
unitVector d hash = fmap (* invMagnitude) uniform
where
uniform = listArray (0, d - 1) (evalRand components (pureMT (fromIntegral hash)))
magnitude = sqrtDouble (sum (fmap (** 2) uniform))
invMagnitude = 1 / sqrtDouble (sum (fmap (** 2) uniform))
components = sequenceA (replicate d (liftRand randomDouble))
-- | Strips the head annotation off a term annotated with non-empty records.

View File

@ -106,7 +106,7 @@ summaryToTexts DiffSummary{..} = appendParentContexts <$> summaries patch
jsonSummary { summary = show $ summary jsonSummary <+> parentContexts parentAnnotation }
-- Returns a list of 'DiffSummary' given two source blobs and a diff.
diffToDiffSummaries :: (StringConv leaf Text, DefaultFields fields) => Both (Source Char) -> SyntaxDiff leaf fields -> [DiffSummary DiffInfo]
diffToDiffSummaries :: (StringConv leaf Text, DefaultFields fields) => Both Source -> SyntaxDiff leaf fields -> [DiffSummary DiffInfo]
diffToDiffSummaries sources = para $ \diff ->
let
diff' = free (Prologue.fst <$> diff)
@ -186,7 +186,7 @@ toLeafInfos LeafInfo{..} = pure $ JSONSummary (summary leafCategory termName) so
vowels = Text.singleton <$> ("aeiouAEIOU" :: [Char])
-- Returns a text representing a specific term given a source and a term.
toTermName :: forall leaf fields. (StringConv leaf Text, DefaultFields fields) => Source Char -> SyntaxTerm leaf fields -> Text
toTermName :: forall leaf fields. (StringConv leaf Text, DefaultFields fields) => Source -> SyntaxTerm leaf fields -> Text
toTermName source term = case unwrap term of
S.Send _ _ -> termNameFromSource term
S.Ty _ -> termNameFromSource term
@ -323,7 +323,7 @@ parentContexts contexts = hsep $ either identifiableDoc annotatableDoc <$> conte
toDoc :: Text -> Doc
toDoc = string . toS
termToDiffInfo :: (StringConv leaf Text, DefaultFields fields) => Source Char -> SyntaxTerm leaf fields -> DiffInfo
termToDiffInfo :: (StringConv leaf Text, DefaultFields fields) => Source -> SyntaxTerm leaf fields -> DiffInfo
termToDiffInfo blob term = case unwrap term of
S.Indexed children -> BranchInfo (termToDiffInfo' <$> children) (category $ extract term) BIndexed
S.Fixed children -> BranchInfo (termToDiffInfo' <$> children) (category $ extract term) BFixed
@ -340,7 +340,7 @@ termToDiffInfo blob term = case unwrap term of
-- | For a DiffSummary without a parentAnnotation, we append a parentAnnotation with the first identifiable term.
-- | For a DiffSummary with a parentAnnotation, we append the next annotatable term to the extant parentAnnotation.
-- | If a DiffSummary already has a parentAnnotation, and a (grand) parentAnnotation, then we return the summary without modification.
appendSummary :: (StringConv leaf Text, DefaultFields fields) => Source Char -> SyntaxTerm leaf fields -> DiffSummary DiffInfo -> DiffSummary DiffInfo
appendSummary :: (StringConv leaf Text, DefaultFields fields) => Source -> SyntaxTerm leaf fields -> DiffSummary DiffInfo -> DiffSummary DiffInfo
appendSummary source term summary =
case (parentAnnotation summary, identifiable term, annotatable term) of
([], Identifiable _, _) -> appendParentAnnotation Left

View File

@ -50,7 +50,7 @@ diffFiles parse render sourceBlobs = do
(_, _) ->
runBothWith (diffTerms construct compareCategoryEq diffCostWithCachedTermCosts) terms
areNullOids a b = (hasNullOid a, hasNullOid b)
hasNullOid blob = oid blob == nullOid || null (source blob)
hasNullOid blob = oid blob == nullOid || Source.null (source blob)
construct (info :< syntax) = free (Free ((setCost <$> info <*> sumCost syntax) :< syntax))
sumCost = fmap getSum . foldMap (fmap Sum . getCost)
getCost diff = case runFree diff of

View File

@ -8,7 +8,7 @@ import qualified Syntax as S
import Term
termAssignment
:: Source Char -- ^ The source of the term.
:: Source -- ^ The source of the term.
-> Category -- ^ The category for the term.
-> [ SyntaxTerm Text '[Range, Category, SourceSpan] ] -- ^ The child nodes of the term.
-> Maybe (S.Syntax Text (SyntaxTerm Text '[Range, Category, SourceSpan])) -- ^ The resulting term, in Maybe.

View File

@ -8,7 +8,7 @@ import Term
import qualified Syntax as S
termAssignment
:: Source Char -- ^ The source of the term.
:: Source -- ^ The source of the term.
-> Category -- ^ The category for the term.
-> [ SyntaxTerm Text '[Range, Category, SourceSpan] ] -- ^ The child nodes of the term.
-> Maybe (S.Syntax Text (SyntaxTerm Text '[Range, Category, SourceSpan])) -- ^ The resulting term, in Maybe.

View File

@ -9,7 +9,7 @@ import qualified Syntax as S
import Term
termAssignment
:: Source Char -- ^ The source of the term.
:: Source -- ^ The source of the term.
-> Category -- ^ The category for the term.
-> [ SyntaxTerm Text '[Range, Category, SourceSpan] ] -- ^ The child nodes of the term.
-> Maybe (S.Syntax Text (SyntaxTerm Text '[Range, Category, SourceSpan])) -- ^ The resulting term, in Maybe.

View File

@ -7,7 +7,6 @@ import Data.Text
import Info
import Parser
import Prologue
import Range
import Source
import Syntax

View File

@ -4,13 +4,13 @@ module Language.Ruby where
import Data.List (partition)
import Info
import Prologue
import Source
import Source hiding (null)
import Language
import qualified Syntax as S
import Term
termAssignment
:: Source Char -- ^ The source of the term.
:: Source -- ^ The source of the term.
-> Category -- ^ The category for the term.
-> [ SyntaxTerm Text '[Range, Category, SourceSpan] ] -- ^ The child nodes of the term.
-> Maybe (S.Syntax Text (SyntaxTerm Text '[Range, Category, SourceSpan])) -- ^ The resulting term, in Maybe.

View File

@ -5,7 +5,7 @@ import Arguments
import Category
import Data.Aeson (ToJSON)
import Data.Aeson.Encode.Pretty
import qualified Data.ByteString.Char8 as B1
import qualified Data.ByteString as B1
import qualified Data.Text.ICU.Convert as Convert
import qualified Data.Text.ICU.Detect as Detect
import Data.Record
@ -94,7 +94,7 @@ termCostDecorator :: (Foldable f, Functor f) => TermDecorator f a Cost
termCostDecorator c = 1 + sum (cost <$> tailF c)
-- | Term decorator extracting the source text for a term.
termSourceDecorator :: (HasField fields Range) => Source Char -> TermDecorator f fields SourceText
termSourceDecorator :: (HasField fields Range) => Source -> TermDecorator f fields SourceText
termSourceDecorator source c = SourceText . toText $ Source.slice range' source
where range' = characterRange $ headF c
@ -104,24 +104,24 @@ lineByLineParser SourceBlob{..} = pure . cofree . root $ case foldl' annotateLea
(leaves, _) -> cofree <$> leaves
where
lines = actualLines source
root children = (Range 0 (length source) :. Program :. rangeToSourceSpan source (Range 0 (length source)) :. Nil) :< Indexed children
root children = (sourceRange :. Program :. rangeToSourceSpan source sourceRange :. Nil) :< Indexed children
sourceRange = Source.totalRange source
leaf charIndex line = (Range charIndex (charIndex + T.length line) :. Program :. rangeToSourceSpan source (Range charIndex (charIndex + T.length line)) :. Nil) :< Leaf line
annotateLeaves (accum, charIndex) line =
(accum <> [ leaf charIndex (toText line) ] , charIndex + length line)
toText = T.pack . Source.toString
(accum <> [ leaf charIndex (Source.toText line) ] , charIndex + Source.length line)
-- | Return the parser that should be used for a given path.
parserForFilepath :: FilePath -> Parser (Syntax Text) (Record '[Cost, Range, Category, SourceSpan])
parserForFilepath path blob = decorateTerm termCostDecorator <$> parserForType (toS (takeExtension path)) blob
-- | Read the file and convert it to Unicode.
readAndTranscodeFile :: FilePath -> IO (Source Char)
readAndTranscodeFile :: FilePath -> IO Source
readAndTranscodeFile path = do
text <- B1.readFile path
transcode text
-- | Transcode a file to a unicode source.
transcode :: B1.ByteString -> IO (Source Char)
transcode :: B1.ByteString -> IO Source
transcode text = fromText <$> do
match <- Detect.detectCharset text
converter <- Convert.open match Nothing

View File

@ -20,9 +20,16 @@ rangeAt a = Range a a
rangeLength :: Range -> Int
rangeLength range = end range - start range
-- | Return a range that covers the entire text.
totalRange :: Foldable f => f a -> Range
totalRange t = Range 0 $ length t
-- | Offset a range by a constant delta.
offsetRange :: Range -> Int -> Range
offsetRange a b = Range (start a + b) (end a + b)
-- | Divide a range in two at the given coordinate.
--
-- Passing a coordinate that does not lie between start and end will result in one of the ranges being empty.
divideRange :: Range -> Int -> (Range, Range)
divideRange Range{..} at = (Range start divider, Range divider end)
where divider = max (min end at) start
-- | Break a string down into words and sequences of punctuation. Return a list
-- | strings with ranges, assuming that the first character in the string is

View File

@ -10,27 +10,27 @@ import Data.Bifunctor.Join
import Data.Functor.Both as Both
import Data.List (span, unzip)
import Data.Record
import Data.String
import Data.Text (pack)
import qualified Data.Text as Text
import Data.These
import Diff
import Patch
import Prologue hiding (fst, snd)
import Range
import Renderer
import Source hiding (break)
import qualified Source
import Source hiding (break, length, null)
import SplitDiff
-- | Render a timed out file as a truncated diff.
truncatePatch :: DiffArguments -> Both SourceBlob -> Text
truncatePatch _ blobs = pack $ header blobs <> "#timed_out\nTruncating diff: timeout reached.\n"
truncatePatch _ blobs = header blobs <> "#timed_out\nTruncating diff: timeout reached.\n"
-- | Render a diff in the traditional patch format.
patch :: HasField fields Range => Renderer (Record fields)
patch blobs diff = PatchOutput . pack $ case getLast (foldMap (Last . Just) string) of
Just c | c /= '\n' -> string <> "\n\\ No newline at end of file\n"
_ -> string
where string = header blobs <> mconcat (showHunk blobs <$> hunks diff blobs)
patch blobs diff = PatchOutput $ if not (Text.null text) && Text.last text /= '\n'
then text <> "\n\\ No newline at end of file\n"
else text
where text = header blobs <> mconcat (showHunk blobs <$> hunks diff blobs)
-- | A hunk in a patch, including the offset, changes, and context.
data Hunk a = Hunk { offset :: Both (Sum Int), changes :: [Change a], trailingContext :: [Join These a] }
@ -53,9 +53,9 @@ rowIncrement :: Join These a -> Both (Sum Int)
rowIncrement = Join . fromThese (Sum 0) (Sum 0) . runJoin . (Sum 1 <$)
-- | Given the before and after sources, render a hunk to a string.
showHunk :: Functor f => HasField fields Range => Both SourceBlob -> Hunk (SplitDiff f (Record fields)) -> String
showHunk :: Functor f => HasField fields Range => Both SourceBlob -> Hunk (SplitDiff f (Record fields)) -> Text
showHunk blobs hunk = maybeOffsetHeader <>
concat (showChange sources <$> changes hunk) <>
mconcat (showChange sources <$> changes hunk) <>
showLines (snd sources) ' ' (maybeSnd . runJoin <$> trailingContext hunk)
where sources = source <$> blobs
maybeOffsetHeader = if lengthA > 0 && lengthB > 0
@ -66,45 +66,45 @@ showHunk blobs hunk = maybeOffsetHeader <>
(offsetA, offsetB) = runJoin . fmap (show . getSum) $ offset hunk
-- | Given the before and after sources, render a change to a string.
showChange :: Functor f => HasField fields Range => Both (Source Char) -> Change (SplitDiff f (Record fields)) -> String
showChange :: Functor f => HasField fields Range => Both Source -> Change (SplitDiff f (Record fields)) -> Text
showChange sources change = showLines (snd sources) ' ' (maybeSnd . runJoin <$> context change) <> deleted <> inserted
where (deleted, inserted) = runJoin $ pure showLines <*> sources <*> both '-' '+' <*> Join (unzip (fromThese Nothing Nothing . runJoin . fmap Just <$> contents change))
-- | Given a source, render a set of lines to a string with a prefix.
showLines :: Functor f => HasField fields Range => Source Char -> Char -> [Maybe (SplitDiff f (Record fields))] -> String
showLines :: Functor f => HasField fields Range => Source -> Char -> [Maybe (SplitDiff f (Record fields))] -> Text
showLines source prefix lines = fromMaybe "" . mconcat $ fmap prepend . showLine source <$> lines
where prepend "" = ""
prepend source = prefix : source
prepend source = Text.singleton prefix <> source
-- | Given a source, render a line to a string.
showLine :: Functor f => HasField fields Range => Source Char -> Maybe (SplitDiff f (Record fields)) -> Maybe String
showLine source line | Just line <- line = Just . toString . (`slice` source) $ getRange line
showLine :: Functor f => HasField fields Range => Source -> Maybe (SplitDiff f (Record fields)) -> Maybe Text
showLine source line | Just line <- line = Just . toText . (`slice` source) $ getRange line
| otherwise = Nothing
-- | Returns the header given two source blobs and a hunk.
header :: Both SourceBlob -> String
header blobs = intercalate "\n" ([filepathHeader, fileModeHeader] <> maybeFilepaths) <> "\n"
header :: Both SourceBlob -> Text
header blobs = Text.intercalate "\n" ([filepathHeader, fileModeHeader] <> maybeFilepaths) <> "\n"
where filepathHeader = "diff --git a/" <> pathA <> " b/" <> pathB
fileModeHeader = case (modeA, modeB) of
(Nothing, Just mode) -> intercalate "\n" [ "new file mode " <> modeToDigits mode, blobOidHeader ]
(Just mode, Nothing) -> intercalate "\n" [ "deleted file mode " <> modeToDigits mode, blobOidHeader ]
(Nothing, Just mode) -> Text.intercalate "\n" [ "new file mode " <> modeToDigits mode, blobOidHeader ]
(Just mode, Nothing) -> Text.intercalate "\n" [ "deleted file mode " <> modeToDigits mode, blobOidHeader ]
(Just mode, Just other) | mode == other -> "index " <> oidA <> ".." <> oidB <> " " <> modeToDigits mode
(Just mode1, Just mode2) -> intercalate "\n" [
(Just mode1, Just mode2) -> Text.intercalate "\n" [
"old mode " <> modeToDigits mode1,
"new mode " <> modeToDigits mode2,
blobOidHeader
]
(Nothing, Nothing) -> ""
blobOidHeader = "index " <> oidA <> ".." <> oidB
modeHeader :: String -> Maybe SourceKind -> String -> String
modeHeader :: Text -> Maybe SourceKind -> Text -> Text
modeHeader ty maybeMode path = case maybeMode of
Just _ -> ty <> "/" <> path
Nothing -> "/dev/null"
maybeFilepaths = if (nullOid == oidA && null (snd sources)) || (nullOid == oidB && null (fst sources)) then [] else [ beforeFilepath, afterFilepath ]
maybeFilepaths = if (nullOid == oidA && Source.null (snd sources)) || (nullOid == oidB && Source.null (fst sources)) then [] else [ beforeFilepath, afterFilepath ]
beforeFilepath = "--- " <> modeHeader "a" modeA pathA
afterFilepath = "+++ " <> modeHeader "b" modeB pathB
sources = source <$> blobs
(pathA, pathB) = case runJoin $ path <$> blobs of
(pathA, pathB) = case runJoin $ toS . path <$> blobs of
("", path) -> (path, path)
(path, "") -> (path, path)
paths -> paths
@ -119,7 +119,7 @@ emptyHunk = Hunk { offset = mempty, changes = [], trailingContext = [] }
hunks :: HasField fields Range => SyntaxDiff leaf fields -> Both SourceBlob -> [Hunk (SplitSyntaxDiff leaf fields)]
hunks _ blobs | sources <- source <$> blobs
, sourcesEqual <- runBothWith (==) sources
, sourcesNull <- runBothWith (&&) (null <$> sources)
, sourcesNull <- runBothWith (&&) (Source.null <$> sources)
, sourcesEqual || sourcesNull
= [emptyHunk]
hunks diff blobs = hunksInRows (pure 1) $ alignDiff (source <$> blobs) diff

View File

@ -192,13 +192,13 @@ split blobs diff = SplitOutput . TL.toStrict . renderHtml
data Cell a = Cell !Bool !Int !a
-- | Something that can be rendered as markup with reference to some source.
data Renderable a = Renderable !(Source Char) !a
data Renderable a = Renderable !Source !a
contentElements :: (Foldable t, ToMarkup f) => Source Char -> Range -> t (f, Range) -> [Markup]
contentElements :: (Foldable t, ToMarkup f) => Source -> Range -> t (f, Range) -> [Markup]
contentElements source range children = let (elements, next) = foldr' (markupForContextAndChild source) ([], end range) children in
text (toText (slice (Range (start range) (max next (start range))) source)) : elements
markupForContextAndChild :: ToMarkup f => Source Char -> (f, Range) -> ([Markup], Int) -> ([Markup], Int)
markupForContextAndChild :: ToMarkup f => Source -> (f, Range) -> ([Markup], Int) -> ([Markup], Int)
markupForContextAndChild source (child, range) (rows, next) = (toMarkup child : text (toText (slice (Range (end range) next) source)) : rows, start range)
wrapIn :: (Markup -> Markup) -> Markup -> Markup
@ -213,7 +213,7 @@ wrapIn f p = f p
instance (ToMarkup f, HasField fields Category, HasField fields Range) => ToMarkup (Renderable (SyntaxTermF leaf fields (f, Range))) where
toMarkup (Renderable source (info :< syntax)) = classifyMarkup (category info) $ case syntax of
Leaf _ -> span . string . toString $ slice (characterRange info) source
Leaf _ -> span . text . toText $ slice (characterRange info) source
_ -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) (toList syntax)
instance (HasField fields Category, HasField fields Range) => ToMarkup (Renderable (SyntaxTerm leaf fields)) where

View File

@ -6,7 +6,7 @@ import Renderer
import Data.Record
import DiffSummary
import Data.Map as Map hiding (null)
import Source
import Source hiding (null)
import Data.Aeson
import Data.List as List

View File

@ -9,10 +9,11 @@ import Data.Record
import Diff
import Info
import Prologue
import Range
import qualified Data.List as List
import qualified Data.Map as Map hiding (null)
import Renderer
import Source
import Source hiding (null)
import Syntax as S
import Term
import Patch
@ -71,7 +72,7 @@ diffTOC blobs diff = do
removeDupes [] = []
removeDupes xs = (fmap unsafeHead . List.groupBy (\a b -> parentInfo a == parentInfo b)) xs
diffToTOCSummaries :: (StringConv leaf Text, DefaultFields fields) => Both (Source Char) -> SyntaxDiff leaf fields -> [TOCSummary DiffInfo]
diffToTOCSummaries :: (StringConv leaf Text, DefaultFields fields) => Both Source -> SyntaxDiff leaf fields -> [TOCSummary DiffInfo]
diffToTOCSummaries sources = para $ \diff ->
let
diff' = free (Prologue.fst <$> diff)
@ -105,17 +106,17 @@ toLeafInfos' :: DiffInfo -> [DiffInfo]
toLeafInfos' BranchInfo{..} = branches >>= toLeafInfos'
toLeafInfos' leaf = [leaf]
mapToInSummarizable :: DefaultFields fields => Both (Source Char) -> SyntaxDiff leaf fields -> [TOCSummary DiffInfo] -> [TOCSummary DiffInfo]
mapToInSummarizable :: forall leaf fields. DefaultFields fields => Both Source -> SyntaxDiff leaf fields -> [TOCSummary DiffInfo] -> [TOCSummary DiffInfo]
mapToInSummarizable sources diff children = case (beforeTerm diff, afterTerm diff) of
(_, Just diff') -> mapToInSummarizable' (Both.snd sources) diff' <$> children
(Just diff', _) -> mapToInSummarizable' (Both.fst sources) diff' <$> children
(Nothing, Nothing) -> []
where
mapToInSummarizable' :: DefaultFields fields => Source Char -> SyntaxTerm leaf fields -> TOCSummary DiffInfo -> TOCSummary DiffInfo
mapToInSummarizable' :: Source -> SyntaxTerm leaf fields -> TOCSummary DiffInfo -> TOCSummary DiffInfo
mapToInSummarizable' source term summary =
case (parentInfo summary, summarizable term) of
(NotSummarizable, SummarizableTerm _) ->
summary { parentInfo = InSummarizable (category (extract term)) (toTermName source term) (Info.sourceSpan (extract term)) }
summary { parentInfo = InSummarizable (category (extract term)) (toTermName 0 source term) (Info.sourceSpan (extract term)) }
(_, _) -> summary
summarizable :: ComonadCofree (Syntax t) w => w a -> SummarizableTerm (w a)
@ -137,26 +138,28 @@ toJSONSummaries TOCSummary{..} = case afterOrBefore summaryPatch of
NotSummarizable -> []
_ -> pure $ JSONSummary parentInfo
termToDiffInfo :: (StringConv leaf Text, DefaultFields fields) => Source Char -> SyntaxTerm leaf fields -> DiffInfo
termToDiffInfo blob term = case unwrap term of
termToDiffInfo :: forall leaf fields. (StringConv leaf Text, DefaultFields fields) => Source -> SyntaxTerm leaf fields -> DiffInfo
termToDiffInfo source term = case unwrap term of
S.Indexed children -> BranchInfo (termToDiffInfo' <$> children) (category $ extract term)
S.Fixed children -> BranchInfo (termToDiffInfo' <$> children) (category $ extract term)
S.AnonymousFunction _ _ -> LeafInfo C.AnonymousFunction (toTermName' term) (getField $ extract term)
S.Commented cs leaf -> BranchInfo (termToDiffInfo' <$> cs <> maybeToList leaf) (category $ extract term)
S.ParseError _ -> ErrorInfo (getField $ extract term) (toTermName' term)
_ -> toLeafInfo term
where toTermName' = toTermName blob
termToDiffInfo' = termToDiffInfo blob
toLeafInfo term = LeafInfo (category $ extract term) (toTermName' term) (getField $ extract term)
where
toTermName' = toTermName 0 source
termToDiffInfo' = termToDiffInfo source
toLeafInfo term = LeafInfo (category $ extract term) (toTermName' term) (getField $ extract term)
toTermName :: forall leaf fields. DefaultFields fields => Source Char -> SyntaxTerm leaf fields -> Text
toTermName source term = case unwrap term of
toTermName :: forall leaf fields. DefaultFields fields => Int -> Source -> SyntaxTerm leaf fields -> Text
toTermName parentOffset parentSource term = case unwrap term of
S.Function identifier _ _ _ -> toTermName' identifier
S.Method identifier Nothing _ _ _ -> toTermName' identifier
S.Method identifier (Just receiver) _ _ _ -> toTermName' receiver <> "." <> toTermName' identifier
_ -> termNameFromSource term
_ -> toText source
where
toTermName' = toTermName source
termNameFromSource term = termNameFromRange (range term)
termNameFromRange range = toText $ Source.slice range source
source = Source.slice (offsetRange (range term) (negate parentOffset)) parentSource
offset = start (range term)
toTermName' :: SyntaxTerm leaf fields -> Text
toTermName' = toTermName offset source
range = characterRange . extract

View File

@ -161,7 +161,7 @@ getSourceBlob path sha = do
Nothing -> pure (mempty, mempty, Nothing)
Just (BlobEntry entryOid entryKind) -> do
blob <- lookupBlob entryOid
let (BlobString s) = blobContents blob
s <- blobToByteString blob
let oid = renderObjOid $ blobOid blob
pure (s, oid, Just entryKind)
s <- liftIO $ transcode bytestring

View File

@ -2,30 +2,30 @@
{-# OPTIONS_GHC -funbox-strict-fields #-}
module Source where
import Prologue hiding (uncons)
import Data.Text (unpack, pack)
import Data.String
import qualified Data.Vector as Vector
import Prologue
import qualified Data.Text as Text
import Data.Text.Listable
import Numeric
import Range
import SourceSpan
import Test.LeanCheck
-- | The source, oid, path, and Maybe SourceKind of a blob in a Git repo.
data SourceBlob = SourceBlob { source :: Source Char, oid :: String, path :: FilePath, blobKind :: Maybe SourceKind }
data SourceBlob = SourceBlob { source :: Source, oid :: Text, path :: FilePath, blobKind :: Maybe SourceKind }
deriving (Show, Eq)
-- | The contents of a source file, backed by a vector for efficient slicing.
newtype Source a = Source { getVector :: Vector.Vector a }
deriving (Eq, Show, Foldable, Functor, Traversable)
-- | The contents of a source file, represented as Text.
newtype Source = Source { sourceText :: Text }
deriving (Eq, Show)
-- | The kind of a blob, along with it's file mode.
data SourceKind = PlainBlob Word32 | ExecutableBlob Word32 | SymlinkBlob Word32
deriving (Show, Eq)
modeToDigits :: SourceKind -> String
modeToDigits (PlainBlob mode) = showOct mode ""
modeToDigits (ExecutableBlob mode) = showOct mode ""
modeToDigits (SymlinkBlob mode) = showOct mode ""
modeToDigits :: SourceKind -> Text
modeToDigits (PlainBlob mode) = toS $ showOct mode ""
modeToDigits (ExecutableBlob mode) = toS $ showOct mode ""
modeToDigits (SymlinkBlob mode) = toS $ showOct mode ""
-- | The default plain blob mode
@ -35,7 +35,7 @@ defaultPlainBlob = PlainBlob 0o100644
emptySourceBlob :: FilePath -> SourceBlob
emptySourceBlob filepath = SourceBlob (Source.fromList "") Source.nullOid filepath Nothing
sourceBlob :: Source Char -> FilePath -> SourceBlob
sourceBlob :: Source -> FilePath -> SourceBlob
sourceBlob source filepath = SourceBlob source Source.nullOid filepath (Just defaultPlainBlob)
-- | Map blobs with Nothing blobKind to empty blobs.
@ -44,74 +44,82 @@ idOrEmptySourceBlob blob = if isNothing (blobKind blob)
then blob { oid = nullOid, blobKind = Nothing }
else blob
nullOid :: String
nullOid :: Text
nullOid = "0000000000000000000000000000000000000000"
-- | Return a Source from a list of items.
fromList :: [a] -> Source a
fromList = Source . Vector.fromList
fromList :: [Char] -> Source
fromList = Source . Text.pack
-- | Return a Source of Chars from a Text.
fromText :: Text -> Source Char
fromText = Source . Vector.fromList . unpack
fromText :: Text -> Source
fromText = Source
-- | Return a Source that contains a slice of the given Source.
slice :: Range -> Source a -> Source a
slice range = Source . Vector.slice (start range) (rangeLength range) . getVector
-- | Return a String with the contents of the Source.
toString :: Source Char -> String
toString = toList
slice :: Range -> Source -> Source
slice range = Source . take . drop . sourceText
where drop = Text.drop (start range)
take = Text.take (rangeLength range)
-- | Return a text with the contents of the Source.
toText :: Source Char -> Text
toText = pack . toList
toText :: Source -> Text
toText = sourceText
-- | Return the item at the given index.
at :: Source a -> Int -> a
at = (Vector.!) . getVector
-- | Remove the first item and return it with the rest of the source.
uncons :: Source a -> Maybe (a, Source a)
uncons (Source vector) = if null vector then Nothing else Just (Vector.head vector, Source $ Vector.tail vector)
at :: Source -> Int -> Char
at = Text.index . sourceText
-- | Split the source into the longest prefix of elements that do not satisfy the predicate and the rest without copying.
break :: (a -> Bool) -> Source a -> (Source a, Source a)
break predicate (Source vector) = let (start, remainder) = Vector.break predicate vector in (Source start, Source remainder)
break :: (Char -> Bool) -> Source -> (Source, Source)
break predicate (Source text) = let (start, remainder) = Text.break predicate text in (Source start, Source remainder)
-- | Split the contents of the source after newlines.
actualLines :: Source Char -> [Source Char]
actualLines source | null source = [ source ]
actualLines source = case Source.break (== '\n') source of
(l, lines') -> case uncons lines' of
Nothing -> [ l ]
Just (_, lines') -> (l <> fromList "\n") : actualLines lines'
actualLines :: Source -> [Source]
actualLines = fmap Source . actualLines' . sourceText
where actualLines' text
| Text.null text = [ text ]
| otherwise = case Text.break (== '\n') text of
(l, lines') -> case Text.uncons lines' of
Nothing -> [ l ]
Just (_, lines') -> (l <> Text.singleton '\n') : actualLines' lines'
-- | Compute the line ranges within a given range of a string.
actualLineRanges :: Range -> Source Char -> [Range]
actualLineRanges :: Range -> Source -> [Range]
actualLineRanges range = drop 1 . scanl toRange (Range (start range) (start range)) . actualLines . slice range
where toRange previous string = Range (end previous) $ end previous + length string
where toRange previous string = Range (end previous) $ end previous + Text.length (sourceText string)
-- | Compute the character range given a Source and a SourceSpan.
sourceSpanToRange :: Source Char -> SourceSpan -> Range
sourceSpanToRange :: Source -> SourceSpan -> Range
sourceSpanToRange source SourceSpan{..} = Range start end
where start = sumLengths leadingRanges + column spanStart
end = start + sumLengths (take (line spanEnd - line spanStart) remainingRanges) + (column spanEnd - column spanStart)
(leadingRanges, remainingRanges) = splitAt (line spanStart) (actualLineRanges (totalRange source) source)
(leadingRanges, remainingRanges) = splitAt (line spanStart) (actualLineRanges (Source.totalRange source) source)
sumLengths = sum . fmap (\ Range{..} -> end - start)
rangeToSourceSpan :: Source Char -> Range -> SourceSpan
-- | Return a range that covers the entire text.
totalRange :: Source -> Range
totalRange = Range 0 . Text.length . sourceText
rangeToSourceSpan :: Source -> Range -> SourceSpan
rangeToSourceSpan source range@Range{} = SourceSpan startPos endPos
where startPos = maybe (SourcePos 1 1) (toStartPos 1) (head lineRanges)
endPos = toEndPos (length lineRanges) (fromMaybe (rangeAt 0) (snd <$> unsnoc lineRanges))
endPos = toEndPos (Prologue.length lineRanges) (fromMaybe (rangeAt 0) (snd <$> unsnoc lineRanges))
lineRanges = actualLineRanges range source
toStartPos line range = SourcePos line (start range)
toEndPos line range = SourcePos line (end range)
length :: Source -> Int
length = Text.length . sourceText
instance Semigroup (Source a) where
Source a <> Source b = Source (a Vector.++ b)
null :: Source -> Bool
null = Text.null . sourceText
instance Monoid (Source a) where
instance Semigroup Source where
Source a <> Source b = Source (a <> b)
instance Monoid Source where
mempty = fromList []
mappend = (<>)
instance Listable Source where
tiers = (Source . unListableText) `mapT` tiers

View File

@ -17,7 +17,8 @@ import Range
import Source
import qualified Syntax
import Foreign
import Foreign.C.String
import Foreign.C.String (peekCString)
import Data.Text.Foreign (withCStringLen)
import qualified Syntax as S
import Term
import Text.Parser.TreeSitter hiding (Language(..))
@ -30,50 +31,56 @@ treeSitterParser :: Language -> Ptr TS.Language -> Parser (Syntax.Syntax Text) (
treeSitterParser language grammar blob = do
document <- ts_document_new
ts_document_set_language document grammar
withCString (toString $ source blob) (\source -> do
ts_document_set_input_string document source
withCStringLen (toText (source blob)) $ \ (source, len) -> do
ts_document_set_input_string_with_length document source len
ts_document_parse document
term <- documentToTerm language document blob
ts_document_free document
pure term)
pure term
-- | Return a parser for a tree sitter language & document.
documentToTerm :: Language -> Ptr Document -> Parser (Syntax.Syntax Text) (Record '[Range, Category, SourceSpan])
documentToTerm language document SourceBlob{..} = alloca $ \ root -> do
ts_document_root_node_p document root
toTerm root
where toTerm node = do
toTerm root (totalRange source) source
where toTerm node range source = do
name <- ts_node_p_name node document
name <- peekCString name
count <- ts_node_p_named_child_count node
children <- filter isNonEmpty <$> traverse (alloca . getChild node) (take (fromIntegral count) [0..])
let range = Range { start = fromIntegral $ ts_node_p_start_char node, end = fromIntegral $ ts_node_p_end_char node }
let getChild getter parentNode n childNode = do
_ <- getter parentNode n childNode
let childRange = nodeRange childNode
toTerm childNode childRange (slice (offsetRange childRange (negate (start range))) source)
children <- filter isNonEmpty <$> traverse (alloca . getChild ts_node_p_named_child node) (take (fromIntegral count) [0..])
let startPos = SourcePos (1 + (fromIntegral $! ts_node_p_start_point_row node)) (1 + (fromIntegral $! ts_node_p_start_point_column node))
let endPos = SourcePos (1 + (fromIntegral $! ts_node_p_end_point_row node)) (1 + (fromIntegral $! ts_node_p_end_point_column node))
let sourceSpan = SourceSpan { spanStart = startPos , spanEnd = endPos }
allChildrenCount <- ts_node_p_child_count node
let allChildren = filter isNonEmpty <$> traverse (alloca . getUnnamedChild node) (take (fromIntegral allChildrenCount) [0..])
let allChildren = filter isNonEmpty <$> traverse (alloca . getChild ts_node_p_child node) (take (fromIntegral allChildrenCount) [0..])
-- Note: The strict application here is semantically important.
-- Without it, we may not evaluate the value until after weve exited
-- the scope that `node` was allocated within, meaning `alloca` will
-- free it & other stack data may overwrite it.
range `seq` sourceSpan `seq` assignTerm language (slice range source) (range :. categoryForLanguageProductionName language (toS name) :. sourceSpan :. Nil) children allChildren
getChild node n out = ts_node_p_named_child node n out >> toTerm out
{-# INLINE getChild #-}
getUnnamedChild node n out = ts_node_p_child node n out >> toTerm out
{-# INLINE getUnnamedChild #-}
isNonEmpty child = category (extract child) /= Empty
range `seq` sourceSpan `seq` assignTerm language source (range :. categoryForLanguageProductionName language (toS name) :. sourceSpan :. Nil) children allChildren
assignTerm :: Language -> Source Char -> Record '[Range, Category, SourceSpan] -> [ SyntaxTerm Text '[ Range, Category, SourceSpan ] ] -> IO [ SyntaxTerm Text '[ Range, Category, SourceSpan ] ] -> IO (SyntaxTerm Text '[ Range, Category, SourceSpan ])
isNonEmpty :: HasField fields Category => SyntaxTerm Text fields -> Bool
isNonEmpty = (/= Empty) . category . extract
nodeRange :: Ptr Node -> Range
nodeRange node = Range { start = fromIntegral (ts_node_p_start_char node), end = fromIntegral (ts_node_p_end_char node) }
assignTerm :: Language -> Source -> Record '[Range, Category, SourceSpan] -> [ SyntaxTerm Text '[ Range, Category, SourceSpan ] ] -> IO [ SyntaxTerm Text '[ Range, Category, SourceSpan ] ] -> IO (SyntaxTerm Text '[ Range, Category, SourceSpan ])
assignTerm language source annotation children allChildren =
cofree . (annotation :<) <$> case assignTermByLanguage language source (category annotation) children of
Just a -> pure a
_ -> defaultTermAssignment source (category annotation) children allChildren
where assignTermByLanguage :: Language -> Source Char -> Category -> [ SyntaxTerm Text '[ Range, Category, SourceSpan ] ] -> Maybe (S.Syntax Text (SyntaxTerm Text '[ Range, Category, SourceSpan ]))
where assignTermByLanguage :: Language -> Source -> Category -> [ SyntaxTerm Text '[ Range, Category, SourceSpan ] ] -> Maybe (S.Syntax Text (SyntaxTerm Text '[ Range, Category, SourceSpan ]))
assignTermByLanguage = \case
JavaScript -> JS.termAssignment
C -> C.termAssignment
@ -81,7 +88,7 @@ assignTerm language source annotation children allChildren =
Ruby -> Ruby.termAssignment
_ -> \ _ _ _ -> Nothing
defaultTermAssignment :: Source Char -> Category -> [ SyntaxTerm Text '[Range, Category, SourceSpan] ] -> IO [ SyntaxTerm Text '[Range, Category, SourceSpan] ] -> IO (S.Syntax Text (SyntaxTerm Text '[Range, Category, SourceSpan]))
defaultTermAssignment :: Source -> Category -> [ SyntaxTerm Text '[Range, Category, SourceSpan] ] -> IO [ SyntaxTerm Text '[Range, Category, SourceSpan] ] -> IO (S.Syntax Text (SyntaxTerm Text '[Range, Category, SourceSpan]))
defaultTermAssignment source category children allChildren
| category `elem` operatorCategories = S.Operator <$> allChildren
| otherwise = pure $! case (category, children) of

View File

@ -11,7 +11,7 @@ import Data.Functor.Listable
import Data.List (nub)
import Data.Monoid hiding ((<>))
import Data.Record
import Data.String
import qualified Data.Text as Text
import Data.These
import Patch
import Prologue hiding (fst, snd)
@ -31,7 +31,7 @@ spec :: Spec
spec = parallel $ do
describe "alignBranch" $ do
it "produces symmetrical context" $
alignBranch getRange ([] :: [Join These (SplitDiff (Syntax String) (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 2, Range 2 4]) `shouldBe`
alignBranch getRange ([] :: [Join These (SplitDiff (Syntax Text) (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 2, Range 2 4]) `shouldBe`
[ Join (These (Range 0 2, [])
(Range 0 2, []))
, Join (These (Range 2 4, [])
@ -39,7 +39,7 @@ spec = parallel $ do
]
it "produces asymmetrical context" $
alignBranch getRange ([] :: [Join These (SplitDiff (Syntax String) (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 1]) `shouldBe`
alignBranch getRange ([] :: [Join These (SplitDiff (Syntax Text) (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 1]) `shouldBe`
[ Join (These (Range 0 2, [])
(Range 0 1, []))
, Join (This (Range 2 4, []))
@ -61,13 +61,13 @@ spec = parallel $ do
describe "alignDiff" $ do
it "aligns identical branches on a single line" $
let sources = both (Source.fromList "[ foo ]") (Source.fromList "[ foo ]") in
let sources = both (Source.fromText "[ foo ]") (Source.fromText "[ foo ]") in
align sources (pure (info 0 7) `branch` [ pure (info 2 5) `leaf` "foo" ]) `shouldBe` prettyDiff sources
[ Join (These (info 0 7 `branch` [ info 2 5 `leaf` "foo" ])
(info 0 7 `branch` [ info 2 5 `leaf` "foo" ])) ]
it "aligns identical branches spanning multiple lines" $
let sources = both (Source.fromList "[\nfoo\n]") (Source.fromList "[\nfoo\n]") in
let sources = both (Source.fromText "[\nfoo\n]") (Source.fromText "[\nfoo\n]") in
align sources (pure (info 0 7) `branch` [ pure (info 2 5) `leaf` "foo" ]) `shouldBe` prettyDiff sources
[ Join (These (info 0 2 `branch` [])
(info 0 2 `branch` []))
@ -78,7 +78,7 @@ spec = parallel $ do
]
it "aligns reformatted branches" $
let sources = both (Source.fromList "[ foo ]") (Source.fromList "[\nfoo\n]") in
let sources = both (Source.fromText "[ foo ]") (Source.fromText "[\nfoo\n]") in
align sources (pure (info 0 7) `branch` [ pure (info 2 5) `leaf` "foo" ]) `shouldBe` prettyDiff sources
[ Join (That (info 0 2 `branch` []))
, Join (These (info 0 7 `branch` [ info 2 5 `leaf` "foo" ])
@ -87,7 +87,7 @@ spec = parallel $ do
]
it "aligns nodes following reformatted branches" $
let sources = both (Source.fromList "[ foo ]\nbar\n") (Source.fromList "[\nfoo\n]\nbar\n") in
let sources = both (Source.fromText "[ foo ]\nbar\n") (Source.fromText "[\nfoo\n]\nbar\n") in
align sources (pure (info 0 12) `branch` [ pure (info 0 7) `branch` [ pure (info 2 5) `leaf` "foo" ], pure (info 8 11) `leaf` "bar" ]) `shouldBe` prettyDiff sources
[ Join (That (info 0 2 `branch` [ info 0 2 `branch` [] ]))
, Join (These (info 0 8 `branch` [ info 0 7 `branch` [ info 2 5 `leaf` "foo" ] ])
@ -100,12 +100,12 @@ spec = parallel $ do
]
it "aligns identical branches with multiple children on the same line" $
let sources = pure (Source.fromList "[ foo, bar ]") in
let sources = pure (Source.fromText "[ foo, bar ]") in
align sources (pure (info 0 12) `branch` [ pure (info 2 5) `leaf` "foo", pure (info 7 10) `leaf` "bar" ]) `shouldBe` prettyDiff sources
[ Join (runBothWith These (pure (info 0 12 `branch` [ info 2 5 `leaf` "foo", info 7 10 `leaf` "bar" ])) ) ]
it "aligns insertions" $
let sources = both (Source.fromList "a") (Source.fromList "a\nb") in
let sources = both (Source.fromText "a") (Source.fromText "a\nb") in
align sources (both (info 0 1) (info 0 3) `branch` [ pure (info 0 1) `leaf` "a", insert (info 2 3 `leaf` "b") ]) `shouldBe` prettyDiff sources
[ Join (These (info 0 1 `branch` [ info 0 1 `leaf` "a" ])
(info 0 2 `branch` [ info 0 1 `leaf` "a" ]))
@ -113,19 +113,19 @@ spec = parallel $ do
]
it "aligns total insertions" $
let sources = both (Source.fromList "") (Source.fromList "a") in
let sources = both (Source.fromText "") (Source.fromText "a") in
align sources (insert (info 0 1 `leaf` "a")) `shouldBe` prettyDiff sources
[ Join (That (insert (info 0 1 `leaf` "a"))) ]
it "aligns insertions into empty branches" $
let sources = both (Source.fromList "[ ]") (Source.fromList "[a]") in
let sources = both (Source.fromText "[ ]") (Source.fromText "[a]") in
align sources (pure (info 0 3) `branch` [ insert (info 1 2 `leaf` "a") ]) `shouldBe` prettyDiff sources
[ Join (That (info 0 3 `branch` [ insert (info 1 2 `leaf` "a") ]))
, Join (This (info 0 3 `branch` []))
]
it "aligns symmetrically following insertions" $
let sources = both (Source.fromList "a\nc") (Source.fromList "a\nb\nc") in
let sources = both (Source.fromText "a\nc") (Source.fromText "a\nb\nc") in
align sources (both (info 0 3) (info 0 5) `branch` [ pure (info 0 1) `leaf` "a", insert (info 2 3 `leaf` "b"), both (info 2 3) (info 4 5) `leaf` "c" ])
`shouldBe` prettyDiff sources
[ Join (These (info 0 2 `branch` [ info 0 1 `leaf` "a" ])
@ -136,13 +136,13 @@ spec = parallel $ do
]
it "symmetrical nodes force the alignment of asymmetrical nodes on both sides" $
let sources = both (Source.fromList "[ a, b ]") (Source.fromList "[ b, c ]") in
let sources = both (Source.fromText "[ a, b ]") (Source.fromText "[ b, c ]") in
align sources (pure (info 0 8) `branch` [ delete (info 2 3 `leaf` "a"), both (info 5 6) (info 2 3) `leaf` "b", insert (info 5 6 `leaf` "c") ]) `shouldBe` prettyDiff sources
[ Join (These (info 0 8 `branch` [ delete (info 2 3 `leaf` "a"), info 5 6 `leaf` "b" ])
(info 0 8 `branch` [ info 2 3 `leaf` "b", insert (info 5 6 `leaf` "c") ])) ]
it "when one of two symmetrical nodes must be split, splits the latter" $
let sources = both (Source.fromList "[ a, b ]") (Source.fromList "[ a\n, b\n]") in
let sources = both (Source.fromText "[ a, b ]") (Source.fromText "[ a\n, b\n]") in
align sources (both (info 0 8) (info 0 9) `branch` [ pure (info 2 3) `leaf` "a", both (info 5 6) (info 6 7) `leaf` "b" ]) `shouldBe` prettyDiff sources
[ Join (These (info 0 8 `branch` [ info 2 3 `leaf` "a", info 5 6 `leaf` "b" ])
(info 0 4 `branch` [ info 2 3 `leaf` "a" ]))
@ -151,14 +151,14 @@ spec = parallel $ do
]
it "aligns deletions before insertions" $
let sources = both (Source.fromList "[ a ]") (Source.fromList "[ b ]") in
let sources = both (Source.fromText "[ a ]") (Source.fromText "[ b ]") in
align sources (pure (info 0 5) `branch` [ delete (info 2 3 `leaf` "a"), insert (info 2 3 `leaf` "b") ]) `shouldBe` prettyDiff sources
[ Join (This (info 0 5 `branch` [ delete (info 2 3 `leaf` "a") ]))
, Join (That (info 0 5 `branch` [ insert (info 2 3 `leaf` "b") ]))
]
it "aligns context-only lines symmetrically" $
let sources = both (Source.fromList "[\n a\n,\n b\n]") (Source.fromList "[\n a, b\n\n\n]") in
let sources = both (Source.fromText "[\n a\n,\n b\n]") (Source.fromText "[\n a, b\n\n\n]") in
align sources (both (info 0 13) (info 0 12) `branch` [ pure (info 4 5) `leaf` "a", both (info 10 11) (info 7 8) `leaf` "b" ]) `shouldBe` prettyDiff sources
[ Join (These (info 0 2 `branch` [])
(info 0 2 `branch` []))
@ -173,7 +173,7 @@ spec = parallel $ do
]
it "aligns asymmetrical nodes preceding their symmetrical siblings conservatively" $
let sources = both (Source.fromList "[ b, c ]") (Source.fromList "[ a\n, c\n]") in
let sources = both (Source.fromText "[ b, c ]") (Source.fromText "[ a\n, c\n]") in
align sources (both (info 0 8) (info 0 9) `branch` [ insert (info 2 3 `leaf` "a"), delete (info 2 3 `leaf` "b"), both (info 5 6) (info 6 7) `leaf` "c" ]) `shouldBe` prettyDiff sources
[ Join (That (info 0 4 `branch` [ insert (info 2 3 `leaf` "a") ]))
, Join (These (info 0 8 `branch` [ delete (info 2 3 `leaf` "b"), info 5 6 `leaf` "c" ])
@ -182,7 +182,7 @@ spec = parallel $ do
]
it "aligns symmetrical reformatted nodes" $
let sources = both (Source.fromList "a [ b ]\nc") (Source.fromList "a [\nb\n]\nc") in
let sources = both (Source.fromText "a [ b ]\nc") (Source.fromText "a [\nb\n]\nc") in
align sources (pure (info 0 9) `branch` [ pure (info 0 1) `leaf` "a", pure (info 2 7) `branch` [ pure (info 4 5) `leaf` "b" ], pure (info 8 9) `leaf` "c" ]) `shouldBe` prettyDiff sources
[ Join (These (info 0 8 `branch` [ info 0 1 `leaf` "a", info 2 7 `branch` [ info 4 5 `leaf` "b" ] ])
(info 0 4 `branch` [ info 0 1 `leaf` "a", info 2 4 `branch` [] ]))
@ -197,39 +197,39 @@ spec = parallel $ do
\ xs -> counts (numberedRows (unListableF <$> xs :: [Join These Char])) `shouldBe` length . catMaybes <$> Join (unalign (runJoin . unListableF <$> xs))
data BranchElement
= Child String (Join These String)
| Margin (Join These String)
= Child Text (Join These Text)
| Margin (Join These Text)
deriving Show
branchElementKey :: BranchElement -> Maybe String
branchElementKey :: BranchElement -> Maybe Text
branchElementKey (Child key _) = Just key
branchElementKey _ = Nothing
toAlignBranchInputs :: [BranchElement] -> (Both (Source.Source Char), [Join These (String, Range)], Both [Range])
toAlignBranchInputs :: [BranchElement] -> (Both Source.Source, [Join These (Text, Range)], Both [Range])
toAlignBranchInputs elements = (sources, join . (`evalState` both 0 0) . traverse go $ elements, ranges)
where go :: BranchElement -> State (Both Int) [Join These (String, Range)]
where go :: BranchElement -> State (Both Int) [Join These (Text, Range)]
go child@(Child key _) = do
lines <- traverse (\ (Child _ contents) -> do
prev <- get
let next = (+) <$> prev <*> modifyJoin (fromThese 0 0) (length <$> contents)
let next = (+) <$> prev <*> modifyJoin (fromThese 0 0) (Text.length <$> contents)
put next
pure $! modifyJoin (runBothWith bimap (const <$> (Range <$> prev <*> next))) contents) (alignBranchElement child)
pure $! fmap ((,) key) <$> lines
go (Margin contents) = do
prev <- get
put $ (+) <$> prev <*> modifyJoin (fromThese 0 0) (length <$> contents)
put $ (+) <$> prev <*> modifyJoin (fromThese 0 0) (Text.length <$> contents)
pure []
alignBranchElement element = case element of
Child key contents -> Child key <$> joinCrosswalk lines contents
Margin contents -> Margin <$> joinCrosswalk lines contents
where lines = fmap toList . Source.actualLines . Source.fromList
sources = foldMap Source.fromList <$> bothContents elements
ranges = fmap (filter (\ (Range start end) -> start /= end)) $ Source.actualLineRanges <$> (totalRange <$> sources) <*> sources
where lines = fmap Source.sourceText . Source.actualLines . Source.fromText
sources = foldMap Source.fromText <$> bothContents elements
ranges = fmap (filter (\ (Range start end) -> start /= end)) $ Source.actualLineRanges <$> (Source.totalRange <$> sources) <*> sources
bothContents = foldMap (modifyJoin (fromThese [] []) . fmap (:[]) . branchElementContents)
branchElementContents (Child _ contents) = contents
branchElementContents (Margin contents) = contents
keysOfAlignedChildren :: [Join These (Range, [(String, Range)])] -> [String]
keysOfAlignedChildren :: [Join These (Range, [(Text, Range)])] -> [Text]
keysOfAlignedChildren lines = lines >>= these identity identity (<>) . runJoin . fmap (fmap Prologue.fst . Prologue.snd)
joinCrosswalk :: Bicrosswalk p => Align f => (a -> f b) -> Join p a -> f (Join p b)
@ -237,10 +237,10 @@ joinCrosswalk f = fmap Join . bicrosswalk f f . runJoin
instance Listable BranchElement where
tiers = oneof [ (\ key -> Child key `mapT` joinTheseOf (contents key)) `concatMapT` key
, Margin `mapT` joinTheseOf (pure `mapT` padding '-') ]
where key = pure `mapT` [['a'..'z'] <> ['A'..'Z'] <> ['0'..'9']]
contents key = (wrap key . pure) `mapT` padding '*'
wrap key contents = "(" <> key <> contents <> ")" :: String
, Margin `mapT` joinTheseOf (Text.singleton `mapT` padding '-') ]
where key = Text.singleton `mapT` [['a'..'z'] <> ['A'..'Z'] <> ['0'..'9']]
contents key = (wrap key . Text.singleton) `mapT` padding '*'
wrap key contents = "(" <> key <> contents <> ")" :: Text
padding :: Char -> [Tier Char]
padding char = frequency [ (10, [[char]])
, (1, [['\n']]) ]
@ -256,16 +256,16 @@ instance Listable BranchElement where
counts :: [Join These (Int, a)] -> Both Int
counts numbered = fromMaybe 0 . getLast . mconcat . fmap Last <$> Join (unalign (runJoin . fmap Prologue.fst <$> numbered))
align :: Both (Source.Source Char) -> ConstructibleFree (Patch (Term (Syntax String) (Record '[Range]))) (Both (Record '[Range])) -> PrettyDiff (SplitDiff (Syntax String) (Record '[Range]))
align :: Both Source.Source -> ConstructibleFree (Patch (Term (Syntax Text) (Record '[Range]))) (Both (Record '[Range])) -> PrettyDiff (SplitDiff (Syntax Text) (Record '[Range]))
align sources = PrettyDiff sources . fmap (fmap (getRange &&& identity)) . alignDiff sources . deconstruct
info :: Int -> Int -> Record '[Range]
info start end = Range start end :. Nil
prettyDiff :: Both (Source.Source Char) -> [Join These (ConstructibleFree (SplitPatch (Term (Syntax String) (Record '[Range]))) (Record '[Range]))] -> PrettyDiff (SplitDiff (Syntax String) (Record '[Range]))
prettyDiff :: Both Source.Source -> [Join These (ConstructibleFree (SplitPatch (Term (Syntax Text) (Record '[Range]))) (Record '[Range]))] -> PrettyDiff (SplitDiff (Syntax Text) (Record '[Range]))
prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& identity) . deconstruct))
data PrettyDiff a = PrettyDiff { unPrettySources :: Both (Source.Source Char), unPrettyLines :: [Join These (Range, a)] }
data PrettyDiff a = PrettyDiff { unPrettySources :: Both Source.Source, unPrettyLines :: [Join These (Range, a)] }
deriving Eq
instance Show (PrettyDiff a) where
@ -273,22 +273,22 @@ instance Show (PrettyDiff a) where
where prettyPrinted = showLine (maximum (0 : (maximum . fmap length <$> shownLines))) <$> shownLines >>= ('\n':)
shownLines = catMaybes $ toBoth <$> lines
showLine n line = uncurry ((<>) . (++ " | ")) (fromThese (replicate n ' ') (replicate n ' ') (runJoin (pad n <$> line)))
showDiff (range, _) = filter (/= '\n') . toList . Source.slice range
showDiff (range, _) = filter (/= '\n') . Text.unpack . Source.sourceText . Source.slice range
pad n string = (<>) (take n string) (replicate (max 0 (n - length string)) ' ')
toBoth them = showDiff <$> them `applyThese` modifyJoin (uncurry These) sources
newtype ConstructibleFree patch annotation = ConstructibleFree { deconstruct :: Free (CofreeF (Syntax String) annotation) patch }
newtype ConstructibleFree patch annotation = ConstructibleFree { deconstruct :: Free (CofreeF (Syntax Text) annotation) patch }
class PatchConstructible p where
insert :: Term (Syntax String) (Record '[Range]) -> p
delete :: Term (Syntax String) (Record '[Range]) -> p
insert :: Term (Syntax Text) (Record '[Range]) -> p
delete :: Term (Syntax Text) (Record '[Range]) -> p
instance PatchConstructible (Patch (Term (Syntax String) (Record '[Range]))) where
instance PatchConstructible (Patch (Term (Syntax Text) (Record '[Range]))) where
insert = Insert
delete = Delete
instance PatchConstructible (SplitPatch (Term (Syntax String) (Record '[Range]))) where
instance PatchConstructible (SplitPatch (Term (Syntax Text) (Record '[Range]))) where
insert = SplitInsert
delete = SplitDelete
@ -297,13 +297,13 @@ instance PatchConstructible patch => PatchConstructible (ConstructibleFree patch
delete = ConstructibleFree . pure . delete
class SyntaxConstructible s where
leaf :: annotation -> String -> s annotation
leaf :: annotation -> Text -> s annotation
branch :: annotation -> [s annotation] -> s annotation
instance SyntaxConstructible (ConstructibleFree patch) where
leaf info = ConstructibleFree . free . Free . (info :<) . Leaf
branch info = ConstructibleFree . free . Free . (info :<) . Indexed . fmap deconstruct
instance SyntaxConstructible (Cofree (Syntax String)) where
instance SyntaxConstructible (Cofree (Syntax Text)) where
info `leaf` value = cofree $ info :< Leaf value
info `branch` children = cofree $ info :< Indexed children

View File

@ -86,7 +86,7 @@ testDiff renderer paths diff matcher = do
matcher actual (Just expected)
where diffFiles' sources parser = diffFiles parser renderer (sourceBlobs sources paths)
parser = parserWithCost <$> runBothWith (<|>) paths
sourceBlobs :: Both (Maybe (S.Source Char)) -> Both (Maybe FilePath) -> Both S.SourceBlob
sourceBlobs :: Both (Maybe (S.Source)) -> Both (Maybe FilePath) -> Both S.SourceBlob
sourceBlobs sources paths = case runJoin paths of
(Nothing, Nothing) -> Join (S.emptySourceBlob "", S.emptySourceBlob "")
(Nothing, Just filepath) -> Join (S.emptySourceBlob "", S.sourceBlob (unsafeFromJust $ snd sources) filepath)

View File

@ -84,7 +84,7 @@ spec = parallel $ do
let listOfLeaves = foldMap extractLeaves (join $ toList <$> branchPatches)
listOfDiffLeaves = foldMap extractDiffLeaves (diffPatches >>= toList)
in
length listOfLeaves `shouldBe` length listOfDiffLeaves
Prologue.length listOfLeaves `shouldBe` Prologue.length listOfDiffLeaves
isIndexedOrFixed :: Patch (Term (Syntax a) annotation) -> Bool
isIndexedOrFixed = any (isIndexedOrFixed' . unwrap)

View File

@ -1,22 +1,22 @@
module Source.Spec where
import qualified Data.Text as Text
import qualified Prelude
import Prologue
import Range
import Source
import SourceSpan
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.Hspec.LeanCheck
spec :: Spec
spec = parallel $ do
describe "actualLineRanges" $ do
prop "produces 1 more range than there are newlines" $
\ s -> length (actualLineRanges (totalRange s) (fromList s)) `shouldBe` succ (length (filter (== '\n') s))
\ source -> Prologue.length (actualLineRanges (totalRange source) source) `shouldBe` succ (Text.count "\n" (sourceText source))
prop "produces exhaustive ranges" $
\ s -> let source = fromList s in
foldMap (`slice` source) (actualLineRanges (totalRange s) source) `shouldBe` source
\ source -> foldMap (`slice` source) (actualLineRanges (totalRange source) source) `shouldBe` source
describe "sourceSpanToRange" $ do
prop "computes single-line ranges" $
@ -40,8 +40,8 @@ spec = parallel $ do
prop "covers multiple lines" $
\ n -> totalSpan (fromList (intersperse '\n' (replicate n '*'))) `shouldBe` SourceSpan (SourcePos 0 0) (SourcePos (max 0 (pred n)) (if n > 0 then 1 else 0))
totalSpan :: Source Char -> SourceSpan
totalSpan source = SourceSpan (SourcePos 0 0) (SourcePos (pred (length ranges)) (end lastRange - start lastRange))
totalSpan :: Source -> SourceSpan
totalSpan source = SourceSpan (SourcePos 0 0) (SourcePos (pred (Prologue.length ranges)) (end lastRange - start lastRange))
where ranges = actualLineRanges (totalRange source) source
lastRange = Prelude.last ranges

@ -1 +1 @@
Subproject commit 8116ff5ec37cae106ed78771a9676057ad702b80
Subproject commit b4575e8083ea210eec2de50862cce3fbb2c97b00