Skip to content
索引

自动重命名 HTML 标签的函数 doAutoRenameTag

它接受输入的文本 text、光标位置 offset、新的标签名 newWord、旧的标签名 oldWord 和语言标识 languageId,并尝试根据输入的信息来自动重命名标签。

ts
export const doAutoRenameTag = (
  text: string,
  offset: number,
  newWord: string,
  oldWord: string,
  languageId: string
) =>
  | {
      startOffset: number;
      endOffset: number;
      tagName: string;
    }
  | undefined = (text, offset, newWord, oldWord, languageId) => {
  // 获取匹配的标签对
  const matchingTagPairs = getMatchingTagPairs(languageId);
  // 判断标签是否是自闭合标签
  const isSelfClosingTag = isSelfClosingTagInLanguage(languageId);
  // 判断是否是 React 语言
  const isReact =
    languageId === 'javascript' ||
    languageId === 'typescript' ||
    languageId === 'javascriptreact' ||
    languageId === 'typescriptreact';
  // 创建一个快速扫描器
  const scanner = createScannerFast({
    input: text,
    initialOffset: 0,
    initialState: ScannerStateFast.WithinContent,
    matchingTagPairs
  });

  // 检查新的标签名是否以 "</" 开头,如果是,执行以下逻辑
  if (newWord.startsWith('</')) {
    // 将扫描器定位到光标位置
    scanner.stream.goTo(offset);
    // 提取新的标签名(去掉 "</")
    const tagName = newWord.slice(2);
    // 提取旧的标签名(去掉 "</")
    const oldTagName = oldWord.slice(2);
    // 如果旧的标签名以 "script" 或 "style" 开头
    if (oldTagName.startsWith('script') || oldTagName.startsWith('style')) {
      // 构造一个匹配的标签,例如 "</oldTagName>"
      const tag = `<${oldTagName}`;
      let i = scanner.stream.position;
      let found = false;
      // 从当前位置开始向前搜索
      while (i--) {
        // 如果找到了与标签匹配的内容
        if (text.slice(i).startsWith(tag)) {
          found = true;
          break;
        }
      }
      // 如果没有找到匹配的内容,返回 undefined
      if (!found) {
        return undefined;
      }
      // 如果找到了匹配的内容,返回对应的标签名和位置
      return {
        startOffset: i + 1,
        endOffset: i + 1 + oldTagName.length,
        tagName
      };
    }

    // 如果不是以 "script" 或 "style" 开头的标签
    // 获取当前标签的父标签信息
    const parent = getPreviousOpeningTagName(
      scanner,
      scanner.stream.position,
      isSelfClosingTag,
      isReact
    );
    // 如果没有找到父标签,返回 undefined
    if (!parent) {
      return undefined;
    }
    // 如果当前标签的父标签名与新标签名相同,返回 undefined
    if (parent.tagName === tagName) {
      return undefined;
    }
    // 如果当前标签的父标签名与旧标签名不同,返回 undefined
    if (parent.tagName !== oldTagName) {
      return undefined;
    }
    // 如果当前标签的父标签没有闭合,返回 undefined
    if (!parent.seenRightAngleBracket) {
      return undefined;
    }

    // 返回对应的标签名和位置
    const startOffset = parent.offset;
    const endOffset = parent.offset + parent.tagName.length;
    return {
      startOffset,
      endOffset,
      tagName
    };
  } else {
    // 如果新的标签名不是以 "</" 开头,执行以下逻辑
    // 将扫描器定位到光标位置后一位
    scanner.stream.goTo(offset + 1);
    // 提取新的标签名(去掉 "<")
    const tagName = newWord.slice(1);
    // 提取旧的标签名(去掉 "<")
    const oldTagName = oldWord.slice(1);
    // 如果旧的标签名以 "script" 或 "style" 开头
    if (oldTagName.startsWith('script') || oldTagName.startsWith('style')) {
      // 继续扫描直到遇到 ">"
      const hasAdvanced = scanner.stream.advanceUntilEitherChar(
        ['>'],
        true,
        isReact
      );
      // 如果没有扫描到 ">", 返回 undefined
      if (!hasAdvanced) {
        return undefined;
      }
      // 在扫描到 ">", 之后的文本中查找 "</oldTagName>"
      const match = text
        .slice(scanner.stream.position)
        .match(new RegExp(`</${oldTagName}`));
      // 如果没有找到 "</oldTagName>",返回 undefined
      if (!match) {
        return undefined;
      }
      // 找到 "</oldTagName>" 的位置
      const index = match.index as number;
      // 返回对应的标签名和位置
      return {
        startOffset: scanner.stream.position + index + 2,
        endOffset: scanner.stream.position + index + 2 + oldTagName.length,
        tagName
      };
    }

    // 如果不是以 "script" 或 "style" 开头的标签
    // 继续扫描直到遇到 "<" 或 ">"
    const hasAdvanced = scanner.stream.advanceUntilEitherChar(
      ['<', '>'],
      true,
      isReact
    );
    // 如果遇到了 "<",返回 undefined
    if (scanner.stream.peekRight(0) === '<') {
      return undefined;
    }
    // 如果没有扫描到 "<" 或 ">", 返回 undefined
    if (!hasAdvanced) {
      return undefined;
    }
    // 如果光标前面的一个字符是 "/",返回 undefined
    if (scanner.stream.peekLeft(1) === '/') {
      return undefined;
    }

    // 获取当前位置的可能是标签的结束位置
    const possibleEndOfStartTag = scanner.stream.position;
    // 检查是否可能是结束标签
    while (scanner.stream.peekLeft(1).match(/[a-zA-Z\-\:]/)) {
      scanner.stream.goBack(1);
      if (scanner.stream.peekLeft(1) === '/') {
        return undefined;
      }
    }
    scanner.stream.goTo(possibleEndOfStartTag);
    scanner.stream.advance(1);
    // 获取下一个闭合标签的信息
    const nextClosingTag = getNextClosingTagName(
      scanner,
      scanner.stream.position,
      isSelfClosingTag,
      isReact
    );
    // 如果没有找到下一个闭合标签,返回 undefined
    if (!nextClosingTag) {
      return undefined;
    }
    // 如果下一个闭合标签名与新标签名相同,返回 undefined
    if (nextClosingTag.tagName === tagName) {
      return undefined;
    }
    // 如果下一个闭合标签名与旧标签名不同,返回 undefined
    if (nextClosingTag.tagName !== oldTagName) {
      return undefined;
    }

    // 获取当前光标位置之前的标签信息
    const previousOpenTag = getPreviousOpeningTagName(
      scanner,
      offset,
      isSelfClosingTag,
      isReact
    );

    // 如果之前有一个与旧标签名相同且缩进相同的开始标签,返回 undefined
    if (
      previousOpenTag &&
      previousOpenTag.tagName === oldTagName &&
      previousOpenTag.indent === nextClosingTag.indent
    ) {
      return undefined;
    }

    // 返回对应的标签名和位置
    const startOffset = nextClosingTag.offset;
    const endOffset = nextClosingTag.offset + nextClosingTag.tagName.length;

    return {
      startOffset,
      endOffset,
      tagName
    };
  }
};
export const doAutoRenameTag = (
  text: string,
  offset: number,
  newWord: string,
  oldWord: string,
  languageId: string
) =>
  | {
      startOffset: number;
      endOffset: number;
      tagName: string;
    }
  | undefined = (text, offset, newWord, oldWord, languageId) => {
  // 获取匹配的标签对
  const matchingTagPairs = getMatchingTagPairs(languageId);
  // 判断标签是否是自闭合标签
  const isSelfClosingTag = isSelfClosingTagInLanguage(languageId);
  // 判断是否是 React 语言
  const isReact =
    languageId === 'javascript' ||
    languageId === 'typescript' ||
    languageId === 'javascriptreact' ||
    languageId === 'typescriptreact';
  // 创建一个快速扫描器
  const scanner = createScannerFast({
    input: text,
    initialOffset: 0,
    initialState: ScannerStateFast.WithinContent,
    matchingTagPairs
  });

  // 检查新的标签名是否以 "</" 开头,如果是,执行以下逻辑
  if (newWord.startsWith('</')) {
    // 将扫描器定位到光标位置
    scanner.stream.goTo(offset);
    // 提取新的标签名(去掉 "</")
    const tagName = newWord.slice(2);
    // 提取旧的标签名(去掉 "</")
    const oldTagName = oldWord.slice(2);
    // 如果旧的标签名以 "script" 或 "style" 开头
    if (oldTagName.startsWith('script') || oldTagName.startsWith('style')) {
      // 构造一个匹配的标签,例如 "</oldTagName>"
      const tag = `<${oldTagName}`;
      let i = scanner.stream.position;
      let found = false;
      // 从当前位置开始向前搜索
      while (i--) {
        // 如果找到了与标签匹配的内容
        if (text.slice(i).startsWith(tag)) {
          found = true;
          break;
        }
      }
      // 如果没有找到匹配的内容,返回 undefined
      if (!found) {
        return undefined;
      }
      // 如果找到了匹配的内容,返回对应的标签名和位置
      return {
        startOffset: i + 1,
        endOffset: i + 1 + oldTagName.length,
        tagName
      };
    }

    // 如果不是以 "script" 或 "style" 开头的标签
    // 获取当前标签的父标签信息
    const parent = getPreviousOpeningTagName(
      scanner,
      scanner.stream.position,
      isSelfClosingTag,
      isReact
    );
    // 如果没有找到父标签,返回 undefined
    if (!parent) {
      return undefined;
    }
    // 如果当前标签的父标签名与新标签名相同,返回 undefined
    if (parent.tagName === tagName) {
      return undefined;
    }
    // 如果当前标签的父标签名与旧标签名不同,返回 undefined
    if (parent.tagName !== oldTagName) {
      return undefined;
    }
    // 如果当前标签的父标签没有闭合,返回 undefined
    if (!parent.seenRightAngleBracket) {
      return undefined;
    }

    // 返回对应的标签名和位置
    const startOffset = parent.offset;
    const endOffset = parent.offset + parent.tagName.length;
    return {
      startOffset,
      endOffset,
      tagName
    };
  } else {
    // 如果新的标签名不是以 "</" 开头,执行以下逻辑
    // 将扫描器定位到光标位置后一位
    scanner.stream.goTo(offset + 1);
    // 提取新的标签名(去掉 "<")
    const tagName = newWord.slice(1);
    // 提取旧的标签名(去掉 "<")
    const oldTagName = oldWord.slice(1);
    // 如果旧的标签名以 "script" 或 "style" 开头
    if (oldTagName.startsWith('script') || oldTagName.startsWith('style')) {
      // 继续扫描直到遇到 ">"
      const hasAdvanced = scanner.stream.advanceUntilEitherChar(
        ['>'],
        true,
        isReact
      );
      // 如果没有扫描到 ">", 返回 undefined
      if (!hasAdvanced) {
        return undefined;
      }
      // 在扫描到 ">", 之后的文本中查找 "</oldTagName>"
      const match = text
        .slice(scanner.stream.position)
        .match(new RegExp(`</${oldTagName}`));
      // 如果没有找到 "</oldTagName>",返回 undefined
      if (!match) {
        return undefined;
      }
      // 找到 "</oldTagName>" 的位置
      const index = match.index as number;
      // 返回对应的标签名和位置
      return {
        startOffset: scanner.stream.position + index + 2,
        endOffset: scanner.stream.position + index + 2 + oldTagName.length,
        tagName
      };
    }

    // 如果不是以 "script" 或 "style" 开头的标签
    // 继续扫描直到遇到 "<" 或 ">"
    const hasAdvanced = scanner.stream.advanceUntilEitherChar(
      ['<', '>'],
      true,
      isReact
    );
    // 如果遇到了 "<",返回 undefined
    if (scanner.stream.peekRight(0) === '<') {
      return undefined;
    }
    // 如果没有扫描到 "<" 或 ">", 返回 undefined
    if (!hasAdvanced) {
      return undefined;
    }
    // 如果光标前面的一个字符是 "/",返回 undefined
    if (scanner.stream.peekLeft(1) === '/') {
      return undefined;
    }

    // 获取当前位置的可能是标签的结束位置
    const possibleEndOfStartTag = scanner.stream.position;
    // 检查是否可能是结束标签
    while (scanner.stream.peekLeft(1).match(/[a-zA-Z\-\:]/)) {
      scanner.stream.goBack(1);
      if (scanner.stream.peekLeft(1) === '/') {
        return undefined;
      }
    }
    scanner.stream.goTo(possibleEndOfStartTag);
    scanner.stream.advance(1);
    // 获取下一个闭合标签的信息
    const nextClosingTag = getNextClosingTagName(
      scanner,
      scanner.stream.position,
      isSelfClosingTag,
      isReact
    );
    // 如果没有找到下一个闭合标签,返回 undefined
    if (!nextClosingTag) {
      return undefined;
    }
    // 如果下一个闭合标签名与新标签名相同,返回 undefined
    if (nextClosingTag.tagName === tagName) {
      return undefined;
    }
    // 如果下一个闭合标签名与旧标签名不同,返回 undefined
    if (nextClosingTag.tagName !== oldTagName) {
      return undefined;
    }

    // 获取当前光标位置之前的标签信息
    const previousOpenTag = getPreviousOpeningTagName(
      scanner,
      offset,
      isSelfClosingTag,
      isReact
    );

    // 如果之前有一个与旧标签名相同且缩进相同的开始标签,返回 undefined
    if (
      previousOpenTag &&
      previousOpenTag.tagName === oldTagName &&
      previousOpenTag.indent === nextClosingTag.indent
    ) {
      return undefined;
    }

    // 返回对应的标签名和位置
    const startOffset = nextClosingTag.offset;
    const endOffset = nextClosingTag.offset + nextClosingTag.tagName.length;

    return {
      startOffset,
      endOffset,
      tagName
    };
  }
};

Released under the MIT License.