• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

antonvw / wex / 13205085727

07 Feb 2025 05:39PM UTC coverage: 58.702% (-0.002%) from 58.704%
13205085727

push

github

web-flow
reduce double commands code (#837)

16544 of 31039 branches covered (53.3%)

Branch coverage included in aggregate %.

13320 of 19835 relevant lines covered (67.15%)

1227.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.98
/src/ex/addressrange.cpp
1
////////////////////////////////////////////////////////////////////////////////
2
// Name:      addressrange.cpp
3
// Purpose:   Implementation of class wex::addressrange
4
// Author:    Anton van Wezenbeek
5
// Copyright: (c) 2015-2025 Anton van Wezenbeek
6
////////////////////////////////////////////////////////////////////////////////
7

8
#include <boost/algorithm/string.hpp>
9
#include <charconv>
10

11
#include <wex/common/util.h>
12
#include <wex/core/cmdline.h>
13
#include <wex/core/core.h>
14
#include <wex/core/file.h>
15
#include <wex/core/log.h>
16
#include <wex/ex/addressrange.h>
17
#include <wex/ex/command-parser.h>
18
#include <wex/ex/ex-stream.h>
19
#include <wex/ex/ex.h>
20
#include <wex/ex/macros.h>
21
#include <wex/ex/util.h>
22
#include <wex/factory/process.h>
23
#include <wex/factory/sort.h>
24
#include <wex/factory/stc-undo.h>
25
#include <wex/syntax/stc.h>
26
#include <wex/ui/frame.h>
27
#include <wx/app.h>
28
#include <wx/msgdlg.h>
29

30
#include "addressrange-mark.h"
31
#include "global-env.h"
32
#include "util.h"
33

34
namespace wex
35
{
36
void convert_case(factory::stc* stc, std::string& target, char c)
16✔
37
{
38
  c == 'U' ? boost::algorithm::to_upper(target) :
28!
39
             boost::algorithm::to_lower(target);
16!
40

41
  stc->Replace(stc->GetTargetStart(), stc->GetTargetEnd(), target);
16✔
42
}
16✔
43

44
bool prep(data::substitute& data, int searchFlags, const command_parser& cp)
74✔
45
{
46
  char        cmd = cp.command()[0];
74✔
47
  const auto& text(cp.text());
74✔
48

49
  switch (cmd)
74!
50
  {
51
    case 's':
61✔
52
      if (!data.set(text))
61!
53
      {
54
        return false;
×
55
      }
56
      break;
61✔
57

58
    case '&':
13✔
59
    case '~':
60
      data.set_options(text);
13✔
61
      break;
13✔
62

63
    default:
×
64
      log::debug("substitute unhandled command") << cmd;
×
65
      return false;
×
66
  }
67

68
  if (data.pattern().empty())
74!
69
  {
70
    log::status("Pattern is empty");
×
71
    log::debug("substitute") << cmd << "empty pattern" << text;
×
72
    return false;
×
73
  }
74

75
  if (
74✔
76
    (searchFlags & wxSTC_FIND_REGEXP) && data.pattern().size() == 2 &&
74✔
77
    data.pattern().back() == '*' && data.replacement().empty())
148!
78
  {
79
    log::status("Replacement leads to infinite loop");
2✔
80
    return false;
2✔
81
  }
82

83
  return true;
72✔
84
}
85
}; // namespace wex
86

87
wex::addressrange::addressrange(ex* ex, int lines)
597✔
88
  : m_begin(ex)
597✔
89
  , m_end(ex)
597✔
90
  , m_ex(ex)
597✔
91
  , m_stc(ex->get_stc())
597✔
92
  , m_commands(init_commands())
597✔
93
{
94
  if (lines > 0)
597✔
95
  {
96
    set(m_begin, m_end, lines);
591✔
97
  }
98
  else if (lines < 0)
6✔
99
  {
100
    set(m_end, m_begin, lines);
3✔
101
  }
102
}
597✔
103

104
wex::addressrange::addressrange(ex* ex, int begin_line, int end_line)
28✔
105
  : m_begin(ex, begin_line)
28✔
106
  , m_end(ex, end_line)
28✔
107
  , m_ex(ex)
28✔
108
{
109
}
28✔
110

111
wex::addressrange::addressrange(ex* ex, const std::string& range)
454✔
112
  : m_begin(ex)
454✔
113
  , m_end(ex)
454✔
114
  , m_ex(ex)
454✔
115
  , m_stc(ex->get_stc())
454✔
116
  , m_commands(init_commands())
454✔
117
{
118
  set_range(range);
454✔
119
}
454✔
120

121
const std::string
122
wex::addressrange::build_replacement(const std::string& text) const
27✔
123
{
124
  if (!text.contains("&") && !text.contains("\0"))
27!
125
  {
126
    return text;
×
127
  }
128

129
  std::string target(
130
    m_stc->GetTextRange(m_stc->GetTargetStart(), m_stc->GetTargetEnd())),
27✔
131
    replacement;
27✔
132

133
  bool backslash = false;
27✔
134

135
  for (const auto& c : text)
364✔
136
  {
137
    switch (c)
337✔
138
    {
139
      case '&':
28✔
140
        if (!backslash)
28!
141
        {
142
          replacement += target;
28✔
143
        }
144
        else
145
        {
146
          replacement += c;
×
147
        }
148
        backslash = false;
28✔
149
        break;
28✔
150

151
      case '0':
8✔
152
        if (backslash)
8!
153
        {
154
          replacement += target;
8✔
155
        }
156
        else
157
        {
158
          replacement += c;
×
159
        }
160
        backslash = false;
8✔
161
        break;
8✔
162

163
      case 'L':
34✔
164
      case 'U':
165
        if (backslash)
34✔
166
        {
167
          convert_case(m_stc, target, c);
16✔
168
        }
169
        else
170
        {
171
          replacement += c;
18✔
172
        }
173
        backslash = false;
34✔
174
        break;
34✔
175

176
      case '\\':
28✔
177
        if (backslash)
28✔
178
        {
179
          replacement += c;
2✔
180
        }
181
        backslash = !backslash;
28✔
182
        break;
28✔
183

184
      default:
239✔
185
        replacement += c;
239✔
186
        backslash = false;
239✔
187
    }
188
  }
189

190
  return replacement;
27✔
191
}
27✔
192

193
bool wex::addressrange::change(const std::string& text) const
6✔
194
{
195
  if (!erase())
6!
196
  {
197
    return false;
×
198
  }
199

200
  m_stc->add_text(text + m_stc->eol());
6✔
201

202
  return true;
6✔
203
}
204

205
int wex::addressrange::confirm(
×
206
  const std::string& pattern,
207
  const std::string& replacement) const
208
{
209
  wxMessageDialog msgDialog(
210
    m_stc,
×
211
    _("Replace") + " " + pattern + " " + _("with") + " " + replacement,
×
212
    _("Replace"),
213
    wxCANCEL | wxYES_NO);
×
214

215
  const auto line = m_stc->LineFromPosition(m_stc->GetTargetStart());
×
216

217
  msgDialog.SetExtendedMessage(
×
218
    "Line " + std::to_string(line + 1) + ": " + m_stc->GetLineText(line));
×
219

220
  m_stc->goto_line(line);
×
221
  m_stc->set_indicator(m_find_indicator);
×
222

223
  return msgDialog.ShowModal();
×
224
}
×
225

226
bool wex::addressrange::copy(const command_parser& cp)
16✔
227
{
228
  if (cp.command() != "co" && cp.command() != "copy")
16✔
229
  {
230
    if (cp.text().contains('|'))
8✔
231
    {
232
      return change(find_after(cp.text(), "|"));
12✔
233
    }
234

235
    return m_ex->frame()->show_ex_input(m_ex->get_stc(), cp.command()[0]);
2✔
236
  }
237

238
  return false;
8✔
239
}
240

241
bool wex::addressrange::copy(const address& destination) const
19✔
242
{
243
  return !m_stc->is_visual() ? m_ex->ex_stream()->copy(*this, destination) :
19!
244
                               general(
38!
245
                                 destination,
246
                                 [=, this]()
38✔
247
                                 {
248
                                   return yank();
15✔
249
                                 });
38!
250
}
251

252
bool wex::addressrange::erase() const
94✔
253
{
254
  if (!m_stc->is_visual())
94!
255
  {
256
    return m_ex->ex_stream()->erase(*this);
×
257
  }
258

259
  if (m_stc->GetReadOnly() || m_stc->is_hexmode() || !set_selection())
94!
260
  {
261
    return false;
3✔
262
  }
263

264
  m_ex->cut();
91✔
265

266
  m_begin.marker_delete();
91✔
267
  m_end.marker_delete();
91✔
268

269
  return true;
91✔
270
}
271

272
bool wex::addressrange::escape(const std::string& command)
3✔
273
{
274
  /// Filters range with command.
275
  /// The address range is used as input for the command,
276
  /// and the output of the command replaces the address range.
277
  /// For example: addressrange(96, 99).escape("sort")
278
  /// or (ex command::96,99!sort)
279
  /// will pass lines 96 through 99 through the sort filter and
280
  /// replace those lines with the output of sort.
281
  /// If you did not specify an address range,
282
  /// the command is run as an asynchronous process.
283

284
  if (m_begin.m_address.empty() && m_end.m_address.empty())
3!
285
  {
286
    auto expanded(command);
×
287
    if (
×
288
      !marker_and_register_expansion(m_ex, expanded) ||
×
289
      !shell_expansion(expanded))
×
290
    {
291
      return false;
×
292
    }
293

294
    return m_ex->frame()->process_async_system(
×
295
      process_data(expanded).start_dir(m_stc->path().parent_path()));
×
296
  }
×
297

298
  if (!is_ok())
3!
299
  {
300
    return false;
×
301
  }
302

303
  if (m_stc->GetReadOnly() || m_stc->is_hexmode() || !set_selection())
3!
304
  {
305
    return false;
×
306
  }
307
  if (factory::process process;
3✔
308
      process.system(
6✔
309
        wex::process_data(command).std_in(m_stc->get_selected_text())) == 0)
3!
310
  {
311
    if (const auto& out(process.std_out()); !out.empty())
3!
312
    {
313
      stc_undo undo(m_stc);
3✔
314

315
      if (erase())
3!
316
      {
317
        m_stc->add_text(out);
3✔
318
      }
319

320
      return true;
3✔
321
    }
3✔
322
    if (const auto& err(process.std_err()); !err.empty())
×
323
    {
324
      m_ex->frame()->show_ex_message(err);
×
325
      log("escape") << err;
×
326
    }
327
  }
3!
328

329
  return false;
×
330
}
331

332
bool wex::addressrange::execute(const std::string& reg) const
×
333
{
334
  if (!is_ok() || !ex::get_macros().is_recorded_macro(reg))
×
335
  {
336
    return false;
×
337
  }
338

339
  bool error = false;
×
340

341
  stc_undo undo(m_stc);
×
342

343
  for (auto i = m_begin.get_line() - 1; i < m_end.get_line() && !error; i++)
×
344
  {
345
    if (!m_ex->command("@" + reg))
×
346
    {
347
      error = true;
×
348
    }
349
  }
350

351
  return !error;
×
352
}
×
353

354
bool wex::addressrange::general(
28✔
355
  const address&               destination,
356
  const std::function<bool()>& f) const
357
{
358
  const auto dest_line = destination.get_line();
28✔
359

360
  if (
28✔
361
    m_stc->GetReadOnly() || m_stc->is_hexmode() || !is_ok() || dest_line == 0 ||
53!
362
    (dest_line >= m_begin.get_line() && dest_line <= m_end.get_line()))
25✔
363
  {
364
    return false;
4✔
365
  }
366

367
  stc_undo undo(m_stc);
24✔
368

369
  if (f())
24!
370
  {
371
    m_stc->goto_line(dest_line - 1);
24✔
372
    m_stc->add_text(m_ex->register_text());
24✔
373
  }
374

375
  return true;
24✔
376
}
24✔
377

378
bool wex::addressrange::global(const command_parser& cp) const
51✔
379
{
380
  if (!m_stc->is_visual())
51!
381
  {
382
    m_ex->frame()->show_ex_message("not supported in ex mode");
×
383
    return false;
×
384
  }
385

386
  if (!m_substitute.set_global(cp.command() + cp.text()))
51✔
387
  {
388
    return false;
3✔
389
  }
390

391
  /// Performs the global command (g) on this range.
392
  /// normally performs command on each match, if inverse
393
  /// performs (v) command if line does not match
394

395
  global_env g(*this);
48✔
396

397
  if (!g.global(m_substitute))
48!
398
  {
399
    return false;
×
400
  }
401

402
  if (g.hits() > 0)
48✔
403
  {
404
    if (g.has_commands())
31✔
405
    {
406
      m_ex->frame()->show_ex_message(
48✔
407
        "executed: " + std::to_string(g.hits()) + " commands");
48✔
408
    }
409
    else
410
    {
411
      m_ex->frame()->show_ex_message(
14✔
412
        "found: " + std::to_string(g.hits()) + " matches");
14✔
413
    }
414
  }
415

416
  return true;
48✔
417
}
48✔
418

419
bool wex::addressrange::indent(bool forward) const
12✔
420
{
421
  if (
12✔
422
    m_stc->GetReadOnly() || m_stc->is_hexmode() || !is_ok() || !set_selection())
12!
423
  {
424
    return false;
3✔
425
  }
426

427
  stc_undo undo(m_stc);
9✔
428
  m_stc->SendMsg(forward ? wxSTC_CMD_TAB : wxSTC_CMD_BACKTAB);
9✔
429

430
  return true;
9✔
431
}
9✔
432

433
const wex::addressrange::commands_t wex::addressrange::init_commands()
1,051✔
434
{
435
  return {
436
    {"c",
437
     [&](const command_parser& cp, info_message_t& msg)
×
438
     {
439
       if (copy(cp))
16✔
440
       {
441
         return true;
8✔
442
       }
443
       msg = info_message_t::COPY;
8✔
444
       return copy(address(m_ex, cp.text()));
8✔
445
     }},
446
    {"d",
447
     [&](const command_parser& cp, info_message_t& msg)
×
448
     {
449
       msg = info_message_t::DEL;
43✔
450
       return erase();
43✔
451
     }},
452
    {"gv",
453
     [&](const command_parser& cp, info_message_t& msg)
×
454
     {
455
       return global(cp);
51✔
456
     }},
457
    {"j",
458
     [&](const command_parser& cp, info_message_t& msg)
×
459
     {
460
       return join();
4✔
461
     }},
462
    {"lp#n",
463
     [&](const command_parser& cp, info_message_t& msg)
×
464
     {
465
       return print(cp);
113✔
466
     }},
467
    {"m",
468
     [&](const command_parser& cp, info_message_t& msg)
×
469
     {
470
       msg = info_message_t::MOVE;
9✔
471
       return move(address(m_ex, cp.text()));
9✔
472
     }},
473
    {"s&~",
474
     [&](const command_parser& cp, info_message_t& msg)
×
475
     {
476
       return substitute(cp);
74✔
477
     }},
478
    {"S",
479
     [&](const command_parser& cp, info_message_t& msg)
×
480
     {
481
       return sort(cp.text());
26✔
482
     }},
483
    {"t",
484
     [&](const command_parser& cp, info_message_t& msg)
×
485
     {
486
       msg = info_message_t::COPY;
6✔
487
       return copy(address(m_ex, cp.text()));
6✔
488
     }},
489
    {"w",
490
     [&](const command_parser& cp, info_message_t& msg)
×
491
     {
492
       const bool result = write(cp);
11✔
493
       if (cp.command() == "wq")
11!
494
       {
495
         POST_CLOSE(wxEVT_CLOSE_WINDOW, !cp.text().contains("!"))
×
496
       }
497
       return result;
11✔
498
     }},
499
    {"x",
500
     [&](const command_parser& cp, info_message_t& msg)
×
501
     {
502
       write(cp);
×
503
       POST_CLOSE(wxEVT_CLOSE_WINDOW, !cp.text().contains("!"))
×
504
       return true;
×
505
     }},
506
    {"y",
507
     [&](const command_parser& cp, info_message_t& msg)
×
508
     {
509
       msg = info_message_t::YANK;
22✔
510
       return yank(cp);
22✔
511
     }},
512
    {">",
513
     [&](const command_parser& cp, info_message_t& msg)
×
514
     {
515
       return cp.text().empty() && shift_right();
4!
516
     }},
517
    {"<",
518
     [&](const command_parser& cp, info_message_t& msg)
×
519
     {
520
       return cp.text().empty() && shift_left();
5!
521
     }},
522
    {"!",
523
     [&](const command_parser& cp, info_message_t& msg)
×
524
     {
525
       return escape(cp.text());
3✔
526
     }},
527
    {"@",
528
     [&](const command_parser& cp, info_message_t& msg)
×
529
     {
530
       return execute(cp.text());
×
531
     }}};
18,918!
532
}
1,051!
533

534
bool wex::addressrange::is_ok() const
436✔
535
{
536
  return m_begin.get_line() > 0 && m_end.get_line() > 0 &&
851✔
537
         m_begin.get_line() <= m_end.get_line();
851✔
538
}
539

540
bool wex::addressrange::is_selection() const
135✔
541
{
542
  return (m_begin.m_address + "," + m_end.m_address) ==
270✔
543
         ex_command::selection_range();
405✔
544
}
545

546
bool wex::addressrange::join() const
7✔
547
{
548
  if (!m_stc->is_visual())
7!
549
  {
550
    return m_ex->ex_stream()->join(*this);
×
551
  }
552

553
  if (m_stc->GetReadOnly() || m_stc->is_hexmode() || !is_ok())
7!
554
  {
555
    return false;
×
556
  }
557

558
  stc_undo undo(m_stc);
7✔
559

560
  m_stc->SetTargetRange(
7✔
561
    m_stc->PositionFromLine(m_begin.get_line() - 1),
7✔
562
    m_stc->PositionFromLine(m_end.get_line() - 1));
7✔
563
  m_stc->LinesJoin();
7✔
564

565
  return true;
7✔
566
}
7✔
567

568
bool wex::addressrange::move(const address& destination) const
9✔
569
{
570
  return !m_stc->is_visual() ? m_ex->ex_stream()->move(*this, destination) :
9!
571
                               general(
18!
572
                                 destination,
573
                                 [=, this]()
18✔
574
                                 {
575
                                   return erase();
9✔
576
                                 });
18!
577
}
578

579
bool wex::addressrange::parse(const command_parser& cp, info_message_t& im)
388✔
580
{
581
  if (!cp.range().empty() && !set_range(cp.range()))
388!
582
  {
583
    return false;
×
584
  }
585

586
  if (const auto& it =
388✔
587
        find_from<addressrange::commands_t>(m_commands, cp.command());
388✔
588
      it != m_commands.end())
388✔
589
  {
590
    im = info_message_t::NONE;
387✔
591
    return it->second(cp, im);
387✔
592
  }
593

594
  log::status("Unknown range command") << cp.command();
1✔
595
  return false;
1✔
596
}
597

598
bool wex::addressrange::print(const command_parser& cp)
113✔
599
{
600
  std::string arg;
113✔
601

602
  if (cp.command()[0] == '#' || cp.command()[0] == 'n')
113✔
603
  {
604
    arg = "#";
5✔
605
  }
606
  else if (cp.command()[0] == 'l')
108✔
607
  {
608
    arg = "l";
5✔
609
  }
610

611
  return (
612
    m_stc->GetName() != "Print" ? m_ex->print(*this, arg + cp.text()) : false);
226!
613
}
113✔
614

615
const std::string wex::addressrange::regex_commands() const
535✔
616
{
617
  // 2addr commands
618
  return "(change\\b|c\\b|"
535✔
619
         "copy|co|t|"
620
         "delete\\b|d\\b|"
621
         "global\\b|g\\b|"
622
         "join\\b|j\\b|"
623
         "list\\b|l\\b|"
624
         "move|m|"
625
         "number\\b|nu\\b|"
626
         "print\\b|p\\b|"
627
         "substitute\\b|s\\b|"
628
         "write\\b|wq|w\\b|"
629
         "xit\\b|x\\b|"
630
         "yank\\b|ya\\b|"
631
         "[Sv<>\\!&~@#])([\\s\\S]*)";
1,070✔
632
}
633

634
void wex::addressrange::set(int begin, int end)
1✔
635
{
636
  m_begin.m_type = address::address_t::IS_BEGIN;
1✔
637
  m_end.m_type   = address::address_t::IS_END;
1✔
638

639
  m_begin.set_line(begin);
1✔
640
  m_end.set_line(end);
1✔
641
}
1✔
642

643
bool wex::addressrange::set(const std::string& begin, const std::string& end)
840✔
644
{
645
  m_begin.m_type = address::address_t::IS_BEGIN;
840✔
646
  m_end.m_type   = address::address_t::IS_END;
840✔
647

648
  if (!set_single(begin, m_begin))
840✔
649
  {
650
    return false;
15✔
651
  }
652

653
  if (begin == end)
825✔
654
  {
655
    m_end = m_begin;
362✔
656
    return true;
362✔
657
  }
658

659
  return set_single(end, m_end);
463✔
660
}
661

662
void wex::addressrange::set(address& begin, address& end, int lines) const
594✔
663
{
664
  begin.m_type = address::address_t::IS_BEGIN;
594✔
665
  end.m_type   = address::address_t::IS_END;
594✔
666

667
  begin.set_line(m_stc->LineFromPosition(m_stc->GetCurrentPos()) + 1);
594✔
668
  end.set_line(begin.get_line() + lines - 1);
594✔
669
}
594✔
670

671
bool wex::addressrange::set_range(const std::string& range)
841✔
672
{
673
  if (range == "%")
841✔
674
  {
675
    return set("1", "$");
652✔
676
  }
677

678
  if (range == "*")
678✔
679
  {
680
    set(
2✔
681
      m_stc->GetFirstVisibleLine() + 1,
1✔
682
      m_stc->GetFirstVisibleLine() + m_stc->LinesOnScreen() + 1);
1✔
683
    return true;
1✔
684
  }
685

686
  if (const auto comma(range.find(',')); comma != std::string::npos)
677✔
687
  {
688
    return set(range.substr(0, comma), range.substr(comma + 1));
312✔
689
  }
690

691
  return set(range, range);
365✔
692
}
693

694
bool wex::addressrange::set_selection() const
199✔
695
{
696
  if (!m_stc->GetSelectedText().empty())
199✔
697
  {
698
    return true;
81✔
699
  }
700
  if (!is_ok())
118✔
701
  {
702
    return false;
3✔
703
  }
704

705
  m_stc->SetSelection(
345✔
706
    m_stc->PositionFromLine(m_begin.get_line() - 1),
115✔
707
    m_stc->PositionFromLine(m_end.get_line()));
115✔
708
  return true;
115✔
709
}
710

711
bool wex::addressrange::set_single(const std::string& line, address& addr)
1,303✔
712
{
713
  addr.m_address = line;
1,303✔
714

715
  if (const auto line_no = addr.get_line(
2,606✔
716
        addr.type() == address::address_t::IS_BEGIN ? m_stc->GetCurrentPos() :
2,143✔
717
                                                      m_stc->GetTargetEnd());
463✔
718
      line_no > 0)
719
  {
720
    addr.set_line(line_no);
1,284✔
721
    return true;
1,284✔
722
  }
723

724
  log::debug("addressrange line") << line;
19✔
725
  return false;
19✔
726
}
727

728
bool wex::addressrange::sort(const std::string& parameters) const
26✔
729
{
730
  if (m_stc->GetReadOnly() || m_stc->is_hexmode() || !set_selection())
26!
731
  {
732
    return false;
×
733
  }
734

735
  /// Sorts range, with optional parameters:
736
  /// -u to sort unique lines
737
  /// -r to sort reversed (descending)
738
  /// -x,y sorts rectangle within range: x start col, y end col (exclusive).
739

740
  factory::sort::sort_t sort_t = 0;
26✔
741

742
  size_t pos = 0, len = std::string::npos;
26✔
743

744
  if (m_stc->SelectionIsRectangle())
26!
745
  {
746
    pos = m_stc->GetColumn(m_stc->GetSelectionStart());
×
747
    len = m_stc->GetColumn(m_stc->GetSelectionEnd() - pos);
×
748
  }
749

750
  if (!parameters.empty())
26✔
751
  {
752
    /// -u to sort unique lines
753
    /// -r to sort reversed (descending)
754
    if (
23✔
755
      (parameters[0] == '0') ||
45✔
756
      (!parameters.starts_with("u") && !parameters.starts_with("r") &&
22✔
757
       !isdigit(parameters[0])))
12✔
758
    {
759
      return false;
4✔
760
    }
761

762
    if (parameters.contains("r"))
20✔
763
    {
764
      sort_t.set(factory::sort::SORT_DESCENDING);
9✔
765
    }
766
    if (parameters.contains("u"))
20✔
767
    {
768
      sort_t.set(factory::sort::SORT_UNIQUE);
10✔
769
    }
770

771
    std::string filter; // filter r, u
20✔
772
    std::copy_if(
20✔
773
      parameters.begin(),
774
      parameters.end(),
775
      std::back_inserter(filter),
776
      [](char c)
40✔
777
      {
778
        return c != 'r' && c != 'u';
40✔
779
      });
780

781
    /// -x,y sorts rectangle within range: x start col, y end col (exclusive).
782
    if (
20✔
783
      !filter.empty() &&
31✔
784
      std::from_chars(filter.data(), filter.data() + filter.size(), pos).ec ==
11!
785
        std::errc())
786
    {
787
      pos--;
11✔
788

789
      if (const auto co = filter.find(','); co != std::string::npos)
11✔
790
      {
791
        size_t end;
792
        if (
5✔
793
          std::from_chars(
5✔
794
            filter.data() + co + 1,
5✔
795
            filter.data() + filter.size(),
5✔
796
            end)
797
              .ec != std::errc() ||
10!
798
          end <= pos)
5✔
799
        {
800
          return false;
1✔
801
        }
802

803
        len = end - pos;
4✔
804
      }
805
    }
806
  }
20✔
807

808
  return factory::sort(sort_t, pos, len).selection(m_stc);
22✔
809
}
810

811
bool wex::addressrange::substitute(const command_parser& cp)
74✔
812
{
813
  data::substitute data(m_substitute);
74✔
814
  auto             searchFlags = m_ex->search_flags();
74✔
815

816
  /// Substitutes range.
817
  /// text format: /pattern/replacement/options
818
  /// Pattern might contain:
819
  /// - $ to match a line end
820
  /// Replacement might contain:
821
  /// - & or \\0 to represent the target in the replacement
822
  /// - \\U to convert target to uppercase
823
  /// - \\L to convert target to lowercase
824
  /// Options can be:
825
  /// - c : Ask for confirm
826
  /// - i : Case insensitive
827
  /// - g : Do global on line, without this flag replace first match only
828
  /// e.g. /$/EOL appends the string EOL at the end of each line.
829
  /// Merging is not yet possible using a \n target,
830
  /// you can create a macro for that.
831
  /// cmd is one of s, & or ~
832
  /// - s : default, normal substitute
833
  /// - & : repeat last substitute (text contains options)
834
  /// - ~ : repeat last substitute with pattern from find replace data
835
  ///      (text contains options)
836

837
  if (!is_ok() || !prep(data, searchFlags, cp))
74!
838
  {
839
    return false;
2✔
840
  }
841

842
  if (!m_stc->is_visual())
72!
843
  {
844
    return m_ex->ex_stream()->substitute(*this, data);
×
845
  }
846
  if (m_stc->GetReadOnly())
72!
847
  {
848
    return false;
×
849
  }
850

851
  if (data.is_ignore_case())
72✔
852
  {
853
    searchFlags &= ~wxSTC_FIND_MATCHCASE;
3✔
854
  }
855

856
  addressrange_mark am(*this, data);
72✔
857

858
  if (!am.set())
72!
859
  {
860
    log::debug("substitute could not set marker");
×
861
    return false;
×
862
  }
863

864
  m_substitute = data;
72✔
865
  m_stc->set_search_flags(searchFlags);
72✔
866

867
  int        nr_replacements = 0;
72✔
868
  int        result          = wxID_YES;
72✔
869
  const bool do_build =
870
    (data.replacement().find_first_of("&0LU\\") != std::string::npos);
72✔
871
  auto replacement(data.replacement());
72✔
872

873
  while (am.search() && result != wxID_CANCEL)
292!
874
  {
875
    if (do_build)
230✔
876
    {
877
      replacement = build_replacement(data.replacement());
27✔
878
    }
879

880
    if (data.is_confirmed())
230!
881
    {
882
      result = confirm(data.pattern(), replacement);
×
883
    }
884

885
    if (result == wxID_YES)
230!
886
    {
887
      if (m_stc->is_hexmode())
230!
888
      {
889
        m_stc->get_hexmode_replace_target(replacement, false);
×
890
      }
891
      else
892
      {
893
        if (data.pattern() == "$")
230✔
894
        {
895
          m_stc->InsertText(m_stc->GetTargetStart(), replacement);
43✔
896
        }
897
        else
898
        {
899
          (searchFlags & wxSTC_FIND_REGEXP) ?
187!
900
            m_stc->ReplaceTargetRE(replacement) :
374!
901
            m_stc->ReplaceTarget(replacement);
187!
902
        }
903
      }
904

905
      nr_replacements++;
230✔
906
    }
907

908
    if (!am.update())
230✔
909
    {
910
      break;
10✔
911
    }
912
  }
913

914
  am.end();
72✔
915

916
  m_ex->frame()->show_ex_message(
144✔
917
    "Replaced: " + std::to_string(nr_replacements) +
144✔
918
    " occurrences of: " + data.pattern());
216✔
919

920
  return true;
72✔
921
}
74✔
922

923
bool wex::addressrange::write(const command_parser& cp)
11✔
924
{
925
  if (!cp.text().empty() && !cmdline::use_events())
11!
926
  {
927
    stc_undo undo(
928
      m_stc,
11✔
929
      stc_undo::undo_t().set(stc_undo::UNDO_POS).set(stc_undo::UNDO_SEL_NONE));
11✔
930
    return write(cp.text());
11✔
931
  }
11✔
932

933
  if (!m_stc->is_visual())
×
934
  {
935
    return m_ex->ex_stream()->write();
×
936
  }
937

938
  wxCommandEvent event(
939
    wxEVT_COMMAND_MENU_SELECTED,
940
    cp.text().empty() ? wxID_SAVE : wxID_SAVEAS);
×
941
  event.SetString(boost::algorithm::trim_left_copy(cp.text()));
×
942
  wxPostEvent(wxTheApp->GetTopWindow(), event);
×
943
  return true;
×
944
}
×
945

946
bool wex::addressrange::write(const std::string& text) const
11✔
947
{
948
  if (!set_selection())
11!
949
  {
950
    return false;
×
951
  }
952

953
  auto filename(boost::algorithm::trim_left_copy(
11✔
954
    text.contains(">>") ? rfind_after(text, ">") : text));
19!
955

956
#ifdef __UNIX__
957
  if (filename.contains("~"))
11!
958
  {
959
    filename.replace(filename.find("~"), 1, wxGetHomeDir());
×
960
  }
961
#endif
962

963
  if (!m_stc->is_visual())
11!
964
  {
965
    return m_ex->ex_stream()->write(*this, filename, text.contains(">>"));
×
966
  }
967

968
  return file(
×
969
           path(filename),
22✔
970
           text.contains(">>") ? std::ios::out | std::ios_base::app :
15✔
971
                                 std::ios::out)
22✔
972
    .write(m_stc->get_selected_text());
11✔
973
}
11✔
974

975
bool wex::addressrange::yank(const command_parser& cp)
22✔
976
{
977
  return yank(cp.text().empty() ? '0' : static_cast<char>(cp.text().back()));
22✔
978
}
979

980
bool wex::addressrange::yank(char name) const
57✔
981
{
982
  if (!m_stc->is_visual())
57!
983
  {
984
    return m_ex->ex_stream()->yank(*this, name);
×
985
  }
986

987
  return set_selection() && m_ex->yank(name);
57!
988
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc