full_line如何在SublimeText中工作?

时间:2019-05-07 14:45:00

标签: python sublimetext3

我正在尝试使用python模仿view.full_line SublimeText函数,如果我们阅读docs,我们将会看到:

  

line(point):返回包含该点的线。

     

line(region):返回区域的修改后的副本,以使其开始于行的开头,并结束于行的结尾。请注意,它可能跨越几行。

     

full_line(point):与line()相同,但该区域包括结尾的换行符(如果有)。

     

full_line(region):与line()相同,但是该区域包括结尾的换行符(如果有)。

我尝试遵循该文档中的说明,这就是我得到的:

class Region(object):
    __slots__ = ['a', 'b', 'xpos']

    def __init__(self, a, b=None, xpos=-1):
        if b is None:
            b = a
        self.a = a
        self.b = b
        self.xpos = xpos

    def __str__(self):
        return "(" + str(self.a) + ", " + str(self.b) + ")"

    def __repr__(self):
        return "(" + str(self.a) + ", " + str(self.b) + ")"

    def begin(self):
        if self.a < self.b:
            return self.a
        else:
            return self.b

    def end(self):
        if self.a < self.b:
            return self.b
        else:
            return self.a


def lskip_nonewlines(text, pt):
    len_text = len(text)

    while True:
        if pt <= 0 or pt >= len_text:
            break
        if text[pt - 1] == "\n" or text[pt] == "\n":
            break
        pt -= 1

    return pt


def rskip_nonewlines(text, pt):
    len_text = len(text)

    while True:
        if pt <= 0 or pt >= len_text:
            break
        if text[pt] == "\n":
            break
        pt += 1

    return pt


def full_line(text, x):
    region = Region(x)

    if region.a <= region.b:
        # try:
        #     if text[region.a]=="\n":
        #         region.a-=1
        # except Exception as e:
        #     pass

        region.a = lskip_nonewlines(text, region.a)
        region.b = rskip_nonewlines(text, region.b)
        region.b = region.b + 1 if region.b < len(text) else region.b
    else:
        region.a = rskip_nonewlines(text, region.a)
        region.b = lskip_nonewlines(text, region.b)
        region.a = region.a + 1 if region.a < len(text) else region.a

    return (region.begin(), region.end())


if __name__ == '__main__':
    text = "# I'm a comment\n\n\ndef foo():\n    print('# No comment')\n"
    sublime_output = [
        [0, (0, 16)],
        [1, (0, 16)],
        [2, (0, 16)],
        [3, (0, 16)],
        [4, (0, 16)],
        [5, (0, 16)],
        [6, (0, 16)],
        [7, (0, 16)],
        [8, (0, 16)],
        [9, (0, 16)],
        [10, (0, 16)],
        [11, (0, 16)],
        [12, (0, 16)],
        [13, (0, 16)],
        [14, (0, 16)],
        [15, (0, 16)],
        [16, (16, 17)],
        [17, (17, 18)],
        [18, (18, 29)],
        [19, (18, 29)],
        [20, (18, 29)],
        [21, (18, 29)],
        [22, (18, 29)],
        [23, (18, 29)],
        [24, (18, 29)],
        [25, (18, 29)],
        [26, (18, 29)],
        [27, (18, 29)],
        [28, (18, 29)],
        [29, (29, 55)],
        [30, (29, 55)],
        [31, (29, 55)],
        [32, (29, 55)],
        [33, (29, 55)],
        [34, (29, 55)],
        [35, (29, 55)],
        [36, (29, 55)],
        [37, (29, 55)],
        [38, (29, 55)],
        [39, (29, 55)],
        [40, (29, 55)],
        [41, (29, 55)],
        [42, (29, 55)],
        [43, (29, 55)],
        [44, (29, 55)],
        [45, (29, 55)],
        [46, (29, 55)],
        [47, (29, 55)],
        [48, (29, 55)],
        [49, (29, 55)],
        [50, (29, 55)],
        [51, (29, 55)],
        [52, (29, 55)],
        [53, (29, 55)],
        [54, (29, 55)],
    ]

    for test in sublime_output:
        pos, expected_output = test
        output = full_line(text, pos)

        try:
            assert output == expected_output
        except Exception as e:
            print(f"Error at pos: {pos}, output {output}, expected output {expected_output}")

上面的mcve将输出与我从SublimeText本身得到的结果进行比较。您可以看到该函数的性能很好,但在某些极端情况下仍会失败:

Error at pos: 0, output (0, 1), expected output (0, 16)
Error at pos: 15, output (15, 16), expected output (0, 16)
Error at pos: 28, output (28, 29), expected output (18, 29)
Error at pos: 54, output (54, 55), expected output (29, 55)

那么,我该如何修复该例程,使其像SublimeText一样表现为1:1?

1 个答案:

答案 0 :(得分:1)

您的代码中有几个相当明显的错误,导致其无法按预期进行。

Error at pos: 0, output (0, 1), expected output (0, 16)

这似乎表明,当代码从位置0向前扫描以确定行的结尾时,它将停止在位置1而不是位置16。

因此,查看您的代码,您在这里有这句话;它是if的上部,因为您的测试区域总是以a == b的方式制作:

        region.a = lskip_nonewlines(text, region.a)
        region.b = rskip_nonewlines(text, region.b)
        region.b = region.b + 1 if region.b < len(text) else region.b

因此,查看此内容,找到结束位置是rskip_nonewlines()的工作,然后,只要返回的区域小于文本,我们就将其向上推一位。因此,可以推断出此方法旨在返回找到的newline字符的位置:

def rskip_nonewlines(text, pt):
    len_text = len(text)

    while True:
        if pt <= 0 or pt >= len_text:
            break
        if text[pt] == "\n":
            break
        pt += 1

    return pt

当您以pt为0调用此函数时,首先要做的是确定0 <= 0的值为True,从而使它脱离while循环,使它立即返回0。然后,由于0小于文本,因此它添加了1,最终结果为(0, 1)

如果您删除条件语句的pt <= 0 or部分,它将正确地将换行符定位在位置15处,向其添加1,并在(0, 16)结尾正如它应该。看来这可能是lskip_nonewlines()的复制/粘贴错误。

Error at pos: 15, output (15, 16), expected output (0, 16)

这似乎表明,当代码从位置15向后扫描以确定该行的开始位置时,它将停止在位置15而不是位置0。

再次根据上述代码,lskip_nonewlines()的工作是找到行的开始:

def lskip_nonewlines(text, pt):
    len_text = len(text)

    while True:
        if pt <= 0 or pt >= len_text:
            break
        if text[pt - 1] == "\n" or text[pt] == "\n":
            break
        pt -= 1

    return pt

在此循环中,我们首先要确保不会超出字符串的结尾,然后检查pt之前的字符还是pt处的字符换行符。

从前面的示例中我们已经知道位置15是一个newline字符,因此在循环的第一次迭代中,我们立即发现我们在换行符上并返回{{1 }}保持原样。最终结果为pt

在这种情况下,从条件中删除(15, 16)可以阻止它立即确定行在其开始处结束,并允许它在决定停止之前向后扫描到位置0,这为您提供了所需的{{1 }}返回。

进行这两项更改后,您的测试程序不会产生任何错误。