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 }
// 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; }