Ejemplo n.º 1
0
int main () {
    test ( "ABCDE", 5 );
    test ( "a", 1 );

    test ( L"ABCDE", 5 );
    test ( L"a", 1 );

#if TEST_STD_VER >= 11
    test ( u"ABCDE", 5 );
    test ( u"a", 1 );

    test ( U"ABCDE", 5 );
    test ( U"a", 1 );
#endif

#if TEST_STD_VER >= 11
    {
    constexpr std::basic_string_view<char> sv ( "ABC", 2 );
    static_assert ( sv.length() ==  2,  "" );
    static_assert ( sv.at(0) == 'A', "" );
    static_assert ( sv.at(1) == 'B', "" );
    }
#endif
}
Ejemplo n.º 2
0
// Routine Description:
// - Takes a array of attribute runs, and inserts them into this row from startIndex to endIndex.
// - For example, if the current row was was [{4, BLUE}], the merge string
//   was [{ 2, RED }], with (StartIndex, EndIndex) = (1, 2),
//   then the row would modified to be = [{ 1, BLUE}, {2, RED}, {1, BLUE}].
// Arguments:
// - rgInsertAttrs - The array of attrRuns to merge into this row.
// - cInsertAttrs - The number of elements in rgInsertAttrs
// - iStart - The index in the row to place the array of runs.
// - iEnd - the final index of the merge runs
// - BufferWidth - the width of the row.
// Return Value:
// - STATUS_NO_MEMORY if there wasn't enough memory to insert the runs
//   otherwise STATUS_SUCCESS if we were successful.
[[nodiscard]]
HRESULT ATTR_ROW::InsertAttrRuns(const std::basic_string_view<TextAttributeRun> newAttrs,
                                 const size_t iStart,
                                 const size_t iEnd,
                                 const size_t cBufferWidth)
{
    // Definitions:
    // Existing Run = The run length encoded color array we're already storing in memory before this was called.
    // Insert Run = The run length encoded color array that someone is asking us to inject into our stored memory run.
    // New Run = The run length encoded color array that we have to allocate and rebuild to store internally
    //           which will replace Existing Run at the end of this function.
    // Example:
    // cBufferWidth = 10.
    // Existing Run: R3 -> G5 -> B2
    // Insert Run: Y1 -> N1 at iStart = 5 and iEnd = 6
    //            (rgInsertAttrs is a 2 length array with Y1->N1 in it and cInsertAttrs = 2)
    // Final Run: R3 -> G2 -> Y1 -> N1 -> G1 -> B2

    // We'll need to know what the last valid column is for some calculations versus iEnd
    // because iEnd is specified to us as an inclusive index value.
    // Do the -1 math here now so we don't have to have -1s scattered all over this function.
    const size_t iLastBufferCol = cBufferWidth - 1;

    // If the insertion size is 1, do some pre-processing to
    // see if we can get this done quickly.
    if (newAttrs.size() == 1)
    {
        // Get the new color attribute we're trying to apply
        const TextAttribute NewAttr = newAttrs.at(0).GetAttributes();

        // If the existing run was only 1 element...
        // ...and the new color is the same as the old, we don't have to do anything and can exit quick.
        if (_list.size() == 1 && _list.at(0).GetAttributes() == NewAttr)
        {
            return S_OK;
        }
        // .. otherwise if we internally have a list of 2 and we're about to insert a single color
        // it's probable that we're just walking left-to-right through the row and changing each
        // cell one at a time.
        // e.g.
        // AAAAABBBBBBB
        // AAAAAABBBBBB
        // AAAAAAABBBBB
        // Check for that circumstance by seeing if we're inserting a single run of the
        // left side color right at the boundary and just adjust the counts in the existing
        // two elements in our internal list.
        else if (_list.size() == 2 && newAttrs.at(0).GetLength() == 1)
        {
            auto left = _list.begin();
            if (iStart == left->GetLength() && NewAttr == left->GetAttributes())
            {
                auto right = left + 1;
                left->IncrementLength();
                right->DecrementLength();

                // If we just reduced the right half to zero, just erase it out of the list.
                if (right->GetLength() == 0)
                {
                    _list.erase(right);
                }
                return S_OK;
            }
        }
    }

    // If we're about to cover the entire existing run with a new one, we can also make an optimization.
    if (iStart == 0 && iEnd == iLastBufferCol)
    {
        // Just dump what we're given over what we have and call it a day.
        _list.assign(newAttrs.cbegin(), newAttrs.cend());

        return S_OK;
    }

    // In the worst case scenario, we will need a new run that is the length of
    // The existing run in memory + The new run in memory + 1.
    // This worst case occurs when we inject a new item in the middle of an existing run like so
    // Existing R3->B5->G2, Insertion Y2 starting at 5 (in the middle of the B5)
    // becomes R3->B2->Y2->B1->G2.
    // The original run was 3 long. The insertion run was 1 long. We need 1 more for the
    // fact that an existing piece of the run was split in half (to hold the latter half).
    const size_t cNewRun = _list.size() + newAttrs.size() + 1;
    std::vector<TextAttributeRun> newRun;
    newRun.resize(cNewRun);

    // We will start analyzing from the beginning of our existing run.
    // Use some pointers to keep track of where we are in walking through our runs.

    // Get the existing run that we'll be updating/manipulating.
    const auto existingRun = _list.begin();
    auto pExistingRunPos = existingRun;
    const auto pExistingRunEnd = existingRun + _list.size();
    auto pInsertRunPos = newAttrs.begin();
    size_t cInsertRunRemaining = newAttrs.size();
    auto pNewRunPos = newRun.begin();
    size_t iExistingRunCoverage = 0;

    // Copy the existing run into the new buffer up to the "start index" where the new run will be injected.
    // If the new run starts at 0, we have nothing to copy from the beginning.
    if (iStart != 0)
    {
        // While we're less than the desired insertion position...
        while (iExistingRunCoverage < iStart)
        {
            // Add up how much length we can cover by copying an item from the existing run.
            iExistingRunCoverage += pExistingRunPos->GetLength();

            // Copy it to the new run buffer and advance both pointers.
            *pNewRunPos++ = *pExistingRunPos++;
        }

        // When we get to this point, we've copied full segments from the original existing run
        // into our new run buffer. We will have 1 or more full segments of color attributes and
        // we MIGHT have to cut the last copied segment's length back depending on where the inserted
        // attributes will fall in the final/new run.
        // Some examples:
        // - Starting with the original string R3 -> G5 -> B2
        // - 1. If the insertion is Y5 at start index 3
        //      We are trying to get a result/final/new run of R3 -> Y5 -> B2.
        //      We just copied R3 to the new destination buffer and we cang skip down and start inserting the new attrs.
        // - 2. If the insertion is Y3 at start index 5
        //      We are trying to get a result/final/new run of R3 -> G2 -> Y3 -> B2.
        //      We just copied R3 -> G5 to the new destination buffer with the code above.
        //      But the insertion is going to cut out some of the length of the G5.
        //      We need to fix this up below so it says G2 instead to leave room for the Y3 to fit in
        //      the new/final run.

        // Copying above advanced the pointer to an empty cell beyond what we copied.
        // Back up one cell so we can manipulate the final item we copied from the existing run to the new run.
        pNewRunPos--;

        // Fetch out the length so we can fix it up based on the below conditions.
        size_t length = pNewRunPos->GetLength();

        // If we've covered more cells already than the start of the attributes to be inserted...
        if (iExistingRunCoverage > iStart)
        {
            // ..then subtract some of the length of the final cell we copied.
            // We want to take remove the difference in distance between the cells we've covered in the new
            // run and the insertion point.
            // (This turns G5 into G2 from Example 2 just above)
            length -= (iExistingRunCoverage - iStart);
        }

        // Now we're still on that "last cell copied" into the new run.
        // If the color of that existing copied cell matches the color of the first segment
        // of the run we're about to insert, we can just increment the length to extend the coverage.
        if (pNewRunPos->GetAttributes() == pInsertRunPos->GetAttributes())
        {
            length += pInsertRunPos->GetLength();

            // Since the color matched, we have already "used up" part of the insert run
            // and can skip it in our big "memcopy" step below that will copy the bulk of the insert run.
            cInsertRunRemaining--;
            pInsertRunPos++;
        }

        // We're done manipulating the length. Store it back.
        pNewRunPos->SetLength(length);

        // Now that we're done adjusting the last copied item, advance the pointer into a fresh/blank
        // part of the new run array.
        pNewRunPos++;
    }

    // Bulk copy the majority (or all, depending on circumstance) of the insert run into the final run buffer.
    std::copy_n(pInsertRunPos, cInsertRunRemaining, pNewRunPos);

    // Advance the new run pointer into the position just after everything we copied.
    pNewRunPos += cInsertRunRemaining;

    // We're technically done with the insert run now and have 0 remaining, but won't bother updating its pointers
    // and counts any further because we won't use them.

    // Now we need to move our pointer for the original existing run forward and update our counts
    // on how many cells we could have copied from the source before finishing off the new run.
    while (iExistingRunCoverage <= iEnd)
    {
        FAIL_FAST_IF(!(pExistingRunPos != pExistingRunEnd));
        iExistingRunCoverage += pExistingRunPos->GetLength();
        pExistingRunPos++;
    }

    // If we still have original existing run cells remaining, copy them into the final new run.
    if (pExistingRunPos != pExistingRunEnd || iExistingRunCoverage != (iEnd + 1))
    {
        // Back up one cell so we can inspect the most recent item copied into the new run for optimizations.
        pNewRunPos--;

        // We advanced the existing run pointer and its count to on or past the end of what the insertion run filled in.
        // If this ended up being past the end of what the insertion run covers, we have to account for the cells after
        // the insertion run but before the next piece of the original existing run.
        // The example in this case is if we had...
        // Existing Run = R3 -> G5 -> B2 -> X5
        // Insert Run = Y2 @ iStart = 7 and iEnd = 8
        // ... then at this point in time, our states would look like...
        // New Run so far = R3 -> G4 -> Y2
        // Existing Run Pointer is at X5
        // Existing run coverage count at 3 + 5 + 2 = 10.
        // However, in order to get the final desired New Run
        //   (which is R3 -> G4 -> Y2 -> B1 -> X5)
        //   we would need to grab a piece of that B2 we already skipped past.
        // iExistingRunCoverage = 10. iEnd = 8. iEnd+1 = 9. 10 > 9. So we skipped something.
        if (iExistingRunCoverage > (iEnd + 1))
        {
            // Back up the existing run pointer so we can grab the piece we skipped.
            pExistingRunPos--;

            // If the color matches what's already in our run, just increment the count value.
            // This case is slightly off from the example above. This case is for if the B2 above was actually Y2.
            // That Y2 from the existing run is the same color as the Y2 we just filled a few columns left in the final run
            // so we can just adjust the final run's column count instead of adding another segment here.
            if (pNewRunPos->GetAttributes() == pExistingRunPos->GetAttributes())
            {
                size_t length = pNewRunPos->GetLength();
                length += (iExistingRunCoverage - (iEnd + 1));
                pNewRunPos->SetLength(length);
            }
            else
            {
                // If the color didn't match, then we just need to copy the piece we skipped and adjust
                // its length for the discrepency in columns not yet covered by the final/new run.

                // Move forward to a blank spot in the new run
                pNewRunPos++;

                // Copy the existing run's color information to the new run
                pNewRunPos->SetAttributes(pExistingRunPos->GetAttributes());

                // Adjust the length of that copied color to cover only the reduced number of columns needed
                // now that some have been replaced by the insert run.
                pNewRunPos->SetLength(iExistingRunCoverage - (iEnd + 1));
            }

            // Now that we're done recovering a piece of the existing run we skipped, move the pointer forward again.
            pExistingRunPos++;
        }

        // OK. In this case, we didn't skip anything. The end of the insert run fell right at a boundary
        // in columns that was in the original existing run.
        // However, the next piece of the original existing run might happen to have the same color attribute
        // as the final piece of what we just copied.
        // As an example...
        // Existing Run = R3 -> G5 -> B2.
        // Insert Run = B5 @ iStart = 3 and iEnd = 7
        // New Run so far = R3 -> B5
        // New Run desired when done = R3 -> B7
        // Existing run pointer is on B2.
        // We want to merge the 2 from the B2 into the B5 so we get B7.
        else if (pNewRunPos->GetAttributes() == pExistingRunPos->GetAttributes())
        {
            // Add the value from the existing run into the current new run position.
            size_t length = pNewRunPos->GetLength();
            length += pExistingRunPos->GetLength();
            pNewRunPos->SetLength(length);

            // Advance the existing run position since we consumed its value and merged it in.
            pExistingRunPos++;
        }

        // OK. We're done inspecting the most recently copied cell for optimizations.
        pNewRunPos++;

        // Now bulk copy any segments left in the original existing run
        if (pExistingRunPos < pExistingRunEnd)
        {
            std::copy_n(pExistingRunPos, (pExistingRunEnd - pExistingRunPos), pNewRunPos);

            // Fix up the end pointer so we know where we are for counting how much of the new run's memory space we used.
            pNewRunPos += (pExistingRunEnd - pExistingRunPos);
        }
    }

    // OK, phew. We're done. Now we just need to free the existing run, store the new run in its place,
    // and update the count for the correct length of the new run now that we've filled it up.

    newRun.erase(pNewRunPos, newRun.end());
    _list.swap(newRun);

    return S_OK;
}